Iteration#
This package contains the trees used for data iteration.
The DataTree stores an actual data point, which consists of nested dict and list objects, with DataLiteral leaves.
Then, the IterationTree provides a framework to build complex searches over data trees. Its leaves consist of literal values, or data iterators. Those leaves can simply be iterated over, but they can also be used to build more complex iteration trees.
First, they can be combined with IterationMethod nodes, which provide a way to iterate over multiple data sources. Then, Transform nodes can be inserted in those trees in order to modify the data trees generated while iterating.
- class phileas.iteration.Accumulator(child: IterationTree, recursive: bool = False, start_value: dict[phileas.iteration.base.Key, phileas.iteration.base.DataTree] | None = None)[source]#
Transform node that accumulates its inputs, as a kind of unlazifying transform:
if its successive inputs are dictionaries, merge them using the union operator (recursively or not), and return the results;
otherwise, leave its inputs untouched.
- recursive: bool = False#
Specify if the accumulation must be done recursively or not. For example, accumulating values
{"a": 1, "b": {"ba": 1}}and{"a": 2, "b": {"bb": 2}}recursively will return{"a": 2, "b": {ba": 1, "bb": 2}}, whereas doing it non-recursively will return{"a": 2, "b": {"bb": 2}}.
- class phileas.iteration.CartesianProduct(children: list[IterationTree] | dict[phileas.iteration.base.Key, IterationTree], order: list[phileas.iteration.base.Key] | None = None, lazy: bool = False, snake: bool = False)[source]#
Iteration over the cartesian product of the children. The iteration order is the same as
itertools.product(). In other words, iteration will behave roughly asfor v1 in c1: for v2 in c2: for v3 in c3: yield [v1, v2, v3]
If an order is specified, its first element will correspond to the outermost loop, and its last to the innermost one.
- class phileas.iteration.Configurations(children: dict[Key, IterationTree], order: list[Key] | None = None, lazy: bool = False, default_configuration: Key | None = None, move_up: bool = False, insert_name: bool = False)[source]#
Represents a set of named configurations that can be invoked using
IterationTree.get_configuration(). When it is called, with argumentname, all theConfigurationsnodes that have a matching configuration will be replaced by it. Other ones will be replaced by their default value.This allows to escape from the recursive and local nature of trees: a single call can modify nodes throughout a whole tree. However, it is often convenient to convert configurable trees back to classical, non-configurable trees. This is done by
IterationTree.unroll_configurations().The
Configurationsnode holds a set of iteration trees, called configurations, which are identified by their name. Two parameters,move_upandinsert_name, control howIterationTree.get_configuration()behaves.By default,
move_up == insert_name == False. TheConfigurationnode is simply replaced by the content of the requested configuration.>>> tree = CartesianProduct({ ... "instrument": Configurations({ ... "config1": CartesianProduct({ ... "param1": IterationLiteral(value="1-1"), ... "param2": IterationLiteral(value="1-2"), ... }), ... "config2": CartesianProduct({ ... "param1": IterationLiteral(value="2-1"), ... "param3": IterationLiteral(value="2-3"), ... }) ... }) ... }) >>> tree.get_configuration("config1").to_pseudo_data_tree() {'instrument': {'param1': '1-1', 'param2': '1-2'}}
If
move_up == True, the content of the chosen configuration is moved one level up, so that it is at the same level as theConfigurationsnode. This requires it to have anIterationMethodparent withdictchildren. This can be used to factorize configurations.>>> tree = CartesianProduct({ ... "_": Configurations({ ... "config1": CartesianProduct({ ... "param1": IterationLiteral(value="1-1"), ... }), ... "config2": CartesianProduct({ ... "param1": IterationLiteral(value="2-1"), ... }) ... }, move_up=True), ... "param2": IterationLiteral(value="2") ... }) >>> tree.get_configuration("config1").to_pseudo_data_tree() {'param1': '1-1', 'param2': '2'}
If
insert_name == True, the name of the requested configuration is inserted in the resulting tree. If the requested configuration allows it (_ie_. it is anIterationMethodwithdictchildren), the name is inserted into itself, with the key_configuration.>>> tree = CartesianProduct({ ... "instrument": Configurations({ ... "config1": CartesianProduct({ ... "param1": IterationLiteral(value="1-1"), ... "param2": IterationLiteral(value="1-2"), ... }), ... "config2": CartesianProduct({ ... "param1": IterationLiteral(value="2-1"), ... "param3": IterationLiteral(value="2-3"), ... }) ... }, insert_name=True) ... }) >>> tree.get_configuration("config1").to_pseudo_data_tree() {'instrument': {'_configuration': 'config1', 'param1': '1-1', 'param2': '1-2'}}
If, additionally,
move_up == True, the name of the configuration is inserted instead of theConfigurationsnode.>>> tree = CartesianProduct({ ... "instrument": Configurations({ ... "config1": CartesianProduct({ ... "param1": IterationLiteral(value="1-1"), ... "param2": IterationLiteral(value="1-2"), ... }), ... "config2": CartesianProduct({ ... "param1": IterationLiteral(value="2-1"), ... "param3": IterationLiteral(value="2-3"), ... }) ... }, insert_name=True, move_up=True) ... }) >>> tree.get_configuration("config1").to_pseudo_data_tree() {'instrument': 'config1', 'param1': '1-1', 'param2': '2'}
However, if the requested configuration is not an
IterationMethod, itsnameis inserted as a sibling, assigned to the keyf"_{name}_configuration".>>> tree = CartesianProduct({ ... "param": Configurations({ ... "config1": IterationLiteral(value="1"), ... "config2": IterationLiteral(value="2"), ... }, insert_name=True), ... }) >>> tree.get_configuration("config1").to_pseudo_data_tree() {'_param_configuration': 'config1', 'param': '1'}
This means that the content of the configurations affects how they are handled. Although this might change in the future, it is made to support most situations. It is recommended to keep all the configurations with the same shape.
insert_nameis not necessary. It is possible to replace it with the following kind of tree:>>> tree = CartesianProduct({ ... "param": Configurations({ ... "config1": IterationLiteral(value="1"), ... "config2": IterationLiteral(value="2"), ... }, ... "config": Configurations({ ... "config1": IterationLiteral(value="config1"), ... "config2": IterationLiteral(value="config2"), ... })), ... }) >>> tree.get_configuration("config1").to_pseudo_data_tree() {'param': '1', 'config': 'config1'}
- children: dict[Key, IterationTree]#
The children of the node. It must not be empty.
- default_configuration: Key | None = None#
Key of the default configuration, which must be in the set of keys of children.
- move_up: bool = False#
If set, the content of a requested configuration is moved up, at the parent level of the current node. In other words, it becomes a sibling of the current
Configurationsnode.Otherwise, the content is inserted at the same level as the configurations themselves.
- insert_name: bool = False#
If set, insert the name of the requested configuration when calling
get_configuration(). Ifmove_up, then theConfigurationsnode is replaced by this name. Otherwise, a"_configuration"sibling node is inserted.
- class phileas.iteration.First(child: IterationTree, size: int | None)[source]#
Return only the first elements of its child.
- class phileas.iteration.FunctionalTranform(child: IterationTree, f: Callable[[phileas.iteration.base.DataTree], phileas.iteration.base.DataTree])[source]#
Transform node using its function attribute to modify its child.
- class phileas.iteration.GeneratorWrapper(generator_function: ~typing.Callable[[...], ~typing.Iterator[None | bool | str | int | float | ~phileas.iteration.base._NoDefault | dict[bool | str | int | float, phileas.iteration.base.DataTree] | list[phileas.iteration.base.DataTree]]], args: list = <factory>, kwargs: dict = <factory>, size: int | None = None, default_value: None | bool | str | int | float | ~phileas.iteration.base._NoDefault | dict[bool | str | int | float, phileas.iteration.base.DataTree] | list[phileas.iteration.base.DataTree] = <factory>)[source]#
Wrapper around a generator function, which can be used in order not to have to implement a new iteration leave, and its iterator. Note that only continuous forward iteration is supported by the node.
- size: int | None = None#
Size of the tree. If the generator can provide more elements, only the first
sizeones are returned. If it cannot generate enough, aStopIterationis raised during iteration.Nonerepresents an infinite generator.
- class phileas.iteration.GeometricRange(start: ~phileas.iteration.leaf.T, end: ~phileas.iteration.leaf.T, default_value: ~phileas.iteration.leaf.T | ~phileas.iteration.base._NoDefault = <factory>, steps: int = 2)[source]#
Generate
stepsvalues geometrically spaced betweenstartandend, both included.
- exception phileas.iteration.InfiniteLength[source]#
Exception raised when requesting the length of an infinite collection.
- class phileas.iteration.IntegerRange(start: ~phileas.iteration.leaf.T, end: ~phileas.iteration.leaf.T, default_value: ~phileas.iteration.leaf.T | ~phileas.iteration.base._NoDefault = <factory>, step: int = 1)[source]#
Generate integer values
stepspaced, betweenstartandend, both included.startmust be anint, butendcan also bemath.infor-math.inf. In these cases, the range is infinite.
- class phileas.iteration.IterationMethod(children: list[IterationTree] | dict[phileas.iteration.base.Key, IterationTree], order: list[phileas.iteration.base.Key] | None = None, lazy: bool = False)[source]#
Iteration node having multiple children, supplied either as a list or dictionary.
In order to implement a concrete iteration method, you should sub-class
IterationMethodand implement a correspondingIterationMethodIterator, which is returned by_iter().This should remain the only node in an iteration tree that can hold
dictandlistchildren. If you are tempted to create another node doing so, you should verify that it cannot be done by sub-classingIterationMethodinstead.- children: list[IterationTree] | dict[Key, IterationTree]#
The children of the node. It must not be empty.
- order: list[Key] | None = None#
Order of iteration over the children. How it is used depends on the concrete iteration method implementation. It must be a permutation of the set of keys of
children.
- lazy: bool = False#
Notify the iteration method to be lazy. For now, this feature is only supported for
dictchildren. In this case, lazy iteration will just yield the keys that have changed at each step.Note that concrete iteration method classes are not required to actually implement lazy iteration, and if they don’t, they will probably do so silently. Refer to their documentation or their implementation.
- class phileas.iteration.IterationTree[source]#
Represents a set of data trees, as well as the way to iterate over them. In order to be able to get a single data tree from an iteration tree, they are able to build a default data tree, which (usually) has the same shape as the generated data tree.
An iteration tree cannot be modified, as it is a frozen
dataclass. Instead, a new one must be created from this one.- get_configuration(key: phileas.iteration.base.Key) IterationTree[source]#
Returns a given configuration of the tree, if it exists. Otherwise, raises a
KeyError. SeeConfigurationsnode for more details on the behavior of this method.
- unroll_configurations() IterationTree[source]#
Transforms a configurable tree, ie. one with
configurations != {}, to a non-configurable tree. It requests all the available configurations, and gather them into aUnionnode withreset=False. AMoveUpTransformis used to get rid of the name of the configurations.If the tree is not configurable, return it.
- safe_len() int | None[source]#
Return the number of data trees represented by the iteration tree. If it is finite, it is the same as the number of elements yielded by
__iter__(). Otherwise, returnNone.
- iterate() TreeIterator[source]#
Implementation of
__len__()which assumes thatselfdoes not have any configuration.
- abstract to_pseudo_data_tree() phileas.iteration.base.PseudoDataTree[source]#
Converts the iteration tree to a pseudo data tree.
- default(no_default_policy: NoDefaultPolicy = NoDefaultPolicy.ERROR) DataTree | _NoDefault[source]#
Returns a default data tree. If the tree does not have a default value, follows the behavior dictated by
no_default_policy.
- with_params(path: list[bool | str | int | float | _Child] | None = None, **kwargs) Self[source]#
Returns a similar iteration tree, where the node at
pathis assigned the given keyword parameters. Ifpathis not specified, modifies the root of the tree directly.
- get(path: list[bool | str | int | float | _Child]) IterationTree[source]#
Get a node inside a tree. It should not be used to modify the tree.
- insert_child(path: list[bool | str | int | float | _Child], tree: IterationTree | None) IterationTree[source]#
Insert a child anywhere in the tree, whose location is specified by path argument. Return the newly created tree.
If there is already a node at this location, it will be replaced.
If the specified tree is
None, a node is supposed to exist at this location (otherwise, a KeyError is raised), and will be removed if possible. Only iteration methods nodes will support child removal, see their implementation of_remove_child(). In any other case, aTypeErroris raised.Note that the root of a tree cannot be removed, so specifying an empty path with a
Nonetree will raise aKeyError.
- remove_child(path: list[bool | str | int | float | _Child]) IterationTree[source]#
Remove a node in a tree. It is equivalent to
insert_child(path, None).
- insert_unary(path: ChildPath, Parent: type[UnaryNode], *args, **kwargs) IterationTree[source]#
Insert a parent to the node at the given path, parent which is necessarily a transform node, as it will only have a single child. The parent is built using the
Parentclass, and the supplied arguments.The newly created tree is returned.
- replace_node(path: list[bool | str | int | float | _Child], Node: type[IterationTree], *args, **kwargs) IterationTree[source]#
Replace the node at the given path with another one. The other node is built using its type,
Node, and theargsandkwargsarguments. Note that the sub-tree of the replaced node is not modified.This requires
Nodeto be of the same kind of the node that is being replaced: a transform for a transform, an iteration method for an iteration method, a leaf for a leaf.
- depth_first_modify(modifier: Callable[[IterationTree, list[bool | str | int | float | _Child]], IterationTree]) IterationTree[source]#
Using a post-fix depth-first search, replace each
nodeof the tree, located atpath, withmodifier(node, path).
- class phileas.iteration.Lazify(child: IterationTree)[source]#
Transform node that only returns the elements that were updated in its children values (for dictionary values), or leaves its inputs untouched (for other types).
- class phileas.iteration.LinearRange(start: ~phileas.iteration.leaf.T, end: ~phileas.iteration.leaf.T, default_value: ~phileas.iteration.leaf.T | ~phileas.iteration.base._NoDefault = <factory>, steps: int = 2)[source]#
Generate
stepsvalues linearly spaced betweenstartandend, both included.
- exception phileas.iteration.NoDefaultError(message: str | None, path: list[bool | str | int | float | _Child])[source]#
Indicates that
IterationTree.default()has been called on an iteration tree where a node does not have a default value.
- class phileas.iteration.NoDefaultPolicy(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]#
Behavior of
IterationTree.default()for trees not having a default value.- ERROR = 'ERROR'#
Raise a
NoDefaultErrorif any of the nodes in the tree does not have a default value.
- SENTINEL = 'SENTINEL'#
Return the
_NoDefaultsentinel if any of the nodes in the tree does not have a default value.
- SKIP = 'SKIP'#
Skip elements without a default element. If the root of the tree does not have a default value, return a
_NoDefaultsentinel.Note that this is not supported by iteration method nodes with list children.
- FIRST_ELEMENT = 'FIRST_ELEMENT'#
Return the first element of iteration leaves without a default value.
- class phileas.iteration.NumericRange(start: ~phileas.iteration.leaf.T, end: ~phileas.iteration.leaf.T, default_value: ~phileas.iteration.leaf.T | ~phileas.iteration.base._NoDefault = <factory>)[source]#
Represents a range of numeric values.
- class phileas.iteration.NumpyRNG(seed: ~phileas.iteration.random.Seed | None = None, size: None | int = None, default_value: None | bool | str | int | float | ~phileas.iteration.base._NoDefault | dict[bool | str | int | float, phileas.iteration.base.DataTree] | list[phileas.iteration.base.DataTree] = <factory>, distribution: ~typing.Callable = <cyfunction Generator.random>, args: list = <factory>, kwargs: dict[str, ~typing.Any] = <factory>)[source]#
Random iteration leaf based on the RNG of numpy.
Note:
Iteration is based on
NumpyRNGIterator. It works by seeding a PRNG with a value which depends on the requested iteration position. This might introduce a bias on the generated random values. This is done as a way to provide a random-access PRNG, although an actual random-access PRNG would be preferable.Alternatively, the iterator could be built from a usual iterative PRNG, and caching would be used to provide access to previous values. This would impact iteration performances (especially for cache misses, which can induce unbound time delays).
- distribution(size=None, dtype=<class 'numpy.float64'>, out=None)#
Which distribution to use for the node. It must be a distribution method of
numpy.random.Generator.
- class phileas.iteration.Pick(children: list[IterationTree] | dict[Key, IterationTree], order: list[Key] | None = None, lazy: bool = False, seed: Seed | None = None, default_child: Key | None = None)[source]#
Randomly pick and return one child at a time. For finite trees, it behaves in a way similar to composing
ShuffleandUnion (reset=False)nodes. It can additionally handle infinite children, whereasShufflecannot.
- class phileas.iteration.PrimeRng(seed: ~phileas.iteration.random.Seed | None = None, size: None | int = None, default_value: None | bool | str | int | float | ~phileas.iteration.base._NoDefault | dict[bool | str | int | float, phileas.iteration.base.DataTree] | list[phileas.iteration.base.DataTree] = <factory>, high: int = 0, low: int = 255)[source]#
Random iteration leaf generating prime numbers.
Generation is done in two steps:
a uniform integer is uniformly drawn from the interval
[low, high];sympy
nextprime()andprevprime()are used to find the closest prime.
- class phileas.iteration.RandomIterationLeaf(seed: ~phileas.iteration.random.Seed | None = None, size: None | int = None, default_value: None | bool | str | int | float | ~phileas.iteration.base._NoDefault | dict[bool | str | int | float, phileas.iteration.base.DataTree] | list[phileas.iteration.base.DataTree] = <factory>)[source]#
Deterministic pseudo-random elements generator.
- class phileas.iteration.Seed(path: list[bool | str | int | float | _Child], salt: None | bool | str | int | float | _NoDefault | dict[bool | str | int | float, DataTree] | list[DataTree])[source]#
Seed of a random iteration node, used by its RNG.
- class phileas.iteration.Sequence(elements: list[None | bool | str | int | float | ~phileas.iteration.base._NoDefault | dict[bool | str | int | float, phileas.iteration.base.DataTree] | list[phileas.iteration.base.DataTree]], default_value: None | bool | str | int | float | ~phileas.iteration.base._NoDefault | dict[bool | str | int | float, phileas.iteration.base.DataTree] | list[phileas.iteration.base.DataTree] = <factory>)[source]#
Non-empty sequence of data trees.
- class phileas.iteration.Shuffle(child: IterationTree, seed: Seed | None = None)[source]#
Unary node that shuffles the order of its child. In other words, it iterates over a random permutation of its children values.
Shuffling has a constant memory cost. The permutation of the order of the child tree is obtained through the use of a cypher, which means that it is entirely represented by a short key.
To shuffle a tree with size \(n\), a symmetric cypher on \(\{ 0, \dots , n-1 \}\) is used. It is built with two components:
a block cypher working on \(2 \lceil \log_2 n / 2 \rceil\)-bit words. It consists in a 3-round Feistel network which uses SipHash 1-3 as a round function.
Cycle walking is used to restrict the message space to \(\{ 0, \dots, n - 1 \}\). The block cypher is iterated until its output is valid.
- class phileas.iteration.Transform(child: IterationTree)[source]#
Node that modifies the data trees generated by its child during iteration.
If you want to transform a
listordictof iteration trees, you should wrap them in anIterationMethodobject first.
- class phileas.iteration.TreeIterator(tree: T)[source]#
Iteration tree iterator.
Compared to a usual iterator, it supports forward and backward iteration, and is endlessly usable. This means that, whenever it is “exhausted” (
__next__()raisesStopIteration), it can either be reset to its starting position withreset(), or its iteration direction can be switched usingreverse().Additionally, it supports random access with the
__getitem__()method, which usesupdate()under the hood.- position: int#
Position of the last value that was yielded. Valid values start at -1 (backward-exhausted iterator, or forward iteration start), and go up to
size(forward-exhausted iterator, or backward iteration start).It can be directly modified by
update().
- tree: T#
Reference to the tree being iterated over.
- size: int | None#
Cached size of the iterated tree. Consider using this instead of
len (self.tree), as computing the length of a tree can be an expensive operation.
- reset()[source]#
Reset the internal state of the iterator, so that its next value will be the first iterated value in the current direction. It takes into account the value of forward, going either to the start or the end of the iterated collection.
- reverse()[source]#
Reverse the iteration direction of the iterator, but stay at the same position. Thus, if
itis anyTreeIterator, the following behavior is expected:>>> it.reset() >>> it.reverse() >>> list(it) []
If you want to iterate over an iteration tree
treebackward, you have to reset the iterator after having reversed it:>>> it = iter(tree) >>> it.reverse() >>> it.reset()
- update(position: int)[source]#
Update the position of the iterator to any supported position. This includes the positions ranging from
0toself.size - 1, included, as well as --1, which represents a reset forward iterator and -self.size, which represents a reset backward iterator.If an invalid position is requested, an
IndexErroris raised, and the state of the iterator remains unchanged.
- class phileas.iteration.UniformBigIntegerRng(seed: ~phileas.iteration.random.Seed | None = None, size: None | int = None, default_value: None | bool | str | int | float | ~phileas.iteration.base._NoDefault | dict[bool | str | int | float, phileas.iteration.base.DataTree] | list[phileas.iteration.base.DataTree] = <factory>, low: int = 0, high: int = 255)[source]#
Random iteration leaf generating arbitrarily big integers. It works by iteratively repeatedly using a Numpy RNG.
By default returns a byte (integer larger or equal to 0 and smaller than 256).
- class phileas.iteration.Union(children: list[IterationTree] | dict[phileas.iteration.base.Key, IterationTree], order: list[phileas.iteration.base.Key] | None = None, lazy: bool = False, preset: Literal['first'] | Literal['default'] | None = 'first', common_preset: bool = False, reset: Literal['first'] | Literal['last'] | Literal['default'] | None = 'first')[source]#
Iteration over one child at a time, starting with the first one (or the first one of the order, if specified). Children that are not being iterated over have
their default value if it exists and
their first value otherwise.
- preset: Literal['first'] | Literal['default'] | None = 'first'#
Defines which value to use before starting the iteration over a child. “first” uses the child’s first value and “default” its default value.
Nonedoes not set the children values before iteration. Thus, it is only applicable to dict children.
- common_preset: bool = False#
You can only set it when
preset == "first". Then, children are all set to their first value at the first iteration. When iterating over them, they start directly at their second value.
- reset: Literal['first'] | Literal['last'] | Literal['default'] | None = 'first'#
Defines which value to use after ending the iteration over a child. “first” resets it to its starting value, “last” leaves it unchanged, and “default” resets it to its default value.
Nonedoes not reset the child after iteration. Thus, it is only applicable to dict children.
- class phileas.iteration.Zip(children: list[IterationTree] | dict[phileas.iteration.base.Key, IterationTree], order: list[phileas.iteration.base.Key] | None = None, lazy: bool = False, stops_at: Literal['longest', 'shortest'] = 'shortest', ignore_fixed: bool = True)[source]#
Iteration over all of the children of the nodes at the same time, in a way similar to
zip().- stops_at: Literal['longest'] | Literal['shortest'] = 'shortest'#
If
shortest, iteration stops whenever the first child is exhausted. Iflongest, iteration continues until the last child is exhausted. Note thatlongestis only supported fordictchildren.
- phileas.iteration.generate_seeds(tree: IterationTree, salt: None | bool | str | int | float | _NoDefault | dict[bool | str | int | float, DataTree] | list[DataTree] = None) IterationTree[source]#
Populate the seeds of the random nodes and leaves of a tree, using the given salt value.
- phileas.iteration.restrict_leaves_sizes(tree: IterationTree, policy: RestrictionPolicy = RestrictionPolicy.FIRST_LAST) IterationTree[source]#
Restrict the size of the iteration leaves of the tree, depending on the restriction policy. This is useful for troubleshooting, or to verify that the full range of the leaves is supported by something.
This module contains the definition of the base type and classes used for iteration (data tree, pseudo data tree and iteration tree).
- phileas.iteration.base.DataLiteral#
Data values that can be used as data tree leaves
- phileas.iteration.base.Key = bool | str | int | float#
Dictionary keys
- phileas.iteration.base.DataTree#
A data tree consists of literal leaves, and dictionary or list nodes
alias of
None|bool|str|int|float|_NoDefault|dict[bool|str|int|float, DataTree] |list[DataTree]
- phileas.iteration.base.PseudoDataLiteral#
A leave of a pseudo data tree is either a data tree leave, or a non-trivial iteration tree leave.
alias of
None|bool|str|int|float|_NoDefault|IterationLeaf
- phileas.iteration.base.PseudoDataTree#
A pseudo data tree is a data tree whose leaves can be non literal iteration leaves.
alias of
None|bool|str|int|float|_NoDefault|IterationLeaf|dict[bool|str|int|float, PseudoDataTree] |list[PseudoDataTree]
- class phileas.iteration.base.DefaultIndex[source]#
Bases:
SentinelIndex of the default value of an iteration tree.
- class phileas.iteration.base.TreeIterator(tree: T)[source]#
-
Iteration tree iterator.
Compared to a usual iterator, it supports forward and backward iteration, and is endlessly usable. This means that, whenever it is “exhausted” (
__next__()raisesStopIteration), it can either be reset to its starting position withreset(), or its iteration direction can be switched usingreverse().Additionally, it supports random access with the
__getitem__()method, which usesupdate()under the hood.- position: int#
Position of the last value that was yielded. Valid values start at -1 (backward-exhausted iterator, or forward iteration start), and go up to
size(forward-exhausted iterator, or backward iteration start).It can be directly modified by
update().
- tree: T#
Reference to the tree being iterated over.
- size: int | None#
Cached size of the iterated tree. Consider using this instead of
len (self.tree), as computing the length of a tree can be an expensive operation.
- reset()[source]#
Reset the internal state of the iterator, so that its next value will be the first iterated value in the current direction. It takes into account the value of forward, going either to the start or the end of the iterated collection.
- reverse()[source]#
Reverse the iteration direction of the iterator, but stay at the same position. Thus, if
itis anyTreeIterator, the following behavior is expected:>>> it.reset() >>> it.reverse() >>> list(it) []
If you want to iterate over an iteration tree
treebackward, you have to reset the iterator after having reversed it:>>> it = iter(tree) >>> it.reverse() >>> it.reset()
- update(position: int)[source]#
Update the position of the iterator to any supported position. This includes the positions ranging from
0toself.size - 1, included, as well as --1, which represents a reset forward iterator and -self.size, which represents a reset backward iterator.If an invalid position is requested, an
IndexErroris raised, and the state of the iterator remains unchanged.
- class phileas.iteration.base.OneWayTreeIterator[source]#
Bases:
objectIterator used in cases where random access iteration is too cumbersome to implement. It only requires implementing the
_next()method.Random access is obtained by using a cache. Thus, this method is usually more time and memory expensive than classical random access iterators, so it should only be used as a last resort. For now, the cache size is unbound, as it stores all the iterated values. This might change in the future.
This is not a subclass of TreeIterator in order to prevent diamond inheritance issues. Child classes must thus inherit from this, alongside
TreeIterator. It is recommended to placeOneWayTreeIterator()first, so that its_current_value()implementation is used.
- class phileas.iteration.base.NoDefaultPolicy(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]#
Bases:
EnumBehavior of
IterationTree.default()for trees not having a default value.- ERROR = 'ERROR'#
Raise a
NoDefaultErrorif any of the nodes in the tree does not have a default value.
- SENTINEL = 'SENTINEL'#
Return the
_NoDefaultsentinel if any of the nodes in the tree does not have a default value.
- SKIP = 'SKIP'#
Skip elements without a default element. If the root of the tree does not have a default value, return a
_NoDefaultsentinel.Note that this is not supported by iteration method nodes with list children.
- FIRST_ELEMENT = 'FIRST_ELEMENT'#
Return the first element of iteration leaves without a default value.
- exception phileas.iteration.base.NoDefaultError(message: str | None, path: list[bool | str | int | float | _Child])[source]#
Bases:
ExceptionIndicates that
IterationTree.default()has been called on an iteration tree where a node does not have a default value.
- phileas.iteration.base.no_default = _NoDefault()#
You can store this value - instead of an actual default value - in instances of classes that can have a default value, but don’t.
- exception phileas.iteration.base.InfiniteLength[source]#
Bases:
BaseExceptionException raised when requesting the length of an infinite collection.
- class phileas.iteration.base.IterationTree[source]#
Bases:
ABCRepresents a set of data trees, as well as the way to iterate over them. In order to be able to get a single data tree from an iteration tree, they are able to build a default data tree, which (usually) has the same shape as the generated data tree.
An iteration tree cannot be modified, as it is a frozen
dataclass. Instead, a new one must be created from this one.- get_configuration(key: phileas.iteration.base.Key) IterationTree[source]#
Returns a given configuration of the tree, if it exists. Otherwise, raises a
KeyError. SeeConfigurationsnode for more details on the behavior of this method.
- unroll_configurations() IterationTree[source]#
Transforms a configurable tree, ie. one with
configurations != {}, to a non-configurable tree. It requests all the available configurations, and gather them into aUnionnode withreset=False. AMoveUpTransformis used to get rid of the name of the configurations.If the tree is not configurable, return it.
- safe_len() int | None[source]#
Return the number of data trees represented by the iteration tree. If it is finite, it is the same as the number of elements yielded by
__iter__(). Otherwise, returnNone.
- iterate() TreeIterator[source]#
Implementation of
__len__()which assumes thatselfdoes not have any configuration.
- abstract to_pseudo_data_tree() phileas.iteration.base.PseudoDataTree[source]#
Converts the iteration tree to a pseudo data tree.
- default(no_default_policy: NoDefaultPolicy = NoDefaultPolicy.ERROR) DataTree | _NoDefault[source]#
Returns a default data tree. If the tree does not have a default value, follows the behavior dictated by
no_default_policy.
- with_params(path: list[bool | str | int | float | _Child] | None = None, **kwargs) Self[source]#
Returns a similar iteration tree, where the node at
pathis assigned the given keyword parameters. Ifpathis not specified, modifies the root of the tree directly.
- get(path: list[bool | str | int | float | _Child]) IterationTree[source]#
Get a node inside a tree. It should not be used to modify the tree.
- insert_child(path: list[bool | str | int | float | _Child], tree: IterationTree | None) IterationTree[source]#
Insert a child anywhere in the tree, whose location is specified by path argument. Return the newly created tree.
If there is already a node at this location, it will be replaced.
If the specified tree is
None, a node is supposed to exist at this location (otherwise, a KeyError is raised), and will be removed if possible. Only iteration methods nodes will support child removal, see their implementation of_remove_child(). In any other case, aTypeErroris raised.Note that the root of a tree cannot be removed, so specifying an empty path with a
Nonetree will raise aKeyError.
- remove_child(path: list[bool | str | int | float | _Child]) IterationTree[source]#
Remove a node in a tree. It is equivalent to
insert_child(path, None).
- insert_unary(path: ChildPath, Parent: type[UnaryNode], *args, **kwargs) IterationTree[source]#
Insert a parent to the node at the given path, parent which is necessarily a transform node, as it will only have a single child. The parent is built using the
Parentclass, and the supplied arguments.The newly created tree is returned.
- replace_node(path: list[bool | str | int | float | _Child], Node: type[IterationTree], *args, **kwargs) IterationTree[source]#
Replace the node at the given path with another one. The other node is built using its type,
Node, and theargsandkwargsarguments. Note that the sub-tree of the replaced node is not modified.This requires
Nodeto be of the same kind of the node that is being replaced: a transform for a transform, an iteration method for an iteration method, a leaf for a leaf.
- depth_first_modify(modifier: Callable[[IterationTree, list[bool | str | int | float | _Child]], IterationTree]) IterationTree[source]#
Using a post-fix depth-first search, replace each
nodeof the tree, located atpath, withmodifier(node, path).
- class phileas.iteration.base.IterationLeaf[source]#
Bases:
IterationTree
This module defines abstract and concrete iteration leaves, which are the actual data sources of an iteration tree, alongside their iterators.
- class phileas.iteration.leaf.IterationLiteral(value: DT)[source]#
Bases:
IterationLeaf,Generic[DT]Wrapper around a data tree.
- class phileas.iteration.leaf.LiteralIterator(tree: T)[source]#
Bases:
TreeIterator[IterationLiteral]
- class phileas.iteration.leaf.GeneratorWrapper(generator_function: ~typing.Callable[[...], ~typing.Iterator[None | bool | str | int | float | ~phileas.iteration.base._NoDefault | dict[bool | str | int | float, phileas.iteration.base.DataTree] | list[phileas.iteration.base.DataTree]]], args: list = <factory>, kwargs: dict = <factory>, size: int | None = None, default_value: None | bool | str | int | float | ~phileas.iteration.base._NoDefault | dict[bool | str | int | float, phileas.iteration.base.DataTree] | list[phileas.iteration.base.DataTree] = <factory>)[source]#
Bases:
IterationLeafWrapper around a generator function, which can be used in order not to have to implement a new iteration leave, and its iterator. Note that only continuous forward iteration is supported by the node.
- size: int | None = None#
Size of the tree. If the generator can provide more elements, only the first
sizeones are returned. If it cannot generate enough, aStopIterationis raised during iteration.Nonerepresents an infinite generator.
- class phileas.iteration.leaf.GeneratorWrapperIterator(tree: GeneratorWrapper)[source]#
- class phileas.iteration.leaf.NumericRange(start: ~phileas.iteration.leaf.T, end: ~phileas.iteration.leaf.T, default_value: ~phileas.iteration.leaf.T | ~phileas.iteration.base._NoDefault = <factory>)[source]#
Bases:
IterationLeaf,Generic[T]Represents a range of numeric values.
- class phileas.iteration.leaf.LinearRange(start: ~phileas.iteration.leaf.T, end: ~phileas.iteration.leaf.T, default_value: ~phileas.iteration.leaf.T | ~phileas.iteration.base._NoDefault = <factory>, steps: int = 2)[source]#
Bases:
NumericRange[float]Generate
stepsvalues linearly spaced betweenstartandend, both included.
- class phileas.iteration.leaf.GeometricRange(start: ~phileas.iteration.leaf.T, end: ~phileas.iteration.leaf.T, default_value: ~phileas.iteration.leaf.T | ~phileas.iteration.base._NoDefault = <factory>, steps: int = 2)[source]#
Bases:
NumericRange[float]Generate
stepsvalues geometrically spaced betweenstartandend, both included.
- class phileas.iteration.leaf.IntegerRange(start: ~phileas.iteration.leaf.T, end: ~phileas.iteration.leaf.T, default_value: ~phileas.iteration.leaf.T | ~phileas.iteration.base._NoDefault = <factory>, step: int = 1)[source]#
Bases:
NumericRange[int|float]Generate integer values
stepspaced, betweenstartandend, both included.startmust be anint, butendcan also bemath.infor-math.inf. In these cases, the range is infinite.
- class phileas.iteration.leaf.IntegerRangeIterator(tree: IntegerRange)[source]#
Bases:
TreeIterator[IntegerRange]
- class phileas.iteration.leaf.Sequence(elements: list[None | bool | str | int | float | ~phileas.iteration.base._NoDefault | dict[bool | str | int | float, phileas.iteration.base.DataTree] | list[phileas.iteration.base.DataTree]], default_value: None | bool | str | int | float | ~phileas.iteration.base._NoDefault | dict[bool | str | int | float, phileas.iteration.base.DataTree] | list[phileas.iteration.base.DataTree] = <factory>)[source]#
Bases:
IterationLeafNon-empty sequence of data trees.
- class phileas.iteration.leaf.SequenceIterator(tree: T)[source]#
Bases:
TreeIterator[Sequence]
- class phileas.iteration.leaf.RandomIterationLeaf(seed: ~phileas.iteration.random.Seed | None = None, size: None | int = None, default_value: None | bool | str | int | float | ~phileas.iteration.base._NoDefault | dict[bool | str | int | float, phileas.iteration.base.DataTree] | list[phileas.iteration.base.DataTree] = <factory>)[source]#
Bases:
IterationLeaf,RandomTreeDeterministic pseudo-random elements generator.
- class phileas.iteration.leaf.NumpyRNG(seed: ~phileas.iteration.random.Seed | None = None, size: None | int = None, default_value: None | bool | str | int | float | ~phileas.iteration.base._NoDefault | dict[bool | str | int | float, phileas.iteration.base.DataTree] | list[phileas.iteration.base.DataTree] = <factory>, distribution: ~typing.Callable = <cyfunction Generator.random>, args: list = <factory>, kwargs: dict[str, ~typing.Any] = <factory>)[source]#
Bases:
RandomIterationLeafRandom iteration leaf based on the RNG of numpy.
Note:
Iteration is based on
NumpyRNGIterator. It works by seeding a PRNG with a value which depends on the requested iteration position. This might introduce a bias on the generated random values. This is done as a way to provide a random-access PRNG, although an actual random-access PRNG would be preferable.Alternatively, the iterator could be built from a usual iterative PRNG, and caching would be used to provide access to previous values. This would impact iteration performances (especially for cache misses, which can induce unbound time delays).
- distribution(size=None, dtype=<class 'numpy.float64'>, out=None)#
Which distribution to use for the node. It must be a distribution method of
numpy.random.Generator.
- class phileas.iteration.leaf.NumpyRNGIterator(tree: NumpyRNG)[source]#
Bases:
TreeIterator[NumpyRNG]Iterator that generates random numbers by reseeding a numpy bit generator, and getting its first returned values.
- class phileas.iteration.leaf.UniformBigIntegerRng(seed: ~phileas.iteration.random.Seed | None = None, size: None | int = None, default_value: None | bool | str | int | float | ~phileas.iteration.base._NoDefault | dict[bool | str | int | float, phileas.iteration.base.DataTree] | list[phileas.iteration.base.DataTree] = <factory>, low: int = 0, high: int = 255)[source]#
Bases:
RandomIterationLeafRandom iteration leaf generating arbitrarily big integers. It works by iteratively repeatedly using a Numpy RNG.
By default returns a byte (integer larger or equal to 0 and smaller than 256).
- class phileas.iteration.leaf.UniformBigIntegerRngIterator(tree: UniformBigIntegerRng)[source]#
Bases:
TreeIterator[UniformBigIntegerRng]Iterator that generates random numbers by reseeding a numpy byte generator.
Bytes are repeatedly sampled until the generated number fits in the required bounds.
- class phileas.iteration.leaf.PrimeRng(seed: ~phileas.iteration.random.Seed | None = None, size: None | int = None, default_value: None | bool | str | int | float | ~phileas.iteration.base._NoDefault | dict[bool | str | int | float, phileas.iteration.base.DataTree] | list[phileas.iteration.base.DataTree] = <factory>, high: int = 0, low: int = 255)[source]#
Bases:
RandomIterationLeafRandom iteration leaf generating prime numbers.
Generation is done in two steps:
a uniform integer is uniformly drawn from the interval
[low, high];sympy
nextprime()andprevprime()are used to find the closest prime.
- class phileas.iteration.leaf.PrimeRngIterator(tree: PrimeRng)[source]#
Bases:
TreeIterator[PrimeRng]Iterator that generates random prime numbers by uniformly generating an number with the big integer RNG, before finding a neighboring prime with sympy
prevprime()andnextprime().Details on the process:
Generate a big integer,
find the next and previous primes with Sympy,
return the closest one inside the range; if they are equidistant, pick one randomly.
This module defines abstract and concrete iteration tree nodes, which are iteration methods and transform nodes, as well as their iterators.
- class phileas.iteration.node.IterationMethod(children: list[IterationTree] | dict[phileas.iteration.base.Key, IterationTree], order: list[phileas.iteration.base.Key] | None = None, lazy: bool = False)[source]#
Bases:
IterationTreeIteration node having multiple children, supplied either as a list or dictionary.
In order to implement a concrete iteration method, you should sub-class
IterationMethodand implement a correspondingIterationMethodIterator, which is returned by_iter().This should remain the only node in an iteration tree that can hold
dictandlistchildren. If you are tempted to create another node doing so, you should verify that it cannot be done by sub-classingIterationMethodinstead.- children: list[IterationTree] | dict[Key, IterationTree]#
The children of the node. It must not be empty.
- order: list[Key] | None = None#
Order of iteration over the children. How it is used depends on the concrete iteration method implementation. It must be a permutation of the set of keys of
children.
- lazy: bool = False#
Notify the iteration method to be lazy. For now, this feature is only supported for
dictchildren. In this case, lazy iteration will just yield the keys that have changed at each step.Note that concrete iteration method classes are not required to actually implement lazy iteration, and if they don’t, they will probably do so silently. Refer to their documentation or their implementation.
- class phileas.iteration.node.IterationMethodIterator(tree: T)[source]#
Bases:
TreeIterator[T]Base class used to implement concrete
IterationMethodnodes iterators, and providing helper attributes to do so.- keys: list[Key | int]#
Access keys for the children iterators, such that
iterators[i]is an iterator oftree.children[keys[i]].Note that the previous statement is not properly typed, as the current type hints do not state that
keyscontains valid keys forchildren. However, the constructor takes care of the validity of the runtime types. Because of that, ignoring type checks can be required when implementing concrete iteration methods.
- iterators: list[TreeIterator]#
Children iterators stored in a list. This, with
keys, allows child-class to only implement iteration over lists.If the iteration tree does specify an order, use it. Otherwise, dictionaries are sorted by their key value, and lists keep the same order.
- positions: Sequence[int | DefaultIndex | None]#
Last returned positions of the child iterators.
- reset()[source]#
Reset the internal state of the iterator, so that its next value will be the first iterated value in the current direction. It takes into account the value of forward, going either to the start or the end of the iterated collection.
- reverse()[source]#
Reverse the iteration direction of the iterator, but stay at the same position. Thus, if
itis anyTreeIterator, the following behavior is expected:>>> it.reset() >>> it.reverse() >>> list(it) []
If you want to iterate over an iteration tree
treebackward, you have to reset the iterator after having reversed it:>>> it = iter(tree) >>> it.reverse() >>> it.reset()
- class phileas.iteration.node.CartesianProduct(children: list[IterationTree] | dict[phileas.iteration.base.Key, IterationTree], order: list[phileas.iteration.base.Key] | None = None, lazy: bool = False, snake: bool = False)[source]#
Bases:
IterationMethodIteration over the cartesian product of the children. The iteration order is the same as
itertools.product(). In other words, iteration will behave roughly asfor v1 in c1: for v2 in c2: for v3 in c3: yield [v1, v2, v3]
If an order is specified, its first element will correspond to the outermost loop, and its last to the innermost one.
- snake: bool = False#
Enable snake iteration, which guarantees that successive yielded elements differ by only one key at most (a la Gray code).
- children: list[IterationTree] | dict[Key, IterationTree]#
The children of the node. It must not be empty.
- class phileas.iteration.node.CartesianProductIterator(product: CartesianProduct)[source]#
- class phileas.iteration.node.Union(children: list[IterationTree] | dict[phileas.iteration.base.Key, IterationTree], order: list[phileas.iteration.base.Key] | None = None, lazy: bool = False, preset: Literal['first'] | Literal['default'] | None = 'first', common_preset: bool = False, reset: Literal['first'] | Literal['last'] | Literal['default'] | None = 'first')[source]#
Bases:
IterationMethodIteration over one child at a time, starting with the first one (or the first one of the order, if specified). Children that are not being iterated over have
their default value if it exists and
their first value otherwise.
- preset: Literal['first'] | Literal['default'] | None = 'first'#
Defines which value to use before starting the iteration over a child. “first” uses the child’s first value and “default” its default value.
Nonedoes not set the children values before iteration. Thus, it is only applicable to dict children.
- common_preset: bool = False#
You can only set it when
preset == "first". Then, children are all set to their first value at the first iteration. When iterating over them, they start directly at their second value.
- reset: Literal['first'] | Literal['last'] | Literal['default'] | None = 'first'#
Defines which value to use after ending the iteration over a child. “first” resets it to its starting value, “last” leaves it unchanged, and “default” resets it to its default value.
Nonedoes not reset the child after iteration. Thus, it is only applicable to dict children.
- children: list[IterationTree] | dict[Key, IterationTree]#
The children of the node. It must not be empty.
- class phileas.iteration.node.UnionIterator(tree: Union)[source]#
Bases:
IterationMethodIterator[Union]- cumsizes: list[int | float]#
Cumulated sum of the iterated sizes of the children, containing
intormath.infvalues. After, and including, the first infinite children, it only containsmath.infvalues. Its size islen(self.sizes) + 1, as it is prefixed with a 0.
- initial_value: int | DefaultIndex | None#
Value of a child before iteration over it starts
- final_value: int | DefaultIndex | None#
Value of a child after iteration over it ends
- class phileas.iteration.node.Zip(children: list[IterationTree] | dict[phileas.iteration.base.Key, IterationTree], order: list[phileas.iteration.base.Key] | None = None, lazy: bool = False, stops_at: Literal['longest', 'shortest'] = 'shortest', ignore_fixed: bool = True)[source]#
Bases:
IterationMethodIteration over all of the children of the nodes at the same time, in a way similar to
zip().- stops_at: Literal['longest'] | Literal['shortest'] = 'shortest'#
If
shortest, iteration stops whenever the first child is exhausted. Iflongest, iteration continues until the last child is exhausted. Note thatlongestis only supported fordictchildren.
- ignore_fixed: bool = True#
Ignore children with a fixed value, ie those with length 1. When set, they do not restrict the node size to 1.
- children: list[IterationTree] | dict[Key, IterationTree]#
The children of the node. It must not be empty.
- class phileas.iteration.node.ZipIterator(tree: T)[source]#
Bases:
IterationMethodIterator[Zip]
- class phileas.iteration.node.Pick(children: list[IterationTree] | dict[Key, IterationTree], order: list[Key] | None = None, lazy: bool = False, seed: Seed | None = None, default_child: Key | None = None)[source]#
Bases:
RandomTree,IterationMethodRandomly pick and return one child at a time. For finite trees, it behaves in a way similar to composing
ShuffleandUnion (reset=False)nodes. It can additionally handle infinite children, whereasShufflecannot.- children: list[IterationTree] | dict[Key, IterationTree]#
The children of the node. It must not be empty.
- class phileas.iteration.node.UnaryNode(child: 'IterationTree')[source]#
Bases:
IterationTree
- class phileas.iteration.node.Shuffle(child: IterationTree, seed: Seed | None = None)[source]#
Bases:
RandomTree,UnaryNodeUnary node that shuffles the order of its child. In other words, it iterates over a random permutation of its children values.
Shuffling has a constant memory cost. The permutation of the order of the child tree is obtained through the use of a cypher, which means that it is entirely represented by a short key.
To shuffle a tree with size \(n\), a symmetric cypher on \(\{ 0, \dots , n-1 \}\) is used. It is built with two components:
a block cypher working on \(2 \lceil \log_2 n / 2 \rceil\)-bit words. It consists in a 3-round Feistel network which uses SipHash 1-3 as a round function.
Cycle walking is used to restrict the message space to \(\{ 0, \dots, n - 1 \}\). The block cypher is iterated until its output is valid.
- class phileas.iteration.node.ShuffleIterator(tree: Shuffle)[source]#
Bases:
TreeIterator
- class phileas.iteration.node.First(child: IterationTree, size: int | None)[source]#
Bases:
UnaryNodeReturn only the first elements of its child.
- class phileas.iteration.node.FirstIterator(tree: First)[source]#
Bases:
TreeIterator[First]
- class phileas.iteration.node.Transform(child: IterationTree)[source]#
Bases:
UnaryNodeNode that modifies the data trees generated by its child during iteration.
If you want to transform a
listordictof iteration trees, you should wrap them in anIterationMethodobject first.
- class phileas.iteration.node.TransformIterator(tree: U)[source]#
Bases:
TreeIterator[U]- reset()[source]#
Reset the internal state of the iterator, so that its next value will be the first iterated value in the current direction. It takes into account the value of forward, going either to the start or the end of the iterated collection.
- reverse()[source]#
Reverse the iteration direction of the iterator, but stay at the same position. Thus, if
itis anyTreeIterator, the following behavior is expected:>>> it.reset() >>> it.reverse() >>> list(it) []
If you want to iterate over an iteration tree
treebackward, you have to reset the iterator after having reversed it:>>> it = iter(tree) >>> it.reverse() >>> it.reset()
- class phileas.iteration.node.FunctionalTranform(child: IterationTree, f: Callable[[phileas.iteration.base.DataTree], phileas.iteration.base.DataTree])[source]#
Bases:
TransformTransform node using its function attribute to modify its child.
- class phileas.iteration.node.Accumulator(child: IterationTree, recursive: bool = False, start_value: dict[phileas.iteration.base.Key, phileas.iteration.base.DataTree] | None = None)[source]#
Bases:
TransformTransform node that accumulates its inputs, as a kind of unlazifying transform:
if its successive inputs are dictionaries, merge them using the union operator (recursively or not), and return the results;
otherwise, leave its inputs untouched.
- recursive: bool = False#
Specify if the accumulation must be done recursively or not. For example, accumulating values
{"a": 1, "b": {"ba": 1}}and{"a": 2, "b": {"bb": 2}}recursively will return{"a": 2, "b": {ba": 1, "bb": 2}}, whereas doing it non-recursively will return{"a": 2, "b": {"bb": 2}}.
- start_value: dict[Key, DataTree] | None = None#
Start value of the accumulator, which must either be a dictionary, or
None.
- class phileas.iteration.node.AccumulatorIterator(tree: Accumulator)[source]#
Bases:
TransformIterator[Accumulator]
- class phileas.iteration.node.Lazify(child: IterationTree)[source]#
Bases:
TransformTransform node that only returns the elements that were updated in its children values (for dictionary values), or leaves its inputs untouched (for other types).
- class phileas.iteration.node.LazifyIterator(tree: Lazify)[source]#
Bases:
TransformIterator[Lazify]
- class phileas.iteration.node.MoveUpTransform(child: 'IterationTree', insert_name: 'bool | str' = False)[source]#
Bases:
Transform
- class phileas.iteration.node.Configurations(children: dict[Key, IterationTree], order: list[Key] | None = None, lazy: bool = False, default_configuration: Key | None = None, move_up: bool = False, insert_name: bool = False)[source]#
Bases:
IterationMethodRepresents a set of named configurations that can be invoked using
IterationTree.get_configuration(). When it is called, with argumentname, all theConfigurationsnodes that have a matching configuration will be replaced by it. Other ones will be replaced by their default value.This allows to escape from the recursive and local nature of trees: a single call can modify nodes throughout a whole tree. However, it is often convenient to convert configurable trees back to classical, non-configurable trees. This is done by
IterationTree.unroll_configurations().The
Configurationsnode holds a set of iteration trees, called configurations, which are identified by their name. Two parameters,move_upandinsert_name, control howIterationTree.get_configuration()behaves.By default,
move_up == insert_name == False. TheConfigurationnode is simply replaced by the content of the requested configuration.>>> tree = CartesianProduct({ ... "instrument": Configurations({ ... "config1": CartesianProduct({ ... "param1": IterationLiteral(value="1-1"), ... "param2": IterationLiteral(value="1-2"), ... }), ... "config2": CartesianProduct({ ... "param1": IterationLiteral(value="2-1"), ... "param3": IterationLiteral(value="2-3"), ... }) ... }) ... }) >>> tree.get_configuration("config1").to_pseudo_data_tree() {'instrument': {'param1': '1-1', 'param2': '1-2'}}
If
move_up == True, the content of the chosen configuration is moved one level up, so that it is at the same level as theConfigurationsnode. This requires it to have anIterationMethodparent withdictchildren. This can be used to factorize configurations.>>> tree = CartesianProduct({ ... "_": Configurations({ ... "config1": CartesianProduct({ ... "param1": IterationLiteral(value="1-1"), ... }), ... "config2": CartesianProduct({ ... "param1": IterationLiteral(value="2-1"), ... }) ... }, move_up=True), ... "param2": IterationLiteral(value="2") ... }) >>> tree.get_configuration("config1").to_pseudo_data_tree() {'param1': '1-1', 'param2': '2'}
If
insert_name == True, the name of the requested configuration is inserted in the resulting tree. If the requested configuration allows it (_ie_. it is anIterationMethodwithdictchildren), the name is inserted into itself, with the key_configuration.>>> tree = CartesianProduct({ ... "instrument": Configurations({ ... "config1": CartesianProduct({ ... "param1": IterationLiteral(value="1-1"), ... "param2": IterationLiteral(value="1-2"), ... }), ... "config2": CartesianProduct({ ... "param1": IterationLiteral(value="2-1"), ... "param3": IterationLiteral(value="2-3"), ... }) ... }, insert_name=True) ... }) >>> tree.get_configuration("config1").to_pseudo_data_tree() {'instrument': {'_configuration': 'config1', 'param1': '1-1', 'param2': '1-2'}}
If, additionally,
move_up == True, the name of the configuration is inserted instead of theConfigurationsnode.>>> tree = CartesianProduct({ ... "instrument": Configurations({ ... "config1": CartesianProduct({ ... "param1": IterationLiteral(value="1-1"), ... "param2": IterationLiteral(value="1-2"), ... }), ... "config2": CartesianProduct({ ... "param1": IterationLiteral(value="2-1"), ... "param3": IterationLiteral(value="2-3"), ... }) ... }, insert_name=True, move_up=True) ... }) >>> tree.get_configuration("config1").to_pseudo_data_tree() {'instrument': 'config1', 'param1': '1-1', 'param2': '2'}
However, if the requested configuration is not an
IterationMethod, itsnameis inserted as a sibling, assigned to the keyf"_{name}_configuration".>>> tree = CartesianProduct({ ... "param": Configurations({ ... "config1": IterationLiteral(value="1"), ... "config2": IterationLiteral(value="2"), ... }, insert_name=True), ... }) >>> tree.get_configuration("config1").to_pseudo_data_tree() {'_param_configuration': 'config1', 'param': '1'}
This means that the content of the configurations affects how they are handled. Although this might change in the future, it is made to support most situations. It is recommended to keep all the configurations with the same shape.
insert_nameis not necessary. It is possible to replace it with the following kind of tree:>>> tree = CartesianProduct({ ... "param": Configurations({ ... "config1": IterationLiteral(value="1"), ... "config2": IterationLiteral(value="2"), ... }, ... "config": Configurations({ ... "config1": IterationLiteral(value="config1"), ... "config2": IterationLiteral(value="config2"), ... })), ... }) >>> tree.get_configuration("config1").to_pseudo_data_tree() {'param': '1', 'config': 'config1'}
- children: dict[Key, IterationTree]#
The children of the node. It must not be empty.
- default_configuration: Key | None = None#
Key of the default configuration, which must be in the set of keys of children.
- move_up: bool = False#
If set, the content of a requested configuration is moved up, at the parent level of the current node. In other words, it becomes a sibling of the current
Configurationsnode.Otherwise, the content is inserted at the same level as the configurations themselves.
- insert_name: bool = False#
If set, insert the name of the requested configuration when calling
get_configuration(). Ifmove_up, then theConfigurationsnode is replaced by this name. Otherwise, a"_configuration"sibling node is inserted.
- class phileas.iteration.random.Seed(path: list[bool | str | int | float | _Child], salt: None | bool | str | int | float | _NoDefault | dict[bool | str | int | float, DataTree] | list[DataTree])[source]#
Bases:
objectSeed of a random iteration node, used by its RNG.
- class phileas.iteration.random.RandomTree(seed: Seed | None = None)[source]#
Bases:
objectAdditional base class of random iteration trees.
- phileas.iteration.random.generate_seeds(tree: IterationTree, salt: None | bool | str | int | float | _NoDefault | dict[bool | str | int | float, DataTree] | list[DataTree] = None) IterationTree[source]#
Populate the seeds of the random nodes and leaves of a tree, using the given salt value.
This module defines utility functions related to iteration.
- class phileas.iteration.utility.RestrictionPolicy(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]#
Bases:
Enum- FIRST_LAST = 'FIRST_LAST'#
Iteration leaves only keep their first and last values.
- FIRST_SECOND = 'FIRST_SECOND'#
Iteration leaves only keep their two first values.
- COMBINED = 'COMBINED'#
Iteration leaves keep their first, second and last values.
- phileas.iteration.utility.restrict_leaves_sizes(tree: IterationTree, policy: RestrictionPolicy = RestrictionPolicy.FIRST_LAST) IterationTree[source]#
Restrict the size of the iteration leaves of the tree, depending on the restriction policy. This is useful for troubleshooting, or to verify that the full range of the leaves is supported by something.
- phileas.iteration.utility.recursive_union(tree1: None | bool | str | int | float | _NoDefault | dict[bool | str | int | float, DataTree] | list[DataTree], tree2: None | bool | str | int | float | _NoDefault | dict[bool | str | int | float, DataTree] | list[DataTree]) None | bool | str | int | float | _NoDefault | dict[bool | str | int | float, DataTree] | list[DataTree][source]#
Return the recursive union of two datatrees. If any of those is not a dictionary, it returns the latter. Otherwise, it recursively applies the union operator of dictionaries.
- phileas.iteration.utility.is_transformed_iteration_leaf(tree: IterationTree) bool[source]#
A transformed iteration leaf is an iteration tree that only contained
TransformandIterationLeafnodes. That is, it does not containIterationMethodnodes.This function checks if a tree is a transformed iteration leaf.
- phileas.iteration.utility.flatten_datatree(tree: None | bool | str | int | float | _NoDefault | dict[bool | str | int | float, DataTree] | list[DataTree] | IterationLeaf | dict[bool | str | int | float, PseudoDataTree] | list[PseudoDataTree], key_prefix: None | str = None, separator: str = '.') dict[str, None | bool | str | int | float | _NoDefault | IterationLeaf] | None | bool | str | int | float | _NoDefault | IterationLeaf[source]#
Transform nested
dictandlistobjects to a single-leveldict.DataTreereturns aDataTree, and flattening aPseudoDataTreereturns aPseudoDataTree.Keys are converted to
str, and concatenated using the specifiedseparator.listobjects are considered asint-keyeddict.>>> tree = { ... "key1": { ... "key1-1": 1 ... }, ... "key2": [1, 2], ... "key3": "value" ... } >>> flatten_datatree(tree) {'key1.key1-1': 1, 'key2.0': 1, 'key2.1': 2, 'key3': 'value'}
- phileas.iteration.utility.iteration_tree_to_xarray_parameters(tree: IterationTree) tuple[dict[str, list | None | bool | str | int | float | _NoDefault], list[str], list[int]][source]#
Generate the arguments required to build an
xarray.DataArrayorxarray.DataFrame. You can then modify them, if needed, and build the xarray container used to store the results of your experiment.This function can only be applied to trees that only have finite leaves.
>>> import numpy as np >>> import xarray as xr >>> coords, dims_name, dims_shape = iteration_tree_to_xarray_parameters(tree) >>> # Single data to be recorded for each iteration point >>> xr.DataArray(data=np.empty(dims_shape), coords=coords, dims=dims_name) >>> # Multiple data for each iteration point >>> xr.Dataset( >>> data_vars=dict( >>> measurement_1=(dims_name, np.full(dims_shape, np.nan)), >>> measurement_2=(dims_name, np.full(dims_shape, np.nan)), >>> ), >>> coords=coords, >>> )
Caution
This function is tested against trees whose iteration methods are cartesian products only, as they represent the coordinates of full gridded datasets. You can use it on other kinds of trees, but then make sure to verify that its behaves properly.
See also
data_tree_to_xarray_index()to index an xarray container built with this function.iteration_tree_to_multiindex()if you want to work with Pandas tabular datasets.
- phileas.iteration.utility.data_tree_to_xarray_index(tree: None | bool | str | int | float | _NoDefault | dict[bool | str | int | float, DataTree] | list[DataTree], dims_name: list[str]) dict[str, None | bool | str | int | float | _NoDefault][source]#
Convert a data tree generated by iterating over an iteration tree to an index suitable for indexing an xarray container initialized using
iteration_tree_to_xarray_parameters().
- phileas.iteration.utility.iteration_tree_to_multiindex(tree: IterationTree) pandas.MultiIndex[source]#
Create a
pandas.MultiIndexthat holds the values stored in an iteration tree. This method iterates over the whole tree in order to create the index. Thus, it requires the iterated trees to have all the same shape.Caution
This function expects the shape of the configurations generated by the tree not to change during iteration.
See also
iteration_tree_to_xarray_parameters()if you want to work with xarray gridded datasets.