Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
File size: 7,070 Bytes
65b799e daba1b9 d58c100 fe3a41d d58c100 9d7dc15 fe3a41d d58c100 65b799e 2fccde8 2f5db5e daba1b9 fe3a41d 2f5db5e d58c100 2f5db5e daba1b9 2f5db5e daba1b9 2fccde8 daba1b9 fe3a41d daba1b9 fe3a41d d58c100 fe3a41d d58c100 fe3a41d d58c100 fe3a41d daba1b9 d58c100 9d7dc15 d58c100 daba1b9 fe3a41d d58c100 2fccde8 daba1b9 d58c100 2fccde8 daba1b9 d58c100 fe3a41d 2fccde8 fe3a41d daba1b9 d58c100 2fccde8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | 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,
)
|