11"""Core data structures for DeepTrack2.
22
3- This module defines the foundational data structures used throughout
4- DeepTrack2 for constructing, managing, and evaluating computational graphs
5- with flexible data storage and dependency management.
3+ This module defines the foundational data structures used throughout DeepTrack2
4+ for constructing, managing, and evaluating computational graphs with flexible
5+ data storage and dependency management.
66
77Key Features
88------------
99- **Hierarchical Data Management**
1010
11- Provides validated, hierarchical data containers (`DeepTrackDataObject`
12- and `DeepTrackDataDict`) for storing data and managing complex, nested
13- data structures. Supports dependency tracking and flexible indexing.
11+ Provides validated, hierarchical data containers (`DeepTrackDataObject` and
12+ `DeepTrackDataDict`) for storing data and managing complex, nested data
13+ structures. Supports dependency tracking and flexible indexing.
1414
1515- **Computation Graphs with Lazy Evaluation**
1616
17- Implements the `DeepTrackNode` class, the core abstraction for nodes in
18- a computational graph. Supports lazy evaluation, caching, dependency
17+ Implements the `DeepTrackNode` class, the core abstraction for nodes in a
18+ computational graph. Supports lazy evaluation, caching, dependency
1919 tracking, and operator overloading for intuitive composition of complex
2020 computational pipelines.
2121
4040
4141- `DeepTrackNode`: Node in a computation graph with operator overloading.
4242
43- Represents a node in a computation graph, capable of storing and
44- computing values based on dependencies, with full support for lazy
45- evaluation, dependency tracking, and operator overloading.
43+ Represents a node in a computation graph, capable of storing and computing
44+ values based on dependencies, with full support for lazy evaluation,
45+ dependency tracking, and operator overloading.
4646
4747Functions:
4848
49- - `_equivalent(a, b)`
49+ - `_equivalent(a, b) -> bool `
5050
51- def _equivalent(a: Any, b: Any) -> bool
51+ Determines whether two objects should be considered equivalent, according
52+ to DeepTrack2's internal rules (identity, empty lists, etc).
5253
53- Determines whether two objects should be considered equivalent,
54- according to DeepTrack2's internal rules (identity, empty lists, etc).
54+ - `_create_node_with_operator(op, a, b) -> DeepTrackNode`
5555
56- - `_create_node_with_operator(op, a, b)`
57-
58- def _create_node_with_operator(
59- op: Callable,
60- a: Any,
61- b: Any,
62- ) -> DeepTrackNode
63-
64- Internal helper to create a new computation node by applying a
56+ Internal helper function to create a new computation node by applying a
6557 specified operator to two operands, establishing correct graph
6658 relationships and supporting operator overloading.
6759
@@ -120,9 +112,9 @@ def _create_node_with_operator(
120112from __future__ import annotations
121113
122114from collections .abc import ItemsView , KeysView , ValuesView
123- import operator # Operator overloading for computation nodes.
124- from weakref import WeakSet # Manages relationships between nodes without
125- # creating circular dependencies.
115+ import operator # Operator overloading for computation nodes
116+ from weakref import WeakSet # To manage relationships between nodes without
117+ # creating circular dependencies
126118from typing import Any , Callable , Iterator
127119
128120from deeptrack .utils import get_kwarg_names
@@ -183,23 +175,29 @@ class DeepTrackDataObject:
183175 >>> import deeptrack as dt
184176
185177 Create a `DeepTrackDataObject`:
178+
186179 >>> data_obj = dt.DeepTrackDataObject()
187180 >>> data_obj
181+ DeepTrackDataObject(data=None, valid=False)
188182
189183 Store a value in this container:
184+
190185 >>> data_obj.store(42)
191186 >>> data_obj
192187 DeepTrackDataObject(data=42, valid=True)
193188
194189 Access the currently stored value:
190+
195191 >>> data_obj.current_value()
196192 42
197193
198194 Check if the stored data is valid:
195+
199196 >>> data_obj.is_valid()
200197 True
201198
202199 Invalidate the stored data:
200+
203201 >>> data_obj.invalidate()
204202 >>> data_obj
205203 DeepTrackDataObject(data=42, valid=False)
@@ -208,6 +206,7 @@ class DeepTrackDataObject:
208206 False
209207
210208 Validate the data to restore its valid status:
209+
211210 >>> data_obj.validate()
212211 >>> data_obj
213212 DeepTrackDataObject(data=42, valid=True)
@@ -284,8 +283,7 @@ def __repr__(self: DeepTrackDataObject) -> str:
284283 """Return the string representation of the object.
285284
286285 Provides a concise representation of the data object, including the
287- stored data and its validity flag. It is useful for debugging and
288- logging purposes.
286+ stored data and its validity flag. Useful for debugging and logging.
289287
290288 Returns
291289 -------
@@ -307,15 +305,13 @@ class DeepTrackDataDict:
307305 `DeepTrackDataDict` can store multiple `DeepTrackDataObject` instances,
308306 each associated with a unique tuple of integers (its `_ID`).
309307
310- **Use of _IDs**
311-
312308 The default `_ID` is an empty tuple, `_ID = ()`.
313309
314- Once the first entry is created, all `_ID`s must match the set key length.
310+ Once the first entry is created, all `_ID`s must match the set key- length.
315311
316312 When retrieving the data associated to an `_ID`:
317- - If an `_ID` longer than the set key length is requested, it is trimmed.
318- - If an `_ID` shorter than the set key length is requested, a dictionary
313+ - If an `_ID` longer than the set key- length is requested, it is trimmed.
314+ - If an `_ID` shorter than the set key- length is requested, a dictionary
319315 slice containing all matching entries is returned.
320316
321317 NOTE: The `_ID`s are specifically used in the `Repeat` feature to allow it
@@ -325,8 +321,8 @@ class DeepTrackDataDict:
325321 ----------
326322 keylength: int or None
327323 Read-only property exposing the internal variable with the length of
328- the `_ID`s set when the first entry is created. If `None`, no entries
329- have been created, and any `_ID` length is valid.
324+ the `_ID`s set when the first entry is created. If `None`, no entry has
325+ been created, and any `_ID` length is valid.
330326 dict: dict[tuple[int, ...], DeepTrackDataObject] or {}
331327 Read-only property exposing the internal dictionary of stored data,
332328 `_dict`. This is a dictionary mapping tuples of integers (`_ID`s) to
@@ -341,7 +337,7 @@ class DeepTrackDataDict:
341337 `validate() -> None`
342338 Mark all stored data objects as valid.
343339 `valid_index(_ID) -> bool`
344- Check if the given _ID is valid for the current configuration.
340+ Check if the given ` _ID` is valid for the current configuration.
345341 `__getitem__(_ID) -> DeepTrackDataObject or dict[_ID, DeepTrackDataObject]`
346342 Retrieve data associated with the `_ID`. Can return a
347343 `DeepTrackDataObject`, or a dict of `DeepTrackDataObject`s if `_ID` is
@@ -366,11 +362,13 @@ class DeepTrackDataDict:
366362 >>> import deeptrack as dt
367363
368364 Create a structure to store multiple, indexed instances of data:
365+
369366 >>> data_dict = dt.DeepTrackDataDict()
370367 >>> data_dict
371368 DeepTrackDataDict(0 entries, keylength=None)
372369
373370 Create the entries:
371+
374372 >>> data_dict.create_index((0, 0))
375373 >>> data_dict.create_index((0, 1))
376374 >>> data_dict.create_index((1, 0))
@@ -379,6 +377,7 @@ class DeepTrackDataDict:
379377 DeepTrackDataDict(4 entries, keylength=2)
380378
381379 Store the values associated with each `_ID`:
380+
382381 >>> data_dict[(0, 0)].store("Data at (0, 0)")
383382 >>> data_dict[(0, 1)].store("Data at (0, 1)")
384383 >>> data_dict[(1, 0)].store("Data at (1, 0)")
@@ -387,24 +386,29 @@ class DeepTrackDataDict:
387386 DeepTrackDataDict(4 entries, keylength=2)
388387
389388 Retrieve values based on their `_ID`s:
389+
390390 >>> data_dict[(0, 0)]
391391 DeepTrackDataObject(data='Data at (0, 0)', valid=True)
392392
393393 >>> data_dict[(0, 0)].current_value()
394394 'Data at (0, 0)'
395395
396396 >>> data_dict[(1, 1)]
397+
397398 DeepTrackDataObject(data='Data at (1, 1)', valid=True)
398399
399400 >>> data_dict[(1, 1)].current_value()
401+
400402 'Data at (1, 1)'
401403
402404 If requesting a shorter `_ID`, it returns all matching nested entries:
405+
403406 >>> data_dict[(0,)]
404407 {(0, 0): DeepTrackDataObject(data='Data at (0, 0)', valid=True),
405408 (0, 1): DeepTrackDataObject(data='Data at (0, 1)', valid=True)}
406409
407410 Validate and invalidate all entries at once:
411+
408412 >>> data_dict.invalidate()
409413 >>> data_dict[(0, 0)].is_valid()
410414 False
@@ -420,6 +424,7 @@ class DeepTrackDataDict:
420424 True
421425
422426 Invalidate and validate a single entry:
427+
423428 >>> data_dict[(0, 1)].invalidate()
424429 >>> data_dict[(0, 1)].is_valid()
425430 False
@@ -429,19 +434,21 @@ class DeepTrackDataDict:
429434 True
430435
431436 Check if a given `_ID` exists:
437+
432438 >>> (1, 0) in data_dict
433439 True
434440
435441 >>> (2, 2) in data_dict
436442 False
437443
438444 Iterate over all entries:
445+
439446 >>> for key, value in data_dict.items():
440447 ... print(key, value.current_value())
441- (0, 0) DeepTrackDataObject(data=' Data at (0, 0)', valid=True )
442- (0, 1) DeepTrackDataObject(data=' Data at (0, 1)', valid=True )
443- (1, 0) DeepTrackDataObject(data=' Data at (1, 0)', valid=True )
444- (1, 1) DeepTrackDataObject(data=' Data at (1, 1)', valid=True )
448+ (0, 0) Data at (0, 0)
449+ (0, 1) Data at (0, 1)
450+ (1, 0) Data at (1, 0)
451+ (1, 1) Data at (1, 1)
445452
446453 >>> for key in data_dict.keys():
447454 ... print(key)
@@ -458,6 +465,7 @@ class DeepTrackDataDict:
458465 DeepTrackDataObject(data='Data at (1, 1)', valid=True)
459466
460467 Check if an `_ID` is valid according to current keylength:
468+
461469 >>> data_dict.valid_index((0, 1))
462470 True
463471
@@ -848,7 +856,7 @@ class DeepTrackNode:
848856 garbage collection of nodes that are no longer used.
849857 dependencies: WeakSet[DeepTrackNode]
850858 Read-only property exposing the internal weak set `_dependencies`
851- containign the nodes on which this node depends (its parents).
859+ containing the nodes on which this node depends (its parents).
852860 This is a weakref.WeakSet, for efficient memory management.
853861 _action: Callable[..., Any]
854862 The function or lambda-function to compute the node value.
@@ -940,6 +948,7 @@ class DeepTrackNode:
940948 >>> from deeptrack.backend.core import DeepTrackNode
941949
942950 Create three `DeepTrackNode` objects, as parent, child, and grandchild:
951+
943952 >>> parent = DeepTrackNode(
944953 ... node_name="parent",
945954 ... action=lambda: 10,
@@ -956,32 +965,37 @@ class DeepTrackNode:
956965 >>> child.add_child(grandchild)
957966
958967 Check all children of `parent` (includes `parent` itself):
968+
959969 >>> for node in parent.recurse_children():
960970 ... print(node)
961971 DeepTrackNode(name='parent', len=0, action=<lambda>)
962972 DeepTrackNode(name='child', len=0, action=<lambda>)
963973 DeepTrackNode(name='grandchild', len=0, action=<lambda>)
964974
965975 Print the children tree:
976+
966977 >>> parent.print_children_tree()
967978 - DeepTrackNode 'parent' at 0x334202650
968979 - DeepTrackNode 'child' at 0x334201cf0
969980 - DeepTrackNode 'grandchild' at 0x334201ea0
970981
971982 Check all dependencies of `grandchild` (includes `grandchild` itself):
983+
972984 >>> for node in grandchild.recurse_dependencies():
973985 ... print(node)
974986 DeepTrackNode(name='grandchild', len=0, action=<lambda>)
975987 DeepTrackNode(name='child', len=0, action=<lambda>)
976988 DeepTrackNode(name='parent', len=0, action=<lambda>)
977989
978990 Print the dependency tree:
991+
979992 >>> grandchild.print_dependencies_tree()
980993 - DeepTrackNode 'grandchild' at 0x334201ea0
981994 - DeepTrackNode 'child' at 0x334201cf0
982995 - DeepTrackNode 'parent' at 0x334202650
983996
984997 Store and retrieve data for specific _IDs:
998+
985999 >>> parent.store(15, _ID=(0,))
9861000 >>> parent.store(20, _ID=(1,))
9871001 >>> parent.current_value((0,))
@@ -990,6 +1004,7 @@ class DeepTrackNode:
9901004 20
9911005
9921006 Compute and retrieve the value for the child and grandchild node:
1007+
9931008 >>> child(_ID=(0,))
9941009 30
9951010 >>> child(_ID=(1,))
@@ -1000,6 +1015,7 @@ class DeepTrackNode:
10001015 120
10011016
10021017 Validation and invalidation:
1018+
10031019 >>> parent.is_valid((0,))
10041020 True
10051021 >>> child.is_valid((0,))
@@ -1024,6 +1040,7 @@ class DeepTrackNode:
10241040 False
10251041
10261042 Setting a value and automatic invalidation:
1043+
10271044 >>> parent.current_value((0,))
10281045 15
10291046 >>> grandchild((0,)) # Computes and stores the value in grandchild
@@ -1038,6 +1055,7 @@ class DeepTrackNode:
10381055 252
10391056
10401057 Resetting all data in the dependency tree (recomputation required):
1058+
10411059 >>> grandchild.update()
10421060 >>> grandchild()
10431061 60
@@ -1047,6 +1065,7 @@ class DeepTrackNode:
10471065 60
10481066
10491067 Operator overloading—arithmetic and comparison:
1068+
10501069 >>> node_a = DeepTrackNode(lambda: 5)
10511070 >>> node_b = DeepTrackNode(lambda: 3)
10521071
@@ -1079,26 +1098,31 @@ class DeepTrackNode:
10791098 True
10801099
10811100 Indexing into computed data:
1101+
10821102 >>> vector_node = DeepTrackNode(lambda: [10, 20, 30])
10831103 >>> first_element = vector_node[0]
10841104 >>> first_element()
10851105 10
10861106
10871107 Accessing a value before computing it raises an error:
1108+
10881109 >>> new_node = DeepTrackNode(lambda: 123)
10891110 >>> new_node.is_valid((42,))
10901111 False
1112+
10911113 >>> new_node.current_value((42,))
10921114 KeyError: 'Attempting to index an empty dict.'
10931115
10941116 Working with nested _ID slicing:
1117+
10951118 >>> parent = DeepTrackNode(lambda: 5)
10961119 >>> child = DeepTrackNode(lambda _ID=None: parent(_ID[:1]) + _ID[1])
10971120 >>> parent.add_child(child)
10981121 >>> child((0, 3)) # Equivalent to parent((0,)) + 3
10991122 8
11001123
11011124 Citations for a node and its dependencies:
1125+
11021126 >>> parent.get_citations() # Set of citation strings
11031127 {...}
11041128
0 commit comments