Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion process/core/scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from process.core.solver.solver_handler import SolverHandler
from process.data_structure.numerics import FiguresOfMerit, PROCESSRunMode
from process.data_structure.scan_variables import IPNSCNS, NOUTVARS, ScanData
from process.models.availability import AvailabilityModel

if TYPE_CHECKING:
from process.core.model import DataStructure, Model
Expand Down Expand Up @@ -1145,7 +1146,10 @@ def scan_select(self, nwp, swp, iscn):
case 20:
self.data.constraints.t_burn_min = swp[iscn - 1]
case 22:
if self.data.costs.i_plant_availability == 1:
Comment thread
chris-ashe marked this conversation as resolved.
if (
AvailabilityModel(self.data.costs.i_plant_availability)
!= AvailabilityModel.USER_INPUT
):
raise ProcessValueError(
"Do not scan f_t_plant_available if i_plant_availability=1"
)
Expand Down
11 changes: 9 additions & 2 deletions process/core/solver/objectives.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from process.core.exceptions import ProcessValueError
from process.core.model import DataStructure
from process.data_structure.numerics import FiguresOfMerit
from process.models.availability import AvailabilityModel


def objective_function(minmax: int, data: DataStructure) -> float:
Expand Down Expand Up @@ -70,8 +71,14 @@ def objective_function(minmax: int, data: DataStructure) -> float:
elif figure_of_merit == FiguresOfMerit.PULSE_LENGTH:
objective_metric = data.times.t_plant_pulse_burn / 2.0e4
elif figure_of_merit == FiguresOfMerit.PLANT_AVAILABILITY_FACTOR:
if data.costs.i_plant_availability != 1:
raise ProcessValueError("minmax=15 requires i_plant_availability=1")
if (
AvailabilityModel(data.costs.i_plant_availability)
== AvailabilityModel.USER_INPUT
):
Comment thread
chris-ashe marked this conversation as resolved.
raise ProcessValueError(
"minmax=15 requires `f_t_plant_available` to be calculated, not user "
"input"
)
objective_metric = data.costs.f_t_plant_available
elif figure_of_merit == FiguresOfMerit.MIN_R0_MAX_TAU_BURN:
objective_metric = 0.95 * (data.physics.rmajor / 9.0) - 0.05 * (
Expand Down
69 changes: 57 additions & 12 deletions process/models/availability.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import logging
import math
from enum import IntEnum
from types import DynamicClassAttribute

from scipy.special import comb as combinations

Expand All @@ -11,6 +13,41 @@

logger = logging.getLogger(__name__)


class AvailabilityModel(IntEnum):
"""Enum for availability models"""

USER_INPUT = (0, "Input value for `f_t_plant_available`")
WARD_TAYLOR = (1, "Ward and Taylor model (1999)")
MORRIS = (2, "Morris model (2015)")
ST = (3, "ST model (2023)")

def __new__(cls, value: int, full_name: str):
"""Create a new AvailabilityModel enum instance.

Parameters
----------
value : int
The integer value of the enum member.
full_name : str
The full name/description of the availability model.

Returns
-------
AvailabilityModel
A new enum instance with the specified value and full_name.
"""
obj = int.__new__(cls, value)
obj._value_ = value
obj._full_name_ = full_name
return obj

@DynamicClassAttribute
def full_name(self):
"""The full name of the availability model."""
return self._full_name_


DAY_SECONDS = 60 * 60 * 24
# Number of seconds in a day [s]

Expand All @@ -20,13 +57,6 @@
YEAR_SECONDS = DAY_SECONDS * DAYS_IN_YEAR
# Number of seconds in a year [s]

AVAILABILITY_MODELS = {
0: "Input value for f_t_plant_available",
1: "Ward and Taylor model (1999)",
2: "Morris model (2015)",
3: "ST model (2023)",
}


class Availability(Model):
"""Module containing plant availability routines
Expand Down Expand Up @@ -58,15 +88,21 @@ def run(self, output: bool = False):
output :
indicate whether output should be written to the output file, or not
"""
if self.data.costs.i_plant_availability == 3:
if (
AvailabilityModel(self.data.costs.i_plant_availability)
== AvailabilityModel.ST
):
if self.data.physics.itart != 1:
raise ProcessValueError(
f"{self.data.costs.i_plant_availability=}"
" is for a Spherical Tokamak."
" Please set itart=1 to use this model."
)
self.avail_st(output) # ST model (2023)
elif self.data.costs.i_plant_availability == 2:
elif (
AvailabilityModel(self.data.costs.i_plant_availability)
== AvailabilityModel.MORRIS
):
self.avail_2(output) # Morris model (2015)
else:
self.avail(output) # Taylor and Ward model (1999)
Expand Down Expand Up @@ -167,7 +203,10 @@ def avail(self, output: bool):
# if i_plant_availability = 0 use input value for f_t_plant_available

# Taylor and Ward 1999 model (i_plant_availability=1)
if self.data.costs.i_plant_availability == 1:
if (
AvailabilityModel(self.data.costs.i_plant_availability)
== AvailabilityModel.WARD_TAYLOR
):
# Which component has the shorter life?
if self.data.costs.life_div_fpy < self.data.fwbs.life_blkt_fpy:
ld = self.data.costs.life_div_fpy
Expand Down Expand Up @@ -294,7 +333,10 @@ def avail(self, output: bool):
self.data.costs.life_plant,
)

if self.data.costs.i_plant_availability == 1:
if (
AvailabilityModel(self.data.costs.i_plant_availability)
== AvailabilityModel.WARD_TAYLOR
):
if self.data.costs.life_div_fpy < self.data.fwbs.life_blkt_fpy:
po.ovarre(
self.outfile,
Expand Down Expand Up @@ -331,7 +373,10 @@ def avail(self, output: bool):
"OP ",
)

if self.data.costs.i_plant_availability == 0:
if (
AvailabilityModel(self.data.costs.i_plant_availability)
== AvailabilityModel.USER_INPUT
):
po.ovarre(
self.outfile,
"Total plant availability fraction",
Expand Down
Loading