diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml
index 0e6ebadbea..2898a04fb7 100644
--- a/.github/workflows/examples.yml
+++ b/.github/workflows/examples.yml
@@ -35,6 +35,7 @@ jobs:
# to the CI runners
python ./scripts/run_examples.py \
-e 'plasmod_example' \
+ -e 'process_example' \
-e 'solver_example' \
-e 'equilibria/fem_fixed_boundary' \
-e 'codes/ext_code_script'
diff --git a/bluemira/base/parameter_frame/_frame.py b/bluemira/base/parameter_frame/_frame.py
index 01b4bf9362..555600ec1d 100644
--- a/bluemira/base/parameter_frame/_frame.py
+++ b/bluemira/base/parameter_frame/_frame.py
@@ -405,6 +405,20 @@ def _validate_parameter_field(field, member_type: Type) -> Tuple[Type, ...]:
def _validate_units(param_data: Dict, value_type: Iterable[Type]):
try:
quantity = pint.Quantity(param_data["value"], param_data["unit"])
+ except ValueError:
+ try:
+ quantity = pint.Quantity(f'{param_data["value"]}*{param_data["unit"]}')
+ except pint.errors.PintError as pe:
+ if param_data["value"] is None:
+ quantity = pint.Quantity(
+ 1 if param_data["unit"] in (None, "") else param_data["unit"]
+ )
+ param_data["source"] = f"{param_data.get('source', '')}\nMAD UNIT 🤯 ðŸ˜:"
+ else:
+ raise ValueError("Unit conversion failed") from pe
+ else:
+ param_data["value"] = quantity.magnitude
+ param_data["unit"] = quantity.units
except KeyError as ke:
raise ValueError("Parameters need a value and a unit") from ke
except TypeError:
@@ -434,6 +448,9 @@ def _validate_units(param_data: Dict, value_type: Iterable[Type]):
param_data["unit"] = f"{unit:~P}"
+ if "MAD UNIT" in param_data.get("source", ""):
+ param_data["source"] += f"{quantity.magnitude}{param_data['unit']}"
+
def _remake_units(dimensionality: Union[Dict, pint.util.UnitsContainer]) -> pint.Unit:
"""Reconstruct unit from its dimensionality"""
diff --git a/bluemira/builders/coil_supports.py b/bluemira/builders/coil_supports.py
index c7eaeab91a..ac0ac0cded 100644
--- a/bluemira/builders/coil_supports.py
+++ b/bluemira/builders/coil_supports.py
@@ -28,6 +28,7 @@
from typing import Dict, List, Optional, Tuple, Type, Union
import numpy as np
+import numpy.typing as npt
from bluemira.base.builder import Builder
from bluemira.base.components import Component, PhysicalComponent
@@ -38,7 +39,7 @@
from bluemira.builders.tools import apply_component_display_options
from bluemira.display.palettes import BLUE_PALETTE
from bluemira.geometry.compound import BluemiraCompound
-from bluemira.geometry.constants import VERY_BIG
+from bluemira.geometry.constants import D_TOLERANCE, VERY_BIG
from bluemira.geometry.coordinates import Coordinates, get_intersect
from bluemira.geometry.error import GeometryError
from bluemira.geometry.face import BluemiraFace
@@ -621,7 +622,9 @@ def bounds() -> Tuple[np.ndarray, np.ndarray]:
return np.array([0, 0]), np.array([1, 1])
@staticmethod
- def f_L_to_wire(wire: BluemiraWire, x_norm: List[float]): # noqa: N802
+ def f_L_to_wire( # noqa: N802
+ wire: BluemiraWire, x_norm: Union[List[float], npt.NDArray]
+ ):
"""
Convert a pair of normalised L values to a wire
"""
@@ -667,6 +670,9 @@ def constrain_koz(self, x_norm: np.ndarray) -> np.ndarray:
-------
KOZ constraint array
"""
+ if np.isnan(x_norm).any():
+ bluemira_warn(f"NaN in x_norm {x_norm}")
+ x_norm = np.array([0, D_TOLERANCE])
straight_line = self.f_L_to_wire(self.wire, x_norm)
straight_points = straight_line.discretize(ndiscr=self.n_koz_discr).xz.T
return signed_distance_2D_polygon(straight_points, self.koz_points)
diff --git a/bluemira/codes/_freecadapi.py b/bluemira/codes/_freecadapi.py
index 60bc7cee2a..34a68dc28a 100644
--- a/bluemira/codes/_freecadapi.py
+++ b/bluemira/codes/_freecadapi.py
@@ -595,6 +595,8 @@ def offset_wire(
raise FreeCADError(msg) from None
fix_wire(wire)
+ if not wire.isClosed() and not open_wire:
+ raise FreeCADError("offset failed to close wire")
return wire
diff --git a/bluemira/codes/interface.py b/bluemira/codes/interface.py
index 47e148b42e..bb3ade871a 100644
--- a/bluemira/codes/interface.py
+++ b/bluemira/codes/interface.py
@@ -236,9 +236,11 @@ def _map_external_outputs_to_bluemira_params(
for bm_name, mapping in self.params.mappings.items():
if not (mapping.recv or recv_all):
continue
- output_value = self._get_output_or_raise(external_outputs, mapping.name)
+ output_value = self._get_output_or_raise(external_outputs, mapping.out_name)
if mapping.unit is None:
- bluemira_warn(f"{mapping.name} from code {self._name} has no known unit")
+ bluemira_warn(
+ f"{mapping.out_name} from code {self._name} has no known unit"
+ )
value = output_value
elif output_value is None:
value = output_value
diff --git a/bluemira/codes/params.py b/bluemira/codes/params.py
index 70cb0654a4..b81f4cef4d 100644
--- a/bluemira/codes/params.py
+++ b/bluemira/codes/params.py
@@ -47,7 +47,9 @@ def defaults(self) -> Dict:
"""
@classmethod
- def from_defaults(cls, data: Dict) -> MappedParameterFrame:
+ def from_defaults(
+ cls, data: Dict, source: str = "bluemira codes default"
+ ) -> MappedParameterFrame:
"""
Create ParameterFrame with default values for external codes.
@@ -62,7 +64,7 @@ def from_defaults(cls, data: Dict) -> MappedParameterFrame:
new_param_dict[bm_map_name] = {
"value": data.get(param_map.name, None),
"unit": param_map.unit,
- "source": "bluemira codes default",
+ "source": source,
}
return cls.from_dict(new_param_dict)
@@ -130,6 +132,7 @@ class ParameterMapping:
"""
name: str
+ out_name: Optional[str] = None
send: bool = True
recv: bool = True
unit: Optional[str] = None
@@ -140,7 +143,9 @@ def __post_init__(self):
"""
Freeze the dataclass
"""
- self._frozen = ("name", "unit", "_frozen")
+ if self.out_name is None:
+ self.out_name = self.name
+ self._frozen = ("name", "out_name", "unit", "_frozen")
def to_dict(self) -> Dict:
"""
@@ -148,6 +153,7 @@ def to_dict(self) -> Dict:
"""
return {
"name": self.name,
+ "out_name": self.out_name,
"send": self.send,
"recv": self.recv,
"unit": self.unit,
@@ -179,10 +185,10 @@ def __setattr__(self, attr: str, value: Union[bool, str]):
Value of attribute
"""
if (
- attr not in ["send", "recv", "name", "unit", "_frozen"]
+ attr not in ["send", "recv", "name", "out_name", "unit", "_frozen"]
or attr in self._frozen
):
- raise KeyError(f"{attr} cannot be set for a {self.__class__.__name__}")
+ raise KeyError(f"{attr} cannot be set for a {type(self).__name__}")
if attr in ["send", "recv"] and not isinstance(value, bool):
raise ValueError(f"{attr} must be a bool")
super().__setattr__(attr, value)
diff --git a/bluemira/codes/plasmod/params.py b/bluemira/codes/plasmod/params.py
index 68b147e12f..62aea546a9 100644
--- a/bluemira/codes/plasmod/params.py
+++ b/bluemira/codes/plasmod/params.py
@@ -25,7 +25,7 @@
from copy import deepcopy
from dataclasses import asdict, dataclass
from enum import Enum
-from typing import ClassVar, Dict, Union
+from typing import Dict, Union
from bluemira.base.parameter_frame import Parameter
from bluemira.codes.params import MappedParameterFrame
@@ -126,7 +126,7 @@ class PlasmodSolverParams(MappedParameterFrame):
v_burn: Parameter[float]
"""Target loop voltage (if lower than -1e-3, ignored)-> plasma loop voltage [V]."""
- _mappings: ClassVar = deepcopy(mappings)
+ _mappings = deepcopy(mappings)
_defaults = PlasmodInputs()
@property
diff --git a/bluemira/codes/process/_equation_variable_mapping.py b/bluemira/codes/process/_equation_variable_mapping.py
new file mode 100644
index 0000000000..d900cc92a9
--- /dev/null
+++ b/bluemira/codes/process/_equation_variable_mapping.py
@@ -0,0 +1,731 @@
+# bluemira is an integrated inter-disciplinary design tool for future fusion
+# reactors. It incorporates several modules, some of which rely on other
+# codes, to carry out a range of typical conceptual fusion reactor design
+# activities.
+#
+# Copyright (C) 2021-2023 M. Coleman, J. Cook, F. Franza, I.A. Maione, S. McIntosh,
+# J. Morris, D. Short
+#
+# bluemira is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# bluemira is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with bluemira; if not, see .
+"""
+Death to PROCESS integers
+"""
+from dataclasses import dataclass, field
+from typing import Tuple
+
+from bluemira.codes.utilities import Model
+
+
+class Objective(Model):
+ """
+ Enum for PROCESS optimisation objective
+ """
+
+ MAJOR_RADIUS = 1
+ # 2 NOT USED
+ NEUTRON_WALL_LOAD = 3
+ MAX_COIL_POWER = 4
+ FUSION_GAIN = 5
+ ELECTRICITY_COST = 6
+ CAPITAL_COST = 7
+ ASPECT_RATIO = 8
+ DIVERTOR_HEAT_LOAD = 9
+ TOROIDAL_FIELD = 10
+ INJECTED_POWER = 11
+ # 12, 13 NOT USED
+ PULSE_LENGTH = 14
+ AVAILABILITY = 15
+ MAJOR_RADIUS_PULSE_LENGTH = 16
+ NET_ELECTRICITY = 17
+ NULL = 18
+ FUSION_GAIN_PULSE_LENGTH = 19
+
+
+OBJECTIVE_MIN_ONLY = (16, 19)
+
+
+@dataclass
+class ConstraintSelection:
+ """
+ Mixin dataclass for a Constraint selection in PROCESSModel
+
+ Parameters
+ ----------
+ _value_:
+ Integer value of the constraint
+ requires_variables:
+ List of required iteration variables for the constraint
+ requires_values:
+ List of required inputs for the constraint
+ description:
+ Short description of the model constraint
+ """
+
+ _value_: int
+ requires_variables: Tuple[int] = field(default_factory=tuple)
+ requires_values: Tuple[str] = field(default_factory=tuple)
+ description: str = ""
+
+
+class Constraint(ConstraintSelection, Model):
+ """
+ Enum for PROCESS constraints
+ """
+
+ BETA_CONSISTENCY = 1, (5,), (), "Beta consistency"
+ GLOBAL_POWER_CONSISTENCY = (
+ 2,
+ (1, 2, 3, 4, 6, 10, 11),
+ (),
+ "Global Power Balance Consistency",
+ )
+ ION_POWER_CONSISTENCY = (
+ 3,
+ (1, 2, 3, 4, 6, 10, 11),
+ (),
+ "DEPRECATED - Ion Power Balance Consistency",
+ )
+ ELECTRON_POWER_CONSISTENCY = (
+ 4,
+ (1, 2, 3, 4, 6, 10, 11),
+ (),
+ "DEPRECATED - Electron Power Balance Consistency",
+ )
+ DENSITY_UPPER_LIMIT = (
+ 5,
+ (1, 2, 3, 4, 6, 9),
+ (),
+ "Density Upper Limit (Greenwald)",
+ )
+ EPS_BETA_POL_UPPER_LIMIT = (
+ 6,
+ (1, 2, 3, 4, 6, 8),
+ ("epbetmax",),
+ "Equation for epsilon beta-poloidal upper limit",
+ )
+ HOT_BEAM_ION_DENSITY = 7, (7,), (), "Equation for hot beam ion density"
+ NWL_UPPER_LIMIT = (
+ 8,
+ (1, 2, 3, 4, 6, 14),
+ ("walalw",),
+ "Neutron wall load upper limit",
+ )
+ FUSION_POWER_UPPER_LIMIT = (
+ 9,
+ (1, 2, 3, 4, 6, 26),
+ ("powfmax",),
+ "Equation for fusion power upper limit",
+ )
+ # 10 NOT USED
+ RADIAL_BUILD_CONSISTENCY = (
+ 11,
+ (1, 3, 13, 16, 29, 42, 61),
+ (),
+ "Radial Build Consistency",
+ )
+ VS_LOWER_LIMIT = (
+ 12,
+ (1, 2, 3, 15),
+ (),
+ "Equation for volt-second capability lower limit",
+ )
+ BURN_TIME_LOWER_LIMIT = (
+ 13,
+ (1, 2, 3, 6, 16, 17, 19, 29, 42, 44, 61),
+ (),
+ "Burn time lower limit",
+ )
+ NBI_LAMBDA_CENTRE = (
+ 14,
+ (),
+ (),
+ "Equation to fix number of NBI decay lengths to plasma centre",
+ )
+ LH_THRESHHOLD_LIMIT = 15, (103,), (), "L-H Power ThresHhold Limit"
+ NET_ELEC_LOWER_LIMIT = (
+ 16,
+ (1, 2, 3, 25),
+ ("pnetelin",),
+ "Net electric power lower limit",
+ )
+ RAD_POWER_UPPER_LIMIT = 17, (28,), (), "Equation for radiation power upper limit"
+ DIVERTOR_HEAT_UPPER_LIMIT = (
+ 18,
+ (27),
+ (),
+ "Equation for divertor heat load upper limit",
+ )
+ MVA_UPPER_LIMIT = 19, (30,), ("mvalim",), "Equation for MVA upper limit"
+ NBI_TANGENCY_UPPER_LIMIT = (
+ 20,
+ (3, 13, 31, 33),
+ (),
+ "Equation for neutral beam tangency radius upper limit",
+ )
+ AMINOR_LOWER_LIMIT = 21, (32,), (), "Equation for minor radius lower limit"
+ DIV_COLL_CONN_UPPER_LIMIT = (
+ 22,
+ (34,),
+ (),
+ "Equation for divertor collision/connection length ratio upper limit",
+ )
+ COND_SHELL_R_RATIO_UPPER_LIMIT = (
+ 23,
+ (1, 74, 104),
+ ("cwrmax",),
+ "Equation for conducting shell radius / rminor upper limit",
+ )
+ BETA_UPPER_LIMIT = 24, (1, 2, 3, 4, 6, 18, 36), (), "Beta Upper Limit"
+ PEAK_TF_UPPER_LIMIT = (
+ 25,
+ (3, 13, 29, 35),
+ ("bmxlim",),
+ "Peak toroidal field upper limit",
+ )
+ CS_EOF_DENSITY_LIMIT = (
+ 26,
+ (37, 38, 41),
+ (),
+ "Central solenoid EOF current density upper limit",
+ )
+ CS_BOP_DENSITY_LIMIT = (
+ 27,
+ (37, 38, 41),
+ (),
+ "Central solenoid bop current density upper limit",
+ )
+ Q_LOWER_LIMIT = (
+ 28,
+ (40, 45, 47),
+ ("bigqmin",),
+ "Equation for fusion gain (big Q) lower limit",
+ )
+ IB_RADIAL_BUILD_CONSISTENCY = (
+ 29,
+ (1, 3, 13, 16, 29, 42, 61),
+ (),
+ "Equation for minor radius lower limit OR Inboard radial build consistency",
+ )
+ PINJ_UPPER_LIMIT = 30, (11, 46, 47), ("pinjalw",), "Injection Power Upper Limit"
+ TF_CASE_STRESS_UPPER_LIMIT = (
+ 31,
+ (48, 56, 57, 58, 59, 60),
+ ("sig_tf_case_max",),
+ "TF coil case stress upper limit",
+ )
+ TF_JACKET_STRESS_UPPER_LIMIT = (
+ 32,
+ (49, 56, 57, 58, 59, 60),
+ ("sig_tf_wp_max",),
+ "TF WP steel jacket/conduit stress upper limit",
+ )
+ TF_JCRIT_RATIO_UPPER_LIMIT = (
+ 33,
+ (50, 56, 57, 58, 59, 60),
+ (),
+ "TF superconductor operating current / critical current density",
+ )
+ TF_DUMP_VOLTAGE_UPPER_LIMIT = (
+ 34,
+ (51, 52, 56, 57, 58, 59, 60),
+ ("vdalw",),
+ "TF dump voltage upper limit",
+ )
+ TF_CURRENT_DENSITY_UPPER_LIMIT = (
+ 35,
+ (53, 56, 57, 58, 59, 60),
+ (),
+ "TF winding pack current density upper limit",
+ )
+ TF_T_MARGIN_LOWER_LIMIT = (
+ 36,
+ (54, 56, 57, 58, 59, 60),
+ ("tftmp",),
+ "TF temperature margin upper limit",
+ )
+ CD_GAMMA_UPPER_LIMIT = (
+ 37,
+ (40, 47),
+ ("gammax",),
+ "Equation for current drive gamma upper limit",
+ )
+ # 38 NOT USED
+ FW_TEMP_UPPER_LIMIT = 39, (63,), (), "First wall peak temperature upper limit"
+ PAUX_LOWER_LIMIT = (
+ 40,
+ (64,),
+ ("auxmin",),
+ "Start-up injection power upper limit (PULSE)",
+ )
+ IP_RAMP_LOWER_LIMIT = (
+ 41,
+ (65, 66),
+ ("tohsmn",),
+ "Plasma ramp-up time lower limit (PULSE)",
+ )
+ CYCLE_TIME_LOWER_LIMIT = (
+ 42,
+ (17, 65, 67),
+ ("tcycmn",),
+ "Cycle time lower limit (PULSE)",
+ )
+ CENTREPOST_TEMP_AVERAGE = (
+ 43,
+ (13, 20, 69, 70),
+ (),
+ "Average centrepost temperature (TART) consistency equation",
+ )
+ CENTREPOST_TEMP_UPPER_LIMIT = (
+ 44,
+ (68, 69, 70),
+ ("ptempalw",),
+ "Peak centrepost temperature upper limit (TART)",
+ )
+ QEDGE_LOWER_LIMIT = 45, (1, 2, 3, 70), (), "Edge safety factor lower limit (TART)"
+ IP_IROD_UPPER_LIMIT = 46, (2, 60, 72), (), "Equation for Ip/Irod upper limit (TART)"
+ # 47 NOT USED (or maybe it is, WTF?!)
+ BETAPOL_UPPER_LIMIT = 48, (2, 3, 18, 79), ("betpmax",), "Poloidal beta upper limit"
+ # 49 NOT USED
+ REP_RATE_UPPER_LIMIT = 50, (86,), (), "IFE repetition rate upper limit (IFE)"
+ CS_FLUX_CONSISTENCY = (
+ 51,
+ (1, 3, 16, 29),
+ (),
+ "Startup volt-seconds consistency (PULSE)",
+ )
+ TBR_LOWER_LIMIT = 52, (89, 90, 91), ("tbrmin",), "Tritium breeding ratio lower limit"
+ NFLUENCE_TF_UPPER_LIMIT = (
+ 53,
+ (92, 93, 94),
+ ("nflutfmax",),
+ "Neutron fluence on TF coil upper limit",
+ )
+ PNUCL_TF_UPPER_LIMIT = (
+ 54,
+ (93, 94, 95),
+ ("ptfnucmax",),
+ "Peak TF coil nuclear heating upper limit",
+ )
+ HE_VV_UPPER_LIMIT = (
+ 55,
+ (93, 94, 96),
+ ("vvhealw",),
+ "Vacuum vessel helium concentration upper limit iblanket=2",
+ )
+ PSEPR_UPPER_LIMIT = (
+ 56,
+ (1, 3, 97, 102),
+ ("pseprmax",),
+ "Pseparatrix/Rmajor upper limit",
+ )
+ # 57, 58 NOT USED
+ NBI_SHINETHROUGH_UPPER_LIMIT = (
+ 59,
+ (4, 6, 19, 105),
+ ("nbshinefmax",),
+ "Neutral beam shinethrough fraction upper limit (NBI)",
+ )
+ CS_T_MARGIN_LOWER_LIMIT = (
+ 60,
+ (106,),
+ (),
+ "Central solenoid temperature margin lower limit (SCTF)[sic.."
+ " I guess they mean SCCS]",
+ )
+ AVAIL_LOWER_LIMIT = 61, (107,), ("avail_min",), "Minimum availability value"
+ CONFINEMENT_RATIO_LOWER_LIMIT = (
+ 62,
+ (110,),
+ ("taulimit",),
+ "taup/taueff the ratio of particle to energy confinement times",
+ )
+ NITERPUMP_UPPER_LIMIT = (
+ 63,
+ (111,),
+ (),
+ "The number of ITER-like vacuum pumps niterpump < tfno",
+ )
+ ZEFF_UPPER_LIMIT = 64, (112,), ("zeffmax",), "Zeff less than or equal to zeffmax"
+ DUMP_TIME_LOWER_LIMIT = (
+ 65,
+ (56, 113),
+ ("max_vv_stress",),
+ "Dump time set by VV loads",
+ )
+ PF_ENERGY_RATE_UPPER_LIMIT = (
+ 66,
+ (65, 113),
+ ("tohs",),
+ "Limit on rate of change of energy in poloidal field",
+ )
+ WALL_RADIATION_UPPER_LIMIT = (
+ 67,
+ (4, 6, 102, 116),
+ ("peakfactrad", "peakradwallload"),
+ "Simple radiation wall load limit",
+ )
+ PSEPB_QAR_UPPER_LIMIT = (
+ 68,
+ (117,),
+ ("psepbqarmax",),
+ "P_separatrix Bt / q A R upper limit",
+ )
+ PSEP_KALLENBACH_UPPER_LIMIT = (
+ 69,
+ (118,),
+ (),
+ "ensure the separatrix power = the value from Kallenbach divertor",
+ )
+ TSEP_CONSISTENCY = (
+ 70,
+ (119,),
+ (),
+ "ensure that temp = separatrix in the pedestal profile",
+ )
+ NSEP_CONSISTENCY = (
+ 71,
+ (),
+ (),
+ "ensure that neomp = separatrix density (nesep) x neratio",
+ )
+ CS_STRESS_UPPER_LIMIT = (
+ 72,
+ (123,),
+ (),
+ "Central solenoid shear stress limit (Tresca yield criterion)",
+ )
+ PSEP_LH_AUX_CONSISTENCY = 73, (137,), (), "Psep >= Plh + Paux"
+ TF_CROCO_T_UPPER_LIMIT = 74, (141,), ("tmax_croco",), "TFC quench"
+ TF_CROCO_CU_AREA_CONSTRAINT = (
+ 75,
+ (143,),
+ ("coppera_m2_max",),
+ "TFC current / copper area < maximum",
+ )
+ EICH_SEP_DENSITY_CONSTRAINT = 76, (144,), (), "Eich critical separatrix density"
+ TF_TURN_CURRENT_UPPER_LIMIT = (
+ 77,
+ (146,),
+ ("cpttf_max",),
+ "TF coil current per turn upper limit",
+ )
+ REINKE_IMP_FRAC_LOWER_LIMIT = (
+ 78,
+ (147,),
+ (),
+ "Reinke criterion impurity fraction lower limit",
+ )
+ BMAX_CS_UPPER_LIMIT = 79, (149,), ("bmaxcs_lim",), "Peak CS field upper limit"
+ PDIVT_LOWER_LIMIT = 80, (153,), ("pdivtlim",), "Divertor power lower limit"
+ DENSITY_PROFILE_CONSISTENCY = 81, (154,), (), "Ne(0) > ne(ped) constraint"
+ STELLARATOR_COIL_CONSISTENCY = (
+ 82,
+ (171,),
+ ("toroidalgap",),
+ )
+ STELLARATOR_RADIAL_BUILD_CONSISTENCY = (
+ 83,
+ (172,),
+ (),
+ "Radial build consistency for stellarators",
+ )
+ BETA_LOWER_LIMIT = 84, (173,), (), "Lower limit for beta"
+ CP_LIFETIME_LOWER_LIMIT = (
+ 85,
+ (),
+ ("nflutfmax",),
+ "Constraint for centrepost lifetime",
+ )
+ TURN_SIZE_UPPER_LIMIT = (
+ 86,
+ (),
+ ("t_turn_tf_max",),
+ "Constraint for TF coil turn dimension",
+ )
+ CRYOPOWER_UPPER_LIMIT = 87, (), (), "Constraint for cryogenic power"
+ TF_STRAIN_UPPER_LIMIT = (
+ 88,
+ (),
+ ("str_wp_max",),
+ "Constraint for TF coil strain absolute value",
+ )
+ OH_CROCO_CU_AREA_CONSTRAINT = (
+ 89,
+ (166,),
+ ("copperaoh_m2_max",),
+ "Constraint for CS coil quench protection",
+ )
+ CS_FATIGUE = (
+ 90,
+ (167,),
+ (
+ "residual_sig_hoop",
+ "n_cycle_min",
+ "t_crack_radial",
+ "t_crack_vertical",
+ "t_structural_radial",
+ "t_structural_vertical",
+ "sf_vertical_crack",
+ "sf_radial_crack",
+ "sf_fast_fracture",
+ "paris_coefficient",
+ "paris_power_law",
+ "walker_coefficient",
+ "fracture_toughness",
+ ),
+ "CS fatigue constraints",
+ )
+ ECRH_IGNITABILITY = 91, (168,), (), "Checking if the design point is ECRH ignitable"
+
+
+# The dreaded f-values
+FV_CONSTRAINT_ITVAR_MAPPING = {
+ 5: 9,
+ 6: 8,
+ 8: 14,
+ 9: 26,
+ 12: 15,
+ 13: 21,
+ 15: 103,
+ 16: 25,
+ 17: 28,
+ 18: 27,
+ 19: 30,
+ 20: 33,
+ 21: 32,
+ 22: 34,
+ 23: 104,
+ 24: 36,
+ 25: 35,
+ 26: 38,
+ 27: 39,
+ 28: 45,
+ # 30: 46, # Keep this as an equality constraint by default
+ 31: 48,
+ 32: 49,
+ 33: 50,
+ 34: 51,
+ 35: 53,
+ 36: 54,
+ 37: 40,
+ 38: 62,
+ 39: 63,
+ 40: 64,
+ 41: 66,
+ 42: 67,
+ 44: 68,
+ 45: 71,
+ 46: 72,
+ 48: 79,
+ 50: 86,
+ 52: 89,
+ 53: 92,
+ 54: 95,
+ 55: 96,
+ 56: 97,
+ 59: 105,
+ 60: 106,
+ 61: 107,
+ 62: 110,
+ 63: 111,
+ 64: 112,
+ 65: 113,
+ 66: 115,
+ 67: 116,
+ 68: 117,
+ 69: 118,
+ 72: 123,
+ 73: 137,
+ 74: 141,
+ 75: 143,
+ 76: 144,
+ 77: 146,
+ 78: 146,
+ 81: 154,
+ 83: 160, # OR 172?!
+ 84: 161, # OR 173?!
+ 89: 166,
+ 90: 167,
+ 91: 168,
+}
+
+ITERATION_VAR_MAPPING = {
+ "aspect": 1,
+ "bt": 2,
+ "rmajor": 3,
+ "te": 4,
+ "beta": 5,
+ "dene": 6,
+ "rnbeam": 7,
+ "fbeta": 8,
+ "fdene": 9,
+ "hfact": 10,
+ "pheat": 11,
+ # NO LONGER USED "oacdp": 12,
+ "tfcth": 13,
+ "fwalld": 14,
+ "fvs": 15,
+ "ohcth": 16,
+ "tdwell": 17,
+ "q": 18,
+ "enbeam": 19,
+ "tcpav": 20,
+ "ftburn": 21,
+ # 22 NOT USED
+ "fcoolcp": 23,
+ # 24 NOT USED
+ "fpnetel": 25,
+ "ffuspow": 26,
+ "fhldiv": 27,
+ "fradpwr": 28,
+ "bore": 29,
+ "fmva": 30,
+ "gapomin": 31,
+ "frminor": 32,
+ "fportsz": 33,
+ "fdivcol": 34,
+ "fpeakb": 35,
+ "fbetatry": 36,
+ "coheof": 37,
+ "fjohc": 38,
+ "fjohc0": 39,
+ "fgamcd": 40,
+ "fcohbop": 41,
+ "gapoh": 42,
+ # 43 NOT USED
+ "fvsbrnni": 44,
+ "fqval": 45,
+ "fpinj": 46,
+ "feffcd": 47,
+ "fstrcase": 48,
+ "fstrcond": 49,
+ "fiooic": 50,
+ "fvdump": 51,
+ "vdalw": 52,
+ "fjprot": 53,
+ "ftmargtf": 54,
+ # 55 NOT USED
+ "tdmptf": 56,
+ "thkcas": 57,
+ "thwcndut": 58,
+ "fcutfsu": 59,
+ "cpttf": 60,
+ "gapds": 61,
+ "fdtmp": 62,
+ "ftpeak": 63,
+ "fauxmn": 64,
+ "tohs": 65,
+ "ftohs": 66,
+ "ftcycl": 67,
+ "fptemp": 68,
+ "rcool": 69,
+ "vcool": 70,
+ "fq": 71,
+ "fipir": 72,
+ "scrapli": 73,
+ "scraplo": 74,
+ "tfootfi": 75,
+ # 76, 77, 78 NOT USED
+ "fbetap": 79,
+ # 80 NOT USED
+ "edrive": 81,
+ "drveff": 82,
+ "tgain": 83,
+ "chrad": 84,
+ "pdrive": 85,
+ "frrmax": 86,
+ # 87, 88 NOT USED
+ "ftbr": 89,
+ "blbuith": 90,
+ "blbuoth": 91,
+ "fflutf": 92,
+ "shldith": 93,
+ "shldoth": 94,
+ "fptfnuc": 95,
+ "fvvhe": 96,
+ "fpsepr": 97,
+ "li6enrich": 98,
+ # 99, 100, 101 NOT USED
+ "fimpvar": 102,
+ "flhthresh": 103,
+ "fcwr": 104,
+ "fnbshinef": 105,
+ "ftmargoh": 106,
+ "favail": 107,
+ "breeder_f": 108,
+ "ralpne": 109,
+ "ftaulimit": 110,
+ "fniterpump": 111,
+ "fzeffmax": 112,
+ "fmaxvvstress": 113, # OR IS IT fmaxvvstress ?! ftaucq
+ "fw_channel_length": 114,
+ "fpoloidalpower": 115,
+ "fradwall": 116,
+ "fpsepbqar": 117,
+ "fpsep": 118,
+ "tesep": 119,
+ "ttarget": 120,
+ "neratio": 121,
+ "oh_steel_frac": 122,
+ "foh_stress": 123,
+ "qtargettotal": 124,
+ "fimp(3)": 125, # Beryllium
+ "fimp(4)": 126, # Carbon
+ "fimp(5)": 127, # Nitrogen
+ "fimp(6)": 128, # Oxygen
+ "fimp(7)": 129, # Neon
+ "fimp(8)": 130, # Silicon
+ "fimp(9)": 131, # Argon
+ "fimp(10)": 132, # Iron
+ "fimp(11)": 133, # Nickel
+ "fimp(12)": 134, # Krypton
+ "fimp(13)": 135, # Xenon
+ "fimp(14)": 136, # Tungsten
+ "fplhsep": 137,
+ "rebco_thickness": 138,
+ "copper_thick": 139,
+ "dr_tf_wp": 140, # TODO: WTF
+ "fcqt": 141,
+ "nesep": 142,
+ "f_coppera_m2": 143,
+ "fnesep": 144,
+ "fgwped": 145,
+ "fcpttf": 146,
+ "freinke": 147,
+ "fzactual": 148,
+ "fbmaxcs": 149,
+ # 150, 151 NOT USED
+ "fgwsep": 152,
+ "fpdivlim": 153,
+ "fne0": 154,
+ "pfusife": 155,
+ "rrin": 156,
+ "fvssu": 157,
+ "croco_thick": 158,
+ "ftoroidalgap": 159,
+ "f_avspace": 160,
+ "fbetatry_lower": 161,
+ "r_cp_top": 162,
+ "f_t_turn_tf": 163,
+ "f_crypmw": 164,
+ "fstr_wp": 165,
+ "f_copperaoh_m2": 166,
+ "fncycle": 167,
+ "fecrh_ignition": 168,
+ "te0_ecrh_achievable": 169,
+ "beta_div": 170,
+}
+
+
+VAR_ITERATION_MAPPING = {v: k for k, v in ITERATION_VAR_MAPPING.items()}
diff --git a/bluemira/codes/process/_inputs.py b/bluemira/codes/process/_inputs.py
index f46ec00f89..3013941da4 100644
--- a/bluemira/codes/process/_inputs.py
+++ b/bluemira/codes/process/_inputs.py
@@ -22,8 +22,8 @@
Parameter classes/structures for Process
"""
-from dataclasses import dataclass, field, fields
-from typing import Dict, Generator, List, Tuple, Union
+from dataclasses import dataclass, fields
+from typing import Dict, Generator, List, Optional, Tuple, Union
from bluemira.codes.process.api import _INVariable
@@ -42,230 +42,670 @@ class ProcessInputs:
`process.io.python_fortran_dicts.get_dicts()["DICT_DESCRIPTIONS"]`
"""
- bounds: Dict[str, Dict[str, str]] = field(
- default_factory=lambda: {
- "2": {"u": "20.0"},
- "3": {"u": "13"},
- "4": {"u": "150.0"},
- "9": {"u": "1.2"},
- "18": {"l": "3.5"},
- "29": {"l": "0.1"},
- "38": {"u": "1.0"},
- "39": {"u": "1.0"},
- "42": {"l": "0.05", "u": "0.1"},
- "50": {"u": "1.0"},
- "52": {"u": "10.0"},
- "61": {"l": "0.02"},
- "103": {"u": "10.0"},
- "60": {"l": "6.0e4", "u": "9.0e4"},
- "59": {"l": "0.50", "u": "0.94"},
- }
- )
- # fmt: off
- icc: List[int] = field(default_factory=lambda: [1, 2, 5, 8, 11, 13, 15, 16, 24, 25,
- 26, 27, 30, 31, 32, 33, 34, 35, 36,
- 60, 62, 65, 68, 72])
- ixc: List[int] = field(default_factory=lambda: [2, 3, 4, 5, 6, 9, 13, 14, 16, 18,
- 29, 36, 37, 38, 39, 41, 42, 44, 48,
- 49, 50, 51, 52, 53, 54, 56, 57, 58,
- 59, 60, 61, 102, 103, 106, 109, 110,
- 113, 117, 122, 123])
- # fmt: on
- abktflnc: float = 15.0
- adivflnc: float = 20.0
- alphan: float = 1.0
- alphat: float = 1.45
- alstroh: float = 660000000.0
- aspect: float = 3.1
- beta: float = 0.031421
- blnkith: float = 0.755
- blnkoth: float = 0.982
- bmxlim: float = 11.2
- bore: float = 2.3322
- bscfmax: float = 0.99
- bt: float = 5.3292
- casths: float = 0.05
- cfactr: float = 0.75
- coheof: float = 20726000.0
- coreradiationfraction: float = 0.6
- coreradius: float = 0.75
- cost_model: int = 0
- cptdin: List[float] = field(
- default_factory=lambda: [*([42200.0] * 4), *([43000.0] * 4)]
- )
- cpttf: float = 65000.0
- d_vv_bot: float = 0.6
- d_vv_in: float = 0.6
- d_vv_out: float = 1.1
- d_vv_top: float = 0.6
- ddwex: float = 0.15
- dene: float = 7.4321e19
- dhecoil: float = 0.01
- dintrt: float = 0.0
- discount_rate: float = 0.06
- divdum: int = 1
- divfix: float = 0.621
- dnbeta: float = 3.0
- dr_tf_case_in: float = 0.52465
- dr_tf_case_out: float = 0.06
- emult: float = 1.35
- enbeam: float = 1e3
- epsvmc: float = 1e-08
- etaech: float = 0.4
- etahtp: float = 0.87
- etaiso: float = 0.9
- etanbi: float = 0.3
- etath: float = 0.375
- fbetatry: float = 0.48251
- fcap0: float = 1.15
- fcap0cp: float = 1.06
- fcohbop: float = 0.93176
- fcontng: float = 0.15
- fcr0: float = 0.065
- fcuohsu: float = 0.7
- fcutfsu: float = 0.80884
- fdene: float = 1.2
- ffuspow: float = 1.0
- fgwped: float = 0.85
- fimp: List[float] = field(
- default_factory=lambda: [1.0, 0.1, *([0.0] * 10), 0.00044, 5e-05]
- )
- fimpvar: float = 0.00037786
- fiooic: float = 0.63437
- fjohc0: float = 0.53923
- fjohc: float = 0.57941
- fjprot: float = 1.0
- fkind: float = 1.0
- fkzohm: float = 1.0245
- flhthresh: float = 1.4972
- foh_stress: float = 1.0
- fpeakb: float = 1.0
- fpinj: float = 1.0
- fpnetel: float = 1.0
- fpsepbqar: float = 1.0
- fstrcase: float = 1.0
- fstrcond: float = 0.92007
- ftaucq: float = 0.91874
- ftaulimit: float = 1.0
- ftburn: float = 1.0
- ftmargoh: float = 1.0
- ftmargtf: float = 1.0
- fvdump: float = 1.0
- fvsbrnni: float = 0.39566
- fwalld: float = 0.131
- gamma: float = 0.3
- gamma_ecrh: float = 0.3
- gapds: float = 0.02
- gapoh: float = 0.05
- gapomin: float = 0.2
- hfact: float = 1.1
- hldivlim: float = 10.0
- i_single_null: int = 1
- i_tf_sc_mat: int = 5
- i_tf_turns_integer: int = 1
- iavail: int = 0
- ibss: int = 4
- iculbl: int = 1
- icurr: int = 4
- idensl: int = 7
- iefrf: int = 10
- ieped: int = 1
- ifalphap: int = 1
- ifispact: int = 0
- ifueltyp: int = 1
- iinvqd: int = 1
- impvar: int = 13
- inuclear: int = 1
- iohcl: int = 1
- ioptimz: int = 1
- ipedestal: int = 1
- ipfloc: List[int] = field(default_factory=lambda: [2, 2, 3, 3])
- ipowerflow: int = 0
- iprimshld: int = 1
- iprofile: int = 1
- isc: int = 34
- ishape: int = 0
- isumatoh: int = 5
- isumatpf: int = 3
- kappa: float = 1.848
- ksic: float = 1.4
- lpulse: int = 1
- lsa: int = 2
- minmax: int = 1
- n_layer: int = 10
- n_pancake: int = 20
- n_tf: int = 16
- ncls: List[int] = field(default_factory=lambda: [1, 1, 2, 2])
- neped: float = 6.78e19
- nesep: float = 2e19
- ngrp: int = 4
- oacdcp: float = 8673900.0
- oh_steel_frac: float = 0.57875
- ohcth: float = 0.55242
- ohhghf: float = 0.9
- output_costs: int = 0
- pheat: float = 50.0
- pinjalw: float = 51.0
- plasma_res_factor: float = 0.66
- pnetelin: float = 500.0
- primary_pumping: int = 3
- prn1: float = 0.4
- psepbqarmax: float = 9.2
- pulsetimings: float = 0.0
- q0: float = 1.0
- q: float = 3.5
- qnuc: float = 12920.0
- ralpne: float = 0.06894
- rhopedn: float = 0.94
- rhopedt: float = 0.94
- ripmax: float = 0.6
- rjconpf: List[float] = field(
- default_factory=lambda: [1.1e7, 1.1e7, 6e6, 6e6, 8e6, 8e6, 8e6, 8e6]
- )
- rmajor: float = 8.8901
- rpf2: float = -1.825
- scrapli: float = 0.225
- scraplo: float = 0.225
- secondary_cycle: int = 2
- shldith: float = 1e-06
- shldlth: float = 1e-06
- shldoth: float = 1e-06
- shldtth: float = 1e-06
- sig_tf_case_max: float = 580000000.0
- sig_tf_wp_max: float = 580000000.0
- ssync: float = 0.6
- tbeta: float = 2.0
- tbrnmn: float = 7200.0
- tburn: float = 10000.0
- tdmptf: float = 25.829
- tdwell: float = 0.0
- te: float = 12.33
- teped: float = 5.5
- tesep: float = 0.1
- tfcth: float = 1.208
- tftmp: float = 4.75
- tftsgap: float = 0.05
- thicndut: float = 0.002
- thshield: float = 0
- thwcndut: float = 0.008
- tinstf: float = 0.008
- tlife: float = 40.0
- tmargmin: float = 1.5
- tramp: float = 500.0
- triang: float = 0.5
- ucblvd: float = 280.0
- ucdiv: float = 500000.0
- ucme: float = 300000000.0
- vdalw: float = 10.0
- vfshld: float = 0.6
- vftf: float = 0.3
- vgap2: float = 0.05
- vvblgap: float = 0.02
- walalw: float = 8.0
- zeffdiv: float = 3.5
- zref: List[float] = field(
- default_factory=lambda: [3.6, 1.2, 1.0, 2.8, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
- )
+ runtitle: Optional[str] = None
+
+ # Optimisation problem setup
+ bounds: Optional[Dict[str, Dict[str, str]]] = None
+ icc: Optional[List[int]] = None
+ ixc: Optional[List[int]] = None
+
+ # Settings
+ maxcal: Optional[int] = None
+ minmax: Optional[int] = None
+ epsvmc: Optional[float] = None
+ ioptimz: Optional[int] = None
+ output_costs: Optional[int] = None
+ isweep: Optional[int] = None
+ nsweep: Optional[int] = None
+ sweep: List[float] = None
+ pulsetimings: Optional[int] = None
+ # Top down of PROCESS variables list
+
+ # Times
+ tburn: Optional[float] = None
+ tdwell: Optional[float] = None
+ theat: Optional[float] = None
+ tohs: Optional[float] = None
+ tqnch: Optional[float] = None
+ tramp: Optional[float] = None
+
+ # FWBS
+ ibkt_life: Optional[int] = None
+ denstl: Optional[float] = None
+ denw: Optional[float] = None
+ emult: Optional[float] = None
+ fblss: Optional[float] = None
+ fdiv: Optional[float] = None
+ fwbsshape: Optional[int] = None
+ fw_armour_thickness: Optional[float] = None
+ iblanket: Optional[int] = None
+ iblnkith: Optional[int] = None
+ li6enrich: Optional[float] = None
+ breeder_f: Optional[float] = None
+ breeder_multiplier: Optional[float] = None
+ vfcblkt: Optional[float] = None
+ vfpblkt: Optional[float] = None
+ blktmodel: Optional[int] = None # Listed as an output...
+ # f_neut_shield: float = # -1.0 the documentation defaults cannot be right...
+ breedmat: Optional[int] = None
+ fblbe: Optional[float] = None
+ fblbreed: Optional[float] = None
+ fblhebmi: Optional[float] = None
+ fblhebmo: Optional[float] = None
+ fblhebpi: Optional[float] = None
+ fblhebpo: Optional[float] = None
+ hcdportsize: Optional[int] = None
+ npdiv: Optional[int] = None
+ nphcdin: Optional[int] = None
+ nphcdout: Optional[int] = None
+ wallpf: Optional[float] = None
+ iblanket_thickness: Optional[int] = None
+ secondary_cycle: Optional[int] = None # Listed as an output...
+ secondary_cycle_liq: Optional[int] = None
+ afwi: Optional[float] = None
+ afwo: Optional[float] = None
+ fw_wall: Optional[float] = None
+ afw: Optional[float] = None
+ pitch: Optional[float] = None
+ fwinlet: Optional[float] = None
+ fwoutlet: Optional[float] = None
+ fwpressure: Optional[float] = None
+ roughness: Optional[float] = None
+ fw_channel_length: Optional[float] = None
+ peaking_factor: Optional[float] = None
+ blpressure: Optional[float] = None
+ inlet_temp: Optional[float] = None
+ outlet_temp: Optional[float] = None
+ coolp: Optional[float] = None
+ nblktmodpo: Optional[int] = None
+ nblktmodpi: Optional[int] = None
+ nblktmodto: Optional[int] = None
+ nblktmodti: Optional[int] = None
+ tfwmatmax: Optional[float] = None
+ fw_th_conductivity: Optional[float] = None
+ fvoldw: Optional[float] = None
+ fvolsi: Optional[float] = None
+ fvolso: Optional[float] = None
+ fwclfr: Optional[float] = None
+ rpf2dewar: Optional[float] = None
+ vfshld: Optional[float] = None
+ irefprop: Optional[int] = None
+ fblli2o: Optional[float] = None
+ fbllipb: Optional[float] = None
+ vfblkt: Optional[float] = None
+ declblkt: Optional[float] = None
+ declfw: Optional[float] = None
+ declshld: Optional[float] = None
+ blkttype: Optional[int] = None
+ etaiso: Optional[float] = None
+ etahtp: Optional[float] = None
+ n_liq_recirc: Optional[int] = None
+ bz_channel_conduct_liq: Optional[float] = None
+ blpressure_liq: Optional[float] = None
+ inlet_temp_liq: Optional[float] = None
+ outlet_temp_liq: Optional[float] = None
+ f_nuc_pow_bz_struct: Optional[float] = None
+ pnuc_fw_ratio_dcll: Optional[float] = None
+
+ # TF coil
+ sig_tf_case_max: Optional[float] = None
+ sig_tf_wp_max: Optional[float] = None
+ bcritsc: Optional[float] = None
+ casthi_fraction: Optional[float] = None
+ casths_fraction: Optional[float] = None
+ f_t_turn_tf: Optional[float] = None
+ t_turn_tf_max: Optional[float] = None
+ cpttf: Optional[float] = None
+ cpttf_max: Optional[float] = None
+ dcase: Optional[float] = None
+ dcond: List[float] = None
+ dcondins: Optional[float] = None
+ dhecoil: Optional[float] = None
+ farc4tf: Optional[float] = None
+ b_crit_upper_nbti: Optional[float] = None
+ t_crit_nbti: Optional[float] = None
+ fcutfsu: Optional[float] = None
+ fhts: Optional[float] = None
+ i_tf_stress_model: Optional[int] = None
+ i_tf_wp_geom: Optional[int] = None
+ i_tf_case_geom: Optional[int] = None # Listed as an output
+ i_tf_turns_integer: Optional[int] = None # Listed as an output
+ i_tf_sc_mat: Optional[int] = None
+ i_tf_sup: Optional[int] = None
+ i_tf_shape: Optional[int] = None # Listed as an output
+ i_tf_cond_eyoung_trans: Optional[int] = None
+ n_pancake: Optional[int] = None
+ n_layer: Optional[int] = None
+ n_rad_per_layer: Optional[int] = None
+ i_tf_bucking: Optional[int] = None
+ n_tf_graded_layers: Optional[int] = None
+ jbus: Optional[float] = None
+ eyoung_ins: Optional[float] = None
+ eyoung_steel: Optional[float] = None
+ eyong_cond_axial: Optional[float] = None
+ eyoung_res_tf_buck: Optional[float] = None
+ # eyoung_al: Optional[float] = 69000000000.0 # defaults cannot be right
+ poisson_steel: Optional[float] = None
+ poisson_copper: Optional[float] = None
+ poisson_al: Optional[float] = None
+ str_cs_con_res: Optional[float] = None
+ str_pf_con_res: Optional[float] = None
+ str_tf_con_res: Optional[float] = None
+ str_wp_max: Optional[float] = None
+ i_str_wp: Optional[int] = None
+ quench_model: str = None
+ tcritsc: Optional[float] = None
+ tdmptf: Optional[float] = None
+ tfinsgap: Optional[float] = None
+ # rhotfbus: Optional[float] = -1.0 # defaults cannot be right
+ frhocp: Optional[float] = None
+ frholeg: Optional[float] = None
+ # i_cp_joints: Optional[int] = -1 # defaults cannot be right
+ rho_tf_joints: Optional[float] = None
+ n_tf_joints_contact: Optional[int] = None
+ n_tf_joints: Optional[int] = None
+ th_joint_contact: Optional[float] = None
+ # eff_tf_cryo: Optional[float] = -1.0 # defaults cannot be right
+ n_tf: Optional[int] = None
+ tftmp: Optional[float] = None
+ thicndut: Optional[float] = None
+ thkcas: Optional[float] = None
+ thwcndut: Optional[float] = None
+ tinstf: Optional[float] = None
+ tmaxpro: Optional[float] = None
+ tmax_croco: Optional[float] = None
+ tmpcry: Optional[float] = None
+ vdalw: Optional[float] = None
+ f_vforce_inboard: Optional[float] = None
+ vftf: Optional[float] = None
+ etapump: Optional[float] = None
+ fcoolcp: Optional[float] = None
+ fcoolleg: Optional[float] = None
+ ptempalw: Optional[float] = None
+ rcool: Optional[float] = None
+ tcoolin: Optional[float] = None
+ tcpav: Optional[float] = None
+ vcool: Optional[float] = None
+ theta1_coil: Optional[float] = None
+ theta1_vv: Optional[float] = None
+ max_vv_stress: Optional[float] = None
+ inuclear: Optional[int] = None
+ qnuc: Optional[float] = None
+ ripmax: Optional[float] = None
+ tf_in_cs: Optional[int] = None
+ tfcth: Optional[float] = None
+ tftsgap: Optional[float] = None
+ casthi: Optional[float] = None
+ casths: Optional[float] = None
+ tmargmin: Optional[float] = None
+ oacdcp: Optional[float] = None
+
+ # PF Power
+ iscenr: Optional[int] = None
+ maxpoloidalpower: Optional[float] = None
+
+ # Cost variables
+ abktflnc: Optional[float] = None
+ adivflnc: Optional[float] = None
+ cconfix: Optional[float] = None
+ cconshpf: Optional[float] = None
+ cconshtf: Optional[float] = None
+ cfactr: Optional[float] = None
+ cfind: List[float] = None
+ cland: Optional[float] = None
+ costexp: Optional[float] = None
+ costexp_pebbles: Optional[float] = None
+ cost_factor_buildings: Optional[float] = None
+ cost_factor_land: Optional[float] = None
+ cost_factor_tf_coils: Optional[float] = None
+ cost_factor_fwbs: Optional[float] = None
+ cost_factor_tf_rh: Optional[float] = None
+ cost_factor_tf_vv: Optional[float] = None
+ cost_factor_tf_bop: Optional[float] = None
+ cost_factor_tf_misc: Optional[float] = None
+ maintenance_fwbs: Optional[float] = None
+ maintenance_gen: Optional[float] = None
+ amortization: Optional[float] = None
+ cost_model: Optional[int] = None
+ cowner: Optional[float] = None
+ cplife_input: Optional[float] = None
+ cpstflnc: Optional[float] = None
+ csi: Optional[float] = None
+ # cturbb: Optional[float] = 38.0 # defaults cannot be right
+ decomf: Optional[float] = None
+ dintrt: Optional[float] = None
+ fcap0: Optional[float] = None
+ fcap0cp: Optional[float] = None
+ fcdfuel: Optional[float] = None
+ fcontng: Optional[float] = None
+ fcr0: Optional[float] = None
+ fkind: Optional[float] = None
+ iavail: Optional[int] = None
+ life_dpa: Optional[float] = None
+ avail_min: Optional[float] = None
+ favail: Optional[float] = None
+ num_rh_systems: Optional[int] = None
+ conf_mag: Optional[float] = None
+ div_prob_fail: Optional[float] = None
+ div_umain_time: Optional[float] = None
+ div_nref: Optional[float] = None
+ div_nu: Optional[float] = None
+ fwbs_nref: Optional[float] = None
+ fwbs_nu: Optional[float] = None
+ fwbs_prob_fail: Optional[float] = None
+ fwbs_umain_time: Optional[float] = None
+ redun_vacp: Optional[float] = None
+ tbktrepl: Optional[float] = None
+ tcomrepl: Optional[float] = None
+ tdivrepl: Optional[float] = None
+ uubop: Optional[float] = None
+ uucd: Optional[float] = None
+ uudiv: Optional[float] = None
+ uufuel: Optional[float] = None
+ uufw: Optional[float] = None
+ uumag: Optional[float] = None
+ uuves: Optional[float] = None
+ ifueltyp: Optional[int] = None
+ ucblvd: Optional[float] = None
+ ucdiv: Optional[float] = None
+ ucme: Optional[float] = None
+ ireactor: Optional[int] = None
+ lsa: Optional[int] = None
+ discount_rate: Optional[float] = None
+ startupratio: Optional[float] = None
+ tlife: Optional[float] = None
+ bkt_life_csf: Optional[int] = None
+ # ...
+
+ # CS fatigue
+ residual_sig_hoop: Optional[float] = None
+ n_cycle_min: Optional[int] = None
+ t_crack_vertical: Optional[float] = None
+ t_crack_radial: Optional[float] = None
+ t_structural_radial: Optional[float] = None
+ t_structural_vertical: Optional[float] = None
+ sf_vertical_crack: Optional[float] = None
+ sf_radial_crack: Optional[float] = None
+ sf_fast_fracture: Optional[float] = None
+ paris_coefficient: Optional[float] = None
+ paris_power_law: Optional[float] = None
+ walker_coefficient: Optional[float] = None
+ fracture_toughness: Optional[float] = None
+
+ # REBCO
+ rebco_thickness: Optional[float] = None
+ copper_thick: Optional[float] = None
+ hastelloy_thickness: Optional[float] = None
+ tape_width: Optional[float] = None
+ tape_thickness: Optional[float] = None
+ croco_thick: Optional[float] = None
+ copper_rrr: Optional[float] = None
+ copper_m2_max: Optional[float] = None
+ f_coppera_m2: Optional[float] = None
+ copperaoh_m2_max: Optional[float] = None
+ f_copperaoh_m2: Optional[float] = None
+
+ # Primary pumping
+ primary_pumping: Optional[int] = None
+ gamma_he: Optional[float] = None
+ t_in_bb: Optional[float] = None
+ t_out_bb: Optional[float] = None
+ p_he: Optional[float] = None
+ dp_he: Optional[float] = None
+
+ # Constraint variables
+ auxmin: Optional[float] = None
+ betpmx: Optional[float] = None
+ bigqmin: Optional[float] = None
+ bmxlim: Optional[float] = None
+ fauxmn: Optional[float] = None
+ fbeta: Optional[float] = None
+ fbetap: Optional[float] = None
+ fbetatry: Optional[float] = None
+ fbetatry_lower: Optional[float] = None
+ fcwr: Optional[float] = None
+ fdene: Optional[float] = None
+ fdivcol: Optional[float] = None
+ fdtmp: Optional[float] = None
+ fecrh_ignition: Optional[float] = None
+ fflutf: Optional[float] = None
+ ffuspow: Optional[float] = None
+ fgamcd: Optional[float] = None
+ fhldiv: Optional[float] = None
+ fiooic: Optional[float] = None
+ fipir: Optional[float] = None
+ fjohc: Optional[float] = None
+ fjohc0: Optional[float] = None
+ fjprot: Optional[float] = None
+ flhthresh: Optional[float] = None
+ fmva: Optional[float] = None
+ fnbshinef: Optional[float] = None
+ fncycle: Optional[float] = None
+ fnesep: Optional[float] = None
+ foh_stress: Optional[float] = None
+ fpeakb: Optional[float] = None
+ fpinj: Optional[float] = None
+ fpnetel: Optional[float] = None
+ fportsz: Optional[float] = None
+ fpsepbqar: Optional[float] = None
+ fpsepr: Optional[float] = None
+ fptemp: Optional[float] = None
+ fq: Optional[float] = None
+ fqval: Optional[float] = None
+ fradwall: Optional[float] = None
+ freinke: Optional[float] = None
+ fstrcase: Optional[float] = None
+ fstrcond: Optional[float] = None
+ fstr_wp: Optional[float] = None
+ fmaxvvstress: Optional[float] = None
+ ftbr: Optional[float] = None
+ ftburn: Optional[float] = None
+ ftcycl: Optional[float] = None
+ ftmargoh: Optional[float] = None
+ ftmargtf: Optional[float] = None
+ ftohs: Optional[float] = None
+ ftpeak: Optional[float] = None
+ fvdump: Optional[float] = None
+ fvs: Optional[float] = None
+ fvvhe: Optional[float] = None
+ fwalld: Optional[float] = None
+ fzeffmax: Optional[float] = None
+ gammax: Optional[float] = None
+ maxradwallload: Optional[float] = None
+ mvalim: Optional[float] = None
+ nbshinefmax: Optional[float] = None
+ nflutfmax: Optional[float] = None
+ pdivtlim: Optional[float] = None
+ peakfactrad: Optional[float] = None
+ pnetelin: Optional[float] = None
+ powfmax: Optional[float] = None
+ psepbqarmax: Optional[float] = None
+ pseprmax: Optional[float] = None
+ ptfnucmax: Optional[float] = None
+ tbrmin: Optional[float] = None
+ tbrnmn: Optional[float] = None
+ vvhealw: Optional[float] = None
+ walalw: Optional[float] = None
+ taulimit: Optional[float] = None
+ ftaulimit: Optional[float] = None
+ fniterpump: Optional[float] = None
+ zeffmax: Optional[float] = None
+ fpoloidalpower: Optional[float] = None
+ fpsep: Optional[float] = None
+ fcqt: Optional[float] = None
+
+ # Build variables
+ aplasmin: Optional[float] = None
+ blbmith: Optional[float] = None
+ blbmoth: Optional[float] = None
+ blbpith: Optional[float] = None
+ blbpoth: Optional[float] = None
+ blbuith: Optional[float] = None
+ blbuoth: Optional[float] = None
+ blnkith: Optional[float] = None
+ blnkoth: Optional[float] = None
+ bore: Optional[float] = None
+ clhsf: Optional[float] = None
+ ddwex: Optional[float] = None
+ d_vv_in: Optional[float] = None
+ d_vv_out: Optional[float] = None
+ d_vv_top: Optional[float] = None
+ d_vv_bot: Optional[float] = None
+ f_avspace: Optional[float] = None
+ fcspc: Optional[float] = None
+ fhole: Optional[float] = None
+ fseppc: Optional[float] = None
+ gapds: Optional[float] = None
+ gapoh: Optional[float] = None
+ gapomin: Optional[float] = None
+ iohcl: Optional[int] = None
+ iprecomp: Optional[int] = None
+ ohcth: Optional[float] = None
+ rinboard: Optional[float] = None
+ f_r_cp: Optional[float] = None
+ scrapli: Optional[float] = None
+ scraplo: Optional[float] = None
+ shldith: Optional[float] = None
+ shldlth: Optional[float] = None
+ shldoth: Optional[float] = None
+ shldtth: Optional[float] = None
+ sigallpc: Optional[float] = None
+ tfoofti: Optional[float] = None
+ thshield_ib: Optional[float] = None
+ thshield_ob: Optional[float] = None
+ thshield_vb: Optional[float] = None
+ vgap: Optional[float] = None
+ vgap2: Optional[float] = None
+ vgaptop: Optional[float] = None
+ vvblgap: Optional[float] = None
+ plleni: Optional[float] = None
+ plsepi: Optional[float] = None
+ plsepo: Optional[float] = None
+
+ # Buildings
+
+ # Current drive
+ beamwd: Optional[float] = None
+ bscfmax: Optional[float] = None
+ cboot: Optional[float] = None
+ harnum: Optional[float] = None
+ enbeam: Optional[float] = None
+ etaech: Optional[float] = None
+ etanbi: Optional[float] = None
+ feffcd: Optional[float] = None
+ frbeam: Optional[float] = None
+ ftritbm: Optional[float] = None
+ gamma_ecrh: Optional[float] = None
+ rho_ecrh: Optional[float] = None
+ xi_ebw: Optional[float] = None
+ iefrf: Optional[int] = None
+ irfcf: Optional[int] = None
+ nbshield: Optional[float] = None
+ pheat: Optional[float] = None # Listed as an output
+ pinjalw: Optional[float] = None
+ tbeamin: Optional[float] = None
+
+ # Impurity radiation
+ coreradius: Optional[float] = None
+ coreradiationfraction: Optional[float] = None
+ fimp: List[float] = None
+ fimpvar: Optional[float] = None
+ impvar: Optional[int] = None
+
+ # Reinke
+ impvardiv: Optional[int] = None
+ lhat: Optional[float] = None
+ fzactual: Optional[float] = None
+
+ # Divertor
+ divdum: Optional[int] = None
+ anginc: Optional[float] = None
+ beta_div: Optional[float] = None
+ betai: Optional[float] = None
+ betao: Optional[float] = None
+ bpsout: Optional[float] = None
+ c1div: Optional[float] = None
+ c2div: Optional[float] = None
+ c3div: Optional[float] = None
+ c4div: Optional[float] = None
+ c5div: Optional[float] = None
+ delld: Optional[float] = None
+ divclfr: Optional[float] = None
+ divdens: Optional[float] = None
+ divfix: Optional[float] = None
+ divleg_profile_inner: Optional[float] = None
+ divleg_profile_outer: Optional[float] = None
+ divplt: Optional[float] = None
+ fdfs: Optional[float] = None
+ fdiva: Optional[float] = None
+ fgamp: Optional[float] = None
+ fififi: Optional[float] = None
+ flux_exp: Optional[float] = None
+ frrp: Optional[float] = None
+ hldivlim: Optional[float] = None
+ ksic: Optional[float] = None
+ omegan: Optional[float] = None
+ prn1: Optional[float] = None
+ rlenmax: Optional[float] = None
+ tdiv: Optional[float] = None
+ xparain: Optional[float] = None
+ xpertin: Optional[float] = None
+ zeffdiv: Optional[float] = None
+
+ # Pulse
+ bctmp: Optional[float] = None
+ dtstor: Optional[float] = None
+ istore: Optional[int] = None
+ itcycl: Optional[int] = None
+ lpulse: Optional[int] = None # Listed as an output
+
+ # IFE
+
+ # Heat transport
+ baseel: Optional[float] = None
+ crypw_max: Optional[float] = None
+ f_crypmw: Optional[float] = None
+ etatf: Optional[float] = None
+ etath: Optional[float] = None
+ fpumpblkt: Optional[float] = None
+ fpumpdiv: Optional[float] = None
+ fpumpfw: Optional[float] = None
+ fpumpshld: Optional[float] = None
+ ipowerflow: Optional[int] = None
+ iprimshld: Optional[int] = None
+ pinjmax: Optional[float] = None
+ pwpm2: Optional[float] = None
+ trithtmw: Optional[float] = None
+ vachtmw: Optional[float] = None
+ irfcd: Optional[int] = None
+
+ # Water usage
+
+ # Vacuum
+ ntype: Optional[int] = None
+ pbase: Optional[float] = None
+ prdiv: Optional[float] = None
+ pumptp: Optional[float] = None
+ rat: Optional[float] = None
+ tn: Optional[float] = None
+ pumpareafraction: Optional[float] = None
+ pumpspeedmax: Optional[float] = None
+ pumpspeedfactor: Optional[float] = None
+ initialpressure: Optional[float] = None
+ outgasindex: Optional[float] = None
+ outgasfactor: Optional[float] = None
+
+ # PF coil
+ alfapf: Optional[float] = None
+ alstroh: Optional[float] = None
+ coheof: Optional[float] = None
+ cptdin: List[float] = None
+ etapsu: Optional[float] = None
+ fcohbop: Optional[float] = None
+ fcuohsu: Optional[float] = None
+ fcupfsu: Optional[float] = None
+ fvssu: Optional[float] = None
+ ipfloc: Optional[List[int]] = None
+ ipfres: Optional[int] = None # Listed as an output
+ isumatoh: Optional[int] = None
+ isumatpf: Optional[int] = None
+ i_pf_current: Optional[int] = None
+ ncls: Optional[List[int]] = None
+ nfxfh: Optional[int] = None
+ ngrp: Optional[int] = None
+ ohhghf: Optional[float] = None
+ oh_steel_frac: Optional[float] = None
+ pfclres: Optional[float] = None
+ rjconpf: List[float] = None
+ routr: Optional[float] = None
+ rpf2: Optional[float] = None
+ rref: List[float] = None
+ sigpfcalw: Optional[float] = None
+ sigpfcf: Optional[float] = None
+ vf: List[float] = None
+ vhohc: Optional[float] = None
+ zref: List[float] = None
+ bmaxcs_lim: Optional[float] = None
+ fbmaxcs: Optional[float] = None
+ ld_ratio_cst: Optional[float] = None
+
+ # Physics
+ alphaj: Optional[float] = None
+ alphan: Optional[float] = None
+ alphat: Optional[float] = None
+ aspect: Optional[float] = None
+ beamfus0: Optional[float] = None
+ beta: Optional[float] = None
+ betbm0: Optional[float] = None
+ bt: Optional[float] = None
+ csawth: Optional[float] = None
+ cvol: Optional[float] = None
+ cwrmax: Optional[float] = None
+ dene: Optional[float] = None
+ dnbeta: Optional[float] = None
+ epbetmax: Optional[float] = None
+ falpha: Optional[float] = None
+ fdeut: Optional[float] = None
+ ftar: Optional[float] = None
+ ffwal: Optional[float] = None
+ fgwped: Optional[float] = None
+ fgwsep: Optional[float] = None
+ fkzohm: Optional[float] = None
+ fpdivlim: Optional[float] = None
+ fne0: Optional[float] = None
+ ftrit: Optional[float] = None
+ fvsbrnni: Optional[float] = None
+ gamma: Optional[float] = None
+ hfact: Optional[float] = None
+ taumax: Optional[float] = None
+ ibss: Optional[int] = None
+ iculbl: Optional[int] = None # listed as an output...
+ icurr: Optional[int] = None
+ idensl: Optional[int] = None
+ ifalphap: Optional[int] = None
+ ifispact: Optional[int] = None # listed as an output...
+ iinvqd: Optional[int] = None
+ ipedestal: Optional[int] = None
+ ieped: Optional[int] = None # listed as an output...
+ eped_sf: Optional[float] = None
+ neped: Optional[float] = None
+ nesep: Optional[float] = None
+ plasma_res_factor: Optional[float] = None
+ rhopedn: Optional[float] = None
+ rhopedt: Optional[float] = None
+ tbeta: Optional[float] = None
+ teped: Optional[float] = None
+ tesep: Optional[float] = None
+ iprofile: Optional[int] = None
+ iradloss: Optional[int] = None
+ isc: Optional[int] = None
+ iscrp: Optional[int] = None
+ ishape: Optional[int] = None # listed as an output...
+ itart: Optional[int] = None # listed as an output...
+ itartpf: Optional[int] = None # listed as an output...
+ iwalld: Optional[int] = None
+ kappa: Optional[float] = None
+ kappa95: Optional[float] = None
+ m_s_limit: Optional[float] = None
+ ilhthresh: Optional[int] = None
+ q: Optional[float] = None
+ q0: Optional[float] = None
+ tauratio: Optional[float] = None
+ rad_fraction_sol: Optional[float] = None
+ ralpne: Optional[float] = None
+ rli: Optional[float] = None
+ rmajor: Optional[float] = None
+ rnbeam: Optional[float] = None
+ i_single_null: Optional[int] = None
+ ssync: Optional[float] = None
+ te: Optional[float] = None
+ ti: Optional[float] = None
+ tratio: Optional[float] = None
+ triang: Optional[float] = None
+ triang95: Optional[float] = None
+
+ # Stellarator
+ fblvd: Optional[float] = None
def __iter__(self) -> Generator[Tuple[str, Union[float, List, Dict]], None, None]:
"""
@@ -285,25 +725,30 @@ def to_invariable(self) -> Dict[str, _INVariable]:
"""
out_dict = {}
for name, value in self:
- if name not in ["icc", "ixc", "bounds"]:
+ if name not in ["icc", "ixc", "bounds"] and value is not None:
new_val = _INVariable(name, value, "Parameter", "", "")
out_dict[name] = new_val
out_dict["icc"] = _INVariable(
"icc",
- self.icc,
+ [] if self.icc is None else self.icc,
"Constraint Equation",
"Constraint Equation",
"Constraint Equations",
)
+ # PROCESS iteration variables need to be sorted to converge well(!)
out_dict["ixc"] = _INVariable(
"ixc",
- self.ixc,
+ [] if self.ixc is None else sorted(self.ixc),
"Iteration Variable",
"Iteration Variable",
"Iteration Variables",
)
out_dict["bounds"] = _INVariable(
- "bounds", self.bounds, "Bound", "Bound", "Bounds"
+ "bounds",
+ {} if self.bounds is None else self.bounds,
+ "Bound",
+ "Bound",
+ "Bounds",
)
return out_dict
diff --git a/bluemira/codes/process/_model_mapping.py b/bluemira/codes/process/_model_mapping.py
new file mode 100644
index 0000000000..c15d28eccd
--- /dev/null
+++ b/bluemira/codes/process/_model_mapping.py
@@ -0,0 +1,1151 @@
+# bluemira is an integrated inter-disciplinary design tool for future fusion
+# reactors. It incorporates several modules, some of which rely on other
+# codes, to carry out a range of typical conceptual fusion reactor design
+# activities.
+#
+# Copyright (C) 2021-2023 M. Coleman, J. Cook, F. Franza, I.A. Maione, S. McIntosh,
+# J. Morris, D. Short
+#
+# bluemira is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# bluemira is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with bluemira; if not, see .
+
+"""
+PROCESS model mappings
+"""
+from dataclasses import dataclass, field
+from typing import Tuple
+
+from bluemira.codes.utilities import Model
+
+
+class classproperty: # noqa: N801
+ """
+ Hacking for properties to work with Enums
+ """
+
+ def __init__(self, func):
+ self.func = func
+
+ def __get__(self, obj, owner):
+ """
+ Apply function to owner
+ """
+ return self.func(owner)
+
+
+@dataclass
+class ModelSelection:
+ """
+ Mixin dataclass for a Model selection in PROCESSModel
+
+ Parameters
+ ----------
+ _value_:
+ Integer value of the model selection
+ requires:
+ List of required inputs for the model selection
+ description:
+ Short description of the model selection
+ """
+
+ _value_: int
+ requires_values: Tuple[str] = field(default_factory=tuple)
+ description: str = ""
+
+
+class PROCESSModel(ModelSelection, Model):
+ """
+ Baseclass for PROCESS models
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ raise NotImplementedError(f"{self.__name__} has no 'switch_name' property.")
+
+
+class PROCESSOptimisationAlgorithm(PROCESSModel):
+ """
+ Switch for the optimisation algorithm to use in PROCESS
+
+ # TODO: This switch will be used in future to support
+ alternative optimisation algorithms.
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "ioptimz"
+
+ NO_OPTIMISATION = 0, (), "Do not use optimisation"
+ VMCON = 1, (), "The traditional VMCON optimisation algorithm"
+
+
+class PlasmaGeometryModel(PROCESSModel):
+ """
+ Switch for plasma geometry
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "ishape"
+
+ HENDER_K_D_100 = 0, ("kappa", "triang")
+ GALAMBOS_K_D_95 = 1, ("kappa95", "triang95")
+ ZOHM_ITER = 2, ("triang", "fkzohm")
+ ZOHM_ITER_D_95 = 3, ("triang95", "fkzohm")
+ HENDER_K_D_95 = 4, ("kappa95, triang95")
+ MAST_95 = 5, ("kappa95, triang95")
+ MAST_100 = 6, ("kappa, triang")
+ FIESTA_95 = 7, ("kappa95, triang95")
+ FIESTA_100 = 8, ("kappa, triang")
+ A_LI3 = 9, ("triang",)
+ CREATE_A_M_S = (
+ 10,
+ ("aspect", "m_s_limit", "triang"),
+ "A fit to CREATE data for conventional A tokamaks",
+ )
+ MENARD = 11, ("triang", "aspect")
+
+
+class PlasmaNullConfigurationModel(PROCESSModel):
+ """
+ Switch for single-null / double-null
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "i_single_null"
+
+ DOUBLE_NULL = 0, ("ftar",)
+ SINGLE_NULL = 1
+
+
+class PlasmaPedestalModel(PROCESSModel):
+ """
+ Switch for plasma profile model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "ipedestal"
+
+ NO_PEDESTAL = 0, ("te",)
+ PEDESTAL_GW = 1, (
+ "te",
+ "neped",
+ "nesep",
+ "rhopedn",
+ "rhopedt",
+ "tbeta",
+ "teped",
+ "tesep",
+ "ralpne",
+ )
+ PLASMOD_GW = 2, ("te", "neped", "nesep", "tbeta", "teped", "tesep", "ralpne")
+ PLASMOD = 3, ("te", "rhopedn", "rhopedt", "teped", "tesep")
+
+
+class PlasmaProfileModel(PROCESSModel):
+ """
+ Switch for current profile consistency
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "iprofile"
+
+ INPUT = 0, ("alphaj", "rli")
+ CONSISTENT = 1, ("q", "q0")
+
+
+class EPEDScalingModel(PROCESSModel):
+ """
+ Switch for the pedestal scaling model
+
+ TODO: This is largely undocumented and bound to some extent with PLASMOD
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "ieped"
+
+ UKNOWN_0 = 0, ("teped",)
+ SAARELMA = 1
+ UNKNOWN_1 = 2
+ UNKNOWN_2 = 3
+
+
+class BetaLimitModel(PROCESSModel):
+ """
+ Switch for the plasma beta limit model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "iculbl"
+
+ TOTAL = 0 # Including fast ion contribution
+ THERMAL = 1
+ THERMAL_NBI = 2
+ TOTAL_TF = 3 # Calculated using only the toroidal field
+
+
+class BetaGScalingModel(PROCESSModel):
+ """
+ Switch for the beta g coefficient dnbeta model
+
+ NOTE: Over-ridden if iprofile = 1
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "gtscale"
+
+ INPUT = 0, ("dnbeta",)
+ CONVENTIONAL = 1
+ MENARD_ST = 2
+
+
+class AlphaPressureModel(PROCESSModel):
+ """
+ Switch for the pressure contribution from fast alphas
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "ifalphap"
+
+ HENDER = 0
+ WARD = 1
+
+
+class DensityLimitModel(PROCESSModel):
+ """
+ Switch for the density limit model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "idensl"
+
+ ASDEX = 1
+ BORRASS_ITER_I = 2
+ BORRASS_ITER_II = 3
+ JET_RADIATION = 4
+ JET_SIMPLE = 5
+ HUGILL_MURAKAMI = 6
+ GREENWALD = 7
+
+
+class PlasmaCurrentScalingLaw(PROCESSModel):
+ """
+ Switch for plasma current scaling law
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "icurr"
+
+ PENG = 1
+ PENG_DN = 2
+ ITER_SIMPLE = 3
+ ITER_REVISED = 4 # Recommended for iprofile = 1
+ TODD_I = 5
+ TODD_II = 6
+ CONNOR_HASTIE = 7
+ SAUTER = 8
+ FIESTA = 9
+
+
+class ConfinementTimeScalingLaw(PROCESSModel):
+ """
+ Switch for the energy confinement time scaling law
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "isc"
+
+ NEO_ALCATOR_OHMIC = 1
+ MIRNOV_H_MODE = 2
+ MEREZHKIN_MUHKOVATOV_L_MODE = 3
+ SHIMOMURA_H_MODE = 4
+ KAYE_GOLDSTON_L_MODE = 5
+ ITER_89_P_L_MODE = 6
+ ITER_89_O_L_MODE = 7
+ REBUT_LALLIA_L_MODE = 8
+ GOLDSTON_L_MODE = 9
+ T10_L_MODE = 10
+ JAERI_88_L_MODE = 11
+ KAYE_BIG_COMPLEX_L_MODE = 12
+ ITER_H90_P_H_MODE = 13
+ ITER_MIX = 14 # Minimum of 6 and 7
+ RIEDEL_L_MODE = 15
+ CHRISTIANSEN_L_MODE = 16
+ LACKNER_GOTTARDI_L_MODE = 17
+ NEO_KAYE_L_MODE = 18
+ RIEDEL_H_MODE = 19
+ ITER_H90_P_H_MODE_AMENDED = 20
+ LHD_STELLARATOR = 21
+ GRYO_RED_BOHM_STELLARATOR = 22
+ LACKNER_GOTTARDI_STELLARATOR = 23
+ ITER_93H_H_MODE = 24
+ TITAN_RFP = 25
+ ITER_H97_P_NO_ELM_H_MODE = 26
+ ITER_H97_P_ELMY_H_MODE = 27
+ ITER_96P_L_MODE = 28
+ VALOVIC_ELMY_H_MODE = 29
+ KAYE_PPPL98_L_MODE = 30
+ ITERH_PB98P_H_MODE = 31
+ IPB98_Y_H_MODE = 32
+ IPB98_Y1_H_MODE = 33
+ IPB98_Y2_H_MODE = 34
+ IPB98_Y3_H_MODE = 35
+ IPB98_Y4_H_MODE = 36
+ ISS95_STELLARATOR = 37
+ ISS04_STELLARATOR = 38
+ DS03_H_MODE = 39
+ MURARI_H_MODE = 40
+ PETTY_H_MODE = 41
+ LANG_H_MODE = 42
+ HUBBARD_NOM_I_MODE = 43
+ HUBBARD_LOW_I_MODE = 44
+ HUBBARD_HI_I_MODE = 45
+ NSTX_H_MODE = 46
+ NSTX_PETTY_H_MODE = 47
+ NSTX_GB_H_MODE = 48
+ INPUT = 49, ("tauee_in",)
+
+
+class BootstrapCurrentScalingLaw(PROCESSModel):
+ """
+ Switch for the model to calculate bootstrap fraction
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "ibss"
+
+ ITER = 1, ("cboot",)
+ GENERAL = 2
+ NUMERICAL = 3
+ SAUTER = 4
+
+
+class LHThreshholdScalingLaw(PROCESSModel):
+ """
+ Switch for the model to calculate the L-H power threshhold
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "ilhthresh"
+
+ ITER_1996_NOM = 1
+ ITER_1996_LOW = 2
+ ITER_1996_HI = 3
+ ITER_1997 = 4
+ ITER_1997_K = 5
+ MARTIN_NOM = 6
+ MARTIN_HI = 7
+ MARTIN_LOW = 8
+ SNIPES_NOM = 9
+ SNIPES_HI = 10
+ SNIPES_LOW = 11
+ SNIPES_CLOSED_DIVERTOR_NOM = 12
+ SNIPES_CLOSED_DIVERTOR_HI = 13
+ SNIPES_CLOSED_DIVERTOR_LOW = 14
+ HUBBARD_LI_NOM = 15
+ HUBBARD_LI_HI = 16
+ HUBBARD_LI_LOW = 17
+ HUBBARD_2017_LI = 18
+ MARTIN_ACORRECT_NOM = 19
+ MARTIN_ACORRECT_HI = 20
+ MARTIN_ACORRECT_LOW = 21
+
+
+class RadiationLossModel(PROCESSModel):
+ """
+ Switch for radiation loss term usage in power balance
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "iradloss"
+
+ SCALING_PEDSETAL = 0 # ipedestal 2, 3
+ SCALING_CORE = 1
+ SCALING_ONLY = 2
+
+
+class PlasmaWallGapModel(PROCESSModel):
+ """
+ Switch to select plasma-wall gap model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "iscrp"
+
+ TEN_PERCENT = 0, (), "SOL thickness calculated as 10 percent of minor radius"
+ INPUT = 1, ("scrapli", "scraplo"), "Fixed thickness SOL values"
+
+
+class OperationModel(PROCESSModel):
+ """
+ Switch to set the operation mode
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "lpulse"
+
+ STEADY_STATE = 0
+ PULSED = 1
+
+
+class PowerFlowModel(PROCESSModel):
+ """
+ Switch to control power flow model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "ipowerflow"
+
+ SIMPLE = 0
+ STELLARATOR = 1
+
+
+class ThermalStorageModel(PROCESSModel):
+ """
+ Switch to et the power cycle thermal storage model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "istore"
+
+ INHERENT_STEAM = 1
+ BOILER = 2
+ STEEL = 3, ("dtstor",) # Obsolete
+
+
+class BlanketModel(PROCESSModel):
+ """
+ Switch to select the blanket model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "blktmodel"
+
+ CCFE_HCPB = 1
+ KIT_HCPB = 2
+ CCFE_HCPB_TBR = 3
+
+
+class InboardBlanketSwitch(PROCESSModel):
+ """
+ Switch to determin whether or not there is an inboard blanket
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "iblktith"
+
+ ABSENT = 0
+ PRESENT = 1
+
+
+class InVesselGeometryModel(PROCESSModel):
+ """
+ Switch to control the geometry of the FW, blanket, shield, and VV shape
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "fwbsshape"
+
+ CYL_ELLIPSE = 1
+ TWO_ELLIPSE = 2
+
+
+class TFCSTopologyModel(PROCESSModel):
+ """
+ Switch to select the TF-CS topology
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "tf_in_cs"
+
+ ITER = 0
+ INSANITY = 1
+
+
+class TFCoilConductorTechnology(PROCESSModel):
+ """
+ Switch for TF coil conductor model:
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "i_tf_sup"
+
+ COPPER = 0, ("tfootfi",)
+ SC = 1
+ CRYO_AL = 2
+
+
+class TFSuperconductorModel(PROCESSModel):
+ """
+ Switch for the TF superconductor model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "i_tf_sc_mat"
+
+ NB3SN_ITER_STD = 1
+ BI_2212 = 2
+ NBTI = 3
+ NB3SN_ITER_INPUT = 4 # User-defined critical parameters
+ NB3SN_WST = 5
+ REBCO_CROCO = 6
+ NBTI_DGL = 7
+ REBCO_DGL = 8
+ REBCO_ZHAI = 9
+
+
+class TFCasingGeometryModel(PROCESSModel):
+ """
+ Switch for the TF casing geometry model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "i_tf_case_geom"
+
+ CURVED = 0
+ FLAT = 1
+
+
+class TFWindingPackGeometryModel(PROCESSModel):
+ """
+ Switch for the TF winding pack geometry model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "i_tf_wp_geom"
+
+ RECTANGULAR = 0
+ DOUBLE_RECTANGULAR = 1
+ TRAPEZOIDAL = 2
+
+
+class TFWindingPackTurnModel(PROCESSModel):
+ """
+ Switch for the TF winding pack turn model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "i_tf_turns_integer"
+
+ CURRENT_PER_TURN = 0, ("cpttf",) # or t_cable_tf or t_turn_tf
+ INTEGER_TURN = 1, ("n_layer", "n_pancake")
+
+
+class TFCoilShapeModel(PROCESSModel):
+ """
+ Switch for the TF coil shape model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "i_tf_shape"
+
+ PRINCETON = 1
+ PICTURE_FRAME = 2
+
+
+class ResistiveCentrepostModel(PROCESSModel):
+ """
+ Swtich for the resistive centrepost model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "i_r_cp_top"
+
+ CALCULATED = 0
+ INPUT = 1
+ MID_TOP_RATIO = 2
+
+
+class TFCoilJointsModel(PROCESSModel):
+ """
+ Switch for the TF coil joints
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "i_cp_joints"
+
+ SC_CLAMP_RES_SLIDE = (
+ -1,
+ (),
+ "Chooses clamped joints for SC magnets (i_tf_sup=1)"
+ " and sliding joints for resistive magnets (i_tf_sup=0,2)",
+ )
+ NO_JOINTS = 0
+ SLIDING_JOINTS = 1, (
+ "tho_tf_joints",
+ "n_tf_joints_contact",
+ "n_tf_joints",
+ "th_joint_contact",
+ )
+
+
+class TFStressModel(PROCESSModel):
+ """
+ Switch for the TF inboard midplane stress model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "i_tf_stress_model"
+
+ GEN_PLANE_STRAIN = 0
+ PLANE_STRESS = 1
+ GEN_PLANE_STRAIN_NEW = 2
+
+
+class TFCoilSupportModel(PROCESSModel):
+ """
+ Switch for the TF inboard coil support model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "i_tf_bucking"
+
+ NO_SUPPORT = 0
+ BUCKED = 1
+ BUCKED_WEDGED = 2
+
+
+class PFConductorModel(PROCESSModel):
+ """
+ Switch for the PF conductor technology model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "ipfres"
+
+ SUPERCONDUCTING = 0
+ RESISTIVE = 1
+
+
+class PFSuperconductorModel(PROCESSModel):
+ """
+ Switch for the PF superconductor model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "isumatpf"
+
+ NB3SN_ITER_STD = 1
+ BI_2212 = 2, ("fhts",)
+ NBTI = 3
+ NB3SN_ITER_INPUT = 4 # User-defined critical parameters
+ NB3SN_WST = 5
+ REBCO_CROCO = 6
+ NBTI_DGL = 7
+ REBCO_DGL = 8
+ REBCO_ZHAI = 9
+
+
+class PFCurrentControlModel(PROCESSModel):
+ """
+ Switch to control the currents in the PF coils
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "i_pf_current"
+
+ INPUT = 0, ("curpfb", "curpff", "curpfs")
+ SVD = 1
+
+
+class SolenoidSwitchModel(PROCESSModel):
+ """
+ Switch to control whether or not a central solenoid should be
+ used.
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "iohcl"
+
+ NO_SOLENOID = 0
+ SOLENOID = 1
+
+
+class CSSuperconductorModel(PROCESSModel):
+ """
+ Switch for the CS superconductor model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "isumatoh"
+
+ NB3SN_ITER_STD = 1
+ BI_2212 = 2
+ NBTI = 3
+ NB3SN_ITER_INPUT = 4 # User-defined critical parameters
+ NB3SN_WST = 5
+ REBCO_CROCO = 6
+ NBTI_DGL = 7
+ REBCO_DGL = 8
+ REBCO_ZHAI = 9
+
+
+class CSPrecompressionModel(PROCESSModel):
+ """
+ Switch to control the existence of pre-compression tie plates in the CS
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "iprecomp"
+
+ ABSENT = 0
+ PRESENT = 1
+
+
+class CSStressModel(PROCESSModel):
+ """
+ Switch for the calculation of the CS stress
+
+ # TODO: Listed as an output?!
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "i_cs_stress"
+
+ HOOP_ONLY = 0
+ HOOP_AXIAL = 1
+
+
+class DivertorHeatFluxModel(PROCESSModel):
+ """
+ Switch for the divertor heat flux model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "i_hldiv"
+
+ # TODO: What about Kallenbach?
+ INPUT = 0
+ CHAMBER = 1
+ WADE = 2
+
+
+class DivertorThermalHeatUse(PROCESSModel):
+ """
+ Switch to control if the divertor thermal power is used in the
+ power cycle
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "iprimdiv"
+
+ LOW_GRADE_HEAT = 0
+ HIGH_GRADE_HEAT = 1
+
+
+class ShieldThermalHeatUse(PROCESSModel):
+ """
+ Switch to control if shield (inside VV) is used in the power cycle
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "iprimshld"
+
+ NOT_USED = 0
+ LOW_GRADE_HEAT = 1
+
+
+class TFNuclearHeatingModel(PROCESSModel):
+ """
+ Switch to control nuclear heating in TF model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "inuclear"
+
+ FRANCES_FOX = 0
+ INPUT = 1, ("qnuc",)
+
+
+class PrimaryPumpingModel(PROCESSModel):
+ """
+ Switch for the calculation method of the pumping power
+ required for the primary coolant
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "primary_pumping"
+
+ INPUT = 0
+ FRACTION = 1
+ PRESSURE_DROP = 2
+ PRESSURE_DROP_INPUT = 3
+
+
+class SecondaryCycleModel(PROCESSModel):
+ """
+ Switch for the calculation of thermal to electric conversion efficiency
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "secondary_cycle"
+
+ FIXED = 0
+ FIXED_W_DIVERTOR = 1
+ INPUT = 2
+ RANKINE = 3
+ BRAYTON = 4
+
+
+class CurrentDriveEfficiencyModel(PROCESSModel):
+ """
+ Switch for current drive efficiency model:
+
+ 1 - Fenstermacher Lower Hybrid
+ 2 - Ion Cyclotron current drive
+ 3 - Fenstermacher ECH
+ 4 - Ehst Lower Hybrid
+ 5 - ITER Neutral Beam
+ 6 - new Culham Lower Hybrid model
+ 7 - new Culham ECCD model
+ 8 - new Culham Neutral Beam model
+ 10 - ECRH user input gamma
+ 11 - ECRH "HARE" model (E. Poli, Physics of Plasmas 2019)
+ 12 - EBW user scaling input. Scaling (S. Freethy)
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "iefrf"
+
+ FENSTER_LH = 1
+ ICYCCD = 2
+ FENSTER_ECH = 3
+ EHST_LH = 4
+ ITER_NB = 5
+ CUL_LH = 6
+ CUL_ECCD = 7
+ CUL_NB = 8
+ ECRH_UI_GAM = 10
+ ECRH_HARE = 11
+ EBW_UI = 12
+
+
+class PlasmaIgnitionModel(PROCESSModel):
+ """
+ Switch to control whether or not the plasma is ignited
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "ignite"
+
+ NOT_IGNITED = 0
+ IGNITED = 1
+
+
+class VacuumPumpingModel(PROCESSModel):
+ """
+ Switch to control the vacuum pumping technology model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "ntype"
+
+ TURBO_PUMP = 0
+ CRYO_PUMP = 1
+
+
+class VacuumPumpingDwellModel(PROCESSModel):
+ """
+ Switch to control when vacuum pumping occurs
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "dwell_pump"
+
+ T_DWELL = 0
+ T_RAMP = 1
+ T_DWELL_RAMP = 2
+
+
+class FISPACTSwitchModel(PROCESSModel):
+ """
+ Switch to control FISPACT-II neutronics calculations
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "ifispact"
+
+ OFF = 0
+ ON = 1 # Presumably...
+
+
+class AvailabilityModel(PROCESSModel):
+ """
+ Switch to control the availability model
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "iavail"
+
+ INPUT = 0
+ TAYLOR_WARD = 1
+ MORRIS = 2
+
+
+class SafetyAssuranceLevel(PROCESSModel):
+ """
+ Switch to control the level of safety assurance
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "lsa"
+
+ TRULY_SAFE = 1
+ VERY_SAFE = 2 # In-between
+ SOMEWHAT_SAFE = 3 # In-between
+ FISSION = 4 # Not sure what this is implying...
+
+
+class CostModel(PROCESSModel):
+ """
+ Switch to control the cost model used
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "cost_model"
+
+ TETRA_1990 = 0
+ KOVARI_2015 = 1
+
+
+class OutputCostsSwitch(PROCESSModel):
+ """
+ Switch to control whether or not cost information is output
+ """
+
+ @classproperty
+ def switch_name(self) -> str:
+ """
+ PROCESS switch name
+ """
+ return "output_costs"
+
+ NO = 0, (), "Do not print cost information to output"
+ YES = 1, (), "Print cost information to output"
diff --git a/bluemira/codes/process/_plotting.py b/bluemira/codes/process/_plotting.py
index cb25c55ac8..4b77b49769 100644
--- a/bluemira/codes/process/_plotting.py
+++ b/bluemira/codes/process/_plotting.py
@@ -210,7 +210,11 @@ def read_radial_build(num): # Be careful that the numbers don't change
rb = []
num += 1
while "***" not in raw[num]:
- if read_rb_line(raw[num]) is None:
+ if "TF coil radial placement switch" in raw[num]:
+ # PROCESS v3.0.0 added this line to the start of the RB
+ # TF coil radial placement switch ... (tf_in_cs) .... 0
+ pass
+ elif read_rb_line(raw[num]) is None:
pass
else:
rb.append(read_rb_line(raw[num]))
diff --git a/bluemira/codes/process/_run.py b/bluemira/codes/process/_run.py
index c79e59aaa0..bd026fe8ca 100644
--- a/bluemira/codes/process/_run.py
+++ b/bluemira/codes/process/_run.py
@@ -77,7 +77,17 @@ def runinput(self):
"""
self._run_process()
+ @staticmethod
+ def flush_callable(line: str) -> bool:
+ """Callable for flushed output"""
+ try:
+ int(line.split("|")[0])
+ except ValueError:
+ return False
+ else:
+ return True
+
def _run_process(self):
bluemira_print(f"Running '{PROCESS_NAME}' systems code")
command = [self.binary, "-i", self.in_dat_path]
- self._run_subprocess(command)
+ self._run_subprocess(command, flush_callable=self.flush_callable)
diff --git a/bluemira/codes/process/_setup.py b/bluemira/codes/process/_setup.py
index 00e27da360..39abec2f6a 100644
--- a/bluemira/codes/process/_setup.py
+++ b/bluemira/codes/process/_setup.py
@@ -24,16 +24,16 @@
from pathlib import Path
from typing import ClassVar, Dict, Optional, Union
+from bluemira.base.parameter_frame import ParameterFrame
from bluemira.codes.error import CodesError
from bluemira.codes.interface import CodesSetup
from bluemira.codes.process._inputs import ProcessInputs
-from bluemira.codes.process.api import InDat, _INVariable, update_obsolete_vars
-from bluemira.codes.process.constants import NAME as PROCESS_NAME
-from bluemira.codes.process.mapping import (
+from bluemira.codes.process._model_mapping import (
CurrentDriveEfficiencyModel,
TFCoilConductorTechnology,
)
-from bluemira.codes.process.params import ProcessSolverParams
+from bluemira.codes.process.api import ENABLED, InDat, _INVariable, update_obsolete_vars
+from bluemira.codes.process.constants import NAME as PROCESS_NAME
class Setup(CodesSetup):
@@ -46,9 +46,6 @@ class Setup(CodesSetup):
The bluemira parameters for this task.
in_dat_path:
The path to where the IN.DAT file should be written.
- template_in_dat_path:
- The path to a template PROCESS IN.DAT file. By default this
- points to a sample one within the Bluemira repository.
problem_settings:
The PROCESS parameters that do not exist in Bluemira.
"""
@@ -60,17 +57,13 @@ class Setup(CodesSetup):
def __init__(
self,
- params: ProcessSolverParams,
+ params: Union[Dict, ParameterFrame],
in_dat_path: str,
- template_in_dat: Union[str, ProcessInputs] = None,
problem_settings: Optional[Dict[str, Union[float, str]]] = None,
):
super().__init__(params, PROCESS_NAME)
self.in_dat_path = in_dat_path
- self.template_in_dat = (
- self.params.template_defaults if template_in_dat is None else template_in_dat
- )
self.problem_settings = problem_settings if problem_settings is not None else {}
def run(self):
@@ -102,14 +95,16 @@ def _write_in_dat(self, use_bp_inputs: bool = True):
Default, True
"""
# Load defaults in bluemira folder
- writer = _make_writer(self.template_in_dat)
+ writer = _make_writer(self.params.template_defaults)
if use_bp_inputs:
inputs = self._get_new_inputs(remapper=update_obsolete_vars)
for key, value in inputs.items():
- writer.add_parameter(key, value)
+ if value is not None:
+ writer.add_parameter(key, value)
for key, value in self.problem_settings.items():
- writer.add_parameter(key, value)
+ if value is not None:
+ writer.add_parameter(key, value)
self._validate_models(writer)
@@ -130,12 +125,23 @@ def _validate_models(self, writer):
writer.add_parameter(name, model.value)
-def _make_writer(template_in_dat: Union[str, Dict[str, _INVariable]]) -> InDat:
- if isinstance(template_in_dat, Dict):
- indat = InDat(filename=None)
- indat.data = template_in_dat
- return indat
- if isinstance(template_in_dat, str) and Path(template_in_dat).is_file():
+def _make_writer(template_in_dat: Dict[str, _INVariable]) -> InDat:
+ indat = InDat(filename=None)
+ indat.data = template_in_dat
+ return indat
+
+
+def create_template_from_path(template_in_dat: Union[str, Path]) -> ProcessInputs:
+ if not ENABLED:
+ raise CodesError(
+ f"{PROCESS_NAME} is not installed cannot read template {template_in_dat}"
+ )
+ if Path(template_in_dat).is_file():
# InDat autoloads IN.DAT without checking for existence
- return InDat(filename=template_in_dat)
+ return ProcessInputs(
+ **{
+ k: v.value if k == "runtitle" else v.get_value
+ for k, v in InDat(filename=template_in_dat).data.items()
+ }
+ )
raise CodesError(f"Template IN.DAT '{template_in_dat}' is not a file.")
diff --git a/bluemira/codes/process/_solver.py b/bluemira/codes/process/_solver.py
index 514656f755..e58f664b3c 100644
--- a/bluemira/codes/process/_solver.py
+++ b/bluemira/codes/process/_solver.py
@@ -22,16 +22,20 @@
import copy
from enum import auto
from pathlib import Path
-from typing import Dict, List, Mapping, Tuple, Union
+from typing import Dict, List, Mapping, Tuple, Type, Union
import numpy as np
from bluemira.base.look_and_feel import bluemira_warn
from bluemira.base.parameter_frame import ParameterFrame
from bluemira.codes.error import CodesError
-from bluemira.codes.interface import BaseRunMode, CodesSolver
+from bluemira.codes.interface import (
+ BaseRunMode,
+ CodesSolver,
+)
+from bluemira.codes.process._inputs import ProcessInputs
from bluemira.codes.process._run import Run
-from bluemira.codes.process._setup import Setup
+from bluemira.codes.process._setup import Setup, create_template_from_path
from bluemira.codes.process._teardown import Teardown
from bluemira.codes.process.api import Impurities
from bluemira.codes.process.constants import BINARY as PROCESS_BINARY
@@ -76,9 +80,21 @@ class Solver(CodesSolver):
The directory in which to run PROCESS. It is also the
directory in which to look for PROCESS input and output
files. Default is current working directory.
+ * read_dir:
+ The directory from which data is read when running in read mode.
+ * template_in_dat_path:
+ The path to a template PROCESS IN.DAT file or and instances of
+ :class:`bluemira.codes.process._inputs.ProcessInputs`.
+ By default this is an empty instance of the class. To create a new
+ instance
+ :class:`bluemira.codes.process.template_builder.PROCESSTemplateBuilder`
+ should be used.
* problem_settings:
Any PROCESS parameters that do not correspond to a bluemira
parameter.
+ * in_dat_path:
+ The path to save the IN.DAT file that is run by PROCESS.
+ By default this is '/IN.DAT'.
Notes
-----
@@ -106,11 +122,11 @@ class Solver(CodesSolver):
overwriting data with PROCESS outputs would be undesirable.
"""
- name = PROCESS_NAME
- setup_cls = Setup
- run_cls = Run
- teardown_cls = Teardown
- run_mode_cls = RunMode
+ name: str = PROCESS_NAME
+ setup_cls: Type[Setup] = Setup
+ run_cls: Type[Run] = Run
+ teardown_cls: Type[Teardown] = Teardown
+ run_mode_cls: Type[RunMode] = RunMode
def __init__(
self,
@@ -123,27 +139,22 @@ def __init__(
self._run: Union[Run, None] = None
self._teardown: Union[Teardown, None] = None
- self.params = ProcessSolverParams.from_defaults()
-
- if isinstance(params, ParameterFrame):
- self.params.update_from_frame(params)
- else:
- try:
- self.params.update_from_dict(params)
- except TypeError:
- self.params.update_values(params)
-
_build_config = copy.deepcopy(build_config)
self.binary = _build_config.pop("binary", PROCESS_BINARY)
self.run_directory = _build_config.pop("run_dir", Path.cwd().as_posix())
self.read_directory = _build_config.pop("read_dir", Path.cwd().as_posix())
- self.template_in_dat = _build_config.pop(
- "template_in_dat", self.params.template_defaults
- )
+ self.template_in_dat = _build_config.pop("template_in_dat", ProcessInputs())
self.problem_settings = _build_config.pop("problem_settings", {})
self.in_dat_path = _build_config.pop(
"in_dat_path", Path(self.run_directory, "IN.DAT").as_posix()
)
+
+ if isinstance(self.template_in_dat, (str, Path)):
+ self.template_in_dat = create_template_from_path(self.template_in_dat)
+
+ self.params = ProcessSolverParams.from_defaults(self.template_in_dat)
+ self.params.update(params)
+
if len(_build_config) > 0:
quoted_delim = "', '"
bluemira_warn(
@@ -164,14 +175,15 @@ def execute(self, run_mode: Union[str, RunMode]) -> ParameterFrame:
"""
if isinstance(run_mode, str):
run_mode = self.run_mode_cls.from_string(run_mode)
- self._setup = Setup(
+ self._setup = self.setup_cls(
self.params,
self.in_dat_path,
- self.template_in_dat,
self.problem_settings,
)
- self._run = Run(self.params, self.in_dat_path, self.binary)
- self._teardown = Teardown(self.params, self.run_directory, self.read_directory)
+ self._run = self.run_cls(self.params, self.in_dat_path, self.binary)
+ self._teardown = self.teardown_cls(
+ self.params, self.run_directory, self.read_directory
+ )
if setup := self._get_execution_method(self._setup, run_mode):
setup()
@@ -205,7 +217,9 @@ def get_raw_variables(self, params: Union[List, str]) -> List[float]:
)
@staticmethod
- def get_species_data(impurity: str) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
+ def get_species_data(
+ impurity: str, confinement_time_ms: float
+ ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
"""
Get species data from PROCESS section of OPEN-ADAS database.
@@ -217,18 +231,27 @@ def get_species_data(impurity: str) -> Tuple[np.ndarray, np.ndarray, np.ndarray]
The impurity to get the species data for. This string should
be one of the names in the
:class:`~bluemira.codes.process.api.Impurities` Enum.
+ confinement_time_ms:
+ the confinement time to read the data for options are:
+ [0.1, 1.0, 10.0, 100.0, 1000.0, np.inf]
Returns
-------
tref:
- The temperature in keV.
+ The temperature in eV.
l_ref:
The loss function value $L_z(n_e, T_e)$ in W.m3.
z_ref:
Average effective charge.
"""
- t_ref, lz_ref, z_av_ref = np.genfromtxt(Impurities[impurity].file()).T
- return t_ref, lz_ref, z_av_ref
+ lz_ref, z_ref = Impurities[impurity].read_impurity_files(("lz", "z"))
+
+ t_ref = filter(lambda lz: lz.content == "Te[eV]", lz_ref)
+ lz_ref = filter(lambda lz: f"{confinement_time_ms:.1f}" in lz.content, lz_ref)
+ z_av_ref = filter(lambda z: f"{confinement_time_ms:.1f}" in z.content, z_ref)
+ return tuple(
+ np.array(next(ref).data, dtype=float) for ref in (t_ref, lz_ref, z_av_ref)
+ )
def get_species_fraction(self, impurity: str) -> float:
"""
diff --git a/bluemira/codes/process/_teardown.py b/bluemira/codes/process/_teardown.py
index d80b57f38f..467812c845 100644
--- a/bluemira/codes/process/_teardown.py
+++ b/bluemira/codes/process/_teardown.py
@@ -232,7 +232,7 @@ def _derive_radial_build_params(self, data: Dict) -> Dict[str, float]:
except KeyError:
# PROCESS updated their parameter names in v2.4.0, splitting
# 'thshield' into 'thshield_ib', 'thshield_ob', and 'thshield_vb'
- shield_th = data["thshield_ib"] + data["thshield_ib"]
+ shield_th = data["thshield_ib"]
try:
rtfin = data["bore"] + data["ohcth"] + data["precomp"] + data["gapoh"]
diff --git a/bluemira/codes/process/api.py b/bluemira/codes/process/api.py
index bf6d8d4fcd..ef6a11c522 100644
--- a/bluemira/codes/process/api.py
+++ b/bluemira/codes/process/api.py
@@ -22,10 +22,13 @@
"""
PROCESS api
"""
+from __future__ import annotations
+
from dataclasses import dataclass
from enum import Enum
+from importlib import resources
from pathlib import Path
-from typing import Dict, List, TypeVar, Union
+from typing import Dict, Iterable, List, Literal, Tuple, TypeVar, Union
from bluemira.base.look_and_feel import bluemira_print, bluemira_warn
from bluemira.codes.error import CodesError
@@ -57,7 +60,7 @@ def __init__(self, filename):
imp_data = None # placeholder for PROCESS module
try:
- import process.data.impuritydata as imp_data
+ from process.impurity_radiation import ImpurityDataHeader, read_impurity_file
from process.io.in_dat import InDat # noqa: F401, F811
from process.io.mfile import MFile # noqa: F401, F811
from process.io.python_fortran_dicts import get_dicts
@@ -141,12 +144,20 @@ class Impurities(Enum):
Xe = 13
W = 14
- def file(self):
+ def files(self) -> Dict[str, Path]:
"""
Get PROCESS impurity data file path
"""
+ with resources.path(
+ "process.data.lz_non_corona_14_elements", "Ar_lz_tau.dat"
+ ) as dp:
+ data_path = dp.parent
+
try:
- return Path(Path(imp_data.__file__).parent, f"{self.name:_<2}Lzdata.dat")
+ return {
+ i: Path(data_path, f"{self.name:_<3}{i}_tau.dat")
+ for i in ("lz", "z", "z2")
+ }
except NameError:
raise CodesError("PROCESS impurity data directory not found") from None
@@ -154,7 +165,16 @@ def id(self): # noqa: A003
"""
Get variable string for impurity fraction
"""
- return f"fimp({self.value:02}"
+ return f"fimp({self.value:02})"
+
+ def read_impurity_files(
+ self, filetype: Iterable[Literal["lz", "z2", "z"]]
+ ) -> Tuple[list[ImpurityDataHeader]]:
+ """Get contents of impurity data files"""
+ files = self.files()
+ return tuple(
+ read_impurity_file(files[file]) for file in set(filetype).intersection(files)
+ )
def update_obsolete_vars(process_map_name: str) -> Union[str, List[str], None]:
diff --git a/bluemira/codes/process/mapping.py b/bluemira/codes/process/mapping.py
index 5de53a5319..c9a513c161 100644
--- a/bluemira/codes/process/mapping.py
+++ b/bluemira/codes/process/mapping.py
@@ -22,59 +22,9 @@
"""
PROCESS mappings
"""
-from bluemira.codes.utilities import Model, create_mapping
-
-
-class CurrentDriveEfficiencyModel(Model):
- """
- Switch for current drive efficiency model:
-
- 1 - Fenstermacher Lower Hybrid
- 2 - Ion Cyclotron current drive
- 3 - Fenstermacher ECH
- 4 - Ehst Lower Hybrid
- 5 - ITER Neutral Beam
- 6 - new Culham Lower Hybrid model
- 7 - new Culham ECCD model
- 8 - new Culham Neutral Beam model
- 10 - ECRH user input gamma
- 11 - ECRH "HARE" model (E. Poli, Physics of Plasmas 2019)
- 12 - EBW user scaling input. Scaling (S. Freethy)
-
- PROCESS variable name: "iefrf"
- """
-
- FENSTER_LH = 1
- ICYCCD = 2
- FENSTER_ECH = 3
- EHST_LH = 4
- ITER_NB = 5
- CUL_LH = 6
- CUL_ECCD = 7
- CUL_NB = 8
- ECRH_UI_GAM = 10
- ECRH_HARE = 11
- EBW_UI = 12
-
-
-class TFCoilConductorTechnology(Model):
- """
- Switch for TF coil conductor model:
-
- 0 - copper
- 1 - superconductor
- 2 - Cryogenic aluminium
-
- PROCESS variable name: "i_tf_sup"
- """
-
- COPPER = 0
- SC = 1
- CRYO_AL = 2
-
+from bluemira.codes.utilities import create_mapping
IN_mappings = {
- "P_el_net": ("pnetelin", "MW"),
"n_TF": ("n_tf", "dimensionless"),
"TF_ripple_limit": ("ripmax", "%"),
"C_Ejima": ("gamma", "dimensionless"),
@@ -82,20 +32,26 @@ class TFCoilConductorTechnology(Model):
"P_hcd_ss": ("pinjalw", "MW"),
"eta_nb": ("etanbi", "dimensionless"),
"e_mult": ("emult", "dimensionless"),
- "tk_sh_out": ("shldoth", "m"),
- "tk_sh_top": ("shldtth", "m"),
- "tk_sh_bot": ("shldlth", "m"),
- "tk_vv_out": ("d_vv_out", "m"),
- "tk_vv_top": ("d_vv_top", "m"),
- "tk_vv_bot": ("d_vv_bot", "m"),
"tk_cr_vv": ("ddwex", "m"),
- "tk_tf_front_ib": ("dr_tf_case_out", "m"),
+ "tk_tf_front_ib": ("casthi", "m"),
"tk_tf_side": ("casths", "m"),
"PsepB_qAR_max": ("psepbqarmax", "MW.T/m"),
+ "q_0": ("q0", "dimensionless"),
+ "m_s_limit": ("m_s_limit", "dimensionless"),
+ "delta": ("triang", "dimensionless"),
+ "sigma_tf_case_max": ("sig_tf_case_max", "Pa"),
+ "sigma_tf_wp_max": ("sig_tf_wp_max", "Pa"),
+ "sigma_cs_wp_max": ("alstroh", "Pa"),
+ "H_star": ("hfact", "dimensionless"),
+ "bb_pump_eta_el": ("etahtp", "dimensionless"),
+ "bb_pump_eta_isen": ("etaiso", "dimensionless"),
+ "bb_t_inlet": ("inlet_temp", "K"),
+ "bb_t_outlet": ("outlet_temp", "K"),
+ "eta_ecrh": ("etaech", "dimensionless"),
+ "gamma_ecrh": ("gamma_ecrh", "1e20 A/W/m^2"),
}
OUT_mappings = {
- "P_el_net_process": ("pnetelmw", "MW"),
"R_0": ("rmajor", "m"),
"B_0": ("bt", "T"),
"kappa_95": ("kappa95", "dimensionless"),
@@ -108,8 +64,8 @@ class TFCoilConductorTechnology(Model):
"P_fus_DD": ("pdd", "MW"),
"H_star": ("hfact", "dimensionless"),
"P_sep": ("pdivt", "MW"),
- "P_rad_core": ("pcoreradmw", "MW"),
- "P_rad_edge": ("pedgeradmw", "MW"),
+ "P_rad_core": ("pinnerzoneradmw", "MW"),
+ "P_rad_edge": ("pouterzoneradmw", "MW"),
"P_rad": ("pradmw", "MW"),
"P_line": ("plinepv*vol", "MW"),
"P_sync": ("psyncpv*vol", "MW"),
@@ -122,7 +78,7 @@ class TFCoilConductorTechnology(Model):
"tk_fw_in": ("fwith", "m"),
"tk_fw_out": ("fwoth", "m"),
"tk_tf_inboard": ("tfcth", "m"),
- "tk_tf_nose": ("dr_tf_case_in", "m"),
+ "tk_tf_nose": ("thkcas", "m"),
"tf_wp_width": ("dr_tf_wp", "m"),
"tf_wp_depth": ("wwp1", "m"),
"tk_tf_ins": ("tinstf", "m"),
@@ -145,38 +101,52 @@ class TFCoilConductorTechnology(Model):
"TF_respc_ob": ("tflegres", "ohm"),
"TF_currpt_ob": ("cpttf", "A"),
"P_bd_in": ("pinjmw", "MW"),
- "condrad_cryo_heat": ("qss/1.0D6", "MW"),
+ "condrad_cryo_heat": ("qss/1.0d6", "MW"),
}
IO_mappings = {
"A": ("aspect", "dimensionless"),
+ "tau_flattop": (("tbrnmn", "tburn"), "s"),
+ "P_el_net": (("pnetelin", "pnetelmw"), "MW"),
"tk_bb_ib": ("blnkith", "m"),
"tk_bb_ob": ("blnkoth", "m"),
- "tk_sh_in": ("shldith", "m"),
"tk_vv_in": ("d_vv_in", "m"),
"tk_sol_ib": ("scrapli", "m"),
"tk_sol_ob": ("scraplo", "m"),
- "tk_ts": ("thshield", "m"),
"g_cs_tf": ("gapoh", "m"),
"g_ts_tf": ("tftsgap", "m"),
"g_vv_bb": ("vvblgap", "m"),
}
NONE_mappings = {
- "tau_flattop": ("tburn", "s"),
"B_tf_peak": ("bmaxtfrp", "T"),
- "q_95": ("q95", "dimensionless"),
"T_e": ("te", "keV"),
"Z_eff": ("zeff", "amu"),
"V_p": ("vol", "m^3"),
"l_i": ("rli", "dimensionless"),
"f_ni": ("faccd", "dimensionless"),
"tk_tf_outboard": ("tfthko", "m"),
- "sigma_tf_case_max": ("sig_tf_case_max", "Pa"),
- "sigma_tf_wp_max": ("sig_tf_wp_max", "Pa"),
"h_cp_top": ("h_cp_top", "m"),
"h_tf_max_in": ("hmax", "m"),
"r_tf_inboard_out": ("r_tf_inboard_out", "m"),
+ # The following mappings are not 1:1
+ "tk_sh_in": ("shldith", "m"),
+ "tk_sh_out": ("shldoth", "m"),
+ "tk_sh_top": ("shldtth", "m"),
+ "tk_sh_bot": ("shldlth", "m"),
+ "tk_vv_out": ("d_vv_out", "m"),
+ "tk_vv_top": ("d_vv_top", "m"),
+ "tk_vv_bot": ("d_vv_bot", "m"),
+ # Thermal shield thickness is a constant for us
+ "tk_ts": ("thshield_ib", "m"),
+ # "tk_ts": ("thshield_ob", "m"),
+ # "tk_ts": ("thshield_vb", "m"),
+ # TODO: q is not properly put in the MFILE output
+ # This should be ok OK most of the time as q_95 is input and then
+ # used as the lower bound of the q iteration variable, but this
+ # should be fixed as soon as PROCESS deal with this issue on
+ # their side
+ "q_95": ("q", "dimensionless"),
}
mappings = create_mapping(IN_mappings, OUT_mappings, IO_mappings, NONE_mappings)
diff --git a/bluemira/codes/process/params.py b/bluemira/codes/process/params.py
index a6054448fc..0fc56afe78 100644
--- a/bluemira/codes/process/params.py
+++ b/bluemira/codes/process/params.py
@@ -22,17 +22,21 @@
"""
PROCESS's parameter definitions.
"""
+from __future__ import annotations
from copy import deepcopy
from dataclasses import dataclass
-from typing import ClassVar, Dict, List, Union
+from typing import TYPE_CHECKING, Dict, List, Optional, Union
-from bluemira.base.parameter_frame import Parameter
+from bluemira.base.parameter_frame import Parameter # noqa: TCH001
from bluemira.codes.params import MappedParameterFrame, ParameterMapping
from bluemira.codes.process._inputs import ProcessInputs
-from bluemira.codes.process.api import _INVariable
+from bluemira.codes.process.constants import NAME
from bluemira.codes.process.mapping import mappings
+if TYPE_CHECKING:
+ from bluemira.codes.process.api import _INVariable
+
@dataclass
class ProcessSolverParams(MappedParameterFrame):
@@ -60,6 +64,9 @@ class ProcessSolverParams(MappedParameterFrame):
P_el_net: Parameter[float]
"""Net electrical power output [megawatt]."""
+ tau_flattop: Parameter[float]
+ """Flat-top duration [second]."""
+
P_hcd_ss: Parameter[float]
"""Steady-state HCD power [megawatt]."""
@@ -96,6 +103,48 @@ class ProcessSolverParams(MappedParameterFrame):
PsepB_qAR_max: Parameter[float]
"""Maximum PsepB/q95AR vale [MW.T/m]"""
+ q_0: Parameter[float]
+ """Plasma safety factor on axis [dimensionless]"""
+
+ q_95: Parameter[float]
+ """Plasma safety factor at the 95th percentile flux surface [dimensionless]"""
+
+ m_s_limit: Parameter[float]
+ """Margin to vertical stability [dimensionless]"""
+
+ delta: Parameter[float]
+ """Triangularity [dimensionless]"""
+
+ sigma_tf_case_max: Parameter[float]
+ """Maximum von Mises stress in the TF coil case nose [pascal]."""
+
+ sigma_tf_wp_max: Parameter[float]
+ """Maximum von Mises stress in the TF coil winding pack [pascal]."""
+
+ sigma_cs_wp_max: Parameter[float]
+ """Maximum von Mises stress in the CS coil winding pack [pascal]."""
+
+ H_star: Parameter[float]
+ """H factor (radiation corrected) [dimensionless]."""
+
+ bb_pump_eta_el: Parameter[float]
+ """Breeding blanket pumping electrical efficiency [dimensionless]"""
+
+ bb_pump_eta_isen: Parameter[float]
+ """Breeding blanket pumping isentropic efficiency [dimensionless]"""
+
+ bb_t_inlet: Parameter[float]
+ """Breeding blanket inlet temperature [K]"""
+
+ bb_t_outlet: Parameter[float]
+ """Breeding blanket outlet temperature [K]"""
+
+ eta_ecrh: Parameter[float]
+ """Electron cyclotron resonce heating wallplug efficiency [dimensionless]"""
+
+ gamma_ecrh: Parameter[float]
+ """Electron cyclotron resonce heating current drive efficiency [TODO: UNITS!]"""
+
# Out parameters
B_0: Parameter[float]
"""Toroidal field at R_0 [tesla]."""
@@ -112,7 +161,6 @@ class ProcessSolverParams(MappedParameterFrame):
delta_95: Parameter[float]
"""95th percentile plasma triangularity [dimensionless]."""
- delta: Parameter[float]
"""Last closed surface plasma triangularity [dimensionless]."""
f_bs: Parameter[float]
@@ -121,9 +169,6 @@ class ProcessSolverParams(MappedParameterFrame):
g_vv_ts: Parameter[float]
"""Gap between VV and TS [meter]."""
- H_star: Parameter[float]
- """H factor (radiation corrected) [dimensionless]."""
-
I_p: Parameter[float]
"""Plasma current [megaampere]."""
@@ -139,9 +184,6 @@ class ProcessSolverParams(MappedParameterFrame):
P_brehms: Parameter[float]
"""Bremsstrahlung [megawatt]."""
- P_el_net_process: Parameter[float]
- """Net electrical power output as provided by PROCESS [megawatt]."""
-
P_fus_DD: Parameter[float]
"""D-D fusion power [megawatt]."""
@@ -303,24 +345,12 @@ class ProcessSolverParams(MappedParameterFrame):
l_i: Parameter[float]
"""Normalised internal plasma inductance [dimensionless]."""
- q_95: Parameter[float]
- """Plasma safety factor [dimensionless]."""
-
r_tf_inboard_out: Parameter[float]
"""Outboard Radius of the TF coil inboard leg tapered region [meter]."""
- sigma_tf_case_max: Parameter[float]
- """Maximum von Mises stress in the TF coil case nose [pascal]."""
-
- sigma_tf_wp_max: Parameter[float]
- """Maximum von Mises stress in the TF coil winding pack nose [pascal]."""
-
T_e: Parameter[float]
"""Average plasma electron temperature [kiloelectron_volt]."""
- tau_flattop: Parameter[float]
- """Flat-top duration [second]."""
-
tk_tf_outboard: Parameter[float]
"""TF coil outboard thickness [meter]."""
@@ -330,8 +360,19 @@ class ProcessSolverParams(MappedParameterFrame):
Z_eff: Parameter[float]
"""Effective particle radiation atomic mass [unified_atomic_mass_unit]."""
- _mappings: ClassVar = deepcopy(mappings)
- _defaults = ProcessInputs()
+ _mappings = deepcopy(mappings)
+
+ @property
+ def _defaults(self):
+ try:
+ return self.__defaults
+ except AttributeError:
+ self.__defaults = ProcessInputs()
+ return self.__defaults
+
+ @_defaults.setter
+ def _defaults(self, value: ProcessInputs):
+ self.__defaults = value
@property
def mappings(self) -> Dict[str, ParameterMapping]:
@@ -353,8 +394,18 @@ def template_defaults(self) -> Dict[str, _INVariable]:
return self._defaults.to_invariable()
@classmethod
- def from_defaults(cls) -> MappedParameterFrame:
+ def from_defaults(
+ cls, template: Optional[ProcessInputs] = None
+ ) -> ProcessSolverParams:
"""
Initialise from defaults
"""
- return super().from_defaults(cls._defaults.to_dict())
+ if template is None:
+ template = ProcessInputs()
+ self = super().from_defaults(template.to_dict())
+ else:
+ self = super().from_defaults(
+ template.to_dict(), source=f"{NAME} user input template"
+ )
+ self.__defaults = template
+ return self
diff --git a/bluemira/codes/process/template_builder.py b/bluemira/codes/process/template_builder.py
new file mode 100644
index 0000000000..76db50ed78
--- /dev/null
+++ b/bluemira/codes/process/template_builder.py
@@ -0,0 +1,321 @@
+# bluemira is an integrated inter-disciplinary design tool for future fusion
+# reactors. It incorporates several modules, some of which rely on other
+# codes, to carry out a range of typical conceptual fusion reactor design
+# activities.
+#
+# Copyright (C) 2021-2023 M. Coleman, J. Cook, F. Franza, I.A. Maione, S. McIntosh,
+# J. Morris, D. Short
+#
+# bluemira is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# bluemira is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with bluemira; if not, see .
+
+"""
+PROCESS IN.DAT template builder
+"""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Union
+
+if TYPE_CHECKING:
+ from bluemira.codes.process._equation_variable_mapping import (
+ Constraint,
+ ConstraintSelection,
+ Objective,
+ )
+ from bluemira.codes.process._model_mapping import (
+ PROCESSModel,
+ PROCESSOptimisationAlgorithm,
+ )
+
+from bluemira.base.look_and_feel import bluemira_warn
+from bluemira.codes.process._equation_variable_mapping import (
+ FV_CONSTRAINT_ITVAR_MAPPING,
+ ITERATION_VAR_MAPPING,
+ OBJECTIVE_MIN_ONLY,
+ VAR_ITERATION_MAPPING,
+)
+from bluemira.codes.process._inputs import ProcessInputs
+from bluemira.codes.process.api import Impurities
+
+
+class PROCESSTemplateBuilder:
+ """
+ An API patch to make PROCESS a little easier to work with before
+ the PROCESS team write a Python API.
+ """
+
+ def __init__(self):
+ self._models: Dict[str, PROCESSModel] = {}
+ self._constraints: List[Constraint] = []
+ self.values: Dict[str, Any] = {}
+ self.variables: Dict[str, float] = {}
+ self.bounds: Dict[str, Dict[str, str]] = {}
+ self.ixc: List[int] = []
+ self.fimp: List[float] = 14 * [0.0]
+
+ self.minmax: int = 0
+ self.ioptimiz: int = 0
+ self.maxcal: int = 1000
+ self.epsvmc: float = 1.0e-8
+
+ def set_run_title(self, run_title: str):
+ """
+ Set the run title
+ """
+ self.values["runtitle"] = run_title
+
+ def set_optimisation_algorithm(self, algorithm_choice: PROCESSOptimisationAlgorithm):
+ """
+ Set the optimisation algorithm to use
+ """
+ self.ioptimiz = algorithm_choice.value
+
+ def set_optimisation_numerics(
+ self, max_iterations: int = 1000, tolerance: float = 1e-8
+ ):
+ """
+ Set optimisation numerics
+ """
+ self.maxcal = max_iterations
+ self.epsvmc = tolerance
+
+ def set_minimisation_objective(self, objective: Objective):
+ """
+ Set the minimisation objective equation to use when running PROCESS
+ """
+ self.minmax = objective.value
+
+ def set_maximisation_objective(self, objective: Objective):
+ """
+ Set the maximisation objective equation to use when running PROCESS
+ """
+ minmax = objective.value
+ if minmax in OBJECTIVE_MIN_ONLY:
+ raise ValueError(
+ f"Equation {objective} can only be used as a minimisation objective."
+ )
+ self.minmax = -minmax
+
+ def set_model(self, model_choice: PROCESSModel):
+ """
+ Set a model switch to the PROCESS run
+ """
+ if model_choice.switch_name in self._models:
+ bluemira_warn(f"Over-writing model choice {model_choice}.")
+ self._models[model_choice.switch_name] = model_choice
+
+ def add_constraint(self, constraint: Constraint):
+ """
+ Add a constraint to the PROCESS run
+ """
+ if constraint in self._constraints:
+ bluemira_warn(
+ f"Constraint {constraint.name} is already in the constraint list."
+ )
+
+ if constraint.value in FV_CONSTRAINT_ITVAR_MAPPING:
+ # Sensible (?) defaults. bounds are standard PROCESS for f-values for _most_
+ # f-value constraints.
+ self.add_fvalue_constraint(constraint, None, None, None)
+ else:
+ self._constraints.append(constraint)
+
+ def add_fvalue_constraint(
+ self,
+ constraint: Constraint,
+ value: Optional[float] = None,
+ lower_bound: Optional[float] = None,
+ upper_bound: Optional[float] = None,
+ ):
+ """
+ Add an f-value constraint to the PROCESS run
+ """
+ if constraint.value not in FV_CONSTRAINT_ITVAR_MAPPING:
+ raise ValueError(
+ f"Constraint '{constraint.name}' is not an f-value constraint."
+ )
+ self._constraints.append(constraint)
+
+ itvar = FV_CONSTRAINT_ITVAR_MAPPING[constraint.value]
+ if itvar not in self.ixc:
+ self.add_variable(
+ VAR_ITERATION_MAPPING[itvar], value, lower_bound, upper_bound
+ )
+
+ def add_variable(
+ self,
+ name: str,
+ value: Optional[float] = None,
+ lower_bound: Optional[float] = None,
+ upper_bound: Optional[float] = None,
+ ):
+ """
+ Add an iteration variable to the PROCESS run
+ """
+ itvar = ITERATION_VAR_MAPPING.get(name, None)
+ if not itvar:
+ raise ValueError(f"There is no iteration variable: '{name}'")
+
+ if itvar in self.ixc:
+ bluemira_warn(
+ f"Iteration variable '{name}' is already in the variable list."
+ " Updating value and bounds."
+ )
+ self.adjust_variable(name, value, lower_bound, upper_bound)
+
+ else:
+ self.ixc.append(itvar)
+ self._add_to_dict(self.variables, name, value)
+
+ if lower_bound or upper_bound:
+ var_bounds = {}
+ if lower_bound:
+ var_bounds["l"] = str(lower_bound)
+ if upper_bound:
+ var_bounds["u"] = str(upper_bound)
+ if var_bounds:
+ self.bounds[str(itvar)] = var_bounds
+
+ def adjust_variable(
+ self,
+ name: str,
+ value: Optional[float] = None,
+ lower_bound: Optional[float] = None,
+ upper_bound: Optional[float] = None,
+ ):
+ """
+ Adjust an iteration variable in the PROCESS run
+ """
+ itvar = ITERATION_VAR_MAPPING.get(name, None)
+ if not itvar:
+ raise ValueError(f"There is no iteration variable: '{name}'")
+ if itvar not in self.ixc:
+ bluemira_warn(
+ f"Iteration variable '{name}' is not in the variable list. Adding it."
+ )
+ self.add_variable(name, value, lower_bound, upper_bound)
+ else:
+ self._add_to_dict(self.variables, name, value)
+ if (lower_bound or upper_bound) and str(itvar) not in self.bounds:
+ self.bounds[str(itvar)] = {}
+
+ if lower_bound:
+ self.bounds[str(itvar)]["l"] = str(lower_bound)
+ if upper_bound:
+ self.bounds[str(itvar)]["u"] = str(upper_bound)
+
+ def add_input_value(self, name: str, value: Union[float, Iterable[float]]):
+ """
+ Add a fixed input value to the PROCESS run
+ """
+ if name in self.values:
+ bluemira_warn(f"Over-writing {name} from {self.values[name]} to {value}")
+ self._add_to_dict(self.values, name, value)
+
+ def add_input_values(self, mapping: Dict[str, Any]):
+ """
+ Add a dictionary of fixed input values to the PROCESS run
+ """
+ for name, value in mapping.items():
+ self.add_input_value(name, value)
+
+ def add_impurity(self, impurity: Impurities, value: float):
+ """
+ Add an impurity concentration
+ """
+ idx = impurity.value - 1
+ self.fimp[idx] = value
+
+ def _add_to_dict(self, mapping: Dict[str, Any], name: str, value: Any):
+ if "fimp(" in name:
+ num = int(name.strip("fimp(")[:2])
+ impurity = Impurities(num)
+ self.add_impurity(impurity, value)
+ else:
+ mapping[name] = value
+
+ def _check_model_inputs(self):
+ """
+ Check the required inputs for models have been provided.
+ """
+ for model in self._models.values():
+ self._check_missing_inputs(model)
+
+ def _check_constraint_inputs(self):
+ """
+ Check the required inputs for the constraints have been provided
+ """
+ for constraint in self._constraints:
+ self._check_missing_iteration_variables(constraint)
+ self._check_missing_inputs(constraint)
+
+ def _check_missing_inputs(self, model: Union[PROCESSModel, ConstraintSelection]):
+ missing_inputs = [
+ input_name
+ for input_name in model.requires_values
+ if (input_name not in self.values and input_name not in self.variables)
+ ]
+
+ if missing_inputs:
+ model_name = f"{model.__class__.__name__}.{model.name}"
+ inputs = ", ".join([f"'{inp}'" for inp in missing_inputs])
+ bluemira_warn(
+ f"{model_name} requires inputs {inputs} which have not been specified."
+ " Default values will be used."
+ )
+
+ def _check_missing_iteration_variables(self, constraint: ConstraintSelection):
+ missing_itv = [
+ VAR_ITERATION_MAPPING[itv_num]
+ for itv_num in constraint.requires_variables
+ if (
+ VAR_ITERATION_MAPPING[itv_num] not in self.variables
+ and VAR_ITERATION_MAPPING[itv_num] not in self.values
+ )
+ ]
+ if missing_itv:
+ con_name = f"{constraint.__class__.__name__}.{constraint.name}"
+ inputs = ", ".join([f"'{inp}'" for inp in missing_itv])
+ bluemira_warn(
+ f"{con_name} requires iteration variable {inputs} "
+ "which have not been specified. Default values will be used."
+ )
+
+ def make_inputs(self) -> ProcessInputs:
+ """
+ Make the ProcessInputs InVariable for the specified template
+ """
+ if self.ioptimiz != 0 and self.minmax == 0:
+ bluemira_warn(
+ "You are running in optimisation mode,"
+ " but have not set an objective function."
+ )
+
+ self._check_constraint_inputs()
+ icc = [con.value for con in self._constraints]
+ self._check_model_inputs()
+ models = {k: v.value for k, v in self._models.items()}
+
+ return ProcessInputs(
+ bounds=self.bounds,
+ icc=icc,
+ ixc=self.ixc,
+ minmax=self.minmax,
+ ioptimz=self.ioptimiz,
+ epsvmc=self.epsvmc,
+ maxcal=self.maxcal,
+ fimp=self.fimp,
+ **self.values,
+ **models,
+ **self.variables,
+ )
diff --git a/bluemira/codes/utilities.py b/bluemira/codes/utilities.py
index 97ab35e6f6..1026158fb2 100644
--- a/bluemira/codes/utilities.py
+++ b/bluemira/codes/utilities.py
@@ -29,7 +29,7 @@
import threading
from enum import Enum
from types import ModuleType
-from typing import Any, Dict, List
+from typing import Any, Callable, Dict, List
from bluemira.base.look_and_feel import (
_bluemira_clean_flush,
@@ -113,8 +113,14 @@ def create_mapping(
]:
if puts is not None:
for bm_key, (ec_key, unit) in puts.items():
+ if isinstance(ec_key, tuple):
+ ec_in = ec_key[0]
+ ec_out = ec_key[1]
+ else:
+ ec_in = ec_out = ec_key
+
mappings[bm_key] = ParameterMapping(
- ec_key, send=sr["send"], recv=sr["recv"], unit=unit
+ ec_in, ec_out, send=sr["send"], recv=sr["recv"], unit=unit
)
return mappings
@@ -133,13 +139,18 @@ class LogPipe(threading.Thread):
"""
- def __init__(self, loglevel: str):
+ def __init__(
+ self,
+ loglevel: str,
+ flush_callable: Callable[[str], bool] = lambda line: False, # noqa: ARG005
+ ):
super().__init__(daemon=True)
self.logfunc = {"print": bluemira_print_clean, "error": bluemira_error_clean}[
loglevel
]
self.logfunc_flush = _bluemira_clean_flush
+ self.flush_callable = flush_callable
self.fd_read, self.fd_write = os.pipe()
self.pipe = os.fdopen(self.fd_read, encoding="utf-8", errors="ignore")
self.start()
@@ -155,7 +166,7 @@ def run(self):
Run the thread and pipe it all into the logger.
"""
for line in iter(self.pipe.readline, ""):
- if line.startswith("==>"):
+ if self.flush_callable(line):
self.logfunc_flush(line.strip("\n"))
else:
self.logfunc(line)
@@ -169,7 +180,12 @@ def close(self):
os.close(self.fd_write)
-def run_subprocess(command: List[str], run_directory: str = ".", **kwargs) -> int:
+def run_subprocess(
+ command: List[str],
+ run_directory: str = ".",
+ flush_callable: Callable[[str], bool] = lambda line: False, # noqa: ARG005
+ **kwargs,
+) -> int:
"""
Run a subprocess terminal command piping the output into bluemira's
logs.
@@ -189,8 +205,8 @@ def run_subprocess(command: List[str], run_directory: str = ".", **kwargs) -> in
return_code: int
The return code of the subprocess.
"""
- stdout = LogPipe("print")
- stderr = LogPipe("error")
+ stdout = LogPipe("print", flush_callable)
+ stderr = LogPipe("error", flush_callable)
kwargs["cwd"] = run_directory
kwargs.pop("shell", None) # Protect against user input
diff --git a/bluemira/equilibria/coils/_field.py b/bluemira/equilibria/coils/_field.py
index f1e2ff8d24..9c8da5e668 100644
--- a/bluemira/equilibria/coils/_field.py
+++ b/bluemira/equilibria/coils/_field.py
@@ -632,8 +632,14 @@ def control_F(self, coil: CoilGroup) -> np.ndarray:
same_pos = np.nonzero(xw == zw)[0]
if same_pos.size > 0:
# self inductance
- xxw = xw[same_pos]
- cr = self._current_radius[xxw]
+ # same_pos could be an array that is indexed from zw.
+ # This loops over zw and creates an index in xw where xw == zw
+ # better ways welcome!
+ xxw = []
+ for _z in zw:
+ if (_pos := np.nonzero(_z == xw)[0]).size > 0:
+ xxw.extend(_pos)
+ cr = self._current_radius[np.array(xxw)]
Bz = np.zeros((x.size, 1))
Bx = Bz.copy() # Should be 0 anyway
mask = np.zeros_like(Bz, dtype=bool)
diff --git a/bluemira/equilibria/plotting.py b/bluemira/equilibria/plotting.py
index 9988f6acde..44e3b4206c 100644
--- a/bluemira/equilibria/plotting.py
+++ b/bluemira/equilibria/plotting.py
@@ -369,8 +369,13 @@ def _plot_coil(self, x_boundary, z_boundary, ctype, fill=True, **kwargs):
x = np.append(x_boundary, x_boundary[0])
z = np.append(z_boundary, z_boundary[0])
+ if all(x_boundary == x_boundary[0]) or all(z_boundary == z_boundary[0]):
+ self.ax.plot(
+ x[0], z[0], zorder=11, color="k", linewidth=linewidth, marker="+"
+ )
+ else:
+ self.ax.plot(x, z, zorder=11, color=color, linewidth=linewidth)
- self.ax.plot(x, z, zorder=11, color=color, linewidth=linewidth)
if fill:
if mask:
self.ax.fill(x, z, color="w", zorder=10, alpha=1)
diff --git a/bluemira/geometry/tools.py b/bluemira/geometry/tools.py
index a44bae3d06..9c2666765c 100644
--- a/bluemira/geometry/tools.py
+++ b/bluemira/geometry/tools.py
@@ -598,6 +598,7 @@ def _offset_wire_discretised(
label="",
*,
fallback_method="square",
+ fallback_force_spline=False,
byedges=True,
ndiscr=200,
**fallback_kwargs,
@@ -624,10 +625,16 @@ def _offset_wire_discretised(
coordinates = wire.discretize(byedges=byedges, ndiscr=ndiscr)
- result = offset_clipper(
- coordinates, thickness, method=fallback_method, **fallback_kwargs
+ wire = make_polygon(
+ offset_clipper(
+ coordinates, thickness, method=fallback_method, **fallback_kwargs
+ ),
+ label=label,
+ closed=True,
)
- return make_polygon(result, label=label, closed=True)
+ if fallback_force_spline:
+ return force_wire_to_spline(wire, n_edges_max=ndiscr)
+ return wire
@fallback_to(_offset_wire_discretised, cadapi.FreeCADError)
diff --git a/conda/environment.yml b/conda/environment.yml
index 6a1fe22b3a..aaf52f8474 100644
--- a/conda/environment.yml
+++ b/conda/environment.yml
@@ -25,12 +25,12 @@ dependencies:
- libpng=1.6.37 # malloc crash on 1.6.38
- hdf5=1.12.1
- netcdf4=1.6.0
- - numpy=1.22.4
+ - numpy=1.23
- fenics=2019.1.0
- MeshPy=2020.1
- meshio=4.4.5
- mshr=2019.1.0
- - scipy=1.7.3
+ - scipy=1.9
- graphviz
- pip
- pip:
diff --git a/data/reactors/EU-DEMO/systems_code/mockPROCESS.json b/data/reactors/EU-DEMO/systems_code/mockPROCESS.json
index 76869685cb..4114fdeb77 100644
--- a/data/reactors/EU-DEMO/systems_code/mockPROCESS.json
+++ b/data/reactors/EU-DEMO/systems_code/mockPROCESS.json
@@ -5,7 +5,7 @@
"H_star": 1.1,
"I_p": 19.117,
"P_brehms": 60.956,
- "P_el_net_process": 500.0,
+ "P_el_net": 500.0,
"P_fus": 1790.6,
"P_fus_DD": 2.1206,
"P_fus_DT": 1788.5,
diff --git a/documentation/source/examples.rst b/documentation/source/examples.rst
index d31a93e6b7..a5f8887435 100644
--- a/documentation/source/examples.rst
+++ b/documentation/source/examples.rst
@@ -63,6 +63,7 @@ External Code Examples
examples/codes/external_code
examples/codes/run_plasmod_example
+ examples/codes/run_process_example
examples/codes/equilibrium_plasmod_example
examples/codes/solver_example
diff --git a/eudemo/config/build_config.json b/eudemo/config/build_config.json
index dae712a6ad..dacd3aa4d5 100644
--- a/eudemo/config/build_config.json
+++ b/eudemo/config/build_config.json
@@ -4,7 +4,7 @@
"run_mode": "run",
"read_dir": "config/",
"run_dir": "config/",
- "plot": false
+ "plot": true
},
"Fixed boundary equilibrium": {
"run_mode": "read",
diff --git a/eudemo/config/mockPROCESS.json b/eudemo/config/mockPROCESS.json
index 1a59577b43..ce1ae3ae06 100644
--- a/eudemo/config/mockPROCESS.json
+++ b/eudemo/config/mockPROCESS.json
@@ -8,7 +8,6 @@
"P_bd_in": 50.0,
"P_brehms": 59.668,
"P_el_net": 500,
- "P_el_net_process": 500.0,
"P_fus": 1994.6,
"P_fus_DD": 2.4746,
"P_fus_DT": 1992.1,
diff --git a/eudemo/config/params.json b/eudemo/config/params.json
index 07829d337c..9cc0a95976 100644
--- a/eudemo/config/params.json
+++ b/eudemo/config/params.json
@@ -24,7 +24,7 @@
"long_name": "Peak field inside the TF coil winding pack"
},
"C_Ejima": {
- "value": 0.4,
+ "value": 0.3,
"unit": "dimensionless",
"source": "Input (Ejima, et al., Volt-second analysis and consumption in Doublet III plasmas, Nuclear Fusion 22, 1313 (1982))",
"long_name": "Ejima constant"
@@ -59,12 +59,6 @@
"source": "Input",
"long_name": "Net electrical power output"
},
- "P_el_net_process": {
- "value": 0.0,
- "unit": "megawatt",
- "source": "Input",
- "long_name": "Net electrical power output as provided by PROCESS"
- },
"P_fus": {
"value": 2000,
"unit": "megawatt",
@@ -84,7 +78,7 @@
"long_name": "D-T fusion power"
},
"P_hcd_ss": {
- "value": 50,
+ "value": 51,
"unit": "megawatt",
"source": "Input",
"long_name": "Steady-state HCD power"
@@ -376,6 +370,12 @@
"source": "Input",
"long_name": "95th percentile plasma elongation"
},
+ "m_s_limit": {
+ "value": 0.1,
+ "unit": "dimensionless",
+ "source": "Input",
+ "long_name": "Margin to vertical stability criterion"
+ },
"l_i": {
"value": 0.8,
"unit": "dimensionless",
@@ -383,16 +383,22 @@
"long_name": "Normalised internal plasma inductance"
},
"n_TF": {
- "value": 18,
+ "value": 16,
"unit": "dimensionless",
"source": "Input",
"long_name": "Number of TF coils"
},
+ "q_0": {
+ "value": 1.0,
+ "unit": "dimensionless",
+ "source": "Input",
+ "long_name": "Plasma safety factor on axis"
+ },
"q_95": {
"value": 3.5,
"unit": "dimensionless",
"source": "Input",
- "long_name": "Plasma safety factor"
+ "long_name": "Plasma safety factor at the 95th percentile flux surface"
},
"r_cs_corner": {
"value": 0,
@@ -472,17 +478,23 @@
"source": "Input",
"long_name": "Outboard vessel inner radius"
},
+ "sigma_cs_wp_max": {
+ "value": 660000000.0,
+ "unit": "pascal",
+ "source": "Input",
+ "long_name": "Maximum von Mises stress in the CS coil winding pack"
+ },
"sigma_tf_case_max": {
- "value": 550000000.0,
+ "value": 580000000.0,
"unit": "pascal",
"source": "Input",
"long_name": "Maximum von Mises stress in the TF coil case nose"
},
"sigma_tf_wp_max": {
- "value": 550000000.0,
+ "value": 580000000.0,
"unit": "pascal",
"source": "Input",
- "long_name": "Maximum von Mises stress in the TF coil winding pack nose"
+ "long_name": "Maximum von Mises stress in the TF coil winding pack"
},
"tau_e": {
"value": 3,
@@ -517,19 +529,19 @@
"long_name": "Minimum breeding blanket angle"
},
"tk_bb_ib": {
- "value": 0.8,
+ "value": 0.755,
"unit": "meter",
"source": "Input",
"long_name": "Inboard blanket thickness"
},
"tk_bb_ob": {
- "value": 1.1,
+ "value": 0.982,
"unit": "meter",
"source": "Input",
"long_name": "Outboard blanket thickness"
},
"tk_cr_vv": {
- "value": 0.3,
+ "value": 0.15,
"unit": "meter",
"source": "Input",
"long_name": "Cryostat VV thickness"
@@ -625,28 +637,28 @@
"long_name": "ITER-like TF gravity support base plate depth"
},
"tk_sh_bot": {
- "value": 1e-6,
+ "value": 0.0,
"unit": "meter",
"source": "Input",
"long_name": "Lower shield thickness",
"description": "DO NOT USE - PROCESS has VV = VV + shield"
},
"tk_sh_in": {
- "value": 1e-6,
+ "value": 0.0,
"unit": "meter",
"source": "Input",
"long_name": "Inboard shield thickness",
"description": "DO NOT USE - PROCESS has VV = VV + shield"
},
"tk_sh_out": {
- "value": 1e-6,
+ "value": 0.0,
"unit": "meter",
"source": "Input",
"long_name": "Outboard shield thickness",
"description": "DO NOT USE - PROCESS has VV = VV + shield"
},
"tk_sh_top": {
- "value": 1e-6,
+ "value": 0.0,
"unit": "meter",
"source": "Input",
"long_name": "Upper shield thickness",
@@ -665,7 +677,7 @@
"long_name": "Outboard SOL thickness"
},
"tk_tf_front_ib": {
- "value": 0.04,
+ "value": 0.06,
"unit": "meter",
"source": "Input",
"long_name": "TF coil inboard steel front plasma-facing"
@@ -702,7 +714,7 @@
"long_name": "TF coil outboard thickness"
},
"tk_tf_side": {
- "value": 0.1,
+ "value": 0.05,
"unit": "meter",
"source": "Input",
"long_name": "TF coil inboard case minimum side wall thickness"
@@ -778,7 +790,7 @@
"long_name": "Maximum peak field to use in CS modules"
},
"CS_jmax": {
- "value": 16,
+ "value": 16.5,
"unit": "megaampere / meter ** 2",
"source": "Input",
"long_name": "Maximum current density to use in CS modules"
@@ -832,7 +844,7 @@
"long_name": "Shafranov shift of plasma (geometric=>magnetic)"
},
"tk_cs_casing": {
- "value": 0.07,
+ "value": 0.02,
"unit": "meter",
"source": "Input",
"long_name": "Thickness of the CS coil casing"
@@ -1021,5 +1033,17 @@
"value": 150,
"unit": "MW",
"long_name": "Steady-state heating and current drive electrical power"
+ },
+ "eta_ecrh": {
+ "value": 0.4,
+ "unit": "dimensionless",
+ "source": "Input",
+ "long_name": "Electron cyclotron resonce heating wallplug efficiency"
+ },
+ "gamma_ecrh": {
+ "value": 0.3e20,
+ "unit": "A/W/m^2",
+ "source": "Input",
+ "long_name": "Electron cyclotron resonce heating current drive efficiency"
}
}
diff --git a/eudemo/eudemo/params.py b/eudemo/eudemo/params.py
index c5be839128..c32065a9d3 100644
--- a/eudemo/eudemo/params.py
+++ b/eudemo/eudemo/params.py
@@ -64,6 +64,7 @@ class EUDEMOReactorParams(ParameterFrame):
ib_offset_angle: Parameter[float]
kappa_95: Parameter[float]
kappa: Parameter[float]
+ m_s_limit: Parameter[float]
l_i: Parameter[float]
n_CS: Parameter[int]
n_PF: Parameter[int]
@@ -71,7 +72,6 @@ class EUDEMOReactorParams(ParameterFrame):
ob_offset_angle: Parameter[float]
P_bd_in: Parameter[float]
P_brehms: Parameter[float]
- P_el_net_process: Parameter[float]
P_el_net: Parameter[float]
P_fus_DD: Parameter[float]
P_fus_DT: Parameter[float]
@@ -85,6 +85,7 @@ class EUDEMOReactorParams(ParameterFrame):
P_sync: Parameter[float]
PF_bmax: Parameter[float]
PF_jmax: Parameter[float]
+ q_0: Parameter[float]
q_95: Parameter[float]
R_0: Parameter[float]
r_cs_corner: Parameter[float]
@@ -100,6 +101,7 @@ class EUDEMOReactorParams(ParameterFrame):
r_ts_ib_in: Parameter[float]
r_vv_ib_in: Parameter[float]
r_vv_ob_in: Parameter[float]
+ sigma_cs_wp_max: Parameter[float]
sigma_tf_case_max: Parameter[float]
sigma_tf_wp_max: Parameter[float]
T_e: Parameter[float]
@@ -151,6 +153,10 @@ class EUDEMOReactorParams(ParameterFrame):
T_e_ped: Parameter[float]
q_control: Parameter[float]
+ # Heating and current drive
+ eta_ecrh: Parameter[float]
+ gamma_ecrh: Parameter[float]
+
# Equilibrium
div_L2D_ib: Parameter[float]
div_L2D_ob: Parameter[float]
@@ -164,7 +170,7 @@ class EUDEMOReactorParams(ParameterFrame):
div_Ltarg: Parameter[float] # noqa: N815
div_open: Parameter[bool]
- # Plasma face
+ # Remote maintenance
c_rm: Parameter[float]
# Vacuum vessel
diff --git a/eudemo/eudemo/pf_coils/tools.py b/eudemo/eudemo/pf_coils/tools.py
index c814839ce7..720eeb87ec 100644
--- a/eudemo/eudemo/pf_coils/tools.py
+++ b/eudemo/eudemo/pf_coils/tools.py
@@ -204,7 +204,10 @@ def make_coilset(
for s in solenoid:
s.fix_size()
- tf_track = offset_wire(tf_boundary, 1)
+ tf_track = offset_wire(
+ tf_boundary, 1, fallback_method="miter", fallback_force_spline=True
+ )
+
x_c, z_c = make_PF_coil_positions(
tf_track,
n_PF,
@@ -329,12 +332,10 @@ def make_coil_mapper(
if len(_bin) < 1:
bluemira_warn("There is a segment of the track which has no coils on it.")
elif len(_bin) == 1:
- coil = _bin[0]
- interpolator_dict[coil.name] = PathInterpolator(segment)
+ interpolator_dict[_bin[0].name] = PathInterpolator(segment)
else:
- coils = _bin
l_values = np.array(
- [segment.parameter_at([c.x, 0, c.z], tolerance=VERY_BIG) for c in coils]
+ [segment.parameter_at([c.x, 0, c.z], tolerance=VERY_BIG) for c in _bin]
)
idx = np.argsort(l_values)
l_values = l_values[idx]
@@ -344,7 +345,7 @@ def make_coil_mapper(
sub_segs = _split_segment(segment, split_positions)
# Sorted coils
- for coil, sub_seg in zip([coils[i] for i in idx], sub_segs):
+ for coil, sub_seg in zip([_bin[i] for i in idx], sub_segs):
interpolator_dict[coil.name] = PathInterpolator(sub_seg)
return PositionMapper(interpolator_dict)
@@ -383,7 +384,9 @@ def make_pf_coil_path(tf_boundary: BluemiraWire, offset_value: float) -> Bluemir
-------
Path along which the PF coil centroids should be positioned
"""
- tf_offset = offset_wire(tf_boundary, offset_value)
+ tf_offset = offset_wire(
+ tf_boundary, offset_value, fallback_method="miter", fallback_force_spline=True
+ )
# Find top-left and bottom-left "corners"
coordinates = tf_offset.discretize(byedges=True, ndiscr=200)
diff --git a/eudemo/eudemo/radial_build.py b/eudemo/eudemo/radial_build.py
index bb10cbd417..7960423eb4 100644
--- a/eudemo/eudemo/radial_build.py
+++ b/eudemo/eudemo/radial_build.py
@@ -24,10 +24,344 @@
from bluemira.base.parameter_frame import ParameterFrame
from bluemira.codes import plot_radial_build, systems_code_solver
+from bluemira.codes.process._equation_variable_mapping import Constraint, Objective
+from bluemira.codes.process._model_mapping import (
+ AlphaPressureModel,
+ AvailabilityModel,
+ BetaLimitModel,
+ BootstrapCurrentScalingLaw,
+ CSSuperconductorModel,
+ ConfinementTimeScalingLaw,
+ CostModel,
+ CurrentDriveEfficiencyModel,
+ DensityLimitModel,
+ EPEDScalingModel,
+ FISPACTSwitchModel,
+ OperationModel,
+ OutputCostsSwitch,
+ PFSuperconductorModel,
+ PROCESSOptimisationAlgorithm,
+ PlasmaCurrentScalingLaw,
+ PlasmaGeometryModel,
+ PlasmaNullConfigurationModel,
+ PlasmaPedestalModel,
+ PlasmaProfileModel,
+ PowerFlowModel,
+ PrimaryPumpingModel,
+ SecondaryCycleModel,
+ ShieldThermalHeatUse,
+ SolenoidSwitchModel,
+ TFNuclearHeatingModel,
+ TFSuperconductorModel,
+ TFWindingPackTurnModel,
+)
+from bluemira.codes.process.api import Impurities
+from bluemira.codes.process.template_builder import PROCESSTemplateBuilder
_PfT = TypeVar("_PfT", bound=ParameterFrame)
+template_builder = PROCESSTemplateBuilder()
+template_builder.set_optimisation_algorithm(PROCESSOptimisationAlgorithm.VMCON)
+template_builder.set_optimisation_numerics(max_iterations=1000, tolerance=1e-8)
+
+template_builder.set_minimisation_objective(Objective.MAJOR_RADIUS)
+
+for constraint in (
+ Constraint.BETA_CONSISTENCY,
+ Constraint.GLOBAL_POWER_CONSISTENCY,
+ Constraint.DENSITY_UPPER_LIMIT,
+ Constraint.NWL_UPPER_LIMIT,
+ Constraint.RADIAL_BUILD_CONSISTENCY,
+ Constraint.BURN_TIME_LOWER_LIMIT,
+ Constraint.LH_THRESHHOLD_LIMIT,
+ Constraint.NET_ELEC_LOWER_LIMIT,
+ Constraint.BETA_UPPER_LIMIT,
+ Constraint.CS_EOF_DENSITY_LIMIT,
+ Constraint.CS_BOP_DENSITY_LIMIT,
+ Constraint.PINJ_UPPER_LIMIT,
+ Constraint.TF_CASE_STRESS_UPPER_LIMIT,
+ Constraint.TF_JACKET_STRESS_UPPER_LIMIT,
+ Constraint.TF_JCRIT_RATIO_UPPER_LIMIT,
+ Constraint.TF_DUMP_VOLTAGE_UPPER_LIMIT,
+ Constraint.TF_CURRENT_DENSITY_UPPER_LIMIT,
+ Constraint.TF_T_MARGIN_LOWER_LIMIT,
+ Constraint.CS_T_MARGIN_LOWER_LIMIT,
+ Constraint.CONFINEMENT_RATIO_LOWER_LIMIT,
+ Constraint.DUMP_TIME_LOWER_LIMIT,
+ Constraint.PSEPB_QAR_UPPER_LIMIT,
+ Constraint.CS_STRESS_UPPER_LIMIT,
+ Constraint.DENSITY_PROFILE_CONSISTENCY,
+ Constraint.CS_FATIGUE,
+):
+ template_builder.add_constraint(constraint)
+
+# Variable vector values and bounds
+template_builder.add_variable("bt", 5.3292, upper_bound=20.0)
+template_builder.add_variable("rmajor", 9.2901, upper_bound=13.0)
+template_builder.add_variable("te", 12.33, upper_bound=150.0)
+template_builder.add_variable("beta", 3.4421e-2)
+template_builder.add_variable("dene", 7.4321e19)
+template_builder.add_variable("q", 3.5, lower_bound=3.5)
+template_builder.add_variable("pheat", 50.0)
+template_builder.add_variable("ralpne", 6.8940e-02)
+template_builder.add_variable("bore", 2.3322, lower_bound=0.1)
+template_builder.add_variable("ohcth", 0.55242, lower_bound=0.1)
+template_builder.add_variable("thwcndut", 8.0e-3, lower_bound=8.0e-3)
+template_builder.add_variable("thkcas", 0.52465)
+template_builder.add_variable("tfcth", 1.2080)
+template_builder.add_variable("gapoh", 0.05, lower_bound=0.05, upper_bound=0.1)
+template_builder.add_variable("gapds", 0.02, lower_bound=0.02)
+template_builder.add_variable("oh_steel_frac", 0.57875)
+template_builder.add_variable("coheof", 2.0726e07)
+template_builder.add_variable("cpttf", 6.5e4, lower_bound=6.0e4, upper_bound=9.0e4)
+template_builder.add_variable("tdmptf", 2.5829e01)
+template_builder.add_variable("vdalw", 10.0, upper_bound=10.0)
+template_builder.add_variable("fimp(13)", 3.573e-04)
+
+# Some constraints require multiple f-values, but they are getting ridding of those,
+# so no fancy mechanics for now...
+template_builder.add_variable("fcutfsu", 0.80884, lower_bound=0.5, upper_bound=0.94)
+template_builder.add_variable("fcohbop", 0.93176)
+template_builder.add_variable("fvsbrnni", 0.39566)
+template_builder.add_variable("fncycle", 1.0)
+# template_builder.add_variable("feffcd", 1.0, lower_bound=0.001, upper_bound=1.0)
+
+# Modified f-values and bounds w.r.t. defaults
+template_builder.adjust_variable("fne0", 0.6, upper_bound=0.95)
+template_builder.adjust_variable("fdene", 1.2, upper_bound=1.2)
+template_builder.adjust_variable("flhthresh", 1.2, lower_bound=1.1, upper_bound=1.2)
+template_builder.adjust_variable("ftburn", 1.0, upper_bound=1.0)
+
+# Modifying the initial variable vector to improve convergence
+template_builder.adjust_variable("fpnetel", 1.0)
+template_builder.adjust_variable("fstrcase", 1.0)
+template_builder.adjust_variable("ftmargtf", 1.0)
+template_builder.adjust_variable("ftmargoh", 1.0)
+template_builder.adjust_variable("ftaulimit", 1.0)
+template_builder.adjust_variable("fjohc", 0.57941, upper_bound=1.0)
+template_builder.adjust_variable("fjohc0", 0.53923, upper_bound=1.0)
+template_builder.adjust_variable("foh_stress", 1.0)
+template_builder.adjust_variable("fbetatry", 0.48251)
+template_builder.adjust_variable("fwalld", 0.131)
+template_builder.adjust_variable("fmaxvvstress", 1.0)
+template_builder.adjust_variable("fpsepbqar", 1.0)
+template_builder.adjust_variable("fvdump", 1.0)
+template_builder.adjust_variable("fstrcond", 0.92007)
+template_builder.adjust_variable("fiooic", 0.63437, upper_bound=1.0)
+template_builder.adjust_variable("fjprot", 1.0)
+
+# Set model switches
+for model_choice in (
+ BootstrapCurrentScalingLaw.SAUTER,
+ ConfinementTimeScalingLaw.IPB98_Y2_H_MODE,
+ PlasmaCurrentScalingLaw.ITER_REVISED,
+ PlasmaProfileModel.CONSISTENT,
+ PlasmaPedestalModel.PEDESTAL_GW,
+ PlasmaNullConfigurationModel.SINGLE_NULL,
+ EPEDScalingModel.SAARELMA,
+ BetaLimitModel.THERMAL,
+ DensityLimitModel.GREENWALD,
+ AlphaPressureModel.WARD,
+ PlasmaGeometryModel.CREATE_A_M_S,
+ PowerFlowModel.SIMPLE,
+ ShieldThermalHeatUse.LOW_GRADE_HEAT,
+ SecondaryCycleModel.INPUT,
+ CurrentDriveEfficiencyModel.ECRH_UI_GAM,
+ OperationModel.PULSED,
+ PFSuperconductorModel.NBTI,
+ SolenoidSwitchModel.SOLENOID,
+ CSSuperconductorModel.NB3SN_WST,
+ TFSuperconductorModel.NB3SN_WST,
+ TFWindingPackTurnModel.INTEGER_TURN,
+ FISPACTSwitchModel.OFF,
+ PrimaryPumpingModel.PRESSURE_DROP_INPUT,
+ TFNuclearHeatingModel.INPUT,
+ CostModel.TETRA_1990,
+ AvailabilityModel.INPUT,
+ OutputCostsSwitch.NO,
+):
+ template_builder.set_model(model_choice)
+
+template_builder.add_impurity(Impurities.H, 1.0)
+template_builder.add_impurity(Impurities.He, 0.1)
+template_builder.add_impurity(Impurities.W, 5.0e-5)
+
+# Set fixed input values
+template_builder.add_input_values(
+ {
+ # CS fatigue variables
+ "residual_sig_hoop": 150.0e6,
+ # "n_cycle_min": ,
+ # "t_crack_radial": ,
+ # "t_structural_radial": ,
+ "t_crack_vertical": 0.649e-3,
+ "sf_vertical_crack": 1.0,
+ "sf_radial_crack": 1.0,
+ "sf_fast_fracture": 1.0,
+ "paris_coefficient": 3.86e-11,
+ "paris_power_law": 2.394,
+ "walker_coefficient": 0.5,
+ "fracture_toughness": 150.0,
+ # Undocumented danger stuff
+ "iblanket": 1,
+ "lsa": 2,
+ # Profile parameterisation inputs
+ "alphan": 1.0,
+ "alphat": 1.45,
+ "rhopedn": 0.94,
+ "rhopedt": 0.94,
+ "tbeta": 2.0,
+ "teped": 5.5,
+ "tesep": 0.1,
+ "fgwped": 0.85,
+ "neped": 0.678e20,
+ "nesep": 0.2e20,
+ "dnbeta": 3.0,
+ # Plasma impurity stuff
+ "coreradius": 0.75,
+ "coreradiationfraction": 0.6,
+ # Important stuff
+ "pnetelin": 500.0,
+ "tbrnmn": 7.2e3,
+ "sig_tf_case_max": 5.8e8,
+ "sig_tf_wp_max": 5.8e8,
+ "alstroh": 6.6e8,
+ "psepbqarmax": 9.2,
+ "aspect": 3.1,
+ "m_s_limit": 0.1,
+ "triang": 0.5,
+ "q0": 1.0,
+ "ssync": 0.6,
+ "plasma_res_factor": 0.66,
+ "gamma": 0.3,
+ "hfact": 1.1,
+ "life_dpa": 70.0,
+ # Radial build inputs
+ "tftsgap": 0.05,
+ "vvblgap": 0.02,
+ "blnkith": 0.755,
+ "scrapli": 0.225,
+ "scraplo": 0.225,
+ "blnkoth": 0.982,
+ "ddwex": 0.15,
+ "gapomin": 0.2,
+ # Vertical build inputs
+ "vgap2": 0.05,
+ "divfix": 0.621,
+ # HCD inputs
+ "pinjalw": 51.0,
+ "gamma_ecrh": 0.3,
+ "etaech": 0.4,
+ "bscfmax": 0.99,
+ # BOP inputs
+ "etath": 0.375,
+ "etahtp": 0.87,
+ "etaiso": 0.9,
+ "vfshld": 0.6,
+ "tdwell": 0.0,
+ "tramp": 500.0,
+ # CS / PF coil inputs
+ "fcuohsu": 0.7,
+ "ohhghf": 0.9,
+ "rpf2": -1.825,
+ "cptdin": [4.22e4, 4.22e4, 4.22e4, 4.22e4, 4.3e4, 4.3e4, 4.3e4, 4.3e4],
+ "ipfloc": [2, 2, 3, 3],
+ "ncls": [1, 1, 2, 2],
+ "ngrp": 4,
+ "rjconpf": [1.1e7, 1.1e7, 6.0e6, 6.0e6, 8.0e6, 8.0e6, 8.0e6, 8.0e6],
+ # TF coil inputs
+ "n_tf": 16,
+ "casthi": 0.06,
+ "casths": 0.05,
+ "ripmax": 0.6,
+ "dhecoil": 0.01,
+ "tftmp": 4.75,
+ "thicndut": 2.0e-3,
+ "tinstf": 0.008,
+ # "tfinsgap": 0.01,
+ "tmargmin": 1.5,
+ "vftf": 0.3,
+ "n_pancake": 20,
+ "n_layer": 10,
+ "qnuc": 1.292e4,
+ # Inputs we don't care about but must specify
+ "cfactr": 0.75, # Ha!
+ "kappa": 1.848, # Should be overwritten
+ "walalw": 8.0, # Should never get even close to this
+ "tlife": 40.0,
+ "abktflnc": 15.0,
+ "adivflnc": 20.0,
+ # For sanity...
+ "hldivlim": 10,
+ "ksic": 1.4,
+ "prn1": 0.4,
+ "zeffdiv": 3.5,
+ "bmxlim": 11.2,
+ "ffuspow": 1.0,
+ "fpeakb": 1.0,
+ "divdum": 1,
+ "ibkt_life": 1,
+ "fkzohm": 1.0245,
+ "iinvqd": 1,
+ "dintrt": 0.0,
+ "fcap0": 1.15,
+ "fcap0cp": 1.06,
+ "fcontng": 0.15,
+ "fcr0": 0.065,
+ "fkind": 1.0,
+ "ifueltyp": 1,
+ "discount_rate": 0.06,
+ "bkt_life_csf": 1,
+ "ucblvd": 280.0,
+ "ucdiv": 5e5,
+ "ucme": 3.0e8,
+ # Suspicous stuff
+ "zref": [3.6, 1.2, 1.0, 2.8, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
+ "fpinj": 1.0,
+ }
+)
+
+
+def apply_specific_interface_rules(params: _PfT):
+ """
+ Apply specific rules for the interface between PROCESS and BLUEMIRA
+ that relate to the EU-DEMO design parameterisation
+ """
+ # Apply q_95 as a boundary on the iteration vector rather than a fixed input
+ q_95_min = params.q_95.value
+ template_builder.adjust_variable("q", value=q_95_min, lower_bound=q_95_min)
+
+ # Apply thermal shield thickness to all values in PROCESS
+ tk_ts = params.tk_ts.value
+ template_builder.add_input_values(
+ {
+ "thshield_ib": tk_ts,
+ "thshield_ob": tk_ts,
+ "thshield_vb": tk_ts,
+ }
+ )
+
+ # Apply the summation of "shield" and "VV" thicknesses in PROCESS
+ default_vv_tk = 0.3
+ tk_vv_ib = params.tk_vv_in.value
+ tk_vv_ob = params.tk_vv_out.value
+ tk_sh_ib = tk_vv_ib - default_vv_tk
+ tk_sh_ob = tk_vv_ob - default_vv_tk
+ template_builder.add_input_values(
+ {
+ "shldith": tk_sh_ib,
+ "shldoth": tk_sh_ob,
+ "shldtth": tk_sh_ib,
+ "shldlth": tk_sh_ib,
+ "d_vv_in": default_vv_tk,
+ "d_vv_out": default_vv_tk,
+ "d_vv_top": default_vv_tk,
+ "d_vv_bot": default_vv_tk,
+ }
+ )
+
+
def radial_build(params: _PfT, build_config: Dict) -> _PfT:
"""
Update parameters after a radial build is run/read/mocked using PROCESS.
@@ -45,10 +379,17 @@ def radial_build(params: _PfT, build_config: Dict) -> _PfT:
"""
run_mode = build_config.pop("run_mode", "mock")
plot = build_config.pop("plot", False)
+ if run_mode == "run":
+ template_builder.set_run_title(
+ build_config.pop("PROCESS_runtitle", "Bluemira EUDEMO")
+ )
+ apply_specific_interface_rules(params)
+ build_config["template_in_dat"] = template_builder.make_inputs()
solver = systems_code_solver(params, build_config)
new_params = solver.execute(run_mode)
if plot:
plot_radial_build(solver.read_directory)
+
params.update_from_frame(new_params)
return params
diff --git a/eudemo/eudemo_tests/template.json b/eudemo/eudemo_tests/template.json
index 94cb7d7fb9..ffd771a437 100644
--- a/eudemo/eudemo_tests/template.json
+++ b/eudemo/eudemo_tests/template.json
@@ -233,21 +233,8 @@
"mapping": {
"PROCESS": {
"name": "pnetelin",
+ "out_name": "pnetelmw",
"send": true,
- "recv": false,
- "unit": "MW"
- }
- }
- },
- "P_el_net_process": {
- "name": "Net electrical power output as provided by PROCESS",
- "value": null,
- "unit": "MW",
- "source": "Input",
- "mapping": {
- "PROCESS": {
- "name": "pnetelmw",
- "send": false,
"recv": true,
"unit": "MW"
}
diff --git a/examples/codes/run_process_example.ex.py b/examples/codes/run_process_example.ex.py
new file mode 100644
index 0000000000..39cbf2f400
--- /dev/null
+++ b/examples/codes/run_process_example.ex.py
@@ -0,0 +1,380 @@
+# ---
+# jupyter:
+# jupytext:
+# cell_metadata_filter: tags,-all
+# notebook_metadata_filter: -jupytext.text_representation.jupytext_version
+# text_representation:
+# extension: .py
+# format_name: percent
+# format_version: '1.3'
+# kernelspec:
+# display_name: Python 3 (ipykernel)
+# language: python
+# name: python3
+# ---
+
+# %% tags=["remove-cell"]
+# bluemira is an integrated inter-disciplinary design tool for future fusion
+# reactors. It incorporates several modules, some of which rely on other
+# codes, to carry out a range of typical conceptual fusion reactor design
+# activities.
+#
+# Copyright (C) 2021-2023 M. Coleman, J. Cook, F. Franza, I.A. Maione, S. McIntosh,
+# J. Morris, D. Short
+#
+# bluemira is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# bluemira is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with bluemira; if not, see .
+
+"""
+Run PROCESS using the PROCESSTemplateBuilder
+"""
+
+# %% [markdown]
+# # Running PROCESS from "scratch"
+# This example shows how to build a PROCESS template IN.DAT file
+
+
+# %%
+from bluemira.codes import systems_code_solver
+from bluemira.codes.process._equation_variable_mapping import Constraint, Objective
+from bluemira.codes.process._model_mapping import (
+ AlphaPressureModel,
+ AvailabilityModel,
+ BetaLimitModel,
+ BootstrapCurrentScalingLaw,
+ CSSuperconductorModel,
+ ConfinementTimeScalingLaw,
+ CostModel,
+ CurrentDriveEfficiencyModel,
+ DensityLimitModel,
+ EPEDScalingModel,
+ FISPACTSwitchModel,
+ OperationModel,
+ OutputCostsSwitch,
+ PFSuperconductorModel,
+ PROCESSOptimisationAlgorithm,
+ PlasmaCurrentScalingLaw,
+ PlasmaGeometryModel,
+ PlasmaNullConfigurationModel,
+ PlasmaPedestalModel,
+ PlasmaProfileModel,
+ PowerFlowModel,
+ PrimaryPumpingModel,
+ SecondaryCycleModel,
+ ShieldThermalHeatUse,
+ SolenoidSwitchModel,
+ TFNuclearHeatingModel,
+ TFSuperconductorModel,
+ TFWindingPackTurnModel,
+)
+from bluemira.codes.process.api import Impurities
+from bluemira.codes.process.template_builder import PROCESSTemplateBuilder
+
+# %%[markdown]
+# First we are going to build a template using the :py:class:`PROCESSTemplateBuilder`,
+# without interacting with any of PROCESS' integers.
+
+# %%
+
+template_builder = PROCESSTemplateBuilder()
+
+
+# %%[markdown]
+# Now we're going to specify which optimisation algorithm we want to use, and the
+# number of iterations and tolerance.
+
+# %%
+template_builder.set_run_title("Example that won't converge")
+template_builder.set_optimisation_algorithm(PROCESSOptimisationAlgorithm.VMCON)
+template_builder.set_optimisation_numerics(max_iterations=1000, tolerance=1e-8)
+
+
+# %%[markdown]
+# Let's select the optimisation objective as the major radius:
+
+# %%
+template_builder.set_minimisation_objective(Objective.MAJOR_RADIUS)
+
+# %%[markdown]
+# You can inspect what options are available by taking a look at the
+# :py:class:`Objective` Enum. The options are hopefully self-explanatory.
+# The values of the options correspond to the PROCESS integers.
+
+# %%
+print("\n".join(str(o) for o in list(Objective)))
+
+
+# %%[markdown]
+# Now we will add a series of constraint equations to the PROCESS problem
+# we wish to solve. You can read more about these constraints an what
+# they mean in the PROCESS documentation
+
+# %%
+for constraint in (
+ Constraint.BETA_CONSISTENCY,
+ Constraint.GLOBAL_POWER_CONSISTENCY,
+ Constraint.DENSITY_UPPER_LIMIT,
+ Constraint.RADIAL_BUILD_CONSISTENCY,
+ Constraint.BURN_TIME_LOWER_LIMIT,
+ Constraint.LH_THRESHHOLD_LIMIT,
+ Constraint.NET_ELEC_LOWER_LIMIT,
+ Constraint.TF_CASE_STRESS_UPPER_LIMIT,
+ Constraint.TF_JACKET_STRESS_UPPER_LIMIT,
+ Constraint.TF_JCRIT_RATIO_UPPER_LIMIT,
+ Constraint.TF_CURRENT_DENSITY_UPPER_LIMIT,
+ Constraint.TF_T_MARGIN_LOWER_LIMIT,
+ Constraint.PSEPB_QAR_UPPER_LIMIT,
+):
+ template_builder.add_constraint(constraint)
+
+
+# %%[markdown]
+# Many of these constraints require certain iteration variables to have been
+# specified, or certain input values. The novice user can easily not be
+# aware that this is the case, or simply forget to specify these.
+
+# The :py:class:`PROCESSTemplateBuilder` will warn the user if certain
+# values have not been specified. For example, if we try to make a set of
+# inputs for an IN.DAT now, we will get many warning messages:
+
+# %%
+inputs = template_builder.make_inputs()
+
+
+# %%[markdown]
+# So let's go ahead and add the iteration variables we want to the problem:
+
+# %%
+template_builder.add_variable("bt", 5.3292, upper_bound=20.0)
+template_builder.add_variable("rmajor", 8.8901, upper_bound=13.0)
+template_builder.add_variable("te", 12.33, upper_bound=150.0)
+template_builder.add_variable("beta", 3.1421e-2)
+template_builder.add_variable("dene", 7.4321e19)
+template_builder.add_variable("q", 3.5, lower_bound=3.5)
+template_builder.add_variable("pheat", 50.0)
+template_builder.add_variable("ralpne", 6.8940e-02)
+template_builder.add_variable("bore", 2.3322, lower_bound=0.1)
+template_builder.add_variable("ohcth", 0.55242, lower_bound=0.1)
+template_builder.add_variable("thwcndut", 8.0e-3, lower_bound=8.0e-3)
+template_builder.add_variable("thkcas", 0.52465)
+template_builder.add_variable("tfcth", 1.2080)
+template_builder.add_variable("gapoh", 0.05, lower_bound=0.05, upper_bound=0.1)
+template_builder.add_variable("gapds", 0.02, lower_bound=0.02)
+template_builder.add_variable("cpttf", 6.5e4, lower_bound=6.0e4, upper_bound=9.0e4)
+template_builder.add_variable("tdmptf", 2.5829e01)
+template_builder.add_variable("fcutfsu", 0.80884, lower_bound=0.5, upper_bound=0.94)
+template_builder.add_variable("fvsbrnni", 0.39566)
+
+# %%[markdown]
+# Many of the PROCESS constraints use so-called 'f-values', which are automatically
+# added to the iteration variables using this API. However, often one wants to modify
+# the defaults of these f-values, which one can do as such:
+
+# %%
+# Modified f-values and bounds w.r.t. defaults
+template_builder.adjust_variable("fne0", 0.6, upper_bound=0.95)
+template_builder.adjust_variable("fdene", 1.2, upper_bound=1.2)
+
+
+# %%[markdown]
+# Often one wants to specify certain impurity concentrations, and even use
+# one of these as an iteration variable.
+
+# %%
+template_builder.add_impurity(Impurities.H, 1.0)
+template_builder.add_impurity(Impurities.He, 0.1)
+template_builder.add_impurity(Impurities.W, 5.0e-5)
+template_builder.add_variable(Impurities.Xe.id(), 3.573e-04)
+
+
+# %%[markdown]
+# We also want to specify some input values that are not variables:
+
+# %%
+template_builder.add_input_values(
+ {
+ # Profile parameterisation inputs
+ "alphan": 1.0,
+ "alphat": 1.45,
+ "rhopedn": 0.94,
+ "rhopedt": 0.94,
+ "tbeta": 2.0,
+ "teped": 5.5,
+ "tesep": 0.1,
+ "fgwped": 0.85,
+ "neped": 0.678e20,
+ "nesep": 0.2e20,
+ "dnbeta": 3.0,
+ # Plasma impurity stuff
+ "coreradius": 0.75,
+ "coreradiationfraction": 0.6,
+ # Important stuff
+ "pnetelin": 500.0,
+ "tbrnmn": 7.2e3,
+ "sig_tf_case_max": 5.8e8,
+ "sig_tf_wp_max": 5.8e8,
+ "alstroh": 6.6e8,
+ "psepbqarmax": 9.2,
+ "aspect": 3.1,
+ "m_s_limit": 0.1,
+ "triang": 0.5,
+ "q0": 1.0,
+ "ssync": 0.6,
+ "plasma_res_factor": 0.66,
+ "gamma": 0.3,
+ "hfact": 1.1,
+ "life_dpa": 70.0,
+ # Radial build inputs
+ "tftsgap": 0.05,
+ "d_vv_in": 0.3,
+ "shldith": 0.3,
+ "vvblgap": 0.02,
+ "blnkith": 0.755,
+ "scrapli": 0.225,
+ "scraplo": 0.225,
+ "blnkoth": 0.982,
+ "d_vv_out": 0.3,
+ "shldoth": 0.8,
+ "ddwex": 0.15,
+ "gapomin": 0.2,
+ # Vertical build inputs
+ "d_vv_top": 0.3,
+ "vgap2": 0.05,
+ "shldtth": 0.3,
+ "divfix": 0.621,
+ "d_vv_bot": 0.3,
+ # HCD inputs
+ "pinjalw": 51.0,
+ "gamma_ecrh": 0.3,
+ "etaech": 0.4,
+ "bscfmax": 0.99,
+ # BOP inputs
+ "etath": 0.375,
+ "etahtp": 0.87,
+ "etaiso": 0.9,
+ "vfshld": 0.6,
+ "tdwell": 0.0,
+ "tramp": 500.0,
+ # CS / PF coil inputs
+ "t_crack_vertical": 0.4e-3,
+ "fcuohsu": 0.7,
+ "ohhghf": 0.9,
+ "rpf2": -1.825,
+ "cptdin": [4.22e4, 4.22e4, 4.22e4, 4.22e4, 4.3e4, 4.3e4, 4.3e4, 4.3e4],
+ "ipfloc": [2, 2, 3, 3],
+ "ncls": [1, 1, 2, 2],
+ "ngrp": 4,
+ "rjconpf": [1.1e7, 1.1e7, 6.0e6, 6.0e6, 8.0e6, 8.0e6, 8.0e6, 8.0e6],
+ # TF coil inputs
+ "n_tf": 16,
+ "casthi": 0.06,
+ "casths": 0.05,
+ "ripmax": 0.6,
+ "dhecoil": 0.01,
+ "tftmp": 4.75,
+ "thicndut": 2.0e-3,
+ "tinstf": 0.008,
+ # "tfinsgap": 0.01,
+ "tmargmin": 1.5,
+ "vftf": 0.3,
+ }
+)
+
+# %%[markdown]
+# PROCESS has many different models with integer-value 'switches'. We can specify
+# these choices as follows:
+
+# %%
+for model_choice in (
+ BootstrapCurrentScalingLaw.SAUTER,
+ ConfinementTimeScalingLaw.IPB98_Y2_H_MODE,
+ PlasmaCurrentScalingLaw.ITER_REVISED,
+ PlasmaProfileModel.CONSISTENT,
+ PlasmaPedestalModel.PEDESTAL_GW,
+ PlasmaNullConfigurationModel.SINGLE_NULL,
+ EPEDScalingModel.SAARELMA,
+ BetaLimitModel.THERMAL,
+ DensityLimitModel.GREENWALD,
+ AlphaPressureModel.WARD,
+ PlasmaGeometryModel.CREATE_A_M_S,
+ PowerFlowModel.SIMPLE,
+ ShieldThermalHeatUse.LOW_GRADE_HEAT,
+ SecondaryCycleModel.INPUT,
+ CurrentDriveEfficiencyModel.ECRH_UI_GAM,
+ OperationModel.PULSED,
+ PFSuperconductorModel.NBTI,
+ SolenoidSwitchModel.SOLENOID,
+ CSSuperconductorModel.NB3SN_WST,
+ TFSuperconductorModel.NB3SN_WST,
+ TFWindingPackTurnModel.INTEGER_TURN,
+ FISPACTSwitchModel.OFF,
+ PrimaryPumpingModel.PRESSURE_DROP_INPUT,
+ TFNuclearHeatingModel.INPUT,
+ CostModel.TETRA_1990,
+ AvailabilityModel.INPUT,
+ OutputCostsSwitch.NO,
+):
+ template_builder.set_model(model_choice)
+
+# %%[markdown]
+# Some of these model choices also require certain input values
+# to be specified. If these are not specified by the user, default
+# values are used, which may not be desirable. Let us see what
+# we're still missing:
+
+# %%
+inputs = template_builder.make_inputs()
+
+# %%[markdown]
+# And now let's add those missing inputs:
+
+# %%
+template_builder.add_input_value("qnuc", 1.3e4)
+template_builder.add_input_value("n_layer", 20)
+template_builder.add_input_value("n_pancake", 20)
+
+
+# %%[markdown]
+# Finally, let us run PROCESS with our inputs. In this case, we're just running
+# PROCESS as an external code (see e.g. [External code example](../external_code.ex.py))
+# So we are not interesed in passing any parameters into it. In future, once the
+# input template has been refined to something desirable, one can pass in parameters
+# in mapped names to PROCESS, and not need to explicitly know all the PROCESS
+# parameter names.
+
+# %%
+solver = systems_code_solver(
+ params={}, build_config={"template_in_dat": template_builder.make_inputs()}
+)
+
+result = solver.execute("run")
+
+# %%
+# Great, so it runs! All we need to do now is make sure we have properly
+# specified our design problem, and perhaps adjust the initial values
+# of the iteration variables to give the optimisation algorithm a better
+# chance of finding a feasible point.
+
+# %%
+template_builder.adjust_variable("fpnetel", 1.0)
+template_builder.adjust_variable("fstrcase", 1.0)
+template_builder.adjust_variable("ftmargtf", 1.0)
+template_builder.adjust_variable("ftmargoh", 1.0)
+template_builder.adjust_variable("ftaulimit", 1.0)
+template_builder.adjust_variable("fbetatry", 0.48251)
+template_builder.adjust_variable("fpsepbqar", 1.0)
+template_builder.adjust_variable("fvdump", 1.0)
+template_builder.adjust_variable("fstrcond", 0.92007)
+template_builder.adjust_variable("fjprot", 1.0)
+
+# %%
diff --git a/pyproject.toml b/pyproject.toml
index e55f5bed3a..f511bc85c8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -87,9 +87,6 @@ openmc = [
"OpenMC @git+https://github.com/openmc-dev/openmc.git",
"parametric-plasma-source @git+https://github.com/open-radiation-sources/parametric-plasma-source.git",
]
-process = [
- "cmake>=3.13.0",
-]
polyscope = ["polyscope"]
[build-system]
@@ -326,6 +323,7 @@ ignore-names = [
]
"bluemira/__init__.py" = ["TID252"]
"bluemira/codes/__init__.py" = ["E402"]
+"bluemira/codes/process/_model_mapping.py" = ["PLR6301"]
"bluemira/geometry/parameterisations.py" = ["E731"]
"bluemira/codes/plasmod/api/_outputs.py" = ["N815"]
"bluemira/codes/plasmod/api/_inputs.py" = ["N815"]
diff --git a/requirements.txt b/requirements.txt
index 775fe67a0d..0ff9e1f652 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -17,9 +17,9 @@ neutronics-material-maker==0.1.11
nlopt==2.7.1
meshio==4.4.5
numba==0.58.0
-numba-scipy==0.3.1
+numba-scipy @ git+https://github.com/numba/numba-scipy@1e2f244
numexpr==2.8.6
-numpy==1.22.4
+numpy==1.23.5
packaging==23.2
pandas==2.0.3
periodictable==1.6.1
@@ -34,7 +34,7 @@ python-dateutil==2.8.2
pytz==2023.3.post1
rich==13.6.0
scikit-learn==1.3.1
-scipy==1.7.3
+scipy==1.10.1
seaborn==0.13.0
six==1.16.0
tables==3.8.0
diff --git a/scripts/install-process.sh b/scripts/install-process.sh
index a0c2abbc80..76d902b38a 100644
--- a/scripts/install-process.sh
+++ b/scripts/install-process.sh
@@ -14,18 +14,18 @@ if [[ $(basename $PWD) == *"bluemira"* ]]; then
fi
if [ ! -d process ]; then
- git clone git@git.ccfe.ac.uk:process/process.git
+ git clone git@github.com:ukaea/process.git
fi
cd process
-git checkout develop
+git checkout main
git pull
if [ "$1" ]
then
git checkout "$1"
else
- git checkout v2.3.0-hotfix
+ git checkout v3.0.1
fi
@@ -35,34 +35,10 @@ if [ -d build ]; then
rm -rf build
fi
-# Come out of bluemira conda and make a new environment for our build
-conda deactivate
-conda env remove -n bluemira-process-build || true
-conda create -y -n bluemira-process-build python=3.8 numpy=1.21.5
-conda activate bluemira-process-build
-
-# Install requirements into the build environment
-pip install -r ../bluemira/requirements.txt
-pip install -r requirements.txt --no-cache-dir
-pip install --upgrade --no-cache-dir -e ../bluemira/'[process]'
-
# Do the PROCESS build
-cmake -S . -B build
+cmake -S . -B build -DRELEASE=TRUE
cmake --build build
-# Deactivate build environment and reactivate bluemira
-conda deactivate && conda activate bluemira
-
-# Install PROCESS dependencies into bluemira environment
-pip install -r requirements.txt --no-cache-dir
-pip install --upgrade --no-cache-dir -e ../bluemira/'[process]'
-
-# Install PROCESS into bluemira environment
-pip install .
-
-# Clean up our build environment
-conda env remove -n bluemira-process-build || true
-
# The following suggests how to install PROCESS via a manylinux wheel, if you have docker
# installed.
diff --git a/tests/base/parameterframe/test_parameterframe.py b/tests/base/parameterframe/test_parameterframe.py
index 51ca4a9de9..5f71fdf769 100644
--- a/tests/base/parameterframe/test_parameterframe.py
+++ b/tests/base/parameterframe/test_parameterframe.py
@@ -131,6 +131,15 @@ class GenericFrame(ParameterFrame):
assert frame.x.value == 10
+ def test_horrible_scalar_unit(self):
+ @dataclass
+ class GenericFrame(ParameterFrame):
+ x: Parameter
+
+ frame = GenericFrame.from_dict({"x": {"value": 10, "unit": "1e50 m"}})
+
+ assert frame.x.value == pytest.approx(1e51)
+
@pytest.mark.parametrize("value", ["OK", ["OK"]])
def test_TypeError_given_field_has_Union_Parameter_type(self, value):
@dataclass
diff --git a/tests/codes/process/test_api.py b/tests/codes/process/test_api.py
index 73d71e825d..a3e6a0eb61 100644
--- a/tests/codes/process/test_api.py
+++ b/tests/codes/process/test_api.py
@@ -19,9 +19,10 @@
# You should have received a copy of the GNU Lesser General Public
# License along with bluemira; if not, see .
-from pathlib import Path
from unittest.mock import patch
+import pytest
+
from bluemira.codes.process import api
PROCESS_OBS_VAR = {
@@ -39,15 +40,16 @@ def test_update_obsolete_vars():
assert str2 == "shrubbery"
+@pytest.mark.skipif(not api.ENABLED, reason="PROCESS is not installed on the system.")
@patch.object(api, "imp_data")
def test_impurities(imp_data_mock):
imp_data_mock.__file__ = "./__init__.py"
assert api.Impurities["H"] == api.Impurities.H
assert api.Impurities(1) == api.Impurities.H
- assert api.Impurities(1).id() == "fimp(01"
- assert api.Impurities(10).id() == "fimp(10"
- assert api.Impurities(1).file() == Path("./H_Lzdata.dat")
- assert api.Impurities(10).file() == Path("./FeLzdata.dat")
+ assert api.Impurities(1).id() == "fimp(01)"
+ assert api.Impurities(10).id() == "fimp(10)"
+ assert api.Impurities(1).file().parts[-1] == "H__lz_tau.dat"
+ assert api.Impurities(10).file().parts[-1] == "Fe_lz_tau.dat"
def test_INVariable_works_with_floats():
diff --git a/tests/codes/process/test_data/MFILE.DAT b/tests/codes/process/test_data/MFILE.DAT
index 260e055032..2218ef718b 100644
--- a/tests/codes/process/test_data/MFILE.DAT
+++ b/tests/codes/process/test_data/MFILE.DAT
@@ -977,7 +977,7 @@
Heat_extracted_from_divertor_(MW)_______________________________________ (pthermdiv)___________________ 5.2392E+02
Nuclear_and_photon_power_lost_to_H/CD_system_(MW)_______________________ (psechcd)_____________________ 0.0000E+00
Total_(MW)______________________________________________________________ ______________________________ 4.2155E+03
- Net_electric_power_output(MW)___________________________________________ (pnetelmw.)___________________ 5.0000E+02
+ Net_electric_power_output(MW)___________________________________________ (pnetelmw.)___________________ 6.0000E+02
Required_Net_electric_power_output(MW)__________________________________ (pnetelin)____________________ 5.0000E+02
Electric_power_for_heating_and_current_drive_(MW)_______________________ (pinjwp)______________________ 5.6250E+02
Electric_power_for_primary_coolant_pumps_(MW)___________________________ (htpmw)_______________________ 3.8236E+02
@@ -992,7 +992,7 @@
Fusion_power_(MW)_______________________________________________________ (powfmw.)_____________________ 3.0729E+03
Power_from_energy_multiplication_in_blanket_and_shield_(MW)_____________ (emultmw)_____________________ 5.8473E+02
Total_(MW)______________________________________________________________ ______________________________ 3.6576E+03
- Net_electrical_output_(MW) _____________________________________________ (pnetelmw)____________________ 5.0000E+02
+ Net_electrical_output_(MW) _____________________________________________ (pnetelmw)____________________ 6.0000E+02
Heat_rejected_by_main_power_conversion_circuit_(MW)_____________________ (rejected_main)_______________ 2.6347E+03
Heat_rejected_by_other_cooling_circuits_(MW)____________________________ (psechtmw)____________________ 5.2323E+02
Total_(MW)______________________________________________________________ ______________________________ 3.6579E+03
diff --git a/tests/codes/process/test_data/mfile_data.json b/tests/codes/process/test_data/mfile_data.json
index 53e049fe4b..8ae55eb637 100644
--- a/tests/codes/process/test_data/mfile_data.json
+++ b/tests/codes/process/test_data/mfile_data.json
@@ -931,59 +931,59 @@
"var_mod": "Plasma",
"scan01": 0.094054
},
- "fimp(01": {
+ "fimp(01)": {
"var_mod": "Plasma",
"scan01": 0.74267
},
- "fimp(02": {
+ "fimp(02)": {
"var_mod": "Plasma",
"scan01": 0.094054
},
- "fimp(03": {
+ "fimp(03)": {
"var_mod": "Plasma",
"scan01": 0.0
},
- "fimp(04": {
+ "fimp(04)": {
"var_mod": "Plasma",
"scan01": 0.0
},
- "fimp(05": {
+ "fimp(05)": {
"var_mod": "Plasma",
"scan01": 0.0
},
- "fimp(06": {
+ "fimp(06)": {
"var_mod": "Plasma",
"scan01": 0.0
},
- "fimp(07": {
+ "fimp(07)": {
"var_mod": "Plasma",
"scan01": 0.0
},
- "fimp(08": {
+ "fimp(08)": {
"var_mod": "Plasma",
"scan01": 0.0
},
- "fimp(09": {
+ "fimp(09)": {
"var_mod": "Plasma",
"scan01": 0.0
},
- "fimp(10": {
+ "fimp(10)": {
"var_mod": "Plasma",
"scan01": 0.0
},
- "fimp(11": {
+ "fimp(11)": {
"var_mod": "Plasma",
"scan01": 0.0
},
- "fimp(12": {
+ "fimp(12)": {
"var_mod": "Plasma",
"scan01": 0.0
},
- "fimp(13": {
+ "fimp(13)": {
"var_mod": "Plasma",
"scan01": 0.0013085
},
- "fimp(14": {
+ "fimp(14)": {
"var_mod": "Plasma",
"scan01": 5e-5
},
@@ -3717,7 +3717,7 @@
},
"pnetelmw.": {
"var_mod": "Plant Power / Heat Transport Balance",
- "scan01": 500.0
+ "scan01": 600.0
},
"pnetelin": {
"var_mod": "Plant Power / Heat Transport Balance",
@@ -3737,7 +3737,7 @@
},
"pnetelmw": {
"var_mod": "Plant Power / Heat Transport Balance",
- "scan01": 500.0
+ "scan01": 600.0
},
"rejected_main": {
"var_mod": "Plant Power / Heat Transport Balance",
diff --git a/tests/codes/process/test_data/params.json b/tests/codes/process/test_data/params.json
index c46fc27ff1..d15569ff62 100644
--- a/tests/codes/process/test_data/params.json
+++ b/tests/codes/process/test_data/params.json
@@ -196,12 +196,6 @@
"source": "Input",
"long_name": "Bremsstrahlung"
},
- "P_el_net_process": {
- "value": 0.0,
- "unit": "megawatt",
- "source": "Input",
- "long_name": "Net electrical power output as provided by PROCESS"
- },
"P_fus_DD": {
"value": 5,
"unit": "megawatt",
@@ -530,6 +524,12 @@
"source": "Input",
"long_name": "Plasma side TF coil maximum height"
},
+ "m_s_limit": {
+ "value": 0.1,
+ "unit": "dimensionless",
+ "source": "Input",
+ "long_name": "Margin to vertical stability criterion"
+ },
"l_i": {
"value": 0.8,
"unit": "dimensionless",
@@ -542,12 +542,24 @@
"source": "Input",
"long_name": "Plasma safety factor"
},
+ "q_0": {
+ "value": 1.0,
+ "unit": "dimensionless",
+ "source": "Input",
+ "long_name": "Plasma safety factor on axis"
+ },
"r_tf_inboard_out": {
"value": 0.6265,
"unit": "meter",
"source": "Input",
"long_name": "Outboard Radius of the TF coil inboard leg tapered region"
},
+ "sigma_cs_wp_max": {
+ "value": 660000000.0,
+ "unit": "pascal",
+ "source": "Input",
+ "long_name": "Maximum von Mises stress in the CS coil winding pack"
+ },
"sigma_tf_case_max": {
"value": 550000000.0,
"unit": "pascal",
@@ -571,5 +583,37 @@
"unit": "meter",
"source": "Input",
"long_name": "TF coil outboard thickness"
+ },
+ "bb_pump_eta_el": {
+ "value": 0.87,
+ "unit": "",
+ "long_name": "Breeding blanket pumping electrical efficiency"
+ },
+ "bb_pump_eta_isen": {
+ "value": 0.9,
+ "unit": "",
+ "long_name": "Breeding blanket pumping isentropic efficiency"
+ },
+ "bb_t_inlet": {
+ "value": 573.15,
+ "unit": "K",
+ "long_name": "Breeding blanket inlet temperature"
+ },
+ "bb_t_outlet": {
+ "value": 773.15,
+ "unit": "K",
+ "long_name": "Breeding blanket outlet temperature"
+ },
+ "eta_ecrh": {
+ "value": 0.4,
+ "unit": "dimensionless",
+ "source": "Input",
+ "long_name": "Electron cyclotron resonce heating wallplug efficiency"
+ },
+ "gamma_ecrh": {
+ "value": 0.3e20,
+ "unit": "A/W/m^2",
+ "source": "Input",
+ "long_name": "Electron cyclotron resonce heating current drive efficiency"
}
}
diff --git a/tests/codes/process/test_data/read/mockPROCESS.json b/tests/codes/process/test_data/read/mockPROCESS.json
index 69c50edb80..1a6b06c083 100644
--- a/tests/codes/process/test_data/read/mockPROCESS.json
+++ b/tests/codes/process/test_data/read/mockPROCESS.json
@@ -8,7 +8,6 @@
"P_bd_in": 50.0,
"P_brehms": 59.668,
"P_el_net": 500,
- "P_el_net_process": 500.0,
"P_fus": 1994.6,
"P_fus_DD": 2.4746,
"P_fus_DT": 1992.1,
diff --git a/tests/codes/process/test_run.py b/tests/codes/process/test_run.py
index 46e797d195..b885891515 100644
--- a/tests/codes/process/test_run.py
+++ b/tests/codes/process/test_run.py
@@ -46,6 +46,8 @@ def test_run_func_calls_subprocess_with_in_dat_path(self, run_func):
getattr(run, run_func)()
- self.run_subprocess_mock.assert_called_once_with(
- [process.BINARY, "-i", "input/path_IN.DAT"]
- )
+ assert self.run_subprocess_mock.call_args[0][0] == [
+ process.BINARY,
+ "-i",
+ "input/path_IN.DAT",
+ ]
diff --git a/tests/codes/process/test_setup.py b/tests/codes/process/test_setup.py
index cb8d6a15d0..2ccfa459ed 100644
--- a/tests/codes/process/test_setup.py
+++ b/tests/codes/process/test_setup.py
@@ -24,11 +24,10 @@
import pytest
from bluemira.codes import process
-from bluemira.codes.error import CodesError
+from bluemira.codes.params import ParameterMapping
from bluemira.codes.process._setup import Setup
from bluemira.codes.process.mapping import mappings as process_mappings
from bluemira.codes.process.params import ProcessSolverParams
-from tests._helpers import file_exists
from tests.codes.process.utilities import PARAM_FILE
MODULE_REF = "bluemira.codes.process._setup"
@@ -71,30 +70,6 @@ def test_run_adds_problem_setting_params_to_InDat_writer(self):
assert writer.add_parameter.call_count > 0
assert mock.call("input0", 0.0) in writer.add_parameter.call_args_list
- def test_run_inits_writer_with_template_file_if_file_exists(self):
- with self._indat_patch as indat_cls_mock:
- setup = Setup(self.default_pf, "", template_in_dat="template/path/in.dat")
- indat_cls_mock.return_value.data = {"input": 0.0}
-
- with file_exists("template/path/in.dat", f"{MODULE_REF}.Path.is_file"):
- setup.run()
-
- indat_cls_mock.assert_called_once_with(filename="template/path/in.dat")
-
- def test_run_inits_writer_without_template_returns_default_filled_data(self):
- with self._indat_patch as indat_cls_mock:
- setup = Setup(self.default_pf, "", template_in_dat=None)
- setup.run()
-
- assert indat_cls_mock.return_value.data == self.default_pf.template_defaults
-
- @pytest.mark.parametrize("run_func", ["run", "runinput"])
- def test_run_raises_CodesError_given_no_data_in_template_file(self, run_func):
- setup = Setup(self.default_pf, "", template_in_dat="template/path/in.dat")
-
- with pytest.raises(CodesError):
- getattr(setup, run_func)()
-
def test_runinput_does_not_write_bluemira_outputs_to_in_dat(self):
with self._writer_patch as writer_cls_mock:
setup = Setup(self.default_pf, "")
@@ -130,11 +105,14 @@ class TestSetupIntegration:
@mock.patch(f"{MODULE_REF}.InDat")
def test_obsolete_parameter_names_are_updated(self, writer_cls_mock):
pf = ProcessSolverParams.from_json(PARAM_FILE)
+ pf.mappings["tk_tf_front_ib"] = ParameterMapping(
+ "dr_tf_case_out", send=True, recv=False, unit="m"
+ )
setup = Setup(pf, "")
writer_cls_mock.return_value.data = {"x": 0}
setup.run()
writer = writer_cls_mock.return_value
- # 'dr_tf_case_out' is new name for 'casthi'
- assert mock.call("dr_tf_case_out", 0.04) in writer.add_parameter.call_args_list
+ # 'dr_tf_case_out' is old name for 'casthi'
+ assert mock.call("casthi", 0.04) in writer.add_parameter.call_args_list
diff --git a/tests/codes/process/test_solver.py b/tests/codes/process/test_solver.py
index 7dfbb45ce6..99a1442905 100644
--- a/tests/codes/process/test_solver.py
+++ b/tests/codes/process/test_solver.py
@@ -19,9 +19,10 @@
# You should have received a copy of the GNU Lesser General Public
# License along with bluemira; if not, see .
+import contextlib
import filecmp
+import json
import re
-import tempfile
from pathlib import Path
from unittest import mock
@@ -29,8 +30,10 @@
from bluemira.codes.error import CodesError
from bluemira.codes.process import ENABLED
+from bluemira.codes.process._inputs import ProcessInputs
from bluemira.codes.process._solver import RunMode, Solver
from bluemira.codes.process.params import ProcessSolverParams
+from tests._helpers import file_exists
from tests.codes.process import utilities as utils
@@ -96,28 +99,32 @@ def test_get_species_fraction_retrieves_parameter_value(self):
@pytest.mark.skipif(not ENABLED, reason="PROCESS is not installed on the system.")
class TestSolverIntegration:
DATA_DIR = Path(Path(__file__).parent, "test_data")
+ MODULE_REF = "bluemira.codes.process._setup"
def setup_method(self):
self.params = ProcessSolverParams.from_json(utils.PARAM_FILE)
+ self._indat_patch = mock.patch(f"{self.MODULE_REF}.InDat")
+
+ def teardown_method(self):
+ self._indat_patch.stop()
+
@pytest.mark.longrun
- def test_run_mode_outputs_process_files(self):
- run_dir = tempfile.TemporaryDirectory()
- build_config = {"run_dir": run_dir.name}
- solver = Solver(self.params, build_config)
+ def test_run_mode_outputs_process_files(self, tmp_path):
+ solver = Solver(self.params, {"run_dir": tmp_path})
- solver.execute(RunMode.RUN)
+ with contextlib.suppress(CodesError):
+ solver.execute(RunMode.RUNINPUT)
- assert Path(run_dir.name, "IN.DAT").exists()
- assert Path(run_dir.name, "MFILE.DAT").exists()
+ assert Path(tmp_path, "IN.DAT").exists()
+ assert Path(tmp_path, "MFILE.DAT").exists()
@pytest.mark.parametrize("run_mode", [RunMode.READ, RunMode.READALL])
def test_read_mode_updates_params_from_mfile(self, run_mode):
# Assert here to check the parameter is actually changing
assert self.params.r_tf_in_centre.value != pytest.approx(2.6354)
- build_config = {"read_dir": self.DATA_DIR}
- solver = Solver(self.params, build_config)
+ solver = Solver(self.params, {"read_dir": self.DATA_DIR})
solver.execute(run_mode)
# Expected value comes from ./test_data/MFILE.DAT
@@ -125,9 +132,7 @@ def test_read_mode_updates_params_from_mfile(self, run_mode):
@pytest.mark.parametrize("run_mode", [RunMode.READ, RunMode.READALL])
def test_derived_radial_build_params_are_updated(self, run_mode):
- build_config = {"read_dir": self.DATA_DIR}
-
- solver = Solver(self.params, build_config)
+ solver = Solver(self.params, {"read_dir": self.DATA_DIR})
solver.execute(run_mode)
# Expected values come from derivation (I added the numbers up by hand)
@@ -139,23 +144,22 @@ def test_derived_radial_build_params_are_updated(self, run_mode):
assert solver.params.r_vv_ob_in.value == pytest.approx(13.69696)
@pytest.mark.longrun
- def test_runinput_mode_does_not_edit_template(self):
- run_dir = tempfile.TemporaryDirectory()
+ def test_runinput_mode_does_not_edit_template(self, tmp_path):
template_path = Path(self.DATA_DIR, "IN.DAT")
build_config = {
- "run_dir": run_dir.name,
+ "run_dir": tmp_path,
"template_in_dat": template_path,
}
solver = Solver(self.params, build_config)
- solver.execute(RunMode.RUN)
-
- assert Path(run_dir.name, "IN.DAT").is_file()
- filecmp.cmp(Path(run_dir.name, "IN.DAT"), template_path)
- assert Path(run_dir.name, "MFILE.DAT").is_file()
+ with contextlib.suppress(CodesError):
+ solver.execute(RunMode.RUN)
+ assert Path(tmp_path, "IN.DAT").is_file()
+ filecmp.cmp(Path(tmp_path, "IN.DAT"), template_path)
+ assert Path(tmp_path, "MFILE.DAT").is_file()
def test_get_species_data_returns_row_vectors(self):
- temp, loss_f, z_eff = Solver.get_species_data("H")
+ temp, loss_f, z_eff = Solver.get_species_data("H", confinement_time_ms=1.0)
assert isinstance(temp.size, int) == 1
assert temp.size > 0
@@ -163,3 +167,67 @@ def test_get_species_data_returns_row_vectors(self):
assert loss_f.size > 0
assert isinstance(z_eff.size, int) == 1
assert z_eff.size > 0
+
+ def test_run_inits_writer_with_template_file_if_file_exists(self, tmp_path):
+ build_config = {
+ "run_dir": tmp_path,
+ "template_in_dat": "template/path/in.dat",
+ }
+
+ class BLANK:
+ get_value = 0.0
+
+ with self._indat_patch as indat_cls_mock, file_exists(
+ "template/path/in.dat", f"{self.MODULE_REF}.Path.is_file"
+ ):
+ indat_cls_mock.return_value.data = {"casthi": BLANK}
+ Solver(self.params, build_config)
+
+ indat_cls_mock.assert_called_once_with(filename="template/path/in.dat")
+
+ def test_run_inits_writer_without_template_returns_default_filled_data(self):
+ with self._indat_patch as indat_cls_mock:
+ solver = Solver(self.params, {})
+ solver.run_cls = lambda *_, **_kw: None
+ solver.teardown_cls = lambda *_, **_kw: None
+ solver.execute("run")
+ assert indat_cls_mock.return_value.data == self.params.template_defaults
+
+ def test_run_raises_CodesError_given_no_data_in_template_file(self):
+ build_config = {
+ "template_in_dat": "template/path/in.dat",
+ }
+
+ with pytest.raises(CodesError):
+ Solver(self.params, build_config)
+
+ @pytest.mark.parametrize(("pf_n", "pf_v"), [(None, None), ("tk_sh_in", 3)])
+ @pytest.mark.parametrize(
+ ("template", "result"),
+ [
+ (ProcessInputs(bore=5, shldith=5, i_tf_wp_geom=2), (5, 5, 2)),
+ ],
+ )
+ def test_indat_creation_with_template(self, template, result, pf_n, pf_v, tmp_path):
+ if pf_n is None:
+ pf = {}
+ else:
+ with open(utils.PARAM_FILE) as pf_h:
+ pf = {pf_n: json.load(pf_h)[pf_n]}
+ pf[pf_n]["value"] = pf_v
+ result = (result[0], pf_v, result[2])
+ path = tmp_path / "IN.DAT"
+ build_config = {
+ "in_dat_path": path,
+ "template_in_dat": template,
+ }
+
+ solver = Solver(pf, build_config)
+ solver.params.mappings["tk_sh_in"].send = True
+ solver.run_cls = lambda *_, **_kw: None
+ solver.teardown_cls = lambda *_, **_kw: None
+ solver.execute("run")
+
+ assert f"bore = {result[0]}" in open(path).read() # noqa: SIM115
+ assert f"shldith = {result[1]}" in open(path).read() # noqa: SIM115
+ assert f"i_tf_wp_geom = {result[2]}" in open(path).read() # noqa: SIM115
diff --git a/tests/codes/process/test_teardown.py b/tests/codes/process/test_teardown.py
index ae3d9a020c..c401cd5139 100644
--- a/tests/codes/process/test_teardown.py
+++ b/tests/codes/process/test_teardown.py
@@ -59,6 +59,7 @@ def test_run_func_updates_bluemira_params_from_mfile(self, run_func):
# Expected value comes from ./test_data/mfile_data.json
assert teardown.params.tau_e.value == pytest.approx(4.3196)
+ assert teardown.params.P_el_net.value == pytest.approx(6e8)
@pytest.mark.parametrize("run_func", ["read", "readall"])
def test_read_func_updates_bluemira_params_from_mfile(self, run_func):
@@ -70,7 +71,7 @@ def test_read_func_updates_bluemira_params_from_mfile(self, run_func):
# Expected value comes from ./test_data/mfile_data.json
assert teardown.params.tau_e.value == pytest.approx(4.3196)
# auto unit conversion
- assert teardown.params.P_el_net.value == pytest.approx(5e8)
+ assert teardown.params.P_el_net.value == pytest.approx(6e8)
def test_read_unknown_outputs_set_to_nan(self):
"""
diff --git a/tests/codes/process/test_template_builder.py b/tests/codes/process/test_template_builder.py
new file mode 100644
index 0000000000..918e200835
--- /dev/null
+++ b/tests/codes/process/test_template_builder.py
@@ -0,0 +1,547 @@
+# bluemira is an integrated inter-disciplinary design tool for future fusion
+# reactors. It incorporates several modules, some of which rely on other
+# codes, to carry out a range of typical conceptual fusion reactor design
+# activities.
+#
+# Copyright (C) 2021-2023 M. Coleman, J. Cook, F. Franza, I.A. Maione, S. McIntosh,
+# J. Morris, D. Short
+#
+# bluemira is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# bluemira is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with bluemira; if not, see .
+
+"""
+Test PROCESS template builder
+"""
+import os
+from pathlib import Path
+
+import numpy as np
+import pytest
+
+from bluemira.base.constants import EPS
+from bluemira.base.file import try_get_bluemira_private_data_root
+from bluemira.codes.process._equation_variable_mapping import Constraint, Objective
+from bluemira.codes.process._model_mapping import (
+ AlphaPressureModel,
+ AvailabilityModel,
+ BetaLimitModel,
+ BootstrapCurrentScalingLaw,
+ CSSuperconductorModel,
+ ConfinementTimeScalingLaw,
+ CostModel,
+ CurrentDriveEfficiencyModel,
+ DensityLimitModel,
+ EPEDScalingModel,
+ FISPACTSwitchModel,
+ OperationModel,
+ OutputCostsSwitch,
+ PFSuperconductorModel,
+ PROCESSOptimisationAlgorithm,
+ PlasmaCurrentScalingLaw,
+ PlasmaGeometryModel,
+ PlasmaNullConfigurationModel,
+ PlasmaPedestalModel,
+ PlasmaProfileModel,
+ PowerFlowModel,
+ PrimaryPumpingModel,
+ SecondaryCycleModel,
+ ShieldThermalHeatUse,
+ SolenoidSwitchModel,
+ TFNuclearHeatingModel,
+ TFSuperconductorModel,
+ TFWindingPackTurnModel,
+)
+from bluemira.codes.process.api import ENABLED, Impurities
+from bluemira.codes.process.template_builder import PROCESSTemplateBuilder
+from bluemira.utilities.tools import compare_dicts
+
+
+def extract_warning(caplog):
+ result = [line for message in caplog.messages for line in message.split(os.linesep)]
+ result = " ".join(result)
+ caplog.clear()
+ return result
+
+
+class TestPROCESSTemplateBuilder:
+ def test_no_error_on_nothing(self, caplog):
+ t = PROCESSTemplateBuilder()
+ _ = t.make_inputs()
+ assert len(caplog.messages) == 0
+
+ def test_warn_on_optimisation_with_no_objective(self, caplog):
+ t = PROCESSTemplateBuilder()
+ t.set_optimisation_algorithm(PROCESSOptimisationAlgorithm.VMCON)
+ _ = t.make_inputs()
+ assert len(caplog.messages) == 1
+ warning = extract_warning(caplog)
+ assert "You are running in optimisation mode, but" in warning
+
+ @pytest.mark.parametrize(
+ "objective",
+ [Objective.FUSION_GAIN_PULSE_LENGTH, Objective.MAJOR_RADIUS_PULSE_LENGTH],
+ )
+ def test_error_on_maxmin_objective(self, objective):
+ t = PROCESSTemplateBuilder()
+ with pytest.raises(
+ ValueError, match="can only be used as a minimisation objective"
+ ):
+ t.set_maximisation_objective(objective)
+
+ @pytest.mark.parametrize("bad_name", ["spoon", "aaaaaaaaaaaaa"])
+ def test_error_on_bad_itv_name(self, bad_name):
+ t = PROCESSTemplateBuilder()
+ with pytest.raises(ValueError, match="There is no iteration variable:"):
+ t.add_variable(bad_name, 3.14159)
+
+ @pytest.mark.parametrize("bad_name", ["spoon", "aaaaaaaaaaaaa"])
+ def test_error_on_adjusting_bad_variable(self, bad_name):
+ t = PROCESSTemplateBuilder()
+ with pytest.raises(ValueError, match="There is no iteration variable:"):
+ t.adjust_variable(bad_name, 3.14159)
+
+ def test_warn_on_repeated_constraint(self, caplog):
+ t = PROCESSTemplateBuilder()
+ t.add_constraint(Constraint.BETA_CONSISTENCY)
+ t.add_constraint(Constraint.BETA_CONSISTENCY)
+ assert len(caplog.messages) == 1
+ warning = extract_warning(caplog)
+ assert "is already in" in warning
+
+ def test_warn_on_repeated_itv(self, caplog):
+ t = PROCESSTemplateBuilder()
+ t.add_variable("bore", 2.0)
+ t.add_variable("bore", 3.0)
+ assert len(caplog.messages) == 1
+ warning = extract_warning(caplog)
+ assert "Iteration variable 'bore' is already" in warning
+
+ def test_warn_on_adjusting_nonexistent_variable(self, caplog):
+ t = PROCESSTemplateBuilder()
+ t.adjust_variable("bore", 2.0)
+ assert len(caplog.messages) == 1
+ warning = extract_warning(caplog)
+ assert "Iteration variable 'bore' is not in" in warning
+ assert "bore" in t.variables
+
+ def test_warn_on_missing_input_constraint(self, caplog):
+ t = PROCESSTemplateBuilder()
+ t.add_constraint(Constraint.NWL_UPPER_LIMIT)
+ t.add_variable("aspect", 3.1)
+ t.add_variable("bt", 5.0)
+ t.add_variable("rmajor", 9.0)
+ t.add_variable("te", 12.0)
+ t.add_variable("dene", 8.0e19)
+ _ = t.make_inputs()
+ assert len(caplog.messages) == 1
+ warning = extract_warning(caplog)
+ assert "requires inputs 'walalw'" in warning
+
+ def test_warn_on_missing_itv_constraint(self, caplog):
+ t = PROCESSTemplateBuilder()
+ t.add_constraint(Constraint.RADIAL_BUILD_CONSISTENCY)
+ _ = t.make_inputs()
+ assert len(caplog.messages) == 1
+ assert "requires iteration" in extract_warning(caplog)
+
+ def test_no_warn_on_missing_itv_constraint_but_as_input(self, caplog):
+ t = PROCESSTemplateBuilder()
+ t.add_constraint(Constraint.NWL_UPPER_LIMIT)
+ t.add_variable("bt", 5.0)
+ t.add_variable("rmajor", 9.0)
+ t.add_variable("te", 12.0)
+ t.add_variable("dene", 8.0e19)
+ t.add_input_value("walalw", 8.0)
+ t.add_input_value("aspect", 3.1)
+ _ = t.make_inputs()
+ assert len(caplog.messages) == 0
+
+ def test_warn_on_missing_input_model(self, caplog):
+ t = PROCESSTemplateBuilder()
+ t.set_model(PlasmaGeometryModel.CREATE_A_M_S)
+ _ = t.make_inputs()
+ assert len(caplog.messages) == 1
+ assert "requires inputs" in extract_warning(caplog)
+
+ def test_automatic_fvalue_itv(self):
+ t = PROCESSTemplateBuilder()
+ t.set_minimisation_objective(Objective.MAJOR_RADIUS)
+ t.add_constraint(Constraint.NET_ELEC_LOWER_LIMIT)
+ assert "fpnetel" in t.variables
+
+ def test_warn_on_overwrite_value(self, caplog):
+ t = PROCESSTemplateBuilder()
+ t.add_input_value("dummy", 1.0)
+ t.add_input_value("dummy", 2.0)
+ assert len(caplog.messages) == 1
+ assert "Over-writing" in extract_warning(caplog)
+
+ def test_warn_on_overwrite_model(self, caplog):
+ t = PROCESSTemplateBuilder()
+ t.set_model(PlasmaGeometryModel.CREATE_A_M_S)
+ t.set_model(PlasmaGeometryModel.FIESTA_100)
+ assert len(caplog.messages) == 1
+ assert "Over-writing" in extract_warning(caplog)
+
+ def test_impurity_shenanigans(self):
+ t = PROCESSTemplateBuilder()
+ t.add_impurity(Impurities.Xe, 0.5)
+ assert t.fimp[12] == pytest.approx(0.5, rel=0, abs=EPS)
+ t.add_variable("fimp(13)", 0.6)
+ assert t.fimp[12] == pytest.approx(0.6, rel=0, abs=EPS)
+ t.add_impurity(Impurities.Xe, 0.4)
+ assert t.fimp[12] == pytest.approx(0.4, rel=0, abs=EPS)
+
+ def test_input_appears_in_dat(self):
+ t = PROCESSTemplateBuilder()
+ t.add_input_value("tinstf", 1000.0)
+ assert t.values["tinstf"] == pytest.approx(1000.0, rel=0, abs=EPS)
+ data = t.make_inputs()
+ assert data.to_invariable()["tinstf"]._value == pytest.approx(
+ 1000.0, rel=0, abs=EPS
+ )
+
+ def test_inputs_appear_in_dat(self):
+ t = PROCESSTemplateBuilder()
+ t.add_input_values({"tinstf": 1000.0, "bore": 1000})
+ assert t.values["tinstf"] == pytest.approx(1000.0, rel=0, abs=EPS)
+ assert t.values["bore"] == pytest.approx(1000.0, rel=0, abs=EPS)
+ data = t.make_inputs().to_invariable()
+ assert data["tinstf"]._value == pytest.approx(1000.0, rel=0, abs=EPS)
+ assert data["bore"]._value == pytest.approx(1000.0, rel=0, abs=EPS)
+
+
+def read_indat(filename):
+ from process.io.in_dat import InDat
+
+ naughties = ["runtitle", "pulsetimings"]
+ data = InDat(filename=filename).data
+ return {k: v for k, v in data.items() if k not in naughties}
+
+
+@pytest.mark.private
+@pytest.mark.skipif(not ENABLED, reason="PROCESS is not installed on the system.")
+class TestInDatOneForOne:
+ @classmethod
+ def setup_class(cls):
+ fp = Path(
+ try_get_bluemira_private_data_root(),
+ "process/DEMO_2023_TEMPLATE_TEST_IN.DAT",
+ )
+
+ cls.true_data = read_indat(fp)
+
+ template_builder = PROCESSTemplateBuilder()
+ template_builder.set_optimisation_algorithm(PROCESSOptimisationAlgorithm.VMCON)
+ template_builder.set_optimisation_numerics(max_iterations=1000, tolerance=1e-8)
+
+ template_builder.set_minimisation_objective(Objective.MAJOR_RADIUS)
+
+ for constraint in (
+ Constraint.BETA_CONSISTENCY,
+ Constraint.GLOBAL_POWER_CONSISTENCY,
+ Constraint.DENSITY_UPPER_LIMIT,
+ Constraint.NWL_UPPER_LIMIT,
+ Constraint.RADIAL_BUILD_CONSISTENCY,
+ Constraint.BURN_TIME_LOWER_LIMIT,
+ Constraint.LH_THRESHHOLD_LIMIT,
+ Constraint.NET_ELEC_LOWER_LIMIT,
+ Constraint.BETA_UPPER_LIMIT,
+ Constraint.CS_EOF_DENSITY_LIMIT,
+ Constraint.CS_BOP_DENSITY_LIMIT,
+ Constraint.PINJ_UPPER_LIMIT,
+ Constraint.TF_CASE_STRESS_UPPER_LIMIT,
+ Constraint.TF_JACKET_STRESS_UPPER_LIMIT,
+ Constraint.TF_JCRIT_RATIO_UPPER_LIMIT,
+ Constraint.TF_DUMP_VOLTAGE_UPPER_LIMIT,
+ Constraint.TF_CURRENT_DENSITY_UPPER_LIMIT,
+ Constraint.TF_T_MARGIN_LOWER_LIMIT,
+ Constraint.CS_T_MARGIN_LOWER_LIMIT,
+ Constraint.CONFINEMENT_RATIO_LOWER_LIMIT,
+ Constraint.DUMP_TIME_LOWER_LIMIT,
+ Constraint.PSEPB_QAR_UPPER_LIMIT,
+ Constraint.CS_STRESS_UPPER_LIMIT,
+ Constraint.DENSITY_PROFILE_CONSISTENCY,
+ Constraint.CS_FATIGUE,
+ ):
+ template_builder.add_constraint(constraint)
+
+ # Variable vector values and bounds
+ template_builder.add_variable("bt", 5.3292, upper_bound=20.0)
+ template_builder.add_variable("rmajor", 8.8901, upper_bound=13)
+ template_builder.add_variable("te", 12.33, upper_bound=150.0)
+ template_builder.add_variable("beta", 3.1421e-2)
+ template_builder.add_variable("dene", 7.4321e19)
+ template_builder.add_variable("q", 3.5, lower_bound=3.5)
+ template_builder.add_variable("pheat", 50.0)
+ template_builder.add_variable("ralpne", 6.8940e-02)
+ template_builder.add_variable("bore", 2.3322, lower_bound=0.1)
+ template_builder.add_variable("ohcth", 0.55242, lower_bound=0.1)
+ template_builder.add_variable("thwcndut", 8.0e-3, lower_bound=8.0e-3)
+ template_builder.add_variable("thkcas", 0.52465)
+ template_builder.add_variable("tfcth", 1.2080)
+ template_builder.add_variable("gapoh", 0.05, lower_bound=0.05, upper_bound=0.1)
+ template_builder.add_variable("gapds", 0.02, lower_bound=0.02)
+ template_builder.add_variable("oh_steel_frac", 0.57875)
+ template_builder.add_variable("coheof", 2.0726e07)
+ template_builder.add_variable(
+ "cpttf", 6.5e4, lower_bound=6.0e4, upper_bound=9.0e4
+ )
+ template_builder.add_variable("tdmptf", 2.5829e01)
+ template_builder.add_variable("vdalw", 10.0, upper_bound=10.0)
+ template_builder.add_variable("fimp(13)", 3.573e-04)
+
+ # Some constraints require multiple f-values, but they are getting
+ # ridding of those, so no fancy mechanics for now...
+ template_builder.add_variable(
+ "fcutfsu", 0.80884, lower_bound=0.5, upper_bound=0.94
+ )
+ template_builder.add_variable("fcohbop", 0.93176)
+ template_builder.add_variable("fvsbrnni", 0.39566)
+ template_builder.add_variable("fncycle", 1.0)
+
+ # Modified f-values and bounds w.r.t. defaults
+ template_builder.adjust_variable("fne0", 0.6, upper_bound=0.95)
+ template_builder.adjust_variable("fdene", 1.2, upper_bound=1.2)
+ template_builder.adjust_variable(
+ "flhthresh", 1.2, lower_bound=1.1, upper_bound=1.2
+ )
+ template_builder.adjust_variable("ftburn", 1.0, upper_bound=1.0)
+
+ # Modifying the initial variable vector to improve convergence
+ template_builder.adjust_variable("fpnetel", 1.0)
+ template_builder.adjust_variable("fstrcase", 1.0)
+ template_builder.adjust_variable("ftmargtf", 1.0)
+ template_builder.adjust_variable("ftmargoh", 1.0)
+ template_builder.adjust_variable("ftaulimit", 1.0)
+ template_builder.adjust_variable("fjohc", 0.57941, upper_bound=1.0)
+ template_builder.adjust_variable("fjohc0", 0.53923, upper_bound=1.0)
+ template_builder.adjust_variable("foh_stress", 1.0)
+ template_builder.adjust_variable("fbetatry", 0.48251)
+ template_builder.adjust_variable("fwalld", 0.131)
+ template_builder.adjust_variable("fmaxvvstress", 1.0)
+ template_builder.adjust_variable("fpsepbqar", 1.0)
+ template_builder.adjust_variable("fvdump", 1.0)
+ template_builder.adjust_variable("fstrcond", 0.92007)
+ template_builder.adjust_variable("fiooic", 0.63437, upper_bound=1.0)
+ template_builder.adjust_variable("fjprot", 1.0)
+
+ # Set model switches
+ for model_choice in (
+ BootstrapCurrentScalingLaw.SAUTER,
+ ConfinementTimeScalingLaw.IPB98_Y2_H_MODE,
+ PlasmaCurrentScalingLaw.ITER_REVISED,
+ PlasmaProfileModel.CONSISTENT,
+ PlasmaPedestalModel.PEDESTAL_GW,
+ PlasmaNullConfigurationModel.SINGLE_NULL,
+ EPEDScalingModel.SAARELMA,
+ BetaLimitModel.THERMAL,
+ DensityLimitModel.GREENWALD,
+ AlphaPressureModel.WARD,
+ PlasmaGeometryModel.CREATE_A_M_S,
+ PowerFlowModel.SIMPLE,
+ ShieldThermalHeatUse.LOW_GRADE_HEAT,
+ SecondaryCycleModel.INPUT,
+ CurrentDriveEfficiencyModel.ECRH_UI_GAM,
+ OperationModel.PULSED,
+ PFSuperconductorModel.NBTI,
+ SolenoidSwitchModel.SOLENOID,
+ CSSuperconductorModel.NB3SN_WST,
+ TFSuperconductorModel.NB3SN_WST,
+ TFWindingPackTurnModel.INTEGER_TURN,
+ FISPACTSwitchModel.OFF,
+ PrimaryPumpingModel.PRESSURE_DROP_INPUT,
+ TFNuclearHeatingModel.INPUT,
+ CostModel.TETRA_1990,
+ AvailabilityModel.INPUT,
+ OutputCostsSwitch.NO,
+ ):
+ template_builder.set_model(model_choice)
+
+ template_builder.add_impurity(Impurities.H, 1.0)
+ template_builder.add_impurity(Impurities.He, 0.1)
+ template_builder.add_impurity(Impurities.W, 5.0e-5)
+
+ # Set fixed input values
+ template_builder.add_input_values(
+ {
+ # Undocumented danger stuff
+ "iblanket": 1,
+ "lsa": 2,
+ # Profile parameterisation inputs
+ "alphan": 1.0,
+ "alphat": 1.45,
+ "rhopedn": 0.94,
+ "rhopedt": 0.94,
+ "tbeta": 2.0,
+ "teped": 5.5,
+ "tesep": 0.1,
+ "fgwped": 0.85,
+ "neped": 0.678e20,
+ "nesep": 0.2e20,
+ "dnbeta": 3.0,
+ # Plasma impurity stuff
+ "coreradius": 0.75,
+ "coreradiationfraction": 0.6,
+ # Important stuff
+ "pnetelin": 500.0,
+ "tbrnmn": 7.2e3,
+ "sig_tf_case_max": 5.8e8,
+ "sig_tf_wp_max": 5.8e8,
+ "alstroh": 6.6e8,
+ "psepbqarmax": 9.2,
+ "aspect": 3.1,
+ "m_s_limit": 0.1,
+ "triang": 0.5,
+ "q0": 1.0,
+ "ssync": 0.6,
+ "plasma_res_factor": 0.66,
+ "gamma": 0.3,
+ "hfact": 1.1,
+ "life_dpa": 70.0,
+ # Radial build inputs
+ "tftsgap": 0.05,
+ "d_vv_in": 0.3,
+ "shldith": 0.3,
+ "vvblgap": 0.02,
+ "blnkith": 0.755,
+ "scrapli": 0.225,
+ "scraplo": 0.225,
+ "blnkoth": 0.982,
+ "d_vv_out": 0.3,
+ "shldoth": 0.8,
+ "ddwex": 0.15,
+ "gapomin": 0.2,
+ # Vertical build inputs
+ "d_vv_top": 0.3,
+ "vgap2": 0.05,
+ "shldtth": 0.3,
+ "divfix": 0.621,
+ "d_vv_bot": 0.3,
+ # HCD inputs
+ "pinjalw": 51.0,
+ "gamma_ecrh": 0.3,
+ "etaech": 0.4,
+ "bscfmax": 0.99,
+ # BOP inputs
+ "etath": 0.375,
+ "etahtp": 0.87,
+ "etaiso": 0.9,
+ "vfshld": 0.6,
+ "tdwell": 0.0,
+ "tramp": 500.0,
+ # CS / PF coil inputs
+ "t_crack_vertical": 0.4e-3,
+ "fcuohsu": 0.7,
+ "ohhghf": 0.9,
+ "rpf2": -1.825,
+ "cptdin": [4.22e4, 4.22e4, 4.22e4, 4.22e4, 4.3e4, 4.3e4, 4.3e4, 4.3e4],
+ "ipfloc": [2, 2, 3, 3],
+ "ncls": [1, 1, 2, 2],
+ "ngrp": 4,
+ "rjconpf": [1.1e7, 1.1e7, 6.0e6, 6.0e6, 8.0e6, 8.0e6, 8.0e6, 8.0e6],
+ # TF coil inputs
+ "n_tf": 16,
+ "casthi": 0.06,
+ "casths": 0.05,
+ "ripmax": 0.6,
+ "dhecoil": 0.01,
+ "tftmp": 4.75,
+ "thicndut": 2.0e-3,
+ "tinstf": 0.008,
+ # "tfinsgap": 0.01,
+ "tmargmin": 1.5,
+ "vftf": 0.3,
+ "n_pancake": 20,
+ "n_layer": 10,
+ "qnuc": 1.292e4,
+ # Inputs we don't care about but must specify
+ "cfactr": 0.75, # Ha!
+ "kappa": 1.848, # Should be overwritten
+ "walalw": 8.0, # Should never get even close to this
+ "tlife": 40.0,
+ "abktflnc": 15.0,
+ "adivflnc": 20.0,
+ # For sanity...
+ "hldivlim": 10,
+ "ksic": 1.4,
+ "prn1": 0.4,
+ "zeffdiv": 3.5,
+ "bmxlim": 11.2,
+ "ffuspow": 1.0,
+ "fpeakb": 1.0,
+ "divdum": 1,
+ "ibkt_life": 1,
+ "fkzohm": 1.0245,
+ "iinvqd": 1,
+ "dintrt": 0.0,
+ "fcap0": 1.15,
+ "fcap0cp": 1.06,
+ "fcontng": 0.15,
+ "fcr0": 0.065,
+ "fkind": 1.0,
+ "ifueltyp": 1,
+ "discount_rate": 0.06,
+ "bkt_life_csf": 1,
+ "ucblvd": 280.0,
+ "ucdiv": 5e5,
+ "ucme": 3.0e8,
+ # Suspicous stuff
+ "zref": [3.6, 1.2, 1.0, 2.8, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
+ "fpinj": 1.0,
+ }
+ )
+
+ cls.template = template_builder.make_inputs().to_invariable()
+
+ def test_indat_bounds_the_same(self):
+ true_bounds = self.true_data.pop("bounds").get_value
+ new_bounds = self.template.pop("bounds").get_value
+
+ # Make everything floats for easier comparison
+ for k in true_bounds:
+ for kk in true_bounds[k]:
+ true_bounds[k][kk] = float(true_bounds[k][kk])
+ for k in new_bounds:
+ for kk in new_bounds[k]:
+ new_bounds[k][kk] = float(new_bounds[k][kk])
+ assert compare_dicts(true_bounds, new_bounds)
+
+ def test_indat_constraints(self):
+ true_cons = self.true_data.pop("icc").get_value
+ new_cons = self.template.pop("icc").get_value
+
+ np.testing.assert_allclose(sorted(true_cons), sorted(new_cons))
+
+ def test_indat_variables(self):
+ true_vars = self.true_data.pop("ixc").get_value
+ new_vars = self.template.pop("ixc").get_value
+
+ np.testing.assert_allclose(sorted(true_vars), sorted(new_vars))
+
+ def test_inputs_same(self):
+ for k in self.true_data:
+ if not isinstance(self.true_data[k].get_value, (list, dict)):
+ assert np.allclose(
+ self.true_data[k].get_value, self.template[k].get_value
+ )
+ elif isinstance(self.true_data[k].get_value, dict):
+ compare_dicts(self.true_data[k].get_value, self.template[k]._value)
+ else:
+ assert not set(self.true_data[k].get_value) - set(
+ self.template[k].get_value
+ )
+
+ def test_no_extra_inputs(self):
+ for k in self.template:
+ assert k in self.true_data
diff --git a/tests/codes/test_interface.py b/tests/codes/test_interface.py
index 5457d26558..e621dbf505 100644
--- a/tests/codes/test_interface.py
+++ b/tests/codes/test_interface.py
@@ -47,7 +47,7 @@ class Params(MappedParameterFrame):
_mappings: ClassVar = {
"param1": ParameterMapping("ext1", send=True, recv=True, unit="MW"),
- "param2": ParameterMapping("ext2", send=False, recv=False),
+ "param2": ParameterMapping("ext2", "ext3", send=False, recv=False),
}
@property
@@ -87,3 +87,7 @@ def test_no_defaults_are_set_to_None(self):
assert params.param2.value is None
assert params.param1.unit == "W"
assert params.param2.unit == ""
+ assert params.mappings["param1"].name == "ext1"
+ assert params.mappings["param1"].out_name == "ext1"
+ assert params.mappings["param2"].name == "ext2"
+ assert params.mappings["param2"].out_name == "ext3"
diff --git a/tests/test_data/reactors/BLUEPRINT-INTEGRATION-TEST/systems_code/mockPROCESS.json b/tests/test_data/reactors/BLUEPRINT-INTEGRATION-TEST/systems_code/mockPROCESS.json
index 69c50edb80..1a6b06c083 100644
--- a/tests/test_data/reactors/BLUEPRINT-INTEGRATION-TEST/systems_code/mockPROCESS.json
+++ b/tests/test_data/reactors/BLUEPRINT-INTEGRATION-TEST/systems_code/mockPROCESS.json
@@ -8,7 +8,6 @@
"P_bd_in": 50.0,
"P_brehms": 59.668,
"P_el_net": 500,
- "P_el_net_process": 500.0,
"P_fus": 1994.6,
"P_fus_DD": 2.4746,
"P_fus_DT": 1992.1,
diff --git a/tests/test_data/reactors/SMOKE-TEST-EU-DEMO/systems_code/mockPROCESS.json b/tests/test_data/reactors/SMOKE-TEST-EU-DEMO/systems_code/mockPROCESS.json
index daefd7f2eb..58be6e21eb 100644
--- a/tests/test_data/reactors/SMOKE-TEST-EU-DEMO/systems_code/mockPROCESS.json
+++ b/tests/test_data/reactors/SMOKE-TEST-EU-DEMO/systems_code/mockPROCESS.json
@@ -5,7 +5,7 @@
"H_star": 1.1,
"I_p": 19.117,
"P_brehms": 60.956,
- "P_el_net_process": 500.0,
+ "P_el_net": 500.0,
"P_fus": 1790.6,
"P_fus_DD": 2.1206,
"P_fus_DT": 1788.5,