diff --git a/documentation/proc-pages/eng-models/plant-availability.md b/documentation/proc-pages/eng-models/plant-availability.md index c07da428d3..b23f6044ab 100644 --- a/documentation/proc-pages/eng-models/plant-availability.md +++ b/documentation/proc-pages/eng-models/plant-availability.md @@ -35,9 +35,6 @@ It is assumed that the vacuum system can be maintained in parallel with blanket 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}}. $$ @@ -50,7 +47,7 @@ $$ 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}} $$ +$$ U_{\text{planned,CP}} = t_{\text{main}} / t_{\text{maint cycle}} $$ and the total operational time is given by @@ -60,7 +57,9 @@ 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. +where $U_{\text{unplanned}}$ is unplanned unavailability. The cross term takes account of overlap between planned and unplanned unavailability. + +This model uses the unplanned unavailability calculations implemented in `iavail = 2` (see above). This includes the magnets, divertor, first wall and blanket, balance of plant, heating and current drive, and vacuum systems. The centrepost unplanned unavailability $U_{\text{unplanned,CP}}$ is provided by the user i.e. how often do you expect the centrepost to break over its lifetime. These unplanned unavailabilities are then added to $U_{\text{unplanned,CP}}$ to give $U_{\text{unplanned}}$. Finally, the capcity factor is given by diff --git a/process/availability.py b/process/availability.py index 78e5ceefb1..8cb39e9113 100644 --- a/process/availability.py +++ b/process/availability.py @@ -1004,16 +1004,54 @@ def avail_st(self, output: bool): # Operational time (years) cv.t_operation = cv.tlife * (1.0e0 - u_planned) + if output: + po.oheadr(self.outfile, "Plant Availability") + + # Un-planned unavailability + + # Magnets + u_unplanned_magnets = self.calc_u_unplanned_magnets(output) + + # Divertor + u_unplanned_div = self.calc_u_unplanned_divertor(output) + + # First wall and blanket + u_unplanned_fwbs = self.calc_u_unplanned_fwbs(output) + + # Balance of plant + u_unplanned_bop = self.calc_u_unplanned_bop(output) + + # Heating and current drive + u_unplanned_hcd = self.calc_u_unplanned_hcd() + + # Vacuum systems + + # Number of redundant pumps + cv.redun_vac = math.floor(vacv.vpumpn * cv.redun_vacp / 100.0 + 0.5e0) + + u_unplanned_vacuum = self.calc_u_unplanned_vacuum(output) + + # Total unplanned unavailability + u_unplanned = min( + 1.0e0, + u_unplanned_magnets + + u_unplanned_div + + u_unplanned_fwbs + + u_unplanned_bop + + u_unplanned_hcd + + u_unplanned_vacuum + + cv.u_unplanned_cp, + ) + # Total availability cv.cfactr = max( - 1.0e0 - (u_planned + cv.u_unplanned + u_planned * cv.u_unplanned), 0.0e0 + 1.0e0 - (u_planned + u_unplanned + u_planned * 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, @@ -1085,8 +1123,8 @@ def avail_st(self, output: bool): self.outfile, "Total unplanned unavailability", "(u_unplanned)", - cv.u_unplanned, - "IP ", + u_unplanned, + "OP ", ) po.ovarre( self.outfile, diff --git a/source/fortran/cost_variables.f90 b/source/fortran/cost_variables.f90 index f677f61b9b..5877b36329 100644 --- a/source/fortran/cost_variables.f90 +++ b/source/fortran/cost_variables.f90 @@ -346,7 +346,7 @@ module cost_variables real(dp) :: tmain !! Maintenance time for replacing CP (years) (iavail = 3) - real(dp) :: u_unplanned + real(dp) :: u_unplanned_cp !! User-input CP unplanned unavailability (iavail = 3) real(dp), parameter :: ucad = 180.0D0 @@ -752,6 +752,7 @@ subroutine init_cost_variables ucwindpf = 465.0D0 ucwindtf = 480.0D0 ucwst = (/0.0D0, 3.94D0, 5.91D0, 7.88D0/) + u_unplanned_cp = 0.0 i_cp_lifetime = 0 cplife_input = 2.0D0 diff --git a/source/fortran/input.f90 b/source/fortran/input.f90 index a9385813f3..d40ebaeacb 100644 --- a/source/fortran/input.f90 +++ b/source/fortran/input.f90 @@ -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, tmain, u_unplanned + startupratio, tmain, u_unplanned_cp 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, & @@ -2406,8 +2406,8 @@ subroutine parse_input_file(in_file,out_file,show_changes) 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, & + case ('u_unplanned_cp') + call parse_real_variable('u_unplanned_cp', u_unplanned_cp, 0.0D0, 1.0D0, & 'User-input CP unplanned unavailability (iavail = 3)') ! Unit cost settings diff --git a/tests/unit/test_availability.py b/tests/unit/test_availability.py index 9ed0f97c9e..117c293ee4 100644 --- a/tests/unit/test_availability.py +++ b/tests/unit/test_availability.py @@ -486,18 +486,23 @@ def test_avail_st(monkeypatch, availability): :param availability: fixture containing an initialised `Availability` object :type availability: tests.unit.test_availability.availability (functional fixture) """ + # Initialise fortran variables to keep test isolated from others + fortran.init_module.init_all_module_vars() monkeypatch.setattr(cv, "tmain", 1.0) monkeypatch.setattr(cv, "tlife", 30.0) - monkeypatch.setattr(cv, "u_unplanned", 0.1) + monkeypatch.setattr(cv, "u_unplanned_cp", 0.05) monkeypatch.setattr(tv, "tburn", 5.0) monkeypatch.setattr(tv, "tcycle", 10.0) availability.avail_st(output=False) assert pytest.approx(cv.t_operation) == 29.03225806 - assert pytest.approx(cv.cfactr) == 0.86451613 - assert pytest.approx(cv.cpfact) == 0.43225806 + assert pytest.approx(cv.cfactr) == 0.82579737 + assert pytest.approx(cv.cpfact) == 0.41289868 + + # Initialise fortran variables again to reset for other tests + fortran.init_module.init_all_module_vars() @pytest.mark.parametrize("i_tf_sup, exp", ((1, 6.337618), (0, 4)))