Skip to content

Commit 16dc860

Browse files
committed
feat: Extend evaluation_time to problems
1 parent 3e1cab0 commit 16dc860

2 files changed

Lines changed: 35 additions & 27 deletions

File tree

SCHEMA.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ There are separate files which contain curated usage examples for problems or su
9191
| `description` | str | required |
9292
| `language` | str? (e.g. `python`, `c`) | |
9393
| `links` | list of [Link](#link)? | repo, release, docs… |
94-
| `evaluation_time` | str? | free-form ("8 minutes", "fast") |
94+
| `evaluation_time` | set of str? | free-form list ("8 minutes", "fast") |
9595
| `requirements` | str or list of str? | URL to requirements file or list of packages |
9696

9797
```yaml
@@ -135,11 +135,15 @@ The schema deliberately puts most descriptive fields here so suites can be chara
135135
| `modality` | set of str? | `{"unimodal"}`, `{"multimodal"}` |
136136
| `fidelity_levels` | set of int? | `{1}` = single-fidelity, `{1,2}` = multi-fidelity |
137137
| `code_examples` | set of str? | paths to example scripts |
138+
| `evaluation_time` | set of str? | free-form list ("8 minutes", "fast") |
138139
| `source` | set of str? | `{"artificial"}`, `{"real-world"}` |
139140

140141
> `objectives` is a set of integers because we don't assume extreme scalability in this property so explicit enumeration is fine.
141142
> Dimensions of variables on the other hand are ranges because here problems often are scalable over wide ranges.
142143

144+
When no `evaluation_time` is set, it percolates up from any referenced implementations.
145+
The same is true for the `variables` and `constraints` properties of a suite that has references to problems.
146+
143147
### Problem
144148

145149
One optimization problem (possibly parameterised by instances).

src/opltools/schema.py

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from enum import Enum
2+
from typing import Any
23
from typing_extensions import Self
34
from pydantic import BaseModel, RootModel, ConfigDict, model_validator
45

@@ -98,7 +99,7 @@ class Implementation(Thing):
9899
description: str
99100
links: list[Link] | None = None
100101
language: str | None = None
101-
evaluation_time: str | None = None
102+
evaluation_time: set[str] | None = None
102103
requirements: str | list[str] | None = None
103104

104105

@@ -118,6 +119,7 @@ class ProblemLike(Thing):
118119
can_evaluate_objectives_independently: YesNoSome | None = None
119120
modality: set[str] | None = None
120121
fidelity_levels: set[int] | None = None
122+
evaluation_time: set[str] | None = None
121123
code_examples: set[str] | None = None
122124
source: set[str] | None = None
123125

@@ -152,35 +154,32 @@ def _check_id_references(self, ids, type: OPLType) -> None:
152154
else:
153155
raise ValueError(f"Missing {type.name} with id '{id}'")
154156

155-
# For a given suite, make sure the fidelty_levels property contains
156-
# the fidelity_levels of all problems in the suite.
157-
def _fixup_suite_fidelity(self, suite: Suite):
158-
if suite.problems:
159-
if not suite.fidelity_levels:
160-
suite.fidelity_levels = set()
161-
for pid in suite.problems:
162-
problem = self.root[pid]
163-
assert isinstance(problem, Problem)
164-
if problem.fidelity_levels:
165-
suite.fidelity_levels.update(problem.fidelity_levels)
166-
167-
return suite
168-
169-
def _fixup_suite_variables(self, suite: Suite):
170-
if not suite.problems:
157+
def _percolate_set(self, thing: Any, children: set | None, property: str):
158+
"""Propagate some `property` from child objects to the parent by calculating the union of all the child property sets.
159+
160+
This is useful to propagate properties like `variables`, `constraints`, etc. from problems up to the suite.
161+
"""
162+
if children is None:
171163
return
172164

173-
if suite.variables is None:
174-
suite.variables = set()
175-
for pid in suite.problems:
176-
problem = self.root[pid]
177-
assert isinstance(problem, Problem)
178-
if problem.variables is not None:
179-
suite.variables.update(problem.variables)
165+
if getattr(thing, property, None) is None:
166+
setattr(thing, property, set())
167+
thing_set = getattr(thing, property)
168+
169+
for child_id in children:
170+
child = self.root[child_id]
171+
child_set = getattr(child, property, None)
172+
if child_set is not None:
173+
thing_set.update(child_set)
180174

181175
@model_validator(mode="after")
182176
def _validate(self) -> Self:
183-
# Make sure all problems referenced in suites exists
177+
# First check and fixup all problems
178+
for id, thing in self.root.items():
179+
if isinstance(thing, Problem) and thing.implementations:
180+
self._percolate_set(thing, thing.implementations, "evaluation_time")
181+
182+
# Then check and fixup all suites because changes from the problems need to propagate to the suites
184183
for id, thing in self.root.items():
185184
if isinstance(thing, Suite) and thing.problems:
186185
for problem_id in thing.problems:
@@ -192,7 +191,12 @@ def _validate(self) -> Self:
192191
raise ValueError(
193192
f"Suite {id} references problem with id '{problem_id}' but id is a {self.root[problem_id].type.name}."
194193
)
195-
self._fixup_suite_fidelity(thing)
194+
195+
self._percolate_set(thing, thing.problems, "fidelity_levels")
196+
self._percolate_set(thing, thing.problems, "variables")
197+
self._percolate_set(thing, thing.problems, "constraints")
198+
self._percolate_set(thing, thing.problems, "evaluation_time")
199+
196200
return self
197201

198202

0 commit comments

Comments
 (0)