hpcflow-new2 0.2.0a190__py3-none-any.whl → 0.2.0a200__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 (132) hide show
  1. hpcflow/__pyinstaller/hook-hpcflow.py +1 -0
  2. hpcflow/_version.py +1 -1
  3. hpcflow/data/scripts/bad_script.py +2 -0
  4. hpcflow/data/scripts/do_nothing.py +2 -0
  5. hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
  6. hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
  7. hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
  8. hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
  9. hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
  10. hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
  11. hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
  12. hpcflow/data/scripts/input_file_generator_basic.py +3 -0
  13. hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
  14. hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
  15. hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
  16. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
  17. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
  18. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
  19. hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
  20. hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
  21. hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
  22. hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
  23. hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
  24. hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
  25. hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
  26. hpcflow/data/scripts/output_file_parser_basic.py +3 -0
  27. hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
  28. hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
  29. hpcflow/data/scripts/script_exit_test.py +5 -0
  30. hpcflow/data/template_components/environments.yaml +1 -1
  31. hpcflow/sdk/__init__.py +5 -0
  32. hpcflow/sdk/app.py +166 -92
  33. hpcflow/sdk/cli.py +263 -84
  34. hpcflow/sdk/cli_common.py +99 -5
  35. hpcflow/sdk/config/callbacks.py +38 -1
  36. hpcflow/sdk/config/config.py +102 -13
  37. hpcflow/sdk/config/errors.py +19 -5
  38. hpcflow/sdk/config/types.py +3 -0
  39. hpcflow/sdk/core/__init__.py +25 -1
  40. hpcflow/sdk/core/actions.py +914 -262
  41. hpcflow/sdk/core/cache.py +76 -34
  42. hpcflow/sdk/core/command_files.py +14 -128
  43. hpcflow/sdk/core/commands.py +35 -6
  44. hpcflow/sdk/core/element.py +122 -50
  45. hpcflow/sdk/core/errors.py +58 -2
  46. hpcflow/sdk/core/execute.py +207 -0
  47. hpcflow/sdk/core/loop.py +408 -50
  48. hpcflow/sdk/core/loop_cache.py +4 -4
  49. hpcflow/sdk/core/parameters.py +382 -37
  50. hpcflow/sdk/core/run_dir_files.py +13 -40
  51. hpcflow/sdk/core/skip_reason.py +7 -0
  52. hpcflow/sdk/core/task.py +119 -30
  53. hpcflow/sdk/core/task_schema.py +68 -0
  54. hpcflow/sdk/core/test_utils.py +66 -27
  55. hpcflow/sdk/core/types.py +54 -1
  56. hpcflow/sdk/core/utils.py +136 -19
  57. hpcflow/sdk/core/workflow.py +1587 -356
  58. hpcflow/sdk/data/workflow_spec_schema.yaml +2 -0
  59. hpcflow/sdk/demo/cli.py +7 -0
  60. hpcflow/sdk/helper/cli.py +1 -0
  61. hpcflow/sdk/log.py +42 -15
  62. hpcflow/sdk/persistence/base.py +405 -53
  63. hpcflow/sdk/persistence/json.py +177 -52
  64. hpcflow/sdk/persistence/pending.py +237 -69
  65. hpcflow/sdk/persistence/store_resource.py +3 -2
  66. hpcflow/sdk/persistence/types.py +15 -4
  67. hpcflow/sdk/persistence/zarr.py +928 -81
  68. hpcflow/sdk/submission/jobscript.py +1408 -489
  69. hpcflow/sdk/submission/schedulers/__init__.py +40 -5
  70. hpcflow/sdk/submission/schedulers/direct.py +33 -19
  71. hpcflow/sdk/submission/schedulers/sge.py +51 -16
  72. hpcflow/sdk/submission/schedulers/slurm.py +44 -16
  73. hpcflow/sdk/submission/schedulers/utils.py +7 -2
  74. hpcflow/sdk/submission/shells/base.py +68 -20
  75. hpcflow/sdk/submission/shells/bash.py +222 -129
  76. hpcflow/sdk/submission/shells/powershell.py +200 -150
  77. hpcflow/sdk/submission/submission.py +852 -119
  78. hpcflow/sdk/submission/types.py +18 -21
  79. hpcflow/sdk/typing.py +24 -5
  80. hpcflow/sdk/utils/arrays.py +71 -0
  81. hpcflow/sdk/utils/deferred_file.py +55 -0
  82. hpcflow/sdk/utils/hashing.py +16 -0
  83. hpcflow/sdk/utils/patches.py +12 -0
  84. hpcflow/sdk/utils/strings.py +33 -0
  85. hpcflow/tests/api/test_api.py +32 -0
  86. hpcflow/tests/conftest.py +19 -0
  87. hpcflow/tests/data/benchmark_script_runner.yaml +26 -0
  88. hpcflow/tests/data/multi_path_sequences.yaml +29 -0
  89. hpcflow/tests/data/workflow_test_run_abort.yaml +34 -35
  90. hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
  91. hpcflow/tests/scripts/test_input_file_generators.py +282 -0
  92. hpcflow/tests/scripts/test_main_scripts.py +821 -70
  93. hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
  94. hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
  95. hpcflow/tests/shells/wsl/test_wsl_submission.py +6 -0
  96. hpcflow/tests/unit/test_action.py +176 -0
  97. hpcflow/tests/unit/test_app.py +20 -0
  98. hpcflow/tests/unit/test_cache.py +46 -0
  99. hpcflow/tests/unit/test_cli.py +133 -0
  100. hpcflow/tests/unit/test_config.py +122 -1
  101. hpcflow/tests/unit/test_element_iteration.py +47 -0
  102. hpcflow/tests/unit/test_jobscript_unit.py +757 -0
  103. hpcflow/tests/unit/test_loop.py +1332 -27
  104. hpcflow/tests/unit/test_meta_task.py +325 -0
  105. hpcflow/tests/unit/test_multi_path_sequences.py +229 -0
  106. hpcflow/tests/unit/test_parameter.py +13 -0
  107. hpcflow/tests/unit/test_persistence.py +190 -8
  108. hpcflow/tests/unit/test_run.py +109 -3
  109. hpcflow/tests/unit/test_run_directories.py +29 -0
  110. hpcflow/tests/unit/test_shell.py +20 -0
  111. hpcflow/tests/unit/test_submission.py +5 -76
  112. hpcflow/tests/unit/test_workflow_template.py +31 -0
  113. hpcflow/tests/unit/utils/test_arrays.py +40 -0
  114. hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
  115. hpcflow/tests/unit/utils/test_hashing.py +65 -0
  116. hpcflow/tests/unit/utils/test_patches.py +5 -0
  117. hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
  118. hpcflow/tests/workflows/__init__.py +0 -0
  119. hpcflow/tests/workflows/test_directory_structure.py +31 -0
  120. hpcflow/tests/workflows/test_jobscript.py +332 -0
  121. hpcflow/tests/workflows/test_run_status.py +198 -0
  122. hpcflow/tests/workflows/test_skip_downstream.py +696 -0
  123. hpcflow/tests/workflows/test_submission.py +140 -0
  124. hpcflow/tests/workflows/test_workflows.py +142 -2
  125. hpcflow/tests/workflows/test_zip.py +18 -0
  126. hpcflow/viz_demo.ipynb +6587 -3
  127. {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a200.dist-info}/METADATA +7 -4
  128. hpcflow_new2-0.2.0a200.dist-info/RECORD +222 -0
  129. hpcflow_new2-0.2.0a190.dist-info/RECORD +0 -165
  130. {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a200.dist-info}/LICENSE +0 -0
  131. {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a200.dist-info}/WHEEL +0 -0
  132. {hpcflow_new2-0.2.0a190.dist-info → hpcflow_new2-0.2.0a200.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,325 @@
1
+ from textwrap import dedent
2
+
3
+ import pytest
4
+
5
+ from hpcflow.app import app as hf
6
+ from hpcflow.sdk.config.errors import UnknownMetaTaskConstitutiveSchema
7
+
8
+
9
+ def test_basic_meta_task_workflow(new_null_config, tmp_path):
10
+ wk_yaml = dedent(
11
+ """\
12
+ name: test_meta_task
13
+ template_components:
14
+ task_schemas:
15
+ - objective: s0
16
+ inputs:
17
+ - parameter: p1
18
+ outputs:
19
+ - parameter: p2
20
+ actions:
21
+ - commands:
22
+ - command: echo "$((<<parameter:p1>> + 1))"
23
+ stdout: <<int(parameter:p2)>>
24
+
25
+ - objective: s1
26
+ inputs:
27
+ - parameter: p2
28
+ - parameter: p2b
29
+ outputs:
30
+ - parameter: p3
31
+ actions:
32
+ - commands:
33
+ - command: echo "$((<<parameter:p2>> + <<parameter:p2b>>))"
34
+ stdout: <<int(parameter:p3)>>
35
+
36
+ - objective: s2
37
+ inputs:
38
+ - parameter: p3
39
+ outputs:
40
+ - parameter: p4
41
+ actions:
42
+ - commands:
43
+ - command: echo "$((<<parameter:p3>> + 1))"
44
+ stdout: <<int(parameter:p4)>>
45
+
46
+ - objective: s3
47
+ inputs:
48
+ - parameter: p4
49
+ outputs:
50
+ - parameter: p5
51
+ actions:
52
+ - commands:
53
+ - command: echo "$((<<parameter:p4>> + 1))"
54
+ stdout: <<int(parameter:p5)>>
55
+
56
+ meta_task_schemas:
57
+ - objective: system_analysis
58
+ inputs:
59
+ - parameter: p2
60
+ outputs:
61
+ - parameter: p4
62
+
63
+ meta_tasks:
64
+ system_analysis:
65
+ - schema: s1
66
+ inputs:
67
+ p2b: 220
68
+ - schema: s2
69
+
70
+ tasks:
71
+ - schema: s0
72
+ inputs:
73
+ p1: 100
74
+ - schema: system_analysis
75
+ - schema: s3
76
+ """
77
+ )
78
+ wk = hf.Workflow.from_YAML_string(wk_yaml, path=tmp_path)
79
+
80
+ # basic check of param dependendices
81
+ s0_di = wk.tasks.s0.elements[0].get_data_idx()
82
+ s1_di = wk.tasks.s1.elements[0].get_data_idx()
83
+ s2_di = wk.tasks.s2.elements[0].get_data_idx()
84
+ s3_di = wk.tasks.s3.elements[0].get_data_idx()
85
+
86
+ assert s0_di["outputs.p2"] == s1_di["inputs.p2"]
87
+ assert s1_di["outputs.p3"] == s2_di["inputs.p3"]
88
+ assert s2_di["outputs.p4"] == s3_di["inputs.p4"]
89
+
90
+
91
+ def test_basic_meta_task_workflow_API(new_null_config, tmp_path):
92
+ """as above but using Python API."""
93
+ # normal task schemas:
94
+ s0 = hf.TaskSchema(
95
+ objective="s0",
96
+ inputs=[hf.SchemaInput("p1")],
97
+ outputs=[hf.SchemaOutput("p2")],
98
+ actions=[
99
+ hf.Action(
100
+ commands=[
101
+ hf.Command(
102
+ command='echo "$((<<parameter:p1>> + 1))"',
103
+ stdout="<<int(parameter:p2)>>",
104
+ )
105
+ ]
106
+ )
107
+ ],
108
+ )
109
+ s1 = hf.TaskSchema(
110
+ objective="s1",
111
+ inputs=[hf.SchemaInput("p2")],
112
+ outputs=[hf.SchemaOutput("p3")],
113
+ actions=[
114
+ hf.Action(
115
+ commands=[
116
+ hf.Command(
117
+ command='echo "$((<<parameter:p2>> + 1))"',
118
+ stdout="<<int(parameter:p3)>>",
119
+ )
120
+ ]
121
+ )
122
+ ],
123
+ )
124
+ s2 = hf.TaskSchema(
125
+ objective="s2",
126
+ inputs=[hf.SchemaInput("p3")],
127
+ outputs=[hf.SchemaOutput("p4")],
128
+ actions=[
129
+ hf.Action(
130
+ commands=[
131
+ hf.Command(
132
+ command='echo "$((<<parameter:p3>> + 1))"',
133
+ stdout="<<int(parameter:p4)>>",
134
+ )
135
+ ]
136
+ )
137
+ ],
138
+ )
139
+ s3 = hf.TaskSchema(
140
+ objective="s3",
141
+ inputs=[hf.SchemaInput("p4")],
142
+ outputs=[hf.SchemaOutput("p5")],
143
+ actions=[
144
+ hf.Action(
145
+ commands=[
146
+ hf.Command(
147
+ command='echo "$((<<parameter:p4>> + 1))"',
148
+ stdout="<<int(parameter:p5)>>",
149
+ )
150
+ ]
151
+ )
152
+ ],
153
+ )
154
+
155
+ # meta=task schema:
156
+ ms = hf.MetaTaskSchema(
157
+ objective="system_analysis",
158
+ inputs=[hf.SchemaInput("p2")],
159
+ outputs=[hf.SchemaOutput("p4")],
160
+ )
161
+
162
+ # meta-task:
163
+ m1 = hf.MetaTask(
164
+ schema=ms,
165
+ tasks=[
166
+ hf.Task(schema=s1),
167
+ hf.Task(schema=s2),
168
+ ],
169
+ )
170
+
171
+ # workflow template tasks list:
172
+ tasks = [
173
+ hf.Task(schema=s0, inputs={"p1": 100}),
174
+ m1,
175
+ hf.Task(schema=s3),
176
+ ]
177
+
178
+ wk = hf.Workflow.from_template_data(
179
+ template_name="meta_task_workflow",
180
+ tasks=tasks,
181
+ path=tmp_path,
182
+ )
183
+
184
+ # basic check of param dependendices
185
+ s0_di = wk.tasks.s0.elements[0].get_data_idx()
186
+ s1_di = wk.tasks.s1.elements[0].get_data_idx()
187
+ s2_di = wk.tasks.s2.elements[0].get_data_idx()
188
+ s3_di = wk.tasks.s3.elements[0].get_data_idx()
189
+
190
+ assert s0_di["outputs.p2"] == s1_di["inputs.p2"]
191
+ assert s1_di["outputs.p3"] == s2_di["inputs.p3"]
192
+ assert s2_di["outputs.p4"] == s3_di["inputs.p4"]
193
+
194
+
195
+ def test_meta_task_custom_parametrisation(new_null_config, tmp_path):
196
+ """test customising the parametrisation of inputs, sequences, and resources within the
197
+ `tasks` list."""
198
+ wk_yaml = dedent(
199
+ """\
200
+ name: test_metatask_multi_element_sets_custom_parametrisation
201
+ template_components:
202
+ task_schemas:
203
+ - objective: s1
204
+ inputs:
205
+ - parameter: p1
206
+ - parameter: p2
207
+ outputs:
208
+ - parameter: p3
209
+ actions:
210
+ - commands:
211
+ - command: echo "$((<<parameter:p1>> + <<parameter:p2>>))"
212
+ stdout: <<int(parameter:p3)>>
213
+
214
+ meta_task_schemas:
215
+ - objective: system_analysis
216
+ inputs:
217
+ - parameter: p1
218
+ - parameter: p2
219
+ outputs:
220
+ - parameter: p3
221
+
222
+ meta_tasks:
223
+ system_analysis:
224
+ - schema: s1
225
+ element_sets:
226
+ - inputs:
227
+ p1: 100
228
+ p2: 200
229
+ - inputs:
230
+ p1: 100
231
+ sequences:
232
+ - path: inputs.p2
233
+ values: [200, 201]
234
+ tasks:
235
+ - schema: system_analysis
236
+ inputs:
237
+ s1: # should apply to first element set by default
238
+ p1: 101
239
+ resources:
240
+ s1: # should apply to first element set by default
241
+ any:
242
+ num_cores: 2
243
+ - schema: system_analysis
244
+ inputs:
245
+ s1.0: # applies to first element set of s1
246
+ p1: 102
247
+ s1.1: # applies to second element set of s1
248
+ p1: 103
249
+ sequences:
250
+ s1.1: # sequences list in second element set is replaced with this list:
251
+ - path: inputs.p2
252
+ values: [300, 301]
253
+ """
254
+ )
255
+ wk = hf.Workflow.from_YAML_string(wk_yaml, path=tmp_path)
256
+
257
+ assert wk.tasks.s1_1.template.element_sets[0].resources[0].num_cores == 2 # modified
258
+ assert (
259
+ wk.tasks.s1_2.template.element_sets[0].resources[0].num_cores is None
260
+ ) # unaffected
261
+
262
+ assert wk.tasks.s1_1.template.element_sets[0].inputs[0].value == 101 # modified
263
+ assert wk.tasks.s1_1.template.element_sets[0].inputs[1].value == 200 # unaffected
264
+ assert wk.tasks.s1_1.template.element_sets[1].sequences[0].values == [
265
+ 200,
266
+ 201,
267
+ ] # unaffected
268
+
269
+ assert wk.tasks.s1_2.template.element_sets[0].inputs[0].value == 102 # modified
270
+ assert wk.tasks.s1_2.template.element_sets[1].inputs[0].value == 103 # modified
271
+ assert wk.tasks.s1_2.template.element_sets[1].sequences[0].values == [
272
+ 300,
273
+ 301,
274
+ ] # modified
275
+
276
+
277
+ def test_meta_task_custom_parametrisation_raises_on_bad_schema_name(
278
+ new_null_config, tmp_path
279
+ ):
280
+ wk_yaml = dedent(
281
+ """\
282
+ name: test_metatask_raise_on_bad_schema_name
283
+ template_components:
284
+ task_schemas:
285
+ - objective: s1
286
+ inputs:
287
+ - parameter: p1
288
+ - parameter: p2
289
+ outputs:
290
+ - parameter: p3
291
+ actions:
292
+ - commands:
293
+ - command: echo "$((<<parameter:p1>> + <<parameter:p2>>))"
294
+ stdout: <<int(parameter:p3)>>
295
+
296
+ meta_task_schemas:
297
+ - objective: system_analysis
298
+ inputs:
299
+ - parameter: p1
300
+ - parameter: p2
301
+ outputs:
302
+ - parameter: p3
303
+
304
+ meta_tasks:
305
+ system_analysis:
306
+ - schema: s1
307
+ element_sets:
308
+ - inputs:
309
+ p1: 100
310
+ p2: 200
311
+ - inputs:
312
+ p1: 100
313
+ sequences:
314
+ - path: inputs.p2
315
+ values: [200, 201]
316
+ tasks:
317
+ - schema: system_analysis
318
+ resources:
319
+ BAD_SCHEMA_NAME: # should raise!
320
+ any:
321
+ num_cores: 2
322
+ """
323
+ )
324
+ with pytest.raises(UnknownMetaTaskConstitutiveSchema):
325
+ wk = hf.Workflow.from_YAML_string(wk_yaml, path=tmp_path)
@@ -0,0 +1,229 @@
1
+ from __future__ import annotations
2
+ from textwrap import dedent
3
+
4
+ import numpy as np
5
+ import pytest
6
+
7
+ from hpcflow.app import app as hf
8
+ from hpcflow.sdk.core.test_utils import make_schemas
9
+ from hpcflow.sdk.core.utils import get_file_context
10
+
11
+
12
+ def test_MPS_sequences():
13
+ mps = hf.MultiPathSequence(paths=("inputs.p1", "inputs.p2"), values=[[0, 1], [2, 3]])
14
+ assert mps.sequences == [
15
+ hf.ValueSequence(path="inputs.p1", values=[0, 1]),
16
+ hf.ValueSequence(path="inputs.p2", values=[2, 3]),
17
+ ]
18
+
19
+
20
+ def test_MPS_sequences_moved_to_element_set():
21
+ mps = hf.MultiPathSequence(paths=("inputs.p1", "inputs.p2"), values=[[0, 1], [2, 3]])
22
+ es = hf.ElementSet(multi_path_sequences=[mps])
23
+ expected_mps_seqs = [
24
+ hf.ValueSequence(path="inputs.p1", values=[0, 1]),
25
+ hf.ValueSequence(path="inputs.p2", values=[2, 3]),
26
+ ]
27
+ assert mps._sequences is None
28
+ assert mps.sequence_indices == [0, 2]
29
+ assert es.sequences == mps.sequences
30
+ assert es.sequences == expected_mps_seqs
31
+
32
+
33
+ def test_MPS_sequences_moved_to_element_set_with_existing_sequences():
34
+ mps = hf.MultiPathSequence(paths=("inputs.p1", "inputs.p2"), values=[[0, 1], [2, 3]])
35
+ seq = hf.ValueSequence(path="inputs.p0", values=[0, 1])
36
+ expected_mps_seqs = [
37
+ hf.ValueSequence(path="inputs.p1", values=[0, 1]),
38
+ hf.ValueSequence(path="inputs.p2", values=[2, 3]),
39
+ ]
40
+ es = hf.ElementSet(
41
+ sequences=[seq],
42
+ multi_path_sequences=[mps],
43
+ )
44
+ assert mps._sequences is None
45
+ assert mps.sequence_indices == [1, 3]
46
+ assert mps.sequences == expected_mps_seqs
47
+ assert es.sequences == [seq, *expected_mps_seqs]
48
+
49
+
50
+ def test_MPS_sequences_moved_to_task_element_set():
51
+ mps = hf.MultiPathSequence(paths=("inputs.p1", "inputs.p2"), values=[[0, 1], [2, 3]])
52
+ s1 = make_schemas(({"p1": None, "p2": None}, ()))
53
+ t1 = hf.Task(s1, multi_path_sequences=[mps])
54
+ expected_mps_seqs = [
55
+ hf.ValueSequence(path="inputs.p1", values=[0, 1]),
56
+ hf.ValueSequence(path="inputs.p2", values=[2, 3]),
57
+ ]
58
+ es = t1.element_sets[0]
59
+ assert mps._sequences is None
60
+ assert mps.sequence_indices == [0, 2]
61
+ assert es.sequences == mps.sequences
62
+ assert es.sequences == expected_mps_seqs
63
+
64
+
65
+ def test_MPS_sequences_moved_to_task_element_set_with_existing_sequences():
66
+ mps = hf.MultiPathSequence(paths=("inputs.p1", "inputs.p2"), values=[[0, 1], [2, 3]])
67
+ seq = hf.ValueSequence(path="inputs.p0", values=[0, 1])
68
+ s1 = make_schemas(({"p0": None, "p1": None, "p2": None}, ()))
69
+ t1 = hf.Task(s1, sequences=[seq], multi_path_sequences=[mps])
70
+ expected_mps_seqs = [
71
+ hf.ValueSequence(path="inputs.p1", values=[0, 1]),
72
+ hf.ValueSequence(path="inputs.p2", values=[2, 3]),
73
+ ]
74
+ es = t1.element_sets[0]
75
+ assert mps._sequences is None
76
+ assert mps.sequence_indices == [1, 3]
77
+ assert mps.sequences == expected_mps_seqs
78
+ assert es.sequences == [seq, *expected_mps_seqs]
79
+
80
+
81
+ def test_MPS_sequence_element_inputs(null_config, tmp_path):
82
+ mps = hf.MultiPathSequence(paths=("inputs.p1", "inputs.p2"), values=[[0, 1], [2, 3]])
83
+ s1 = make_schemas(({"p1": None, "p2": None}, ()))
84
+ t1 = hf.Task(s1, multi_path_sequences=[mps])
85
+ wf = hf.Workflow.from_template_data(
86
+ tasks=[t1],
87
+ template_name="test_multi_path_sequence",
88
+ path=tmp_path,
89
+ )
90
+ assert len(wf.template.tasks[0].element_sets[0].sequences) == 2
91
+ assert wf.tasks[0].num_elements == 2
92
+ assert wf.tasks[0].elements[0].get("inputs") == {"p1": 0, "p2": 2}
93
+ assert wf.tasks[0].elements[1].get("inputs") == {"p1": 1, "p2": 3}
94
+
95
+
96
+ def test_MPS_sequence_element_inputs_with_existing_sequence(null_config, tmp_path):
97
+ mps = hf.MultiPathSequence(paths=("inputs.p1", "inputs.p2"), values=[[0, 1], [2, 3]])
98
+ seq = hf.ValueSequence(path="inputs.p0", values=[0, 1])
99
+ s1 = make_schemas(({"p0": None, "p1": None, "p2": None}, ()))
100
+ t1 = hf.Task(s1, sequences=[seq], multi_path_sequences=[mps])
101
+ wf = hf.Workflow.from_template_data(
102
+ tasks=[t1],
103
+ template_name="test_multi_path_sequence",
104
+ path=tmp_path,
105
+ )
106
+ assert len(wf.template.tasks[0].element_sets[0].sequences) == 3
107
+ assert wf.tasks[0].num_elements == 2
108
+ assert wf.tasks[0].elements[0].get("inputs") == {"p0": 0, "p1": 0, "p2": 2}
109
+ assert wf.tasks[0].elements[1].get("inputs") == {"p0": 1, "p1": 1, "p2": 3}
110
+
111
+ # check the same on reload:
112
+ wf = wf.reload()
113
+ assert len(wf.template.tasks[0].element_sets[0].sequences) == 3
114
+ assert wf.tasks[0].num_elements == 2
115
+ assert wf.tasks[0].elements[0].get("inputs") == {"p0": 0, "p1": 0, "p2": 2}
116
+ assert wf.tasks[0].elements[1].get("inputs") == {"p0": 1, "p1": 1, "p2": 3}
117
+
118
+
119
+ @pytest.mark.integration
120
+ def test_MPS_element_outputs(null_config, tmp_path):
121
+ with get_file_context("hpcflow.tests.data", "multi_path_sequences.yaml") as file_path:
122
+ wf = hf.make_and_submit_workflow(
123
+ file_path,
124
+ path=tmp_path,
125
+ status=False,
126
+ add_to_known=False,
127
+ wait=True,
128
+ )
129
+ assert wf.tasks[0].num_elements == 2
130
+
131
+ p2 = wf.tasks[0].elements[0].outputs.p2
132
+ assert isinstance(p2, hf.ElementParameter)
133
+ assert p2.value == 302
134
+
135
+ p2 = wf.tasks[0].elements[1].outputs.p2
136
+ assert isinstance(p2, hf.ElementParameter)
137
+ assert p2.value == 304
138
+
139
+
140
+ def test_MPS_latin_hypercube_sequence_values():
141
+ wft_yaml = dedent(
142
+ """\
143
+ name: test_latin_hypercube_sampling
144
+ template_components:
145
+ task_schemas:
146
+ - objective: define_p1
147
+ inputs:
148
+ - parameter: p1
149
+ tasks:
150
+ - schema: define_p1
151
+ inputs:
152
+ p1: {}
153
+ multi_path_sequences:
154
+ - paths: [inputs.p1.a, inputs.p1.b]
155
+ values::from_latin_hypercube:
156
+ num_samples: 5
157
+ """
158
+ )
159
+ wft = hf.WorkflowTemplate.from_YAML_string(wft_yaml)
160
+ es = wft.tasks[0].element_sets[0]
161
+ assert len(es.multi_path_sequences) == 1
162
+ mps_values = np.asarray(es.multi_path_sequences[0].values)
163
+ assert mps_values.shape == (2, 5)
164
+ assert len(es.sequences) == 2
165
+ seq_1 = es.sequences[0]
166
+ seq_2 = es.sequences[1]
167
+ assert seq_1.path == "inputs.p1.a"
168
+ assert seq_2.path == "inputs.p1.b"
169
+ assert np.array_equal(np.asarray(seq_1.values), mps_values[0])
170
+ assert np.array_equal(np.asarray(seq_2.values), mps_values[1])
171
+
172
+
173
+ def test_MPS_move_from_sequences_list():
174
+ wft_yaml = dedent(
175
+ """\
176
+ name: test_latin_hypercube_sampling
177
+ template_components:
178
+ task_schemas:
179
+ - objective: define_p1_p2_p3_p4
180
+ inputs:
181
+ - parameter: p1
182
+ - parameter: p2
183
+ - parameter: p3
184
+ - parameter: p4
185
+ tasks:
186
+ - schema: define_p1_p2_p3_p4
187
+ inputs:
188
+ p1: {}
189
+ p2: {}
190
+ p3: {}
191
+
192
+ multi_path_sequences:
193
+ - paths: [inputs.p1.a, inputs.p1.b]
194
+ values::from_latin_hypercube:
195
+ num_samples: 4
196
+
197
+ sequences:
198
+ - paths: [inputs.p2.a, inputs.p2.b] # actually a multi-path sequence
199
+ values::from_latin_hypercube:
200
+ num_samples: 4
201
+
202
+ - path: inputs.p4 # a normal sequence
203
+ values: [0, 1, 2, 3]
204
+
205
+ - paths: [inputs.p3.a, inputs.p3.b] # actually a multi-path sequence
206
+ values::from_latin_hypercube:
207
+ num_samples: 4
208
+ """
209
+ )
210
+ wft = hf.WorkflowTemplate.from_YAML_string(wft_yaml)
211
+ es = wft.tasks[0].element_sets[0]
212
+ mps_lst = es.multi_path_sequences
213
+ seq_lst = es.sequences
214
+ assert len(mps_lst) == 3
215
+ assert len(seq_lst) == 7 # one original plus three multi-path with two paths each
216
+
217
+ # check ordering of multi-path sequences is preserved:
218
+ assert mps_lst[0].paths == ["inputs.p1.a", "inputs.p1.b"]
219
+ assert mps_lst[1].paths == ["inputs.p2.a", "inputs.p2.b"]
220
+ assert mps_lst[2].paths == ["inputs.p3.a", "inputs.p3.b"]
221
+
222
+ # check sensible ordering of sequences:
223
+ assert seq_lst[0].path == "inputs.p4"
224
+ assert seq_lst[1].path == "inputs.p1.a"
225
+ assert seq_lst[2].path == "inputs.p1.b"
226
+ assert seq_lst[3].path == "inputs.p2.a"
227
+ assert seq_lst[4].path == "inputs.p2.b"
228
+ assert seq_lst[5].path == "inputs.p3.a"
229
+ assert seq_lst[6].path == "inputs.p3.b"
@@ -11,6 +11,7 @@ import pytest
11
11
  import requests
12
12
 
13
13
  from hpcflow.app import app as hf
14
+ from hpcflow.sdk.core.errors import ParametersMetadataReadOnlyError
14
15
  from hpcflow.sdk.typing import hydrate
15
16
  from hpcflow.sdk.core.parameters import ParameterValue
16
17
 
@@ -228,3 +229,15 @@ def test_demo_data_substitution_value_sequence_class_method(
228
229
  "9",
229
230
  "10",
230
231
  ]
232
+
233
+
234
+ def test_json_store_parameters_metadata_cache_raises_on_modify(
235
+ null_config, tmp_path: Path
236
+ ):
237
+ wk = hf.make_demo_workflow("workflow_1", path=tmp_path, store="json")
238
+ assert isinstance(wk, hf.Workflow)
239
+ num_params = len(wk.get_all_parameters())
240
+ with pytest.raises(ParametersMetadataReadOnlyError):
241
+ with wk._store.parameters_metadata_cache():
242
+ wk._add_unset_parameter_data(source={"type": "UNSET-FOR-THIS-TEST"})
243
+ assert len(wk.get_all_parameters()) == num_params