Start filling out set operations
This commit is contained in:
parent
adeccec4e5
commit
62c7a49c59
@ -9,7 +9,7 @@ jupytext:
|
|||||||
# Quickstart
|
# Quickstart
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
```
|
```bash
|
||||||
pip install qubed
|
pip install qubed
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -29,17 +29,49 @@ q = Qube.from_dict({
|
|||||||
"expver=0002": {"param=1":{}, "param=2":{}},
|
"expver=0002": {"param=1":{}, "param=2":{}},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
print(f"{q.n_leaves = }, {q.n_nodes = }")
|
||||||
q
|
q
|
||||||
```
|
```
|
||||||
|
|
||||||
Compress the qube:
|
Compress it:
|
||||||
|
|
||||||
```{code-cell} python3
|
```{code-cell} python3
|
||||||
q.compress()
|
cq = q.compress()
|
||||||
|
assert cq.n_leaves == q.n_leaves
|
||||||
|
print(f"{cq.n_leaves = }, {cq.n_nodes = }")
|
||||||
|
cq
|
||||||
```
|
```
|
||||||
|
|
||||||
Load some example qubes:
|
Load a larger example qube (requires source checkout):
|
||||||
|
|
||||||
```{code-cell} python3
|
```{code-cell} python3
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
data_path = Path("../tests/example_qubes/climate_dt.json")
|
||||||
|
with data_path.open("r") as f:
|
||||||
|
climate_dt = Qube.from_json(json.loads(f.read()))
|
||||||
|
|
||||||
|
# Using the html or print methods is optional but lets you specify things like the depth of the tree to display.
|
||||||
|
print(f"{climate_dt.n_leaves = }, {climate_dt.n_nodes = }")
|
||||||
|
climate_dt.html(depth=1) # Limit how much is open initially, click leave to see more.
|
||||||
|
```
|
||||||
|
|
||||||
### Set Operations
|
### Set Operations
|
||||||
|
|
||||||
|
```{code-cell} python3
|
||||||
|
A = Qube.from_dict({
|
||||||
|
"a=1/2/3" : {"b=1/2/3" : {"c=1/2/3" : {}}},
|
||||||
|
"a=5" : { "b=4" : { "c=4" : {}}}
|
||||||
|
})
|
||||||
|
|
||||||
|
B = Qube.from_dict({
|
||||||
|
"a=1/2/3" : {"b=1/2/3" : {"c=1/2/3" : {}}},
|
||||||
|
"a=5" : { "b=4" : { "c=4" : {}}}
|
||||||
|
})
|
||||||
|
|
||||||
|
A.print(name="A"), B.print(name="B");
|
||||||
|
|
||||||
|
A | B
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ from typing import Any, Callable, Hashable, Literal, Mapping
|
|||||||
|
|
||||||
from frozendict import frozendict
|
from frozendict import frozendict
|
||||||
|
|
||||||
|
from . import set_operations
|
||||||
from .tree_formatters import HTML, node_tree_to_html, node_tree_to_string
|
from .tree_formatters import HTML, node_tree_to_html, node_tree_to_string
|
||||||
from .value_types import DateRange, Enum, IntRange, TimeRange, Values
|
from .value_types import DateRange, Enum, IntRange, TimeRange, Values
|
||||||
|
|
||||||
@ -32,6 +33,12 @@ class NodeData:
|
|||||||
def summary(self) -> str:
|
def summary(self) -> str:
|
||||||
return f"{self.key}={self.values.summary()}" if self.key != "root" else "root"
|
return f"{self.key}={self.values.summary()}" if self.key != "root" else "root"
|
||||||
|
|
||||||
|
@dataclass(frozen=True, eq=True, order=True)
|
||||||
|
class RootNodeData(NodeData):
|
||||||
|
"Helper class to print a custom root name"
|
||||||
|
def summary(self) -> str:
|
||||||
|
return self.key
|
||||||
|
|
||||||
@dataclass(frozen=True, eq=True, order=True)
|
@dataclass(frozen=True, eq=True, order=True)
|
||||||
class Qube:
|
class Qube:
|
||||||
data: NodeData
|
data: NodeData
|
||||||
@ -91,10 +98,12 @@ class Qube:
|
|||||||
return cls.make("root", Enum(("root",)), [])
|
return cls.make("root", Enum(("root",)), [])
|
||||||
|
|
||||||
|
|
||||||
def __str__(self, depth = None) -> str:
|
def __str__(self, depth = None, name = None) -> str:
|
||||||
return "".join(node_tree_to_string(node=self, depth = depth))
|
node = dataclasses.replace(self, data = RootNodeData(key = name, values=self.values, metadata=self.metadata)) if name is not None else self
|
||||||
|
return "".join(node_tree_to_string(node=node, depth = depth))
|
||||||
|
|
||||||
def print(self, depth = None): print(self.__str__(depth = depth))
|
def print(self, depth = None, name: str | None = None):
|
||||||
|
print(self.__str__(depth = depth, name = name))
|
||||||
|
|
||||||
def html(self, depth = 2, collapse = True) -> HTML:
|
def html(self, depth = 2, collapse = True) -> HTML:
|
||||||
return HTML(node_tree_to_html(self, depth = depth, collapse = collapse))
|
return HTML(node_tree_to_html(self, depth = depth, collapse = collapse))
|
||||||
@ -102,6 +111,9 @@ class Qube:
|
|||||||
def _repr_html_(self) -> str:
|
def _repr_html_(self) -> str:
|
||||||
return node_tree_to_html(self, depth = 2, collapse = True)
|
return node_tree_to_html(self, depth = 2, collapse = True)
|
||||||
|
|
||||||
|
def __or__(self, other: "Qube") -> "Qube":
|
||||||
|
return set_operations.operation(self, other, set_operations.SetOperation.UNION)
|
||||||
|
|
||||||
|
|
||||||
def __getitem__(self, args) -> 'Qube':
|
def __getitem__(self, args) -> 'Qube':
|
||||||
key, value = args
|
key, value = args
|
||||||
@ -111,7 +123,13 @@ class Qube:
|
|||||||
return dataclasses.replace(c, data = data)
|
return dataclasses.replace(c, data = data)
|
||||||
raise KeyError(f"Key {key} not found in children of {self.key}")
|
raise KeyError(f"Key {key} not found in children of {self.key}")
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def n_leaves(self) -> int:
|
||||||
|
return len(self.values) * (sum(c.n_leaves for c in self.children) if self.children else 1)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def n_nodes(self) -> int:
|
||||||
|
return 1 + sum(c.n_nodes for c in self.children)
|
||||||
|
|
||||||
def transform(self, func: 'Callable[[Qube], Qube | list[Qube]]') -> 'Qube':
|
def transform(self, func: 'Callable[[Qube], Qube | list[Qube]]') -> 'Qube':
|
||||||
"""
|
"""
|
||||||
|
21
src/python/qubed/set_operations.py
Normal file
21
src/python/qubed/set_operations.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from enum import Enum
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
|
||||||
|
class SetOperation(Enum):
|
||||||
|
UNION = (1, 1, 1)
|
||||||
|
INTERSECTION = (0, 1, 0)
|
||||||
|
DIFFERENCE = (1, 0, 0)
|
||||||
|
SYMMETRIC_DIFFERENCE = (1, 0, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def operation(A: "Qube", B : "Qube", type: SetOperation) -> "Qube":
|
||||||
|
# Sort nodes from both qubes by their keys
|
||||||
|
nodes_by_key = defaultdict(lambda : dict(A = [], B = []))
|
||||||
|
for node in A.nodes:
|
||||||
|
nodes_by_key[node.key]["A"].append(node)
|
||||||
|
for key, ndoes
|
||||||
|
|
||||||
|
# The root node is special so we need a helper method that we can recurse on
|
||||||
|
def _operation(A: list["Qube"], B : list["Qube"], type: SetOperation) -> "Qube":
|
||||||
|
pass
|
27
tests/test_basic_operations.py
Normal file
27
tests/test_basic_operations.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from qubed import Qube
|
||||||
|
|
||||||
|
|
||||||
|
def test_eq():
|
||||||
|
d = {
|
||||||
|
"class=od" : {
|
||||||
|
"expver=0001": {"param=1":{}, "param=2":{}},
|
||||||
|
"expver=0002": {"param=1":{}, "param=2":{}},
|
||||||
|
},
|
||||||
|
"class=rd" : {
|
||||||
|
"expver=0001": {"param=1":{}, "param=2":{}, "param=3":{}},
|
||||||
|
"expver=0002": {"param=1":{}, "param=2":{}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
q = Qube.from_dict(d)
|
||||||
|
r = Qube.from_dict(d)
|
||||||
|
|
||||||
|
assert q == r
|
||||||
|
|
||||||
|
def test_n_leaves():
|
||||||
|
q = Qube.from_dict({
|
||||||
|
"a=1/2/3" : {"b=1/2/3" : {"c=1/2/3" : {}}},
|
||||||
|
"a=5" : { "b=4" : { "c=4" : {}}}
|
||||||
|
})
|
||||||
|
|
||||||
|
# Size is 3*3*3 + 1*1*1 = 27 + 1
|
||||||
|
assert q.n_leaves == 27 + 1
|
Loading…
x
Reference in New Issue
Block a user