Skip to content

Commit 3c7f5a3

Browse files
committed
'add_model'
1 parent 5152a5a commit 3c7f5a3

47 files changed

Lines changed: 8101 additions & 1629 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

UQPyL/problem/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .mop import DTLZ1, DTLZ2, DTLZ3, DTLZ4, DTLZ5, DTLZ6, DTLZ7
77
from .base import ProblemABC
88
from .problem import Problem
9+
from .model import SimModel
910

1011
singleFunc = ProblemABC.singleFunc
1112

@@ -21,5 +22,6 @@
2122
sop,
2223
mop,
2324
"ProblemABC",
24-
"Problem"
25+
"Problem",
26+
"SimModel"
2527
]

UQPyL/problem/model/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .sim_model import SimModel

UQPyL/problem/model/evaluator.py

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from pathlib import Path
2+
from typing import List, Optional, Tuple
3+
import os
4+
import numpy as np
5+
6+
def parse_fixed_width(line: str, span_1based_inclusive: Tuple[int, int]) -> Optional[float]:
7+
a, b = span_1based_inclusive
8+
s = line[a-1:b].strip()
9+
if not s:
10+
return None
11+
try:
12+
return float(s)
13+
except ValueError:
14+
return None
15+
16+
def parse_col_list(line: str, cols_1based: int, delimiter: str = "whitespace") -> Optional[float]:
17+
parts = line.split() if delimiter == "whitespace" else line.split(delimiter)
18+
if not cols_1based:
19+
return None
20+
idx = cols_1based - 1
21+
if idx < 0 or idx >= len(parts):
22+
return None
23+
try:
24+
return float(parts[idx])
25+
except ValueError:
26+
return None
27+
28+
def read_extract(
29+
dir,
30+
extract, # ExtractSpec
31+
encoding: str = "mbcs",
32+
):
33+
targets = extract.rows
34+
if not targets:
35+
return []
36+
37+
if dir is None:
38+
p = Path(extract.file)
39+
else:
40+
p = Path(os.path.join(dir, extract.file))
41+
42+
vals: List[float] = []
43+
44+
with p.open("r", encoding=encoding, errors="ignore") as f:
45+
cur = 1
46+
for t in targets:
47+
skip = t - cur
48+
for _ in range(skip):
49+
if not f.readline():
50+
return vals # EOF
51+
line = f.readline()
52+
if not line:
53+
return vals
54+
cur = t + 1
55+
56+
col = extract.column
57+
if col.kind == "span":
58+
v = parse_fixed_width(line, col.span) # type: ignore[arg-type]
59+
else:
60+
v = parse_col_list(line, col.col or [], delimiter=col.delimiter)
61+
62+
if v is not None:
63+
vals.append(v)
64+
65+
return np.asarray(vals)

0 commit comments

Comments
 (0)