MATE-runtime 1.0.0__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.
Files changed (93) hide show
  1. MATE/__init__.py +22 -0
  2. MATE/__main__.py +7 -0
  3. MATE/cli/__init__.py +181 -0
  4. MATE/cli/commands.py +573 -0
  5. MATE/cli/workdir.py +571 -0
  6. MATE/compat/__init__.py +1 -0
  7. MATE/compat/interface.py +248 -0
  8. MATE/compat/sizes.py +144 -0
  9. MATE/config/__init__.py +1 -0
  10. MATE/config/axes.py +310 -0
  11. MATE/config/builders.py +498 -0
  12. MATE/config/calendar.py +371 -0
  13. MATE/config/canonical.py +543 -0
  14. MATE/config/criteria.py +396 -0
  15. MATE/config/events.py +183 -0
  16. MATE/config/invariance.py +227 -0
  17. MATE/config/loader.py +498 -0
  18. MATE/config/model.py +883 -0
  19. MATE/config/resolve.py +943 -0
  20. MATE/config/runtable.py +342 -0
  21. MATE/config/seeds.py +344 -0
  22. MATE/config/stages.py +632 -0
  23. MATE/config/structure.py +635 -0
  24. MATE/config/toml.py +237 -0
  25. MATE/conformance/__init__.py +14 -0
  26. MATE/conformance/baseline.py +250 -0
  27. MATE/conformance/checks.py +1604 -0
  28. MATE/conformance/sampler.py +179 -0
  29. MATE/errors.py +236 -0
  30. MATE/evaluation/__init__.py +1 -0
  31. MATE/evaluation/budget.py +160 -0
  32. MATE/evaluation/invalids.py +181 -0
  33. MATE/evaluation/requests.py +148 -0
  34. MATE/execution/__init__.py +1 -0
  35. MATE/execution/accelerator.py +60 -0
  36. MATE/execution/base.py +643 -0
  37. MATE/execution/choose.py +152 -0
  38. MATE/execution/devices.py +108 -0
  39. MATE/execution/process.py +377 -0
  40. MATE/execution/serial.py +37 -0
  41. MATE/execution/thread.py +106 -0
  42. MATE/execution/vectorised.py +32 -0
  43. MATE/launch.py +267 -0
  44. MATE/loop/__init__.py +6 -0
  45. MATE/loop/continuous.py +172 -0
  46. MATE/loop/driver.py +783 -0
  47. MATE/loop/events.py +266 -0
  48. MATE/loop/generational.py +140 -0
  49. MATE/loop/owns_loop.py +333 -0
  50. MATE/loop/subpops.py +508 -0
  51. MATE/modules/__init__.py +1 -0
  52. MATE/modules/construct.py +198 -0
  53. MATE/modules/discovery.py +238 -0
  54. MATE/modules/registry.py +970 -0
  55. MATE/modules/software.py +593 -0
  56. MATE/modules/versions.py +72 -0
  57. MATE/numerics.py +159 -0
  58. MATE/observation/__init__.py +10 -0
  59. MATE/observation/control.py +199 -0
  60. MATE/observation/hub.py +451 -0
  61. MATE/observation/records.py +125 -0
  62. MATE/observation/sinks.py +313 -0
  63. MATE/provenance.py +396 -0
  64. MATE/readout/__init__.py +10 -0
  65. MATE/readout/catalogue.py +282 -0
  66. MATE/readout/front.py +151 -0
  67. MATE/readout/readout.py +927 -0
  68. MATE/readout/series.py +155 -0
  69. MATE/readout/statistics.py +202 -0
  70. MATE/run/__init__.py +1 -0
  71. MATE/run/checkpointing.py +266 -0
  72. MATE/run/contexts.py +553 -0
  73. MATE/run/declarations.py +240 -0
  74. MATE/run/plan.py +464 -0
  75. MATE/run/preconditions.py +165 -0
  76. MATE/run/run.py +991 -0
  77. MATE/run/specs.py +319 -0
  78. MATE/run/times.py +103 -0
  79. MATE/scheduling/__init__.py +214 -0
  80. MATE/scheduling/local.py +119 -0
  81. MATE/scheduling/slurm.py +380 -0
  82. MATE/storage/__init__.py +1 -0
  83. MATE/storage/checkpoint.py +414 -0
  84. MATE/storage/codec.py +315 -0
  85. MATE/storage/database.py +359 -0
  86. MATE/storage/lease.py +397 -0
  87. MATE/storage/rundir.py +248 -0
  88. mate_runtime-1.0.0.dist-info/METADATA +58 -0
  89. mate_runtime-1.0.0.dist-info/RECORD +93 -0
  90. mate_runtime-1.0.0.dist-info/WHEEL +4 -0
  91. mate_runtime-1.0.0.dist-info/entry_points.txt +2 -0
  92. mate_runtime-1.0.0.dist-info/licenses/COPYING +674 -0
  93. mate_runtime-1.0.0.dist-info/licenses/COPYING.LESSER +165 -0
@@ -0,0 +1,371 @@
1
+ """The environment calendar: parsing ``[environment]``, building and evaluating it.
2
+
3
+ :class:`Calendar` is the single calendar implementation: the loop evaluates it at each
4
+ request date ([X] §10.1) and a system that owns its loop receives it as a dated function
5
+ ([C] R10.6).
6
+ """
7
+
8
+ from collections.abc import Mapping
9
+ from dataclasses import dataclass
10
+ from types import MappingProxyType
11
+
12
+ import numpy as np
13
+ from MATE_contract import (
14
+ Categorical,
15
+ Domain,
16
+ Environment,
17
+ EnvironmentSpec,
18
+ Integer,
19
+ Log,
20
+ Ordinal,
21
+ Real,
22
+ )
23
+
24
+ from MATE.config.model import EnvironmentDecl, Phase, Value
25
+ from MATE.errors import Refusal
26
+
27
+ __all__ = ["parse_environment", "build_calendar", "Calendar"]
28
+
29
+ _UNITS = ("generation", "evaluation")
30
+ _RESERVED = ("unit", "phase")
31
+ _PHASE_KEYS = frozenset({"until", "settings", "interpolation"})
32
+
33
+
34
+ def _is_int(x: object) -> bool:
35
+ """A TOML integer (booleans excluded)."""
36
+ return isinstance(x, int) and not isinstance(x, bool)
37
+
38
+
39
+ def _reactive(value: object) -> bool:
40
+ """A condition-shaped value: a table with a ``serie`` key (R10.15)."""
41
+ return isinstance(value, Mapping) and "serie" in value
42
+
43
+
44
+ def _reactive_refusal(where: str) -> Refusal:
45
+ """``P-PHASE-FORM`` for a setting written as a condition on a series."""
46
+ return Refusal("P-PHASE-FORM", where,
47
+ "calendar form not available in MATE 1.0.0: reactive (a setting written "
48
+ "as a condition on a series); reactive calendars are not yet specified")
49
+
50
+
51
+ def _freeze(value: object) -> Value:
52
+ """Arrays as tuples, tables as read-only mappings."""
53
+ if isinstance(value, (list, tuple)):
54
+ return tuple(_freeze(v) for v in value)
55
+ if isinstance(value, Mapping):
56
+ return MappingProxyType({k: _freeze(v) for k, v in value.items()})
57
+ return value
58
+
59
+
60
+ def parse_environment(table: Mapping) -> tuple[EnvironmentDecl | None, list[Refusal]]:
61
+ """Parse an ``[environment]`` table.
62
+
63
+ Parameters
64
+ ----------
65
+ table : mapping
66
+ The ``[environment]`` table (empty when absent).
67
+
68
+ Returns
69
+ -------
70
+ EnvironmentDecl or None
71
+ Settings as written (labels for ``Ordinal`` and ``Categorical``, linear pairs as
72
+ tuples); ``None`` when refused.
73
+ list of Refusal
74
+ ``P-PHASE-ORDER``, ``P-PHASE-UNTIL``, ``P-PHASE-LINEAR``, ``P-PHASE-FORM``,
75
+ ``P-UNKNOWN-KEY`` (a phase key, or ``unit`` not a string).
76
+
77
+ Notes
78
+ -----
79
+ [C] R10.7 (keys other than ``unit`` and ``phase`` are settings from date 0), R10.9,
80
+ R10.10 syntax, R10.15. Domains, units and completeness are :func:`build_calendar`'s.
81
+ """
82
+ if not isinstance(table, Mapping):
83
+ return None, [Refusal("P-UNKNOWN-KEY", "[environment]",
84
+ f"[environment] is a table, got {table!r}")]
85
+ refusals: list[Refusal] = []
86
+ unit = table.get("unit")
87
+ if "unit" in table and (not isinstance(unit, str) or not unit):
88
+ refusals.append(Refusal("P-UNKNOWN-KEY", "[environment] unit",
89
+ f"unit is \"generation\" or \"evaluation\", got {unit!r}"))
90
+ base: dict[str, Value] = {}
91
+ for name, value in table.items():
92
+ if name in _RESERVED:
93
+ continue
94
+ if _reactive(value):
95
+ refusals.append(_reactive_refusal(f"[environment] {name}"))
96
+ base[name] = _freeze(value)
97
+
98
+ raw_phases = table.get("phase", [])
99
+ if not isinstance(raw_phases, (list, tuple)):
100
+ refusals.append(Refusal("P-PHASE-UNTIL", "[environment] phase",
101
+ "phases are an array of tables ([[environment.phase]])"))
102
+ raw_phases = []
103
+ phases: list[Phase] = []
104
+ previous = 0
105
+ last = len(raw_phases) - 1
106
+ for i, raw in enumerate(raw_phases):
107
+ where = f"[[environment.phase]] #{i + 1}"
108
+ if not isinstance(raw, Mapping):
109
+ refusals.append(Refusal("P-PHASE-UNTIL", where, f"a phase is a table, got {raw!r}"))
110
+ continue
111
+ refusals.extend(Refusal("P-UNKNOWN-KEY", f"{where} {k}",
112
+ f"unknown phase key '{k}'; known keys: until, settings, "
113
+ f"interpolation") for k in raw if k not in _PHASE_KEYS)
114
+ until = raw.get("until")
115
+ if until is None:
116
+ if i != last:
117
+ refusals.append(Refusal("P-PHASE-UNTIL", where,
118
+ "only the last phase may omit until"))
119
+ elif not _is_int(until):
120
+ refusals.append(Refusal("P-PHASE-UNTIL", f"{where} until",
121
+ f"until is an integer date, got {until!r}"))
122
+ until = None
123
+ elif until <= previous:
124
+ refusals.append(Refusal(
125
+ "P-PHASE-ORDER", f"{where} until",
126
+ f"until values increase strictly from 0: {until} follows {previous}"))
127
+ else:
128
+ previous = until
129
+ interpolation = raw.get("interpolation")
130
+ if interpolation is not None and interpolation != "linear":
131
+ refusals.append(Refusal(
132
+ "P-PHASE-FORM", f"{where} interpolation",
133
+ f"calendar form not available in MATE 1.0.0: {interpolation}; "
134
+ f"periodic and random calendars are not yet specified"))
135
+ interpolation = None
136
+ settings = raw.get("settings", {})
137
+ if not isinstance(settings, Mapping):
138
+ refusals.append(Refusal("P-UNKNOWN-KEY", f"{where} settings",
139
+ f"settings is a table, got {settings!r}"))
140
+ settings = {}
141
+ frozen: dict[str, Value] = {}
142
+ for name, value in settings.items():
143
+ if _reactive(value):
144
+ refusals.append(_reactive_refusal(f"{where} settings.{name}"))
145
+ elif interpolation == "linear" and (not isinstance(value, (list, tuple))
146
+ or len(value) != 2):
147
+ refusals.append(Refusal(
148
+ "P-PHASE-LINEAR", f"{where} settings.{name}",
149
+ f"a linear phase writes [from, to] pairs, got {value!r}"))
150
+ frozen[name] = _freeze(value)
151
+ if interpolation == "linear" and raw.get("until") is None:
152
+ refusals.append(Refusal("P-PHASE-LINEAR", where, "a linear phase needs until"))
153
+ phases.append(Phase(until, MappingProxyType(frozen), interpolation))
154
+ if refusals:
155
+ return None, refusals
156
+ return EnvironmentDecl(unit, MappingProxyType(base), tuple(phases)), []
157
+
158
+
159
+ def _on_circle(value: Value, domain: Domain) -> Value:
160
+ """A value of a periodic ``Real``, ``hi`` written as ``lo``: the same point of the circle."""
161
+ if isinstance(domain, Real) and domain.periodic and value == domain.hi:
162
+ return domain.lo # type: ignore[return-value]
163
+ return value
164
+
165
+
166
+ def _normalise(value: object, domain: Domain, *, point: bool = True
167
+ ) -> tuple[Value, str | None]:
168
+ """Normalise one written value in its domain (R6.10); return (value, error).
169
+
170
+ A point of a periodic ``Real`` written at ``hi`` becomes ``lo``. ``point=False`` keeps it
171
+ for an end of a linear pair, which bounds a path: ``[lo, hi]`` is a full turn.
172
+ """
173
+ if isinstance(domain, (Ordinal, Categorical)):
174
+ for label in domain.values:
175
+ if (isinstance(value, bool) == isinstance(label, bool) and type(value) is not tuple
176
+ and value == label):
177
+ return label, None
178
+ return value, f"{value!r} is not one of {list(domain.values)}"
179
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
180
+ return value, f"{value!r} is not a number"
181
+ if isinstance(domain, Integer):
182
+ if not isinstance(value, int):
183
+ return value, f"{value!r} is not an integer"
184
+ else:
185
+ value = float(value)
186
+ if not bool(domain.contains(np.asarray(value))):
187
+ return value, f"{value!r} is outside {domain!r}"
188
+ return (_on_circle(value, domain) if point else value), None
189
+
190
+
191
+ def build_calendar(decl: EnvironmentDecl, spec: EnvironmentSpec, *, clock: str | None,
192
+ dated: bool) -> tuple["Calendar | None", list[Refusal]]:
193
+ """Check a calendar against the system's environment spec and build it.
194
+
195
+ Parameters
196
+ ----------
197
+ decl : EnvironmentDecl
198
+ From :func:`parse_environment`.
199
+ spec : EnvironmentSpec
200
+ The system's environment spec.
201
+ clock : str or None
202
+ The system's ``capabilities.clock`` when it owns its loop, else ``None``.
203
+ dated : bool
204
+ Some ``[[event]]`` has an ``at`` date.
205
+
206
+ Returns
207
+ -------
208
+ Calendar or None
209
+ With values normalised (R6.10) and, under a clock, ``unit`` resolved to it;
210
+ ``None`` when refused.
211
+ list of Refusal
212
+ ``P-ENV-RESERVED`` (R10.7), ``P-ENV-UNKNOWN``, ``P-ENV-DOMAIN`` (R10.8),
213
+ ``P-PHASE-LINEAR-DOMAIN`` (R10.10), ``P-ENV-UNSET`` (R10.12), ``P-UNIT-MISSING``,
214
+ ``P-UNIT-CLOCK`` (R10.13; also a unit other than ``"generation"`` and
215
+ ``"evaluation"`` without a clock).
216
+ """
217
+ refusals: list[Refusal] = []
218
+ fields = spec.fields
219
+ for name in fields:
220
+ if name in _RESERVED:
221
+ refusals.append(Refusal(
222
+ "P-ENV-RESERVED", f"[environment] {name}",
223
+ f"the environment spec declares a dimension named '{name}', a reserved "
224
+ f"key of [environment]"))
225
+
226
+ unit = decl.unit
227
+ if clock is not None:
228
+ if unit is None:
229
+ unit = clock
230
+ elif unit != clock:
231
+ refusals.append(Refusal(
232
+ "P-UNIT-CLOCK", "[environment] unit",
233
+ f"the system owns its loop and dates in its clock '{clock}'; unit = '{unit}'",
234
+ f"unit = \"{clock}\", or omit unit"))
235
+ elif unit is not None and unit not in _UNITS:
236
+ refusals.append(Refusal(
237
+ "P-UNIT-CLOCK", "[environment] unit",
238
+ f"unit is \"generation\" or \"evaluation\", got '{unit}' (the system does not "
239
+ f"own its loop)"))
240
+ if unit is None and (dated or any(p.until is not None for p in decl.phases)):
241
+ refusals.append(Refusal(
242
+ "P-UNIT-MISSING", "[environment] unit",
243
+ "a date is written (a phase until or an event at) without its unit",
244
+ "unit = \"generation\" or unit = \"evaluation\""))
245
+
246
+ def check(where: str, name: str, value: Value, linear: bool) -> Value:
247
+ domain = fields.get(name)
248
+ if domain is None:
249
+ refusals.append(Refusal(
250
+ "P-ENV-UNKNOWN", where,
251
+ f"'{name}' is not a dimension of the environment spec "
252
+ f"(dimensions: {', '.join(fields) or 'none'})"))
253
+ return value
254
+ if linear:
255
+ if not isinstance(domain, (Real, Log)):
256
+ refusals.append(Refusal(
257
+ "P-PHASE-LINEAR-DOMAIN", where,
258
+ f"linear interpolation applies to Real and Log dimensions; "
259
+ f"'{name}' is {domain!r}"))
260
+ return value
261
+ pair = []
262
+ for member in value:
263
+ v, error = _normalise(member, domain, point=False)
264
+ if error is not None:
265
+ refusals.append(Refusal("P-ENV-DOMAIN", where, f"'{name}': {error}"))
266
+ pair.append(v)
267
+ return tuple(pair)
268
+ v, error = _normalise(value, domain)
269
+ if error is not None:
270
+ refusals.append(Refusal("P-ENV-DOMAIN", where, f"'{name}': {error}"))
271
+ return v
272
+
273
+ base = {n: check(f"[environment] {n}", n, v, False) for n, v in decl.base.items()}
274
+ phases = []
275
+ for i, p in enumerate(decl.phases):
276
+ linear = p.interpolation == "linear"
277
+ settings = {n: check(f"[[environment.phase]] #{i + 1} settings.{n}", n, v, linear)
278
+ for n, v in p.settings.items()}
279
+ phases.append(Phase(p.until, MappingProxyType(settings), p.interpolation))
280
+
281
+ first = decl.phases[0].settings if decl.phases else {}
282
+ for name in fields:
283
+ if name in _RESERVED or name in base or name in first:
284
+ continue
285
+ later = [i + 1 for i, p in enumerate(decl.phases) if name in p.settings]
286
+ detail = (f"it is first set in phase #{later[0]}, so it has no value from date 0"
287
+ if later else "the calendar never sets it")
288
+ refusals.append(Refusal(
289
+ "P-ENV-UNSET", f"[environment] {name}",
290
+ f"environment dimension '{name}' has no value at every date: {detail}",
291
+ f"write [environment] {name} = <value>"))
292
+ if refusals:
293
+ return None, refusals
294
+ normalised = EnvironmentDecl(unit, MappingProxyType(base), tuple(phases))
295
+ return Calendar(normalised, spec), []
296
+
297
+
298
+ @dataclass(frozen=True)
299
+ class Calendar:
300
+ """A checked calendar: the environment point at each date.
301
+
302
+ Parameters
303
+ ----------
304
+ decl : EnvironmentDecl
305
+ Normalised by :func:`build_calendar`.
306
+ spec : EnvironmentSpec
307
+ The system's environment spec.
308
+
309
+ Notes
310
+ -----
311
+ [C] §10.2, [X] CA2. Phase ``i`` covers ``[until_{i-1}, until_i)`` with
312
+ ``until_{-1} = 0``; a dimension a phase does not write keeps its value at the end of
313
+ the previous phase; a linear phase gives ``from + (to - from) (d - start) /
314
+ (until - start)``; after the last ``until`` every value holds. On a periodic ``Real``,
315
+ a linear phase runs from ``from`` to ``to`` as written, at most one turn, and the point
316
+ gives ``lo`` for ``hi``.
317
+ """
318
+
319
+ decl: EnvironmentDecl
320
+ spec: EnvironmentSpec
321
+
322
+ def point(self, date: int | float) -> Environment:
323
+ """Return the environment point at a date.
324
+
325
+ Parameters
326
+ ----------
327
+ date : int or float
328
+ In the calendar's unit, ``>= 0``.
329
+
330
+ Returns
331
+ -------
332
+ Environment
333
+ One value per dimension of the spec, in its order; ``Ordinal`` and
334
+ ``Categorical`` values as codes (contract EN1), a periodic ``Real`` in
335
+ ``[lo, hi)``.
336
+
337
+ Raises
338
+ ------
339
+ ValueError
340
+ If ``date`` is negative or not a number.
341
+ """
342
+ if isinstance(date, bool) or not isinstance(date, (int, float)) or not date >= 0:
343
+ raise ValueError(f"Calendar.point: date must be a number >= 0, got {date!r}")
344
+ values: dict[str, Value] = dict(self.decl.base)
345
+ start = 0
346
+ for p in self.decl.phases:
347
+ inside = p.until is None or date < p.until
348
+ for name, v in p.settings.items():
349
+ if p.interpolation != "linear":
350
+ values[name] = v
351
+ elif inside:
352
+ lo, hi = v
353
+ values[name] = lo + (hi - lo) * (date - start) / (p.until - start)
354
+ else:
355
+ values[name] = v[1]
356
+ if inside:
357
+ break
358
+ start = p.until
359
+ point: dict[str, int | float] = {}
360
+ for name, domain in self.spec.fields.items():
361
+ v = values[name]
362
+ if isinstance(domain, (Ordinal, Categorical)):
363
+ point[name] = next(i for i, label in enumerate(domain.values)
364
+ if label == v and isinstance(label, bool) == isinstance(v, bool))
365
+ else:
366
+ point[name] = _on_circle(v, domain) # the end of a linear turn
367
+ return Environment(point)
368
+
369
+ def __call__(self, date: int | float) -> Environment:
370
+ """The dated function handed to a system that owns its loop: :meth:`point`."""
371
+ return self.point(date)