hpcflow 0.1.15__py3-none-any.whl → 0.2.0a271__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. hpcflow/__init__.py +2 -11
  2. hpcflow/__pyinstaller/__init__.py +5 -0
  3. hpcflow/__pyinstaller/hook-hpcflow.py +40 -0
  4. hpcflow/_version.py +1 -1
  5. hpcflow/app.py +43 -0
  6. hpcflow/cli.py +2 -461
  7. hpcflow/data/demo_data_manifest/__init__.py +3 -0
  8. hpcflow/data/demo_data_manifest/demo_data_manifest.json +6 -0
  9. hpcflow/data/jinja_templates/test/test_template.txt +8 -0
  10. hpcflow/data/programs/hello_world/README.md +1 -0
  11. hpcflow/data/programs/hello_world/hello_world.c +87 -0
  12. hpcflow/data/programs/hello_world/linux/hello_world +0 -0
  13. hpcflow/data/programs/hello_world/macos/hello_world +0 -0
  14. hpcflow/data/programs/hello_world/win/hello_world.exe +0 -0
  15. hpcflow/data/scripts/__init__.py +1 -0
  16. hpcflow/data/scripts/bad_script.py +2 -0
  17. hpcflow/data/scripts/demo_task_1_generate_t1_infile_1.py +8 -0
  18. hpcflow/data/scripts/demo_task_1_generate_t1_infile_2.py +8 -0
  19. hpcflow/data/scripts/demo_task_1_parse_p3.py +7 -0
  20. hpcflow/data/scripts/do_nothing.py +2 -0
  21. hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
  22. hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
  23. hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
  24. hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
  25. hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
  26. hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
  27. hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
  28. hpcflow/data/scripts/generate_t1_file_01.py +7 -0
  29. hpcflow/data/scripts/import_future_script.py +7 -0
  30. hpcflow/data/scripts/input_file_generator_basic.py +3 -0
  31. hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
  32. hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
  33. hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
  34. hpcflow/data/scripts/main_script_test_direct_in_direct_out.py +6 -0
  35. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
  36. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
  37. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
  38. hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
  39. hpcflow/data/scripts/main_script_test_direct_in_direct_out_all_iters_test.py +15 -0
  40. hpcflow/data/scripts/main_script_test_direct_in_direct_out_env_spec.py +7 -0
  41. hpcflow/data/scripts/main_script_test_direct_in_direct_out_labels.py +8 -0
  42. hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
  43. hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
  44. hpcflow/data/scripts/main_script_test_direct_sub_param_in_direct_out.py +6 -0
  45. hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +12 -0
  46. hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
  47. hpcflow/data/scripts/main_script_test_hdf5_in_obj_group.py +12 -0
  48. hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +11 -0
  49. hpcflow/data/scripts/main_script_test_json_and_direct_in_json_out.py +14 -0
  50. hpcflow/data/scripts/main_script_test_json_in_json_and_direct_out.py +17 -0
  51. hpcflow/data/scripts/main_script_test_json_in_json_out.py +14 -0
  52. hpcflow/data/scripts/main_script_test_json_in_json_out_labels.py +16 -0
  53. hpcflow/data/scripts/main_script_test_json_in_obj.py +12 -0
  54. hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
  55. hpcflow/data/scripts/main_script_test_json_out_obj.py +10 -0
  56. hpcflow/data/scripts/main_script_test_json_sub_param_in_json_out_labels.py +16 -0
  57. hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
  58. hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
  59. hpcflow/data/scripts/output_file_parser_basic.py +3 -0
  60. hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
  61. hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
  62. hpcflow/data/scripts/parse_t1_file_01.py +4 -0
  63. hpcflow/data/scripts/script_exit_test.py +5 -0
  64. hpcflow/data/template_components/__init__.py +1 -0
  65. hpcflow/data/template_components/command_files.yaml +26 -0
  66. hpcflow/data/template_components/environments.yaml +13 -0
  67. hpcflow/data/template_components/parameters.yaml +14 -0
  68. hpcflow/data/template_components/task_schemas.yaml +139 -0
  69. hpcflow/data/workflows/workflow_1.yaml +5 -0
  70. hpcflow/examples.ipynb +1037 -0
  71. hpcflow/sdk/__init__.py +149 -0
  72. hpcflow/sdk/app.py +4266 -0
  73. hpcflow/sdk/cli.py +1479 -0
  74. hpcflow/sdk/cli_common.py +385 -0
  75. hpcflow/sdk/config/__init__.py +5 -0
  76. hpcflow/sdk/config/callbacks.py +246 -0
  77. hpcflow/sdk/config/cli.py +388 -0
  78. hpcflow/sdk/config/config.py +1410 -0
  79. hpcflow/sdk/config/config_file.py +501 -0
  80. hpcflow/sdk/config/errors.py +272 -0
  81. hpcflow/sdk/config/types.py +150 -0
  82. hpcflow/sdk/core/__init__.py +38 -0
  83. hpcflow/sdk/core/actions.py +3857 -0
  84. hpcflow/sdk/core/app_aware.py +25 -0
  85. hpcflow/sdk/core/cache.py +224 -0
  86. hpcflow/sdk/core/command_files.py +814 -0
  87. hpcflow/sdk/core/commands.py +424 -0
  88. hpcflow/sdk/core/element.py +2071 -0
  89. hpcflow/sdk/core/enums.py +221 -0
  90. hpcflow/sdk/core/environment.py +256 -0
  91. hpcflow/sdk/core/errors.py +1043 -0
  92. hpcflow/sdk/core/execute.py +207 -0
  93. hpcflow/sdk/core/json_like.py +809 -0
  94. hpcflow/sdk/core/loop.py +1320 -0
  95. hpcflow/sdk/core/loop_cache.py +282 -0
  96. hpcflow/sdk/core/object_list.py +933 -0
  97. hpcflow/sdk/core/parameters.py +3371 -0
  98. hpcflow/sdk/core/rule.py +196 -0
  99. hpcflow/sdk/core/run_dir_files.py +57 -0
  100. hpcflow/sdk/core/skip_reason.py +7 -0
  101. hpcflow/sdk/core/task.py +3792 -0
  102. hpcflow/sdk/core/task_schema.py +993 -0
  103. hpcflow/sdk/core/test_utils.py +538 -0
  104. hpcflow/sdk/core/types.py +447 -0
  105. hpcflow/sdk/core/utils.py +1207 -0
  106. hpcflow/sdk/core/validation.py +87 -0
  107. hpcflow/sdk/core/values.py +477 -0
  108. hpcflow/sdk/core/workflow.py +4820 -0
  109. hpcflow/sdk/core/zarr_io.py +206 -0
  110. hpcflow/sdk/data/__init__.py +13 -0
  111. hpcflow/sdk/data/config_file_schema.yaml +34 -0
  112. hpcflow/sdk/data/config_schema.yaml +260 -0
  113. hpcflow/sdk/data/environments_spec_schema.yaml +21 -0
  114. hpcflow/sdk/data/files_spec_schema.yaml +5 -0
  115. hpcflow/sdk/data/parameters_spec_schema.yaml +7 -0
  116. hpcflow/sdk/data/task_schema_spec_schema.yaml +3 -0
  117. hpcflow/sdk/data/workflow_spec_schema.yaml +22 -0
  118. hpcflow/sdk/demo/__init__.py +3 -0
  119. hpcflow/sdk/demo/cli.py +242 -0
  120. hpcflow/sdk/helper/__init__.py +3 -0
  121. hpcflow/sdk/helper/cli.py +137 -0
  122. hpcflow/sdk/helper/helper.py +300 -0
  123. hpcflow/sdk/helper/watcher.py +192 -0
  124. hpcflow/sdk/log.py +288 -0
  125. hpcflow/sdk/persistence/__init__.py +18 -0
  126. hpcflow/sdk/persistence/base.py +2817 -0
  127. hpcflow/sdk/persistence/defaults.py +6 -0
  128. hpcflow/sdk/persistence/discovery.py +39 -0
  129. hpcflow/sdk/persistence/json.py +954 -0
  130. hpcflow/sdk/persistence/pending.py +948 -0
  131. hpcflow/sdk/persistence/store_resource.py +203 -0
  132. hpcflow/sdk/persistence/types.py +309 -0
  133. hpcflow/sdk/persistence/utils.py +73 -0
  134. hpcflow/sdk/persistence/zarr.py +2388 -0
  135. hpcflow/sdk/runtime.py +320 -0
  136. hpcflow/sdk/submission/__init__.py +3 -0
  137. hpcflow/sdk/submission/enums.py +70 -0
  138. hpcflow/sdk/submission/jobscript.py +2379 -0
  139. hpcflow/sdk/submission/schedulers/__init__.py +281 -0
  140. hpcflow/sdk/submission/schedulers/direct.py +233 -0
  141. hpcflow/sdk/submission/schedulers/sge.py +376 -0
  142. hpcflow/sdk/submission/schedulers/slurm.py +598 -0
  143. hpcflow/sdk/submission/schedulers/utils.py +25 -0
  144. hpcflow/sdk/submission/shells/__init__.py +52 -0
  145. hpcflow/sdk/submission/shells/base.py +229 -0
  146. hpcflow/sdk/submission/shells/bash.py +504 -0
  147. hpcflow/sdk/submission/shells/os_version.py +115 -0
  148. hpcflow/sdk/submission/shells/powershell.py +352 -0
  149. hpcflow/sdk/submission/submission.py +1402 -0
  150. hpcflow/sdk/submission/types.py +140 -0
  151. hpcflow/sdk/typing.py +194 -0
  152. hpcflow/sdk/utils/arrays.py +69 -0
  153. hpcflow/sdk/utils/deferred_file.py +55 -0
  154. hpcflow/sdk/utils/hashing.py +16 -0
  155. hpcflow/sdk/utils/patches.py +31 -0
  156. hpcflow/sdk/utils/strings.py +69 -0
  157. hpcflow/tests/api/test_api.py +32 -0
  158. hpcflow/tests/conftest.py +123 -0
  159. hpcflow/tests/data/__init__.py +0 -0
  160. hpcflow/tests/data/benchmark_N_elements.yaml +6 -0
  161. hpcflow/tests/data/benchmark_script_runner.yaml +26 -0
  162. hpcflow/tests/data/multi_path_sequences.yaml +29 -0
  163. hpcflow/tests/data/workflow_1.json +10 -0
  164. hpcflow/tests/data/workflow_1.yaml +5 -0
  165. hpcflow/tests/data/workflow_1_slurm.yaml +8 -0
  166. hpcflow/tests/data/workflow_1_wsl.yaml +8 -0
  167. hpcflow/tests/data/workflow_test_run_abort.yaml +42 -0
  168. hpcflow/tests/jinja_templates/test_jinja_templates.py +161 -0
  169. hpcflow/tests/programs/test_programs.py +180 -0
  170. hpcflow/tests/schedulers/direct_linux/test_direct_linux_submission.py +12 -0
  171. hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
  172. hpcflow/tests/schedulers/slurm/test_slurm_submission.py +14 -0
  173. hpcflow/tests/scripts/test_input_file_generators.py +282 -0
  174. hpcflow/tests/scripts/test_main_scripts.py +1361 -0
  175. hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
  176. hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
  177. hpcflow/tests/shells/wsl/test_wsl_submission.py +14 -0
  178. hpcflow/tests/unit/test_action.py +1066 -0
  179. hpcflow/tests/unit/test_action_rule.py +24 -0
  180. hpcflow/tests/unit/test_app.py +132 -0
  181. hpcflow/tests/unit/test_cache.py +46 -0
  182. hpcflow/tests/unit/test_cli.py +172 -0
  183. hpcflow/tests/unit/test_command.py +377 -0
  184. hpcflow/tests/unit/test_config.py +195 -0
  185. hpcflow/tests/unit/test_config_file.py +162 -0
  186. hpcflow/tests/unit/test_element.py +666 -0
  187. hpcflow/tests/unit/test_element_iteration.py +88 -0
  188. hpcflow/tests/unit/test_element_set.py +158 -0
  189. hpcflow/tests/unit/test_group.py +115 -0
  190. hpcflow/tests/unit/test_input_source.py +1479 -0
  191. hpcflow/tests/unit/test_input_value.py +398 -0
  192. hpcflow/tests/unit/test_jobscript_unit.py +757 -0
  193. hpcflow/tests/unit/test_json_like.py +1247 -0
  194. hpcflow/tests/unit/test_loop.py +2674 -0
  195. hpcflow/tests/unit/test_meta_task.py +325 -0
  196. hpcflow/tests/unit/test_multi_path_sequences.py +259 -0
  197. hpcflow/tests/unit/test_object_list.py +116 -0
  198. hpcflow/tests/unit/test_parameter.py +243 -0
  199. hpcflow/tests/unit/test_persistence.py +664 -0
  200. hpcflow/tests/unit/test_resources.py +243 -0
  201. hpcflow/tests/unit/test_run.py +286 -0
  202. hpcflow/tests/unit/test_run_directories.py +29 -0
  203. hpcflow/tests/unit/test_runtime.py +9 -0
  204. hpcflow/tests/unit/test_schema_input.py +372 -0
  205. hpcflow/tests/unit/test_shell.py +129 -0
  206. hpcflow/tests/unit/test_slurm.py +39 -0
  207. hpcflow/tests/unit/test_submission.py +502 -0
  208. hpcflow/tests/unit/test_task.py +2560 -0
  209. hpcflow/tests/unit/test_task_schema.py +182 -0
  210. hpcflow/tests/unit/test_utils.py +616 -0
  211. hpcflow/tests/unit/test_value_sequence.py +549 -0
  212. hpcflow/tests/unit/test_values.py +91 -0
  213. hpcflow/tests/unit/test_workflow.py +827 -0
  214. hpcflow/tests/unit/test_workflow_template.py +186 -0
  215. hpcflow/tests/unit/utils/test_arrays.py +40 -0
  216. hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
  217. hpcflow/tests/unit/utils/test_hashing.py +65 -0
  218. hpcflow/tests/unit/utils/test_patches.py +5 -0
  219. hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
  220. hpcflow/tests/unit/utils/test_strings.py +97 -0
  221. hpcflow/tests/workflows/__init__.py +0 -0
  222. hpcflow/tests/workflows/test_directory_structure.py +31 -0
  223. hpcflow/tests/workflows/test_jobscript.py +355 -0
  224. hpcflow/tests/workflows/test_run_status.py +198 -0
  225. hpcflow/tests/workflows/test_skip_downstream.py +696 -0
  226. hpcflow/tests/workflows/test_submission.py +140 -0
  227. hpcflow/tests/workflows/test_workflows.py +564 -0
  228. hpcflow/tests/workflows/test_zip.py +18 -0
  229. hpcflow/viz_demo.ipynb +6794 -0
  230. hpcflow-0.2.0a271.dist-info/LICENSE +375 -0
  231. hpcflow-0.2.0a271.dist-info/METADATA +65 -0
  232. hpcflow-0.2.0a271.dist-info/RECORD +237 -0
  233. {hpcflow-0.1.15.dist-info → hpcflow-0.2.0a271.dist-info}/WHEEL +4 -5
  234. hpcflow-0.2.0a271.dist-info/entry_points.txt +6 -0
  235. hpcflow/api.py +0 -490
  236. hpcflow/archive/archive.py +0 -307
  237. hpcflow/archive/cloud/cloud.py +0 -45
  238. hpcflow/archive/cloud/errors.py +0 -9
  239. hpcflow/archive/cloud/providers/dropbox.py +0 -427
  240. hpcflow/archive/errors.py +0 -5
  241. hpcflow/base_db.py +0 -4
  242. hpcflow/config.py +0 -233
  243. hpcflow/copytree.py +0 -66
  244. hpcflow/data/examples/_config.yml +0 -14
  245. hpcflow/data/examples/damask/demo/1.run.yml +0 -4
  246. hpcflow/data/examples/damask/demo/2.process.yml +0 -29
  247. hpcflow/data/examples/damask/demo/geom.geom +0 -2052
  248. hpcflow/data/examples/damask/demo/load.load +0 -1
  249. hpcflow/data/examples/damask/demo/material.config +0 -185
  250. hpcflow/data/examples/damask/inputs/geom.geom +0 -2052
  251. hpcflow/data/examples/damask/inputs/load.load +0 -1
  252. hpcflow/data/examples/damask/inputs/material.config +0 -185
  253. hpcflow/data/examples/damask/profiles/_variable_lookup.yml +0 -21
  254. hpcflow/data/examples/damask/profiles/damask.yml +0 -4
  255. hpcflow/data/examples/damask/profiles/damask_process.yml +0 -8
  256. hpcflow/data/examples/damask/profiles/damask_run.yml +0 -5
  257. hpcflow/data/examples/damask/profiles/default.yml +0 -6
  258. hpcflow/data/examples/thinking.yml +0 -177
  259. hpcflow/errors.py +0 -2
  260. hpcflow/init_db.py +0 -37
  261. hpcflow/models.py +0 -2595
  262. hpcflow/nesting.py +0 -9
  263. hpcflow/profiles.py +0 -455
  264. hpcflow/project.py +0 -81
  265. hpcflow/scheduler.py +0 -322
  266. hpcflow/utils.py +0 -103
  267. hpcflow/validation.py +0 -166
  268. hpcflow/variables.py +0 -543
  269. hpcflow-0.1.15.dist-info/METADATA +0 -168
  270. hpcflow-0.1.15.dist-info/RECORD +0 -45
  271. hpcflow-0.1.15.dist-info/entry_points.txt +0 -8
  272. hpcflow-0.1.15.dist-info/top_level.txt +0 -1
  273. /hpcflow/{archive → data/jinja_templates}/__init__.py +0 -0
  274. /hpcflow/{archive/cloud → data/programs}/__init__.py +0 -0
  275. /hpcflow/{archive/cloud/providers → data/workflows}/__init__.py +0 -0
@@ -0,0 +1,282 @@
1
+ """
2
+ Cache of loop statuses.
3
+ """
4
+
5
+ from __future__ import annotations
6
+ from dataclasses import dataclass
7
+ from collections import defaultdict
8
+ from typing import TYPE_CHECKING
9
+ from typing_extensions import Generic, TypeVar
10
+
11
+ from hpcflow.sdk.core.utils import nth_key
12
+ from hpcflow.sdk.log import TimeIt
13
+ from hpcflow.sdk.core.cache import ObjectCache
14
+
15
+ if TYPE_CHECKING:
16
+ from collections.abc import Mapping, Sequence
17
+ from typing_extensions import Self
18
+ from ..typing import DataIndex
19
+ from .loop import Loop
20
+ from .task import WorkflowTask
21
+ from .types import DependentDescriptor, ElementDescriptor
22
+ from .workflow import Workflow
23
+
24
+ K = TypeVar("K")
25
+ V = TypeVar("V")
26
+
27
+
28
+ class _LoopIndexError(TypeError):
29
+ """
30
+ A type error special to loop indices.
31
+ """
32
+
33
+ def __init__(self, loop_index: LoopIndex) -> None:
34
+ super().__init__(
35
+ f"{loop_index.__class__.__name__} does not support item assignment"
36
+ )
37
+
38
+
39
+ class LoopIndex(dict[K, V], Generic[K, V]):
40
+ """
41
+ Hashable dict implementation, suitable for use as a key into
42
+ other dicts. Once used as a key, becomes immutable.
43
+
44
+ Example
45
+ -------
46
+
47
+ >>> h1 = LoopIndex({"apples": 1, "bananas":2})
48
+ >>> h2 = LoopIndex({"bananas": 3, "mangoes": 5})
49
+ >>> h1+h2
50
+ LoopIndex(apples=1, bananas=3, mangoes=5)
51
+ >>> d1 = {}
52
+ >>> d1[h1] = "salad"
53
+ >>> d1[h1]
54
+ 'salad'
55
+ >>> d1[h2]
56
+ Traceback (most recent call last):
57
+ ...
58
+ KeyError: LoopIndex(bananas=3, mangoes=5)
59
+
60
+ Notes
61
+ -----
62
+ * Based on answers from
63
+ http://stackoverflow.com/questions/1151658/python-hashable-dicts
64
+ * Assumes both keys and values are hashable. True in practice.
65
+ """
66
+
67
+ def __init__(self, map: Mapping[K, V] | None = None) -> None:
68
+ """
69
+ Make an instance from another dictionary.
70
+ This object will be mutable until it is used as a key.
71
+ """
72
+ super().__init__(map or {})
73
+ self.__hash: int | None = None
74
+
75
+ def __repr__(self):
76
+ return f"""{self.__class__.__name__}({
77
+ ', '.join(f'{k!r}={v!r}' for k, v in self.items())
78
+ })"""
79
+
80
+ def __hash__(self):
81
+ if self.__hash is None:
82
+ self.__hash = hash(frozenset(self.items()))
83
+ return self.__hash
84
+
85
+ def _validate_update(self) -> None:
86
+ if self.__hash is not None:
87
+ raise _LoopIndexError(self)
88
+
89
+ def __setitem__(self, key: K, value: V) -> None:
90
+ self._validate_update()
91
+ super().__setitem__(key, value)
92
+
93
+ def __delitem__(self, key: K) -> None:
94
+ self._validate_update()
95
+ super().__delitem__(key)
96
+
97
+ def clear(self) -> None:
98
+ self._validate_update()
99
+ super().clear()
100
+
101
+ def pop(self, *args, **kwargs) -> V:
102
+ self._validate_update()
103
+ return super().pop(*args, **kwargs)
104
+
105
+ def popitem(self) -> tuple[K, V]:
106
+ self._validate_update()
107
+ return super().popitem()
108
+
109
+ def setdefault(self, key: K, default: V) -> V:
110
+ self._validate_update()
111
+ return super().setdefault(key, default)
112
+
113
+ def update(self, *args, **kwargs) -> None:
114
+ self._validate_update()
115
+ super().update(*args, **kwargs)
116
+
117
+ def __add__(self, right: Mapping[K, V]) -> Self:
118
+ result = self.__class__(self)
119
+ result.update(right)
120
+ return result
121
+
122
+
123
+ @dataclass
124
+ class LoopCache:
125
+ """Class to store a cache for use in :py:meth:`.Workflow.add_empty_loop` and
126
+ :py:meth:`.WorkflowLoop.add_iterations`. Use :py:meth:`build` to get a new instance.
127
+
128
+ Parameters
129
+ ----------
130
+ element_dependents:
131
+ Keys are element IDs, values are dicts whose keys are element IDs that depend on
132
+ the key element ID (via `Element.get_dependent_elements_recursively`), and whose
133
+ values are dicts with keys: `group_names`, which is a tuple of the string group
134
+ names associated with the dependent element's element set.
135
+ elements:
136
+ Keys are element IDs, values are dicts with keys: `input_statuses`,
137
+ `input_sources`, and `task_insert_ID`.
138
+ zeroth_iters:
139
+ Keys are element IDs, values are data associated with the zeroth iteration of that
140
+ element, namely a tuple of iteration ID and `ElementIteration.data_idx`.
141
+ data_idx:
142
+ Keys are element IDs, values are data associated with all iterations of that
143
+ element, namely a dict whose keys are the iteration loop index as a tuple, and
144
+ whose values are data indices via `ElementIteration.get_data_idx()`.
145
+ iterations:
146
+ Keys are iteration IDs, values are tuples of element ID and iteration index within
147
+ that element.
148
+ task_iterations:
149
+ Keys are task insert IDs, values are list of all iteration IDs associated with
150
+ that task.
151
+ """
152
+
153
+ #: Keys are element IDs, values are dicts whose keys are element IDs that depend on
154
+ #: the key element ID (via `Element.get_dependent_elements_recursively`), and whose
155
+ #: values are dicts with keys: `group_names`, which is a tuple of the string group
156
+ #: names associated with the dependent element's element set.
157
+ element_dependents: dict[int, dict[int, DependentDescriptor]]
158
+ #: Keys are element IDs, values are dicts with keys: `input_statuses`,
159
+ #: `input_sources`, and `task_insert_ID`.
160
+ elements: dict[int, ElementDescriptor]
161
+ #: Keys are element IDs, values are data associated with the zeroth iteration of that
162
+ #: element, namely a tuple of iteration ID and `ElementIteration.data_idx`.
163
+ zeroth_iters: dict[int, tuple[int, DataIndex]]
164
+ #: Keys are element IDs, values are data associated with all iterations of that
165
+ #: element, namely a dict whose keys are the iteration loop index as a tuple, and
166
+ #: whose values are data indices via `ElementIteration.get_data_idx()`.
167
+ data_idx: dict[int, dict[LoopIndex[str, int], DataIndex]]
168
+ #: Keys are iteration IDs, values are tuples of element ID and iteration index within
169
+ #: that element.
170
+ iterations: dict[int, tuple[int, int]]
171
+ #: Keys are task insert IDs, values are list of all iteration IDs associated with
172
+ #: that task.
173
+ task_iterations: dict[int, list[int]]
174
+
175
+ @TimeIt.decorator
176
+ def get_iter_IDs(self, loop: Loop) -> list[int]:
177
+ """Retrieve a list of iteration IDs belonging to a given loop."""
178
+ return [
179
+ i_id for t_id in loop.task_insert_IDs for i_id in self.task_iterations[t_id]
180
+ ]
181
+
182
+ @TimeIt.decorator
183
+ def get_iter_loop_indices(self, iter_IDs: list[int]) -> Sequence[Mapping[str, int]]:
184
+ """
185
+ Retrieve the mapping from element to loop index for each given iteration.
186
+ """
187
+ iter_loop_idx: list[LoopIndex[str, int]] = []
188
+ for id_ in iter_IDs:
189
+ elem_id, idx = self.iterations[id_]
190
+ iter_loop_idx.append(nth_key(self.data_idx[elem_id], idx))
191
+ return iter_loop_idx
192
+
193
+ @TimeIt.decorator
194
+ def update_loop_indices(self, new_loop_name: str, iter_IDs: list[int]) -> None:
195
+ """
196
+ Set the loop indices for a named loop to the given list of iteration IDs.
197
+ """
198
+ elem_ids = {e_ids[0] for k, e_ids in self.iterations.items() if k in iter_IDs}
199
+ new_loop_entry = {new_loop_name: 0}
200
+ for id_ in elem_ids:
201
+ self.data_idx[id_] = {
202
+ k + new_loop_entry: v for k, v in self.data_idx[id_].items()
203
+ }
204
+
205
+ @TimeIt.decorator
206
+ def add_iteration(
207
+ self,
208
+ iter_ID: int,
209
+ task_insert_ID: int,
210
+ element_ID: int,
211
+ loop_idx: LoopIndex[str, int],
212
+ data_idx: DataIndex,
213
+ ):
214
+ """Update the cache to include a newly added iteration."""
215
+ self.task_iterations[task_insert_ID].append(iter_ID)
216
+ new_iter_idx = len(self.data_idx[element_ID])
217
+ self.data_idx[element_ID][loop_idx] = data_idx
218
+ self.iterations[iter_ID] = (element_ID, new_iter_idx)
219
+
220
+ @classmethod
221
+ @TimeIt.decorator
222
+ def build(cls, workflow: Workflow, loops: list[Loop] | None = None) -> Self:
223
+ """Build a cache of data for use in adding loops and iterations."""
224
+
225
+ deps_cache = ObjectCache.build(workflow, dependencies=True, elements=True)
226
+
227
+ loops = [*workflow.template.loops, *(loops or ())]
228
+ task_iIDs = {t_id for loop in loops for t_id in loop.task_insert_IDs}
229
+ tasks: list[WorkflowTask] = [
230
+ workflow.tasks.get(insert_ID=t_id) for t_id in sorted(task_iIDs)
231
+ ]
232
+ elem_deps: dict[int, dict[int, DependentDescriptor]] = {}
233
+
234
+ # keys: element IDs, values: dict with keys: tuple(loop_idx), values: data index
235
+ data_idx_cache: dict[int, dict[LoopIndex[str, int], DataIndex]] = {}
236
+
237
+ # keys: iteration IDs, values: tuple of (element ID, integer index into values
238
+ # dict in `data_idx_cache` [accessed via `.keys()[index]`])
239
+ iters: dict[int, tuple[int, int]] = {}
240
+
241
+ # keys: element IDs, values: dict with keys: "input_statues", "input_sources",
242
+ # "task_insert_ID":
243
+ elements: dict[int, ElementDescriptor] = {}
244
+
245
+ zeroth_iters: dict[int, tuple[int, DataIndex]] = {}
246
+ task_iterations = defaultdict(list)
247
+ for task in tasks:
248
+ for elem_id in task.element_IDs:
249
+ element = deps_cache.elements[elem_id]
250
+ inp_statuses = task.template.get_input_statuses(element.element_set)
251
+ elements[element.id_] = {
252
+ "input_statuses": inp_statuses,
253
+ "input_sources": element.input_sources,
254
+ "task_insert_ID": task.insert_ID,
255
+ }
256
+ elem_deps[element.id_] = {
257
+ de_id: {
258
+ "group_names": tuple(
259
+ grp.name
260
+ for grp in deps_cache.elements[de_id].element_set.groups
261
+ ),
262
+ }
263
+ for de_id in deps_cache.elem_elem_dependents_rec[element.id_]
264
+ }
265
+ elem_iters: dict[LoopIndex[str, int], DataIndex] = {}
266
+ for idx, iter_i in enumerate(element.iterations):
267
+ if idx == 0:
268
+ zeroth_iters[element.id_] = (iter_i.id_, iter_i.data_idx)
269
+ elem_iters[iter_i.loop_idx] = iter_i.get_data_idx()
270
+ task_iterations[task.insert_ID].append(iter_i.id_)
271
+ iters[iter_i.id_] = (element.id_, idx)
272
+ data_idx_cache[element.id_] = elem_iters
273
+
274
+ task_iterations.default_factory = None
275
+ return cls(
276
+ element_dependents=elem_deps,
277
+ elements=elements,
278
+ zeroth_iters=zeroth_iters,
279
+ data_idx=data_idx_cache,
280
+ iterations=iters,
281
+ task_iterations=task_iterations,
282
+ )