Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| 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"] | |
| 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, | |
| ) | |