hpcflow-new2 0.2.0a69__py3-none-any.whl → 0.2.0a71__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.0a69"
1
+ __version__ = "0.2.0a71"
hpcflow/sdk/__init__.py CHANGED
@@ -88,6 +88,7 @@ sdk_classes = {
88
88
  "DirectPosix": "hpcflow.sdk.submission.schedulers.direct",
89
89
  "SlurmPosix": "hpcflow.sdk.submission.schedulers.slurm",
90
90
  "SGEPosix": "hpcflow.sdk.submission.schedulers.sge",
91
+ "OutputLabel": "hpcflow.sdk.core.task",
91
92
  }
92
93
 
93
94
  sdk_funcs = {
@@ -468,7 +468,18 @@ class ElementActionRun:
468
468
  return self.action.environments[0].environment
469
469
 
470
470
  def get_input_values(self) -> Dict[str, Any]:
471
- return {i.path[len("inputs.") :]: i.value for i in self.inputs}
471
+ out = {}
472
+ for name in self.inputs.prefixed_names_unlabelled:
473
+ i = getattr(self.inputs, name)
474
+ try:
475
+ value = i.value
476
+ except AttributeError:
477
+ value = {}
478
+ for k, v in i.items():
479
+ value[k] = v.value
480
+ out[name] = value
481
+
482
+ return out
472
483
 
473
484
  def get_IFG_input_values(self) -> Dict[str, Any]:
474
485
  if not self.action._from_expand:
@@ -835,13 +846,15 @@ class ElementAction:
835
846
 
836
847
  def get_parameter_names(self, prefix):
837
848
  if prefix == "inputs":
838
- return list(f"{i}" for i in self.action.get_input_types())
849
+ single_lab_lookup = self.element_iteration._get_single_label_lookup()
850
+ out = list(single_lab_lookup.get(i, i) for i in self.action.get_input_types())
839
851
  elif prefix == "outputs":
840
- return list(f"{i}" for i in self.action.get_output_types())
852
+ out = list(f"{i}" for i in self.action.get_output_types())
841
853
  elif prefix == "input_files":
842
- return list(f"{i}" for i in self.action.get_input_file_labels())
854
+ out = list(f"{i}" for i in self.action.get_input_file_labels())
843
855
  elif prefix == "output_files":
844
- return list(f"{i}" for i in self.action.get_output_file_labels())
856
+ out = list(f"{i}" for i in self.action.get_output_file_labels())
857
+ return out
845
858
 
846
859
 
847
860
  @dataclass
@@ -1628,8 +1641,12 @@ class Action(JSONLike):
1628
1641
  # these are consumed by the OFP, so should not be considered to generate new data:
1629
1642
  OFP_outs = [j for i in self.output_file_parsers for j in i.outputs or []]
1630
1643
 
1631
- # keep all resources data:
1632
- sub_data_idx = {k: v for k, v in schema_data_idx.items() if "resources" in k}
1644
+ # keep all resources and repeats data:
1645
+ sub_data_idx = {
1646
+ k: v
1647
+ for k, v in schema_data_idx.items()
1648
+ if ("resources" in k or "repeats" in k)
1649
+ }
1633
1650
  param_src_update = []
1634
1651
  for key in keys:
1635
1652
  sub_param_idx = {}
@@ -4,6 +4,7 @@ from dataclasses import dataclass
4
4
  from typing import Any, Dict, List, Optional, Union
5
5
 
6
6
  from valida.conditions import ConditionLike
7
+ from valida.rules import Rule
7
8
 
8
9
  from hpcflow.sdk import app
9
10
  from hpcflow.sdk.core.json_like import JSONLike
@@ -25,25 +26,44 @@ class _ElementPrefixedParameter:
25
26
  self._element_action = element_action
26
27
  self._element_action_run = element_action_run
27
28
 
29
+ self._prefixed_names_unlabelled = None # assigned on first access
30
+
28
31
  def __getattr__(self, name):
29
- if name not in self._get_prefixed_names():
32
+ if name not in self.prefixed_names_unlabelled:
30
33
  raise ValueError(
31
34
  f"No {self._prefix} named {name!r}. Available {self._prefix} are: "
32
- f"{self._get_prefixed_names_str()}."
35
+ f"{self.prefixed_names_unlabelled_str}."
33
36
  )
34
37
 
35
- data_idx = self._parent.get_data_idx(path=f"{self._prefix}.{name}")
36
- param = self._app.ElementParameter(
37
- path=f"{self._prefix}.{name}",
38
- task=self._task,
39
- data_idx=data_idx,
40
- parent=self._parent,
41
- element=self._element_iteration_obj,
42
- )
43
- return param
38
+ labels = self.prefixed_names_unlabelled.get(name)
39
+ if labels:
40
+ # is multiple; return a dict of `ElementParameter`s
41
+ out = {}
42
+ for label_i in labels:
43
+ path_i = f"{self._prefix}.{name}[{label_i}]"
44
+ data_idx = self._parent.get_data_idx(path=path_i)
45
+ out[label_i] = self._app.ElementParameter(
46
+ path=path_i,
47
+ task=self._task,
48
+ data_idx=data_idx,
49
+ parent=self._parent,
50
+ element=self._element_iteration_obj,
51
+ )
52
+
53
+ else:
54
+ path_i = f"{self._prefix}.{name}"
55
+ data_idx = self._parent.get_data_idx(path=path_i)
56
+ out = self._app.ElementParameter(
57
+ path=path_i,
58
+ task=self._task,
59
+ data_idx=data_idx,
60
+ parent=self._parent,
61
+ element=self._element_iteration_obj,
62
+ )
63
+ return out
44
64
 
45
65
  def __dir__(self):
46
- return super().__dir__() + self._get_prefixed_names()
66
+ return super().__dir__() + self.prefixed_names_unlabelled
47
67
 
48
68
  @property
49
69
  def _parent(self):
@@ -60,17 +80,47 @@ class _ElementPrefixedParameter:
60
80
  def _task(self):
61
81
  return self._parent.task
62
82
 
63
- def __repr__(self):
64
- return f"{self.__class__.__name__}({self._get_prefixed_names_str()})"
83
+ @property
84
+ def prefixed_names_unlabelled(self):
85
+ if self._prefixed_names_unlabelled is None:
86
+ self._prefixed_names_unlabelled = self._get_prefixed_names_unlabelled()
87
+ return self._prefixed_names_unlabelled
88
+
89
+ @property
90
+ def prefixed_names_unlabelled_str(self):
91
+ return ", ".join(i for i in self.prefixed_names_unlabelled)
65
92
 
66
- def _get_prefixed_names_str(self):
67
- return f"{', '.join(f'{i!r}' for i in self._get_prefixed_names())}"
93
+ def __repr__(self):
94
+ # If there are one or more labels present, then replace with a single name
95
+ # indicating there could be multiple (using `multi_prefix` prefix):
96
+ names = []
97
+ for unlabelled, labels in self.prefixed_names_unlabelled.items():
98
+ name_i = unlabelled
99
+ if labels:
100
+ name_i = "*" + name_i
101
+ names.append(name_i)
102
+ names_str = ", ".join(i for i in names)
103
+ return f"{self.__class__.__name__}({names_str})"
68
104
 
69
105
  def _get_prefixed_names(self):
70
106
  return sorted(self._parent.get_parameter_names(self._prefix))
71
107
 
108
+ def _get_prefixed_names_unlabelled(self) -> Dict[str, List[str]]:
109
+ names = self._get_prefixed_names()
110
+ all_names = {}
111
+ for i in list(names):
112
+ if "[" in i:
113
+ unlab_i, rem = i.split("[")
114
+ label = rem.split("]")[0]
115
+ if unlab_i not in all_names:
116
+ all_names[unlab_i] = []
117
+ all_names[unlab_i].append(label)
118
+ else:
119
+ all_names[i] = []
120
+ return all_names
121
+
72
122
  def __iter__(self):
73
- for name in self._get_prefixed_names():
123
+ for name in self.prefixed_names_unlabelled:
74
124
  yield getattr(self, name)
75
125
 
76
126
 
@@ -269,7 +319,7 @@ class ElementIteration:
269
319
  action_idx=act_idx,
270
320
  runs=runs,
271
321
  )
272
- for act_idx, runs in self._EARs.items()
322
+ for act_idx, runs in (self._EARs or {}).items()
273
323
  }
274
324
  return self._action_objs
275
325
 
@@ -304,8 +354,9 @@ class ElementIteration:
304
354
  return self._output_files
305
355
 
306
356
  def get_parameter_names(self, prefix: str) -> List[str]:
357
+ single_label_lookup = self._get_single_label_lookup("inputs")
307
358
  return list(
308
- ".".join(i.split(".")[1:])
359
+ ".".join(single_label_lookup.get(i, i).split(".")[1:])
309
360
  for i in self.schema_parameters
310
361
  if i.startswith(prefix)
311
362
  )
@@ -323,7 +374,10 @@ class ElementIteration:
323
374
  The index of the action within the schema.
324
375
  """
325
376
 
326
- if action_idx is None:
377
+ if not self.actions:
378
+ data_idx = self.data_idx
379
+
380
+ elif action_idx is None:
327
381
  # inputs should be from first action where that input is defined, and outputs
328
382
  # should include modifications from all actions; we can't just take
329
383
  # `self.data_idx`, because 1) this is used for initial runs, and subsequent
@@ -438,6 +492,16 @@ class ElementIteration:
438
492
 
439
493
  return out
440
494
 
495
+ def _get_single_label_lookup(self, prefix=""):
496
+ lookup = {}
497
+ if prefix and not prefix.endswith("."):
498
+ prefix += "."
499
+ for sch_inp in self.task.template.all_schema_inputs:
500
+ if not sch_inp.multiple and sch_inp.single_label:
501
+ labelled_type = sch_inp.single_labelled_type
502
+ lookup[f"{prefix}{labelled_type}"] = f"{prefix}{sch_inp.typ}"
503
+ return lookup
504
+
441
505
  def get(
442
506
  self,
443
507
  path: str = None,
@@ -450,8 +514,20 @@ class ElementIteration:
450
514
  # TODO include a "stats" parameter which when set we know the run has been
451
515
  # executed (or if start time is set but not end time, we know it's running or
452
516
  # failed.)
517
+
518
+ data_idx = self.get_data_idx(action_idx=action_idx, run_idx=run_idx)
519
+ single_label_lookup = self._get_single_label_lookup(prefix="inputs")
520
+
521
+ if single_label_lookup:
522
+ # For any non-multiple `SchemaParameter`s of this task with non-empty labels,
523
+ # remove the trivial label:
524
+ for key in list(data_idx.keys()):
525
+ lookup_val = single_label_lookup.get(key)
526
+ if lookup_val:
527
+ data_idx[lookup_val] = data_idx.pop(key)
528
+
453
529
  return self.task._get_merged_parameter_data(
454
- data_index=self.get_data_idx(action_idx=action_idx, run_idx=run_idx),
530
+ data_index=data_idx,
455
531
  path=path,
456
532
  raise_on_missing=raise_on_missing,
457
533
  default=default,
@@ -1004,10 +1080,8 @@ class ElementParameter:
1004
1080
  raise NotImplementedError
1005
1081
 
1006
1082
 
1007
- @dataclass
1008
- class ElementFilter:
1009
- parameter_path: app.ParameterPath
1010
- condition: ConditionLike
1083
+ class ElementFilter(Rule):
1084
+ pass
1011
1085
 
1012
1086
 
1013
1087
  @dataclass