Skip to content
2 changes: 1 addition & 1 deletion documentation/source/cost-models/cost-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 *N<sup>th</sup>*-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.

Expand Down
33 changes: 16 additions & 17 deletions documentation/source/development/standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment on lines +40 to 41

@clmould clmould Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
Refrain from declaring or typing known numerical constants directly in the code. Instead call the value from `constants.py`.
If the constant doesn't exist then add it with a source link.

The constants in constants.py just seem to have a source link?


---------------------
Expand Down Expand Up @@ -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
```

Expand All @@ -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 [MW]"""
```

---------------------
Expand All @@ -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_ =
theta_ = ...
```

For dimensions

```fortran
dr_cs =
```python
dr_cs = ...

dz_cs =
dz_cs = ...

dtheta_description =
```

---------------------
Expand Down
2 changes: 1 addition & 1 deletion documentation/source/fusion-devices/stellarator.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The stellarator model is largely contained within source file `stellarator.py`.
The stellarator model is contained within the `process/models/stellarator/` directory.


The model call is in the following order

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion documentation/source/solver/solver-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this reference the figures of merit enum instead?


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
Expand Down
3 changes: 1 addition & 2 deletions process/core/caller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------
Expand Down
15 changes: 3 additions & 12 deletions process/core/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Comment thread
timothy-nunn marked this conversation as resolved.
line_match = re.match(
r"([a-zA-Z0-9_]+)(?:\(([0-9]+)\))?[ ]*=[ ]*([ +\-a-zA-Z0-9.,]+).*",
stripped_line,
Expand Down Expand Up @@ -1407,12 +1406,9 @@ 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
Comment thread
chris-ashe marked this conversation as resolved.
# and definately will be when moving to a Python data structure
# use ... sentinel because None is a valid initial/default value for variables
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)
Expand All @@ -1421,11 +1417,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 :
Expand Down
8 changes: 2 additions & 6 deletions process/core/io/data_structure_dicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -78,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.f90 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'
Expand Down
43 changes: 1 addition & 42 deletions process/core/io/in_dat/base.py

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@timothy-nunn I do not know if any of the old comments are still necessary here (some are definitely outdated, but others are unclear to me)

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion process/core/io/vary_run/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
2 changes: 1 addition & 1 deletion process/core/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def write(models, data, _outfile):
models : process.main.Models
physics and engineering model objects
_outfile : int
Comment thread
chris-ashe marked this conversation as resolved.
Fortran output unit identifier
output unit identifier

"""
Comment on lines 16 to 21
# ensure we are capturing warnings that occur in the 'output' stage
Expand Down
2 changes: 1 addition & 1 deletion process/core/scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 1 addition & 5 deletions process/core/solver/iteration_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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).
"""


Expand Down Expand Up @@ -271,9 +270,6 @@ def load_iteration_variables(data):
variable_index = data.numerics.ixc[i]
iteration_variable = ITERATION_VARIABLES[variable_index]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# use ... sentinel because None is a valid initial/default value for variables

# 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)
Expand Down
7 changes: 1 addition & 6 deletions process/core/solver/solver_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,10 @@ 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
Comment thread
chris-ashe marked this conversation as resolved.
# iteration variables being defined above
# Trim maximum size arrays down to actually used size
# Initialise solver variables from numerics module

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Initialise solver variables from numerics module
# Initialise iteration variables from numerics module
# Trim maximum size arrays down to actually used size

Think it's worth explaining the slicing

n = self.data.numerics.nvar
x = self.data.numerics.xcm[:n]
bndl = self.data.numerics.itv_scaled_lower_bounds[:n]
Expand Down Expand Up @@ -100,7 +97,5 @@ def output(self):
Objective function value, solution vector and constraints vector.
"""
self.data.numerics.norm_objf = self.solver.objf
Comment thread
chris-ashe marked this conversation as resolved.
# 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
4 changes: 0 additions & 4 deletions process/data_structure/numerics.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +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(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
lablcc: list[str] = field(
# WARNING lablcc entries should not be changed
lablcc: list[str] = field(

default_factory=lambda: [
"⟨β⟩ consistency ",
Expand Down Expand Up @@ -538,7 +535,6 @@ 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
# other python utilities, so they cannot easily be changed.

Expand Down
1 change: 0 additions & 1 deletion process/data_structure/tfcoil_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.f90 from i_tf_bucking and n_tf_graded_layers
"""

n_tf_wp_stress_layers: int = 5
Expand Down
Loading
Loading