1+ import numpy as np
2+ from load_cfg_general import expand_row_ranges
3+ from run_error import RunError
4+
5+
6+ class Evaluator :
7+ def __init__ (self , cfg , funcManager ):
8+ self .cfg = cfg
9+ self .funcManager = funcManager
10+
11+ self .nOutput , self .optType , self .obj_refs , self .nConstraints , self .con_refs = (
12+ self ._parse_objectives_constraints ()
13+ )
14+ self .diag_refs = self ._parse_diagnostics ()
15+
16+ def _parse_objectives_constraints (self ):
17+ optType = []
18+ obj_refs = {}
19+ con_refs = {}
20+
21+ for obj_id in self .cfg .objectives .use :
22+ obj_cfg = self .cfg .objectives .items [obj_id ]
23+ optType .append (obj_cfg .sense )
24+ obj_refs [obj_id ] = obj_cfg .ref
25+
26+ nOutput = len (optType )
27+
28+ for con_id in self .cfg .constraints .use :
29+ con_cfg = self .cfg .constraints .items [con_id ]
30+ con_refs [con_id ] = con_cfg .ref
31+
32+ nConstraints = len (con_refs )
33+
34+ return nOutput , optType , obj_refs , nConstraints , con_refs
35+
36+ def _parse_diagnostics (self ):
37+ diag_refs = {}
38+
39+ for diag_id in self .cfg .diagnostics .use :
40+ diag_cfg = self .cfg .diagnostics .items [diag_id ]
41+ diag_refs [diag_id ] = diag_cfg .ref
42+
43+ return diag_refs
44+
45+ def _normalize_value (self , val ):
46+ if isinstance (val , (list , tuple , np .ndarray )):
47+ return np .asarray (val ).ravel ()
48+ return val
49+
50+ def _to_scalar (self , value , label : str ) -> float :
51+ value = self ._normalize_value (value )
52+
53+ if isinstance (value , np .ndarray ):
54+ if value .size != 1 :
55+ raise ValueError (
56+ f"{ label } must be scalar, but got array with shape { value .shape } "
57+ )
58+ return float (value .item ())
59+
60+ return float (value )
61+
62+ def _collect_record_values (self , refs : dict , env : dict , kind : str ) -> dict :
63+ result = {}
64+
65+ for item_id , ref_id in refs .items ():
66+ if ref_id not in env :
67+ raise RunError (
68+ stage = "evaluator" ,
69+ code = "MISSING_CONTEXT" ,
70+ target = item_id ,
71+ message = f"{ kind } '{ item_id } ' requires context key '{ ref_id } '"
72+ )
73+
74+ try :
75+ result [item_id ] = self ._to_scalar (env [ref_id ], f"{ kind } '{ item_id } '" )
76+ except Exception as e :
77+ raise RunError (
78+ stage = "evaluator" ,
79+ code = "INVALID_VALUE" ,
80+ target = item_id ,
81+ message = f"{ kind } '{ item_id } ' cannot be converted to scalar: { e } "
82+ ) from e
83+
84+ return result
85+
86+ def evaluate_all (self , context ):
87+ env = context
88+ record = {}
89+
90+ for derived in self .cfg .derived :
91+ d_id = derived .id
92+
93+ if getattr (derived , "call" , None ):
94+ func_name = derived .call .func
95+ args_map = derived .call .args
96+ kwargs = {}
97+
98+ for arg_name , context_key in args_map .items ():
99+ if context_key not in env :
100+ raise RunError (
101+ stage = "derived" ,
102+ code = "DEPENDENCY_MISSING" ,
103+ target = d_id ,
104+ message = f"Derived '{ d_id } ' requires context key '{ context_key } '"
105+ )
106+
107+ kwargs [arg_name ] = self ._normalize_value (env [context_key ])
108+
109+ try :
110+ result = self .funcManager .call (func_name , ** kwargs )
111+ except Exception as e :
112+ raise RunError (
113+ stage = "derived" ,
114+ code = "FUNC_CALL_FAILED" ,
115+ target = d_id ,
116+ message = f"Function '{ func_name } ' failed: { e } "
117+ ) from e
118+
119+ elif getattr (derived , "expr" , None ):
120+ deps = expand_row_ranges (derived .expr )
121+ expr_env = {}
122+
123+ for dep in deps :
124+ if dep not in env :
125+ raise RunError (
126+ stage = "derived" ,
127+ code = "DEPENDENCY_MISSING" ,
128+ target = d_id ,
129+ message = f"Derived '{ d_id } ' expr requires context key '{ dep } '"
130+ )
131+
132+ expr_env [dep ] = self ._normalize_value (env [dep ])
133+
134+ try :
135+ result = eval (derived .expr , {"__builtins__" : {}, "np" : np }, expr_env )
136+ except NameError as e :
137+ raise RunError (
138+ stage = "derived" ,
139+ code = "EXPR_EVAL_FAILED" ,
140+ target = d_id ,
141+ message = f"Derived '{ d_id } ' expr evaluation failed: { e } "
142+ ) from e
143+ except Exception as e :
144+ raise RunError (
145+ stage = "derived" ,
146+ code = "EXPR_EVAL_FAILED" ,
147+ target = d_id ,
148+ message = f"Derived '{ d_id } ' expr evaluation failed: { e } "
149+ ) from e
150+
151+ else :
152+ raise RunError (
153+ stage = "derived" ,
154+ code = "INVALID_CONFIG" ,
155+ target = d_id ,
156+ message = f"Derived '{ d_id } ' has neither 'call' nor 'expr'"
157+ )
158+
159+ if result is None :
160+ raise RunError (
161+ stage = "derived" ,
162+ code = "EMPTY_RESULT" ,
163+ target = d_id ,
164+ message = f"Derived '{ d_id } ' returned None"
165+ )
166+
167+ env [d_id ] = result
168+
169+ record .update (self ._collect_record_values (self .obj_refs , env , "Objective" ))
170+ record .update (self ._collect_record_values (self .con_refs , env , "Constraint" ))
171+ record .update (self ._collect_record_values (self .diag_refs , env , "Diagnostic" ))
172+
173+ return record
174+
175+ def get_evaluation_info (self ):
176+ return self .nOutput , self .optType , self .nConstraints
0 commit comments