22from typing_extensions import Self
33from pydantic import BaseModel , RootModel , ConfigDict , model_validator
44
5+ from .yesnosome import YesNoSome
56from .utils import ValueRange , union_range
67
78
89class OPLType (Enum ):
910 problem = "problem"
1011 suite = "suite"
1112 generator = "generator"
12- implementation = 'implementation'
13-
14-
15- class YesNoSome (Enum ):
16- yes = "yes"
17- no = "no"
18- some = "some"
19- unknown = "?"
13+ implementation = "implementation"
2014
2115
2216class Link (BaseModel ):
2317 type : str | None = None
2418 url : str
2519
20+ def __hash__ (self ):
21+ return hash (self .type ) + hash (self .url )
22+
2623
2724class Thing (BaseModel ):
2825 type : OPLType
29- model_config = ConfigDict (extra = ' allow' )
26+ model_config = ConfigDict (extra = " allow" )
3027
3128
3229class Objectives (RootModel ):
@@ -37,45 +34,64 @@ def union(self, other: Self) -> Self:
3734 return self
3835
3936
40- class Variables (BaseModel ):
41- continuous : int | set [int ] | ValueRange = 0
42- integer : int | set [int ] | ValueRange = 0
43- binary : int | set [int ] | ValueRange = 0
44- categorical : int | set [int ] | ValueRange = 0
37+ class VariableType (Enum ):
38+ continuous = "continuous"
39+ integer = "integer"
40+ binary = "binary"
41+ categorical = "categorical"
42+ unknown = "unknown"
4543
46- def union (self , other : Self ) -> Self :
47- self .continuous = union_range (self .continuous , other .continuous )
48- self .integer = union_range (self .integer , other .integer )
49- self .binary = union_range (self .integer , other .binary )
50- self .categorical = union_range (self .integer , other .categorical )
51- return self
5244
45+ class Variable (BaseModel ):
46+ type : VariableType = VariableType .unknown
47+ dim : int | set [int ] | ValueRange | None = 0
5348
54- class Constraints (BaseModel ):
55- box : int | set [int ] | ValueRange = 0
56- linear : int | set [int ] | ValueRange = 0
57- function : int | set [int ] | ValueRange = 0
49+ def __hash__ (self ):
50+ if isinstance (self .dim , set ):
51+ dim_hash = hash (frozenset (self .dim ))
52+ else :
53+ dim_hash = hash (self .dim )
54+ return hash (self .type ) + dim_hash
5855
59- def union (self , other : Variables ) -> Self :
60- self .box = union_range (self .box , other .box )
61- self .linear = union_range (self .linear , other .linear )
62- self .function = union_range (self .function , other .function )
63- return self
56+
57+ class ConstraintType (Enum ):
58+ box = "box"
59+ linear = "linear"
60+ function = "function"
61+ unknown = "unknown"
62+
63+
64+ class Constraint (BaseModel ):
65+ type : ConstraintType = ConstraintType .unknown
66+ hard : YesNoSome | None = None
67+ equality : YesNoSome | None = None
68+ number : int | set [int ] | ValueRange | None = None
69+
70+ def __hash__ (self ):
71+ number = frozenset (self .number ) if isinstance (self .number , set ) else self .number
72+ return hash ((self .type , self .hard , self .equality , number ))
6473
6574
6675class Reference (BaseModel ):
6776 title : str
6877 authors : list [str ]
6978 link : Link | None = None
7079
80+ def __hash__ (self ):
81+ return (
82+ hash (self .title )
83+ + sum ([hash (author ) for author in self .authors ])
84+ + hash (self .link )
85+ )
86+
7187
7288class Usage (BaseModel ):
7389 language : str
7490 code : str
7591
7692
7793class Implementation (Thing ):
78- type : OPLType = OPLType .implementation
94+ type : OPLType = OPLType .implementation
7995 name : str
8096 description : str
8197 links : list [Link ] | None = None
@@ -92,9 +108,8 @@ class ProblemLike(Thing):
92108 references : set [Reference ] | None = None
93109 implementations : set [str ] | None = None
94110 objectives : set [int ] | None = None
95- variables : Variables | None = None
96- constraints : Constraints | None = None
97- soft_constraints : Constraints | None = None
111+ variables : set [Variable ] | None = None
112+ constraints : set [Constraint ] | None = None
98113 dynamic_type : set [str ] | None = None
99114 noise_type : set [str ] | None = None
100115 allows_partial_evaluation : YesNoSome | None = None
@@ -104,59 +119,90 @@ class ProblemLike(Thing):
104119 code_examples : set [str ] | None = None
105120 source : set [str ] | None = None
106121
122+ def __hash__ (self ):
123+ return hash ((self .type , self .name ))
124+
107125
108126class Problem (ProblemLike ):
109- type :OPLType = OPLType .problem
127+ type : OPLType = OPLType .problem
110128 instances : ValueRange | list [str ] | None = None
111129
112130
113131class Suite (ProblemLike ):
114- type :OPLType = OPLType .suite
132+ type : OPLType = OPLType .suite
115133 problems : set [str ] | None = None
116134
117135
118136class Generator (ProblemLike ):
119- type :OPLType = OPLType .generator
137+ type : OPLType = OPLType .generator
120138
121139
122140class Library (RootModel ):
123- root : dict [str , Problem | Generator | Suite | Implementation ] | None
141+ root : dict [str , Problem | Generator | Suite | Implementation ] = {}
124142
125- def _check_id_references (self , ids , type :OPLType ) -> None :
126- if not self .root :
127- return
143+ def _check_id_references (self , ids , type : OPLType ) -> None :
128144 for id in ids :
129145 if id in self .root :
130146 if self .root [id ].type != type :
131- raise ValueError (f"ID { id } is a { self .root [id ].name } , expected a { type .name } " )
147+ raise ValueError (
148+ f"ID { id } is a { self .root [id ].name } , expected a { type .name } "
149+ )
132150 else :
133151 raise ValueError (f"Missing { type .name } with id '{ id } '" )
134152
135- def _fixup_fidelity (self , suite : Suite ) -> Suite :
153+ # For a given suite, make sure the fidelty_levels property contains
154+ # the fidelity_levels of all problems in the suite.
155+ def _fixup_suite_fidelity (self , suite : Suite ):
156+ if suite .problems :
157+ if not suite .fidelity_levels :
158+ suite .fidelity_levels = set ()
159+ for pid in suite .problems :
160+ problem = self .root [pid ]
161+ assert isinstance (problem , Problem )
162+ if problem .fidelity_levels :
163+ suite .fidelity_levels .update (problem .fidelity_levels )
164+
136165 return suite
137166
138- @model_validator (mode = "after" )
139- def validate (self ) -> Self :
140- if not self .root :
141- return self
167+ def _fixup_suite_variables (self , suite : Suite ):
168+ if not suite .problems :
169+ return
142170
171+ if suite .variables is None :
172+ suite .variables = set ()
173+ for pid in suite .problems :
174+ problem = self .root [pid ]
175+ assert isinstance (problem , Problem )
176+ if problem .variables is not None :
177+ suite .variables .update (problem .variables )
178+
179+ @model_validator (mode = "after" )
180+ def _validate (self ) -> Self :
143181 # Make sure all problems referenced in suites exists
144182 for id , thing in self .root .items ():
145183 if isinstance (thing , Suite ) and thing .problems :
146184 for problem_id in thing .problems :
147185 if problem_id not in self .root :
148- raise ValueError (f"Suite { id } references problem with undefined id '{ problem_id } '." )
186+ raise ValueError (
187+ f"Suite { id } references problem with undefined id '{ problem_id } '."
188+ )
149189 if self .root [problem_id ].type != OPLType .problem :
150- raise ValueError (f"Suite { id } references problem with id '{ problem_id } ' but id is a { self .root [problem_id ].type .name } ." )
190+ raise ValueError (
191+ f"Suite { id } references problem with id '{ problem_id } ' but id is a { self .root [problem_id ].type .name } ."
192+ )
193+ self ._fixup_suite_fidelity (thing )
151194 return self
152195
196+
153197__all__ = [
154- "Problem" ,
155- "Suite" ,
198+ "Constraint" ,
156199 "Generator" ,
157200 "Implementation" ,
158201 "Library" ,
159- "YesNoSome" ,
160202 "Link" ,
161- "Reference"
203+ "Problem" ,
204+ "Reference" ,
205+ "Suite" ,
206+ "Variable" ,
207+ "YesNoSome" ,
162208]
0 commit comments