完善系统仿真优化计划交互

This commit is contained in:
huojiarong committed 2026-09-15 08:32:31 +00:00
1 parent 42ffdfff7d
commit 50c9f59d78
4 files changed
+812 -24

No files matched your search

@@ -55,6 +55,12 @@ MAX_DESIGN_VARIABLES = 16
MAX_RESPONSE_CONSTRAINTS = 16
MAX_SIMULATION_RUNS = 200
MAX_WALL_SECONDS = 7 * 24 * 60 * 60
RECOMMENDED_ALGORITHM_SEED = 0
RECOMMENDED_POPULATION_SIZE = 8
RECOMMENDED_MUTATION_FACTOR = 0.8
RECOMMENDED_CROSSOVER_PROBABILITY = 0.7
RECOMMENDED_VALIDATION_RELATIVE_TOLERANCE = 1e-8
RECOMMENDED_VALIDATION_ABSOLUTE_TOLERANCE = 0.0
OPTIMIZATION_ID_PATTERN = re.compile(r"^[A-Za-z0-9._-]{1,96}$")
IDENTIFIER_PATTERN = re.compile(r"^[A-Za-z][A-Za-z0-9._-]{0,63}$")
SUPPORTED_STATISTICS = {
@@ -306,6 +312,148 @@ class RuntimePlan:
timeout: float
confirmation_token: str
def presentation_dict(self) -> dict[str, object]:
"""Return the strict allowlist used for an ordinary user-facing plan."""
objective = self.spec.objective
objective_metadata = self.variables[objective.result_key]
payload: dict[str, object] = {
"objective": {
"resultKey": objective.result_key,
"componentId": objective_metadata.get("componentId"),
"label": objective_metadata.get("label"),
"quantity": objective_metadata.get("quantity"),
"statistic": objective.statistic.as_dict(),
"goal": objective.goal.as_dict(),
"seriesUnit": objective.expected_unit,
"metricUnit": _metric_unit(
objective.expected_unit,
objective.statistic.kind,
),
},
"designVariables": [
{
"id": item.spec.id,
"componentId": item.spec.component_id,
"parameter": item.spec.parameter,
"label": item.label,
"quantity": item.quantity,
"current": item.initial,
"lower": item.spec.lower,
"upper": item.spec.upper,
"unit": item.spec.unit,
}
for item in self.resolved_design_variables
],
"constraints": [
{
"id": item.id,
"resultKey": item.result_key,
"componentId": self.variables[item.result_key].get(
"componentId"
),
"label": self.variables[item.result_key].get("label"),
"quantity": self.variables[item.result_key].get("quantity"),
"statistic": item.statistic.as_dict(),
"lower": item.lower,
"upper": item.upper,
"tolerance": item.tolerance,
"seriesUnit": item.expected_unit,
"metricUnit": _metric_unit(
item.expected_unit,
item.statistic.kind,
),
}
for item in self.spec.constraints
],
"runLimits": {
"maxSimulationBudgetSlots": (
self.spec.budget.max_simulation_runs
),
"searchLaunchTimeLimitSeconds": (
self.spec.budget.max_wall_seconds
),
"inFlightSimulationMayFinishAfterLimit": True,
"freshVerificationSlotsReserved": 1,
"verificationRunsOnlyIfFeasibleCandidateFound": True,
},
"assumptions": {
"continuousLinearSiDesignVariableIds": [
item.spec.id for item in self.resolved_design_variables
],
"portsTopologyModesUnchanged": True,
},
"outputDirectory": str(self.output_directory),
"sourceWillBeOverwritten": False,
}
non_default_settings: dict[str, object] = {}
search_settings: dict[str, object] = {}
algorithm = self.spec.algorithm
if algorithm.seed != RECOMMENDED_ALGORITHM_SEED:
search_settings["randomSeed"] = algorithm.seed
if algorithm.population_size != RECOMMENDED_POPULATION_SIZE:
search_settings["populationSize"] = algorithm.population_size
if algorithm.mutation_factor != RECOMMENDED_MUTATION_FACTOR:
search_settings["mutationFactor"] = algorithm.mutation_factor
if (
algorithm.crossover_probability
!= RECOMMENDED_CROSSOVER_PROBABILITY
):
search_settings["crossoverProbability"] = (
algorithm.crossover_probability
)
if search_settings:
non_default_settings["search"] = search_settings
verification_settings: dict[str, object] = {}
validation = self.spec.validation
if (
validation.relative_tolerance
!= RECOMMENDED_VALIDATION_RELATIVE_TOLERANCE
):
verification_settings["relativeTolerance"] = (
validation.relative_tolerance
)
if (
validation.absolute_tolerance
!= RECOMMENDED_VALIDATION_ABSOLUTE_TOLERANCE
):
verification_settings["absoluteTolerance"] = (
validation.absolute_tolerance
)
if verification_settings:
non_default_settings["verification"] = verification_settings
constraint_scales = []
for constraint in self.spec.constraints:
bounds = [
abs(bound)
for bound in (constraint.lower, constraint.upper)
if bound is not None
]
recommended_scale = max(bounds, default=0.0) or 1.0
if constraint.scale != recommended_scale:
constraint_scales.append(
{"id": constraint.id, "scale": constraint.scale}
)
if constraint_scales:
non_default_settings["constraintRankingScales"] = (
constraint_scales
)
if non_default_settings:
payload["nonDefaultSettings"] = non_default_settings
search_run_limit = self.spec.budget.max_simulation_runs - 1
population_size = self.spec.algorithm.population_size
if (search_run_limit - population_size) // population_size < 1:
payload["attention"] = [
(
"The budget can cover the initial candidate set and reserve "
"fresh verification, but it cannot cover one complete search "
"update cycle if every candidate is unique."
)
]
return payload
def public_dict(self) -> dict[str, object]:
search_run_limit = self.spec.budget.max_simulation_runs - 1
population_size = self.spec.algorithm.population_size
@@ -849,6 +997,86 @@ def _validate_output_target(path_text: str) -> Path:
return path
def _validate_plan_file_target(
path_text: str,
output_directory_text: str,
) -> Path:
requested_path = Path(path_text).expanduser()
if requested_path.exists() or requested_path.is_symlink():
raise simulation.InputError(
"OPTIMIZATION_PLAN_FILE_ALREADY_EXISTS",
"The saved optimization plan path must be new.",
{"path": str(requested_path.absolute())},
)
try:
path = requested_path.resolve(strict=False)
output_directory = Path(output_directory_text).expanduser().resolve(
strict=False
)
except (OSError, RuntimeError) as exc:
raise simulation.InputError(
"OPTIMIZATION_PLAN_FILE_PATH_INVALID",
"The saved plan file path could not be resolved safely.",
{"path": str(requested_path.absolute())},
) from exc
if path.exists():
raise simulation.InputError(
"OPTIMIZATION_PLAN_FILE_ALREADY_EXISTS",
"The saved optimization plan path must be new.",
{"path": str(path)},
)
if (
path == output_directory
or path.is_relative_to(output_directory)
or output_directory.is_relative_to(path)
):
raise simulation.InputError(
"OPTIMIZATION_PLAN_FILE_OUTPUT_CONFLICT",
"The saved plan file and optimization output directory must be separate sibling paths.",
{
"planFile": str(path),
"outputDirectory": str(output_directory),
},
)
return path
def _write_new_private_file(path: Path, data: bytes) -> None:
"""Exclusively create a plan receipt readable only by its owner."""
created = False
descriptor: int | None = None
try:
path.parent.mkdir(parents=True, exist_ok=True)
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
if hasattr(os, "O_BINARY"):
flags |= os.O_BINARY
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
descriptor = os.open(path, flags, 0o600)
created = True
if hasattr(os, "fchmod"):
os.fchmod(descriptor, 0o600)
with os.fdopen(descriptor, "wb") as handle:
descriptor = None
handle.write(data)
handle.flush()
os.fsync(handle.fileno())
except OSError as exc:
if descriptor is not None:
try:
os.close(descriptor)
except OSError:
pass
try:
if created and path.exists():
path.unlink()
except OSError:
pass
raise simulation.ArtifactError(
"OUTPUT_WRITE_FAILED", str(exc), {"path": str(path)}
) from exc
def _parameter_contracts(
inspection: Mapping[str, object],
) -> dict[tuple[str, str], tuple[dict[str, object], dict[str, object] | None]]:
@@ -1173,6 +1401,18 @@ def build_runtime_plan(
def command_plan(args: argparse.Namespace) -> int:
present = bool(getattr(args, "present", False))
plan_file_text = getattr(args, "plan_file", None)
if present != bool(plan_file_text):
raise simulation.InputError(
"OPTIMIZATION_PLAN_PRESENTATION_OPTIONS_REQUIRED",
"--present and --plan-file must be supplied together.",
)
plan_file = (
_validate_plan_file_target(plan_file_text, args.output_dir)
if plan_file_text
else None
)
plan = build_runtime_plan(
args.input,
args.spec,
@@ -1180,7 +1420,30 @@ def command_plan(args: argparse.Namespace) -> int:
base_url=args.base_url,
timeout=args.timeout,
)
simulation.emit_json(plan.public_dict())
audit_payload = plan.public_dict()
if plan_file is not None:
serialized = (
json.dumps(
audit_payload,
ensure_ascii=False,
allow_nan=False,
indent=2,
)
+ "\n"
).encode("utf-8")
_write_new_private_file(plan_file, serialized)
if present:
simulation.emit_json(
{
"ok": True,
"command": "optimization-plan",
"view": "presentation",
"confirmationRequired": True,
"presentation": plan.presentation_dict(),
}
)
else:
simulation.emit_json(audit_payload)
return 0
@@ -3789,6 +4052,20 @@ def build_parser() -> argparse.ArgumentParser:
plan_parser.add_argument("input")
plan_parser.add_argument("--spec", required=True)
plan_parser.add_argument("--output-dir", required=True)
plan_parser.add_argument(
"--plan-file",
help=(
"Write the complete auditable plan to this new path outside the "
"optimization output directory; requires --present."
),
)
plan_parser.add_argument(
"--present",
action="store_true",
help=(
"Emit only the user-facing plan allowlist; requires --plan-file."
),
)
plan_parser.set_defaults(handler=command_plan)
optimize_parser = subparsers.add_parser(