Skip to content

Commit

Permalink
2980 create availability model for spherical tokamaks (#3039)
Browse files Browse the repository at this point in the history
* Added CP lifetime routine to remove duplicated code

* Added variables for avail_st model

* Added avail_st model and tests

* Added error if itart not set to ST

* Added relevant flux and fluence outputs and CP lifetime

* Revert "Added variables for avail_st model"

This reverts commit fb10173.

Reversing commit above to correct autoformatting inserting unwanted changes.

* Readded new variables for ST availability model

* Flake8 fixes

* Updated docstrings

* Renamed plant.md to plant-availability.md

* Added docs for ST availability model and CP lifetime calculation

* Updated yaml so docs generate correctly

* Fixed table in plant availability docs

* Minor changes to docs

* Minor changes to docs
  • Loading branch information
j-a-foster authored and chris-ashe committed May 1, 2024
1 parent 5190c73 commit 6e7a2c8
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ If `iavail = 0`, the input value of `cfactr` is used.

If `iavail = 1`, a model by N. Taylor and D. Ward[^1] is used instead, in which `cfactr` is calculated taking into account the time taken to replace certain components of the fusion power core, and various unplanned unavailability fractions which may be set by the user, as summerised in Table 1.

| Input parameter | description |
| --- | --- | --- |
| Input parameter | Description |
| :-: | - |
| `tbktrepl` | time needed to replace blanket (years) |
| `tdivrepl` | time needed to replace divertor (years) |
| `tcomrepl` | time needed to replace both blanket and divertor (years) |
Expand All @@ -33,6 +33,60 @@ The unplanned downtime for the blanket is based on the number of cycles it exper

It is assumed that the vacuum system can be maintained in parallel with blanket replacement, so it does not contribute to the planned downtime. The unplanned downtime is baed on an assumed failure rate for a cryo-pump, and a specified total number pumps, with some of them being redundant. The resulting downtime can be reduced to a negligible level if there are several redundant pumps, but in addition, there is a fixed unavailability to allow for common mode failures affecting several pumps.

If `iavail = 3`, the availability model for Spherical Tokamaks (ST) is implemented.

!!! Warning "Warning"
Currently, this model only uses the centrepost to calculate the availability of an ST plant. Other systems/components will be added in the future.

This model takes the user-specified time to replace a centrepost `tmain` and the centrepost lifetime `cplife` (calculated, see below) and calculates the number of maintenance cycles

$$ t_{\text{main}} + t_{\text{CP,life}} = t_{\text{maint cycle}}. $$

The number of maintenance cycles over the lifetime of the plant is calculated and then the ceiling of this value is taken as the number of centreposts required over the lifetime of the plant

$$ n_{\text{cycles}} = t_{\text{life}} / t_{\text{maint cycle}}, $$

$$ n_{\text{CP}} = \lceil n_{\text{cycles}} \rceil. $$

The planned unavailability is then what percent of a maintenance cycle is taken up by the user-specified maintenance time

$$ U_{\text{planned}} = t_{\text{main}} / t_{\text{maint cycle}} $$

and the total operational time is given by

$$ t_{\text{op}} = t_{\text{life}} (1 - U_{\text{planned}}). $$

The total availability of the plant is then given by

$$ A_{\text{tot}} = 1 - (U_{\text{planned}} + U_{\text{unplanned}} + U_{\text{planned}}U_{\text{unplanned}}) $$

where $U_{unplanned}$ is unplanned unavailability which is provided by the user i.e. how often do you expect the centrepost to break over its lifetime. The cross term takes account of overlap between planned and unplanned unavailability.

Finally, the capcity factor is given by

$$ C = A_{\text{tot}} (t_{\text{burn}} / t_{\text{cycle}}) $$

where $t_{\text{burn}}$ is the burn time and $t_{\text{cycle}}$ is the full cycle time.

## Centrepost Lifetime

All availability models in PROCESS require the calculation of the centerpost lifetime, which is detailed here.

!!! Note "Note"
The centrepost lifetime is calculated in full-power years (FPY).

For superconducting magnets (`i_tf_sup = 1`), the centrepost lifetime is calculated as

$$ t_{\text{CP,life}} = min(f_{\text{TF,max}}/(\phi_{\text{CP,max}}t_{\text{year}}),t_{\text{life}}) $$

where $f_{\text{TF,max}}$ is the max fast neutron fluence on the TF coil ($\mathrm{m}^{-2} \mathrm{s}$), $\phi_{\text{CP,max}}$ is the centrepost TF fast neutron flux ($\mathrm{m}^{-2}$ $\mathrm{s}^{-1}$) and $t_{\text{year}}$ is the number of seconds in a year.

For copper or cryogenic aluminium magnets (`i_tf_sup = 0 or 2`), the centrepost lifetime is

$$ t_{\text{CP,life}} = min(f_{\text{CP, allowable}}/P_{\text{wall}}, t_{\text{life}}) $$

where $f_{\text{CP, allowable}}$ is the allowable centrepost neutron fluence and $P_{\text{wall}}$ is the average neutron wall load ($\mathrm{MW} \mathrm{m}^{-2}$).

[^1]: P. J. Knight, *"PROCESS 3020: Plant Availability Model"*, Work File Note
F/PL/PJK/PROCESS/CODE/<br>
[^2]: M. Kovari, F. Fox, C. Harrington, R. Kembleton, P. Knight, H. Lux, J. Morris *"PROCESS: a systems code for fusion power plants - Part 2: Engineering"*, Fus. Eng. & Des. 104, 9-20 (2016)
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ nav:
- ITER Model: eng-models/heating_and_current_drive/NBI/iter_nb.md
- Culham Model: eng-models/heating_and_current_drive/NBI/culham_nb.md
- Cryostat and vacuum system: eng-models/cryostat-and-vacuum-system.md
- Plant Availability: eng-models/plant.md
- Plant Availability: eng-models/plant-availability.md
- Power Requirements: eng-models/power-requirements.md
- Vacuum Vessel: eng-models/vacuum-vessel.md
- Unique Models:
Expand Down
191 changes: 165 additions & 26 deletions process/availability.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,19 @@ def run(self, output: bool = False):
0 | Input value for cfactr
1 | Ward and Taylor model (1999)
2 | Morris model (2015)
3 | ST model (2023)
:param output: indicate whether output should be written to the output file, or not (default = False)
:type output: boolean
"""

if cv.iavail > 1:
if cv.iavail == 3:
if pv.itart != 1:
raise ValueError(
f"{cv.iavail=} is for a Spherical Tokamak. Please set itart=1 to use this model."
)
self.avail_st(output) # ST model (2023)
elif cv.iavail == 2:
self.avail_2(output) # Morris model (2015)
else:
self.avail(output) # Taylor and Ward model (1999)
Expand Down Expand Up @@ -115,17 +122,7 @@ def avail(self, output: bool):

# Centrepost lifetime (years) (ST machines only)
if pv.itart == 1:
# SC magnets CP lifetime
# Rem : only the TF maximum fluence is considered for now
if tfv.i_tf_sup == 1:
cv.cplife = min(
ctv.nflutfmax / (fwbsv.neut_flux_cp * YEAR_SECONDS), cv.tlife
)

# Aluminium/Copper magnets CP lifetime
# For now, we keep the original def, developped for GLIDCOP magnets ...
else:
cv.cplife = min(cv.cpstflnc / pv.wallmw, cv.tlife)
cv.cplife = self.cp_lifetime()

# Plant Availability (iavail=0,1)

Expand Down Expand Up @@ -450,17 +447,7 @@ def calc_u_planned(self, output: bool) -> float:

# Centrepost lifetime (years) (ST only)
if pv.itart == 1:
# SC magnets CP lifetime
# Rem : only the TF maximum fluence is considered for now
if tfv.i_tf_sup == 1:
cv.cplife = min(
ctv.nflutfmax / (fwbsv.neut_flux_cp * YEAR_SECONDS), cv.tlife
)

# Aluminium/Copper magnets CP lifetime
# For now, we keep the original def, developped for GLIDCOP magnets ...
else:
cv.cplife = min(cv.cpstflnc / pv.wallmw, cv.tlife)
cv.cplife = self.cp_lifetime()

# Current drive lifetime (assumed equal to first wall and blanket lifetime)
cv.cdrlife = fwbsv.bktlife
Expand Down Expand Up @@ -499,7 +486,6 @@ def calc_u_planned(self, output: bool) -> float:

# Output
if output:

po.oheadr(self.outfile, "Plant Availability (2014 Model)")

po.ocmmnt(self.outfile, "Planned unavailability:")
Expand Down Expand Up @@ -610,7 +596,6 @@ def calc_u_unplanned_magnets(self, output: bool) -> float:
# !!!!!!!!!

if output:

po.ocmmnt(self.outfile, "Magnets:")
po.oblnkl(self.outfile)
po.ovarre(
Expand Down Expand Up @@ -944,7 +929,6 @@ def calc_u_unplanned_vacuum(self, output: bool) -> float:
sum_prob = 0.0e0

for n in range(cv.redun_vac + 1, total_pumps + 1):

# Probability for n failures in the operational period, n > number of redundant pumps
# vac_fail_p.append(maths_library.binomial(total_pumps,n) * (cryo_nfailure_rate**(total_pumps-n)) *(cryo_failure_rate**n))

Expand Down Expand Up @@ -994,3 +978,158 @@ def calc_u_unplanned_vacuum(self, output: bool) -> float:
po.oblnkl(self.outfile)

return u_unplanned_vacuum

def avail_st(self, output: bool):
"""Routine to calculate availability for plant with a Spherical Tokamak
:param output: indicate whether output should be written to the output file, or not
:type output: boolean
"""
# CP lifetime
cv.cplife = self.cp_lifetime()

# Time for a maintenance cycle (years)
# Lifetime of CP + time to replace
maint_cycle = cv.cplife + cv.tmain

# Number of maintenance cycles over plant lifetime
n_cycles_main = cv.tlife / maint_cycle

# Number of centre columns over plant lifetime
n_centre_cols = math.ceil(n_cycles_main)

# Planned unavailability
u_planned = cv.tmain / maint_cycle

# Operational time (years)
cv.t_operation = cv.tlife * (1.0e0 - u_planned)

# Total availability
cv.cfactr = max(
1.0e0 - (u_planned + cv.u_unplanned + u_planned * cv.u_unplanned), 0.0e0
)

# Capacity factor
cv.cpfact = cv.cfactr * (tv.tburn / tv.tcycle)

if output:
po.oheadr(self.outfile, "Plant Availability")
if tfv.i_tf_sup == 1:
po.ovarre(
self.outfile,
"Max fast neutron fluence on TF coil (n/m2)",
"(nflutfmax)",
ctv.nflutfmax,
"OP ",
)
po.ovarre(
self.outfile,
"Centrepost TF fast neutron flux (E > 0.1 MeV) (m^(-2).^(-1))",
"(neut_flux_cp)",
fwbsv.neut_flux_cp,
"OP ",
)
else:
po.ovarre(
self.outfile,
"Allowable ST centrepost neutron fluence (MW-yr/m2)",
"(cpstflnc)",
cv.cpstflnc,
"OP ",
)
po.ovarre(
self.outfile,
"Average neutron wall load (MW/m2)",
"(wallmw)",
pv.wallmw,
"OP ",
)
po.ovarre(
self.outfile,
"Centrepost lifetime (years)",
"(cplife)",
cv.cplife,
"OP ",
)
po.oblnkl(self.outfile)
po.ovarre(
self.outfile,
"Length of maintenance cycle (years)",
"(maint_cycle)",
maint_cycle,
"OP ",
)
po.ovarre(
self.outfile,
"Number of maintenance cycles over lifetime",
"(n_cycles_main)",
n_cycles_main,
"OP ",
)
po.ovarre(
self.outfile,
"Number of centre columns over lifetime",
"(n_centre_cols)",
n_centre_cols,
"OP ",
)
po.oblnkl(self.outfile)
po.ovarre(
self.outfile,
"Total planned unavailability",
"(u_planned)",
u_planned,
"OP ",
)
po.ovarre(
self.outfile,
"Total unplanned unavailability",
"(u_unplanned)",
cv.u_unplanned,
"IP ",
)
po.ovarre(
self.outfile,
"Total plant availability fraction",
"(cfactr)",
cv.cfactr,
"OP ",
)
po.ovarre(
self.outfile,
"Capacity factor: total lifetime elec. energy output / output power",
"(cpfact)",
cv.cpfact,
"OP ",
)
po.ovarre(
self.outfile,
"Total DT operational time (years)",
"(t_operation)",
cv.t_operation,
"OP ",
)
po.ovarre(
self.outfile, "Total plant lifetime (years)", "(tlife)", cv.tlife, "OP"
)

def cp_lifetime(self):
"""Calculate Centrepost Lifetime
This routine calculates the lifetime of the centrepost,
either for superconducting or aluminium/resistive magnets.
:returns: CP lifetime
:rtype: float
"""
# SC magnets CP lifetime
# Rem : only the TF maximum fluence is considered for now
if tfv.i_tf_sup == 1:
cplife = min(ctv.nflutfmax / (fwbsv.neut_flux_cp * YEAR_SECONDS), cv.tlife)

# Aluminium/Copper magnets CP lifetime
# For now, we keep the original def, developped for GLIDCOP magnets ...
else:
cplife = min(cv.cpstflnc / pv.wallmw, cv.tlife)

return cplife
7 changes: 7 additions & 0 deletions source/fortran/cost_variables.f90
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ module cost_variables
!! - =0 use input value for cfactr
!! - =1 calculate cfactr using Taylor and Ward 1999 model
!! - =2 calculate cfactr using new (2015) model
!! - =3 calculate cfactr using ST model

integer :: ibkt_life
!! Switch for fw/blanket lifetime calculation in availability module:
Expand Down Expand Up @@ -342,6 +343,12 @@ module cost_variables
real(dp) :: tlife
!! Full power year plant lifetime (years)

real(dp) :: tmain
!! Maintenance time for replacing CP (years) (iavail = 3)

real(dp) :: u_unplanned
!! User-input CP unplanned unavailability (iavail = 3)

real(dp), parameter :: ucad = 180.0D0
!! unit cost for administration buildings (M$/m3)

Expand Down
10 changes: 8 additions & 2 deletions source/fortran/input.f90
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ subroutine parse_input_file(in_file,out_file,show_changes)
ucblli, ucpfcb, tlife, ipnet, fcdfuel, ucbus, ucpfb, uchts, &
maintenance_fwbs, fwbs_prob_fail, uclh, ucblss, ucblvd, ucsc, ucturb, &
ucpens, cland, ucwindpf, i_cp_lifetime, cplife_input, &
startupratio
startupratio, tmain, u_unplanned
use current_drive_variables, only: pinjfixmw, etaech, pinjalw, etanbi, &
ftritbm, gamma_ecrh, pheat, beamwd, enbeam, pheatfix, bscfmax, &
forbitloss, nbshield, tbeamin, feffcd, iefrf, iefrffix, irfcd, cboot, &
Expand Down Expand Up @@ -2563,6 +2563,12 @@ subroutine parse_input_file(in_file,out_file,show_changes)
case ('startupratio')
call parse_real_variable('startupratio', startupratio, 0.0D0, 10.0D0, &
'Ratio (additional HCD power for start-up) / (flat-top operational requirements)')
case ('tmain')
call parse_real_variable('tmain', tmain, 0.0D0, 100.0D0, &
'Maintenance time for replacing CP (years) (iavail = 3)')
case ('u_unplanned')
call parse_real_variable('u_unplanned', u_unplanned, 0.0D0, 1.0D0, &
'User-input CP unplanned unavailability (iavail = 3)')

! Unit cost settings

Expand Down Expand Up @@ -2781,7 +2787,7 @@ subroutine parse_input_file(in_file,out_file,show_changes)
! Availability settings

case ('iavail')
call parse_int_variable('iavail', iavail, 0, 2, &
call parse_int_variable('iavail', iavail, 0, 3, &
'Switch for plant availability model')
case ('ibkt_life')
call parse_int_variable('ibkt_life', ibkt_life, 0, 2, &
Expand Down
Loading

0 comments on commit 6e7a2c8

Please sign in to comment.