From 7a0a76f39ed521d2d0d4226f87bae55864bfe9f9 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 22 May 2026 15:52:00 +0100 Subject: [PATCH 1/9] Add FiguresOfMerit enum and update documentation for figure of merit usage --- documentation/source/development/add-vars.md | 4 +- documentation/source/io/input-guide.md | 6 +- process/core/init.py | 5 +- process/core/io/plot/solutions.py | 3 +- process/core/io/plot/summary.py | 6 +- process/core/scan.py | 3 +- process/data_structure/numerics.py | 105 ++++++++++--------- 7 files changed, 67 insertions(+), 65 deletions(-) diff --git a/documentation/source/development/add-vars.md b/documentation/source/development/add-vars.md index 5e4f16160f..64d057991c 100644 --- a/documentation/source/development/add-vars.md +++ b/documentation/source/development/add-vars.md @@ -80,9 +80,9 @@ ITERATION_VARIABLES = { New figures of merit are added to `PROCESS` in the following way: -1. Increment the parameter `ipnfoms` in module `numerics` in source file `numerics.f90` to accommodate the new figure of merit. +1. Increment the parameter `ipnfoms` in module `numerics` in source file `numerics.py` to accommodate the new figure of merit. -2. Assign a description of the new figure of merit to the relevant element of array `lablmm` in module `numerics` in the source file `numerics.f90`. +2. Assign the new integer value and description string of the new figure of merit to the `FiguresOfMerit` enumerator in `numerics.py`. 3. Add the new figure of merit equation to `objective_function()` in `objectives.py`, following the method used in the existing examples. The value of figure of merit case should be of order unity, so select a reasonable scaling factor if necessary. diff --git a/documentation/source/io/input-guide.md b/documentation/source/io/input-guide.md index 4176d5ba1b..e55ce939c9 100644 --- a/documentation/source/io/input-guide.md +++ b/documentation/source/io/input-guide.md @@ -118,7 +118,7 @@ ioptimz = 1 * for optimisation VMCON only The user can select the figure of merit to be used: ``` -minmax = 1 * Switch for figure-of-merit (see lablmm for descriptions) +minmax = 1 * Switch for figure-of-merit (see `FiguresOfMerit` for descriptions) ``` In this case the user is choosing option `1`, which is major radius. For `minmax` @@ -132,10 +132,6 @@ The user can also input the allowed error tolerance on the solver solution: epsvmc = 1.0e-8 * Error tolerance for vmcon ``` -!!! Info "Figure of Merit" - A full list of figures of merit is given on the variable description page in the row labelled - `lablmm` [here](../../source/reference/process/data_structure/numerics/#process.data_structure.numerics.lablmm). - ## Input Variables One can enter an input into the `IN.DAT` by: diff --git a/process/core/init.py b/process/core/init.py index 1ecc36d607..e042e0cfd1 100644 --- a/process/core/init.py +++ b/process/core/init.py @@ -17,6 +17,7 @@ from process.core.solver import iteration_variables from process.core.solver.constraints import ConstraintManager from process.data_structure.impurity_radiation_variables import N_IMPURITIES +from process.data_structure.numerics import FiguresOfMerit from process.data_structure.physics_variables import ( DivertorNumberModels, init_physics_module, @@ -191,9 +192,7 @@ def run_summary(): minmax_string = " -- maximise " minmax_sign = "-" - fom_string = data_structure.numerics.lablmm[ - abs(data_structure.numerics.minmax) - 1 - ] + fom_string = FiguresOfMerit(abs(data_structure.numerics.minmax)).description process_output.ocmmnt( outfile, f"Figure of merit : {minmax_sign}{abs(data_structure.numerics.minmax)}{minmax_string}{fom_string}", diff --git a/process/core/io/plot/solutions.py b/process/core/io/plot/solutions.py index 1e3848e13d..cc875e895b 100644 --- a/process/core/io/plot/solutions.py +++ b/process/core/io/plot/solutions.py @@ -22,6 +22,7 @@ from process.core.io.mfile import MFile from process.data_structure import numerics +from process.data_structure.numerics import FiguresOfMerit # Variables of interest in mfiles and subsequent dataframes # Be specific about exact names, patterns and regex @@ -446,7 +447,7 @@ def _plot_solutions( else: numerics.init_numerics() objf_list = { - numerics.lablmm[int(abs(minmax)) - 1] for minmax in diffs_df["minmax"] + FiguresOfMerit(abs(minmax)).description for minmax in diffs_df["minmax"] } if len(objf_list) != 1: diff --git a/process/core/io/plot/summary.py b/process/core/io/plot/summary.py index a0180e00c4..8389fa89aa 100644 --- a/process/core/io/plot/summary.py +++ b/process/core/io/plot/summary.py @@ -11596,10 +11596,10 @@ def plot_cover_page( objective_text = "" elif minmax_switch >= 0: minmax_switch = int(minmax_switch) - objective_text = f"• Minimising {objf_name}" + objective_text = f" -> Minimising: {objf_name}" else: minmax_switch = int(minmax_switch) - objective_text = f"• Maximising {objf_name}" + objective_text = f" -> Maximising: {objf_name}" axis.text( 0.1, @@ -11661,9 +11661,9 @@ def plot_cover_page( settings_info = ( f"• Optimisation Switch: {int(optmisation_switch)}\n" f"• Figure of Merit Switch (minmax): {minmax_switch}\n" + f" {objective_text}\n" f"• Fail Status (ifail): {int(ifail)}\n" f"• Number of Iteration Variables: {int(nvars)}\n" - f"{objective_text}\n" f"• Constraint Residuals (sqrt sum sq): {sqsumsq}\n" f"• Convergence Parameter: {convergence_parameter}\n" f"• Solver Iterations: {nviter}\n" diff --git a/process/core/scan.py b/process/core/scan.py index 87cf5cff6e..80f22848ff 100644 --- a/process/core/scan.py +++ b/process/core/scan.py @@ -23,6 +23,7 @@ scan_variables, tfcoil_variables, ) +from process.data_structure.numerics import FiguresOfMerit if TYPE_CHECKING: from process.core.model import DataStructure, Model @@ -353,7 +354,7 @@ def post_optimise(self, ifail: int): constants.NOUT, "Figure of merit switch", "(minmax)", numerics.minmax ) - objf_name = f'"{numerics.lablmm[abs(numerics.minmax) - 1]}"' + objf_name = f'"{FiguresOfMerit(abs(numerics.minmax)).description}"' numerics.objf_name = objf_name diff --git a/process/data_structure/numerics.py b/process/data_structure/numerics.py index 79f1feb671..7c1dec7851 100644 --- a/process/data_structure/numerics.py +++ b/process/data_structure/numerics.py @@ -1,5 +1,59 @@ +from enum import IntEnum +from types import DynamicClassAttribute + import numpy as np + +class FiguresOfMerit(IntEnum): + """Enumeration of the available figures of merit (FoM) that can be used as + objective functions for optimisation in PROCESS. + """ + + MAJOR_RADIUS = (1, "Plasma major radius") + NEUTRON_WALL_LOAD = (3, "Neutron wall load") + P_TF_PLUS_P_PF = (4, "P_tf + P_pf") + FUSION_GAIN_Q = (5, "Fusion gain Q") + COST_OF_ELECTRICITY = (6, "Cost of electricity") + CAPITAL_COST = (7, "Capital cost") + ASPECT_RATIO = (8, "Aspect ratio") + DIVERTOR_HEAT_LOAD = (9, "Divertor heat load") + TOROIDAL_FIELD = (10, "Toroidal field") + TOTAL_INJECTED_POWER = (11, "Total injected power") + PULSE_LENGTH = (14, "Pulse length") + PLANT_AVAILABILITY_FACTOR = (15, "Plant availability factor") + MIN_R0_MAX_TAU_BURN = ( + 16, + "Linear combination of major radius (minimised) and pulse length (maximised)", + ) + NET_ELECTRICAL_OUTPUT = (17, "Net electrical output") + NULL_FIGURE_OF_MERIT = (18, "Null Figure of Merit") + MAX_Q_MAX_T_PLANT_PULSE_BURN = ( + 19, + "Linear combination of big Q and pulse length (maximised)", + ) + + def __new__(cls, value: int, description: str): + """Create a new FiguresOfMerit enum member with description. + + Args: + value: The integer value of the enum member. + description: The description for this figure of merit. + + Returns + ------- + The new enum member with attached description. + """ + obj = int.__new__(cls, value) + obj._value_ = value + obj._description_ = description + return obj + + @DynamicClassAttribute + def description(self): + """Return the description for this figure of merit.""" + return self._description_ + + ipnvars: int = 177 """total number of variables available for iteration""" @@ -21,37 +75,10 @@ minmax: int = None """ -Switch for figure-of-merit (see lablmm for descriptions) +Switch for figure-of-merit (see `FiguresOfMerit` for descriptions) negative => maximise, positive => minimise """ -lablmm: list[str] = None -"""lablmm(ipnfoms) : labels describing figures of merit: