Files
SystemSimulationApp/tests/test_system_optimization_skill.py
T

1756 lines
68 KiB
Python

"""Contract tests for the system-simulation optimization helper.
The optimizer is exercised with deterministic in-process simulation results. No
test starts the FastAPI service or submits a real, potentially long simulation.
"""
from __future__ import annotations
import argparse
import contextlib
import copy
import dataclasses
import hashlib
import importlib.util
import json
import math
import sys
import tempfile
import unittest
import xml.etree.ElementTree as ET
from pathlib import Path
from unittest import mock
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
SCRIPTS_DIRECTORY = (
REPOSITORY_ROOT / "skills" / "system-simulation" / "scripts"
)
SCRIPT_PATH = SCRIPTS_DIRECTORY / "optimization_skill.py"
MODULE_NAME = "system_optimization_skill_under_test"
MODULE_SPEC = importlib.util.spec_from_file_location(MODULE_NAME, SCRIPT_PATH)
if MODULE_SPEC is None or MODULE_SPEC.loader is None: # pragma: no cover
raise RuntimeError(f"Cannot load optimization helper from {SCRIPT_PATH}")
optimization = importlib.util.module_from_spec(MODULE_SPEC)
sys.modules[MODULE_NAME] = optimization
MODULE_SPEC.loader.exec_module(optimization)
BASE_URL = "http://127.0.0.1:8000"
ABSENT = object()
RESULT_VARIABLES = [
{
"key": "sensor.output",
"componentId": "sensor",
"name": "output",
"label": "Output",
"quantity": "displacement",
"unit": "m",
},
{
"key": "sensor.limit",
"componentId": "sensor",
"name": "limit",
"label": "Limit response",
"quantity": "displacement",
"unit": "m",
},
]
PROJECT = {
"projectSchemaVersion": 1,
"name": "optimization-fixture",
"nodes": [
{
"id": "component-a",
"type": "component",
"position": {"x": 10, "y": 20},
"data": {
"componentType": "FIXTURE",
"label": "A",
"parameters": {"gain": "1 + 1", "fixed": 99.0},
"custom": {"preserve": True},
},
},
{
"id": "component-b",
"type": "component",
"data": {
"componentType": "FIXTURE",
"parameters": {"gain": 7.0},
},
},
],
"edges": [{"id": "edge-1", "source": "component-a", "target": "component-b"}],
"simulation": {"startTime": 0.0, "endTime": 1.0, "sampleStep": 0.1},
"customRoot": {"preserve": [1, 2, 3]},
}
BASELINE_XML = b"""<?xml version="1.0" encoding="UTF-8"?>
<System name="optimization-fixture" schemaVersion="3">
<Simulation startTime="0" endTime="1" sampleStep="0.1" />
<Components>
<Component id="component-a" type="FIXTURE">
<Parameter name="gain" value="2" />
<Parameter name="fixed" value="99" />
</Component>
<Component id="component-b" type="FIXTURE">
<Parameter name="gain" value="7" />
</Component>
</Components>
</System>
"""
def _json_bytes(value: object) -> bytes:
return (json.dumps(value, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
def _valid_spec(
*,
seed: int = 12345,
max_simulation_runs: int = 6,
lower: float = 0.0,
upper: float = 4.0,
) -> dict[str, object]:
return {
"optimizationSchemaVersion": 1,
"objective": {
"resultKey": "sensor.output",
"expectedUnit": "m",
"statistic": {"kind": "final", "window": None},
"goal": {"kind": "minimize"},
},
"designVariables": [
{
"id": "gain",
"componentId": "component-a",
"parameter": "gain",
"unit": "m",
"lower": lower,
"upper": upper,
}
],
"constraints": [
{
"id": "limit",
"resultKey": "sensor.limit",
"expectedUnit": "m",
"statistic": {"kind": "maximum", "window": None},
"lower": None,
"upper": 4.0,
"tolerance": 0.0,
"scale": 1.0,
}
],
"algorithm": {
"name": "differentialEvolution",
"seed": seed,
"populationSize": 4,
"mutationFactor": 0.8,
"crossoverProbability": 0.7,
},
"budget": {
"maxSimulationRuns": max_simulation_runs,
"maxWallSeconds": 60.0,
},
"validation": {
"relativeTolerance": 1e-12,
"absoluteTolerance": 1e-12,
},
}
def _inspection(
*,
optimization_eligible: object = ABSENT,
editor: object = ABSENT,
options: object = ABSENT,
unit: object = "m",
value: object = 2.0,
minimum: object = 0.0,
maximum: object = 4.0,
minimum_exclusive: object = False,
) -> dict[str, object]:
contract = {
"name": "gain",
"label": "Gain",
"quantity": "displacement",
"unit": unit,
"value": value,
"minimum": minimum,
"maximum": maximum,
"minimumExclusive": minimum_exclusive,
}
if optimization_eligible is not ABSENT:
contract["optimizationEligible"] = optimization_eligible
if editor is not ABSENT:
contract["editor"] = editor
if options is not ABSENT:
contract["options"] = options
return {
"ok": True,
"system": {
"name": "optimization-fixture",
"componentDetails": [
{
"id": "component-a",
"compiled": {"parameters": [contract]},
"source": copy.deepcopy(PROJECT["nodes"][0]),
}
],
"resultVariables": copy.deepcopy(RESULT_VARIABLES),
},
}
def _make_plan(
directory: Path,
*,
output_name: str = "optimization-output",
seed: int = 12345,
max_simulation_runs: int = 6,
lower: float = 0.0,
upper: float = 4.0,
inspection: dict[str, object] | None = None,
) -> optimization.RuntimePlan:
directory.mkdir(parents=True, exist_ok=True)
source_path = directory / "source.json"
source_path.write_bytes(_json_bytes(PROJECT))
spec_payload = _valid_spec(
seed=seed,
max_simulation_runs=max_simulation_runs,
lower=lower,
upper=upper,
)
spec_path = directory / "optimization-spec.json"
spec_path.write_bytes(_json_bytes(spec_payload))
source = optimization.simulation.load_source(str(source_path), "json")
spec_source = optimization.simulation.load_source(str(spec_path), "json")
spec = optimization.parse_optimization_spec(spec_source.parsed)
resolved_inspection = inspection if inspection is not None else _inspection()
variables = optimization.simulation._available_variables(resolved_inspection)
resolved = optimization._resolve_design_variables(spec, resolved_inspection)
output_directory = directory / output_name
token = optimization._confirmation_token(
source_sha256=source.sha256,
spec_sha256=spec_source.sha256,
baseline_xml_sha256=hashlib.sha256(BASELINE_XML).hexdigest(),
output_directory=output_directory,
base_url=BASE_URL,
timeout=10.0,
)
return optimization.RuntimePlan(
source=source,
spec_source=spec_source,
spec=spec,
inspection=resolved_inspection,
baseline_xml=BASELINE_XML,
variables=variables,
resolved_design_variables=resolved,
output_directory=output_directory,
base_url=BASE_URL,
timeout=10.0,
confirmation_token=token,
)
def _xml_parameter_values(xml: bytes) -> dict[tuple[str, str], str]:
root = ET.fromstring(xml)
values: dict[tuple[str, str], str] = {}
for component in root.findall("./Components/Component"):
component_id = component.get("id")
if component_id is None:
continue
for parameter in component.findall("./Parameter"):
name = parameter.get("name")
value = parameter.get("value")
if name is not None and value is not None:
values[(component_id, name)] = value
return values
class OptimizationSpecTests(unittest.TestCase):
def test_spec_rejects_unknown_fields_at_every_nested_contract(self) -> None:
parsed = optimization.parse_optimization_spec(_valid_spec())
self.assertEqual(parsed.algorithm.name, "differentialEvolution")
cases: list[tuple[str, dict[str, object]]] = []
root_unknown = _valid_spec()
root_unknown["surprise"] = True
cases.append(("root", root_unknown))
objective_unknown = _valid_spec()
objective = objective_unknown["objective"]
assert isinstance(objective, dict)
objective["label"] = "not part of schema 1"
cases.append(("objective", objective_unknown))
variable_unknown = _valid_spec()
design_variables = variable_unknown["designVariables"]
assert isinstance(design_variables, list)
assert isinstance(design_variables[0], dict)
design_variables[0]["logScale"] = True
cases.append(("design variable", variable_unknown))
window_unknown = _valid_spec()
objective = window_unknown["objective"]
assert isinstance(objective, dict)
statistic = objective["statistic"]
assert isinstance(statistic, dict)
statistic["window"] = {"start": 0.0, "end": 1.0, "closed": True}
cases.append(("window", window_unknown))
algorithm_unknown = _valid_spec()
algorithm = algorithm_unknown["algorithm"]
assert isinstance(algorithm, dict)
algorithm["workers"] = 2
cases.append(("algorithm", algorithm_unknown))
for label, payload in cases:
with self.subTest(label=label):
with self.assertRaises(optimization.simulation.InputError) as caught:
optimization.parse_optimization_spec(payload)
self.assertEqual(caught.exception.code, "OPTIMIZATION_SPEC_INVALID")
self.assertTrue(caught.exception.details["unknown"])
def test_spec_rejects_boolean_numeric_values_and_invalid_bounds(self) -> None:
boolean_bound = _valid_spec()
variables = boolean_bound["designVariables"]
assert isinstance(variables, list) and isinstance(variables[0], dict)
variables[0]["lower"] = False
with self.assertRaises(optimization.simulation.InputError) as caught:
optimization.parse_optimization_spec(boolean_bound)
self.assertEqual(caught.exception.code, "OPTIMIZATION_SPEC_INVALID")
reversed_bounds = _valid_spec()
variables = reversed_bounds["designVariables"]
assert isinstance(variables, list) and isinstance(variables[0], dict)
variables[0]["lower"] = 4.0
with self.assertRaises(optimization.simulation.InputError) as caught:
optimization.parse_optimization_spec(reversed_bounds)
self.assertEqual(caught.exception.code, "OPTIMIZATION_BOUNDS_INVALID")
overflowing_span = _valid_spec(lower=-1e308, upper=1e308)
with self.assertRaises(optimization.simulation.InputError) as caught:
optimization.parse_optimization_spec(overflowing_span)
self.assertEqual(caught.exception.code, "OPTIMIZATION_BOUNDS_INVALID")
class StatisticTests(unittest.TestCase):
def test_statistics_use_linearly_interpolated_window_boundaries(self) -> None:
times = [0.0, 1.0, 2.0]
values = [0.0, 2.0, 0.0]
window = optimization.TimeWindow(0.5, 1.5)
expected = {
"final": 1.0,
"minimum": 1.0,
"maximum": 2.0,
"timeMean": 1.5,
"rms": math.sqrt(2.5),
"integral": 1.5,
"absoluteIntegral": 1.5,
"peakAbsolute": 2.0,
}
for kind, expected_value in expected.items():
with self.subTest(kind=kind):
actual = optimization.statistic_value(
times,
values,
optimization.StatisticSpec(kind, window),
)
self.assertAlmostEqual(actual, expected_value)
def test_statistic_rejects_uncovered_windows_and_nonmonotonic_time(self) -> None:
with self.assertRaises(optimization.OptimizationError) as caught:
optimization.statistic_value(
[0.0, 1.0],
[1.0, 2.0],
optimization.StatisticSpec(
"final", optimization.TimeWindow(-0.1, 0.5)
),
)
self.assertEqual(caught.exception.code, "OPTIMIZATION_WINDOW_NOT_COVERED")
with self.assertRaises(optimization.OptimizationError) as caught:
optimization.statistic_value(
[0.0, 1.0, 1.0],
[1.0, 2.0, 3.0],
optimization.StatisticSpec("maximum", None),
)
self.assertEqual(caught.exception.code, "OPTIMIZATION_RESULT_TIME_INVALID")
def test_completed_metrics_reject_result_unit_drift(self) -> None:
spec = optimization.parse_optimization_spec(_valid_spec())
variables = copy.deepcopy(RESULT_VARIABLES)
variables[0]["unit"] = "cm"
result = {
"status": "completed",
"success": True,
"variables": variables,
"series": {
"time": [0.0, 1.0],
"sensor.output": [1.0, 1.0],
"sensor.limit": [1.0, 1.0],
},
}
with self.assertRaises(optimization.OptimizationError) as caught:
optimization._completed_metrics(result, spec)
self.assertEqual(
caught.exception.code, "OPTIMIZATION_RESULT_METADATA_MISMATCH"
)
self.assertEqual(caught.exception.details["expected"], "m")
self.assertEqual(caught.exception.details["received"], "cm")
def test_metric_overflow_is_a_structured_error(self) -> None:
with self.assertRaises(optimization.OptimizationError) as caught:
optimization.statistic_value(
[0.0, 1.0],
[1e308, 1e308],
optimization.StatisticSpec("rms", None),
)
self.assertEqual(caught.exception.code, "OPTIMIZATION_METRIC_OVERFLOW")
def test_signed_integral_fsum_value_error_is_structured_overflow(self) -> None:
with self.assertRaises(optimization.OptimizationError) as caught:
optimization.statistic_value(
[0.0, 1.0, 2.0, 3.0],
[1e308, 1e308, -1e308, -1e308],
optimization.StatisticSpec("integral", None),
)
self.assertEqual(caught.exception.code, "OPTIMIZATION_METRIC_OVERFLOW")
def test_integral_metrics_report_series_unit_times_seconds(self) -> None:
payload = _valid_spec()
objective = payload["objective"]
constraints = payload["constraints"]
assert isinstance(objective, dict)
assert isinstance(constraints, list) and isinstance(constraints[0], dict)
objective["statistic"] = {"kind": "integral", "window": None}
constraints[0]["statistic"] = {
"kind": "absoluteIntegral",
"window": None,
}
spec = optimization.parse_optimization_spec(payload)
result = {
"status": "completed",
"success": True,
"variables": copy.deepcopy(RESULT_VARIABLES),
"series": {
"time": [0.0, 1.0],
"sensor.output": [1.0, 1.0],
"sensor.limit": [2.0, 2.0],
},
}
metrics = optimization._completed_metrics(result, spec)
self.assertEqual(spec.objective.as_dict()["metricUnit"], "(m)*s")
self.assertEqual(metrics[2][0]["seriesUnit"], "m")
self.assertEqual(metrics[2][0]["unit"], "(m)*s")
class ObjectiveEndpointTrendTests(unittest.TestCase):
@staticmethod
def _objective(*, statistic_kind: str = "final") -> optimization.ObjectiveSpec:
payload = _valid_spec()
objective = payload["objective"]
assert isinstance(objective, dict)
statistic = objective["statistic"]
assert isinstance(statistic, dict)
statistic["kind"] = statistic_kind
return optimization.parse_optimization_spec(payload).objective
def test_final_objective_detects_material_terminal_change(self) -> None:
times = [float(index) for index in range(101)]
values = [100.0] * 95 + [100.0, 98.0, 96.0, 94.0, 92.0, 90.0]
trend = optimization._objective_endpoint_trend(
{
"status": "completed",
"success": True,
"series": {"time": times, "sensor.output": values},
},
self._objective(),
expected_end=100.0,
)
self.assertTrue(trend["applicable"])
self.assertEqual(trend["status"], "materialChangeDetected")
self.assertTrue(trend["materialChangeDetected"])
self.assertEqual(trend["direction"], "decreasing")
self.assertFalse(trend["steadyStateProven"])
def test_final_objective_reports_no_material_terminal_change(self) -> None:
times = [float(index) for index in range(101)]
values = [100.0] * len(times)
trend = optimization._objective_endpoint_trend(
{
"status": "completed",
"success": True,
"series": {"time": times, "sensor.output": values},
},
self._objective(),
expected_end=100.0,
)
self.assertTrue(trend["applicable"])
self.assertEqual(trend["status"], "noMaterialChangeDetected")
self.assertFalse(trend["materialChangeDetected"])
self.assertEqual(trend["direction"], "flat")
self.assertFalse(trend["steadyStateProven"])
def test_final_objective_labels_range_only_signal_as_fluctuation(self) -> None:
times = [float(index) for index in range(101)]
values = [100.0] * 95 + [100.0, 104.0, 96.0, 104.0, 96.0, 100.0]
trend = optimization._objective_endpoint_trend(
{
"status": "completed",
"success": True,
"series": {"time": times, "sensor.output": values},
},
self._objective(),
expected_end=100.0,
)
self.assertEqual(trend["status"], "materialChangeDetected")
self.assertEqual(trend["direction"], "fluctuating")
self.assertFalse(trend["directionalChangeDetected"])
self.assertTrue(trend["tailVariabilityDetected"])
self.assertEqual(trend["detectionReasons"], ["tailVariability"])
self.assertFalse(trend["steadyStateProven"])
def test_flat_tail_followed_by_step_is_directional_not_fluctuating(self) -> None:
times = [float(index) for index in range(101)]
values = [100.0] * 100 + [103.0]
trend = optimization._objective_endpoint_trend(
{
"status": "completed",
"success": True,
"series": {"time": times, "sensor.output": values},
},
self._objective(),
expected_end=100.0,
)
self.assertEqual(trend["status"], "materialChangeDetected")
self.assertEqual(trend["direction"], "increasing")
self.assertTrue(trend["directionalChangeDetected"])
self.assertEqual(trend["directionalConsistency"], 1.0)
self.assertEqual(trend["nonzeroIncrementCount"], 1)
def test_final_objective_reports_insufficient_terminal_samples(self) -> None:
trend = optimization._objective_endpoint_trend(
{
"status": "completed",
"success": True,
"series": {
"time": [0.0, 1.0, 2.0, 3.0, 4.0],
"sensor.output": [5.0, 4.0, 3.0, 2.0, 1.0],
}
},
self._objective(),
expected_end=4.0,
)
self.assertTrue(trend["applicable"])
self.assertEqual(trend["status"], "insufficientData")
self.assertEqual(trend["availableSamples"], 5)
self.assertFalse(trend["steadyStateProven"])
def test_non_final_objective_is_not_applicable(self) -> None:
trend = optimization._objective_endpoint_trend(
{},
self._objective(statistic_kind="maximum"),
)
self.assertFalse(trend["applicable"])
self.assertEqual(trend["status"], "notApplicable")
self.assertFalse(trend["steadyStateProven"])
def test_incomplete_or_short_of_planned_endpoint_is_unavailable(self) -> None:
incomplete = optimization._objective_endpoint_trend(
{
"status": "stopped",
"success": False,
"series": {
"time": [0.0, 0.5],
"sensor.output": [2.0, 1.0],
},
},
self._objective(),
expected_end=1.0,
)
short = optimization._objective_endpoint_trend(
{
"status": "completed",
"success": True,
"series": {
"time": [index / 10 for index in range(10)],
"sensor.output": [1.0] * 10,
},
},
self._objective(),
expected_end=1.0,
)
self.assertEqual(incomplete["status"], "unavailable")
self.assertEqual(incomplete["reason"], "freshVerificationNotCompleted")
self.assertEqual(short["status"], "unavailable")
self.assertEqual(short["reason"], "plannedEndpointNotCovered")
self.assertFalse(incomplete["steadyStateProven"])
self.assertFalse(short["steadyStateProven"])
def test_terminal_trend_numeric_overflow_degrades_to_unavailable(self) -> None:
times = [float(index) for index in range(101)]
values = [1e308] * 95 + [1e308, -1e308, 1e308, -1e308, 1e308, -1e308]
trend = optimization._objective_endpoint_trend(
{
"status": "completed",
"success": True,
"series": {"time": times, "sensor.output": values},
},
self._objective(),
expected_end=100.0,
)
self.assertEqual(trend["status"], "unavailable")
self.assertEqual(trend["reason"], "terminalTrendNumericOverflow")
self.assertFalse(trend["steadyStateProven"])
class ReportSafetyTests(unittest.TestCase):
def test_report_escapes_markdown_link_and_image_delimiters(self) -> None:
with tempfile.TemporaryDirectory() as directory_text:
plan = _make_plan(Path(directory_text))
injected_unit = "![x](https://example.invalid/pixel)"
objective = dataclasses.replace(
plan.spec.objective,
expected_unit=injected_unit,
)
plan = dataclasses.replace(
plan,
spec=dataclasses.replace(plan.spec, objective=objective),
)
runner = optimization.OptimizationRunner(plan, "report-safety-test")
report = runner._report_markdown(
{
"solutionStatus": "noFeasibleCandidate",
"terminationReason": "simulationBudgetExhausted",
"counts": {},
}
)
self.assertNotIn(injected_unit, report)
self.assertNotRegex(report, r"!\[[^]]*\]\([^)]*\)")
self.assertNotRegex(report, r"\[[^]]*\]\([^)]*\)")
class ParameterContractTests(unittest.TestCase):
def test_user_selected_si_parameter_resolves_with_source_provenance(self) -> None:
spec = optimization.parse_optimization_spec(_valid_spec())
resolved = optimization._resolve_design_variables(spec, _inspection())
self.assertEqual(len(resolved), 1)
self.assertEqual(resolved[0].initial, 2.0)
self.assertEqual(resolved[0].spec.unit, "m")
self.assertTrue(resolved[0].was_explicit)
self.assertEqual(resolved[0].original_value, "1 + 1")
self.assertIsNone(resolved[0].catalog_optimization_eligible)
def test_discrete_metadata_unit_and_bounds_are_authoritative(self) -> None:
cases = [
(
"explicit backend opt-out",
_valid_spec(),
_inspection(optimization_eligible=False),
"OPTIMIZATION_PARAMETER_INELIGIBLE",
),
(
"editor",
_valid_spec(),
_inspection(editor="choice"),
"OPTIMIZATION_PARAMETER_DISCRETE_METADATA",
),
(
"discrete options",
_valid_spec(),
_inspection(options=[{"value": 1.0, "label": "one"}]),
"OPTIMIZATION_PARAMETER_DISCRETE_METADATA",
),
(
"unit",
_valid_spec(),
_inspection(unit="cm"),
"OPTIMIZATION_PARAMETER_UNIT_MISMATCH",
),
(
"initial",
_valid_spec(),
_inspection(value=5.0, maximum=10.0),
"OPTIMIZATION_INITIAL_OUTSIDE_BOUNDS",
),
(
"registered minimum",
_valid_spec(lower=-0.1),
_inspection(),
"OPTIMIZATION_BOUNDS_OUTSIDE_CONTRACT",
),
(
"exclusive minimum",
_valid_spec(lower=0.0),
_inspection(minimum_exclusive=True),
"OPTIMIZATION_BOUNDS_OUTSIDE_CONTRACT",
),
(
"registered maximum",
_valid_spec(upper=4.1),
_inspection(maximum=4.0),
"OPTIMIZATION_BOUNDS_OUTSIDE_CONTRACT",
),
]
for label, payload, inspection, expected_code in cases:
with self.subTest(label=label):
spec = optimization.parse_optimization_spec(payload)
with self.assertRaises(optimization.simulation.InputError) as caught:
optimization._resolve_design_variables(spec, inspection)
self.assertEqual(caught.exception.code, expected_code)
def test_optional_backend_eligibility_contract_must_be_boolean(self) -> None:
spec = optimization.parse_optimization_spec(_valid_spec())
malformed_inspections = [
_inspection(optimization_eligible="yes"),
_inspection(optimization_eligible=None),
_inspection(editor=""),
_inspection(editor=1),
_inspection(options=[]),
_inspection(options="choice"),
]
for inspection in malformed_inspections:
with self.subTest(inspection=inspection):
with self.assertRaises(optimization.simulation.BackendError) as caught:
optimization._resolve_design_variables(spec, inspection)
self.assertEqual(
caught.exception.code,
"BACKEND_PARAMETER_CONTRACT_INVALID",
)
def test_optional_backend_eligibility_true_is_recorded(self) -> None:
spec = optimization.parse_optimization_spec(_valid_spec())
resolved = optimization._resolve_design_variables(
spec,
_inspection(optimization_eligible=True),
)
self.assertIs(resolved[0].catalog_optimization_eligible, True)
class CandidateIsolationTests(unittest.TestCase):
def test_reflection_maps_any_finite_coordinate_into_unit_interval(self) -> None:
cases = (
(-2.25, 0.25),
(-1.25, 0.75),
(-0.25, 0.25),
(0.0, 0.0),
(0.25, 0.25),
(1.0, 1.0),
(1.25, 0.75),
(2.0, 0.0),
(2.25, 0.25),
)
for coordinate, expected in cases:
with self.subTest(coordinate=coordinate):
self.assertEqual(
optimization._reflect_unit_interval(coordinate),
expected,
)
for coordinate in (math.inf, -math.inf, math.nan):
with self.subTest(coordinate=coordinate):
with self.assertRaises(AssertionError):
optimization._reflect_unit_interval(coordinate)
def test_candidates_only_change_whitelisted_parameters_and_never_source(self) -> None:
with tempfile.TemporaryDirectory() as directory_text:
directory = Path(directory_text)
plan = _make_plan(directory)
original_project = copy.deepcopy(plan.source.parsed)
original_source_bytes = plan.source.path.read_bytes()
original_xml_values = _xml_parameter_values(plan.baseline_xml)
first_project = optimization._optimized_project(plan, {"gain": 3.5})
second_project = optimization._optimized_project(plan, {"gain": 0.25})
expected_first = copy.deepcopy(original_project)
expected_second = copy.deepcopy(original_project)
expected_first["nodes"][0]["data"]["parameters"]["gain"] = 3.5
expected_second["nodes"][0]["data"]["parameters"]["gain"] = 0.25
self.assertEqual(first_project, expected_first)
self.assertEqual(second_project, expected_second)
self.assertEqual(plan.source.parsed, original_project)
self.assertEqual(plan.source.path.read_bytes(), original_source_bytes)
first_xml_values = _xml_parameter_values(
optimization._candidate_xml(plan, {"gain": 3.5})
)
second_xml_values = _xml_parameter_values(
optimization._candidate_xml(plan, {"gain": 0.25})
)
self.assertEqual(first_xml_values[("component-a", "gain")], "3.5")
self.assertEqual(second_xml_values[("component-a", "gain")], "0.25")
for target in (
("component-a", "fixed"),
("component-b", "gain"),
):
self.assertEqual(first_xml_values[target], original_xml_values[target])
self.assertEqual(second_xml_values[target], original_xml_values[target])
self.assertEqual(plan.baseline_xml, BASELINE_XML)
class RankingAndConfirmationTests(unittest.TestCase):
def test_plan_warns_when_budget_cannot_cover_a_complete_generation(self) -> None:
with tempfile.TemporaryDirectory() as directory_text:
minimal = _make_plan(Path(directory_text) / "minimal", max_simulation_runs=5)
one_generation = _make_plan(
Path(directory_text) / "one-generation", max_simulation_runs=9
)
minimal_public = minimal.public_dict()
one_generation_public = one_generation.public_dict()
self.assertEqual(
minimal_public["execution"]["fullGenerationsWithUniqueCandidates"],
0,
)
self.assertEqual(
[warning["code"] for warning in minimal_public["warnings"]],
["OPTIMIZATION_BUDGET_INITIAL_POPULATION_ONLY"],
)
self.assertEqual(
minimal_public["parameterContinuity"][
"userAssertionRequiredFor"
],
["gain"],
)
self.assertEqual(
minimal_public["designVariables"][0]["continuity"],
{
"machineVerified": False,
"userAssertionRequired": True,
"catalogOptimizationEligible": None,
"editorAbsent": True,
"optionsAbsent": True,
},
)
self.assertEqual(
minimal_public["requiredAssertions"][0]["code"],
"OPTIMIZATION_CONTINUITY_USER_ASSERTION",
)
self.assertEqual(
one_generation_public["execution"][
"fullGenerationsWithUniqueCandidates"
],
1,
)
self.assertEqual(one_generation_public["warnings"], [])
def test_confirmation_token_binds_all_plan_hash_inputs(self) -> None:
arguments = {
"source_sha256": "source-hash",
"spec_sha256": "spec-hash",
"baseline_xml_sha256": "xml-hash",
"output_directory": Path("/tmp/optimization-output"),
"base_url": BASE_URL,
"timeout": 10.0,
}
token = optimization._confirmation_token(**arguments)
self.assertEqual(len(token), 64)
variants = [
{**arguments, "source_sha256": "changed-source"},
{**arguments, "spec_sha256": "changed-spec"},
{**arguments, "baseline_xml_sha256": "changed-xml"},
{
**arguments,
"output_directory": Path("/tmp/different-optimization-output"),
},
{**arguments, "base_url": "http://localhost:8000"},
{**arguments, "timeout": 11.0},
]
for changed in variants:
with self.subTest(changed=changed):
self.assertNotEqual(
optimization._confirmation_token(**changed), token
)
with mock.patch.object(
optimization,
"CONTINUITY_POLICY",
"different-continuity-policy",
):
self.assertNotEqual(
optimization._confirmation_token(**arguments),
token,
)
with mock.patch.object(
optimization,
"SEARCH_POLICY",
"different-search-policy",
):
self.assertNotEqual(
optimization._confirmation_token(**arguments),
token,
)
def test_failed_or_infeasible_trial_cannot_beat_a_feasible_trial(self) -> None:
feasible = optimization.Trial(
evaluation_id=3,
stage="search",
simulation_id="opt.0003",
parameters={"gain": 2.0},
status="completed",
duration_seconds=0.1,
objective_value=1000.0,
objective_loss=1000.0,
feasible=True,
total_constraint_violation=0.0,
)
infeasible = optimization.Trial(
evaluation_id=2,
stage="search",
simulation_id="opt.0002",
parameters={"gain": 1.0},
status="completed",
duration_seconds=0.1,
objective_value=-100.0,
objective_loss=-100.0,
feasible=False,
total_constraint_violation=0.01,
)
failed = optimization.Trial(
evaluation_id=1,
stage="search",
simulation_id="opt.0001",
parameters={"gain": 0.0},
status="failed",
duration_seconds=0.1,
objective_value=-10000.0,
objective_loss=-10000.0,
feasible=True,
total_constraint_violation=0.0,
)
self.assertLess(optimization._trial_rank(feasible), optimization._trial_rank(infeasible))
self.assertLess(optimization._trial_rank(feasible), optimization._trial_rank(failed))
with tempfile.TemporaryDirectory() as directory_text:
runner = optimization.OptimizationRunner(
_make_plan(Path(directory_text)), "ranking-test"
)
runner.trials.extend([failed, infeasible, feasible])
self.assertIs(runner._best_trial(feasible_only=True), feasible)
self.assertIs(runner._best_trial(feasible_only=False), feasible)
def test_optimize_rejects_a_stale_confirmation_token_before_runner_starts(self) -> None:
with tempfile.TemporaryDirectory() as directory_text:
plan = _make_plan(Path(directory_text))
args = argparse.Namespace(
confirmed=True,
input=str(plan.source.path),
spec=str(plan.spec_source.path),
output_dir=str(plan.output_directory),
base_url=plan.base_url,
timeout=plan.timeout,
expected_source_sha256=plan.source.sha256,
expected_spec_sha256=plan.spec_source.sha256,
confirmation_token="stale-token",
optimization_id="must-not-start",
)
with mock.patch.object(
optimization, "build_runtime_plan", return_value=plan
), mock.patch.object(optimization, "OptimizationRunner") as runner_class:
with self.assertRaises(optimization.simulation.InputError) as caught:
optimization.command_optimize(args)
self.assertEqual(caught.exception.code, "OPTIMIZATION_CONFIRMATION_STALE")
self.assertIn("confirmationToken", caught.exception.details)
runner_class.assert_not_called()
class DeterministicRunnerTests(unittest.TestCase):
@staticmethod
def _run_mock_stagnating_search(
directory: Path,
population: list[list[float]],
) -> tuple[
optimization.OptimizationRunner,
str,
int,
list[dict[str, object]],
]:
plan = _make_plan(directory, max_simulation_runs=6)
runner = optimization.OptimizationRunner(plan, "stagnation-test")
initial_trials: list[optimization.Trial] = []
fresh_trials: dict[tuple[float, ...], optimization.Trial] = {}
events: list[dict[str, object]] = []
def evaluate(
normalized_vector: list[float],
*,
stage: str,
**_kwargs: object,
) -> tuple[optimization.Trial, None]:
if stage != "search":
raise AssertionError("The search fixture only accepts search trials.")
proposal_index = runner.search_proposals
runner.optimizer_calls += 1
runner.search_proposals += 1
if proposal_index < len(population):
key = tuple(normalized_vector)
trial = fresh_trials.get(key)
if trial is None:
runner.backend_submissions += 1
runner.search_backend_submissions += 1
parameter = optimization._candidate_parameters(
plan, normalized_vector
)["gain"]
trial = optimization.Trial(
evaluation_id=runner.backend_submissions,
stage="search",
simulation_id=(
f"stagnation-test.{runner.backend_submissions:04d}"
),
parameters={"gain": parameter},
status="completed",
duration_seconds=0.0,
objective_value=0.0,
objective_loss=0.0,
feasible=True,
total_constraint_violation=0.0,
)
fresh_trials[key] = trial
runner.trials.append(trial)
else:
runner.cache_hits += 1
initial_trials.append(trial)
return trial, None
target_index = (proposal_index - len(population)) % len(population)
runner.cache_hits += 1
return initial_trials[target_index], None
with mock.patch.object(
runner,
"_initial_population",
return_value=copy.deepcopy(population),
), mock.patch.object(
runner,
"_evaluate",
side_effect=evaluate,
), mock.patch.object(
runner,
"_checkpoint",
), mock.patch.object(
runner,
"_emit",
side_effect=lambda payload: events.append(dict(payload)),
):
reason, generations = runner._search()
return runner, reason, generations, events
def test_one_dimensional_initial_population_contains_baseline_and_endpoints(
self,
) -> None:
cases = (
("interior", 0.0, 4.0),
("baseline-at-lower", 2.0, 4.0),
("baseline-at-upper", 0.0, 2.0),
)
with tempfile.TemporaryDirectory() as directory_text:
root = Path(directory_text)
for label, lower, upper in cases:
with self.subTest(label=label):
plan = _make_plan(
root / label,
lower=lower,
upper=upper,
)
runner = optimization.OptimizationRunner(plan, label)
population = runner._initial_population(
optimization.random.Random(2026)
)
self.assertEqual(
population[0],
optimization._normalized_initial(plan),
)
self.assertEqual(
len(population),
plan.spec.algorithm.population_size,
)
self.assertIn([0.0], population)
self.assertIn([1.0], population)
mapped = [
optimization._candidate_parameters(plan, vector)["gain"]
for vector in population
]
self.assertIn(lower, mapped)
self.assertIn(upper, mapped)
self.assertEqual(
len({tuple(vector) for vector in population}),
len(population),
)
def test_de_generation_uses_a_frozen_donor_population(self) -> None:
class FixedRandom:
def __init__(self, _seed: int) -> None:
pass
@staticmethod
def sample(values: list[int], count: int) -> list[int]:
return values[:count]
@staticmethod
def randrange(_stop: int) -> int:
return 0
@staticmethod
def random() -> float:
return 0.0
with tempfile.TemporaryDirectory() as directory_text:
plan = _make_plan(Path(directory_text), max_simulation_runs=9)
runner = optimization.OptimizationRunner(plan, "deferred-update-test")
population = [[0.1], [0.2], [0.3], [0.4]]
candidates: list[list[float]] = []
def evaluate(
normalized_vector: list[float],
*,
stage: str,
**_kwargs: object,
) -> tuple[optimization.Trial, None]:
self.assertEqual(stage, "search")
runner.optimizer_calls += 1
runner.search_proposals += 1
runner.backend_submissions += 1
runner.search_backend_submissions += 1
evaluation_id = runner.backend_submissions
if evaluation_id > len(population):
candidates.append(list(normalized_vector))
loss = 100.0 + evaluation_id if evaluation_id <= 4 else 0.0
trial = optimization.Trial(
evaluation_id=evaluation_id,
stage="search",
simulation_id=f"deferred-update-test.{evaluation_id:04d}",
parameters=optimization._candidate_parameters(
plan, normalized_vector
),
status="completed",
duration_seconds=0.0,
objective_value=loss,
objective_loss=loss,
feasible=True,
total_constraint_violation=0.0,
)
runner.trials.append(trial)
return trial, None
with mock.patch.object(
runner,
"_initial_population",
return_value=copy.deepcopy(population),
), mock.patch.object(
runner,
"_evaluate",
side_effect=evaluate,
), mock.patch.object(
runner,
"_checkpoint",
), mock.patch.object(
runner,
"_emit",
), mock.patch.object(
optimization.random,
"Random",
FixedRandom,
):
reason, generations = runner._search()
self.assertEqual(reason, "simulationBudgetExhausted")
self.assertEqual(generations, 1)
self.assertEqual(len(candidates), 4)
self.assertAlmostEqual(candidates[0][0], 0.12)
self.assertAlmostEqual(candidates[1][0], 0.02)
def test_three_empty_generations_report_population_collapse_as_stagnation(
self,
) -> None:
with tempfile.TemporaryDirectory() as directory_text:
runner, reason, generations, events = self._run_mock_stagnating_search(
Path(directory_text),
[[0.5], [0.5], [0.5], [0.5]],
)
self.assertEqual(
reason,
"populationCollapsedAfterDuplicateStagnation",
)
self.assertEqual(
generations,
optimization.NO_NEW_SUBMISSION_GENERATION_LIMIT,
)
counts = runner._counts_payload()
self.assertEqual(counts["searchBackendSubmissions"], 1)
self.assertEqual(counts["verificationBackendSubmissions"], 0)
self.assertEqual(counts["cacheHits"], 15)
self.assertEqual(counts["generationsWithNewBackendSubmissions"], 0)
self.assertEqual(counts["generationsWithoutNewBackendSubmissions"], 3)
self.assertEqual(counts["endingNoNewSubmissionGenerationStreak"], 3)
self.assertEqual(counts["finalPopulationUniqueCandidates"], 1)
summary = runner._search_summary(reason)
self.assertFalse(summary["searchConvergenceEstablished"])
self.assertTrue(summary["populationCollapsedToSingleCandidate"])
self.assertEqual(summary["terminationCategory"], "stagnation")
generation_events = [
event
for event in events
if event.get("event") == "optimization-generation-completed"
]
self.assertEqual(len(generation_events), 3)
self.assertEqual(
[
event["backendSubmissionsThisGeneration"]
for event in generation_events
],
[0, 0, 0],
)
self.assertEqual(
[
event["consecutiveGenerationsWithoutNewBackendSubmissions"]
for event in generation_events
],
[1, 2, 3],
)
def test_three_empty_generations_report_duplicate_proposal_stagnation(
self,
) -> None:
with tempfile.TemporaryDirectory() as directory_text:
runner, reason, generations, _events = (
self._run_mock_stagnating_search(
Path(directory_text),
[[0.0], [0.25], [0.5], [1.0]],
)
)
self.assertEqual(reason, "duplicateProposalStagnation")
self.assertEqual(
generations,
optimization.NO_NEW_SUBMISSION_GENERATION_LIMIT,
)
counts = runner._counts_payload()
self.assertEqual(counts["searchBackendSubmissions"], 4)
self.assertEqual(counts["verificationBackendSubmissions"], 0)
self.assertEqual(counts["cacheHits"], 12)
self.assertEqual(counts["generationsWithNewBackendSubmissions"], 0)
self.assertEqual(counts["generationsWithoutNewBackendSubmissions"], 3)
self.assertEqual(counts["maxNoNewSubmissionGenerationStreak"], 3)
self.assertEqual(counts["finalPopulationUniqueCandidates"], 4)
summary = runner._search_summary(reason)
self.assertFalse(summary["searchConvergenceEstablished"])
self.assertFalse(summary["populationCollapsedToSingleCandidate"])
self.assertEqual(
summary["terminationCategory"],
"stagnation",
)
@staticmethod
def _run_mock_optimization(
directory: Path,
*,
seed: int,
change_source_after_artifacts: bool = False,
force_infeasible: bool = False,
) -> tuple[dict[str, object], list[dict[str, object]], optimization.RuntimePlan]:
plan = _make_plan(directory, seed=seed, max_simulation_runs=6)
runner = optimization.OptimizationRunner(plan, "deterministic-test")
submissions: list[dict[str, object]] = []
def simulation_result(
_base_url: str,
xml: bytes,
simulation_id: str,
_timeout: float,
_progress_path: Path,
**kwargs: object,
) -> tuple[dict[str, object], None]:
parameter_values = _xml_parameter_values(xml)
gain = float(parameter_values[("component-a", "gain")])
submissions.append(
{"simulationId": simulation_id, "gain": gain, "xml": xml}
)
event_sink = kwargs.get("event_sink")
if callable(event_sink):
event_sink({"event": "progress", "progress": 0.5})
objective = (gain - 0.3) ** 2
return (
{
"status": "completed",
"success": True,
"variables": copy.deepcopy(RESULT_VARIABLES),
"series": {
"time": [0.0, 1.0],
"sensor.output": [objective, objective],
"sensor.limit": (
[10.0, 10.0] if force_infeasible else [gain, gain]
),
},
},
None,
)
original_write_best_artifacts = runner._write_best_artifacts
def write_best_artifacts_then_change_source(
*args: object, **kwargs: object
) -> dict[str, object]:
artifacts = original_write_best_artifacts(*args, **kwargs)
plan.source.path.write_bytes(plan.source.raw + b"\n")
return artifacts
artifact_context = (
mock.patch.object(
runner,
"_write_best_artifacts",
side_effect=write_best_artifacts_then_change_source,
)
if change_source_after_artifacts
else contextlib.nullcontext()
)
with mock.patch.object(
optimization.simulation,
"_read_simulation_stream",
side_effect=simulation_result,
), mock.patch.object(
optimization.simulation,
"_download_csv",
return_value=b"time,sensor.output,sensor.limit\n0,0,0\n",
), mock.patch.object(optimization.simulation, "emit_json"), artifact_context:
result = runner.run()
return result, submissions, plan
def test_de_seed_budget_fresh_verification_and_artifacts(self) -> None:
with tempfile.TemporaryDirectory() as directory_text:
root = Path(directory_text)
first, first_submissions, first_plan = self._run_mock_optimization(
root / "first", seed=8675309
)
second, second_submissions, _ = self._run_mock_optimization(
root / "second", seed=8675309
)
first_search = [
trial["parameters"]
for trial in first["trials"]
if trial["stage"] == "search"
]
second_search = [
trial["parameters"]
for trial in second["trials"]
if trial["stage"] == "search"
]
self.assertEqual(first_search, second_search)
self.assertEqual(
[item["gain"] for item in first_submissions[:-1]],
[item["gain"] for item in second_submissions[:-1]],
)
self.assertEqual(first["optimizationResultSchemaVersion"], 2)
self.assertEqual(first["solutionStatus"], "verified")
self.assertIn("proof", first["claim"])
self.assertIn("global optimality", first["claim"])
self.assertEqual(
first["objectiveEndpointTrend"]["status"],
"insufficientData",
)
self.assertFalse(
first["objectiveEndpointTrend"]["steadyStateProven"]
)
self.assertEqual(first["planHash"], first_plan.confirmation_token)
self.assertEqual(
first["continuityPolicy"]["id"],
optimization.CONTINUITY_POLICY,
)
self.assertTrue(
first["continuityPolicy"]["confirmedUserAssertion"]
)
self.assertEqual(
first["continuityPolicy"]["designVariableIds"],
["gain"],
)
self.assertEqual(first["terminationReason"], "simulationBudgetExhausted")
self.assertEqual(first["counts"]["generations"], 0)
self.assertEqual(first["counts"]["backendSubmissions"], 6)
self.assertEqual(
first["counts"]["backendSubmissionSlotsConsumed"],
6,
)
self.assertEqual(first["counts"]["searchBackendSubmissions"], 5)
self.assertEqual(
first["counts"]["searchSubmissionSlotsConsumed"],
5,
)
self.assertEqual(
first["counts"]["verificationBackendSubmissions"],
1,
)
self.assertEqual(
first["counts"]["cacheHits"],
first["search"]["cacheHits"],
)
self.assertEqual(first["search"]["backendSubmissions"], 5)
self.assertEqual(first["search"]["budget"]["used"], 5)
self.assertTrue(first["search"]["budget"]["exhausted"])
self.assertEqual(first["verification"]["backendSubmissions"], 1)
self.assertEqual(
first["verification"]["submissionSlotsConsumed"],
1,
)
self.assertEqual(len(first_submissions), 6)
self.assertEqual(
sum(trial["stage"] == "search" for trial in first["trials"]),
5,
)
self.assertEqual(first["trials"][-1]["stage"], "verification")
self.assertTrue(first["verification"]["passed"])
self.assertTrue(first["verification"]["sourceUnchanged"])
self.assertEqual(
first_submissions[-1]["gain"],
first["bestSearch"]["parameters"]["gain"],
)
self.assertEqual(
first["verifiedBest"]["parameters"],
first["bestSearch"]["parameters"],
)
self.assertTrue(first["source"]["unchanged"])
self.assertEqual(
first_plan.source.path.read_bytes(), first_plan.source.raw
)
expected_artifacts = {
"plan",
"bestParameters",
"bestSystemXml",
"bestProject",
"result",
"csv",
"charts",
"report",
"evaluations",
"events",
"checkpoint",
}
self.assertEqual(set(first["artifacts"]), expected_artifacts)
for name, artifact in first["artifacts"].items():
artifacts = artifact if name == "charts" else [artifact]
for item in artifacts:
path = Path(item["path"])
data = path.read_bytes()
self.assertEqual(item["sizeBytes"], len(data))
self.assertEqual(item["sha256"], hashlib.sha256(data).hexdigest())
for filename in (
"optimization-plan.json",
"optimization-result.json",
"report.md",
"best-parameters.json",
"best-project.json",
"best-system.xml",
"result.json",
"results.csv",
):
self.assertTrue((first_plan.output_directory / filename).is_file())
optimized_project = json.loads(
(first_plan.output_directory / "best-project.json").read_text(
encoding="utf-8"
)
)
expected_project = copy.deepcopy(PROJECT)
expected_project["nodes"][0]["data"]["parameters"]["gain"] = first[
"verifiedBest"
]["parameters"]["gain"]
self.assertEqual(optimized_project, expected_project)
def test_source_change_during_fresh_verification_invalidates_solution(self) -> None:
with tempfile.TemporaryDirectory() as directory_text:
root = Path(directory_text)
plan = _make_plan(root, seed=42, max_simulation_runs=5)
runner = optimization.OptimizationRunner(plan, "source-race-test")
submissions = 0
def simulation_result(
_base_url: str,
xml: bytes,
_simulation_id: str,
_timeout: float,
_progress_path: Path,
**_kwargs: object,
) -> tuple[dict[str, object], None]:
nonlocal submissions
submissions += 1
gain = float(_xml_parameter_values(xml)[("component-a", "gain")])
if submissions == plan.spec.budget.max_simulation_runs:
plan.source.path.write_bytes(plan.source.raw + b"\n")
return (
{
"status": "completed",
"success": True,
"variables": copy.deepcopy(RESULT_VARIABLES),
"series": {
"time": [0.0, 1.0],
"sensor.output": [gain, gain],
"sensor.limit": [gain, gain],
},
},
None,
)
with mock.patch.object(
optimization.simulation,
"_read_simulation_stream",
side_effect=simulation_result,
), mock.patch.object(optimization.simulation, "emit_json"):
result = runner.run()
self.assertEqual(result["solutionStatus"], "verificationFailed")
self.assertFalse(result["verification"]["passed"])
self.assertFalse(result["verification"]["sourceUnchanged"])
self.assertFalse(result["source"]["unchanged"])
self.assertFalse((plan.output_directory / "best-project.json").exists())
self.assertFalse((plan.output_directory / "best-system.xml").exists())
def test_source_change_while_writing_artifacts_removes_best_outputs(self) -> None:
with tempfile.TemporaryDirectory() as directory_text:
result, _submissions, plan = self._run_mock_optimization(
Path(directory_text),
seed=7,
change_source_after_artifacts=True,
)
self.assertEqual(result["solutionStatus"], "verificationFailed")
self.assertFalse(result["verification"]["passed"])
self.assertFalse(result["source"]["unchanged"])
self.assertEqual(result["artifacts"].keys(), {
"plan",
"report",
"evaluations",
"events",
"checkpoint",
})
for filename in (
"best-parameters.json",
"best-system.xml",
"best-project.json",
"result.json",
"results.csv",
):
self.assertFalse((plan.output_directory / filename).exists())
def test_late_best_artifact_export_failure_removes_all_best_outputs(self) -> None:
failures = (
("csv", "_download_csv", "CSV_EXPORT_FAILED"),
("charts", "_write_charts", "CHART_EXPORT_FAILED"),
)
for label, failing_export, expected_code in failures:
with self.subTest(
export=label
), tempfile.TemporaryDirectory() as directory_text:
plan = _make_plan(Path(directory_text), max_simulation_runs=5)
plan.output_directory.mkdir()
runner = optimization.OptimizationRunner(plan, "artifact-failure-test")
trial = optimization.Trial(
evaluation_id=5,
stage="verification",
simulation_id="artifact-failure-test.0005",
parameters={"gain": 1.0},
status="completed",
duration_seconds=0.1,
objective_value=1.0,
objective_loss=1.0,
feasible=True,
total_constraint_violation=0.0,
)
result = {
"status": "completed",
"success": True,
"variables": copy.deepcopy(RESULT_VARIABLES),
"series": {
"time": [0.0, 1.0],
"sensor.output": [1.0, 1.0],
"sensor.limit": [1.0, 1.0],
},
}
csv_patch = (
mock.patch.object(
optimization.simulation,
"_download_csv",
side_effect=optimization.simulation.BackendError(
"CSV_EXPORT_FAILED", "forced CSV export failure"
),
)
if failing_export == "_download_csv"
else mock.patch.object(
optimization.simulation,
"_download_csv",
return_value=b"time,sensor.output,sensor.limit\n0,1,1\n",
)
)
chart_patch = (
mock.patch.object(
optimization.simulation,
"_write_charts",
side_effect=optimization.simulation.ArtifactError(
"CHART_EXPORT_FAILED", "forced chart export failure"
),
)
if failing_export == "_write_charts"
else contextlib.nullcontext()
)
with csv_patch, chart_patch, self.assertRaises(
optimization.simulation.SkillCliError
) as caught:
runner._write_best_artifacts(trial, result)
self.assertEqual(caught.exception.code, expected_code)
for filename in (
"best-parameters.json",
"best-system.xml",
"best-project.json",
"result.json",
"results.csv",
):
self.assertFalse((plan.output_directory / filename).exists())
self.assertEqual(
list(plan.output_directory.glob("curve-*.svg")), []
)
def test_no_feasible_candidate_reports_diagnostic_without_best_files(self) -> None:
with tempfile.TemporaryDirectory() as directory_text:
result, submissions, plan = self._run_mock_optimization(
Path(directory_text), seed=9, force_infeasible=True
)
self.assertEqual(result["solutionStatus"], "noFeasibleCandidate")
self.assertIn("no complete feasible candidate", result["claim"])
self.assertEqual(result["counts"]["backendSubmissions"], 5)
self.assertEqual(len(submissions), 5)
self.assertIsNone(result["bestSearch"])
self.assertIsNotNone(result["bestDiagnosticSearch"])
self.assertFalse(result["bestDiagnosticSearch"]["feasible"])
self.assertIsNone(result["verifiedBest"])
self.assertNotIn("bestProject", result["artifacts"])
self.assertFalse((plan.output_directory / "best-project.json").exists())
report = (plan.output_directory / "report.md").read_text(
encoding="utf-8"
)
self.assertNotIn("搜索阶段最佳可行点", report)
self.assertIn("约束违反最小的完整诊断点(不可行)", report)
self.assertIn("它不是可行方案", report)
def test_interrupted_stream_keeps_id_available_for_backend_cancel(self) -> None:
with tempfile.TemporaryDirectory() as directory_text:
runner = optimization.OptimizationRunner(
_make_plan(Path(directory_text)), "cancel-test"
)
with mock.patch.object(
optimization.simulation,
"_read_simulation_stream",
side_effect=KeyboardInterrupt,
), mock.patch.object(
optimization.simulation, "emit_json"
), mock.patch.object(
optimization.simulation, "http_json"
) as http_json:
with self.assertRaises(KeyboardInterrupt):
runner._evaluate([0.5], stage="search")
self.assertEqual(runner.current_simulation_id, "cancel-test.0001")
runner.cancel_active()
http_json.assert_called_once()
self.assertIsNone(runner.current_simulation_id)
def test_completed_but_unsuccessful_baseline_is_recorded_as_failed(self) -> None:
with tempfile.TemporaryDirectory() as directory_text:
plan = _make_plan(Path(directory_text), max_simulation_runs=5)
runner = optimization.OptimizationRunner(plan, "failed-baseline-test")
failed_result = {
"status": "completed",
"success": False,
"message": "solver rejected the baseline",
}
with mock.patch.object(
optimization.simulation,
"_read_simulation_stream",
return_value=(failed_result, None),
), mock.patch.object(optimization.simulation, "emit_json"):
with self.assertRaises(optimization.OptimizationError) as caught:
runner.run()
self.assertEqual(caught.exception.code, "OPTIMIZATION_BASELINE_FAILED")
self.assertEqual(len(runner.trials), 1)
self.assertEqual(runner.trials[0].status, "failed")
checkpoint = json.loads(
(plan.output_directory / "checkpoint.json").read_text(
encoding="utf-8"
)
)
self.assertEqual(checkpoint["counts"]["completedTrials"], 0)
self.assertEqual(checkpoint["counts"]["failedTrials"], 1)
def test_completed_contract_error_is_recorded_before_abort(self) -> None:
with tempfile.TemporaryDirectory() as directory_text:
plan = _make_plan(Path(directory_text), max_simulation_runs=5)
runner = optimization.OptimizationRunner(plan, "contract-error-test")
variables = copy.deepcopy(RESULT_VARIABLES)
variables[0]["unit"] = "cm"
invalid_result = {
"status": "completed",
"success": True,
"variables": variables,
"series": {
"time": [0.0, 1.0],
"sensor.output": [1.0, 1.0],
"sensor.limit": [1.0, 1.0],
},
}
with mock.patch.object(
optimization.simulation,
"_read_simulation_stream",
return_value=(invalid_result, None),
), mock.patch.object(optimization.simulation, "emit_json"):
with self.assertRaises(optimization.OptimizationError) as caught:
runner.run()
self.assertEqual(
caught.exception.code, "OPTIMIZATION_RESULT_METADATA_MISMATCH"
)
self.assertEqual(runner.backend_submissions, 1)
self.assertEqual(len(runner.trials), 1)
self.assertEqual(
runner.trials[0].failure_code,
"OPTIMIZATION_RESULT_METADATA_MISMATCH",
)
evaluations = (
plan.output_directory / "evaluations.csv"
).read_text(encoding="utf-8")
self.assertIn("OPTIMIZATION_RESULT_METADATA_MISMATCH", evaluations)
if __name__ == "__main__": # pragma: no cover
unittest.main()