hpcflow-new2 0.2.0a149__py3-none-any.whl → 0.2.0a153__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 +35 -4
- hpcflow/sdk/cli.py +32 -1
- hpcflow/sdk/cli_common.py +11 -0
- hpcflow/sdk/core/actions.py +2 -0
- hpcflow/sdk/core/errors.py +4 -0
- hpcflow/sdk/core/task.py +8 -0
- hpcflow/sdk/core/utils.py +44 -16
- hpcflow/sdk/core/workflow.py +108 -20
- hpcflow/sdk/demo/cli.py +7 -0
- hpcflow/sdk/log.py +124 -0
- hpcflow/sdk/persistence/base.py +4 -1
- hpcflow/sdk/persistence/pending.py +26 -0
- hpcflow/sdk/persistence/zarr.py +3 -0
- hpcflow/tests/data/benchmark_N_elements.yaml +6 -0
- hpcflow/tests/unit/test_utils.py +45 -1
- hpcflow/tests/unit/test_workflow_template.py +9 -0
- {hpcflow_new2-0.2.0a149.dist-info → hpcflow_new2-0.2.0a153.dist-info}/METADATA +1 -1
- {hpcflow_new2-0.2.0a149.dist-info → hpcflow_new2-0.2.0a153.dist-info}/RECORD +21 -20
- {hpcflow_new2-0.2.0a149.dist-info → hpcflow_new2-0.2.0a153.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a149.dist-info → hpcflow_new2-0.2.0a153.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/core/workflow.py
CHANGED
@@ -25,6 +25,7 @@ from hpcflow.sdk.core import (
|
|
25
25
|
ABORT_EXIT_CODE,
|
26
26
|
)
|
27
27
|
from hpcflow.sdk.core.actions import EARStatus
|
28
|
+
from hpcflow.sdk.log import TimeIt
|
28
29
|
from hpcflow.sdk.persistence import store_cls_from_str, DEFAULT_STORE_FORMAT
|
29
30
|
from hpcflow.sdk.persistence.base import TEMPLATE_COMP_TYPES, AnySEAR
|
30
31
|
from hpcflow.sdk.persistence.utils import ask_pw_on_auth_exc, infer_store
|
@@ -42,7 +43,7 @@ from hpcflow.sdk.core.json_like import ChildObjectSpec, JSONLike
|
|
42
43
|
from .utils import (
|
43
44
|
read_JSON_file,
|
44
45
|
read_JSON_string,
|
45
|
-
|
46
|
+
read_YAML_str,
|
46
47
|
read_YAML_file,
|
47
48
|
replace_items,
|
48
49
|
)
|
@@ -152,6 +153,7 @@ class WorkflowTemplate(JSONLike):
|
|
152
153
|
self.doc = [self.doc]
|
153
154
|
|
154
155
|
@classmethod
|
156
|
+
@TimeIt.decorator
|
155
157
|
def _from_data(cls, data: Dict) -> app.WorkflowTemplate:
|
156
158
|
# use element_sets if not already:
|
157
159
|
for task_idx, task_dat in enumerate(data["tasks"]):
|
@@ -201,16 +203,22 @@ class WorkflowTemplate(JSONLike):
|
|
201
203
|
return cls.from_json_like(data, shared_data=cls.app.template_components)
|
202
204
|
|
203
205
|
@classmethod
|
204
|
-
|
206
|
+
@TimeIt.decorator
|
207
|
+
def from_YAML_string(
|
208
|
+
cls,
|
209
|
+
string: str,
|
210
|
+
variables: Optional[Dict[str, str]] = None,
|
211
|
+
) -> app.WorkflowTemplate:
|
205
212
|
"""Load from a YAML string.
|
206
213
|
|
207
214
|
Parameters
|
208
215
|
----------
|
209
216
|
string
|
210
217
|
The YAML string containing the workflow template parametrisation.
|
211
|
-
|
218
|
+
variables
|
219
|
+
String variables to substitute in `string`.
|
212
220
|
"""
|
213
|
-
return cls._from_data(
|
221
|
+
return cls._from_data(read_YAML_str(string, variables=variables))
|
214
222
|
|
215
223
|
@classmethod
|
216
224
|
def _check_name(cls, data: Dict, path: PathLike) -> str:
|
@@ -228,54 +236,75 @@ class WorkflowTemplate(JSONLike):
|
|
228
236
|
data["name"] = name
|
229
237
|
|
230
238
|
@classmethod
|
231
|
-
|
239
|
+
@TimeIt.decorator
|
240
|
+
def from_YAML_file(
|
241
|
+
cls,
|
242
|
+
path: PathLike,
|
243
|
+
variables: Optional[Dict[str, str]] = None,
|
244
|
+
) -> app.WorkflowTemplate:
|
232
245
|
"""Load from a YAML file.
|
233
246
|
|
234
247
|
Parameters
|
235
248
|
----------
|
236
249
|
path
|
237
250
|
The path to the YAML file containing the workflow template parametrisation.
|
251
|
+
variables
|
252
|
+
String variables to substitute in the file given by `path`.
|
238
253
|
|
239
254
|
"""
|
240
255
|
cls.app.logger.debug("parsing workflow template from a YAML file")
|
241
|
-
data = read_YAML_file(path)
|
256
|
+
data = read_YAML_file(path, variables=variables)
|
242
257
|
cls._check_name(data, path)
|
243
258
|
data["source_file"] = str(path)
|
244
259
|
return cls._from_data(data)
|
245
260
|
|
246
261
|
@classmethod
|
247
|
-
|
262
|
+
@TimeIt.decorator
|
263
|
+
def from_JSON_string(
|
264
|
+
cls,
|
265
|
+
string: str,
|
266
|
+
variables: Optional[Dict[str, str]] = None,
|
267
|
+
) -> app.WorkflowTemplate:
|
248
268
|
"""Load from a JSON string.
|
249
269
|
|
250
270
|
Parameters
|
251
271
|
----------
|
252
272
|
string
|
253
273
|
The JSON string containing the workflow template parametrisation.
|
254
|
-
|
274
|
+
variables
|
275
|
+
String variables to substitute in `string`.
|
255
276
|
"""
|
256
|
-
return cls._from_data(read_JSON_string(string))
|
277
|
+
return cls._from_data(read_JSON_string(string, variables=variables))
|
257
278
|
|
258
279
|
@classmethod
|
259
|
-
|
280
|
+
@TimeIt.decorator
|
281
|
+
def from_JSON_file(
|
282
|
+
cls,
|
283
|
+
path: PathLike,
|
284
|
+
variables: Optional[Dict[str, str]] = None,
|
285
|
+
) -> app.WorkflowTemplate:
|
260
286
|
"""Load from a JSON file.
|
261
287
|
|
262
288
|
Parameters
|
263
289
|
----------
|
264
290
|
path
|
265
291
|
The path to the JSON file containing the workflow template parametrisation.
|
266
|
-
|
292
|
+
variables
|
293
|
+
String variables to substitute in the file given by `path`.
|
267
294
|
"""
|
268
295
|
cls.app.logger.debug("parsing workflow template from a JSON file")
|
269
|
-
data = read_JSON_file(path)
|
296
|
+
data = read_JSON_file(path, variables=variables)
|
270
297
|
cls._check_name(data, path)
|
271
298
|
data["source_file"] = str(path)
|
272
299
|
return cls._from_data(data)
|
273
300
|
|
274
301
|
@classmethod
|
302
|
+
@TimeIt.decorator
|
275
303
|
def from_file(
|
276
304
|
cls,
|
277
305
|
path: PathLike,
|
278
306
|
template_format: Optional[str] = None,
|
307
|
+
variables: Optional[Dict[str, str]] = None,
|
279
308
|
) -> app.WorkflowTemplate:
|
280
309
|
"""Load from either a YAML or JSON file, depending on the file extension.
|
281
310
|
|
@@ -286,14 +315,16 @@ class WorkflowTemplate(JSONLike):
|
|
286
315
|
template_format
|
287
316
|
The file format to expect at `path`. One of "json" or "yaml", if specified. By
|
288
317
|
default, "yaml".
|
318
|
+
variables
|
319
|
+
String variables to substitute in the file given by `path`.
|
289
320
|
|
290
321
|
"""
|
291
322
|
path = Path(path)
|
292
323
|
fmt = template_format.lower() if template_format else None
|
293
324
|
if fmt == "yaml" or path.suffix in (".yaml", ".yml"):
|
294
|
-
return cls.from_YAML_file(path)
|
325
|
+
return cls.from_YAML_file(path, variables=variables)
|
295
326
|
elif fmt == "json" or path.suffix in (".json", ".jsonc"):
|
296
|
-
return cls.from_JSON_file(path)
|
327
|
+
return cls.from_JSON_file(path, variables=variables)
|
297
328
|
else:
|
298
329
|
raise ValueError(
|
299
330
|
f"Unknown workflow template file extension {path.suffix!r}. Supported "
|
@@ -450,6 +481,7 @@ class Workflow:
|
|
450
481
|
return len(self.tasks)
|
451
482
|
|
452
483
|
@classmethod
|
484
|
+
@TimeIt.decorator
|
453
485
|
def from_template(
|
454
486
|
cls,
|
455
487
|
template: WorkflowTemplate,
|
@@ -508,6 +540,7 @@ class Workflow:
|
|
508
540
|
return wk
|
509
541
|
|
510
542
|
@classmethod
|
543
|
+
@TimeIt.decorator
|
511
544
|
def from_YAML_file(
|
512
545
|
cls,
|
513
546
|
YAML_path: PathLike,
|
@@ -518,6 +551,7 @@ class Workflow:
|
|
518
551
|
ts_fmt: Optional[str] = None,
|
519
552
|
ts_name_fmt: Optional[str] = None,
|
520
553
|
store_kwargs: Optional[Dict] = None,
|
554
|
+
variables: Optional[Dict[str, str]] = None,
|
521
555
|
) -> app.Workflow:
|
522
556
|
"""Generate from a YAML file.
|
523
557
|
|
@@ -546,8 +580,13 @@ class Workflow:
|
|
546
580
|
includes a timestamp.
|
547
581
|
store_kwargs
|
548
582
|
Keyword arguments to pass to the store's `write_empty_workflow` method.
|
583
|
+
variables
|
584
|
+
String variables to substitute in the file given by `YAML_path`.
|
549
585
|
"""
|
550
|
-
template = cls.app.WorkflowTemplate.from_YAML_file(
|
586
|
+
template = cls.app.WorkflowTemplate.from_YAML_file(
|
587
|
+
path=YAML_path,
|
588
|
+
variables=variables,
|
589
|
+
)
|
551
590
|
return cls.from_template(
|
552
591
|
template,
|
553
592
|
path,
|
@@ -570,6 +609,7 @@ class Workflow:
|
|
570
609
|
ts_fmt: Optional[str] = None,
|
571
610
|
ts_name_fmt: Optional[str] = None,
|
572
611
|
store_kwargs: Optional[Dict] = None,
|
612
|
+
variables: Optional[Dict[str, str]] = None,
|
573
613
|
) -> app.Workflow:
|
574
614
|
"""Generate from a YAML string.
|
575
615
|
|
@@ -598,8 +638,13 @@ class Workflow:
|
|
598
638
|
includes a timestamp.
|
599
639
|
store_kwargs
|
600
640
|
Keyword arguments to pass to the store's `write_empty_workflow` method.
|
641
|
+
variables
|
642
|
+
String variables to substitute in the string `YAML_str`.
|
601
643
|
"""
|
602
|
-
template = cls.app.WorkflowTemplate.from_YAML_string(
|
644
|
+
template = cls.app.WorkflowTemplate.from_YAML_string(
|
645
|
+
string=YAML_str,
|
646
|
+
variables=variables,
|
647
|
+
)
|
603
648
|
return cls.from_template(
|
604
649
|
template,
|
605
650
|
path,
|
@@ -622,6 +667,7 @@ class Workflow:
|
|
622
667
|
ts_fmt: Optional[str] = None,
|
623
668
|
ts_name_fmt: Optional[str] = None,
|
624
669
|
store_kwargs: Optional[Dict] = None,
|
670
|
+
variables: Optional[Dict[str, str]] = None,
|
625
671
|
) -> app.Workflow:
|
626
672
|
"""Generate from a JSON file.
|
627
673
|
|
@@ -650,8 +696,13 @@ class Workflow:
|
|
650
696
|
includes a timestamp.
|
651
697
|
store_kwargs
|
652
698
|
Keyword arguments to pass to the store's `write_empty_workflow` method.
|
699
|
+
variables
|
700
|
+
String variables to substitute in the file given by `JSON_path`.
|
653
701
|
"""
|
654
|
-
template = cls.app.WorkflowTemplate.from_JSON_file(
|
702
|
+
template = cls.app.WorkflowTemplate.from_JSON_file(
|
703
|
+
path=JSON_path,
|
704
|
+
variables=variables,
|
705
|
+
)
|
655
706
|
return cls.from_template(
|
656
707
|
template,
|
657
708
|
path,
|
@@ -674,6 +725,7 @@ class Workflow:
|
|
674
725
|
ts_fmt: Optional[str] = None,
|
675
726
|
ts_name_fmt: Optional[str] = None,
|
676
727
|
store_kwargs: Optional[Dict] = None,
|
728
|
+
variables: Optional[Dict[str, str]] = None,
|
677
729
|
) -> app.Workflow:
|
678
730
|
"""Generate from a JSON string.
|
679
731
|
|
@@ -702,8 +754,13 @@ class Workflow:
|
|
702
754
|
includes a timestamp.
|
703
755
|
store_kwargs
|
704
756
|
Keyword arguments to pass to the store's `write_empty_workflow` method.
|
757
|
+
variables
|
758
|
+
String variables to substitute in the string `JSON_str`.
|
705
759
|
"""
|
706
|
-
template = cls.app.WorkflowTemplate.from_JSON_string(
|
760
|
+
template = cls.app.WorkflowTemplate.from_JSON_string(
|
761
|
+
string=JSON_str,
|
762
|
+
variables=variables,
|
763
|
+
)
|
707
764
|
return cls.from_template(
|
708
765
|
template,
|
709
766
|
path,
|
@@ -716,6 +773,7 @@ class Workflow:
|
|
716
773
|
)
|
717
774
|
|
718
775
|
@classmethod
|
776
|
+
@TimeIt.decorator
|
719
777
|
def from_file(
|
720
778
|
cls,
|
721
779
|
template_path: PathLike,
|
@@ -727,6 +785,7 @@ class Workflow:
|
|
727
785
|
ts_fmt: Optional[str] = None,
|
728
786
|
ts_name_fmt: Optional[str] = None,
|
729
787
|
store_kwargs: Optional[Dict] = None,
|
788
|
+
variables: Optional[Dict[str, str]] = None,
|
730
789
|
) -> app.Workflow:
|
731
790
|
"""Generate from either a YAML or JSON file, depending on the file extension.
|
732
791
|
|
@@ -759,8 +818,14 @@ class Workflow:
|
|
759
818
|
includes a timestamp.
|
760
819
|
store_kwargs
|
761
820
|
Keyword arguments to pass to the store's `write_empty_workflow` method.
|
821
|
+
variables
|
822
|
+
String variables to substitute in the file given by `template_path`.
|
762
823
|
"""
|
763
|
-
template = cls.app.WorkflowTemplate.from_file(
|
824
|
+
template = cls.app.WorkflowTemplate.from_file(
|
825
|
+
template_path,
|
826
|
+
template_format,
|
827
|
+
variables=variables,
|
828
|
+
)
|
764
829
|
return cls.from_template(
|
765
830
|
template,
|
766
831
|
path,
|
@@ -773,6 +838,7 @@ class Workflow:
|
|
773
838
|
)
|
774
839
|
|
775
840
|
@classmethod
|
841
|
+
@TimeIt.decorator
|
776
842
|
def from_template_data(
|
777
843
|
cls,
|
778
844
|
template_name: str,
|
@@ -841,6 +907,7 @@ class Workflow:
|
|
841
907
|
store_kwargs,
|
842
908
|
)
|
843
909
|
|
910
|
+
@TimeIt.decorator
|
844
911
|
def _add_empty_task(
|
845
912
|
self,
|
846
913
|
task: app.Task,
|
@@ -878,9 +945,9 @@ class Workflow:
|
|
878
945
|
self._pending["template_components"][comp_type].append(idx)
|
879
946
|
|
880
947
|
self._pending["tasks"].append(new_index)
|
881
|
-
|
882
948
|
return self.tasks[new_index]
|
883
949
|
|
950
|
+
@TimeIt.decorator
|
884
951
|
def _add_task(self, task: app.Task, new_index: Optional[int] = None) -> None:
|
885
952
|
new_wk_task = self._add_empty_task(task=task, new_index=new_index)
|
886
953
|
new_wk_task._add_elements(element_sets=task.element_sets)
|
@@ -916,6 +983,7 @@ class Workflow:
|
|
916
983
|
self.add_task(new_task, new_index)
|
917
984
|
# TODO: add new downstream elements?
|
918
985
|
|
986
|
+
@TimeIt.decorator
|
919
987
|
def _add_empty_loop(self, loop: app.Loop) -> app.WorkflowLoop:
|
920
988
|
"""Add a new loop (zeroth iterations only) to the workflow."""
|
921
989
|
|
@@ -955,6 +1023,7 @@ class Workflow:
|
|
955
1023
|
|
956
1024
|
return wk_loop
|
957
1025
|
|
1026
|
+
@TimeIt.decorator
|
958
1027
|
def _add_loop(self, loop: app.Loop, parent_loop_indices: Dict = None) -> None:
|
959
1028
|
new_wk_loop = self._add_empty_loop(loop)
|
960
1029
|
if loop.num_iterations is not None:
|
@@ -1075,17 +1144,21 @@ class Workflow:
|
|
1075
1144
|
def num_added_tasks(self) -> int:
|
1076
1145
|
return self._store._get_num_total_added_tasks()
|
1077
1146
|
|
1147
|
+
@TimeIt.decorator
|
1078
1148
|
def get_store_EARs(self, id_lst: Iterable[int]) -> List[AnySEAR]:
|
1079
1149
|
return self._store.get_EARs(id_lst)
|
1080
1150
|
|
1151
|
+
@TimeIt.decorator
|
1081
1152
|
def get_store_element_iterations(
|
1082
1153
|
self, id_lst: Iterable[int]
|
1083
1154
|
) -> List[AnySElementIter]:
|
1084
1155
|
return self._store.get_element_iterations(id_lst)
|
1085
1156
|
|
1157
|
+
@TimeIt.decorator
|
1086
1158
|
def get_store_elements(self, id_lst: Iterable[int]) -> List[AnySElement]:
|
1087
1159
|
return self._store.get_elements(id_lst)
|
1088
1160
|
|
1161
|
+
@TimeIt.decorator
|
1089
1162
|
def get_store_tasks(self, id_lst: Iterable[int]) -> List[AnySTask]:
|
1090
1163
|
return self._store.get_tasks_by_IDs(id_lst)
|
1091
1164
|
|
@@ -1186,6 +1259,7 @@ class Workflow:
|
|
1186
1259
|
|
1187
1260
|
return objs
|
1188
1261
|
|
1262
|
+
@TimeIt.decorator
|
1189
1263
|
def get_EARs_from_IDs(self, id_lst: Iterable[int]) -> List[app.ElementActionRun]:
|
1190
1264
|
"""Return element action run objects from a list of IDs."""
|
1191
1265
|
|
@@ -1228,12 +1302,15 @@ class Workflow:
|
|
1228
1302
|
|
1229
1303
|
return objs
|
1230
1304
|
|
1305
|
+
@TimeIt.decorator
|
1231
1306
|
def get_all_elements(self) -> List[app.Element]:
|
1232
1307
|
return self.get_elements_from_IDs(range(self.num_elements))
|
1233
1308
|
|
1309
|
+
@TimeIt.decorator
|
1234
1310
|
def get_all_element_iterations(self) -> List[app.ElementIteration]:
|
1235
1311
|
return self.get_element_iterations_from_IDs(range(self.num_element_iterations))
|
1236
1312
|
|
1313
|
+
@TimeIt.decorator
|
1237
1314
|
def get_all_EARs(self) -> List[app.ElementActionRun]:
|
1238
1315
|
return self.get_EARs_from_IDs(range(self.num_EARs))
|
1239
1316
|
|
@@ -1339,6 +1416,7 @@ class Workflow:
|
|
1339
1416
|
return all_replaced[-1]
|
1340
1417
|
|
1341
1418
|
@classmethod
|
1419
|
+
@TimeIt.decorator
|
1342
1420
|
def _write_empty_workflow(
|
1343
1421
|
cls,
|
1344
1422
|
template: app.WorkflowTemplate,
|
@@ -1467,15 +1545,19 @@ class Workflow:
|
|
1467
1545
|
) -> List[AnySParameter]:
|
1468
1546
|
return self._store.get_parameters(id_lst, **kwargs)
|
1469
1547
|
|
1548
|
+
@TimeIt.decorator
|
1470
1549
|
def get_parameter_sources(self, id_lst: Iterable[int]) -> List[Dict]:
|
1471
1550
|
return self._store.get_parameter_sources(id_lst)
|
1472
1551
|
|
1552
|
+
@TimeIt.decorator
|
1473
1553
|
def get_parameter_set_statuses(self, id_lst: Iterable[int]) -> List[bool]:
|
1474
1554
|
return self._store.get_parameter_set_statuses(id_lst)
|
1475
1555
|
|
1556
|
+
@TimeIt.decorator
|
1476
1557
|
def get_parameter(self, index: int, **kwargs: Dict) -> AnySParameter:
|
1477
1558
|
return self.get_parameters([index], **kwargs)[0]
|
1478
1559
|
|
1560
|
+
@TimeIt.decorator
|
1479
1561
|
def get_parameter_data(self, index: int, **kwargs: Dict) -> Any:
|
1480
1562
|
param = self.get_parameter(index, **kwargs)
|
1481
1563
|
if param.data is not None:
|
@@ -1483,18 +1565,22 @@ class Workflow:
|
|
1483
1565
|
else:
|
1484
1566
|
return param.file
|
1485
1567
|
|
1568
|
+
@TimeIt.decorator
|
1486
1569
|
def get_parameter_source(self, index: int) -> Dict:
|
1487
1570
|
return self.get_parameter_sources([index])[0]
|
1488
1571
|
|
1572
|
+
@TimeIt.decorator
|
1489
1573
|
def is_parameter_set(self, index: int) -> bool:
|
1490
1574
|
return self.get_parameter_set_statuses([index])[0]
|
1491
1575
|
|
1576
|
+
@TimeIt.decorator
|
1492
1577
|
def get_all_parameters(self, **kwargs: Dict) -> List[AnySParameter]:
|
1493
1578
|
"""Retrieve all store parameters."""
|
1494
1579
|
num_params = self._store._get_num_total_parameters()
|
1495
1580
|
id_lst = list(range(num_params))
|
1496
1581
|
return self._store.get_parameters(id_lst, **kwargs)
|
1497
1582
|
|
1583
|
+
@TimeIt.decorator
|
1498
1584
|
def get_all_parameter_data(self, **kwargs: Dict) -> Dict[int, Any]:
|
1499
1585
|
"""Retrieve all workflow parameter data."""
|
1500
1586
|
params = self.get_all_parameters(**kwargs)
|
@@ -1671,6 +1757,7 @@ class Workflow:
|
|
1671
1757
|
def execution_path(self):
|
1672
1758
|
return Path(self.path) / self._exec_dir_name
|
1673
1759
|
|
1760
|
+
@TimeIt.decorator
|
1674
1761
|
def get_task_elements(self, task: app.Task, selection: slice) -> List[app.Element]:
|
1675
1762
|
return [
|
1676
1763
|
self.app.Element(task=task, **{k: v for k, v in i.items() if k != "task_ID"})
|
@@ -1818,6 +1905,7 @@ class Workflow:
|
|
1818
1905
|
with self._store.cached_load():
|
1819
1906
|
return self._store.get_EAR_skipped(EAR_ID)
|
1820
1907
|
|
1908
|
+
@TimeIt.decorator
|
1821
1909
|
def set_parameter_value(
|
1822
1910
|
self, param_id: int, value: Any, commit: bool = False
|
1823
1911
|
) -> None:
|
hpcflow/sdk/demo/cli.py
CHANGED
@@ -11,6 +11,7 @@ from hpcflow.sdk.cli_common import (
|
|
11
11
|
store_option,
|
12
12
|
ts_fmt_option,
|
13
13
|
ts_name_fmt_option,
|
14
|
+
variables_option,
|
14
15
|
js_parallelism_option,
|
15
16
|
wait_option,
|
16
17
|
add_to_known_opt,
|
@@ -98,6 +99,7 @@ def get_demo_workflow_CLI(app):
|
|
98
99
|
@store_option
|
99
100
|
@ts_fmt_option
|
100
101
|
@ts_name_fmt_option
|
102
|
+
@variables_option
|
101
103
|
def make_demo_workflow(
|
102
104
|
workflow_name,
|
103
105
|
format,
|
@@ -107,6 +109,7 @@ def get_demo_workflow_CLI(app):
|
|
107
109
|
store,
|
108
110
|
ts_fmt=None,
|
109
111
|
ts_name_fmt=None,
|
112
|
+
variables=None,
|
110
113
|
):
|
111
114
|
wk = app.make_demo_workflow(
|
112
115
|
workflow_name=workflow_name,
|
@@ -117,6 +120,7 @@ def get_demo_workflow_CLI(app):
|
|
117
120
|
store=store,
|
118
121
|
ts_fmt=ts_fmt,
|
119
122
|
ts_name_fmt=ts_name_fmt,
|
123
|
+
variables=variables,
|
120
124
|
)
|
121
125
|
click.echo(wk.path)
|
122
126
|
|
@@ -129,6 +133,7 @@ def get_demo_workflow_CLI(app):
|
|
129
133
|
@store_option
|
130
134
|
@ts_fmt_option
|
131
135
|
@ts_name_fmt_option
|
136
|
+
@variables_option
|
132
137
|
@js_parallelism_option
|
133
138
|
@wait_option
|
134
139
|
@add_to_known_opt
|
@@ -143,6 +148,7 @@ def get_demo_workflow_CLI(app):
|
|
143
148
|
store,
|
144
149
|
ts_fmt=None,
|
145
150
|
ts_name_fmt=None,
|
151
|
+
variables=None,
|
146
152
|
js_parallelism=None,
|
147
153
|
wait=False,
|
148
154
|
add_to_known=True,
|
@@ -158,6 +164,7 @@ def get_demo_workflow_CLI(app):
|
|
158
164
|
store=store,
|
159
165
|
ts_fmt=ts_fmt,
|
160
166
|
ts_name_fmt=ts_name_fmt,
|
167
|
+
variables=variables,
|
161
168
|
JS_parallelism=js_parallelism,
|
162
169
|
wait=wait,
|
163
170
|
add_to_known=add_to_known,
|
hpcflow/sdk/log.py
CHANGED
@@ -1,4 +1,128 @@
|
|
1
|
+
from functools import wraps
|
1
2
|
import logging
|
3
|
+
from pathlib import Path
|
4
|
+
import time
|
5
|
+
from collections import defaultdict
|
6
|
+
import statistics
|
7
|
+
|
8
|
+
|
9
|
+
class TimeIt:
|
10
|
+
|
11
|
+
active = False
|
12
|
+
file_path = None
|
13
|
+
timers = defaultdict(list)
|
14
|
+
trace = []
|
15
|
+
trace_idx = []
|
16
|
+
trace_prev = []
|
17
|
+
trace_idx_prev = []
|
18
|
+
|
19
|
+
@classmethod
|
20
|
+
def decorator(cls, func):
|
21
|
+
@wraps(func)
|
22
|
+
def wrapper(*args, **kwargs):
|
23
|
+
|
24
|
+
if not cls.active:
|
25
|
+
return func(*args, **kwargs)
|
26
|
+
|
27
|
+
cls.trace.append(func.__qualname__)
|
28
|
+
|
29
|
+
if cls.trace_prev == cls.trace:
|
30
|
+
new_trace_idx = cls.trace_idx_prev[-1] + 1
|
31
|
+
else:
|
32
|
+
new_trace_idx = 0
|
33
|
+
cls.trace_idx.append(new_trace_idx)
|
34
|
+
|
35
|
+
tic = time.perf_counter()
|
36
|
+
out = func(*args, **kwargs)
|
37
|
+
toc = time.perf_counter()
|
38
|
+
elapsed = toc - tic
|
39
|
+
|
40
|
+
cls.timers[tuple(cls.trace)].append(elapsed)
|
41
|
+
|
42
|
+
cls.trace_prev = list(cls.trace)
|
43
|
+
cls.trace_idx_prev = list(cls.trace_idx)
|
44
|
+
|
45
|
+
cls.trace.pop()
|
46
|
+
cls.trace_idx.pop()
|
47
|
+
|
48
|
+
return out
|
49
|
+
|
50
|
+
return wrapper
|
51
|
+
|
52
|
+
@classmethod
|
53
|
+
def summarise(cls):
|
54
|
+
stats = {
|
55
|
+
k: {
|
56
|
+
"number": len(v),
|
57
|
+
"mean": statistics.mean(v),
|
58
|
+
"stddev": statistics.pstdev(v),
|
59
|
+
"min": min(v),
|
60
|
+
"max": max(v),
|
61
|
+
"sum": sum(v),
|
62
|
+
}
|
63
|
+
for k, v in cls.timers.items()
|
64
|
+
}
|
65
|
+
|
66
|
+
# make a graph
|
67
|
+
for k in stats:
|
68
|
+
stats[k]["children"] = {}
|
69
|
+
|
70
|
+
for key in sorted(stats.keys(), key=lambda x: len(x), reverse=True):
|
71
|
+
if len(key) == 1:
|
72
|
+
continue
|
73
|
+
value = stats.pop(key)
|
74
|
+
parent = key[:-1]
|
75
|
+
for other_key in stats.keys():
|
76
|
+
if other_key == parent:
|
77
|
+
stats[other_key]["children"][key] = value
|
78
|
+
break
|
79
|
+
|
80
|
+
return stats
|
81
|
+
|
82
|
+
@classmethod
|
83
|
+
def summarise_string(cls):
|
84
|
+
def _format_nodes(node, depth=0, depth_final=None):
|
85
|
+
if depth_final is None:
|
86
|
+
depth_final = []
|
87
|
+
out = []
|
88
|
+
for idx, (k, v) in enumerate(node.items()):
|
89
|
+
is_final_child = idx == len(node) - 1
|
90
|
+
angle = "└ " if is_final_child else "├ "
|
91
|
+
bars = ""
|
92
|
+
if depth > 0:
|
93
|
+
bars = "".join(f"{'│ ' if not i else ' '}" for i in depth_final)
|
94
|
+
k_str = bars + (angle if depth > 0 else "") + f"{k[depth]}"
|
95
|
+
number = v["number"]
|
96
|
+
min_str = f"{v['min']:10.6f}" if number > 1 else f"{f'-':^12s}"
|
97
|
+
max_str = f"{v['max']:10.6f}" if number > 1 else f"{f'-':^12s}"
|
98
|
+
stddev_str = f"({v['stddev']:8.6f})" if number > 1 else f"{f' ':^10s}"
|
99
|
+
out.append(
|
100
|
+
f"{k_str:.<60s} {v['sum']:12.6f} "
|
101
|
+
f"{v['mean']:10.6f} {stddev_str} {number:8d} "
|
102
|
+
f"{min_str} {max_str} "
|
103
|
+
)
|
104
|
+
depth_final_next = list(depth_final) + (
|
105
|
+
[is_final_child] if depth > 0 else []
|
106
|
+
)
|
107
|
+
out.extend(
|
108
|
+
_format_nodes(
|
109
|
+
v["children"], depth=depth + 1, depth_final=depth_final_next
|
110
|
+
)
|
111
|
+
)
|
112
|
+
return out
|
113
|
+
|
114
|
+
summary = cls.summarise()
|
115
|
+
|
116
|
+
out = [
|
117
|
+
f"{'function':^60s} {'sum /s':^12s} {'mean (stddev) /s':^20s} {'N':^8s} "
|
118
|
+
f"{'min /s':^12s} {'max /s':^12s}"
|
119
|
+
]
|
120
|
+
out += _format_nodes(summary)
|
121
|
+
out_str = "\n".join(out)
|
122
|
+
if cls.file_path:
|
123
|
+
Path(cls.file_path).write_text(out_str, encoding="utf-8")
|
124
|
+
else:
|
125
|
+
print(out_str)
|
2
126
|
|
3
127
|
|
4
128
|
class AppLog:
|
hpcflow/sdk/persistence/base.py
CHANGED
@@ -22,6 +22,7 @@ from hpcflow.sdk.core.utils import (
|
|
22
22
|
set_in_container,
|
23
23
|
JSONLikeDirSnapShot,
|
24
24
|
)
|
25
|
+
from hpcflow.sdk.log import TimeIt
|
25
26
|
from hpcflow.sdk.persistence.pending import PendingChanges
|
26
27
|
|
27
28
|
AnySTask = TypeVar("AnySTask", bound="StoreTask")
|
@@ -925,6 +926,7 @@ class PersistentStore(ABC):
|
|
925
926
|
self.save()
|
926
927
|
return new_ID
|
927
928
|
|
929
|
+
@TimeIt.decorator
|
928
930
|
def add_EAR(
|
929
931
|
self,
|
930
932
|
elem_iter_ID: int,
|
@@ -1054,7 +1056,7 @@ class PersistentStore(ABC):
|
|
1054
1056
|
file: Dict = None,
|
1055
1057
|
save: bool = True,
|
1056
1058
|
) -> int:
|
1057
|
-
self.logger.debug(f"Adding store parameter{f' (unset)' if
|
1059
|
+
self.logger.debug(f"Adding store parameter{f' (unset)' if not is_set else ''}.")
|
1058
1060
|
new_idx = self._get_num_total_parameters()
|
1059
1061
|
self._pending.add_parameters[new_idx] = self._store_param_cls(
|
1060
1062
|
id_=new_idx,
|
@@ -1203,6 +1205,7 @@ class PersistentStore(ABC):
|
|
1203
1205
|
if save:
|
1204
1206
|
self.save()
|
1205
1207
|
|
1208
|
+
@TimeIt.decorator
|
1206
1209
|
def update_param_source(self, param_id: int, source: Dict, save: bool = True) -> None:
|
1207
1210
|
self.logger.debug(f"Updating parameter ID {param_id!r} source to {source!r}.")
|
1208
1211
|
self._pending.update_param_sources[param_id] = source
|