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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.2.0a176"
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(ctx, path, overwrite, log):
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(ctx.obj["workflow"].zip(path=path, overwrite=overwrite, log=log))
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(workflow_ref, path, overwrite, log, ref_type):
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(wk.zip(path=path, overwrite=overwrite, log=log))
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
+ )
@@ -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.