CreativeEngineer's picture
feat: add verifier-native reward v1
2fccde8
from __future__ import annotations
from dataclasses import dataclass
from typing import Final, Literal
import numpy as np
from constellaration.forward_model import (
ConstellarationMetrics,
ConstellarationSettings,
forward_model,
)
from constellaration.mhd.vmec_settings import VmecPresetSettings
from constellaration.geometry import surface_rz_fourier
from constellaration.geometry.surface_rz_fourier import SurfaceRZFourier
from constellaration.initial_guess import generate_rotating_ellipse
from constellaration.problems import GeometricalProblem
from fusion_lab.models import ConstraintName, LowDimBoundaryParams
ASPECT_RATIO_MAX: Final[float] = 4.0
AVERAGE_TRIANGULARITY_MAX: Final[float] = -0.5
EDGE_IOTA_OVER_NFP_MIN: Final[float] = 0.3
FEASIBILITY_TOLERANCE: Final[float] = 0.01
MAX_POLOIDAL_MODE: Final[int] = 3
MAX_TOROIDAL_MODE: Final[int] = 3
FAILED_FEASIBILITY: Final[float] = 1_000_000.0
FAILED_ELONGATION: Final[float] = 10.0
EvaluationFidelity = Literal["low", "high"]
@dataclass(frozen=True)
class EvaluationMetrics:
max_elongation: float
aspect_ratio: float
average_triangularity: float
edge_iota_over_nfp: float
aspect_ratio_violation: float
triangularity_violation: float
iota_violation: float
dominant_constraint: ConstraintName
p1_score: float
p1_feasibility: float
constraints_satisfied: bool
vacuum_well: float
evaluation_fidelity: EvaluationFidelity
evaluation_failed: bool
failure_reason: str
def build_boundary_from_params(
params: LowDimBoundaryParams,
*,
n_field_periods: int = 3,
max_poloidal_mode: int = MAX_POLOIDAL_MODE,
max_toroidal_mode: int = MAX_TOROIDAL_MODE,
) -> SurfaceRZFourier:
surface = generate_rotating_ellipse(
aspect_ratio=params.aspect_ratio,
elongation=params.elongation,
rotational_transform=params.rotational_transform,
n_field_periods=n_field_periods,
)
expanded_surface = surface_rz_fourier.set_max_mode_numbers(
surface,
max_poloidal_mode=max_poloidal_mode,
max_toroidal_mode=max_toroidal_mode,
)
r_cos = np.asarray(expanded_surface.r_cos, dtype=float).copy()
z_sin = np.asarray(expanded_surface.z_sin, dtype=float).copy()
center = r_cos.shape[1] // 2
minor_radius = float(r_cos[1, center])
r_cos[2, center] = -params.triangularity_scale * minor_radius
r_cos[0, :center] = 0.0
z_sin[0, : center + 1] = 0.0
return SurfaceRZFourier(
r_cos=r_cos,
z_sin=z_sin,
n_field_periods=n_field_periods,
is_stellarator_symmetric=True,
)
def evaluate_boundary(
boundary: SurfaceRZFourier,
*,
fidelity: EvaluationFidelity = "low",
) -> EvaluationMetrics:
settings = _settings_for_fidelity(fidelity)
try:
metrics, _ = forward_model(boundary, settings=settings)
except RuntimeError as error:
return _failure_metrics(fidelity=fidelity, failure_reason=str(error))
return _to_evaluation_metrics(metrics, fidelity=fidelity)
def _settings_for_fidelity(fidelity: EvaluationFidelity) -> ConstellarationSettings:
if fidelity == "high":
return ConstellarationSettings(
vmec_preset_settings=VmecPresetSettings(fidelity="from_boundary_resolution"),
boozer_preset_settings=None,
qi_settings=None,
turbulent_settings=None,
)
return ConstellarationSettings(
boozer_preset_settings=None,
qi_settings=None,
turbulent_settings=None,
)
def _to_evaluation_metrics(
metrics: ConstellarationMetrics,
*,
fidelity: EvaluationFidelity,
) -> EvaluationMetrics:
problem = GeometricalProblem()
constraints_satisfied = problem.is_feasible(metrics)
p1_feasibility = float(problem.compute_feasibility(metrics))
objective, minimize_objective = problem.get_objective(metrics)
if not minimize_objective:
raise ValueError("P1 objective is expected to be minimize-only.")
p1_score = _score_from_objective(float(objective)) if constraints_satisfied else 0.0
aspect_ratio_violation, triangularity_violation, iota_violation, dominant_constraint = (
_constraint_violation_metrics(metrics)
)
return EvaluationMetrics(
max_elongation=float(objective),
aspect_ratio=float(metrics.aspect_ratio),
average_triangularity=float(metrics.average_triangularity),
edge_iota_over_nfp=float(metrics.edge_rotational_transform_over_n_field_periods),
aspect_ratio_violation=aspect_ratio_violation,
triangularity_violation=triangularity_violation,
iota_violation=iota_violation,
dominant_constraint=dominant_constraint,
p1_score=p1_score,
p1_feasibility=p1_feasibility,
constraints_satisfied=constraints_satisfied,
vacuum_well=float(metrics.vacuum_well),
evaluation_fidelity=fidelity,
evaluation_failed=False,
failure_reason="",
)
def _failure_metrics(
*,
fidelity: EvaluationFidelity,
failure_reason: str,
) -> EvaluationMetrics:
return EvaluationMetrics(
max_elongation=FAILED_ELONGATION,
aspect_ratio=0.0,
average_triangularity=0.0,
edge_iota_over_nfp=0.0,
aspect_ratio_violation=0.0,
triangularity_violation=0.0,
iota_violation=0.0,
dominant_constraint="none",
p1_score=0.0,
p1_feasibility=FAILED_FEASIBILITY,
constraints_satisfied=False,
vacuum_well=0.0,
evaluation_fidelity=fidelity,
evaluation_failed=True,
failure_reason=failure_reason,
)
def _score_from_objective(objective: float) -> float:
normalized = min(max((objective - 1.0) / 9.0, 0.0), 1.0)
return 1.0 - normalized
def _constraint_violation_metrics(
metrics: ConstellarationMetrics,
) -> tuple[float, float, float, ConstraintName]:
aspect_ratio_violation = max(float(metrics.aspect_ratio) - ASPECT_RATIO_MAX, 0.0) / (
ASPECT_RATIO_MAX
)
triangularity_violation = max(
float(metrics.average_triangularity) - AVERAGE_TRIANGULARITY_MAX,
0.0,
) / abs(AVERAGE_TRIANGULARITY_MAX)
iota_violation = (
max(
EDGE_IOTA_OVER_NFP_MIN
- abs(float(metrics.edge_rotational_transform_over_n_field_periods)),
0.0,
)
/ EDGE_IOTA_OVER_NFP_MIN
)
dominant_constraint: ConstraintName = "none"
dominant_violation = 0.0
constraint_violations: tuple[tuple[ConstraintName, float], ...] = (
("aspect_ratio", aspect_ratio_violation),
("average_triangularity", triangularity_violation),
("edge_iota_over_nfp", iota_violation),
)
for constraint_name, violation in constraint_violations:
if violation > dominant_violation:
dominant_constraint = constraint_name
dominant_violation = violation
return (
aspect_ratio_violation,
triangularity_violation,
iota_violation,
dominant_constraint,
)