From 460a2319f61684a6db354489671575cd8ae1e206 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Wed, 3 Jun 2026 09:12:34 +0100 Subject: [PATCH 1/8] Remove Fortran references from docstrings and comments across multiple files --- process/core/caller.py | 3 +- process/core/input.py | 14 +------ process/core/io/data_structure_dicts.py | 1 - process/core/io/in_dat/base.py | 43 +--------------------- process/core/io/vary_run/tools.py | 1 - process/core/output.py | 1 - process/core/scan.py | 2 +- process/core/solver/iteration_variables.py | 6 +-- process/core/solver/solver_handler.py | 6 --- process/data_structure/numerics.py | 2 - process/main.py | 27 +++++--------- process/models/pfcoil.py | 6 +-- process/models/physics/physics.py | 2 +- process/models/tfcoil/base.py | 2 +- tests/unit/models/test_pfcoil.py | 5 +-- tracking/tracking_data.py | 3 -- 16 files changed, 21 insertions(+), 103 deletions(-) diff --git a/process/core/caller.py b/process/core/caller.py index 2dacd1309d..2c16f3a82b 100644 --- a/process/core/caller.py +++ b/process/core/caller.py @@ -247,8 +247,7 @@ def _call_models_once(self, xc: np.ndarray): """Call the physics and engineering models. This method is the principal caller of all the physics and - engineering models. Some are Fortran subroutines within modules, others - will be methods on Python model objects. + engineering models. Parameters ---------- diff --git a/process/core/input.py b/process/core/input.py index 946132889e..fa9d05506d 100644 --- a/process/core/input.py +++ b/process/core/input.py @@ -53,7 +53,7 @@ class InputVariable: """A variable to be parsed from the input file.""" module: Any - """The Fortran module that this variable should be set on.""" + """The module that this variable should be set on.""" type: type """The expected type of the variable""" range: tuple[NumberType, NumberType] | None = None @@ -1213,7 +1213,6 @@ def parse_input_file(data_structure_obj: DataStructure): continue # matches (variable name, array index, value) - # NOTE: array index is Fortran-based hence starts at 1. line_match = re.match( r"([a-zA-Z0-9_]+)(?:\(([0-9]+)\))?[ ]*=[ ]*([ +\-a-zA-Z0-9.,]+).*", stripped_line, @@ -1407,12 +1406,8 @@ def set_scalar_variable(name: str, value: ValidInputTypes, config: InputVariable """ current_value = getattr(config.module, name, ...) - # use ... sentinel because None is probably a valid return from Fortran - # and definately will be when moving to a Python data structure if current_value is ...: - error_msg = ( - f"Fortran module '{config.module}' does not have a variable '{name}'." - ) + error_msg = f"Module '{config.module}' does not have a variable '{name}'." raise ProcessValueError(error_msg) setattr(config.module, name, value) @@ -1421,11 +1416,6 @@ def set_scalar_variable(name: str, value: ValidInputTypes, config: InputVariable def set_array_variable(name: str, value: str, array_index: int, config: InputVariable): """Set an array variable in the `config.module`. - The way PROCESS input files are structured, each element of the array is provided on - one line - so this function just needs to set the `value` at `array_index` (-1) - because of Fortran-based indexing. - Parameters ---------- name : diff --git a/process/core/io/data_structure_dicts.py b/process/core/io/data_structure_dicts.py index 04da862a7d..e47c206911 100644 --- a/process/core/io/data_structure_dicts.py +++ b/process/core/io/data_structure_dicts.py @@ -51,7 +51,6 @@ def publish(self): class SourceDictionary(Dictionary): - # Dictionary created from Fortran source def __init__(self, name, dict_creator_func): Dictionary.__init__(self, name) # Function that creates the dict diff --git a/process/core/io/in_dat/base.py b/process/core/io/in_dat/base.py index 8a581473b1..d910dfaeef 100644 --- a/process/core/io/in_dat/base.py +++ b/process/core/io/in_dat/base.py @@ -1109,11 +1109,6 @@ def process_line(self, line_type, line): line_commentless = line.split("*")[0] array_name = line_commentless.split("(")[0] empty_array = dicts["DICT_DEFAULT"][array_name] - # empty_array is what the array is initialised to when it is - # declared in the Fortran. If the array is declared but not - # initialised until later in a separate "init" subroutine, then - # empty_array will be None. This is a side-effect of the need to - # re-initialise Fortran arrays in a separate subroutine. if empty_array is None: # Array is declared but not initialised at the same time; @@ -1355,61 +1350,25 @@ def process_array(self, line, empty_array): empty_array: Default array for this array name """ - # TODO This is a mess after the Fortran module variable - # re-initialisation work. It is hard to see how this can be improved - # as the regex method of Fortran inspection (i.e. Python-Fortran - # dictionaries) is increasingly untenable. An attempt is made here, - # but with a view to the dictionaries method being dropped in future - # in light of increasing Python f2py conversion. - line_commentless = line.split("*")[0] if "*" in line else line name = line_commentless.split("(")[0] index = int(line_commentless.split("(")[1].split(")")[0]) - 1 - # The -1 assumes all Fortran arrays begin at 1: they don't in Process! - # This bug would cause a Fortran index of 0 to be interpreted as a - # Python index of -1 (last element). This didn't cause any exceptions, - # but would obviously set the wrong list index. This now throws - # exceptions when trying to access [-1] of an empty list []. Hence it - # must be guarded against with the following: + if index == -1: index = 0 - # This will cause Fortran index 0 and 1 to overwrite the same Python - # index of 0, which is clearly awful. However, it is equally bad to the - # previous case above, where Fortran [0] would overwrite Python [-1]. - # There isn't a way of reconciling this without knowing whether the - # Fortran array begins at 0 or 1. - # TODO Either enforce Fortran arrays that start at 1 throughout, or - # find a way of determining the starting index of the array value = line_commentless.split("=")[-1].replace(",", "") - # Many arrays are now declared but not initialised until the "init" - # subroutine is run in each Fortran module, to allow re-initialisation - # on demand for a new run etc. - # In Fortran, we have a declared but uninitialised array of a given - # length. This results in an empty list in the value attribute here. - # However, we need to set the value for a given index. Therefore - # make a list of Nones, so that we can set the value for a given - # index. We don't know the length of the Fortran array (its value is - # []), so we have to make the Python list as long as this Fortran index. - # This way, at least the list is long enough for this Fortran index. - # TODO Again, this requires a more robust solution - list_len = len(self.data[name].value) # Length of Python list in self.data max_list_index = list_len - 1 # The greatest index in that Python list array_len = index + 1 - # The Fortran array must be at least this long - # If the default array is an empty list, make a list of Nones of - # matching length to the Fortran array if len(empty_array) == 0: empty_array = [None] * array_len - # If the Pyhton list is an empty list, make a list of Nones of - # matching length to the Fortran array if list_len == 0: self.data[name].value = [None] * array_len # Check the Python list is long enough to store the new index diff --git a/process/core/io/vary_run/tools.py b/process/core/io/vary_run/tools.py index 78a136e7c1..49f3561fda 100644 --- a/process/core/io/vary_run/tools.py +++ b/process/core/io/vary_run/tools.py @@ -349,7 +349,6 @@ def set_variable_in_indat(in_dat, varname, value): elif "(" in varname: name = varname.split("(")[0] - # Fortran numbering converted to Python numbering! identity = int((varname.split("("))[1].split(")")[0]) - 1 in_dat.change_array(name, identity, float(value)) diff --git a/process/core/output.py b/process/core/output.py index 82c070b44b..b640280316 100644 --- a/process/core/output.py +++ b/process/core/output.py @@ -16,7 +16,6 @@ def write(models, data, _outfile): models : process.main.Models physics and engineering model objects _outfile : int - Fortran output unit identifier """ # ensure we are capturing warnings that occur in the 'output' stage diff --git a/process/core/scan.py b/process/core/scan.py index 535eae7f36..75066b349a 100644 --- a/process/core/scan.py +++ b/process/core/scan.py @@ -187,7 +187,7 @@ def _missing_(cls, var): class Scan: - """Perform a parameter scan using the Fortran scan module.""" + """Perform a parameter scan using the scan module.""" def __init__(self, models: Model, solver: str, data: DataStructure): """Immediately run the run_scan() method. diff --git a/process/core/solver/iteration_variables.py b/process/core/solver/iteration_variables.py index 38f53a7e70..2dccb6a224 100644 --- a/process/core/solver/iteration_variables.py +++ b/process/core/solver/iteration_variables.py @@ -20,7 +20,7 @@ class IterationVariable: name: str """The name of the variable""" module: str | Any - """The Fortran module that this variable should be set on.""" + """The module that this variable should be set on.""" lower_bound: float """The default lower bound of the iteration variable""" upper_bound: float @@ -33,7 +33,6 @@ class IterationVariable: """If `module.name` is an array, the iteration variable can only modify `array_index` of that array. - NOTE: The indexes start at 0 (despite indexing Fortran arrays). """ @@ -271,9 +270,6 @@ def load_iteration_variables(data): variable_index = data.numerics.ixc[i] iteration_variable = ITERATION_VARIABLES[variable_index] - # use ... as the default return value because - # None might be a valid return from Fortran? - module = ( getattr(data, iteration_variable.module) if isinstance(iteration_variable.module, str) diff --git a/process/core/solver/solver_handler.py b/process/core/solver/solver_handler.py index b457e25ca9..326407b6eb 100644 --- a/process/core/solver/solver_handler.py +++ b/process/core/solver/solver_handler.py @@ -29,13 +29,9 @@ def __init__(self, models, solver_name, data): def run(self): """Run solver and retry if it fails in certain ways.""" - # Initialise iteration variables and bounds in Fortran load_iteration_variables(self.data) load_scaled_bounds(self.data) - # Initialise iteration variables and bounds in Python: relies on Fortran - # iteration variables being defined above - # Trim maximum size arrays down to actually used size n = self.data.numerics.nvar x = self.data.numerics.xcm[:n] bndl = self.data.numerics.itv_scaled_lower_bounds[:n] @@ -100,7 +96,5 @@ def output(self): Objective function value, solution vector and constraints vector. """ self.data.numerics.norm_objf = self.solver.objf - # Slicing required due to Fortran arrays being maximum possible, rather - # than required, size self.data.numerics.xcm[: self.solver.x.shape[0]] = self.solver.x self.data.numerics.rcm[: self.solver.conf.shape[0]] = self.solver.conf diff --git a/process/data_structure/numerics.py b/process/data_structure/numerics.py index 2bdec08e41..40ce4e9865 100644 --- a/process/data_structure/numerics.py +++ b/process/data_structure/numerics.py @@ -155,8 +155,6 @@ class NumericsData: active_constraints: list[bool] = field(default_factory=lambda: [False] * IPEQNS) """Logical array showing which constraints are active""" - # TODO Do not change the comments for lablcc: they are used to create the - # Python-Fortran dictionaries. This must be improved on. lablcc: list[str] = field( default_factory=lambda: [ diff --git a/process/main.py b/process/main.py index 551fe9f852..7e88874d9f 100644 --- a/process/main.py +++ b/process/main.py @@ -1,14 +1,10 @@ -"""Run Process by calling into the Fortran. +"""Run Process by calling into the Python module. This uses a Python module called fortran.py, which uses an extension module called "_fortran.cpython... .so", which are both generated from process_module.f90. The process_module module contains the code to actually run Process. -This file, process.py, is now analogous to process.f90, which contains the -Fortran "program" statement. This Python module effectively acts as the Fortran -"program". - Power Reactor Optimisation Code for Environmental and Safety Studies This is a systems code that evaluates various physics and @@ -24,9 +20,7 @@ The code was transferred to Culham Laboratory, Oxfordshire, UK, in April 1992, and the physics models were updated by P.J.Knight to include the findings of the Culham reactor studies documented in -Culham Report AEA FUS 172 (1992). The standard of the Fortran has -been thoroughly upgraded since that time, and a number of additional -models have been added. +Culham Report AEA FUS 172 (1992). During 2012, PROCESS was upgraded from FORTRAN 77 to Fortran 95, to facilitate the restructuring of the code into proper modules @@ -34,6 +28,8 @@ aid the inclusion of more advanced physics and engineering models under development as part of a number of EFDA-sponsored collaborations. +The code is now fully Python based. + Box file F/RS/CIRE5523/PWF (up to 15/01/96) Box file F/MI/PJK/PROCESS and F/PL/PJK/PROCESS (15/01/96 to 24/01/12) Box file T&M/PKNIGHT/PROCESS (from 24/01/12) @@ -354,7 +350,7 @@ def run(self): @staticmethod def init_module_vars(): - """Initialise all module variables in the Fortran. + """Initialise all module variables This "resets" all module variables to their initialised values, so each new run doesn't have any side-effects from previous runs. @@ -401,13 +397,12 @@ def set_input(self): "in the analysis folder", ) - # Set the input file in the Fortran self.data.globals.fileprefix = self.input_path.resolve() def set_output(self): """Set the output file name. - Set Path object on the Process object, and set the prefix in the Fortran. + Set Path object on the Process object """ self.output_path = Path(self.data.globals.output_prefix + "OUT.DAT") @@ -456,10 +451,8 @@ def show_errors(): @staticmethod def finish(): - """Run the finish subroutine to close files open in the Fortran. - - Files being handled by Fortran must be closed before attempting to - write to them using Python, otherwise only parts are written. + """Run the finish subroutine to close files that are open at the end of a run. + n. """ oheadr(constants.NOUT, "End of PROCESS Output") oheadr(constants.IOTTY, "End of PROCESS Output") @@ -610,14 +603,14 @@ def validate_user_model(self): class Models: """Creates instances of physics and engineering model classes. - Creates objects to interface with corresponding Fortran physics and + Creates objects to interface with corresponding physics and engineering modules. """ def __init__(self, data: DataStructure): """Create physics and engineering model objects. - This also initialises module variables in the Fortran for that module. + This also initialises module variables for each model. """ self.data = data diff --git a/process/models/pfcoil.py b/process/models/pfcoil.py index fc0cd99df6..73ec7ff49b 100644 --- a/process/models/pfcoil.py +++ b/process/models/pfcoil.py @@ -59,7 +59,7 @@ class PFCoil(Model): """Calculate poloidal field coil system parameters.""" def __init__(self, cs_fatigue, cs_coil): - """Initialise Fortran module variables.""" + """Initialise the PF coil model.""" self.outfile = constants.NOUT # output file unit self.mfile = constants.MFILE # mfile file unit self.cs_fatigue = cs_fatigue @@ -1784,8 +1784,6 @@ def induct(self, output): noh = min(noh, nohmax) - # TODO In FNSF case, noh = -7! noh should always be positive. Fortran - # array allocation with -ve bound previously coerced to 0 noh = max(noh, 0) roh = np.zeros(noh) @@ -2993,7 +2991,7 @@ class CSCoil(Model): """Calculate central solenoid coil system parameters.""" def __init__(self, cs_fatigue): - """Initialise Fortran module variables.""" + """Initialise the CS coil model.""" self.outfile = constants.NOUT # output file unit`` self.mfile = constants.MFILE # mfile file unit self.cs_fatigue = cs_fatigue diff --git a/process/models/physics/physics.py b/process/models/physics/physics.py index 3b1ec51d90..2f825db9a1 100644 --- a/process/models/physics/physics.py +++ b/process/models/physics/physics.py @@ -1740,7 +1740,7 @@ def calculate_effective_charge_ionisation_profiles(self): def outplas(self): """Subroutine to output the plasma physics information - self.outfile : input integer : Fortran output unit identifier + self.outfile : file to write to This routine writes the plasma physics information to a file, in a tidy format. """ diff --git a/process/models/tfcoil/base.py b/process/models/tfcoil/base.py index 60bb77f222..dd26b5c01f 100644 --- a/process/models/tfcoil/base.py +++ b/process/models/tfcoil/base.py @@ -113,7 +113,7 @@ class TFCoil(Model): """ def __init__(self): - """Initialise Fortran module variables.""" + """Initialise the TF coil model.""" self.outfile = constants.NOUT # output file unit def run(self): diff --git a/tests/unit/models/test_pfcoil.py b/tests/unit/models/test_pfcoil.py index 14e60ac74c..10814a0d53 100644 --- a/tests/unit/models/test_pfcoil.py +++ b/tests/unit/models/test_pfcoil.py @@ -59,7 +59,7 @@ def cs_coil(process_models): def test_init_pfcoil(pfcoil): - """Test initialisation of Fortran module variables. + """Test initialisation of module variables. :param pfcoil: PFCoil object :type pfcoil: process.pfcoil.PFCoil @@ -2768,9 +2768,6 @@ def test_efc(pfcoil: PFCoil, monkeypatch: pytest.MonkeyPatch): n_pf_coil_groups = 4 n_pf_coils_in_group = np.array([1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - # This 2D array argument discovered via gdb prints as a 1D array, therefore - # needs to be reshaped into its original 2D. Fortran ordering is essential - # when passing greater-than-1D arrays from Python to Fortran r_pf_coil_middle_group_array = np.reshape( [ 6.7651653417201345, diff --git a/tracking/tracking_data.py b/tracking/tracking_data.py index e9af8d9230..c9917da46d 100644 --- a/tracking/tracking_data.py +++ b/tracking/tracking_data.py @@ -261,9 +261,6 @@ def _generate_data(self): # tracking data for var in self.tracking_variables: if "." in var: - # a dotted variable is for variables that - # no longer exist in Fortran module variables - # see tracking_variables docstring try: _, var = var.split(".") # noqa: PLW2901 except (AttributeError, ValueError): From cc89be28a9748c95ee725313b708964b38178615 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Wed, 3 Jun 2026 09:34:13 +0100 Subject: [PATCH 2/8] Remove Fortran references from documentation and update file extensions to Python --- .../source/cost-models/cost-models.md | 2 +- documentation/source/development/standards.md | 27 +++++++++---------- .../source/fusion-devices/stellarator.md | 2 +- .../fusion_reactions/plasma_reactions.md | 4 +-- documentation/source/solver/solver-guide.md | 2 +- process/core/io/data_structure_dicts.py | 2 +- process/data_structure/numerics.py | 3 +-- process/data_structure/tfcoil_variables.py | 2 +- process/main.py | 7 +---- process/models/blankets/dcll.py | 2 +- process/models/blankets/hcpb.py | 2 -- process/models/physics/physics.py | 4 --- process/models/power.py | 2 +- process/models/stellarator/coils/coils.py | 2 +- process/models/tfcoil/base.py | 2 -- .../models/physics/test_impurity_radiation.py | 2 +- tests/unit/models/physics/test_physics.py | 2 +- tests/unit/models/physics/test_plasma_geom.py | 2 +- tests/unit/models/test_divertor.py | 2 +- tracking/tracking_data.py | 3 --- 20 files changed, 29 insertions(+), 47 deletions(-) diff --git a/documentation/source/cost-models/cost-models.md b/documentation/source/cost-models/cost-models.md index 4b3f0221fa..d985b0c7c5 100644 --- a/documentation/source/cost-models/cost-models.md +++ b/documentation/source/cost-models/cost-models.md @@ -4,7 +4,7 @@ Two cost models are available, determined by the switch `cost_model`. ## 1990 cost model (`cost_model = 0`) -This combines methods[^1] used in the TETRA code [^2] and the Generomak[^3] scheme. The costs are split into accounting categories[^4]. The best references for the algorithms used are[^5], and source file `costs.f90` in the code itself. The majority of the costed items have a unit cost associated with them. These values scale with (for example) power output, volume, component mass etc., and many are available to be changed via the input file. All costs and their algorithms correspond to 1990 dollars. +This combines methods[^1] used in the TETRA code [^2] and the Generomak[^3] scheme. The costs are split into accounting categories[^4]. The best references for the algorithms used are[^5], and source file `costs.py` in the code itself. The majority of the costed items have a unit cost associated with them. These values scale with (for example) power output, volume, component mass etc., and many are available to be changed via the input file. All costs and their algorithms correspond to 1990 dollars. The unit costs of the components of the fusion power core are relevant to "first-of-a-kind" items. That is to say, the items are assumed to be relatively expensive to build as they are effectively prototypes and specialised tools and machines have perhaps been made specially to create them. However, if a "production line" has been set up, and R & D progress has allowed more experience to be gained in constructing the power core components, the cost will be reduced as a result. Variable `fkind` may be used to multiply the raw unit costs of the fusion power core items (by a factor less than one) to simulate this cost reduction for an *Nth*-of-a-kind device. In other systems studies of fusion power plants[^6], values for this multiplier have ranged from 0.5 to 0.8. diff --git a/documentation/source/development/standards.md b/documentation/source/development/standards.md index 7aea9d99e1..7d8bc05cc1 100644 --- a/documentation/source/development/standards.md +++ b/documentation/source/development/standards.md @@ -37,7 +37,7 @@ Switches should start with the `i_` prefix in their name, be of integer type and Use an uppercase single letter, word, or words. Separate words with underscores to improve readability. -Refrain from declaring or typing known numerical constants directly in the code. Instead call the value from `constants.f90` +Refrain from declaring or typing known numerical constants directly in the code. Instead call the value from `constants.py` If the constants doesn't exist then add it with a source link and uncertainty value. --------------------- @@ -530,13 +530,13 @@ Try to keep names to a sensible length while also keeping the name explicit and The physical type of the variable should form the first part of the variable name, e.g. for plasma resistance the variable should be named: -```fortran +```python res_plasma = 1.0 ``` Another example would be pulse length -```fortran +```python time_pulse_length = 7200.0 ``` @@ -546,17 +546,17 @@ time_pulse_length = 7200.0 Inside PROCESS all variables should be in SI units unless otherwise stated. For example: -```fortran -! Fusion power [W] -p_fusion_total = 1000.0d6 +```python +p_fusion_total = 1e6 +"""Fusion power [W]""" ``` If a variable is not in SI units then its units should be put at the end of of the variable name. Example: -```fortran -! Fusion power [MW] -p_fusion_total_mw = 1000.0d0 +```python +p_fusion_total_mw = 1000.0 +"""Fusion power [W]""" ``` --------------------- @@ -565,22 +565,21 @@ p_fusion_total_mw = 1000.0d0 Coordinates should be defined as -```fortran -r_plasma_centre = 9.0d0 +```python +r_plasma_centre = 9.0 -z_plasma_centre = 0.0d0 +z_plasma_centre = 0.0 theta_ = ``` For dimensions -```fortran +```python dr_cs = dz_cs = -dtheta_description = ``` --------------------- diff --git a/documentation/source/fusion-devices/stellarator.md b/documentation/source/fusion-devices/stellarator.md index c3885f6435..a337050c17 100644 --- a/documentation/source/fusion-devices/stellarator.md +++ b/documentation/source/fusion-devices/stellarator.md @@ -82,7 +82,7 @@ ixc = 169 * Achievable Temperature of the ECRH at the ignition point ## Code specifics -The stellarator model is largely contained within source file `stellarator.f90`. +The stellarator model is largely contained within source file `stellarator.py`. The model call is in the following order diff --git a/documentation/source/physics-models/fusion_reactions/plasma_reactions.md b/documentation/source/physics-models/fusion_reactions/plasma_reactions.md index 735f044401..bdfadce19f 100644 --- a/documentation/source/physics-models/fusion_reactions/plasma_reactions.md +++ b/documentation/source/physics-models/fusion_reactions/plasma_reactions.md @@ -125,8 +125,8 @@ There are 4 key functions for calculating the fusion reaction for the plasma. Th #### Detailed Steps 1. **Initialize Bosch-Hale Constants**: Initializes the Bosch-Hale constants for the required reaction using predefined reaction constants stored in the BoschHaleConstants dataclass. 2. **Calculate Fusion Reaction Rate**: Uses Simpson's rule to integrate the fusion reaction rate over the plasma profile. -3. **Calculate Fusion Power Density**: Compute the fusion power density produced by the given reaction. Using the reaction energy calculated and stored in `constants.f90`. The reactant density is given by $\mathtt{f\_deuterium, f\_tritium}$ or $\mathtt{f\_helium3}$ multiplied by the volume averaged ion density. For the D-D reactions the fusion reaction rate is scaled with the output of [`deuterium_branching()`](#deuterium-branching-fraction--deuterium_branching) to simulate the different branching ratios. -4. **Calculate Specific Fusion Power Densities**: Compute the fusion power density for alpha particles, neutrons and other charged particles, depending on the reaction. Energy branching fractions used are calculated and called from `constants.f90`. +3. **Calculate Fusion Power Density**: Compute the fusion power density produced by the given reaction. Using the reaction energy calculated and stored in `constants.py`. The reactant density is given by $\mathtt{f\_deuterium, f\_tritium}$ or $\mathtt{f\_helium3}$ multiplied by the volume averaged ion density. For the D-D reactions the fusion reaction rate is scaled with the output of [`deuterium_branching()`](#deuterium-branching-fraction--deuterium_branching) to simulate the different branching ratios. +4. **Calculate Specific Fusion Power Densities**: Compute the fusion power density for alpha particles, neutrons and other charged particles, depending on the reaction. Energy branching fractions used are calculated and called from `constants.py`. 5. **Calculate Fusion Rate Densities**: Compute the total fusion rate density and fusion rates just for the alpha particles, neutrons and other charged particles, depending on the reaction. 6. **Update Reaction Power Density**: Updates the object attribute for the specific reaction power density. 7. **Sum Fusion Rates**: Call the [`sum_fusion_rates()`](#sum-the-fusion-rates--sum_fusion_rates) function to add the reaction to the global plasma power balance. diff --git a/documentation/source/solver/solver-guide.md b/documentation/source/solver/solver-guide.md index 361cd43017..2be4b6e1bb 100644 --- a/documentation/source/solver/solver-guide.md +++ b/documentation/source/solver/solver-guide.md @@ -94,7 +94,7 @@ variable values that maximises or minimises a certain function of them, known as the figure of merit. Several possible figures of merit are available, all of which are in the -source file `evaluators.f90`. +source file `evaluators.py`. Switch `minmax` is used to control which figure of merit is to be used. If the figure of merit is to be minimised, `minmax` should be **positive**, and if a diff --git a/process/core/io/data_structure_dicts.py b/process/core/io/data_structure_dicts.py index e47c206911..6e30c8a96f 100644 --- a/process/core/io/data_structure_dicts.py +++ b/process/core/io/data_structure_dicts.py @@ -77,7 +77,7 @@ def make_dict(self): def dict_var_type(): """Function to return a dictionary mapping variable name to variable type - eg. 'real_variable' or 'int_array'. Looks in input.f90 at the process + eg. 'real_variable' or 'int_array'. Looks in input.py at the process functions that read in variables from IN.DAT. Example of line we are looking for: diff --git a/process/data_structure/numerics.py b/process/data_structure/numerics.py index 40ce4e9865..ac39867819 100644 --- a/process/data_structure/numerics.py +++ b/process/data_structure/numerics.py @@ -536,8 +536,7 @@ class NumericsData: * (174) NOT USED * (175) NOT USED """ - # Issue 287 iteration variables are now defined in module define_iteration_variables in iteration variables.f90 - # WARNING These labels are used as variable names by new_indat(), and possibly + # WARNING These labels are used as variable names by new_indat(), and possibly # other python utilities, so they cannot easily be changed. name_xc: list[str] = field(default_factory=lambda: [""] * IPNVARS) diff --git a/process/data_structure/tfcoil_variables.py b/process/data_structure/tfcoil_variables.py index c01a16cbb1..b30925fb35 100644 --- a/process/data_structure/tfcoil_variables.py +++ b/process/data_structure/tfcoil_variables.py @@ -333,7 +333,7 @@ class TFData: n_tf_stress_layers: int = 0 """Number of layers considered for the inboard TF stress calculations - set in initial.f90 from i_tf_bucking and n_tf_graded_layers + set in initial.py from i_tf_bucking and n_tf_graded_layers """ n_tf_wp_stress_layers: int = 5 diff --git a/process/main.py b/process/main.py index 7e88874d9f..dcdece0172 100644 --- a/process/main.py +++ b/process/main.py @@ -1,9 +1,4 @@ -"""Run Process by calling into the Python module. - -This uses a Python module called fortran.py, which uses an extension module -called "_fortran.cpython... .so", which are both generated from -process_module.f90. The process_module module contains the code to actually run -Process. +"""Run `PROCESS` by calling into the Python module. Power Reactor Optimisation Code for Environmental and Safety Studies diff --git a/process/models/blankets/dcll.py b/process/models/blankets/dcll.py index 6d31e0c929..1c8a24487e 100644 --- a/process/models/blankets/dcll.py +++ b/process/models/blankets/dcll.py @@ -479,7 +479,7 @@ def dcll_masses(self, output: bool): FW Armour - Tungsten - - Use den_tungsten form constants.f90 + - Use den_tungsten form constants.py FW and BB Structure Coolant - Helium - See primary_coolant_properties for denisty etc. diff --git a/process/models/blankets/hcpb.py b/process/models/blankets/hcpb.py index 4198403355..6a14e08e8d 100644 --- a/process/models/blankets/hcpb.py +++ b/process/models/blankets/hcpb.py @@ -320,8 +320,6 @@ def component_masses(self): # and pressures (kg) self.data.fwbs.m_fw_blkt_div_coolant_total = coolvol * 1.517 - # Average first wall coolant fraction, only used by old routines in fispact.f90, - # safety.f90 self.data.fwbs.fwclfr = ( self.data.first_wall.a_fw_inboard * self.data.build.dr_fw_inboard diff --git a/process/models/physics/physics.py b/process/models/physics/physics.py index 2f825db9a1..3b590ecf47 100644 --- a/process/models/physics/physics.py +++ b/process/models/physics/physics.py @@ -474,10 +474,6 @@ def run(self): self.data.times.t_plant_pulse_plasma_current_ramp_up ) - # Reset second self.data.times.t_plant_pulse_burn value - # (self.data.times.t_burn_0). - # This is used to ensure that the burn time is used consistently; - # see convergence loop in fcnvmc1, evaluators.f90 self.data.times.t_burn_0 = self.data.times.t_plant_pulse_burn # Time during the pulse in which a plasma is present diff --git a/process/models/power.py b/process/models/power.py index 08c76bb1fd..52941e92c6 100644 --- a/process/models/power.py +++ b/process/models/power.py @@ -928,7 +928,7 @@ def component_thermal_powers(self): + self.data.heat_transport.p_div_coolant_pump_mw ) - # Heat removal from first wall and divertor (MW) (only used in costs.f90) + # Heat removal from first wall and divertor (MW) i_p_coolant_pumping = PumpingPowerModelTypes(self.data.fwbs.i_p_coolant_pumping) if i_p_coolant_pumping != PumpingPowerModelTypes.MECHANICAL_WITH_PRESSURE_DROP: self.data.heat_transport.p_fw_div_heat_deposited_mw = ( diff --git a/process/models/stellarator/coils/coils.py b/process/models/stellarator/coils/coils.py index 5b16e1122c..acda620a61 100644 --- a/process/models/stellarator/coils/coils.py +++ b/process/models/stellarator/coils/coils.py @@ -41,7 +41,7 @@ def jcrit_from_material( # of a cable conductor. if i_tf_sc_mat == 1: # ITER Nb3Sn critical surface parameterization - bc20m = 32.97 # these are values taken from sctfcoil.f90 + bc20m = 32.97 tc0m = 16.06 # j_crit_sc returned by itersc is the critical current density in the diff --git a/process/models/tfcoil/base.py b/process/models/tfcoil/base.py index dd26b5c01f..bc851fb262 100644 --- a/process/models/tfcoil/base.py +++ b/process/models/tfcoil/base.py @@ -2547,8 +2547,6 @@ def stresscl( # Superconducting CS if i_pf_conductor == PFConductorModel.SUPERCONDUCTING: # Getting the turn dimention from scratch - # as the TF is called before CS in caller.f90 - # -# # Maximum current in Central Solenoid, at either BOP or EOF [MA-turns] # Absolute value diff --git a/tests/unit/models/physics/test_impurity_radiation.py b/tests/unit/models/physics/test_impurity_radiation.py index 335736faaf..e3576eaa0c 100644 --- a/tests/unit/models/physics/test_impurity_radiation.py +++ b/tests/unit/models/physics/test_impurity_radiation.py @@ -1,4 +1,4 @@ -"""Unit tests for the impurity_radiation.f90.py module.""" +"""Unit tests for the impurity_radiation.py module.""" from typing import NamedTuple diff --git a/tests/unit/models/physics/test_physics.py b/tests/unit/models/physics/test_physics.py index 897258deff..bc1dc5bcea 100644 --- a/tests/unit/models/physics/test_physics.py +++ b/tests/unit/models/physics/test_physics.py @@ -1,4 +1,4 @@ -"""Unit tests for physics.f90.""" +"""Unit tests for physics.py module.""" from typing import Any, NamedTuple diff --git a/tests/unit/models/physics/test_plasma_geom.py b/tests/unit/models/physics/test_plasma_geom.py index 9c72edfbf3..cc7113781c 100644 --- a/tests/unit/models/physics/test_plasma_geom.py +++ b/tests/unit/models/physics/test_plasma_geom.py @@ -1,4 +1,4 @@ -"""Unit tests for plasma_geometry.f90.""" +"""Unit tests for plasma_geometry.py module.""" from typing import Any, NamedTuple diff --git a/tests/unit/models/test_divertor.py b/tests/unit/models/test_divertor.py index 61aef5e3f9..2b03b0ed5c 100644 --- a/tests/unit/models/test_divertor.py +++ b/tests/unit/models/test_divertor.py @@ -1,4 +1,4 @@ -"""Unit tests for divertor.f90 subroutines/functions""" +"""Unit tests for divertor.py module.""" import pytest diff --git a/tracking/tracking_data.py b/tracking/tracking_data.py index c9917da46d..1414200082 100644 --- a/tracking/tracking_data.py +++ b/tracking/tracking_data.py @@ -41,9 +41,6 @@ To add a variable to track: Add the variable to ProcessTracker.tracking_variables (in this file). -If the variable is not a fortran module variable, -ensure to override its parent module name -e.g. FOO.bar says `bar`'s parent module is `FOO`. """ import argparse From 133957ea02e60fa43f9b29bb6674f6fccb09c116 Mon Sep 17 00:00:00 2001 From: Christopher Ashe <91618944+chris-ashe@users.noreply.github.com> Date: Wed, 3 Jun 2026 09:49:06 +0100 Subject: [PATCH 3/8] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- documentation/source/development/standards.md | 8 ++++---- process/models/blankets/dcll.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/documentation/source/development/standards.md b/documentation/source/development/standards.md index 7d8bc05cc1..8e4cc83398 100644 --- a/documentation/source/development/standards.md +++ b/documentation/source/development/standards.md @@ -556,7 +556,7 @@ Example: ```python p_fusion_total_mw = 1000.0 -"""Fusion power [W]""" +"""Fusion power [MW]""" ``` --------------------- @@ -570,15 +570,15 @@ r_plasma_centre = 9.0 z_plasma_centre = 0.0 -theta_ = +theta_ = ... ``` For dimensions ```python -dr_cs = +dr_cs = ... -dz_cs = +dz_cs = ... ``` diff --git a/process/models/blankets/dcll.py b/process/models/blankets/dcll.py index 1c8a24487e..013043c1ba 100644 --- a/process/models/blankets/dcll.py +++ b/process/models/blankets/dcll.py @@ -479,7 +479,7 @@ def dcll_masses(self, output: bool): FW Armour - Tungsten - - Use den_tungsten form constants.py + - Use DEN_TUNGSTEN from constants.py FW and BB Structure Coolant - Helium - See primary_coolant_properties for denisty etc. From e879f8630fdd5727683cedb69359426e0f297f1e Mon Sep 17 00:00:00 2001 From: mn3981 Date: Wed, 3 Jun 2026 09:49:03 +0100 Subject: [PATCH 4/8] Refactor docstrings to remove Fortran references and clarify variable mapping descriptions --- process/core/io/data_structure_dicts.py | 7 ++----- process/main.py | 4 +--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/process/core/io/data_structure_dicts.py b/process/core/io/data_structure_dicts.py index 6e30c8a96f..ea9ee94331 100644 --- a/process/core/io/data_structure_dicts.py +++ b/process/core/io/data_structure_dicts.py @@ -77,11 +77,8 @@ def make_dict(self): def dict_var_type(): """Function to return a dictionary mapping variable name to variable type - eg. 'real_variable' or 'int_array'. Looks in input.py at the process - functions that read in variables from IN.DAT. - - Example of line we are looking for: - call parse_real_variable('BETA', beta, 0.0D0, 1.0D0, & + eg. 'real_variable' or 'int_array'. Iterates over INPUT_VARIABLES to build + the mapping using the type and array properties of each variable config. Example dictionary entry: DICT_VAR_TYPE['beta'] = 'real_variable' diff --git a/process/main.py b/process/main.py index dcdece0172..fbcec79738 100644 --- a/process/main.py +++ b/process/main.py @@ -446,9 +446,7 @@ def show_errors(): @staticmethod def finish(): - """Run the finish subroutine to close files that are open at the end of a run. - n. - """ + """Run the finish subroutine to close files that are open at the end of a run.""" oheadr(constants.NOUT, "End of PROCESS Output") oheadr(constants.IOTTY, "End of PROCESS Output") oheadr(constants.NOUT, "Copy of PROCESS Input Follows") From bc9ed31b93e6723830ada92c421fb8ad97d99e8f Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 5 Jun 2026 11:11:09 +0100 Subject: [PATCH 5/8] Remove Fortran references from documentation, comments, and variable descriptions --- process/core/input.py | 1 + process/core/solver/solver_handler.py | 1 + process/data_structure/tfcoil_variables.py | 1 - process/models/cs_fatigue.py | 2 +- process/models/stellarator/coils/coils.py | 1 - process/models/stellarator/stellarator.py | 3 +-- 6 files changed, 4 insertions(+), 5 deletions(-) diff --git a/process/core/input.py b/process/core/input.py index fa9d05506d..436e8310fc 100644 --- a/process/core/input.py +++ b/process/core/input.py @@ -1406,6 +1406,7 @@ def set_scalar_variable(name: str, value: ValidInputTypes, config: InputVariable """ current_value = getattr(config.module, name, ...) + # use ... sentinel because None is a valid initial/default value for variables if current_value is ...: error_msg = f"Module '{config.module}' does not have a variable '{name}'." raise ProcessValueError(error_msg) diff --git a/process/core/solver/solver_handler.py b/process/core/solver/solver_handler.py index 326407b6eb..ed1c79446d 100644 --- a/process/core/solver/solver_handler.py +++ b/process/core/solver/solver_handler.py @@ -32,6 +32,7 @@ def run(self): load_iteration_variables(self.data) load_scaled_bounds(self.data) + # Initialise solver variables from numerics module n = self.data.numerics.nvar x = self.data.numerics.xcm[:n] bndl = self.data.numerics.itv_scaled_lower_bounds[:n] diff --git a/process/data_structure/tfcoil_variables.py b/process/data_structure/tfcoil_variables.py index b30925fb35..b459e3d760 100644 --- a/process/data_structure/tfcoil_variables.py +++ b/process/data_structure/tfcoil_variables.py @@ -333,7 +333,6 @@ class TFData: n_tf_stress_layers: int = 0 """Number of layers considered for the inboard TF stress calculations - set in initial.py from i_tf_bucking and n_tf_graded_layers """ n_tf_wp_stress_layers: int = 5 diff --git a/process/models/cs_fatigue.py b/process/models/cs_fatigue.py index 5ef510cee4..efc01c71f0 100644 --- a/process/models/cs_fatigue.py +++ b/process/models/cs_fatigue.py @@ -101,7 +101,7 @@ def ncycle( # run euler_method and find number of cycles needed to give crack increase delta_n = delta / (cr * (k_max**self.data.cs_fatigue.paris_power_law)) - # update a and c, N (+= doesnt work for fortran (?) reasons) + # update a and c, N a += delta * (k_a / k_max) ** self.data.cs_fatigue.paris_power_law c += delta * (k_c / k_max) ** self.data.cs_fatigue.paris_power_law n_pulse += delta_n diff --git a/process/models/stellarator/coils/coils.py b/process/models/stellarator/coils/coils.py index acda620a61..b5519d6fe0 100644 --- a/process/models/stellarator/coils/coils.py +++ b/process/models/stellarator/coils/coils.py @@ -72,7 +72,6 @@ def jcrit_from_material( jstrand = j_wp / (1 - f_he) # jstrand = 0 # as far as I can tell this will always be 0 - # because jwp was never set in fortran (so 0) j_crit_cable, tmarg = superconductors.bi2212( b_max, jstrand, t_helium, f_hts diff --git a/process/models/stellarator/stellarator.py b/process/models/stellarator/stellarator.py index c270567c76..7f65eefecd 100644 --- a/process/models/stellarator/stellarator.py +++ b/process/models/stellarator/stellarator.py @@ -1236,8 +1236,7 @@ def st_fwbs(self, output: bool): * f_a_fw_coolant_outboard ) - # Average first wall coolant fraction, only used by old routines - # in fispact.f90, safety.f90 + # Average first wall coolant fraction, only used by old routines self.data.fwbs.fwclfr = ( self.data.first_wall.a_fw_inboard From fb9d87141017ef99747b0c7ecf9189d9e70c1386 Mon Sep 17 00:00:00 2001 From: mn3981 Date: Fri, 19 Jun 2026 12:05:19 +0100 Subject: [PATCH 6/8] Post rebase fixes --- process/data_structure/numerics.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/process/data_structure/numerics.py b/process/data_structure/numerics.py index ac39867819..342b314186 100644 --- a/process/data_structure/numerics.py +++ b/process/data_structure/numerics.py @@ -155,7 +155,6 @@ class NumericsData: active_constraints: list[bool] = field(default_factory=lambda: [False] * IPEQNS) """Logical array showing which constraints are active""" - lablcc: list[str] = field( default_factory=lambda: [ "⟨β⟩ consistency ", @@ -536,7 +535,7 @@ class NumericsData: * (174) NOT USED * (175) NOT USED """ - # WARNING These labels are used as variable names by new_indat(), and possibly + # WARNING These labels are used as variable names by new_indat(), and possibly # other python utilities, so they cannot easily be changed. name_xc: list[str] = field(default_factory=lambda: [""] * IPNVARS) From 94ff90dd7a54599fd6b634a8c826420f9372b52d Mon Sep 17 00:00:00 2001 From: Christopher Ashe <91618944+chris-ashe@users.noreply.github.com> Date: Mon, 22 Jun 2026 09:53:52 +0100 Subject: [PATCH 7/8] Update process/models/stellarator/stellarator.py Co-authored-by: clmould <86794332+clmould@users.noreply.github.com> --- process/models/stellarator/stellarator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/process/models/stellarator/stellarator.py b/process/models/stellarator/stellarator.py index 7f65eefecd..7c58520706 100644 --- a/process/models/stellarator/stellarator.py +++ b/process/models/stellarator/stellarator.py @@ -1236,7 +1236,7 @@ def st_fwbs(self, output: bool): * f_a_fw_coolant_outboard ) - # Average first wall coolant fraction, only used by old routines + # Average first wall coolant fraction self.data.fwbs.fwclfr = ( self.data.first_wall.a_fw_inboard From 0427b25965ff8a8710169ebe861b8637e1159df8 Mon Sep 17 00:00:00 2001 From: Christopher Ashe <91618944+chris-ashe@users.noreply.github.com> Date: Mon, 22 Jun 2026 09:54:17 +0100 Subject: [PATCH 8/8] Update process/core/output.py Co-authored-by: clmould <86794332+clmould@users.noreply.github.com> --- process/core/output.py | 1 + 1 file changed, 1 insertion(+) diff --git a/process/core/output.py b/process/core/output.py index b640280316..abd5ddf51c 100644 --- a/process/core/output.py +++ b/process/core/output.py @@ -16,6 +16,7 @@ def write(models, data, _outfile): models : process.main.Models physics and engineering model objects _outfile : int + output unit identifier """ # ensure we are capturing warnings that occur in the 'output' stage