@@ -167,3 +167,126 @@ def get_random_solution(self, rand_sol_rng: MRG32k3a) -> tuple: # noqa: D102
167167 factorized = False ,
168168 )
169169 )
170+
171+
172+ class Example2ModelConfig (BaseModel ):
173+ """Configuration model for Example-2 simulation.
174+
175+ A model that is a deterministic quadratic function evaluated with noise.
176+ """
177+
178+ x : Annotated [
179+ tuple [int , ...],
180+ Field (
181+ default = (0 , 0 , 0 , 0 ),
182+ description = "point to evaluate" ,
183+ ),
184+ ]
185+
186+
187+ class Example2ProblemConfig (BaseModel ):
188+ """Configuration model for Example-2 Problem.
189+
190+ Base class to implement simulation-optimization problems.
191+ """
192+
193+ initial_solution : Annotated [
194+ tuple [int , ...],
195+ Field (
196+ default = (0 , 0 , 0 , 0 ),
197+ description = "initial solution" ,
198+ ),
199+ ]
200+ budget : Annotated [
201+ int ,
202+ Field (
203+ default = 1000 ,
204+ description = "max # of replications for a solver to take" ,
205+ gt = 0 ,
206+ json_schema_extra = {"isDatafarmable" : False },
207+ ),
208+ ]
209+
210+
211+ class Example2Model (Model ):
212+ """A model that is a deterministic quadratic function evaluated with noise."""
213+
214+ class_name_abbr : ClassVar [str ] = "EXAMPLE-2-MODEL"
215+ class_name : ClassVar [str ] = "Quadratic Function + Noise (Discrete)"
216+ config_class : ClassVar [type [BaseModel ]] = Example2ModelConfig
217+ n_rngs : ClassVar [int ] = 1
218+ n_responses : ClassVar [int ] = 1
219+
220+ def __init__ (self , fixed_factors : dict | None = None ) -> None :
221+ """Initialize the model.
222+
223+ Args:
224+ fixed_factors (dict | None): fixed factors of the model.
225+ If None, use default values.
226+ """
227+ # Let the base class handle default arguments.
228+ super ().__init__ (fixed_factors )
229+ self .noise_model = Normal ()
230+
231+ def before_replicate (self , rng_list : list [MRG32k3a ]) -> None : # noqa: D102
232+ self .noise_model .set_rng (rng_list [0 ])
233+
234+ def replicate (self ) -> tuple [dict , dict ]:
235+ """Evaluate a quadratic function f(x) with stochastic noise."""
236+ x = np .array (self .factors ["x" ])
237+ target = np .array ([1 , 2 , 3 , 4 ])
238+ fn_eval_at_x = np .sum ((x - target ) ** 2 ) + self .noise_model .random ()
239+
240+ responses = {"est_f(x)" : fn_eval_at_x }
241+ return responses , {}
242+
243+
244+ class Example2Problem (Problem ):
245+ """Discrete quadratic minimization example with noise."""
246+
247+ class_name_abbr : ClassVar [str ] = "EXAMPLE-2"
248+ class_name : ClassVar [str ] = "Min Quadratic Function + Noise (Discrete)"
249+ config_class : ClassVar [type [BaseModel ]] = Example2ProblemConfig
250+ model_class : ClassVar [type [Model ]] = Example2Model
251+ n_objectives : ClassVar [int ] = 1
252+ n_stochastic_constraints : ClassVar [int ] = 0
253+ minmax : ClassVar [tuple [int , ...]] = (- 1 ,)
254+ constraint_type : ClassVar [ConstraintType ] = ConstraintType .UNCONSTRAINED
255+ variable_type : ClassVar [VariableType ] = VariableType .DISCRETE
256+ gradient_available : ClassVar [bool ] = False
257+ model_default_factors : ClassVar [dict ] = {}
258+ model_decision_factors : ClassVar [set [str ]] = {"x" }
259+
260+ @property
261+ def optimal_value (self ) -> float | None : # noqa: D102
262+ return 0.0
263+
264+ @property
265+ def optimal_solution (self ) -> tuple | None : # noqa: D102
266+ return (1 , 2 , 3 , 4 )
267+
268+ @property
269+ def dim (self ) -> int : # noqa: D102
270+ return 4
271+
272+ @property
273+ def lower_bounds (self ) -> tuple : # noqa: D102
274+ return (- 4 ,) * self .dim
275+
276+ @property
277+ def upper_bounds (self ) -> tuple : # noqa: D102
278+ return (4 ,) * self .dim
279+
280+ def vector_to_factor_dict (self , vector : tuple ) -> dict : # noqa: D102
281+ return {"x" : vector [:]}
282+
283+ def factor_dict_to_vector (self , factor_dict : dict ) -> tuple : # noqa: D102
284+ return tuple (factor_dict ["x" ])
285+
286+ def replicate (self , _x : tuple ) -> RepResult : # noqa: D102
287+ responses , _ = self .model .replicate ()
288+ objectives = [Objective (stochastic = responses ["est_f(x)" ])]
289+ return RepResult (objectives = objectives )
290+
291+ def get_random_solution (self , rand_sol_rng : MRG32k3a ) -> tuple : # noqa: D102
292+ return tuple (rand_sol_rng .randint (- 4 , 4 ) for _ in range (self .dim ))
0 commit comments