hpcflow-new2 0.2.0a189__py3-none-any.whl → 0.2.0a199__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 (176) hide show
  1. hpcflow/__pyinstaller/hook-hpcflow.py +9 -6
  2. hpcflow/_version.py +1 -1
  3. hpcflow/app.py +1 -0
  4. hpcflow/data/scripts/bad_script.py +2 -0
  5. hpcflow/data/scripts/do_nothing.py +2 -0
  6. hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
  7. hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
  8. hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
  9. hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
  10. hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
  11. hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
  12. hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
  13. hpcflow/data/scripts/input_file_generator_basic.py +3 -0
  14. hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
  15. hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
  16. hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
  17. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
  18. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
  19. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
  20. hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
  21. hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
  22. hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
  23. hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +1 -1
  24. hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
  25. hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +1 -1
  26. hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
  27. hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
  28. hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
  29. hpcflow/data/scripts/output_file_parser_basic.py +3 -0
  30. hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
  31. hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
  32. hpcflow/data/scripts/script_exit_test.py +5 -0
  33. hpcflow/data/template_components/environments.yaml +1 -1
  34. hpcflow/sdk/__init__.py +26 -15
  35. hpcflow/sdk/app.py +2192 -768
  36. hpcflow/sdk/cli.py +506 -296
  37. hpcflow/sdk/cli_common.py +105 -7
  38. hpcflow/sdk/config/__init__.py +1 -1
  39. hpcflow/sdk/config/callbacks.py +115 -43
  40. hpcflow/sdk/config/cli.py +126 -103
  41. hpcflow/sdk/config/config.py +674 -318
  42. hpcflow/sdk/config/config_file.py +131 -95
  43. hpcflow/sdk/config/errors.py +125 -84
  44. hpcflow/sdk/config/types.py +148 -0
  45. hpcflow/sdk/core/__init__.py +25 -1
  46. hpcflow/sdk/core/actions.py +1771 -1059
  47. hpcflow/sdk/core/app_aware.py +24 -0
  48. hpcflow/sdk/core/cache.py +139 -79
  49. hpcflow/sdk/core/command_files.py +263 -287
  50. hpcflow/sdk/core/commands.py +145 -112
  51. hpcflow/sdk/core/element.py +828 -535
  52. hpcflow/sdk/core/enums.py +192 -0
  53. hpcflow/sdk/core/environment.py +74 -93
  54. hpcflow/sdk/core/errors.py +455 -52
  55. hpcflow/sdk/core/execute.py +207 -0
  56. hpcflow/sdk/core/json_like.py +540 -272
  57. hpcflow/sdk/core/loop.py +751 -347
  58. hpcflow/sdk/core/loop_cache.py +164 -47
  59. hpcflow/sdk/core/object_list.py +370 -207
  60. hpcflow/sdk/core/parameters.py +1100 -627
  61. hpcflow/sdk/core/rule.py +59 -41
  62. hpcflow/sdk/core/run_dir_files.py +21 -37
  63. hpcflow/sdk/core/skip_reason.py +7 -0
  64. hpcflow/sdk/core/task.py +1649 -1339
  65. hpcflow/sdk/core/task_schema.py +308 -196
  66. hpcflow/sdk/core/test_utils.py +191 -114
  67. hpcflow/sdk/core/types.py +440 -0
  68. hpcflow/sdk/core/utils.py +485 -309
  69. hpcflow/sdk/core/validation.py +82 -9
  70. hpcflow/sdk/core/workflow.py +2544 -1178
  71. hpcflow/sdk/core/zarr_io.py +98 -137
  72. hpcflow/sdk/data/workflow_spec_schema.yaml +2 -0
  73. hpcflow/sdk/demo/cli.py +53 -33
  74. hpcflow/sdk/helper/cli.py +18 -15
  75. hpcflow/sdk/helper/helper.py +75 -63
  76. hpcflow/sdk/helper/watcher.py +61 -28
  77. hpcflow/sdk/log.py +122 -71
  78. hpcflow/sdk/persistence/__init__.py +8 -31
  79. hpcflow/sdk/persistence/base.py +1360 -606
  80. hpcflow/sdk/persistence/defaults.py +6 -0
  81. hpcflow/sdk/persistence/discovery.py +38 -0
  82. hpcflow/sdk/persistence/json.py +568 -188
  83. hpcflow/sdk/persistence/pending.py +382 -179
  84. hpcflow/sdk/persistence/store_resource.py +39 -23
  85. hpcflow/sdk/persistence/types.py +318 -0
  86. hpcflow/sdk/persistence/utils.py +14 -11
  87. hpcflow/sdk/persistence/zarr.py +1337 -433
  88. hpcflow/sdk/runtime.py +44 -41
  89. hpcflow/sdk/submission/{jobscript_info.py → enums.py} +39 -12
  90. hpcflow/sdk/submission/jobscript.py +1651 -692
  91. hpcflow/sdk/submission/schedulers/__init__.py +167 -39
  92. hpcflow/sdk/submission/schedulers/direct.py +121 -81
  93. hpcflow/sdk/submission/schedulers/sge.py +170 -129
  94. hpcflow/sdk/submission/schedulers/slurm.py +291 -268
  95. hpcflow/sdk/submission/schedulers/utils.py +12 -2
  96. hpcflow/sdk/submission/shells/__init__.py +14 -15
  97. hpcflow/sdk/submission/shells/base.py +150 -29
  98. hpcflow/sdk/submission/shells/bash.py +283 -173
  99. hpcflow/sdk/submission/shells/os_version.py +31 -30
  100. hpcflow/sdk/submission/shells/powershell.py +228 -170
  101. hpcflow/sdk/submission/submission.py +1014 -335
  102. hpcflow/sdk/submission/types.py +140 -0
  103. hpcflow/sdk/typing.py +182 -12
  104. hpcflow/sdk/utils/arrays.py +71 -0
  105. hpcflow/sdk/utils/deferred_file.py +55 -0
  106. hpcflow/sdk/utils/hashing.py +16 -0
  107. hpcflow/sdk/utils/patches.py +12 -0
  108. hpcflow/sdk/utils/strings.py +33 -0
  109. hpcflow/tests/api/test_api.py +32 -0
  110. hpcflow/tests/conftest.py +27 -6
  111. hpcflow/tests/data/multi_path_sequences.yaml +29 -0
  112. hpcflow/tests/data/workflow_test_run_abort.yaml +34 -35
  113. hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
  114. hpcflow/tests/schedulers/slurm/test_slurm_submission.py +5 -2
  115. hpcflow/tests/scripts/test_input_file_generators.py +282 -0
  116. hpcflow/tests/scripts/test_main_scripts.py +866 -85
  117. hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
  118. hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
  119. hpcflow/tests/shells/wsl/test_wsl_submission.py +12 -4
  120. hpcflow/tests/unit/test_action.py +262 -75
  121. hpcflow/tests/unit/test_action_rule.py +9 -4
  122. hpcflow/tests/unit/test_app.py +33 -6
  123. hpcflow/tests/unit/test_cache.py +46 -0
  124. hpcflow/tests/unit/test_cli.py +134 -1
  125. hpcflow/tests/unit/test_command.py +71 -54
  126. hpcflow/tests/unit/test_config.py +142 -16
  127. hpcflow/tests/unit/test_config_file.py +21 -18
  128. hpcflow/tests/unit/test_element.py +58 -62
  129. hpcflow/tests/unit/test_element_iteration.py +50 -1
  130. hpcflow/tests/unit/test_element_set.py +29 -19
  131. hpcflow/tests/unit/test_group.py +4 -2
  132. hpcflow/tests/unit/test_input_source.py +116 -93
  133. hpcflow/tests/unit/test_input_value.py +29 -24
  134. hpcflow/tests/unit/test_jobscript_unit.py +757 -0
  135. hpcflow/tests/unit/test_json_like.py +44 -35
  136. hpcflow/tests/unit/test_loop.py +1396 -84
  137. hpcflow/tests/unit/test_meta_task.py +325 -0
  138. hpcflow/tests/unit/test_multi_path_sequences.py +229 -0
  139. hpcflow/tests/unit/test_object_list.py +17 -12
  140. hpcflow/tests/unit/test_parameter.py +29 -7
  141. hpcflow/tests/unit/test_persistence.py +237 -42
  142. hpcflow/tests/unit/test_resources.py +20 -18
  143. hpcflow/tests/unit/test_run.py +117 -6
  144. hpcflow/tests/unit/test_run_directories.py +29 -0
  145. hpcflow/tests/unit/test_runtime.py +2 -1
  146. hpcflow/tests/unit/test_schema_input.py +23 -15
  147. hpcflow/tests/unit/test_shell.py +23 -2
  148. hpcflow/tests/unit/test_slurm.py +8 -7
  149. hpcflow/tests/unit/test_submission.py +38 -89
  150. hpcflow/tests/unit/test_task.py +352 -247
  151. hpcflow/tests/unit/test_task_schema.py +33 -20
  152. hpcflow/tests/unit/test_utils.py +9 -11
  153. hpcflow/tests/unit/test_value_sequence.py +15 -12
  154. hpcflow/tests/unit/test_workflow.py +114 -83
  155. hpcflow/tests/unit/test_workflow_template.py +0 -1
  156. hpcflow/tests/unit/utils/test_arrays.py +40 -0
  157. hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
  158. hpcflow/tests/unit/utils/test_hashing.py +65 -0
  159. hpcflow/tests/unit/utils/test_patches.py +5 -0
  160. hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
  161. hpcflow/tests/workflows/__init__.py +0 -0
  162. hpcflow/tests/workflows/test_directory_structure.py +31 -0
  163. hpcflow/tests/workflows/test_jobscript.py +334 -1
  164. hpcflow/tests/workflows/test_run_status.py +198 -0
  165. hpcflow/tests/workflows/test_skip_downstream.py +696 -0
  166. hpcflow/tests/workflows/test_submission.py +140 -0
  167. hpcflow/tests/workflows/test_workflows.py +160 -15
  168. hpcflow/tests/workflows/test_zip.py +18 -0
  169. hpcflow/viz_demo.ipynb +6587 -3
  170. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/METADATA +8 -4
  171. hpcflow_new2-0.2.0a199.dist-info/RECORD +221 -0
  172. hpcflow/sdk/core/parallel.py +0 -21
  173. hpcflow_new2-0.2.0a189.dist-info/RECORD +0 -158
  174. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/LICENSE +0 -0
  175. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/WHEEL +0 -0
  176. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,757 @@
1
+ import numpy as np
2
+ from hpcflow.app import app as hf
3
+ from hpcflow.sdk.core.test_utils import make_schemas, make_workflow
4
+ from hpcflow.sdk.submission.jobscript import is_jobscript_array, resolve_jobscript_blocks
5
+
6
+ import pytest
7
+
8
+
9
+ def test_resolve_jobscript_blocks():
10
+ # separate jobscripts due to `is_array`:
11
+ jobscripts = {
12
+ 0: {"is_array": True, "resource_hash": 0, "dependencies": {}},
13
+ 1: {"is_array": True, "resource_hash": 0, "dependencies": {0: "DEP_DATA"}},
14
+ }
15
+ assert resolve_jobscript_blocks(jobscripts) == [
16
+ {"resources": None, "is_array": True, "blocks": [{"dependencies": {}}]},
17
+ {
18
+ "resources": None,
19
+ "is_array": True,
20
+ "blocks": [{"dependencies": {(0, 0): "DEP_DATA"}}],
21
+ },
22
+ ]
23
+
24
+ # separate jobscripts due to different `resource_hash`:
25
+ jobscripts = {
26
+ 0: {"is_array": False, "resource_hash": 0, "dependencies": {}},
27
+ 1: {"is_array": False, "resource_hash": 1, "dependencies": {0: "DEP_DATA"}},
28
+ }
29
+ assert resolve_jobscript_blocks(jobscripts) == [
30
+ {"resources": None, "is_array": False, "blocks": [{"dependencies": {}}]},
31
+ {
32
+ "resources": None,
33
+ "is_array": False,
34
+ "blocks": [{"dependencies": {(0, 0): "DEP_DATA"}}],
35
+ },
36
+ ]
37
+
38
+ # separate jobscripts due to `is_array`:
39
+ jobscripts = {
40
+ 0: {"is_array": False, "resource_hash": 0, "dependencies": {}},
41
+ 1: {"is_array": True, "resource_hash": 0, "dependencies": {0: "DEP_DATA"}},
42
+ }
43
+ assert resolve_jobscript_blocks(jobscripts) == [
44
+ {"resources": None, "is_array": False, "blocks": [{"dependencies": {}}]},
45
+ {
46
+ "resources": None,
47
+ "is_array": True,
48
+ "blocks": [{"dependencies": {(0, 0): "DEP_DATA"}}],
49
+ },
50
+ ]
51
+
52
+ # separate jobscripts due to `is_array`:
53
+ jobscripts = {
54
+ 0: {"is_array": True, "resource_hash": 0, "dependencies": {}},
55
+ 1: {"is_array": False, "resource_hash": 0, "dependencies": {0: "DEP_DATA"}},
56
+ }
57
+ assert resolve_jobscript_blocks(jobscripts) == [
58
+ {"resources": None, "is_array": True, "blocks": [{"dependencies": {}}]},
59
+ {
60
+ "resources": None,
61
+ "is_array": False,
62
+ "blocks": [{"dependencies": {(0, 0): "DEP_DATA"}}],
63
+ },
64
+ ]
65
+
66
+ # combined jobscript due to same resource_hash, not is_array, and dependencies:
67
+ jobscripts = {
68
+ 0: {"is_array": False, "resource_hash": 0, "dependencies": {}},
69
+ 1: {"is_array": False, "resource_hash": 0, "dependencies": {0: "DEP_DATA"}},
70
+ 2: {"is_array": False, "resource_hash": 0, "dependencies": {1: "DEP_DATA"}},
71
+ }
72
+ assert resolve_jobscript_blocks(jobscripts) == [
73
+ {
74
+ "resources": None,
75
+ "is_array": False,
76
+ "blocks": [
77
+ {"dependencies": {}},
78
+ {"dependencies": {(0, 0): "DEP_DATA"}},
79
+ {"dependencies": {(0, 1): "DEP_DATA"}},
80
+ ],
81
+ }
82
+ ]
83
+
84
+ # combined jobscript due to same resource_hash, not is_array, and dependencies:
85
+ # (checking non-consecutive jobscript index `3` is inconsequential)
86
+ jobscripts = {
87
+ 0: {"is_array": False, "resource_hash": 0, "dependencies": {}},
88
+ 1: {"is_array": False, "resource_hash": 0, "dependencies": {0: "DEP_DATA"}},
89
+ 3: {"is_array": False, "resource_hash": 0, "dependencies": {1: "DEP_DATA"}},
90
+ }
91
+ assert resolve_jobscript_blocks(jobscripts) == [
92
+ {
93
+ "resources": None,
94
+ "is_array": False,
95
+ "blocks": [
96
+ {"dependencies": {}},
97
+ {"dependencies": {(0, 0): "DEP_DATA"}},
98
+ {"dependencies": {(0, 1): "DEP_DATA"}},
99
+ ],
100
+ }
101
+ ]
102
+
103
+ # jobscript 0 and 1 combined, not 2 due to independence:
104
+ jobscripts = {
105
+ 0: {"is_array": False, "resource_hash": 0, "dependencies": {}},
106
+ 1: {"is_array": False, "resource_hash": 0, "dependencies": {0: "DEP_DATA"}},
107
+ 2: {"is_array": False, "resource_hash": 0, "dependencies": {}},
108
+ }
109
+ assert resolve_jobscript_blocks(jobscripts) == [
110
+ {
111
+ "resources": None,
112
+ "is_array": False,
113
+ "blocks": [{"dependencies": {}}, {"dependencies": {(0, 0): "DEP_DATA"}}],
114
+ },
115
+ {"resources": None, "is_array": False, "blocks": [{"dependencies": {}}]},
116
+ ]
117
+
118
+ # separate jobscripts 0,1 due to independence, separate jobscript 2 due to dependence
119
+ # that spans multiple upstream jobscripts that are independent:
120
+ jobscripts = {
121
+ 0: {"is_array": False, "resource_hash": 0, "dependencies": {}},
122
+ 1: {"is_array": False, "resource_hash": 0, "dependencies": {}},
123
+ 2: {
124
+ "is_array": False,
125
+ "resource_hash": 0,
126
+ "dependencies": {0: "DEP_DATA", 1: "DEP_DATA"},
127
+ },
128
+ }
129
+ assert resolve_jobscript_blocks(jobscripts) == [
130
+ {"resources": None, "is_array": False, "blocks": [{"dependencies": {}}]},
131
+ {"resources": None, "is_array": False, "blocks": [{"dependencies": {}}]},
132
+ {
133
+ "resources": None,
134
+ "is_array": False,
135
+ "blocks": [{"dependencies": {(0, 0): "DEP_DATA", (1, 0): "DEP_DATA"}}],
136
+ },
137
+ ]
138
+
139
+ # combine jobscripts due to dependence
140
+ jobscripts = {
141
+ 0: {"is_array": False, "resource_hash": 0, "dependencies": {}},
142
+ 1: {"is_array": False, "resource_hash": 0, "dependencies": {0: "DEP_DATA"}},
143
+ 2: {
144
+ "is_array": False,
145
+ "resource_hash": 0,
146
+ "dependencies": {0: "DEP_DATA", 1: "DEP_DATA"},
147
+ },
148
+ }
149
+ assert resolve_jobscript_blocks(jobscripts) == [
150
+ {
151
+ "resources": None,
152
+ "is_array": False,
153
+ "blocks": [
154
+ {"dependencies": {}},
155
+ {"dependencies": {(0, 0): "DEP_DATA"}},
156
+ {"dependencies": {(0, 0): "DEP_DATA", (0, 1): "DEP_DATA"}},
157
+ ],
158
+ }
159
+ ]
160
+
161
+ # separate jobscripts 0,1 due to independence, combined jobscripts 3,4 due to shared
162
+ # dependencies:
163
+ jobscripts = {
164
+ 0: {"is_array": False, "resource_hash": 0, "dependencies": {}},
165
+ 1: {"is_array": False, "resource_hash": 0, "dependencies": {}},
166
+ 2: {
167
+ "is_array": False,
168
+ "resource_hash": 0,
169
+ "dependencies": {0: "DEP_DATA", 1: "DEP_DATA"},
170
+ },
171
+ 3: {
172
+ "is_array": False,
173
+ "resource_hash": 0,
174
+ "dependencies": {0: "DEP_DATA", 1: "DEP_DATA", 2: "DEP_DATA"},
175
+ },
176
+ }
177
+ assert resolve_jobscript_blocks(jobscripts) == [
178
+ {"resources": None, "is_array": False, "blocks": [{"dependencies": {}}]},
179
+ {"resources": None, "is_array": False, "blocks": [{"dependencies": {}}]},
180
+ {
181
+ "resources": None,
182
+ "is_array": False,
183
+ "blocks": [
184
+ {"dependencies": {(0, 0): "DEP_DATA", (1, 0): "DEP_DATA"}},
185
+ {
186
+ "dependencies": {
187
+ (0, 0): "DEP_DATA",
188
+ (1, 0): "DEP_DATA",
189
+ (2, 0): "DEP_DATA",
190
+ }
191
+ },
192
+ ],
193
+ },
194
+ ]
195
+
196
+ # seperate jobscripts 0,1,2 due to resource hashes, combined 2,3 due to shared
197
+ # upstream dependencies:
198
+ jobscripts = {
199
+ 0: {"is_array": False, "resource_hash": 0, "dependencies": {}},
200
+ 1: {"is_array": False, "resource_hash": 1, "dependencies": {0: "DEP_DATA"}},
201
+ 2: {"is_array": False, "resource_hash": 0, "dependencies": {1: "DEP_DATA"}},
202
+ 3: {
203
+ "is_array": False,
204
+ "resource_hash": 0,
205
+ "dependencies": {0: "DEP_DATA", 2: "DEP_DATA"},
206
+ },
207
+ }
208
+ assert resolve_jobscript_blocks(jobscripts) == [
209
+ {"resources": None, "is_array": False, "blocks": [{"dependencies": {}}]},
210
+ {
211
+ "resources": None,
212
+ "is_array": False,
213
+ "blocks": [{"dependencies": {(0, 0): "DEP_DATA"}}],
214
+ },
215
+ {
216
+ "resources": None,
217
+ "is_array": False,
218
+ "blocks": [
219
+ {"dependencies": {(1, 0): "DEP_DATA"}},
220
+ {"dependencies": {(0, 0): "DEP_DATA", (2, 0): "DEP_DATA"}},
221
+ ],
222
+ },
223
+ ]
224
+
225
+ # test non-consecutive jobscript indices (i.e. 0,1 merged across tasks in previous
226
+ # step); separate jobscripts 0,2,3 due to resource hashes, combined 3,4 due to shared
227
+ # upstream dependencies:
228
+ jobscripts = {
229
+ 0: {"resource_hash": 0, "dependencies": {}, "is_array": False},
230
+ 2: {
231
+ "resource_hash": 1,
232
+ "dependencies": {0: "DEP_DATA"},
233
+ "is_array": False,
234
+ },
235
+ 3: {
236
+ "resource_hash": 0,
237
+ "dependencies": {0: "DEP_DATA", 2: "DEP_DATA"},
238
+ "is_array": False,
239
+ },
240
+ 4: {
241
+ "resource_hash": 0,
242
+ "dependencies": {0: "DEP_DATA", 3: "DEP_DATA"},
243
+ "is_array": False,
244
+ },
245
+ }
246
+ assert resolve_jobscript_blocks(jobscripts) == [
247
+ {"resources": None, "is_array": False, "blocks": [{"dependencies": {}}]},
248
+ {
249
+ "resources": None,
250
+ "is_array": False,
251
+ "blocks": [{"dependencies": {(0, 0): "DEP_DATA"}}],
252
+ },
253
+ {
254
+ "resources": None,
255
+ "is_array": False,
256
+ "blocks": [
257
+ {"dependencies": {(0, 0): "DEP_DATA", (1, 0): "DEP_DATA"}},
258
+ {"dependencies": {(0, 0): "DEP_DATA", (2, 0): "DEP_DATA"}},
259
+ ],
260
+ },
261
+ ]
262
+
263
+
264
+ def test_is_job_array_raises_on_bad_scheduler():
265
+ resources = hf.ElementResources(use_job_array=True)
266
+ resources.set_defaults()
267
+ with pytest.raises(ValueError):
268
+ is_jobscript_array(resources=resources, num_elements=2, store=None)
269
+
270
+
271
+ def test_force_array(null_config, tmp_path):
272
+ wk = make_workflow(
273
+ [[{"p1": None}, ("p2",), "t1"]],
274
+ path=tmp_path,
275
+ local_sequences={0: [("inputs.p1", 2, 0)]},
276
+ name="w1",
277
+ overwrite=False,
278
+ )
279
+ sub = wk.add_submission(force_array=True)
280
+ assert len(sub.jobscripts) == 1
281
+ assert sub.jobscripts[0].is_array
282
+
283
+
284
+ def test_merge_jobscript_multi_dependence(null_config, tmp_path):
285
+ s1, s2, s3 = make_schemas(
286
+ ({}, ("p1", "p2"), "t1"),
287
+ (
288
+ {
289
+ "p1": None,
290
+ },
291
+ ("p3",),
292
+ "t2",
293
+ ),
294
+ ({"p1": None, "p3": None}, tuple(), "t3"),
295
+ )
296
+ wk = hf.Workflow.from_template_data(
297
+ template_name="test_merge_js",
298
+ workflow_name="test_merge_js",
299
+ overwrite=True,
300
+ path=tmp_path,
301
+ tasks=[
302
+ hf.Task(schema=s1, repeats=2),
303
+ hf.Task(schema=s2),
304
+ hf.Task(schema=s3),
305
+ ],
306
+ )
307
+ sub = wk.add_submission()
308
+ assert len(sub.jobscripts) == 1
309
+ assert len(sub.jobscripts[0].blocks) == 1
310
+
311
+
312
+ def test_merge_jobscript_multi_dependence_non_array_source(null_config, tmp_path):
313
+ # the second two jobscripts should merge
314
+ s1, s2, s3 = make_schemas(
315
+ ({}, ("p1", "p2"), "t1"),
316
+ (
317
+ {
318
+ "p1": None,
319
+ },
320
+ ("p3",),
321
+ "t2",
322
+ ),
323
+ ({"p1": None, "p3": None}, tuple(), "t3"),
324
+ )
325
+ wk = hf.Workflow.from_template_data(
326
+ template_name="wk_test_merge",
327
+ path=tmp_path,
328
+ tasks=[
329
+ hf.Task(schema=s1),
330
+ hf.Task(schema=s2, repeats=2),
331
+ hf.Task(schema=s3),
332
+ ],
333
+ )
334
+ sub = wk.add_submission(force_array=True)
335
+
336
+ assert len(sub.jobscripts) == 2
337
+ assert len(sub.jobscripts[0].blocks) == 1
338
+ assert len(sub.jobscripts[1].blocks) == 1
339
+
340
+
341
+ def test_multi_block_jobscript_multi_dependence(null_config, tmp_path):
342
+
343
+ s1, s2, s3, s4 = make_schemas(
344
+ ({"p1": None}, ("p2", "p3"), "t1"),
345
+ ({"p2": None}, ("p4",), "t2"),
346
+ ({"p4": None}, ("p5",), "t3"),
347
+ ({"p3": None, "p5": None}, (), "t4"),
348
+ )
349
+ tasks = [
350
+ hf.Task(schema=s1, inputs={"p1": 101}),
351
+ hf.Task(schema=s2),
352
+ hf.Task(schema=s3),
353
+ hf.Task(schema=s4),
354
+ ]
355
+ wk = hf.Workflow.from_template_data(
356
+ template_name="test_js_blocks",
357
+ workflow_name="test_js_blocks",
358
+ tasks=tasks,
359
+ path=tmp_path,
360
+ )
361
+ sub = wk.add_submission()
362
+ assert len(sub.jobscripts) == 1
363
+ assert len(sub.jobscripts[0].blocks) == 1
364
+
365
+
366
+ def test_multi_block_jobscript_multi_dependence_distinct_resources(null_config, tmp_path):
367
+
368
+ s1, s2, s3, s4 = make_schemas(
369
+ ({"p1": None}, ("p2", "p3"), "t1"),
370
+ ({"p2": None}, ("p4",), "t2"),
371
+ ({"p4": None}, ("p5",), "t3"),
372
+ ({"p3": None, "p5": None}, (), "t4"),
373
+ )
374
+ tasks = [
375
+ hf.Task(schema=s1, inputs={"p1": 101}),
376
+ hf.Task(schema=s2, resources={"any": {"num_cores": 2}}),
377
+ hf.Task(schema=s3),
378
+ hf.Task(schema=s4),
379
+ ]
380
+ wk = hf.Workflow.from_template_data(
381
+ template_name="test_js_blocks",
382
+ workflow_name="test_js_blocks",
383
+ tasks=tasks,
384
+ path=tmp_path,
385
+ )
386
+ sub = wk.add_submission()
387
+ assert len(sub.jobscripts) == 3
388
+ assert len(sub.jobscripts[0].blocks) == 1
389
+ assert len(sub.jobscripts[1].blocks) == 1
390
+ assert len(sub.jobscripts[2].blocks) == 2
391
+
392
+
393
+ def test_multi_block_jobscript_multi_dependence_distinct_resources_sequence_and_group(
394
+ null_config, tmp_path
395
+ ):
396
+
397
+ s1, s2, s3 = make_schemas(
398
+ ({"p1": None}, ("p2",), "t1"),
399
+ ({"p2": None}, ("p4",), "t2"),
400
+ ({"p4": None}, ("p5",), "t3"),
401
+ )
402
+ s4 = hf.TaskSchema(
403
+ objective="t4",
404
+ inputs=[hf.SchemaInput("p2", group="g1"), hf.SchemaInput("p5", group="g1")],
405
+ actions=[
406
+ hf.Action(
407
+ commands=[
408
+ hf.Command("echo $((<<sum(parameter:p2)>> + <<sum(parameter:p5)>>))")
409
+ ]
410
+ )
411
+ ],
412
+ )
413
+ tasks = [
414
+ hf.Task(
415
+ schema=s1,
416
+ sequences=[hf.ValueSequence(path="inputs.p1", values=[1, 2])],
417
+ groups=[hf.ElementGroup(name="g1")],
418
+ ),
419
+ hf.Task(schema=s2, resources={"any": {"num_cores": 2}}),
420
+ hf.Task(schema=s3, groups=[hf.ElementGroup(name="g1")]),
421
+ hf.Task(schema=s4),
422
+ ]
423
+ wk = hf.Workflow.from_template_data(
424
+ template_name="test_js_blocks",
425
+ workflow_name="test_js_blocks",
426
+ tasks=tasks,
427
+ overwrite=True,
428
+ path=tmp_path,
429
+ )
430
+ sub = wk.add_submission()
431
+ assert len(sub.jobscripts) == 3
432
+ assert len(sub.jobscripts[0].blocks) == 1
433
+ assert len(sub.jobscripts[1].blocks) == 1
434
+ assert len(sub.jobscripts[2].blocks) == 2
435
+
436
+
437
+ def test_combine_scripts_unset_False_jobscript_hash_equivalence(null_config, tmp_path):
438
+
439
+ s1 = hf.TaskSchema(
440
+ objective="t1",
441
+ actions=[
442
+ hf.Action(
443
+ script="<<script:main_script_test_direct_in.py>>",
444
+ script_data_in="direct",
445
+ script_data_out="direct",
446
+ script_exe="python_script",
447
+ environments=[hf.ActionEnvironment(environment="python_env")],
448
+ ),
449
+ hf.Action(commands=[hf.Command(command='echo "hello!"')]),
450
+ ],
451
+ )
452
+ t1 = hf.Task(schema=s1)
453
+ wk = hf.Workflow.from_template_data(
454
+ tasks=[t1],
455
+ resources={
456
+ "any": {
457
+ "combine_scripts": False, # only applies to the Python script action
458
+ },
459
+ },
460
+ template_name="combine_scripts_test",
461
+ path=tmp_path,
462
+ )
463
+ sub = wk.add_submission()
464
+
465
+ # test that even though `combine_scripts` is not set on second action (because it is
466
+ # not a Python script action), the resources have an equivalent hash and thus only one
467
+ # jobscript is generated:
468
+
469
+ iter_1 = wk.tasks.t1.elements[0].iterations[0]
470
+ act_1 = iter_1.action_runs[0].action
471
+ act_2 = iter_1.action_runs[1].action
472
+
473
+ res_1 = iter_1.get_resources_obj(act_1)
474
+ res_2 = iter_1.get_resources_obj(act_2)
475
+
476
+ # set to False on first action:
477
+ assert iter_1.get_resources_obj(act_1).combine_scripts == False
478
+
479
+ # not set on second action:
480
+ assert iter_1.get_resources_obj(act_2).combine_scripts == None
481
+
482
+ # hashes equivalent:
483
+ assert res_1.get_jobscript_hash() == res_2.get_jobscript_hash()
484
+
485
+ assert len(sub.jobscripts) == 1
486
+
487
+
488
+ def test_JS_parallelism_default_zarr(null_config, tmp_path):
489
+ t1 = hf.Task(
490
+ schema=hf.task_schemas.test_t1_conditional_OS,
491
+ inputs={"p1": 100},
492
+ )
493
+ wk = hf.Workflow.from_template_data(
494
+ template_name="test_JS_parallelism_default_set_zarr",
495
+ path=tmp_path,
496
+ tasks=[t1],
497
+ store="zarr",
498
+ )
499
+
500
+ wk.add_submission() # do not set JS_parallelism
501
+
502
+ # zarr supports JS parallelism, so by default should be set to "scheduled":
503
+ assert wk.submissions[0].JS_parallelism == "scheduled"
504
+
505
+
506
+ def test_JS_parallelism_default_json(null_config, tmp_path):
507
+ t1 = hf.Task(
508
+ schema=hf.task_schemas.test_t1_conditional_OS,
509
+ inputs={"p1": 100},
510
+ )
511
+ wk = hf.Workflow.from_template_data(
512
+ template_name="test_JS_parallelism_default_set_json",
513
+ path=tmp_path,
514
+ tasks=[t1],
515
+ store="json",
516
+ )
517
+
518
+ wk.add_submission() # do not set JS_parallelism
519
+
520
+ # json does not support JS parallelism, so by default should be set to False:
521
+ assert wk.submissions[0].JS_parallelism is False
522
+
523
+
524
+ def test_jobscript_block_run_IDs_equivalence_JSON_Zarr(null_config, tmp_path):
525
+ """The zarr store keeps jobscript-block run IDs in separate arrays, so test
526
+ equivalence."""
527
+
528
+ s1, s2, s3, s4 = make_schemas(
529
+ ({"p1": None}, ("p2", "p3"), "t1"),
530
+ ({"p2": None}, ("p4",), "t2"),
531
+ ({"p4": None}, ("p5",), "t3"),
532
+ ({"p3": None, "p5": None}, (), "t4"),
533
+ )
534
+ tasks_zarr = [
535
+ hf.Task(schema=s1, inputs={"p1": 101}),
536
+ hf.Task(schema=s2, resources={"any": {"num_cores": 2}}),
537
+ hf.Task(schema=s3),
538
+ hf.Task(schema=s4),
539
+ ]
540
+ wk_zarr = hf.Workflow.from_template_data(
541
+ template_name="test_js_blocks_zarr",
542
+ tasks=tasks_zarr,
543
+ path=tmp_path,
544
+ store="zarr",
545
+ )
546
+ sub_zarr = wk_zarr.add_submission()
547
+
548
+ tasks_json = [
549
+ hf.Task(schema=s1, inputs={"p1": 101}),
550
+ hf.Task(schema=s2, resources={"any": {"num_cores": 2}}),
551
+ hf.Task(schema=s3),
552
+ hf.Task(schema=s4),
553
+ ]
554
+ wk_json = hf.Workflow.from_template_data(
555
+ template_name="test_js_blocks_json",
556
+ tasks=tasks_json,
557
+ path=tmp_path,
558
+ store="json",
559
+ )
560
+ sub_json = wk_json.add_submission()
561
+
562
+ assert len(sub_zarr.jobscripts) == len(sub_json.jobscripts)
563
+
564
+ for js_idx, js_zarr in enumerate(sub_zarr.jobscripts):
565
+ js_json = sub_json.jobscripts[js_idx]
566
+ assert np.array_equal(js_zarr.all_EAR_IDs, js_json.all_EAR_IDs)
567
+
568
+ # reload both workflows from disk, and check again, since above will check data from
569
+ # in-memory modified Submission object
570
+ sub_json = wk_json.reload().submissions[0]
571
+ sub_zarr = wk_zarr.reload().submissions[0]
572
+
573
+ assert len(sub_zarr.jobscripts) == len(sub_json.jobscripts)
574
+
575
+ for js_idx, js_zarr in enumerate(sub_zarr.jobscripts):
576
+ js_json = sub_json.jobscripts[js_idx]
577
+ assert np.array_equal(js_zarr.all_EAR_IDs, js_json.all_EAR_IDs)
578
+
579
+
580
+ def test_jobscript_task_element_maps_equivalence_JSON_Zarr(null_config, tmp_path):
581
+ """The zarr store keeps jobscript-block task-element maps in separate arrays, so test
582
+ equivalence."""
583
+
584
+ s1, s2, s3, s4 = make_schemas(
585
+ ({"p1": None}, ("p2", "p3"), "t1"),
586
+ ({"p2": None}, ("p4",), "t2"),
587
+ ({"p4": None}, ("p5",), "t3"),
588
+ ({"p3": None, "p5": None}, (), "t4"),
589
+ )
590
+ tasks_zarr = [
591
+ hf.Task(schema=s1, inputs={"p1": 101}),
592
+ hf.Task(schema=s2, resources={"any": {"num_cores": 2}}),
593
+ hf.Task(schema=s3),
594
+ hf.Task(schema=s4),
595
+ ]
596
+ wk_zarr = hf.Workflow.from_template_data(
597
+ template_name="test_js_blocks_zarr",
598
+ tasks=tasks_zarr,
599
+ path=tmp_path,
600
+ store="zarr",
601
+ )
602
+ sub_zarr = wk_zarr.add_submission()
603
+
604
+ tasks_json = [
605
+ hf.Task(schema=s1, inputs={"p1": 101}),
606
+ hf.Task(schema=s2, resources={"any": {"num_cores": 2}}),
607
+ hf.Task(schema=s3),
608
+ hf.Task(schema=s4),
609
+ ]
610
+ wk_json = hf.Workflow.from_template_data(
611
+ template_name="test_js_blocks_json",
612
+ tasks=tasks_json,
613
+ path=tmp_path,
614
+ store="json",
615
+ )
616
+ sub_json = wk_json.add_submission()
617
+
618
+ assert len(sub_zarr.jobscripts) == len(sub_json.jobscripts)
619
+
620
+ for js_idx, js_zarr in enumerate(sub_zarr.jobscripts):
621
+ assert len(js_zarr.blocks) == len(sub_json.jobscripts[js_idx].blocks)
622
+ for blk_idx, js_blk_zarr in enumerate(js_zarr.blocks):
623
+ js_blk_json = sub_json.jobscripts[js_idx].blocks[blk_idx]
624
+ assert js_blk_zarr.task_elements == js_blk_json.task_elements
625
+
626
+ # reload both workflows from disk, and check again, since above will check data from
627
+ # in-memory modified Submission object
628
+ sub_json = wk_json.reload().submissions[0]
629
+ sub_zarr = wk_zarr.reload().submissions[0]
630
+
631
+ assert len(sub_zarr.jobscripts) == len(sub_json.jobscripts)
632
+
633
+ for js_idx, js_zarr in enumerate(sub_zarr.jobscripts):
634
+ assert len(js_zarr.blocks) == len(sub_json.jobscripts[js_idx].blocks)
635
+ for blk_idx, js_blk_zarr in enumerate(js_zarr.blocks):
636
+ js_blk_json = sub_json.jobscripts[js_idx].blocks[blk_idx]
637
+ assert js_blk_zarr.task_elements == js_blk_json.task_elements
638
+
639
+
640
+ def test_jobscript_task_actions_equivalence_JSON_Zarr(null_config, tmp_path):
641
+ """The zarr store keeps jobscript-block task-actions in separate arrays, so test
642
+ equivalence."""
643
+
644
+ s1, s2, s3, s4 = make_schemas(
645
+ ({"p1": None}, ("p2", "p3"), "t1"),
646
+ ({"p2": None}, ("p4",), "t2"),
647
+ ({"p4": None}, ("p5",), "t3"),
648
+ ({"p3": None, "p5": None}, (), "t4"),
649
+ )
650
+ tasks_zarr = [
651
+ hf.Task(schema=s1, inputs={"p1": 101}),
652
+ hf.Task(schema=s2, resources={"any": {"num_cores": 2}}),
653
+ hf.Task(schema=s3),
654
+ hf.Task(schema=s4),
655
+ ]
656
+ wk_zarr = hf.Workflow.from_template_data(
657
+ template_name="test_js_blocks_zarr",
658
+ tasks=tasks_zarr,
659
+ path=tmp_path,
660
+ store="zarr",
661
+ )
662
+ sub_zarr = wk_zarr.add_submission()
663
+
664
+ tasks_json = [
665
+ hf.Task(schema=s1, inputs={"p1": 101}),
666
+ hf.Task(schema=s2, resources={"any": {"num_cores": 2}}),
667
+ hf.Task(schema=s3),
668
+ hf.Task(schema=s4),
669
+ ]
670
+ wk_json = hf.Workflow.from_template_data(
671
+ template_name="test_js_blocks_json",
672
+ tasks=tasks_json,
673
+ path=tmp_path,
674
+ store="json",
675
+ )
676
+ sub_json = wk_json.add_submission()
677
+
678
+ assert len(sub_zarr.jobscripts) == len(sub_json.jobscripts)
679
+
680
+ for js_idx, js_zarr in enumerate(sub_zarr.jobscripts):
681
+ assert len(js_zarr.blocks) == len(sub_json.jobscripts[js_idx].blocks)
682
+ for blk_idx, js_blk_zarr in enumerate(js_zarr.blocks):
683
+ js_blk_json = sub_json.jobscripts[js_idx].blocks[blk_idx]
684
+ assert np.array_equal(js_blk_zarr.task_actions, js_blk_json.task_actions)
685
+
686
+ # reload both workflows from disk, and check again, since above will check data from
687
+ # in-memory modified Submission object
688
+ sub_json = wk_json.reload().submissions[0]
689
+ sub_zarr = wk_zarr.reload().submissions[0]
690
+
691
+ assert len(sub_zarr.jobscripts) == len(sub_json.jobscripts)
692
+
693
+ for js_idx, js_zarr in enumerate(sub_zarr.jobscripts):
694
+ assert len(js_zarr.blocks) == len(sub_json.jobscripts[js_idx].blocks)
695
+ for blk_idx, js_blk_zarr in enumerate(js_zarr.blocks):
696
+ js_blk_json = sub_json.jobscripts[js_idx].blocks[blk_idx]
697
+ assert np.array_equal(js_blk_zarr.task_actions, js_blk_json.task_actions)
698
+
699
+
700
+ def test_jobscript_dependencies_equivalence_JSON_Zarr(null_config, tmp_path):
701
+ """The zarr store keeps jobscript-block dependencies in separate arrays, so test
702
+ equivalence."""
703
+
704
+ s1, s2, s3, s4 = make_schemas(
705
+ ({"p1": None}, ("p2", "p3"), "t1"),
706
+ ({"p2": None}, ("p4",), "t2"),
707
+ ({"p4": None}, ("p5",), "t3"),
708
+ ({"p3": None, "p5": None}, (), "t4"),
709
+ )
710
+ tasks_zarr = [
711
+ hf.Task(schema=s1, inputs={"p1": 101}),
712
+ hf.Task(schema=s2, resources={"any": {"num_cores": 2}}),
713
+ hf.Task(schema=s3),
714
+ hf.Task(schema=s4),
715
+ ]
716
+ wk_zarr = hf.Workflow.from_template_data(
717
+ template_name="test_js_blocks_zarr",
718
+ tasks=tasks_zarr,
719
+ path=tmp_path,
720
+ store="zarr",
721
+ )
722
+ sub_zarr = wk_zarr.add_submission()
723
+
724
+ tasks_json = [
725
+ hf.Task(schema=s1, inputs={"p1": 101}),
726
+ hf.Task(schema=s2, resources={"any": {"num_cores": 2}}),
727
+ hf.Task(schema=s3),
728
+ hf.Task(schema=s4),
729
+ ]
730
+ wk_json = hf.Workflow.from_template_data(
731
+ template_name="test_js_blocks_json",
732
+ tasks=tasks_json,
733
+ path=tmp_path,
734
+ store="json",
735
+ )
736
+ sub_json = wk_json.add_submission()
737
+
738
+ assert len(sub_zarr.jobscripts) == len(sub_json.jobscripts)
739
+
740
+ for js_idx, js_zarr in enumerate(sub_zarr.jobscripts):
741
+ assert len(js_zarr.blocks) == len(sub_json.jobscripts[js_idx].blocks)
742
+ for blk_idx, js_blk_zarr in enumerate(js_zarr.blocks):
743
+ js_blk_json = sub_json.jobscripts[js_idx].blocks[blk_idx]
744
+ assert js_blk_zarr.dependencies == js_blk_json.dependencies
745
+
746
+ # reload both workflows from disk, and check again, since above will check data from
747
+ # in-memory modified Submission object
748
+ sub_json = wk_json.reload().submissions[0]
749
+ sub_zarr = wk_zarr.reload().submissions[0]
750
+
751
+ assert len(sub_zarr.jobscripts) == len(sub_json.jobscripts)
752
+
753
+ for js_idx, js_zarr in enumerate(sub_zarr.jobscripts):
754
+ assert len(js_zarr.blocks) == len(sub_json.jobscripts[js_idx].blocks)
755
+ for blk_idx, js_blk_zarr in enumerate(js_zarr.blocks):
756
+ js_blk_json = sub_json.jobscripts[js_idx].blocks[blk_idx]
757
+ assert js_blk_zarr.dependencies == js_blk_json.dependencies