hpcflow-new2 0.2.0a176__py3-none-any.whl → 0.2.0a178__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/cli.py +97 -4
- hpcflow/sdk/cli_common.py +22 -0
- hpcflow/sdk/core/cache.py +142 -0
- hpcflow/sdk/core/element.py +7 -0
- hpcflow/sdk/core/loop.py +105 -84
- hpcflow/sdk/core/loop_cache.py +140 -0
- hpcflow/sdk/core/task.py +29 -24
- hpcflow/sdk/core/utils.py +11 -1
- hpcflow/sdk/core/workflow.py +108 -24
- hpcflow/sdk/persistence/base.py +16 -3
- hpcflow/sdk/persistence/json.py +11 -4
- hpcflow/sdk/persistence/pending.py +2 -0
- hpcflow/sdk/persistence/zarr.py +132 -3
- hpcflow/tests/unit/test_persistence.py +118 -1
- hpcflow/tests/unit/test_utils.py +21 -0
- hpcflow_new2-0.2.0a178.dist-info/LICENSE +375 -0
- {hpcflow_new2-0.2.0a176.dist-info → hpcflow_new2-0.2.0a178.dist-info}/METADATA +1 -1
- {hpcflow_new2-0.2.0a176.dist-info → hpcflow_new2-0.2.0a178.dist-info}/RECORD +21 -18
- {hpcflow_new2-0.2.0a176.dist-info → hpcflow_new2-0.2.0a178.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a176.dist-info → hpcflow_new2-0.2.0a178.dist-info}/entry_points.txt +0 -0
hpcflow/_version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.2.
|
1
|
+
__version__ = "0.2.0a178"
|
hpcflow/sdk/cli.py
CHANGED
@@ -31,8 +31,13 @@ from hpcflow.sdk.cli_common import (
|
|
31
31
|
zip_path_opt,
|
32
32
|
zip_overwrite_opt,
|
33
33
|
zip_log_opt,
|
34
|
+
zip_include_execute_opt,
|
35
|
+
zip_include_rechunk_backups_opt,
|
34
36
|
unzip_path_opt,
|
35
37
|
unzip_log_opt,
|
38
|
+
rechunk_backup_opt,
|
39
|
+
rechunk_chunk_size_opt,
|
40
|
+
rechunk_status_opt,
|
36
41
|
)
|
37
42
|
from hpcflow.sdk.helper.cli import get_helper_CLI
|
38
43
|
from hpcflow.sdk.log import TimeIt
|
@@ -425,11 +430,28 @@ def _make_workflow_CLI(app):
|
|
425
430
|
@zip_path_opt
|
426
431
|
@zip_overwrite_opt
|
427
432
|
@zip_log_opt
|
433
|
+
@zip_include_execute_opt
|
434
|
+
@zip_include_rechunk_backups_opt
|
428
435
|
@click.pass_context
|
429
|
-
def zip_workflow(
|
436
|
+
def zip_workflow(
|
437
|
+
ctx,
|
438
|
+
path,
|
439
|
+
overwrite,
|
440
|
+
log,
|
441
|
+
include_execute,
|
442
|
+
include_rechunk_backups,
|
443
|
+
):
|
430
444
|
"""Generate a copy of the workflow in the zip file format in the current working
|
431
445
|
directory."""
|
432
|
-
click.echo(
|
446
|
+
click.echo(
|
447
|
+
ctx.obj["workflow"].zip(
|
448
|
+
path=path,
|
449
|
+
overwrite=overwrite,
|
450
|
+
log=log,
|
451
|
+
include_execute=include_execute,
|
452
|
+
include_rechunk_backups=include_rechunk_backups,
|
453
|
+
)
|
454
|
+
)
|
433
455
|
|
434
456
|
@workflow.command(name="unzip")
|
435
457
|
@unzip_path_opt
|
@@ -440,6 +462,37 @@ def _make_workflow_CLI(app):
|
|
440
462
|
current working directory."""
|
441
463
|
click.echo(ctx.obj["workflow"].unzip(path=path, log=log))
|
442
464
|
|
465
|
+
@workflow.command(name="rechunk")
|
466
|
+
@rechunk_backup_opt
|
467
|
+
@rechunk_chunk_size_opt
|
468
|
+
@rechunk_status_opt
|
469
|
+
@click.pass_context
|
470
|
+
def rechunk(ctx, backup, chunk_size, status):
|
471
|
+
"""Rechunk metadata/runs and parameters/base arrays."""
|
472
|
+
ctx.obj["workflow"].rechunk(backup=backup, chunk_size=chunk_size, status=status)
|
473
|
+
|
474
|
+
@workflow.command(name="rechunk-runs")
|
475
|
+
@rechunk_backup_opt
|
476
|
+
@rechunk_chunk_size_opt
|
477
|
+
@rechunk_status_opt
|
478
|
+
@click.pass_context
|
479
|
+
def rechunk_runs(ctx, backup, chunk_size, status):
|
480
|
+
"""Rechunk the metadata/runs array."""
|
481
|
+
ctx.obj["workflow"].rechunk_runs(
|
482
|
+
backup=backup, chunk_size=chunk_size, status=status
|
483
|
+
)
|
484
|
+
|
485
|
+
@workflow.command(name="rechunk-parameter-base")
|
486
|
+
@rechunk_backup_opt
|
487
|
+
@rechunk_chunk_size_opt
|
488
|
+
@rechunk_status_opt
|
489
|
+
@click.pass_context
|
490
|
+
def rechunk_parameter_base(ctx, backup, chunk_size, status):
|
491
|
+
"""Rechunk the parameters/base array."""
|
492
|
+
ctx.obj["workflow"].rechunk_parameter_base(
|
493
|
+
backup=backup, chunk_size=chunk_size, status=status
|
494
|
+
)
|
495
|
+
|
443
496
|
workflow.help = workflow.help.format(app_name=app.name)
|
444
497
|
|
445
498
|
workflow.add_command(_make_workflow_submission_CLI(app))
|
@@ -711,8 +764,18 @@ def _make_zip_CLI(app):
|
|
711
764
|
@zip_path_opt
|
712
765
|
@zip_overwrite_opt
|
713
766
|
@zip_log_opt
|
767
|
+
@zip_include_execute_opt
|
768
|
+
@zip_include_rechunk_backups_opt
|
714
769
|
@workflow_ref_type_opt
|
715
|
-
def zip_workflow(
|
770
|
+
def zip_workflow(
|
771
|
+
workflow_ref,
|
772
|
+
path,
|
773
|
+
overwrite,
|
774
|
+
log,
|
775
|
+
include_execute,
|
776
|
+
include_rechunk_backups,
|
777
|
+
ref_type,
|
778
|
+
):
|
716
779
|
"""Generate a copy of the specified workflow in the zip file format in the
|
717
780
|
current working directory.
|
718
781
|
|
@@ -721,7 +784,15 @@ def _make_zip_CLI(app):
|
|
721
784
|
"""
|
722
785
|
workflow_path = app._resolve_workflow_reference(workflow_ref, ref_type)
|
723
786
|
wk = app.Workflow(workflow_path)
|
724
|
-
click.echo(
|
787
|
+
click.echo(
|
788
|
+
wk.zip(
|
789
|
+
path=path,
|
790
|
+
overwrite=overwrite,
|
791
|
+
log=log,
|
792
|
+
include_execute=include_execute,
|
793
|
+
include_rechunk_backups=include_rechunk_backups,
|
794
|
+
)
|
795
|
+
)
|
725
796
|
|
726
797
|
return zip_workflow
|
727
798
|
|
@@ -760,6 +831,27 @@ def _make_cancel_CLI(app):
|
|
760
831
|
return cancel
|
761
832
|
|
762
833
|
|
834
|
+
def _make_rechunk_CLI(app):
|
835
|
+
@click.command(name="rechunk")
|
836
|
+
@click.argument("workflow_ref")
|
837
|
+
@workflow_ref_type_opt
|
838
|
+
@rechunk_backup_opt
|
839
|
+
@rechunk_chunk_size_opt
|
840
|
+
@rechunk_status_opt
|
841
|
+
def rechunk(workflow_ref, ref_type, backup, chunk_size, status):
|
842
|
+
"""Rechunk metadata/runs and parameters/base arrays.
|
843
|
+
|
844
|
+
WORKFLOW_REF is the local ID (that provided by the `show` command}) or the
|
845
|
+
workflow path.
|
846
|
+
|
847
|
+
"""
|
848
|
+
workflow_path = app._resolve_workflow_reference(workflow_ref, ref_type)
|
849
|
+
wk = app.Workflow(workflow_path)
|
850
|
+
wk.rechunk(backup=backup, chunk_size=chunk_size, status=status)
|
851
|
+
|
852
|
+
return rechunk
|
853
|
+
|
854
|
+
|
763
855
|
def _make_open_CLI(app):
|
764
856
|
@click.group(name="open")
|
765
857
|
def open_file():
|
@@ -1129,6 +1221,7 @@ def make_cli(app):
|
|
1129
1221
|
new_CLI.add_command(_make_cancel_CLI(app))
|
1130
1222
|
new_CLI.add_command(_make_zip_CLI(app))
|
1131
1223
|
new_CLI.add_command(_make_unzip_CLI(app))
|
1224
|
+
new_CLI.add_command(_make_rechunk_CLI(app))
|
1132
1225
|
for cli_cmd in _make_API_CLI(app):
|
1133
1226
|
new_CLI.add_command(cli_cmd)
|
1134
1227
|
|
hpcflow/sdk/cli_common.py
CHANGED
@@ -141,6 +141,8 @@ zip_overwrite_opt = click.option(
|
|
141
141
|
help="If set, any existing file will be overwritten.",
|
142
142
|
)
|
143
143
|
zip_log_opt = click.option("--log", help="Path to a log file to use during zipping.")
|
144
|
+
zip_include_execute_opt = click.option("--include-execute", is_flag=True)
|
145
|
+
zip_include_rechunk_backups_opt = click.option("--include-rechunk-backups", is_flag=True)
|
144
146
|
unzip_path_opt = click.option(
|
145
147
|
"--path",
|
146
148
|
default=".",
|
@@ -151,3 +153,23 @@ unzip_path_opt = click.option(
|
|
151
153
|
),
|
152
154
|
)
|
153
155
|
unzip_log_opt = click.option("--log", help="Path to a log file to use during unzipping.")
|
156
|
+
rechunk_backup_opt = click.option(
|
157
|
+
"--backup/--no-backup",
|
158
|
+
default=True,
|
159
|
+
help=("First copy a backup of the array to a directory ending in `.bak`."),
|
160
|
+
)
|
161
|
+
rechunk_chunk_size_opt = click.option(
|
162
|
+
"--chunk-size",
|
163
|
+
type=click.INT,
|
164
|
+
default=None,
|
165
|
+
help=(
|
166
|
+
"New chunk size (array items per chunk). If unset (as by default), the array "
|
167
|
+
"will be rechunked to a single chunk array (i.e with a chunk size equal to the "
|
168
|
+
"array's shape)."
|
169
|
+
),
|
170
|
+
)
|
171
|
+
rechunk_status_opt = click.option(
|
172
|
+
"--status/--no-status",
|
173
|
+
default=True,
|
174
|
+
help="If True, display a live status to track rechunking progress.",
|
175
|
+
)
|
@@ -0,0 +1,142 @@
|
|
1
|
+
from collections import defaultdict
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import Set, Dict
|
4
|
+
|
5
|
+
from hpcflow.sdk.log import TimeIt
|
6
|
+
|
7
|
+
|
8
|
+
@dataclass
|
9
|
+
class DependencyCache:
|
10
|
+
"""Class to bulk-retrieve dependencies between elements, iterations, and runs."""
|
11
|
+
|
12
|
+
run_dependencies: Dict[int, Set]
|
13
|
+
run_dependents: Dict[int, Set]
|
14
|
+
iter_run_dependencies: Dict[int, Set]
|
15
|
+
iter_iter_dependencies: Dict[int, Set]
|
16
|
+
elem_iter_dependencies: Dict[int, Set]
|
17
|
+
elem_elem_dependencies: Dict[int, Set]
|
18
|
+
elem_elem_dependents: Dict[int, Set]
|
19
|
+
elem_elem_dependents_rec: Dict[int, Set]
|
20
|
+
|
21
|
+
elements: Dict
|
22
|
+
iterations: Dict
|
23
|
+
|
24
|
+
@classmethod
|
25
|
+
@TimeIt.decorator
|
26
|
+
def build(cls, workflow):
|
27
|
+
num_iters = workflow.num_element_iterations
|
28
|
+
num_elems = workflow.num_elements
|
29
|
+
num_runs = workflow.num_EARs
|
30
|
+
|
31
|
+
all_store_runs = workflow._store.get_EARs(list(range(num_runs)))
|
32
|
+
all_store_iters = workflow._store.get_element_iterations(list(range(num_iters)))
|
33
|
+
all_store_elements = workflow._store.get_elements(list(range(num_elems)))
|
34
|
+
all_param_sources = workflow.get_all_parameter_sources()
|
35
|
+
all_data_idx = [
|
36
|
+
{
|
37
|
+
k: v if isinstance(v, list) else [v]
|
38
|
+
for k, v in i.data_idx.items()
|
39
|
+
if k not in ("repeats.",)
|
40
|
+
}
|
41
|
+
for i in all_store_runs
|
42
|
+
]
|
43
|
+
|
44
|
+
# run dependencies and dependents
|
45
|
+
run_dependencies = {}
|
46
|
+
run_dependents = defaultdict(set)
|
47
|
+
for idx, i in enumerate(all_data_idx):
|
48
|
+
run_i_sources = set()
|
49
|
+
for j in i.values():
|
50
|
+
for k in j:
|
51
|
+
run_k = all_param_sources[k].get("EAR_ID")
|
52
|
+
if run_k is not None and run_k != idx:
|
53
|
+
run_i_sources.add(run_k)
|
54
|
+
run_dependencies[idx] = run_i_sources
|
55
|
+
for m in run_i_sources:
|
56
|
+
run_dependents[m].add(idx)
|
57
|
+
|
58
|
+
# add missing:
|
59
|
+
for k in range(num_runs):
|
60
|
+
run_dependents[k]
|
61
|
+
|
62
|
+
run_dependents = dict(run_dependents)
|
63
|
+
|
64
|
+
# iteration dependencies
|
65
|
+
all_iter_run_IDs = {
|
66
|
+
i.id_: [k for j in i.EAR_IDs.values() for k in j] for i in all_store_iters
|
67
|
+
}
|
68
|
+
# for each iteration, which runs does it depend on?
|
69
|
+
iter_run_dependencies = {
|
70
|
+
k: set(j for i in v for j in run_dependencies[i])
|
71
|
+
for k, v in all_iter_run_IDs.items()
|
72
|
+
}
|
73
|
+
|
74
|
+
# for each run, which iteration does it belong to?
|
75
|
+
all_run_iter_IDs = {}
|
76
|
+
for iter_ID, run_IDs in all_iter_run_IDs.items():
|
77
|
+
for run_ID in run_IDs:
|
78
|
+
all_run_iter_IDs[run_ID] = iter_ID
|
79
|
+
|
80
|
+
# for each iteration, which iterations does it depend on?
|
81
|
+
iter_iter_dependencies = {
|
82
|
+
k: set(all_run_iter_IDs[i] for i in v)
|
83
|
+
for k, v in iter_run_dependencies.items()
|
84
|
+
}
|
85
|
+
|
86
|
+
all_elem_iter_IDs = {i.id_: i.iteration_IDs for i in all_store_elements}
|
87
|
+
|
88
|
+
elem_iter_dependencies = {
|
89
|
+
k: set(j for i in v for j in iter_iter_dependencies[i])
|
90
|
+
for k, v in all_elem_iter_IDs.items()
|
91
|
+
}
|
92
|
+
|
93
|
+
# for each iteration, which element does it belong to?
|
94
|
+
all_iter_elem_IDs = {}
|
95
|
+
for elem_ID, iter_IDs in all_elem_iter_IDs.items():
|
96
|
+
for iter_ID in iter_IDs:
|
97
|
+
all_iter_elem_IDs[iter_ID] = elem_ID
|
98
|
+
|
99
|
+
# element dependencies
|
100
|
+
elem_elem_dependencies = {
|
101
|
+
k: set(all_iter_elem_IDs[i] for i in v)
|
102
|
+
for k, v in elem_iter_dependencies.items()
|
103
|
+
}
|
104
|
+
|
105
|
+
# for each element, which elements depend on it (directly)?
|
106
|
+
elem_elem_dependents = defaultdict(set)
|
107
|
+
for k, v in elem_elem_dependencies.items():
|
108
|
+
for i in v:
|
109
|
+
elem_elem_dependents[i].add(k)
|
110
|
+
|
111
|
+
# for each element, which elements depend on it (recursively)?
|
112
|
+
elem_elem_dependents_rec = defaultdict(set)
|
113
|
+
for k in list(elem_elem_dependents):
|
114
|
+
for i in elem_elem_dependents[k]:
|
115
|
+
elem_elem_dependents_rec[k].add(i)
|
116
|
+
elem_elem_dependents_rec[k].update(
|
117
|
+
{m for m in elem_elem_dependents[i] if m != k}
|
118
|
+
)
|
119
|
+
|
120
|
+
# add missing keys:
|
121
|
+
for k in range(num_elems):
|
122
|
+
elem_elem_dependents[k]
|
123
|
+
elem_elem_dependents_rec[k]
|
124
|
+
|
125
|
+
elem_elem_dependents = dict(elem_elem_dependents)
|
126
|
+
elem_elem_dependents_rec = dict(elem_elem_dependents_rec)
|
127
|
+
|
128
|
+
elements = workflow.get_all_elements()
|
129
|
+
iterations = workflow.get_all_element_iterations()
|
130
|
+
|
131
|
+
return cls(
|
132
|
+
run_dependencies=run_dependencies,
|
133
|
+
run_dependents=run_dependents,
|
134
|
+
iter_run_dependencies=iter_run_dependencies,
|
135
|
+
iter_iter_dependencies=iter_iter_dependencies,
|
136
|
+
elem_iter_dependencies=elem_iter_dependencies,
|
137
|
+
elem_elem_dependencies=elem_elem_dependencies,
|
138
|
+
elem_elem_dependents=elem_elem_dependents,
|
139
|
+
elem_elem_dependents_rec=elem_elem_dependents_rec,
|
140
|
+
elements=elements,
|
141
|
+
iterations=iterations,
|
142
|
+
)
|
hpcflow/sdk/core/element.py
CHANGED
@@ -675,6 +675,7 @@ class ElementIteration:
|
|
675
675
|
default=default,
|
676
676
|
)
|
677
677
|
|
678
|
+
@TimeIt.decorator
|
678
679
|
def get_EAR_dependencies(
|
679
680
|
self,
|
680
681
|
as_objects: Optional[bool] = False,
|
@@ -708,6 +709,7 @@ class ElementIteration:
|
|
708
709
|
out = self.workflow.get_EARs_from_IDs(out)
|
709
710
|
return out
|
710
711
|
|
712
|
+
@TimeIt.decorator
|
711
713
|
def get_element_iteration_dependencies(
|
712
714
|
self, as_objects: bool = False
|
713
715
|
) -> List[Union[int, app.ElementIteration]]:
|
@@ -719,6 +721,7 @@ class ElementIteration:
|
|
719
721
|
out = self.workflow.get_element_iterations_from_IDs(out)
|
720
722
|
return out
|
721
723
|
|
724
|
+
@TimeIt.decorator
|
722
725
|
def get_element_dependencies(
|
723
726
|
self,
|
724
727
|
as_objects: Optional[bool] = False,
|
@@ -769,6 +772,7 @@ class ElementIteration:
|
|
769
772
|
|
770
773
|
return out
|
771
774
|
|
775
|
+
@TimeIt.decorator
|
772
776
|
def get_dependent_EARs(
|
773
777
|
self, as_objects: bool = False
|
774
778
|
) -> List[Union[int, app.ElementActionRun]]:
|
@@ -793,6 +797,7 @@ class ElementIteration:
|
|
793
797
|
|
794
798
|
return deps
|
795
799
|
|
800
|
+
@TimeIt.decorator
|
796
801
|
def get_dependent_element_iterations(
|
797
802
|
self, as_objects: bool = False
|
798
803
|
) -> List[Union[int, app.ElementIteration]]:
|
@@ -816,6 +821,7 @@ class ElementIteration:
|
|
816
821
|
|
817
822
|
return deps
|
818
823
|
|
824
|
+
@TimeIt.decorator
|
819
825
|
def get_dependent_elements(
|
820
826
|
self,
|
821
827
|
as_objects: bool = False,
|
@@ -1246,6 +1252,7 @@ class Element:
|
|
1246
1252
|
"""Get tasks that depend on the most recent iteration of this element."""
|
1247
1253
|
return self.latest_iteration.get_dependent_tasks(as_objects=as_objects)
|
1248
1254
|
|
1255
|
+
@TimeIt.decorator
|
1249
1256
|
def get_dependent_elements_recursively(self, task_insert_ID=None):
|
1250
1257
|
"""Get downstream elements that depend on this element, including recursive
|
1251
1258
|
dependencies.
|