Skip to content

Commit beeaec2

Browse files
committed
feat!: Implement step parameter space iteration for chunks
* Modify StepParameterSpaceIterator to handle parameter spaces that include a CHUNK[INT] parameter. The iteration pattern depends on the values in the 'chunks' object of the parameter definition. * If targetRuntimeSeconds is 0 or unspecified, split the CHUNK[INT] dimension into chunks, and then iterate over the whole space with that selection of chunks. * If targetRuntimeSeconds is a positive integer, move the CHUNK[INT] dimension to the innermost loop, and dynamically select the chunk each time it advances. Let the user of the iterator modify its chunks_default_task_count to adapt the chunk size over time. * The StepParameterSpaceIterator object behaved more like a pseudo-container than an iterator. Change it to act as the iterator itself (returning 'self' from __iter__() instead of a new object). * Changed how the special case zero-dimensional iteration works to be a Node/Iterator pair just like the others, for a more uniform interface. * Changes to IntRangeExpr: * Fix a bug in IntRangeExpr.from_list that wasn't handling the end of a linear sequence correctly. * Change the string representation of IntRangeExpr to use "," instead of "-" when there are just two values. * Add and update unit tests for all of the modifications. * Update parameter space validation to ensure only one CHUNK[INT] parameter is defined. Signed-off-by: Mark Wiebe <399551+mwiebe@users.noreply.github.com>
1 parent 1d2f554 commit beeaec2

File tree

10 files changed

+1064
-52
lines changed

10 files changed

+1064
-52
lines changed

src/openjd/model/_range_expr.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def from_str(range_str: str) -> IntRangeExpr:
5454
return Parser().parse(range_str)
5555

5656
@staticmethod
57-
def from_list(values: list[int | str]) -> IntRangeExpr:
57+
def from_list(values: list[int] | list[str] | list[int | str]) -> IntRangeExpr:
5858
"""Creates a range expression object from a list of integers/strings containing integers."""
5959
if len(values) == 0:
6060
return IntRangeExpr([])
@@ -66,7 +66,7 @@ def from_list(values: list[int | str]) -> IntRangeExpr:
6666
values_as_int: list[int] = sorted({int(i) for i in values})
6767
# Find all the ranges, and concatenate them
6868
ranges = []
69-
start = values_as_int[0]
69+
start = end = values_as_int[0]
7070
step = None
7171

7272
for value in values_as_int[1:]:
@@ -78,7 +78,7 @@ def from_list(values: list[int | str]) -> IntRangeExpr:
7878
end = value
7979
else:
8080
ranges.append(IntRange(start, end, step))
81-
start = value
81+
start = end = value
8282
step = None
8383
ranges.append(IntRange(start, end, step or 1))
8484
return IntRangeExpr(ranges)
@@ -183,8 +183,11 @@ def __init__(self, start: int, end: int, step: int = 1):
183183
self._validate()
184184

185185
def __str__(self) -> str:
186-
if len(self) == 1:
186+
len_self = len(self)
187+
if len_self == 1:
187188
return str(self._start)
189+
elif len_self == 2:
190+
return f"{self._start},{self._end}"
188191
elif self.step == 1:
189192
return f"{self._start}-{self._end}"
190193
else:

src/openjd/model/_step_param_space_iter.py

Lines changed: 443 additions & 33 deletions
Large diffs are not rendered by default.

src/openjd/model/_types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ class ParameterValueType(str, Enum):
7575
INT = "INT"
7676
FLOAT = "FLOAT"
7777
PATH = "PATH"
78+
# This type is only used for task parameters, not job parameters
79+
CHUNK_INT = "CHUNK[INT]"
7880

7981

8082
@dataclass(frozen=True, **dataclass_kwargs)

src/openjd/model/v2023_09/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
StringRangeList,
7676
StringTaskParameterDefinition,
7777
TaskChunksDefinition,
78+
TaskChunksRangeConstraint,
7879
TaskParameterList,
7980
TaskParameterStringValue,
8081
TaskParameterStringValueAsJob,
@@ -157,6 +158,7 @@
157158
"StringRangeList",
158159
"StringTaskParameterDefinition",
159160
"TaskChunksDefinition",
161+
"TaskChunksRangeConstraint",
160162
"TaskParameterList",
161163
"TaskParameterStringValue",
162164
"TaskParameterStringValueAsJob",

src/openjd/model/v2023_09/_model.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,8 @@ def _validate_default_task_count(cls, value: Any) -> Any:
584584
@field_validator("targetRuntimeSeconds", mode="before")
585585
@classmethod
586586
def _validate_target_runtime_seconds(cls, value: Any) -> Any:
587+
if value is None:
588+
return value
587589
return validate_int_fmtstring_field(value, ge=0)
588590

589591

@@ -909,6 +911,9 @@ class StepParameterSpaceDefinition(OpenJDModel_v2023_09):
909911
@field_validator("taskParameterDefinitions")
910912
@classmethod
911913
def _validate_parameters(cls, v: TaskParameterList) -> TaskParameterList:
914+
# Only one CHUNK[INT] parameter is permitted
915+
if len([param for param in v if param.type == TaskParameterType.CHUNK_INT]) > 1:
916+
raise ValueError("Only one CHUNK[INT] task parameter is permitted")
912917
# Must have a unique name for each Task parameter
913918
return validate_unique_elements(v, item_value=lambda v: v.name, property="name")
914919

test/openjd/model/_internal/test_range_expr.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ def test_parse_one_positive_range_no_step(
128128
"range_expr,start,end,total_range,range_str",
129129
[
130130
pytest.param("1-100,101-200", 1, 200, 200, "1-200"),
131-
pytest.param("0-1,3-4,7-9,10", 0, 10, 8, "0-1,3-4,7-10"),
131+
pytest.param("0-1,3-4,7-9,10", 0, 10, 8, "0,1,3,4,7-10"),
132+
pytest.param("0-3:3,5-10:5,12,13,14,15", 0, 15, 8, "0,3,5,10,12-15"),
132133
pytest.param("20-29,0-9,10-19", 0, 29, 30, "0-29"),
133134
],
134135
)
@@ -208,6 +209,7 @@ def test_range_expr_from_str(self, range_input_str: str, range_str: str):
208209
"range_list,range_str",
209210
[
210211
pytest.param([5], "5", id="one int"),
212+
pytest.param([1, 2, 3, 4, 5, 7], "1-5,7", id="two ranges"),
211213
pytest.param(["7"], "7", id="one int as a str"),
212214
pytest.param([9, 0, 3, 2, 8, 10, 1, 4, 7, 6, 5], "0-10", id="values 0-10 out of order"),
213215
pytest.param(

test/openjd/model/test_step_param_space_iter.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,10 @@
1212
parse_model,
1313
)
1414

15-
from openjd.model.v2023_09 import JobTemplate as JobTemplate_2023_09
1615
from openjd.model.v2023_09 import (
16+
JobTemplate as JobTemplate_2023_09,
1717
RangeExpressionTaskParameterDefinition as RangeExpressionTaskParameterDefinition_2023_09,
18-
)
19-
from openjd.model.v2023_09 import (
2018
RangeListTaskParameterDefinition as RangeListTaskParameterDefinition_2023_09,
21-
)
22-
from openjd.model.v2023_09 import (
2319
StepParameterSpace as StepParameterSpace_2023_09,
2420
)
2521

@@ -68,13 +64,14 @@ def test_no_param_iteration(self):
6864
job = create_job(job_template=job_template, job_parameter_values=dict())
6965

7066
space = job.steps[0].parameterSpace
71-
iterator = StepParameterSpaceIterator(space=space)
7267

7368
# WHEN
74-
result = list(iterator)
69+
it = StepParameterSpaceIterator(space=space)
7570

7671
# THEN
77-
assert result == expected
72+
assert list(it) == expected
73+
it.reset_iter()
74+
assert list(it) == expected
7875

7976
def test_no_param_getelem(self):
8077
# GIVEN
@@ -90,16 +87,16 @@ def test_no_param_getelem(self):
9087
space = job.steps[0].parameterSpace
9188

9289
# WHEN
93-
result = StepParameterSpaceIterator(space=space)
90+
it = StepParameterSpaceIterator(space=space)
9491

9592
# THEN
9693
with pytest.raises(IndexError):
97-
result[1]
94+
it[1]
9895
with pytest.raises(IndexError):
99-
result[-2]
96+
it[-2]
10097
expected = {}
101-
assert result[0] == expected
102-
assert result[-1] == expected
98+
assert it[0] == expected
99+
assert it[-1] == expected
103100

104101
@pytest.mark.parametrize(
105102
"range_int_param",
@@ -130,6 +127,9 @@ def test_single_param_iteration(self, range_int_param):
130127
} == next(it), f"i = {i}"
131128
with pytest.raises(StopIteration):
132129
next(it)
130+
# The chunks parameter is only relevant when the parameter space is chunked
131+
with pytest.raises(ValueError):
132+
it.chunks_default_task_count = 1
133133

134134
@pytest.mark.parametrize("param_range", [["10"], ["10", "11", "12", "13", "14", "15"]])
135135
def test_single_param_getelem(self, param_range):

0 commit comments

Comments
 (0)