hpcflow-new2 0.2.0a223__py3-none-any.whl → 0.2.0a228__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.0a223"
1
+ __version__ = "0.2.0a228"
hpcflow/sdk/app.py CHANGED
@@ -3315,12 +3315,13 @@ class BaseApp(metaclass=Singleton):
3315
3315
  out_item["deleted"] = True
3316
3316
 
3317
3317
  else:
3318
- if status:
3319
- status.update(
3320
- f"Reading workflow {file_dat_i['path']!r} submission info..."
3321
- )
3322
3318
  with wk_i._store.cache_ctx():
3323
3319
  sub = wk_i.submissions[file_dat_i["sub_idx"]]
3320
+ if status:
3321
+ status.update(
3322
+ f"Loading workflow {file_dat_i['path']!r} run metadata..."
3323
+ )
3324
+ sub.use_EARs_cache = True # pre-cache EARs of this submission
3324
3325
 
3325
3326
  all_jobscripts = sub._submission_parts[submit_time_str]
3326
3327
  out_item["jobscripts"] = all_jobscripts
@@ -3583,7 +3584,7 @@ class BaseApp(metaclass=Singleton):
3583
3584
  )
3584
3585
  all_cells["status"] = "/".join(
3585
3586
  js_state.rich_repr
3586
- for js_state in sorted(act_js_states, key=lambda x: x.value)
3587
+ for js_state in sorted(act_js_states, key=lambda x: x.id)
3587
3588
  )
3588
3589
  else:
3589
3590
  if deleted:
@@ -3645,7 +3646,7 @@ class BaseApp(metaclass=Singleton):
3645
3646
  all_cells["actions_compact"] = " | ".join(
3646
3647
  f"[{k.colour}]{k.symbol}[/{k.colour}]:{v}" # type: ignore
3647
3648
  for k, v in dict(
3648
- sorted(EAR_stat_count.items(), key=lambda x: x[0].value)
3649
+ sorted(EAR_stat_count.items(), key=lambda x: x[0].id)
3649
3650
  ).items()
3650
3651
  )
3651
3652
  else:
hpcflow/sdk/cli.py CHANGED
@@ -4,6 +4,7 @@ Command line interface implementation.
4
4
 
5
5
  from __future__ import annotations
6
6
  import contextlib
7
+ import datetime
7
8
  import json
8
9
  import os
9
10
  import time
@@ -772,6 +773,12 @@ def _make_submission_CLI(app: BaseApp):
772
773
  def get_login_nodes(scheduler: SGEPosix):
773
774
  pprint(scheduler.get_login_nodes())
774
775
 
776
+ class _DateTimeJSONEncoder(json.JSONEncoder):
777
+ def default(self, obj):
778
+ if isinstance(obj, datetime.datetime):
779
+ return obj.isoformat()
780
+ return super().default(obj)
781
+
775
782
  @submission.command()
776
783
  @click.option(
777
784
  "as_json",
@@ -784,7 +791,7 @@ def _make_submission_CLI(app: BaseApp):
784
791
  """Print known-submissions information as a formatted Python object."""
785
792
  out = app.get_known_submissions(as_json=as_json)
786
793
  if as_json:
787
- click.echo(json.dumps(out))
794
+ click.echo(json.dumps(out, cls=_DateTimeJSONEncoder))
788
795
  else:
789
796
  pprint(out)
790
797
 
@@ -3017,6 +3017,7 @@ class Action(JSONLike):
3017
3017
  if all(rule.test(element_iteration=element_iter) for rule in cmd.rules)
3018
3018
  ]
3019
3019
 
3020
+ @TimeIt.decorator
3020
3021
  def get_required_executables(self) -> Iterator[str]:
3021
3022
  """Return executable labels required by this action."""
3022
3023
  for command in self.commands:
@@ -11,6 +11,7 @@ from typing import Any, ClassVar, TYPE_CHECKING
11
11
 
12
12
  import numpy as np
13
13
 
14
+ from hpcflow.sdk.log import TimeIt
14
15
  from hpcflow.sdk.typing import hydrate
15
16
  from hpcflow.sdk.core.element import ElementResources
16
17
  from hpcflow.sdk.core.errors import NoCLIFormatMethodError
@@ -416,6 +417,7 @@ class Command(JSONLike):
416
417
  def _extract_executable_labels(cls, cmd_str: str) -> Sequence[str]:
417
418
  return cls.__EXE_RE.findall(cmd_str)
418
419
 
420
+ @TimeIt.decorator
419
421
  def get_required_executables(self) -> Sequence[str]:
420
422
  """Return executable labels required by this command."""
421
423
  # an executable label might appear in the `command` or `executable` attribute:
hpcflow/sdk/core/enums.py CHANGED
@@ -26,45 +26,85 @@ class ActionScopeType(Enum):
26
26
 
27
27
 
28
28
  @dataclass(frozen=True)
29
- class _EARStatus:
29
+ class _ReportableStateData:
30
30
  """
31
- Model of the state of an EARStatus.
31
+ Model of the state of something that is renderable using a symbol and colour.
32
+
33
+ Notes
34
+ -----
35
+ This class is used as the value in the the enumeration subclasses
36
+ :py:class:`EARStatus` and :py:class:`JobscriptElementState`.
37
+
32
38
  """
33
39
 
34
- _value: int
35
- #: Symbol to use when rendering a status.
40
+ #: ID that distinguishes the state.
41
+ id: int
42
+ #: Symbol to use when rendering a state.
36
43
  symbol: str
37
- #: Colour to use when rendering a status.
44
+ #: Colour to use when rendering a state.
38
45
  colour: str
46
+ #: Documentation of the meaning of the state.
39
47
  __doc__: str = ""
40
48
 
41
49
 
42
- class EARStatus(_EARStatus, Enum):
50
+ class _ReportableStateEnum(Enum):
51
+ """Enumeration superclass for reportable state subclasses with some shared methods."""
52
+
53
+ @property
54
+ def id(self) -> int:
55
+ """
56
+ The integer ID associated with this state.
57
+ """
58
+ return self.value.id
59
+
60
+ @property
61
+ def colour(self) -> str:
62
+ """
63
+ The colour associated with this state.
64
+ """
65
+ return self.value.colour
66
+
67
+ @property
68
+ def symbol(self) -> str:
69
+ """
70
+ The symbol associated with this state.
71
+ """
72
+ return self.value.symbol
73
+
74
+ @property
75
+ def rich_repr(self) -> str:
76
+ """
77
+ Rich representation of this enumeration element.
78
+ """
79
+ return f"[{self.colour}]{self.symbol}[/{self.colour}]"
80
+
81
+
82
+ class EARStatus(_ReportableStateEnum):
43
83
  """Enumeration of all possible EAR statuses, and their associated status colour."""
44
84
 
45
85
  #: Not yet associated with a submission.
46
- pending = (
86
+ pending = _ReportableStateData(
47
87
  0,
48
88
  ".",
49
89
  "grey46",
50
90
  "Not yet associated with a submission.",
51
91
  )
52
92
  #: Associated with a prepared submission that is not yet submitted.
53
- prepared = (
93
+ prepared = _ReportableStateData(
54
94
  1,
55
95
  ".",
56
96
  "grey46",
57
97
  "Associated with a prepared submission that is not yet submitted.",
58
98
  )
59
99
  #: Submitted for execution.
60
- submitted = (
100
+ submitted = _ReportableStateData(
61
101
  2,
62
102
  ".",
63
103
  "grey46",
64
104
  "Submitted for execution.",
65
105
  )
66
106
  #: Executing now.
67
- running = (
107
+ running = _ReportableStateData(
68
108
  3,
69
109
  "●",
70
110
  "dodger_blue1",
@@ -72,7 +112,7 @@ class EARStatus(_EARStatus, Enum):
72
112
  )
73
113
  #: Not attempted due to a failure of an upstream action on which this depends,
74
114
  #: or a loop termination condition being satisfied.
75
- skipped = (
115
+ skipped = _ReportableStateData(
76
116
  4,
77
117
  "s",
78
118
  "dark_orange",
@@ -82,32 +122,27 @@ class EARStatus(_EARStatus, Enum):
82
122
  ),
83
123
  )
84
124
  #: Aborted by the user; downstream actions will be attempted.
85
- aborted = (
125
+ aborted = _ReportableStateData(
86
126
  5,
87
127
  "A",
88
128
  "deep_pink4",
89
129
  "Aborted by the user; downstream actions will be attempted.",
90
130
  )
91
131
  #: Probably exited successfully.
92
- success = (
132
+ success = _ReportableStateData(
93
133
  6,
94
134
  "■",
95
135
  "green3",
96
136
  "Probably exited successfully.",
97
137
  )
98
138
  #: Probably failed.
99
- error = (
139
+ error = _ReportableStateData(
100
140
  7,
101
141
  "E",
102
142
  "red3",
103
143
  "Probably failed.",
104
144
  )
105
145
 
106
- @property
107
- def value(self) -> int:
108
- #: The value of the status.
109
- return self._value
110
-
111
146
  @classmethod
112
147
  def get_non_running_submitted_states(cls) -> frozenset[EARStatus]:
113
148
  """Return the set of all non-running states, excluding those before submission."""
@@ -120,13 +155,6 @@ class EARStatus(_EARStatus, Enum):
120
155
  }
121
156
  )
122
157
 
123
- @property
124
- def rich_repr(self) -> str:
125
- """
126
- The rich representation of the value.
127
- """
128
- return f"[{self.colour}]{self.symbol}[/{self.colour}]"
129
-
130
158
 
131
159
  class InputSourceType(Enum):
132
160
  """
@@ -1584,7 +1584,7 @@ class MultiPathSequence(_BaseSequence):
1584
1584
  paths: Sequence[str],
1585
1585
  num_samples: int,
1586
1586
  *,
1587
- bounds: dict[str, Sequence[float]] | None = None,
1587
+ bounds: dict[str, dict[str, str | Sequence[float]]] | None = None,
1588
1588
  scramble: bool = True,
1589
1589
  strength: int = 1,
1590
1590
  optimization: Literal["random-cd", "lloyd"] | None = None,
@@ -1602,10 +1602,20 @@ class MultiPathSequence(_BaseSequence):
1602
1602
 
1603
1603
  bounds = bounds or {}
1604
1604
 
1605
- parameter_ranges = np.array([bounds.get(path, [0, 1]) for path in paths]).T
1605
+ scaling = np.asarray(
1606
+ [bounds.get(path, {}).get("scaling", "linear") for path in paths]
1607
+ )
1608
+
1609
+ # extents including defaults for unspecified:
1610
+ all_extents = [bounds.get(path, {}).get("extent", [0, 1]) for path in paths]
1606
1611
 
1607
- lower_bound = parameter_ranges[0]
1608
- upper_bound = parameter_ranges[1]
1612
+ # extents accounting for scaling type:
1613
+ extent = np.asarray(
1614
+ [
1615
+ np.log10(all_extents[i]) if scaling[i] == "log" else all_extents[i]
1616
+ for i in range(len(scaling))
1617
+ ]
1618
+ ).T
1609
1619
 
1610
1620
  try:
1611
1621
  sampler = LatinHypercube(**kwargs)
@@ -1614,9 +1624,15 @@ class MultiPathSequence(_BaseSequence):
1614
1624
  kwargs["seed"] = kwargs.pop("rng")
1615
1625
  sampler = LatinHypercube(**kwargs)
1616
1626
 
1617
- samples = scale(sampler.random(n=num_samples), lower_bound, upper_bound).T
1627
+ samples = scale(
1628
+ sampler.random(n=num_samples), l_bounds=extent[0], u_bounds=extent[1]
1629
+ )
1630
+
1631
+ for i in range(len(scaling)):
1632
+ if scaling[i] == "log":
1633
+ samples[:, i] = 10 ** samples[:, i]
1618
1634
 
1619
- return samples
1635
+ return samples.T
1620
1636
 
1621
1637
  @classmethod
1622
1638
  def from_latin_hypercube(
@@ -1624,7 +1640,7 @@ class MultiPathSequence(_BaseSequence):
1624
1640
  paths: Sequence[str],
1625
1641
  num_samples: int,
1626
1642
  *,
1627
- bounds: dict[str, Sequence[float]] | None = None,
1643
+ bounds: dict[str, dict[str, str | Sequence[float]]] | None = None,
1628
1644
  scramble: bool = True,
1629
1645
  strength: int = 1,
1630
1646
  optimization: Literal["random-cd", "lloyd"] | None = None,
@@ -1634,6 +1650,32 @@ class MultiPathSequence(_BaseSequence):
1634
1650
  ) -> Self:
1635
1651
  """
1636
1652
  Generate values from SciPy's latin hypercube sampler: :class:`scipy.stats.qmc.LatinHypercube`.
1653
+
1654
+ Parameters
1655
+ ----------
1656
+ paths : Sequence[str]
1657
+ List of dot-delimited paths within the parameter's nested data structure for which
1658
+ 'value' should be set.
1659
+ num_samples : int
1660
+ Number of random hypercube samples to take.
1661
+ bounds : dict[str, dict[str, str | Sequence[float]]] | None, optional
1662
+ Bounds dictionary structure which takes a path as a key and returns another dictionary
1663
+ which takes `scaling` and `extent` as keys. `extent` defines the width of the parameter
1664
+ space, and `scaling` defines whether to take logarithmically spaced samples ("log") or not ("linear"). By default,
1665
+ linear scaling and an extent between 0 and 1 is used.
1666
+ scramble : bool, optional
1667
+ See `scipy.stats.qmc.LatinHypercube`, by default True
1668
+ strength : int, optional
1669
+ See 'scipy.stats.qmc.LatinHypercube', by default 1
1670
+ optimization : Literal["random, optional
1671
+ See 'scipy.stats.qmc.LatinHypercube', by default None
1672
+ rng : _type_, optional
1673
+ See 'scipy.stats.qmc.LatinHypercube', by default None
1674
+
1675
+ Returns
1676
+ -------
1677
+ NDArray
1678
+ Array of hypercube samples.
1637
1679
  """
1638
1680
  kwargs = {
1639
1681
  "paths": paths,
@@ -1786,6 +1786,20 @@ class Workflow(AppAware):
1786
1786
  elem: int
1787
1787
  task: int
1788
1788
 
1789
+ @TimeIt.decorator
1790
+ def __get_elements_by_task_idx(
1791
+ self, element_idx_by_task: dict[int, set[int]]
1792
+ ) -> dict[int, dict[int, Element]]:
1793
+ return {
1794
+ task_idx: {
1795
+ idx: element
1796
+ for idx, element in zip(
1797
+ elem_indices, self.tasks[task_idx].elements[list(elem_indices)]
1798
+ )
1799
+ }
1800
+ for task_idx, elem_indices in element_idx_by_task.items()
1801
+ }
1802
+
1789
1803
  @TimeIt.decorator
1790
1804
  def get_elements_from_IDs(self, id_lst: Iterable[int]) -> list[Element]:
1791
1805
  """Return element objects from a list of IDs."""
@@ -1800,10 +1814,7 @@ class Workflow(AppAware):
1800
1814
  index_paths.append(Workflow._IndexPath1(elem_idx, task.index))
1801
1815
  element_idx_by_task[task.index].add(elem_idx)
1802
1816
 
1803
- elements_by_task = {
1804
- task_idx: {idx: self.tasks[task_idx].elements[idx] for idx in elem_idxes}
1805
- for task_idx, elem_idxes in element_idx_by_task.items()
1806
- }
1817
+ elements_by_task = self.__get_elements_by_task_idx(element_idx_by_task)
1807
1818
 
1808
1819
  return [elements_by_task[path.task][path.elem] for path in index_paths]
1809
1820
 
@@ -1832,10 +1843,7 @@ class Workflow(AppAware):
1832
1843
  index_paths.append(Workflow._IndexPath2(iter_idx, elem_idx, task.index))
1833
1844
  element_idx_by_task[task.index].add(elem_idx)
1834
1845
 
1835
- elements_by_task = {
1836
- task_idx: {idx: self.tasks[task_idx].elements[idx] for idx in elem_idx}
1837
- for task_idx, elem_idx in element_idx_by_task.items()
1838
- }
1846
+ elements_by_task = self.__get_elements_by_task_idx(element_idx_by_task)
1839
1847
 
1840
1848
  return [
1841
1849
  elements_by_task[path.task][path.elem].iterations[path.iter]
@@ -3627,7 +3635,8 @@ class Workflow(AppAware):
3627
3635
  if status:
3628
3636
  status.update("Adding new submission: resolving jobscripts...")
3629
3637
 
3630
- cache = ObjectCache.build(self, elements=True, iterations=True, runs=True)
3638
+ with self._store.cache_ctx():
3639
+ cache = ObjectCache.build(self, elements=True, iterations=True, runs=True)
3631
3640
 
3632
3641
  sub_obj: Submission = self._app.Submission(
3633
3642
  index=new_idx,
@@ -3719,7 +3728,7 @@ class Workflow(AppAware):
3719
3728
 
3720
3729
  """
3721
3730
  with self._app.config.cached_config():
3722
- with self.cached_merged_parameters():
3731
+ with self.cached_merged_parameters(), self._store.cache_ctx():
3723
3732
  js, element_deps = self._resolve_singular_jobscripts(
3724
3733
  cache, tasks, force_array
3725
3734
  )
@@ -1218,12 +1218,15 @@ class PersistentStore(
1218
1218
  @contextlib.contextmanager
1219
1219
  def cache_ctx(self) -> Iterator[None]:
1220
1220
  """Context manager for using the persistent element/iteration/run cache."""
1221
- self._use_cache = True
1222
- try:
1221
+ if self._use_cache:
1223
1222
  yield
1224
- finally:
1225
- self._use_cache = False
1226
- self._reset_cache()
1223
+ else:
1224
+ self._use_cache = True
1225
+ try:
1226
+ yield
1227
+ finally:
1228
+ self._use_cache = False
1229
+ self._reset_cache()
1227
1230
 
1228
1231
  @contextlib.contextmanager
1229
1232
  def parameters_metadata_cache(self):
@@ -6,82 +6,56 @@ from __future__ import annotations
6
6
  from dataclasses import dataclass
7
7
  from enum import Enum
8
8
 
9
+ from hpcflow.sdk.core.enums import _ReportableStateData, _ReportableStateEnum
9
10
 
10
- @dataclass(frozen=True)
11
- class _JES:
12
- """
13
- Model of the state of a JobscriptElementState
14
- """
15
-
16
- _value: int
17
- #: The symbol used to render the state.
18
- symbol: str
19
- #: The colour used to render the state.
20
- colour: str
21
- __doc__: str = ""
22
11
 
23
-
24
- class JobscriptElementState(_JES, Enum):
12
+ class JobscriptElementState(_ReportableStateEnum):
25
13
  """Enumeration to convey a particular jobscript element state as reported by the
26
14
  scheduler."""
27
15
 
28
16
  #: Waiting for resource allocation.
29
- pending = (
17
+ pending = _ReportableStateData(
30
18
  0,
31
19
  "○",
32
20
  "yellow",
33
21
  "Waiting for resource allocation.",
34
22
  )
35
23
  #: Waiting for one or more dependencies to finish.
36
- waiting = (
24
+ waiting = _ReportableStateData(
37
25
  1,
38
26
  "◊",
39
27
  "grey46",
40
28
  "Waiting for one or more dependencies to finish.",
41
29
  )
42
30
  #: Executing now.
43
- running = (
31
+ running = _ReportableStateData(
44
32
  2,
45
33
  "●",
46
34
  "dodger_blue1",
47
35
  "Executing now.",
48
36
  )
49
37
  #: Previously submitted but is no longer active.
50
- finished = (
38
+ finished = _ReportableStateData(
51
39
  3,
52
40
  "■",
53
41
  "grey46",
54
42
  "Previously submitted but is no longer active.",
55
43
  )
56
44
  #: Cancelled by the user.
57
- cancelled = (
45
+ cancelled = _ReportableStateData(
58
46
  4,
59
47
  "C",
60
48
  "red3",
61
49
  "Cancelled by the user.",
62
50
  )
63
51
  #: The scheduler reports an error state.
64
- errored = (
52
+ errored = _ReportableStateData(
65
53
  5,
66
54
  "E",
67
55
  "red3",
68
56
  "The scheduler reports an error state.",
69
57
  )
70
58
 
71
- @property
72
- def value(self) -> int:
73
- """
74
- The numerical value of this state.
75
- """
76
- return self._value
77
-
78
- @property
79
- def rich_repr(self) -> str:
80
- """
81
- Rich representation of this enumeration element.
82
- """
83
- return f"[{self.colour}]{self.symbol}[/{self.colour}]"
84
-
85
59
 
86
60
  class SubmissionStatus(Enum):
87
61
  """
@@ -901,6 +901,7 @@ class Jobscript(JSONLike):
901
901
  )
902
902
 
903
903
  @property
904
+ @TimeIt.decorator
904
905
  def all_EAR_IDs(self) -> NDArray:
905
906
  """Return all run IDs of this jobscripts (across all blocks), removing missing
906
907
  run IDs (i.e. -1 values)"""
@@ -912,6 +913,8 @@ class Jobscript(JSONLike):
912
913
  """
913
914
  Description of EAR information for this jobscript.
914
915
  """
916
+ if self.submission._use_EARs_cache:
917
+ return [self.submission._EARs_cache[ear_id] for ear_id in self.all_EAR_IDs]
915
918
  return self.workflow.get_EARs_from_IDs(self.all_EAR_IDs)
916
919
 
917
920
  @property
@@ -11,6 +11,7 @@ from textwrap import indent
11
11
  from typing import Any, Literal, overload, TYPE_CHECKING
12
12
  from typing_extensions import override
13
13
  import warnings
14
+ from contextlib import contextmanager
14
15
 
15
16
 
16
17
  from hpcflow.sdk.utils.strings import shorten_list_str
@@ -135,6 +136,10 @@ class Submission(JSONLike):
135
136
  None # assigned on first access
136
137
  )
137
138
 
139
+ # updated in _submission_EARs_cache context manager:
140
+ self._use_EARs_cache = False
141
+ self._EARs_cache: dict[int, ElementActionRun] = {}
142
+
138
143
  if workflow:
139
144
  #: The workflow this is part of.
140
145
  self.workflow = workflow
@@ -226,6 +231,8 @@ class Submission(JSONLike):
226
231
  del dct["_workflow"]
227
232
  del dct["_index"]
228
233
  del dct["_submission_parts_lst"]
234
+ del dct["_use_EARs_cache"]
235
+ del dct["_EARs_cache"]
229
236
  return {k.lstrip("_"): v for k, v in dct.items()}
230
237
 
231
238
  @property
@@ -265,6 +272,30 @@ class Submission(JSONLike):
265
272
  ]
266
273
  return self._submission_parts_lst
267
274
 
275
+ @property
276
+ @TimeIt.decorator
277
+ def use_EARs_cache(self) -> bool:
278
+ """Whether to pre-cache all EARs associated with the submission."""
279
+ return self._use_EARs_cache
280
+
281
+ @use_EARs_cache.setter
282
+ @TimeIt.decorator
283
+ def use_EARs_cache(self, value: bool):
284
+ """Toggle the EAR caching facility."""
285
+ if self._use_EARs_cache == value:
286
+ return
287
+ self._use_EARs_cache = value
288
+ if value:
289
+ all_EAR_IDs = list(self.all_EAR_IDs)
290
+ self._EARs_cache = {
291
+ ear_ID: ear
292
+ for ear_ID, ear in zip(
293
+ all_EAR_IDs, self.workflow.get_EARs_from_IDs(all_EAR_IDs)
294
+ )
295
+ }
296
+ else:
297
+ self._EARs_cache = {} # reset the cache
298
+
268
299
  @TimeIt.decorator
269
300
  def get_start_time(self, submit_time: str) -> datetime | None:
270
301
  """Get the start time of a given submission part."""
@@ -283,17 +314,36 @@ class Submission(JSONLike):
283
314
  @TimeIt.decorator
284
315
  def start_time(self) -> datetime | None:
285
316
  """Get the first non-None start time over all submission parts."""
286
- times = (
287
- self.get_start_time(submit_time) for submit_time in self._submission_parts
288
- )
289
- return min((t for t in times if t is not None), default=None)
317
+ with self.using_EARs_cache():
318
+ times = (
319
+ self.get_start_time(submit_time) for submit_time in self._submission_parts
320
+ )
321
+ return min((t for t in times if t is not None), default=None)
290
322
 
291
323
  @property
292
324
  @TimeIt.decorator
293
325
  def end_time(self) -> datetime | None:
294
326
  """Get the final non-None end time over all submission parts."""
295
- times = (self.get_end_time(submit_time) for submit_time in self._submission_parts)
296
- return max((t for t in times if t is not None), default=None)
327
+ with self.using_EARs_cache():
328
+ times = (
329
+ self.get_end_time(submit_time) for submit_time in self._submission_parts
330
+ )
331
+ return max((t for t in times if t is not None), default=None)
332
+
333
+ @contextmanager
334
+ def using_EARs_cache(self):
335
+ """
336
+ A context manager to load and cache all EARs associated with this submission (and
337
+ its jobscripts).
338
+ """
339
+ if self.use_EARs_cache:
340
+ yield
341
+ else:
342
+ self.use_EARs_cache = True
343
+ try:
344
+ yield
345
+ finally:
346
+ self.use_EARs_cache = False
297
347
 
298
348
  @property
299
349
  def jobscripts(self) -> list[Jobscript]:
@@ -592,15 +642,18 @@ class Submission(JSONLike):
592
642
  """
593
643
  The IDs of all EARs in this submission.
594
644
  """
595
- return (i for js in self.jobscripts for i in js.all_EAR_IDs)
645
+ return (int(i) for js in self.jobscripts for i in js.all_EAR_IDs)
596
646
 
597
647
  @property
598
648
  @TimeIt.decorator
599
- def all_EARs(self) -> Iterable[ElementActionRun]:
649
+ def all_EARs(self) -> list[ElementActionRun]:
600
650
  """
601
651
  All EARs in this submission.
602
652
  """
603
- return (ear for js in self.jobscripts for ear in js.all_EARs)
653
+ if self.use_EARs_cache:
654
+ return list(self._EARs_cache.values())
655
+ else:
656
+ return self.workflow.get_EARs_from_IDs(self.all_EAR_IDs)
604
657
 
605
658
  @property
606
659
  @TimeIt.decorator
@@ -610,9 +663,10 @@ class Submission(JSONLike):
610
663
  @property
611
664
  @TimeIt.decorator
612
665
  def all_EARs_by_jobscript(self) -> list[list[ElementActionRun]]:
613
- ids = [i.all_EAR_IDs for i in self.jobscripts]
614
- all_EARs = {i.id_: i for i in self.workflow.get_EARs_from_IDs(self.all_EAR_IDs)}
615
- return [[all_EARs[i] for i in js_ids] for js_ids in ids]
666
+ all_EARs = {i.id_: i for i in self.all_EARs}
667
+ return [
668
+ [all_EARs[i] for i in js_ids] for js_ids in self.all_EARs_IDs_by_jobscript
669
+ ]
616
670
 
617
671
  @property
618
672
  @TimeIt.decorator
@@ -650,11 +704,12 @@ class Submission(JSONLike):
650
704
  """Get jobscripts that are active on this machine, and their active states."""
651
705
  # this returns: {JS_IDX: {BLOCK_IDX: {JS_ELEMENT_IDX: STATE}}}
652
706
  # TODO: query the scheduler once for all jobscripts?
653
- return {
654
- js.index: act_states
655
- for js in self.jobscripts
656
- if (act_states := js.get_active_states(as_json=as_json))
657
- }
707
+ with self.using_EARs_cache():
708
+ return {
709
+ js.index: act_states
710
+ for js in self.jobscripts
711
+ if (act_states := js.get_active_states(as_json=as_json))
712
+ }
658
713
 
659
714
  @TimeIt.decorator
660
715
  def _write_scripts(
@@ -172,25 +172,32 @@ def test_MPS_latin_hypercube_sequence_values():
172
172
 
173
173
  def test_MPS_latin_hypercube_sequence_bounds():
174
174
 
175
- bounds = {"inputs.a": [16789.2, 17812.5]}
175
+ bounds = {
176
+ "inputs.a": {"extent": [16789.2, 17812.5], "scaling": "linear"},
177
+ "inputs.c": {"extent": [1.0e-10, 1.0e-5], "scaling": "log"},
178
+ }
176
179
 
177
180
  mps = hf.MultiPathSequence.from_latin_hypercube(
178
- paths=["inputs.a", "inputs.b"],
181
+ paths=["inputs.a", "inputs.b", "inputs.c"],
179
182
  num_samples=10,
180
183
  bounds=bounds,
181
184
  )
182
185
 
183
186
  vals_arr = np.array(mps.values)
184
- assert vals_arr.shape == (2, 10)
185
187
 
186
- vals_0 = vals_arr[0]
187
- bounds_0 = list(bounds.values())[0]
188
+ assert vals_arr.shape == (3, 10)
188
189
 
189
- vals_1 = vals_arr[1]
190
- bounds_1 = [0, 1]
190
+ vals_a = vals_arr[0]
191
+ vals_b = vals_arr[1]
192
+ vals_c = vals_arr[2]
191
193
 
192
- assert np.logical_and(vals_0 > bounds_0[0], vals_0 < bounds_0[1]).all()
193
- assert np.logical_and(vals_1 > bounds_1[0], vals_1 < bounds_1[1]).all()
194
+ extent_a = bounds["inputs.a"]["extent"]
195
+ extent_b = [0.0, 1.0]
196
+ extent_c = bounds["inputs.c"]["extent"]
197
+
198
+ assert np.logical_and(vals_a > extent_a[0], vals_a < extent_a[1]).all()
199
+ assert np.logical_and(vals_b > extent_b[0], vals_b < extent_b[1]).all()
200
+ assert np.logical_and(vals_c > extent_c[0], vals_c < extent_c[1]).all()
194
201
 
195
202
 
196
203
  def test_MPS_move_from_sequences_list():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hpcflow-new2
3
- Version: 0.2.0a223
3
+ Version: 0.2.0a228
4
4
  Summary: Computational workflow management
5
5
  License: MPL-2.0
6
6
  Author: aplowman
@@ -1,7 +1,7 @@
1
1
  hpcflow/__init__.py,sha256=WIETuRHeOp2SqUqHUzpjQ-lk9acbYv-6aWOhZPRdlhs,64
2
2
  hpcflow/__pyinstaller/__init__.py,sha256=YOzBlPSck6slucv6lJM9K80JtsJWxXRL00cv6tRj3oc,98
3
3
  hpcflow/__pyinstaller/hook-hpcflow.py,sha256=P2b-8QdQqkSS7cJB6CB3CudUuJ9iZzTh2fQF4hNdCa4,1118
4
- hpcflow/_version.py,sha256=ExqG_C9ZO0pdvjMEloB9_jw5T23HBOJ1y0ajtaOl5UY,26
4
+ hpcflow/_version.py,sha256=YvMcTgWRlIcTbAldeVKZfYALuAqq320nrXFTTSaItfU,26
5
5
  hpcflow/app.py,sha256=gl2viVS65PbpDhUp2DARaYHFDqDWQjuoyB3ikrCNRW4,1367
6
6
  hpcflow/cli.py,sha256=G2J3D9v6MnMWOWMMWK6UEKLn_6wnV9lT_qygEBBxg-I,66
7
7
  hpcflow/data/demo_data_manifest/__init__.py,sha256=Hsq0jT8EXM13wu1MpGy5FQgyuz56ygep4VWOnulFn50,41
@@ -64,8 +64,8 @@ hpcflow/data/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
64
64
  hpcflow/data/workflows/workflow_1.yaml,sha256=lF7Re2SVc_5gQk5AwB0gXaq-n-T5ia4su3zNQ9oMRV0,220
65
65
  hpcflow/examples.ipynb,sha256=cLKp4QsxwwMXRanDnfWY9kqsV23q6G4raOpu6IZXnMw,28553
66
66
  hpcflow/sdk/__init__.py,sha256=BEbIz8adCAX06ZmvEcquEu29PmQDvVP1I1qn_q78dYc,6289
67
- hpcflow/sdk/app.py,sha256=ZFMnEV43dlZlnTpJNzBsfRZ1ndiMGyeJ3hKxlhrm39I,146726
68
- hpcflow/sdk/cli.py,sha256=C_7Ts6TbspTZtUXlPwF37Vt-t71NABJ_2zNpZLYFPUE,45563
67
+ hpcflow/sdk/app.py,sha256=UWnpxdEqVD07VE-Rr23xhVHesF3zZI6ryLJ1-yXKp7M,146820
68
+ hpcflow/sdk/cli.py,sha256=qMFACsIA32gD2oLC8_FD9JjFYgidCoGxOH-L3qM2xx0,45818
69
69
  hpcflow/sdk/cli_common.py,sha256=MO0TbC280JdZTLnxSV15wE7PDNx83sotpJsyAN1E2F4,10458
70
70
  hpcflow/sdk/config/__init__.py,sha256=pkufl_CXm_Kswk1x7LTavuplWjUbUCitK0AJ2E60gwM,137
71
71
  hpcflow/sdk/config/callbacks.py,sha256=cuW31ZW0YTOi5K5P-NYHuiK1YLsglMEH3c_u6mddVE8,8078
@@ -75,13 +75,13 @@ hpcflow/sdk/config/config_file.py,sha256=wfDjozV9lXTnaxQoe-z-1Y7dtO0N4o1kgEwKtrL
75
75
  hpcflow/sdk/config/errors.py,sha256=Ft0e0rqIh9NoxWu3R8RSHel2fLONrXrgmFdNGZ_nOac,8341
76
76
  hpcflow/sdk/config/types.py,sha256=hw8latN2PqgGB_cOcD2pmgf5hSE7Dl94xLGKosydpCo,3935
77
77
  hpcflow/sdk/core/__init__.py,sha256=uxvGIUmzvFvylxFL4OGoKHs8GABHHPFaki-Xyc7Nquk,861
78
- hpcflow/sdk/core/actions.py,sha256=zLsCmeKW8dmRZ-6MEkO8M4kboFxldASLu0IdCwGU-uk,118467
78
+ hpcflow/sdk/core/actions.py,sha256=ZoM-2L9qPc52fi26YBDwKHr1axPqhSI_89lRPsmnokM,118489
79
79
  hpcflow/sdk/core/app_aware.py,sha256=SnQCix09IHhqPAHBpbdhQAf3wDKvO6n6ym4XJ8vrv1s,569
80
80
  hpcflow/sdk/core/cache.py,sha256=6b0emvJGP-yf5UxKmEY_s4_gdyUfaNaXafq-qQaoBfU,8174
81
81
  hpcflow/sdk/core/command_files.py,sha256=LcrK7wjQnxmTDUrsC0Xgbjsy1NGodf62LrrTSNAAxxE,24942
82
- hpcflow/sdk/core/commands.py,sha256=vFyZ9Cv_tvGqSGtVEnAHAeXA-HAxWoF0nmf1e0bFCvs,15993
82
+ hpcflow/sdk/core/commands.py,sha256=mm4FVTpkjVXxfCOlyHcfEPy2DrBkhPaOaq0gk5ExvKA,16050
83
83
  hpcflow/sdk/core/element.py,sha256=pPRezx0HATKH55qEstXp1xOG4S8oGLVnJ3sQcEmLsXo,68152
84
- hpcflow/sdk/core/enums.py,sha256=BV_cFqTWkME3zVLswmb21QXeIiCBb9_9avr6VIhL3HM,4387
84
+ hpcflow/sdk/core/enums.py,sha256=scp7b7gbFSefu1bks2M2u6amq6G_lB_j_h8U_TPM4bU,5316
85
85
  hpcflow/sdk/core/environment.py,sha256=m0Qp4Y1XcPJ1yPrdHp6RJv9_H61nA9Qo2ZJV_-wGne8,7959
86
86
  hpcflow/sdk/core/errors.py,sha256=8PxlFewrRZU2MSPoNUiQ2jAX9iQTr9-Qr-O5hDywU_A,28869
87
87
  hpcflow/sdk/core/execute.py,sha256=Gc2SauA4QNA1W4Gi2FYoKoqkoJtGg57cgn1p87WNCIs,7221
@@ -89,7 +89,7 @@ hpcflow/sdk/core/json_like.py,sha256=tzCpaarGCHr_NXA-vLcX1jrDYlWbZuAbUPjR6jREhX8
89
89
  hpcflow/sdk/core/loop.py,sha256=vs7pNVe3Io5zNO6Xl_Tqmra9mCJUw8IESLZC4p2QFvo,50084
90
90
  hpcflow/sdk/core/loop_cache.py,sha256=C0t9UpvHFdTmw3bViZ2wccjZWtkbSbN9APvcwzBSREg,10801
91
91
  hpcflow/sdk/core/object_list.py,sha256=WvUBb-2DA7-7_7oNoPykSsTihVbGB-1cgHh8W78IzKI,29882
92
- hpcflow/sdk/core/parameters.py,sha256=5iuqTCz9ZxQnhgDElDIRBB32PDSHnqMJ1701v-M5GVI,98597
92
+ hpcflow/sdk/core/parameters.py,sha256=BlRSdPp_fA4jOfBmaIZ45Y-jGouMYz4tnn5J8Pcmv4w,100384
93
93
  hpcflow/sdk/core/rule.py,sha256=LiNyKp09a6TRGJvtiB1CcNqgyAIuPhuSW2RVfDWf5FE,6274
94
94
  hpcflow/sdk/core/run_dir_files.py,sha256=yh49BSlswqqyr97x5nDT87qu2MlOLMu7Arzl_B-fxy0,2061
95
95
  hpcflow/sdk/core/skip_reason.py,sha256=xPm8bd8sbPpiZ3sx9ZeW0jxgRQ0TBokSv0XIJKrEW2Y,113
@@ -99,7 +99,7 @@ hpcflow/sdk/core/test_utils.py,sha256=UXe8P-1b_6Pn6vdgJfMVsTGJdVI65TZ2mSUgWHlVnn
99
99
  hpcflow/sdk/core/types.py,sha256=kqVXpiH8qwOVVBAw6GFvUQVdNYLEv7qIbgEkgfK0DO8,13021
100
100
  hpcflow/sdk/core/utils.py,sha256=EFflyTr6Ju8v0AfZYQuFnJm-hNjtx0XbnFuccM7cV-k,36950
101
101
  hpcflow/sdk/core/validation.py,sha256=4-0g5z3CgjFLQ2FCM0p0mjvNqHYuLhIqvQR_kpM0tsU,1838
102
- hpcflow/sdk/core/workflow.py,sha256=aM4JTfaIhtDA3dOeNC4ThTA1L7_EGOyBOPMcvf6KjC8,183779
102
+ hpcflow/sdk/core/workflow.py,sha256=qZdQm0AxcV9XhZ2WrzqZNkPOTyWh9ifsZmoYMasH_q0,184085
103
103
  hpcflow/sdk/core/zarr_io.py,sha256=i6WqkFXe-q1sJGTCYAbsNRXRrdkxZy6NRahTcuZPuX4,5906
104
104
  hpcflow/sdk/data/__init__.py,sha256=-YzROirohSKU2UGYj5vkCe_J2KejbzhIjUXNaJwKHLk,568
105
105
  hpcflow/sdk/data/config_file_schema.yaml,sha256=7i3z_m3GBRtLyB4c7qPngnlQWqcIq1CyCcOysDyq4es,791
@@ -117,7 +117,7 @@ hpcflow/sdk/helper/helper.py,sha256=T45BEwhPgTlwvqDNbT4Us4UVmdRKRoszIMt8a9B69kE,
117
117
  hpcflow/sdk/helper/watcher.py,sha256=aQyuSnBiPhsp1ijBylwR5mxjIqlwD4rT_zH9DYc83no,5744
118
118
  hpcflow/sdk/log.py,sha256=_wqB0qt65nt4oNNV6eyD39bNZyciyH6biyyOgU_MViQ,8285
119
119
  hpcflow/sdk/persistence/__init__.py,sha256=1e6nVADj6xEsE7XGXHaIsHZur4IkUJAxLuxBlvHi0CE,408
120
- hpcflow/sdk/persistence/base.py,sha256=y7L7FFyuTJvy9K_CRFZZH8Cost2seiNz_KKaKZ2XREo,92727
120
+ hpcflow/sdk/persistence/base.py,sha256=WBkVpbFsNiGjhSNTqmlBs3GO_3yMWCGufSbATYaNlbE,92811
121
121
  hpcflow/sdk/persistence/defaults.py,sha256=hpZk2R9HFqo7YTGaIlts6CGBRzQLn-JXebWc2ZHLBe4,129
122
122
  hpcflow/sdk/persistence/discovery.py,sha256=jEREI651S3FK-Qoz150oIHP72dsuhCFawXRQIy6j_WQ,1320
123
123
  hpcflow/sdk/persistence/json.py,sha256=QUa_xwgcspZWXi6zlIusVRnh4TzDa4xU5UCDcCU6BXE,36746
@@ -128,8 +128,8 @@ hpcflow/sdk/persistence/utils.py,sha256=TuQp0BKwiy1-5WTgiMaYXn00bZb6HWHAjs4BBFE9
128
128
  hpcflow/sdk/persistence/zarr.py,sha256=ozJSx0W8L5yVRVvF7gM7M8psF5vd_-dNDnXNa-Rh3sI,88979
129
129
  hpcflow/sdk/runtime.py,sha256=vPNu4_DXYMnRyJenkVLq8yt6_XG4tCuwX69Oew4OWmo,11269
130
130
  hpcflow/sdk/submission/__init__.py,sha256=79xJXVkddsuj3uJz3BV9iOnAV7vCeJvLrhKccOA_dnU,67
131
- hpcflow/sdk/submission/enums.py,sha256=DykNHISQDmfa0oDggj6iIKugeFXJd8iF9NwqV3MN2oA,2049
132
- hpcflow/sdk/submission/jobscript.py,sha256=QvvJAwOAYQ6gkNkRAU1Ke4zJnQd6OneMUkAJapmPMGw,90846
131
+ hpcflow/sdk/submission/enums.py,sha256=W6KDSkDBs-DYj5yZeU0Yt42R7LmomHKJTtPh6jPx3aE,1667
132
+ hpcflow/sdk/submission/jobscript.py,sha256=xxrrcYnmmzyt1nZadIPLdO-ByHYibUJgHk5xfxm7onE,91000
133
133
  hpcflow/sdk/submission/schedulers/__init__.py,sha256=E-XBhSMflInTrQ5ZBUy8Hhq-JtAuIcCx6IM_7fI1Axw,9291
134
134
  hpcflow/sdk/submission/schedulers/direct.py,sha256=b_LS_xB0vu2tRHs-Xd08-16PC-nojRB6aNXwT9qtv0A,7493
135
135
  hpcflow/sdk/submission/schedulers/sge.py,sha256=WRohm-1eBEcadcKGg5QPd1DHvGEUV759IVtLbbDx2qU,13927
@@ -140,7 +140,7 @@ hpcflow/sdk/submission/shells/base.py,sha256=orjshO0jBmcpt2frMk41Cyc6CeFz7mjiiYu
140
140
  hpcflow/sdk/submission/shells/bash.py,sha256=x-0hBRX1OlpylWcrHlSS72Bxw8us2ZY7m2PbgWml0-c,16351
141
141
  hpcflow/sdk/submission/shells/os_version.py,sha256=_RK5A56png1Zk4_rzumy17xZ4xVguuPMS0Mll99SKaA,3521
142
142
  hpcflow/sdk/submission/shells/powershell.py,sha256=OFxSEC9Quh-WDFOeYNCQLX_vg-VzO8KdpsYCS9AQfo0,11725
143
- hpcflow/sdk/submission/submission.py,sha256=nbbSxf6Kkn2omsZLk5ZQ1PiW4mMttxR_EH7KPLOAXCQ,51985
143
+ hpcflow/sdk/submission/submission.py,sha256=_sCV88L6yFE4BWNpnDjMYzabD_i95IiYqPos0kDOj2s,53659
144
144
  hpcflow/sdk/submission/types.py,sha256=8xYzjrFbRlTLtZoOwZJZeY3mDI4nCJlLSJ4ekbDoGVI,4220
145
145
  hpcflow/sdk/typing.py,sha256=gu2h7a0RJs_r5UtV8AFVh9TNjo8kGe1n6UOxeH3zdgg,5060
146
146
  hpcflow/sdk/utils/arrays.py,sha256=7znIaqfl_bGElZ6U_da0vumWPez6ITyNrSDu_AlACDI,2084
@@ -185,7 +185,7 @@ hpcflow/tests/unit/test_jobscript_unit.py,sha256=E_uszw3-3Lf2nBcIUZUngMMIgd0S1X6
185
185
  hpcflow/tests/unit/test_json_like.py,sha256=b0_BHW0zfBmc_J848s0ZcYIl4NJkTxNciiAMMRvUCtA,30434
186
186
  hpcflow/tests/unit/test_loop.py,sha256=pN2xVcHhDPYOxR0WA9knVP0F3_PQ3qiXu97wmX32Byc,90046
187
187
  hpcflow/tests/unit/test_meta_task.py,sha256=FEWLuUrP-6e6BO0CRJe2fQDDOZUjz1jfljL_-V7n5Gs,9592
188
- hpcflow/tests/unit/test_multi_path_sequences.py,sha256=6cVYD2e7jMt-m4sr3Z6ixnKewvs-s--slP344trKIWM,9079
188
+ hpcflow/tests/unit/test_multi_path_sequences.py,sha256=eodhujCQkN3DWSyrUHxNlcrIHSOOBE9BnGtRyQnEunc,9362
189
189
  hpcflow/tests/unit/test_object_list.py,sha256=uWBou5QCyScaM3DLqL_7RCguMdYZKMkeZR0V8Lpterk,3342
190
190
  hpcflow/tests/unit/test_parameter.py,sha256=_ImfDhmroXzPKwIuKiehsAw9qQPPHC9FQa8jN7OJqoA,7440
191
191
  hpcflow/tests/unit/test_persistence.py,sha256=bpBiL4YPMosbZk18Ug1JRqYJ69HHJzDYUff_2wDi-5w,18544
@@ -218,8 +218,8 @@ hpcflow/tests/workflows/test_submission.py,sha256=SUbBUbD8C8LSulrI7aETkzP9RqED48
218
218
  hpcflow/tests/workflows/test_workflows.py,sha256=9z3rtXjA5iMOp4C0q4TkD_9kLzwourCY-obpeOtnNt0,18927
219
219
  hpcflow/tests/workflows/test_zip.py,sha256=MzEwsIAYV_1A3bD0XRo23zUwUKVzkkmNd8_cil6YdWQ,578
220
220
  hpcflow/viz_demo.ipynb,sha256=6D9uBbWK3oMfbaf93Tnv5riFPtW-2miUTWNr9kGcnd4,228913
221
- hpcflow_new2-0.2.0a223.dist-info/LICENSE,sha256=Xhxf_KsrJNJFGMogumZhXSTPhUOVHCWf7nU-TDzqg0E,16763
222
- hpcflow_new2-0.2.0a223.dist-info/METADATA,sha256=yMUy1XOWyJ9GWx93QPFA-RlqpLVVl7YeXikl2Vx7zIc,2663
223
- hpcflow_new2-0.2.0a223.dist-info/WHEEL,sha256=kLuE8m1WYU0Ig0_YEGrXyTtiJvKPpLpDEiChiNyei5Y,88
224
- hpcflow_new2-0.2.0a223.dist-info/entry_points.txt,sha256=aoGtCnFdfPcXfBdu2zZyMOJoz6fPgdR0elqsgrE-USU,106
225
- hpcflow_new2-0.2.0a223.dist-info/RECORD,,
221
+ hpcflow_new2-0.2.0a228.dist-info/LICENSE,sha256=Xhxf_KsrJNJFGMogumZhXSTPhUOVHCWf7nU-TDzqg0E,16763
222
+ hpcflow_new2-0.2.0a228.dist-info/METADATA,sha256=RBn2uQkdv3z3_ywUFrR3ou7cVAKVyRu4IJGu4B-vpm4,2663
223
+ hpcflow_new2-0.2.0a228.dist-info/WHEEL,sha256=kLuE8m1WYU0Ig0_YEGrXyTtiJvKPpLpDEiChiNyei5Y,88
224
+ hpcflow_new2-0.2.0a228.dist-info/entry_points.txt,sha256=aoGtCnFdfPcXfBdu2zZyMOJoz6fPgdR0elqsgrE-USU,106
225
+ hpcflow_new2-0.2.0a228.dist-info/RECORD,,