From 588c53cf1da0f08e9887b648603f220640590f24 Mon Sep 17 00:00:00 2001 From: TimSlendebroek <32385057+TimSlendebroek@users.noreply.github.com> Date: Fri, 22 Jul 2022 15:07:14 -0700 Subject: [PATCH 01/74] use pressure_core instead of Beta_n for equi and core_prof init --- cases/ARC.jl | 8 ++++++-- cases/ITER.jl | 5 +++++ cases/SPARC.jl | 11 ++++++----- src/actors/equilibrium_actors.jl | 8 +++++++- src/ddinit/init.jl | 6 ++++++ src/ddinit/init_core_profiles.jl | 13 +++++++++++++ src/ddinit/init_equilibrium.jl | 5 +++-- src/parameters_init.jl | 1 + 8 files changed, 47 insertions(+), 10 deletions(-) diff --git a/cases/ARC.jl b/cases/ARC.jl index a2e401e39..ae7cbbc53 100644 --- a/cases/ARC.jl +++ b/cases/ARC.jl @@ -16,7 +16,8 @@ function case_parameters(::Type{Val{:ARC}})::Tuple{ParametersAllInits, Parameter ini.equilibrium.B0 = -11.5 ini.equilibrium.Z0 = 0.0 ini.equilibrium.ip = 9.9e6 - ini.equilibrium.βn = 1.4 + ini.equilibrium.βn = 0.5 + ini.equilibrium.pressure_core = 1.45e6 ini.equilibrium.x_point = (3.1, 1.85) ini.equilibrium.symmetric = true act.ActorCXbuild.rebuild_wall = false @@ -52,7 +53,7 @@ function case_parameters(::Type{Val{:ARC}})::Tuple{ParametersAllInits, Parameter ini.oh.technology = coil_technology(:HTS) ini.oh.flattop_duration = 1800 - ini.core_profiles.ne_ped = 7e19 #estimate (from ITER) + ini.core_profiles.ne_ped = 1.0e20 ini.core_profiles.greenwald_fraction = 0.49 ini.core_profiles.helium_fraction = 0.10 #estimate ini.core_profiles.T_shaping = 1.8 #estimate (from ITER) @@ -65,6 +66,9 @@ function case_parameters(::Type{Val{:ARC}})::Tuple{ParametersAllInits, Parameter ini.ic_antennas.power_launched = 4 * 1e6 #rf power coupled act.ActorPFcoilsOpt.symmetric = true #note: symmetric, but not evenly spaced + act.ActorEquilibrium.model = :CHEASE + act.ActorCHEASE.rescale_eq_to_ip = true # This scales j_tor to match Ip (but also scales pressure) + return set_new_base!(ini), set_new_base!(act) end diff --git a/cases/ITER.jl b/cases/ITER.jl index a0cd0af3e..5fc9d4ac7 100644 --- a/cases/ITER.jl +++ b/cases/ITER.jl @@ -30,6 +30,7 @@ function case_parameters(::Type{Val{:ITER}}; init_from::Symbol)::Tuple{Parameter ini.equilibrium.Z0 = 0.4 ini.equilibrium.ip = 15e6 ini.equilibrium.βn = 2.0 + ini.equilibrium.pressure_core = 0.643e6 ini.equilibrium.x_point = true ini.equilibrium.symmetric = false ini.equilibrium.MXH_params = [ @@ -37,8 +38,12 @@ function case_parameters(::Type{Val{:ITER}}; init_from::Symbol)::Tuple{Parameter 0.15912, -0.05842, -0.04573, 0.00694, 0.00614, 0.00183, 0.43714, 0.09583, -0.05597, -0.01655, 0.00204, 0.00306] + ini.equilibrium.boundary_from = :MXH_params act.ActorCXbuild.rebuild_wall = true act.ActorHFSsizing.fixed_aspect_ratio = true + act.ActorEquilibrium.model = :CHEASE + act.ActorCHEASE.rescale_eq_to_ip = true # This scales j_tor to match Ip (but also scales pressure) + end # explicitly set thickness of radial build layers diff --git a/cases/SPARC.jl b/cases/SPARC.jl index cdad63ffc..f46dbf042 100644 --- a/cases/SPARC.jl +++ b/cases/SPARC.jl @@ -16,7 +16,8 @@ function case_parameters(::Type{Val{:SPARC}})::Tuple{ParametersAllInits, Paramet ini.equilibrium.B0 = -12.2 ini.equilibrium.Z0 = 0.0 ini.equilibrium.ip = 8.7e6 - ini.equilibrium.βn = 9.05 # high beta only for purpose of getting reasonable boundary with Solovev (otherwise should be 1) + ini.equilibrium.βn = 0.5 + ini.equilibrium.pressure_core = 2.22e6 ini.equilibrium.x_point = (1.55, 1.1) ini.equilibrium.symmetric = true act.ActorCXbuild.rebuild_wall = false @@ -48,7 +49,7 @@ function case_parameters(::Type{Val{:SPARC}})::Tuple{ParametersAllInits, Paramet ini.oh.technology = coil_technology(:HTS) ini.oh.flattop_duration = 10 - ini.core_profiles.ne_ped = 1.9e20 + ini.core_profiles.ne_ped = 1.5e20 ini.core_profiles.greenwald_fraction = 0.37 ini.core_profiles.helium_fraction = 0.1 #estimate ini.core_profiles.T_shaping = 1.8 #estimate (from ITER) @@ -58,12 +59,12 @@ function case_parameters(::Type{Val{:SPARC}})::Tuple{ParametersAllInits, Paramet ini.core_profiles.bulk = :DT ini.core_profiles.impurity = :Ne #estimate (from ITER) - ini.nbi.power_launched = 0.0 - ini.nbi.beam_energy = 0.0 - ini.ec_launchers.power_launched = 0.0 ini.ic_antennas.power_launched = 11.1 * 1e6 #25 MW maximum available, P_threshold = 21 MW act.ActorPFcoilsOpt.symmetric = true + act.ActorEquilibrium.model = :CHEASE + act.ActorCHEASE.rescale_eq_to_ip = true # This scales j_tor to match Ip (but also scales pressure) + return set_new_base!(ini), set_new_base!(act) end diff --git a/src/actors/equilibrium_actors.jl b/src/actors/equilibrium_actors.jl index 90c0287fa..080f8ad1b 100644 --- a/src/actors/equilibrium_actors.jl +++ b/src/actors/equilibrium_actors.jl @@ -336,6 +336,7 @@ function ParametersActor(::Type{Val{:ActorCHEASE}}) par = ParametersActor(nothing) par.free_boundary = Entry(Bool, "", "Convert fixed boundary equilibrium to free boundary one"; default=true) par.clear_workdir = Entry(Bool, "", "Clean the temporary workdir for CHEASE"; default=true) + par.rescale_eq_to_ip = Entry(Bool, "", ""; default=false) return par end @@ -400,7 +401,12 @@ function step(actor::ActorCHEASE) rho_pol = sqrt.(eq1d.psi_norm) pressure_sep = pressure[end] - actor.chease = CHEASE.run_chease(ϵ, z_geo, pressure_sep, Bt_geo, r_geo, Ip, r_bound, z_bound, 82, rho_pol, pressure, j_tor, clear_workdir=actor.par.clear_workdir) + actor.chease = CHEASE.run_chease( + ϵ, z_geo, pressure_sep, Bt_geo, + r_geo, Ip, r_bound, z_bound, 82, + rho_pol, pressure, j_tor, + rescale_eq_to_ip=actor.par.rescale_eq_to_ip, + clear_workdir=actor.par.clear_workdir) if actor.par.free_boundary # convert from fixed to free boundary equilibrium diff --git a/src/ddinit/init.jl b/src/ddinit/init.jl index fff36a85a..d7a5ef030 100644 --- a/src/ddinit/init.jl +++ b/src/ddinit/init.jl @@ -65,6 +65,12 @@ function init(dd::IMAS.dd, ini::ParametersAllInits, act::ParametersAllActors; do # initialize missing IDSs from ODS (if loading from ODS) init_missing_from_ods(dd, ini, act) + # If the equilibrium was initialized from chease and the current and pressure was rescaled to match Ip recalculate the equilibrium + if act.ActorCHEASE.rescale_eq_to_ip + act.ActorCHEASE.rescale_eq_to_ip = false + prepare(dd, :ActorEquilibrium, act) + ActorEquilibrium(dd,act) + end return dd end diff --git a/src/ddinit/init_core_profiles.jl b/src/ddinit/init_core_profiles.jl index 197aec906..c87693e89 100644 --- a/src/ddinit/init_core_profiles.jl +++ b/src/ddinit/init_core_profiles.jl @@ -19,11 +19,17 @@ function init_core_profiles(dd::IMAS.dd, ini::ParametersAllInits, act::Parameter end if init_from == :scalars + if ismissing(ini.equilibrium, :pressure_core) + pressure_core = dd.equilibrium.time_slice[].profiles_1d.pressure[1] + else + pressure_core = ini.equilibrium.pressure_core + end init_core_profiles( dd.core_profiles, dd.equilibrium, dd.summary; ne_ped=ini.core_profiles.ne_ped, + pressure_core=pressure_core, greenwald_fraction=ini.core_profiles.greenwald_fraction, helium_fraction=ini.core_profiles.helium_fraction, T_shaping=ini.core_profiles.T_shaping, @@ -48,6 +54,7 @@ function init_core_profiles( eq::IMAS.equilibrium, summary::IMAS.summary; ne_ped::Real, + pressure_core::Real, greenwald_fraction::Real, helium_fraction::Real, w_ped::Real, @@ -94,6 +101,7 @@ function init_core_profiles( return (nel / ngw - greenwald_fraction)^2 end ne0_guess = ne_ped * 1.4 + @show IMAS.greenwald_density(eqt) res = Optim.optimize(cost_greenwald_fraction, [ne0_guess], Optim.NelderMead(), Optim.Options(g_tol=1E-4)) ne_core = res.minimizer[1] cp1d.electrons.density_thermal = Hmode_profiles(0.5 * ne_ped, ne_ped, ne_core, ngrid, n_shaping, n_shaping, w_ped) @@ -107,8 +115,10 @@ function init_core_profiles( niFraction[1] = (zimp - zeff + 4 * niFraction[3] - 2 * zimp * niFraction[3]) / (zimp - 1) niFraction[2] = (zeff - niFraction[1] - 4 * niFraction[3]) / zimp^2 @assert !any(niFraction .< 0.0) "zeff impossible to match for given helium fraction [$helium_fraction] and zeff [$zeff]" + ni_core = 0. for i = 1:length(cp1d.ion) cp1d.ion[i].density_thermal = cp1d.electrons.density_thermal .* niFraction[i] + ni_core += cp1d.electrons.density_thermal[1] * niFraction[i] end # Set temperatures @@ -118,6 +128,9 @@ function init_core_profiles( Ip = eqt.global_quantities.ip a = eqt.boundary.minor_radius Te_core = 10.0 * betaN * abs(Bt * (Ip / 1e6)) / a / (ne_core / 1e20) / (2.0 * 1.6e1 * 4.0 * pi * 1.0e-4) + @show Te_core ,"before" + Te_core = pressure_core / (ni_core + ne_core) / IMAS.constants.e + @show Te_core ,"after" Te_ped = Te_core / 4 cp1d.electrons.temperature = Hmode_profiles(80.0, Te_ped, Te_core, ngrid, T_shaping, T_shaping, w_ped) for i = 1:length(cp1d.ion) diff --git a/src/ddinit/init_equilibrium.jl b/src/ddinit/init_equilibrium.jl index 5f5f6dfed..17e565d76 100644 --- a/src/ddinit/init_equilibrium.jl +++ b/src/ddinit/init_equilibrium.jl @@ -32,6 +32,7 @@ function init_equilibrium(dd::IMAS.dd, ini::ParametersAllInits, act::ParametersA δ=ini.equilibrium.δ, ζ=ini.equilibrium.ζ, βn=ini.equilibrium.βn, + pressure_core=ini.equilibrium.pressure_core, ip=ini.equilibrium.ip, boundary_switch=ini.equilibrium.boundary_from, MXH_params=getproperty(ini.equilibrium, :MXH_params, missing), @@ -80,6 +81,7 @@ function init_equilibrium( δ::Real, ζ::Real, βn::Real, + pressure_core::Real, ip::Real, boundary_switch::Symbol, rz_points::Union{Missing,Vector{Vector{<:Real}}}=missing, @@ -122,9 +124,8 @@ function init_equilibrium( # initial guesses for pressure and j_tor eq1d = eqt.profiles_1d psin = eq1d.psi = LinRange(0, 1, 129) - p_core_estimate = 6.0 * IMAS.pressure_avg_from_beta_n(βn, minor_radius, B0, ip) eq1d.j_tor = eqt.global_quantities.ip .* (1.0 .- psin .^ 2) ./ eqt.boundary.geometric_axis.r - eq1d.pressure = p_core_estimate .- p_core_estimate .* psin + eq1d.pressure = pressure_core .- pressure_core .* psin # R,Z boundary from: points if boundary_switch == :rz_points diff --git a/src/parameters_init.jl b/src/parameters_init.jl index 47c51b34e..46cf20649 100644 --- a/src/parameters_init.jl +++ b/src/parameters_init.jl @@ -28,6 +28,7 @@ function ParametersInit(::Type{Val{:equilibrium}}) equilibrium.κ = Entry(Real, IMAS.equilibrium__time_slice___boundary, :elongation) equilibrium.δ = Entry(Real, IMAS.equilibrium__time_slice___boundary, :triangularity) equilibrium.ζ = Entry(Real, IMAS.equilibrium__time_slice___boundary, :squareness; default=0.0) + equilibrium.pressure_core = Entry(Real, "Pa","On axis pressure" ) equilibrium.βn = Entry(Real, IMAS.equilibrium__time_slice___global_quantities, :beta_normal) equilibrium.ip = Entry(Real, IMAS.equilibrium__time_slice___global_quantities, :ip) equilibrium.x_point = Entry(Union{NTuple{2},Bool}, IMAS.equilibrium__time_slice___boundary, :x_point) From 51276c00b4245b7c4df41c15707938132d042ffa Mon Sep 17 00:00:00 2001 From: TimSlendebroek <32385057+TimSlendebroek@users.noreply.github.com> Date: Fri, 22 Jul 2022 15:10:44 -0700 Subject: [PATCH 02/74] equilibrium plot was updated --- examples/cases/SPARC_and_ARC.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/cases/SPARC_and_ARC.ipynb b/examples/cases/SPARC_and_ARC.ipynb index b287a779a..4173c7698 100644 --- a/examples/cases/SPARC_and_ARC.ipynb +++ b/examples/cases/SPARC_and_ARC.ipynb @@ -58,7 +58,7 @@ "source": [ "CAD = FUSE.TraceCAD(:SPARC)\n", "plot(CAD, size=(900,900))\n", - "plot!(dd.equilibrium.time_slice[], color=:red)\n", + "plot!(dd.equilibrium, cx=true, color=:red)\n", "plot!(dd.build, wireframe=true, linewidth=2, color=:black)" ] }, @@ -105,7 +105,7 @@ "source": [ "CAD = FUSE.TraceCAD(:ARC)\n", "plot(CAD, size=(900,900))\n", - "plot!(dd.equilibrium.time_slice[], color=:red)\n", + "plot!(dd.equilibrium, cx=true, color=:red)\n", "plot!(dd.build, wireframe=true, linewidth=2, color=:black)" ] } From cffca6224d803dc71b11e07cef7005c7a6a0e38c Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sat, 23 Jul 2022 22:19:20 -0700 Subject: [PATCH 03/74] Minor formatting --- src/FUSE.jl | 2 -- src/logging.jl | 2 +- src/parameters_init.jl | 4 ++-- src/technology.jl | 6 +++--- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/FUSE.jl b/src/FUSE.jl index aa45ad278..84f3b2486 100644 --- a/src/FUSE.jl +++ b/src/FUSE.jl @@ -7,8 +7,6 @@ import Plots using Plots using Printf -Plots.default(background_color_legend = Plots.Colors.RGBA(1.0,1.0,1.0,0.6)) - #= ===== =# # UTILS # #= ===== =# diff --git a/src/logging.jl b/src/logging.jl index 3f9c94475..1e5fc12f0 100644 --- a/src/logging.jl +++ b/src/logging.jl @@ -4,7 +4,7 @@ function uwanted_warnings_filter(log_args) return !any([ contains(log_args.message, "both ImageMetadata and ImageAxes export"), startswith(log_args.message, "Keyword argument letter not supported with Plots.GRBackend") - ]) + ]) end logger = ActiveFilteredLogger(uwanted_warnings_filter, global_logger()) diff --git a/src/parameters_init.jl b/src/parameters_init.jl index 47c51b34e..2df52947a 100644 --- a/src/parameters_init.jl +++ b/src/parameters_init.jl @@ -34,9 +34,9 @@ function ParametersInit(::Type{Val{:equilibrium}}) equilibrium.symmetric = Entry(Bool, "", "Is plasma up-down symmetric") equilibrium.ngrid = Entry(Int, "", "Resolution of the equilibrium grid"; default=129) equilibrium.field_null_surface = Entry(Real, "", "ψn value of the field_null_surface. Disable with 0.0"; default=0.25)#, min=0.0, max=1.0) - equilibrium.boundary_from = Switch([:scalars, :MXH_params, :rz_points], "" ,"The starting r, z boundary taken from"; default=:scalars) + equilibrium.boundary_from = Switch([:scalars, :MXH_params, :rz_points], "", "The starting r, z boundary taken from"; default=:scalars) equilibrium.MXH_params = Entry(Union{Nothing,Vector{<:Real}}, "", "Vector of MXH flats", default=missing) - equilibrium.rz_points = Entry(Union{Nothing, Vector{Vector{<:Real}}}, "m", "R_Z boundary as Vector{Vector{<:Real}}} : r = rz_points[1], z = rz_points[2]", default=missing) + equilibrium.rz_points = Entry(Union{Nothing,Vector{Vector{<:Real}}}, "m", "R_Z boundary as Vector{Vector{<:Real}}} : r = rz_points[1], z = rz_points[2]", default=missing) return equilibrium end diff --git a/src/technology.jl b/src/technology.jl index de6df39af..354c7f071 100644 --- a/src/technology.jl +++ b/src/technology.jl @@ -274,7 +274,7 @@ function solve_1D_solid_mechanics!( f_tf_sash=0.873, # : (float), conversion factor from hoop stress to axial stress for TF coil (nominally 0.873) f_oh_sash=0.37337, # : (float), conversion factor from hoop stress to axial stress for OH coil (nominally 0.37337) n_points::Integer=21, # : (int), number of radial points - verbose::Bool=false, # : (bool), flag for verbose output to terminal + verbose::Bool=false # : (bool), flag for verbose output to terminal ) tp = typeof(promote(R0, B0, R_tf_in, R_tf_out, Bz_oh, R_oh_in, R_oh_out)[1]) @@ -535,11 +535,11 @@ function solve_1D_solid_mechanics!( hoop_stress_tf = sh(r_tf, em_tf, gam_tf, displacement_tf, ddiplacementdr_tf) if axial_stress_tf_avg === nothing - hoop_stress_tf_avg = sum(hoop_stress_tf)/length(hoop_stress_tf) + hoop_stress_tf_avg = sum(hoop_stress_tf) / length(hoop_stress_tf) axial_stress_tf_avg = -f_tf_sash * hoop_stress_tf_avg end if axial_stress_oh_avg === nothing - hoop_stress_oh_avg = sum(hoop_stress_oh)/length(hoop_stress_oh) + hoop_stress_oh_avg = sum(hoop_stress_oh) / length(hoop_stress_oh) axial_stress_oh_avg = -f_oh_sash * hoop_stress_oh_avg end From efec1a35b3019d62bd15def94a4aa18c8234bb3b Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sun, 24 Jul 2022 00:09:24 -0700 Subject: [PATCH 04/74] Don't use beta_n for solovev --- cases/ARC.jl | 5 +---- cases/ITER.jl | 5 +---- cases/SPARC.jl | 5 +---- src/actors/equilibrium_actors.jl | 33 +++++++++++++++++--------------- src/ddinit/init.jl | 7 +------ src/ddinit/init_core_profiles.jl | 21 ++++++-------------- src/ddinit/init_equilibrium.jl | 21 ++++++++++---------- src/gasc.jl | 9 ++++++++- src/parameters.jl | 18 ----------------- src/parameters_init.jl | 3 +-- 10 files changed, 48 insertions(+), 79 deletions(-) diff --git a/cases/ARC.jl b/cases/ARC.jl index ae7cbbc53..2101a3a9e 100644 --- a/cases/ARC.jl +++ b/cases/ARC.jl @@ -16,7 +16,6 @@ function case_parameters(::Type{Val{:ARC}})::Tuple{ParametersAllInits, Parameter ini.equilibrium.B0 = -11.5 ini.equilibrium.Z0 = 0.0 ini.equilibrium.ip = 9.9e6 - ini.equilibrium.βn = 0.5 ini.equilibrium.pressure_core = 1.45e6 ini.equilibrium.x_point = (3.1, 1.85) ini.equilibrium.symmetric = true @@ -66,9 +65,7 @@ function case_parameters(::Type{Val{:ARC}})::Tuple{ParametersAllInits, Parameter ini.ic_antennas.power_launched = 4 * 1e6 #rf power coupled act.ActorPFcoilsOpt.symmetric = true #note: symmetric, but not evenly spaced - act.ActorEquilibrium.model = :CHEASE - act.ActorCHEASE.rescale_eq_to_ip = true # This scales j_tor to match Ip (but also scales pressure) - + # act.ActorEquilibrium.model = :CHEASE return set_new_base!(ini), set_new_base!(act) end diff --git a/cases/ITER.jl b/cases/ITER.jl index 5fc9d4ac7..aee7fce00 100644 --- a/cases/ITER.jl +++ b/cases/ITER.jl @@ -29,7 +29,6 @@ function case_parameters(::Type{Val{:ITER}}; init_from::Symbol)::Tuple{Parameter ini.equilibrium.B0 = -5.3 ini.equilibrium.Z0 = 0.4 ini.equilibrium.ip = 15e6 - ini.equilibrium.βn = 2.0 ini.equilibrium.pressure_core = 0.643e6 ini.equilibrium.x_point = true ini.equilibrium.symmetric = false @@ -41,9 +40,7 @@ function case_parameters(::Type{Val{:ITER}}; init_from::Symbol)::Tuple{Parameter ini.equilibrium.boundary_from = :MXH_params act.ActorCXbuild.rebuild_wall = true act.ActorHFSsizing.fixed_aspect_ratio = true - act.ActorEquilibrium.model = :CHEASE - act.ActorCHEASE.rescale_eq_to_ip = true # This scales j_tor to match Ip (but also scales pressure) - + # act.ActorEquilibrium.model = :CHEASE end # explicitly set thickness of radial build layers diff --git a/cases/SPARC.jl b/cases/SPARC.jl index f46dbf042..0ec1d0e7b 100644 --- a/cases/SPARC.jl +++ b/cases/SPARC.jl @@ -16,7 +16,6 @@ function case_parameters(::Type{Val{:SPARC}})::Tuple{ParametersAllInits, Paramet ini.equilibrium.B0 = -12.2 ini.equilibrium.Z0 = 0.0 ini.equilibrium.ip = 8.7e6 - ini.equilibrium.βn = 0.5 ini.equilibrium.pressure_core = 2.22e6 ini.equilibrium.x_point = (1.55, 1.1) ini.equilibrium.symmetric = true @@ -62,9 +61,7 @@ function case_parameters(::Type{Val{:SPARC}})::Tuple{ParametersAllInits, Paramet ini.ic_antennas.power_launched = 11.1 * 1e6 #25 MW maximum available, P_threshold = 21 MW act.ActorPFcoilsOpt.symmetric = true - act.ActorEquilibrium.model = :CHEASE - act.ActorCHEASE.rescale_eq_to_ip = true # This scales j_tor to match Ip (but also scales pressure) - + # act.ActorEquilibrium.model = :CHEASE return set_new_base!(ini), set_new_base!(act) end diff --git a/src/actors/equilibrium_actors.jl b/src/actors/equilibrium_actors.jl index 080f8ad1b..23c189646 100644 --- a/src/actors/equilibrium_actors.jl +++ b/src/actors/equilibrium_actors.jl @@ -24,19 +24,19 @@ end The ActorEquilibrium provides a common interface to run multiple equilibrium actors """ -function ActorEquilibrium(dd::IMAS.dd, act::ParametersAllActors; kw...) +function ActorEquilibrium(dd::IMAS.dd, act::ParametersAllActors; kw_ActorSolovev=Dict(), kw_ActorCHEASE=Dict(), kw...) par = act.ActorEquilibrium(kw...) - actor = ActorEquilibrium(dd, par, act) + actor = ActorEquilibrium(dd, par, act; kw_ActorSolovev, kw_ActorCHEASE, kw...) step(actor) finalize(actor) return actor end -function ActorEquilibrium(dd::IMAS.dd, par::ParametersActor, act::ParametersAllActors) +function ActorEquilibrium(dd::IMAS.dd, par::ParametersActor, act::ParametersAllActors; kw_ActorSolovev=Dict(), kw_ActorCHEASE=Dict(), kw...) if par.model == :Solovev - eq_actor = ActorSolovev(dd, act.ActorSolovev) + eq_actor = ActorSolovev(dd, act.ActorSolovev; kw_ActorSolovev...) elseif par.model == :CHEASE - eq_actor = ActorCHEASE(dd, act.ActorCHEASE) + eq_actor = ActorCHEASE(dd, act.ActorCHEASE; kw_ActorCHEASE...) else error("ActorEquilibrium: model = $(par.model) is unknown") end @@ -91,7 +91,7 @@ function ParametersActor(::Type{Val{:ActorSolovev}}) par = ParametersActor(nothing) par.ngrid = Entry(Integer, "", "Grid size (for R, Z follows proportionally to plasma elongation)"; default=129) par.qstar = Entry(Real, "", "Initial guess of kink safety factor"; default=1.5) - par.alpha = Entry(Real, "", "Initial guess of constant relating to beta regime"; default=0.0) + par.alpha = Entry(Real, "", "Initial guess of constant relating to pressure"; default=0.0) par.volume = Entry(Real, "m³", "Scalar volume to match (optional)"; default=missing) par.area = Entry(Real, "m²", "Scalar area to match (optional)"; default=missing) par.verbose = Entry(Bool, "", "verbose"; default=false) @@ -151,16 +151,18 @@ end prepare(dd::IMAS.dd, :ActorSolovev, act::ParametersAllActors; kw...) Prepare dd to run ActorSolovev -* Copy beta_n from summary to equilibrium +* Copy pressure from core_profiles to equilibrium """ function prepare(dd::IMAS.dd, ::Type{Val{:ActorSolovev}}, act::ParametersAllActors; kw...) - dd.equilibrium.time_slice[].global_quantities.beta_normal = @ddtime(dd.summary.global_quantities.beta_tor_thermal_norm.value) + eq1d = dd.equilibrium.time_slice[].profiles_1d + cp1d = dd.core_profiles.profiles_1d[] + eq1d.pressure = IMAS.interp1d(cp1d.grid.psi_norm, cp1d.pressure).(eq1d.psi_norm) end """ step(actor::ActorSolovev) -Non-linear optimization to obtain a target `ip` and `beta_normal` +Non-linear optimization to obtain a target `ip` and `pressure_core` """ function step(actor::ActorSolovev) S0 = actor.S @@ -168,16 +170,17 @@ function step(actor::ActorSolovev) eqt = actor.eq.time_slice[] target_ip = abs(eqt.global_quantities.ip) - target_beta = eqt.global_quantities.beta_normal + target_pressure_core = eqt.profiles_1d.pressure[1] - B0, R0, epsilon, delta, kappa, alpha, qstar, target_ip, target_beta = promote(S0.B0, S0.R0, S0.epsilon, S0.delta, S0.kappa, S0.alpha, S0.qstar, target_ip, target_beta) + B0, R0, epsilon, delta, kappa, alpha, qstar, target_ip, target_pressure_core = promote(S0.B0, S0.R0, S0.epsilon, S0.delta, S0.kappa, S0.alpha, S0.qstar, target_ip, target_pressure_core) function cost(x) - # NOTE: Ip/Beta calculation is very much off in Equilibrium.jl for diverted plasmas because boundary calculation is wrong + # NOTE: Ip/pressure calculation is very much off in Equilibrium.jl for diverted plasmas because boundary calculation is wrong S = Equilibrium.solovev(abs(B0), R0, epsilon, delta, kappa, x[1], x[2], B0_dir=sign(B0), Ip_dir=1, symmetric=true, x_point=nothing) - beta_cost = (Equilibrium.beta_n(S) - target_beta) / target_beta + psimag, psibry = Equilibrium.psi_limits(S) + pressure_cost = (Equilibrium.pressure(S, psimag) - target_pressure_core) / target_pressure_core ip_cost = (Equilibrium.plasma_current(S) - target_ip) / target_ip - c = beta_cost^2 + ip_cost^2 + c = pressure_cost^2 + ip_cost^2 return c end @@ -336,7 +339,7 @@ function ParametersActor(::Type{Val{:ActorCHEASE}}) par = ParametersActor(nothing) par.free_boundary = Entry(Bool, "", "Convert fixed boundary equilibrium to free boundary one"; default=true) par.clear_workdir = Entry(Bool, "", "Clean the temporary workdir for CHEASE"; default=true) - par.rescale_eq_to_ip = Entry(Bool, "", ""; default=false) + par.rescale_eq_to_ip = Entry(Bool, "", "Scale equilibrium to match Ip"; default=false) return par end diff --git a/src/ddinit/init.jl b/src/ddinit/init.jl index d7a5ef030..6a37e6bb9 100644 --- a/src/ddinit/init.jl +++ b/src/ddinit/init.jl @@ -13,6 +13,7 @@ function init(dd::IMAS.dd, ini::ParametersAllInits, act::ParametersAllActors; do if ini.general.init_from == :ods ods_items = keys(IMAS.json2imas(ini.ods.filename)) end + # initialize equilibrium if !ismissing(ini.equilibrium, :B0) || :equilibrium ∈ ods_items init_equilibrium(dd, ini, act) @@ -65,12 +66,6 @@ function init(dd::IMAS.dd, ini::ParametersAllInits, act::ParametersAllActors; do # initialize missing IDSs from ODS (if loading from ODS) init_missing_from_ods(dd, ini, act) - # If the equilibrium was initialized from chease and the current and pressure was rescaled to match Ip recalculate the equilibrium - if act.ActorCHEASE.rescale_eq_to_ip - act.ActorCHEASE.rescale_eq_to_ip = false - prepare(dd, :ActorEquilibrium, act) - ActorEquilibrium(dd,act) - end return dd end diff --git a/src/ddinit/init_core_profiles.jl b/src/ddinit/init_core_profiles.jl index c87693e89..a955e494e 100644 --- a/src/ddinit/init_core_profiles.jl +++ b/src/ddinit/init_core_profiles.jl @@ -75,10 +75,10 @@ function init_core_profiles( cp1d.zeff = ones(ngrid) .* zeff cp1d.rotation_frequency_tor_sonic = rot_core .* (1.0 .- cp1d.grid.rho_tor_norm) - # Set ions - # DT == 1 - # Imp == 2 - # He == 3 + # Set ions: + # 1. DT + # 2. Imp + # 3. He ion = resize!(cp1d.ion, "label" => String(bulk)) fill!(ion, IMAS.ion_element(ion_symbol=bulk)) @assert ion.element[1].z_n == 1 "Bulk ion must be a Hydrogen isotope [:H, :D, :DT, :T]" @@ -101,7 +101,6 @@ function init_core_profiles( return (nel / ngw - greenwald_fraction)^2 end ne0_guess = ne_ped * 1.4 - @show IMAS.greenwald_density(eqt) res = Optim.optimize(cost_greenwald_fraction, [ne0_guess], Optim.NelderMead(), Optim.Options(g_tol=1E-4)) ne_core = res.minimizer[1] cp1d.electrons.density_thermal = Hmode_profiles(0.5 * ne_ped, ne_ped, ne_core, ngrid, n_shaping, n_shaping, w_ped) @@ -115,22 +114,14 @@ function init_core_profiles( niFraction[1] = (zimp - zeff + 4 * niFraction[3] - 2 * zimp * niFraction[3]) / (zimp - 1) niFraction[2] = (zeff - niFraction[1] - 4 * niFraction[3]) / zimp^2 @assert !any(niFraction .< 0.0) "zeff impossible to match for given helium fraction [$helium_fraction] and zeff [$zeff]" - ni_core = 0. + ni_core = 0.0 for i = 1:length(cp1d.ion) cp1d.ion[i].density_thermal = cp1d.electrons.density_thermal .* niFraction[i] ni_core += cp1d.electrons.density_thermal[1] * niFraction[i] end # Set temperatures - eqt = eq.time_slice[] - betaN = eqt.global_quantities.beta_normal - Bt = @ddtime eq.vacuum_toroidal_field.b0 - Ip = eqt.global_quantities.ip - a = eqt.boundary.minor_radius - Te_core = 10.0 * betaN * abs(Bt * (Ip / 1e6)) / a / (ne_core / 1e20) / (2.0 * 1.6e1 * 4.0 * pi * 1.0e-4) - @show Te_core ,"before" Te_core = pressure_core / (ni_core + ne_core) / IMAS.constants.e - @show Te_core ,"after" Te_ped = Te_core / 4 cp1d.electrons.temperature = Hmode_profiles(80.0, Te_ped, Te_core, ngrid, T_shaping, T_shaping, w_ped) for i = 1:length(cp1d.ion) @@ -139,7 +130,7 @@ function init_core_profiles( # remove He if not present if sum(niFraction[3]) == 0.0 - deleteat!(cp1d.ion,3) + deleteat!(cp1d.ion, 3) end # ejima diff --git a/src/ddinit/init_equilibrium.jl b/src/ddinit/init_equilibrium.jl index 17e565d76..f6eb1f8b5 100644 --- a/src/ddinit/init_equilibrium.jl +++ b/src/ddinit/init_equilibrium.jl @@ -31,7 +31,6 @@ function init_equilibrium(dd::IMAS.dd, ini::ParametersAllInits, act::ParametersA κ=ini.equilibrium.κ, δ=ini.equilibrium.δ, ζ=ini.equilibrium.ζ, - βn=ini.equilibrium.βn, pressure_core=ini.equilibrium.pressure_core, ip=ini.equilibrium.ip, boundary_switch=ini.equilibrium.boundary_from, @@ -40,7 +39,8 @@ function init_equilibrium(dd::IMAS.dd, ini::ParametersAllInits, act::ParametersA symmetric=ini.equilibrium.symmetric) # solve equilibrium - ActorEquilibrium(dd, act) + ActorEquilibrium(dd, act; kw_ActorCHEASE=Dict(:rescale_eq_to_ip=>true)) + end # field null surface @@ -55,7 +55,7 @@ function init_equilibrium(dd::IMAS.dd, ini::ParametersAllInits, act::ParametersA end """ - function init_equilibrium( + init_equilibrium( eq::IMAS.equilibrium; B0::Real, R0::Real, @@ -64,9 +64,12 @@ end κ::Real, δ::Real, ζ::Real, - βn::Real, + pressure_core::Real, ip::Real, - x_point::Union{Vector,NTuple{2},Bool} = false, + boundary_switch::Symbol, + rz_points::Union{Missing,Vector{Vector{<:Real}}}=missing, + MXH_params::Union{Missing,Vector{<:Real}}=missing, + x_point::Union{AbstractVector,NTuple{2},Bool}=false, symmetric::Bool=true) Initialize equilibrium IDS based on some basic Miller geometry parameters @@ -80,7 +83,6 @@ function init_equilibrium( κ::Real, δ::Real, ζ::Real, - βn::Real, pressure_core::Real, ip::Real, boundary_switch::Symbol, @@ -117,7 +119,6 @@ function init_equilibrium( # scalar quantities eqt.global_quantities.ip = ip - eqt.global_quantities.beta_normal = βn eq.vacuum_toroidal_field.r0 = R0 @ddtime eq.vacuum_toroidal_field.b0 = B0 @@ -125,7 +126,7 @@ function init_equilibrium( eq1d = eqt.profiles_1d psin = eq1d.psi = LinRange(0, 1, 129) eq1d.j_tor = eqt.global_quantities.ip .* (1.0 .- psin .^ 2) ./ eqt.boundary.geometric_axis.r - eq1d.pressure = pressure_core .- pressure_core .* psin + eq1d.pressure = pressure_core .* (1.0 .- psin) # R,Z boundary from: points if boundary_switch == :rz_points @@ -134,7 +135,7 @@ function init_equilibrium( end eqt.boundary.outline.r, eqt.boundary.outline.z = rz_points[1], rz_points[2] - # R,Z boundary from: MXH + # R,Z boundary from: MXH elseif boundary_switch == :MXH_params if ismissing(MXH_params) error("ini.equilibrium.boundary_from is set as $boundary_switch but MXH_params wasn't set") @@ -142,7 +143,7 @@ function init_equilibrium( mxh = IMAS.MXH(MXH_params)() eqt.boundary.outline.r, eqt.boundary.outline.z = mxh[1], mxh[2] - # R,Z boundary from: scalars + # R,Z boundary from: scalars elseif boundary_switch == :scalars eqt.boundary.outline.r, eqt.boundary.outline.z = square_miller(R0, ϵ, κ, δ, ζ; exact=true, x_points=x_point !== false) eqt.boundary.outline.z .+= Z0 diff --git a/src/gasc.jl b/src/gasc.jl index f124766c4..cf1f27efe 100644 --- a/src/gasc.jl +++ b/src/gasc.jl @@ -89,13 +89,20 @@ Convert equilibrium information in GASC solution to FUSE `ini` and `act` paramet """ function gasc_2_equilibrium(gasc::GASC, ini::ParametersAllInits, act::ParametersAllActors) gascsol = gasc.solution + ini.equilibrium.B0 = gascsol["INPUTS"]["conductors"]["magneticFieldOnAxis"] ini.equilibrium.R0 = gascsol["INPUTS"]["radial build"]["majorRadius"] ini.equilibrium.Z0 = 0.0 ini.equilibrium.ϵ = 1 / gascsol["INPUTS"]["radial build"]["aspectRatio"] ini.equilibrium.κ = gascsol["OUTPUTS"]["plasma parameters"]["elongation"] ini.equilibrium.δ = gascsol["INPUTS"]["plasma parameters"]["triangularity"] - ini.equilibrium.βn = gascsol["OUTPUTS"]["plasma parameters"]["betaN"] + + Pavg = gascsol["OUTPUTS"]["plasma parameters"]["pressureVolAvg"] + V = gascsol["OUTPUTS"]["plasma parameters"]["plasmaVolume"] + vol = gascsol["OUTPUTS"]["numerical profiles"]["volumeProf"] .* V + P1 = sum(IMAS.gradient(vol) .* LinRange(1.0, 0.0, length(vol))) / V + ini.equilibrium.pressure_core = Pavg / P1 + ini.equilibrium.ip = gascsol["INPUTS"]["plasma parameters"]["plasmaCurrent"] * 1E6 ini.equilibrium.x_point = gascsol["INPUTS"]["divertor metrics"]["numberDivertors"] > 0 ini.equilibrium.symmetric = (mod(gascsol["INPUTS"]["divertor metrics"]["numberDivertors"], 2) == 0) diff --git a/src/parameters.jl b/src/parameters.jl index fa842f0e5..b5ed74bc6 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -406,24 +406,6 @@ function Base.show(io::IO, ::MIME"text/plain", pars::AbstractParameters, depth:: return AbstractTrees.print_tree(io, pars) end -function AbstractTrees.children(obj::AbstractDict) - return [obj[k] for k in sort(collect(keys(obj)))] -end - -function AbstractTrees.children(obj::Pair) - return [] -end - -function AbstractTrees.printnode(io::IO, obj::Pair) - printstyled(io, obj.first) - printstyled(io, " ➡ ") - if typeof(obj.second) <: AbstractFloat - printstyled(io, @sprintf("%3.3f", obj.second)) - else - printstyled(io, "$(repr(obj.second))") - end -end - function AbstractTrees.children(pars::AbstractParameters) return [pars[k] for k in sort(collect(keys(pars)))] end diff --git a/src/parameters_init.jl b/src/parameters_init.jl index 8f3ae4392..c536f05cf 100644 --- a/src/parameters_init.jl +++ b/src/parameters_init.jl @@ -28,8 +28,7 @@ function ParametersInit(::Type{Val{:equilibrium}}) equilibrium.κ = Entry(Real, IMAS.equilibrium__time_slice___boundary, :elongation) equilibrium.δ = Entry(Real, IMAS.equilibrium__time_slice___boundary, :triangularity) equilibrium.ζ = Entry(Real, IMAS.equilibrium__time_slice___boundary, :squareness; default=0.0) - equilibrium.pressure_core = Entry(Real, "Pa","On axis pressure" ) - equilibrium.βn = Entry(Real, IMAS.equilibrium__time_slice___global_quantities, :beta_normal) + equilibrium.pressure_core = Entry(Real, "Pa", "On axis pressure") equilibrium.ip = Entry(Real, IMAS.equilibrium__time_slice___global_quantities, :ip) equilibrium.x_point = Entry(Union{NTuple{2},Bool}, IMAS.equilibrium__time_slice___boundary, :x_point) equilibrium.symmetric = Entry(Bool, "", "Is plasma up-down symmetric") From 85cce6287d1a186d7f2957fb188399607e7ce2b8 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sun, 24 Jul 2022 01:02:09 -0700 Subject: [PATCH 05/74] pf_active opt gives more weight near the x-points --- src/actors/pf_active_actors.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/actors/pf_active_actors.jl b/src/actors/pf_active_actors.jl index 8b5667892..c257d06d8 100644 --- a/src/actors/pf_active_actors.jl +++ b/src/actors/pf_active_actors.jl @@ -539,8 +539,11 @@ function optimize_coils_rail( # find ψp Bp_fac, ψp, Rp, Zp = VacuumFields.ψp_on_fixed_eq_boundary(fixed_eq, fixed_coils; Rx, Zx) push!(fixed_eqs, (Bp_fac, ψp, Rp, Zp)) + # weight more near the x-points + h = (maximum(Zp) - minimum(Zp)) / 2.0 + o = (maximum(Zp) + minimum(Zp)) / 2.0 + weight = sqrt.(((Zp .- o) ./ h) .^ 2 .+ h) / h # give each strike point the same weight as the lcfs - weight = Rp .* 0.0 .+ 1.0 weight[end-length(Rx)+1:end] .= length(Rp) / (1 + length(Rx)) * λ_strike if all(weight .== 1.0) weight = Float64[] From 1c6dba2f9d5e6b34a0c8ec22e518d3e6ab9b9d75 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sun, 24 Jul 2022 23:12:42 -0700 Subject: [PATCH 06/74] par() always does a deepcopy --- src/actors/equilibrium_actors.jl | 16 ++++++++++------ src/parameters.jl | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/actors/equilibrium_actors.jl b/src/actors/equilibrium_actors.jl index 23c189646..b078ea323 100644 --- a/src/actors/equilibrium_actors.jl +++ b/src/actors/equilibrium_actors.jl @@ -26,13 +26,14 @@ The ActorEquilibrium provides a common interface to run multiple equilibrium act """ function ActorEquilibrium(dd::IMAS.dd, act::ParametersAllActors; kw_ActorSolovev=Dict(), kw_ActorCHEASE=Dict(), kw...) par = act.ActorEquilibrium(kw...) - actor = ActorEquilibrium(dd, par, act; kw_ActorSolovev, kw_ActorCHEASE, kw...) + actor = ActorEquilibrium(dd, par, act; kw_ActorSolovev, kw_ActorCHEASE) step(actor) finalize(actor) return actor end function ActorEquilibrium(dd::IMAS.dd, par::ParametersActor, act::ParametersAllActors; kw_ActorSolovev=Dict(), kw_ActorCHEASE=Dict(), kw...) + par = par(kw...) if par.model == :Solovev eq_actor = ActorSolovev(dd, act.ActorSolovev; kw_ActorSolovev...) elseif par.model == :CHEASE @@ -40,7 +41,7 @@ function ActorEquilibrium(dd::IMAS.dd, par::ParametersActor, act::ParametersAllA else error("ActorEquilibrium: model = $(par.model) is unknown") end - return ActorEquilibrium(dd, deepcopy(par), eq_actor) + return ActorEquilibrium(dd, par, eq_actor) end """ @@ -116,7 +117,9 @@ function ActorSolovev(dd::IMAS.dd, act::ParametersAllActors; kw...) return actor end -function ActorSolovev(dd::IMAS.dd, par::ParametersActor) +function ActorSolovev(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + # extract info from dd eq = dd.equilibrium eqt = eq.time_slice[] @@ -144,7 +147,7 @@ function ActorSolovev(dd::IMAS.dd, par::ParametersActor) # run Solovev S = Equilibrium.solovev(abs(B0), R0, ϵ, δ, κ, par.alpha, par.qstar, B0_dir=Int64(sign(B0)), Ip_dir=1, x_point=x_point, symmetric=symmetric) - return ActorSolovev(dd.equilibrium, deepcopy(par), S) + return ActorSolovev(dd.equilibrium, par, S) end """ @@ -355,8 +358,9 @@ function ActorCHEASE(dd::IMAS.dd, act::ParametersAllActors; kw...) return actor end -function ActorCHEASE(dd::IMAS.dd, par::ParametersActor) - ActorCHEASE(dd, deepcopy(par), nothing) +function ActorCHEASE(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + ActorCHEASE(dd, par, nothing) end """ diff --git a/src/parameters.jl b/src/parameters.jl index b5ed74bc6..02f6d6428 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -458,8 +458,8 @@ end This functor is used to override the parameters at function call """ function (par::AbstractParameters)(kw...) + par = deepcopy(par) if !isempty(kw) - par = deepcopy(par) for (key, value) in kw setproperty!(par, key, value) end From f0dad0f56048da78343d0dc2ad9c8bdcb6027bde Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sun, 24 Jul 2022 23:13:38 -0700 Subject: [PATCH 07/74] move gasc.jl under ddinit/ --- src/FUSE.jl | 6 +----- src/{ => ddinit}/gasc.jl | 0 2 files changed, 1 insertion(+), 5 deletions(-) rename src/{ => ddinit}/gasc.jl (100%) diff --git a/src/FUSE.jl b/src/FUSE.jl index 84f3b2486..305af25c8 100644 --- a/src/FUSE.jl +++ b/src/FUSE.jl @@ -18,11 +18,6 @@ include("utils.jl") include("parameters.jl") include("parameters_init.jl") -#= ============== =# -# GASC interface # -#= ============== =# -include("gasc.jl") - #= ====================== =# # PHYSICS and TECHNOLOGY # #= ====================== =# @@ -32,6 +27,7 @@ include("technology.jl") #= ====== =# # DDINIT # #= ====== =# +include(joinpath("ddinit", "gasc.jl")) include(joinpath("ddinit", "init.jl")) include(joinpath("ddinit", "init_equilibrium.jl")) include(joinpath("ddinit", "init_build.jl")) diff --git a/src/gasc.jl b/src/ddinit/gasc.jl similarity index 100% rename from src/gasc.jl rename to src/ddinit/gasc.jl From 5bfc96a4c0962c63949f566b9ef9e1c9c53a2676 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sun, 24 Jul 2022 23:15:16 -0700 Subject: [PATCH 08/74] init_core_profiles from ini.equilibrium.pressure_core --- src/ddinit/init_core_profiles.jl | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/ddinit/init_core_profiles.jl b/src/ddinit/init_core_profiles.jl index a955e494e..398af9220 100644 --- a/src/ddinit/init_core_profiles.jl +++ b/src/ddinit/init_core_profiles.jl @@ -19,17 +19,12 @@ function init_core_profiles(dd::IMAS.dd, ini::ParametersAllInits, act::Parameter end if init_from == :scalars - if ismissing(ini.equilibrium, :pressure_core) - pressure_core = dd.equilibrium.time_slice[].profiles_1d.pressure[1] - else - pressure_core = ini.equilibrium.pressure_core - end init_core_profiles( dd.core_profiles, dd.equilibrium, dd.summary; ne_ped=ini.core_profiles.ne_ped, - pressure_core=pressure_core, + pressure_core=ini.equilibrium.pressure_core, greenwald_fraction=ini.core_profiles.greenwald_fraction, helium_fraction=ini.core_profiles.helium_fraction, T_shaping=ini.core_profiles.T_shaping, From b32074979fb89670ea355fc0c3d29c965e189a44 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sun, 24 Jul 2022 23:15:28 -0700 Subject: [PATCH 09/74] better estimate of Te_ped --- src/ddinit/init_core_profiles.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ddinit/init_core_profiles.jl b/src/ddinit/init_core_profiles.jl index 398af9220..6cd55fc0d 100644 --- a/src/ddinit/init_core_profiles.jl +++ b/src/ddinit/init_core_profiles.jl @@ -117,7 +117,8 @@ function init_core_profiles( # Set temperatures Te_core = pressure_core / (ni_core + ne_core) / IMAS.constants.e - Te_ped = Te_core / 4 + Te_ped = sqrt(Te_core / 1000.0 / 3.0) * 1000.0 + cp1d.electrons.temperature = Hmode_profiles(80.0, Te_ped, Te_core, ngrid, T_shaping, T_shaping, w_ped) for i = 1:length(cp1d.ion) cp1d.ion[i].temperature = cp1d.electrons.temperature ./ T_ratio From 879e553dc872d989abcc5efbb1d0c665a9044c63 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sun, 24 Jul 2022 23:16:18 -0700 Subject: [PATCH 10/74] =?UTF-8?q?fix=20meaning=20of=20=CE=BB=5Fnull=20and?= =?UTF-8?q?=20=CE=BB=5Flcfs=20in=20optimize=5Fcoils=5Frail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/actors/pf_active_actors.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/actors/pf_active_actors.jl b/src/actors/pf_active_actors.jl index c257d06d8..56b129d40 100644 --- a/src/actors/pf_active_actors.jl +++ b/src/actors/pf_active_actors.jl @@ -588,14 +588,14 @@ function optimize_coils_rail( push!(all_cost_currents, norm(exp.(fraction_max_current_densities / λ_currents) / exp(1)) / length(currents)) # boundary cost if ismissing(eq.time_slice[time_index].global_quantities, :ip) - push!(all_cost_lcfs, cost_lcfs0 / λ_null) + push!(all_cost_lcfs, cost_lcfs0 * λ_null) push!(all_cost_oh, 0.0) else #OH cost oh_current_densities = current_densities[oh_indexes] avg_oh = sum(oh_current_densities) / length(oh_current_densities) cost_oh = norm(oh_current_densities .- avg_oh) / avg_oh - push!(all_cost_lcfs, cost_lcfs0 / λ_lcfs) + push!(all_cost_lcfs, cost_lcfs0 * λ_lcfs) push!(all_cost_oh, cost_oh) end end From 706f5c9ea50aaff69711c96177e8bfe39e659f75 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Mon, 25 Jul 2022 00:16:46 -0700 Subject: [PATCH 11/74] update pf_active optimization costs --- src/actors/pf_active_actors.jl | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/actors/pf_active_actors.jl b/src/actors/pf_active_actors.jl index 56b129d40..5097b419b 100644 --- a/src/actors/pf_active_actors.jl +++ b/src/actors/pf_active_actors.jl @@ -208,7 +208,7 @@ function finalize(pfactor::ActorPFcoilsOpt; scale_eq_domain_size=1.0, update_eq_ # # update ψ map R = range(EQfixed.r[1] / scale_eq_domain_size, EQfixed.r[end] * scale_eq_domain_size, length=length(EQfixed.r)) Z = range(EQfixed.z[1] * scale_eq_domain_size, EQfixed.z[end] * scale_eq_domain_size, length=length(EQfixed.z)) - ψ_f2f = VacuumFields.fixed2free(EQfixed, coils, R, Z) + ψ_f2f = transpose(VacuumFields.fixed2free(EQfixed, coils, R, Z)) pfactor.eq_out.time_slice[time_index].profiles_2d[1].grid.dim1 = R pfactor.eq_out.time_slice[time_index].profiles_2d[1].grid.dim2 = Z pfactor.eq_out.time_slice[time_index].profiles_2d[1].psi = transpose(ψ_f2f) @@ -563,7 +563,7 @@ function optimize_coils_rail( index = findall(.>(1.0), abs.(packed[1:end-1])) if length(index) > 0 - cost_1to1 = sum(abs.(packed[index]) .- 1.0) * 10 + cost_1to1 = sum(abs.(packed[index]) .- 1.0) else cost_1to1 = 0.0 end @@ -591,7 +591,7 @@ function optimize_coils_rail( push!(all_cost_lcfs, cost_lcfs0 * λ_null) push!(all_cost_oh, 0.0) else - #OH cost + #OH cost (this is to avoid using the OH for X-points) oh_current_densities = current_densities[oh_indexes] avg_oh = sum(oh_current_densities) / length(oh_current_densities) cost_oh = norm(oh_current_densities .- avg_oh) / avg_oh @@ -611,13 +611,20 @@ function optimize_coils_rail( end end end - cost_spacing = cost_spacing / R0 + cost_spacing = cost_spacing / length(optim_coils)^2 / R0 + + cost_lcfs_2 = cost_lcfs^2 * 10000.0 + cost_currents_2 = cost_currents^2 + cost_oh_2 = cost_oh^2 + cost_1to1_2 = cost_1to1^2 + cost_spacing_2 = cost_spacing^2 + # total cost - cost = sqrt(cost_lcfs^2 + cost_currents^2 + 0.1 * cost_oh^2 + cost_1to1^2 + 10 * cost_spacing^2) + cost = sqrt(cost_lcfs_2 + cost_currents_2 + cost_oh_2 + cost_1to1_2 + cost_spacing_2) if do_trace push!(trace.params, packed) - push!(trace.cost_lcfs, cost_lcfs) - push!(trace.cost_currents, cost_currents) + push!(trace.cost_lcfs, sqrt(cost_lcfs_2)) + push!(trace.cost_currents, sqrt(cost_currents_2)) push!(trace.cost_total, cost) end if isnan(cost) From 41832b4c8257c4c570c07e98898aa4ebcc778a75 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Mon, 25 Jul 2022 12:32:20 -0700 Subject: [PATCH 12/74] =?UTF-8?q?rename=20=CE=BB=5F=20-->=20weight=5F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/actors/pf_active_actors.jl | 71 +++++++++++++++++++--------------- src/ddinit/init_equilibrium.jl | 6 +-- 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/src/actors/pf_active_actors.jl b/src/actors/pf_active_actors.jl index 5097b419b..29d82d6f7 100644 --- a/src/actors/pf_active_actors.jl +++ b/src/actors/pf_active_actors.jl @@ -34,10 +34,10 @@ function ParametersActor(::Type{Val{:ActorPFcoilsOpt}}) ] par.green_model = Switch(options, "", "Model used for the coils Green function calculations"; default=:simple) par.symmetric = Entry(Bool, "", "Force PF coils location to be up-down symmetric"; default=true) - par.λ_currents = Entry(Real, "", "Weight of current limit constraint"; default=0.5) - par.λ_strike = Entry(Real, "", "Weight given to matching the strike-points"; default=0.0) - par.λ_lcfs = Entry(Real, "", "Weight given to matching last closed flux surface"; default=1.0) - par.λ_null = Entry(Real, "", "Weight given to get field null for plasma breakdown"; default=1E-3) + par.weight_currents = Entry(Real, "", "Weight of current limit constraint"; default=0.5) + par.weight_strike = Entry(Real, "", "Weight given to matching the strike-points"; default=0.0) + par.weight_lcfs = Entry(Real, "", "Weight given to matching last closed flux surface"; default=1.0) + par.weight_null = Entry(Real, "", "Weight given to get field null for plasma breakdown"; default=1E-3) options = [ :none => "Do not optimize", :currents => "Find optimial coil currents but do not change coil positions", @@ -73,12 +73,12 @@ function ActorPFcoilsOpt(dd::IMAS.dd, act::ParametersAllActors; kw...) else if par.optimization_scheme == :currents # find coil currents - step(actor; par.λ_lcfs, par.λ_null, par.λ_currents, par.λ_strike, par.verbose, maxiter=1000, optimization_scheme=:currents) + step(actor; par.weight_lcfs, par.weight_null, par.weight_currents, par.weight_strike, par.verbose, maxiter=1000, optimization_scheme=:currents) finalize(actor) elseif par.optimization_scheme == :rail # optimize coil location and currents - step(actor; par.λ_lcfs, par.λ_null, par.λ_currents, par.λ_strike, par.verbose, maxiter=1000, optimization_scheme=:rail) + step(actor; par.weight_lcfs, par.weight_null, par.weight_currents, par.weight_strike, par.verbose, maxiter=1000, optimization_scheme=:rail) finalize(actor) if par.do_plot @@ -131,10 +131,10 @@ end step(pfactor::ActorPFcoilsOpt; symmetric=pfactor.symmetric, λ_regularize=pfactor.λ_regularize, - λ_lcfs=1.0, - λ_null=1E-3, - λ_currents=0.5, - λ_strike=0.0, + weight_lcfs=1.0, + weight_null=1E-3, + weight_currents=0.5, + weight_strike=0.0, maxiter=10000, optimization_scheme=:rail, verbose=false) @@ -144,10 +144,10 @@ Optimize coil currents and positions to produce sets of equilibria while minimiz function step(pfactor::ActorPFcoilsOpt; symmetric=pfactor.symmetric, λ_regularize=pfactor.λ_regularize, - λ_lcfs=1.0, - λ_null=1E-3, - λ_currents=0.5, - λ_strike=0.0, + weight_lcfs=1.0, + weight_null=1E-3, + weight_currents=0.5, + weight_strike=0.0, maxiter=10000, optimization_scheme=:rail, verbose=false) @@ -162,7 +162,7 @@ function step(pfactor::ActorPFcoilsOpt; bd = pfactor.bd # run rail type optimizer if optimization_scheme in [:rail, :currents] - (λ_regularize, trace) = optimize_coils_rail(pfactor.eq_in; pinned_coils, optim_coils, fixed_coils, symmetric, λ_regularize, λ_lcfs, λ_null, λ_currents, λ_strike, bd, maxiter, verbose) + (λ_regularize, trace) = optimize_coils_rail(pfactor.eq_in; pinned_coils, optim_coils, fixed_coils, symmetric, λ_regularize, weight_lcfs, weight_null, weight_currents, weight_strike, bd, maxiter, verbose) else error("Supported ActorPFcoilsOpt optimization_scheme are `:currents` or `:rail`") end @@ -211,7 +211,11 @@ function finalize(pfactor::ActorPFcoilsOpt; scale_eq_domain_size=1.0, update_eq_ ψ_f2f = transpose(VacuumFields.fixed2free(EQfixed, coils, R, Z)) pfactor.eq_out.time_slice[time_index].profiles_2d[1].grid.dim1 = R pfactor.eq_out.time_slice[time_index].profiles_2d[1].grid.dim2 = Z - pfactor.eq_out.time_slice[time_index].profiles_2d[1].psi = transpose(ψ_f2f) + if false # hack to force up-down symmetric equilibrium + pfactor.eq_out.time_slice[time_index].profiles_2d[1].psi = (ψ_f2f .+ ψ_f2f[1:end, end:-1:1]) ./ 2.0 + else + pfactor.eq_out.time_slice[time_index].profiles_2d[1].psi = ψ_f2f + end end # update psi @@ -496,10 +500,10 @@ function optimize_coils_rail( fixed_coils::Vector{GS_IMAS_pf_active__coil}, symmetric::Bool, λ_regularize::Real, - λ_lcfs::Real, - λ_null::Real, - λ_currents::Real, - λ_strike::Real, + weight_lcfs::Real, + weight_null::Real, + weight_currents::Real, + weight_strike::Real, bd::IMAS.build, maxiter::Int, verbose::Bool) @@ -524,7 +528,7 @@ function optimize_coils_rail( # private flux regions Rx = Float64[] Zx = Float64[] - if λ_strike > 0.0 + if weight_strike > 0.0 private = IMAS.flux_surface(eqt, eqt.profiles_1d.psi[end], false) vessel = IMAS.get_build(bd, type=_plasma_) for (pr, pz) in private @@ -533,7 +537,7 @@ function optimize_coils_rail( append!(Zx, pvy) end if isempty(Rx) - @warn "λ_strike>0 but no strike point found" + @warn "weight_strike>0 but no strike point found" end end # find ψp @@ -544,7 +548,7 @@ function optimize_coils_rail( o = (maximum(Zp) + minimum(Zp)) / 2.0 weight = sqrt.(((Zp .- o) ./ h) .^ 2 .+ h) / h # give each strike point the same weight as the lcfs - weight[end-length(Rx)+1:end] .= length(Rp) / (1 + length(Rx)) * λ_strike + weight[end-length(Rx)+1:end] .= length(Rp) / (1 + length(Rx)) * weight_strike if all(weight .== 1.0) weight = Float64[] end @@ -585,17 +589,22 @@ function optimize_coils_rail( max_current_densities = vcat(oh_max_current_densities, pf_max_current_densities) fraction_max_current_densities = abs.(current_densities ./ max_current_densities) #currents cost - push!(all_cost_currents, norm(exp.(fraction_max_current_densities / λ_currents) / exp(1)) / length(currents)) - # boundary cost + push!(all_cost_currents, norm(exp.(fraction_max_current_densities * weight_currents) / exp(1)) / length(currents)) + # boundary and oh costs if ismissing(eq.time_slice[time_index].global_quantities, :ip) - push!(all_cost_lcfs, cost_lcfs0 * λ_null) + push!(all_cost_lcfs, cost_lcfs0 * weight_null) push!(all_cost_oh, 0.0) else - #OH cost (this is to avoid using the OH for X-points) - oh_current_densities = current_densities[oh_indexes] - avg_oh = sum(oh_current_densities) / length(oh_current_densities) - cost_oh = norm(oh_current_densities .- avg_oh) / avg_oh - push!(all_cost_lcfs, cost_lcfs0 * λ_lcfs) + push!(all_cost_lcfs, cost_lcfs0 * weight_lcfs) + # OH cost (to avoid OH shrinking too much) + index = findfirst(rail -> rail.name === "OH", bd.pf_active.rail) + if index !== nothing + oh_rail_length = diff(collect(extrema(bd.pf_active.rail[index].outline.z)))[1] + total_oh_coils_length = sum([coil.height for coil in coils[oh_indexes.==true]]) + cost_oh = oh_rail_length / total_oh_coils_length + else + cost_oh = 0.0 + end push!(all_cost_oh, cost_oh) end end diff --git a/src/ddinit/init_equilibrium.jl b/src/ddinit/init_equilibrium.jl index f6eb1f8b5..67242c199 100644 --- a/src/ddinit/init_equilibrium.jl +++ b/src/ddinit/init_equilibrium.jl @@ -128,23 +128,23 @@ function init_equilibrium( eq1d.j_tor = eqt.global_quantities.ip .* (1.0 .- psin .^ 2) ./ eqt.boundary.geometric_axis.r eq1d.pressure = pressure_core .* (1.0 .- psin) - # R,Z boundary from: points if boundary_switch == :rz_points + # R,Z boundary from: points if ismissing(rz_points) error("ini.equilibrium.boundary_from is set as $boundary_switch but rz_points wasn't set") end eqt.boundary.outline.r, eqt.boundary.outline.z = rz_points[1], rz_points[2] - # R,Z boundary from: MXH elseif boundary_switch == :MXH_params + # R,Z boundary from: MXH if ismissing(MXH_params) error("ini.equilibrium.boundary_from is set as $boundary_switch but MXH_params wasn't set") end mxh = IMAS.MXH(MXH_params)() eqt.boundary.outline.r, eqt.boundary.outline.z = mxh[1], mxh[2] - # R,Z boundary from: scalars elseif boundary_switch == :scalars + # R,Z boundary from: scalars eqt.boundary.outline.r, eqt.boundary.outline.z = square_miller(R0, ϵ, κ, δ, ζ; exact=true, x_points=x_point !== false) eqt.boundary.outline.z .+= Z0 end From 9fa346973ca42e98dfffe8c18fb7fa313a9b8844 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Mon, 25 Jul 2022 12:32:31 -0700 Subject: [PATCH 13/74] Better chease --- src/actors/equilibrium_actors.jl | 33 +++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/actors/equilibrium_actors.jl b/src/actors/equilibrium_actors.jl index b078ea323..444c37a6f 100644 --- a/src/actors/equilibrium_actors.jl +++ b/src/actors/equilibrium_actors.jl @@ -387,36 +387,47 @@ function step(actor::ActorCHEASE) eqt = dd.equilibrium.time_slice[] eq1d = eqt.profiles_1d + # remove points at high curvature points (ie. X-points) r_bound = eqt.boundary.outline.r z_bound = eqt.boundary.outline.z - index = abs.(IMAS.curvature(r_bound,z_bound)) .< 1.0 + r_bound, z_bound = IMAS.resample_2d_line(r_bound, z_bound; n_points=201) + index = abs.(IMAS.curvature(r_bound, z_bound)) .< 0.9 r_bound = r_bound[index] z_bound = z_bound[index] + # scalars Ip = eqt.global_quantities.ip Bt_center = @ddtime(dd.equilibrium.vacuum_toroidal_field.b0) r_center = dd.equilibrium.vacuum_toroidal_field.r0 - r_geo = eqt.boundary.geometric_axis.r z_geo = eqt.boundary.geometric_axis.z Bt_geo = Bt_center * r_center / r_geo - ϵ = eqt.boundary.minor_radius / r_geo + # pressure and j_tor + psin = eq1d.psi_norm j_tor = eq1d.j_tor pressure = eq1d.pressure - rho_pol = sqrt.(eq1d.psi_norm) + rho_pol = sqrt.(psin) pressure_sep = pressure[end] - actor.chease = CHEASE.run_chease( - ϵ, z_geo, pressure_sep, Bt_geo, - r_geo, Ip, r_bound, z_bound, 82, - rho_pol, pressure, j_tor, - rescale_eq_to_ip=actor.par.rescale_eq_to_ip, - clear_workdir=actor.par.clear_workdir) + # run and handle errors + try + actor.chease = CHEASE.run_chease( + ϵ, z_geo, pressure_sep, Bt_geo, + r_geo, Ip, r_bound, z_bound, 82, + rho_pol, pressure, j_tor, + rescale_eq_to_ip=actor.par.rescale_eq_to_ip, + clear_workdir=actor.par.clear_workdir) + catch + display(plot(r_bound, z_bound; marker=:dot, aspect_ratio=:equal)) + display(plot(psin, pressure)) + display(plot(psin, abs.(j_tor))) + rethrow() + end + # convert from fixed to free boundary equilibrium if actor.par.free_boundary - # convert from fixed to free boundary equilibrium EQ = Equilibrium.efit(actor.chease.gfile, 1) psi_free_rz = VacuumFields.fixed2free(EQ, actor.chease.gfile.nbbbs) actor.chease.gfile.psirz = psi_free_rz From 776b5ec55ef55faca669fe34d831e1131d849b70 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Mon, 25 Jul 2022 12:32:52 -0700 Subject: [PATCH 14/74] refine generation of divertor regions --- src/actors/build_actors.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/actors/build_actors.jl b/src/actors/build_actors.jl index c9f78873c..6c740e9e4 100644 --- a/src/actors/build_actors.jl +++ b/src/actors/build_actors.jl @@ -700,7 +700,8 @@ function wall_from_eq(bd::IMAS.build, eqt::IMAS.equilibrium__time_slice; diverto t = LinRange(0, 2pi, 31) # divertor lengths - max_divertor_length = (maximum(zlcfs) - minimum(zlcfs)) * divertor_length_fraction + linear_plasma_size = sqrt((maximum(zlcfs) - minimum(zlcfs)) * (maximum(rlcfs) - minimum(rlcfs))) + max_divertor_length = linear_plasma_size * divertor_length_fraction # private flux regions private = IMAS.flux_surface(eqt, ψb, false) @@ -708,7 +709,7 @@ function wall_from_eq(bd::IMAS.build, eqt::IMAS.equilibrium__time_slice; diverto if sign(pz[1] - Z0) != sign(pz[end] - Z0) # open flux surface does not encicle the plasma continue - elseif IMAS.minimum_distance_two_shapes(pr, pz, rlcfs, zlcfs) > (maximum(zlcfs) - minimum(zlcfs)) / 20 + elseif IMAS.minimum_distance_two_shapes(pr, pz, rlcfs, zlcfs) > linear_plasma_size / 5 # secondary Xpoint far away continue elseif (sum(pz) - Z0) < 0 @@ -872,6 +873,7 @@ function build_cx!(dd::IMAS.dd; rebuild_wall::Bool=true) build_cx!(dd.build, pr, pz) divertor_regions!(dd.build, dd.equilibrium.time_slice[]) + blanket_regions!(dd.build, dd.equilibrium.time_slice[]) if wall === missing || rebuild_wall From 437f07dab3e391dd1ded02d2ade8c8a850394769 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Mon, 25 Jul 2022 13:26:48 -0700 Subject: [PATCH 15/74] Use GeoInterface.coordinates instead of LibGEOS.coordinates --- Project.toml | 1 + src/actors/build_actors.jl | 18 +++++++++--------- src/ddinit/init_build.jl | 1 + src/ddinit/init_pf_active.jl | 4 ++-- src/physics.jl | 2 +- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Project.toml b/Project.toml index 1912ca37f..ea0f360ee 100644 --- a/Project.toml +++ b/Project.toml @@ -21,6 +21,7 @@ FiniteElementHermite = "6ce10167-d368-4c0e-af34-9787cdd175e5" Fortran90Namelists = "8fb689aa-71ff-4044-8071-0cffc910b57d" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" FusionMaterials = "4c86da02-02c8-4634-8460-96566129f8e0" +GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f" IMAS = "13ead8c1-b7d1-41bb-a6d0-5b8b65ed587a" IMASDD = "06b86afa-9f21-11ec-2ef8-e51b8960cfc5" Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0" diff --git a/src/actors/build_actors.jl b/src/actors/build_actors.jl index 6c740e9e4..d762b1566 100644 --- a/src/actors/build_actors.jl +++ b/src/actors/build_actors.jl @@ -689,8 +689,8 @@ function wall_from_eq(bd::IMAS.build, eqt::IMAS.equilibrium__time_slice; diverto wall_poly = LibGEOS.buffer(plasma_poly, a) # hfs and lfs spacing may not be symmetric - R = [v[1] for v in LibGEOS.coordinates(wall_poly)[1]] - Z = [v[2] for v in LibGEOS.coordinates(wall_poly)[1]] + R = [v[1] for v in GeoInterface.coordinates(wall_poly)[1]] + Z = [v[2] for v in GeoInterface.coordinates(wall_poly)[1]] R .+= ((R_lfs_plasma + R_hfs_plasma) - (maximum(rlcfs) + minimum(rlcfs))) / 2.0 R[R.R_lfs_plasma] .= R_lfs_plasma @@ -755,8 +755,8 @@ function wall_from_eq(bd::IMAS.build, eqt::IMAS.equilibrium__time_slice; diverto wall_poly = LibGEOS.buffer(wall_poly, -a / 4) wall_poly = LibGEOS.buffer(wall_poly, a / 4) - pr = [v[1] for v in LibGEOS.coordinates(wall_poly)[1]] - pz = [v[2] for v in LibGEOS.coordinates(wall_poly)[1]] + pr = [v[1] for v in GeoInterface.coordinates(wall_poly)[1]] + pz = [v[2] for v in GeoInterface.coordinates(wall_poly)[1]] pr, pz = IMAS.resample_2d_line(pr, pz; step=0.1) @@ -785,8 +785,8 @@ function divertor_regions!(bd::IMAS.build, eqt::IMAS.equilibrium__time_slice) divertor_poly = LibGEOS.intersection(wall_poly, domain) divertor_poly = LibGEOS.difference(divertor_poly, plasma_poly) - pr = [v[1] for v in LibGEOS.coordinates(divertor_poly)[1]] - pz = [v[2] for v in LibGEOS.coordinates(divertor_poly)[1]] + pr = [v[1] for v in GeoInterface.coordinates(divertor_poly)[1]] + pz = [v[2] for v in GeoInterface.coordinates(divertor_poly)[1]] # assign to build structure if Zx > Z0 @@ -828,7 +828,7 @@ function blanket_regions!(bd::IMAS.build, eqt::IMAS.equilibrium__time_slice) geometries = LibGEOS.getGeometries(ring_poly) blankets = IMAS.IDSvectorElement[] for poly in geometries - coords = LibGEOS.coordinates(poly) + coords = GeoInterface.coordinates(poly) pr = [v[1] for v in coords[1]] pz = [v[2] for v in coords[1]] @@ -1015,8 +1015,8 @@ function optimize_shape(bd::IMAS.build, obstr_index::Int, layer_index::Int, shap # handle offset, negative offset, negative offset, and convex-hull if layer.shape in [Int(_offset_), Int(_negative_offset_), Int(_convex_hull_)] poly = LibGEOS.buffer(xy_polygon(oR, oZ), (hfs_thickness + lfs_thickness) / 2.0) - R = [v[1] .+ r_offset for v in LibGEOS.coordinates(poly)[1]] - Z = [v[2] for v in LibGEOS.coordinates(poly)[1]] + R = [v[1] .+ r_offset for v in GeoInterface.coordinates(poly)[1]] + Z = [v[2] for v in GeoInterface.coordinates(poly)[1]] if layer.shape == Int(_convex_hull_) h = [[r, z] for (r, z) in collect(zip(R, Z))] hull = convex_hull(h; closed_polygon=true) diff --git a/src/ddinit/init_build.jl b/src/ddinit/init_build.jl index 26eaf4bd1..c99cfd51b 100644 --- a/src/ddinit/init_build.jl +++ b/src/ddinit/init_build.jl @@ -1,4 +1,5 @@ import LibGEOS +import GeoInterface import Interpolations import DataStructures diff --git a/src/ddinit/init_pf_active.jl b/src/ddinit/init_pf_active.jl index db63b0e02..d379abd49 100644 --- a/src/ddinit/init_pf_active.jl +++ b/src/ddinit/init_pf_active.jl @@ -149,8 +149,8 @@ function init_pf_active( dcoil = (coil_size + coils_cleareance[krail]) / 2 * sqrt(2) inner_layer = IMAS.get_build(bd, identifier=bd.layer[k-1].identifier, fs=_hfs_) poly = LibGEOS.buffer(xy_polygon(inner_layer.outline.r, inner_layer.outline.z), dcoil) - rail_r = [v[1] for v in LibGEOS.coordinates(poly)[1]] - rail_z = [v[2] for v in LibGEOS.coordinates(poly)[1]] + rail_r = [v[1] for v in GeoInterface.coordinates(poly)[1]] + rail_z = [v[2] for v in GeoInterface.coordinates(poly)[1]] rail_r, rail_z = IMAS.resample_2d_line(rail_r, rail_z, step=dr/3) # mark what regions on that rail do not intersect solid structures and can hold coils diff --git a/src/physics.jl b/src/physics.jl index 96298bf5c..8e5bae96b 100644 --- a/src/physics.jl +++ b/src/physics.jl @@ -549,7 +549,7 @@ function layer_structure_intersect_volume(layer::IMAS.build__layer, structure::I end vol = 0.0 - for poly in LibGEOS.coordinates(LibGEOS.intersection(ring_poly, structure_poly)) + for poly in GeoInterface.coordinates(LibGEOS.intersection(ring_poly, structure_poly)) pr = [v[1] for v in poly] pz = [v[2] for v in poly] vol += IMAS.area(pr, pz) * structure.toroidal_extent * length(toroidal_angles) From dd004e574b452d6a0c1312b16f6d64c9c3fabefc Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Mon, 25 Jul 2022 13:37:34 -0700 Subject: [PATCH 16/74] refactored ActorTauenn #114 --- src/actors/transport_actors.jl | 37 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/actors/transport_actors.jl b/src/actors/transport_actors.jl index 1af7cee82..06164d5ff 100644 --- a/src/actors/transport_actors.jl +++ b/src/actors/transport_actors.jl @@ -6,6 +6,7 @@ import TAUENN Base.@kwdef mutable struct ActorTauenn <: PlasmaAbstractActor dd::IMAS.dd + par::ParametersActor tauenn_parameters::TAUENN.TauennParameters tauenn_outputs::TAUENN.TauennOutputs end @@ -28,7 +29,7 @@ end """ ActorTauenn(dd::IMAS.dd, act::ParametersAllActors; kw...) -This actor estimates the core-transport using Tauenn and evolves the kinetic profiles according to heat and particle flux matching. +This actor estimates the core-transport using Tauenn, which evolves the kinetic profiles according to heat and particle flux matching. The pedestal in this actor is evolved using EPED-NN. @@ -37,20 +38,12 @@ The pedestal in this actor is evolved using EPED-NN. """ function ActorTauenn(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorTauenn(kw...) + actor = ActorTauenn(dd, par) if par.do_plot ps = plot(dd.core_sources; color=:gray) pp = plot(dd.core_profiles; color=:gray, label="") end - actor = ActorTauenn(dd; - par.error, - par.eped_factor, - par.rho_fluxmatch, - par.T_shaping, - par.temp_pedestal_ratio, - par.transport_model, - par.confinement_factor, - par.warn_nn_train_bounds) - step(actor; verbose=par.verbose) + step(actor) finalize(actor) if par.do_plot display(plot!(ps, dd.core_sources)) @@ -62,15 +55,21 @@ function ActorTauenn(dd::IMAS.dd, act::ParametersAllActors; kw...) return actor end -function ActorTauenn(dd::IMAS.dd; kw...) - tauenn_parameters = TAUENN.TauennParameters() - for key in keys(kw) - setfield!(tauenn_parameters, key, kw[key]) - end - return ActorTauenn(dd, tauenn_parameters, TAUENN.TauennOutputs()) +function ActorTauenn(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + tauenn_parameters = TAUENN.TauennParameters(; + par.error, + par.eped_factor, + par.rho_fluxmatch, + par.T_shaping, + par.temp_pedestal_ratio, + par.transport_model, + par.confinement_factor, + par.warn_nn_train_bounds) + return ActorTauenn(dd, par, tauenn_parameters, TAUENN.TauennOutputs()) end -function step(actor::ActorTauenn; verbose=false) - actor.tauenn_outputs = TAUENN.tau_enn(actor.dd, actor.tauenn_parameters; verbose) +function step(actor::ActorTauenn) + actor.tauenn_outputs = TAUENN.tau_enn(actor.dd, actor.tauenn_parameters; actor.par.verbose) return actor end \ No newline at end of file From a2ff9ab12672be318c444d1e6c3865065e7b3f7b Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Mon, 25 Jul 2022 21:50:42 -0700 Subject: [PATCH 17/74] Standardized several actors #114 --- cases/HDB5.jl | 3 +- src/actors/balance_of_plant_actors.jl | 28 ++-- src/actors/blanket_actors.jl | 8 +- src/actors/build_actors.jl | 63 +++++--- src/actors/compound_actors.jl | 29 ++-- src/actors/costing_actors.jl | 8 +- src/actors/current_actors.jl | 9 +- src/actors/divertors_actors.jl | 8 +- src/actors/neutronics_actors.jl | 12 +- src/actors/pf_active_actors.jl | 212 +++++++++++++------------- src/actors/sources_actors.jl | 92 ++++++----- src/actors/stresses_actors.jl | 11 +- src/ddinit/init_core_sources.jl | 34 ++--- 13 files changed, 305 insertions(+), 212 deletions(-) diff --git a/cases/HDB5.jl b/cases/HDB5.jl index 04a3ce30b..03aaf689c 100644 --- a/cases/HDB5.jl +++ b/cases/HDB5.jl @@ -30,9 +30,8 @@ function case_parameters(data_row::DataFrames.DataFrameRow) ini.equilibrium.ϵ = data_row[:AMIN] / data_row[:RGEO] ini.equilibrium.κ = data_row[:KAPPA] ini.equilibrium.δ = data_row[:DELTA] - ini.equilibrium.βn = 1.0 ini.equilibrium.ip = data_row[:IP] - + ini.equilibrium.pressure_core = pressure_avg_from_beta_n(1.0, data_row[:AMIN], data_row[:BT], data_row[:IP]) * 3.0 act.ActorSolovev.area = data_row[:AREA] act.ActorSolovev.volume = data_row[:VOL] diff --git a/src/actors/balance_of_plant_actors.jl b/src/actors/balance_of_plant_actors.jl index 6b4c282d2..447049e0d 100644 --- a/src/actors/balance_of_plant_actors.jl +++ b/src/actors/balance_of_plant_actors.jl @@ -4,6 +4,7 @@ Base.@kwdef mutable struct ActorBalanceOfPlant <: FacilityAbstractActor dd::IMAS.dd + par::ParametersActor blanket_multiplier::Real efficiency_reclaim::Real thermal_electric_conversion_efficiency::Real @@ -11,6 +12,7 @@ end function ParametersActor(::Type{Val{:ActorBalanceOfPlant}}) par = ParametersActor(nothing) + par.model = Switch([:gasc, :EU_DEMO], "", "Balance of plant model"; default=:EU_DEMO) par.blanket_multiplier = Entry(Real, "", "Neutron thermal power multiplier in blanket"; default=1.2) par.efficiency_reclaim = Entry(Real, "", "Reclaim efficiency of thermal power hitting the blanket"; default=0.6) par.thermal_electric_conversion_efficiency = Entry(Real, "", "Efficiency of the steam cycle, thermal to electric"; default=0.4) @@ -18,26 +20,31 @@ function ParametersActor(::Type{Val{:ActorBalanceOfPlant}}) end """ - ActorBalanceOfPlant(dd::IMAS.dd, act::ParametersAllActors; gasc_method=false, kw...) + ActorBalanceOfPlant(dd::IMAS.dd, act::ParametersAllActors; kw...) Balance of plant actor that estimates the Net electrical power output by estimating the balance of plant electrical needs and compares it to the electricity generated from the thermal cycle. -Setting `gasc_method = true` simply assumes that the power to balance a plant is 7% of the electricity generated. +* `model = :gasc` simply assumes that the power to balance a plant is 7% of the electricity generated. -Setting `gasc_method = false` subdivides the power plant electrical needs to [:cryostat, :tritium_handling, :pumping] using EU-DEMO numbers. +* `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` """ -function ActorBalanceOfPlant(dd::IMAS.dd, act::ParametersAllActors; gasc_method=false, kw...) +function ActorBalanceOfPlant(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorBalanceOfPlant(kw...) - actor = ActorBalanceOfPlant(dd, par.blanket_multiplier, par.efficiency_reclaim, par.thermal_electric_conversion_efficiency) + actor = ActorBalanceOfPlant(dd, par) step(actor, gasc_method) finalize(actor) return actor end -function step(actor::ActorBalanceOfPlant, gasc_method) +function ActorBalanceOfPlant(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + ActorBalanceOfPlant(dd, par, par.blanket_multiplier, par.efficiency_reclaim, par.thermal_electric_conversion_efficiency) +end + +function step(actor::ActorBalanceOfPlant) dd = actor.dd bop = dd.balance_of_plant empty!(bop) @@ -73,16 +80,19 @@ function step(actor::ActorBalanceOfPlant, gasc_method) end ## balance of plant systems - if gasc_method + if actor.par.model == :gasc sys = resize!(bop_electric.system, "name" => "BOP_gasc", "index" => 2) sys.power = 0.07 .* bop_thermal.power_electric_generated - else + + elseif actor.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, bop.time) end + else + error("act.ActorBalanceOfPlant.model = $(actor.par.model) not recognized") end return actor end @@ -143,7 +153,7 @@ end function thermal_power(::Type{Val{:blanket}}, dd::IMAS.dd, actor::ActorBalanceOfPlant, time_array::Vector{<:Real}) power_fusion, time_array_fusion = IMAS.total_power_time(dd.core_sources, [6]) - return actor.blanket_multiplier .* IMAS.interp1d(time_array_fusion, 4 .* power_fusion, :constant).(time_array) # blanket_multiplier * P_neutron + return actor.par.blanket_multiplier .* IMAS.interp1d(time_array_fusion, 4 .* power_fusion, :constant).(time_array) # blanket_multiplier * P_neutron end function thermal_power(::Type{Val{:diverters}}, dd::IMAS.dd, actor::ActorBalanceOfPlant, time_array::Vector{<:Real}) diff --git a/src/actors/blanket_actors.jl b/src/actors/blanket_actors.jl index 52444644d..afb3e7739 100644 --- a/src/actors/blanket_actors.jl +++ b/src/actors/blanket_actors.jl @@ -4,6 +4,7 @@ Base.@kwdef mutable struct ActorBlanket <: ReactorAbstractActor dd::IMAS.dd + par::ParametersActor blanket_multiplier::Real thermal_power_extraction_efficiency::Real end @@ -30,12 +31,17 @@ Blanket actor """ function ActorBlanket(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorBlanket(kw...) - actor = ActorBlanket(dd, par.blanket_multiplier, par.thermal_power_extraction_efficiency) + actor = ActorBlanket(dd, par) step(actor) finalize(actor) return actor end +function ActorBlanket(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return ActorBlanket(dd, par, par.blanket_multiplier, par.thermal_power_extraction_efficiency) +end + function step(actor::ActorBlanket) dd = actor.dd diff --git a/src/actors/build_actors.jl b/src/actors/build_actors.jl index d762b1566..1e23bbfa1 100644 --- a/src/actors/build_actors.jl +++ b/src/actors/build_actors.jl @@ -129,6 +129,7 @@ end #= ========== =# Base.@kwdef mutable struct ActorFluxSwing <: ReactorAbstractActor dd::IMAS.dd + par::ParametersActor operate_at_j_crit::Bool j_tolerance::Real end @@ -167,12 +168,17 @@ OH flux consumption based on: """ function ActorFluxSwing(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorFluxSwing(kw...) - actor = ActorFluxSwing(; dd, par...) + actor = ActorFluxSwing(dd, par) step(actor) finalize(actor) return actor end +function ActorFluxSwing(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return ActorFluxSwing(dd, par, par.operate_at_j_crit, par.j_tolerance) +end + """ step(actor::ActorFluxSwing; operate_at_j_crit::Bool=actor.operate_at_j_crit, j_tolerance::Real=actor.j_tolerance, only=:all) @@ -294,6 +300,7 @@ end #= ========== =# Base.@kwdef mutable struct ActorLFSsizing <: ReactorAbstractActor dd::IMAS.dd + par::ParametersActor end function ParametersActor(::Type{Val{:ActorLFSsizing}}) @@ -315,10 +322,10 @@ Actor that resizes the Low Field Side of the build. """ function ActorLFSsizing(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorLFSsizing(kw...) + actor = ActorLFSsizing(dd, par) if par.do_plot plot(dd.build) end - actor = ActorLFSsizing(dd) step(actor; par.verbose) finalize(actor) if par.do_plot @@ -327,6 +334,11 @@ function ActorLFSsizing(dd::IMAS.dd, act::ParametersAllActors; kw...) return actor end +function ActorLFSsizing(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return ActorLFSsizing(dd, par) +end + function step(actor::ActorLFSsizing; verbose::Bool=false) dd = actor.dd @@ -361,6 +373,8 @@ end # HFS sizing # #= ========== =# Base.@kwdef mutable struct ActorHFSsizing <: ReactorAbstractActor + dd::IMAS.dd + par::ParametersActor stresses_actor::ActorStresses fluxswing_actor::ActorFluxSwing end @@ -386,15 +400,13 @@ Actor that resizes the High Field Side of the build. !!! note Manipulates radial build information in `dd.build.layer` """ -function ActorHFSsizing(dd::IMAS.dd, act::ParametersAllActors; kw...) +function ActorHFSsizing(dd::IMAS.dd, act::ParametersAllActors; kw_ActorFluxSwing=Dict(), kw_ActorStresses=Dict(), kw...) par = act.ActorHFSsizing(kw...) + actor = ActorHFSsizing(dd, par, act; kw_ActorFluxSwing, kw_ActorStresses) if par.do_plot p = plot(dd.build) end - fluxswing_actor = ActorFluxSwing(; dd, operate_at_j_crit=par.unconstrained_flattop_duration, par.j_tolerance) - stresses_actor = ActorStresses(dd) - actor = ActorHFSsizing(stresses_actor, fluxswing_actor) - step(actor; par.verbose, par.j_tolerance, par.stress_tolerance, par.fixed_aspect_ratio, par.unconstrained_flattop_duration) + step(actor) finalize(actor) if par.do_plot display(plot!(p, dd.build; cx=false)) @@ -402,14 +414,21 @@ function ActorHFSsizing(dd::IMAS.dd, act::ParametersAllActors; kw...) return actor end +function ActorHFSsizing(dd::IMAS.dd, par::ParametersActor, act::ParametersAllActors; kw_ActorFluxSwing=Dict(), kw_ActorStresses=Dict(), kw...) + par = act.ActorHFSsizing(kw...) + fluxswing_actor = ActorFluxSwing(dd, act.ActorFluxSwing; kw_ActorFluxSwing...) + stresses_actor = ActorStresses(dd, act.ActorStresses; kw_ActorStresses...) + return ActorHFSsizing(dd, par, stresses_actor, fluxswing_actor) +end + function step( actor::ActorHFSsizing; - j_tolerance::Real=0.4, - stress_tolerance::Real=0.2, - fixed_aspect_ratio::Bool=true, - unconstrained_flattop_duration::Bool=true, - verbose::Bool=false, - do_plot=false + j_tolerance::Real=actor.par.j_tolerance, + stress_tolerance::Real=actor.par.stress_tolerance, + fixed_aspect_ratio::Bool=actor.par.fixed_aspect_ratio, + unconstrained_flattop_duration::Bool=actor.par.unconstrained_flattop_duration, + verbose::Bool=actor.par.verbose, + debug_plot=false ) function target_value(value, target, tolerance) # relative error with tolerance @@ -478,7 +497,7 @@ function step( c_stf = target_value(maximum(dd.solid_mechanics.center_stack.stress.vonmises.tf), stainless_steel.yield_strength, stress_tolerance) end - if do_plot + if debug_plot push!(C_JOH, c_joh) push!(C_SOH, c_soh) push!(C_JTF, c_jtf) @@ -506,7 +525,7 @@ function step( old_a = plasma.thickness / 2.0 old_ϵ = old_R0 / old_a - if do_plot + if debug_plot C_JOH = [] C_SOH = [] C_JTF = [] @@ -575,7 +594,7 @@ function step( a = plasma.thickness / 2.0 ϵ = R0 / a - if do_plot + if debug_plot p = plot(yscale=:log10) plot!(p, C_JOH ./ (C_JOH .> 0.0), label="Jcrit OH") plot!(p, C_SOH ./ (C_SOH .> 0.0), label="Stresses OH") @@ -634,6 +653,7 @@ end #= ============= =# Base.@kwdef mutable struct ActorCXbuild <: ReactorAbstractActor dd::IMAS.dd + par::ParametersActor end function ParametersActor(::Type{Val{:ActorCXbuild}}) @@ -653,8 +673,8 @@ Actor that builds the 2D cross section of the build. """ function ActorCXbuild(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorCXbuild(kw...) - actor = ActorCXbuild(dd) - step(actor; par.rebuild_wall) + actor = ActorCXbuild(dd, par) + step(actor) finalize(actor) if par.do_plot plot(dd.build) @@ -663,7 +683,12 @@ function ActorCXbuild(dd::IMAS.dd, act::ParametersAllActors; kw...) return actor end -function step(actor::ActorCXbuild; rebuild_wall::Bool=true) +function ActorCXbuild(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return ActorCXbuild(dd, par) +end + +function step(actor::ActorCXbuild; rebuild_wall::Bool=actor.par.rebuild_wall) build_cx!(actor.dd; rebuild_wall) end diff --git a/src/actors/compound_actors.jl b/src/actors/compound_actors.jl index e8a7f3acc..cd51760f7 100644 --- a/src/actors/compound_actors.jl +++ b/src/actors/compound_actors.jl @@ -3,9 +3,10 @@ #= ========================= =# Base.@kwdef mutable struct ActorEquilibriumTransport <: PlasmaAbstractActor dd::IMAS.dd - actor_jt::Union{Nothing,ActorSteadyStateCurrent} - actor_eq::Union{Nothing,ActorEquilibrium} - actor_tr::Union{Nothing,ActorTauenn} + par::ParametersActor + actor_jt::ActorSteadyStateCurrent + actor_eq::ActorEquilibrium + actor_tr::ActorTauenn end function ParametersActor(::Type{Val{:ActorEquilibriumTransport}}) @@ -28,14 +29,22 @@ ActorEquilibrium(dd, act) # Equilibrium !!! note Stores data in `dd.equilibrium, dd.core_profiles, dd.core_sources` """ -function ActorEquilibriumTransport(dd::IMAS.dd, act::ParametersAllActors; kw...) +function ActorEquilibriumTransport(dd::IMAS.dd, act::ParametersAllActors; kw_ActorSteadyStateCurrent=Dict(), kw_ActorEquilibrium=Dict(), kw_ActorTauenn=Dict(), kw...) par = act.ActorEquilibriumTransport(kw...) - actor = ActorEquilibriumTransport(dd, nothing, nothing, nothing) + actor = ActorEquilibriumTransport(dd, par, act; kw_ActorSteadyStateCurrent, kw_ActorEquilibrium, kw_ActorTauenn) step(actor; act, iterations=par.iterations, do_plot=par.do_plot) finalize(actor) return actor end +function ActorEquilibriumTransport(dd::IMAS.dd, par::ParametersActor, act::ParametersAllActors; kw_ActorSteadyStateCurrent=Dict(), kw_ActorEquilibrium=Dict(), kw_ActorTauenn=Dict(), kw...) + par = par(kw...) + actor_jt = ActorSteadyStateCurrent(dd, act.ActorSteadyStateCurrent; kw_ActorSteadyStateCurrent...) + actor_eq = ActorEquilibrium(dd, act.ActorEquilibrium, act; kw_ActorEquilibrium...) + actor_tr = ActorTauenn(dd, act.ActorTauenn; kw_ActorTauenn...) + return ActorEquilibriumTransport(dd, par, actor_jt, actor_eq, actor_tr) +end + function step(actor::ActorEquilibriumTransport; act::Union{Missing,ParametersAllActors}=missing, iterations::Int=1, do_plot::Bool=false) dd = actor.dd if act === missing @@ -49,20 +58,20 @@ function step(actor::ActorEquilibriumTransport; act::Union{Missing,ParametersAll end # Set j_ohmic to steady state - ActorSteadyStateCurrent(dd, act) + finalize(step(actor.actor_jt)) for iteration in 1:iterations # run transport actor - actor.actor_tr = ActorTauenn(dd, act) + finalize(step(actor.actor_tr)) # prepare equilibrium input based on transport core_profiles output prepare(dd, :ActorEquilibrium, act) # run equilibrium actor with the updated beta - actor.actor_eq = ActorEquilibrium(dd, act) + finalize(step(actor.actor_eq)) # Set j_ohmic to steady state - actor.actor_jt = ActorSteadyStateCurrent(dd, act) + finalize(step(actor.actor_jt)) end if do_plot @@ -113,7 +122,7 @@ function step(actor::ActorWholeFacility; act::Union{Missing,ParametersAllActors} ActorNeutronics(dd, act) ActorBlanket(dd, act) ActorDivertors(dd, act) - ActorBalanceOfPlant(dd,act) + ActorBalanceOfPlant(dd, act) ActorCosting(dd, act) return actor end \ No newline at end of file diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index 19d4e5720..496f32275 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -97,6 +97,7 @@ end Base.@kwdef mutable struct ActorCosting <: FacilityAbstractActor dd::IMAS.dd + par::ParametersActor end function ParametersActor(::Type{Val{:ActorCosting}}) @@ -114,12 +115,17 @@ This actor estimates the cost of the fusion power plant. """ function ActorCosting(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorCosting(kw...) - actor = ActorCosting(dd) + actor = ActorCosting(dd, par) step(actor) finalize(actor) return actor end +function ActorCosting(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return ActorCosting(dd, par) +end + function step(actor::ActorCosting) dd = actor.dd cst = dd.costing diff --git a/src/actors/current_actors.jl b/src/actors/current_actors.jl index 26ece8394..b96091cdb 100644 --- a/src/actors/current_actors.jl +++ b/src/actors/current_actors.jl @@ -115,6 +115,7 @@ end #= ======================= =# Base.@kwdef mutable struct ActorSteadyStateCurrent <: PlasmaAbstractActor dd::IMAS.dd + par::ParametersActor end function ParametersActor(::Type{Val{:ActorSteadyStateCurrent}}) @@ -134,16 +135,22 @@ Also sets the ohmic, bootstrap and non-inductive current profiles in `dd.core_pr """ function ActorSteadyStateCurrent(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorSteadyStateCurrent(kw...) - actor = ActorSteadyStateCurrent(dd, par...) + actor = ActorSteadyStateCurrent(dd, par) step(actor) finalize(actor) return actor end +function ActorSteadyStateCurrent(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return ActorSteadyStateCurrent(dd, par) +end + function step(actor::ActorSteadyStateCurrent) dd = actor.dd IMAS.j_ohmic_steady_state!(dd.equilibrium.time_slice[], dd.core_profiles.profiles_1d[]) # update core_sources related to current IMAS.bootstrap_source!(dd) IMAS.ohmic_source!(dd) + return actor end diff --git a/src/actors/divertors_actors.jl b/src/actors/divertors_actors.jl index 8e6200b52..9309178ea 100644 --- a/src/actors/divertors_actors.jl +++ b/src/actors/divertors_actors.jl @@ -4,6 +4,7 @@ Base.@kwdef mutable struct ActorDivertors <: ReactorAbstractActor dd::IMAS.dd + par::ParametersActor thermal_power_extraction_efficiency::Real end @@ -24,12 +25,17 @@ Simple Divertor actor """ function ActorDivertors(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorDivertors(kw...) - actor = ActorDivertors(dd, par.thermal_power_extraction_efficiency) + actor = ActorDivertors(dd, par) step(actor) finalize(actor) return actor end +function ActorDivertors(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return ActorDivertors(dd, par, par.thermal_power_extraction_efficiency) +end + function step(actor::ActorDivertors) dd = actor.dd diff --git a/src/actors/neutronics_actors.jl b/src/actors/neutronics_actors.jl index 8d77ea596..5d221af8d 100644 --- a/src/actors/neutronics_actors.jl +++ b/src/actors/neutronics_actors.jl @@ -21,6 +21,7 @@ end Base.@kwdef mutable struct ActorNeutronics <: PlasmaAbstractActor dd::IMAS.dd + par::ParametersActor end function ParametersActor(::Type{Val{:ActorNeutronics}}) @@ -41,13 +42,18 @@ This actor estimates the neutron loading on the wall using the fusion source fro """ function ActorNeutronics(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorNeutronics(kw...) - actor = ActorNeutronics(dd) - step(actor; N=par.N, step=par.step, do_plot=par.do_plot) + actor = ActorNeutronics(dd, par) + step(actor) finalize(actor) return actor end -function step(actor::ActorNeutronics; N::Integer=100000, step=0.05, do_plot::Bool=false) +function ActorNeutronics(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return ActorNeutronics(dd, par) +end + +function step(actor::ActorNeutronics; N::Integer=actor.par.N, step=actor.par.step, do_plot::Bool=actor.par.do_plot) dd = actor.dd cp1d = dd.core_profiles.profiles_1d[] eqt = dd.equilibrium.time_slice[] diff --git a/src/actors/pf_active_actors.jl b/src/actors/pf_active_actors.jl index 29d82d6f7..986db31b0 100644 --- a/src/actors/pf_active_actors.jl +++ b/src/actors/pf_active_actors.jl @@ -14,6 +14,7 @@ Base.@kwdef mutable struct PFcoilsOptTrace end Base.@kwdef mutable struct ActorPFcoilsOpt <: ReactorAbstractActor + par::ParametersActor eq_in::IMAS.equilibrium eq_out::IMAS.equilibrium pf_active::IMAS.pf_active @@ -38,6 +39,7 @@ function ParametersActor(::Type{Val{:ActorPFcoilsOpt}}) par.weight_strike = Entry(Real, "", "Weight given to matching the strike-points"; default=0.0) par.weight_lcfs = Entry(Real, "", "Weight given to matching last closed flux surface"; default=1.0) par.weight_null = Entry(Real, "", "Weight given to get field null for plasma breakdown"; default=1E-3) + par.maxiter = Entry(Integer, "", "Maximum number of optimizer iterations"; default=1000) options = [ :none => "Do not optimize", :currents => "Find optimial coil currents but do not change coil positions", @@ -61,7 +63,7 @@ to match the equilibrium boundary shape and obtain a field-null region at plasma """ function ActorPFcoilsOpt(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorPFcoilsOpt(kw...) - actor = ActorPFcoilsOpt(dd; par.green_model, par.symmetric) + actor = ActorPFcoilsOpt(dd, par) if par.optimization_scheme == :none if par.do_plot @@ -73,13 +75,13 @@ function ActorPFcoilsOpt(dd::IMAS.dd, act::ParametersAllActors; kw...) else if par.optimization_scheme == :currents # find coil currents - step(actor; par.weight_lcfs, par.weight_null, par.weight_currents, par.weight_strike, par.verbose, maxiter=1000, optimization_scheme=:currents) - finalize(actor) + step(actor) + finalize(actor; update_equilibrium=false) elseif par.optimization_scheme == :rail # optimize coil location and currents - step(actor; par.weight_lcfs, par.weight_null, par.weight_currents, par.weight_strike, par.verbose, maxiter=1000, optimization_scheme=:rail) - finalize(actor) + step(actor) + finalize(actor; update_equilibrium=false) if par.do_plot display(plot(actor.trace, :cost)) @@ -96,106 +98,106 @@ function ActorPFcoilsOpt(dd::IMAS.dd, act::ParametersAllActors; kw...) display(plot(actor, equilibrium=true, time_index=length(dd.equilibrium.time))) end - finalize(actor; update_eq_in=par.update_equilibrium) + finalize(actor) end return actor end -function ActorPFcoilsOpt(dd::IMAS.dd; kw...) - return ActorPFcoilsOpt(dd.equilibrium, dd.build, dd.pf_active; kw...) -end - -function ActorPFcoilsOpt( - eq_in::IMAS.equilibrium, - bd::IMAS.build, - pf::IMAS.pf_active; - λ_regularize=1E-3, - green_model=:simple, - symmetric=false) +function ActorPFcoilsOpt(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + eq_in = dd.equilibrium + eq_out = deepcopy(eq_in) + pf = dd.pf_active + bd = dd.build + par.symmetric + λ_regularize=1E-3 + trace=PFcoilsOptTrace() + par.green_model # reset pf coil rails n_coils = [rail.coils_number for rail in bd.pf_active.rail] init_pf_active(pf, bd, n_coils) - # basic constructors - eq_out = deepcopy(eq_in) - - # constructor - pfactor = ActorPFcoilsOpt(eq_in, eq_out, pf, bd, symmetric, λ_regularize, PFcoilsOptTrace(), green_model) - - return pfactor + return ActorPFcoilsOpt(par, eq_in, eq_out, pf, bd, par.symmetric, λ_regularize, trace, par.green_model) end """ - step(pfactor::ActorPFcoilsOpt; - symmetric=pfactor.symmetric, - λ_regularize=pfactor.λ_regularize, - weight_lcfs=1.0, - weight_null=1E-3, - weight_currents=0.5, - weight_strike=0.0, - maxiter=10000, - optimization_scheme=:rail, - verbose=false) + step(actor::ActorPFcoilsOpt; + symmetric::Bool=actor.symmetric, + λ_regularize::Real=actor.λ_regularize, + weight_lcfs::Real=actor.par.weight_lcfs, + weight_null::Real=actor.par.weight_null, + weight_currents::Real=actor.par.weight_currents, + weight_strike::Real=actor.par.weight_strike, + maxiter::Real=actor.par.maxiter, + optimization_scheme::Symbol=actor.par.optimization_scheme, + verbose::Bool=actor.par.verbose) Optimize coil currents and positions to produce sets of equilibria while minimizing coil currents """ -function step(pfactor::ActorPFcoilsOpt; - symmetric=pfactor.symmetric, - λ_regularize=pfactor.λ_regularize, - weight_lcfs=1.0, - weight_null=1E-3, - weight_currents=0.5, - weight_strike=0.0, - maxiter=10000, - optimization_scheme=:rail, - verbose=false) - - fixed_coils, pinned_coils, optim_coils = fixed_pinned_optim_coils(pfactor, optimization_scheme) +function step(actor::ActorPFcoilsOpt; + symmetric::Bool=actor.symmetric, + λ_regularize::Real=actor.λ_regularize, + weight_lcfs::Real=actor.par.weight_lcfs, + weight_null::Real=actor.par.weight_null, + weight_currents::Real=actor.par.weight_currents, + weight_strike::Real=actor.par.weight_strike, + maxiter::Real=actor.par.maxiter, + optimization_scheme::Symbol=actor.par.optimization_scheme, + verbose::Bool=actor.par.verbose) + + fixed_coils, pinned_coils, optim_coils = fixed_pinned_optim_coils(actor, optimization_scheme) coils = vcat(pinned_coils, optim_coils, fixed_coils) for coil in coils - coil.current_time = pfactor.eq_in.time - coil.current_data = zeros(size(pfactor.eq_in.time)) + coil.current_time = actor.eq_in.time + coil.current_data = zeros(size(actor.eq_in.time)) end - bd = pfactor.bd + bd = actor.bd # run rail type optimizer if optimization_scheme in [:rail, :currents] - (λ_regularize, trace) = optimize_coils_rail(pfactor.eq_in; pinned_coils, optim_coils, fixed_coils, symmetric, λ_regularize, weight_lcfs, weight_null, weight_currents, weight_strike, bd, maxiter, verbose) + (λ_regularize, trace) = optimize_coils_rail(actor.eq_in; pinned_coils, optim_coils, fixed_coils, symmetric, λ_regularize, weight_lcfs, weight_null, weight_currents, weight_strike, bd, maxiter, verbose) else error("Supported ActorPFcoilsOpt optimization_scheme are `:currents` or `:rail`") end - pfactor.λ_regularize = λ_regularize - pfactor.trace = trace + actor.λ_regularize = λ_regularize + actor.trace = trace # transfer the results to IMAS.pf_active for coil in coils transfer_info_GS_coil_to_IMAS(bd, coil) end - return pfactor + return actor end """ - finalize(pfactor::ActorPFcoilsOpt; scale_eq_domain_size = 1.0) + finalize( + actor::ActorPFcoilsOpt; + update_equilibrium::Bool=actor.par.update_equilibrium, + scale_eq_domain_size::Real=1.0) -Update pfactor.eq_out 2D equilibrium PSI based on coils positions and currents +Update actor.eq_out 2D equilibrium PSI based on coils positions and currents """ -function finalize(pfactor::ActorPFcoilsOpt; scale_eq_domain_size=1.0, update_eq_in=false) +function finalize( + actor::ActorPFcoilsOpt; + update_equilibrium::Bool=actor.par.update_equilibrium, + scale_eq_domain_size::Real=1.0) + coils = GS_IMAS_pf_active__coil[] - for (k, coil) in enumerate(pfactor.pf_active.coil) - if k <= pfactor.bd.pf_active.rail[1].coils_number - coil_tech = pfactor.bd.oh.technology + for (k, coil) in enumerate(actor.pf_active.coil) + if k <= actor.bd.pf_active.rail[1].coils_number + coil_tech = actor.bd.oh.technology else - coil_tech = pfactor.bd.pf_active.technology + coil_tech = actor.bd.pf_active.technology end - push!(coils, GS_IMAS_pf_active__coil(coil, coil_tech, pfactor.green_model)) + push!(coils, GS_IMAS_pf_active__coil(coil, coil_tech, actor.green_model)) end # update equilibrium - for time_index in 1:length(pfactor.eq_in.time_slice) - if ismissing(pfactor.eq_in.time_slice[time_index].global_quantities, :ip) + for time_index in 1:length(actor.eq_in.time_slice) + if ismissing(actor.eq_in.time_slice[time_index].global_quantities, :ip) continue end for coil in coils @@ -203,28 +205,28 @@ function finalize(pfactor::ActorPFcoilsOpt; scale_eq_domain_size=1.0, update_eq_ end # convert equilibrium to Equilibrium.jl format, since this is what VacuumFields uses - EQfixed = IMAS2Equilibrium(pfactor.eq_in.time_slice[time_index]) + EQfixed = IMAS2Equilibrium(actor.eq_in.time_slice[time_index]) # # update ψ map R = range(EQfixed.r[1] / scale_eq_domain_size, EQfixed.r[end] * scale_eq_domain_size, length=length(EQfixed.r)) Z = range(EQfixed.z[1] * scale_eq_domain_size, EQfixed.z[end] * scale_eq_domain_size, length=length(EQfixed.z)) ψ_f2f = transpose(VacuumFields.fixed2free(EQfixed, coils, R, Z)) - pfactor.eq_out.time_slice[time_index].profiles_2d[1].grid.dim1 = R - pfactor.eq_out.time_slice[time_index].profiles_2d[1].grid.dim2 = Z + actor.eq_out.time_slice[time_index].profiles_2d[1].grid.dim1 = R + actor.eq_out.time_slice[time_index].profiles_2d[1].grid.dim2 = Z if false # hack to force up-down symmetric equilibrium - pfactor.eq_out.time_slice[time_index].profiles_2d[1].psi = (ψ_f2f .+ ψ_f2f[1:end, end:-1:1]) ./ 2.0 + actor.eq_out.time_slice[time_index].profiles_2d[1].psi = (ψ_f2f .+ ψ_f2f[1:end, end:-1:1]) ./ 2.0 else - pfactor.eq_out.time_slice[time_index].profiles_2d[1].psi = ψ_f2f + actor.eq_out.time_slice[time_index].profiles_2d[1].psi = ψ_f2f end end # update psi - if update_eq_in - for time_index in 1:length(pfactor.eq_out.time_slice) - if !ismissing(pfactor.eq_out.time_slice[time_index].global_quantities, :ip) - psi1 = pfactor.eq_out.time_slice[time_index].profiles_2d[1].psi - pfactor.eq_in.time_slice[time_index].profiles_2d[1].psi = psi1 - IMAS.flux_surfaces(pfactor.eq_in.time_slice[time_index]) + if update_equilibrium + for time_index in 1:length(actor.eq_out.time_slice) + if !ismissing(actor.eq_out.time_slice[time_index].global_quantities, :ip) + psi1 = actor.eq_out.time_slice[time_index].profiles_2d[1].psi + actor.eq_in.time_slice[time_index].profiles_2d[1].psi = psi1 + IMAS.flux_surfaces(actor.eq_in.time_slice[time_index]) end end end @@ -505,7 +507,7 @@ function optimize_coils_rail( weight_currents::Real, weight_strike::Real, bd::IMAS.build, - maxiter::Int, + maxiter::Integer, verbose::Bool) R0 = eq.vacuum_toroidal_field.r0 @@ -679,31 +681,31 @@ function optimize_coils_rail( end """ - fixed_pinned_optim_coils(pfactor, optimization_scheme) + fixed_pinned_optim_coils(actor, optimization_scheme) Returns tuple of GS_IMAS_pf_active__coil coils organized by their function: - fixed: fixed position and current - pinned: coils with fixed position but current is optimized - optim: coils that have theri position and current optimized """ -function fixed_pinned_optim_coils(pfactor, optimization_scheme) +function fixed_pinned_optim_coils(actor, optimization_scheme) fixed_coils = GS_IMAS_pf_active__coil[] pinned_coils = GS_IMAS_pf_active__coil[] optim_coils = GS_IMAS_pf_active__coil[] - for (k, coil) in enumerate(pfactor.pf_active.coil) - if k <= pfactor.bd.pf_active.rail[1].coils_number - coil_tech = pfactor.bd.oh.technology + for (k, coil) in enumerate(actor.pf_active.coil) + if k <= actor.bd.pf_active.rail[1].coils_number + coil_tech = actor.bd.oh.technology else - coil_tech = pfactor.bd.pf_active.technology + coil_tech = actor.bd.pf_active.technology end if coil.identifier == "pinned" - push!(pinned_coils, GS_IMAS_pf_active__coil(coil, coil_tech, pfactor.green_model)) + push!(pinned_coils, GS_IMAS_pf_active__coil(coil, coil_tech, actor.green_model)) elseif (coil.identifier == "optim") && (optimization_scheme == :currents) - push!(pinned_coils, GS_IMAS_pf_active__coil(coil, coil_tech, pfactor.green_model)) + push!(pinned_coils, GS_IMAS_pf_active__coil(coil, coil_tech, actor.green_model)) elseif coil.identifier == "optim" - push!(optim_coils, GS_IMAS_pf_active__coil(coil, coil_tech, pfactor.green_model)) + push!(optim_coils, GS_IMAS_pf_active__coil(coil, coil_tech, actor.green_model)) elseif coil.identifier == "fixed" - push!(fixed_coils, GS_IMAS_pf_active__coil(coil, coil_tech, pfactor.green_model)) + push!(fixed_coils, GS_IMAS_pf_active__coil(coil, coil_tech, actor.green_model)) else error("Accepted type of coil.identifier are only \"optim\", \"pinned\", or \"fixed\"") end @@ -715,12 +717,12 @@ end # plotting # #= ======== =# """ - plot_pfcoilsactor_cx(pfactor::ActorPFcoilsOpt; time_index=1, equilibrium=true, rail=true) + plot_pfcoilsactor_cx(actor::ActorPFcoilsOpt; time_index=1, equilibrium=true, rail=true) Plot ActorPFcoilsOpt optimization cross-section """ @recipe function plot_ActorPFcoilsOpt_cx( - pfactor::ActorPFcoilsOpt; + actor::ActorPFcoilsOpt; time_index=nothing, equilibrium=true, build=true, @@ -736,13 +738,13 @@ Plot ActorPFcoilsOpt optimization cross-section @assert typeof(plot_r_buffer) <: Real if time_index === nothing - time_index = length(pfactor.eq_out.time_slice) + time_index = length(actor.eq_out.time_slice) end - time = pfactor.eq_out.time_slice[time_index].time + time = actor.eq_out.time_slice[time_index].time # if there is no equilibrium then treat this as a field_null plot field_null = false - if length(pfactor.eq_out.time_slice[time_index].profiles_2d) == 0 || ismissing(pfactor.eq_out.time_slice[time_index].profiles_2d[1], :psi) + if length(actor.eq_out.time_slice[time_index].profiles_2d) == 0 || ismissing(actor.eq_out.time_slice[time_index].profiles_2d[1], :psi) coils_flux = equilibrium field_null = true end @@ -753,8 +755,8 @@ Plot ActorPFcoilsOpt optimization cross-section end # setup plotting area - xlim = [0.0, maximum(pfactor.bd.layer[end].outline.r)] - ylim = [minimum(pfactor.bd.layer[end].outline.z), maximum(pfactor.bd.layer[end].outline.z)] + xlim = [0.0, maximum(actor.bd.layer[end].outline.r)] + ylim = [minimum(actor.bd.layer[end].outline.z), maximum(actor.bd.layer[end].outline.z)] xlim --> xlim * plot_r_buffer ylim --> ylim aspect_ratio --> :equal @@ -763,7 +765,7 @@ Plot ActorPFcoilsOpt optimization cross-section if build @series begin exclude_layers --> [:oh] - pfactor.bd + actor.bd end end @@ -774,19 +776,19 @@ Plot ActorPFcoilsOpt optimization cross-section Z = range(ylim[1], ylim[2], length=Int(ceil(ngrid * (ylim[2] - ylim[1]) / (xlim[2] - xlim[1])))) coils = GS_IMAS_pf_active__coil[] - for (k, coil) in enumerate(pfactor.pf_active.coil) - if k <= pfactor.bd.pf_active.rail[1].coils_number - coil_tech = pfactor.bd.oh.technology + for (k, coil) in enumerate(actor.pf_active.coil) + if k <= actor.bd.pf_active.rail[1].coils_number + coil_tech = actor.bd.oh.technology else - coil_tech = pfactor.bd.pf_active.technology + coil_tech = actor.bd.pf_active.technology end - coil = GS_IMAS_pf_active__coil(coil, coil_tech, pfactor.green_model) + coil = GS_IMAS_pf_active__coil(coil, coil_tech, actor.green_model) coil.time_index = time_index push!(coils, coil) end # ψ coil currents - ψbound = pfactor.eq_out.time_slice[time_index].global_quantities.psi_boundary + ψbound = actor.eq_out.time_slice[time_index].global_quantities.psi_boundary ψ = VacuumFields.coils_flux(2 * pi, coils, R, Z) ψmin = minimum(x -> isnan(x) ? Inf : x, ψ) @@ -821,7 +823,7 @@ Plot ActorPFcoilsOpt optimization cross-section @series begin wireframe --> true exclude_layers --> [:oh] - pfactor.bd + actor.bd end end @@ -832,14 +834,14 @@ Plot ActorPFcoilsOpt optimization cross-section cx := true label --> "Field null region" seriescolor --> :red - pfactor.eq_out.time_slice[time_index] + actor.eq_out.time_slice[time_index] end else @series begin cx := true label --> "Final" seriescolor --> :red - pfactor.eq_out.time_slice[time_index] + actor.eq_out.time_slice[time_index] end @series begin cx := true @@ -847,7 +849,7 @@ Plot ActorPFcoilsOpt optimization cross-section seriescolor --> :blue lcfs --> true linestyle --> :dash - pfactor.eq_in.time_slice[time_index] + actor.eq_in.time_slice[time_index] end end end @@ -855,14 +857,14 @@ Plot ActorPFcoilsOpt optimization cross-section # plot pf_active coils @series begin time --> time - pfactor.pf_active + actor.pf_active end # plot optimization rails if rail @series begin label --> (build ? "Coil opt. rail" : "") - pfactor.bd.pf_active.rail + actor.bd.pf_active.rail end end diff --git a/src/actors/sources_actors.jl b/src/actors/sources_actors.jl index 996060f04..348bd660d 100644 --- a/src/actors/sources_actors.jl +++ b/src/actors/sources_actors.jl @@ -5,16 +5,17 @@ import NumericalIntegration: integrate #= === =# Base.@kwdef mutable struct ActorNBIsimple <: HCDAbstractActor dd::IMAS.dd - width::Vector{Real} - rho_0::Vector{Real} - current_efficiency::Vector{Real} + par::ParametersActor + width::AbstractVector{<:Real} + rho_0::AbstractVector{<:Real} + current_efficiency::AbstractVector{<:Real} end function ParametersActor(::Type{Val{:ActorNBIsimple}}) par = ParametersActor(nothing) - par.width = Entry(Real, "", "Width of the deposition profile"; default=0.3) - par.rho_0 = Entry(Real, "", "Radial location of the deposition profile"; default=0.0) - par.current_efficiency = Entry(Real, "A/W", "Current drive efficiency"; default=0.3) + par.width = Entry(Union{Real,AbstractVector{<:Real}}, "", "Width of the deposition profile"; default=0.3) + par.rho_0 = Entry(Union{Real,AbstractVector{<:Real}}, "", "Radial location of the deposition profile"; default=0.0) + par.current_efficiency = Entry(Union{Real,AbstractVector{<:Real}}, "A/W", "Current drive efficiency"; default=0.3) return par end @@ -28,15 +29,17 @@ This actor estimates the NBI ion/electron energy deposition, particle source, ro """ function ActorNBIsimple(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorNBIsimple(kw...) - actor = ActorNBIsimple(dd; par.width, par.rho_0, par.current_efficiency) + actor = ActorNBIsimple(dd, par) step(actor) finalize(actor) return actor end -function ActorNBIsimple(dd::IMAS.dd; width::Real=0.3, rho_0::Real=0.0, current_efficiency::Real=0.3) - nbeam = ones(length(dd.nbi.unit)) - return ActorNBIsimple(dd, nbeam .* width, nbeam .* rho_0, nbeam .* current_efficiency) +function ActorNBIsimple(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + n_beams = length(dd.nbi.unit) + _, width, rho_0, current_efficiency = same_length_vectors(1:n_beams, par.width, par.rho_0, par.current_efficiency) + return ActorNBIsimple(dd, par, width, rho_0, current_efficiency) end function step(actor::ActorNBIsimple) @@ -89,16 +92,17 @@ end #= == =# Base.@kwdef mutable struct ActorECsimple <: HCDAbstractActor dd::IMAS.dd - width::Vector{Real} - rho_0::Vector{Real} - current_efficiency::Vector{Real} + par::ParametersActor + width::AbstractVector{<:Real} + rho_0::AbstractVector{<:Real} + current_efficiency::AbstractVector{<:Real} end function ParametersActor(::Type{Val{:ActorECsimple}}) par = ParametersActor(nothing) - par.width = Entry(Real, "", "Width of the deposition profile"; default=0.1) - par.rho_0 = Entry(Real, "", "Radial location of the deposition profile"; default=0.0) - par.current_efficiency = Entry(Real, "A/W", "Current drive efficiency"; default=0.2) + par.width = Entry(Union{Real,AbstractVector{<:Real}}, "", "Width of the deposition profile"; default=0.1) + par.rho_0 = Entry(Union{Real,AbstractVector{<:Real}}, "", "Radial location of the deposition profile"; default=0.0) + par.current_efficiency = Entry(Union{Real,AbstractVector{<:Real}}, "A/W", "Current drive efficiency"; default=0.2) return par end @@ -112,15 +116,17 @@ This actor estimates the EC electron energy deposition and current drive as a ga """ function ActorECsimple(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorECsimple(kw...) - actor = ActorECsimple(dd; par.width, par.rho_0, par.current_efficiency) + actor = ActorECsimple(dd, par) step(actor) finalize(actor) return actor end -function ActorECsimple(dd::IMAS.dd; width::Real=0.1, rho_0::Real=0.0, current_efficiency::Real=0.2) - n_launchers = ones(length(dd.ec_launchers.launcher)) - return ActorECsimple(dd, n_launchers .* width, n_launchers .* rho_0, n_launchers .* current_efficiency) +function ActorECsimple(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + n_launchers = length(dd.ec_launchers.launcher) + _, width, rho_0, current_efficiency = same_length_vectors(1:n_launchers, par.width, par.rho_0, par.current_efficiency) + return ActorECsimple(dd, par, width, rho_0, current_efficiency) end function step(actor::ActorECsimple) @@ -165,16 +171,17 @@ end #= == =# Base.@kwdef mutable struct ActorICsimple <: HCDAbstractActor dd::IMAS.dd - width::Vector{Real} - rho_0::Vector{Real} - current_efficiency::Vector{Real} + par::ParametersActor + width::AbstractVector{<:Real} + rho_0::AbstractVector{<:Real} + current_efficiency::AbstractVector{<:Real} end function ParametersActor(::Type{Val{:ActorICsimple}}) par = ParametersActor(nothing) - par.width = Entry(Real, "", "Width of the deposition profile"; default=0.1) - par.rho_0 = Entry(Real, "", "Radial location of the deposition profile"; default=0.0) - par.current_efficiency = Entry(Real, "A/W", "Current drive efficiency"; default=0.125) + par.width = Entry(Union{Real,AbstractVector{<:Real}}, "", "Width of the deposition profile"; default=0.1) + par.rho_0 = Entry(Union{Real,AbstractVector{<:Real}}, "", "Radial location of the deposition profile"; default=0.0) + par.current_efficiency = Entry(Union{Real,AbstractVector{<:Real}}, "A/W", "Current drive efficiency"; default=0.125) return par end @@ -188,15 +195,17 @@ This actor estimates the ion-cyclotron electron/ion energy deposition and curren """ function ActorICsimple(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorICsimple(kw...) - actor = ActorICsimple(dd; par.width, par.rho_0, par.current_efficiency) + actor = ActorICsimple(dd, par) step(actor) finalize(actor) return actor end -function ActorICsimple(dd::IMAS.dd; width::Real=0.1, rho_0::Real=0.0, current_efficiency::Real=0.125) - n_antennas = ones(length(dd.ic_antennas.antenna)) - return ActorICsimple(dd, n_antennas .* width, n_antennas .* rho_0, n_antennas .* current_efficiency) +function ActorICsimple(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + n_antennas = length(dd.ic_antennas.antenna) + _, width, rho_0, current_efficiency = same_length_vectors(1:n_antennas, par.width, par.rho_0, par.current_efficiency) + return ActorICsimple(dd, par, width, rho_0, current_efficiency) end function step(actor::ActorICsimple) @@ -241,16 +250,17 @@ end #= == =# Base.@kwdef mutable struct ActorLHsimple <: HCDAbstractActor dd::IMAS.dd - width::Vector{Real} - rho_0::Vector{Real} - current_efficiency::Vector{Real} + par::ParametersActor + width::AbstractVector{<:Real} + rho_0::AbstractVector{<:Real} + current_efficiency::AbstractVector{<:Real} end function ParametersActor(::Type{Val{:ActorLHsimple}}) par = ParametersActor(nothing) - par.width = Entry(Real, "", "Width of the deposition profile"; default=0.15) - par.rho_0 = Entry(Real, "", "Radial location of the deposition profile"; default=0.6) - par.current_efficiency = Entry(Real, "A/W", "Current drive efficiency"; default=0.4) + par.width = Entry(Union{Real,AbstractVector{<:Real}}, "", "Width of the deposition profile"; default=0.15) + par.rho_0 = Entry(Union{Real,AbstractVector{<:Real}}, "", "Radial location of the deposition profile"; default=0.6) + par.current_efficiency = Entry(Union{Real,AbstractVector{<:Real}}, "A/W", "Current drive efficiency"; default=0.4) return par end @@ -264,15 +274,17 @@ This actor estimates the Lower-hybrid electron energy deposition and current dri """ function ActorLHsimple(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorLHsimple(kw...) - actor = ActorLHsimple(dd; par.width, par.rho_0, par.current_efficiency) + actor = ActorLHsimple(dd, par) step(actor) finalize(actor) return actor end -function ActorLHsimple(dd::IMAS.dd; width::Real=0.15, rho_0::Real=0.6, current_efficiency::Real=0.4) - n_antennas = ones(length(dd.lh_antennas.antenna)) - return ActorICsimple(dd, n_antennas .* width, n_antennas .* rho_0, n_antennas .* current_efficiency) +function ActorLHsimple(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + n_antennas = length(dd.lh_antennas.antenna) + _, width, rho_0, current_efficiency = same_length_vectors(1:n_antennas, par.width, par.rho_0, par.current_efficiency) + return ActorLHsimple(dd, par, width, rho_0, current_efficiency) end function step(actor::ActorLHsimple) diff --git a/src/actors/stresses_actors.jl b/src/actors/stresses_actors.jl index 36cbc8f58..3bf54109d 100644 --- a/src/actors/stresses_actors.jl +++ b/src/actors/stresses_actors.jl @@ -3,6 +3,7 @@ #= ============== =# Base.@kwdef mutable struct ActorStresses <: ReactorAbstractActor dd::IMAS.dd + par::ParametersActor end function ParametersActor(::Type{Val{:ActorStresses}}) @@ -15,15 +16,14 @@ end """ ActorStresses(dd::IMAS.dd, act::ParametersAllActors; kw...) -This actor estimates vertical field from PF coils and its contribution to flux swing, where -`eqt` is supposed to be the equilibrium right at the end of the rampup phase, beginning of flattop +This actor estimates vertical field from PF coils and its contribution to flux swing !!! note Stores data in `dd.solid_mechanics` """ function ActorStresses(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorStresses(kw...) - actor = ActorStresses(dd) + actor = ActorStresses(dd, par) step(actor; par.n_points) finalize(actor) if par.do_plot @@ -32,6 +32,11 @@ function ActorStresses(dd::IMAS.dd, act::ParametersAllActors; kw...) return actor end +function ActorStresses(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return ActorStresses(dd, par) +end + function step(actor::ActorStresses; n_points::Integer=5) eq = actor.dd.equilibrium bd = actor.dd.build diff --git a/src/ddinit/init_core_sources.jl b/src/ddinit/init_core_sources.jl index c036486b6..e7d6721c6 100644 --- a/src/ddinit/init_core_sources.jl +++ b/src/ddinit/init_core_sources.jl @@ -18,12 +18,12 @@ end function init_nbi( dd::IMAS.dd, - power_launched::Union{Real,Vector}, - beam_energy::Union{Real,Vector}, - beam_mass::Union{Real,Vector}, - toroidal_angle::Union{Real,Vector}, - efficiency_conversion::Union{Missing,Real,Vector}, - efficiency_transmission::Union{Missing,Real,Vector}) + power_launched::Union{Real,AbstractVector{<:Real}}, + beam_energy::Union{Real,AbstractVector{<:Real}}, + beam_mass::Union{Real,AbstractVector{<:Real}}, + toroidal_angle::Union{Real,AbstractVector{<:Real}}, + efficiency_conversion::Union{Missing,Real,AbstractVector{<:Real}}, + efficiency_transmission::Union{Missing,Real,AbstractVector{<:Real}}) power_launched, beam_energy, beam_mass, toroidal_angle, efficiency_conversion, efficiency_transmission = same_length_vectors(power_launched, beam_energy, beam_mass, toroidal_angle, efficiency_conversion, efficiency_transmission) @@ -60,9 +60,9 @@ end function init_ec_launchers( dd::IMAS.dd, - power_launched::Union{Real,Vector}, - efficiency_conversion::Union{Real,Vector,Missing}, - efficiency_transmission::Union{Real,Vector,Missing}) + power_launched::Union{Real,AbstractVector{<:Real}}, + efficiency_conversion::Union{Real,AbstractVector{<:Real},Missing}, + efficiency_transmission::Union{Real,AbstractVector{<:Real},Missing}) (power_launched, efficiency_conversion, efficiency_transmission) = same_length_vectors(power_launched, efficiency_conversion, efficiency_transmission) for idx in 1:length(power_launched) @@ -91,10 +91,10 @@ end function init_ic_antennas( dd::IMAS.dd, - power_launched::Union{Real,Vector}, - efficiency_conversion::Union{Real,Vector,Missing}, - efficiency_transmission::Union{Real,Vector,Missing}, - efficiency_coupling::Union{Real,Vector,Missing}) + power_launched::Union{Real,AbstractVector{<:Real}}, + efficiency_conversion::Union{Real,AbstractVector{<:Real},Missing}, + efficiency_transmission::Union{Real,AbstractVector{<:Real},Missing}, + efficiency_coupling::Union{Real,AbstractVector{<:Real},Missing}) (power_launched, efficiency_conversion, efficiency_transmission, efficiency_coupling) = same_length_vectors(power_launched, efficiency_conversion, efficiency_transmission, efficiency_coupling) for idx in 1:length(power_launched) @@ -124,10 +124,10 @@ end function init_lh_antennas( dd::IMAS.dd, - power_launched::Union{Real,Vector}, - efficiency_conversion::Union{Real,Vector,Missing}, - efficiency_transmission::Union{Real,Vector,Missing}, - efficiency_coupling::Union{Real,Vector,Missing}) + power_launched::Union{Real,AbstractVector{<:Real}}, + efficiency_conversion::Union{Real,AbstractVector{<:Real},Missing}, + efficiency_transmission::Union{Real,AbstractVector{<:Real},Missing}, + efficiency_coupling::Union{Real,AbstractVector{<:Real},Missing}) (power_launched, efficiency_conversion, efficiency_transmission, efficiency_coupling) = same_length_vectors(power_launched, efficiency_conversion, efficiency_transmission, efficiency_coupling) for idx in 1:length(power_launched) lha = resize!(dd.lh_antennas.antenna, idx)[idx] From 3289df2e94eec510d2dfa945ac2357709daf07d5 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Mon, 25 Jul 2022 22:44:28 -0700 Subject: [PATCH 18/74] remove Base.@kwdef from mutable struct Actor close #114 --- docs/src/develop.md | 15 ++++++++++++--- src/actors/balance_of_plant_actors.jl | 14 +++++++------- src/actors/blanket_actors.jl | 2 +- src/actors/build_actors.jl | 8 ++++---- src/actors/compound_actors.jl | 4 ++-- src/actors/costing_actors.jl | 2 +- src/actors/current_actors.jl | 4 ++-- src/actors/divertors_actors.jl | 2 +- src/actors/equilibrium_actors.jl | 6 +++--- src/actors/neutronics_actors.jl | 2 +- src/actors/pf_active_actors.jl | 2 +- src/actors/sources_actors.jl | 8 ++++---- src/actors/stresses_actors.jl | 2 +- src/actors/transport_actors.jl | 2 +- 14 files changed, 41 insertions(+), 32 deletions(-) diff --git a/docs/src/develop.md b/docs/src/develop.md index f9b83e34f..fc07381cf 100644 --- a/docs/src/develop.md +++ b/docs/src/develop.md @@ -72,12 +72,15 @@ abstract type HCDAbstractActor <: AbstractActor end abstract type PlasmaAbstractActor <: AbstractActor end ``` -The definition of each FUSE actor follows a well defined pattern: +The definition of each FUSE actor follows a well defined pattern. +**DO NOT** deviate from this pattern. This is important to ensure modularity and compostability of the actors. ```julia # Defintion of the actor structure -Base.@kwdef mutable struct ActorNAME <: ???AbstractActor +mutable struct ActorNAME <: ???AbstractActor dd::IMAS.dd + par::ParametersActor # Actors carry with them the parameters they are run with + ngrid::Integer ... end @@ -98,12 +101,18 @@ What does this actor do... """ function ActorNAME(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorNAME(kw...) - actor = ActorNAME(;dd, par...) + actor = ActorNAME(dd, par) step(actor) finalize(actor) return actor end +# define `init` function for this actor as constructor with `par` +function ActorNAME(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return ActorNAME(dd, par, par.ngrid) +end + # define `step` function for this actor function step(actor::ActorNAME; ...) ... diff --git a/src/actors/balance_of_plant_actors.jl b/src/actors/balance_of_plant_actors.jl index 447049e0d..af11eb9d7 100644 --- a/src/actors/balance_of_plant_actors.jl +++ b/src/actors/balance_of_plant_actors.jl @@ -2,7 +2,7 @@ # ActorBalanceOfPlant # #= =================== =# -Base.@kwdef mutable struct ActorBalanceOfPlant <: FacilityAbstractActor +mutable struct ActorBalanceOfPlant <: FacilityAbstractActor dd::IMAS.dd par::ParametersActor blanket_multiplier::Real @@ -34,7 +34,7 @@ Balance of plant actor that estimates the Net electrical power output by estimat function ActorBalanceOfPlant(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorBalanceOfPlant(kw...) actor = ActorBalanceOfPlant(dd, par) - step(actor, gasc_method) + step(actor) finalize(actor) return actor end @@ -44,7 +44,7 @@ function ActorBalanceOfPlant(dd::IMAS.dd, par::ParametersActor; kw...) ActorBalanceOfPlant(dd, par, par.blanket_multiplier, par.efficiency_reclaim, par.thermal_electric_conversion_efficiency) end -function step(actor::ActorBalanceOfPlant) +function step(actor::ActorBalanceOfPlant; model::Symbol=actor.par.model) dd = actor.dd bop = dd.balance_of_plant empty!(bop) @@ -80,11 +80,11 @@ function step(actor::ActorBalanceOfPlant) end ## balance of plant systems - if actor.par.model == :gasc + if model == :gasc sys = resize!(bop_electric.system, "name" => "BOP_gasc", "index" => 2) sys.power = 0.07 .* bop_thermal.power_electric_generated - elseif actor.par.model == :EU_DEMO + elseif 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) @@ -92,7 +92,7 @@ function step(actor::ActorBalanceOfPlant) sys.power = electricity(system, bop.time) end else - error("act.ActorBalanceOfPlant.model = $(actor.par.model) not recognized") + error("ActorBalanceOfPlant: model = $(model) not recognized") end return actor end @@ -153,7 +153,7 @@ end function thermal_power(::Type{Val{:blanket}}, dd::IMAS.dd, actor::ActorBalanceOfPlant, time_array::Vector{<:Real}) power_fusion, time_array_fusion = IMAS.total_power_time(dd.core_sources, [6]) - return actor.par.blanket_multiplier .* IMAS.interp1d(time_array_fusion, 4 .* power_fusion, :constant).(time_array) # blanket_multiplier * P_neutron + return actor.blanket_multiplier .* IMAS.interp1d(time_array_fusion, 4 .* power_fusion, :constant).(time_array) # blanket_multiplier * P_neutron end function thermal_power(::Type{Val{:diverters}}, dd::IMAS.dd, actor::ActorBalanceOfPlant, time_array::Vector{<:Real}) diff --git a/src/actors/blanket_actors.jl b/src/actors/blanket_actors.jl index afb3e7739..eb83d3d2e 100644 --- a/src/actors/blanket_actors.jl +++ b/src/actors/blanket_actors.jl @@ -2,7 +2,7 @@ # ActorBlanket # #= ============ =# -Base.@kwdef mutable struct ActorBlanket <: ReactorAbstractActor +mutable struct ActorBlanket <: ReactorAbstractActor dd::IMAS.dd par::ParametersActor blanket_multiplier::Real diff --git a/src/actors/build_actors.jl b/src/actors/build_actors.jl index 1e23bbfa1..b3c9b4874 100644 --- a/src/actors/build_actors.jl +++ b/src/actors/build_actors.jl @@ -127,7 +127,7 @@ end #= ========== =# # flux-swing # #= ========== =# -Base.@kwdef mutable struct ActorFluxSwing <: ReactorAbstractActor +mutable struct ActorFluxSwing <: ReactorAbstractActor dd::IMAS.dd par::ParametersActor operate_at_j_crit::Bool @@ -298,7 +298,7 @@ end #= ========== =# # LFS sizing # #= ========== =# -Base.@kwdef mutable struct ActorLFSsizing <: ReactorAbstractActor +mutable struct ActorLFSsizing <: ReactorAbstractActor dd::IMAS.dd par::ParametersActor end @@ -372,7 +372,7 @@ end #= ========== =# # HFS sizing # #= ========== =# -Base.@kwdef mutable struct ActorHFSsizing <: ReactorAbstractActor +mutable struct ActorHFSsizing <: ReactorAbstractActor dd::IMAS.dd par::ParametersActor stresses_actor::ActorStresses @@ -651,7 +651,7 @@ end #= ============= =# # cross-section # #= ============= =# -Base.@kwdef mutable struct ActorCXbuild <: ReactorAbstractActor +mutable struct ActorCXbuild <: ReactorAbstractActor dd::IMAS.dd par::ParametersActor end diff --git a/src/actors/compound_actors.jl b/src/actors/compound_actors.jl index cd51760f7..92c6b75e4 100644 --- a/src/actors/compound_actors.jl +++ b/src/actors/compound_actors.jl @@ -1,7 +1,7 @@ #= ========================= =# # ActorEquilibriumTransport # #= ========================= =# -Base.@kwdef mutable struct ActorEquilibriumTransport <: PlasmaAbstractActor +mutable struct ActorEquilibriumTransport <: PlasmaAbstractActor dd::IMAS.dd par::ParametersActor actor_jt::ActorSteadyStateCurrent @@ -87,7 +87,7 @@ end #= ================== =# # ActorWholeFacility # #= ================== =# -Base.@kwdef mutable struct ActorWholeFacility <: FacilityAbstractActor +mutable struct ActorWholeFacility <: FacilityAbstractActor dd::IMAS.dd end diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index 496f32275..554881836 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -95,7 +95,7 @@ end # ActorCosting # #= ============ =# -Base.@kwdef mutable struct ActorCosting <: FacilityAbstractActor +mutable struct ActorCosting <: FacilityAbstractActor dd::IMAS.dd par::ParametersActor end diff --git a/src/actors/current_actors.jl b/src/actors/current_actors.jl index b96091cdb..d93c0f9b4 100644 --- a/src/actors/current_actors.jl +++ b/src/actors/current_actors.jl @@ -3,7 +3,7 @@ import QED #= =============== =# # ActorQEDcurrent # #= =============== =# -Base.@kwdef mutable struct ActorQEDcurrent <: PlasmaAbstractActor +mutable struct ActorQEDcurrent <: PlasmaAbstractActor dd::IMAS.dd QI::QED.QED_state η#::Base.Callable @@ -113,7 +113,7 @@ end #= ======================= =# # ActorSteadyStateCurrent # #= ======================= =# -Base.@kwdef mutable struct ActorSteadyStateCurrent <: PlasmaAbstractActor +mutable struct ActorSteadyStateCurrent <: PlasmaAbstractActor dd::IMAS.dd par::ParametersActor end diff --git a/src/actors/divertors_actors.jl b/src/actors/divertors_actors.jl index 9309178ea..d0e1634de 100644 --- a/src/actors/divertors_actors.jl +++ b/src/actors/divertors_actors.jl @@ -2,7 +2,7 @@ # ActorDivertors # #= ============== =# -Base.@kwdef mutable struct ActorDivertors <: ReactorAbstractActor +mutable struct ActorDivertors <: ReactorAbstractActor dd::IMAS.dd par::ParametersActor thermal_power_extraction_efficiency::Real diff --git a/src/actors/equilibrium_actors.jl b/src/actors/equilibrium_actors.jl index 444c37a6f..8818ed1dc 100644 --- a/src/actors/equilibrium_actors.jl +++ b/src/actors/equilibrium_actors.jl @@ -7,7 +7,7 @@ import Optim #= ================ =# # ActorEquilibrium # #= ================ =# -Base.@kwdef mutable struct ActorEquilibrium <: PlasmaAbstractActor +mutable struct ActorEquilibrium <: PlasmaAbstractActor dd::IMAS.dd par::ParametersActor eq_actor::PlasmaAbstractActor @@ -82,7 +82,7 @@ end #= ============ =# # ActorSolovev # #= ============ =# -Base.@kwdef mutable struct ActorSolovev <: PlasmaAbstractActor +mutable struct ActorSolovev <: PlasmaAbstractActor eq::IMAS.equilibrium par::ParametersActor S::Equilibrium.SolovevEquilibrium @@ -332,7 +332,7 @@ end #= =========== =# # ActorCHEASE # #= =========== =# -Base.@kwdef mutable struct ActorCHEASE <: PlasmaAbstractActor +mutable struct ActorCHEASE <: PlasmaAbstractActor dd::IMAS.dd par::ParametersActor chease::Union{Nothing,CHEASE.Chease} diff --git a/src/actors/neutronics_actors.jl b/src/actors/neutronics_actors.jl index 5d221af8d..35bae4ca6 100644 --- a/src/actors/neutronics_actors.jl +++ b/src/actors/neutronics_actors.jl @@ -19,7 +19,7 @@ end # ActorNeutronics # #= =============== =# -Base.@kwdef mutable struct ActorNeutronics <: PlasmaAbstractActor +mutable struct ActorNeutronics <: PlasmaAbstractActor dd::IMAS.dd par::ParametersActor end diff --git a/src/actors/pf_active_actors.jl b/src/actors/pf_active_actors.jl index 986db31b0..30bd8784c 100644 --- a/src/actors/pf_active_actors.jl +++ b/src/actors/pf_active_actors.jl @@ -13,7 +13,7 @@ Base.@kwdef mutable struct PFcoilsOptTrace cost_total::Vector{Real} = Real[] end -Base.@kwdef mutable struct ActorPFcoilsOpt <: ReactorAbstractActor +mutable struct ActorPFcoilsOpt <: ReactorAbstractActor par::ParametersActor eq_in::IMAS.equilibrium eq_out::IMAS.equilibrium diff --git a/src/actors/sources_actors.jl b/src/actors/sources_actors.jl index 348bd660d..d1fda1cf6 100644 --- a/src/actors/sources_actors.jl +++ b/src/actors/sources_actors.jl @@ -3,7 +3,7 @@ import NumericalIntegration: integrate #= === =# # NBI # #= === =# -Base.@kwdef mutable struct ActorNBIsimple <: HCDAbstractActor +mutable struct ActorNBIsimple <: HCDAbstractActor dd::IMAS.dd par::ParametersActor width::AbstractVector{<:Real} @@ -90,7 +90,7 @@ end #= == =# # EC # #= == =# -Base.@kwdef mutable struct ActorECsimple <: HCDAbstractActor +mutable struct ActorECsimple <: HCDAbstractActor dd::IMAS.dd par::ParametersActor width::AbstractVector{<:Real} @@ -169,7 +169,7 @@ end #= == =# # IC # #= == =# -Base.@kwdef mutable struct ActorICsimple <: HCDAbstractActor +mutable struct ActorICsimple <: HCDAbstractActor dd::IMAS.dd par::ParametersActor width::AbstractVector{<:Real} @@ -248,7 +248,7 @@ end #= == =# # LH # #= == =# -Base.@kwdef mutable struct ActorLHsimple <: HCDAbstractActor +mutable struct ActorLHsimple <: HCDAbstractActor dd::IMAS.dd par::ParametersActor width::AbstractVector{<:Real} diff --git a/src/actors/stresses_actors.jl b/src/actors/stresses_actors.jl index 3bf54109d..3f8b22560 100644 --- a/src/actors/stresses_actors.jl +++ b/src/actors/stresses_actors.jl @@ -1,7 +1,7 @@ #= ============== =# # OH TF stresses # #= ============== =# -Base.@kwdef mutable struct ActorStresses <: ReactorAbstractActor +mutable struct ActorStresses <: ReactorAbstractActor dd::IMAS.dd par::ParametersActor end diff --git a/src/actors/transport_actors.jl b/src/actors/transport_actors.jl index 06164d5ff..c936dbd74 100644 --- a/src/actors/transport_actors.jl +++ b/src/actors/transport_actors.jl @@ -4,7 +4,7 @@ import TAUENN # TAUENN actor # #= ================ =# -Base.@kwdef mutable struct ActorTauenn <: PlasmaAbstractActor +mutable struct ActorTauenn <: PlasmaAbstractActor dd::IMAS.dd par::ParametersActor tauenn_parameters::TAUENN.TauennParameters From 02b60caa5ef7c6c1eb5e117bdac233a154ddac91 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Mon, 25 Jul 2022 23:05:40 -0700 Subject: [PATCH 19/74] use inner constructors where needed --- src/actors/build_actors.jl | 18 ++++++++---------- src/actors/costing_actors.jl | 9 ++++----- src/actors/current_actors.jl | 18 +++++++++--------- src/actors/neutronics_actors.jl | 9 ++++----- src/actors/stresses_actors.jl | 9 ++++----- 5 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/actors/build_actors.jl b/src/actors/build_actors.jl index b3c9b4874..c98ffbb00 100644 --- a/src/actors/build_actors.jl +++ b/src/actors/build_actors.jl @@ -301,6 +301,10 @@ end mutable struct ActorLFSsizing <: ReactorAbstractActor dd::IMAS.dd par::ParametersActor + function ActorLFSsizing(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return ActorLFSsizing(dd, par) + end end function ParametersActor(::Type{Val{:ActorLFSsizing}}) @@ -334,11 +338,6 @@ function ActorLFSsizing(dd::IMAS.dd, act::ParametersAllActors; kw...) return actor end -function ActorLFSsizing(dd::IMAS.dd, par::ParametersActor; kw...) - par = par(kw...) - return ActorLFSsizing(dd, par) -end - function step(actor::ActorLFSsizing; verbose::Bool=false) dd = actor.dd @@ -654,6 +653,10 @@ end mutable struct ActorCXbuild <: ReactorAbstractActor dd::IMAS.dd par::ParametersActor + function ActorCXbuild(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return ActorCXbuild(dd, par) + end end function ParametersActor(::Type{Val{:ActorCXbuild}}) @@ -683,11 +686,6 @@ function ActorCXbuild(dd::IMAS.dd, act::ParametersAllActors; kw...) return actor end -function ActorCXbuild(dd::IMAS.dd, par::ParametersActor; kw...) - par = par(kw...) - return ActorCXbuild(dd, par) -end - function step(actor::ActorCXbuild; rebuild_wall::Bool=actor.par.rebuild_wall) build_cx!(actor.dd; rebuild_wall) end diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index 554881836..37e36e48d 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -98,6 +98,10 @@ end mutable struct ActorCosting <: FacilityAbstractActor dd::IMAS.dd par::ParametersActor + function ActorCosting(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return ActorCosting(dd, par) + end end function ParametersActor(::Type{Val{:ActorCosting}}) @@ -121,11 +125,6 @@ function ActorCosting(dd::IMAS.dd, act::ParametersAllActors; kw...) return actor end -function ActorCosting(dd::IMAS.dd, par::ParametersActor; kw...) - par = par(kw...) - return ActorCosting(dd, par) -end - function step(actor::ActorCosting) dd = actor.dd cst = dd.costing diff --git a/src/actors/current_actors.jl b/src/actors/current_actors.jl index d93c0f9b4..e114e9e15 100644 --- a/src/actors/current_actors.jl +++ b/src/actors/current_actors.jl @@ -5,6 +5,7 @@ import QED #= =============== =# mutable struct ActorQEDcurrent <: PlasmaAbstractActor dd::IMAS.dd + par::ParametersActor QI::QED.QED_state η#::Base.Callable QO::Union{QED.QED_state,Missing} @@ -27,14 +28,15 @@ This actor evolves the current using QED. """ function ActorQEDcurrent(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorQEDcurrent(kw...) - actor = ActorQEDcurrent(dd) + actor = ActorQEDcurrent(dd, par) step(actor) finalize(actor) return actor end -function ActorQEDcurrent(dd::IMAS.dd) - ActorQEDcurrent(dd, from_imas(dd), η_imas(dd), missing, @ddtime(dd.equilibrium.time), 0.0) +function ActorQEDcurrent(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return ActorQEDcurrent(dd, par, from_imas(dd), η_imas(dd), missing, @ddtime(dd.equilibrium.time), dd.global_time) end function step(actor::ActorQEDcurrent, tmax, Nt, Vedge=nothing, Ip=nothing; resume=false) @@ -45,7 +47,6 @@ function step(actor::ActorQEDcurrent, tmax, Nt, Vedge=nothing, Ip=nothing; resum end actor.tmax += tmax else - actor.initial_time = @ddtime(dd.equilibrium.time) actor.tmax = tmax end actor.QO = QED.diffuse(actor.QI, actor.η, tmax, Nt; Vedge, Ip) @@ -116,6 +117,10 @@ end mutable struct ActorSteadyStateCurrent <: PlasmaAbstractActor dd::IMAS.dd par::ParametersActor + function ActorSteadyStateCurrent(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return ActorSteadyStateCurrent(dd, par) + end end function ParametersActor(::Type{Val{:ActorSteadyStateCurrent}}) @@ -141,11 +146,6 @@ function ActorSteadyStateCurrent(dd::IMAS.dd, act::ParametersAllActors; kw...) return actor end -function ActorSteadyStateCurrent(dd::IMAS.dd, par::ParametersActor; kw...) - par = par(kw...) - return ActorSteadyStateCurrent(dd, par) -end - function step(actor::ActorSteadyStateCurrent) dd = actor.dd IMAS.j_ohmic_steady_state!(dd.equilibrium.time_slice[], dd.core_profiles.profiles_1d[]) diff --git a/src/actors/neutronics_actors.jl b/src/actors/neutronics_actors.jl index 35bae4ca6..7cea1004f 100644 --- a/src/actors/neutronics_actors.jl +++ b/src/actors/neutronics_actors.jl @@ -22,6 +22,10 @@ end mutable struct ActorNeutronics <: PlasmaAbstractActor dd::IMAS.dd par::ParametersActor + function ActorNeutronics(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return ActorNeutronics(dd, par) + end end function ParametersActor(::Type{Val{:ActorNeutronics}}) @@ -48,11 +52,6 @@ function ActorNeutronics(dd::IMAS.dd, act::ParametersAllActors; kw...) return actor end -function ActorNeutronics(dd::IMAS.dd, par::ParametersActor; kw...) - par = par(kw...) - return ActorNeutronics(dd, par) -end - function step(actor::ActorNeutronics; N::Integer=actor.par.N, step=actor.par.step, do_plot::Bool=actor.par.do_plot) dd = actor.dd cp1d = dd.core_profiles.profiles_1d[] diff --git a/src/actors/stresses_actors.jl b/src/actors/stresses_actors.jl index 3f8b22560..850ab888f 100644 --- a/src/actors/stresses_actors.jl +++ b/src/actors/stresses_actors.jl @@ -4,6 +4,10 @@ mutable struct ActorStresses <: ReactorAbstractActor dd::IMAS.dd par::ParametersActor + function ActorStresses(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return ActorStresses(dd, par) + end end function ParametersActor(::Type{Val{:ActorStresses}}) @@ -32,11 +36,6 @@ function ActorStresses(dd::IMAS.dd, act::ParametersAllActors; kw...) return actor end -function ActorStresses(dd::IMAS.dd, par::ParametersActor; kw...) - par = par(kw...) - return ActorStresses(dd, par) -end - function step(actor::ActorStresses; n_points::Integer=5) eq = actor.dd.equilibrium bd = actor.dd.build From 0f558684b79c4c7bbafd8c1c863eb5572bdfeb06 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Tue, 26 Jul 2022 14:02:35 -0700 Subject: [PATCH 20/74] fix use of inner constructors --- src/actors/build_actors.jl | 6 +++--- src/actors/costing_actors.jl | 2 +- src/actors/current_actors.jl | 2 +- src/actors/neutronics_actors.jl | 2 +- src/actors/stresses_actors.jl | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/actors/build_actors.jl b/src/actors/build_actors.jl index c98ffbb00..229730e3f 100644 --- a/src/actors/build_actors.jl +++ b/src/actors/build_actors.jl @@ -303,7 +303,7 @@ mutable struct ActorLFSsizing <: ReactorAbstractActor par::ParametersActor function ActorLFSsizing(dd::IMAS.dd, par::ParametersActor; kw...) par = par(kw...) - return ActorLFSsizing(dd, par) + return new(dd, par) end end @@ -655,8 +655,8 @@ mutable struct ActorCXbuild <: ReactorAbstractActor par::ParametersActor function ActorCXbuild(dd::IMAS.dd, par::ParametersActor; kw...) par = par(kw...) - return ActorCXbuild(dd, par) - end + return new(dd, par) + end end function ParametersActor(::Type{Val{:ActorCXbuild}}) diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index 37e36e48d..71d9419f2 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -100,7 +100,7 @@ mutable struct ActorCosting <: FacilityAbstractActor par::ParametersActor function ActorCosting(dd::IMAS.dd, par::ParametersActor; kw...) par = par(kw...) - return ActorCosting(dd, par) + return new(dd, par) end end diff --git a/src/actors/current_actors.jl b/src/actors/current_actors.jl index e114e9e15..7663faaf1 100644 --- a/src/actors/current_actors.jl +++ b/src/actors/current_actors.jl @@ -119,7 +119,7 @@ mutable struct ActorSteadyStateCurrent <: PlasmaAbstractActor par::ParametersActor function ActorSteadyStateCurrent(dd::IMAS.dd, par::ParametersActor; kw...) par = par(kw...) - return ActorSteadyStateCurrent(dd, par) + return new(dd, par) end end diff --git a/src/actors/neutronics_actors.jl b/src/actors/neutronics_actors.jl index 7cea1004f..dc1fb91f4 100644 --- a/src/actors/neutronics_actors.jl +++ b/src/actors/neutronics_actors.jl @@ -24,7 +24,7 @@ mutable struct ActorNeutronics <: PlasmaAbstractActor par::ParametersActor function ActorNeutronics(dd::IMAS.dd, par::ParametersActor; kw...) par = par(kw...) - return ActorNeutronics(dd, par) + return new(dd, par) end end diff --git a/src/actors/stresses_actors.jl b/src/actors/stresses_actors.jl index 850ab888f..f2d343f18 100644 --- a/src/actors/stresses_actors.jl +++ b/src/actors/stresses_actors.jl @@ -6,7 +6,7 @@ mutable struct ActorStresses <: ReactorAbstractActor par::ParametersActor function ActorStresses(dd::IMAS.dd, par::ParametersActor; kw...) par = par(kw...) - return ActorStresses(dd, par) + return new(dd, par) end end @@ -73,7 +73,7 @@ function step(actor::ActorStresses; n_points::Integer=5) f_struct_oh=f_struct_oh, f_struct_pl=1.0, n_points=n_points, - verbose=false, + verbose=false ) end From 4f2fb641823c15b8e6368ad5c6b7f3b37dc97e20 Mon Sep 17 00:00:00 2001 From: TimSlendebroek <32385057+TimSlendebroek@users.noreply.github.com> Date: Tue, 26 Jul 2022 14:44:44 -0700 Subject: [PATCH 21/74] not using ini.equilibrium in init_core_profiles --- src/ddinit/init_core_profiles.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ddinit/init_core_profiles.jl b/src/ddinit/init_core_profiles.jl index 6cd55fc0d..6d54bfb62 100644 --- a/src/ddinit/init_core_profiles.jl +++ b/src/ddinit/init_core_profiles.jl @@ -24,7 +24,7 @@ function init_core_profiles(dd::IMAS.dd, ini::ParametersAllInits, act::Parameter dd.equilibrium, dd.summary; ne_ped=ini.core_profiles.ne_ped, - pressure_core=ini.equilibrium.pressure_core, + pressure_core=dd.equilibrium.time_slice[].profiles_1d.pressure[1], greenwald_fraction=ini.core_profiles.greenwald_fraction, helium_fraction=ini.core_profiles.helium_fraction, T_shaping=ini.core_profiles.T_shaping, From b3ad5a2f53e63c4c53dfa6faaa7c755fd77dc626 Mon Sep 17 00:00:00 2001 From: TimSlendebroek <32385057+TimSlendebroek@users.noreply.github.com> Date: Tue, 26 Jul 2022 14:57:06 -0700 Subject: [PATCH 22/74] function in IMAS --- cases/HDB5.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cases/HDB5.jl b/cases/HDB5.jl index 03aaf689c..3b313092b 100644 --- a/cases/HDB5.jl +++ b/cases/HDB5.jl @@ -31,7 +31,7 @@ function case_parameters(data_row::DataFrames.DataFrameRow) ini.equilibrium.κ = data_row[:KAPPA] ini.equilibrium.δ = data_row[:DELTA] ini.equilibrium.ip = data_row[:IP] - ini.equilibrium.pressure_core = pressure_avg_from_beta_n(1.0, data_row[:AMIN], data_row[:BT], data_row[:IP]) * 3.0 + ini.equilibrium.pressure_core = IMAS.pressure_avg_from_beta_n(1.0, data_row[:AMIN], data_row[:BT], data_row[:IP]) * 3.0 act.ActorSolovev.area = data_row[:AREA] act.ActorSolovev.volume = data_row[:VOL] From 895a207e1294cbcb785ed796974065463d5a3601 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Thu, 28 Jul 2022 13:37:31 -0700 Subject: [PATCH 23/74] Solovev step returns actor --- src/actors/equilibrium_actors.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actors/equilibrium_actors.jl b/src/actors/equilibrium_actors.jl index 8818ed1dc..f37fd21a7 100644 --- a/src/actors/equilibrium_actors.jl +++ b/src/actors/equilibrium_actors.jl @@ -195,7 +195,7 @@ function step(actor::ActorSolovev) actor.S = Equilibrium.solovev(abs(B0), R0, epsilon, delta, kappa, res.minimizer[1], res.minimizer[2], B0_dir=sign(B0), Ip_dir=1, symmetric=S0.symmetric, x_point=S0.x_point) - return res + return actor end """ From 12091ca5b30f1e2033636a5e3c98134c9f7f6a97 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Thu, 28 Jul 2022 22:03:08 -0700 Subject: [PATCH 24/74] split Equilibrium CHEASE Solovev actors --- src/actors/chease_actor.jl | 156 +++++++++++++ src/actors/equilibrium_actors.jl | 367 +------------------------------ src/actors/solovev_actor.jl | 216 ++++++++++++++++++ 3 files changed, 374 insertions(+), 365 deletions(-) create mode 100644 src/actors/chease_actor.jl create mode 100644 src/actors/solovev_actor.jl diff --git a/src/actors/chease_actor.jl b/src/actors/chease_actor.jl new file mode 100644 index 000000000..47941b26f --- /dev/null +++ b/src/actors/chease_actor.jl @@ -0,0 +1,156 @@ +import CHEASE + +#= =========== =# +# ActorCHEASE # +#= =========== =# +mutable struct ActorCHEASE <: PlasmaAbstractActor + dd::IMAS.dd + par::ParametersActor + chease::Union{Nothing,CHEASE.Chease} +end + +function ParametersActor(::Type{Val{:ActorCHEASE}}) + par = ParametersActor(nothing) + par.free_boundary = Entry(Bool, "", "Convert fixed boundary equilibrium to free boundary one"; default=true) + par.clear_workdir = Entry(Bool, "", "Clean the temporary workdir for CHEASE"; default=true) + par.rescale_eq_to_ip = Entry(Bool, "", "Scale equilibrium to match Ip"; default=false) + return par +end + +""" + ActorCHEASE(dd::IMAS.dd, act::ParametersAllActors; kw...) + +This actor runs the Fixed boundary equilibrium solver CHEASE""" +function ActorCHEASE(dd::IMAS.dd, act::ParametersAllActors; kw...) + par = act.ActorCHEASE(kw...) + actor = ActorCHEASE(dd, par) + step(actor) + finalize(actor) + return actor +end + +function ActorCHEASE(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + ActorCHEASE(dd, par, nothing) +end + +""" + prepare(dd::IMAS.dd, :ActorCHEASE, act::ParametersAllActors; kw...) + +Prepare dd to run ActorCHEASE +* Copy pressure from core_profiles to equilibrium +* Copy j_parallel from core_profiles to equilibrium +""" +function prepare(dd::IMAS.dd, ::Type{Val{:ActorCHEASE}}, act::ParametersAllActors; kw...) + eq1d = dd.equilibrium.time_slice[].profiles_1d + cp1d = dd.core_profiles.profiles_1d[] + eq1d.j_tor = IMAS.interp1d(cp1d.grid.psi_norm, cp1d.j_tor).(eq1d.psi_norm) + eq1d.pressure = IMAS.interp1d(cp1d.grid.psi_norm, cp1d.pressure).(eq1d.psi_norm) +end + +""" + step(actor::ActorCHEASE) + +Runs CHEASE on the r_z boundary, equilibrium pressure and equilibrium j_tor +""" +function step(actor::ActorCHEASE) + dd = actor.dd + eqt = dd.equilibrium.time_slice[] + eq1d = eqt.profiles_1d + + # remove points at high curvature points (ie. X-points) + r_bound = eqt.boundary.outline.r + z_bound = eqt.boundary.outline.z + r_bound, z_bound = IMAS.resample_2d_line(r_bound, z_bound; n_points=201) + index = abs.(IMAS.curvature(r_bound, z_bound)) .< 0.9 + r_bound = r_bound[index] + z_bound = z_bound[index] + + # scalars + Ip = eqt.global_quantities.ip + Bt_center = @ddtime(dd.equilibrium.vacuum_toroidal_field.b0) + r_center = dd.equilibrium.vacuum_toroidal_field.r0 + r_geo = eqt.boundary.geometric_axis.r + z_geo = eqt.boundary.geometric_axis.z + Bt_geo = Bt_center * r_center / r_geo + ϵ = eqt.boundary.minor_radius / r_geo + + # pressure and j_tor + psin = eq1d.psi_norm + j_tor = eq1d.j_tor + pressure = eq1d.pressure + rho_pol = sqrt.(psin) + pressure_sep = pressure[end] + + # run and handle errors + try + actor.chease = CHEASE.run_chease( + ϵ, z_geo, pressure_sep, Bt_geo, + r_geo, Ip, r_bound, z_bound, 82, + rho_pol, pressure, j_tor, + rescale_eq_to_ip=actor.par.rescale_eq_to_ip, + clear_workdir=actor.par.clear_workdir) + catch + display(plot(r_bound, z_bound; marker=:dot, aspect_ratio=:equal)) + display(plot(psin, pressure)) + display(plot(psin, abs.(j_tor))) + rethrow() + end + + # convert from fixed to free boundary equilibrium + if actor.par.free_boundary + EQ = Equilibrium.efit(actor.chease.gfile, 1) + psi_free_rz = VacuumFields.fixed2free(EQ, actor.chease.gfile.nbbbs) + actor.chease.gfile.psirz = psi_free_rz + EQ = Equilibrium.efit(actor.chease.gfile, 1) + psi_b = Equilibrium.psi_boundary(EQ; r=EQ.r, z=EQ.z) + psi_a = EQ.psi_rz(EQ.axis...) + actor.chease.gfile.psirz = (psi_free_rz .- psi_a) * ((actor.chease.gfile.psi[end] - actor.chease.gfile.psi[1]) / (psi_b - psi_a)) .+ actor.chease.gfile.psi[1] + end + + return actor +end + +# define `finalize` function for this actor +function finalize(actor::ActorCHEASE) + gEQDSK2IMAS(actor.chease.gfile, actor.dd.equilibrium) + return actor +end + +""" + gEQDSK2IMAS(GEQDSKFile::GEQDSKFile,eq::IMAS.equilibrium) + +Convert IMAS.equilibrium__time_slice to Equilibrium.jl EFIT structure +""" +function gEQDSK2IMAS(g::EFIT.GEQDSKFile, eq::IMAS.equilibrium) + + tc = transform_cocos(1, 11) # chease output is cocos 1 , dd is cocos 11 + + eqt = eq.time_slice[] + eq1d = eqt.profiles_1d + resize!(eqt.profiles_2d, 1) + eq2d = eqt.profiles_2d[1] + + @ddtime(eq.vacuum_toroidal_field.b0 = g.bcentr) + eq.vacuum_toroidal_field.r0 = g.rcentr + + eqt.global_quantities.magnetic_axis.r = g.rmaxis + eqt.boundary.geometric_axis.r = g.rcentr + eqt.boundary.geometric_axis.z = g.zmid + eqt.global_quantities.magnetic_axis.z = g.zmaxis + eqt.global_quantities.ip = g.current + + eq1d.psi = g.psi .* tc["PSI"] + eq1d.q = g.qpsi + eq1d.pressure = g.pres + eq1d.dpressure_dpsi = g.pprime .* tc["PPRIME"] + eq1d.f = g.fpol .* tc["F"] + eq1d.f_df_dpsi = g.ffprim .* tc["F_FPRIME"] + + eq2d.grid_type.index = 1 + eq2d.grid.dim1 = g.r + eq2d.grid.dim2 = g.z + eq2d.psi = g.psirz .* tc["PSI"] + + IMAS.flux_surfaces(eqt) +end \ No newline at end of file diff --git a/src/actors/equilibrium_actors.jl b/src/actors/equilibrium_actors.jl index f37fd21a7..4d55686f9 100644 --- a/src/actors/equilibrium_actors.jl +++ b/src/actors/equilibrium_actors.jl @@ -1,9 +1,3 @@ -import Equilibrium -import EFIT -import CHEASE -import ForwardDiff -import Optim - #= ================ =# # ActorEquilibrium # #= ================ =# @@ -82,366 +76,9 @@ end #= ============ =# # ActorSolovev # #= ============ =# -mutable struct ActorSolovev <: PlasmaAbstractActor - eq::IMAS.equilibrium - par::ParametersActor - S::Equilibrium.SolovevEquilibrium -end - -function ParametersActor(::Type{Val{:ActorSolovev}}) - par = ParametersActor(nothing) - par.ngrid = Entry(Integer, "", "Grid size (for R, Z follows proportionally to plasma elongation)"; default=129) - par.qstar = Entry(Real, "", "Initial guess of kink safety factor"; default=1.5) - par.alpha = Entry(Real, "", "Initial guess of constant relating to pressure"; default=0.0) - par.volume = Entry(Real, "m³", "Scalar volume to match (optional)"; default=missing) - par.area = Entry(Real, "m²", "Scalar area to match (optional)"; default=missing) - par.verbose = Entry(Bool, "", "verbose"; default=false) - return par -end - -""" - ActorSolovev(dd::IMAS.dd, act::ParametersAllActors; kw...) - -Solovev equilibrium actor, based on: -“One size fits all” analytic solutions to the Grad–Shafranov equation -Phys. Plasmas 17, 032502 (2010); https://doi.org/10.1063/1.3328818 -""" -function ActorSolovev(dd::IMAS.dd, act::ParametersAllActors; kw...) - par = act.ActorSolovev(kw...) - actor = ActorSolovev(dd, par) - step(actor) - finalize(actor) - # record optimized values of qstar and alpha in `act` for subsequent ActorSolovev calls - par.qstar = actor.S.qstar - par.alpha = actor.S.alpha - return actor -end - -function ActorSolovev(dd::IMAS.dd, par::ParametersActor; kw...) - par = par(kw...) - - # extract info from dd - eq = dd.equilibrium - eqt = eq.time_slice[] - a = eqt.boundary.minor_radius - R0 = eqt.boundary.geometric_axis.r - κ = eqt.boundary.elongation - δ = eqt.boundary.triangularity - ϵ = a / R0 - B0 = @ddtime eq.vacuum_toroidal_field.b0 - - # check number of x_points to infer symmetry - if mod(length(eqt.boundary.x_point), 2) == 0 - symmetric = true - else - symmetric = false - end - - # add x_point info - if length(eqt.boundary.x_point) > 0 - x_point = (eqt.boundary.x_point[1].r, -abs(eqt.boundary.x_point[1].z)) - else - x_point = nothing - end - - # run Solovev - S = Equilibrium.solovev(abs(B0), R0, ϵ, δ, κ, par.alpha, par.qstar, B0_dir=Int64(sign(B0)), Ip_dir=1, x_point=x_point, symmetric=symmetric) - - return ActorSolovev(dd.equilibrium, par, S) -end - -""" - prepare(dd::IMAS.dd, :ActorSolovev, act::ParametersAllActors; kw...) - -Prepare dd to run ActorSolovev -* Copy pressure from core_profiles to equilibrium -""" -function prepare(dd::IMAS.dd, ::Type{Val{:ActorSolovev}}, act::ParametersAllActors; kw...) - eq1d = dd.equilibrium.time_slice[].profiles_1d - cp1d = dd.core_profiles.profiles_1d[] - eq1d.pressure = IMAS.interp1d(cp1d.grid.psi_norm, cp1d.pressure).(eq1d.psi_norm) -end - -""" - step(actor::ActorSolovev) - -Non-linear optimization to obtain a target `ip` and `pressure_core` -""" -function step(actor::ActorSolovev) - S0 = actor.S - par = actor.par - - eqt = actor.eq.time_slice[] - target_ip = abs(eqt.global_quantities.ip) - target_pressure_core = eqt.profiles_1d.pressure[1] - - B0, R0, epsilon, delta, kappa, alpha, qstar, target_ip, target_pressure_core = promote(S0.B0, S0.R0, S0.epsilon, S0.delta, S0.kappa, S0.alpha, S0.qstar, target_ip, target_pressure_core) - - function cost(x) - # NOTE: Ip/pressure calculation is very much off in Equilibrium.jl for diverted plasmas because boundary calculation is wrong - S = Equilibrium.solovev(abs(B0), R0, epsilon, delta, kappa, x[1], x[2], B0_dir=sign(B0), Ip_dir=1, symmetric=true, x_point=nothing) - psimag, psibry = Equilibrium.psi_limits(S) - pressure_cost = (Equilibrium.pressure(S, psimag) - target_pressure_core) / target_pressure_core - ip_cost = (Equilibrium.plasma_current(S) - target_ip) / target_ip - c = pressure_cost^2 + ip_cost^2 - return c - end - - res = Optim.optimize(cost, [alpha, qstar], Optim.NelderMead(), Optim.Options(g_tol=1E-3)) - - if par.verbose - println(res) - end - - actor.S = Equilibrium.solovev(abs(B0), R0, epsilon, delta, kappa, res.minimizer[1], res.minimizer[2], B0_dir=sign(B0), Ip_dir=1, symmetric=S0.symmetric, x_point=S0.x_point) - - return actor -end - -""" - finalize( - actor::ActorSolovev; - rlims::NTuple{2,<:Real}=(maximum([actor.S.R0 * (1 - actor.S.epsilon * 2), 0.0]), actor.S.R0 * (1 + actor.S.epsilon * 2)), - zlims::NTuple{2,<:Real}=(-actor.S.R0 * actor.S.epsilon * actor.S.kappa * 1.7, actor.S.R0 * actor.S.epsilon * actor.S.kappa * 1.7) - )::IMAS.equilibrium__time_slice - -Store ActorSolovev data in IMAS.equilibrium format -""" -function finalize( - actor::ActorSolovev; - rlims::NTuple{2,<:Real}=(maximum([actor.S.R0 * (1 - actor.S.epsilon * 2), 0.0]), actor.S.R0 * (1 + actor.S.epsilon * 2)), - zlims::NTuple{2,<:Real}=(-actor.S.R0 * actor.S.epsilon * actor.S.kappa * 1.7, actor.S.R0 * actor.S.epsilon * actor.S.kappa * 1.7) -)::IMAS.equilibrium__time_slice - - ngrid = actor.par.ngrid - tc = transform_cocos(3, 11) - - eq = actor.eq - eqt = eq.time_slice[] - ip = eqt.global_quantities.ip - sign_Ip = sign(ip) - sign_Bt = sign(eqt.profiles_1d.f[end]) - - Z0 = eqt.boundary.geometric_axis.z - flip_z = 1.0 - if mod(length(eqt.boundary.x_point), 2) == 1 && eqt.boundary.x_point[1].z > Z0 - flip_z = -1.0 - end - - eq.vacuum_toroidal_field.r0 = actor.S.R0 - @ddtime eq.vacuum_toroidal_field.b0 = actor.S.B0 * sign_Bt - - empty!(eqt) - - eqt.global_quantities.ip = ip - eqt.boundary.geometric_axis.r = actor.S.R0 - eqt.boundary.geometric_axis.z = Z0 - orig_psi = collect(range(Equilibrium.psi_limits(actor.S)..., length=ngrid)) - eqt.profiles_1d.psi = orig_psi * (tc["PSI"] * sign_Ip) - - eqt.profiles_1d.pressure = Equilibrium.pressure(actor.S, orig_psi) - eqt.profiles_1d.dpressure_dpsi = Equilibrium.pressure_gradient(actor.S, orig_psi) / (tc["PSI"] * sign_Ip) - - eqt.profiles_1d.f = Equilibrium.poloidal_current(actor.S, orig_psi) * (tc["F"] * sign_Bt) - eqt.profiles_1d.f_df_dpsi = Equilibrium.poloidal_current(actor.S, orig_psi) .* Equilibrium.poloidal_current_gradient(actor.S, orig_psi) * (tc["F_FPRIME"] * sign_Bt * sign_Ip) - - resize!(eqt.profiles_2d, 1) - eqt.profiles_2d[1].grid_type.index = 1 - eqt.profiles_2d[1].grid.dim1 = range(rlims[1], rlims[2], length=ngrid) - eqt.profiles_2d[1].grid.dim2 = range(zlims[1] + Z0, zlims[2] + Z0, length=Int(ceil(ngrid * actor.S.kappa))) - - eqt.profiles_2d[1].psi = [actor.S(rr, flip_z * (zz - Z0)) * (tc["PSI"] * sign_Ip) for rr in eqt.profiles_2d[1].grid.dim1, zz in eqt.profiles_2d[1].grid.dim2] - IMAS.flux_surfaces(eqt) - - # correct equilibrium volume and area - if !ismissing(actor.par, :volume) - eqt.profiles_1d.volume .*= actor.par.volume / eqt.profiles_1d.volume[end] - end - if !ismissing(actor.par, :area) - eqt.profiles_1d.area .*= actor.par.area / eqt.profiles_1d.area[end] - end - - return eqt -end - -""" - IMAS2Equilibrium(eqt::IMAS.equilibrium__time_slice) - -Convert IMAS.equilibrium__time_slice to Equilibrium.jl EFIT structure -""" -function IMAS2Equilibrium(eqt::IMAS.equilibrium__time_slice) - dim1 = range(eqt.profiles_2d[1].grid.dim1[1], eqt.profiles_2d[1].grid.dim1[end], length=length(eqt.profiles_2d[1].grid.dim1)) - @assert collect(dim1) ≈ eqt.profiles_2d[1].grid.dim1 - dim2 = range(eqt.profiles_2d[1].grid.dim2[1], eqt.profiles_2d[1].grid.dim2[end], length=length(eqt.profiles_2d[1].grid.dim2)) - @assert collect(dim2) ≈ eqt.profiles_2d[1].grid.dim2 - psi = range(eqt.profiles_1d.psi[1], eqt.profiles_1d.psi[end], length=length(eqt.profiles_1d.psi)) - @assert collect(psi) ≈ eqt.profiles_1d.psi - - Equilibrium.efit(Equilibrium.cocos(11), # COCOS - dim1, # Radius/R range - dim2, # Elevation/Z range - psi, # Polodial Flux range (polodial flux from magnetic axis) - eqt.profiles_2d[1].psi, # Polodial Flux on RZ grid (polodial flux from magnetic axis) - eqt.profiles_1d.f, # Polodial Current - eqt.profiles_1d.pressure, # Plasma pressure - eqt.profiles_1d.q, # Q profile - eqt.profiles_1d.psi .* 0, # Electric Potential - (eqt.global_quantities.magnetic_axis.r, eqt.global_quantities.magnetic_axis.z), # Magnetic Axis (raxis,zaxis) - Int(sign(eqt.profiles_1d.f[end]) * sign(eqt.global_quantities.ip)) # sign(dot(J,B)) - ) -end - -""" - gEQDSK2IMAS(GEQDSKFile::GEQDSKFile,eq::IMAS.equilibrium) - -Convert IMAS.equilibrium__time_slice to Equilibrium.jl EFIT structure -""" -function gEQDSK2IMAS(g::EFIT.GEQDSKFile, eq::IMAS.equilibrium) - - tc = transform_cocos(1, 11) # chease output is cocos 1 , dd is cocos 11 - - eqt = eq.time_slice[] - eq1d = eqt.profiles_1d - resize!(eqt.profiles_2d, 1) - eq2d = eqt.profiles_2d[1] - - @ddtime(eq.vacuum_toroidal_field.b0 = g.bcentr) - eq.vacuum_toroidal_field.r0 = g.rcentr - - eqt.global_quantities.magnetic_axis.r = g.rmaxis - eqt.boundary.geometric_axis.r = g.rcentr - eqt.boundary.geometric_axis.z = g.zmid - eqt.global_quantities.magnetic_axis.z = g.zmaxis - eqt.global_quantities.ip = g.current - - eq1d.psi = g.psi .* tc["PSI"] - eq1d.q = g.qpsi - eq1d.pressure = g.pres - eq1d.dpressure_dpsi = g.pprime .* tc["PPRIME"] - eq1d.f = g.fpol .* tc["F"] - eq1d.f_df_dpsi = g.ffprim .* tc["F_FPRIME"] - - eq2d.grid_type.index = 1 - eq2d.grid.dim1 = g.r - eq2d.grid.dim2 = g.z - eq2d.psi = g.psirz .* tc["PSI"] - - IMAS.flux_surfaces(eqt) -end +include("solovev_actor.jl") #= =========== =# # ActorCHEASE # #= =========== =# -mutable struct ActorCHEASE <: PlasmaAbstractActor - dd::IMAS.dd - par::ParametersActor - chease::Union{Nothing,CHEASE.Chease} -end - -function ParametersActor(::Type{Val{:ActorCHEASE}}) - par = ParametersActor(nothing) - par.free_boundary = Entry(Bool, "", "Convert fixed boundary equilibrium to free boundary one"; default=true) - par.clear_workdir = Entry(Bool, "", "Clean the temporary workdir for CHEASE"; default=true) - par.rescale_eq_to_ip = Entry(Bool, "", "Scale equilibrium to match Ip"; default=false) - return par -end - -""" - ActorCHEASE(dd::IMAS.dd, act::ParametersAllActors; kw...) - -This actor runs the Fixed boundary equilibrium solver CHEASE""" -function ActorCHEASE(dd::IMAS.dd, act::ParametersAllActors; kw...) - par = act.ActorCHEASE(kw...) - actor = ActorCHEASE(dd, par) - step(actor) - finalize(actor) - return actor -end - -function ActorCHEASE(dd::IMAS.dd, par::ParametersActor; kw...) - par = par(kw...) - ActorCHEASE(dd, par, nothing) -end - -""" - prepare(dd::IMAS.dd, :ActorCHEASE, act::ParametersAllActors; kw...) - -Prepare dd to run ActorCHEASE -* Copy pressure from core_profiles to equilibrium -* Copy j_parallel from core_profiles to equilibrium -""" -function prepare(dd::IMAS.dd, ::Type{Val{:ActorCHEASE}}, act::ParametersAllActors; kw...) - eq1d = dd.equilibrium.time_slice[].profiles_1d - cp1d = dd.core_profiles.profiles_1d[] - eq1d.j_tor = IMAS.interp1d(cp1d.grid.psi_norm, cp1d.j_tor).(eq1d.psi_norm) - eq1d.pressure = IMAS.interp1d(cp1d.grid.psi_norm, cp1d.pressure).(eq1d.psi_norm) -end - -""" - step(actor::ActorCHEASE) - -Runs CHEASE on the r_z boundary, equilibrium pressure and equilibrium j_tor -""" -function step(actor::ActorCHEASE) - dd = actor.dd - eqt = dd.equilibrium.time_slice[] - eq1d = eqt.profiles_1d - - # remove points at high curvature points (ie. X-points) - r_bound = eqt.boundary.outline.r - z_bound = eqt.boundary.outline.z - r_bound, z_bound = IMAS.resample_2d_line(r_bound, z_bound; n_points=201) - index = abs.(IMAS.curvature(r_bound, z_bound)) .< 0.9 - r_bound = r_bound[index] - z_bound = z_bound[index] - - # scalars - Ip = eqt.global_quantities.ip - Bt_center = @ddtime(dd.equilibrium.vacuum_toroidal_field.b0) - r_center = dd.equilibrium.vacuum_toroidal_field.r0 - r_geo = eqt.boundary.geometric_axis.r - z_geo = eqt.boundary.geometric_axis.z - Bt_geo = Bt_center * r_center / r_geo - ϵ = eqt.boundary.minor_radius / r_geo - - # pressure and j_tor - psin = eq1d.psi_norm - j_tor = eq1d.j_tor - pressure = eq1d.pressure - rho_pol = sqrt.(psin) - pressure_sep = pressure[end] - - # run and handle errors - try - actor.chease = CHEASE.run_chease( - ϵ, z_geo, pressure_sep, Bt_geo, - r_geo, Ip, r_bound, z_bound, 82, - rho_pol, pressure, j_tor, - rescale_eq_to_ip=actor.par.rescale_eq_to_ip, - clear_workdir=actor.par.clear_workdir) - catch - display(plot(r_bound, z_bound; marker=:dot, aspect_ratio=:equal)) - display(plot(psin, pressure)) - display(plot(psin, abs.(j_tor))) - rethrow() - end - - # convert from fixed to free boundary equilibrium - if actor.par.free_boundary - EQ = Equilibrium.efit(actor.chease.gfile, 1) - psi_free_rz = VacuumFields.fixed2free(EQ, actor.chease.gfile.nbbbs) - actor.chease.gfile.psirz = psi_free_rz - EQ = Equilibrium.efit(actor.chease.gfile, 1) - psi_b = Equilibrium.psi_boundary(EQ; r=EQ.r, z=EQ.z) - psi_a = EQ.psi_rz(EQ.axis...) - actor.chease.gfile.psirz = (psi_free_rz .- psi_a) * ((actor.chease.gfile.psi[end] - actor.chease.gfile.psi[1]) / (psi_b - psi_a)) .+ actor.chease.gfile.psi[1] - end - - return actor -end - -# define `finalize` function for this actor -function finalize(actor::ActorCHEASE) - gEQDSK2IMAS(actor.chease.gfile, actor.dd.equilibrium) - return actor -end \ No newline at end of file +include("chease_actor.jl") \ No newline at end of file diff --git a/src/actors/solovev_actor.jl b/src/actors/solovev_actor.jl new file mode 100644 index 000000000..142c09463 --- /dev/null +++ b/src/actors/solovev_actor.jl @@ -0,0 +1,216 @@ +import Equilibrium +import EFIT +import ForwardDiff +import Optim + +#= ============ =# +# ActorSolovev # +#= ============ =# +mutable struct ActorSolovev <: PlasmaAbstractActor + eq::IMAS.equilibrium + par::ParametersActor + S::Equilibrium.SolovevEquilibrium +end + +function ParametersActor(::Type{Val{:ActorSolovev}}) + par = ParametersActor(nothing) + par.ngrid = Entry(Integer, "", "Grid size (for R, Z follows proportionally to plasma elongation)"; default=129) + par.qstar = Entry(Real, "", "Initial guess of kink safety factor"; default=1.5) + par.alpha = Entry(Real, "", "Initial guess of constant relating to pressure"; default=0.0) + par.volume = Entry(Real, "m³", "Scalar volume to match (optional)"; default=missing) + par.area = Entry(Real, "m²", "Scalar area to match (optional)"; default=missing) + par.verbose = Entry(Bool, "", "verbose"; default=false) + return par +end + +""" + ActorSolovev(dd::IMAS.dd, act::ParametersAllActors; kw...) + +Solovev equilibrium actor, based on: +“One size fits all” analytic solutions to the Grad–Shafranov equation +Phys. Plasmas 17, 032502 (2010); https://doi.org/10.1063/1.3328818 +""" +function ActorSolovev(dd::IMAS.dd, act::ParametersAllActors; kw...) + par = act.ActorSolovev(kw...) + actor = ActorSolovev(dd, par) + step(actor) + finalize(actor) + # record optimized values of qstar and alpha in `act` for subsequent ActorSolovev calls + par.qstar = actor.S.qstar + par.alpha = actor.S.alpha + return actor +end + +function ActorSolovev(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + + # extract info from dd + eq = dd.equilibrium + eqt = eq.time_slice[] + a = eqt.boundary.minor_radius + R0 = eqt.boundary.geometric_axis.r + κ = eqt.boundary.elongation + δ = eqt.boundary.triangularity + ϵ = a / R0 + B0 = @ddtime eq.vacuum_toroidal_field.b0 + + # check number of x_points to infer symmetry + if mod(length(eqt.boundary.x_point), 2) == 0 + symmetric = true + else + symmetric = false + end + + # add x_point info + if length(eqt.boundary.x_point) > 0 + x_point = (eqt.boundary.x_point[1].r, -abs(eqt.boundary.x_point[1].z)) + else + x_point = nothing + end + + # run Solovev + S = Equilibrium.solovev(abs(B0), R0, ϵ, δ, κ, par.alpha, par.qstar, B0_dir=Int64(sign(B0)), Ip_dir=1, x_point=x_point, symmetric=symmetric) + + return ActorSolovev(dd.equilibrium, par, S) +end + +""" + prepare(dd::IMAS.dd, :ActorSolovev, act::ParametersAllActors; kw...) + +Prepare dd to run ActorSolovev +* Copy pressure from core_profiles to equilibrium +""" +function prepare(dd::IMAS.dd, ::Type{Val{:ActorSolovev}}, act::ParametersAllActors; kw...) + eq1d = dd.equilibrium.time_slice[].profiles_1d + cp1d = dd.core_profiles.profiles_1d[] + eq1d.pressure = IMAS.interp1d(cp1d.grid.psi_norm, cp1d.pressure).(eq1d.psi_norm) +end + +""" + step(actor::ActorSolovev) + +Non-linear optimization to obtain a target `ip` and `pressure_core` +""" +function step(actor::ActorSolovev) + S0 = actor.S + par = actor.par + + eqt = actor.eq.time_slice[] + target_ip = abs(eqt.global_quantities.ip) + target_pressure_core = eqt.profiles_1d.pressure[1] + + B0, R0, epsilon, delta, kappa, alpha, qstar, target_ip, target_pressure_core = promote(S0.B0, S0.R0, S0.epsilon, S0.delta, S0.kappa, S0.alpha, S0.qstar, target_ip, target_pressure_core) + + function cost(x) + # NOTE: Ip/pressure calculation is very much off in Equilibrium.jl for diverted plasmas because boundary calculation is wrong + S = Equilibrium.solovev(abs(B0), R0, epsilon, delta, kappa, x[1], x[2], B0_dir=sign(B0), Ip_dir=1, symmetric=true, x_point=nothing) + psimag, psibry = Equilibrium.psi_limits(S) + pressure_cost = (Equilibrium.pressure(S, psimag) - target_pressure_core) / target_pressure_core + ip_cost = (Equilibrium.plasma_current(S) - target_ip) / target_ip + c = pressure_cost^2 + ip_cost^2 + return c + end + + res = Optim.optimize(cost, [alpha, qstar], Optim.NelderMead(), Optim.Options(g_tol=1E-3)) + + if par.verbose + println(res) + end + + actor.S = Equilibrium.solovev(abs(B0), R0, epsilon, delta, kappa, res.minimizer[1], res.minimizer[2], B0_dir=sign(B0), Ip_dir=1, symmetric=S0.symmetric, x_point=S0.x_point) + + return actor +end + +""" + finalize( + actor::ActorSolovev; + rlims::NTuple{2,<:Real}=(maximum([actor.S.R0 * (1 - actor.S.epsilon * 2), 0.0]), actor.S.R0 * (1 + actor.S.epsilon * 2)), + zlims::NTuple{2,<:Real}=(-actor.S.R0 * actor.S.epsilon * actor.S.kappa * 1.7, actor.S.R0 * actor.S.epsilon * actor.S.kappa * 1.7) + )::IMAS.equilibrium__time_slice + +Store ActorSolovev data in IMAS.equilibrium format +""" +function finalize( + actor::ActorSolovev; + rlims::NTuple{2,<:Real}=(maximum([actor.S.R0 * (1 - actor.S.epsilon * 2), 0.0]), actor.S.R0 * (1 + actor.S.epsilon * 2)), + zlims::NTuple{2,<:Real}=(-actor.S.R0 * actor.S.epsilon * actor.S.kappa * 1.7, actor.S.R0 * actor.S.epsilon * actor.S.kappa * 1.7) +)::IMAS.equilibrium__time_slice + + ngrid = actor.par.ngrid + tc = transform_cocos(3, 11) + + eq = actor.eq + eqt = eq.time_slice[] + ip = eqt.global_quantities.ip + sign_Ip = sign(ip) + sign_Bt = sign(eqt.profiles_1d.f[end]) + + Z0 = eqt.boundary.geometric_axis.z + flip_z = 1.0 + if mod(length(eqt.boundary.x_point), 2) == 1 && eqt.boundary.x_point[1].z > Z0 + flip_z = -1.0 + end + + eq.vacuum_toroidal_field.r0 = actor.S.R0 + @ddtime eq.vacuum_toroidal_field.b0 = actor.S.B0 * sign_Bt + + empty!(eqt) + + eqt.global_quantities.ip = ip + eqt.boundary.geometric_axis.r = actor.S.R0 + eqt.boundary.geometric_axis.z = Z0 + orig_psi = collect(range(Equilibrium.psi_limits(actor.S)..., length=ngrid)) + eqt.profiles_1d.psi = orig_psi * (tc["PSI"] * sign_Ip) + + eqt.profiles_1d.pressure = Equilibrium.pressure(actor.S, orig_psi) + eqt.profiles_1d.dpressure_dpsi = Equilibrium.pressure_gradient(actor.S, orig_psi) / (tc["PSI"] * sign_Ip) + + eqt.profiles_1d.f = Equilibrium.poloidal_current(actor.S, orig_psi) * (tc["F"] * sign_Bt) + eqt.profiles_1d.f_df_dpsi = Equilibrium.poloidal_current(actor.S, orig_psi) .* Equilibrium.poloidal_current_gradient(actor.S, orig_psi) * (tc["F_FPRIME"] * sign_Bt * sign_Ip) + + resize!(eqt.profiles_2d, 1) + eqt.profiles_2d[1].grid_type.index = 1 + eqt.profiles_2d[1].grid.dim1 = range(rlims[1], rlims[2], length=ngrid) + eqt.profiles_2d[1].grid.dim2 = range(zlims[1] + Z0, zlims[2] + Z0, length=Int(ceil(ngrid * actor.S.kappa))) + + eqt.profiles_2d[1].psi = [actor.S(rr, flip_z * (zz - Z0)) * (tc["PSI"] * sign_Ip) for rr in eqt.profiles_2d[1].grid.dim1, zz in eqt.profiles_2d[1].grid.dim2] + IMAS.flux_surfaces(eqt) + + # correct equilibrium volume and area + if !ismissing(actor.par, :volume) + eqt.profiles_1d.volume .*= actor.par.volume / eqt.profiles_1d.volume[end] + end + if !ismissing(actor.par, :area) + eqt.profiles_1d.area .*= actor.par.area / eqt.profiles_1d.area[end] + end + + return eqt +end + +""" + IMAS2Equilibrium(eqt::IMAS.equilibrium__time_slice) + +Convert IMAS.equilibrium__time_slice to Equilibrium.jl EFIT structure +""" +function IMAS2Equilibrium(eqt::IMAS.equilibrium__time_slice) + dim1 = range(eqt.profiles_2d[1].grid.dim1[1], eqt.profiles_2d[1].grid.dim1[end], length=length(eqt.profiles_2d[1].grid.dim1)) + @assert collect(dim1) ≈ eqt.profiles_2d[1].grid.dim1 + dim2 = range(eqt.profiles_2d[1].grid.dim2[1], eqt.profiles_2d[1].grid.dim2[end], length=length(eqt.profiles_2d[1].grid.dim2)) + @assert collect(dim2) ≈ eqt.profiles_2d[1].grid.dim2 + psi = range(eqt.profiles_1d.psi[1], eqt.profiles_1d.psi[end], length=length(eqt.profiles_1d.psi)) + @assert collect(psi) ≈ eqt.profiles_1d.psi + + Equilibrium.efit(Equilibrium.cocos(11), # COCOS + dim1, # Radius/R range + dim2, # Elevation/Z range + psi, # Polodial Flux range (polodial flux from magnetic axis) + eqt.profiles_2d[1].psi, # Polodial Flux on RZ grid (polodial flux from magnetic axis) + eqt.profiles_1d.f, # Polodial Current + eqt.profiles_1d.pressure, # Plasma pressure + eqt.profiles_1d.q, # Q profile + eqt.profiles_1d.psi .* 0, # Electric Potential + (eqt.global_quantities.magnetic_axis.r, eqt.global_quantities.magnetic_axis.z), # Magnetic Axis (raxis,zaxis) + Int(sign(eqt.profiles_1d.f[end]) * sign(eqt.global_quantities.ip)) # sign(dot(J,B)) + ) +end From 71a27ee04aab159f0a309d68a5094f6d1227402f Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Fri, 29 Jul 2022 23:38:48 -0700 Subject: [PATCH 25/74] fixes and better handling of first wall CX --- src/actors/build_actors.jl | 13 ++++++++++++- src/actors/chease_actor.jl | 3 ++- src/ddinit/gasc.jl | 5 +++-- src/ddinit/init_build.jl | 13 ++++++++++--- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/actors/build_actors.jl b/src/actors/build_actors.jl index 229730e3f..123d2be89 100644 --- a/src/actors/build_actors.jl +++ b/src/actors/build_actors.jl @@ -936,10 +936,12 @@ function build_cx!(bd::IMAS.build, pr::Vector{Float64}, pz::Vector{Float64}) # k-1 means the layer outside (ie. towards the tf) # k is the current layer # k+1 means the layer inside (ie. towards the plasma) + # # forward pass: from plasma to TF _convex_hull_ and then desired TF shape tf_to_plasma = IMAS.get_build(bd, fs=_hfs_, return_only_one=false, return_index=true) plasma_to_tf = reverse(tf_to_plasma) for k in plasma_to_tf + original_shape = bd.layer[k].shape layer_shape = BuildLayerShape(mod(mod(bd.layer[k].shape, 1000), 100)) if k == itf + 1 # layer that is inside of the TF sets TF shape @@ -947,7 +949,7 @@ function build_cx!(bd::IMAS.build, pr::Vector{Float64}, pz::Vector{Float64}) else # everything else is conformal convex hull optimize_shape(bd, k + 1, k, _convex_hull_) - bd.layer[k].shape = Int(layer_shape) + bd.layer[k].shape = original_shape end end # reverse pass: from TF to plasma only with negative offset @@ -956,6 +958,15 @@ function build_cx!(bd::IMAS.build, pr::Vector{Float64}, pz::Vector{Float64}) optimize_shape(bd, k, k + 1, _negative_offset_) end end + # forward pass: from plasma to TF with desired shapes + for k in plasma_to_tf[1:end-1] + if bd.layer[k].shape == Int(_negative_offset_) + break + else + layer_shape = BuildLayerShape(mod(mod(bd.layer[k].shape, 1000), 100)) + optimize_shape(bd, k + 1, k, layer_shape) + end + end # _in_ D = minimum(IMAS.get_build(bd, type=_tf_, fs=_hfs_).outline.z) diff --git a/src/actors/chease_actor.jl b/src/actors/chease_actor.jl index 47941b26f..cb85c2c15 100644 --- a/src/actors/chease_actor.jl +++ b/src/actors/chease_actor.jl @@ -20,7 +20,8 @@ end """ ActorCHEASE(dd::IMAS.dd, act::ParametersAllActors; kw...) -This actor runs the Fixed boundary equilibrium solver CHEASE""" +This actor runs the Fixed boundary equilibrium solver CHEASE +""" function ActorCHEASE(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorCHEASE(kw...) actor = ActorCHEASE(dd, par) diff --git a/src/ddinit/gasc.jl b/src/ddinit/gasc.jl index cf1f27efe..ce253c006 100644 --- a/src/ddinit/gasc.jl +++ b/src/ddinit/gasc.jl @@ -259,7 +259,7 @@ function gasc_2_layers(gascsol::Dict) d = gascrb[replace(g2, "InnerTF" => "TF")] - gascrb[replace(g1, "InnerTF" => "TF")] f1 = mapper[replace(replace(replace(replace(g1, "Ri" => ""), "Ro" => ""), "Inner" => ""), "Outer" => "")] f2 = mapper[replace(replace(replace(replace(g2, "Ri" => ""), "Ro" => ""), "Inner" => ""), "Outer" => "")] - if startswith(g1, "Ri") + if startswith(g1, "Ri") # hfs if contains(g1, "Inner") f1 = "hfs_" * f1 elseif contains(g1, "Outer") @@ -268,7 +268,8 @@ function gasc_2_layers(gascsol::Dict) f = f1 f = replace(f, r".fs_gap_TF_OH" => "gap_TF_OH") layers[f] = d - elseif startswith(g1, "Ro") && d > 0 + + elseif startswith(g1, "Ro") && (d > 0) # lfs if contains(g2, "Outer") f = "lfs_gap_$(f1)_$(f2)" else diff --git a/src/ddinit/init_build.jl b/src/ddinit/init_build.jl index c99cfd51b..224d4190c 100644 --- a/src/ddinit/init_build.jl +++ b/src/ddinit/init_build.jl @@ -58,7 +58,7 @@ Initialize `dd.build` starting from `ini` and `act` parameters """ function init_build(dd::IMAS.dd, ini::ParametersAllInits, act::ParametersAllActors) init_from = ini.general.init_from - + if init_from == :ods dd1 = IMAS.json2imas(ini.ods.filename) if length(keys(dd1.wall)) > 0 @@ -91,11 +91,18 @@ function init_build(dd::IMAS.dd, ini::ParametersAllInits, act::ParametersAllActo tf_to_plasma = IMAS.get_build(dd.build, fs=_hfs_, return_only_one=false, return_index=true) dd.build.layer[tf_to_plasma[1]].shape = Int(_offset_) dd.build.layer[tf_to_plasma[2]].shape = Int(to_enum(ini.tf.shape)) + # set all other shapes for k in tf_to_plasma[2:end] dd.build.layer[k+1].shape = Int(_convex_hull_) end - for k in tf_to_plasma[2:end-ini.build.n_first_wall_conformal_layers] - dd.build.layer[k+1].shape = Int(_negative_offset_) + k = tf_to_plasma[end] + if (dd.build.layer[k].type == Int(_wall_)) && ((dd.build.layer[k-1].type == Int(_blanket_)) || (dd.build.layer[k-1].type == Int(_shield_))) + dd.build.layer[k].shape = Int(_offset_) + end + if ini.build.n_first_wall_conformal_layers >= 0 + for k in tf_to_plasma[2:end-ini.build.n_first_wall_conformal_layers] + dd.build.layer[k+1].shape = Int(_negative_offset_) + end end # 2D build cross-section From 91da384d6166c356c2d10fb45148cb1fb6978d25 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sat, 30 Jul 2022 14:44:38 -0700 Subject: [PATCH 26/74] Add 2cm wall to GASC FPP --- cases/FPP.jl | 22 ++++++++++++++++--- src/actors/build_actors.jl | 12 +++++++++-- src/ddinit/gasc.jl | 43 ++++++++++++++++++++++++++++++++------ src/physics.jl | 10 ++++----- 4 files changed, 71 insertions(+), 16 deletions(-) diff --git a/cases/FPP.jl b/cases/FPP.jl index 111c854f9..d4dfaa4b1 100644 --- a/cases/FPP.jl +++ b/cases/FPP.jl @@ -42,7 +42,7 @@ function case_parameters(::Type{Val{:FPP}}; version::Symbol, init_from::Symbol): if init_from == :ods ini.pf_active.n_pf_coils_outside = 8 else - ini.pf_active.n_pf_coils_outside = 6 + ini.pf_active.n_pf_coils_outside = 5 end ini.material.shield = "Tungsten" @@ -66,10 +66,26 @@ function case_parameters(::Type{Val{:FPP}}; version::Symbol, init_from::Symbol): # greenwald_fraction is a powerful knob ini.core_profiles.greenwald_fraction = 0.9 - # ini.equilibrium.δ *= -1 # negative triangularity + # negative triangularity + # ini.equilibrium.δ *= -1 - # ini.equilibrium.ζ = 0.1 # squareness + # squareness + # ini.equilibrium.ζ = 0.1 # act.ActorEquilibrium.model = :CHEASE + # add wall layer + if true + gasc_add_wall_layers!(ini.build.layers; thickness=0.02) + if version != :v1 + ini.build.n_first_wall_conformal_layers = 2 + end + end + + # bucking + ini.center_stack.bucked = false + if ini.center_stack.bucked + gasc_buck_OH_TF!(ini.build.layers) + end + return ini, act end diff --git a/src/actors/build_actors.jl b/src/actors/build_actors.jl index 123d2be89..0e5932e3a 100644 --- a/src/actors/build_actors.jl +++ b/src/actors/build_actors.jl @@ -793,6 +793,15 @@ function divertor_regions!(bd::IMAS.build, eqt::IMAS.equilibrium__time_slice) ipl = IMAS.get_build(bd, type=_plasma_, return_index=true) plasma_poly = xy_polygon(bd.layer[ipl]) + wall_poly = xy_polygon(bd.layer[ipl-1]) + for ltype in [_blanket_, _shield_, _wall_,] + iwl = IMAS.get_build(bd, type=ltype, fs=_hfs_, return_index=true, raise_error_on_missing=false) + if iwl !== missing + wall_poly = xy_polygon(bd.layer[iwl]) + break + end + end + divertors = IMAS.IDSvectorElement[] for x_point in eqt.boundary.x_point Zx = x_point.z @@ -804,7 +813,6 @@ function divertor_regions!(bd::IMAS.build, eqt::IMAS.equilibrium__time_slice) pz = vcat(yy, [Zx * 5, Zx * 5], yy[1]) domain = xy_polygon(pr, pz) - wall_poly = xy_polygon(bd.layer[ipl-1]) divertor_poly = LibGEOS.intersection(wall_poly, domain) divertor_poly = LibGEOS.difference(divertor_poly, plasma_poly) @@ -942,9 +950,9 @@ function build_cx!(bd::IMAS.build, pr::Vector{Float64}, pz::Vector{Float64}) plasma_to_tf = reverse(tf_to_plasma) for k in plasma_to_tf original_shape = bd.layer[k].shape - layer_shape = BuildLayerShape(mod(mod(bd.layer[k].shape, 1000), 100)) if k == itf + 1 # layer that is inside of the TF sets TF shape + layer_shape = BuildLayerShape(mod(mod(bd.layer[k].shape, 1000), 100)) optimize_shape(bd, k + 1, k, layer_shape; tight=!coils_inside) else # everything else is conformal convex hull diff --git a/src/ddinit/gasc.jl b/src/ddinit/gasc.jl index ce253c006..42af9d065 100644 --- a/src/ddinit/gasc.jl +++ b/src/ddinit/gasc.jl @@ -289,15 +289,46 @@ function gasc_2_layers(gascsol::Dict) end end - if false # to remove gap that prevents bucking - for k in collect(keys(layers)) - if k == "gap_TF_OH" - layers["OH"] += layers["gap_TF_OH"] - delete!(layers, k) - end + return layers +end + +""" + gasc_buck_OH_TF!(layers::DataStructures.OrderedDict) + +Remove gap between OH and TF to allow bucking (gap gets added to OH thickness) +""" +function gasc_buck_OH_TF!(layers::DataStructures.OrderedDict) + for k in collect(keys(layers)) + if k == "gap_TF_OH" + layers["OH"] += layers["gap_TF_OH"] + delete!(layers, k) end end + return layers +end +""" + gasc_add_wall_layers!(layers::DataStructures.OrderedDict, thickness::Float64) + +Add wall layer of given thickness expressed [meters] (gets subtracted from blanket layer) +""" +function gasc_add_wall_layers!(layers::DataStructures.OrderedDict; thickness::Float64) + tmp = DataStructures.OrderedDict() + for layer in keys(layers) + if layer == "hfs_blanket" + tmp[layer] = layers[layer] - thickness + tmp["hfs_first_wall"] = thickness + elseif layer == "lfs_blanket" + tmp["lfs_first_wall"] = thickness + tmp[layer] = layers[layer] - thickness + else + tmp[layer] = layers[layer] + end + end + empty!(layers) + for layer in keys(tmp) + layers[layer] = tmp[layer] + end return layers end diff --git a/src/physics.jl b/src/physics.jl index 8e5bae96b..2eb093e70 100644 --- a/src/physics.jl +++ b/src/physics.jl @@ -176,7 +176,7 @@ function optimize_shape(r_obstruction, z_obstruction, target_clearance, func, r_ return shape_parameters end - function cost_TF_shape(r_obstruction, z_obstruction, rz_obstruction, target_clearance, func, r_start, r_end, shape_parameters; verbose=false) + function cost_shape(r_obstruction, z_obstruction, rz_obstruction, target_clearance, func, r_start, r_end, shape_parameters; verbose=false) R, Z = func(r_start, r_end, shape_parameters...) # disregard near r_start and r_end where optimizer has no control and shape is allowed to go over obstruction @@ -195,7 +195,7 @@ function optimize_shape(r_obstruction, z_obstruction, target_clearance, func, r_ cost_mean_distance = mean_distance_error / target_clearance # favor up/down symmetric solutions - cost_up_down_symmetry = abs(maximum(Z) + minimum(Z)) / maximum(abs.(Z)) + cost_up_down_symmetry = abs(maximum(Z) + minimum(Z)) / (maximum(Z) - minimum(Z)) if verbose @show minimum_distance @@ -213,9 +213,9 @@ function optimize_shape(r_obstruction, z_obstruction, target_clearance, func, r_ rz_obstruction = collect(zip(r_obstruction, z_obstruction)) initial_guess = copy(shape_parameters) - # res = optimize(shape_parameters-> cost_TF_shape(r_obstruction, z_obstruction, rz_obstruction, target_clearance, func, r_start, r_end, shape_parameters), + # res = optimize(shape_parameters-> cost_shape(r_obstruction, z_obstruction, rz_obstruction, target_clearance, func, r_start, r_end, shape_parameters), # initial_guess, Newton(), Optim.Options(time_limit=time_limit); autodiff=:forward) - res = Optim.optimize(shape_parameters -> cost_TF_shape(r_obstruction, z_obstruction, rz_obstruction, target_clearance, func, r_start, r_end, shape_parameters), + res = Optim.optimize(shape_parameters -> cost_shape(r_obstruction, z_obstruction, rz_obstruction, target_clearance, func, r_start, r_end, shape_parameters), initial_guess, length(shape_parameters) == 1 ? Optim.BFGS() : Optim.NelderMead(), Optim.Options(time_limit=time_limit); autodiff=:forward) if verbose println(res) @@ -225,7 +225,7 @@ function optimize_shape(r_obstruction, z_obstruction, target_clearance, func, r_ # plot(func(r_start, r_end, initial_guess...); markershape=:x) # plot!(r_obstruction, z_obstruction, ; markershape=:x) # display(plot!(R, Z; markershape=:x, aspect_ratio=:equal)) - # cost_TF_shape(r_obstruction, z_obstruction, rz_obstruction, obstruction_area, target_clearance, func, r_start, r_end, shape_parameters; verbose=true) + # cost_shape(r_obstruction, z_obstruction, rz_obstruction, obstruction_area, target_clearance, func, r_start, r_end, shape_parameters; verbose=true) return shape_parameters end From 1df3f7e86e5536b133f1aa4a6a0a5fa5d8fe3f2c Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sat, 30 Jul 2022 14:45:10 -0700 Subject: [PATCH 27/74] Add NNeutronics dependency --- Makefile | 15 +++++++++------ Project.toml | 1 + 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 4300c2eea..efe78a523 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ install_no_registry: julia -e '\ using Pkg;\ Pkg.activate(".");\ -Pkg.develop(["IMAS", "IMASDD", "CoordinateConventions", "MillerExtendedHarmonic", "FusionMaterials", "VacuumFields", "Equilibrium", "TAUENN", "EPEDNN", "TGLFNN", "QED", "FiniteElementHermite", "Fortran90Namelists", "CHEASE", "EFIT"]);\ +Pkg.develop(["IMAS", "IMASDD", "CoordinateConventions", "MillerExtendedHarmonic", "FusionMaterials", "VacuumFields", "Equilibrium", "TAUENN", "EPEDNN", "TGLFNN", "QED", "FiniteElementHermite", "Fortran90Namelists", "CHEASE", "EFIT", "NNeutronics"]);\ Pkg.activate();\ Pkg.develop(["FUSE", "IMAS", "IMASDD", "CoordinateConventions", "MillerExtendedHarmonic", "FusionMaterials", "VacuumFields", "Equilibrium", "TAUENN", "EPEDNN", "TGLFNN", "QED", "FiniteElementHermite", "Fortran90Namelists", "CHEASE", "EFIT"]);\ ' @@ -61,7 +61,7 @@ precompile: julia -e 'using Pkg; Pkg.activate("."); Pkg.precompile()' clone_update_all: - make -j 100 FUSE IMAS IMASDD CoordinateConventions MillerExtendedHarmonic FusionMaterials VacuumFields Equilibrium TAUENN EPEDNN TGLFNN QED FiniteElementHermite Fortran90Namelists CHEASE EFIT + make -j 100 FUSE IMAS IMASDD CoordinateConventions MillerExtendedHarmonic FusionMaterials VacuumFields Equilibrium TAUENN EPEDNN TGLFNN QED FiniteElementHermite Fortran90Namelists CHEASE EFIT NNeutronics update: install clone_update_all precompile @@ -101,16 +101,19 @@ EPEDNN: QED: $(call clone_update_repo,$@) -CHEASE: +FiniteElementHermite: $(call clone_update_repo,$@) -EFIT: +Fortran90Namelists: $(call clone_update_repo,$@) -Fortran90Namelists: +CHEASE: $(call clone_update_repo,$@) -FiniteElementHermite: +EFIT: + $(call clone_update_repo,$@) + +NNeutronics: $(call clone_update_repo,$@) docker_image: diff --git a/Project.toml b/Project.toml index ea0f360ee..3d36c27d2 100644 --- a/Project.toml +++ b/Project.toml @@ -34,6 +34,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" LoggingExtras = "e6f89c97-d47a-5376-807f-9c37f3926c36" Metaheuristics = "bcdb8e00-2c21-11e9-3065-2b553b22f898" MillerExtendedHarmonic = "c82744c2-dc08-461a-8c37-87ab04d0f9b8" +NNeutronics = "a9424c20-d414-11ec-167b-9106c24d956c" NumericalIntegration = "e7bfaba1-d571-5449-8927-abc22e82249b" Optim = "429524aa-4258-5aef-a3af-852621145aeb" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" From 8a73130d6ba5c1a88a828beac73d08db346f7d10 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sat, 30 Jul 2022 14:46:34 -0700 Subject: [PATCH 28/74] Add orso jupyter playground --- playground/orso.ipynb | 252 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 playground/orso.ipynb diff --git a/playground/orso.ipynb b/playground/orso.ipynb new file mode 100644 index 000000000..648c98b5f --- /dev/null +++ b/playground/orso.ipynb @@ -0,0 +1,252 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "f316ddfa-6709-40b0-8e5a-4c53f9ad89ad", + "metadata": {}, + "outputs": [], + "source": [ + "using Plots; gr()#plotlyjs();\n", + "using Revise\n", + "using FUSE\n", + "global_logger(FUSE.logger);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4db92ed0-e6ee-4404-900b-eb4bd8c99505", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "#ini,act = FUSE.case_parameters(:ITER; init_from=:ods);\n", + "#ini,act = FUSE.case_parameters(:ITER; init_from=:scalars);\n", + "#ini,act = FUSE.case_parameters(:D3D);\n", + "ini,act = FUSE.case_parameters(:FPP; version=:v1_demount, init_from=:scalars);\n", + "#ini,act = FUSE.case_parameters(:FPP; version=:v1, init_from=:scalars);\n", + "#ini,act = FUSE.case_parameters(:CAT);\n", + "#ini,act = FUSE.case_parameters(:HDB5; tokamak=:JET, case=500);\n", + "\n", + "# Modify default settings\n", + "#ini.core_profiles.zeff = 2.0\n", + "#ini.equilibrium.ζ = 0.1\n", + "#act.ActorTauenn.transport_model = :ds03\n", + "#act.ActorEquilibrium.model = :CHEASE;" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39e7a502-262f-4dbb-8bc7-8d905ec29c67", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# initialize data structure\n", + "dd = FUSE.init(ini, act; do_plot=true);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6a317c40-d87b-4c07-bc5e-928e569ad5ee", + "metadata": {}, + "outputs": [], + "source": [ + "dd_copy=deepcopy(dd);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "066ecf57-a146-41f6-8047-28fbd7c3b0ad", + "metadata": {}, + "outputs": [], + "source": [ + "dd=deepcopy(dd_copy)\n", + "FUSE.ActorEquilibriumTransport(dd,act;do_plot=true);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "da0d6844-86dd-4114-b265-1053f412d3cf", + "metadata": {}, + "outputs": [], + "source": [ + "@time FUSE.ActorHFSsizing(dd, act; do_plot=true);\n", + "display(plot(dd.solid_mechanics.center_stack.stress))\n", + "dd.build.oh" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1fa74e76-af45-4104-b97a-fecded19f485", + "metadata": {}, + "outputs": [], + "source": [ + "@time FUSE.ActorLFSsizing(dd, act; do_plot=true);\n", + "display(dd.build.tf)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a974d49d-2579-4fee-87ec-899b9ec09a83", + "metadata": {}, + "outputs": [], + "source": [ + "@time FUSE.ActorCXbuild(dd, act; do_plot=true);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "04376c82-2b1c-402c-bf15-195663d27a95", + "metadata": {}, + "outputs": [], + "source": [ + "dd_copy=deepcopy(dd);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "510bad4a-4c83-471a-880b-7e5ef17d627d", + "metadata": {}, + "outputs": [], + "source": [ + "dd=deepcopy(dd_copy)\n", + "ini.pf_active.n_pf_coils_outside=5\n", + "FUSE.init_pf_active(dd,ini,act)\n", + "# λ_currents: make bigger to force currents to fit within the current limits\n", + "# update_equilibrium: overwrite the target equilibrium with what the coils can actually generate\n", + "@time FUSE.ActorPFcoilsOpt(dd, act; weight_lcfs=1, weight_null=1E-3, weight_currents=1.0, weight_strike=1.0, do_plot=true, update_equilibrium=true);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1c250273-e169-4dc7-8b33-d68b5943a6db", + "metadata": {}, + "outputs": [], + "source": [ + "@time FUSE.ActorCXbuild(dd, act, rebuild_wall=true);\n", + "@time FUSE.ActorPFcoilsOpt(dd, act; weight_lcfs=1, weight_null=1E-3, weight_currents=1.0, weight_strike=1.0, do_plot=true, update_equilibrium=false);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0855932d-1a32-4831-af29-caea247b37b5", + "metadata": {}, + "outputs": [], + "source": [ + "@time FUSE.ActorNeutronics(dd, act; do_plot=true);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b66d7d31-cdf8-4bab-8cb6-8758ad804f46", + "metadata": {}, + "outputs": [], + "source": [ + "@time FUSE.ActorBlanket(dd, act)#; do_plot=true);\n", + "dd.blanket" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92866983-eac5-4473-9342-93ae8b422552", + "metadata": {}, + "outputs": [], + "source": [ + "@time FUSE.ActorDivertors(dd, act)#; do_plot=true);\n", + "dd.divertors" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "878ed705-e9f1-4451-a1e6-2adc7e5e996b", + "metadata": {}, + "outputs": [], + "source": [ + "@time FUSE.ActorBalanceOfPlant(dd,act)#; do_plot=true);\n", + "IMAS.freeze(dd.balance_of_plant)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0281c3a3-1c7f-43b8-8b2d-ea3af6cd8bea", + "metadata": {}, + "outputs": [], + "source": [ + "@time FUSE.ActorCosting(dd, act)#; do_plot=true);\n", + "IMAS.freeze(dd.costing)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d483726c-164f-4cbb-8f2a-ccb1b0bd6772", + "metadata": {}, + "outputs": [], + "source": [ + "# Save `dd` to Json, so we can share our results with others\n", + "IMAS.imas2json(dd,\"fpp_v1_FUSE.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d3578913-d500-4ffd-a293-b89711bb7c8f", + "metadata": {}, + "outputs": [], + "source": [ + "plot(dd.equilibrium; cx=true)\n", + "plot!(dd.build; legend=false)\n", + "display(plot!(dd.pf_active))\n", + "\n", + "display(dd.build.layer)\n", + "\n", + "display(plot(dd.equilibrium))\n", + "\n", + "display(plot(dd.core_profiles))\n", + "\n", + "display(plot(dd.core_sources))\n", + "\n", + "display(IMAS.freeze(dd.balance_of_plant))\n", + "\n", + "display(IMAS.freeze(dd.costing))" + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": "110bb38f-4915-470d-85b0-34426cb313a1", + "lastKernelId": "b1e80a98-e4fb-4f36-a2e2-5db5359a95d8" + }, + "kernelspec": { + "display_name": "Julia 1.7.3", + "language": "julia", + "name": "julia-1.7" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 84291fdc97a4e7f3348d10e024522d8b9490af5f Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sat, 30 Jul 2022 14:48:36 -0700 Subject: [PATCH 29/74] fix missing packages for CI --- .github/workflows/runtests.yml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/runtests.yml b/.github/workflows/runtests.yml index a55c1ebcd..c64190c2a 100644 --- a/.github/workflows/runtests.yml +++ b/.github/workflows/runtests.yml @@ -35,7 +35,7 @@ jobs: using Pkg Pkg.activate(".") dependencies = PackageSpec[] - for package in ["IMAS", "IMASDD", "CoordinateConventions", "MillerExtendedHarmonic", "FusionMaterials", "VacuumFields", "Equilibrium", "TAUENN", "EPEDNN", "TGLFNN", "QED", "FiniteElementHermite", "CHEASE", "Fortran90Namelists", "EFIT"] + for package in ["IMAS", "IMASDD", "CoordinateConventions", "MillerExtendedHarmonic", "FusionMaterials", "VacuumFields", "Equilibrium", "TAUENN", "EPEDNN", "TGLFNN", "QED", "FiniteElementHermite", "Fortran90Namelists", "CHEASE", "EFIT", "NNeutronics"] push!(dependencies, PackageSpec(url="https://project-torrey-pines:${{secrets.PTP_READ_TOKEN}}@github.com/ProjectTorreyPines/$package.jl.git")) end Pkg.add(dependencies) diff --git a/Makefile b/Makefile index efe78a523..d1d04d136 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ using Pkg;\ Pkg.activate(".");\ Pkg.develop(["IMAS", "IMASDD", "CoordinateConventions", "MillerExtendedHarmonic", "FusionMaterials", "VacuumFields", "Equilibrium", "TAUENN", "EPEDNN", "TGLFNN", "QED", "FiniteElementHermite", "Fortran90Namelists", "CHEASE", "EFIT", "NNeutronics"]);\ Pkg.activate();\ -Pkg.develop(["FUSE", "IMAS", "IMASDD", "CoordinateConventions", "MillerExtendedHarmonic", "FusionMaterials", "VacuumFields", "Equilibrium", "TAUENN", "EPEDNN", "TGLFNN", "QED", "FiniteElementHermite", "Fortran90Namelists", "CHEASE", "EFIT"]);\ +Pkg.develop(["FUSE", "IMAS", "IMASDD", "CoordinateConventions", "MillerExtendedHarmonic", "FusionMaterials", "VacuumFields", "Equilibrium", "TAUENN", "EPEDNN", "TGLFNN", "QED", "FiniteElementHermite", "Fortran90Namelists", "CHEASE", "EFIT", "NNeutronics"]);\ ' install: clone_update_all install_no_registry precompile From d099fbfd5fd25048494987b5880f4da5fa9861d2 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sat, 30 Jul 2022 23:25:47 -0700 Subject: [PATCH 30/74] bug fix --- src/FUSE.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FUSE.jl b/src/FUSE.jl index 305af25c8..d9a73b454 100644 --- a/src/FUSE.jl +++ b/src/FUSE.jl @@ -27,7 +27,6 @@ include("technology.jl") #= ====== =# # DDINIT # #= ====== =# -include(joinpath("ddinit", "gasc.jl")) include(joinpath("ddinit", "init.jl")) include(joinpath("ddinit", "init_equilibrium.jl")) include(joinpath("ddinit", "init_build.jl")) @@ -35,6 +34,7 @@ include(joinpath("ddinit", "init_core_profiles.jl")) include(joinpath("ddinit", "init_core_sources.jl")) include(joinpath("ddinit", "init_pf_active.jl")) include(joinpath("ddinit", "init_others.jl")) +include(joinpath("ddinit", "gasc.jl")) #= ====== =# # ACTORS # From 16a9b67c6696178db1affd90a5d8421116db2c3c Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sat, 30 Jul 2022 23:58:48 -0700 Subject: [PATCH 31/74] hook-up NNeutronics 1D model in FUSE @kevinm387 --- src/actors/blanket_actors.jl | 49 ++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/actors/blanket_actors.jl b/src/actors/blanket_actors.jl index eb83d3d2e..f7472ad19 100644 --- a/src/actors/blanket_actors.jl +++ b/src/actors/blanket_actors.jl @@ -2,6 +2,8 @@ # ActorBlanket # #= ============ =# +import NNeutronics + mutable struct ActorBlanket <: ReactorAbstractActor dd::IMAS.dd par::ParametersActor @@ -16,7 +18,7 @@ function ParametersActor(::Type{Val{:ActorBlanket}}) Real, "", "Fraction of thermal power that is carried out by the coolant at the blanket interface, rather than being lost in the surrounding strutures."; - default=1.0, + default=1.0 ) return par end @@ -45,7 +47,6 @@ end function step(actor::ActorBlanket) dd = actor.dd - empty!(dd.blanket) eqt = dd.equilibrium.time_slice[] nnt = dd.neutronics.time_slice[] @@ -55,9 +56,8 @@ function step(actor::ActorBlanket) total_power_radiated = 0.0 # IMAS.radiative_power(dd.core_profiles.profiles_1d[]) blankets = [structure for structure in dd.build.structure if structure.type == Int(_blanket_)] - - tritium_breeding_ratio = 0.0 resize!(dd.blanket.module, length(blankets)) + for (k, structure) in enumerate(blankets) bm = dd.blanket.module[k] bm.name = structure.name @@ -81,9 +81,44 @@ function step(actor::ActorBlanket) bmt.power_thermal_extracted = actor.thermal_power_extraction_efficiency * (bmt.power_thermal_neutrons + bmt.power_thermal_radiated) - bmt.tritium_breeding_ratio = 1.0 # some function - tritium_breeding_ratio += bmt.tritium_breeding_ratio * bmt.power_incident_neutrons + # blanket layer structure + resize!(bm.layer, 3) + if sum(structure.outline.r) / length(structure.outline.r) < eqt.boundary.geometric_axis.r + d1 = IMAS.get_build(dd.build, type=_wall_, fs=_hfs_) + d2 = IMAS.get_build(dd.build, type=_blanket_, fs=_hfs_) + d3 = IMAS.get_build(dd.build, type=_shield_, fs=_hfs_, return_only_one=false)[end] + else + d1 = IMAS.get_build(dd.build, type=_wall_, fs=_lfs_) + d2 = IMAS.get_build(dd.build, type=_blanket_, fs=_lfs_) + d3 = IMAS.get_build(dd.build, type=_shield_, fs=_lfs_, return_only_one=false)[1] + end + for (kl, dl) in enumerate([d1, d2, d3]) + for field in [:name, :thickness, :material] + setproperty!(bm.layer[kl], field, getproperty(dl, field)) + end + end end - @ddtime(dd.blanket.tritium_breeding_ratio = tritium_breeding_ratio / total_power_neutrons) + # Optimize Li6/Li7 ratio to obtain target TBR + function target_TBR(blanket_model, Li6, dd, target=nothing) + total_tritium_breeding_ratio = 0.0 + for bm in dd.blanket.module + bmt = bm.time_slice[] + bm.layer[2].name = @sprintf("lithium-lead: Li6/7=%3.3f", Li6) + bmt.tritium_breeding_ratio = NNeutronics.TBR(blanket_model, [dl.thickness for dl in bm.layer]..., Li6) + total_tritium_breeding_ratio += bmt.tritium_breeding_ratio * bmt.power_incident_neutrons / total_power_neutrons + end + if target === nothing + return total_tritium_breeding_ratio + else + return (total_tritium_breeding_ratio - target)^2 + end + end + + target_tritium_breeding_ratio = 0.9 + + res = Optim.optimize(Li6 -> target_TBR(blanket_model_1d, Li6, dd, target_tritium_breeding_ratio), 0.0, 100.0, Optim.GoldenSection(), rel_tol=1E-3) + total_tritium_breeding_ratio = target_TBR(blanket_model_1d, res.minimizer, dd) + + @ddtime(dd.blanket.tritium_breeding_ratio = total_tritium_breeding_ratio) end From 0faabaa50e5a6d564cbfa322971d6e3ad8c14024 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sun, 31 Jul 2022 00:01:20 -0700 Subject: [PATCH 32/74] bug fix --- src/actors/blanket_actors.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actors/blanket_actors.jl b/src/actors/blanket_actors.jl index f7472ad19..1f4331f9f 100644 --- a/src/actors/blanket_actors.jl +++ b/src/actors/blanket_actors.jl @@ -116,7 +116,7 @@ function step(actor::ActorBlanket) end target_tritium_breeding_ratio = 0.9 - + blanket_model_1d = NNeutronics.Blanket() res = Optim.optimize(Li6 -> target_TBR(blanket_model_1d, Li6, dd, target_tritium_breeding_ratio), 0.0, 100.0, Optim.GoldenSection(), rel_tol=1E-3) total_tritium_breeding_ratio = target_TBR(blanket_model_1d, res.minimizer, dd) From 6b33044cdb51e9fbe67ec3fb0c37e76564867a48 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sun, 31 Jul 2022 16:26:57 -0700 Subject: [PATCH 33/74] Better GASC interface --- src/ddinit/gasc.jl | 112 ++++++++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/src/ddinit/gasc.jl b/src/ddinit/gasc.jl index 42af9d065..a50aa3172 100644 --- a/src/ddinit/gasc.jl +++ b/src/ddinit/gasc.jl @@ -1,13 +1,27 @@ import JSON import PeriodicTable: elements -struct GASC +mutable struct GASC filename::String + data::Dict case::Int - solution::Dict version::Int end +function Base.getproperty(gasc::GASC, field::Symbol) + if field == :solution + return getfield(gasc, :data)["SOLUTIONS"][gasc.case+1] + elseif field == :outputs + return getfield(gasc, :data)["SOLUTIONS"][gasc.case+1]["OUTPUTS"] + elseif field == :inputs + return getfield(gasc, :data)["SOLUTIONS"][gasc.case+1]["INPUTS"] + elseif field == :constraints + return getfield(gasc, :data)["SETUP"]["SETTINGS"]["constraints"] + else + return getfield(gasc, field) + end +end + """ GASC(filename::String, case::Int) @@ -24,12 +38,11 @@ function GASC(filename::String, case::Int) version = 1 end # GASC struct - gasc = GASC(filename, case, data["SOLUTIONS"][case_j], version) - # convert list of floats to arrays - for item in keys(gasc.solution["OUTPUTS"]["numerical profiles"]) - if item in ["boundaryInnerTF", "boundaryOuterTF"] - else - gasc.solution["OUTPUTS"]["numerical profiles"][item] = Vector{Float64}(gasc.solution["OUTPUTS"]["numerical profiles"][item]) + gasc = GASC(filename, data, case, version) + # convert list of floats to arrays + for item in keys(gasc.outputs["numerical profiles"]) + if item ∉ ["boundaryInnerTF", "boundaryOuterTF"] + gasc.outputs["numerical profiles"][item] = Vector{Float64}(gasc.outputs["numerical profiles"][item]) end end return gasc @@ -46,7 +59,7 @@ function case_parameters(gasc::GASC) ini.gasc.filename = gasc.filename ini.gasc.case = gasc.case - ini.general.casename = "GASC" + ini.general.casename = gasc.data["LABEL"] ini.general.init_from = :scalars gasc_2_build(gasc, ini, act) @@ -66,19 +79,17 @@ end Convert core_profiles information in GASC solution to FUSE `ini` and `act` parameters """ function gasc_2_core_profiles(gasc::GASC, ini::ParametersAllInits, act::ParametersAllActors) - gascsol = gasc.solution - - ini.core_profiles.ne_ped = gascsol["OUTPUTS"]["plasma parameters"]["neped"] * 1e20 - ini.core_profiles.greenwald_fraction = gascsol["OUTPUTS"]["plasma parameters"]["greenwaldFraction"] + ini.core_profiles.ne_ped = gasc.outputs["plasma parameters"]["neped"] * 1e20 + ini.core_profiles.greenwald_fraction = gasc.outputs["plasma parameters"]["greenwaldFraction"] ini.core_profiles.T_shaping = 1.8 - i_ped = argmin(abs.(gascsol["OUTPUTS"]["numerical profiles"]["neProf"] .- gascsol["OUTPUTS"]["plasma parameters"]["neped"] / gascsol["OUTPUTS"]["plasma parameters"]["ne0"])) - ini.core_profiles.w_ped = 1 - gascsol["OUTPUTS"]["numerical profiles"]["rProf"][i_ped] - ini.core_profiles.zeff = gascsol["OUTPUTS"]["impurities"]["effectiveZ"] + i_ped = argmin(abs.(gasc.outputs["numerical profiles"]["neProf"] .- gasc.outputs["plasma parameters"]["neped"] / gasc.outputs["plasma parameters"]["ne0"])) + ini.core_profiles.w_ped = 1 - gasc.outputs["numerical profiles"]["rProf"][i_ped] + ini.core_profiles.zeff = gasc.outputs["impurities"]["effectiveZ"] ini.core_profiles.rot_core = 0.0 # Not in GASC ini.core_profiles.bulk = :DT - ini.core_profiles.helium_fraction = gascsol["INPUTS"]["impurities"]["heliumFraction"] - ini.core_profiles.impurity = Symbol(elements[Int(gascsol["INPUTS"]["impurities"]["impurityZ"])].symbol) - ini.core_profiles.ejima = gascsol["INPUTS"]["plasma parameters"]["ejimaCoeff"] + ini.core_profiles.helium_fraction = gasc.inputs["impurities"]["heliumFraction"] + ini.core_profiles.impurity = Symbol(elements[Int(gasc.inputs["impurities"]["impurityZ"])].symbol) + ini.core_profiles.ejima = gasc.inputs["plasma parameters"]["ejimaCoeff"] return ini end @@ -88,24 +99,22 @@ end Convert equilibrium information in GASC solution to FUSE `ini` and `act` parameters """ function gasc_2_equilibrium(gasc::GASC, ini::ParametersAllInits, act::ParametersAllActors) - gascsol = gasc.solution - - ini.equilibrium.B0 = gascsol["INPUTS"]["conductors"]["magneticFieldOnAxis"] - ini.equilibrium.R0 = gascsol["INPUTS"]["radial build"]["majorRadius"] + ini.equilibrium.B0 = gasc.inputs["conductors"]["magneticFieldOnAxis"] + ini.equilibrium.R0 = gasc.inputs["radial build"]["majorRadius"] ini.equilibrium.Z0 = 0.0 - ini.equilibrium.ϵ = 1 / gascsol["INPUTS"]["radial build"]["aspectRatio"] - ini.equilibrium.κ = gascsol["OUTPUTS"]["plasma parameters"]["elongation"] - ini.equilibrium.δ = gascsol["INPUTS"]["plasma parameters"]["triangularity"] + ini.equilibrium.ϵ = 1 / gasc.inputs["radial build"]["aspectRatio"] + ini.equilibrium.κ = gasc.outputs["plasma parameters"]["elongation"] + ini.equilibrium.δ = gasc.inputs["plasma parameters"]["triangularity"] - Pavg = gascsol["OUTPUTS"]["plasma parameters"]["pressureVolAvg"] - V = gascsol["OUTPUTS"]["plasma parameters"]["plasmaVolume"] - vol = gascsol["OUTPUTS"]["numerical profiles"]["volumeProf"] .* V + Pavg = gasc.outputs["plasma parameters"]["pressureVolAvg"] + V = gasc.outputs["plasma parameters"]["plasmaVolume"] + vol = gasc.outputs["numerical profiles"]["volumeProf"] .* V P1 = sum(IMAS.gradient(vol) .* LinRange(1.0, 0.0, length(vol))) / V ini.equilibrium.pressure_core = Pavg / P1 - ini.equilibrium.ip = gascsol["INPUTS"]["plasma parameters"]["plasmaCurrent"] * 1E6 - ini.equilibrium.x_point = gascsol["INPUTS"]["divertor metrics"]["numberDivertors"] > 0 - ini.equilibrium.symmetric = (mod(gascsol["INPUTS"]["divertor metrics"]["numberDivertors"], 2) == 0) + ini.equilibrium.ip = gasc.inputs["plasma parameters"]["plasmaCurrent"] * 1E6 + ini.equilibrium.x_point = gasc.inputs["divertor metrics"]["numberDivertors"] > 0 + ini.equilibrium.symmetric = (mod(gasc.inputs["divertor metrics"]["numberDivertors"], 2) == 0) return ini end @@ -115,10 +124,8 @@ end Convert sources (NBI, EC, IC, LH) information in GASC solution to FUSE `ini` and `act` parameters """ function gasc_2_sources(gasc::GASC, ini::ParametersAllInits, act::ParametersAllActors) - gascsol = gasc.solution - - inputs = gascsol["INPUTS"]["current drive"] - outputs = gascsol["OUTPUTS"]["current drive"] + inputs = gasc.inputs["current drive"] + outputs = gasc.outputs["current drive"] cd_powers = Float64[] ini.nbi.power_launched = Float64[] @@ -209,12 +216,12 @@ function gasc_2_build(gasc::GASC, ini::ParametersAllInits, act::ParametersAllAct end """ - function gasc_2_layers(gascsol::Dict) + function gasc_2_layers(gasc::GASC) Convert GASC ["OUTPUTS"]["radial build"] to FUSE build layers dictionary """ -function gasc_2_layers(gascsol::Dict) - gascrb = gascsol["OUTPUTS"]["radial build"] +function gasc_2_layers(gasc::GASC) + gascrb = gasc.outputs["radial build"] layers = DataStructures.OrderedDict() mapper = Dict( @@ -338,35 +345,34 @@ end Convert coil technology information in GASC solution to FUSE `coil_technology` data structure """ function gasc_2_coil_technology(gasc::GASC, coil_type::Symbol) - gascsol = gasc.solution if coil_type ∉ [:OH, :TF, :PF] error("Supported coil type are [:OH, :TF, :PF]") end - if gascsol["INPUTS"]["conductors"]["superConducting"] == "copper" + if gasc.inputs["conductors"]["superConducting"] == "copper" coil_tech = coil_technology(:copper) else - if gascsol["INPUTS"]["conductors"]["superConducting"] == "LTS" + if gasc.inputs["conductors"]["superConducting"] == "LTS" coil_tech = coil_technology(:LTS) - elseif gascsol["INPUTS"]["conductors"]["superConducting"] == "HTS" + elseif gasc.inputs["conductors"]["superConducting"] == "HTS" coil_tech = coil_technology(:HTS) end if coil_type == :PF # assume PF coils are always LTS coil_tech = coil_technology(:LTS) end - if "thermalStrain$coil_type" ∉ keys(gascsol["INPUTS"]["conductors"]) + if "thermalStrain$coil_type" ∉ keys(gasc.inputs["conductors"]) coil_tech.thermal_strain = 0.0 coil_tech.JxB_strain = 0.0 else - coil_tech.thermal_strain = gascsol["INPUTS"]["conductors"]["thermalStrain$coil_type"] - coil_tech.JxB_strain = gascsol["INPUTS"]["conductors"]["structuralStrain$coil_type"] + coil_tech.thermal_strain = gasc.inputs["conductors"]["thermalStrain$coil_type"] + coil_tech.JxB_strain = gasc.inputs["conductors"]["structuralStrain$coil_type"] end end - if "fractionVoid$coil_type" ∉ keys(gascsol["INPUTS"]["conductors"]) - coil_tech.fraction_void = gascsol["INPUTS"]["conductors"]["fractionVoidOH"] - coil_tech.fraction_stainless = gascsol["INPUTS"]["conductors"]["fractionStainlessOH"] + if "fractionVoid$coil_type" ∉ keys(gasc.inputs["conductors"]) + coil_tech.fraction_void = gasc.inputs["conductors"]["fractionVoidOH"] + coil_tech.fraction_stainless = gasc.inputs["conductors"]["fractionStainlessOH"] else - coil_tech.fraction_void = gascsol["INPUTS"]["conductors"]["fractionVoid$coil_type"] - coil_tech.fraction_stainless = gascsol["INPUTS"]["conductors"]["fractionStainless$coil_type"] + coil_tech.fraction_void = gasc.inputs["conductors"]["fractionVoid$coil_type"] + coil_tech.fraction_stainless = gasc.inputs["conductors"]["fractionStainless$coil_type"] end return set_new_base!(coil_tech) end @@ -376,12 +382,12 @@ function compare(dd::IMAS.dd, gasc::GASC) # collisionless bootstrap coefficient FUSE = IMAS.collisionless_bootstrap_coefficient(dd) - GASC = gasc.solution["INPUTS"]["plasma parameters"]["user_bootstrapCoefficient"] + GASC = gasc.inputs["plasma parameters"]["user_bootstrapCoefficient"] df.Cbs = [FUSE, GASC] # fusion power [MW] FUSE = IMAS.fusion_power(dd.core_profiles.profiles_1d[]) / 1E6 - GASC = gasc.solution["OUTPUTS"]["power balance"]["powerFusion"] + GASC = gasc.outputs["power balance"]["powerFusion"] df.Pfusion = [FUSE, GASC] df From 750e624c99c9883c7154b18155b3de66ed8856fc Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sun, 31 Jul 2022 16:27:06 -0700 Subject: [PATCH 34/74] gasc_2_target --- src/ddinit/gasc.jl | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/ddinit/gasc.jl b/src/ddinit/gasc.jl index a50aa3172..7e6509f90 100644 --- a/src/ddinit/gasc.jl +++ b/src/ddinit/gasc.jl @@ -64,6 +64,8 @@ function case_parameters(gasc::GASC) gasc_2_build(gasc, ini, act) + gasc_2_target(gasc, ini, act) + gasc_2_equilibrium(gasc, ini, act) gasc_2_sources(gasc, ini, act) @@ -199,19 +201,27 @@ end Convert radial build information in GASC solution to FUSE `ini` and `act` parameters """ function gasc_2_build(gasc::GASC, ini::ParametersAllInits, act::ParametersAllActors) - gascsol = gasc.solution - ini.build.layers = gasc_2_layers(gascsol) - ini.build.symmetric = (mod(gascsol["INPUTS"]["divertor metrics"]["numberDivertors"], 2) == 0) + ini.build.layers = gasc_2_layers(gasc) + ini.build.symmetric = (mod(gasc.inputs["divertor metrics"]["numberDivertors"], 2) == 0) ini.tf.technology = gasc_2_coil_technology(gasc, :TF) ini.oh.technology = gasc_2_coil_technology(gasc, :OH) ini.pf_active.technology = gasc_2_coil_technology(gasc, :PF) - ini.center_stack.bucked = gascsol["INPUTS"]["radial build"]["isBucked"] - ini.center_stack.noslip = gascsol["INPUTS"]["radial build"]["nonSlip"] - ini.center_stack.plug = gascsol["INPUTS"]["radial build"]["hasPlug"] + ini.center_stack.bucked = gasc.inputs["radial build"]["isBucked"] + ini.center_stack.noslip = gasc.inputs["radial build"]["nonSlip"] + ini.center_stack.plug = gasc.inputs["radial build"]["hasPlug"] + return ini +end + +""" + gasc_2_target(gasc::GASC, ini::ParametersAllInits, act::ParametersAllActors) - ini.oh.flattop_duration = gascsol["INPUTS"]["plasma parameters"]["flattopDuration"] +Convert nominal target design information in GASC solution to FUSE `ini` and `act` parameters +""" +function gasc_2_target(gasc::GASC, ini::ParametersAllInits, act::ParametersAllActors) + ini.target.flattop_duration = gasc.inputs["plasma parameters"]["flattopDuration"] + ini.target.power_electric_net = gasc.constraints["lowerOutputConstraints"]["powerNet"] * 1E6 return ini end From 1723ed151707ab4e21f196aef7098d8f881f8b1b Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sun, 31 Jul 2022 16:27:47 -0700 Subject: [PATCH 35/74] ini.target and dd.target --- src/ddinit/init_build.jl | 3 --- src/ddinit/init_others.jl | 8 ++++++++ src/parameters.jl | 2 +- src/parameters_init.jl | 13 ++++++++++--- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/ddinit/init_build.jl b/src/ddinit/init_build.jl index 224d4190c..6614c5c69 100644 --- a/src/ddinit/init_build.jl +++ b/src/ddinit/init_build.jl @@ -108,9 +108,6 @@ function init_build(dd::IMAS.dd, ini::ParametersAllInits, act::ParametersAllActo # 2D build cross-section ActorCXbuild(dd, act) - # flattop duration - dd.build.oh.flattop_duration = ini.oh.flattop_duration - # TF coils dd.build.tf.coils_n = ini.tf.n_coils # set the toroidal thickness of the TF coils based on the innermost radius and the number of coils diff --git a/src/ddinit/init_others.jl b/src/ddinit/init_others.jl index 182818e17..4f2ff35c2 100644 --- a/src/ddinit/init_others.jl +++ b/src/ddinit/init_others.jl @@ -19,5 +19,13 @@ function init_missing_from_ods(dd::IMAS.dd, ini::ParametersAllInits, act::Parame end end + # target + for field in [:power_electric_net, :flattop_duration, :tritium_breeding_ratio, :cost] + value = getproperty(ini.target, field, missing) + if value !== missing + setproperty!(dd.target, field, value) + end + end + return dd end \ No newline at end of file diff --git a/src/parameters.jl b/src/parameters.jl index 02f6d6428..a330777c7 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -199,7 +199,7 @@ Generates all initalization parameters """ function ParametersAllInits() ini = ParametersAllInits(missing, WeakRef(missing), Dict{Symbol,Union{AbstractParameter,ParametersInit}}()) - for item in [:general, :equilibrium, :core_profiles, :pf_active, :oh, :tf, :center_stack, :nbi, :ec_launchers, :ic_antennas, :lh_antennas, :build, :gasc, :ods, :material] + for item in [:general, :equilibrium, :core_profiles, :pf_active, :oh, :tf, :center_stack, :nbi, :ec_launchers, :ic_antennas, :lh_antennas, :build, :gasc, :ods, :material, :target] setproperty!(ini, item, ParametersInit(item)) end ini._name = :ini diff --git a/src/parameters_init.jl b/src/parameters_init.jl index c536f05cf..f293e09a4 100644 --- a/src/parameters_init.jl +++ b/src/parameters_init.jl @@ -33,7 +33,7 @@ function ParametersInit(::Type{Val{:equilibrium}}) equilibrium.x_point = Entry(Union{NTuple{2},Bool}, IMAS.equilibrium__time_slice___boundary, :x_point) equilibrium.symmetric = Entry(Bool, "", "Is plasma up-down symmetric") equilibrium.ngrid = Entry(Int, "", "Resolution of the equilibrium grid"; default=129) - equilibrium.field_null_surface = Entry(Real, "", "ψn value of the field_null_surface. Disable with 0.0"; default=0.25)#, min=0.0, max=1.0) + equilibrium.field_null_surface = Entry(Real, "", "ψn value of the field_null_surface. Disable with 0.0"; default=0.25) equilibrium.boundary_from = Switch([:scalars, :MXH_params, :rz_points], "", "The starting r, z boundary taken from"; default=:scalars) equilibrium.MXH_params = Entry(Union{Nothing,Vector{<:Real}}, "", "Vector of MXH flats", default=missing) equilibrium.rz_points = Entry(Union{Nothing,Vector{Vector{<:Real}}}, "m", "R_Z boundary as Vector{Vector{<:Real}}} : r = rz_points[1], z = rz_points[2]", default=missing) @@ -78,7 +78,6 @@ end function ParametersInit(::Type{Val{:oh}}) oh = ParametersInit(nothing) oh.technology = ParametersInit(:coil_technology) - oh.flattop_duration = Entry(Real, "s", "Duration of the flattop (use Inf for steady-state)") return oh end @@ -90,7 +89,6 @@ function ParametersInit(::Type{Val{:center_stack}}) return center_stack end - function ParametersInit(::Type{Val{:nbi}}) nbi = ParametersInit(nothing) nbi.power_launched = Entry(Union{X,Vector{X}} where {X<:Real}, "W", "Beam power") @@ -163,3 +161,12 @@ function ParametersInit(::Type{Val{:coil_technology}}) coil_tech.ratio_SC_to_copper = Entry(Real, "", "Fraction of superconductor to copper cross-sectional areas") return coil_tech end + +function ParametersInit(::Type{Val{:target}}) + target = ParametersInit(nothing) + target.power_electric_net = Entry(Real, "W", "Target net electric power generated by the fusion power plant") + target.flattop_duration = Entry(Real, "s", "Target duration of the flattop (use Inf for steady-state)") + target.tritium_breeding_ratio = Entry(Real, "", "Target tritium breeding ratio of the whole plant") + target.cost = Entry(Real, "\$M", "Target total FPP cost") + return target +end From 72329fa0689c408f66e9571c85e3e87b9300a55a Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sun, 31 Jul 2022 16:28:37 -0700 Subject: [PATCH 36/74] detangle flattop_duration and flattop_estimate --- cases/ARC.jl | 5 ++++- cases/CAT.jl | 3 ++- cases/D3D.jl | 3 ++- cases/FPP.jl | 3 +++ cases/ITER.jl | 3 ++- cases/SPARC.jl | 3 ++- src/actors/build_actors.jl | 37 +++++++++++++++++++------------------ src/optimization.jl | 4 ++-- 8 files changed, 36 insertions(+), 25 deletions(-) diff --git a/cases/ARC.jl b/cases/ARC.jl index 2101a3a9e..61415bfcf 100644 --- a/cases/ARC.jl +++ b/cases/ARC.jl @@ -50,7 +50,10 @@ function case_parameters(::Type{Val{:ARC}})::Tuple{ParametersAllInits, Parameter ini.tf.n_coils = 18 ini.tf.technology = coil_technology(:HTS) ini.oh.technology = coil_technology(:HTS) - ini.oh.flattop_duration = 1800 + + #ini.target.power_electric_net = 50E6 ? + ini.target.flattop_duration = 1800 + #ini.target.tritium_breeding_ratio = 1.0 ? ini.core_profiles.ne_ped = 1.0e20 ini.core_profiles.greenwald_fraction = 0.49 diff --git a/cases/CAT.jl b/cases/CAT.jl index 8d0f1d525..fa4003762 100644 --- a/cases/CAT.jl +++ b/cases/CAT.jl @@ -29,7 +29,6 @@ function case_parameters(::Type{Val{:CAT}})::Tuple{ParametersAllInits, Parameter ini.tf.technology = coil_technology(:ITER, :TF) ini.oh.technology = coil_technology(:ITER, :OH) - ini.oh.flattop_duration = 1000 ini.core_profiles.ne_ped = 7E19 ini.core_profiles.greenwald_fraction = 0.8 ini.core_profiles.helium_fraction = 0.01 @@ -40,6 +39,8 @@ function case_parameters(::Type{Val{:CAT}})::Tuple{ParametersAllInits, Parameter ini.core_profiles.bulk = :DT ini.core_profiles.impurity = :Ne + ini.target.flattop_duration = 1000 + ini.nbi.power_launched = 20E6 ini.nbi.beam_energy = 200e3 ini.nbi.beam_mass = 2 diff --git a/cases/D3D.jl b/cases/D3D.jl index 781210175..81da815b8 100644 --- a/cases/D3D.jl +++ b/cases/D3D.jl @@ -27,7 +27,6 @@ function case_parameters(::Type{Val{:D3D}})::Tuple{ParametersAllInits, Parameter ini.tf.technology = coil_technology(:copper) ini.oh.technology = coil_technology(:copper) - ini.oh.flattop_duration = 5 ini.core_profiles.ne_ped = 5E19 ini.core_profiles.greenwald_fraction = 0.7 @@ -44,6 +43,8 @@ function case_parameters(::Type{Val{:D3D}})::Tuple{ParametersAllInits, Parameter ini.nbi.beam_mass = 2 ini.nbi.toroidal_angle = 20.0 / 180 * pi + ini.target.flattop_duration = 5 + act.ActorPFcoilsOpt.symmetric = true return set_new_base!(ini), set_new_base!(act) diff --git a/cases/FPP.jl b/cases/FPP.jl index d4dfaa4b1..1f2a15f25 100644 --- a/cases/FPP.jl +++ b/cases/FPP.jl @@ -32,6 +32,9 @@ function case_parameters(::Type{Val{:FPP}}; version::Symbol, init_from::Symbol): act.ActorHFSsizing.fixed_aspect_ratio = true end + ini.target.tritium_breeding_ratio = 1.1 + ini.target.cost = 5E9 + ini.core_profiles.bulk = :DT ini.core_profiles.rot_core = 0.0 ini.tf.shape = :princeton_D_scaled diff --git a/cases/ITER.jl b/cases/ITER.jl index aee7fce00..892aee50c 100644 --- a/cases/ITER.jl +++ b/cases/ITER.jl @@ -71,9 +71,10 @@ function case_parameters(::Type{Val{:ITER}}; init_from::Symbol)::Tuple{Parameter ini.tf.technology = coil_technology(:ITER, :TF) ini.oh.technology = coil_technology(:ITER, :OH) - ini.oh.flattop_duration = 1000 act.ActorFluxSwing.operate_at_j_crit = false + ini.target.flattop_duration = 1800 + ini.core_profiles.ne_ped = 7e19 ini.core_profiles.greenwald_fraction = 0.9 ini.core_profiles.helium_fraction = 0.01 diff --git a/cases/SPARC.jl b/cases/SPARC.jl index 0ec1d0e7b..05e5d93a3 100644 --- a/cases/SPARC.jl +++ b/cases/SPARC.jl @@ -46,7 +46,8 @@ function case_parameters(::Type{Val{:SPARC}})::Tuple{ParametersAllInits, Paramet ini.tf.n_coils = 18 #estimate (from ARC) ini.tf.technology = coil_technology(:HTS) ini.oh.technology = coil_technology(:HTS) - ini.oh.flattop_duration = 10 + + ini.target.flattop_duration = 10 ini.core_profiles.ne_ped = 1.5e20 ini.core_profiles.greenwald_fraction = 0.37 diff --git a/src/actors/build_actors.jl b/src/actors/build_actors.jl index 0e5932e3a..585c1eb85 100644 --- a/src/actors/build_actors.jl +++ b/src/actors/build_actors.jl @@ -44,7 +44,7 @@ function oh_required_J_B!(bd::IMAS.build; double_swing::Bool=true) innerSolenoidRadius = OH.start_radius outerSolenoidRadius = OH.end_radius - totalOhFluxReq = bd.flux_swing_estimates.rampup + bd.flux_swing_estimates.flattop + bd.flux_swing_estimates.pf + totalOhFluxReq = bd.flux_swing.rampup + bd.flux_swing.flattop + bd.flux_swing.pf # Calculate magnetic field at solenoid bore required to match flux swing request RiRo_factor = innerSolenoidRadius / outerSolenoidRadius @@ -58,11 +58,11 @@ function oh_required_J_B!(bd::IMAS.build; double_swing::Bool=true) end """ - flattop_estimate!(bd::IMAS.build, eqt::IMAS.equilibrium__time_slice, cp1d::IMAS.core_profiles__profiles_1d; double_swing::Bool=true) + flattop_duration!(bd::IMAS.build, eqt::IMAS.equilibrium__time_slice, cp1d::IMAS.core_profiles__profiles_1d; double_swing::Bool=true) Estimate OH flux requirement during flattop (if j_ohmic profile is missing then steady state ohmic profile is assumed) """ -function flattop_estimate!(bd::IMAS.build, eqt::IMAS.equilibrium__time_slice, cp1d::IMAS.core_profiles__profiles_1d; double_swing::Bool=true) +function flattop_duration!(bd::IMAS.build, eqt::IMAS.equilibrium__time_slice, cp1d::IMAS.core_profiles__profiles_1d; double_swing::Bool=true) OH = IMAS.get_build(bd, type=_oh_) innerSolenoidRadius = OH.start_radius outerSolenoidRadius = OH.end_radius @@ -70,8 +70,8 @@ function flattop_estimate!(bd::IMAS.build, eqt::IMAS.equilibrium__time_slice, cp # estimate oh flattop flux and duration RiRo_factor = innerSolenoidRadius / outerSolenoidRadius totalOhFlux = bd.oh.max_b_field * (pi * outerSolenoidRadius^2 * (RiRo_factor^2 + RiRo_factor + 1.0) * (double_swing ? 2 : 1)) / 3.0 - bd.flux_swing_estimates.flattop = totalOhFlux - bd.flux_swing_estimates.rampup - bd.flux_swing_estimates.pf - bd.oh.flattop_estimate = bd.flux_swing_estimates.flattop / abs(integrate(cp1d.grid.area, cp1d.j_ohmic ./ cp1d.conductivity_parallel)) + bd.flux_swing.flattop = totalOhFlux - bd.flux_swing.rampup - bd.flux_swing.pf + bd.oh.flattop_duration = bd.flux_swing.flattop / abs(integrate(cp1d.grid.area, cp1d.j_ohmic ./ cp1d.conductivity_parallel)) end #= ========= =# @@ -163,7 +163,7 @@ OH flux consumption based on: * vertical field from PF coils !!! note - Stores data in `dd.build.flux_swing_estimates`, `dd.build.tf`, and `dd.build.oh` + Stores data in `dd.build.flux_swing`, `dd.build.tf`, and `dd.build.oh` """ function ActorFluxSwing(dd::IMAS.dd, act::ParametersAllActors; kw...) @@ -192,25 +192,26 @@ The `only` parameter controls if :tf, :oh, or :all (both) should be calculated function step(actor::ActorFluxSwing; operate_at_j_crit::Bool=actor.operate_at_j_crit, j_tolerance::Real=actor.j_tolerance, only=:all) bd = actor.dd.build + target = actor.dd.target eq = actor.dd.equilibrium eqt = eq.time_slice[] cp = actor.dd.core_profiles cp1d = cp.profiles_1d[] if only ∈ [:all, :oh] - bd.flux_swing_estimates.rampup = rampup_flux_estimates(eqt, cp) - bd.flux_swing_estimates.pf = pf_flux_estimates(eqt) + bd.flux_swing.rampup = rampup_flux_estimates(eqt, cp) + bd.flux_swing.pf = pf_flux_estimates(eqt) if operate_at_j_crit oh_maximum_J_B!(bd; j_tolerance) - bd.flux_swing_estimates.flattop = flattop_flux_estimates(bd) # target flattop flux based on available current + bd.flux_swing.flattop = flattop_flux_estimates(bd) # flattop flux based on available current else - bd.flux_swing_estimates.flattop = flattop_flux_estimates(bd, cp1d) # target flattop flux based on target duration + bd.flux_swing.flattop = flattop_flux_estimates(target, cp1d) # flattop flux based on target duration oh_required_J_B!(bd) end - # estimate flattop duration - flattop_estimate!(bd, eqt, cp1d) + # flattop duration + flattop_duration!(bd, eqt, cp1d) end if only ∈ [:all, :tf] @@ -250,12 +251,12 @@ function rampup_flux_estimates(eqt::IMAS.equilibrium__time_slice, cp::IMAS.core_ end """ - flattop_flux_estimates(bd::IMAS.build, cp1d::IMAS.core_profiles__profiles_1d) + flattop_flux_estimates(target::IMAS.target, cp1d::IMAS.core_profiles__profiles_1d) Estimate OH flux requirement during flattop (if j_ohmic profile is missing then steady state ohmic profile is assumed) """ -function flattop_flux_estimates(bd::IMAS.build, cp1d::IMAS.core_profiles__profiles_1d) - return abs(integrate(cp1d.grid.area, cp1d.j_ohmic ./ cp1d.conductivity_parallel)) * bd.oh.flattop_duration # V*s +function flattop_flux_estimates(target::IMAS.target, cp1d::IMAS.core_profiles__profiles_1d) + return abs(integrate(cp1d.grid.area, cp1d.j_ohmic ./ cp1d.conductivity_parallel)) * target.flattop_duration # V*s end """ @@ -270,7 +271,7 @@ function flattop_flux_estimates(bd::IMAS.build; double_swing::Bool=true) magneticFieldSolenoidBore = bd.oh.max_b_field RiRo_factor = innerSolenoidRadius / outerSolenoidRadius totalOhFluxReq = magneticFieldSolenoidBore / 3.0 * pi * outerSolenoidRadius^2 * (RiRo_factor^2 + RiRo_factor + 1.0) * (double_swing ? 2 : 1) - bd.flux_swing_estimates.flattop = totalOhFluxReq - bd.flux_swing_estimates.rampup - bd.flux_swing_estimates.pf + bd.flux_swing.flattop = totalOhFluxReq - bd.flux_swing.rampup - bd.flux_swing.pf end """ @@ -607,8 +608,8 @@ function step( @show target_B0 @show dd.build.tf.max_b_field * TFhfs.end_radius / R0 - @show dd.build.oh.flattop_estimate @show dd.build.oh.flattop_duration + @show dd.target.flattop_duration @show dd.build.oh.max_j @show dd.build.oh.critical_j @@ -638,7 +639,7 @@ function step( @assert maximum(dd.solid_mechanics.center_stack.stress.vonmises.oh) < stainless_steel.yield_strength @assert maximum(dd.solid_mechanics.center_stack.stress.vonmises.tf) < stainless_steel.yield_strength if !unconstrained_flattop_duration - @assert rel_error(dd.build.oh.flattop_estimate, dd.build.oh.flattop_duration) < 0.1 "Relative error on flattop duration is more than 10% ($(dd.build.oh.flattop_estimate) --> $(dd.build.oh.flattop_duration))" + @assert rel_error(dd.build.oh.flattop_duration, dd.target.flattop_duration) < 0.1 "Relative error on flattop duration is more than 10% ($(dd.build.oh.flattop_duration) --> $(dd.target.flattop_duration))" end if fixed_aspect_ratio @assert rel_error(ϵ, old_ϵ) < 0.1 "ActorHFSsizing: plasma aspect ratio changed more than 10% ($old_ϵ --> $ϵ)" diff --git a/src/optimization.jl b/src/optimization.jl index c4a02cc81..269684486 100644 --- a/src/optimization.jl +++ b/src/optimization.jl @@ -48,8 +48,8 @@ const ObjectivesFunctionsLibrary = Dict{Symbol,ObjectiveFunction}() ObjectiveFunction(:min_cost, "\$M", dd -> dd.costing.cost, -Inf) ObjectiveFunction(:max_fusion, "MW", dd -> IMAS.fusion_power(dd.core_profiles.profiles_1d[]) / 1E6, Inf) ObjectiveFunction(:max_power_electric_net, "MW", dd -> @ddtime(dd.balance_of_plant.power_electric_net) / 1E6, Inf) -ObjectiveFunction(:max_flattop, "hours", dd -> dd.build.oh.flattop_estimate / 3600, Inf) -ObjectiveFunction(:max_log10_flattop, "log₁₀(hours)", dd -> log10(dd.build.oh.flattop_estimate / 3600), Inf) +ObjectiveFunction(:max_flattop, "hours", dd -> dd.build.oh.flattop_duration / 3600, Inf) +ObjectiveFunction(:max_log10_flattop, "log₁₀(hours)", dd -> log10(dd.build.oh.flattop_duration / 3600), Inf) function Base.show(io::IO, f::ObjectiveFunction) printstyled(io, f.name; bold=true, color=:blue) From 5221d03f4805ae137e5e29e2e6f4d9389bcc05df Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sun, 31 Jul 2022 16:44:07 -0700 Subject: [PATCH 37/74] blanket actor robust to missing wall/shield --- src/actors/blanket_actors.jl | 43 +++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/src/actors/blanket_actors.jl b/src/actors/blanket_actors.jl index 1f4331f9f..73e0e6174 100644 --- a/src/actors/blanket_actors.jl +++ b/src/actors/blanket_actors.jl @@ -47,6 +47,11 @@ end function step(actor::ActorBlanket) dd = actor.dd + if IMAS.get_build(dd.build, type=_blanket_, fs=_hfs_, raise_error_on_missing=false) === missing + empty!(dd.blanket) + return actor + end + eqt = dd.equilibrium.time_slice[] nnt = dd.neutronics.time_slice[] @@ -81,20 +86,35 @@ function step(actor::ActorBlanket) bmt.power_thermal_extracted = actor.thermal_power_extraction_efficiency * (bmt.power_thermal_neutrons + bmt.power_thermal_radiated) - # blanket layer structure + # blanket layer structure (designed to handle missing wall and/or missing shield) resize!(bm.layer, 3) - if sum(structure.outline.r) / length(structure.outline.r) < eqt.boundary.geometric_axis.r - d1 = IMAS.get_build(dd.build, type=_wall_, fs=_hfs_) + if sum(structure.outline.r) / length(structure.outline.r) < eqt.boundary.geometric_axis.r # HFS + d1 = IMAS.get_build(dd.build, type=_wall_, fs=_hfs_, raise_error_on_missing=false) + d1 = missing d2 = IMAS.get_build(dd.build, type=_blanket_, fs=_hfs_) - d3 = IMAS.get_build(dd.build, type=_shield_, fs=_hfs_, return_only_one=false)[end] - else + d3 = IMAS.get_build(dd.build, type=_shield_, fs=_hfs_, return_only_one=false, raise_error_on_missing=false) + if d3 !== missing + d3 = d3[end] + end + d3 = missing + else # LFS d1 = IMAS.get_build(dd.build, type=_wall_, fs=_lfs_) d2 = IMAS.get_build(dd.build, type=_blanket_, fs=_lfs_) - d3 = IMAS.get_build(dd.build, type=_shield_, fs=_lfs_, return_only_one=false)[1] + d3 = IMAS.get_build(dd.build, type=_shield_, fs=_lfs_, return_only_one=false, raise_error_on_missing=false) + if d3 !== missing + d3 = d3[1] + end + end + for (kl, dl) in enumerate(bm.layer) + dl.name = "dummy layer $kl" + dl.thickness = 0.0 + dl.material = "vacuum" end for (kl, dl) in enumerate([d1, d2, d3]) - for field in [:name, :thickness, :material] - setproperty!(bm.layer[kl], field, getproperty(dl, field)) + if dl !== missing + for field in [:name, :thickness, :material] + setproperty!(bm.layer[kl], field, getproperty(dl, field)) + end end end end @@ -104,7 +124,7 @@ function step(actor::ActorBlanket) total_tritium_breeding_ratio = 0.0 for bm in dd.blanket.module bmt = bm.time_slice[] - bm.layer[2].name = @sprintf("lithium-lead: Li6/7=%3.3f", Li6) + bm.layer[2].material = @sprintf("lithium-lead: Li6/7=%3.3f", Li6) bmt.tritium_breeding_ratio = NNeutronics.TBR(blanket_model, [dl.thickness for dl in bm.layer]..., Li6) total_tritium_breeding_ratio += bmt.tritium_breeding_ratio * bmt.power_incident_neutrons / total_power_neutrons end @@ -115,10 +135,11 @@ function step(actor::ActorBlanket) end end - target_tritium_breeding_ratio = 0.9 blanket_model_1d = NNeutronics.Blanket() - res = Optim.optimize(Li6 -> target_TBR(blanket_model_1d, Li6, dd, target_tritium_breeding_ratio), 0.0, 100.0, Optim.GoldenSection(), rel_tol=1E-3) + res = Optim.optimize(Li6 -> target_TBR(blanket_model_1d, Li6, dd, dd.target.tritium_breeding_ratio), 0.0, 100.0, Optim.GoldenSection(), rel_tol=1E-6) total_tritium_breeding_ratio = target_TBR(blanket_model_1d, res.minimizer, dd) @ddtime(dd.blanket.tritium_breeding_ratio = total_tritium_breeding_ratio) + + return actor end From 9791923ad816d450c1825b6c3738084c4fd92217 Mon Sep 17 00:00:00 2001 From: TimSlendebroek <32385057+TimSlendebroek@users.noreply.github.com> Date: Tue, 2 Aug 2022 16:19:15 -0700 Subject: [PATCH 38/74] add warmup function to FUSE useful at the start of a jupyter notebook such that everything will be fast after --- src/utils.jl | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/utils.jl b/src/utils.jl index ebe3833bb..c21619b22 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -204,3 +204,16 @@ function fuse() ╚═╝ ╚═════╝ ╚══════╝╚══════╝ """ end + +""" + warmup() + +Function to execute to precompile the majority of FUSE +""" +function warmup() + ini, act = FUSE.case_parameters(:FPP; version=:v1_demount, init_from=:scalars) + dd = IMAS.dd() + FUSE.init(dd, ini, act) + FUSE.ActorWholeFacility(dd,act) + IMAS.freeze(dd) +end \ No newline at end of file From c8a2f1f441520b44f260ac4198b935ac259fed49 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Thu, 4 Aug 2022 21:36:26 -0700 Subject: [PATCH 39/74] Neutron wall loading always starts on the outer midplane mention @kevinm387 --- src/actors/neutronics_actors.jl | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/actors/neutronics_actors.jl b/src/actors/neutronics_actors.jl index dc1fb91f4..3d49e9af8 100644 --- a/src/actors/neutronics_actors.jl +++ b/src/actors/neutronics_actors.jl @@ -1,3 +1,9 @@ +#= =============== =# +# ActorNeutronics # +#= =============== =# + +using MillerExtendedHarmonic + mutable struct neutron_particle x y @@ -15,10 +21,6 @@ function Zcoord(n::neutron_particle) n.z end -#= =============== =# -# ActorNeutronics # -#= =============== =# - mutable struct ActorNeutronics <: PlasmaAbstractActor dd::IMAS.dd par::ParametersActor @@ -86,14 +88,9 @@ function step(actor::ActorNeutronics; N::Integer=actor.par.N, step=actor.par.ste # resample wall and make sure it's clockwise (for COCOS = 11) wall = IMAS.first_wall(dd.wall) wall_r, wall_z = IMAS.resample_2d_line(wall.r, wall.z; step=0.1) - #wall_r, wall_z = deepcopy(wall.r), deepcopy(wall.z) R0 = eqt.global_quantities.magnetic_axis.r Z0 = eqt.global_quantities.magnetic_axis.z - θ = unwrap(atan.(wall_z .- Z0, wall_r .- R0)) - if θ[1] < θ[end] - reverse!(wall_r) - reverse!(wall_z) - end + MillerExtendedHarmonic.reorder_flux_surface!(wall_r, wall_z, R0, Z0) # advance neutrons until they hit the wall rz_wall = collect(zip(wall_r, wall_z)) From 619eeb9b2f3ec7afb7be08050d57cbee6587aee1 Mon Sep 17 00:00:00 2001 From: TimSlendebroek <32385057+TimSlendebroek@users.noreply.github.com> Date: Fri, 5 Aug 2022 14:25:51 -0700 Subject: [PATCH 40/74] change of costing structure cost functions as well, first commit --- src/actors/costing_actors.jl | 148 ++++++++++++++++++++++++++++++----- 1 file changed, 129 insertions(+), 19 deletions(-) diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index 71d9419f2..2f60a9f69 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -29,7 +29,7 @@ function unit_cost(material::String) end end -function cost(layer::IMAS.build__layer) +function cost_direct_capital(layer::IMAS.build__layer) if layer.type == Int(_oh_) build = IMAS.parent(IMAS.parent(layer)) return unit_cost(build.oh.technology) * layer.volume @@ -47,19 +47,19 @@ function cost(layer::IMAS.build__layer) end end -function cost(ecl::IMAS.ec_launchers__launcher) +function cost_direct_capital(ecl::IMAS.ec_launchers__launcher) ecl.available_launch_power / 1E6 * 3.0 # $/W #ARIES end -function cost(ica::IMAS.ic_antennas__antenna) +function cost_direct_capital(ica::IMAS.ic_antennas__antenna) ica.available_launch_power / 1E6 * 1.64 #$/W ARIES end -function cost(lha::IMAS.lh_antennas__antenna) +function cost_direct_capital(lha::IMAS.lh_antennas__antenna) lha.available_launch_power / 1E6 * 2.13 #$/W ARIES end -function cost(nbu::IMAS.nbi__unit) +function cost_direct_capital(nbu::IMAS.nbi__unit) nbu.available_launch_power / 1E6 * 4.93 #$/W ARIES end @@ -74,23 +74,75 @@ function unit_cost(coil_tech::Union{IMAS.build__tf__technology,IMAS.build__oh__t end end -function cost(pf_active::IMAS.pf_active) +function cost_direct_capital(pf_active::IMAS.pf_active) dd = IMAS.top_dd(pf_active) c = Dict("OH" => 0.0, "PF" => 0.0) for coil in pf_active.coil if coil.name == "OH" - c["OH"] += cost(coil, dd.build.oh.technology) + c["OH"] += cost_direct_capital(coil, dd.build.oh.technology) else - c["PF"] += cost(coil, dd.build.pf_active.technology) + c["PF"] += cost_direct_capital(coil, dd.build.pf_active.technology) end end return c end -function cost(coil::IMAS.pf_active__coil, technology::Union{IMAS.build__tf__technology,IMAS.build__oh__technology,IMAS.build__pf_active__technology}) +function cost_direct_capital(coil::IMAS.pf_active__coil, technology::Union{IMAS.build__tf__technology,IMAS.build__oh__technology,IMAS.build__pf_active__technology}) return IMAS.volume(coil) * unit_cost(technology) end +function cost_direct_capital(::Type{Val{:land}}, land::Real) + 1.2 * 27.0e3*4046.86 * (land) ^ 0.2 +end +# 140.0e3 volume default +function cost_direct_capital(::Type{Val{:buildings}}, building_volume::Real, power_electric_net::Real, power_thermal::Real) # ARIES + cost = 0. + cost += 111.661e6 * (building_volume / 80.0e3) ^ 0.62 # tokamak building + cost += 4.309e6 * (power_electric_net / 1000.0) ^ 0.3 # power core service building + cost += 1.513e6 * (power_electric_net / 1000.0) ^ 0.3 # service water + cost += 25.0e6 * (power_thermal / 1759.0) ^ 0.3 # fuel handling + cost += 7.11e6 # control room + cost += 2.0e6 # site service + cost += 2.0e6 # administrative + cost += 2.09e6 # cyrogenic and inert gas storage + cost += 0.71e6 # security + cost += 22.878e6 * (power_electric_net / 1000.0) ^ 0.3 # service building + cost += 4.7e6 * (power_electric_net / 1000.0) ^ 0.3 + 4.15e6 # On-site AC Power Supply and ventilation + return cost +end + +function cost_direct_capital(::Type{Val{:turbine}},power_electric_generated::Real) + 78.9e6 * (power_electric_generated / 1246) ^ 0.5 +end + +function cost_direct_capital(::Type{Val{:heat_rejection}}, power_electric_net, power_thermal) + 16.804e6 * ((power_thermal - power_electric_net) / 1860.0) ^ 0.5 +end + +function cost_direct_capital(::Type{Val{:electrical_equipment}}, power_electric_net) + 22.878e6 * (power_electric_net / 1000.0) ^ 0.3 +end + +function cost_operations(::Type{Val{:operation_maintanance}}, power_electric_net) + 80.0e6 * (power_electric_net / 1200.0) ^ 0.5 +end + +function cost_operations(::Type{Val{:fuel}}) + 1.0e6 +end + +function cost_operations(::Type{Val{:blanket_replacement}},cost_blanket) # find blanket and replace every x-years + cost_blanket * 1.2 +end + +function cost_decomissioning(::Type{Val{:hot_cell}},building_volume) # https://www.iter.org/mach/HotCell + 0.4 * 111.661e6 * (building_volume / 80.0e3) ^ 0.62 +end + +function cost_decomissioning(::Type{Val{:decom_wild_guess}}) + 2.76e6 # gasc comment needs revisiting +end + #= ============ =# # ActorCosting # #= ============ =# @@ -106,6 +158,8 @@ end function ParametersActor(::Type{Val{:ActorCosting}}) par = ParametersActor(nothing) + par.land_space = Entry(Real, "m^2","Plant site space required in m²";default=4.047e6) + par.building_volume = Entry(Real, "m^3", "Volume of the tokmak building"; default=140.0e3) return par end @@ -126,46 +180,102 @@ function ActorCosting(dd::IMAS.dd, act::ParametersAllActors; kw...) end function step(actor::ActorCosting) + par = actor.par dd = actor.dd cst = dd.costing - empty!(cst) + cost_direct = cst.cost_direct_capital + cost_ops = cst.cost_operations + cost_decom = cst.cost_decommissioning + + ###### Direct Capital ###### - # TOKAMAK - sys = resize!(cst.system, "name" => "tokamak") + empty!(cost_direct) + + ### Tokamak + + # build layers + sys = resize!(cost_direct.system, "name" => "tokamak") for layer in dd.build.layer if layer.fs == Int(_lfs_) continue # avoid double counting of hfs and lfs layers elseif layer.type == Int(_oh_) continue # avoid double counting of oh end - c = cost(layer) + c = cost_direct_capital(layer) if c > 0 sub = resize!(sys.subsystem, "name" => replace(layer.name, r"^hfs " => "")) sub.cost = c end end - for (name, c) in cost(dd.pf_active) + + # PF coils + for (name, c) in cost_direct_capital(dd.pf_active) sub = resize!(sys.subsystem, "name" => name) sub.cost = c end - # HCD - sys = resize!(cst.system, "name" => "hcd") + # Heating and current drive for hcd in vcat(dd.ec_launchers.launcher, dd.ic_antennas.antenna, dd.lh_antennas.antenna, dd.nbi.unit) - c = cost(hcd) + c = cost_direct_capital(hcd) if c > 0 sub = resize!(sys.subsystem, "name" => hcd.name) sub.cost = c end end + ### Facility + sys = resize!(cost_direct.system, "name" => "facility") + + if @ddtime(dd.balance_of_plant.power_electric_net) < 0 + @warn("The plant doesn't generate net electricity therefore costing excludes facility estimates") + else + power_electric_net = @ddtime(dd.balance_of_plant.power_electric_net) # should be pulse average + power_thermal = sum([maximum(sys.power_in) for sys in dd.balance_of_plant.thermal_cycle.system]) # should be pulse average + for item in vcat(:land, :buildings, :turbine, :heat_rejection, :electrical_equipment) + resize!(sys.subsystem, "name" => string(item)) + if item == :land + c = cost_direct_capital(Val{item}, par.land_space) + elseif item == :buildings + c = cost_direct_capital(Val{item}, par.building_volume, power_electric_net, power_thermal) + elseif item == :turbine + c = cost_direct_capital(Val{item}, power_electric_generated) + elseif item == :heat_rejection + c = cost_direct_capital(Val{item}, power_electric_net, power_thermal) + elseif item == :electrical_equipment + c = cost_direct_capital(Val{item}, power_electric_net) + else + c = cost_direct_capital(Val{item}) + end + @show c(item) + sub.cost = c(item) + end + end + + """ + ### Operations cost (yearly costs) + sys = resize!(cost_ops.system, "name" => "maintanance and operatorss") + + # Fuel Cycle + sys = resize!(cost_ops.system, "name" => "fuel cycle") + + ###### Decomissioning ###### + + # Radioactive waste treatment? + sys = resize!(cost_decom.system, "name" => "radioactive waste treatment") + + # Demolition + sys = resize!(cost_decom.system, "name" => "demolition") + """ + display(cst) + display(cst.cost_direct_capital.system[2]) + return actor end function finalize(actor::ActorCosting) # sort system/subsystem costs - sort!(actor.dd.costing.system, by=x -> x.cost, rev=true) - for sys in actor.dd.costing.system + sort!(actor.dd.costing.cost_direct_capital.system, by=x -> x.cost, rev=true) + for sys in actor.dd.dd.costing.cost_direct_capital.system sort!(sys.subsystem, by=x -> x.cost, rev=true) end end \ No newline at end of file From 32b1a812d72f5577639c3717bee042cb4940724f Mon Sep 17 00:00:00 2001 From: TimSlendebroek <32385057+TimSlendebroek@users.noreply.github.com> Date: Fri, 5 Aug 2022 15:13:06 -0700 Subject: [PATCH 41/74] dispatch issue resolved I know there is no need to make a dispatch for every function but symmetry this will change anyway when the cost_direct_captial(IDS_loc) --- src/actors/costing_actors.jl | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index 2f60a9f69..cc8a13500 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -94,7 +94,8 @@ end function cost_direct_capital(::Type{Val{:land}}, land::Real) 1.2 * 27.0e3*4046.86 * (land) ^ 0.2 end -# 140.0e3 volume default +cost_direct_capital(item::Symbol, land) = cost_operations(Val{item}, land) + function cost_direct_capital(::Type{Val{:buildings}}, building_volume::Real, power_electric_net::Real, power_thermal::Real) # ARIES cost = 0. cost += 111.661e6 * (building_volume / 80.0e3) ^ 0.62 # tokamak building @@ -110,26 +111,32 @@ function cost_direct_capital(::Type{Val{:buildings}}, building_volume::Real, pow cost += 4.7e6 * (power_electric_net / 1000.0) ^ 0.3 + 4.15e6 # On-site AC Power Supply and ventilation return cost end +cost_direct_capital(item::Symbol, building_volume, power_electric_net, power_thermal) = cost_operations(Val{item}, building_volume, power_electric_net, power_thermal) function cost_direct_capital(::Type{Val{:turbine}},power_electric_generated::Real) 78.9e6 * (power_electric_generated / 1246) ^ 0.5 end +cost_direct_capital(item::Symbol, power_electric_generated) = cost_operations(Val{item}, power_electric_generated) function cost_direct_capital(::Type{Val{:heat_rejection}}, power_electric_net, power_thermal) 16.804e6 * ((power_thermal - power_electric_net) / 1860.0) ^ 0.5 end +cost_direct_capital(item::Symbol, power_electric_net, power_thermal) = cost_operations(Val{item}, power_electric_net, power_thermal) function cost_direct_capital(::Type{Val{:electrical_equipment}}, power_electric_net) 22.878e6 * (power_electric_net / 1000.0) ^ 0.3 end +cost_direct_capital(item::Symbol, power_electric_net) = cost_operations(Val{item}, power_electric_net) function cost_operations(::Type{Val{:operation_maintanance}}, power_electric_net) 80.0e6 * (power_electric_net / 1200.0) ^ 0.5 end +cost_direct_capital(item::Symbol, power_electric_net) = cost_operations(Val{item}, power_electric_net) function cost_operations(::Type{Val{:fuel}}) 1.0e6 end +cost_direct_capital(item::Symbol, power_electric_net) = cost_operations(Val{item}, power_electric_net) function cost_operations(::Type{Val{:blanket_replacement}},cost_blanket) # find blanket and replace every x-years cost_blanket * 1.2 @@ -234,20 +241,20 @@ function step(actor::ActorCosting) for item in vcat(:land, :buildings, :turbine, :heat_rejection, :electrical_equipment) resize!(sys.subsystem, "name" => string(item)) if item == :land - c = cost_direct_capital(Val{item}, par.land_space) + sub.cost = cost_direct_capital(item, par.land_space) elseif item == :buildings - c = cost_direct_capital(Val{item}, par.building_volume, power_electric_net, power_thermal) + sub.cost = cost_direct_capital(item, par.building_volume, power_electric_net, power_thermal) elseif item == :turbine - c = cost_direct_capital(Val{item}, power_electric_generated) + sub.cost = cost_direct_capital(item, power_electric_generated) elseif item == :heat_rejection - c = cost_direct_capital(Val{item}, power_electric_net, power_thermal) + sub.cost = cost_direct_capital(item, power_electric_net, power_thermal) elseif item == :electrical_equipment - c = cost_direct_capital(Val{item}, power_electric_net) + sub.cost = cost_direct_capital(item, power_electric_net) else - c = cost_direct_capital(Val{item}) + sub.cost = cost_direct_capital(item) end - @show c(item) - sub.cost = c(item) +# @show c(item) +# end end From 67e7d45a3699edc06d1329ec37fc0597175a9331 Mon Sep 17 00:00:00 2001 From: TimSlendebroek <32385057+TimSlendebroek@users.noreply.github.com> Date: Fri, 5 Aug 2022 15:24:55 -0700 Subject: [PATCH 42/74] units (M $ and MW) --- src/actors/costing_actors.jl | 59 ++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index cc8a13500..36f4c3e82 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -92,55 +92,56 @@ function cost_direct_capital(coil::IMAS.pf_active__coil, technology::Union{IMAS. end function cost_direct_capital(::Type{Val{:land}}, land::Real) - 1.2 * 27.0e3*4046.86 * (land) ^ 0.2 + 1.2 * 27.0e-3 * 4046.86 * (land) ^ 0.2 end -cost_direct_capital(item::Symbol, land) = cost_operations(Val{item}, land) +cost_direct_capital(item::Symbol, land) = cost_direct_capital(Val{item}, land) function cost_direct_capital(::Type{Val{:buildings}}, building_volume::Real, power_electric_net::Real, power_thermal::Real) # ARIES cost = 0. - cost += 111.661e6 * (building_volume / 80.0e3) ^ 0.62 # tokamak building - cost += 4.309e6 * (power_electric_net / 1000.0) ^ 0.3 # power core service building - cost += 1.513e6 * (power_electric_net / 1000.0) ^ 0.3 # service water - cost += 25.0e6 * (power_thermal / 1759.0) ^ 0.3 # fuel handling - cost += 7.11e6 # control room - cost += 2.0e6 # site service - cost += 2.0e6 # administrative - cost += 2.09e6 # cyrogenic and inert gas storage - cost += 0.71e6 # security - cost += 22.878e6 * (power_electric_net / 1000.0) ^ 0.3 # service building - cost += 4.7e6 * (power_electric_net / 1000.0) ^ 0.3 + 4.15e6 # On-site AC Power Supply and ventilation + cost += 111.661 * (building_volume / 80.0e3) ^ 0.62 # tokamak building + cost += 4.309e6 * (power_electric_net / 1000.0e6) ^ 0.3 # power core service building + cost += 1.513 * (power_electric_net / 1000.0e6) ^ 0.3 # service water + cost += 25.0 * (power_thermal / 1759.0e6) ^ 0.3 # fuel handling + cost += 7.11 # control room + cost += 2.0 # site service + cost += 2.0 # administrative + cost += 2.09 # cyrogenic and inert gas storage + cost += 0.71 # security + cost += 22.878 * (power_electric_net / 1000.0) ^ 0.3 # service building + cost += 4.7 * (power_electric_net / 1000.0e6) ^ 0.3 + 4.15 # On-site AC Power Supply and ventilation return cost end -cost_direct_capital(item::Symbol, building_volume, power_electric_net, power_thermal) = cost_operations(Val{item}, building_volume, power_electric_net, power_thermal) +cost_direct_capital(item::Symbol, building_volume, power_electric_net, power_thermal) = cost_direct_capital(Val{item}, building_volume, power_electric_net, power_thermal) function cost_direct_capital(::Type{Val{:turbine}},power_electric_generated::Real) - 78.9e6 * (power_electric_generated / 1246) ^ 0.5 + 78.9 * (power_electric_generated / 1246e6) ^ 0.5 end -cost_direct_capital(item::Symbol, power_electric_generated) = cost_operations(Val{item}, power_electric_generated) +cost_direct_capital(item::Symbol, power_electric_generated) = cost_direct_capital(Val{item}, power_electric_generated) function cost_direct_capital(::Type{Val{:heat_rejection}}, power_electric_net, power_thermal) - 16.804e6 * ((power_thermal - power_electric_net) / 1860.0) ^ 0.5 + 16.804 * ((power_thermal - power_electric_net) / 1860.0) ^ 0.5 end -cost_direct_capital(item::Symbol, power_electric_net, power_thermal) = cost_operations(Val{item}, power_electric_net, power_thermal) +cost_direct_capital(item::Symbol, power_electric_net, power_thermal) = cost_direct_capital(Val{item}, power_electric_net, power_thermal) function cost_direct_capital(::Type{Val{:electrical_equipment}}, power_electric_net) - 22.878e6 * (power_electric_net / 1000.0) ^ 0.3 + 22.878 * (power_electric_net / 1000.0e6) ^ 0.3 end -cost_direct_capital(item::Symbol, power_electric_net) = cost_operations(Val{item}, power_electric_net) +cost_direct_capital(item::Symbol, power_electric_net) = cost_direct_capital(Val{item}, power_electric_net) function cost_operations(::Type{Val{:operation_maintanance}}, power_electric_net) - 80.0e6 * (power_electric_net / 1200.0) ^ 0.5 + 80.0 * (power_electric_net / 1200.0e6) ^ 0.5 end -cost_direct_capital(item::Symbol, power_electric_net) = cost_operations(Val{item}, power_electric_net) +cost_operations(item::Symbol, power_electric_net) = cost_operations(Val{item}, power_electric_net) function cost_operations(::Type{Val{:fuel}}) - 1.0e6 + 1.0 end -cost_direct_capital(item::Symbol, power_electric_net) = cost_operations(Val{item}, power_electric_net) +cost_operations(item::Symbol, power_electric_net) = cost_operations(Val{item}, power_electric_net) function cost_operations(::Type{Val{:blanket_replacement}},cost_blanket) # find blanket and replace every x-years cost_blanket * 1.2 end +cost_operations(item::Symbol, cost_blanket) = cost_operations(Val{item}, cost_blanket) function cost_decomissioning(::Type{Val{:hot_cell}},building_volume) # https://www.iter.org/mach/HotCell 0.4 * 111.661e6 * (building_volume / 80.0e3) ^ 0.62 @@ -149,6 +150,7 @@ end function cost_decomissioning(::Type{Val{:decom_wild_guess}}) 2.76e6 # gasc comment needs revisiting end +cost_decomissioning(item::Symbol) = cost_decomissioning(Val{item}) #= ============ =# # ActorCosting # @@ -238,8 +240,9 @@ function step(actor::ActorCosting) else power_electric_net = @ddtime(dd.balance_of_plant.power_electric_net) # should be pulse average power_thermal = sum([maximum(sys.power_in) for sys in dd.balance_of_plant.thermal_cycle.system]) # should be pulse average + power_electric_generated = @ddtime(dd.balance_of_plant.thermal_cycle.power_electric_generated) for item in vcat(:land, :buildings, :turbine, :heat_rejection, :electrical_equipment) - resize!(sys.subsystem, "name" => string(item)) + sub = resize!(sys.subsystem, "name" => string(item)) if item == :land sub.cost = cost_direct_capital(item, par.land_space) elseif item == :buildings @@ -253,14 +256,12 @@ function step(actor::ActorCosting) else sub.cost = cost_direct_capital(item) end -# @show c(item) -# end end """ ### Operations cost (yearly costs) - sys = resize!(cost_ops.system, "name" => "maintanance and operatorss") + sys = resize!(cost_ops.system, "name" => "yearly costs") # Fuel Cycle sys = resize!(cost_ops.system, "name" => "fuel cycle") @@ -282,7 +283,7 @@ end function finalize(actor::ActorCosting) # sort system/subsystem costs sort!(actor.dd.costing.cost_direct_capital.system, by=x -> x.cost, rev=true) - for sys in actor.dd.dd.costing.cost_direct_capital.system + for sys in actor.dd.costing.cost_direct_capital.system sort!(sys.subsystem, by=x -> x.cost, rev=true) end end \ No newline at end of file From 8e6bac12366f89d5860ceaa9813f9be36ed33a90 Mon Sep 17 00:00:00 2001 From: TimSlendebroek <32385057+TimSlendebroek@users.noreply.github.com> Date: Fri, 5 Aug 2022 15:49:51 -0700 Subject: [PATCH 43/74] concede to acres and MW exponents are annoying --- src/actors/costing_actors.jl | 45 ++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index 36f4c3e82..047ce909f 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -91,30 +91,30 @@ function cost_direct_capital(coil::IMAS.pf_active__coil, technology::Union{IMAS. return IMAS.volume(coil) * unit_cost(technology) end -function cost_direct_capital(::Type{Val{:land}}, land::Real) - 1.2 * 27.0e-3 * 4046.86 * (land) ^ 0.2 +function cost_direct_capital(::Type{Val{:land}}, land::Real, power_electric_net::Real) + 1.2 * land * 20.0e-3 * (power_electric_net / 1000.0) ^ 0.3 end -cost_direct_capital(item::Symbol, land) = cost_direct_capital(Val{item}, land) +cost_direct_capital(item::Symbol, land, power_electric_net) = cost_direct_capital(Val{item}, land, power_electric_net) -function cost_direct_capital(::Type{Val{:buildings}}, building_volume::Real, power_electric_net::Real, power_thermal::Real) # ARIES - cost = 0. +function cost_direct_capital(::Type{Val{:buildings}},land::Real, building_volume::Real, power_electric_net::Real, power_thermal::Real) # ARIES + cost = 27.0 * (land / 1000.0)^0.2 cost += 111.661 * (building_volume / 80.0e3) ^ 0.62 # tokamak building - cost += 4.309e6 * (power_electric_net / 1000.0e6) ^ 0.3 # power core service building - cost += 1.513 * (power_electric_net / 1000.0e6) ^ 0.3 # service water - cost += 25.0 * (power_thermal / 1759.0e6) ^ 0.3 # fuel handling + cost += 4.309 * (power_electric_net / 1000.0) ^ 0.3 # power core service building + cost += 1.513 * (power_electric_net / 1000.0) ^ 0.3 # service water + cost += 25.0 * (power_thermal / 1759.0) ^ 0.3 # fuel handling cost += 7.11 # control room cost += 2.0 # site service cost += 2.0 # administrative cost += 2.09 # cyrogenic and inert gas storage cost += 0.71 # security cost += 22.878 * (power_electric_net / 1000.0) ^ 0.3 # service building - cost += 4.7 * (power_electric_net / 1000.0e6) ^ 0.3 + 4.15 # On-site AC Power Supply and ventilation + cost += 4.7 * (power_electric_net / 1000.0) ^ 0.3 + 4.15 # On-site AC Power Supply and ventilation return cost end -cost_direct_capital(item::Symbol, building_volume, power_electric_net, power_thermal) = cost_direct_capital(Val{item}, building_volume, power_electric_net, power_thermal) +cost_direct_capital(item::Symbol, building_volume,land, power_electric_net, power_thermal) = cost_direct_capital(Val{item}, building_volume, land, power_electric_net, power_thermal) function cost_direct_capital(::Type{Val{:turbine}},power_electric_generated::Real) - 78.9 * (power_electric_generated / 1246e6) ^ 0.5 + 78.9 * (power_electric_generated / 1246) ^ 0.5 end cost_direct_capital(item::Symbol, power_electric_generated) = cost_direct_capital(Val{item}, power_electric_generated) @@ -124,12 +124,12 @@ end cost_direct_capital(item::Symbol, power_electric_net, power_thermal) = cost_direct_capital(Val{item}, power_electric_net, power_thermal) function cost_direct_capital(::Type{Val{:electrical_equipment}}, power_electric_net) - 22.878 * (power_electric_net / 1000.0e6) ^ 0.3 + 22.878 * (power_electric_net / 1000.0) ^ 0.3 end cost_direct_capital(item::Symbol, power_electric_net) = cost_direct_capital(Val{item}, power_electric_net) function cost_operations(::Type{Val{:operation_maintanance}}, power_electric_net) - 80.0 * (power_electric_net / 1200.0e6) ^ 0.5 + 80.0 * (power_electric_net / 1200.0) ^ 0.5 end cost_operations(item::Symbol, power_electric_net) = cost_operations(Val{item}, power_electric_net) @@ -144,7 +144,7 @@ end cost_operations(item::Symbol, cost_blanket) = cost_operations(Val{item}, cost_blanket) function cost_decomissioning(::Type{Val{:hot_cell}},building_volume) # https://www.iter.org/mach/HotCell - 0.4 * 111.661e6 * (building_volume / 80.0e3) ^ 0.62 + 0.4 * 111.661 * (building_volume / 80.0e3) ^ 0.62 end function cost_decomissioning(::Type{Val{:decom_wild_guess}}) @@ -167,7 +167,7 @@ end function ParametersActor(::Type{Val{:ActorCosting}}) par = ParametersActor(nothing) - par.land_space = Entry(Real, "m^2","Plant site space required in m²";default=4.047e6) + par.land_space = Entry(Real, "m^2","Plant site space required in m²";default=1000.) par.building_volume = Entry(Real, "m^3", "Volume of the tokmak building"; default=140.0e3) return par end @@ -238,15 +238,15 @@ function step(actor::ActorCosting) if @ddtime(dd.balance_of_plant.power_electric_net) < 0 @warn("The plant doesn't generate net electricity therefore costing excludes facility estimates") else - power_electric_net = @ddtime(dd.balance_of_plant.power_electric_net) # should be pulse average - power_thermal = sum([maximum(sys.power_in) for sys in dd.balance_of_plant.thermal_cycle.system]) # should be pulse average - power_electric_generated = @ddtime(dd.balance_of_plant.thermal_cycle.power_electric_generated) + power_electric_net = @ddtime(dd.balance_of_plant.power_electric_net) / 1e6 # should be pulse average + power_thermal = sum([maximum(sys.power_in) for sys in dd.balance_of_plant.thermal_cycle.system]) / 1e6 # should be pulse average + power_electric_generated = @ddtime(dd.balance_of_plant.thermal_cycle.power_electric_generated) / 1e6 for item in vcat(:land, :buildings, :turbine, :heat_rejection, :electrical_equipment) sub = resize!(sys.subsystem, "name" => string(item)) if item == :land - sub.cost = cost_direct_capital(item, par.land_space) + sub.cost = cost_direct_capital(item, par.land_space, power_electric_net) elseif item == :buildings - sub.cost = cost_direct_capital(item, par.building_volume, power_electric_net, power_thermal) + sub.cost = cost_direct_capital(item, par.building_volume, par.land_space, power_electric_net, power_thermal) elseif item == :turbine sub.cost = cost_direct_capital(item, power_electric_generated) elseif item == :heat_rejection @@ -260,9 +260,6 @@ function step(actor::ActorCosting) end """ - ### Operations cost (yearly costs) - sys = resize!(cost_ops.system, "name" => "yearly costs") - # Fuel Cycle sys = resize!(cost_ops.system, "name" => "fuel cycle") @@ -274,8 +271,6 @@ function step(actor::ActorCosting) # Demolition sys = resize!(cost_decom.system, "name" => "demolition") """ - display(cst) - display(cst.cost_direct_capital.system[2]) return actor end From 681039619235f51bb3d60ef3cb05ec441838bd69 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Fri, 5 Aug 2022 23:37:21 -0700 Subject: [PATCH 44/74] Fix error on InexistentParameterException --- src/parameters.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/parameters.jl b/src/parameters.jl index a330777c7..fe8afd997 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -254,7 +254,7 @@ function ParametersAllActors() setproperty!(act, par, ParametersActor(par)) catch e if typeof(e) <: InexistentParameterException - @warn e + @warn sprint(showerror, e) else rethrow() end @@ -542,7 +542,7 @@ struct InexistentParameterException <: Exception parameter_type::DataType path::Vector{Symbol} end -Base.showerror(io::IO, e::InexistentParameterException) = print(io, "ERROR: $(e.parameter_type) $(join(e.path,".")) does not exist") +Base.showerror(io::IO, e::InexistentParameterException) = print(io, "$(e.parameter_type).$(join(e.path,".")) does not exist") struct NotsetParameterException <: Exception path::Vector{Symbol} @@ -551,9 +551,9 @@ end NotsetParameterException(path::Vector{Symbol}) = NotsetParameterException(path, []) function Base.showerror(io::IO, e::NotsetParameterException) if length(e.options) > 0 - print(io, "ERROR: Parameter $(join(e.path,".")) is not set. Valid options are: $(join(map(repr,e.options),", "))") + print(io, "Parameter $(join(e.path,".")) is not set. Valid options are: $(join(map(repr,e.options),", "))") else - print(io, "ERROR: Parameter $(join(e.path,".")) is not set") + print(io, "Parameter $(join(e.path,".")) is not set") end end @@ -563,7 +563,7 @@ struct BadParameterException <: Exception options::Vector{Any} end Base.showerror(io::IO, e::BadParameterException) = - print(io, "ERROR: Parameter $(join(e.path,".")) = $(repr(e.value)) is not one of the valid options: $(join(map(repr,e.options),", "))") + print(io, "Parameter $(join(e.path,".")) = $(repr(e.value)) is not one of the valid options: $(join(map(repr,e.options),", "))") #= ============ =# # case studies # From 70d5e4d40e215e790a56cb3cc7226c492c71ebf9 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Fri, 5 Aug 2022 23:39:03 -0700 Subject: [PATCH 45/74] fill out pf_passive mention #129 --- src/FUSE.jl | 1 + src/actors/build_actors.jl | 2 + src/actors/neutronics_actors.jl | 2 +- src/actors/pf_passive_actors.jl | 68 +++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/actors/pf_passive_actors.jl diff --git a/src/FUSE.jl b/src/FUSE.jl index d9a73b454..4f2363c76 100644 --- a/src/FUSE.jl +++ b/src/FUSE.jl @@ -43,6 +43,7 @@ include(joinpath("ddinit", "gasc.jl")) include(joinpath("actors", "abstract_actors.jl")) include(joinpath("actors", "equilibrium_actors.jl")) include(joinpath("actors", "pf_active_actors.jl")) +include(joinpath("actors", "pf_passive_actors.jl")) include(joinpath("actors", "stresses_actors.jl")) include(joinpath("actors", "build_actors.jl")) include(joinpath("actors", "blanket_actors.jl")) diff --git a/src/actors/build_actors.jl b/src/actors/build_actors.jl index 585c1eb85..68dc381b9 100644 --- a/src/actors/build_actors.jl +++ b/src/actors/build_actors.jl @@ -1120,6 +1120,8 @@ function optimize_shape(bd::IMAS.build, obstr_index::Int, layer_index::Int, shap layer.shape_parameters = optimize_shape(oR, oZ, target_minimum_distance, func, l_start, l_end, layer.shape_parameters) layer.outline.r, layer.outline.z = func(l_start, l_end, layer.shape_parameters...; resample=false) end + + IMAS.reorder_flux_surface!(layer.outline.r, layer.outline.z) # display(plot!(layer.outline.r, layer.outline.z)) end diff --git a/src/actors/neutronics_actors.jl b/src/actors/neutronics_actors.jl index 3d49e9af8..dbc18d683 100644 --- a/src/actors/neutronics_actors.jl +++ b/src/actors/neutronics_actors.jl @@ -90,7 +90,7 @@ function step(actor::ActorNeutronics; N::Integer=actor.par.N, step=actor.par.ste wall_r, wall_z = IMAS.resample_2d_line(wall.r, wall.z; step=0.1) R0 = eqt.global_quantities.magnetic_axis.r Z0 = eqt.global_quantities.magnetic_axis.z - MillerExtendedHarmonic.reorder_flux_surface!(wall_r, wall_z, R0, Z0) + IMAS.reorder_flux_surface!(wall_r, wall_z, R0, Z0) # advance neutrons until they hit the wall rz_wall = collect(zip(wall_r, wall_z)) diff --git a/src/actors/pf_passive_actors.jl b/src/actors/pf_passive_actors.jl new file mode 100644 index 000000000..664ab19fd --- /dev/null +++ b/src/actors/pf_passive_actors.jl @@ -0,0 +1,68 @@ +#= ========== =# +# PF passive # +#= ========== =# + +mutable struct ActorPassiveStructures <: ReactorAbstractActor + dd::IMAS.dd + par::ParametersActor +end + +function ParametersActor(::Type{Val{:ActorPassiveStructures}}) + par = ParametersActor(nothing) + return par +end + +""" + ActorPassiveStructures(dd::IMAS.dd, act::ParametersAllActors; kw...) + +Populates `pf_passive` structures +""" +function ActorPassiveStructures(dd::IMAS.dd, act::ParametersAllActors; kw...) + par = act.ActorPassiveStructures(kw...) + actor = ActorPassiveStructures(dd, par) + step(actor) + finalize(actor) + return actor +end + +function ActorPassiveStructures(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + ActorPassiveStructures(dd, par) +end + +function step(actor::ActorPassiveStructures) + dd = actor.dd + + for k in IMAS.get_build(dd.build, fs=IMAS._lfs_, return_only_one=false, return_index=true) + l = dd.build.layer[k] + l1 = dd.build.layer[k+1] + if l1.material == "Vacuum" + continue + end + poly = IMAS.join_outlines(l.outline.r, l.outline.z, l1.outline.r, l1.outline.z) + add_pf_passive_loop(dd.pf_passive, l1, poly[1], poly[2]) + end + + add_pf_passive_loop(dd.pf_passive, dd.build.layer[2], dd.build.layer[2].outline.r, dd.build.layer[2].outline.z) + + if dd.build.layer[1].material != "Vacuum" + add_pf_passive_loop(dd.pf_passive, dd.build.layer[1], dd.build.layer[1].outline.r, dd.build.layer[1].outline.z) + end + + layer = dd.build.layer[end] + i = findfirst(layer.outline.r .== 0) + r = [layer.outline.r[i+1:end-1]; layer.outline.r[1:i]] + z = [layer.outline.z[i+1:end-1]; layer.outline.z[1:i]] + layer = dd.build.layer[end-1] + i = findfirst(layer.outline.r .== 0) + r = [layer.outline.r[i+1:end-1]; layer.outline.r[1:i]; reverse(r); layer.outline.r[i+1:i+1]] + z = [layer.outline.z[i+1:end-1]; layer.outline.z[1:i]; reverse(z); layer.outline.z[i+1:i+1]] + add_pf_passive_loop(dd.pf_passive, dd.build.layer[end], r, z) +end + +function add_pf_passive_loop(pf_passive::IMAS.pf_passive, layer::IMAS.build__layer, r::AbstractVector{T}, z::AbstractVector{T}) where {T<:Real} + resize!(resize!(pf_passive.loop, length(pf_passive.loop) + 1)[end].element, 1) + pf_passive.loop[end].name = replace(layer.name, r"[lh]fs " => "") + pf_passive.loop[end].element[end].geometry.outline.r = r + pf_passive.loop[end].element[end].geometry.outline.z = z +end \ No newline at end of file From 016145593ad8daa48a32f6c49a1578c30486d9d7 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Fri, 5 Aug 2022 23:52:03 -0700 Subject: [PATCH 46/74] Fix missing wall layer --- src/actors/pf_passive_actors.jl | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/actors/pf_passive_actors.jl b/src/actors/pf_passive_actors.jl index 664ab19fd..12396d5bf 100644 --- a/src/actors/pf_passive_actors.jl +++ b/src/actors/pf_passive_actors.jl @@ -9,6 +9,7 @@ end function ParametersActor(::Type{Val{:ActorPassiveStructures}}) par = ParametersActor(nothing) + par.do_plot = Entry(Bool, "", "plot"; default=false) return par end @@ -22,6 +23,9 @@ function ActorPassiveStructures(dd::IMAS.dd, act::ParametersAllActors; kw...) actor = ActorPassiveStructures(dd, par) step(actor) finalize(actor) + if par.do_plot + display(plot(dd.pf_passive)) + end return actor end @@ -33,7 +37,10 @@ end function step(actor::ActorPassiveStructures) dd = actor.dd - for k in IMAS.get_build(dd.build, fs=IMAS._lfs_, return_only_one=false, return_index=true) + # all LFS + ilayers = IMAS.get_build(dd.build, fs=IMAS._lfs_, return_only_one=false, return_index=true) + ilayers = vcat(ilayers[1] - 1, ilayers) + for k in ilayers l = dd.build.layer[k] l1 = dd.build.layer[k+1] if l1.material == "Vacuum" @@ -43,21 +50,24 @@ function step(actor::ActorPassiveStructures) add_pf_passive_loop(dd.pf_passive, l1, poly[1], poly[2]) end + # OH and plug add_pf_passive_loop(dd.pf_passive, dd.build.layer[2], dd.build.layer[2].outline.r, dd.build.layer[2].outline.z) - if dd.build.layer[1].material != "Vacuum" add_pf_passive_loop(dd.pf_passive, dd.build.layer[1], dd.build.layer[1].outline.r, dd.build.layer[1].outline.z) end + # cryostat layer = dd.build.layer[end] - i = findfirst(layer.outline.r .== 0) - r = [layer.outline.r[i+1:end-1]; layer.outline.r[1:i]] - z = [layer.outline.z[i+1:end-1]; layer.outline.z[1:i]] - layer = dd.build.layer[end-1] - i = findfirst(layer.outline.r .== 0) - r = [layer.outline.r[i+1:end-1]; layer.outline.r[1:i]; reverse(r); layer.outline.r[i+1:i+1]] - z = [layer.outline.z[i+1:end-1]; layer.outline.z[1:i]; reverse(z); layer.outline.z[i+1:i+1]] - add_pf_passive_loop(dd.pf_passive, dd.build.layer[end], r, z) + if layer.type == Int(IMAS._cryostat_) + i = findfirst(layer.outline.r .== 0) + r = vcat(layer.outline.r[i+1:end-1], layer.outline.r[1:i]) + z = vcat(layer.outline.z[i+1:end-1], layer.outline.z[1:i]) + layer = dd.build.layer[end-1] + i = findfirst(layer.outline.r .== 0) + r = vcat(layer.outline.r[i+1:end-1], layer.outline.r[1:i], reverse(r), layer.outline.r[i+1:i+1]) + z = vcat(layer.outline.z[i+1:end-1], layer.outline.z[1:i], reverse(z), layer.outline.z[i+1:i+1]) + add_pf_passive_loop(dd.pf_passive, dd.build.layer[end], r, z) + end end function add_pf_passive_loop(pf_passive::IMAS.pf_passive, layer::IMAS.build__layer, r::AbstractVector{T}, z::AbstractVector{T}) where {T<:Real} From 26a465aa80ca8b347db6557f6ec453986f08b2b0 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sun, 7 Aug 2022 23:43:14 -0700 Subject: [PATCH 47/74] Fix regresion --- src/actors/build_actors.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/actors/build_actors.jl b/src/actors/build_actors.jl index 68dc381b9..12ca307d0 100644 --- a/src/actors/build_actors.jl +++ b/src/actors/build_actors.jl @@ -843,7 +843,7 @@ function blanket_regions!(bd::IMAS.build, eqt::IMAS.equilibrium__time_slice) layers = bd.layer iblanket = IMAS.get_build(bd; type=_blanket_, fs=_lfs_, return_index=true, raise_error_on_missing=false) - if iblanket === nothing + if iblanket === missing return IMAS.IDSvectorElement[] end layer = layers[iblanket] @@ -1061,8 +1061,7 @@ function optimize_shape(bd::IMAS.build, obstr_index::Int, layer_index::Int, shap R = [v[1] .+ r_offset for v in GeoInterface.coordinates(poly)[1]] Z = [v[2] for v in GeoInterface.coordinates(poly)[1]] if layer.shape == Int(_convex_hull_) - h = [[r, z] for (r, z) in collect(zip(R, Z))] - hull = convex_hull(h; closed_polygon=true) + hull = convex_hull(R, Z; closed_polygon=true) R = [r for (r, z) in hull] Z = [z for (r, z) in hull] # resample disabled because this can lead to outlines of different layers to be crossing From 35f8edf6bffd0e3cfc93ac8ec74119e1c9e8d9d4 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sun, 7 Aug 2022 23:44:10 -0700 Subject: [PATCH 48/74] Convex hull from VacuumFields --- src/actors/blanket_actors.jl | 2 +- src/physics.jl | 8 ++++---- src/utils.jl | 40 +----------------------------------- 3 files changed, 6 insertions(+), 44 deletions(-) diff --git a/src/actors/blanket_actors.jl b/src/actors/blanket_actors.jl index 73e0e6174..3e34f8883 100644 --- a/src/actors/blanket_actors.jl +++ b/src/actors/blanket_actors.jl @@ -68,7 +68,7 @@ function step(actor::ActorBlanket) bm.name = structure.name # evaluate neutron_capture_fraction - tmp = convex_hull(collect(zip(vcat(eqt.boundary.outline.r, structure.outline.r), vcat(eqt.boundary.outline.z, structure.outline.z))); closed_polygon=true) + tmp = convex_hull(vcat(eqt.boundary.outline.r, structure.outline.r), vcat(eqt.boundary.outline.z, structure.outline.z); closed_polygon=true) index = findall(x -> x == 1, [IMAS.PolygonOps.inpolygon((r, z), tmp) for (r, z) in zip(dd.neutronics.first_wall.r, dd.neutronics.first_wall.z)]) neutron_capture_fraction = sum(nnt.wall_loading.power[index]) / total_power_neutrons diff --git a/src/physics.jl b/src/physics.jl index 2eb093e70..56acdd040 100644 --- a/src/physics.jl +++ b/src/physics.jl @@ -612,9 +612,9 @@ end function add_xpoint(mr::AbstractVector{T}, mz::AbstractVector{T}, i::Integer, R0::T, Z0::T, α::T) where {T<:Real} RX = mr[i] .* α .+ R0 .* (1.0 .- α) ZX = mz[i] .* α .+ Z0 .* (1.0 .- α) - RZ = FUSE.convex_hull(collect(zip(vcat(mr, RX), vcat(mz, ZX))); closed_polygon=true) - R = [r for (r, z) in RZ] - Z = [z for (r, z) in RZ] + RZ = FUSE.convex_hull(vcat(mr, RX), vcat(mz, ZX); closed_polygon=true) + R = T[r for (r, z) in RZ] + Z = T[z for (r, z) in RZ] return RX, ZX, R, Z end @@ -674,7 +674,7 @@ function MXH_boundary(mxh::IMAS.MXH; upper_x_point::Bool, lower_x_point::Bool, n push!(ZX, ZXL) end - RZ = FUSE.convex_hull(collect(zip(vcat(mr, RX), vcat(mz, ZX))); closed_polygon=true) + RZ = FUSE.convex_hull(vcat(mr, RX), vcat(mz, ZX); closed_polygon=true) R = [r for (r, z) in RZ] Z = [z for (r, z) in RZ] diff --git a/src/utils.jl b/src/utils.jl index c21619b22..ebf9170f9 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -81,45 +81,7 @@ end # ****************************************** # Convex Hull # ****************************************** -struct Point - x::Float64 - y::Float64 -end - -function Base.isless(p::Point, q::Point) - p.x < q.x || (p.x == q.x && p.y < q.y) -end - -function isrightturn(p::Point, q::Point, r::Point) - (q.x - p.x) * (r.y - p.y) - (q.y - p.y) * (r.x - p.x) < 0 -end - -function halfhull(points::Vector{Point}) - halfhull = points[1:2] - for p in points[3:end] - push!(halfhull, p) - while length(halfhull) > 2 && !isrightturn(halfhull[end-2:end]...) - deleteat!(halfhull, length(halfhull) - 1) - end - end - halfhull -end - -function grahamscan(points::Vector{Point}) - sorted = sort(points) - upperhull = halfhull(sorted) - lowerhull = halfhull(reverse(sorted)) - [upperhull..., lowerhull[2:end-1]...] -end - -function convex_hull(xy::Vector; closed_polygon::Bool) - tmp = [(k.x, k.y) for k in grahamscan([Point(xx, yx) for (xx, yx) in xy])] - if closed_polygon - return push!(tmp, tmp[1]) - else - return tmp - end -end +import VacuumFields: convex_hull # ****************************************** # TraceCAD From 20e24be260d59f30e8fbe17931232080cb951bd2 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sun, 7 Aug 2022 23:44:32 -0700 Subject: [PATCH 49/74] typing --- src/physics.jl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/physics.jl b/src/physics.jl index 56acdd040..fdb2a87cb 100644 --- a/src/physics.jl +++ b/src/physics.jl @@ -216,7 +216,7 @@ function optimize_shape(r_obstruction, z_obstruction, target_clearance, func, r_ # res = optimize(shape_parameters-> cost_shape(r_obstruction, z_obstruction, rz_obstruction, target_clearance, func, r_start, r_end, shape_parameters), # initial_guess, Newton(), Optim.Options(time_limit=time_limit); autodiff=:forward) res = Optim.optimize(shape_parameters -> cost_shape(r_obstruction, z_obstruction, rz_obstruction, target_clearance, func, r_start, r_end, shape_parameters), - initial_guess, length(shape_parameters) == 1 ? Optim.BFGS() : Optim.NelderMead(), Optim.Options(time_limit=time_limit); autodiff=:forward) + initial_guess, length(shape_parameters) == 1 ? Optim.BFGS() : Optim.NelderMead(), Optim.Options(time_limit=time_limit)) if verbose println(res) end @@ -631,9 +631,13 @@ Control of the X-point location can be achieved by modifying R0, Z0 """ function add_xpoint(mr::AbstractVector{T}, mz::AbstractVector{T}, R0::Union{Nothing,T}=nothing, Z0::Union{Nothing,T}=nothing; upper::Bool) where {T<:Real} - function cost(mr, mz, i, R0, Z0, α) - RX, ZX, R, Z = add_xpoint(mr, mz, i, R0, Z0, α[1]) - return (1.0 - maximum(abs.(IMAS.curvature(R, Z) .* (upper ? Z .> Z0 : Z .< Z0))))^2 + function cost(mr::AbstractVector{T}, mz::AbstractVector{T}, i::Integer, R0::T, Z0::T, α::Float64) + RX, ZX, R, Z = add_xpoint(mr, mz, i, R0, Z0, α) + if upper + return (1.0 - maximum(abs.(IMAS.curvature(R, Z) .* (Z .> Z0))))^2.0 + else + return (1.0 - maximum(abs.(IMAS.curvature(R, Z) .* (Z .< Z0))))^2.0 + end end if Z0 === nothing From 3d1cf8a681bec69ad29249a47d04f83aa855041f Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Sun, 7 Aug 2022 23:44:59 -0700 Subject: [PATCH 50/74] allow running for blanket even if there is no blanket (eg. ITER) --- src/actors/blanket_actors.jl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/actors/blanket_actors.jl b/src/actors/blanket_actors.jl index 3e34f8883..dfefae2fb 100644 --- a/src/actors/blanket_actors.jl +++ b/src/actors/blanket_actors.jl @@ -47,11 +47,6 @@ end function step(actor::ActorBlanket) dd = actor.dd - if IMAS.get_build(dd.build, type=_blanket_, fs=_hfs_, raise_error_on_missing=false) === missing - empty!(dd.blanket) - return actor - end - eqt = dd.equilibrium.time_slice[] nnt = dd.neutronics.time_slice[] From 97c75ead2a9e8c13b8f8b15b2b40af245e188236 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Mon, 8 Aug 2022 10:02:45 -0700 Subject: [PATCH 51/74] Fix constructor for ActorPassiveStructures --- src/actors/pf_passive_actors.jl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/actors/pf_passive_actors.jl b/src/actors/pf_passive_actors.jl index 12396d5bf..1374e083b 100644 --- a/src/actors/pf_passive_actors.jl +++ b/src/actors/pf_passive_actors.jl @@ -5,6 +5,10 @@ mutable struct ActorPassiveStructures <: ReactorAbstractActor dd::IMAS.dd par::ParametersActor + function ActorPassiveStructures(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + return new(dd, par) + end end function ParametersActor(::Type{Val{:ActorPassiveStructures}}) @@ -29,11 +33,6 @@ function ActorPassiveStructures(dd::IMAS.dd, act::ParametersAllActors; kw...) return actor end -function ActorPassiveStructures(dd::IMAS.dd, par::ParametersActor; kw...) - par = par(kw...) - ActorPassiveStructures(dd, par) -end - function step(actor::ActorPassiveStructures) dd = actor.dd From 41a53027ae88ceae7fbbd2aaadd6198e9c031df3 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Mon, 8 Aug 2022 11:22:52 -0700 Subject: [PATCH 52/74] fix ec_launchers.beam according to imas v3.37.0 --- src/actors/balance_of_plant_actors.jl | 2 +- src/actors/costing_actors.jl | 4 ++-- src/actors/sources_actors.jl | 4 ++-- src/ddinit/init_core_sources.jl | 2 +- src/parameters_init.jl | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/actors/balance_of_plant_actors.jl b/src/actors/balance_of_plant_actors.jl index af11eb9d7..e8aacca74 100644 --- a/src/actors/balance_of_plant_actors.jl +++ b/src/actors/balance_of_plant_actors.jl @@ -119,7 +119,7 @@ function electricity(nbi::IMAS.nbi, time_array::Vector{<:Real}) end function electricity(ec_launchers::IMAS.ec_launchers, time_array::Vector{<:Real}) - return heating_and_current_drive_calc(ec_launchers.launcher, time_array) + return heating_and_current_drive_calc(ec_launchers.beam, time_array) end function electricity(ic_antennas::IMAS.ic_antennas, time_array::Vector{<:Real}) diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index 71d9419f2..2f9feb471 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -47,7 +47,7 @@ function cost(layer::IMAS.build__layer) end end -function cost(ecl::IMAS.ec_launchers__launcher) +function cost(ecl::IMAS.ec_launchers__beam) ecl.available_launch_power / 1E6 * 3.0 # $/W #ARIES end @@ -151,7 +151,7 @@ function step(actor::ActorCosting) # HCD sys = resize!(cst.system, "name" => "hcd") - for hcd in vcat(dd.ec_launchers.launcher, dd.ic_antennas.antenna, dd.lh_antennas.antenna, dd.nbi.unit) + for hcd in vcat(dd.ec_launchers.beam, dd.ic_antennas.antenna, dd.lh_antennas.antenna, dd.nbi.unit) c = cost(hcd) if c > 0 sub = resize!(sys.subsystem, "name" => hcd.name) diff --git a/src/actors/sources_actors.jl b/src/actors/sources_actors.jl index d1fda1cf6..7150ac3be 100644 --- a/src/actors/sources_actors.jl +++ b/src/actors/sources_actors.jl @@ -124,13 +124,13 @@ end function ActorECsimple(dd::IMAS.dd, par::ParametersActor; kw...) par = par(kw...) - n_launchers = length(dd.ec_launchers.launcher) + n_launchers = length(dd.ec_launchers.beam) _, width, rho_0, current_efficiency = same_length_vectors(1:n_launchers, par.width, par.rho_0, par.current_efficiency) return ActorECsimple(dd, par, width, rho_0, current_efficiency) end function step(actor::ActorECsimple) - for (idx, ecl) in enumerate(actor.dd.ec_launchers.launcher) + for (idx, ecl) in enumerate(actor.dd.ec_launchers.beam) eqt = actor.dd.equilibrium.time_slice[] cp1d = actor.dd.core_profiles.profiles_1d[] cs = actor.dd.core_sources diff --git a/src/ddinit/init_core_sources.jl b/src/ddinit/init_core_sources.jl index e7d6721c6..4f501435f 100644 --- a/src/ddinit/init_core_sources.jl +++ b/src/ddinit/init_core_sources.jl @@ -66,7 +66,7 @@ function init_ec_launchers( (power_launched, efficiency_conversion, efficiency_transmission) = same_length_vectors(power_launched, efficiency_conversion, efficiency_transmission) for idx in 1:length(power_launched) - ecl = resize!(dd.ec_launchers.launcher, idx)[idx] + ecl = resize!(dd.ec_launchers.beam, idx)[idx] ecl.name = length(power_launched) > 1 ? "ec_$idx" : "ec" @ddtime(ecl.power_launched.data = power_launched[idx]) ecl.available_launch_power = power_launched[idx] diff --git a/src/parameters_init.jl b/src/parameters_init.jl index f293e09a4..096eef80e 100644 --- a/src/parameters_init.jl +++ b/src/parameters_init.jl @@ -103,8 +103,8 @@ end function ParametersInit(::Type{Val{:ec_launchers}}) ec_launchers = ParametersInit(nothing) ec_launchers.power_launched = Entry(Union{X,Vector{X}} where {X<:Real}, "W", "EC launched power") - ec_launchers.efficiency_conversion = Entry(Union{X,Vector{X}} where {X<:Real}, IMAS.ec_launchers__launcher___efficiency, :conversion) - ec_launchers.efficiency_transmission = Entry(Union{X,Vector{X}} where {X<:Real}, IMAS.ec_launchers__launcher___efficiency, :transmission) + ec_launchers.efficiency_conversion = Entry(Union{X,Vector{X}} where {X<:Real}, IMAS.ec_launchers__beam___efficiency, :conversion) + ec_launchers.efficiency_transmission = Entry(Union{X,Vector{X}} where {X<:Real}, IMAS.ec_launchers__beam___efficiency, :transmission) return ec_launchers end From a385c1783d52957a0cf5b1702b4e6e34da77e030 Mon Sep 17 00:00:00 2001 From: TimSlendebroek <32385057+TimSlendebroek@users.noreply.github.com> Date: Mon, 8 Aug 2022 16:06:15 -0700 Subject: [PATCH 53/74] operations and decomissioning added --- examples/cases/FPP_v1_demount.ipynb | 49 +++++++++++++++- src/actors/costing_actors.jl | 90 ++++++++++++++++++----------- 2 files changed, 103 insertions(+), 36 deletions(-) diff --git a/examples/cases/FPP_v1_demount.ipynb b/examples/cases/FPP_v1_demount.ipynb index d80f3eb0d..360a249a8 100644 --- a/examples/cases/FPP_v1_demount.ipynb +++ b/examples/cases/FPP_v1_demount.ipynb @@ -10,10 +10,51 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "038c65a1-369e-487b-a4ea-1d0fe33b3173", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "┌ Info: Precompiling FUSE [e64856f0-3bb8-4376-b4b7-c03396503992]\n", + "└ @ Base loading.jl:1423\n" + ] + }, + { + "data": { + "application/vnd.webio.node+json": { + "children": [], + "instanceArgs": { + "namespace": "html", + "tag": "div" + }, + "nodeType": "DOM", + "props": {}, + "type": "node" + }, + "text/html": [ + "
\n", + "

The WebIO Jupyter extension was not detected. See the\n", + "\n", + " WebIO Jupyter integration documentation\n", + "\n", + "for more information.\n", + "

\n" + ], + "text/plain": [ + "WebIO._IJuliaInit()" + ] + }, + "metadata": { + "@webio": { + "kernelId": "02fd490c-0d00-4f1b-b1fa-b5e978e31b7f" + } + }, + "output_type": "display_data" + } + ], "source": [ "using Revise\n", "using FUSE\n", @@ -125,6 +166,10 @@ } ], "metadata": { + "@webio": { + "lastCommId": "50360ff0-ebaa-41c5-81cb-9ac8fc615b38", + "lastKernelId": "02fd490c-0d00-4f1b-b1fa-b5e978e31b7f" + }, "kernelspec": { "display_name": "Julia 1.7.3", "language": "julia", diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index a8f6f659c..5f09d753a 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -92,59 +92,56 @@ function cost_direct_capital(coil::IMAS.pf_active__coil, technology::Union{IMAS. end function cost_direct_capital(::Type{Val{:land}}, land::Real, power_electric_net::Real) - 1.2 * land * 20.0e-3 * (power_electric_net / 1000.0) ^ 0.3 + 1.2 * land * 20.0e-3 * (power_electric_net / 1000.0)^0.3 end cost_direct_capital(item::Symbol, land, power_electric_net) = cost_direct_capital(Val{item}, land, power_electric_net) -function cost_direct_capital(::Type{Val{:buildings}},land::Real, building_volume::Real, power_electric_net::Real, power_thermal::Real) # ARIES +function cost_direct_capital(::Type{Val{:buildings}}, land::Real, building_volume::Real, power_electric_net::Real, power_thermal::Real) # ARIES cost = 27.0 * (land / 1000.0)^0.2 - cost += 111.661 * (building_volume / 80.0e3) ^ 0.62 # tokamak building - cost += 4.309 * (power_electric_net / 1000.0) ^ 0.3 # power core service building - cost += 1.513 * (power_electric_net / 1000.0) ^ 0.3 # service water - cost += 25.0 * (power_thermal / 1759.0) ^ 0.3 # fuel handling + cost += 111.661 * (building_volume / 80.0e3)^0.62 # tokamak building + cost += 4.309 * (power_electric_net / 1000.0)^0.3 # power core service building + cost += 1.513 * (power_electric_net / 1000.0)^0.3 # service water + cost += 25.0 * (power_thermal / 1759.0)^0.3 # fuel handling cost += 7.11 # control room cost += 2.0 # site service cost += 2.0 # administrative cost += 2.09 # cyrogenic and inert gas storage cost += 0.71 # security - cost += 22.878 * (power_electric_net / 1000.0) ^ 0.3 # service building - cost += 4.7 * (power_electric_net / 1000.0) ^ 0.3 + 4.15 # On-site AC Power Supply and ventilation + cost += 22.878 * (power_electric_net / 1000.0)^0.3 # service building + cost += 4.7 * (power_electric_net / 1000.0)^0.3 + 4.15 # On-site AC Power Supply and ventilation return cost end -cost_direct_capital(item::Symbol, building_volume,land, power_electric_net, power_thermal) = cost_direct_capital(Val{item}, building_volume, land, power_electric_net, power_thermal) +cost_direct_capital(item::Symbol, building_volume, land, power_electric_net, power_thermal) = cost_direct_capital(Val{item}, building_volume, land, power_electric_net, power_thermal) -function cost_direct_capital(::Type{Val{:turbine}},power_electric_generated::Real) - 78.9 * (power_electric_generated / 1246) ^ 0.5 +function cost_direct_capital(::Type{Val{:turbine}}, power_electric_generated::Real) + 78.9 * (power_electric_generated / 1246)^0.5 end cost_direct_capital(item::Symbol, power_electric_generated) = cost_direct_capital(Val{item}, power_electric_generated) function cost_direct_capital(::Type{Val{:heat_rejection}}, power_electric_net, power_thermal) - 16.804 * ((power_thermal - power_electric_net) / 1860.0) ^ 0.5 + 16.804 * ((power_thermal - power_electric_net) / 1860.0)^0.5 end -cost_direct_capital(item::Symbol, power_electric_net, power_thermal) = cost_direct_capital(Val{item}, power_electric_net, power_thermal) function cost_direct_capital(::Type{Val{:electrical_equipment}}, power_electric_net) - 22.878 * (power_electric_net / 1000.0) ^ 0.3 + 22.878 * (power_electric_net / 1000.0)^0.3 +end + +function cost_direct_capital(::Type{Val{:hot_cell}}, building_volume) # https://www.iter.org/mach/HotCell + 0.4 * 111.661 * (building_volume / 80.0e3)^0.62 end -cost_direct_capital(item::Symbol, power_electric_net) = cost_direct_capital(Val{item}, power_electric_net) function cost_operations(::Type{Val{:operation_maintanance}}, power_electric_net) - 80.0 * (power_electric_net / 1200.0) ^ 0.5 + 80.0 * (power_electric_net / 1200.0)^0.5 end -cost_operations(item::Symbol, power_electric_net) = cost_operations(Val{item}, power_electric_net) function cost_operations(::Type{Val{:fuel}}) 1.0 end +cost_operations(item::Symbol) = cost_operations(Val{item}) cost_operations(item::Symbol, power_electric_net) = cost_operations(Val{item}, power_electric_net) -function cost_operations(::Type{Val{:blanket_replacement}},cost_blanket) # find blanket and replace every x-years - cost_blanket * 1.2 -end -cost_operations(item::Symbol, cost_blanket) = cost_operations(Val{item}, cost_blanket) - -function cost_decomissioning(::Type{Val{:hot_cell}},building_volume) # https://www.iter.org/mach/HotCell - 0.4 * 111.661 * (building_volume / 80.0e3) ^ 0.62 +function cost_operations(::Type{Val{:blanket_replacement}}, cost_blanket) # find blanket and replace every x-years + cost_blanket * 1.2 # 20% contingency for extras end function cost_decomissioning(::Type{Val{:decom_wild_guess}}) @@ -167,8 +164,12 @@ end function ParametersActor(::Type{Val{:ActorCosting}}) par = ParametersActor(nothing) - par.land_space = Entry(Real, "m^2","Plant site space required in m²";default=1000.) + par.land_space = Entry(Real, "m^2", "Plant site space required in m²"; default=1000.0) par.building_volume = Entry(Real, "m^3", "Volume of the tokmak building"; default=140.0e3) + par.intrest_rate = Entry(Real, "", "Anual intrest rate fraction of direct capital cost"; default=0.05) + par.lifetime = Entry(Real, "years", "lifetime of the plant"; default=40) + par.availability = Entry(Real, "", "availability fraction of the plant"; default=0.9) + par.escalation_fraction = Entry(Real, "", "yearly escalation fraction based on risk assessment"; default=0.03) return par end @@ -201,7 +202,7 @@ function step(actor::ActorCosting) empty!(cost_direct) ### Tokamak - + # build layers sys = resize!(cost_direct.system, "name" => "tokamak") for layer in dd.build.layer @@ -241,7 +242,7 @@ function step(actor::ActorCosting) power_electric_net = @ddtime(dd.balance_of_plant.power_electric_net) / 1e6 # should be pulse average power_thermal = sum([maximum(sys.power_in) for sys in dd.balance_of_plant.thermal_cycle.system]) / 1e6 # should be pulse average power_electric_generated = @ddtime(dd.balance_of_plant.thermal_cycle.power_electric_generated) / 1e6 - for item in vcat(:land, :buildings, :turbine, :heat_rejection, :electrical_equipment) + for item in vcat(:land, :buildings, :turbine, :heat_rejection, :electrical_equipment, :hot_cell) sub = resize!(sys.subsystem, "name" => string(item)) if item == :land sub.cost = cost_direct_capital(item, par.land_space, power_electric_net) @@ -253,24 +254,45 @@ function step(actor::ActorCosting) sub.cost = cost_direct_capital(item, power_electric_net, power_thermal) elseif item == :electrical_equipment sub.cost = cost_direct_capital(item, power_electric_net) + elseif item == :hot_cell + sub.cost = cost_direct_capital(item, par.building_volume) else sub.cost = cost_direct_capital(item) end end end - """ - # Fuel Cycle + ###### Operations ###### + empty!(cost_ops) + + blanket_cost = sum([item.cost for item in cost_direct.system[1].subsystem if item.name == "blanket"]) # system[1] is always tokamak + sys = resize!(cost_ops.system, "name" => "fuel cycle") + sys.cost = cost_operations(:fuel) + + sys = resize!(cost_ops.system, "name" => "maintanance and operators") + sys.cost = cost_operations(:operation_maintanance, power_electric_net) + + sys = resize!(cost_ops.system, "name" => "replacements") + for item in [:blanket_replacement] + sub = resize!(sys.subsystem, "name" => string(item)) + if item == :blanket_replacement + sub.cost = cost_operations(:blanket_replacement, blanket_cost) + else + sub.cost = cost_operations(item) + end + end ###### Decomissioning ###### + empty!(cost_decom) + + sys = resize!(cost_decom.system, "name" => "decommissioning guess") + sys.cost = cost_decomissioning(:decom_wild_guess) + + ### Levelized Cost Of Electricity - # Radioactive waste treatment? - sys = resize!(cost_decom.system, "name" => "radioactive waste treatment") - # Demolition - sys = resize!(cost_decom.system, "name" => "demolition") - """ + # / 8760 * power_electric_net/1e3 * availability return actor end From fab6db7cc77ea72898a749117a4074844c5af574 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Tue, 9 Aug 2022 10:40:20 -0700 Subject: [PATCH 54/74] new ActorPedestal mention @jmcclena @TimSlendebroek mention https://github.com/ProjectTorreyPines/TAUENN.jl/issues/6 --- src/FUSE.jl | 1 + src/actors/pedestal_actors.jl | 117 ++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 src/actors/pedestal_actors.jl diff --git a/src/FUSE.jl b/src/FUSE.jl index 4f2363c76..fc8379475 100644 --- a/src/FUSE.jl +++ b/src/FUSE.jl @@ -55,6 +55,7 @@ include(joinpath("actors", "transport_actors.jl")) include(joinpath("actors", "costing_actors.jl")) include(joinpath("actors", "compound_actors.jl")) include(joinpath("actors", "neutronics_actors.jl")) +include(joinpath("actors", "pedestal_actors.jl")) #= ============ =# # OPTIMIZATION # diff --git a/src/actors/pedestal_actors.jl b/src/actors/pedestal_actors.jl new file mode 100644 index 000000000..43d61960b --- /dev/null +++ b/src/actors/pedestal_actors.jl @@ -0,0 +1,117 @@ +import EPEDNN + +#= ============= =# +# ActorPedestal # +#= ============= =# +mutable struct ActorPedestal <: PlasmaAbstractActor + dd::IMAS.dd + par::ParametersActor + epedmod::EPEDNN.EPEDmodel + inputs::EPEDNN.InputEPED + wped::Union{Missing,Real} + pped::Union{Missing,Real} +end + +function ParametersActor(::Type{Val{:ActorPedestal}}) + par = ParametersActor(nothing) + par.temp_pedestal_ratio = Entry(Real, "", "Ratio of ion to electron temperatures"; default=1.0) + par.eped_factor = Entry(Real, "", "Pedestal height multiplier (affects width by the squareroot)"; default=1.0) + par.warn_nn_train_bounds = Entry(Bool, "", "Raise warnings if querying cases that are certainly outside of the training range"; default=false) + par.only_powerlaw = Entry(Bool, "", "Use power-law pedestal fit (without NN correction)"; default=false) + return par +end + +""" + ActorPedestal(dd::IMAS.dd, act::ParametersAllActors; kw...) + +The ActorPedestal evaluates the pedestal boundary condition (height and width) +""" +function ActorPedestal(dd::IMAS.dd, act::ParametersAllActors; kw...) + par = act.ActorPedestal(kw...) + actor = ActorPedestal(dd, par) + step(actor) + finalize(actor) + return actor +end + +function ActorPedestal(dd::IMAS.dd, par::ParametersActor; kw...) + par = par(kw...) + + epedmod = EPEDNN.loadmodelonce("EPED1NNmodel.bson") + + eq = dd.equilibrium + eqt = eq.time_slice[] + + m = [ion.element[1].a for ion in dd.core_profiles.profiles_1d[].ion if Int(floor(ion.element[1].z_n)) == 1] + m = sum(m) / length(m) + if m < 2 + m = 2 + elseif m > 2 + m = 2.5 + end + + neped = @ddtime dd.summary.local.pedestal.n_e.value + zeffped = @ddtime dd.summary.local.pedestal.zeff.value + Bt = abs(@ddtime(eq.vacuum_toroidal_field.b0)) * eq.vacuum_toroidal_field.r0 / eqt.boundary.geometric_axis.r + βn = @ddtime(dd.summary.global_quantities.beta_tor_thermal_norm.value) + + inputs = EPEDNN.InputEPED( + eqt.boundary.minor_radius, + βn, + Bt, + eqt.boundary.triangularity, + abs(eqt.global_quantities.ip / 1e6), + eqt.boundary.elongation, + m, + neped / 1e19, + eqt.boundary.geometric_axis.r, + zeffped) + + return ActorPedestal(dd, par, epedmod, inputs, missing, missing) +end + +""" + step(actor::ActorPedestal; + warn_nn_train_bounds::Bool=actor.par.warn_nn_train_bounds, + only_powerlaw::Bool=false) + +Runs pedestal actor to evaluate pedestal width and height +""" +function step(actor::ActorPedestal; + warn_nn_train_bounds::Bool=actor.par.warn_nn_train_bounds, + only_powerlaw::Bool=false) + + sol = actor.epedmod(actor.inputs; only_powerlaw, warn_nn_train_bounds) + + actor.wped = sol.width.GH.H + actor.pped = sol.pressure.GH.H + + return actor +end + +""" + finalize(actor::ActorPedestal; + temp_pedestal_ratio::Real=actor.par.temp_pedestal_ratio, + eped_factor::Real=actor.par.eped_factor) + +Writes results to dd.summary.local.pedestal +""" +function finalize(actor::ActorPedestal; + temp_pedestal_ratio::Real=actor.par.temp_pedestal_ratio, + eped_factor::Real=actor.par.eped_factor) + + dd = actor.dd + + impurity = [ion.element[1].z_n for ion in dd.core_profiles.profiles_1d[].ion if Int(floor(ion.element[1].z_n)) != 1][1] + zi = sum(impurity) / length(impurity) + + nival = actor.inputs.neped * (actor.inputs.zeffped - 1) / (zi^2 - zi) + nval = actor.inputs.neped - zi * nival + nsum = actor.inputs.neped + nval + nival + tped = actor.pped * 1e6 / nsum / 1.60218e-19 + + @ddtime dd.summary.local.pedestal.t_e.value = 2.0 * tped / (1.0 + temp_pedestal_ratio) * eped_factor + @ddtime dd.summary.local.pedestal.position.rho_tor_norm = 1 - actor.wped * sqrt(eped_factor) + + return actor +end From 849473c71028a42a74daeeac24ef6fe6bbf18611 Mon Sep 17 00:00:00 2001 From: TimSlendebroek <32385057+TimSlendebroek@users.noreply.github.com> Date: Tue, 9 Aug 2022 15:18:42 -0700 Subject: [PATCH 55/74] Working + LCoE --- src/actors/costing_actors.jl | 70 +++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index 5f09d753a..bf5548987 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -91,61 +91,62 @@ function cost_direct_capital(coil::IMAS.pf_active__coil, technology::Union{IMAS. return IMAS.volume(coil) * unit_cost(technology) end -function cost_direct_capital(::Type{Val{:land}}, land::Real, power_electric_net::Real) - 1.2 * land * 20.0e-3 * (power_electric_net / 1000.0)^0.3 +function cost_direct_capital(::Type{Val{:land}}, land::Real, power_electric_generated::Real) + 1.2 * land * 20.0e-3 * (power_electric_generated / 1000.0)^0.3 end -cost_direct_capital(item::Symbol, land, power_electric_net) = cost_direct_capital(Val{item}, land, power_electric_net) +cost_direct_capital(item::Symbol, land, power_electric_generated) = cost_direct_capital(Val{item}, land, power_electric_generated) -function cost_direct_capital(::Type{Val{:buildings}}, land::Real, building_volume::Real, power_electric_net::Real, power_thermal::Real) # ARIES +function cost_direct_capital(::Type{Val{:buildings}}, land::Real, building_volume::Real, power_electric_generated::Real, power_thermal::Real) # ARIES cost = 27.0 * (land / 1000.0)^0.2 cost += 111.661 * (building_volume / 80.0e3)^0.62 # tokamak building - cost += 4.309 * (power_electric_net / 1000.0)^0.3 # power core service building - cost += 1.513 * (power_electric_net / 1000.0)^0.3 # service water + cost += 4.309 * (power_electric_generated / 1000.0)^0.3 # power core service building + cost += 1.513 * (power_electric_generated / 1000.0)^0.3 # service water cost += 25.0 * (power_thermal / 1759.0)^0.3 # fuel handling cost += 7.11 # control room cost += 2.0 # site service cost += 2.0 # administrative cost += 2.09 # cyrogenic and inert gas storage cost += 0.71 # security - cost += 22.878 * (power_electric_net / 1000.0)^0.3 # service building - cost += 4.7 * (power_electric_net / 1000.0)^0.3 + 4.15 # On-site AC Power Supply and ventilation + cost += 22.878 * (power_electric_generated / 1000.0)^0.3 # service building + cost += 4.7 * (power_electric_generated / 1000.0)^0.3 + 4.15 # On-site AC Power Supply and ventilation return cost end -cost_direct_capital(item::Symbol, building_volume, land, power_electric_net, power_thermal) = cost_direct_capital(Val{item}, building_volume, land, power_electric_net, power_thermal) +cost_direct_capital(item::Symbol, building_volume, land, power_electric_generated, power_thermal) = cost_direct_capital(Val{item}, building_volume, land, power_electric_generated, power_thermal) function cost_direct_capital(::Type{Val{:turbine}}, power_electric_generated::Real) 78.9 * (power_electric_generated / 1246)^0.5 end cost_direct_capital(item::Symbol, power_electric_generated) = cost_direct_capital(Val{item}, power_electric_generated) -function cost_direct_capital(::Type{Val{:heat_rejection}}, power_electric_net, power_thermal) - 16.804 * ((power_thermal - power_electric_net) / 1860.0)^0.5 +function cost_direct_capital(::Type{Val{:heat_rejection}}, power_electric_generated, power_thermal) + 16.804 * ((power_thermal - power_electric_generated) / 1860.0)^0.5 end -function cost_direct_capital(::Type{Val{:electrical_equipment}}, power_electric_net) - 22.878 * (power_electric_net / 1000.0)^0.3 +function cost_direct_capital(::Type{Val{:electrical_equipment}}, power_electric_generated) + 22.878 * (power_electric_generated / 1000.0)^0.3 end function cost_direct_capital(::Type{Val{:hot_cell}}, building_volume) # https://www.iter.org/mach/HotCell 0.4 * 111.661 * (building_volume / 80.0e3)^0.62 end -function cost_operations(::Type{Val{:operation_maintanance}}, power_electric_net) - 80.0 * (power_electric_net / 1200.0)^0.5 +function cost_operations(::Type{Val{:operation_maintanance}}, power_electric_generated) + 80.0 * (power_electric_generated / 1200.0)^0.5 end function cost_operations(::Type{Val{:fuel}}) 1.0 end cost_operations(item::Symbol) = cost_operations(Val{item}) -cost_operations(item::Symbol, power_electric_net) = cost_operations(Val{item}, power_electric_net) +cost_operations(item::Symbol, a) = cost_operations(Val{item}, a) +cost_operations(item::Symbol, power_electric_generated, cost_blanket) = cost_operations(Val{item}, power_electric_generated, cost_blanket) -function cost_operations(::Type{Val{:blanket_replacement}}, cost_blanket) # find blanket and replace every x-years - cost_blanket * 1.2 # 20% contingency for extras +function cost_operations(::Type{Val{:blanket_replacement}}, cost_blanket, blanket_lifetime) # find blanket and replace every x-years + cost_blanket * 1.2 / blanket_lifetime # 20% contingency for extras end function cost_decomissioning(::Type{Val{:decom_wild_guess}}) - 2.76e6 # gasc comment needs revisiting + 2.76 # gasc comment needs revisiting end cost_decomissioning(item::Symbol) = cost_decomissioning(Val{item}) @@ -168,8 +169,9 @@ function ParametersActor(::Type{Val{:ActorCosting}}) par.building_volume = Entry(Real, "m^3", "Volume of the tokmak building"; default=140.0e3) par.intrest_rate = Entry(Real, "", "Anual intrest rate fraction of direct capital cost"; default=0.05) par.lifetime = Entry(Real, "years", "lifetime of the plant"; default=40) - par.availability = Entry(Real, "", "availability fraction of the plant"; default=0.9) + par.availability = Entry(Real, "", "availability fraction of the plant"; default=0.803) par.escalation_fraction = Entry(Real, "", "yearly escalation fraction based on risk assessment"; default=0.03) + par.blanket_lifetime = Entry(Real, "years", "lifetime of the blanket"; default=6.8) return par end @@ -236,24 +238,24 @@ function step(actor::ActorCosting) ### Facility sys = resize!(cost_direct.system, "name" => "facility") - if @ddtime(dd.balance_of_plant.power_electric_net) < 0 + if @ddtime(dd.balance_of_plant.thermal_cycle.power_electric_generated) < 0 @warn("The plant doesn't generate net electricity therefore costing excludes facility estimates") else - power_electric_net = @ddtime(dd.balance_of_plant.power_electric_net) / 1e6 # should be pulse average +# power_electric_generated = @ddtime(dd.balance_of_plant.power_electric_generated) / 1e6 # should be pulse average power_thermal = sum([maximum(sys.power_in) for sys in dd.balance_of_plant.thermal_cycle.system]) / 1e6 # should be pulse average power_electric_generated = @ddtime(dd.balance_of_plant.thermal_cycle.power_electric_generated) / 1e6 for item in vcat(:land, :buildings, :turbine, :heat_rejection, :electrical_equipment, :hot_cell) sub = resize!(sys.subsystem, "name" => string(item)) if item == :land - sub.cost = cost_direct_capital(item, par.land_space, power_electric_net) + sub.cost = cost_direct_capital(item, par.land_space, power_electric_generated) elseif item == :buildings - sub.cost = cost_direct_capital(item, par.building_volume, par.land_space, power_electric_net, power_thermal) + sub.cost = cost_direct_capital(item, par.building_volume, par.land_space, power_electric_generated, power_thermal) elseif item == :turbine sub.cost = cost_direct_capital(item, power_electric_generated) elseif item == :heat_rejection - sub.cost = cost_direct_capital(item, power_electric_net, power_thermal) + sub.cost = cost_direct_capital(item, power_electric_generated, power_thermal) elseif item == :electrical_equipment - sub.cost = cost_direct_capital(item, power_electric_net) + sub.cost = cost_direct_capital(item, power_electric_generated) elseif item == :hot_cell sub.cost = cost_direct_capital(item, par.building_volume) else @@ -266,18 +268,18 @@ function step(actor::ActorCosting) empty!(cost_ops) blanket_cost = sum([item.cost for item in cost_direct.system[1].subsystem if item.name == "blanket"]) # system[1] is always tokamak - + blanket_cost =275.2622 sys = resize!(cost_ops.system, "name" => "fuel cycle") sys.cost = cost_operations(:fuel) sys = resize!(cost_ops.system, "name" => "maintanance and operators") - sys.cost = cost_operations(:operation_maintanance, power_electric_net) + sys.cost = cost_operations(:operation_maintanance, power_electric_generated) sys = resize!(cost_ops.system, "name" => "replacements") for item in [:blanket_replacement] sub = resize!(sys.subsystem, "name" => string(item)) if item == :blanket_replacement - sub.cost = cost_operations(:blanket_replacement, blanket_cost) + sub.cost = cost_operations(:blanket_replacement, blanket_cost, par.blanket_lifetime) else sub.cost = cost_operations(item) end @@ -290,9 +292,13 @@ function step(actor::ActorCosting) sys.cost = cost_decomissioning(:decom_wild_guess) ### Levelized Cost Of Electricity - - - # / 8760 * power_electric_net/1e3 * availability + capital_cost_rate = par.intrest_rate / (1 - (1 + par.intrest_rate) ^ (-1.0 * par.lifetime)) + total_cost = 0. + for year in 1:par.lifetime + total_cost += (1. + par.escalation_fraction) * (capital_cost_rate * cost_direct.cost + cost_ops.cost + cost_decom.cost) + @show year, total_cost + end + dd.costing.levelized_CoE = total_cost / (par.lifetime * 8760 * power_electric_generated/1e3 * par.availability) return actor end From 2b0c59b776d166fcb348dbf7c04aa01c373dde2f Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Wed, 10 Aug 2022 22:42:16 -0700 Subject: [PATCH 56/74] ini2json, ac2json, json2ini, json2act --- src/parameters.jl | 71 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 9 deletions(-) diff --git a/src/parameters.jl b/src/parameters.jl index fe8afd997..2965b3c4e 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -3,7 +3,6 @@ import AbstractTrees abstract type AbstractParameter end abstract type AbstractParameters end -abstract type AbstractParametersActor end #= ===== =# # Entry # @@ -288,6 +287,10 @@ function Base.keys(p::AbstractParameters) return keys(getfield(p, :_parameters)) end +function Base.values(p::AbstractParameters) + return values(getfield(p, :_parameters)) +end + function Base.getindex(p::AbstractParameters, field::Symbol) return getfield(p, :_parameters)[field] end @@ -398,7 +401,7 @@ function Base.iterate(par::AbstractParameters, state) return nothing end key = popfirst!(state) - data = par[key].value + data = par[key]#.value return key => data, state end @@ -430,7 +433,7 @@ function AbstractTrees.printnode(io::IO, par::AbstractParameter) printstyled(io, par._name) printstyled(io, " ➡ ") printstyled(io, "$(repr(par.value))"; color=color) - if length(par.units) > 0 && par.value !== missing + if length(replace(par.units,"-"=>"")) > 0 && par.value !== missing printstyled(io, " [$(par.units)]"; color=color) end end @@ -500,19 +503,19 @@ Convert FUSE parameters to dictionary """ function par2dict(par::AbstractParameters) ret = Dict() - return par2dict(par, ret) + return par2dict!(par, ret) end -function par2dict(par::AbstractParameters, ret::AbstractDict) +function par2dict!(par::AbstractParameters, ret::AbstractDict) data = getfield(par, :_parameters) - return par2dict(data, ret) + return par2dict!(data, ret) end -function par2dict(data::AbstractDict, ret::AbstractDict) +function par2dict!(data::AbstractDict, ret::AbstractDict) for item in keys(data) if typeof(data[item]) <: AbstractParameters ret[item] = Dict() - par2dict(data[item], ret[item]) + par2dict!(data[item], ret[item]) elseif typeof(data[item]) <: AbstractParameter ret[item] = Dict() ret[item][:value] = data[item].value @@ -524,17 +527,67 @@ function par2dict(data::AbstractDict, ret::AbstractDict) end """ - par2json(@nospecialize(par::AbstractParameters), filename::String; kw...) + ini2json(ini::ParametersAllInits, filename::String; kw...) + +Save the FUSE parameters to a JSON file with give `filename` +`kw` arguments are passed to the JSON.print function +""" +function ini2json(ini::ParametersAllInits, filename::String; kw...) + return par2json(ini, filename; kw...) +end + +""" + act2json(act::ParametersAllActors, filename::String; kw...) Save the FUSE parameters to a JSON file with give `filename` `kw` arguments are passed to the JSON.print function """ +function act2json(act::ParametersAllActors, filename::String; kw...) + return par2json(act, filename; kw...) +end + function par2json(@nospecialize(par::AbstractParameters), filename::String; kw...) open(filename, "w") do io JSON.print(io, par2dict(par); kw...) end end +function dict2par!(dct::AbstractDict, par::AbstractParameters) + for (key, val) in par + if typeof(val) <: AbstractParameters + dict2par!(dct[string(key)], val) + elseif dct[string(key)]["value"] === nothing + setproperty!(par, key, missing) + elseif typeof(dct[string(key)]["value"]) <: AbstractVector # this could be done more generally + setproperty!(par, key, Real[k for k in dct[string(key)]["value"]]) + else + try + setproperty!(par, key, Symbol(dct[string(key)]["value"])) + catch e + try + setproperty!(par, key, dct[string(key)]["value"]) + catch e + display((key, e)) + end + end + end + end + return par +end + +function json2par(filename::AbstractString, par_data::AbstractParameters) + json_data = JSON.parsefile(filename) + return dict2par!(json_data, par_data) +end + +function json2ini(filename::AbstractString) + return json2par(filename, ParametersAllInits()) +end + +function json2act(filename::AbstractString) + return json2par(filename, ParametersAllActors()) +end + #= ================= =# # Parameters errors # #= ================= =# From 4f78870ee02ee203a5348f8a63f4242187644aa9 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Wed, 10 Aug 2022 22:42:33 -0700 Subject: [PATCH 57/74] minor --- docs/make.jl | 1 + src/ddinit/init_pf_active.jl | 4 ++-- src/parameters.jl | 1 - src/utils.jl | 1 + test/runtests_parameters.jl | 1 - 5 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index a5fcc01a5..c49fd46f0 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -7,6 +7,7 @@ import IMAS import IMASDD import AbstractTrees import ProgressMeter +using InteractiveUtils: subtypes function html_link_repr(par::FUSE.AbstractParameter) return "©" * join(FUSE.path(par), ".") * "©©" * string(par._name) * "©" diff --git a/src/ddinit/init_pf_active.jl b/src/ddinit/init_pf_active.jl index d379abd49..050dbb127 100644 --- a/src/ddinit/init_pf_active.jl +++ b/src/ddinit/init_pf_active.jl @@ -125,7 +125,7 @@ function init_pf_active( for k in lfs_out_indexes layer = bd.layer[k] - if (k == gap_cryostat_index) && (length(n_coils) >= krail+1) && (n_coils[krail+1] > 0) + if (k == gap_cryostat_index) && (length(n_coils) >= krail + 1) && (n_coils[krail+1] > 0) #pass elseif !contains(lowercase(layer.name), "coils") continue @@ -151,7 +151,7 @@ function init_pf_active( poly = LibGEOS.buffer(xy_polygon(inner_layer.outline.r, inner_layer.outline.z), dcoil) rail_r = [v[1] for v in GeoInterface.coordinates(poly)[1]] rail_z = [v[2] for v in GeoInterface.coordinates(poly)[1]] - rail_r, rail_z = IMAS.resample_2d_line(rail_r, rail_z, step=dr/3) + rail_r, rail_z = IMAS.resample_2d_line(rail_r, rail_z, step=dr / 3) # mark what regions on that rail do not intersect solid structures and can hold coils valid_k = [] diff --git a/src/parameters.jl b/src/parameters.jl index 2965b3c4e..b3c3c41c6 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -1,4 +1,3 @@ -using InteractiveUtils: subtypes import AbstractTrees abstract type AbstractParameter end diff --git a/src/utils.jl b/src/utils.jl index ebf9170f9..47d1bd192 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1,3 +1,4 @@ +using InteractiveUtils: subtypes import ForwardDiff function unwrap(v, inplace=false) diff --git a/test/runtests_parameters.jl b/test/runtests_parameters.jl index f71169299..8209bca58 100644 --- a/test/runtests_parameters.jl +++ b/test/runtests_parameters.jl @@ -1,7 +1,6 @@ using Revise using FUSE using Test -using InteractiveUtils: subtypes @testset "ParametersInit" begin par = FUSE.ParametersAllInits() From 29e667b44f1630b9d7ee0c3cc5c87604c3f841a3 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Wed, 10 Aug 2022 22:59:15 -0700 Subject: [PATCH 58/74] robustness and testing for dict2par par2dict --- src/parameters.jl | 23 ++++++++++++++++------- test/runtests_parameters.jl | 4 ++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/parameters.jl b/src/parameters.jl index b3c3c41c6..320b98cec 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -432,7 +432,7 @@ function AbstractTrees.printnode(io::IO, par::AbstractParameter) printstyled(io, par._name) printstyled(io, " ➡ ") printstyled(io, "$(repr(par.value))"; color=color) - if length(replace(par.units,"-"=>"")) > 0 && par.value !== missing + if length(replace(par.units, "-" => "")) > 0 && par.value !== missing printstyled(io, " [$(par.units)]"; color=color) end end @@ -553,18 +553,27 @@ end function dict2par!(dct::AbstractDict, par::AbstractParameters) for (key, val) in par + if key ∈ keys(dct) + # this is if dct was par2dict function + dkey = key + dvalue = :value + else + # this is if dct was generated from json + dkey = string(key) + dvalue = "value" + end if typeof(val) <: AbstractParameters - dict2par!(dct[string(key)], val) - elseif dct[string(key)]["value"] === nothing + dict2par!(dct[dkey], val) + elseif dct[dkey][dvalue] === nothing setproperty!(par, key, missing) - elseif typeof(dct[string(key)]["value"]) <: AbstractVector # this could be done more generally - setproperty!(par, key, Real[k for k in dct[string(key)]["value"]]) + elseif typeof(dct[dkey][dvalue]) <: AbstractVector # this could be done more generally + setproperty!(par, key, Real[k for k in dct[dkey][dvalue]]) else try - setproperty!(par, key, Symbol(dct[string(key)]["value"])) + setproperty!(par, key, Symbol(dct[dkey][dvalue])) catch e try - setproperty!(par, key, dct[string(key)]["value"]) + setproperty!(par, key, dct[dkey][dvalue]) catch e display((key, e)) end diff --git a/test/runtests_parameters.jl b/test/runtests_parameters.jl index 8209bca58..33f6cc2c2 100644 --- a/test/runtests_parameters.jl +++ b/test/runtests_parameters.jl @@ -53,4 +53,8 @@ using Test @test typeof(FUSE.ParametersActor(par)) <: FUSE.ParametersActor end + # save load + FUSE.dict2par!(FUSE.par2dict(ini), FUSE.ParametersAllInits()) + FUSE.dict2par!(FUSE.par2dict(act), FUSE.ParametersAllActors()) + end From 8fa0c13821a0ecb99e333f0c7bd82b3696d99294 Mon Sep 17 00:00:00 2001 From: TimSlendebroek <32385057+TimSlendebroek@users.noreply.github.com> Date: Thu, 11 Aug 2022 16:38:21 -0700 Subject: [PATCH 59/74] Update costing and bug fixes --- src/actors/costing_actors.jl | 111 +++++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 37 deletions(-) diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index bf5548987..5d5938c7a 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -91,43 +91,76 @@ function cost_direct_capital(coil::IMAS.pf_active__coil, technology::Union{IMAS. return IMAS.volume(coil) * unit_cost(technology) end -function cost_direct_capital(::Type{Val{:land}}, land::Real, power_electric_generated::Real) - 1.2 * land * 20.0e-3 * (power_electric_generated / 1000.0)^0.3 +# NOTE costs are based on ARES study https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf + +function cost_direct_capital(::Type{Val{:land}}, land::Real, power_electric_net::Real) + 1.2 * land * 20.0e-3 * (power_electric_net / 1000.0)^0.3 end cost_direct_capital(item::Symbol, land, power_electric_generated) = cost_direct_capital(Val{item}, land, power_electric_generated) -function cost_direct_capital(::Type{Val{:buildings}}, land::Real, building_volume::Real, power_electric_generated::Real, power_thermal::Real) # ARIES - cost = 27.0 * (land / 1000.0)^0.2 +function cost_direct_capital(::Type{Val{:buildings}}, land::Real, building_volume::Real, power_electric_generated::Real, power_thermal::Real, power_electric_net::Real) # ARIES + cost = 27.0 * (land / 1000.0)^0.2 # site cost += 111.661 * (building_volume / 80.0e3)^0.62 # tokamak building + cost += 78.9 * (power_electric_generated / 1246)^0.5 # turbine/generator + cost += 16.804 * ((power_thermal - power_electric_net) / 1860.0)^0.5 # heat rejection + cost += 22.878 * (power_electric_net / 1000.0)^0.3 # electrical equipment + cost += 21.96 * (power_electric_generated / 1246)^0.3 # Plant Auxiliary Systems cost += 4.309 * (power_electric_generated / 1000.0)^0.3 # power core service building cost += 1.513 * (power_electric_generated / 1000.0)^0.3 # service water cost += 25.0 * (power_thermal / 1759.0)^0.3 # fuel handling cost += 7.11 # control room - cost += 2.0 # site service + cost += 4.7 * (power_electric_net / 1000.0)^0.3 # On-site AC Power Supply Building cost += 2.0 # administrative + cost += 2.0 # site service cost += 2.09 # cyrogenic and inert gas storage cost += 0.71 # security - cost += 22.878 * (power_electric_generated / 1000.0)^0.3 # service building - cost += 4.7 * (power_electric_generated / 1000.0)^0.3 + 4.15 # On-site AC Power Supply and ventilation + cost += 4.15 # Ventilation Stack return cost end -cost_direct_capital(item::Symbol, building_volume, land, power_electric_generated, power_thermal) = cost_direct_capital(Val{item}, building_volume, land, power_electric_generated, power_thermal) -function cost_direct_capital(::Type{Val{:turbine}}, power_electric_generated::Real) - 78.9 * (power_electric_generated / 1246)^0.5 -end +cost_direct_capital(item::Symbol, + building_volume, + land, + power_electric_generated, + power_thermal, + power_electric_net) = cost_direct_capital( + Val{item}, + building_volume, + land, + power_electric_generated, + power_thermal, + power_electric_net) + cost_direct_capital(item::Symbol, power_electric_generated) = cost_direct_capital(Val{item}, power_electric_generated) -function cost_direct_capital(::Type{Val{:heat_rejection}}, power_electric_generated, power_thermal) - 16.804 * ((power_thermal - power_electric_generated) / 1860.0)^0.5 +function cost_direct_capital(::Type{Val{:hot_cell}}, building_volume) # https://www.iter.org/mach/HotCell + 0.34 * 111.661 * (building_volume / 80.0e3)^0.62 end -function cost_direct_capital(::Type{Val{:electrical_equipment}}, power_electric_generated) - 22.878 * (power_electric_generated / 1000.0)^0.3 +function cost_direct_capital(::Type{Val{:heat_transfer_loop_materials}},power_thermal) # ARIES (warning uses LiPb for blanket) + cost = 50 * (power_thermal / 2000.0) ^ 0.55 # water + cost += 125 * (power_thermal / 2000.0) ^ 0.55 # LiPb + cost += 110.0 * (power_thermal / 2000.0) ^ 0.55 # He + cost += 0.01 * power_thermal # NbIHX + cost += 50.0 * (power_thermal / 2000.0) ^ 0.55 # Na + return cost end -function cost_direct_capital(::Type{Val{:hot_cell}}, building_volume) # https://www.iter.org/mach/HotCell - 0.4 * 111.661 * (building_volume / 80.0e3)^0.62 +function cost_direct_capital(::Type{Val{:balance_of_plant_equipment}},power_thermal, power_electric_generated) # ARIES + cost = 350 * (power_thermal / 2620.0) ^ 0.7 # Turbine equipment + cost += 182.98 * (power_electric_generated / 1200.0) ^ 0.5 # Electrical plant equipment + cost += 87.52 * ((power_thermal - power_electric_generated) / 2300.0) # Heat rejection equipment + cost += 88.89 * (power_electric_generated / 1200.0) ^ 0.6 # Miscellenous equipment + return cost +end + +function cost_direct_capital(::Type{Val{:fuel_cycle_rad_handling}},power_thermal, power_electric_net) # ARIES (warning uses LiPb for blanket) + cost = 15 * (power_thermal / 1758.0) ^ 0.85 # radioactive material treatment and management + cost += 70 * (power_thermal / 1758.0) ^ 0.8 # Fuel handling and storage + cost += 100. * (power_electric_net / 2000.0) ^ 0.55 # Hot cell maintanance + cost += 60. # Instrumentation and Control + cost += 8 * (power_thermal / 1000.0) ^ 0.8 # Misc power core equipment + return cost end function cost_operations(::Type{Val{:operation_maintanance}}, power_electric_generated) @@ -165,12 +198,13 @@ end function ParametersActor(::Type{Val{:ActorCosting}}) par = ParametersActor(nothing) - par.land_space = Entry(Real, "m^2", "Plant site space required in m²"; default=1000.0) + par.land_space = Entry(Real, "acres", "Plant site space required in acres"; default=1000.0) par.building_volume = Entry(Real, "m^3", "Volume of the tokmak building"; default=140.0e3) par.intrest_rate = Entry(Real, "", "Anual intrest rate fraction of direct capital cost"; default=0.05) + par.indirect_cost_rate = Entry(Real, "", "Indirect cost associated with construction, equipment, services, energineering construction management and owners cost"; default=0.4) par.lifetime = Entry(Real, "years", "lifetime of the plant"; default=40) par.availability = Entry(Real, "", "availability fraction of the plant"; default=0.803) - par.escalation_fraction = Entry(Real, "", "yearly escalation fraction based on risk assessment"; default=0.03) + par.escalation_fraction = Entry(Real, "", "yearly escalation fraction based on risk assessment"; default=0.05) par.blanket_lifetime = Entry(Real, "years", "lifetime of the blanket"; default=6.8) return par end @@ -236,28 +270,31 @@ function step(actor::ActorCosting) end ### Facility - sys = resize!(cost_direct.system, "name" => "facility") + sys = resize!(cost_direct.system, "name" => "Facility structures, buildings and site") if @ddtime(dd.balance_of_plant.thermal_cycle.power_electric_generated) < 0 @warn("The plant doesn't generate net electricity therefore costing excludes facility estimates") else -# power_electric_generated = @ddtime(dd.balance_of_plant.power_electric_generated) / 1e6 # should be pulse average - power_thermal = sum([maximum(sys.power_in) for sys in dd.balance_of_plant.thermal_cycle.system]) / 1e6 # should be pulse average + power_electric_net = @ddtime(dd.balance_of_plant.power_electric_net) / 1e6 # should be pulse average + display(dd.balance_of_plant.thermal_cycle) + power_thermal = @ddtime(dd.balance_of_plant.thermal_cycle.power_thermal_convertable_total) / 1e6 power_electric_generated = @ddtime(dd.balance_of_plant.thermal_cycle.power_electric_generated) / 1e6 - for item in vcat(:land, :buildings, :turbine, :heat_rejection, :electrical_equipment, :hot_cell) + for item in vcat(:land, :buildings, :hot_cell, :heat_transfer_loop_materials, :balance_of_plant_equipment, :fuel_cycle_rad_handling) sub = resize!(sys.subsystem, "name" => string(item)) if item == :land sub.cost = cost_direct_capital(item, par.land_space, power_electric_generated) elseif item == :buildings - sub.cost = cost_direct_capital(item, par.building_volume, par.land_space, power_electric_generated, power_thermal) - elseif item == :turbine - sub.cost = cost_direct_capital(item, power_electric_generated) - elseif item == :heat_rejection - sub.cost = cost_direct_capital(item, power_electric_generated, power_thermal) - elseif item == :electrical_equipment - sub.cost = cost_direct_capital(item, power_electric_generated) + sub.cost = cost_direct_capital(item, par.building_volume, + par.land_space, power_electric_generated, + power_thermal, power_electric_net) elseif item == :hot_cell sub.cost = cost_direct_capital(item, par.building_volume) + elseif item == :heat_transfer_loop_materials + sub.cost = cost_direct_capital(item, power_thermal) + elseif item == :balance_of_plant_equipment + sub.cost = cost_direct_capital(item, power_thermal, power_electric_generated) + elseif item == :fuel_cycle_rad_handling + sub.cost = cost_direct_capital(item, power_thermal, power_electric_net) else sub.cost = cost_direct_capital(item) end @@ -268,7 +305,7 @@ function step(actor::ActorCosting) empty!(cost_ops) blanket_cost = sum([item.cost for item in cost_direct.system[1].subsystem if item.name == "blanket"]) # system[1] is always tokamak - blanket_cost =275.2622 + blanket_cost = 275.2622 sys = resize!(cost_ops.system, "name" => "fuel cycle") sys.cost = cost_operations(:fuel) @@ -292,14 +329,14 @@ function step(actor::ActorCosting) sys.cost = cost_decomissioning(:decom_wild_guess) ### Levelized Cost Of Electricity - capital_cost_rate = par.intrest_rate / (1 - (1 + par.intrest_rate) ^ (-1.0 * par.lifetime)) - total_cost = 0. + capital_cost_rate = par.intrest_rate / (1 - (1 + par.intrest_rate)^(-1.0 * par.lifetime)) + total_cost = 0.0 for year in 1:par.lifetime - total_cost += (1. + par.escalation_fraction) * (capital_cost_rate * cost_direct.cost + cost_ops.cost + cost_decom.cost) - @show year, total_cost + yearly_cost = (capital_cost_rate * cost_direct.cost + cost_ops.cost + cost_decom.cost) + total_cost += (1. + par.escalation_fraction) * (1. + par.indirect_cost_rate) * yearly_cost end - dd.costing.levelized_CoE = total_cost / (par.lifetime * 8760 * power_electric_generated/1e3 * par.availability) - + dd.costing.levelized_CoE = total_cost / (par.lifetime * 8760 * power_electric_net / 1e3 * par.availability) + dd.costing.cost_lifetime = total_cost return actor end From 0a8e2a2c9902d2c4f9add3916d71afe71c69b072 Mon Sep 17 00:00:00 2001 From: TimSlendebroek <32385057+TimSlendebroek@users.noreply.github.com> Date: Thu, 11 Aug 2022 16:40:13 -0700 Subject: [PATCH 60/74] notebook.... and display.. --- examples/cases/FPP_v1_demount.ipynb | 49 +++-------------------------- src/actors/costing_actors.jl | 1 - 2 files changed, 4 insertions(+), 46 deletions(-) diff --git a/examples/cases/FPP_v1_demount.ipynb b/examples/cases/FPP_v1_demount.ipynb index 360a249a8..32a89e838 100644 --- a/examples/cases/FPP_v1_demount.ipynb +++ b/examples/cases/FPP_v1_demount.ipynb @@ -10,51 +10,10 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "038c65a1-369e-487b-a4ea-1d0fe33b3173", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "┌ Info: Precompiling FUSE [e64856f0-3bb8-4376-b4b7-c03396503992]\n", - "└ @ Base loading.jl:1423\n" - ] - }, - { - "data": { - "application/vnd.webio.node+json": { - "children": [], - "instanceArgs": { - "namespace": "html", - "tag": "div" - }, - "nodeType": "DOM", - "props": {}, - "type": "node" - }, - "text/html": [ - "
\n", - "

The WebIO Jupyter extension was not detected. See the\n", - "\n", - " WebIO Jupyter integration documentation\n", - "\n", - "for more information.\n", - "

\n" - ], - "text/plain": [ - "WebIO._IJuliaInit()" - ] - }, - "metadata": { - "@webio": { - "kernelId": "02fd490c-0d00-4f1b-b1fa-b5e978e31b7f" - } - }, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "using Revise\n", "using FUSE\n", @@ -167,8 +126,8 @@ ], "metadata": { "@webio": { - "lastCommId": "50360ff0-ebaa-41c5-81cb-9ac8fc615b38", - "lastKernelId": "02fd490c-0d00-4f1b-b1fa-b5e978e31b7f" + "lastCommId": "024680cc-2c95-4c4a-8eae-321f9856d75a", + "lastKernelId": "b2ef48a4-58c0-46ab-9b66-64307913e83a" }, "kernelspec": { "display_name": "Julia 1.7.3", diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index 5d5938c7a..0002de919 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -276,7 +276,6 @@ function step(actor::ActorCosting) @warn("The plant doesn't generate net electricity therefore costing excludes facility estimates") else power_electric_net = @ddtime(dd.balance_of_plant.power_electric_net) / 1e6 # should be pulse average - display(dd.balance_of_plant.thermal_cycle) power_thermal = @ddtime(dd.balance_of_plant.thermal_cycle.power_thermal_convertable_total) / 1e6 power_electric_generated = @ddtime(dd.balance_of_plant.thermal_cycle.power_electric_generated) / 1e6 for item in vcat(:land, :buildings, :hot_cell, :heat_transfer_loop_materials, :balance_of_plant_equipment, :fuel_cycle_rad_handling) From 868dd4cad24d79147b9a4ea471b2f9af39500989 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Thu, 11 Aug 2022 20:43:07 -0700 Subject: [PATCH 61/74] parameters to json with indentation --- src/parameters.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parameters.jl b/src/parameters.jl index 320b98cec..92473780d 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -547,7 +547,7 @@ end function par2json(@nospecialize(par::AbstractParameters), filename::String; kw...) open(filename, "w") do io - JSON.print(io, par2dict(par); kw...) + JSON.print(io, par2dict(par), 1; kw...) end end From 8303394e652cfc2c178406655e8f8962a60d331b Mon Sep 17 00:00:00 2001 From: Tim Slendebroek <32385057+TimSlendebroek@users.noreply.github.com> Date: Thu, 11 Aug 2022 21:38:28 -0700 Subject: [PATCH 62/74] used for debugging --- src/actors/costing_actors.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index 0002de919..4d4318585 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -304,7 +304,6 @@ function step(actor::ActorCosting) empty!(cost_ops) blanket_cost = sum([item.cost for item in cost_direct.system[1].subsystem if item.name == "blanket"]) # system[1] is always tokamak - blanket_cost = 275.2622 sys = resize!(cost_ops.system, "name" => "fuel cycle") sys.cost = cost_operations(:fuel) @@ -345,4 +344,4 @@ function finalize(actor::ActorCosting) for sys in actor.dd.costing.cost_direct_capital.system sort!(sys.subsystem, by=x -> x.cost, rev=true) end -end \ No newline at end of file +end From 8f29f911a53b5b5074cca0acabc751c3d32cdec6 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Thu, 11 Aug 2022 22:07:54 -0700 Subject: [PATCH 63/74] revised costing --- src/actors/costing_actors.jl | 257 ++++++++++++++++++++++++----------- 1 file changed, 181 insertions(+), 76 deletions(-) diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index 4d4318585..db2ed016b 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -1,9 +1,9 @@ #= ============== =# -# materials cost # +# materials cost # #= ============== =# #NOTE: material should be priced by Kg #NOTE: if something is priced by m^3 then it is for a specific part already -function unit_cost(material::String) +function unit_cost(material::AbstractString) if material == "Vacuum" return 0.0 # $M/m^3 elseif material == "ReBCO" @@ -29,13 +29,49 @@ function unit_cost(material::String) end end +function unit_cost(coil_tech::Union{IMAS.build__tf__technology,IMAS.build__oh__technology,IMAS.build__pf_active__technology}) + if coil_tech.material == "Copper" + return unit_cost("Copper") + else + fraction_cable = 1 - coil_tech.fraction_stainless - coil_tech.fraction_void + fraction_SC = fraction_cable * coil_tech.ratio_SC_to_copper + fraction_copper = fraction_cable - fraction_SC + return (coil_tech.fraction_stainless * unit_cost("Steel, Stainless 316") + fraction_copper * unit_cost("Copper") + fraction_SC * unit_cost(coil_tech.material)) + end +end + +#= ================== =# +# Dispatch on symbol # +#= ================== =# + +function cost_direct_capital(item::Symbol, args...; kw...) + return cost_direct_capital(Val{item}, args...; kw...) +end + +function cost_operations(item::Symbol, args...; kw...) + return cost_operations(Val{item}, args...; kw...) +end + +function cost_decomissioning(item::Symbol, args...; kw...) + return cost_decomissioning(Val{item}, args...; kw...) +end + +#= =================== =# +# direct capital cost # +#= =================== =# + +""" + cost_direct_capital(layer::IMAS.build__layer) + +Capital cost for each layer in the build +""" function cost_direct_capital(layer::IMAS.build__layer) if layer.type == Int(_oh_) build = IMAS.parent(IMAS.parent(layer)) - return unit_cost(build.oh.technology) * layer.volume + return layer.volume * unit_cost(build.oh.technology) elseif layer.type == Int(_tf_) build = IMAS.parent(IMAS.parent(layer)) - return unit_cost(build.tf.technology) * layer.volume + return layer.volume * unit_cost(build.tf.technology) elseif layer.type == Int(_shield_) return layer.volume * 0.29 # $M/m^3 elseif layer.type == Int(_blanket_) @@ -43,37 +79,61 @@ function cost_direct_capital(layer::IMAS.build__layer) elseif layer.type ∈ [Int(_wall_), Int(_vessel_), Int(_cryostat_)] return layer.volume * 0.36 # $M/m^3 else - return unit_cost(layer.material) * layer.volume + return layer.volume * unit_cost(layer.material) end end +""" + cost_direct_capital(ecl::IMAS.ec_launchers__beam) + +Capital cost for each EC launcer is proportional to its power + +NOTE: ARIES https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf +""" function cost_direct_capital(ecl::IMAS.ec_launchers__beam) - ecl.available_launch_power / 1E6 * 3.0 # $/W #ARIES + unit_cost = 3.0 # $/W + return ecl.available_launch_power / 1E6 * unit_cost end +""" + cost_direct_capital(ica::IMAS.ic_antennas__antenna) + +Capital cost for each IC antenna is proportional to its power + +NOTE: ARIES https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf +""" function cost_direct_capital(ica::IMAS.ic_antennas__antenna) - ica.available_launch_power / 1E6 * 1.64 #$/W ARIES + unit_cost = 1.64 # $/W + return ica.available_launch_power / 1E6 * unit_cost end +""" + cost_direct_capital(lha::IMAS.lh_antennas__antenna) + +Capital cost for each LH antenna is proportional to its power + +NOTE: ARIES https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf +""" function cost_direct_capital(lha::IMAS.lh_antennas__antenna) - lha.available_launch_power / 1E6 * 2.13 #$/W ARIES + unit_cost = 2.13 # $/W + return lha.available_launch_power / 1E6 * unit_cost end -function cost_direct_capital(nbu::IMAS.nbi__unit) - nbu.available_launch_power / 1E6 * 4.93 #$/W ARIES -end +""" + cost_direct_capital(nbu::IMAS.nbi__unit) -function unit_cost(coil_tech::Union{IMAS.build__tf__technology,IMAS.build__oh__technology,IMAS.build__pf_active__technology}) - if coil_tech.material == "Copper" - return unit_cost("Copper") - else - fraction_cable = 1 - coil_tech.fraction_stainless - coil_tech.fraction_void - fraction_SC = fraction_cable * coil_tech.ratio_SC_to_copper - fraction_copper = fraction_cable - fraction_SC - return (coil_tech.fraction_stainless * unit_cost("Steel, Stainless 316") + fraction_copper * unit_cost("Copper") + fraction_SC * unit_cost(coil_tech.material)) - end +Capital cost for each NBI unit is proportional to its power + +NOTE: ARIES https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf +""" +function cost_direct_capital(nbu::IMAS.nbi__unit) + unit_cost = 4.93 # $/W + return nbu.available_launch_power / 1E6 * unit_cost end +""" + cost_direct_capital(pf_active::IMAS.pf_active) +""" function cost_direct_capital(pf_active::IMAS.pf_active) dd = IMAS.top_dd(pf_active) c = Dict("OH" => 0.0, "PF" => 0.0) @@ -87,17 +147,28 @@ function cost_direct_capital(pf_active::IMAS.pf_active) return c end +""" + cost_direct_capital(coil::IMAS.pf_active__coil, technology::Union{IMAS.build__tf__technology,IMAS.build__oh__technology,IMAS.build__pf_active__technology}) + +""" function cost_direct_capital(coil::IMAS.pf_active__coil, technology::Union{IMAS.build__tf__technology,IMAS.build__oh__technology,IMAS.build__pf_active__technology}) return IMAS.volume(coil) * unit_cost(technology) end -# NOTE costs are based on ARES study https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf +""" + cost_direct_capital(::Type{Val{:land}}, land::Real, power_electric_net::Real) +NOTE: ARIES https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf +""" function cost_direct_capital(::Type{Val{:land}}, land::Real, power_electric_net::Real) - 1.2 * land * 20.0e-3 * (power_electric_net / 1000.0)^0.3 + return 1.2 * land * 20.0e-3 * (power_electric_net / 1000.0)^0.3 end -cost_direct_capital(item::Symbol, land, power_electric_generated) = cost_direct_capital(Val{item}, land, power_electric_generated) +""" + cost_direct_capital(::Type{Val{:buildings}}, land::Real, building_volume::Real, power_electric_generated::Real, power_thermal::Real, power_electric_net::Real) + +NOTE: ARIES https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf +""" function cost_direct_capital(::Type{Val{:buildings}}, land::Real, building_volume::Real, power_electric_generated::Real, power_thermal::Real, power_electric_net::Real) # ARIES cost = 27.0 * (land / 1000.0)^0.2 # site cost += 111.661 * (building_volume / 80.0e3)^0.62 # tokamak building @@ -118,70 +189,99 @@ function cost_direct_capital(::Type{Val{:buildings}}, land::Real, building_volum return cost end -cost_direct_capital(item::Symbol, - building_volume, - land, - power_electric_generated, - power_thermal, - power_electric_net) = cost_direct_capital( - Val{item}, - building_volume, - land, - power_electric_generated, - power_thermal, - power_electric_net) - -cost_direct_capital(item::Symbol, power_electric_generated) = cost_direct_capital(Val{item}, power_electric_generated) - -function cost_direct_capital(::Type{Val{:hot_cell}}, building_volume) # https://www.iter.org/mach/HotCell - 0.34 * 111.661 * (building_volume / 80.0e3)^0.62 +""" + cost_direct_capital(::Type{Val{:hot_cell}}, building_volume) + +NOTE: https://www.iter.org/mach/HotCell +""" +function cost_direct_capital(::Type{Val{:hot_cell}}, building_volume::Real) + return 0.34 * 111.661 * (building_volume / 80.0e3)^0.62 end -function cost_direct_capital(::Type{Val{:heat_transfer_loop_materials}},power_thermal) # ARIES (warning uses LiPb for blanket) - cost = 50 * (power_thermal / 2000.0) ^ 0.55 # water - cost += 125 * (power_thermal / 2000.0) ^ 0.55 # LiPb - cost += 110.0 * (power_thermal / 2000.0) ^ 0.55 # He +""" + cost_direct_capital(::Type{Val{:heat_transfer_loop_materials}}, power_thermal::Real) + +NOTE: ARIES https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf (warning uses LiPb for blanket) +""" +function cost_direct_capital(::Type{Val{:heat_transfer_loop_materials}}, power_thermal::Real) + cost = 50.0 * (power_thermal / 2000.0)^0.55 # water + cost += 125.0 * (power_thermal / 2000.0)^0.55 # LiPb + cost += 110.0 * (power_thermal / 2000.0)^0.55 # He cost += 0.01 * power_thermal # NbIHX - cost += 50.0 * (power_thermal / 2000.0) ^ 0.55 # Na + cost += 50.0 * (power_thermal / 2000.0)^0.55 # Na return cost end -function cost_direct_capital(::Type{Val{:balance_of_plant_equipment}},power_thermal, power_electric_generated) # ARIES - cost = 350 * (power_thermal / 2620.0) ^ 0.7 # Turbine equipment - cost += 182.98 * (power_electric_generated / 1200.0) ^ 0.5 # Electrical plant equipment - cost += 87.52 * ((power_thermal - power_electric_generated) / 2300.0) # Heat rejection equipment - cost += 88.89 * (power_electric_generated / 1200.0) ^ 0.6 # Miscellenous equipment +""" + cost_direct_capital(::Type{Val{:balance_of_plant_equipment}}, power_thermal::Real, power_electric_generated::Real) + +NOTE: ARIES https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf +""" +function cost_direct_capital(::Type{Val{:balance_of_plant_equipment}}, power_thermal::Real, power_electric_generated::Real) + cost = 350.0 * (power_thermal / 2620.0)^0.7 # Turbine equipment + cost += 182.98 * (power_electric_generated / 1200.0)^0.5 # Electrical plant equipment + cost += 87.52 * ((power_thermal - power_electric_generated) / 2300.0) # Heat rejection equipment + cost += 88.89 * (power_electric_generated / 1200.0)^0.6 # Miscellenous equipment return cost end -function cost_direct_capital(::Type{Val{:fuel_cycle_rad_handling}},power_thermal, power_electric_net) # ARIES (warning uses LiPb for blanket) - cost = 15 * (power_thermal / 1758.0) ^ 0.85 # radioactive material treatment and management - cost += 70 * (power_thermal / 1758.0) ^ 0.8 # Fuel handling and storage - cost += 100. * (power_electric_net / 2000.0) ^ 0.55 # Hot cell maintanance - cost += 60. # Instrumentation and Control - cost += 8 * (power_thermal / 1000.0) ^ 0.8 # Misc power core equipment +""" + cost_direct_capital(::Type{Val{:fuel_cycle_rad_handling}}, power_thermal::Real, power_electric_net::Real) + +NOTE: ARIES https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf (warning uses LiPb for blanket) +""" +function cost_direct_capital(::Type{Val{:fuel_cycle_rad_handling}}, power_thermal::Real, power_electric_net::Real) + cost = 15.0 * (power_thermal / 1758.0)^0.85 # radioactive material treatment and management + cost += 70.0 * (power_thermal / 1758.0)^0.8 # Fuel handling and storage + cost += 100.0 * (power_electric_net / 2000.0)^0.55 # Hot cell maintanance + cost += 60.0 # Instrumentation and Control + cost += 8.0 * (power_thermal / 1000.0)^0.8 # Misc power core equipment return cost end -function cost_operations(::Type{Val{:operation_maintanance}}, power_electric_generated) - 80.0 * (power_electric_generated / 1200.0)^0.5 +#= =============== =# +# operations cost # +#= =============== =# + +""" + cost_operations(::Type{Val{:operation_maintanance}}, power_electric_generated::Real) + +NOTE: ARIES https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf +""" +function cost_operations(::Type{Val{:operation_maintanance}}, power_electric_generated::Real) + return 80.0 * (power_electric_generated / 1200.0)^0.5 end +""" + cost_operations(::Type{Val{:fuel}}) + +LIKELY NEEDS FIXING +""" function cost_operations(::Type{Val{:fuel}}) - 1.0 + return 1.0 end -cost_operations(item::Symbol) = cost_operations(Val{item}) -cost_operations(item::Symbol, a) = cost_operations(Val{item}, a) -cost_operations(item::Symbol, power_electric_generated, cost_blanket) = cost_operations(Val{item}, power_electric_generated, cost_blanket) -function cost_operations(::Type{Val{:blanket_replacement}}, cost_blanket, blanket_lifetime) # find blanket and replace every x-years - cost_blanket * 1.2 / blanket_lifetime # 20% contingency for extras +""" + cost_operations(::Type{Val{:blanket_replacement}}, cost_blanket::Real, blanket_lifetime::Real, lifetime::Real) + +Replace blanket every `blanket_lifetime` over the course of plant `lifetime` +""" +function cost_operations(::Type{Val{:blanket_replacement}}, cost_blanket::Real, blanket_lifetime::Real, lifetime::Real) + return cost_blanket * lifetime / blanket_lifetime end +#= =================== =# +# decomissioning cost # +#= =================== =# + +""" + cost_decomissioning(::Type{Val{:decom_wild_guess}}) + +LIKELY NEEDS FIXING +""" function cost_decomissioning(::Type{Val{:decom_wild_guess}}) - 2.76 # gasc comment needs revisiting + 2.76 # From GASC end -cost_decomissioning(item::Symbol) = cost_decomissioning(Val{item}) #= ============ =# # ActorCosting # @@ -272,12 +372,16 @@ function step(actor::ActorCosting) ### Facility sys = resize!(cost_direct.system, "name" => "Facility structures, buildings and site") - if @ddtime(dd.balance_of_plant.thermal_cycle.power_electric_generated) < 0 + if ismissing(dd.balance_of_plant.thermal_cycle,:power_electric_generated) || @ddtime(dd.balance_of_plant.thermal_cycle.power_electric_generated) < 0 @warn("The plant doesn't generate net electricity therefore costing excludes facility estimates") + power_electric_net = 0.0 + power_thermal = 0.0 + power_electric_generated = 0.0 else power_electric_net = @ddtime(dd.balance_of_plant.power_electric_net) / 1e6 # should be pulse average - power_thermal = @ddtime(dd.balance_of_plant.thermal_cycle.power_thermal_convertable_total) / 1e6 + power_thermal = @ddtime(dd.balance_of_plant.thermal_cycle.power_thermal_convertable_total) / 1e6 power_electric_generated = @ddtime(dd.balance_of_plant.thermal_cycle.power_electric_generated) / 1e6 + for item in vcat(:land, :buildings, :hot_cell, :heat_transfer_loop_materials, :balance_of_plant_equipment, :fuel_cycle_rad_handling) sub = resize!(sys.subsystem, "name" => string(item)) if item == :land @@ -303,7 +407,6 @@ function step(actor::ActorCosting) ###### Operations ###### empty!(cost_ops) - blanket_cost = sum([item.cost for item in cost_direct.system[1].subsystem if item.name == "blanket"]) # system[1] is always tokamak sys = resize!(cost_ops.system, "name" => "fuel cycle") sys.cost = cost_operations(:fuel) @@ -314,7 +417,9 @@ function step(actor::ActorCosting) for item in [:blanket_replacement] sub = resize!(sys.subsystem, "name" => string(item)) if item == :blanket_replacement - sub.cost = cost_operations(:blanket_replacement, blanket_cost, par.blanket_lifetime) + tokamak = cost_direct.system[findfirst(system -> system.name=="tokamak", cost_direct.system)] + blanket_cost = sum([item.cost for item in tokamak.subsystem if item.name == "blanket"]) + sub.cost = cost_operations(:blanket_replacement, blanket_cost, par.blanket_lifetime, par.lifetime) else sub.cost = cost_operations(item) end @@ -326,20 +431,20 @@ function step(actor::ActorCosting) sys = resize!(cost_decom.system, "name" => "decommissioning guess") sys.cost = cost_decomissioning(:decom_wild_guess) - ### Levelized Cost Of Electricity + ###### Levelized Cost Of Electricity ###### capital_cost_rate = par.intrest_rate / (1 - (1 + par.intrest_rate)^(-1.0 * par.lifetime)) - total_cost = 0.0 + lifetime_cost = 0.0 for year in 1:par.lifetime yearly_cost = (capital_cost_rate * cost_direct.cost + cost_ops.cost + cost_decom.cost) - total_cost += (1. + par.escalation_fraction) * (1. + par.indirect_cost_rate) * yearly_cost + lifetime_cost += (1.0 + par.escalation_fraction) * (1.0 + par.indirect_cost_rate) * yearly_cost end - dd.costing.levelized_CoE = total_cost / (par.lifetime * 8760 * power_electric_net / 1e3 * par.availability) - dd.costing.cost_lifetime = total_cost + dd.costing.cost_lifetime = lifetime_cost + dd.costing.levelized_CoE = dd.costing.cost_lifetime / (par.lifetime * 8760 * power_electric_net / 1e3 * par.availability) return actor end function finalize(actor::ActorCosting) - # sort system/subsystem costs + # sort system/subsystem by their costs sort!(actor.dd.costing.cost_direct_capital.system, by=x -> x.cost, rev=true) for sys in actor.dd.costing.cost_direct_capital.system sort!(sys.subsystem, by=x -> x.cost, rev=true) From c241ac51f45ee4304ccead74615080f37d86689b Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Fri, 12 Aug 2022 12:07:39 -0700 Subject: [PATCH 64/74] Fixes and cleanup for costing --- src/actors/costing_actors.jl | 79 +++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index db2ed016b..66c29a02f 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -161,6 +161,7 @@ end NOTE: ARIES https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf """ function cost_direct_capital(::Type{Val{:land}}, land::Real, power_electric_net::Real) + power_electric_net = power_electric_net / 1E6 return 1.2 * land * 20.0e-3 * (power_electric_net / 1000.0)^0.3 end @@ -169,23 +170,26 @@ end NOTE: ARIES https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf """ -function cost_direct_capital(::Type{Val{:buildings}}, land::Real, building_volume::Real, power_electric_generated::Real, power_thermal::Real, power_electric_net::Real) # ARIES +function cost_direct_capital(::Type{Val{:buildings}}, land::Real, building_volume::Real, power_electric_generated::Real, power_thermal::Real, power_electric_net::Real) + power_electric_generated = power_electric_generated / 1E6 + power_thermal = power_thermal / 1E6 + power_electric_net = power_electric_net / 1E6 cost = 27.0 * (land / 1000.0)^0.2 # site cost += 111.661 * (building_volume / 80.0e3)^0.62 # tokamak building cost += 78.9 * (power_electric_generated / 1246)^0.5 # turbine/generator cost += 16.804 * ((power_thermal - power_electric_net) / 1860.0)^0.5 # heat rejection cost += 22.878 * (power_electric_net / 1000.0)^0.3 # electrical equipment - cost += 21.96 * (power_electric_generated / 1246)^0.3 # Plant Auxiliary Systems + cost += 21.96 * (power_electric_generated / 1246)^0.3 # Plant auxiliary systems cost += 4.309 * (power_electric_generated / 1000.0)^0.3 # power core service building cost += 1.513 * (power_electric_generated / 1000.0)^0.3 # service water - cost += 25.0 * (power_thermal / 1759.0)^0.3 # fuel handling + cost += 25.0 * (power_thermal / 1759.0)^0.3 # tritium plant and systems cost += 7.11 # control room - cost += 4.7 * (power_electric_net / 1000.0)^0.3 # On-site AC Power Supply Building + cost += 4.7 * (power_electric_net / 1000.0)^0.3 # On-site AC power supply building cost += 2.0 # administrative cost += 2.0 # site service cost += 2.09 # cyrogenic and inert gas storage cost += 0.71 # security - cost += 4.15 # Ventilation Stack + cost += 4.15 # ventilation stack return cost end @@ -204,6 +208,7 @@ end NOTE: ARIES https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf (warning uses LiPb for blanket) """ function cost_direct_capital(::Type{Val{:heat_transfer_loop_materials}}, power_thermal::Real) + power_thermal = power_thermal / 1E6 cost = 50.0 * (power_thermal / 2000.0)^0.55 # water cost += 125.0 * (power_thermal / 2000.0)^0.55 # LiPb cost += 110.0 * (power_thermal / 2000.0)^0.55 # He @@ -218,6 +223,8 @@ end NOTE: ARIES https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf """ function cost_direct_capital(::Type{Val{:balance_of_plant_equipment}}, power_thermal::Real, power_electric_generated::Real) + power_thermal = power_thermal / 1E6 + power_electric_generated = power_electric_generated / 1E6 cost = 350.0 * (power_thermal / 2620.0)^0.7 # Turbine equipment cost += 182.98 * (power_electric_generated / 1200.0)^0.5 # Electrical plant equipment cost += 87.52 * ((power_thermal - power_electric_generated) / 2300.0) # Heat rejection equipment @@ -231,6 +238,8 @@ end NOTE: ARIES https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf (warning uses LiPb for blanket) """ function cost_direct_capital(::Type{Val{:fuel_cycle_rad_handling}}, power_thermal::Real, power_electric_net::Real) + power_thermal = power_thermal / 1E6 + power_electric_net = power_electric_net /1E6 cost = 15.0 * (power_thermal / 1758.0)^0.85 # radioactive material treatment and management cost += 70.0 * (power_thermal / 1758.0)^0.8 # Fuel handling and storage cost += 100.0 * (power_electric_net / 2000.0)^0.55 # Hot cell maintanance @@ -239,48 +248,54 @@ function cost_direct_capital(::Type{Val{:fuel_cycle_rad_handling}}, power_therma return cost end -#= =============== =# -# operations cost # -#= =============== =# +#= ====================== =# +# yearly operations cost # +#= ====================== =# """ cost_operations(::Type{Val{:operation_maintanance}}, power_electric_generated::Real) +Yearly cost for maintenance [\$M/year] NOTE: ARIES https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf """ function cost_operations(::Type{Val{:operation_maintanance}}, power_electric_generated::Real) + power_electric_generated = power_electric_generated / 1E6 return 80.0 * (power_electric_generated / 1200.0)^0.5 end """ - cost_operations(::Type{Val{:fuel}}) + cost_operations(::Type{Val{:tritium_handling}}) -LIKELY NEEDS FIXING +Yearly cost for tritium_handling [\$M/year] +!!!!WRONG!!!! Needs estiamte """ -function cost_operations(::Type{Val{:fuel}}) +function cost_operations(::Type{Val{:tritium_handling}}) return 1.0 end """ - cost_operations(::Type{Val{:blanket_replacement}}, cost_blanket::Real, blanket_lifetime::Real, lifetime::Real) + cost_operations(::Type{Val{:blanket_replacement}}, cost_blanket::Real, blanket_lifetime::Real) -Replace blanket every `blanket_lifetime` over the course of plant `lifetime` +Yearly cost for blanket replacement [\$M/year] """ -function cost_operations(::Type{Val{:blanket_replacement}}, cost_blanket::Real, blanket_lifetime::Real, lifetime::Real) - return cost_blanket * lifetime / blanket_lifetime +function cost_operations(::Type{Val{:blanket_replacement}}, cost_blanket::Real, blanket_lifetime::Real) + return cost_blanket / blanket_lifetime end #= =================== =# -# decomissioning cost # +# Decomissioning cost # #= =================== =# """ - cost_decomissioning(::Type{Val{:decom_wild_guess}}) + cost_decomissioning(::Type{Val{:decom_wild_guess}}, lifetime::Real) + +Cost to decommission the plant [\$M] LIKELY NEEDS FIXING """ -function cost_decomissioning(::Type{Val{:decom_wild_guess}}) - 2.76 # From GASC +function cost_decomissioning(::Type{Val{:decom_wild_guess}}, lifetime::Real) + unit_cost = 2.76 # [$M/year]From GASC + return unit_cost * lifetime end #= ============ =# @@ -302,7 +317,7 @@ function ParametersActor(::Type{Val{:ActorCosting}}) par.building_volume = Entry(Real, "m^3", "Volume of the tokmak building"; default=140.0e3) par.intrest_rate = Entry(Real, "", "Anual intrest rate fraction of direct capital cost"; default=0.05) par.indirect_cost_rate = Entry(Real, "", "Indirect cost associated with construction, equipment, services, energineering construction management and owners cost"; default=0.4) - par.lifetime = Entry(Real, "years", "lifetime of the plant"; default=40) + par.lifetime = Entry(Integer, "years", "lifetime of the plant"; default=40) par.availability = Entry(Real, "", "availability fraction of the plant"; default=0.803) par.escalation_fraction = Entry(Real, "", "yearly escalation fraction based on risk assessment"; default=0.05) par.blanket_lifetime = Entry(Real, "years", "lifetime of the blanket"; default=6.8) @@ -378,9 +393,9 @@ function step(actor::ActorCosting) power_thermal = 0.0 power_electric_generated = 0.0 else - power_electric_net = @ddtime(dd.balance_of_plant.power_electric_net) / 1e6 # should be pulse average - power_thermal = @ddtime(dd.balance_of_plant.thermal_cycle.power_thermal_convertable_total) / 1e6 - power_electric_generated = @ddtime(dd.balance_of_plant.thermal_cycle.power_electric_generated) / 1e6 + power_electric_net = @ddtime(dd.balance_of_plant.power_electric_net) # should be pulse average + power_thermal = @ddtime(dd.balance_of_plant.thermal_cycle.power_thermal_convertable_total) + power_electric_generated = @ddtime(dd.balance_of_plant.thermal_cycle.power_electric_generated) for item in vcat(:land, :buildings, :hot_cell, :heat_transfer_loop_materials, :balance_of_plant_equipment, :fuel_cycle_rad_handling) sub = resize!(sys.subsystem, "name" => string(item)) @@ -407,11 +422,11 @@ function step(actor::ActorCosting) ###### Operations ###### empty!(cost_ops) - sys = resize!(cost_ops.system, "name" => "fuel cycle") - sys.cost = cost_operations(:fuel) + sys = resize!(cost_ops.system, "name" => "tritium handling") + sys.yearly_cost = cost_operations(:tritium_handling) sys = resize!(cost_ops.system, "name" => "maintanance and operators") - sys.cost = cost_operations(:operation_maintanance, power_electric_generated) + sys.yearly_cost = cost_operations(:operation_maintanance, power_electric_generated) sys = resize!(cost_ops.system, "name" => "replacements") for item in [:blanket_replacement] @@ -419,27 +434,27 @@ function step(actor::ActorCosting) if item == :blanket_replacement tokamak = cost_direct.system[findfirst(system -> system.name=="tokamak", cost_direct.system)] blanket_cost = sum([item.cost for item in tokamak.subsystem if item.name == "blanket"]) - sub.cost = cost_operations(:blanket_replacement, blanket_cost, par.blanket_lifetime, par.lifetime) + sub.yearly_cost = cost_operations(:blanket_replacement, blanket_cost, par.blanket_lifetime) else - sub.cost = cost_operations(item) + sub.yearly_cost = cost_operations(item) end end ###### Decomissioning ###### empty!(cost_decom) - sys = resize!(cost_decom.system, "name" => "decommissioning guess") - sys.cost = cost_decomissioning(:decom_wild_guess) + sys = resize!(cost_decom.system, "name" => "decommissioning") + sys.cost = cost_decomissioning(:decom_wild_guess, par.lifetime) ###### Levelized Cost Of Electricity ###### capital_cost_rate = par.intrest_rate / (1 - (1 + par.intrest_rate)^(-1.0 * par.lifetime)) lifetime_cost = 0.0 for year in 1:par.lifetime - yearly_cost = (capital_cost_rate * cost_direct.cost + cost_ops.cost + cost_decom.cost) + yearly_cost = (capital_cost_rate * cost_direct.cost + cost_ops.yearly_cost + cost_decom.cost / par.lifetime) lifetime_cost += (1.0 + par.escalation_fraction) * (1.0 + par.indirect_cost_rate) * yearly_cost end dd.costing.cost_lifetime = lifetime_cost - dd.costing.levelized_CoE = dd.costing.cost_lifetime / (par.lifetime * 8760 * power_electric_net / 1e3 * par.availability) + dd.costing.levelized_CoE = (dd.costing.cost_lifetime * 1E6) / (par.lifetime * 24 * 365 * power_electric_net / 1e3 * par.availability) return actor end From eb0ba69bfafa3ed118e1d71a6ac771664ae4329e Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Fri, 12 Aug 2022 15:40:41 -0700 Subject: [PATCH 65/74] Allow for inplace update of parameters --- src/parameters.jl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/parameters.jl b/src/parameters.jl index 92473780d..62d71d08b 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -455,12 +455,15 @@ function Base.ismissing(p::AbstractParameters, field::Symbol)::Bool end """ - (par::AbstractParameters)(kw...) + (par::AbstractParameters)(inplace:Bool=false;kw...) This functor is used to override the parameters at function call +`inplace` modifies parameters without making a copy """ -function (par::AbstractParameters)(kw...) - par = deepcopy(par) +function (par::AbstractParameters)(inplace::Bool=false;kw...) + if !inplace + par = deepcopy(par) + end if !isempty(kw) for (key, value) in kw setproperty!(par, key, value) From 78a3f15b70855671d6f3cbebe9dc5b16f1a4053d Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Fri, 12 Aug 2022 15:41:46 -0700 Subject: [PATCH 66/74] Simplify passing of keywords for sub-actors --- src/actors/compound_actors.jl | 38 +++++++++++++++----------------- src/actors/equilibrium_actors.jl | 10 ++++----- src/ddinit/init_equilibrium.jl | 4 +++- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/actors/compound_actors.jl b/src/actors/compound_actors.jl index 92c6b75e4..ba87f5d30 100644 --- a/src/actors/compound_actors.jl +++ b/src/actors/compound_actors.jl @@ -4,6 +4,7 @@ mutable struct ActorEquilibriumTransport <: PlasmaAbstractActor dd::IMAS.dd par::ParametersActor + act::ParametersAllActors actor_jt::ActorSteadyStateCurrent actor_eq::ActorEquilibrium actor_tr::ActorTauenn @@ -20,38 +21,35 @@ end ActorEquilibriumTransport(dd::IMAS.dd, act::ParametersAllActors; kw...) Compound actor that runs the following actors in succesion: -```julia -ActorSteadyStateCurrent(dd, act) # Current evolution to steady-state -ActorTauenn(dd, act) # For transport -ActorEquilibrium(dd, act) # Equilibrium -``` +* ActorSteadyStateCurrent +* ActorTauenn +* ActorEquilibrium !!! note Stores data in `dd.equilibrium, dd.core_profiles, dd.core_sources` """ -function ActorEquilibriumTransport(dd::IMAS.dd, act::ParametersAllActors; kw_ActorSteadyStateCurrent=Dict(), kw_ActorEquilibrium=Dict(), kw_ActorTauenn=Dict(), kw...) +function ActorEquilibriumTransport(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorEquilibriumTransport(kw...) - actor = ActorEquilibriumTransport(dd, par, act; kw_ActorSteadyStateCurrent, kw_ActorEquilibrium, kw_ActorTauenn) - step(actor; act, iterations=par.iterations, do_plot=par.do_plot) + actor = ActorEquilibriumTransport(dd, par, act) + step(actor) finalize(actor) return actor end -function ActorEquilibriumTransport(dd::IMAS.dd, par::ParametersActor, act::ParametersAllActors; kw_ActorSteadyStateCurrent=Dict(), kw_ActorEquilibrium=Dict(), kw_ActorTauenn=Dict(), kw...) +function ActorEquilibriumTransport(dd::IMAS.dd, par::ParametersActor, act::ParametersAllActors; kw...) par = par(kw...) - actor_jt = ActorSteadyStateCurrent(dd, act.ActorSteadyStateCurrent; kw_ActorSteadyStateCurrent...) - actor_eq = ActorEquilibrium(dd, act.ActorEquilibrium, act; kw_ActorEquilibrium...) - actor_tr = ActorTauenn(dd, act.ActorTauenn; kw_ActorTauenn...) - return ActorEquilibriumTransport(dd, par, actor_jt, actor_eq, actor_tr) + actor_jt = ActorSteadyStateCurrent(dd, act.ActorSteadyStateCurrent) + actor_eq = ActorEquilibrium(dd, act.ActorEquilibrium, act) + actor_tr = ActorTauenn(dd, act.ActorTauenn) + return ActorEquilibriumTransport(dd, par, act, actor_jt, actor_eq, actor_tr) end -function step(actor::ActorEquilibriumTransport; act::Union{Missing,ParametersAllActors}=missing, iterations::Int=1, do_plot::Bool=false) +function step(actor::ActorEquilibriumTransport) dd = actor.dd - if act === missing - act = ParametersAllActors() - end + par = actor.par + act = actor.act - if do_plot + if par.do_plot pe = plot(dd.equilibrium; color=:gray, label="old", coordinate=:rho_tor_norm) pp = plot(dd.core_profiles; color=:gray, label=" (old)") ps = plot(dd.core_sources; color=:gray, label=" (old)") @@ -60,7 +58,7 @@ function step(actor::ActorEquilibriumTransport; act::Union{Missing,ParametersAll # Set j_ohmic to steady state finalize(step(actor.actor_jt)) - for iteration in 1:iterations + for iteration in 1:par.iterations # run transport actor finalize(step(actor.actor_tr)) @@ -74,7 +72,7 @@ function step(actor::ActorEquilibriumTransport; act::Union{Missing,ParametersAll finalize(step(actor.actor_jt)) end - if do_plot + if par.do_plot display(plot!(pe, dd.equilibrium, coordinate=:rho_tor_norm)) display(plot!(pp, dd.core_profiles)) display(plot!(ps, dd.core_sources)) diff --git a/src/actors/equilibrium_actors.jl b/src/actors/equilibrium_actors.jl index 4d55686f9..d851631a7 100644 --- a/src/actors/equilibrium_actors.jl +++ b/src/actors/equilibrium_actors.jl @@ -18,20 +18,20 @@ end The ActorEquilibrium provides a common interface to run multiple equilibrium actors """ -function ActorEquilibrium(dd::IMAS.dd, act::ParametersAllActors; kw_ActorSolovev=Dict(), kw_ActorCHEASE=Dict(), kw...) +function ActorEquilibrium(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorEquilibrium(kw...) - actor = ActorEquilibrium(dd, par, act; kw_ActorSolovev, kw_ActorCHEASE) + actor = ActorEquilibrium(dd, par, act) step(actor) finalize(actor) return actor end -function ActorEquilibrium(dd::IMAS.dd, par::ParametersActor, act::ParametersAllActors; kw_ActorSolovev=Dict(), kw_ActorCHEASE=Dict(), kw...) +function ActorEquilibrium(dd::IMAS.dd, par::ParametersActor, act::ParametersAllActors; kw...) par = par(kw...) if par.model == :Solovev - eq_actor = ActorSolovev(dd, act.ActorSolovev; kw_ActorSolovev...) + eq_actor = ActorSolovev(dd, act.ActorSolovev) elseif par.model == :CHEASE - eq_actor = ActorCHEASE(dd, act.ActorCHEASE; kw_ActorCHEASE...) + eq_actor = ActorCHEASE(dd, act.ActorCHEASE) else error("ActorEquilibrium: model = $(par.model) is unknown") end diff --git a/src/ddinit/init_equilibrium.jl b/src/ddinit/init_equilibrium.jl index 67242c199..c8677bb0e 100644 --- a/src/ddinit/init_equilibrium.jl +++ b/src/ddinit/init_equilibrium.jl @@ -39,7 +39,9 @@ function init_equilibrium(dd::IMAS.dd, ini::ParametersAllInits, act::ParametersA symmetric=ini.equilibrium.symmetric) # solve equilibrium - ActorEquilibrium(dd, act; kw_ActorCHEASE=Dict(:rescale_eq_to_ip=>true)) + act_copy = deepcopy(act) + act_copy.ActorCHEASE.rescale_eq_to_ip = true + ActorEquilibrium(dd, act_copy) end From 8ecddf8313d23bee19d188dc2d0e4ad5b94b75a8 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Fri, 12 Aug 2022 15:42:49 -0700 Subject: [PATCH 67/74] ActorWholeFacility keeps track of all sub actors --- src/FUSE.jl | 3 +- src/actors/compound_actors.jl | 67 +++++++++++++++++++++++++++-------- src/actors/costing_actors.jl | 8 ++--- 3 files changed, 59 insertions(+), 19 deletions(-) diff --git a/src/FUSE.jl b/src/FUSE.jl index fc8379475..a24a1810c 100644 --- a/src/FUSE.jl +++ b/src/FUSE.jl @@ -53,9 +53,10 @@ include(joinpath("actors", "divertors_actors.jl")) include(joinpath("actors", "sources_actors.jl")) include(joinpath("actors", "transport_actors.jl")) include(joinpath("actors", "costing_actors.jl")) -include(joinpath("actors", "compound_actors.jl")) include(joinpath("actors", "neutronics_actors.jl")) include(joinpath("actors", "pedestal_actors.jl")) +# NOTE: compound actors should be defined last +include(joinpath("actors", "compound_actors.jl")) #= ============ =# # OPTIMIZATION # diff --git a/src/actors/compound_actors.jl b/src/actors/compound_actors.jl index ba87f5d30..97b5a156c 100644 --- a/src/actors/compound_actors.jl +++ b/src/actors/compound_actors.jl @@ -87,6 +87,18 @@ end #= ================== =# mutable struct ActorWholeFacility <: FacilityAbstractActor dd::IMAS.dd + par::ParametersActor + act::ParametersAllActors + EquilibriumTransport::Union{Nothing,ActorEquilibriumTransport} + HFSsizing::Union{Nothing,ActorHFSsizing} + LFSsizing::Union{Nothing,ActorLFSsizing} + CXbuild::Union{Nothing,ActorCXbuild} + PFcoilsOpt::Union{Nothing,ActorPFcoilsOpt} + Neutronics::Union{Nothing,ActorNeutronics} + Blanket::Union{Nothing,ActorBlanket} + Divertors::Union{Nothing,ActorDivertors} + BalanceOfPlant::Union{Nothing,ActorBalanceOfPlant} + Costing::Union{Nothing,ActorCosting} end function ParametersActor(::Type{Val{:ActorWholeFacility}}) @@ -97,30 +109,57 @@ end """ ActorWholeFacility(dd::IMAS.dd, act::ParametersAllActors; kw...) -Compound actor that runs all the actors needed to model the whole plant +Compound actor that runs all the actors needed to model the whole plant: +* ActorEquilibriumTransport +* ActorHFSsizing +* ActorLFSsizing +* ActorCXbuild +* ActorPFcoilsOpt +* ActorNeutronics +* ActorBlanket +* ActorDivertors +* ActorBalanceOfPlant +* ActorCosting !!! note Stores data in `dd` """ function ActorWholeFacility(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorWholeFacility(kw...) - actor = ActorWholeFacility(dd) - step(actor; act) + actor = ActorWholeFacility(dd, par, act) + step(actor) finalize(actor) return actor end -function step(actor::ActorWholeFacility; act::Union{Missing,ParametersAllActors}=missing, iterations::Int=1, do_plot::Bool=false) +function ActorWholeFacility(dd::IMAS.dd, par::ParametersActor, act::ParametersAllActors; kw...) + + par = par(kw...) + ActorWholeFacility(dd, par, act, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing, + nothing) +end + +function step(actor::ActorWholeFacility) dd = actor.dd - ActorEquilibriumTransport(dd, act) - ActorHFSsizing(dd, act) - ActorLFSsizing(dd, act) - ActorCXbuild(dd, act) - ActorPFcoilsOpt(dd, act) - ActorNeutronics(dd, act) - ActorBlanket(dd, act) - ActorDivertors(dd, act) - ActorBalanceOfPlant(dd, act) - ActorCosting(dd, act) + act = actor.act + actor.EquilibriumTransport = ActorEquilibriumTransport(dd, act) + actor.HFSsizing = ActorHFSsizing(dd, act) + actor.LFSsizing = ActorLFSsizing(dd, act) + actor.CXbuild = ActorCXbuild(dd, act) + actor.PFcoilsOpt = ActorPFcoilsOpt(dd, act) + actor.Neutronics = ActorNeutronics(dd, act) + actor.Blanket = ActorBlanket(dd, act) + actor.Divertors = ActorDivertors(dd, act) + actor.BalanceOfPlant = ActorBalanceOfPlant(dd, act) + actor.Costing = ActorCosting(dd, act) return actor end \ No newline at end of file diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index 66c29a02f..678d26d34 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -239,7 +239,7 @@ NOTE: ARIES https://cer.ucsd.edu/_files/publications/UCSD-CER-13-01.pdf (warning """ function cost_direct_capital(::Type{Val{:fuel_cycle_rad_handling}}, power_thermal::Real, power_electric_net::Real) power_thermal = power_thermal / 1E6 - power_electric_net = power_electric_net /1E6 + power_electric_net = power_electric_net / 1E6 cost = 15.0 * (power_thermal / 1758.0)^0.85 # radioactive material treatment and management cost += 70.0 * (power_thermal / 1758.0)^0.8 # Fuel handling and storage cost += 100.0 * (power_electric_net / 2000.0)^0.55 # Hot cell maintanance @@ -387,7 +387,7 @@ function step(actor::ActorCosting) ### Facility sys = resize!(cost_direct.system, "name" => "Facility structures, buildings and site") - if ismissing(dd.balance_of_plant.thermal_cycle,:power_electric_generated) || @ddtime(dd.balance_of_plant.thermal_cycle.power_electric_generated) < 0 + if ismissing(dd.balance_of_plant.thermal_cycle, :power_electric_generated) || @ddtime(dd.balance_of_plant.thermal_cycle.power_electric_generated) < 0 @warn("The plant doesn't generate net electricity therefore costing excludes facility estimates") power_electric_net = 0.0 power_thermal = 0.0 @@ -396,7 +396,7 @@ function step(actor::ActorCosting) power_electric_net = @ddtime(dd.balance_of_plant.power_electric_net) # should be pulse average power_thermal = @ddtime(dd.balance_of_plant.thermal_cycle.power_thermal_convertable_total) power_electric_generated = @ddtime(dd.balance_of_plant.thermal_cycle.power_electric_generated) - + for item in vcat(:land, :buildings, :hot_cell, :heat_transfer_loop_materials, :balance_of_plant_equipment, :fuel_cycle_rad_handling) sub = resize!(sys.subsystem, "name" => string(item)) if item == :land @@ -432,7 +432,7 @@ function step(actor::ActorCosting) for item in [:blanket_replacement] sub = resize!(sys.subsystem, "name" => string(item)) if item == :blanket_replacement - tokamak = cost_direct.system[findfirst(system -> system.name=="tokamak", cost_direct.system)] + tokamak = cost_direct.system[findfirst(system -> system.name == "tokamak", cost_direct.system)] blanket_cost = sum([item.cost for item in tokamak.subsystem if item.name == "blanket"]) sub.yearly_cost = cost_operations(:blanket_replacement, blanket_cost, par.blanket_lifetime) else From 0b54d8aec682c749956b21fa295b3487f3c34e56 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Fri, 12 Aug 2022 15:43:41 -0700 Subject: [PATCH 68/74] ActorTauenn do plot done at step() close https://github.com/ProjectTorreyPines/FUSE.jl/issues/135 --- src/actors/transport_actors.jl | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/actors/transport_actors.jl b/src/actors/transport_actors.jl index c936dbd74..aab48ecab 100644 --- a/src/actors/transport_actors.jl +++ b/src/actors/transport_actors.jl @@ -39,19 +39,8 @@ The pedestal in this actor is evolved using EPED-NN. function ActorTauenn(dd::IMAS.dd, act::ParametersAllActors; kw...) par = act.ActorTauenn(kw...) actor = ActorTauenn(dd, par) - if par.do_plot - ps = plot(dd.core_sources; color=:gray) - pp = plot(dd.core_profiles; color=:gray, label="") - end step(actor) finalize(actor) - if par.do_plot - display(plot!(ps, dd.core_sources)) - display(plot!(pp, dd.core_profiles)) - end - if par.verbose - display(actor.tauenn_parameters) - end return actor end @@ -70,6 +59,19 @@ function ActorTauenn(dd::IMAS.dd, par::ParametersActor; kw...) end function step(actor::ActorTauenn) - actor.tauenn_outputs = TAUENN.tau_enn(actor.dd, actor.tauenn_parameters; actor.par.verbose) + dd = actor.dd + par = actor.par + if par.do_plot + ps = plot(dd.core_sources; color=:gray) + pp = plot(dd.core_profiles; color=:gray, label="") + end + actor.tauenn_outputs = TAUENN.tau_enn(dd, actor.tauenn_parameters; par.verbose) + if par.do_plot + display(plot!(ps, dd.core_sources)) + display(plot!(pp, dd.core_profiles)) + end + if par.verbose + display(actor.tauenn_parameters) + end return actor end \ No newline at end of file From a942f73e45dfb0ccc2c8771cffee72762e61fc0a Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Mon, 15 Aug 2022 16:30:12 -0700 Subject: [PATCH 69/74] Support scaling of layers based on equilibrium mention https://github.com/ProjectTorreyPines/FUSE.jl/issues/131 --- src/ddinit/init_build.jl | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ddinit/init_build.jl b/src/ddinit/init_build.jl index 6614c5c69..b51e69255 100644 --- a/src/ddinit/init_build.jl +++ b/src/ddinit/init_build.jl @@ -83,7 +83,17 @@ function init_build(dd::IMAS.dd, ini::ParametersAllInits, act::ParametersAllActo pf_inside_tf=(ini.pf_active.n_pf_coils_inside > 0), pf_outside_tf=(ini.pf_active.n_pf_coils_outside > 0)) else - init_build(dd.build, ini.build.layers) + layers = deepcopy(ini.build.layers) + + # scale layers based on plasma major radius + iplasma = findfirst(key -> key == "plasma", collect(keys(layers))) + norm = sum([d for (k, d) in enumerate(values(layers)) if k < iplasma]) + R0_build = (norm + layers["plasma"] / 2.0) + for layer in keys(layers) + layers[layer] *= ini.equilibrium.R0 / R0_build + end + + init_build(dd.build, layers) end end From d2d446ea28324d8e2ac6795d08497aa97f4d0656 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Tue, 16 Aug 2022 00:24:46 -0700 Subject: [PATCH 70/74] =?UTF-8?q?Allow=20varying=20R0=20and=20=CF=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mention #131 --- cases/ARC.jl | 2 +- cases/CAT.jl | 2 +- cases/D3D.jl | 2 +- cases/HDB5.jl | 14 ++++---- cases/ITER.jl | 2 +- cases/SPARC.jl | 2 +- src/ddinit/init_build.jl | 70 +++++++++++++++++++++++----------------- src/parameters_init.jl | 1 + 8 files changed, 53 insertions(+), 42 deletions(-) diff --git a/cases/ARC.jl b/cases/ARC.jl index 61415bfcf..f08dc86e4 100644 --- a/cases/ARC.jl +++ b/cases/ARC.jl @@ -3,7 +3,7 @@ CFS/MIT ARC design """ -function case_parameters(::Type{Val{:ARC}})::Tuple{ParametersAllInits, ParametersAllActors} +function case_parameters(::Type{Val{:ARC}})::Tuple{ParametersAllInits,ParametersAllActors} ini = ParametersAllInits() act = ParametersAllActors() ini.general.casename = "ARC" diff --git a/cases/CAT.jl b/cases/CAT.jl index fa4003762..1841e2ff5 100644 --- a/cases/CAT.jl +++ b/cases/CAT.jl @@ -3,7 +3,7 @@ GA Compact Advanced Tokamak design """ -function case_parameters(::Type{Val{:CAT}})::Tuple{ParametersAllInits, ParametersAllActors} +function case_parameters(::Type{Val{:CAT}})::Tuple{ParametersAllInits,ParametersAllActors} ini = ParametersAllInits() act = ParametersAllActors() diff --git a/cases/D3D.jl b/cases/D3D.jl index 81da815b8..3a41081e5 100644 --- a/cases/D3D.jl +++ b/cases/D3D.jl @@ -3,7 +3,7 @@ DIII-D """ -function case_parameters(::Type{Val{:D3D}})::Tuple{ParametersAllInits, ParametersAllActors} +function case_parameters(::Type{Val{:D3D}})::Tuple{ParametersAllInits,ParametersAllActors} ini = ParametersAllInits() act = ParametersAllActors() diff --git a/cases/HDB5.jl b/cases/HDB5.jl index 3b313092b..963b5335e 100644 --- a/cases/HDB5.jl +++ b/cases/HDB5.jl @@ -6,7 +6,7 @@ import CSV For description of cases/variables see https://osf.io/593q6/ """ -function case_parameters(::Type{Val{:HDB5}}; tokamak::Union{String,Symbol}=:any, case=missing, database_case=missing)::Tuple{ParametersAllInits, ParametersAllActors} +function case_parameters(::Type{Val{:HDB5}}; tokamak::Union{String,Symbol}=:any, case=missing, database_case=missing)::Tuple{ParametersAllInits,ParametersAllActors} if !ismissing(database_case) data_row = load_hdb5(database_case=database_case) elseif !ismissing(case) @@ -61,8 +61,8 @@ function case_parameters(data_row::DataFrames.DataFrameRow) # Core_profiles parameters ini.core_profiles.ne_ped = data_row[:NEL] / 1.3 - ini.core_profiles.greenwald_fraction = data_row[:NEL]*1e-20 / (data_row[:IP]/1e6 / (pi * data_row[:AMIN]^2 )) - ini.core_profiles.helium_fraction = 0. + ini.core_profiles.greenwald_fraction = data_row[:NEL] * 1e-20 / (data_row[:IP] / 1e6 / (pi * data_row[:AMIN]^2)) + ini.core_profiles.helium_fraction = 0.0 ini.core_profiles.T_shaping = 1.8 ini.core_profiles.w_ped = 0.03 ini.core_profiles.zeff = data_row[:ZEFF] @@ -97,10 +97,10 @@ end function load_hdb5(tokamak::Union{String,Symbol}=:all; maximum_ohmic_fraction::Float64=0.25, database_case::Union{Int,Missing}=missing, extra_signal_names=Union{String,Symbol}[]) # For description of variables see https://osf.io/593q6/ run_df = CSV.read(joinpath(@__DIR__, "..", "sample", "HDB5_compressed.csv"), DataFrames.DataFrame) - run_df[:,"database_case"] = collect(1:length(run_df[:,"TOK"])) + run_df[:, "database_case"] = collect(1:length(run_df[:, "TOK"])) if !ismissing(database_case) - return run_df[run_df.database_case .== database_case, :] + return run_df[run_df.database_case.==database_case, :] end signal_names = ["TOK", "SHOT", "AMIN", "KAPPA", "DELTA", "NEL", "ZEFF", "TAUTH", "RGEO", "BT", "IP", "PNBI", "ENBI", "PICRH", "PECRH", "POHM", "MEFF", "VOL", "AREA", "WTH", "CONFIG"] @@ -112,8 +112,8 @@ function load_hdb5(tokamak::Union{String,Symbol}=:all; maximum_ohmic_fraction::F # some basic filters run_df = run_df[(run_df.TOK.!="T10").&(run_df.TOK.!="TDEV").&(run_df.KAPPA.>1.0).&(run_df.DELTA.<0.79).&(1.6 .< run_df.MEFF .< 2.2).&(1.1 .< run_df.ZEFF .< 5.9), :] # Filter cases where the ohmic power is dominating - run_df[:,"Paux"] = run_df[:,"PNBI"] .+ run_df[:,"PECRH"] .+ run_df[:,"PICRH"] .+ run_df[:,"POHM"] - run_df = run_df[run_df[:,"POHM"] .< maximum_ohmic_fraction .* (run_df[:,"Paux"] .- run_df[:,"POHM"]),:] + run_df[:, "Paux"] = run_df[:, "PNBI"] .+ run_df[:, "PECRH"] .+ run_df[:, "PICRH"] .+ run_df[:, "POHM"] + run_df = run_df[run_df[:, "POHM"]. 0), - pf_outside_tf=(ini.pf_active.n_pf_coils_outside > 0)) - else - layers = deepcopy(ini.build.layers) - - # scale layers based on plasma major radius - iplasma = findfirst(key -> key == "plasma", collect(keys(layers))) - norm = sum([d for (k, d) in enumerate(values(layers)) if k < iplasma]) - R0_build = (norm + layers["plasma"] / 2.0) - for layer in keys(layers) - layers[layer] *= ini.equilibrium.R0 / R0_build - end + # if layers are not filled explicitly, then generate them from fractions in ini.build. + if ismissing(ini.build, :layers) + layers = layers_meters_from_fractions( + dd.equilibrium.time_slice[], + IMAS.first_wall(dd.wall); + shield=ini.build.shield, + blanket=ini.build.blanket, + vessel=ini.build.vessel, + plasma_gap=ini.build.plasma_gap, + pf_inside_tf=(ini.pf_active.n_pf_coils_inside > 0), + pf_outside_tf=(ini.pf_active.n_pf_coils_outside > 0)) + else + layers = deepcopy(ini.build.layers) + end - init_build(dd.build, layers) + # scale radial build layers based on equilibrium R0 and ϵ + iplasma = findfirst(key -> key in ["plasma", :plasma], collect(keys(layers))) + R_hfs_build = sum([d for (k, d) in enumerate(values(layers)) if k < iplasma]) + if ismissing(ini.equilibrium, :R0) + R0 = R_hfs_build + collect(values(layers))[iplasma] / 2.0 + else + R0 = ini.equilibrium.R0 + end + if ismissing(ini.equilibrium, :ϵ) + a_gap = collect(values(layers))[iplasma] / 2.0 + else + a_gap = ini.equilibrium.ϵ * R0 * (1.0 + ini.build.plasma_gap) + end + R_hfs = R0 - a_gap + for key in keys(layers) + if key in ["plasma", :plasma] + layers[key] = a_gap * 2.0 + else + layers[key] = layers[key] * R_hfs / R_hfs_build end end + # populate dd.build with radial build layers + init_build(dd.build, layers) + # set the TF shape tf_to_plasma = IMAS.get_build(dd.build, fs=_hfs_, return_only_one=false, return_index=true) dd.build.layer[tf_to_plasma[1]].shape = Int(_offset_) @@ -229,13 +243,13 @@ end Initialization of build IDS based on equilibrium time_slice """ -function init_build( - bd::IMAS.build, +function layers_meters_from_fractions( eqt::IMAS.equilibrium__time_slice, wall::T where {T<:Union{IMAS.wall__description_2d___limiter__unit___outline,Missing}}; blanket::Float64=1.0, shield::Float64=0.5, vessel::Float64=0.125, + plasma_gap::Float64=0.1, pf_inside_tf::Bool=false, pf_outside_tf::Bool=true) @@ -245,8 +259,7 @@ function init_build( else rmin = eqt.boundary.geometric_axis.r - eqt.boundary.minor_radius rmax = eqt.boundary.geometric_axis.r + eqt.boundary.minor_radius - gap = (rmax - rmin) / 20.0 # plasma-wall gap - gap = (rmax - rmin) / 20.0 # plasma-wall gap + gap = (rmax - rmin) / 2.0 * plasma_gap rmin -= gap rmax += gap end @@ -302,8 +315,5 @@ function init_build( end end - # radial build - init_build(bd; layers...) - - return bd + return layers end diff --git a/src/parameters_init.jl b/src/parameters_init.jl index 096eef80e..72bcb6d91 100644 --- a/src/parameters_init.jl +++ b/src/parameters_init.jl @@ -132,6 +132,7 @@ function ParametersInit(::Type{Val{:build}}) build.blanket = Entry(Float64, "", "Fraction of blanket in radial build") build.shield = Entry(Float64, "", "Fraction of shield in radial build") build.vessel = Entry(Float64, "", "Fraction of vessel in radial build") + build.plasma_gap = Entry(Real, "", "Fraction of vacuum gap between first wall and plasma separatrix in radial build"; default=0.1) build.symmetric = Entry(Bool, "", "Is the build up-down symmetric") build.n_first_wall_conformal_layers = Entry(Integer, "", "Number of layers that are conformal to the first wall"; default=1) return build From d84a58310d6ddcf2b6ad5dada6df41ee7a446ade Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Tue, 16 Aug 2022 10:28:20 -0700 Subject: [PATCH 71/74] Symmetric ITER wall --- cases/ITER.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cases/ITER.jl b/cases/ITER.jl index 3c18bcd62..142883e43 100644 --- a/cases/ITER.jl +++ b/cases/ITER.jl @@ -50,9 +50,9 @@ function case_parameters(::Type{Val{:ITER}}; init_from::Symbol)::Tuple{Parameter layers[:hfs_TF] = 1.10 layers[:hfs_vacuum_vessel] = 0.30 layers[:hfs_shield] = 0.40 - layers[:hfs_wall] = 0.06 + layers[:hfs_wall] = 0.1 layers[:plasma] = 4.40 - layers[:lfs_wall] = 0.17 + layers[:lfs_wall] = 0.1 layers[:lfs_shield] = 0.40 layers[:lfs_vacuum_vessel] = 1.05 layers[:lfs_TF] = 1.10 From a9347728d29e6c05de6fc471ff9c090f94a89761 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Tue, 16 Aug 2022 10:28:42 -0700 Subject: [PATCH 72/74] remove (par::AbstractParameters) inplace --- src/parameters.jl | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/parameters.jl b/src/parameters.jl index 62d71d08b..92473780d 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -455,15 +455,12 @@ function Base.ismissing(p::AbstractParameters, field::Symbol)::Bool end """ - (par::AbstractParameters)(inplace:Bool=false;kw...) + (par::AbstractParameters)(kw...) This functor is used to override the parameters at function call -`inplace` modifies parameters without making a copy """ -function (par::AbstractParameters)(inplace::Bool=false;kw...) - if !inplace - par = deepcopy(par) - end +function (par::AbstractParameters)(kw...) + par = deepcopy(par) if !isempty(kw) for (key, value) in kw setproperty!(par, key, value) From a40e969ac69f11e5c427a8a46037fdacf32b76d2 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Tue, 16 Aug 2022 10:29:05 -0700 Subject: [PATCH 73/74] =?UTF-8?q?Documentation=20for=20R0=20and=20=CF=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parameters_init.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parameters_init.jl b/src/parameters_init.jl index 72bcb6d91..8daf94603 100644 --- a/src/parameters_init.jl +++ b/src/parameters_init.jl @@ -22,9 +22,9 @@ end function ParametersInit(::Type{Val{:equilibrium}}) equilibrium = ParametersInit(nothing) equilibrium.B0 = Entry(Real, IMAS.equilibrium__vacuum_toroidal_field, :b0) - equilibrium.R0 = Entry(Real, IMAS.equilibrium__vacuum_toroidal_field, :r0) + equilibrium.R0 = Entry(Real, "m", "Geometric genter of the plasma. NOTE: This also scales the radial build layers.") equilibrium.Z0 = Entry(Real, "m", "Z offset of the machine midplane"; default=0.0) - equilibrium.ϵ = Entry(Real, "", "Plasma aspect ratio") + equilibrium.ϵ = Entry(Real, "", "Plasma inverse aspect ratio. NOTE: This also scales the radial build layers.") equilibrium.κ = Entry(Real, IMAS.equilibrium__time_slice___boundary, :elongation) equilibrium.δ = Entry(Real, IMAS.equilibrium__time_slice___boundary, :triangularity) equilibrium.ζ = Entry(Real, IMAS.equilibrium__time_slice___boundary, :squareness; default=0.0) From 658dd6754fe0ded0a9e85a20daa30a52388f3ec6 Mon Sep 17 00:00:00 2001 From: Orso Meneghini Date: Tue, 16 Aug 2022 11:53:35 -0700 Subject: [PATCH 74/74] intrest_rate -> interest_rate --- src/actors/costing_actors.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/actors/costing_actors.jl b/src/actors/costing_actors.jl index 678d26d34..b166ce75e 100644 --- a/src/actors/costing_actors.jl +++ b/src/actors/costing_actors.jl @@ -315,7 +315,7 @@ function ParametersActor(::Type{Val{:ActorCosting}}) par = ParametersActor(nothing) par.land_space = Entry(Real, "acres", "Plant site space required in acres"; default=1000.0) par.building_volume = Entry(Real, "m^3", "Volume of the tokmak building"; default=140.0e3) - par.intrest_rate = Entry(Real, "", "Anual intrest rate fraction of direct capital cost"; default=0.05) + par.interest_rate = Entry(Real, "", "Anual interest rate fraction of direct capital cost"; default=0.05) par.indirect_cost_rate = Entry(Real, "", "Indirect cost associated with construction, equipment, services, energineering construction management and owners cost"; default=0.4) par.lifetime = Entry(Integer, "years", "lifetime of the plant"; default=40) par.availability = Entry(Real, "", "availability fraction of the plant"; default=0.803) @@ -447,7 +447,7 @@ function step(actor::ActorCosting) sys.cost = cost_decomissioning(:decom_wild_guess, par.lifetime) ###### Levelized Cost Of Electricity ###### - capital_cost_rate = par.intrest_rate / (1 - (1 + par.intrest_rate)^(-1.0 * par.lifetime)) + capital_cost_rate = par.interest_rate / (1 - (1 + par.interest_rate)^(-1.0 * par.lifetime)) lifetime_cost = 0.0 for year in 1:par.lifetime yearly_cost = (capital_cost_rate * cost_direct.cost + cost_ops.yearly_cost + cost_decom.cost / par.lifetime)