Skip to content

Commit

Permalink
Merge pull request #251 from ProjectTorreyPines/BOP_power_needs
Browse files Browse the repository at this point in the history
Creation of ActorPowerNeeds initial commit
  • Loading branch information
TimSlendebroek authored Mar 20, 2023
2 parents 127b02a + a833958 commit aa599a7
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 110 deletions.
1 change: 1 addition & 0 deletions src/FUSE.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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"))

Expand Down
98 changes: 11 additions & 87 deletions src/actors/balance_plant/balance_of_plant_actor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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`
"""
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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
23 changes: 12 additions & 11 deletions src/actors/balance_plant/heat_transfer_actor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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...)
Expand All @@ -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
Expand Down Expand Up @@ -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))
Expand Down
148 changes: 148 additions & 0 deletions src/actors/balance_plant/power_needs_actor.jl
Original file line number Diff line number Diff line change
@@ -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
24 changes: 12 additions & 12 deletions src/actors/balance_plant/thermal_cycle_actor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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...)
Expand All @@ -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
Expand All @@ -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)
Expand Down
Loading

0 comments on commit aa599a7

Please sign in to comment.