diff --git a/src/FUSE.jl b/src/FUSE.jl index f3c8d18ff..fe574b0ad 100644 --- a/src/FUSE.jl +++ b/src/FUSE.jl @@ -81,6 +81,7 @@ include(joinpath("actors", "limits_actor.jl")) include(joinpath("actors", "balance_plant", "heat_transfer_actor.jl")) include(joinpath("actors", "balance_plant", "thermal_cycle_actor.jl")) +include(joinpath("actors", "balance_plant", "power_needs_actor.jl")) include(joinpath("actors", "balance_plant", "balance_of_plant_actor.jl")) include(joinpath("actors", "balance_plant", "balance_of_plant_plot.jl")) diff --git a/src/actors/balance_plant/balance_of_plant_actor.jl b/src/actors/balance_plant/balance_of_plant_actor.jl index fc282b6bf..989444c27 100644 --- a/src/actors/balance_plant/balance_of_plant_actor.jl +++ b/src/actors/balance_plant/balance_of_plant_actor.jl @@ -4,8 +4,7 @@ Base.@kwdef mutable struct FUSEparameters__ActorBalanceOfPlant{T} <: ParametersActor where {T<:Real} _parent::WeakRef = WeakRef(Nothing) _name::Symbol = :not_set - needs_model::Switch{Symbol} = Switch(Symbol, [:gasc, :EU_DEMO], "-", "Power plant electrical needs model"; default=:EU_DEMO) - generator_conversion_efficiency::Entry{T} = Entry(T, "-", "Efficiency of the steam cycle, thermal to electric"; default=0.9) + generator_conversion_efficiency::Entry{T} = Entry(T, "-", "Efficiency of the generator"; default=0.95) # Appl. Therm. Eng. 76 (2015) 123–133, https://doi.org/10.1016/j.applthermaleng.2014.10.093 do_plot::Entry{Bool} = Entry(Bool, "-", "plot"; default=false) end @@ -15,16 +14,13 @@ mutable struct ActorBalanceOfPlant <: FacilityAbstractActor act::ParametersAllActors thermal_cycle_actor::ActorThermalCycle IHTS_actor::ActorHeatTransfer + power_needs_actor::ActorPowerNeeds end """ ActorBalanceOfPlant(dd::IMAS.dd, act::ParametersAllActors; kw...) Balance of plant actor that estimates the net electrical power output by comparing the balance of plant electrical needs with the electricity generated from the thermal cycle. - -* `needs_model = :gasc` simply assumes that the power to balance a plant is 7% of the electricity generated. -* `needs_model = :EU_DEMO` subdivides the power plant electrical needs to [:cryostat, :tritium_handling, :pumping] using EU-DEMO numbers. - !!! note Stores data in `dd.balance_of_plant` """ @@ -45,9 +41,10 @@ function ActorBalanceOfPlant(dd::IMAS.dd, par::FUSEparameters__ActorBalanceOfPla breeder_hi_temp, breeder_low_temp, cycle_tmax = ihts_specs(act.ActorThermalCycle.power_cycle_type) - IHTS_actor = ActorHeatTransfer(dd, act; breeder_hi_temp, breeder_low_temp) - thermal_cycle_actor = ActorThermalCycle(dd, act; Tmax=cycle_tmax, rp=3.0) - return ActorBalanceOfPlant(dd, par, act, thermal_cycle_actor, IHTS_actor) + IHTS_actor = ActorHeatTransfer(dd, act.ActorHeatTransfer, act; breeder_hi_temp, breeder_low_temp) + thermal_cycle_actor = ActorThermalCycle(dd, act.ActorThermalCycle, act; Tmax=cycle_tmax, rp=3.0) + power_needs_actor = ActorPowerNeeds(dd, act.ActorPowerNeeds, act) + return ActorBalanceOfPlant(dd, par, act, thermal_cycle_actor, IHTS_actor, power_needs_actor) end function ihts_specs(power_cycle_type::Symbol) @@ -67,38 +64,11 @@ function _step(actor::ActorBalanceOfPlant) bop = dd.balance_of_plant bop_thermal = bop.thermal_cycle - bop_thermal.generator_conversion_efficiency = par.generator_conversion_efficiency .* ones(length(bop.time)) - bop_thermal.power_electric_generated = bop_thermal.net_work .* par.generator_conversion_efficiency .* ones(length(bop.time)) - - @ddtime(bop_thermal.total_useful_heat_power = @ddtime(bop.heat_transfer.wall.heat_delivered) + @ddtime(bop.heat_transfer.divertor.heat_delivered) + @ddtime(bop.heat_transfer.breeder.heat_delivered)) - - bop_electric = bop.power_electric_plant_operation - - ## heating and current drive systems - sys = resize!(bop_electric.system, "name" => "H&CD", "index" => 1) - sys.power = zeros(length(bop.time)) - for (idx, hcd_system) in enumerate(intersect([:nbi, :ec_launchers, :ic_antennas, :lh_antennas], keys(dd))) - sub_sys = resize!(sys.subsystem, "name" => string(hcd_system), "index" => idx) - sub_sys.power = electricity(getproperty(dd, hcd_system), bop.time) - sys.power .+= sub_sys.power - end - - ## balance of plant systems - if par.needs_model == :gasc - sys = resize!(bop_electric.system, "name" => "BOP_gasc", "index" => 2) - sys.power = 0.07 .* bop_thermal.power_electric_generated - - elseif par.needs_model == :EU_DEMO - # More realistic DEMO numbers - bop_systems = [:cryostat, :tritium_handling, :pumping, :pf_active] # index 2 : 5 - for (idx, system) in enumerate(bop_systems) - sys = resize!(bop_electric.system, "name" => string(system), "index" => (idx + 1)) - sys.power = electricity(system, bop.time) - end - else - error("ActorBalanceOfPlant: par.needs_model = $(par.needs_model) not recognized") - end + @ddtime(bop_thermal.generator_conversion_efficiency = par.generator_conversion_efficiency) + finalize(step(actor.IHTS_actor)) + finalize(step(actor.thermal_cycle_actor)) + finalize(step(actor.power_needs_actor)) if par.do_plot core = sys_coords(dd) @@ -175,50 +145,4 @@ function _step(actor::ActorBalanceOfPlant) end return actor -end - -function heating_and_current_drive_calc(system_unit, time_array::Vector{<:Real}) - power_electric_total = zeros(length(time_array)) - for item_unit in system_unit - efficiency = prod([getproperty(item_unit.efficiency, i) for i in keys(item_unit.efficiency)]) - power_electric_total .+= IMAS.get_time_array(item_unit.power_launched, :data, time_array, :constant) ./ efficiency - end - return power_electric_total -end - -function electricity(nbi::IMAS.nbi, time_array::Vector{<:Real}) - return heating_and_current_drive_calc(nbi.unit, time_array) -end - -function electricity(ec_launchers::IMAS.ec_launchers, time_array::Vector{<:Real}) - return heating_and_current_drive_calc(ec_launchers.beam, time_array) -end - -function electricity(ic_antennas::IMAS.ic_antennas, time_array::Vector{<:Real}) - return heating_and_current_drive_calc(ic_antennas.antenna, time_array) -end - -function electricity(lh_antennas::IMAS.lh_antennas, time_array::Vector{<:Real}) - return heating_and_current_drive_calc(lh_antennas.antenna, time_array) -end - -function electricity(symbol::Symbol, time_array::Vector{<:Real}) - return electricity(Val{symbol}, time_array) -end - -# Dummy functions values taken from DEMO 2017 https://iopscience.iop.org/article/10.1088/0029-5515/57/1/016011 -function electricity(::Type{Val{:cryostat}}, time_array::Vector{<:Real}) - return 30e6 .* ones(length(time_array)) # MWe -end - -function electricity(::Type{Val{:tritium_handling}}, time_array::Vector{<:Real}) - return 15e6 .* ones(length(time_array)) # MWe -end - -function electricity(::Type{Val{:pumping}}, time_array::Vector{<:Real}) - return 80e6 .* ones(length(time_array)) # MWe (Note this should not be a constant!) -end - -function electricity(::Type{Val{:pf_active}}, time_array::Vector{<:Real}) - return 0e6 .* ones(length(time_array)) # MWe (Note this should not be a constant!) -end +end \ No newline at end of file diff --git a/src/actors/balance_plant/heat_transfer_actor.jl b/src/actors/balance_plant/heat_transfer_actor.jl index c09724f64..7507a0965 100644 --- a/src/actors/balance_plant/heat_transfer_actor.jl +++ b/src/actors/balance_plant/heat_transfer_actor.jl @@ -3,16 +3,6 @@ #= ================= =# # ACTOR FOR THE INTERMEDIATE HEAT TRANSFER SYSTEM -mutable struct ActorHeatTransfer <: FacilityAbstractActor - dd::IMAS.dd - par::ParametersActor - function ActorHeatTransfer(dd::IMAS.dd, par::ParametersActor; kw...) - logging_actor_init(ActorHeatTransfer) - par = par(kw...) - return new(dd, par) - end -end - const coolant_fluid = [:He, :PbLi] Base.@kwdef mutable struct FUSEparameters__ActorHeatTransfer{T} <: ParametersActor where {T<:Real} @@ -40,6 +30,11 @@ Base.@kwdef mutable struct FUSEparameters__ActorHeatTransfer{T} <: ParametersAct divertor_coolant::Switch{Symbol} = Switch(Symbol, coolant_fluid, "-", "Breeder coolant fluid"; default=:He) end +mutable struct ActorHeatTransfer <: FacilityAbstractActor + dd::IMAS.dd + par::FUSEparameters__ActorHeatTransfer +end + """ ActorHeatTransfer(dd::IMAS.dd, act::ParametersAllActors; kw...) @@ -54,6 +49,12 @@ function ActorHeatTransfer(dd::IMAS.dd, act::ParametersAllActors; kw...) return actor end +function ActorHeatTransfer(dd::IMAS.dd, par::FUSEparameters__ActorHeatTransfer, act::ParametersAllActors; kw...) + logging_actor_init(ActorHeatTransfer) + par = par(kw...) + return ActorHeatTransfer(dd, par) +end + function _step(actor::ActorHeatTransfer) dd = actor.dd par = actor.par @@ -155,7 +156,7 @@ function pbLi_props(Temperature) return [specific_heat, density] end -function gas_circulator(rp, Tin, effC, mflow, nstages = 1) +function gas_circulator(rp, Tin, effC, mflow, nstages=1) cp = 5.1926e3 cv = 3.1156e3 a1c = (effC * (rp^(1.0 / nstages))^(cv / cp) - (rp^(1.0 / nstages))^(cv / cp) + rp^(1.0 / nstages)) / (effC * (rp^(1.0 / nstages))^(cv / cp)) diff --git a/src/actors/balance_plant/power_needs_actor.jl b/src/actors/balance_plant/power_needs_actor.jl new file mode 100644 index 000000000..cda7c922d --- /dev/null +++ b/src/actors/balance_plant/power_needs_actor.jl @@ -0,0 +1,148 @@ +#= =============== =# +# ActorPowerNeeds # +#= =============== =# +Base.@kwdef mutable struct FUSEparameters__ActorPowerNeeds{T} <: ParametersActor where {T<:Real} + _parent::WeakRef = WeakRef(Nothing) + _name::Symbol = :not_set + model::Switch{Symbol} = Switch(Symbol, [:gasc, :EU_DEMO, :FUSE], "-", "Power plant electrical needs model"; default=:FUSE) + do_plot::Entry{Bool} = Entry(Bool, "-", "plot"; default=false) +end + +mutable struct ActorPowerNeeds <: FacilityAbstractActor + dd::IMAS.dd + par::FUSEparameters__ActorPowerNeeds +end + +""" + ActorPowerNeeds(dd::IMAS.dd, act::ParametersAllActors; kw...) + +Power needs actor that calculates the needed power to operate the plant + +* `model = :gasc` simply assumes that the power to balance a plant is 7% of the electricity generated. +* `model = :EU_DEMO` subdivides the power plant electrical needs to [:cryostat, :tritium_handling, :pumping] using EU-DEMO numbers. +* `model = :FUSE` subdivides power plant needs and self-consistently calculates the power needs according to FUSE +!!! note + Stores data in `dd.balance_of_plant.power_electric_plant_operation` +""" +function ActorPowerNeeds(dd::IMAS.dd, act::ParametersAllActors; kw...) + par = act.ActorPowerNeeds(kw...) + actor = ActorPowerNeeds(dd, par, act) + step(actor) + finalize(actor) + return actor +end + +function ActorPowerNeeds(dd::IMAS.dd, par::FUSEparameters__ActorPowerNeeds, act::ParametersAllActors; kw...) + logging_actor_init(ActorPowerNeeds) + par = par(kw...) + return ActorPowerNeeds(dd, par) +end + +function _step(actor::ActorPowerNeeds) + dd = actor.dd + par = actor.par + bop = dd.balance_of_plant + + bop_electric = bop.power_electric_plant_operation + + ## heating and current drive systems + sys = resize!(bop_electric.system, "name" => "H&CD", "index" => 1) + sys.power = zeros(length(bop.time)) + for (idx, hcd_system) in enumerate(intersect([:nbi, :ec_launchers, :ic_antennas, :lh_antennas], keys(dd))) + sub_sys = resize!(sys.subsystem, "name" => string(hcd_system), "index" => idx) + @ddtime(sub_sys.power = electricity(getproperty(dd, hcd_system))) + sys.power .+= sub_sys.power + end + + ## Other subsytems based on model + if par.model == :gasc + sys = resize!(bop_electric.system, "name" => "BOP_gasc", "index" => 2) + sys.power = 0.07 .* bop_thermal.power_electric_generated + + elseif par.model == :FUSE + + # For now electrical needs same as DEMO but pumping self-consistent + bop_systems = [:cryostat, :tritium_handling, :pf_active] + for (idx, system) in enumerate(bop_systems) + if system == :pf_active + idx += 1 + end + sys = resize!(bop_electric.system, "name" => string(system), "index" => (idx + 1)) + @ddtime(sys.power = electricity(system)) + end + + sys = resize!(bop_electric.system, "name" => "pumping", "index" => 5) + @ddtime(sys.power = electricity(:pumping, dd.balance_of_plant)) + + elseif par.model == :EU_DEMO + # More realistic DEMO numbers + bop_systems = [:cryostat, :tritium_handling, :pumping, :pf_active] # index 2 : 5 + for (idx, system) in enumerate(bop_systems) + sys = resize!(bop_electric.system, "name" => string(system), "index" => (idx + 1)) + sys.power = electricity(system) + end + end + return actor +end + +function heating_and_current_drive_calc(system_unit) + power_electric_total = 0.0 + for item_unit in system_unit + efficiency = prod([getproperty(item_unit.efficiency, i) for i in keys(item_unit.efficiency)]) + power_electric_total += @ddtime(item_unit.power_launched.data) / efficiency + end + return power_electric_total +end + +function electricity(nbi::IMAS.nbi) + return heating_and_current_drive_calc(nbi.unit) +end + +function electricity(ec_launchers::IMAS.ec_launchers) + return heating_and_current_drive_calc(ec_launchers.beam) +end + +function electricity(ic_antennas::IMAS.ic_antennas) + return heating_and_current_drive_calc(ic_antennas.antenna) +end + +function electricity(lh_antennas::IMAS.lh_antennas) + return heating_and_current_drive_calc(lh_antennas.antenna) +end + +function electricity(symbol::Symbol) + return electricity(Val{symbol}) +end + +#= =================== =# +# EU DEMO electricity # +#= =================== =# + +# Dummy functions values taken from DEMO 2017 https://iopscience.iop.org/article/10.1088/0029-5515/57/1/016011 +function electricity(::Type{Val{:cryostat}}) + return 30e6 # We +end + +function electricity(::Type{Val{:tritium_handling}}) + return 15e6# We +end + +function electricity(::Type{Val{:pumping}}) + return 80e6 # We (Note this should not be a constant!) +end + +function electricity(::Type{Val{:pf_active}}) + return 0.0 # We (Note this should not be a constant!) +end + +#= =================== =# +# FUSE electricity # +#= =================== =# + +function electricity(::Type{Val{:pumping}}, bop::IMAS.balance_of_plant) + return @ddtime(bop.heat_transfer.breeder.circulator_power) + @ddtime(bop.heat_transfer.divertor.circulator_power) + @ddtime(bop.heat_transfer.wall.circulator_power) +end + +function electricity(symbol::Symbol, bop::IMAS.balance_of_plant) + return electricity(Val{symbol}, bop) +end \ No newline at end of file diff --git a/src/actors/balance_plant/thermal_cycle_actor.jl b/src/actors/balance_plant/thermal_cycle_actor.jl index e44779ce3..3a94aac67 100644 --- a/src/actors/balance_plant/thermal_cycle_actor.jl +++ b/src/actors/balance_plant/thermal_cycle_actor.jl @@ -6,16 +6,6 @@ # SIMPLE rankine # COMBINED BRAYTON RANKINE #= =================== =# -mutable struct ActorThermalCycle <: FacilityAbstractActor - dd::IMAS.dd - par::ParametersActor - act::ParametersAllActors - function ActorThermalCycle(dd::IMAS.dd, par::ParametersActor, act; kw...) - logging_actor_init(ActorThermalCycle) - par = par(kw...) - return new(dd, par, act) - end -end Base.@kwdef mutable struct FUSEparameters__ActorThermalCycle{T} <: ParametersActor where {T<:Real} _parent::WeakRef = WeakRef(Nothing) @@ -31,6 +21,17 @@ Base.@kwdef mutable struct FUSEparameters__ActorThermalCycle{T} <: ParametersAct do_plot::Entry{Bool} = Entry(Bool, "-", "plot"; default=false) end +mutable struct ActorThermalCycle <: FacilityAbstractActor + dd::IMAS.dd + par::FUSEparameters__ActorThermalCycle + act::ParametersAllActors + function ActorThermalCycle(dd::IMAS.dd, par::FUSEparameters__ActorThermalCycle, act::ParametersAllActors; kw...) + logging_actor_init(ActorThermalCycle) + par = par(kw...) + return new(dd, par, act) + end +end + """ ActorThermalCycle(dd::IMAS.dd, act::ParametersAllActors; kw...) @@ -51,7 +52,6 @@ end function _step(actor::ActorThermalCycle) dd = actor.dd par = actor.par - act = actor.act bop = dd.balance_of_plant ihts = bop.heat_transfer wall = ihts.wall @@ -61,7 +61,7 @@ function _step(actor::ActorThermalCycle) bop.power_cycle_type = string(par.power_cycle_type) bop_thermal = bop.thermal_cycle - ihts_par = act.ActorHeatTransfer + ihts_par = actor.act.ActorHeatTransfer blanket_power = @ddtime(bop.heat_transfer.wall.heat_load) breeder_power = @ddtime(bop.heat_transfer.breeder.heat_load) diff --git a/src/parameters_actors.jl b/src/parameters_actors.jl index 4b2844d5f..fe5ef1865 100644 --- a/src/parameters_actors.jl +++ b/src/parameters_actors.jl @@ -13,6 +13,7 @@ mutable struct ParametersActors{T} <: ParametersAllActors where {T<:Real} ActorPassiveStructures::FUSEparameters__ActorPassiveStructures{T} ActorBlanket::FUSEparameters__ActorBlanket{T} ActorBalanceOfPlant::FUSEparameters__ActorBalanceOfPlant{T} + ActorPowerNeeds::FUSEparameters__ActorPowerNeeds{T} ActorThermalCycle::FUSEparameters__ActorThermalCycle{T} ActorHeatTransfer::FUSEparameters__ActorHeatTransfer{T} ActorQEDcurrent::FUSEparameters__ActorQEDcurrent{T} @@ -51,6 +52,7 @@ function ParametersActors{T}() where {T<:Real} FUSEparameters__ActorPassiveStructures{T}(), FUSEparameters__ActorBlanket{T}(), FUSEparameters__ActorBalanceOfPlant{T}(), + FUSEparameters__ActorPowerNeeds{T}(), FUSEparameters__ActorThermalCycle{T}(), FUSEparameters__ActorHeatTransfer{T}(), FUSEparameters__ActorQEDcurrent{T}(),