hpcflow-new2 0.2.0a169__py3-none-any.whl → 0.2.0a174__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.
- hpcflow/_version.py +1 -1
- hpcflow/sdk/app.py +51 -2
- hpcflow/sdk/core/errors.py +8 -0
- hpcflow/sdk/core/loop.py +232 -46
- hpcflow/sdk/core/task.py +19 -0
- hpcflow/sdk/core/test_utils.py +19 -3
- hpcflow/sdk/core/workflow.py +99 -34
- hpcflow/sdk/persistence/base.py +32 -4
- hpcflow/sdk/persistence/json.py +6 -0
- hpcflow/sdk/persistence/pending.py +27 -2
- hpcflow/sdk/persistence/zarr.py +5 -0
- hpcflow/tests/unit/test_app.py +10 -0
- hpcflow/tests/unit/test_group.py +91 -0
- hpcflow/tests/unit/test_input_value.py +10 -0
- hpcflow/tests/unit/test_loop.py +569 -1
- hpcflow/tests/unit/test_parameter.py +18 -0
- hpcflow/tests/unit/test_task.py +284 -2
- hpcflow/tests/unit/test_value_sequence.py +10 -0
- {hpcflow_new2-0.2.0a169.dist-info → hpcflow_new2-0.2.0a174.dist-info}/METADATA +2 -2
- {hpcflow_new2-0.2.0a169.dist-info → hpcflow_new2-0.2.0a174.dist-info}/RECORD +22 -21
- {hpcflow_new2-0.2.0a169.dist-info → hpcflow_new2-0.2.0a174.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a169.dist-info → hpcflow_new2-0.2.0a174.dist-info}/entry_points.txt +0 -0
hpcflow/tests/unit/test_loop.py
CHANGED
@@ -4,7 +4,7 @@ from valida.conditions import Value
|
|
4
4
|
|
5
5
|
|
6
6
|
from hpcflow.app import app as hf
|
7
|
-
from hpcflow.sdk.core.errors import LoopAlreadyExistsError
|
7
|
+
from hpcflow.sdk.core.errors import LoopAlreadyExistsError, LoopTaskSubsetError
|
8
8
|
from hpcflow.sdk.core.test_utils import P1_parameter_cls, make_workflow
|
9
9
|
|
10
10
|
|
@@ -245,6 +245,209 @@ def test_get_iteration_task_pathway_single_task_single_element_three_iters(
|
|
245
245
|
]
|
246
246
|
|
247
247
|
|
248
|
+
def test_get_iteration_task_pathway_nested_loops_multi_iter(null_config, tmp_path):
|
249
|
+
ts1 = hf.TaskSchema(
|
250
|
+
objective="t1",
|
251
|
+
inputs=[hf.SchemaInput("p1")],
|
252
|
+
outputs=[hf.SchemaOutput("p1")],
|
253
|
+
actions=[
|
254
|
+
hf.Action(
|
255
|
+
commands=[
|
256
|
+
hf.Command(
|
257
|
+
"Write-Output (<<parameter:p1>> + 100)",
|
258
|
+
stdout="<<int(parameter:p1)>>",
|
259
|
+
)
|
260
|
+
],
|
261
|
+
),
|
262
|
+
],
|
263
|
+
)
|
264
|
+
wk = hf.Workflow.from_template_data(
|
265
|
+
template_name="test_loop",
|
266
|
+
path=tmp_path,
|
267
|
+
tasks=[
|
268
|
+
hf.Task(schema=ts1, inputs={"p1": 101}),
|
269
|
+
hf.Task(schema=ts1),
|
270
|
+
hf.Task(schema=ts1),
|
271
|
+
],
|
272
|
+
loops=[
|
273
|
+
hf.Loop(name="inner_loop", tasks=[2], num_iterations=2),
|
274
|
+
hf.Loop(name="outer_loop", tasks=[1, 2], num_iterations=2),
|
275
|
+
],
|
276
|
+
)
|
277
|
+
assert wk.get_iteration_task_pathway() == [
|
278
|
+
(0, {}),
|
279
|
+
(1, {"outer_loop": 0}),
|
280
|
+
(2, {"outer_loop": 0, "inner_loop": 0}),
|
281
|
+
(2, {"outer_loop": 0, "inner_loop": 1}),
|
282
|
+
(1, {"outer_loop": 1}),
|
283
|
+
(2, {"outer_loop": 1, "inner_loop": 0}),
|
284
|
+
(2, {"outer_loop": 1, "inner_loop": 1}),
|
285
|
+
]
|
286
|
+
|
287
|
+
|
288
|
+
@pytest.mark.skip(
|
289
|
+
reason="second set of asserts fail; need to re-source inputs on adding iterations."
|
290
|
+
)
|
291
|
+
def test_get_iteration_task_pathway_nested_loops_multi_iter_jagged(null_config, tmp_path):
|
292
|
+
ts1 = hf.TaskSchema(
|
293
|
+
objective="t1",
|
294
|
+
inputs=[hf.SchemaInput("p1")],
|
295
|
+
outputs=[hf.SchemaOutput("p1")],
|
296
|
+
actions=[
|
297
|
+
hf.Action(
|
298
|
+
commands=[
|
299
|
+
hf.Command(
|
300
|
+
"Write-Output (<<parameter:p1>> + 100)",
|
301
|
+
stdout="<<int(parameter:p1)>>",
|
302
|
+
)
|
303
|
+
],
|
304
|
+
),
|
305
|
+
],
|
306
|
+
)
|
307
|
+
wk = hf.Workflow.from_template_data(
|
308
|
+
template_name="test_loop",
|
309
|
+
path=tmp_path,
|
310
|
+
tasks=[
|
311
|
+
hf.Task(schema=ts1, inputs={"p1": 101}),
|
312
|
+
hf.Task(schema=ts1),
|
313
|
+
hf.Task(schema=ts1),
|
314
|
+
hf.Task(schema=ts1),
|
315
|
+
],
|
316
|
+
loops=[
|
317
|
+
hf.Loop(name="inner_loop", tasks=[2], num_iterations=2),
|
318
|
+
hf.Loop(name="outer_loop", tasks=[1, 2], num_iterations=2),
|
319
|
+
],
|
320
|
+
)
|
321
|
+
wk.loops.inner_loop.add_iteration(parent_loop_indices={"outer_loop": 1})
|
322
|
+
wk.loops.inner_loop.add_iteration(parent_loop_indices={"outer_loop": 1})
|
323
|
+
assert wk.get_iteration_task_pathway() == [
|
324
|
+
(0, {}),
|
325
|
+
(1, {"outer_loop": 0}),
|
326
|
+
(2, {"outer_loop": 0, "inner_loop": 0}),
|
327
|
+
(2, {"outer_loop": 0, "inner_loop": 1}),
|
328
|
+
(1, {"outer_loop": 1}),
|
329
|
+
(2, {"outer_loop": 1, "inner_loop": 0}),
|
330
|
+
(2, {"outer_loop": 1, "inner_loop": 1}),
|
331
|
+
(2, {"outer_loop": 1, "inner_loop": 2}),
|
332
|
+
(2, {"outer_loop": 1, "inner_loop": 3}),
|
333
|
+
(3, {}),
|
334
|
+
]
|
335
|
+
pathway = wk.get_iteration_task_pathway(ret_data_idx=True)
|
336
|
+
assert pathway[1][2][0]["inputs.p1"] == pathway[0][2][0]["outputs.p1"]
|
337
|
+
assert pathway[2][2][0]["inputs.p1"] == pathway[1][2][0]["outputs.p1"]
|
338
|
+
assert pathway[3][2][0]["inputs.p1"] == pathway[2][2][0]["outputs.p1"]
|
339
|
+
assert pathway[4][2][0]["inputs.p1"] == pathway[3][2][0]["outputs.p1"]
|
340
|
+
assert pathway[5][2][0]["inputs.p1"] == pathway[4][2][0]["outputs.p1"]
|
341
|
+
assert pathway[6][2][0]["inputs.p1"] == pathway[5][2][0]["outputs.p1"]
|
342
|
+
assert pathway[7][2][0]["inputs.p1"] == pathway[6][2][0]["outputs.p1"]
|
343
|
+
assert pathway[8][2][0]["inputs.p1"] == pathway[7][2][0]["outputs.p1"]
|
344
|
+
|
345
|
+
# FAILS currently:
|
346
|
+
assert pathway[9][2][0]["inputs.p1"] == pathway[8][2][0]["outputs.p1"]
|
347
|
+
|
348
|
+
|
349
|
+
def test_get_iteration_task_pathway_nested_loops_multi_iter_add_outer_iter(
|
350
|
+
null_config, tmp_path
|
351
|
+
):
|
352
|
+
ts1 = hf.TaskSchema(
|
353
|
+
objective="t1",
|
354
|
+
inputs=[hf.SchemaInput("p1")],
|
355
|
+
outputs=[hf.SchemaOutput("p1")],
|
356
|
+
actions=[
|
357
|
+
hf.Action(
|
358
|
+
commands=[
|
359
|
+
hf.Command(
|
360
|
+
"Write-Output (<<parameter:p1>> + 100)",
|
361
|
+
stdout="<<int(parameter:p1)>>",
|
362
|
+
)
|
363
|
+
],
|
364
|
+
),
|
365
|
+
],
|
366
|
+
)
|
367
|
+
wk = hf.Workflow.from_template_data(
|
368
|
+
template_name="test_loop",
|
369
|
+
path=tmp_path,
|
370
|
+
tasks=[
|
371
|
+
hf.Task(schema=ts1, inputs={"p1": 101}),
|
372
|
+
hf.Task(schema=ts1),
|
373
|
+
hf.Task(schema=ts1),
|
374
|
+
],
|
375
|
+
loops=[
|
376
|
+
hf.Loop(name="inner_loop", tasks=[2], num_iterations=2),
|
377
|
+
hf.Loop(name="outer_loop", tasks=[1, 2], num_iterations=2),
|
378
|
+
],
|
379
|
+
)
|
380
|
+
wk.loops.outer_loop.add_iteration()
|
381
|
+
assert wk.get_iteration_task_pathway() == [
|
382
|
+
(0, {}),
|
383
|
+
(1, {"outer_loop": 0}),
|
384
|
+
(2, {"outer_loop": 0, "inner_loop": 0}),
|
385
|
+
(2, {"outer_loop": 0, "inner_loop": 1}),
|
386
|
+
(1, {"outer_loop": 1}),
|
387
|
+
(2, {"outer_loop": 1, "inner_loop": 0}),
|
388
|
+
(2, {"outer_loop": 1, "inner_loop": 1}),
|
389
|
+
(1, {"outer_loop": 2}),
|
390
|
+
(2, {"outer_loop": 2, "inner_loop": 0}),
|
391
|
+
(2, {"outer_loop": 2, "inner_loop": 1}),
|
392
|
+
]
|
393
|
+
|
394
|
+
|
395
|
+
@pytest.mark.skip(
|
396
|
+
reason="second set of asserts fail; need to re-source inputs on adding iterations."
|
397
|
+
)
|
398
|
+
def test_get_iteration_task_pathway_unconnected_loops(null_config, tmp_path):
|
399
|
+
ts1 = hf.TaskSchema(
|
400
|
+
objective="t1",
|
401
|
+
inputs=[hf.SchemaInput("p1")],
|
402
|
+
outputs=[hf.SchemaOutput("p1")],
|
403
|
+
actions=[
|
404
|
+
hf.Action(
|
405
|
+
commands=[
|
406
|
+
hf.Command(
|
407
|
+
"Write-Output (<<parameter:p1>> + 100)",
|
408
|
+
stdout="<<int(parameter:p1)>>",
|
409
|
+
)
|
410
|
+
],
|
411
|
+
),
|
412
|
+
],
|
413
|
+
)
|
414
|
+
wk = hf.Workflow.from_template_data(
|
415
|
+
template_name="test_loop",
|
416
|
+
path=tmp_path,
|
417
|
+
tasks=[
|
418
|
+
hf.Task(schema=ts1, inputs={"p1": 101}),
|
419
|
+
hf.Task(schema=ts1),
|
420
|
+
hf.Task(schema=ts1),
|
421
|
+
hf.Task(schema=ts1),
|
422
|
+
],
|
423
|
+
loops=[
|
424
|
+
hf.Loop(name="loop_A", tasks=[0, 1], num_iterations=2),
|
425
|
+
hf.Loop(name="loop_B", tasks=[2, 3], num_iterations=2),
|
426
|
+
],
|
427
|
+
)
|
428
|
+
assert wk.get_iteration_task_pathway() == [
|
429
|
+
(0, {"loop_A": 0}),
|
430
|
+
(1, {"loop_A": 0}),
|
431
|
+
(0, {"loop_A": 1}),
|
432
|
+
(1, {"loop_A": 1}),
|
433
|
+
(2, {"loop_B": 0}),
|
434
|
+
(3, {"loop_B": 0}),
|
435
|
+
(2, {"loop_B": 1}),
|
436
|
+
(3, {"loop_B": 1}),
|
437
|
+
]
|
438
|
+
|
439
|
+
pathway = wk.get_iteration_task_pathway(ret_data_idx=True)
|
440
|
+
assert pathway[1][2][0]["inputs.p1"] == pathway[0][2][0]["outputs.p1"]
|
441
|
+
assert pathway[2][2][0]["inputs.p1"] == pathway[1][2][0]["outputs.p1"]
|
442
|
+
assert pathway[3][2][0]["inputs.p1"] == pathway[2][2][0]["outputs.p1"]
|
443
|
+
assert pathway[5][2][0]["inputs.p1"] == pathway[4][2][0]["outputs.p1"]
|
444
|
+
assert pathway[6][2][0]["inputs.p1"] == pathway[5][2][0]["outputs.p1"]
|
445
|
+
assert pathway[7][2][0]["inputs.p1"] == pathway[6][2][0]["outputs.p1"]
|
446
|
+
|
447
|
+
# FAILS currently:
|
448
|
+
assert pathway[4][2][0]["inputs.p1"] == pathway[3][2][0]["outputs.p1"]
|
449
|
+
|
450
|
+
|
248
451
|
def test_wk_loop_input_sources_including_non_iteration_task_source(null_config, tmp_path):
|
249
452
|
act_env = hf.ActionEnvironment("null_env")
|
250
453
|
ts1 = hf.TaskSchema(
|
@@ -591,3 +794,368 @@ def test_loop_local_sub_parameters(null_config, tmp_path):
|
|
591
794
|
assert t1_iter_1["inputs.p1c"] == t2_iter_0["outputs.p1c"]
|
592
795
|
assert t2_iter_1["inputs.p2"] == t1_iter_1["outputs.p2"]
|
593
796
|
assert t1_iter_0["inputs.p1c.d"] == t1_iter_1["inputs.p1c.d"]
|
797
|
+
|
798
|
+
|
799
|
+
def test_nested_loop_iter_loop_idx(null_config, tmp_path):
|
800
|
+
ts1 = hf.TaskSchema(
|
801
|
+
objective="t1",
|
802
|
+
inputs=[hf.SchemaInput("p1")],
|
803
|
+
outputs=[hf.SchemaOutput("p1")],
|
804
|
+
actions=[
|
805
|
+
hf.Action(
|
806
|
+
commands=[
|
807
|
+
hf.Command(
|
808
|
+
"Write-Output (<<parameter:p1>> + 100)",
|
809
|
+
stdout="<<int(parameter:p1)>>",
|
810
|
+
)
|
811
|
+
],
|
812
|
+
),
|
813
|
+
],
|
814
|
+
)
|
815
|
+
|
816
|
+
wk = hf.Workflow.from_template_data(
|
817
|
+
template_name="test_loop",
|
818
|
+
path=tmp_path,
|
819
|
+
tasks=[hf.Task(schema=ts1, inputs={"p1": 101})],
|
820
|
+
loops=[
|
821
|
+
hf.Loop(name="outer_loop", tasks=[0], num_iterations=1),
|
822
|
+
hf.Loop(name="inner_loop", tasks=[0], num_iterations=1),
|
823
|
+
],
|
824
|
+
)
|
825
|
+
assert wk.tasks[0].elements[0].iterations[0].loop_idx == {
|
826
|
+
"inner_loop": 0,
|
827
|
+
"outer_loop": 0,
|
828
|
+
}
|
829
|
+
|
830
|
+
|
831
|
+
def test_schema_input_with_group_sourced_from_prev_iteration(null_config, tmp_path):
|
832
|
+
s1 = hf.TaskSchema(
|
833
|
+
objective="t1",
|
834
|
+
inputs=[hf.SchemaInput("p1")],
|
835
|
+
outputs=[hf.SchemaOutput("p2")],
|
836
|
+
actions=[
|
837
|
+
hf.Action(
|
838
|
+
commands=[
|
839
|
+
hf.Command(
|
840
|
+
"echo $(( <<parameter:p1>> + 1 ))", stdout="<<parameter:p2>>"
|
841
|
+
)
|
842
|
+
]
|
843
|
+
)
|
844
|
+
],
|
845
|
+
)
|
846
|
+
s2 = hf.TaskSchema(
|
847
|
+
objective="t2",
|
848
|
+
inputs=[hf.SchemaInput("p2", group="my_group")],
|
849
|
+
outputs=[hf.SchemaOutput("p3")],
|
850
|
+
actions=[
|
851
|
+
hf.Action(
|
852
|
+
commands=[
|
853
|
+
hf.Command(
|
854
|
+
"echo $(( <<parameter:p2>> + 2 ))", stdout="<<parameter:p3>>"
|
855
|
+
)
|
856
|
+
]
|
857
|
+
)
|
858
|
+
],
|
859
|
+
)
|
860
|
+
s3 = hf.TaskSchema(
|
861
|
+
objective="t3",
|
862
|
+
inputs=[hf.SchemaInput("p3")],
|
863
|
+
outputs=[hf.SchemaOutput("p2")],
|
864
|
+
actions=[
|
865
|
+
hf.Action(
|
866
|
+
commands=[
|
867
|
+
hf.Command(
|
868
|
+
"echo $(( <<parameter:p3>> + 3 ))", stdout="<<parameter:p2>>"
|
869
|
+
)
|
870
|
+
]
|
871
|
+
)
|
872
|
+
],
|
873
|
+
)
|
874
|
+
|
875
|
+
t1 = hf.Task(
|
876
|
+
schema=s1,
|
877
|
+
sequences=[hf.ValueSequence("inputs.p1", values=[1, 2, 3])],
|
878
|
+
groups=[hf.ElementGroup(name="my_group")],
|
879
|
+
)
|
880
|
+
t2 = hf.Task(schema=s2)
|
881
|
+
t3 = hf.Task(
|
882
|
+
schema=s3,
|
883
|
+
repeats=3,
|
884
|
+
groups=[hf.ElementGroup(name="my_group")],
|
885
|
+
)
|
886
|
+
|
887
|
+
l1 = hf.Loop(name="my_loop", tasks=[1, 2], num_iterations=2)
|
888
|
+
|
889
|
+
wk = hf.Workflow.from_template_data(
|
890
|
+
template_name="test_loops",
|
891
|
+
path=tmp_path,
|
892
|
+
tasks=[t1, t2, t3],
|
893
|
+
loops=[l1],
|
894
|
+
)
|
895
|
+
|
896
|
+
assert wk.tasks.t2.elements[0].iterations[0].get_data_idx()["inputs.p2"] == [
|
897
|
+
i.get_data_idx()["outputs.p2"] for i in wk.tasks.t1.elements
|
898
|
+
]
|
899
|
+
assert [
|
900
|
+
i.iterations[0].get_data_idx()["inputs.p3"] for i in wk.tasks.t3.elements
|
901
|
+
] == [wk.tasks.t2.elements[0].iterations[0].get_data_idx()["outputs.p3"]] * 3
|
902
|
+
assert wk.tasks.t2.elements[0].iterations[1].get_data_idx()["inputs.p2"] == [
|
903
|
+
i.iterations[0].get_data_idx()["outputs.p2"] for i in wk.tasks.t3.elements
|
904
|
+
]
|
905
|
+
assert [
|
906
|
+
i.iterations[1].get_data_idx()["inputs.p3"] for i in wk.tasks.t3.elements
|
907
|
+
] == [wk.tasks.t2.elements[0].iterations[1].get_data_idx()["outputs.p3"]] * 3
|
908
|
+
|
909
|
+
|
910
|
+
def test_loop_downstream_tasks(null_config, tmp_path):
|
911
|
+
ts1 = hf.TaskSchema(
|
912
|
+
objective="t1",
|
913
|
+
inputs=[hf.SchemaInput("p1")],
|
914
|
+
outputs=[hf.SchemaOutput("p1")],
|
915
|
+
actions=[
|
916
|
+
hf.Action(
|
917
|
+
commands=[
|
918
|
+
hf.Command(
|
919
|
+
"Write-Output (<<parameter:p1>> + 100)",
|
920
|
+
stdout="<<int(parameter:p1)>>",
|
921
|
+
)
|
922
|
+
],
|
923
|
+
),
|
924
|
+
],
|
925
|
+
)
|
926
|
+
ts2 = hf.TaskSchema(
|
927
|
+
objective="t2",
|
928
|
+
inputs=[hf.SchemaInput("p2")],
|
929
|
+
outputs=[hf.SchemaOutput("p2")],
|
930
|
+
actions=[
|
931
|
+
hf.Action(
|
932
|
+
commands=[
|
933
|
+
hf.Command(
|
934
|
+
"Write-Output (<<parameter:p2>> + 100)",
|
935
|
+
stdout="<<int(parameter:p2)>>",
|
936
|
+
)
|
937
|
+
],
|
938
|
+
),
|
939
|
+
],
|
940
|
+
)
|
941
|
+
wk = hf.Workflow.from_template_data(
|
942
|
+
template_name="test_loop",
|
943
|
+
path=tmp_path,
|
944
|
+
tasks=[
|
945
|
+
hf.Task(schema=ts1, inputs={"p1": 101}),
|
946
|
+
hf.Task(schema=ts1),
|
947
|
+
hf.Task(schema=ts1),
|
948
|
+
hf.Task(schema=ts2, inputs={"p2": 201}),
|
949
|
+
],
|
950
|
+
loops=[
|
951
|
+
hf.Loop(name="my_loop", tasks=[1, 2], num_iterations=2),
|
952
|
+
],
|
953
|
+
)
|
954
|
+
assert wk.loops.my_loop.downstream_tasks == [wk.tasks[3]]
|
955
|
+
assert wk.loops.my_loop.upstream_tasks == [wk.tasks[0]]
|
956
|
+
|
957
|
+
|
958
|
+
def test_raise_loop_task_subset_error(null_config, tmp_path):
|
959
|
+
ts1 = hf.TaskSchema(
|
960
|
+
objective="t1",
|
961
|
+
inputs=[hf.SchemaInput("p1")],
|
962
|
+
outputs=[hf.SchemaOutput("p1")],
|
963
|
+
actions=[
|
964
|
+
hf.Action(
|
965
|
+
commands=[
|
966
|
+
hf.Command(
|
967
|
+
"Write-Output (<<parameter:p1>> + 100)",
|
968
|
+
stdout="<<int(parameter:p1)>>",
|
969
|
+
)
|
970
|
+
],
|
971
|
+
),
|
972
|
+
],
|
973
|
+
)
|
974
|
+
with pytest.raises(LoopTaskSubsetError):
|
975
|
+
hf.Workflow.from_template_data(
|
976
|
+
template_name="test_loop",
|
977
|
+
path=tmp_path,
|
978
|
+
tasks=[
|
979
|
+
hf.Task(schema=ts1, inputs={"p1": 101}),
|
980
|
+
hf.Task(schema=ts1),
|
981
|
+
hf.Task(schema=ts1),
|
982
|
+
],
|
983
|
+
loops=[
|
984
|
+
hf.Loop(name="my_loop", tasks=[2, 1], num_iterations=2),
|
985
|
+
],
|
986
|
+
)
|
987
|
+
|
988
|
+
|
989
|
+
def test_raise_downstream_task_with_iterable_parameter(null_config, tmp_path):
|
990
|
+
ts1 = hf.TaskSchema(
|
991
|
+
objective="t1",
|
992
|
+
inputs=[hf.SchemaInput("p1")],
|
993
|
+
outputs=[hf.SchemaOutput("p1")],
|
994
|
+
actions=[
|
995
|
+
hf.Action(
|
996
|
+
commands=[
|
997
|
+
hf.Command(
|
998
|
+
"Write-Output (<<parameter:p1>> + 100)",
|
999
|
+
stdout="<<int(parameter:p1)>>",
|
1000
|
+
)
|
1001
|
+
],
|
1002
|
+
),
|
1003
|
+
],
|
1004
|
+
)
|
1005
|
+
with pytest.raises(NotImplementedError):
|
1006
|
+
hf.Workflow.from_template_data(
|
1007
|
+
template_name="test_loop",
|
1008
|
+
path=tmp_path,
|
1009
|
+
tasks=[
|
1010
|
+
hf.Task(schema=ts1, inputs={"p1": 101}),
|
1011
|
+
hf.Task(schema=ts1),
|
1012
|
+
hf.Task(schema=ts1),
|
1013
|
+
],
|
1014
|
+
loops=[
|
1015
|
+
hf.Loop(name="my_loop", tasks=[1], num_iterations=2),
|
1016
|
+
],
|
1017
|
+
)
|
1018
|
+
|
1019
|
+
|
1020
|
+
def test_adjacent_loops_iteration_pathway(null_config, tmp_path):
|
1021
|
+
ts1 = hf.TaskSchema(
|
1022
|
+
objective="t1",
|
1023
|
+
inputs=[hf.SchemaInput("p1")],
|
1024
|
+
outputs=[hf.SchemaOutput("p1")],
|
1025
|
+
actions=[
|
1026
|
+
hf.Action(
|
1027
|
+
commands=[
|
1028
|
+
hf.Command(
|
1029
|
+
"Write-Output (<<parameter:p1>> + 100)",
|
1030
|
+
stdout="<<int(parameter:p1)>>",
|
1031
|
+
)
|
1032
|
+
],
|
1033
|
+
),
|
1034
|
+
],
|
1035
|
+
)
|
1036
|
+
ts2 = hf.TaskSchema(
|
1037
|
+
objective="t2",
|
1038
|
+
inputs=[hf.SchemaInput("p2")],
|
1039
|
+
outputs=[hf.SchemaOutput("p2")],
|
1040
|
+
actions=[
|
1041
|
+
hf.Action(
|
1042
|
+
commands=[
|
1043
|
+
hf.Command(
|
1044
|
+
"Write-Output (<<parameter:p2>> + 100)",
|
1045
|
+
stdout="<<int(parameter:p2)>>",
|
1046
|
+
)
|
1047
|
+
],
|
1048
|
+
),
|
1049
|
+
],
|
1050
|
+
)
|
1051
|
+
wk = hf.Workflow.from_template_data(
|
1052
|
+
template_name="test_loop",
|
1053
|
+
path=tmp_path,
|
1054
|
+
tasks=[
|
1055
|
+
hf.Task(schema=ts1, inputs={"p1": 101}),
|
1056
|
+
hf.Task(schema=ts1),
|
1057
|
+
hf.Task(schema=ts2, inputs={"p2": 201}),
|
1058
|
+
],
|
1059
|
+
loops=[
|
1060
|
+
hf.Loop(name="loop_A", tasks=[0, 1], num_iterations=2),
|
1061
|
+
hf.Loop(name="loop_B", tasks=[2], num_iterations=2),
|
1062
|
+
],
|
1063
|
+
)
|
1064
|
+
assert wk.get_iteration_task_pathway() == [
|
1065
|
+
(0, {"loop_A": 0}),
|
1066
|
+
(1, {"loop_A": 0}),
|
1067
|
+
(0, {"loop_A": 1}),
|
1068
|
+
(1, {"loop_A": 1}),
|
1069
|
+
(2, {"loop_B": 0}),
|
1070
|
+
(2, {"loop_B": 1}),
|
1071
|
+
]
|
1072
|
+
|
1073
|
+
|
1074
|
+
def test_get_child_loops_ordered_by_depth(null_config, tmp_path):
|
1075
|
+
ts1 = hf.TaskSchema(
|
1076
|
+
objective="t1",
|
1077
|
+
inputs=[hf.SchemaInput("p1")],
|
1078
|
+
outputs=[hf.SchemaOutput("p1")],
|
1079
|
+
actions=[
|
1080
|
+
hf.Action(
|
1081
|
+
commands=[
|
1082
|
+
hf.Command(
|
1083
|
+
"Write-Output (<<parameter:p1>> + 100)",
|
1084
|
+
stdout="<<int(parameter:p1)>>",
|
1085
|
+
)
|
1086
|
+
],
|
1087
|
+
),
|
1088
|
+
],
|
1089
|
+
)
|
1090
|
+
wk = hf.Workflow.from_template_data(
|
1091
|
+
template_name="test_loop",
|
1092
|
+
path=tmp_path,
|
1093
|
+
tasks=[
|
1094
|
+
hf.Task(schema=ts1, inputs={"p1": 101}),
|
1095
|
+
],
|
1096
|
+
loops=[
|
1097
|
+
hf.Loop(name="inner", tasks=[0], num_iterations=1),
|
1098
|
+
hf.Loop(name="middle", tasks=[0], num_iterations=1),
|
1099
|
+
hf.Loop(name="outer", tasks=[0], num_iterations=1),
|
1100
|
+
],
|
1101
|
+
)
|
1102
|
+
assert wk.loops.inner.get_child_loops() == []
|
1103
|
+
assert wk.loops.middle.get_child_loops() == [wk.loops.inner]
|
1104
|
+
assert wk.loops.outer.get_child_loops() == [wk.loops.middle, wk.loops.inner]
|
1105
|
+
|
1106
|
+
|
1107
|
+
def test_multi_nested_loops(null_config, tmp_path):
|
1108
|
+
ts1 = hf.TaskSchema(
|
1109
|
+
objective="t1",
|
1110
|
+
inputs=[hf.SchemaInput("p1")],
|
1111
|
+
outputs=[hf.SchemaOutput("p1")],
|
1112
|
+
actions=[
|
1113
|
+
hf.Action(
|
1114
|
+
commands=[
|
1115
|
+
hf.Command(
|
1116
|
+
"Write-Output (<<parameter:p1>> + 100)",
|
1117
|
+
stdout="<<int(parameter:p1)>>",
|
1118
|
+
)
|
1119
|
+
],
|
1120
|
+
),
|
1121
|
+
],
|
1122
|
+
)
|
1123
|
+
wk = hf.Workflow.from_template_data(
|
1124
|
+
template_name="test_loop",
|
1125
|
+
path=tmp_path,
|
1126
|
+
tasks=[hf.Task(schema=ts1, inputs={"p1": 101})],
|
1127
|
+
loops=[
|
1128
|
+
hf.Loop(name="inner", tasks=[0], num_iterations=2),
|
1129
|
+
hf.Loop(name="middle_1", tasks=[0], num_iterations=3),
|
1130
|
+
hf.Loop(name="middle_2", tasks=[0], num_iterations=2),
|
1131
|
+
hf.Loop(name="outer", tasks=[0], num_iterations=2),
|
1132
|
+
],
|
1133
|
+
)
|
1134
|
+
pathway = wk.get_iteration_task_pathway(ret_iter_IDs=True)
|
1135
|
+
assert len(pathway) == 2 * 3 * 2 * 2
|
1136
|
+
assert wk.get_iteration_task_pathway(ret_iter_IDs=True) == [
|
1137
|
+
(0, {"inner": 0, "middle_1": 0, "middle_2": 0, "outer": 0}, (0,)),
|
1138
|
+
(0, {"inner": 1, "middle_1": 0, "middle_2": 0, "outer": 0}, (1,)),
|
1139
|
+
(0, {"inner": 0, "middle_1": 1, "middle_2": 0, "outer": 0}, (2,)),
|
1140
|
+
(0, {"inner": 1, "middle_1": 1, "middle_2": 0, "outer": 0}, (3,)),
|
1141
|
+
(0, {"inner": 0, "middle_1": 2, "middle_2": 0, "outer": 0}, (4,)),
|
1142
|
+
(0, {"inner": 1, "middle_1": 2, "middle_2": 0, "outer": 0}, (5,)),
|
1143
|
+
(0, {"inner": 0, "middle_1": 0, "middle_2": 1, "outer": 0}, (6,)),
|
1144
|
+
(0, {"inner": 1, "middle_1": 0, "middle_2": 1, "outer": 0}, (7,)),
|
1145
|
+
(0, {"inner": 0, "middle_1": 1, "middle_2": 1, "outer": 0}, (8,)),
|
1146
|
+
(0, {"inner": 1, "middle_1": 1, "middle_2": 1, "outer": 0}, (9,)),
|
1147
|
+
(0, {"inner": 0, "middle_1": 2, "middle_2": 1, "outer": 0}, (10,)),
|
1148
|
+
(0, {"inner": 1, "middle_1": 2, "middle_2": 1, "outer": 0}, (11,)),
|
1149
|
+
(0, {"inner": 0, "middle_1": 0, "middle_2": 0, "outer": 1}, (12,)),
|
1150
|
+
(0, {"inner": 1, "middle_1": 0, "middle_2": 0, "outer": 1}, (13,)),
|
1151
|
+
(0, {"inner": 0, "middle_1": 1, "middle_2": 0, "outer": 1}, (14,)),
|
1152
|
+
(0, {"inner": 1, "middle_1": 1, "middle_2": 0, "outer": 1}, (15,)),
|
1153
|
+
(0, {"inner": 0, "middle_1": 2, "middle_2": 0, "outer": 1}, (16,)),
|
1154
|
+
(0, {"inner": 1, "middle_1": 2, "middle_2": 0, "outer": 1}, (17,)),
|
1155
|
+
(0, {"inner": 0, "middle_1": 0, "middle_2": 1, "outer": 1}, (18,)),
|
1156
|
+
(0, {"inner": 1, "middle_1": 0, "middle_2": 1, "outer": 1}, (19,)),
|
1157
|
+
(0, {"inner": 0, "middle_1": 1, "middle_2": 1, "outer": 1}, (20,)),
|
1158
|
+
(0, {"inner": 1, "middle_1": 1, "middle_2": 1, "outer": 1}, (21,)),
|
1159
|
+
(0, {"inner": 0, "middle_1": 2, "middle_2": 1, "outer": 1}, (22,)),
|
1160
|
+
(0, {"inner": 1, "middle_1": 2, "middle_2": 1, "outer": 1}, (23,)),
|
1161
|
+
]
|
@@ -1,9 +1,11 @@
|
|
1
1
|
from dataclasses import dataclass
|
2
2
|
import random
|
3
3
|
import string
|
4
|
+
import sys
|
4
5
|
from textwrap import dedent
|
5
6
|
|
6
7
|
import pytest
|
8
|
+
import requests
|
7
9
|
|
8
10
|
from hpcflow.app import app as hf
|
9
11
|
from hpcflow.sdk.core.parameters import ParameterValue
|
@@ -148,6 +150,14 @@ def test_slice(new_null_config, tmp_path):
|
|
148
150
|
assert p1_params[1].value == values[2]
|
149
151
|
|
150
152
|
|
153
|
+
@pytest.mark.xfail(
|
154
|
+
condition=sys.platform == "darwin",
|
155
|
+
raises=requests.exceptions.HTTPError,
|
156
|
+
reason=(
|
157
|
+
"GHA MacOS runners use the same IP address, so we get rate limited when "
|
158
|
+
"retrieving demo data from GitHub."
|
159
|
+
),
|
160
|
+
)
|
151
161
|
def test_demo_data_substitution_param_value_class_method(new_null_config, tmp_path):
|
152
162
|
yaml_str = dedent(
|
153
163
|
"""\
|
@@ -171,6 +181,14 @@ def test_demo_data_substitution_param_value_class_method(new_null_config, tmp_pa
|
|
171
181
|
}
|
172
182
|
|
173
183
|
|
184
|
+
@pytest.mark.xfail(
|
185
|
+
condition=sys.platform == "darwin",
|
186
|
+
raises=requests.exceptions.HTTPError,
|
187
|
+
reason=(
|
188
|
+
"GHA MacOS runners use the same IP address, so we get rate limited when "
|
189
|
+
"retrieving demo data from GitHub."
|
190
|
+
),
|
191
|
+
)
|
174
192
|
def test_demo_data_substitution_value_sequence_class_method(new_null_config, tmp_path):
|
175
193
|
yaml_str = dedent(
|
176
194
|
"""\
|