diff --git a/docs/index.html b/docs/index.html index e4b4df0..b30c225 100644 --- a/docs/index.html +++ b/docs/index.html @@ -154,7 +154,7 @@

OPL – Optimisation problem library

BSO-toolbox C++ - {'40 seconds', '1 second'} + {'1 second', '40 seconds'} https://github.com/TUe-excellent-buildings/BSO-toolbox Building Spatial Design toolbox (TU/e) @@ -1710,11 +1710,11 @@

OPL – Optimisation problem library

>=1 - CEC2013 reference code | IOHexperimenter + IOHexperimenter | CEC2013 reference code C++/Python - https://github.com/P-N-Suganthan/CEC2013 | https://github.com/IOHprofiler/IOHexperimenter - Suganthan's reference implementation | IOHprofiler experimenter framework + https://github.com/IOHprofiler/IOHexperimenter | https://github.com/P-N-Suganthan/CEC2013 + IOHprofiler experimenter framework | Suganthan's reference implementation @@ -1876,7 +1876,7 @@

OPL – Optimisation problem library

CFD test problem suite - {'30s', '15m'} + {'15m', '30s'} https://bitbucket.org/arahat/cfd-test-problem-suite Expensive real-world CFD-based test problems @@ -2245,7 +2245,7 @@

OPL – Optimisation problem library

coco-gbea - {'5 seconds', '34 seconds'} + {'34 seconds', '5 seconds'} https://github.com/ttusar/coco-gbea Game-Benchmark for Evolutionary Algorithms (COCO fork) @@ -2899,11 +2899,11 @@

OPL – Optimisation problem library

20 20 - pymoo | modact + modact | pymoo Python {'20ms'} - https://github.com/anyoptimization/pymoo | https://github.com/epfl-lamd/modact - Multi-objective optimization in Python | EPFL-LAMD modact package + https://github.com/epfl-lamd/modact | https://github.com/anyoptimization/pymoo + EPFL-LAMD modact package | Multi-objective optimization in Python @@ -3270,7 +3270,7 @@

OPL – Optimisation problem library

TulipaEnergyModel.jl Julia / JuMP - {'hours', 'minutes'} + {'minutes', 'hours'} https://tulipaenergy.github.io/TulipaEnergyModel.jl/stable/ | https://github.com/TulipaEnergy/Tulipa-OBZ-CaseStudy Large linear program for optimal investment and operation of energy systems diff --git a/docs/problems.html b/docs/problems.html index 92dc2f8..6d9020b 100644 --- a/docs/problems.html +++ b/docs/problems.html @@ -128,7 +128,7 @@ BSO-toolbox C++ - {'40 seconds', '1 second'} + {'1 second', '40 seconds'} https://github.com/TUe-excellent-buildings/BSO-toolbox Building Spatial Design toolbox (TU/e) @@ -1684,11 +1684,11 @@ >=1 - CEC2013 reference code | IOHexperimenter + IOHexperimenter | CEC2013 reference code C++/Python - https://github.com/P-N-Suganthan/CEC2013 | https://github.com/IOHprofiler/IOHexperimenter - Suganthan's reference implementation | IOHprofiler experimenter framework + https://github.com/IOHprofiler/IOHexperimenter | https://github.com/P-N-Suganthan/CEC2013 + IOHprofiler experimenter framework | Suganthan's reference implementation @@ -1850,7 +1850,7 @@ CFD test problem suite - {'30s', '15m'} + {'15m', '30s'} https://bitbucket.org/arahat/cfd-test-problem-suite Expensive real-world CFD-based test problems @@ -2219,7 +2219,7 @@ coco-gbea - {'5 seconds', '34 seconds'} + {'34 seconds', '5 seconds'} https://github.com/ttusar/coco-gbea Game-Benchmark for Evolutionary Algorithms (COCO fork) @@ -2873,11 +2873,11 @@ 20 20 - pymoo | modact + modact | pymoo Python {'20ms'} - https://github.com/anyoptimization/pymoo | https://github.com/epfl-lamd/modact - Multi-objective optimization in Python | EPFL-LAMD modact package + https://github.com/epfl-lamd/modact | https://github.com/anyoptimization/pymoo + EPFL-LAMD modact package | Multi-objective optimization in Python @@ -3244,7 +3244,7 @@ TulipaEnergyModel.jl Julia / JuMP - {'hours', 'minutes'} + {'minutes', 'hours'} https://tulipaenergy.github.io/TulipaEnergyModel.jl/stable/ | https://github.com/TulipaEnergy/Tulipa-OBZ-CaseStudy Large linear program for optimal investment and operation of energy systems diff --git a/problems.yaml b/problems.yaml index c32939e..bf6ab28 100644 --- a/problems.yaml +++ b/problems.yaml @@ -91,11 +91,11 @@ fn_building_spatial: - dim: max: null min: 1 - type: continuous + type: binary - dim: max: null min: 1 - type: binary + type: continuous fn_convex_dtlz2: allows_partial_evaluation: null can_evaluate_objectives_independently: null @@ -142,7 +142,7 @@ fn_emdo: code_examples: null constraints: - equality: null - hard: 'yes' + hard: yes number: null type: box - equality: null @@ -153,28 +153,16 @@ fn_emdo: hard: 'yes' number: 12 type: unknown - description: '# Goal - - Find a design of a synchronous electric motor for power steering systems that - minimizes costs and satisfies all constraints. - - - # Motivation - - Challenging to find good solutions in a limited time. - - - # Key Challenges - - * Time-consuming solution evaluation - - * Highly-constrained problem - - * Constraints are multimodal - - - This is not an available problem, but could be interesting to show to researchers - which difficulties appear in real-world problems.' + - equality: null + hard: yes + number: null + type: box + description: "# Goal\nFind a design of a synchronous electric motor for power steering + systems that minimizes costs and satisfies all constraints.\n\n# Motivation\n\ + Challenging to find good solutions in a limited time.\n\n# Key Challenges\n* Time-consuming + solution evaluation\n* Highly-constrained problem\n* Constraints are multimodal\n\ + \nThis is not an available problem, but could be interesting to show to researchers + which difficulties appear in real-world problems." dynamic_type: null evaluation_time: - 8 minutes @@ -245,8 +233,8 @@ fn_fleetopt: type: problem variables: - dim: - - 13208 - 54 + - 13208 type: integer fn_gasoline: allows_partial_evaluation: null @@ -324,11 +312,11 @@ fn_invdeceptive_deceptive_rotell: - dim: max: null min: 1 - type: continuous + type: binary - dim: max: null min: 1 - type: binary + type: continuous fn_inverted_dtlz1: allows_partial_evaluation: null can_evaluate_objectives_independently: null @@ -442,11 +430,11 @@ fn_onemax_sphere_deceptive_rotell: - dim: max: null min: 1 - type: continuous + type: binary - dim: max: null min: 1 - type: binary + type: continuous fn_onemax_sphere_zeromax_sphere: allows_partial_evaluation: null can_evaluate_objectives_independently: null @@ -479,11 +467,11 @@ fn_onemax_sphere_zeromax_sphere: - dim: max: null min: 1 - type: continuous + type: binary - dim: max: null min: 1 - type: binary + type: continuous fn_radar_waveform: allows_partial_evaluation: null can_evaluate_objectives_independently: null @@ -645,6 +633,10 @@ gen_ealain: tags: null type: generator variables: + - dim: + max: null + min: 1 + type: binary - dim: max: null min: 1 @@ -993,6 +985,10 @@ gen_randoptgen: tags: null type: generator variables: + - dim: + max: null + min: 1 + type: binary - dim: max: null min: 1 @@ -1595,15 +1591,15 @@ suite_amvop: - dim: max: null min: 1 - type: continuous + type: categorical - dim: max: null min: 1 - type: integer + type: continuous - dim: max: null min: 1 - type: categorical + type: integer suite_bbob: allows_partial_evaluation: null can_evaluate_objectives_independently: null @@ -2286,6 +2282,10 @@ suite_cuter: tags: null type: suite variables: + - dim: + max: null + min: 1 + type: binary - dim: max: null min: 1 @@ -2313,6 +2313,10 @@ suite_cutest: max: null min: 1 type: unknown + - equality: null + hard: yes + number: null + type: box description: CUTEst for optimization software dynamic_type: null evaluation_time: null @@ -2339,6 +2343,10 @@ suite_cutest: tags: null type: suite variables: + - dim: + max: null + min: 1 + type: binary - dim: max: null min: 1 @@ -2560,8 +2568,12 @@ suite_expobench: hard: some number: null type: unknown - description: Wind farm layout optimization, gas filter design, pipe shape optimization, - hyperparameter tuning, and hospital simulation + - equality: null + hard: yes + number: null + type: box + description: Wind farm layout optimization, gas filter design, pipe shape + optimization, hyperparameter tuning, and hospital simulation dynamic_type: null evaluation_time: null fidelity_levels: null @@ -2571,8 +2583,8 @@ suite_expobench: modality: null name: EXPObench noise_type: - - real-life - observational + - real-life objectives: - 1 problems: null @@ -2591,15 +2603,15 @@ suite_expobench: - dim: max: 135 min: 10 - type: integer + type: categorical - dim: max: 135 min: 10 - type: categorical + type: continuous - dim: max: 135 min: 10 - type: continuous + type: integer suite_gbea: allows_partial_evaluation: null can_evaluate_objectives_independently: null @@ -2808,11 +2820,11 @@ suite_l1_zdt: - dim: max: null min: 1 - type: continuous + type: binary - dim: max: null min: 1 - type: binary + type: continuous suite_l2_dtlz: allows_partial_evaluation: null can_evaluate_objectives_independently: null @@ -2884,11 +2896,11 @@ suite_l2_zdt: - dim: max: null min: 1 - type: continuous + type: binary - dim: max: null min: 1 - type: binary + type: continuous suite_l3_dtlz: allows_partial_evaluation: null can_evaluate_objectives_independently: null @@ -2960,11 +2972,11 @@ suite_l3_zdt: - dim: max: null min: 1 - type: continuous + type: binary - dim: max: null min: 1 - type: binary + type: continuous suite_maop: allows_partial_evaluation: null can_evaluate_objectives_independently: null @@ -3244,10 +3256,10 @@ suite_modact: tags: null type: suite variables: - - dim: 20 - type: integer - dim: 20 type: continuous + - dim: 20 + type: integer suite_morepo: allows_partial_evaluation: null can_evaluate_objectives_independently: null @@ -3388,11 +3400,11 @@ suite_re: - dim: max: 7 min: 2 - type: integer + type: continuous - dim: max: 7 min: 2 - type: continuous + type: integer suite_rwmvop: allows_partial_evaluation: null can_evaluate_objectives_independently: null @@ -3429,15 +3441,15 @@ suite_rwmvop: - dim: max: null min: 1 - type: continuous + type: categorical - dim: max: null min: 1 - type: integer + type: continuous - dim: max: null min: 1 - type: categorical + type: integer suite_sbox_cost: allows_partial_evaluation: null can_evaluate_objectives_independently: null @@ -3712,8 +3724,8 @@ suite_zdt: - dim: max: null min: 1 - type: continuous + type: binary - dim: max: null min: 1 - type: binary + type: continuous diff --git a/src/opltools/schema.py b/src/opltools/schema.py index c2e1339..d63cd04 100644 --- a/src/opltools/schema.py +++ b/src/opltools/schema.py @@ -1,12 +1,66 @@ +import json from enum import Enum from typing import Any from typing_extensions import Self -from pydantic import BaseModel, RootModel, ConfigDict, model_validator +from pydantic import ( + BaseModel, + RootModel, + ConfigDict, + SerializerFunctionWrapHandler, + model_serializer, + model_validator, +) from .yesnosome import YesNoSome from .utils import ValueRange, union_range +def _sort_key(item: Any) -> Any: + """Stable, total-order sort key for an already-serialized set member. + + Scalars (str, int, float, bool) sort by their natural ordering, so e.g. + `{2, 10}` sorts numerically rather than as the strings "10" < "2". Dicts + and lists (serialized nested models) have no natural ordering, so those + fall back to a canonical JSON string. + """ + if isinstance(item, (dict, list)): + return json.dumps(item, sort_keys=True, default=str) + return item + + +class _CanonicalMixin: + """Serialize any `set`-valued field as a sorted list. + + `set` iteration order depends on Python's hash randomization, so two + otherwise-identical models can serialize to different YAML on every run + unless we impose a fixed order. Rather than hand-listing every `set` + field on every model, this inspects each model's actual field values at + serialization time and sorts whichever ones happen to be a `set` - + including nested models' own sets (each canonicalizes itself the same + way) and any `extra="allow"` fields. + """ + + @model_serializer(mode="wrap") + def _canonicalize(self, handler: SerializerFunctionWrapHandler) -> Any: + data = handler(self) + values = dict(self.__dict__) + extra = getattr(self, "__pydantic_extra__", None) + if extra: + values.update(extra) + for name, value in values.items(): + if isinstance(value, set) and name in data: + data[name] = sorted(data[name], key=_sort_key) + return data + + +class CanonicalModel(_CanonicalMixin, BaseModel): + pass + + +class CanonicalRootModel(_CanonicalMixin, RootModel): + pass + + class OPLType(Enum): problem = "problem" suite = "suite" @@ -14,7 +68,7 @@ class OPLType(Enum): implementation = "implementation" -class Link(BaseModel): +class Link(CanonicalModel): type: str | None = None url: str @@ -22,12 +76,12 @@ def __hash__(self): return hash(self.type) + hash(self.url) -class Thing(BaseModel): +class Thing(CanonicalModel): type: OPLType model_config = ConfigDict(extra="allow") -class Objectives(RootModel): +class Objectives(CanonicalRootModel): root: int | set[int] | ValueRange = 0 def union(self, other: Self) -> Self: @@ -43,7 +97,7 @@ class VariableType(Enum): unknown = "unknown" -class Variable(BaseModel): +class Variable(CanonicalModel): type: VariableType = VariableType.unknown dim: int | set[int] | ValueRange | None = 0 @@ -62,7 +116,7 @@ class ConstraintType(Enum): unknown = "unknown" -class Constraint(BaseModel): +class Constraint(CanonicalModel): type: ConstraintType = ConstraintType.unknown hard: YesNoSome | None = None equality: YesNoSome | None = None @@ -79,7 +133,7 @@ class ReferenceType(Enum): critique = "critique" -class Reference(BaseModel): +class Reference(CanonicalModel): title: str | None = None authors: list[str] | None = None link: Link | None = None @@ -143,7 +197,7 @@ class Generator(ProblemLike): type: OPLType = OPLType.generator -class Library(RootModel): +class Library(CanonicalRootModel): root: dict[str, Problem | Generator | Suite | Implementation] = {} def _check_id_references(self, ids, type: OPLType) -> None: