diff --git a/.github/workflows/debug_checks.yml b/.github/workflows/debug_checks.yml index 468547737..55eef9e63 100644 --- a/.github/workflows/debug_checks.yml +++ b/.github/workflows/debug_checks.yml @@ -23,19 +23,17 @@ jobs: version: '1.10' arch: x64 - uses: julia-actions/cache@v1 - - uses: julia-actions/julia-buildpkg@v1 - with: - project: 'moment_kinetics/' - name: Debug test run: | - cd moment_kinetics - # Hard code the debug level so that we can run without using the # `--compiled-modules=no` flag, which breaks Symbolics.jl at the # moment. - sed -i -e "s/_debug_level = get_options.*/_debug_level = 2/" src/debugging.jl + sed -i -e "s/_debug_level = get_options.*/_debug_level = 2/" moment_kinetics/src/debugging.jl - julia --project -e 'using MPIPreferences; MPIPreferences.use_system_binary(); using Pkg; Pkg.precompile()' + touch Project.toml + julia --project -O3 --check-bounds=yes -e 'using Pkg; Pkg.add(["MPI", "MPIPreferences", "PackageCompiler", "Symbolics"]); using MPIPreferences; MPIPreferences.use_system_binary()' + julia --project -O3 --check-bounds=yes -e 'using Pkg; Pkg.develop(path="moment_kinetics/"); Pkg.precompile()' + julia --project -O3 --check-bounds=yes precompile.jl --debug 2 # Need to use openmpi so that the following arguments work: # * `--mca rmaps_base_oversubscribe 1` allows oversubscription (more processes @@ -43,5 +41,5 @@ jobs: # * `--mca mpi_yield_when_idle 1` changes a setting to prevent excessively # terrible performance when oversubscribing. ## Don't use --compiled-modules=no for now, as it currently breaks Symbolics.jl - #mpiexec -np 4 --mca rmaps_base_oversubscribe 1 julia --project --check-bounds=yes --compiled-modules=no debug_test/sound_wave_tests.jl --debug 2 - mpiexec -np 4 --mca rmaps_base_oversubscribe 1 julia --project --check-bounds=yes debug_test/runtests.jl --debug 2 + #mpiexec -np 4 --mca rmaps_base_oversubscribe 1 julia --project --check-bounds=yes --compiled-modules=no moment_kinetics/debug_test/sound_wave_tests.jl --debug 2 + mpiexec -np 4 --mca rmaps_base_oversubscribe 1 julia --project -Jmoment_kinetics.so -O3 --check-bounds=yes moment_kinetics/debug_test/runtests.jl --debug 2 diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 405ffcb9e..aaf329ac6 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -23,4 +23,8 @@ jobs: touch Project.toml julia -O3 --project -e 'import Pkg; Pkg.develop(path="moment_kinetics/"); Pkg.add("NCDatasets"); Pkg.precompile()' # Reduce nstep for each example to 10 to avoid the CI job taking too long - julia -O3 --project -e 'using moment_kinetics; for (root, dirs, files) in walkdir("examples") for file in files if endswith(file, ".toml") filename = joinpath(root, file); println(filename); input = moment_kinetics.moment_kinetics_input.read_input_file(filename); t_input = get(input, "timestepping", Dict{String,Any}()); t_input["nstep"] = 10; t_input["dt"] = 1.0e-10; pop!(input, "z_nelement_local", ""); pop!(input, "r_nelement_local", ""); run_moment_kinetics(input) end end end' + # Note we skip the example `if (occursin("ARK", get(t_input, "type", "") && Sys.isapple())` + # because the way we use MINPACK.jl (needed for nonlinear solvers + # used for implicit parts of timestep) doesn't currently work on + # macOS. + julia -O3 --project -e 'using moment_kinetics; for (root, dirs, files) in walkdir("examples") for file in files if endswith(file, ".toml") filename = joinpath(root, file); println(filename); input = moment_kinetics.moment_kinetics_input.read_input_file(filename); t_input = get(input, "timestepping", Dict{String,Any}()); if (occursin("ARK", get(t_input, "type", "")) && Sys.isapple()) continue end; t_input["nstep"] = 10; t_input["dt"] = 1.0e-12; input["timestepping"] = t_input; pop!(input, "z_nelement_local", ""); pop!(input, "r_nelement_local", ""); electron_t_input = get(input, "electron_timestepping", Dict{String,Any}()); electron_t_input["initialization_residual_value"] = 1.0e8; electron_t_input["converged_residual_value"] = 1.0e8; input["electron_timestepping"] = electron_t_input; nl_solver_input = get(input, "nonlinear_solver", Dict{String,Any}()); nl_solver_input["rtol"] = 1.0e6; nl_solver_input["atol"] = 1.0e6; input["nonlinear_solver"] = nl_solver_input; run_moment_kinetics(input) end end end' diff --git a/examples/fokker-planck-1D2V/fokker-planck-1D2V-even_nz-shorttest-nstep200.toml b/examples/fokker-planck-1D2V/fokker-planck-1D2V-even_nz-shorttest-nstep200.toml index eb7d60b18..7422ef0b0 100644 --- a/examples/fokker-planck-1D2V/fokker-planck-1D2V-even_nz-shorttest-nstep200.toml +++ b/examples/fokker-planck-1D2V/fokker-planck-1D2V-even_nz-shorttest-nstep200.toml @@ -9,7 +9,6 @@ evolve_moments_parallel_flow = false evolve_moments_parallel_pressure = false evolve_moments_conservation = false #force_Er_zero_at_wall = false #true -#Er_constant = 0.0 #epsilon_offset = 0.1 #use_vpabar_in_mms_dfni = true T_e = 1.0 @@ -95,4 +94,4 @@ vperp_discretization = "gausslegendre_pseudospectral" [fokker_planck_collisions] use_fokker_planck = true nuii = 1.0 -frequency_option = "manual" \ No newline at end of file +frequency_option = "manual" diff --git a/examples/fokker-planck/fokker-planck-relaxation-slowing-down-alphas.toml b/examples/fokker-planck/fokker-planck-relaxation-slowing-down-alphas.toml new file mode 100644 index 000000000..e6b75e29c --- /dev/null +++ b/examples/fokker-planck/fokker-planck-relaxation-slowing-down-alphas.toml @@ -0,0 +1,92 @@ +# cheap input file for a 0D2V relaxation to a collisional Maxwellian distribution with self-ion collisions and collisions with fixed Maxwellian background of cold ions and electrons. +n_ion_species = 1 +n_neutral_species = 0 +electron_physics = "boltzmann_electron_response" +evolve_moments_density = false +evolve_moments_parallel_flow = false +evolve_moments_parallel_pressure = false +evolve_moments_conservation = false +T_e = 1.0 +T_wall = 1.0 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "sinusoid" +z_IC_density_amplitude1 = 0.0 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 0.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.0 +z_IC_temperature_phase1 = 0.0 +vpa_IC_option1 = "isotropic-beam" +vpa_IC_v01 = 1.0 +vpa_IC_vth01 = 0.1 +#vpa_IC_option1 = "directed-beam" +#vpa_IC_vpa01 = -1.5 +#vpa_IC_vperp01 = 0.0 +charge_exchange_frequency = 0.0 +ionization_frequency = 0.0 +constant_ionization_rate = false + +z_ngrid = 1 +z_nelement = 1 +z_nelement_local = 1 +z_bc = "wall" +z_discretization = "chebyshev_pseudospectral" +r_ngrid = 1 +r_nelement = 1 +r_nelement_local = 1 +r_bc = "periodic" +r_discretization = "chebyshev_pseudospectral" +vpa_ngrid = 5 +vpa_nelement = 32 +vpa_L = 3.0 +vpa_bc = "zero" +vpa_discretization = "gausslegendre_pseudospectral" +vperp_ngrid = 5 +vperp_nelement = 16 +vperp_L = 1.5 +vperp_discretization = "gausslegendre_pseudospectral" +vperp_bc = "zero" +# Fokker-Planck operator requires the "gausslegendre_pseudospectral +# options for the vpa and vperp grids + +[fokker_planck_collisions] +# nuii sets the normalised input C[F,F] Fokker-Planck collision frequency +nuii = 1.876334222e-3 #(1/nu_alphae, as computed from input diagnostic) +Zi = 2.0 +self_collisions = true +slowing_down_test = true +frequency_option = "manual" +use_fokker_planck = true +use_conserving_corrections = true +sd_density = 1.0 +sd_temp = 0.025 # TD/Ealpha +sd_mi = 0.5 # mD/malpha +sd_me = 0.000013616 # 0.25/1836.0 me/malpha +sd_q = 1.0 + +[ion_source] +active=false +source_strength=0.0 +source_T=0.005 +source_n=1.0 +r_profile="constant" +r_width=1.0 +r_relative_minimum=0.0 +z_profile="gaussian" +z_width=0.1 +z_relative_minimum=0.0 +source_v0 = 1.0 +#source_type="alphas" +source_type="alphas-with-losses" +#source_type="beam-with-losses" +#source_vpa0 = 1.0 +#source_vperp0 = 1.0 +sink_strength = 0.0 +sink_vth=0.07071067811865475 + +[timestepping] +nstep = 50000 +dt = 2.5e-4 +nwrite = 100 +nwrite_dfns = 100 diff --git a/examples/fokker-planck/fokker-planck-relaxation.toml b/examples/fokker-planck/fokker-planck-relaxation.toml index f2d5651a1..0f633c12b 100644 --- a/examples/fokker-planck/fokker-planck-relaxation.toml +++ b/examples/fokker-planck/fokker-planck-relaxation.toml @@ -13,7 +13,7 @@ initial_temperature1 = 1.0 initial_density2 = 0.5 initial_temperature2 = 1.0 z_IC_option1 = "sinusoid" -z_IC_density_amplitude1 = 0.001 +z_IC_density_amplitude1 = 0.0 z_IC_density_phase1 = 0.0 z_IC_upar_amplitude1 = 0.0 z_IC_upar_phase1 = 0.0 diff --git a/examples/fokker-planck/fokker-planck-source-sink-slowing-down-alphas-no-self-collisions.toml b/examples/fokker-planck/fokker-planck-source-sink-slowing-down-alphas-no-self-collisions.toml new file mode 100644 index 000000000..1a95fc210 --- /dev/null +++ b/examples/fokker-planck/fokker-planck-source-sink-slowing-down-alphas-no-self-collisions.toml @@ -0,0 +1,91 @@ +# cheap input file for a 0D2V relaxation to a collisional Maxwellian distribution with self-ion collisions and collisions with fixed Maxwellian background of cold ions and electrons. +n_ion_species = 1 +n_neutral_species = 0 +electron_physics = "boltzmann_electron_response" +evolve_moments_density = false +evolve_moments_parallel_flow = false +evolve_moments_parallel_pressure = false +evolve_moments_conservation = false +T_e = 1.0 +T_wall = 1.0 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "sinusoid" +z_IC_density_amplitude1 = 0.0 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 0.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.0 +z_IC_temperature_phase1 = 0.0 +vpa_IC_option1 = "isotropic-beam" +vpa_IC_v01 = 1.0 +vpa_IC_vth01 = 0.1 +#vpa_IC_option1 = "directed-beam" +#vpa_IC_vpa01 = -1.5 +#vpa_IC_vperp01 = 0.0 +charge_exchange_frequency = 0.0 +ionization_frequency = 0.0 +constant_ionization_rate = false + +z_ngrid = 1 +z_nelement = 1 +z_nelement_local = 1 +z_bc = "wall" +z_discretization = "chebyshev_pseudospectral" +r_ngrid = 1 +r_nelement = 1 +r_nelement_local = 1 +r_bc = "periodic" +r_discretization = "chebyshev_pseudospectral" +vpa_ngrid = 5 +vpa_nelement = 32 +vpa_L = 3.0 +vpa_bc = "zero" +vpa_discretization = "gausslegendre_pseudospectral" +vperp_ngrid = 5 +vperp_nelement = 16 +vperp_L = 1.5 +vperp_discretization = "gausslegendre_pseudospectral" +vperp_bc = "zero" +# Fokker-Planck operator requires the "gausslegendre_pseudospectral +# options for the vpa and vperp grids + +[fokker_planck_collisions] +# nuii sets the normalised input C[F,F] Fokker-Planck collision frequency +nuii = 1.876334222e-3 #(1/nu_alphae, as computed from input diagnostic) +Zi = 2.0 +self_collisions = false +slowing_down_test = true +frequency_option = "manual" +use_fokker_planck = true +sd_density = 1.0 +sd_temp = 0.0025 # TD/Ealpha +sd_mi = 0.5 # mD/malpha +sd_me = 0.000013616 # 0.25/1836.0 me/malpha +sd_q = 1.0 + +[ion_source] +active=true +source_strength=1.0 +source_T=0.005 +source_n=1.0 +r_profile="constant" +r_width=1.0 +r_relative_minimum=0.0 +z_profile="gaussian" +z_width=0.1 +z_relative_minimum=0.0 +source_v0 = 1.0 +#source_type="alphas" +source_type="alphas-with-losses" +#source_type="beam-with-losses" +#source_vpa0 = 1.0 +#source_vperp0 = 1.0 +sink_strength = 1.0 +sink_vth=0.07071067811865475 + +[timestepping] +nstep = 250000 +dt = 1.0e-4 +nwrite = 500 +nwrite_dfns = 500 diff --git a/examples/fokker-planck/fokker-planck-relaxation-slowing-down-alphas-source-sink.toml b/examples/fokker-planck/fokker-planck-source-sink-slowing-down-alphas.toml similarity index 97% rename from examples/fokker-planck/fokker-planck-relaxation-slowing-down-alphas-source-sink.toml rename to examples/fokker-planck/fokker-planck-source-sink-slowing-down-alphas.toml index 098dc787a..cd49c82e7 100644 --- a/examples/fokker-planck/fokker-planck-relaxation-slowing-down-alphas-source-sink.toml +++ b/examples/fokker-planck/fokker-planck-source-sink-slowing-down-alphas.toml @@ -85,7 +85,7 @@ sink_strength = 1.0 sink_vth=0.07071067811865475 [timestepping] -nstep = 50000 -dt = 5.0e-4 -nwrite = 100 -nwrite_dfns = 100 +nstep = 250000 +dt = 1.0e-4 +nwrite = 500 +nwrite_dfns = 500 diff --git a/examples/kinetic-electrons/periodic_split3_boltzmann.toml b/examples/kinetic-electrons/periodic_split3_boltzmann.toml new file mode 100644 index 000000000..27a3eff33 --- /dev/null +++ b/examples/kinetic-electrons/periodic_split3_boltzmann.toml @@ -0,0 +1,91 @@ +#runtime_plots = true +n_ion_species = 1 +n_neutral_species = 1 +electron_physics = "boltzmann_electron_response" +evolve_moments_density = true +evolve_moments_parallel_flow = true +evolve_moments_parallel_pressure = true +evolve_moments_conservation = true +recycling_fraction = 0.5 +T_e = 1.0 +T_wall = 0.1 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "sinusoid" +z_IC_density_amplitude1 = 0.1 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.1 +z_IC_temperature_phase1 = 1.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "sinusoid" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = 0.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 0.75 +ionization_frequency = 0.0 +constant_ionization_rate = false +nu_ei = 1000.0 +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 17 +z_nelement = 16 +#z_nelement_local = 16 +z_bc = "periodic" +z_discretization = "chebyshev_pseudospectral" +vpa_ngrid = 6 +vpa_nelement = 31 +vpa_L = 12.0 +vpa_bc = "zero" +vpa_discretization = "chebyshev_pseudospectral" +vz_ngrid = 6 +vz_nelement = 31 +vz_L = 12.0 +vz_bc = "zero" +vz_discretization = "chebyshev_pseudospectral" + +[timestepping] +type = "Fekete4(3)" +nstep = 1000000 +dt = 1.0e-6 +minimum_dt = 1.0e-7 +rtol = 1.0e-7 +nwrite = 10000 +nwrite_dfns = 100000 +steady_state_residual = true +converged_residual_value = 1.0e-3 + +[nonlinear_solver] +nonlinear_max_iterations = 100 +#rtol = 1.0e-9 +#atol = 1.0e-12 + +[ion_numerical_dissipation] +vpa_dissipation_coefficient = 1.0e0 +force_minimum_pdf_value = 0.0 + +[neutral_numerical_dissipation] +vz_dissipation_coefficient = 1.0e-1 +force_minimum_pdf_value = 0.0 + +[krook_collisions] +use_krook = true diff --git a/examples/kinetic-electrons/periodic_split3_braginskii-IMEX.toml b/examples/kinetic-electrons/periodic_split3_braginskii-IMEX.toml new file mode 100644 index 000000000..4dfe44807 --- /dev/null +++ b/examples/kinetic-electrons/periodic_split3_braginskii-IMEX.toml @@ -0,0 +1,110 @@ +#runtime_plots = true +n_ion_species = 1 +n_neutral_species = 1 +electron_physics = "braginskii_fluid" +evolve_moments_density = true +evolve_moments_parallel_flow = true +evolve_moments_parallel_pressure = true +evolve_moments_conservation = true +recycling_fraction = 0.5 +T_e = 1.0 +T_wall = 0.1 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "sinusoid" +z_IC_density_amplitude1 = 0.1 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.1 +z_IC_temperature_phase1 = 1.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "sinusoid" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = 0.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 0.75 +ionization_frequency = 0.0 +constant_ionization_rate = false +nu_ei = 1000.0 +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 17 +z_nelement = 16 +#z_nelement_local = 16 +z_bc = "periodic" +z_discretization = "chebyshev_pseudospectral" +vpa_ngrid = 6 +vpa_nelement = 31 +vpa_L = 12.0 +vpa_bc = "zero" +vpa_discretization = "chebyshev_pseudospectral" +vz_ngrid = 6 +vz_nelement = 31 +vz_L = 12.0 +vz_bc = "zero" +vz_discretization = "chebyshev_pseudospectral" + +[timestepping] +type = "KennedyCarpenterARK324" +implicit_ion_advance = false +implicit_vpa_advection = false +implicit_braginskii_conduction = true +nstep = 1000000 +dt = 1.0e-6 +minimum_dt = 1.0e-7 +rtol = 1.0e-7 +nwrite = 10000 +nwrite_dfns = 100000 +steady_state_residual = true +converged_residual_value = 1.0e-3 +#[timestepping] +#type = "KennedyCarpenterARK324" +#implicit_ion_advance = false +#implicit_vpa_advection = false +#implicit_braginskii_conduction = true +#nstep = 1000000 +#dt = 1.0e-6 +#minimum_dt = 1.0e-7 +#rtol = 1.0e-7 +#nwrite = 100 +#nwrite_dfns = 100 +#steady_state_residual = true +#converged_residual_value = 1.0e-3 +#write_after_fixed_step_count = true +#write_error_diagnostics = true +#write_steady_state_diagnostics = true + +[nonlinear_solver] +nonlinear_max_iterations = 100 +#rtol = 1.0e-9 +#atol = 1.0e-12 + +[ion_numerical_dissipation] +vpa_dissipation_coefficient = 1.0e0 +force_minimum_pdf_value = 0.0 + +[neutral_numerical_dissipation] +vz_dissipation_coefficient = 1.0e-1 +force_minimum_pdf_value = 0.0 + +[krook_collisions] +use_krook = true diff --git a/examples/kinetic-electrons/periodic_split3_braginskii.toml b/examples/kinetic-electrons/periodic_split3_braginskii.toml new file mode 100644 index 000000000..31741e462 --- /dev/null +++ b/examples/kinetic-electrons/periodic_split3_braginskii.toml @@ -0,0 +1,104 @@ +#runtime_plots = true +n_ion_species = 1 +n_neutral_species = 1 +electron_physics = "braginskii_fluid" +evolve_moments_density = true +evolve_moments_parallel_flow = true +evolve_moments_parallel_pressure = true +evolve_moments_conservation = true +recycling_fraction = 0.5 +T_e = 1.0 +T_wall = 0.1 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "sinusoid" +z_IC_density_amplitude1 = 0.1 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.1 +z_IC_temperature_phase1 = 1.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "sinusoid" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = 0.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 0.75 +ionization_frequency = 0.0 +constant_ionization_rate = false +nu_ei = 1000.0 +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 17 +z_nelement = 16 +#z_nelement_local = 16 +z_bc = "periodic" +z_discretization = "chebyshev_pseudospectral" +vpa_ngrid = 6 +vpa_nelement = 31 +vpa_L = 12.0 +vpa_bc = "zero" +vpa_discretization = "chebyshev_pseudospectral" +vz_ngrid = 6 +vz_nelement = 31 +vz_L = 12.0 +vz_bc = "zero" +vz_discretization = "chebyshev_pseudospectral" + +[timestepping] +type = "Fekete4(3)" +nstep = 1000000 +dt = 1.0e-6 +minimum_dt = 1.0e-9 +rtol = 1.0e-7 +nwrite = 10000 +nwrite_dfns = 100000 +steady_state_residual = true +converged_residual_value = 1.0e-3 +#[timestepping] +#type = "Fekete4(3)" +#nstep = 1000000 +#dt = 1.0e-6 +#minimum_dt = 1.0e-9 +#rtol = 1.0e-7 +#nwrite = 1000 +#nwrite_dfns = 1000 +#steady_state_residual = true +#converged_residual_value = 1.0e-3 +#write_after_fixed_step_count = true +#write_error_diagnostics = true +#write_steady_state_diagnostics = true + +[nonlinear_solver] +nonlinear_max_iterations = 100 +#rtol = 1.0e-9 +#atol = 1.0e-12 + +[ion_numerical_dissipation] +vpa_dissipation_coefficient = 1.0e0 +force_minimum_pdf_value = 0.0 + +[neutral_numerical_dissipation] +vz_dissipation_coefficient = 1.0e-1 +force_minimum_pdf_value = 0.0 + +[krook_collisions] +use_krook = true diff --git a/examples/kinetic-electrons/periodic_split3_kinetic-IMEX.toml b/examples/kinetic-electrons/periodic_split3_kinetic-IMEX.toml new file mode 100644 index 000000000..ea91e79fa --- /dev/null +++ b/examples/kinetic-electrons/periodic_split3_kinetic-IMEX.toml @@ -0,0 +1,118 @@ +#runtime_plots = true +n_ion_species = 1 +n_neutral_species = 1 +electron_physics = "kinetic_electrons" +evolve_moments_density = true +evolve_moments_parallel_flow = true +evolve_moments_parallel_pressure = true +evolve_moments_conservation = true +recycling_fraction = 0.5 +T_e = 1.0 +T_wall = 0.1 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "sinusoid" +z_IC_density_amplitude1 = 0.1 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.1 +z_IC_temperature_phase1 = 1.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "sinusoid" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = 0.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 0.75 +ionization_frequency = 0.0 +constant_ionization_rate = false +nu_ei = 1000.0 +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 17 +z_nelement = 16 +#z_nelement_local = 16 +z_bc = "periodic" +z_discretization = "chebyshev_pseudospectral" +vpa_ngrid = 6 +vpa_nelement = 31 +vpa_L = 12.0 +vpa_bc = "zero" +vpa_discretization = "chebyshev_pseudospectral" +vz_ngrid = 6 +vz_nelement = 31 +vz_L = 12.0 +vz_bc = "zero" +vz_discretization = "chebyshev_pseudospectral" + +[timestepping] +type = "KennedyCarpenterARK324" +implicit_electron_advance = true +implicit_ion_advance = false +implicit_vpa_advection = false +nstep = 1000000 +dt = 1.0e-6 +minimum_dt = 1.0e-7 +#maximum_dt = 2.0e-5 +rtol = 1.0e-7 +max_increase_factor_near_last_fail = 1.001 +last_fail_proximity_factor = 1.1 +max_increase_factor = 1.05 +nwrite = 1000 #10000 +nwrite_dfns = 1000 #100000 +steady_state_residual = true +converged_residual_value = 1.0e-3 + +[electron_timestepping] +nstep = 5000000 +#nstep = 1 +dt = 2.0e-8 +maximum_dt = 1.0 +nwrite = 10000 +nwrite_dfns = 100000 +#type = "SSPRK4" +type = "Fekete4(3)" +rtol = 1.0e-6 +atol = 1.0e-14 +minimum_dt = 1.0e-10 +initialization_residual_value = 2.5 +converged_residual_value = 0.1 #1.0e-3 +#debug_io = 10000 + +[nonlinear_solver] +nonlinear_max_iterations = 100 +#rtol = 1.0e-9 +#atol = 1.0e-12 + +[ion_numerical_dissipation] +vpa_dissipation_coefficient = 1.0e0 +force_minimum_pdf_value = 0.0 + +[electron_numerical_dissipation] +vpa_dissipation_coefficient = 2.0e0 +force_minimum_pdf_value = 0.0 + +[neutral_numerical_dissipation] +vz_dissipation_coefficient = 1.0e-1 +force_minimum_pdf_value = 0.0 + +[krook_collisions] +use_krook = true diff --git a/examples/kinetic-electrons/periodic_split3_kinetic.toml b/examples/kinetic-electrons/periodic_split3_kinetic.toml new file mode 100644 index 000000000..c2c734a78 --- /dev/null +++ b/examples/kinetic-electrons/periodic_split3_kinetic.toml @@ -0,0 +1,131 @@ +#runtime_plots = true +n_ion_species = 1 +n_neutral_species = 1 +electron_physics = "kinetic_electrons" +evolve_moments_density = true +evolve_moments_parallel_flow = true +evolve_moments_parallel_pressure = true +evolve_moments_conservation = true +recycling_fraction = 0.5 +T_e = 1.0 +T_wall = 0.1 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "sinusoid" +z_IC_density_amplitude1 = 0.1 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.1 +z_IC_temperature_phase1 = 1.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "sinusoid" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = 0.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 0.75 +ionization_frequency = 0.0 +constant_ionization_rate = false +nu_ei = 1000.0 +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 17 +z_nelement = 16 +#z_nelement_local = 16 +z_bc = "periodic" +z_discretization = "chebyshev_pseudospectral" +vpa_ngrid = 6 +vpa_nelement = 31 +vpa_L = 12.0 +vpa_bc = "zero" +vpa_discretization = "chebyshev_pseudospectral" +vz_ngrid = 6 +vz_nelement = 31 +vz_L = 12.0 +vz_bc = "zero" +vz_discretization = "chebyshev_pseudospectral" + +[timestepping] +#type = "KennedyCarpenterARK324" +type = "Fekete4(3)" +implicit_ion_advance = false +implicit_vpa_advection = false +nstep = 1000000 +dt = 1.0e-6 +minimum_dt = 1.0e-7 +rtol = 1.0e-7 +max_increase_factor_near_last_fail = 1.001 +last_fail_proximity_factor = 1.1 +max_increase_factor = 1.05 +nwrite = 10000 +nwrite_dfns = 100000 +steady_state_residual = true +converged_residual_value = 1.0e-3 +#[timestepping] +##type = "KennedyCarpenterARK324" +#type = "Fekete4(3)" +#implicit_ion_advance = false +#implicit_vpa_advection = false +#nstep = 1000000 +#dt = 1.0e-6 +#minimum_dt = 1.0e-7 +#rtol = 1.0e-7 +#max_increase_factor_near_last_fail = 1.001 +#last_fail_proximity_factor = 1.1 +#max_increase_factor = 1.05 +#nwrite = 100 +#nwrite_dfns = 100 +#steady_state_residual = true +#converged_residual_value = 1.0e-3 +#write_after_fixed_step_count = true +#write_error_diagnostics = true +#write_steady_state_diagnostics = true + +[electron_timestepping] +nstep = 5000000 +#nstep = 1 +dt = 2.0e-8 +maximum_dt = 1.0 +nwrite = 10000 +nwrite_dfns = 100000 +#type = "SSPRK4" +type = "Fekete4(3)" +rtol = 1.0e-6 +atol = 1.0e-14 +minimum_dt = 1.0e-10 +initialization_residual_value = 2.5 +converged_residual_value = 0.1 #1.0e-3 +#debug_io = 10000 + +[ion_numerical_dissipation] +vpa_dissipation_coefficient = 1.0e0 +force_minimum_pdf_value = 0.0 + +[electron_numerical_dissipation] +vpa_dissipation_coefficient = 2.0e0 +force_minimum_pdf_value = 0.0 + +[neutral_numerical_dissipation] +vz_dissipation_coefficient = 1.0e-1 +force_minimum_pdf_value = 0.0 + +[krook_collisions] +use_krook = true diff --git a/examples/kinetic-electrons/periodic_split3_kinetic_high-collisionality.toml b/examples/kinetic-electrons/periodic_split3_kinetic_high-collisionality.toml new file mode 100644 index 000000000..133d76b16 --- /dev/null +++ b/examples/kinetic-electrons/periodic_split3_kinetic_high-collisionality.toml @@ -0,0 +1,134 @@ +#runtime_plots = true +n_ion_species = 1 +n_neutral_species = 1 +electron_physics = "kinetic_electrons" +evolve_moments_density = true +evolve_moments_parallel_flow = true +evolve_moments_parallel_pressure = true +evolve_moments_conservation = true +recycling_fraction = 0.5 +T_e = 1.0 +T_wall = 0.1 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "sinusoid" +z_IC_density_amplitude1 = 0.1 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.1 +z_IC_temperature_phase1 = 1.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "sinusoid" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = 0.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 0.75 +ionization_frequency = 0.0 +constant_ionization_rate = false +nu_ei = 1000.0 +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 17 +z_nelement = 16 +#z_nelement_local = 16 +z_bc = "periodic" +z_discretization = "chebyshev_pseudospectral" +vpa_ngrid = 6 +vpa_nelement = 31 +vpa_L = 12.0 +vpa_bc = "zero" +vpa_discretization = "chebyshev_pseudospectral" +vz_ngrid = 6 +vz_nelement = 31 +vz_L = 12.0 +vz_bc = "zero" +vz_discretization = "chebyshev_pseudospectral" + +[reference_params] +Tref = 20.0 + +[timestepping] +#type = "KennedyCarpenterARK324" +type = "Fekete4(3)" +implicit_ion_advance = false +implicit_vpa_advection = false +nstep = 1000000 +dt = 1.0e-6 +minimum_dt = 1.0e-7 +rtol = 1.0e-7 +max_increase_factor_near_last_fail = 1.001 +last_fail_proximity_factor = 1.1 +max_increase_factor = 1.05 +nwrite = 10000 +nwrite_dfns = 100000 +steady_state_residual = true +converged_residual_value = 1.0e-3 +#[timestepping] +##type = "KennedyCarpenterARK324" +#type = "Fekete4(3)" +#implicit_ion_advance = false +#implicit_vpa_advection = false +#nstep = 1000000 +#dt = 1.0e-6 +#minimum_dt = 1.0e-7 +#rtol = 1.0e-7 +#max_increase_factor_near_last_fail = 1.001 +#last_fail_proximity_factor = 1.1 +#max_increase_factor = 1.05 +#nwrite = 100 +#nwrite_dfns = 100 +#steady_state_residual = true +#converged_residual_value = 1.0e-3 +#write_after_fixed_step_count = true +#write_error_diagnostics = true +#write_steady_state_diagnostics = true + +[electron_timestepping] +nstep = 5000000 +#nstep = 1 +dt = 2.0e-8 +maximum_dt = 1.0 +nwrite = 10000 +nwrite_dfns = 100000 +#type = "SSPRK4" +type = "Fekete4(3)" +rtol = 1.0e-6 +atol = 1.0e-14 +minimum_dt = 1.0e-10 +initialization_residual_value = 2.5 +converged_residual_value = 0.1 #1.0e-3 +#debug_io = 10000 + +[ion_numerical_dissipation] +vpa_dissipation_coefficient = 1.0e0 +force_minimum_pdf_value = 0.0 + +[electron_numerical_dissipation] +vpa_dissipation_coefficient = 2.0e0 +force_minimum_pdf_value = 0.0 + +[neutral_numerical_dissipation] +vz_dissipation_coefficient = 1.0e-1 +force_minimum_pdf_value = 0.0 + +[krook_collisions] +use_krook = true diff --git a/examples/kinetic-electrons/wall+sheath-bc_boltzmann_loworder.toml b/examples/kinetic-electrons/wall+sheath-bc_boltzmann_loworder.toml new file mode 100644 index 000000000..35bb42e02 --- /dev/null +++ b/examples/kinetic-electrons/wall+sheath-bc_boltzmann_loworder.toml @@ -0,0 +1,95 @@ +steady_state_residual = true +converged_residual_value = 1.0e-3 +n_ion_species = 1 +n_neutral_species = 1 +#boltzmann_electron_response = false +#electron_physics = "kinetic_electrons" +evolve_moments_density = false +evolve_moments_parallel_flow = false +evolve_moments_parallel_pressure = false +evolve_moments_conservation = false +T_e = 1.0 +T_wall = 1.0 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "gaussian" +z_IC_density_amplitude1 = 0.001 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.0 +z_IC_temperature_phase1 = 0.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "gaussian" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = 0.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 2.0 +electron_charge_exchange_frequency = 0.0 +nu_ei = 0.0 +ionization_frequency = 2.0 +#electron_ionization_frequency = 2.0 +#ionization_energy = 1.0 +constant_ionization_rate = false +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 5 +#z_nelement = 32 +z_nelement = 64 +#z_nelement = 128 +z_bc = "wall" +z_discretization = "chebyshev_pseudospectral" +z_spacing_option = "sqrt" +vpa_ngrid = 5 +#vpa_nelement = 40 +vpa_nelement = 80 +vpa_L = 12.0 #8.0 +vpa_bc = "zero" +vpa_discretization = "chebyshev_pseudospectral" +#vpa_discretization = "gausslegendre_pseudospectral" +vz_ngrid = 5 +vz_nelement = 80 +vz_L = 12.0 +vz_bc = "zero" +vz_discretization = "chebyshev_pseudospectral" + +[output] +ascii_output = true + +[ion_numerical_dissipation] +#moment_dissipation_coefficient = 0.0001 +#moment_dissipation_coefficient = 1.0 +#vpa_dissipation_coefficient = 0.002 +vpa_dissipation_coefficient = 0.1 +#vpa_dissipation_coefficient = 0.2 +#vpa_dissipation_coefficient = 2.0 +#vpa_dissipation_coefficient = 20.0 + +[timestepping] +nstep = 1000000 +#nstep = 1 +dt = 1.0e-5 +nwrite = 10000 +nwrite_dfns = 10000 + +[electron_numerical_dissipation] + +[neutral_numerical_dissipation] diff --git a/examples/kinetic-electrons/wall+sheath-bc_kinetic.toml b/examples/kinetic-electrons/wall+sheath-bc_kinetic.toml new file mode 100644 index 000000000..a0a928479 --- /dev/null +++ b/examples/kinetic-electrons/wall+sheath-bc_kinetic.toml @@ -0,0 +1,88 @@ +n_ion_species = 1 +n_neutral_species = 1 +#boltzmann_electron_response = false +electron_physics = "kinetic_electrons" +evolve_moments_density = false +evolve_moments_parallel_flow = false +evolve_moments_parallel_pressure = false +evolve_moments_conservation = false +T_e = 1.0 +T_wall = 1.0 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "gaussian" +z_IC_density_amplitude1 = 0.001 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.0 +z_IC_temperature_phase1 = 0.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "gaussian" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = 0.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 2.0 +electron_charge_exchange_frequency = 0.0 +nu_ei = 0.0 +ionization_frequency = 2.0 +electron_ionization_frequency = 2.0 +ionization_energy = 1.0 +constant_ionization_rate = false +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 9 +#z_nelement = 16 +z_nelement = 32 +#z_nelement = 64 +z_bc = "wall" +z_discretization = "chebyshev_pseudospectral" +z_spacing_option = "sqrt" +vpa_ngrid = 17 +#vpa_nelement = 10 +vpa_nelement = 20 +vpa_L = 12.0 #8.0 +vpa_bc = "zero" +vpa_discretization = "chebyshev_pseudospectral" +#vpa_discretization = "gausslegendre_pseudospectral" +vz_ngrid = 17 +#vz_nelement = 10 +vz_nelement = 20 +vz_L = 8.0 +vz_bc = "zero" +vz_discretization = "chebyshev_pseudospectral" + +[output] +ascii_output = true + +[electron_numerical_dissipation] +#moment_dissipation_coefficient = 0.0001 +#moment_dissipation_coefficient = 1.0 +#vpa_dissipation_coefficient = 0.002 +#vpa_dissipation_coefficient = 0.2 +#vpa_dissipation_coefficient = 2.0 +vpa_dissipation_coefficient = 20.0 + +[timestepping] +nstep = 40000 +#nstep = 1 +dt = 0.0005 +nwrite = 200 diff --git a/examples/kinetic-electrons/wall+sheath-bc_kinetic_krook_loworder.toml b/examples/kinetic-electrons/wall+sheath-bc_kinetic_krook_loworder.toml new file mode 100644 index 000000000..da7a1bf63 --- /dev/null +++ b/examples/kinetic-electrons/wall+sheath-bc_kinetic_krook_loworder.toml @@ -0,0 +1,90 @@ +n_ion_species = 1 +n_neutral_species = 1 +#boltzmann_electron_response = false +electron_physics = "kinetic_electrons" +evolve_moments_density = false +evolve_moments_parallel_flow = false +evolve_moments_parallel_pressure = false +evolve_moments_conservation = false +T_e = 1.0 +T_wall = 1.0 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "gaussian" +z_IC_density_amplitude1 = 0.001 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.0 +z_IC_temperature_phase1 = 0.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "gaussian" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = 0.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 2.0 +electron_charge_exchange_frequency = 0.0 +nu_ei = 0.0 +ionization_frequency = 2.0 +electron_ionization_frequency = 2.0 +ionization_energy = 1.0 +constant_ionization_rate = false +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 5 +#z_nelement = 32 +z_nelement = 64 +#z_nelement = 128 +z_bc = "wall" +z_discretization = "chebyshev_pseudospectral" +z_spacing_option = "sqrt" +vpa_ngrid = 5 +#vpa_nelement = 40 +vpa_nelement = 80 +vpa_L = 12.0 #8.0 +vpa_bc = "zero" +vpa_discretization = "chebyshev_pseudospectral" +#vpa_discretization = "gausslegendre_pseudospectral" +vz_ngrid = 17 +vz_nelement = 10 +vz_L = 8.0 +vz_bc = "zero" +vz_discretization = "chebyshev_pseudospectral" + +[output] +ascii_output = true + +[electron_numerical_dissipation] +#moment_dissipation_coefficient = 0.0001 +#moment_dissipation_coefficient = 1.0 +#vpa_dissipation_coefficient = 0.002 +#vpa_dissipation_coefficient = 0.2 +#vpa_dissipation_coefficient = 2.0 +vpa_dissipation_coefficient = 20.0 + +[krook_collisions] +use_krook = true + +[timestepping] +nstep = 40000 +#nstep = 1 +dt = 0.0005 +nwrite = 200 diff --git a/examples/kinetic-electrons/wall+sheath-bc_kinetic_loworder.toml b/examples/kinetic-electrons/wall+sheath-bc_kinetic_loworder.toml new file mode 100644 index 000000000..c1e8a7637 --- /dev/null +++ b/examples/kinetic-electrons/wall+sheath-bc_kinetic_loworder.toml @@ -0,0 +1,101 @@ +steady_state_residual = true +converged_residual_value = 1.0e-3 +n_ion_species = 1 +n_neutral_species = 1 +#boltzmann_electron_response = false +electron_physics = "kinetic_electrons" +evolve_moments_density = false +evolve_moments_parallel_flow = false +evolve_moments_parallel_pressure = false +evolve_moments_conservation = false +T_e = 1.0 +T_wall = 1.0 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "gaussian" +z_IC_density_amplitude1 = 0.001 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.0 +z_IC_temperature_phase1 = 0.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "gaussian" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = 0.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 2.0 +electron_charge_exchange_frequency = 0.0 +nu_ei = 0.0 +ionization_frequency = 2.0 +#electron_ionization_frequency = 2.0 +#ionization_energy = 1.0 +constant_ionization_rate = false +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 5 +#z_nelement = 32 +z_nelement = 64 +#z_nelement = 128 +z_bc = "wall" +z_discretization = "chebyshev_pseudospectral" +z_spacing_option = "sqrt" +vpa_ngrid = 5 +#vpa_nelement = 40 +vpa_nelement = 80 +vpa_L = 12.0 #8.0 +vpa_bc = "zero" +vpa_discretization = "chebyshev_pseudospectral" +#vpa_discretization = "gausslegendre_pseudospectral" +vz_ngrid = 5 +vz_nelement = 80 +vz_L = 12.0 +vz_bc = "zero" +vz_discretization = "chebyshev_pseudospectral" + +[output] +#ascii_output = true + +[electron_numerical_dissipation] +#moment_dissipation_coefficient = 0.0001 +#moment_dissipation_coefficient = 1.0 +#vpa_dissipation_coefficient = 0.002 +#vpa_dissipation_coefficient = 0.2 +vpa_dissipation_coefficient = 2.0 +#vpa_dissipation_coefficient = 20.0 + +[timestepping] +nstep = 40000 +#nstep = 1 +dt = 5.0e-5 +nwrite = 200 +nwrite_dfns = 10000 +type = "SSPRK4" + +[electron_timestepping] +nstep = 5000000 +#nstep = 1 +dt = 2.0e-8 +nwrite = 1000 +nwrite_dfns = 1000 +#type = "SSPRK4" +type = "Fekete4(3)" +minimum_dt = 1.0e-8 diff --git a/examples/kinetic-electrons/wall-bc_recyclefraction0.5_split3_boltzmann-vpadiss0.toml b/examples/kinetic-electrons/wall-bc_recyclefraction0.5_split3_boltzmann-vpadiss0.toml new file mode 100644 index 000000000..58998f2f0 --- /dev/null +++ b/examples/kinetic-electrons/wall-bc_recyclefraction0.5_split3_boltzmann-vpadiss0.toml @@ -0,0 +1,97 @@ +#runtime_plots = true +n_ion_species = 1 +n_neutral_species = 1 +boltzmann_electron_response = true +evolve_moments_density = true +evolve_moments_parallel_flow = true +evolve_moments_parallel_pressure = true +evolve_moments_conservation = true +recycling_fraction = 0.5 +T_e = 0.05 # 1.0 +T_wall = 0.1 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "gaussian" +z_IC_density_amplitude1 = 1.0 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.0 +z_IC_temperature_phase1 = 0.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "gaussian" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = -1.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 0.75 +ionization_frequency = 0.5 +constant_ionization_rate = false +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 5 +z_nelement = 32 +z_nelement_local = 16 +z_bc = "wall" +z_discretization = "chebyshev_pseudospectral" +z_element_spacing_option = "sqrt" +vpa_ngrid = 6 +vpa_nelement = 63 +vpa_L = 36.0 +vpa_bc = "zero" +vpa_discretization = "chebyshev_pseudospectral" +vz_ngrid = 6 +vz_nelement = 63 +vz_L = 36.0 +vz_bc = "zero" +vz_discretization = "chebyshev_pseudospectral" + +[timestepping] +type = "Fekete4(3)" +#nstep = 50000 +nstep = 3000000 +dt = 1.0e-6 +minimum_dt = 1.0e-6 +nwrite = 10000 +nwrite_dfns = 100000 +steady_state_residual = true +converged_residual_value = 1.0e-3 + +[ion_source] +active = true +z_profile = "gaussian" +z_width = 0.125 +source_strength = 2.0 +source_T = 2.0 + +[ion_numerical_dissipation] +#vpa_dissipation_coefficient = 1.0e-1 +#vpa_dissipation_coefficient = 1.0e-2 +#vpa_dissipation_coefficient = 1.0e-3 +force_minimum_pdf_value = 0.0 + +[neutral_numerical_dissipation] +#vz_dissipation_coefficient = 1.0e-1 +#vz_dissipation_coefficient = 1.0e-2 +#vz_dissipation_coefficient = 1.0e-3 +force_minimum_pdf_value = 0.0 + +[krook_collisions] +use_krook = true diff --git a/examples/kinetic-electrons/wall-bc_recyclefraction0.5_split3_boltzmann.toml b/examples/kinetic-electrons/wall-bc_recyclefraction0.5_split3_boltzmann.toml new file mode 100644 index 000000000..4b36701ae --- /dev/null +++ b/examples/kinetic-electrons/wall-bc_recyclefraction0.5_split3_boltzmann.toml @@ -0,0 +1,97 @@ +#runtime_plots = true +n_ion_species = 1 +n_neutral_species = 1 +boltzmann_electron_response = true +evolve_moments_density = true +evolve_moments_parallel_flow = true +evolve_moments_parallel_pressure = true +evolve_moments_conservation = true +recycling_fraction = 0.5 +T_e = 0.2 # 1.0 +T_wall = 0.1 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "gaussian" +z_IC_density_amplitude1 = 0.001 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.0 +z_IC_temperature_phase1 = 0.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "gaussian" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = -1.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 0.75 +ionization_frequency = 0.5 +constant_ionization_rate = false +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 5 +z_nelement = 32 +z_nelement_local = 16 +z_bc = "wall" +z_discretization = "chebyshev_pseudospectral" +z_element_spacing_option = "sqrt" +vpa_ngrid = 6 +vpa_nelement = 63 +vpa_L = 36.0 +vpa_bc = "zero" +vpa_discretization = "chebyshev_pseudospectral" +vz_ngrid = 6 +vz_nelement = 63 +vz_L = 36.0 +vz_bc = "zero" +vz_discretization = "chebyshev_pseudospectral" + +[timestepping] +type = "Fekete4(3)" +#nstep = 50000 +nstep = 1000000 +dt = 1.0e-6 +minimum_dt = 1.0e-6 +nwrite = 10000 +nwrite_dfns = 100000 +steady_state_residual = true +converged_residual_value = 1.0e-3 + +[ion_source] +active = true +z_profile = "gaussian" +z_width = 0.125 +source_strength = 2.0 +source_T = 2.0 + +[ion_numerical_dissipation] +vpa_dissipation_coefficient = 1.0e-1 +#vpa_dissipation_coefficient = 1.0e-2 +#vpa_dissipation_coefficient = 1.0e-3 +force_minimum_pdf_value = 0.0 + +[neutral_numerical_dissipation] +vz_dissipation_coefficient = 1.0e-1 +#vz_dissipation_coefficient = 1.0e-2 +#vz_dissipation_coefficient = 1.0e-3 +force_minimum_pdf_value = 0.0 + +[krook_collisions] +use_krook = true diff --git a/examples/kinetic-electrons/wall-bc_recyclefraction0.5_split3_braginskii-vpadiss0-IMEX.toml b/examples/kinetic-electrons/wall-bc_recyclefraction0.5_split3_braginskii-vpadiss0-IMEX.toml new file mode 100644 index 000000000..c9230a33a --- /dev/null +++ b/examples/kinetic-electrons/wall-bc_recyclefraction0.5_split3_braginskii-vpadiss0-IMEX.toml @@ -0,0 +1,111 @@ +#runtime_plots = true +n_ion_species = 1 +n_neutral_species = 1 +electron_physics = "braginskii_fluid" +evolve_moments_density = true +evolve_moments_parallel_flow = true +evolve_moments_parallel_pressure = true +evolve_moments_conservation = true +recycling_fraction = 0.5 +T_e = 0.2 # 1.0 +T_wall = 0.1 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "gaussian" +z_IC_density_amplitude1 = 0.001 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.0 +z_IC_temperature_phase1 = 0.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "gaussian" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = -1.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 0.75 +ionization_frequency = 0.5 +constant_ionization_rate = false +nu_ei = 1000.0 +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 5 +z_nelement = 32 +#z_nelement_local = 16 +z_bc = "wall" +z_discretization = "chebyshev_pseudospectral" +z_element_spacing_option = "sqrt" +vpa_ngrid = 6 +vpa_nelement = 63 +vpa_L = 36.0 +vpa_bc = "zero" +vpa_discretization = "chebyshev_pseudospectral" +vz_ngrid = 6 +vz_nelement = 63 +vz_L = 36.0 +vz_bc = "zero" +vz_discretization = "chebyshev_pseudospectral" + +[timestepping] +type = "KennedyCarpenterARK324" +#type = "KennedyCarpenterARK324-explicit" +#type = "KennedyCarpenterARK437" +#type = "fake_imex" +#nstep = 50000 +implicit_ion_advance = false +implicit_vpa_advection = false +implicit_braginskii_conduction = true +nstep = 1000000 +dt = 1.0e-6 +minimum_dt = 1.0e-9 +rtol = 1.0e-7 +#rtol = 1.0e-9 +nwrite = 10000 +nwrite_dfns = 100000 +steady_state_residual = true +converged_residual_value = 1.0e-3 +#write_after_fixed_step_count = true + +[ion_source] +active = true +z_profile = "gaussian" +z_width = 0.125 +source_strength = 2.0 +source_T = 2.0 + +[ion_numerical_dissipation] +#vpa_dissipation_coefficient = 1.0e-1 +#vpa_dissipation_coefficient = 1.0e-2 +#vpa_dissipation_coefficient = 1.0e-3 +force_minimum_pdf_value = 0.0 + +[electron_numerical_dissipation] +vpa_dissipation_coefficient = 2.0 +force_minimum_pdf_value = 0.0 + +[neutral_numerical_dissipation] +#vz_dissipation_coefficient = 1.0e-1 +#vz_dissipation_coefficient = 1.0e-2 +#vz_dissipation_coefficient = 1.0e-3 +force_minimum_pdf_value = 0.0 + +[krook_collisions] +use_krook = true diff --git a/examples/kinetic-electrons/wall-bc_recyclefraction0.5_split3_kinetic-vpadiss0.toml b/examples/kinetic-electrons/wall-bc_recyclefraction0.5_split3_kinetic-vpadiss0.toml new file mode 100644 index 000000000..0a66ed75e --- /dev/null +++ b/examples/kinetic-electrons/wall-bc_recyclefraction0.5_split3_kinetic-vpadiss0.toml @@ -0,0 +1,117 @@ +#runtime_plots = true +n_ion_species = 1 +n_neutral_species = 1 +electron_physics = "kinetic_electrons_with_temperature_equation" +evolve_moments_density = true +evolve_moments_parallel_flow = true +evolve_moments_parallel_pressure = true +evolve_moments_conservation = true +recycling_fraction = 0.5 +T_e = 0.2 # 1.0 +T_wall = 0.1 +initial_density1 = 1.0 +initial_temperature1 = 0.1 +z_IC_option1 = "gaussian" +z_IC_density_amplitude1 = 0.001 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.0 +z_IC_temperature_phase1 = 0.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "gaussian" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = -1.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 0.75 +ionization_frequency = 0.5 +constant_ionization_rate = false +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 5 +z_nelement = 32 +#z_nelement_local = 16 +z_bc = "wall" +z_discretization = "chebyshev_pseudospectral" +z_element_spacing_option = "sqrt" +vpa_ngrid = 6 +vpa_nelement = 63 +vpa_L = 48.0 +vpa_bc = "zero" +vpa_discretization = "chebyshev_pseudospectral" +vz_ngrid = 6 +vz_nelement = 63 +vz_L = 36.0 +vz_bc = "zero" +vz_discretization = "chebyshev_pseudospectral" + +[timestepping] +type = "Fekete4(3)" +#nstep = 50000 +nstep = 10000000 +dt = 1.0e-6 +minimum_dt = 1.0e-6 +max_increase_factor_near_last_fail = 1.001 +last_fail_proximity_factor = 1.1 +nwrite = 100000 +nwrite_dfns = 100000 +steady_state_residual = true +converged_residual_value = 1.0e-3 + +[electron_timestepping] +nstep = 5000000 +#nstep = 1 +dt = 2.0e-8 +nwrite = 10000 +nwrite_dfns = 100000 +#type = "SSPRK4" +type = "Fekete4(3)" +rtol = 1.0e-3 +atol = 1.0e-14 +minimum_dt = 1.0e-9 +initialization_residual_value = 2.5 +converged_residual_value = 0.1 #1.0e-3 + +[ion_source] +active = true +z_profile = "gaussian" +z_width = 0.125 +source_strength = 2.0 +source_T = 2.0 + +[ion_numerical_dissipation] +#vpa_dissipation_coefficient = 1.0e-1 +#vpa_dissipation_coefficient = 1.0e-2 +#vpa_dissipation_coefficient = 1.0e-3 +force_minimum_pdf_value = 0.0 + +[electron_numerical_dissipation] +#vpa_dissipation_coefficient = 2.0 +force_minimum_pdf_value = 0.0 + +[neutral_numerical_dissipation] +#vz_dissipation_coefficient = 1.0e-1 +#vz_dissipation_coefficient = 1.0e-2 +#vz_dissipation_coefficient = 1.0e-3 +force_minimum_pdf_value = 0.0 + +[krook_collisions] +use_krook = true diff --git a/makie_post_processing/makie_post_processing/src/makie_post_processing.jl b/makie_post_processing/makie_post_processing/src/makie_post_processing.jl index c64e74a5d..fb5aeefa0 100644 --- a/makie_post_processing/makie_post_processing/src/makie_post_processing.jl +++ b/makie_post_processing/makie_post_processing/src/makie_post_processing.jl @@ -36,8 +36,9 @@ using moment_kinetics.manufactured_solns: manufactured_solutions, manufactured_electric_fields using moment_kinetics.load_data: close_run_info, get_run_info_no_setup, get_variable, timestep_diagnostic_variables, em_variables, - ion_moment_variables, neutral_moment_variables, - all_moment_variables, ion_dfn_variables, + ion_moment_variables, electron_moment_variables, + neutral_moment_variables, all_moment_variables, + ion_dfn_variables, electron_dfn_variables, neutral_dfn_variables, all_dfn_variables, ion_variables, neutral_variables, all_variables using moment_kinetics.initial_conditions: vpagrid_to_dzdt @@ -206,6 +207,19 @@ function makie_post_process(run_dir::Union{String,Tuple}, has_rdim = any(ri !== nothing && ri.r.n > 1 for ri ∈ run_info_moments) has_zdim = any(ri !== nothing && ri.z.n > 1 for ri ∈ run_info_moments) + # Only plot electron stuff if some runs have electrons + if any(ri !== nothing for ri ∈ run_info_moments) + has_electrons = any(r.composition.electron_physics + ∈ (braginskii_fluid, kinetic_electrons, + kinetic_electrons_with_temperature_equation) + for r in run_info_moments) + else + has_electrons = any(r.composition.electron_physics + ∈ (braginskii_fluid, kinetic_electrons, + kinetic_electrons_with_temperature_equation) + for r in run_info_dfns) + end + # Only plot neutral stuff if all runs have neutrals if any(ri !== nothing for ri ∈ run_info_moments) has_neutrals = all(r.n_neutral_species > 0 for r in run_info_moments) @@ -220,6 +234,9 @@ function makie_post_process(run_dir::Union{String,Tuple}, ############################# moment_variable_list = tuple(em_variables..., ion_moment_variables...) + if has_electrons + moment_variable_list = tuple(moment_variable_list..., electron_moment_variables...) + end if has_neutrals moment_variable_list = tuple(moment_variable_list..., neutral_moment_variables...) end @@ -251,7 +268,12 @@ function makie_post_process(run_dir::Union{String,Tuple}, end end - timestep_diagnostics(run_info; plot_prefix=plot_prefix) + timestep_diagnostics(run_info, run_info_dfns; plot_prefix=plot_prefix) + if any((ri.composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + && !ri.t_input["implicit_electron_advance"]) for ri ∈ run_info) + timestep_diagnostics(run_info, run_info_dfns; plot_prefix=plot_prefix, electron=true) + end do_steady_state_residuals = any(input_dict[v]["steady_state_residual"] for v ∈ moment_variable_list) @@ -291,6 +313,9 @@ function makie_post_process(run_dir::Union{String,Tuple}, ############################################ if any(ri !== nothing for ri in run_info_dfns) dfn_variable_list = ion_dfn_variables + if has_electrons + dfn_variable_list = tuple(dfn_variable_list..., electron_dfn_variables...) + end if has_neutrals dfn_variable_list = tuple(dfn_variable_list..., neutral_dfn_variables...) end @@ -300,7 +325,10 @@ function makie_post_process(run_dir::Union{String,Tuple}, end end - plot_ion_pdf_2D_at_wall(run_info_dfns; plot_prefix=plot_prefix) + plot_charged_pdf_2D_at_wall(run_info_dfns; plot_prefix=plot_prefix) + if has_electrons + plot_charged_pdf_2D_at_wall(run_info_dfns; plot_prefix=plot_prefix, electron=true) + end if has_neutrals plot_neutral_pdf_2D_at_wall(run_info_dfns; plot_prefix=plot_prefix) end @@ -699,6 +727,15 @@ function _setup_single_input!(this_input_dict::OrderedDict{String,Any}, animation_ext=this_input_dict["animation_ext"], ) + set_defaults_and_check_section!( + this_input_dict, "wall_pdf_electron"; + plot=false, + animate=false, + advection_velocity=false, + colormap=this_input_dict["colormap"], + animation_ext=this_input_dict["animation_ext"], + ) + set_defaults_and_check_section!( this_input_dict, "wall_pdf_neutral"; plot=false, @@ -773,16 +810,33 @@ function _setup_single_input!(this_input_dict::OrderedDict{String,Any}, this_input_dict, "timestep_diagnostics"; plot=true, animate_CFL=false, + plot_timestep_residual=false, + animate_timestep_residual=false, + plot_timestep_error=false, + animate_timestep_error=false, + plot_steady_state_residual=false, + animate_steady_state_residual=false, ) return nothing end +function makie_post_processing_error_handler(e::Exception, message::String) + if isa(e, InterruptException) + rethrow(e) + else + println(message * "\nError was $e.") + return nothing + end +end + """ get_run_info(run_dir...; itime_min=1, itime_max=0, - itime_skip=1, dfns=false, do_setup=true, setup_input_file=nothing) + itime_skip=1, dfns=false, initial_electron=false, do_setup=true, + setup_input_file=nothing) get_run_info((run_dir, restart_index)...; itime_min=1, itime_max=0, - itime_skip=1, dfns=false, do_setup=true, setup_input_file=nothing) + itime_skip=1, dfns=false, initial_electron=false, do_setup=true, + setup_input_file=nothing) Get file handles and other info for a single run @@ -804,7 +858,8 @@ argument can be a String `run_dir` giving a directory to read output from or a T mix Strings and Tuples in a call). By default load data from moments files, pass `dfns=true` to load from distribution -functions files. +functions files, or `initial_electron=true` and `dfns=true` to load from initial electron +state files. The `itime_min`, `itime_max` and `itime_skip` options can be used to select only a slice of time points when loading data. In `makie_post_process` these options are read from the @@ -961,7 +1016,14 @@ function plots_for_variable(run_info, variable_name; plot_prefix, has_rdim=true, println("Making plots for $variable_name") flush(stdout) - variable = get_variable(run_info, variable_name) + variable = nothing + try + variable = get_variable(run_info, variable_name) + catch e + return makie_post_processing_error_handler( + e, + "plots_for_variable() failed for $variable_name - could not load data.") + end if variable_name ∈ em_variables species_indices = (nothing,) @@ -1437,8 +1499,9 @@ for dim ∈ one_dimension_combinations end return fig catch e - println("$($function_name_str) failed for $var_name, is=$is. Error was $e") - return nothing + return makie_post_processing_error_handler( + e, + "$($function_name_str) failed for $var_name, is=$is.") end end @@ -1622,8 +1685,9 @@ for (dim1, dim2) ∈ two_dimension_combinations end return fig catch e - println("$($function_name_str) failed for $var_name, is=$is. Error was $e") - return nothing + return makie_post_processing_error_handler( + e, + "$($function_name_str) failed for $var_name, is=$is.") end end @@ -1847,8 +1911,8 @@ for dim ∈ one_dimension_combinations_no_t time = select_slice(run_info[1].time, :t; input=input, it=it) title = lift(i->string("t = ", time[i]), frame_index) else - time = select_slice(ri.time, :t; input=input, it=it) - title = lift(i->join((string("t", irun, " = ", time[i]) + title = lift(i->join((string("t", irun, " = ", + select_slice(ri.time, :t; input=input, it=it)[i]) for (irun,ri) ∈ enumerate(run_info)), "; "), frame_index) end @@ -1886,8 +1950,9 @@ for dim ∈ one_dimension_combinations_no_t return fig catch e - println("$($function_name_str)() failed for $var_name, is=$is. Error was $e") - return nothing + return makie_post_processing_error_handler( + e, + "$($function_name_str)() failed for $var_name, is=$is.") end end @@ -2085,8 +2150,8 @@ for (dim1, dim2) ∈ two_dimension_combinations_no_t if length(run_info) > 1 title = get_variable_symbol(var_name) - time = select_slice(ri.time, :t; input=input, it=it) - subtitles = (lift(i->string(ri.run_name, "\nt = ", time[i]), + subtitles = (lift(i->string(ri.run_name, "\nt = ", + select_slice(ri.time, :t; input=input, it=it)[i]), frame_index) for ri ∈ run_info) else @@ -2116,8 +2181,9 @@ for (dim1, dim2) ∈ two_dimension_combinations_no_t return fig catch e - println("$($function_name_str) failed for $var_name, is=$is. Error was $e") - return nothing + return makie_post_processing_error_handler( + e, + "$($function_name_str) failed for $var_name, is=$is.") end end @@ -3216,10 +3282,71 @@ function select_slice(variable::AbstractArray{T,4}, dims::Symbol...; input=nothi return slice end +function select_slice(variable::AbstractArray{T,5}, dims::Symbol...; input=nothing, + it=nothing, is=1, ir=nothing, iz=nothing, ivperp=nothing, + ivpa=nothing, kwargs...) where T + # Array is (vpa,vperp,z,r,t) + + if it !== nothing + it0 = it + elseif input === nothing || :it0 ∉ input + it0 = size(variable, 5) + else + it0 = input.it0 + end + if ir !== nothing + ir0 = ir + elseif input === nothing || :ir0 ∉ input + ir0 = max(size(variable, 4) ÷ 3, 1) + else + ir0 = input.ir0 + end + if iz !== nothing + iz0 = iz + elseif input === nothing || :iz0 ∉ input + iz0 = max(size(variable, 3) ÷ 3, 1) + else + iz0 = input.iz0 + end + if ivperp !== nothing + ivperp0 = ivperp + elseif input === nothing || :ivperp0 ∉ input + ivperp0 = max(size(variable, 2) ÷ 3, 1) + else + ivperp0 = input.ivperp0 + end + if ivpa !== nothing + ivpa0 = ivpa + elseif input === nothing || :ivpa0 ∉ input + ivpa0 = max(size(variable, 1) ÷ 3, 1) + else + ivpa0 = input.ivpa0 + end + + slice = variable + if :t ∉ dims || it !== nothing + slice = selectdim(slice, 5, it0) + end + if :r ∉ dims || ir !== nothing + slice = selectdim(slice, 4, ir0) + end + if :z ∉ dims || iz !== nothing + slice = selectdim(slice, 3, iz0) + end + if :vperp ∉ dims || ivperp !== nothing + slice = selectdim(slice, 2, ivperp0) + end + if :vpa ∉ dims || ivpa !== nothing + slice = selectdim(slice, 1, ivpa0) + end + + return slice +end + function select_slice(variable::AbstractArray{T,6}, dims::Symbol...; input=nothing, it=nothing, is=1, ir=nothing, iz=nothing, ivperp=nothing, ivpa=nothing, kwargs...) where T - # Array is (z,r,species,t) + # Array is (vpa,vperp,z,r,species,t) if it !== nothing it0 = it @@ -3281,7 +3408,7 @@ end function select_slice(variable::AbstractArray{T,7}, dims::Symbol...; input=nothing, it=nothing, is=1, ir=nothing, iz=nothing, ivzeta=nothing, ivr=nothing, ivz=nothing, kwargs...) where T - # Array is (z,r,species,t) + # Array is (vz,vr,vzeta,z,r,species,t) if it !== nothing it0 = it @@ -3544,7 +3671,9 @@ function _save_residual_plots(fig_axes, plot_prefix) save(plot_prefix * replace(key, " "=>"_") * ".pdf", fa[1]) end catch e - println("Error in _save_residual_plots(). Error was ", e) + return makie_post_processing_error_handler( + e, + "Error in _save_residual_plots().") end end @@ -3597,7 +3726,9 @@ function calculate_steady_state_residual(run_info::Tuple, variable_name; is=1, return fig_axes catch e - println("Error in calculate_steady_state_residual(). Error was ", e) + return makie_post_processing_error_handler( + e, + "Error in calculate_steady_state_residual().") end end @@ -3728,7 +3859,9 @@ function plot_f_unnorm_vs_vpa(run_info::Tuple; f_over_vpa2=false, neutral=false, return fig catch e - println("Error in plot_f_unnorm_vs_vpa(). Error was ", e) + return makie_post_processing_error_handler( + e, + "Error in plot_f_unnorm_vs_vpa().") end end @@ -3768,15 +3901,17 @@ function plot_f_unnorm_vs_vpa(run_info; f_over_vpa2=false, input=nothing, neutra upar = get_variable(run_info, "uz_neutral"; it=it, is=is, ir=input.ir0, iz=iz) vth = get_variable(run_info, "thermal_speed_neutral"; it=it, is=is, ir=input.ir0, iz=iz) + vcoord = run_info.vz else f = get_variable(run_info, "f"; it=it, is=is, ir=input.ir0, iz=iz, ivperp=input.ivperp0) density = get_variable(run_info, "density"; it=it, is=is, ir=input.ir0, iz=iz) upar = get_variable(run_info, "parallel_flow"; it=it, is=is, ir=input.ir0, iz=iz) vth = get_variable(run_info, "thermal_speed"; it=it, is=is, ir=input.ir0, iz=iz) + vcoord = run_info.vpa end - f_unnorm, dzdt = get_unnormalised_f_dzdt_1d(f, run_info.vpa.grid, density, upar, vth, + f_unnorm, dzdt = get_unnormalised_f_dzdt_1d(f, vcoord.grid, density, upar, vth, run_info.evolve_density, run_info.evolve_upar, run_info.evolve_ppar) @@ -3891,7 +4026,9 @@ function plot_f_unnorm_vs_vpa_z(run_info::Tuple; neutral=false, outfile=nothing, return fig catch e - println("Error in plot_f_unnorm_vs_vpa_z(). Error was ", e) + return makie_post_processing_error_handler( + e, + "Error in plot_f_unnorm_vs_vpa_z().") end end @@ -4057,7 +4194,9 @@ function animate_f_unnorm_vs_vpa(run_info::Tuple; f_over_vpa2=false, neutral=fal return fig catch e - println("Error in animate_f_unnorm_vs_vpa(). Error was ", e) + return makie_post_processing_error_handler( + e, + "Error in animate_f_unnorm_vs_vpa().") end end @@ -4100,6 +4239,7 @@ function animate_f_unnorm_vs_vpa(run_info; f_over_vpa2=false, input=nothing, density = get_variable(run_info, "density_neutral"; is=is, ir=input.ir0, iz=iz) upar = get_variable(run_info, "uz_neutral"; is=is, ir=input.ir0, iz=iz) vth = get_variable(run_info, "thermal_speed_neutral"; is=is, ir=input.ir0, iz=iz) + vcoord = run_info.vz else f = VariableCache(run_info, "f", chunk_size_2d; it=nothing, is=is, ir=input.ir0, iz=iz, ivperp=input.ivperp0, ivpa=nothing, ivzeta=nothing, ivr=nothing, @@ -4107,6 +4247,7 @@ function animate_f_unnorm_vs_vpa(run_info; f_over_vpa2=false, input=nothing, density = get_variable(run_info, "density"; is=is, ir=input.ir0, iz=iz) upar = get_variable(run_info, "parallel_flow"; is=is, ir=input.ir0, iz=iz) vth = get_variable(run_info, "thermal_speed"; is=is, ir=input.ir0, iz=iz) + vcoord = run_info.vpa end function get_this_f_unnorm(it) @@ -4114,7 +4255,7 @@ function animate_f_unnorm_vs_vpa(run_info; f_over_vpa2=false, input=nothing, run_info.evolve_density, run_info.evolve_ppar) if f_over_vpa2 - dzdt = vpagrid_to_dzdt(run_info.vpa.grid, vth[it], upar[it], + dzdt = vpagrid_to_dzdt(vcoord.grid, vth[it], upar[it], run_info.evolve_ppar, run_info.evolve_upar) dzdt2 = dzdt.^2 for i ∈ eachindex(dzdt2) @@ -4135,7 +4276,7 @@ function animate_f_unnorm_vs_vpa(run_info; f_over_vpa2=false, input=nothing, fmin = Inf fmax = -Inf for it ∈ 1:run_info.nt - this_dzdt = vpagrid_to_dzdt(run_info.vpa.grid, vth[it], upar[it], + this_dzdt = vpagrid_to_dzdt(vcoord.grid, vth[it], upar[it], run_info.evolve_ppar, run_info.evolve_upar) this_dzdtmin, this_dzdtmax = extrema(this_dzdt) dzdtmin = min(dzdtmin, this_dzdtmin) @@ -4159,7 +4300,7 @@ function animate_f_unnorm_vs_vpa(run_info; f_over_vpa2=false, input=nothing, fmin - 0.01*yheight, fmax + 0.01*yheight) end - dzdt = @lift vpagrid_to_dzdt(run_info.vpa.grid, vth[$frame_index], upar[$frame_index], + dzdt = @lift vpagrid_to_dzdt(vcoord.grid, vth[$frame_index], upar[$frame_index], run_info.evolve_ppar, run_info.evolve_upar) f_unnorm = @lift transform.(get_this_f_unnorm($frame_index)) @@ -4264,7 +4405,9 @@ function animate_f_unnorm_vs_vpa_z(run_info::Tuple; neutral=false, outfile=nothi return fig catch e - println("Error in animate_f_unnorm_vs_vpa_z(). Error was ", e) + return makie_post_processing_error_handler( + e, + "Error in animate_f_unnorm_vs_vpa_z().") end end @@ -4367,7 +4510,7 @@ function animate_f_unnorm_vs_vpa_z(run_info; input=nothing, neutral=false, is=1, end """ - plot_ion_pdf_2D_at_wall(run_info; plot_prefix) + plot_charged_pdf_2D_at_wall(run_info; plot_prefix, electron=false) Make plots/animations of the ion distribution function at wall boundaries. @@ -4382,79 +4525,61 @@ Settings are read from the `[wall_pdf]` section of the input. will be saved with the format `plot_prefix.pdf`. When `run_info` is not a Tuple, `plot_prefix` is optional - plots/animations will be saved only if it is passed. -""" -function plot_ion_pdf_2D_at_wall(run_info; plot_prefix) - input = Dict_to_NamedTuple(input_dict_dfns["wall_pdf"]) - if !(input.plot || input.animate || input.advection_velocity) - # nothing to do - return nothing - end - if !any(ri !== nothing for ri ∈ run_info) - println("Warning: no distribution function output, skipping wall_pdf plots") - return nothing - end - - z_lower = 1 - z_upper = run_info[1].z.n - if !all(ri.z.n == z_upper for ri ∈ run_info) - println("Cannot run plot_ion_pdf_2D_at_wall() for runs with different " - * "z-grid sizes. Got $(Tuple(ri.z.n for ri ∈ run_info))") - return nothing - end - println("Making plots of ion distribution function at walls") - flush(stdout) +If `electron=true` is passed, plot electron distribution function instead of ion +distribution function. +""" +function plot_charged_pdf_2D_at_wall(run_info; plot_prefix, electron=false) + try + if electron + electron_prefix = "electron_" + electron_suffix = "_electron" + else + electron_prefix = "" + electron_suffix = "" + end + input = Dict_to_NamedTuple(input_dict_dfns["wall_pdf$electron_suffix"]) + if !(input.plot || input.animate || input.advection_velocity) + # nothing to do + return nothing + end + if !any(ri !== nothing for ri ∈ run_info) + println("Warning: no distribution function output, skipping wall_pdf plots") + return nothing + end - has_rdim = any(ri !== nothing && ri.r.n > 1 for ri ∈ run_info) - has_zdim = any(ri !== nothing && ri.z.n > 1 for ri ∈ run_info) - is_1V = all(ri !== nothing && ri.vperp.n == 1 for ri ∈ run_info) - moment_kinetic = any(ri !== nothing - && (ri.evolve_density || ri.evolve_upar || ri.evolve_ppar) - for ri ∈ run_info) + z_lower = 1 + z_upper = run_info[1].z.n + if !all(ri.z.n == z_upper for ri ∈ run_info) + println("Cannot run plot_charged_pdf_2D_at_wall() for runs with different " + * "z-grid sizes. Got $(Tuple(ri.z.n for ri ∈ run_info))") + return nothing + end - nt = minimum(ri.nt for ri ∈ run_info) + if electron + println("Making plots of electron distribution function at walls") + else + println("Making plots of ion distribution function at walls") + end + flush(stdout) - for (z, z_range, label) ∈ ((z_lower, z_lower:z_lower+4, "wall-"), - (z_upper, z_upper-4:z_upper, "wall+")) - f_input = copy(input_dict_dfns["f"]) - f_input["iz0"] = z + has_rdim = any(ri !== nothing && ri.r.n > 1 for ri ∈ run_info) + has_zdim = any(ri !== nothing && ri.z.n > 1 for ri ∈ run_info) + is_1V = all(ri !== nothing && ri.vperp.n == 1 for ri ∈ run_info) + moment_kinetic = !electron && + any(ri !== nothing + && (ri.evolve_density || ri.evolve_upar || ri.evolve_ppar) + for ri ∈ run_info) - if input.plot - fig, ax = get_1d_ax(; xlabel="vpa", ylabel="f") - for iz ∈ z_range - for ri ∈ run_info - if length(run_info) > 1 - run_label = ri.run_name * " " - else - run_label = "" - end - plot_vs_vpa(ri, "f"; is=1, iz=iz, input=f_input, - label="$(run_label)iz=$iz", ax=ax) - end - end - put_legend_right(fig, ax) - outfile=plot_prefix * "pdf_$(label)_vs_vpa.pdf" - save(outfile, fig) + nt = minimum(ri.nt for ri ∈ run_info) - fig, ax = get_1d_ax(; xlabel="vpa", ylabel="f") - for iz ∈ z_range - for ri ∈ run_info - if length(run_info) > 1 - run_label = ri.run_name * " " - else - run_label = "" - end - plot_vs_vpa(ri, "f"; is=1, iz=iz, input=f_input, - label="$(run_label)iz=$iz", ax=ax, yscale=log10, - transform=(x)->positive_or_nan(x; epsilon=1.e-20)) - end - end - put_legend_right(fig, ax) - outfile=plot_prefix * "logpdf_$(label)_vs_vpa.pdf" - save(outfile, fig) + for (z, z_range, label) ∈ ((z_lower, z_lower:z_lower+4, "wall-"), + (z_upper, z_upper-4:z_upper, "wall+")) + f_input = copy(input_dict_dfns["f"]) + f_input["iz0"] = z - if moment_kinetic - fig, ax = get_1d_ax(; xlabel="vpa_unnorm", ylabel="f_unnorm") + if input.plot + fig, ax = get_1d_ax(; xlabel="vpa", ylabel="f$electron_suffix") for iz ∈ z_range for ri ∈ run_info if length(run_info) > 1 @@ -4462,15 +4587,15 @@ function plot_ion_pdf_2D_at_wall(run_info; plot_prefix) else run_label = "" end - plot_f_unnorm_vs_vpa(ri; input=f_input, is=1, iz=iz, - label="$(run_label)iz=$iz", ax=ax) + plot_vs_vpa(ri, "f$electron_suffix"; is=1, iz=iz, input=f_input, + label="$(run_label)iz=$iz", ax=ax) end end put_legend_right(fig, ax) - outfile=plot_prefix * "pdf_unnorm_$(label)_vs_vpa.pdf" + outfile=plot_prefix * "pdf$(electron_suffix)_$(label)_vs_vpa.pdf" save(outfile, fig) - fig, ax = get_1d_ax(; xlabel="vpa_unnorm", ylabel="f_unnorm") + fig, ax = get_1d_ax(; xlabel="vpa", ylabel="f") for iz ∈ z_range for ri ∈ run_info if length(run_info) > 1 @@ -4478,80 +4603,78 @@ function plot_ion_pdf_2D_at_wall(run_info; plot_prefix) else run_label = "" end - plot_f_unnorm_vs_vpa(ri; input=f_input, is=1, iz=iz, - label="$(run_label)iz=$iz", ax=ax, yscale=log10, - transform=(x)->positive_or_nan(x; epsilon=1.e-20)) + plot_vs_vpa(ri, "f$electron_suffix"; is=1, iz=iz, input=f_input, + label="$(run_label)iz=$iz", ax=ax, yscale=log10, + transform=(x)->positive_or_nan(x; epsilon=1.e-20)) end end put_legend_right(fig, ax) - outfile=plot_prefix * "logpdf_unnorm_$(label)_vs_vpa.pdf" + outfile=plot_prefix * "logpdf$(electron_suffix)_$(label)_vs_vpa.pdf" save(outfile, fig) - end - - plot_f_unnorm_vs_vpa(run_info; f_over_vpa2=true, input=f_input, is=1, - outfile=plot_prefix * "pdf_unnorm_over_vpa2_$(label)_vs_vpa.pdf") - if !is_1V - plot_vs_vpa_vperp(run_info, "f"; is=1, input=f_input, - outfile=plot_prefix * "pdf_$(label)_vs_vpa_vperp.pdf") - end + if moment_kinetic + fig, ax = get_1d_ax(; xlabel="vpa_unnorm", ylabel="f$(electron_suffix)_unnorm") + for iz ∈ z_range + for ri ∈ run_info + if length(run_info) > 1 + run_label = ri.run_name * " " + else + run_label = "" + end + plot_f_unnorm_vs_vpa(ri; input=f_input, is=1, iz=iz, + label="$(run_label)iz=$iz", ax=ax) + end + end + put_legend_right(fig, ax) + outfile=plot_prefix * "pdf_unnorm_$(label)_vs_vpa.pdf" + save(outfile, fig) + + fig, ax = get_1d_ax(; xlabel="vpa_unnorm", ylabel="f_unnorm") + for iz ∈ z_range + for ri ∈ run_info + if length(run_info) > 1 + run_label = ri.run_name * " " + else + run_label = "" + end + plot_f_unnorm_vs_vpa(ri; input=f_input, is=1, iz=iz, + label="$(run_label)iz=$iz", ax=ax, yscale=log10, + transform=(x)->positive_or_nan(x; epsilon=1.e-20)) + end + end + put_legend_right(fig, ax) + outfile=plot_prefix * "logpdf_unnorm_$(label)_vs_vpa.pdf" + save(outfile, fig) + end - if has_zdim - plot_vs_vpa_z(run_info, "f"; is=1, input=f_input, iz=z_range, - outfile=plot_prefix * "pdf_$(label)_vs_vpa_z.pdf") - end + if !electron + plot_f_unnorm_vs_vpa(run_info; f_over_vpa2=true, input=f_input, is=1, + outfile=plot_prefix * "pdf_unnorm_over_vpa2_$(label)_vs_vpa.pdf") + end - if has_rdim && has_zdim - plot_vs_z_r(run_info, "f"; is=1, input=f_input, iz=z_range, - outfile=plot_prefix * "pdf_$(label)_vs_z_r.pdf") - end + if !is_1V + plot_vs_vpa_vperp(run_info, "f$electron_suffix"; is=1, input=f_input, + outfile=plot_prefix * "pdf$(electron_suffix)_$(label)_vs_vpa_vperp.pdf") + end - if has_rdim - plot_vs_vpa_r(run_info, "f"; is=1, input=f_input, - outfile=plot_prefix * "pdf_$(label)_vs_vpa_r.pdf") - end - end + if has_zdim + plot_vs_vpa_z(run_info, "f$electron_suffix"; is=1, input=f_input, iz=z_range, + outfile=plot_prefix * "pdf$(electron_suffix)_$(label)_vs_vpa_z.pdf") + end - if input.animate - fig, ax = get_1d_ax(; xlabel="vpa", ylabel="f") - frame_index = Observable(1) - for iz ∈ z_range - for ri ∈ run_info - if length(run_info) > 1 - run_label = ri.run_name * " " - else - run_label = "" - end - animate_vs_vpa(ri, "f"; is=1, iz=iz, input=f_input, - label="$(run_label)iz=$iz", ax=ax, - frame_index=frame_index) + if has_rdim && has_zdim + plot_vs_z_r(run_info, "f$electron_suffix"; is=1, input=f_input, iz=z_range, + outfile=plot_prefix * "pdf$(electron_suffix)_$(label)_vs_z_r.pdf") end - end - put_legend_right(fig, ax) - outfile=plot_prefix * "pdf_$(label)_vs_vpa." * input.animation_ext - save_animation(fig, frame_index, nt, outfile) - fig, ax = get_1d_ax(; xlabel="vpa", ylabel="f", yscale=log10) - frame_index = Observable(1) - for iz ∈ z_range - for ri ∈ run_info - if length(run_info) > 1 - run_label = ri.run_name * " " - else - run_label = "" - end - animate_vs_vpa(ri, "f"; is=1, iz=iz, input=f_input, - label="$(run_label)iz=$iz", ax=ax, - frame_index=frame_index, - transform=(x)->positive_or_nan(x; epsilon=1.e-20)) + if has_rdim + plot_vs_vpa_r(run_info, "f$electron_suffix"; is=1, input=f_input, + outfile=plot_prefix * "pdf$(electron_suffix)_$(label)_vs_vpa_r.pdf") end end - put_legend_right(fig, ax) - outfile=plot_prefix * "logpdf_$(label)_vs_vpa." * input.animation_ext - save_animation(fig, frame_index, nt, outfile) - if moment_kinetic - fig, ax = get_1d_ax(; xlabel="vpa", ylabel="f") + if input.animate + fig, ax = get_1d_ax(; xlabel="vpa", ylabel="f$electron_suffix") frame_index = Observable(1) for iz ∈ z_range for ri ∈ run_info @@ -4560,16 +4683,16 @@ function plot_ion_pdf_2D_at_wall(run_info; plot_prefix) else run_label = "" end - animate_f_unnorm_vs_vpa(ri; is=1, iz=iz, input=f_input, - label="$(run_label)iz=$iz", ax=ax, - frame_index=frame_index) + animate_vs_vpa(ri, "f$electron_suffix"; is=1, iz=iz, input=f_input, + label="$(run_label)iz=$iz", ax=ax, + frame_index=frame_index) end end put_legend_right(fig, ax) - outfile=plot_prefix * "pdf_unnorm_$(label)_vs_vpa." * input.animation_ext + outfile=plot_prefix * "pdf$(electron_suffix)_$(label)_vs_vpa." * input.animation_ext save_animation(fig, frame_index, nt, outfile) - fig, ax = get_1d_ax(; xlabel="vpa", ylabel="f") + fig, ax = get_1d_ax(; xlabel="vpa", ylabel="f$electron_suffix", yscale=log10) frame_index = Observable(1) for iz ∈ z_range for ri ∈ run_info @@ -4578,45 +4701,90 @@ function plot_ion_pdf_2D_at_wall(run_info; plot_prefix) else run_label = "" end - animate_f_unnorm_vs_vpa(ri; is=1, iz=iz, input=f_input, - label="$(run_label)iz=$iz", ax=ax, - frame_index=frame_index, yscale=log10, - transform=(x)->positive_or_nan(x; epsilon=1.e-20)) + animate_vs_vpa(ri, "f$electron_suffix"; is=1, iz=iz, input=f_input, + label="$(run_label)iz=$iz", ax=ax, + frame_index=frame_index, + transform=(x)->positive_or_nan(x; epsilon=1.e-20)) end end put_legend_right(fig, ax) - outfile=plot_prefix * "logpdf_unnorm_$(label)_vs_vpa." * input.animation_ext + outfile=plot_prefix * "logpdf$(electron_suffix)_$(label)_vs_vpa." * input.animation_ext save_animation(fig, frame_index, nt, outfile) - end - animate_f_unnorm_vs_vpa(run_info; f_over_vpa2=true, input=f_input, is=1, - outfile=plot_prefix * "pdf_unnorm_over_vpa2_$(label)_vs_vpa." * input.animation_ext) + if moment_kinetic + fig, ax = get_1d_ax(; xlabel="vpa", ylabel="f") + frame_index = Observable(1) + for iz ∈ z_range + for ri ∈ run_info + if length(run_info) > 1 + run_label = ri.run_name * " " + else + run_label = "" + end + animate_f_unnorm_vs_vpa(ri; is=1, iz=iz, input=f_input, + label="$(run_label)iz=$iz", ax=ax, + frame_index=frame_index) + end + end + put_legend_right(fig, ax) + outfile=plot_prefix * "pdf_unnorm_$(label)_vs_vpa." * input.animation_ext + save_animation(fig, frame_index, nt, outfile) + + fig, ax = get_1d_ax(; xlabel="vpa", ylabel="f") + frame_index = Observable(1) + for iz ∈ z_range + for ri ∈ run_info + if length(run_info) > 1 + run_label = ri.run_name * " " + else + run_label = "" + end + animate_f_unnorm_vs_vpa(ri; is=1, iz=iz, input=f_input, + label="$(run_label)iz=$iz", ax=ax, + frame_index=frame_index, yscale=log10, + transform=(x)->positive_or_nan(x; epsilon=1.e-20)) + end + end + put_legend_right(fig, ax) + outfile=plot_prefix * "logpdf_unnorm_$(label)_vs_vpa." * input.animation_ext + save_animation(fig, frame_index, nt, outfile) + end - if !is_1V - animate_vs_vpa_vperp(run_info, "f"; is=1, input=f_input, - outfile=plot_prefix * "pdf_$(label)_vs_vpa_vperp." * input.animation_ext) - end + if !electron + animate_f_unnorm_vs_vpa(run_info; f_over_vpa2=true, input=f_input, is=1, + outfile=plot_prefix * "pdf_unnorm_over_vpa2_$(label)_vs_vpa." * input.animation_ext) + end - if has_zdim - animate_vs_vpa_z(run_info, "f"; is=1, input=f_input, iz=z_range, - outfile=plot_prefix * "pdf_$(label)_vs_vpa_z." * input.animation_ext) - end + if !is_1V + animate_vs_vpa_vperp(run_info, "f$electron_suffix"; is=1, input=f_input, + outfile=plot_prefix * "pdf$(electron_suffix)_$(label)_vs_vpa_vperp." * input.animation_ext) + end - if has_rdim && has_zdim - animate_vs_z_r(run_info, "f"; is=1, input=f_input, iz=z_range, - outfile=plot_prefix * "pdf_$(label)_vs_z_r." * input.animation_ext) - end + if has_zdim + animate_vs_vpa_z(run_info, "f$electron_suffix"; is=1, input=f_input, iz=z_range, + outfile=plot_prefix * "pdf$(electron_suffix)_$(label)_vs_vpa_z." * input.animation_ext) + end - if has_rdim - animate_vs_vpa_r(run_info, "f"; is=1, input=f_input, - outfile=plot_prefix * "pdf_$(label)_vs_vpa_r." * input.animation_ext) + if has_rdim && has_zdim + animate_vs_z_r(run_info, "f$electron_suffix"; is=1, input=f_input, iz=z_range, + outfile=plot_prefix * "pdf$(electron_suffix)_$(label)_vs_z_r." * input.animation_ext) + end + + if has_rdim + animate_vs_vpa_r(run_info, "f$electron_suffix"; is=1, input=f_input, + outfile=plot_prefix * "pdf$(electron_suffix)_$(label)_vs_vpa_r." * input.animation_ext) + end end - end - if input.advection_velocity - animate_vs_vpa(run_info, "vpa_advect_speed"; is=1, input=f_input, - outfile=plot_prefix * "vpa_advect_speed_$(label)_vs_vpa." * input.animation_ext) + if input.advection_velocity + animate_vs_vpa(run_info, "$(electron_prefix)vpa_advect_speed"; is=1, input=f_input, + outfile=plot_prefix * "$(electron_prefix)vpa_advect_speed_$(label)_vs_vpa." * input.animation_ext) + end end + catch e + return makie_post_processing_error_handler( + e, + "Error in plot_charged_pdf_2D_at_wall().") end return nothing @@ -4640,76 +4808,43 @@ is not a Tuple, `plot_prefix` is optional - plots/animations will be saved only passed. """ function plot_neutral_pdf_2D_at_wall(run_info; plot_prefix) - input = Dict_to_NamedTuple(input_dict_dfns["wall_pdf_neutral"]) - if !(input.plot || input.animate || input.advection_velocity) - # nothing to do - return nothing - end - if !any(ri !== nothing for ri ∈ run_info) - println("Warning: no distribution function output, skipping wall_pdf plots") - return nothing - end - - z_lower = 1 - z_upper = run_info[1].z.n - if !all(ri.z.n == z_upper for ri ∈ run_info) - println("Cannot run plot_neutral_pdf_2D_at_wall() for runs with different " - * "z-grid sizes. Got $(Tuple(ri.z.n for ri ∈ run_info))") - return nothing - end - - println("Making plots of neutral distribution function at walls") - flush(stdout) + try + input = Dict_to_NamedTuple(input_dict_dfns["wall_pdf_neutral"]) + if !(input.plot || input.animate || input.advection_velocity) + # nothing to do + return nothing + end + if !any(ri !== nothing for ri ∈ run_info) + println("Warning: no distribution function output, skipping wall_pdf plots") + return nothing + end - has_rdim = any(ri !== nothing && ri.r.n > 1 for ri ∈ run_info) - has_zdim = any(ri !== nothing && ri.z.n > 1 for ri ∈ run_info) - is_1V = all(ri !== nothing && ri.vzeta.n == 1 && ri.vr.n == 1 for ri ∈ run_info) - moment_kinetic = any(ri !== nothing - && (ri.evolve_density || ri.evolve_upar || ri.evolve_ppar) - for ri ∈ run_info) - nt = minimum(ri.nt for ri ∈ run_info) + z_lower = 1 + z_upper = run_info[1].z.n + if !all(ri.z.n == z_upper for ri ∈ run_info) + println("Cannot run plot_neutral_pdf_2D_at_wall() for runs with different " + * "z-grid sizes. Got $(Tuple(ri.z.n for ri ∈ run_info))") + return nothing + end - for (z, z_range, label) ∈ ((z_lower, z_lower:z_lower+4, "wall-"), - (z_upper, z_upper-4:z_upper, "wall+")) - f_neutral_input = copy(input_dict_dfns["f_neutral"]) - f_neutral_input["iz0"] = z + println("Making plots of neutral distribution function at walls") + flush(stdout) - if input.plot - fig, ax = get_1d_ax(; xlabel="vz", ylabel="f_neutral") - for iz ∈ z_range - for ri ∈ run_info - if length(run_info) > 1 - run_label = ri.run_name * " " - else - run_label = "" - end - plot_vs_vz(ri, "f_neutral"; is=1, iz=iz, input=f_neutral_input, - label="$(run_label)iz=$iz", ax=ax) - end - end - put_legend_right(fig, ax) - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz.pdf" - save(outfile, fig) + has_rdim = any(ri !== nothing && ri.r.n > 1 for ri ∈ run_info) + has_zdim = any(ri !== nothing && ri.z.n > 1 for ri ∈ run_info) + is_1V = all(ri !== nothing && ri.vzeta.n == 1 && ri.vr.n == 1 for ri ∈ run_info) + moment_kinetic = any(ri !== nothing + && (ri.evolve_density || ri.evolve_upar || ri.evolve_ppar) + for ri ∈ run_info) + nt = minimum(ri.nt for ri ∈ run_info) - fig, ax = get_1d_ax(; xlabel="vz", ylabel="f_neutral") - for iz ∈ z_range - for ri ∈ run_info - if length(run_info) > 1 - run_label = ri.run_name * " " - else - run_label = "" - end - plot_vs_vz(ri, "f_neutral"; is=1, iz=iz, input=f_neutral_input, - label="$(run_label)iz=$iz", ax=ax, yscale=log10, - transform=(x)->positive_or_nan(x; epsilon=1.e-20)) - end - end - put_legend_right(fig, ax) - outfile=plot_prefix * "logpdf_neutral_$(label)_vs_vpa.pdf" - save(outfile, fig) + for (z, z_range, label) ∈ ((z_lower, z_lower:z_lower+4, "wall-"), + (z_upper, z_upper-4:z_upper, "wall+")) + f_neutral_input = copy(input_dict_dfns["f_neutral"]) + f_neutral_input["iz0"] = z - if moment_kinetic - fig, ax = get_1d_ax(; xlabel="vz_unnorm", ylabel="f_neutral_unnorm") + if input.plot + fig, ax = get_1d_ax(; xlabel="vz", ylabel="f_neutral") for iz ∈ z_range for ri ∈ run_info if length(run_info) > 1 @@ -4717,16 +4852,15 @@ function plot_neutral_pdf_2D_at_wall(run_info; plot_prefix) else run_label = "" end - plot_f_unnorm_vs_vpa(ri; neutral=true, input=f_neutral_input, - is=1, iz=iz, label="$(run_label)iz=$iz", - ax=ax) + plot_vs_vz(ri, "f_neutral"; is=1, iz=iz, input=f_neutral_input, + label="$(run_label)iz=$iz", ax=ax) end end put_legend_right(fig, ax) - outfile=plot_prefix * "pdf_neutral_unnorm_$(label)_vs_vpa.pdf" + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz.pdf" save(outfile, fig) - fig, ax = get_1d_ax(; xlabel="vz_unnorm", ylabel="f_neutral_unnorm") + fig, ax = get_1d_ax(; xlabel="vz", ylabel="f_neutral") for iz ∈ z_range for ri ∈ run_info if length(run_info) > 1 @@ -4734,95 +4868,92 @@ function plot_neutral_pdf_2D_at_wall(run_info; plot_prefix) else run_label = "" end - plot_f_unnorm_vs_vpa(ri; neutral=true, input=f_neutral_input, - is=1, iz=iz, label="$(run_label)iz=$iz", - ax=ax, yscale=log10, - transform=(x)->positive_or_nan(x; epsilon=1.e-20)) + plot_vs_vz(ri, "f_neutral"; is=1, iz=iz, input=f_neutral_input, + label="$(run_label)iz=$iz", ax=ax, yscale=log10, + transform=(x)->positive_or_nan(x; epsilon=1.e-20)) end end put_legend_right(fig, ax) - outfile=plot_prefix * "logpdf_neutral_unnorm_$(label)_vs_vpa.pdf" + outfile=plot_prefix * "logpdf_neutral_$(label)_vs_vpa.pdf" save(outfile, fig) - end - - if !is_1V - plot_vs_vzeta_vr(run_info, "f_neutral"; is=1, input=f_neutral_input, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vr_vzeta.pdf") - plot_vs_vzeta_vz(run_info, "f_neutral"; is=1, input=f_neutral_input, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz_vzeta.pdf") - plot_vs_vr_vz(run_info, "f_neutral"; is=1, input=f_neutral_input, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz_vr.pdf") - end - - if has_zdim - plot_vs_vz_z(run_info, "f_neutral"; is=1, input=f_neutral_input, iz=z_range, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz_z.pdf") - end - - if has_zdim && !is_1V - plot_vs_vzeta_z(run_info, "f_neutral"; is=1, input=f_neutral_input, iz=z_range, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vzeta_z.pdf") - plot_vs_vr_z(run_info, "f_neutral"; is=1, input=f_neutral_input, iz=z_range, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vr_z.pdf") - end - if has_rdim && has_zdim - plot_vs_z_r(run_info, "f_neutral"; is=1, input=f_neutral_input, iz=z_range, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_z_r.pdf") - end + if moment_kinetic + fig, ax = get_1d_ax(; xlabel="vz_unnorm", ylabel="f_neutral_unnorm") + for iz ∈ z_range + for ri ∈ run_info + if length(run_info) > 1 + run_label = ri.run_name * " " + else + run_label = "" + end + plot_f_unnorm_vs_vpa(ri; neutral=true, input=f_neutral_input, + is=1, iz=iz, label="$(run_label)iz=$iz", + ax=ax) + end + end + put_legend_right(fig, ax) + outfile=plot_prefix * "pdf_neutral_unnorm_$(label)_vs_vpa.pdf" + save(outfile, fig) + + fig, ax = get_1d_ax(; xlabel="vz_unnorm", ylabel="f_neutral_unnorm") + for iz ∈ z_range + for ri ∈ run_info + if length(run_info) > 1 + run_label = ri.run_name * " " + else + run_label = "" + end + plot_f_unnorm_vs_vpa(ri; neutral=true, input=f_neutral_input, + is=1, iz=iz, label="$(run_label)iz=$iz", + ax=ax, yscale=log10, + transform=(x)->positive_or_nan(x; epsilon=1.e-20)) + end + end + put_legend_right(fig, ax) + outfile=plot_prefix * "logpdf_neutral_unnorm_$(label)_vs_vpa.pdf" + save(outfile, fig) + end - if has_rdim - plot_vs_vz_r(run_info, "f_neutral"; is=1, input=f_neutral_input, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz_r.pdf") if !is_1V - plot_vs_vzeta_r(run_info, "f_neutral"; is=1, input=f_neutral_input, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vzeta_r.pdf") + plot_vs_vzeta_vr(run_info, "f_neutral"; is=1, input=f_neutral_input, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vr_vzeta.pdf") + plot_vs_vzeta_vz(run_info, "f_neutral"; is=1, input=f_neutral_input, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz_vzeta.pdf") + plot_vs_vr_vz(run_info, "f_neutral"; is=1, input=f_neutral_input, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz_vr.pdf") + end - plot_vs_vr_r(run_info, "f_neutral"; is=1, input=f_neutral_input, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vr_r.pdf") + if has_zdim + plot_vs_vz_z(run_info, "f_neutral"; is=1, input=f_neutral_input, iz=z_range, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz_z.pdf") end - end - end - if input.animate - fig, ax = get_1d_ax(; xlabel="vz", ylabel="f_neutral") - frame_index = Observable(1) - for iz ∈ z_range - for ri ∈ run_info - if length(run_info) > 1 - run_label = ri.run_name * " " - else - run_label = "" - end - animate_vs_vz(ri, "f_neutral"; is=1, iz=iz, input=f_neutral_input, - label="$(run_label)iz=$iz", ax=ax, - frame_index=frame_index) + if has_zdim && !is_1V + plot_vs_vzeta_z(run_info, "f_neutral"; is=1, input=f_neutral_input, iz=z_range, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vzeta_z.pdf") + plot_vs_vr_z(run_info, "f_neutral"; is=1, input=f_neutral_input, iz=z_range, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vr_z.pdf") end - end - put_legend_right(fig, ax) - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz." * input.animation_ext - save_animation(fig, frame_index, nt, outfile) - fig, ax = get_1d_ax(; xlabel="vz", ylabel="f_neutral", yscale=log10) - frame_index = Observable(1) - for iz ∈ z_range - for ri ∈ run_info - if length(run_info) > 1 - run_label = ri.run_name * " " - else - run_label = "" + if has_rdim && has_zdim + plot_vs_z_r(run_info, "f_neutral"; is=1, input=f_neutral_input, iz=z_range, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_z_r.pdf") + end + + if has_rdim + plot_vs_vz_r(run_info, "f_neutral"; is=1, input=f_neutral_input, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz_r.pdf") + if !is_1V + plot_vs_vzeta_r(run_info, "f_neutral"; is=1, input=f_neutral_input, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vzeta_r.pdf") + + plot_vs_vr_r(run_info, "f_neutral"; is=1, input=f_neutral_input, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vr_r.pdf") end - animate_vs_vz(ri, "f_neutral"; is=1, iz=iz, input=f_neutral_input, - label="$(run_label)iz=$iz", ax=ax, - frame_index=frame_index, - transform=(x)->positive_or_nan(x; epsilon=1.e-20)) end end - put_legend_right(fig, ax) - outfile=plot_prefix * "logpdf_neutral_$(label)_vs_vz." * input.animation_ext - save_animation(fig, frame_index, nt, outfile) - if moment_kinetic + if input.animate fig, ax = get_1d_ax(; xlabel="vz", ylabel="f_neutral") frame_index = Observable(1) for iz ∈ z_range @@ -4832,17 +4963,16 @@ function plot_neutral_pdf_2D_at_wall(run_info; plot_prefix) else run_label = "" end - animate_f_unnorm_vs_vpa(ri; neutral=true, is=1, iz=iz, - input=f_neutral_input, - label="$(run_label)iz=$iz", ax=ax, - frame_index=frame_index) + animate_vs_vz(ri, "f_neutral"; is=1, iz=iz, input=f_neutral_input, + label="$(run_label)iz=$iz", ax=ax, + frame_index=frame_index) end end put_legend_right(fig, ax) - outfile=plot_prefix * "pdf_neutral_unnorm_$(label)_vs_vz." * input.animation_ext + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz." * input.animation_ext save_animation(fig, frame_index, nt, outfile) - fig, ax = get_1d_ax(; xlabel="vz", ylabel="f_neutral") + fig, ax = get_1d_ax(; xlabel="vz", ylabel="f_neutral", yscale=log10) frame_index = Observable(1) for iz ∈ z_range for ri ∈ run_info @@ -4851,59 +4981,103 @@ function plot_neutral_pdf_2D_at_wall(run_info; plot_prefix) else run_label = "" end - animate_f_unnorm_vs_vpa(ri; neutral=true, is=1, iz=iz, - input=f_neutral_input, label="$(run_label)iz=$iz", - ax=ax, frame_index=frame_index, yscale=log10, - transform=(x)->positive_or_nan(x; epsilon=1.e-20)) + animate_vs_vz(ri, "f_neutral"; is=1, iz=iz, input=f_neutral_input, + label="$(run_label)iz=$iz", ax=ax, + frame_index=frame_index, + transform=(x)->positive_or_nan(x; epsilon=1.e-20)) end end put_legend_right(fig, ax) - outfile=plot_prefix * "logpdf_neutral_unnorm_$(label)_vs_vz." * input.animation_ext + outfile=plot_prefix * "logpdf_neutral_$(label)_vs_vz." * input.animation_ext save_animation(fig, frame_index, nt, outfile) - end - if !is_1V - animate_vs_vzeta_vr(run_info, "f_neutral"; is=1, input=f_neutral_input, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vr_vzeta." * input.animation_ext) - animate_vs_vzeta_vz(run_info, "f_neutral"; is=1, input=f_neutral_input, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz_vzeta." * input.animation_ext) - animate_vs_vr_vz(run_info, "f_neutral"; is=1, input=f_neutral_input, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz_vr." * input.animation_ext) - end + if moment_kinetic + fig, ax = get_1d_ax(; xlabel="vz", ylabel="f_neutral") + frame_index = Observable(1) + for iz ∈ z_range + for ri ∈ run_info + if length(run_info) > 1 + run_label = ri.run_name * " " + else + run_label = "" + end + animate_f_unnorm_vs_vpa(ri; neutral=true, is=1, iz=iz, + input=f_neutral_input, + label="$(run_label)iz=$iz", ax=ax, + frame_index=frame_index) + end + end + put_legend_right(fig, ax) + outfile=plot_prefix * "pdf_neutral_unnorm_$(label)_vs_vz." * input.animation_ext + save_animation(fig, frame_index, nt, outfile) + + fig, ax = get_1d_ax(; xlabel="vz", ylabel="f_neutral") + frame_index = Observable(1) + for iz ∈ z_range + for ri ∈ run_info + if length(run_info) > 1 + run_label = ri.run_name * " " + else + run_label = "" + end + animate_f_unnorm_vs_vpa(ri; neutral=true, is=1, iz=iz, + input=f_neutral_input, label="$(run_label)iz=$iz", + ax=ax, frame_index=frame_index, yscale=log10, + transform=(x)->positive_or_nan(x; epsilon=1.e-20)) + end + end + put_legend_right(fig, ax) + outfile=plot_prefix * "logpdf_neutral_unnorm_$(label)_vs_vz." * input.animation_ext + save_animation(fig, frame_index, nt, outfile) + end - if has_zdim - animate_vs_vz_z(run_info, "f_neutral"; is=1, input=f_neutral_input, iz=z_range, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz_z." * input.animation_ext) - end + if !is_1V + animate_vs_vzeta_vr(run_info, "f_neutral"; is=1, input=f_neutral_input, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vr_vzeta." * input.animation_ext) + animate_vs_vzeta_vz(run_info, "f_neutral"; is=1, input=f_neutral_input, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz_vzeta." * input.animation_ext) + animate_vs_vr_vz(run_info, "f_neutral"; is=1, input=f_neutral_input, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz_vr." * input.animation_ext) + end - if has_zdim && !is_1V - animate_vs_vzeta_z(run_info, "f_neutral"; is=1, input=f_neutral_input, iz=z_range, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vzeta_z." * input.animation_ext) - animate_vs_vr_z(run_info, "f_neutral"; is=1, input=f_neutral_input, iz=z_range, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vr_z." * input.animation_ext) - end + if has_zdim + animate_vs_vz_z(run_info, "f_neutral"; is=1, input=f_neutral_input, iz=z_range, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz_z." * input.animation_ext) + end - if has_rdim && has_zdim - animate_vs_z_r(run_info, "f_neutral"; is=1, input=f_neutral_input, iz=z_range, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_z_r." * input.animation_ext) - end + if has_zdim && !is_1V + animate_vs_vzeta_z(run_info, "f_neutral"; is=1, input=f_neutral_input, iz=z_range, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vzeta_z." * input.animation_ext) + animate_vs_vr_z(run_info, "f_neutral"; is=1, input=f_neutral_input, iz=z_range, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vr_z." * input.animation_ext) + end - if has_rdim - animate_vs_vz_r(run_info, "f_neutral"; is=1, input=f_neutral_input, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz_r." * input.animation_ext) - if !is_1V - animate_vs_vzeta_r(run_info, "f_neutral"; is=1, input=f_neutral_input, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vzeta_r." * input.animation_ext) - animate_vs_vr_r(run_info, "f_neutral"; is=1, input=f_neutral_input, - outfile=plot_prefix * "pdf_neutral_$(label)_vs_vr_r." * input.animation_ext) + if has_rdim && has_zdim + animate_vs_z_r(run_info, "f_neutral"; is=1, input=f_neutral_input, iz=z_range, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_z_r." * input.animation_ext) + end + + if has_rdim + animate_vs_vz_r(run_info, "f_neutral"; is=1, input=f_neutral_input, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vz_r." * input.animation_ext) + if !is_1V + animate_vs_vzeta_r(run_info, "f_neutral"; is=1, input=f_neutral_input, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vzeta_r." * input.animation_ext) + animate_vs_vr_r(run_info, "f_neutral"; is=1, input=f_neutral_input, + outfile=plot_prefix * "pdf_neutral_$(label)_vs_vr_r." * input.animation_ext) + end end end - end - if input.advection_velocity - animate_vs_vz(run_info, "neutral_vz_advect_speed"; is=1, input=f_neutral_input, - outfile=plot_prefix * "neutral_vz_advect_speed_$(label)_vs_vz." * input.animation_ext) + if input.advection_velocity + animate_vs_vz(run_info, "neutral_vz_advect_speed"; is=1, input=f_neutral_input, + outfile=plot_prefix * "neutral_vz_advect_speed_$(label)_vs_vz." * input.animation_ext) + end end + catch e + return makie_post_processing_error_handler( + e, + "Error in plot_neutral_pdf_2D_at_wall().") end return nothing @@ -5026,7 +5200,9 @@ function constraints_plots(run_info; plot_prefix=plot_prefix) end # Electrons - #if any(ri.composition.electron_physics == kinetic_electrons for ri ∈ run_info) + #if any(ri.composition.electron_physics ∈ (kinetic_electrons, + # kinetic_electrons_with_temperature_equation) + # for ri ∈ run_info) # fig, ax = get_1d_ax(; xlabel="z", ylabel="constraint coefficient") # for ri ∈ run_info @@ -5188,7 +5364,9 @@ function constraints_plots(run_info; plot_prefix=plot_prefix) end # Electrons - #if any(ri.composition.electron_physics == kinetic_electrons for ri ∈ run_info) + #if any(ri.composition.electron_physics ∈ (kinetic_electrons, + # kinetic_electrons_with_temperature_equation) + # for ri ∈ run_info) # frame_index = Observable(1) # fig, ax = get_1d_ax(; xlabel="z", ylabel="constraint coefficient") @@ -5237,7 +5415,9 @@ function constraints_plots(run_info; plot_prefix=plot_prefix) #end end catch e - println("Error in constraints_plots(). Error was ", e) + return makie_post_processing_error_handler( + e, + "Error in constraints_plots().") end end @@ -5403,7 +5583,9 @@ function Chodura_condition_plots(run_info::Tuple; plot_prefix) save(outfile, fig) end catch e - println("Error in Chodura_condition_plots(). Error was ", e) + return makie_post_processing_error_handler( + e, + "Error in Chodura_condition_plots().") end return nothing @@ -5593,7 +5775,9 @@ function sound_wave_plots(run_info::Tuple; plot_prefix) return fig end catch e - println("Error in sound_wave_plots(). Error was ", e) + return makie_post_processing_error_handler( + e, + "Error in sound_wave_plots().") end return nothing @@ -5983,7 +6167,9 @@ function instability2D_plots(run_info, variable_name; plot_prefix, zind=nothing, plot_Fourier_1D(variable_Fourier_1D, get_variable_symbol(variable_name), variable_name) catch e - println("Warning: error in 1D Fourier analysis for $variable_name. Error was $e") + return makie_post_processing_error_handler( + e, + "Warning: error in 1D Fourier analysis for $variable_name.") end # Do this to allow memory to be garbage-collected. @@ -6042,7 +6228,9 @@ function instability2D_plots(run_info, variable_name; plot_prefix, zind=nothing, plot_Fourier_2D(variable_Fourier, get_variable_symbol(variable_name), variable_name) catch e - println("Warning: error in 2D Fourier analysis for $variable_name. Error was $e") + return makie_post_processing_error_handler( + e, + "Warning: error in 2D Fourier analysis for $variable_name.") end # Do this to allow memory to be garbage-collected. @@ -6070,7 +6258,9 @@ function instability2D_plots(run_info, variable_name; plot_prefix, zind=nothing, colorbar_place=colorbar_place, frame_index=frame_index, outfile=outfile) catch e - println("Warning: error in perturbation animation for $variable_name. Error was $e") + return makie_post_processing_error_handler( + e, + "Warning: error in perturbation animation for $variable_name.") end # Do this to allow memory to be garbage-collected (although this is redundant @@ -7027,7 +7217,9 @@ function manufactured_solutions_analysis(run_info::Tuple; plot_prefix, nvperp) return manufactured_solutions_analysis(run_info[1]; plot_prefix=plot_prefix, nvperp=nvperp) catch e - println("Error in manufactured_solutions_analysis(). Error was ", e) + return makie_post_processing_error_handler( + e, + "Error in manufactured_solutions_analysis().") end end @@ -7116,7 +7308,9 @@ function manufactured_solutions_analysis_dfns(run_info::Tuple; plot_prefix) try return manufactured_solutions_analysis_dfns(run_info[1]; plot_prefix=plot_prefix) catch e - println("Error in manufactured_solutions_analysis_dfns(). Error was ", e) + return makie_post_processing_error_handler( + e, + "Error in manufactured_solutions_analysis_dfns().") end end @@ -7143,7 +7337,7 @@ function manufactured_solutions_analysis_dfns(run_info; plot_prefix) end """ - timestep_diagnostics(run_info; plot_prefix=nothing, it=nothing) + timestep_diagnostics(run_info, run_info_dfns; plot_prefix=nothing, it=nothing) Plot a time-trace of some adaptive-timestep diagnostics: steps per output, timestep failures per output, how many times per output each variable caused a timestep failure, @@ -7154,13 +7348,18 @@ will be saved with the format `plot_prefix_timestep_diagnostics.pdf`. `it` can be used to select a subset of the time points by passing a range. """ -function timestep_diagnostics(run_info; plot_prefix=nothing, it=nothing) +function timestep_diagnostics(run_info, run_info_dfns; plot_prefix=nothing, it=nothing, + electron=false) try if !isa(run_info, Tuple) run_info = (run_info,) end - println("Making timestep diagnostics plots") + if electron + println("Making electron timestep diagnostics plots") + else + println("Making timestep diagnostics plots") + end input = Dict_to_NamedTuple(input_dict["timestep_diagnostics"]) @@ -7168,6 +7367,12 @@ function timestep_diagnostics(run_info; plot_prefix=nothing, it=nothing) dt_fig = nothing CFL_fig = nothing + if electron + electron_prefix = "electron_" + else + electron_prefix = "" + end + if input.plot # Plot numbers of steps and numbers of failures ############################################### @@ -7192,46 +7397,60 @@ function timestep_diagnostics(run_info; plot_prefix=nothing, it=nothing) else time = ri.time end - - plot_1d(time, get_variable(ri, "steps_per_output"; it=it); - label=prefix * "steps", ax=ax) + plot_1d(time, get_variable(ri, "$(electron_prefix)steps_per_output"; + it=it); label=prefix * "steps", ax=ax) # Fudge to create an invisible line on ax_failures that cycles the line colors # and adds a label for "steps_per_output" to the plot because we create the # legend from ax_failures. - plot_1d([time[1]], [0]; label=prefix * "steps", ax=ax_failures) - plot_1d(time, get_variable(ri, "failures_per_output"; it=it); + plot_1d([ri.time[1]], [0]; label=prefix * "steps", ax=ax_failures) + plot_1d(time, + get_variable(ri, "$(electron_prefix)failures_per_output"; it=it); label=prefix * "failures", ax=ax_failures) - failure_caused_by_per_output = get_variable(ri, - "failure_caused_by_per_output"; - it=it) + failure_caused_by_per_output = + get_variable(ri, "$(electron_prefix)failure_caused_by_per_output"; + it=it) counter = 0 - # Ion pdf failure counter + # pdf failure counter counter += 1 + if electron + label = prefix * "failures caused by f_electron" + else + label = prefix * "failures caused by f_ion" + end plot_1d(time, @view failure_caused_by_per_output[counter,:]; - label=prefix * "failures caused by f_ion", ax=ax_failures) - if ri.evolve_density + label=label, ax=ax_failures) + if !electron && ri.evolve_density # Ion density failure counter counter += 1 plot_1d(time, @view failure_caused_by_per_output[counter,:]; linestyle=:dash, label=prefix * "failures caused by n_ion", ax=ax_failures) end - if ri.evolve_upar + if !electron && ri.evolve_upar # Ion flow failure counter counter += 1 plot_1d(time, @view failure_caused_by_per_output[counter,:]; linestyle=:dash, label=prefix * "failures caused by u_ion", ax=ax_failures) end - if ri.evolve_ppar - # Ion flow failure counter + if !electron && ri.evolve_ppar + # Ion parallel pressure failure counter counter += 1 plot_1d(time, @view failure_caused_by_per_output[counter,:]; linestyle=:dash, label=prefix * "failures caused by p_ion", ax=ax_failures) end - if ri.n_neutral_species > 0 + if electron || ri.composition.electron_physics ∈ (braginskii_fluid, + kinetic_electrons, + kinetic_electrons_with_temperature_equation) + # Electron parallel pressure failure counter + counter += 1 + plot_1d(time, @view failure_caused_by_per_output[counter,:]; + linestyle=:dash, label=prefix * "failures caused by p_electron", + ax=ax_failures) + end + if !electron && ri.n_neutral_species > 0 # Neutral pdf failure counter counter += 1 plot_1d(time, @view failure_caused_by_per_output[counter,:]; @@ -7265,6 +7484,14 @@ function timestep_diagnostics(run_info; plot_prefix=nothing, it=nothing) linestyle=:dot, label=prefix * "nonlinear iteration convergence failure", ax=ax_failures) end + if ri.composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + # Kinetic electron iteration failed to converge + counter += 1 + plot_1d(time, @view failure_caused_by_per_output[counter,:]; + linestyle=:dot, + label=prefix * "nonlinear iteration convergence failure", ax=ax_failures) + end end if counter > size(failure_caused_by_per_output, 1) @@ -7284,17 +7511,17 @@ function timestep_diagnostics(run_info; plot_prefix=nothing, it=nothing) ######################## if plot_prefix !== nothing - outfile = plot_prefix * "successful_dt.pdf" + outfile = plot_prefix * "$(electron_prefix)successful_dt.pdf" else outfile = nothing end - dt_fig = plot_vs_t(run_info, "average_successful_dt"; outfile=outfile) + dt_fig = plot_vs_t(run_info, "$(electron_prefix)average_successful_dt"; outfile=outfile) # PLot minimum CFL factors ########################## CFL_fig, ax = get_1d_ax(; xlabel="time", ylabel="(grid spacing) / speed") - maxval = Inf + #maxval = Inf for ri ∈ run_info if length(run_info) == 1 prefix = "" @@ -7307,23 +7534,33 @@ function timestep_diagnostics(run_info; plot_prefix=nothing, it=nothing) time = ri.time end - CFL_vars = String[] - implicit_CFL_vars = String[] + if electron + CFL_vars = ["minimum_CFL_electron_z", "minimum_CFL_electron_vpa"] + implicit_CFL_vars = String[] + else + CFL_vars = String[] + implicit_CFL_vars = String[] - push!(CFL_vars, "minimum_CFL_ion_z") - if occursin("ARK", ri.t_input["type"]) && ri.t_input["implicit_ion_advance"] - push!(implicit_CFL_vars, "minimum_CFL_ion_z") - end - push!(CFL_vars, "minimum_CFL_ion_vpa") - if occursin("ARK", ri.t_input["type"]) && (ri.t_input["implicit_ion_advance"] || ri.t_input["implicit_vpa_advection"]) - push!(implicit_CFL_vars, "minimum_CFL_ion_vpa") + push!(CFL_vars, "minimum_CFL_ion_z") + if occursin("ARK", ri.t_input["type"]) && ri.t_input["implicit_ion_advance"] + push!(implicit_CFL_vars, "minimum_CFL_ion_z") + end + push!(CFL_vars, "minimum_CFL_ion_vpa") + if occursin("ARK", ri.t_input["type"]) && (ri.t_input["implicit_ion_advance"] || ri.t_input["implicit_vpa_advection"]) + push!(implicit_CFL_vars, "minimum_CFL_ion_vpa") + end + if ri.n_neutral_species > 0 + push!(CFL_vars, "minimum_CFL_neutral_z", "minimum_CFL_neutral_vz") + end end - if ri.n_neutral_species > 0 - push!(CFL_vars, "minimum_CFL_neutral_z", "minimum_CFL_neutral_vz") + if it !== nothing + time = ri.time[it] + else + time = ri.time end for varname ∈ CFL_vars var = get_variable(ri, varname) - maxval = NaNMath.min(maxval, NaNMath.maximum(var)) + #maxval = NaNMath.min(maxval, NaNMath.maximum(var)) if occursin("neutral", varname) if varname ∈ implicit_CFL_vars linestyle = :dashdot @@ -7337,10 +7574,12 @@ function timestep_diagnostics(run_info; plot_prefix=nothing, it=nothing) linestyle = nothing end end - plot_1d(time, var; ax=ax, label=prefix*varname, linestyle=linestyle) + plot_1d(time, var; ax=ax, label=prefix*electron_prefix*varname, + linestyle=linestyle, yscale=log10, + transform=x->positive_or_nan(x; epsilon=1.e-20)) end end - ylims!(ax, 0.0, 10.0 * maxval) + #ylims!(ax, 0.0, 10.0 * maxval) put_legend_right(CFL_fig, ax) limits_fig, ax = get_1d_ax(; xlabel="time", ylabel="number of limits per factor per output", @@ -7358,9 +7597,9 @@ function timestep_diagnostics(run_info; plot_prefix=nothing, it=nothing) time = ri.time end - limit_caused_by_per_output = get_variable(ri, - "limit_caused_by_per_output"; - it=it) + limit_caused_by_per_output = + get_variable(ri, "$(electron_prefix)limit_caused_by_per_output"; + it=it) counter = 0 # Maximum timestep increase limit counter @@ -7390,27 +7629,40 @@ function timestep_diagnostics(run_info; plot_prefix=nothing, it=nothing) # Accuracy limit counters counter += 1 + if electron + label = prefix * "electron pdf RK accuracy" + else + label = prefix * "ion pdf RK accuracy" + end plot_1d(time, @view limit_caused_by_per_output[counter,:]; - label=prefix * "ion pdf RK accuracy", ax=ax, linestyle=:dash) - if ri.evolve_density + label=label, ax=ax, linestyle=:dash) + if !electron && ri.evolve_density counter += 1 plot_1d(time, @view limit_caused_by_per_output[counter,:]; label=prefix * "ion density RK accuracy", ax=ax, linestyle=:dash) end - if ri.evolve_upar + if !electron && ri.evolve_upar counter += 1 plot_1d(time, @view limit_caused_by_per_output[counter,:]; label=prefix * "ion upar RK accuracy", ax=ax, linestyle=:dash) end - if ri.evolve_ppar + if !electron && ri.evolve_ppar counter += 1 plot_1d(time, @view limit_caused_by_per_output[counter,:]; label=prefix * "ion ppar RK accuracy", ax=ax, linestyle=:dash) end - if ri.n_neutral_species > 0 + if electron || ri.composition.electron_physics ∈ (braginskii_fluid, + kinetic_electrons, + kinetic_electrons_with_temperature_equation) + counter += 1 + plot_1d(time, @view limit_caused_by_per_output[counter,:]; + label=prefix * "electron ppar RK accuracy", ax=ax, + linestyle=:dash) + end + if !electron && ri.n_neutral_species > 0 counter += 1 plot_1d(time, @view limit_caused_by_per_output[counter,:]; label=prefix * "neutral pdf RK accuracy", ax=ax, @@ -7435,27 +7687,37 @@ function timestep_diagnostics(run_info; plot_prefix=nothing, it=nothing) end end - if !(occursin("ARK", ri.t_input["type"]) && ri.t_input["implicit_ion_advance"]) + if electron || !(occursin("ARK", ri.t_input["type"]) && ri.t_input["implicit_ion_advance"]) # Ion z advection counter += 1 + if electron + label = prefix * "electron z advect" + else + label = prefix * "ion z advect" + end plot_1d(time, @view limit_caused_by_per_output[counter,:]; - label=prefix * "ion z advect", ax=ax, linestyle=:dot) + label=label, ax=ax, linestyle=:dot) end - if !(occursin("ARK", ri.t_input["type"]) && (ri.t_input["implicit_ion_advance"] || ri.t_input["implicit_vpa_advection"])) + if electron || !(occursin("ARK", ri.t_input["type"]) && (ri.t_input["implicit_ion_advance"] || ri.t_input["implicit_vpa_advection"])) # Ion vpa advection counter += 1 + if electron + label = prefix * "electron vpa advect" + else + label = prefix * "ion vpa advect" + end plot_1d(time, @view limit_caused_by_per_output[counter,:]; - label=prefix * "ion vpa advect", ax=ax, linestyle=:dot) + label=label, ax=ax, linestyle=:dot) end - if ri.n_neutral_species > 0 - # Ion z advection + if !electron && ri.n_neutral_species > 0 + # Neutral z advection counter += 1 plot_1d(time, @view limit_caused_by_per_output[counter,:]; label=prefix * "neutral z advect", ax=ax, linestyle=:dot) - # Ion vpa advection + # Neutral vz advection counter += 1 plot_1d(time, @view limit_caused_by_per_output[counter,:]; label=prefix * "neutral vz advect", ax=ax, linestyle=:dot) @@ -7503,6 +7765,13 @@ function timestep_diagnostics(run_info; plot_prefix=nothing, it=nothing) plot_1d(time, linear_iterations, label=prefix * " " * p * " L per NL", ax=ax) end end + + if ri.composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + has_nl_solver = true + electron_steps_per_ion_step = get_variable(ri, "electron_steps_per_ion_step") + plot_1d(time, electron_steps_per_ion_step, label=prefix * " electron steps per solve", ax=ax) + end end if has_nl_solver @@ -7511,13 +7780,13 @@ function timestep_diagnostics(run_info; plot_prefix=nothing, it=nothing) if plot_prefix !== nothing - outfile = plot_prefix * "timestep_diagnostics.pdf" + outfile = plot_prefix * electron_prefix * "timestep_diagnostics.pdf" save(outfile, steps_fig) - outfile = plot_prefix * "CFL_factors.pdf" + outfile = plot_prefix * electron_prefix * "CFL_factors.pdf" save(outfile, CFL_fig) - outfile = plot_prefix * "timestep_limits.pdf" + outfile = plot_prefix * electron_prefix * "timestep_limits.pdf" save(outfile, limits_fig) if has_nl_solver @@ -7539,29 +7808,57 @@ function timestep_diagnostics(run_info; plot_prefix=nothing, it=nothing) if plot_prefix === nothing error("plot_prefix is required when animate_CFL=true") end - data = get_variable(run_info, "CFL_ion_z") - datamin = minimum(minimum(d) for d ∈ data) - animate_vs_vpa_z(run_info, "CFL_ion_z"; data=data, it=it, - outfile=plot_prefix * "CFL_ion_z_vs_vpa_z.gif", - colorscale=log10, - transform=x->positive_or_nan(x; epsilon=1.e-30), - colorrange=(datamin, datamin * 1000.0), - axis_args=Dict(:bottomspinevisible=>false, - :topspinevisible=>false, - :leftspinevisible=>false, - :rightspinevisible=>false)) - data = get_variable(run_info, "CFL_ion_vpa") - datamin = minimum(minimum(d) for d ∈ data) - animate_vs_vpa_z(run_info, "CFL_ion_vpa"; data=data, it=it, - outfile=plot_prefix * "CFL_ion_vpa_vs_vpa_z.gif", - colorscale=log10, - transform=x->positive_or_nan(x; epsilon=1.e-30), - colorrange=(datamin, datamin * 1000.0), - axis_args=Dict(:bottomspinevisible=>false, - :topspinevisible=>false, - :leftspinevisible=>false, - :rightspinevisible=>false)) - if any(ri.n_neutral_species > 0 for ri ∈ run_info) + if !electron + data = get_variable(run_info, "CFL_ion_z") + datamin = minimum(minimum(d) for d ∈ data) + animate_vs_vpa_z(run_info, "CFL_ion_z"; data=data, it=it, + outfile=plot_prefix * "CFL_ion_z_vs_vpa_z.gif", + colorscale=log10, + transform=x->positive_or_nan(x; epsilon=1.e-30), + colorrange=(datamin, datamin * 1000.0), + axis_args=Dict(:bottomspinevisible=>false, + :topspinevisible=>false, + :leftspinevisible=>false, + :rightspinevisible=>false)) + data = get_variable(run_info, "CFL_ion_vpa") + datamin = minimum(minimum(d) for d ∈ data) + animate_vs_vpa_z(run_info, "CFL_ion_vpa"; data=data, it=it, + outfile=plot_prefix * "CFL_ion_vpa_vs_vpa_z.gif", + colorscale=log10, + transform=x->positive_or_nan(x; epsilon=1.e-30), + colorrange=(datamin, datamin * 1000.0), + axis_args=Dict(:bottomspinevisible=>false, + :topspinevisible=>false, + :leftspinevisible=>false, + :rightspinevisible=>false)) + end + if electron || any(ri.composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + for ri ∈ run_info) + data = get_variable(run_info, "CFL_electron_z") + datamin = minimum(minimum(d) for d ∈ data) + animate_vs_vpa_z(run_info, "CFL_electron_z"; data=data, it=it, + outfile=plot_prefix * "CFL_electron_z_vs_vpa_z.gif", + colorscale=log10, + transform=x->positive_or_nan(x; epsilon=1.e-30), + colorrange=(datamin, datamin * 1000.0), + axis_args=Dict(:bottomspinevisible=>false, + :topspinevisible=>false, + :leftspinevisible=>false, + :rightspinevisible=>false)) + data = get_variable(run_info, "CFL_electron_vpa") + datamin = minimum(minimum(d) for d ∈ data) + animate_vs_vpa_z(run_info, "CFL_electron_vpa"; data=data, it=it, + outfile=plot_prefix * "CFL_electron_vpa_vs_vpa_z.gif", + colorscale=log10, + transform=x->positive_or_nan(x; epsilon=1.e-30), + colorrange=(datamin, datamin * 1000.0), + axis_args=Dict(:bottomspinevisible=>false, + :topspinevisible=>false, + :leftspinevisible=>false, + :rightspinevisible=>false)) + end + if !electron && any(ri.n_neutral_species > 0 for ri ∈ run_info) data = get_variable(run_info, "CFL_neutral_z") datamin = minimum(minimum(d) for d ∈ data) animate_vs_vz_z(run_info, "CFL_neutral_z"; data=data, it=it, @@ -7587,9 +7884,169 @@ function timestep_diagnostics(run_info; plot_prefix=nothing, it=nothing) end end + if run_info_dfns[1].dfns + this_input_dict = input_dict_dfns + else + this_input_dict = input_dict + end + if electron + variable_list = (v for v ∈ union((ri.evolving_variables for ri in run_info_dfns)...) + if occursin("electron", v)) + else + variable_list = (v for v ∈ union((ri.evolving_variables for ri in run_info_dfns)...) + if !occursin("electron", v)) + end + all_variable_names = union((ri.variable_names for ri ∈ run_info_dfns)...) + + if input.plot_timestep_residual + for variable_name ∈ variable_list + loworder_name = variable_name * "_loworder" + if loworder_name ∉ all_variable_names + # No data to calculate residual for this variable + continue + end + residual_name = variable_name * "_timestep_residual" + if variable_name == "f_neutral" + plot_vs_vz_z(run_info_dfns, residual_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * residual_name * "_vs_vz_z.pdf") + elseif variable_name ∈ ("f", "f_electron") + plot_vs_vpa_z(run_info_dfns, residual_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * residual_name * "_vs_vpa_z.pdf") + else + plot_vs_z(run_info_dfns, residual_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * residual_name * "_vs_z.pdf") + end + end + end + + if input.animate_timestep_residual + for variable_name ∈ variable_list + loworder_name = variable_name * "_loworder" + if loworder_name ∉ all_variable_names + # No data to calculate residual for this variable + continue + end + residual_name = variable_name * "_timestep_residual" + if variable_name == "f_neutral" + animate_vs_vz_z(run_info_dfns, residual_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * residual_name * "_vs_vz_z." * this_input_dict[variable_name]["animation_ext"]) + elseif variable_name ∈ ("f", "f_electron") + animate_vs_vpa_z(run_info_dfns, residual_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * residual_name * "_vs_vpa_z." * this_input_dict[variable_name]["animation_ext"]) + else + animate_vs_z(run_info_dfns, residual_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * residual_name * "_vs_z." * this_input_dict[variable_name]["animation_ext"]) + end + end + end + + if input.plot_timestep_error + for variable_name ∈ variable_list + loworder_name = variable_name * "_loworder" + if loworder_name ∉ all_variable_names + # No data to calculate error for this variable + continue + end + error_name = variable_name * "_timestep_error" + if variable_name == "f_neutral" + plot_vs_vz_z(run_info_dfns, error_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * error_name * "_vs_vz_z.pdf") + elseif variable_name ∈ ("f", "f_electron") + plot_vs_vpa_z(run_info_dfns, error_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * error_name * "_vs_vpa_z.pdf") + else + plot_vs_z(run_info_dfns, error_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * error_name * "_vs_z.pdf") + end + end + end + + if input.animate_timestep_error + for variable_name ∈ variable_list + loworder_name = variable_name * "_loworder" + if loworder_name ∉ all_variable_names + # No data to calculate error for this variable + continue + end + error_name = variable_name * "_timestep_error" + if variable_name == "f_neutral" + animate_vs_vz_z(run_info_dfns, error_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * error_name * "_vs_vz_z." * this_input_dict[variable_name]["animation_ext"]) + elseif variable_name ∈ ("f", "f_electron") + animate_vs_vpa_z(run_info_dfns, error_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * error_name * "_vs_vpa_z." * this_input_dict[variable_name]["animation_ext"]) + else + animate_vs_z(run_info_dfns, error_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * error_name * "_vs_z." * this_input_dict[variable_name]["animation_ext"]) + end + end + end + + if input.plot_steady_state_residual + for variable_name ∈ variable_list + loworder_name = variable_name * "_loworder" + if loworder_name ∉ all_variable_names + # No data to calculate residual for this variable + continue + end + residual_name = variable_name * "_steady_state_residual" + if variable_name == "f_neutral" + plot_vs_vz_z(run_info_dfns, residual_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * residual_name * "_vs_vz_z.pdf") + elseif variable_name ∈ ("f", "f_electron") + plot_vs_vpa_z(run_info_dfns, residual_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * residual_name * "_vs_vpa_z.pdf") + else + plot_vs_z(run_info_dfns, residual_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * residual_name * "_vs_z.pdf") + end + end + end + + if input.animate_steady_state_residual + for variable_name ∈ variable_list + loworder_name = variable_name * "_loworder" + if loworder_name ∉ all_variable_names + # No data to calculate residual for this variable + continue + end + residual_name = variable_name * "_steady_state_residual" + if variable_name == "f_neutral" + animate_vs_vz_z(run_info_dfns, residual_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * residual_name * "_vs_vz_z." * this_input_dict[variable_name]["animation_ext"]) + elseif variable_name ∈ ("f", "f_electron") + animate_vs_vpa_z(run_info_dfns, residual_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * residual_name * "_vs_vpa_z." * this_input_dict[variable_name]["animation_ext"]) + else + animate_vs_z(run_info_dfns, residual_name; + input=this_input_dict[variable_name], + outfile=plot_prefix * residual_name * "_vs_z." * this_input_dict[variable_name]["animation_ext"]) + end + end + end + return steps_fig, dt_fig, CFL_fig catch e - println("Error in timestep_diagnostics(). Error was ", e) + return makie_post_processing_error_handler( + e, + "Error in timestep_diagnostics().") end end diff --git a/makie_post_processing/makie_post_processing/src/shared_utils.jl b/makie_post_processing/makie_post_processing/src/shared_utils.jl index 031bcda75..32acaf1ec 100644 --- a/makie_post_processing/makie_post_processing/src/shared_utils.jl +++ b/makie_post_processing/makie_post_processing/src/shared_utils.jl @@ -5,7 +5,6 @@ export calculate_and_write_frequencies, get_geometry, get_composition using moment_kinetics.analysis: fit_delta_phi_mode using moment_kinetics.array_allocation: allocate_float using moment_kinetics.coordinates: define_coordinate -using moment_kinetics.geo: init_magnetic_geometry using moment_kinetics.input_structs: boltzmann_electron_response, boltzmann_electron_response_with_simple_sheath, grid_input, geometry_input, species_composition @@ -13,7 +12,7 @@ using moment_kinetics.moment_kinetics_input: get_default_rhostar, setup_referenc using moment_kinetics.type_definitions: mk_float, mk_int using moment_kinetics.reference_parameters: setup_reference_parameters using moment_kinetics.moment_kinetics_input: get_default_rhostar -using moment_kinetics.geo: init_magnetic_geometry +using moment_kinetics.geo: init_magnetic_geometry, setup_geometry_input using MPI """ @@ -90,7 +89,6 @@ function get_composition(scan_input) use_test_neutral_wall_pdf = get(scan_input, "use_test_neutral_wall_pdf", false) gyrokinetic_ions = get(scan_input, "gyrokinetic_ions", false) # constant to be used to test nonzero Er in wall boundary condition - Er_constant = get(scan_input, "Er_constant", 0.0) recycling_fraction = get(scan_input, "recycling_fraction", 1.0) # constant to be used to control Ez divergences epsilon_offset = get(scan_input, "epsilon_offset", 0.001) @@ -106,7 +104,7 @@ function get_composition(scan_input) # ratio of the electron particle mass to the ion particle mass me_over_mi = 1.0/1836.0 composition = species_composition(n_species, n_ion_species, n_neutral_species, - electron_physics, use_test_neutral_wall_pdf, T_e, T_wall, phi_wall, Er_constant, + electron_physics, use_test_neutral_wall_pdf, T_e, T_wall, phi_wall, mn_over_mi, me_over_mi, recycling_fraction, gyrokinetic_ions, allocate_float(n_species)) return composition @@ -114,18 +112,10 @@ end function get_geometry(scan_input,z,r) reference_params = setup_reference_parameters(scan_input) - # set geometry_input - # MRH need to get this in way that does not duplicate code - # MRH from moment_kinetics_input.jl - option = get(scan_input, "geometry_option", "constant-helical") #"1D-mirror" - pitch = get(scan_input, "pitch", 1.0) - rhostar = get(scan_input, "rhostar", get_default_rhostar(reference_params)) - DeltaB = get(scan_input, "DeltaB", 1.0) - geo_in = geometry_input(rhostar,option,pitch,DeltaB) + reference_rhostar = get_default_rhostar(reference_params) + geo_in = setup_geometry_input(scan_input, reference_rhostar) geometry = init_magnetic_geometry(geo_in,z,r) - return geometry - end end # shared_utils.jl diff --git a/moment_kinetics/debug_test/kinetic_electron_inputs.jl b/moment_kinetics/debug_test/kinetic_electron_inputs.jl new file mode 100644 index 000000000..684b114ff --- /dev/null +++ b/moment_kinetics/debug_test/kinetic_electron_inputs.jl @@ -0,0 +1,96 @@ +test_type = "Kinetic electron" + +test_input = Dict("n_ion_species" => 1, + "n_neutral_species" => 1, + "electron_physics" => "kinetic_electrons", + "run_name" => "kinetic_electron", + "base_directory" => test_output_directory, + "evolve_moments_density" => true, + "evolve_moments_parallel_flow" => true, + "evolve_moments_parallel_pressure" => true, + "evolve_moments_conservation" => true, + "recycling_fraction" => 0.5, + "T_e" => 0.2, + "T_wall" => 0.1, + "initial_density1" => 1.0, + "initial_temperature1" => 1.0, + "z_IC_option1" => "gaussian", + "z_IC_density_amplitude1" => 0.001, + "z_IC_density_phase1" => 0.0, + "z_IC_upar_amplitude1" => 1.0, + "z_IC_upar_phase1" => 0.0, + "z_IC_temperature_amplitude1" => 0.0, + "z_IC_temperature_phase1" => 0.0, + "vpa_IC_option1" => "gaussian", + "vpa_IC_density_amplitude1" => 1.0, + "vpa_IC_density_phase1" => 0.0, + "vpa_IC_upar_amplitude1" => 0.0, + "vpa_IC_upar_phase1" => 0.0, + "vpa_IC_temperature_amplitude1" => 0.0, + "vpa_IC_temperature_phase1" => 0.0, + "initial_density2" => 1.0, + "initial_temperature2" => 1.0, + "z_IC_option2" => "gaussian", + "z_IC_density_amplitude2" => 0.001, + "z_IC_density_phase2" => 0.0, + "z_IC_upar_amplitude2" => -1.0, + "z_IC_upar_phase2" => 0.0, + "z_IC_temperature_amplitude2" => 0.0, + "z_IC_temperature_phase2" => 0.0, + "vpa_IC_option2" => "gaussian", + "vpa_IC_density_amplitude2" => 1.0, + "vpa_IC_density_phase2" => 0.0, + "vpa_IC_upar_amplitude2" => 0.0, + "vpa_IC_upar_phase2" => 0.0, + "vpa_IC_temperature_amplitude2" => 0.0, + "vpa_IC_temperature_phase2" => 0.0, + "charge_exchange_frequency" => 0.75, + "ionization_frequency" => 0.5, + "constant_ionization_rate" => false, + "timestepping" => Dict{String,Any}("type" => "Fekete4(3)", + "nstep" => 3, + "dt" => 2.0e-8, + "minimum_dt" => 1.0e-8, + "CFL_prefactor" => 1.0, + "step_update_prefactor" => 0.4, + "nwrite" => 2, + "split_operators" => false), + "electron_timestepping" => Dict{String,Any}("type" => "Fekete4(3)", + "nstep" => 10, + "dt" => 4.0e-11, + "minimum_dt" => 2.0e-11, + "initialization_residual_value" => 1.e10, + "converged_residual_value" => 1.e10, + "nwrite" => 10000, + "nwrite_dfns" => 10000, + "no_restart" => true), + "r_ngrid" => 1, + "r_nelement" => 1, + "z_ngrid" => 3, + "z_nelement" => 24, + "z_bc" => "wall", + "z_discretization" => "chebyshev_pseudospectral", + "z_element_spacing_option" => "sqrt", + "vpa_ngrid" => 3, + "vpa_nelement" => 16, + "vpa_L" => 6.0, + "vpa_bc" => "zero", + "vpa_discretization" => "chebyshev_pseudospectral", + "vz_ngrid" => 3, + "vz_nelement" => 6, + "vz_L" => 6.0, + "vz_bc" => "zero", + "vz_discretization" => "chebyshev_pseudospectral", + "ion_source" => Dict{String,Any}("active" => true, + "z_profile" => "gaussian", + "z_width" => 0.125, + "source_strength" => 2.0, + "source_T" => 2.0), + "krook_collisions" => Dict{String,Any}("use_krook" => true), + "numerical_dissipation" => Dict{String,Any}("force_minimum_pdf_value" => 0.0, + "vpa_dissipation_coefficient" => 1e-2)) + + +test_input_list = [ + test_input, + ] diff --git a/moment_kinetics/debug_test/kinetic_electron_tests.jl b/moment_kinetics/debug_test/kinetic_electron_tests.jl new file mode 100644 index 000000000..f246eed9a --- /dev/null +++ b/moment_kinetics/debug_test/kinetic_electron_tests.jl @@ -0,0 +1,23 @@ +module KineticElectronDebug + +# Debug test using wall boundary conditions. + +include("setup.jl") + +# Create a temporary directory for test output +test_output_directory = get_MPI_tempdir() +mkpath(test_output_directory) + + +# Input parameters for the test +include("kinetic_electron_inputs.jl") + +# Defines the test functions, using variables defined in the *_inputs.jl file +include("runtest_template.jl") + +end # KineticElectronDebug + + +using .KineticElectronDebug + +KineticElectronDebug.runtests() diff --git a/moment_kinetics/debug_test/recycling_fraction_inputs.jl b/moment_kinetics/debug_test/recycling_fraction_inputs.jl index 643b7c954..7f4b24266 100644 --- a/moment_kinetics/debug_test/recycling_fraction_inputs.jl +++ b/moment_kinetics/debug_test/recycling_fraction_inputs.jl @@ -12,7 +12,7 @@ test_input = Dict("n_ion_species" => 1, "recycling_fraction" => 0.5, "krook_collisions" => Dict{String,Any}("use_krook" => true), "T_e" => 0.2, - "T_wall" => 0.1, + "T_wall" => 2.0, "initial_density1" => 1.0, "initial_temperature1" => 1.0, "z_IC_option1" => "gaussian", diff --git a/moment_kinetics/debug_test/runtests.jl b/moment_kinetics/debug_test/runtests.jl index 94b5dbbc7..86d3056ac 100644 --- a/moment_kinetics/debug_test/runtests.jl +++ b/moment_kinetics/debug_test/runtests.jl @@ -4,11 +4,12 @@ include("setup.jl") function runtests() @testset "moment_kinetics tests" begin + include(joinpath(@__DIR__, "kinetic_electron_tests.jl")) include(joinpath(@__DIR__, "sound_wave_tests.jl")) include(joinpath(@__DIR__, "fokker_planck_collisions_tests.jl")) include(joinpath(@__DIR__, "wall_bc_tests.jl")) include(joinpath(@__DIR__, "harrisonthompson.jl")) - include(joinpath(@__DIR__, "mms_tests.jl")) + #include(joinpath(@__DIR__, "mms_tests.jl")) include(joinpath(@__DIR__, "restart_interpolation_tests.jl")) include(joinpath(@__DIR__, "recycling_fraction_tests.jl")) include(joinpath(@__DIR__, "gyroaverage_tests.jl")) diff --git a/moment_kinetics/ext/file_io_netcdf.jl b/moment_kinetics/ext/file_io_netcdf.jl index d1e52c737..bb7a86f2b 100644 --- a/moment_kinetics/ext/file_io_netcdf.jl +++ b/moment_kinetics/ext/file_io_netcdf.jl @@ -8,9 +8,10 @@ module file_io_netcdf import moment_kinetics.file_io: io_has_parallel, open_output_file_implementation, create_io_group, get_group, is_group, get_subgroup_keys, - get_variable_keys, add_attribute!, write_single_value!, - create_dynamic_variable!, append_to_dynamic_var -import moment_kinetics.load_data: open_file_to_read, load_variable, load_slice + get_variable_keys, add_attribute!, modify_attribute!, + write_single_value!, create_dynamic_variable!, + append_to_dynamic_var +import moment_kinetics.load_data: open_file_to_read, get_attribute, load_variable, load_slice using moment_kinetics.coordinates: coordinate using moment_kinetics.input_structs: netcdf @@ -21,9 +22,9 @@ function io_has_parallel(::Val{netcdf}) return false end -function open_output_file_implementation(::Val{netcdf}, prefix, parallel_io, io_comm, +function open_output_file_implementation(::Val{netcdf}, prefix, io_input, io_comm, mode="c") - parallel_io && error("NetCDF interface does not support parallel I/O") + io_input.parallel_io && error("NetCDF interface does not support parallel I/O") # the netcdf file will be given by output_dir/run_name with .cdf appended filename = string(prefix, ".cdf") @@ -32,7 +33,7 @@ function open_output_file_implementation(::Val{netcdf}, prefix, parallel_io, io_ # create the new NetCDF file fid = NCDataset(filename, mode) - return fid, (filename, parallel_io, io_comm) + return fid, (filename, io_input, io_comm) end function create_io_group(parent::NCDataset, name; description=nothing) @@ -76,6 +77,14 @@ function add_attribute!(var::NCDatasets.CFVariable, name, value) var.attrib[name] = value end +function modify_attribute!(file_or_group_or_var::Union{NCDataset,NCDatasets.CFVariable}, name, value) + file_or_group_or_var.attrib[name] = value +end + +function get_attribute(file_or_group_or_var::Union{NCDataset,NCDatasets.CFVariable}, name) + return var.attrib[name] +end + function maybe_create_netcdf_dim(file_or_group::NCDataset, name, size) if !(name ∈ keys(file_or_group.dim)) defDim(file_or_group, name, size) diff --git a/moment_kinetics/ext/manufactured_solns_ext.jl b/moment_kinetics/ext/manufactured_solns_ext.jl index 0707af732..e65c964db 100644 --- a/moment_kinetics/ext/manufactured_solns_ext.jl +++ b/moment_kinetics/ext/manufactured_solns_ext.jl @@ -43,6 +43,8 @@ using IfElse # does not appear in a particular geometric coefficient, because # that coefficient is a constant. struct geometric_coefficients_sym{} + Er_constant::mk_float + Ez_constant::mk_float rhostar::mk_float Bzed::Union{mk_float,Num} Bzeta::Union{mk_float,Num} @@ -62,6 +64,8 @@ using IfElse option = geometry_input_data.option rhostar = geometry_input_data.rhostar pitch = geometry_input_data.pitch + Er_constant = geometry_input_data.Er_constant + Ez_constant = geometry_input_data.Ez_constant if option == "constant-helical" || option == "default" bzed = pitch bzeta = sqrt(1 - bzed^2) @@ -90,10 +94,20 @@ using IfElse end dBdz = expand_derivatives(Dz(Bmag)) jacobian = 1.0 + elseif option == "0D-Spitzer-test" + Bmag = 1.0 + dBdz = geometry_input_data.dBdz_constant + dBdr = geometry_input_data.dBdr_constant + bzed = pitch + bzeta = sqrt(1 - bzed^2) + Bzed = Bmag*bzed + Bzeta = Bmag*bzeta + jacobian = 1.0 else input_option_error("$option", option) end - geo_sym = geometric_coefficients_sym(rhostar,Bzed,Bzeta,Bmag,bzed,bzeta,dBdz,dBdr,jacobian) + geo_sym = geometric_coefficients_sym(Er_constant,Ez_constant, + rhostar,Bzed,Bzeta,Bmag,bzed,bzeta,dBdz,dBdr,jacobian) return geo_sym end @@ -272,7 +286,7 @@ using IfElse upari = 0.0 elseif z_bc == "wall" densi = densi_sym(Lr,Lz,r_bc,z_bc,composition,manufactured_solns_input,species) - Er, Ez, phi = electric_fields(Lr,Lz,r_bc,z_bc,composition,nr,manufactured_solns_input,species) + Er, Ez, phi = electric_fields(Lr,Lz,r_bc,z_bc,composition,geometry,nr,manufactured_solns_input,species) Bzeta = geometry.Bzeta Bmag = geometry.Bmag rhostar = geometry.rhostar @@ -357,7 +371,7 @@ using IfElse if manufactured_solns_input.type == "default" # calculate the electric fields and the potential - Er, Ez, phi = electric_fields(Lr, Lz, r_bc, z_bc, composition, nr, + Er, Ez, phi = electric_fields(Lr, Lz, r_bc, z_bc, composition, geometry, nr, manufactured_solns_input, species) # get geometric/composition data @@ -402,7 +416,7 @@ using IfElse return dfni end - function electric_fields(Lr, Lz, r_bc, z_bc, composition, nr, + function electric_fields(Lr, Lz, r_bc, z_bc, composition, geometry, nr, manufactured_solns_input, species) # define derivative operators @@ -412,7 +426,7 @@ using IfElse # get N_e factor for boltzmann response if composition.electron_physics == boltzmann_electron_response_with_simple_sheath && nr == 1 # so 1D MMS test with 3V neutrals where ion current can be calculated prior to knowing Er - jpari_into_LHS_wall = jpari_into_LHS_wall_sym(Lr, Lz, r_bc, z_bc, composition, + jpari_into_LHS_wall = jpari_into_LHS_wall_sym(Lr, Lz, r_bc, z_bc, manufactured_solns_input) N_e = -2.0*sqrt(pi*composition.me_over_mi)*exp(-composition.phi_wall/composition.T_e)*jpari_into_LHS_wall elseif composition.electron_physics == boltzmann_electron_response_with_simple_sheath && nr > 1 @@ -437,8 +451,8 @@ using IfElse # calculate the electric fields dense = densi # get the electron density via quasineutrality with Zi = 1 phi = composition.T_e*log(dense/N_e) # use the adiabatic response of electrons for me/mi -> 0 - Er = -Dr(phi)*rfac + composition.Er_constant - Ez = -Dz(phi) + Er = -Dr(phi)*rfac + geometry.Er_constant + Ez = -Dz(phi) + geometry.Ez_constant Er_expanded = expand_derivatives(Er) Ez_expanded = expand_derivatives(Ez) @@ -500,11 +514,13 @@ using IfElse return manufactured_solns_list end - function manufactured_electric_fields(Lr, Lz, r_bc, z_bc, composition, nr, + function manufactured_electric_fields(Lr, Lz, r_bc, z_bc, composition, geometry_input_data::geometry_input, nr, manufactured_solns_input, species) + # calculate the geometry symbolically + geometry = geometry_sym(geometry_input_data,Lz,Lr,nr) # calculate the electric fields and the potential - Er, Ez, phi = electric_fields(Lr, Lz, r_bc, z_bc, composition, nr, + Er, Ez, phi = electric_fields(Lr, Lz, r_bc, z_bc, composition, geometry, nr, manufactured_solns_input, species) Er_func = build_function(Er, z, r, t, expression=Val{false}) @@ -603,7 +619,7 @@ using IfElse # calculate the electric fields and the potential Er, Ez, phi = electric_fields(r_coord.L, z_coord.L, r_coord.bc, z_coord.bc, - composition, r_coord.n, manufactured_solns_input, + composition, geometry, r_coord.n, manufactured_solns_input, ion_species) # the adiabatic invariant (for compactness) diff --git a/moment_kinetics/src/boundary_conditions.jl b/moment_kinetics/src/boundary_conditions.jl index 61dff1a08..4a1216bf7 100644 --- a/moment_kinetics/src/boundary_conditions.jl +++ b/moment_kinetics/src/boundary_conditions.jl @@ -416,6 +416,26 @@ function enforce_zero_incoming_bc!(pdf, speed, z, zero) end end end +function get_ion_z_boundary_cutoff_indices(density, upar, ppar, evolve_upar, evolve_ppar, + z, vpa, zero) + if z.irank == 0 + vth = sqrt(2.0*(ppar[1]/density[1])) + @. vpa.scratch = vpagrid_to_dzdt(vpa.grid, vth, + upar[1], evolve_ppar, evolve_upar) + last_negative_vpa_ind = searchsortedlast(vpa.scratch, -zero) + else + last_negative_vpa_ind = nothing + end + if z.irank == z.nrank - 1 + vth = sqrt(2.0*(ppar[end]/density[end])) + @. vpa.scratch2 = vpagrid_to_dzdt(vpa.grid, vth, + upar[end], evolve_ppar, evolve_upar) + first_positive_vpa_ind = searchsortedfirst(vpa.scratch2, zero) + else + first_positive_vpa_ind = nothing + end + return last_negative_vpa_ind, first_positive_vpa_ind +end function enforce_zero_incoming_bc!(pdf, z::coordinate, vpa::coordinate, density, upar, ppar, evolve_upar, evolve_ppar, zero) if z.irank != 0 && z.irank != z.nrank - 1 @@ -429,28 +449,15 @@ function enforce_zero_incoming_bc!(pdf, z::coordinate, vpa::coordinate, density, # so use advection speed below instead of vpa # absolute velocity at left boundary + last_negative_vpa_ind, first_positive_vpa_ind = + get_ion_z_boundary_cutoff_indices(density, upar, ppar, evolve_upar, evolve_ppar, + z, vpa, zero) if z.irank == 0 - @. vpa.scratch = vpagrid_to_dzdt(vpa.grid, sqrt(2.0*(ppar[1]/density[1])), - upar[1], evolve_ppar, evolve_upar) - @loop_vpa ivpa begin - # for left boundary in zed (z = -Lz/2), want - # f(z=-Lz/2, v_parallel > 0) = 0 - if vpa.scratch[ivpa] > zero - pdf[ivpa,:,1] .= 0.0 - end - end + pdf[last_negative_vpa_ind+1:end, :, 1] .= 0.0 end # absolute velocity at right boundary if z.irank == z.nrank - 1 - @. vpa.scratch2 = vpagrid_to_dzdt(vpa.grid, sqrt(2.0*(ppar[end]/density[end])), - upar[end], evolve_ppar, evolve_upar) - @loop_vpa ivpa begin - # for right boundary in zed (z = Lz/2), want - # f(z=Lz/2, v_parallel < 0) = 0 - if vpa.scratch2[ivpa] < -zero - pdf[ivpa,:,end] .= 0.0 - end - end + pdf[1:first_positive_vpa_ind-1, :, end] .= 0.0 end # Special constraint-forcing code that tries to keep the modifications smooth at @@ -478,9 +485,12 @@ function enforce_zero_incoming_bc!(pdf, z::coordinate, vpa::coordinate, density, # Store v_parallel with upar shift removed in vpa.scratch vth = sqrt(2.0*ppar[iz]/density[iz]) @. vpa.scratch = vpa.grid + upar[iz]/vth - # Introduce factor to ensure corrections go smoothly to zero near - # v_parallel=0 - @. vpa.scratch2 = f * abs(vpa.scratch) / (1.0 + abs(vpa.scratch)) + # Introduce factors to ensure corrections go smoothly to zero near + # v_parallel=0, and that there are no large corrections aw large w_parallel as + # those can have a strong effect on the parallel heat flux and make + # timestepping unstable when the cut-off point jumps from one grid point to + # another. + @. vpa.scratch2 = f * abs(vpa.scratch) / (1.0 + abs(vpa.scratch)) / (1.0 + (4.0 * vpa.scratch / vpa.L)^4) J1 = integrate_over_vspace(vpa.scratch2, vpa.grid, vpa.wgts) J2 = integrate_over_vspace(vpa.scratch2, vpa.grid, 2, vpa.wgts) J3 = integrate_over_vspace(vpa.scratch2, vpa.grid, 3, vpa.wgts) @@ -498,9 +508,12 @@ function enforce_zero_incoming_bc!(pdf, z::coordinate, vpa::coordinate, density, # Store v_parallel with upar shift removed in vpa.scratch @. vpa.scratch = vpa.grid + upar[iz] - # Introduce factor to ensure corrections go smoothly to zero near - # v_parallel=0 - @. vpa.scratch2 = f * abs(vpa.scratch) / (1.0 + abs(vpa.scratch)) + # Introduce factors to ensure corrections go smoothly to zero near + # v_parallel=0, and that there are no large corrections aw large w_parallel as + # those can have a strong effect on the parallel heat flux and make + # timestepping unstable when the cut-off point jumps from one grid point to + # another. + @. vpa.scratch2 = f * abs(vpa.scratch) / (1.0 + abs(vpa.scratch)) / (1.0 + (4.0 * vpa.scratch / vpa.L)^4) J1 = integrate_over_vspace(vpa.scratch2, vpa.grid, vpa.wgts) J2 = integrate_over_vspace(vpa.scratch2, vpa.grid, 2, vpa.wgts) @@ -954,6 +967,8 @@ function enforce_v_boundary_condition_local!(f, bc, speed, v_diffusion, v, v_spe elseif bc == "periodic" f[1] = 0.5*(f[1]+f[end]) f[end] = f[1] + elseif bc == "none" + # Do nothing else error("Unsupported boundary condition option '$bc' for $(v.name)") end @@ -972,7 +987,7 @@ function enforce_vperp_boundary_condition!(f::AbstractArray{mk_float,5}, bc, vpe end function enforce_vperp_boundary_condition!(f::AbstractArray{mk_float,4}, bc, vperp, vperp_spectral, vperp_advect, diffusion) - if bc == "zero" + if bc == "zero" || bc == "zero-impose-regularity" nvperp = vperp.n ngrid = vperp.ngrid # set zero boundary condition @@ -982,7 +997,7 @@ function enforce_vperp_boundary_condition!(f::AbstractArray{mk_float,4}, bc, vpe end end # set regularity condition d F / d vperp = 0 at vperp = 0 - if vperp.discretization == "gausslegendre_pseudospectral" || vperp.discretization == "chebyshev_pseudospectral" + if bc == "zero-impose-regularity" && (vperp.discretization == "gausslegendre_pseudospectral" || vperp.discretization == "chebyshev_pseudospectral") D0 = vperp_spectral.radau.D0 buffer = @view vperp.scratch[1:ngrid-1] @loop_r_z_vpa ir iz ivpa begin @@ -992,6 +1007,8 @@ function enforce_vperp_boundary_condition!(f::AbstractArray{mk_float,4}, bc, vpe f[ivpa,1,iz,ir] = -sum(buffer)/D0[1] end end + elseif bc == "zero" + # do nothing else println("vperp.bc=\"$bc\" not supported by discretization " * "$(vperp.discretization)") diff --git a/moment_kinetics/src/calculus.jl b/moment_kinetics/src/calculus.jl index d2c16a478..2b473a232 100644 --- a/moment_kinetics/src/calculus.jl +++ b/moment_kinetics/src/calculus.jl @@ -131,6 +131,70 @@ function second_derivative!(d2f, f, coord, spectral; handle_periodic=true) return nothing end +function laplacian_derivative!(d2f, f, coord, spectral; handle_periodic=true) + # computes (1/coord) d / coord ( coord d f / d(coord)) for vperp coordinate + # For spectral element methods, calculate second derivative by applying first + # derivative twice, with special treatment for element boundaries + + # First derivative + elementwise_derivative!(coord, f, spectral) + derivative_elements_to_full_grid!(coord.scratch3, coord.scratch_2d, coord) + if coord.name == "vperp" + # include the Jacobian + @. coord.scratch3 *= coord.grid + end + # MPI reconcile code here if used with z or r coords + + # Save elementwise first derivative result + coord.scratch2_2d .= coord.scratch_2d + + # Second derivative for element interiors + elementwise_derivative!(coord, coord.scratch3, spectral) + derivative_elements_to_full_grid!(d2f, coord.scratch_2d, coord) + if coord.name == "vperp" + # include the Jacobian + @. d2f /= coord.grid + end + # MPI reconcile code here if used with z or r coords + + # Add contribution to penalise discontinuous first derivatives at element + # boundaries. For smooth functions this would do nothing so should not affect + # convergence of the second derivative. Aims to stabilise numerical instability when + # spike develops at an element boundary. The coefficient is an arbitrary choice, it + # should probably be large enough for stability but as small as possible. + # + # Arbitrary numerical coefficient + C = 1.0 + function penalise_discontinuous_first_derivative!(d2f, imin, imax, df) + # Left element boundary + d2f[imin] += C * df[1] + + # Right element boundary + d2f[imax] -= C * df[end] + + return nothing + end + @views penalise_discontinuous_first_derivative!(d2f, 1, coord.imax[1], + coord.scratch2_2d[:,1]) + for ielement ∈ 2:coord.nelement_local + @views penalise_discontinuous_first_derivative!(d2f, coord.imin[ielement]-1, + coord.imax[ielement], + coord.scratch2_2d[:,ielement]) + end + + if coord.bc ∈ ("zero", "both_zero", "zero-no-regularity") + # For stability don't contribute to evolution at boundaries, in case these + # points are not set by a boundary condition. + # Full grid may be across processes and bc only applied to extreme ends of the + # domain. + if coord.irank == coord.nrank - 1 + d2f[end] = 0.0 + end + else + error("Unsupported bc '$(coord.bc)'") + end + return nothing +end """ mass_matrix_solve!(f, b, spectral::weak_discretization_info) @@ -148,7 +212,7 @@ function second_derivative!(d2f, f, coord, spectral::weak_discretization_info; h # g = d^2 f / d coord^2, which is # M * g = K * f, with M the mass matrix and K an appropriate stiffness matrix # by multiplying by basis functions and integrating by parts - mul!(coord.scratch, spectral.K_matrix, f) + mul!(coord.scratch3, spectral.K_matrix, f) if handle_periodic && coord.bc == "periodic" if coord.nrank > 1 @@ -156,8 +220,8 @@ function second_derivative!(d2f, f, coord, spectral::weak_discretization_info; h * "distributed coordinate") end - coord.scratch[1] = 0.5 * (coord.scratch[1] + coord.scratch[end]) - coord.scratch[end] = coord.scratch[1] + coord.scratch3[1] = 0.5 * (coord.scratch3[1] + coord.scratch3[end]) + coord.scratch3[end] = coord.scratch3[1] end # solve weak form matrix problem M * g = K * f to obtain g = d^2 f / d coord^2 @@ -165,7 +229,7 @@ function second_derivative!(d2f, f, coord, spectral::weak_discretization_info; h error("mass_matrix_solve!() does not support a " * "distributed coordinate") end - mass_matrix_solve!(d2f, coord.scratch, spectral) + mass_matrix_solve!(d2f, coord.scratch3, spectral) end function laplacian_derivative!(d2f, f, coord, spectral::weak_discretization_info) @@ -174,16 +238,18 @@ function laplacian_derivative!(d2f, f, coord, spectral::weak_discretization_info # M * g = K * f, with M the mass matrix, and K an appropriate stiffness matrix, # by multiplying by basis functions and integrating by parts. # for all other coord.name, do exactly the same as second_derivative! above. - mul!(coord.scratch, spectral.L_matrix, f) + mul!(coord.scratch3, spectral.L_matrix, f) - if handle_periodic && coord.bc == "periodic" + if coord.bc == "periodic" && coord.name == "vperp" + error("laplacian_derivative!() cannot handle periodic boundaries for vperp") + elseif coord.bc == "periodic" if coord.nrank > 1 - error("second_derivative!() cannot handle periodic boundaries for a " + error("laplacian_derivative!() cannot handle periodic boundaries for a " * "distributed coordinate") end - coord.scratch[1] = 0.5 * (coord.scratch[1] + coord.scratch[end]) - coord.scratch[end] = coord.scratch[1] + coord.scratch3[1] = 0.5 * (coord.scratch3[1] + coord.scratch3[end]) + coord.scratch3[end] = coord.scratch3[1] end # solve weak form matrix problem M * g = K * f to obtain g = d^2 f / d coord^2 @@ -191,7 +257,7 @@ function laplacian_derivative!(d2f, f, coord, spectral::weak_discretization_info error("mass_matrix_solve!() does not support a " * "distributed coordinate") end - mass_matrix_solve!(d2f, coord.scratch, spectral) + mass_matrix_solve!(d2f, coord.scratch3, spectral) end """ @@ -371,6 +437,9 @@ function assign_endpoint!(df1d::AbstractArray{mk_float,Ndims}, elseif coord.name == "z" && Ndims==3 df1d[j,:,:] .= receive_buffer[:,:] #println("ASSIGNING DATA") + elseif coord.name == "z" && Ndims==4 + df1d[:,:,j,:] .= receive_buffer[:,:,:] + #println("ASSIGNING DATA") elseif coord.name == "z" && Ndims==5 df1d[:,:,j,:,:] .= receive_buffer[:,:,:,:] #println("ASSIGNING DATA") @@ -386,6 +455,9 @@ function assign_endpoint!(df1d::AbstractArray{mk_float,Ndims}, elseif coord.name == "r" && Ndims==3 df1d[:,j,:] .= receive_buffer[:,:] #println("ASSIGNING DATA") + elseif coord.name == "r" && Ndims==4 + df1d[:,:,:,j] .= receive_buffer[:,:,:] + #println("ASSIGNING DATA") elseif coord.name == "r" && Ndims==5 df1d[:,:,:,j,:] .= receive_buffer[:,:,:,:] #println("ASSIGNING DATA") diff --git a/moment_kinetics/src/charge_exchange.jl b/moment_kinetics/src/charge_exchange.jl index e70782c8c..ac460dba0 100644 --- a/moment_kinetics/src/charge_exchange.jl +++ b/moment_kinetics/src/charge_exchange.jl @@ -30,7 +30,7 @@ function ion_charge_exchange_collisions_1V!(f_out, fvec_in, moments, composition fvec_in.density_neutral[:,:,is], fvec_in.upar[:,:,is], fvec_in.uz_neutral[:,:,is], moments.ion.vth[:,:,is], moments.neutral.vth[:,:,is], moments, vpa, vz, charge_exchange_frequency, - vz_spectral, dt) + vz_spectral, dt; neutrals=false) end else begin_s_r_z_region() @@ -38,11 +38,17 @@ function ion_charge_exchange_collisions_1V!(f_out, fvec_in, moments, composition # apply CX collisions to all ion species # for each ion species, obtain affect of charge exchange collisions # with the corresponding neutral species - @loop_r_z_vpa ir iz ivpa begin - f_out[ivpa,1,iz,ir,is] += - dt*charge_exchange_frequency*( - fvec_in.pdf_neutral[ivpa,1,1,iz,ir,is]*fvec_in.density[iz,ir,is] - - fvec_in.pdf[ivpa,1,iz,ir,is]*fvec_in.density_neutral[iz,ir,is]) + isn = is + @loop_r_z ir iz begin + @views interpolate_to_grid_vpa!(vpa.scratch, vpa.grid, + fvec_in.pdf_neutral[:,1,1,iz,ir,isn], vz, + vz_spectral) + @loop_vpa ivpa begin + f_out[ivpa,1,iz,ir,is] += + dt*charge_exchange_frequency*( + vpa.scratch[ivpa]*fvec_in.density[iz,ir,is] + - fvec_in.pdf[ivpa,1,iz,ir,is]*fvec_in.density_neutral[iz,ir,is]) + end end end end @@ -70,7 +76,7 @@ function neutral_charge_exchange_collisions_1V!(f_neutral_out, fvec_in, moments, fvec_in.pdf[:,1,:,:,isn], fvec_in.density[:,:,isn], fvec_in.uz_neutral[:,:,isn], fvec_in.upar[:,:,isn], moments.neutral.vth[:,:,isn], moments.ion.vth[:,:,isn], moments, - vz, vpa, charge_exchange_frequency, vpa_spectral, dt) + vz, vpa, charge_exchange_frequency, vpa_spectral, dt; neutrals=true) end else begin_sn_r_z_region() @@ -78,11 +84,16 @@ function neutral_charge_exchange_collisions_1V!(f_neutral_out, fvec_in, moments, # apply CX collisions to all neutral species # for each neutral species, obtain affect of charge exchange collisions # with the corresponding ion species - @loop_r_z_vz ir iz ivz begin - f_neutral_out[ivz,1,1,iz,ir,isn] += - dt*charge_exchange_frequency*( - fvec_in.pdf[ivz,1,iz,ir,isn]*fvec_in.density_neutral[iz,ir,isn] - - fvec_in.pdf_neutral[ivz,1,1,iz,ir,isn]*fvec_in.density[iz,ir,isn]) + @loop_r_z ir iz begin + @views interpolate_to_grid_vpa!(vz.scratch, vz.grid, + fvec_in.pdf[:,1,iz,ir,isn], vpa, + vpa_spectral) + @loop_vz ivz begin + f_neutral_out[ivz,1,1,iz,ir,isn] += + dt*charge_exchange_frequency*( + vz.scratch[ivz]*fvec_in.density_neutral[iz,ir,isn] + - fvec_in.pdf_neutral[ivz,1,1,iz,ir,isn]*fvec_in.density[iz,ir,isn]) + end end end end @@ -94,7 +105,7 @@ with a single species of the opposite type; e.g., ions with neutrals or neutrals """ function charge_exchange_collisions_single_species!(f_out, pdf_in, pdf_other, density_other, upar, upar_other, vth, vth_other, moments, vpa, vpa_other, - charge_exchange_frequency, spectral_other, dt) + charge_exchange_frequency, spectral_other, dt; neutrals) @loop_r_z ir iz begin if moments.evolve_ppar # will need the ratio of thermal speeds both to interpolate between vpa grids @@ -142,9 +153,16 @@ function charge_exchange_collisions_single_species!(f_out, pdf_in, pdf_other, # no need to interpolate if neither upar or ppar evolved separately from pdf vpa.scratch2 .= pdf_other[:,iz,ir] end - @loop_vpa ivpa begin - f_out[ivpa,iz,ir] += dt * charge_exchange_frequency * density_other[iz,ir] * + if neutrals + @loop_vz ivz begin + f_out[ivz,iz,ir] += dt * charge_exchange_frequency * density_other[iz,ir] * + (vpa.scratch2[ivz] * vth_ratio - pdf_in[ivz,iz,ir]) + end + else + @loop_vpa ivpa begin + f_out[ivpa,iz,ir] += dt * charge_exchange_frequency * density_other[iz,ir] * (vpa.scratch2[ivpa] * vth_ratio - pdf_in[ivpa,iz,ir]) + end end end end diff --git a/moment_kinetics/src/communication.jl b/moment_kinetics/src/communication.jl index 2f88c8cd9..be3ed60a3 100644 --- a/moment_kinetics/src/communication.jl +++ b/moment_kinetics/src/communication.jl @@ -20,6 +20,7 @@ export setup_distributed_memory_MPI export setup_distributed_memory_MPI_for_weights_precomputation export _block_synchronize, _anyv_subblock_synchronize +using LinearAlgebra using MPI using SHA @@ -127,6 +128,10 @@ function __init__() global_size[] = MPI.Comm_size(comm_world) #block_rank[] = MPI.Comm_rank(comm_block) #block_size[] = MPI.Comm_size(comm_block) + + # Ensure BLAS only uses 1 thread, to avoid oversubscribing processes as we are + # probably already fully parallelised. + BLAS.set_num_threads(1) end """ diff --git a/moment_kinetics/src/coordinates.jl b/moment_kinetics/src/coordinates.jl index d034a2dd0..a18c2a75e 100644 --- a/moment_kinetics/src/coordinates.jl +++ b/moment_kinetics/src/coordinates.jl @@ -6,6 +6,7 @@ export define_coordinate, write_coordinate export equally_spaced_grid export set_element_boundaries +using LinearAlgebra using ..type_definitions: mk_float, mk_int using ..array_allocation: allocate_float, allocate_shared_float, allocate_int using ..calculus: derivative! @@ -85,6 +86,10 @@ struct coordinate{T <: AbstractVector{mk_float}} scratch6::Array{mk_float,1} # scratch7 is an array used for intermediate calculations requiring n entries scratch7::Array{mk_float,1} + # scratch8 is an array used for intermediate calculations requiring n entries + scratch8::Array{mk_float,1} + # scratch9 is an array used for intermediate calculations requiring n entries + scratch9::Array{mk_float,1} # scratch_shared is a shared-memory array used for intermediate calculations requiring # n entries scratch_shared::T @@ -231,10 +236,11 @@ function define_coordinate(input, parallel_io::Bool=false; run_directory=nothing cell_width, igrid, ielement, imin, imax, igrid_full, input.discretization, input.fd_option, input.cheb_option, input.bc, wgts, uniform_grid, duniform_dgrid, scratch, copy(scratch), copy(scratch), copy(scratch), copy(scratch), copy(scratch), copy(scratch), - scratch_shared, scratch_shared2, scratch_2d, copy(scratch_2d), advection, - send_buffer, receive_buffer, input.comm, local_io_range, global_io_range, - element_scale, element_shift, input.element_spacing_option, element_boundaries, - radau_first_element, other_nodes, one_over_denominator) + copy(scratch), copy(scratch), scratch_shared, scratch_shared2, scratch_2d, + copy(scratch_2d), advection, send_buffer, receive_buffer, input.comm, + local_io_range, global_io_range, element_scale, element_shift, + input.element_spacing_option, element_boundaries, radau_first_element, + other_nodes, one_over_denominator) if coord.n == 1 && occursin("v", coord.name) spectral = null_velocity_dimension_info() @@ -252,8 +258,7 @@ function define_coordinate(input, parallel_io::Bool=false; run_directory=nothing elseif input.discretization == "gausslegendre_pseudospectral" # create arrays needed for explicit GaussLegendre pseudospectral treatment in this # coordinate and create the matrices for differentiation - spectral = setup_gausslegendre_pseudospectral(coord, collision_operator_dim=collision_operator_dim, - dirichlet_bc=occursin("zero", coord.bc)) + spectral = setup_gausslegendre_pseudospectral(coord, collision_operator_dim=collision_operator_dim) # obtain the local derivatives of the uniform grid with respect to the used grid derivative!(coord.duniform_dgrid, coord.uniform_grid, coord, spectral) else diff --git a/moment_kinetics/src/derivatives.jl b/moment_kinetics/src/derivatives.jl index c3e2c0523..71bd31427 100644 --- a/moment_kinetics/src/derivatives.jl +++ b/moment_kinetics/src/derivatives.jl @@ -267,6 +267,32 @@ function derivative_z!(dfdz::AbstractArray{mk_float,5}, f::AbstractArray{mk_floa end +#4D version for f[vpa,vperp,z,r] -> dfn electron particles +function derivative_z!(dfdz::AbstractArray{mk_float,4}, f::AbstractArray{mk_float,4}, + dfdz_lower_endpoints::AbstractArray{mk_float,3}, + dfdz_upper_endpoints::AbstractArray{mk_float,3}, + z_send_buffer::AbstractArray{mk_float,3}, + z_receive_buffer::AbstractArray{mk_float,3}, z_spectral, z) + + begin_r_vperp_vpa_region() + + # differentiate f w.r.t z + @loop_r_vperp_vpa ir ivperp ivpa begin + @views derivative!(dfdz[ivpa,ivperp,:,ir], f[ivpa,ivperp,:,ir], z, z_spectral) + # get external endpoints to reconcile via MPI + dfdz_lower_endpoints[ivpa,ivperp,ir] = z.scratch_2d[1,1] + dfdz_upper_endpoints[ivpa,ivperp,ir] = z.scratch_2d[end,end] + end + # now reconcile element boundaries across + # processes with large message involving all y + if z.nelement_local < z.nelement_global + reconcile_element_boundaries_MPI!(dfdz, + dfdz_lower_endpoints, dfdz_upper_endpoints, + z_send_buffer, z_receive_buffer, z) + end + +end + #6D version for f[vz,vr,vzeta,z,r] -> dfn neutral particles function derivative_z!(dfdz::AbstractArray{mk_float,6}, f::AbstractArray{mk_float,6}, dfdz_lower_endpoints::AbstractArray{mk_float,5}, @@ -795,6 +821,37 @@ function derivative_z!(dfdz::AbstractArray{mk_float,5}, f::AbstractArray{mk_floa end +#4D version for f[vpa,vperp,z,r] -> dfn electron particles +function derivative_z!(dfdz::AbstractArray{mk_float,4}, f::AbstractArray{mk_float,4}, + advect, adv_fac_lower_buffer::AbstractArray{mk_float,3}, + adv_fac_upper_buffer::AbstractArray{mk_float,3}, + dfdz_lower_endpoints::AbstractArray{mk_float,3}, + dfdz_upper_endpoints::AbstractArray{mk_float,3}, + z_send_buffer::AbstractArray{mk_float,3}, + z_receive_buffer::AbstractArray{mk_float,3}, z_spectral, z) + + begin_r_vperp_vpa_region() + + # differentiate the pdf f w.r.t z + @loop_r_vperp_vpa ir ivperp ivpa begin + @views derivative!(dfdz[ivpa,ivperp,:,ir], f[ivpa,ivperp,:,ir], z, advect[1].adv_fac[:,ivpa,ivperp,ir], z_spectral) + # get external endpoints to reconcile via MPI + dfdz_lower_endpoints[ivpa,ivperp,ir] = z.scratch_2d[1,1] + dfdz_upper_endpoints[ivpa,ivperp,ir] = z.scratch_2d[end,end] + adv_fac_lower_buffer[ivpa,ivperp,ir] = advect[1].adv_fac[1,ivpa,ivperp,ir] + adv_fac_upper_buffer[ivpa,ivperp,ir] = advect[1].adv_fac[end,ivpa,ivperp,ir] + end + # now reconcile element boundaries across + # processes with large message + if z.nelement_local < z.nelement_global + reconcile_element_boundaries_MPI!(dfdz, + adv_fac_lower_buffer, adv_fac_upper_buffer, + dfdz_lower_endpoints,dfdz_upper_endpoints, + z_send_buffer, z_receive_buffer, z) + end + +end + #6D version for f[vz,vr,vzeta,z,r,sn] -> dfn neutral particles function derivative_z!(dfdz::AbstractArray{mk_float,6}, f::AbstractArray{mk_float,6}, advect, adv_fac_lower_buffer::AbstractArray{mk_float,5}, diff --git a/moment_kinetics/src/electron_fluid_equations.jl b/moment_kinetics/src/electron_fluid_equations.jl new file mode 100644 index 000000000..395a94f4c --- /dev/null +++ b/moment_kinetics/src/electron_fluid_equations.jl @@ -0,0 +1,712 @@ +module electron_fluid_equations + +export calculate_electron_density! +export calculate_electron_upar_from_charge_conservation! +export calculate_electron_moments! +export electron_energy_equation! +export calculate_electron_qpar! +export calculate_electron_parallel_friction_force! +export calculate_electron_qpar_from_pdf! +export update_electron_vth_temperature! + +using ..communication +using ..derivatives: derivative_z! +using ..looping +using ..input_structs +using ..moment_kinetics_structs: electron_pdf_substruct, moments_electron_substruct +using ..nonlinear_solvers +using ..type_definitions: mk_float +using ..velocity_moments: integrate_over_vspace + +using MPI + +""" +use quasineutrality to obtain the electron density from the initial +densities of the various ion species: + sum_i dens_i = dens_e +inputs: + dens_e = electron density at previous time level + updated = flag indicating if the electron density is updated + dens_i = updated ion density +output: + dens_e = updated electron density + updated = flag indicating that the electron density has been updated +""" +function calculate_electron_density!(dens_e, updated, dens_i) + # only update the electron density if it has not already been updated + if !updated[] + begin_r_z_region() + # enforce quasineutrality + @loop_r_z ir iz begin + dens_e[iz,ir] = 0.0 + @loop_s is begin + dens_e[iz,ir] += dens_i[iz,ir,is] + end + end + end + # set flag indicating that the electron density has been updated + updated[] = true + return nothing +end + +""" +use charge conservation equation to solve for the electron parallel flow density: + d/dz(sum_i n_i upar_i - n_e upar_e) = 0 + ==> [sum_i n_i upar_i](z) - [sum_i n_i upar_i](zbound) = [n_e upar_e](z) - [n_e upar_e](zbound) +inputs: + upar_e = should contain updated electron parallel flow at boundaries in zed + updated = flag indicating whether the electron parallel flow is already updated + dens_e = electron particle density + upar_i = ion parallel flow density + dens_i = ion particle density +output: + upar_e = contains the updated electron parallel flow +""" +function calculate_electron_upar_from_charge_conservation!(upar_e, updated, dens_e, upar_i, dens_i, electron_model, r, z) + # only calculate the electron parallel flow if it is not already updated + if !updated[] + begin_r_region() + # get the number of zed grid points, nz + nz = size(upar_e,1) + # initialise the electron parallel flow density to zero + @loop_r_z ir iz begin + upar_e[iz,ir] = 0.0 + end + # if using a simple logical sheath model, then the electron parallel current at the boundaries in zed + # is equal and opposite to the ion parallel current + if electron_model ∈ (boltzmann_electron_response_with_simple_sheath, + braginskii_fluid, kinetic_electrons, + kinetic_electrons_with_temperature_equation) + boundary_flux = r.scratch_shared + boundary_ion_flux = r.scratch_shared2 + if z.irank == 0 + @loop_r ir begin + boundary_flux[ir] = 0.0 + boundary_ion_flux[ir] = 0.0 + @loop_s is begin + boundary_flux[ir] += dens_i[1,ir,is] * upar_i[1,ir,is] + boundary_ion_flux[ir] += dens_i[1,ir,is] * upar_i[1,ir,is] + end + end + end + begin_serial_region() + @serial_region begin + MPI.Bcast!(boundary_flux, 0, z.comm) + MPI.Bcast!(boundary_ion_flux, 0, z.comm) + end + # loop over ion species, adding each species contribution to the + # ion parallel particle flux at the boundaries in zed + begin_r_z_region() + @loop_r_z ir iz begin + # initialise the electron particle flux to its value at the boundary in + # zed and subtract the ion boundary flux - we want to calculate upar_e = + # boundary_flux + (ion_flux - boundary_ion_flux) as at this intermediate + # point upar is actually the electron particle flux + upar_e[iz,ir] = boundary_flux[ir] - boundary_ion_flux[ir] + # add the contributions to the electron particle flux from the various ion species + # particle fluxes + @loop_s is begin + upar_e[iz,ir] += dens_i[iz,ir,is] * upar_i[iz,ir,is] + end + # convert from parallel particle flux to parallel particle density + upar_e[iz,ir] /= dens_e[iz,ir] + end + end + updated[] = true + end + return nothing +end + +function calculate_electron_moments!(scratch, pdf, moments, composition, collisions, r, z, + vpa) + calculate_electron_density!(scratch.electron_density, moments.electron.dens_updated, + scratch.density) + calculate_electron_upar_from_charge_conservation!( + scratch.electron_upar, moments.electron.upar_updated, scratch.electron_density, + scratch.upar, scratch.density, composition.electron_physics, r, z) + if composition.electron_physics ∉ (braginskii_fluid, kinetic_electrons, + kinetic_electrons_with_temperature_equation) + begin_r_z_region() + @loop_r_z ir iz begin + scratch.electron_ppar[iz,ir] = 0.5 * composition.me_over_mi * + scratch.electron_density[iz,ir] * + moments.electron.vth[iz,ir]^2 + end + moments.electron.ppar_updated[] = true + end + update_electron_vth_temperature!(moments, scratch.electron_ppar, + scratch.electron_density, composition) + calculate_electron_qpar!(moments.electron, pdf.electron, scratch.electron_ppar, + scratch.electron_upar, scratch.upar, collisions.nu_ei, + composition.me_over_mi, composition.electron_physics, vpa) + if composition.electron_physics == braginskii_fluid + electron_fluid_qpar_boundary_condition!(scratch.electron_ppar, + scratch.electron_upar, + scratch.electron_density, + moments.electron, z) + end + return nothing +end + +""" +use the electron energy or temperature equation to evolve the electron temperature via an +explicit time advance. +NB: so far, this is only set up for 1D problem, where we can assume +an isotropic distribution in f_e so that p_e = n_e T_e = ppar_e +""" +function electron_energy_equation!(ppar_out, ppar_in, electron_density, electron_upar, + ion_density, ion_upar, ion_ppar, density_neutral, + uz_neutral, pz_neutral, moments, collisions, dt, + composition, electron_source_settings, num_diss_params, + z; conduction=true) + if composition.electron_physics == kinetic_electrons_with_temperature_equation + # Hacky way to implement temperature equation: + # - convert ppar to T by dividing by density + # - update T with a forward-Euler step using the temperature equation + # - multiply by density to get back to ppar (should this be new density rather than + # old density? For initial testing, only looking at the electron initialisation + # where density is not updated, this does not matter). + + begin_r_z_region() + # define some abbreviated variables for convenient use in rest of function + me_over_mi = composition.me_over_mi + nu_ei = collisions.nu_ei + T_in = moments.temp + # calculate contribution to rhs of energy equation (formulated in terms of pressure) + # arising from derivatives of ppar, qpar and upar + @loop_r_z ir iz begin + # Convert ppar_out to temperature for most of this function + ppar_out[iz,ir] *= 2.0 / electron_density[iz,ir] + ppar_out[iz,ir] -= dt*(electron_upar[iz,ir]*moments.dT_dz[iz,ir] + + 2.0*T_in[iz,ir]*moments.dupar_dz[iz,ir]) + end + if conduction + @loop_r_z ir iz begin + ppar_out[iz,ir] -= 2.0 * dt*moments.dqpar_dz[iz,ir] / electron_density[iz,ir] + end + end + # compute the contribution to the rhs of the energy equation + # arising from artificial diffusion + diffusion_coefficient = num_diss_params.electron.moment_dissipation_coefficient + if diffusion_coefficient > 0.0 + error("diffusion not implemented for electron temperature equation yet") + @loop_r_z ir iz begin + ppar_out[iz,ir] += dt*diffusion_coefficient*moments.d2T_dz2[iz,ir] + end + end + # compute the contribution to the rhs of the energy equation + # arising from electron-ion collisions + if nu_ei > 0.0 + @loop_s_r_z is ir iz begin + ppar_out[iz,ir] += dt * 2.0 * (2 * me_over_mi * nu_ei * (2.0*ion_ppar[iz,ir,is]/ion_density[iz,ir,is] - T_in[iz,ir])) + ppar_out[iz,ir] += dt * 2.0 * ((2/3) * moments.parallel_friction[iz,ir] + * (ion_upar[iz,ir,is]-electron_upar[iz,ir])) / electron_density[iz,ir] + end + end + # add in contributions due to charge exchange/ionization collisions + if composition.n_neutral_species > 0 + if abs(collisions.charge_exchange_electron) > 0.0 + @loop_sn_r_z isn ir iz begin + ppar_out[iz,ir] += + dt * 2.0 * me_over_mi * collisions.charge_exchange_electron * ( + 2*(pz_neutral[iz,ir,isn] - + density_neutral[iz,ir,isn]*ppar_in[iz,ir]/electron_density[iz,ir]) + + (2/3)*density_neutral[iz,ir,isn] * + (uz_neutral[iz,ir,isn] - electron_upar[iz,ir])^2) + end + end + if abs(collisions.ionization_electron) > 0.0 + @loop_sn_r_z isn ir iz begin + ppar_out[iz,ir] += + dt * 2.0 * collisions.ionization_electron * density_neutral[iz,ir,isn] * ( + ppar_in[iz,ir] / electron_density[iz,ir] - + collisions.ionization_energy) + end + end + end + + if electron_source_settings.active + pressure_source_amplitude = moments.external_source_pressure_amplitude + density_source_amplitude = moments.external_source_density_amplitude + @loop_r_z ir iz begin + ppar_out[iz,ir] += dt * (2.0 * pressure_source_amplitude[iz,ir] + - T_in[iz,ir] * density_source_amplitude[iz,ir]) / + electron_density[iz,ir] + end + end + + # Now that forward-Euler step for temperature is finished, convert ppar_out back to + # pressure. + @loop_r_z ir iz begin + ppar_out[iz,ir] *= 0.5 * electron_density[iz,ir] + end + else + begin_r_z_region() + # define some abbreviated variables for convenient use in rest of function + me_over_mi = composition.me_over_mi + nu_ei = collisions.nu_ei + # calculate contribution to rhs of energy equation (formulated in terms of pressure) + # arising from derivatives of ppar, qpar and upar + @loop_r_z ir iz begin + ppar_out[iz,ir] -= dt*(electron_upar[iz,ir]*moments.dppar_dz[iz,ir] + + 3*ppar_in[iz,ir]*moments.dupar_dz[iz,ir]) + end + if conduction + @loop_r_z ir iz begin + ppar_out[iz,ir] -= dt*moments.dqpar_dz[iz,ir] + end + end + # @loop_r_z ir iz begin + # ppar_out[iz,ir] -= dt*(electron_upar[iz,ir]*moments.dppar_dz[iz,ir] + # + (2/3)*moments.dqpar_dz[iz,ir] + # + (5/3)*ppar_in[iz,ir]*moments.dupar_dz[iz,ir]) + # end + # compute the contribution to the rhs of the energy equation + # arising from artificial diffusion + diffusion_coefficient = num_diss_params.electron.moment_dissipation_coefficient + if diffusion_coefficient > 0.0 + @loop_r_z ir iz begin + ppar_out[iz,ir] += dt*diffusion_coefficient*moments.d2ppar_dz2[iz,ir] + end + end + # compute the contribution to the rhs of the energy equation + # arising from electron-ion collisions + if nu_ei > 0.0 + @loop_s_r_z is ir iz begin + ppar_out[iz,ir] += dt * (2 * me_over_mi * nu_ei * (ion_ppar[iz,ir,is] - ppar_in[iz,ir])) + ppar_out[iz,ir] += dt * ((2/3) * moments.parallel_friction[iz,ir] + * (ion_upar[iz,ir,is]-electron_upar[iz,ir])) + end + end + # add in contributions due to charge exchange/ionization collisions + if composition.n_neutral_species > 0 + if abs(collisions.charge_exchange_electron) > 0.0 + @loop_sn_r_z isn ir iz begin + ppar_out[iz,ir] += + dt * me_over_mi * collisions.charge_exchange_electron * ( + 2*(electron_density[iz,ir]*pz_neutral[iz,ir,isn] - + density_neutral[iz,ir,isn]*ppar_in[iz,ir]) + + (2/3)*electron_density[iz,ir]*density_neutral[iz,ir,isn] * + (uz_neutral[iz,ir,isn] - electron_upar[iz,ir])^2) + end + end + if abs(collisions.ionization_electron) > 0.0 + # @loop_s_r_z is ir iz begin + # ppar_out[iz,ir] += + # dt * collisions.ionization_electron * density_neutral[iz,ir,is] * ( + # ppar_in[iz,ir] - + # (2/3)*electron_density[iz,ir] * collisions.ionization_energy) + # end + @loop_sn_r_z isn ir iz begin + ppar_out[iz,ir] += + dt * collisions.ionization_electron * density_neutral[iz,ir,isn] * ( + ppar_in[iz,ir] - + electron_density[iz,ir] * collisions.ionization_energy) + end + end + end + + if electron_source_settings.active + source_amplitude = moments.external_source_pressure_amplitude + @loop_r_z ir iz begin + ppar_out[iz,ir] += dt * source_amplitude[iz,ir] + end + end + end + + return nothing +end + +""" + electron_energy_residual!(residual, electron_ppar_out, fvec_in, moments, + collisions, composition, external_source_settings, + num_diss_params, z, dt) + +The residual is a function whose input is `electron_ppar`, so that when it's output +`residual` is zero, electron_ppar is the result of a backward-Euler timestep: + (f_out - f_in) / dt = RHS(f_out) +⇒ (f_out - f_in) - dt*RHS(f_out) = 0 + +This function assumes any needed moment derivatives are already calculated using +`electron_ppar_out` and stored in `moments.electron`. +""" +function electron_energy_residual!(residual, electron_ppar_out, fvec_in, moments, + collisions, composition, external_source_settings, + num_diss_params, z, dt) + begin_r_z_region() + electron_ppar_in = fvec_in.electron_ppar + @loop_r_z ir iz begin + residual[iz,ir] = electron_ppar_in[iz,ir] + end + electron_energy_equation!(residual, electron_ppar_out, + fvec_in.density, fvec_in.electron_upar, fvec_in.density, + fvec_in.upar, fvec_in.ppar, fvec_in.density_neutral, + fvec_in.uz_neutral, fvec_in.pz_neutral, moments.electron, + collisions, dt, composition, + external_source_settings.electron, num_diss_params, z) + # Now + # residual = f_in + dt*RHS(f_out) + # so update to desired residual + begin_r_z_region() + @loop_r_z ir iz begin + residual[iz,ir] = (electron_ppar_out[iz,ir] - residual[iz,ir]) + end +end + +""" +Add just the braginskii conduction contribution to the electron pressure, and assume that +we have to calculate qpar and dqpar_dz from ppar within this function (they are not +pre-calculated). +""" +function electron_braginskii_conduction!(ppar_out::AbstractVector{mk_float}, + ppar_in::AbstractVector{mk_float}, + dens::AbstractVector{mk_float}, + upar_e::AbstractVector{mk_float}, + upar_i::AbstractVector{mk_float}, + electron_moments, collisions, composition, z, + z_spectral, scratch_dummy, dt, ir) + + buffer_r_1 = @view scratch_dummy.buffer_rs_1[ir,1] + buffer_r_2 = @view scratch_dummy.buffer_rs_2[ir,1] + buffer_r_3 = @view scratch_dummy.buffer_rs_3[ir,1] + buffer_r_4 = @view scratch_dummy.buffer_rs_4[ir,1] + + temp = @view electron_moments.temp[:,ir] + dT_dz = @view electron_moments.dT_dz[:,ir] + qpar = @view electron_moments.qpar[:,ir] + dqpar_dz = @view electron_moments.dqpar_dz[:,ir] + + update_electron_temperature!(temp, ppar_in, dens, composition) + derivative_z!(dT_dz, temp, buffer_r_1, buffer_r_2, buffer_r_3, buffer_r_4, z_spectral, + z) + electron_moments.qpar_updated[] = false + calculate_electron_qpar!(electron_moments, nothing, ppar_in, upar_e, upar_i, + collisions.nu_ei, composition.me_over_mi, + composition.electron_physics, nothing) + electron_fluid_qpar_boundary_condition!(ppar_in, upar_e, dens, electron_moments, z) + derivative_z!(dqpar_dz, qpar, buffer_r_1, buffer_r_2, buffer_r_3, buffer_r_4, + z_spectral, z) + + @loop_r_z ir iz begin + ppar_out[iz,ir] -= dt*electron_moments.dqpar_dz[iz,ir] + end + + return nothing +end + +function implicit_braginskii_conduction!(fvec_out, fvec_in, moments, z, r, dt, z_spectral, + composition, collisions, scratch_dummy, + nl_solver_params) + begin_z_region() + + for ir ∈ 1:r.n + ppar_out = @view fvec_out.electron_ppar[:,ir] + ppar_in = @view fvec_in.electron_ppar[:,ir] + dens = @view fvec_in.electron_density[:,ir] + upar_e = @view fvec_in.electron_upar[:,ir] + upar_i = @view fvec_in.upar[:,ir] + + # Explicit timestep to give initial guess for implicit solve + electron_braginskii_conduction!(ppar_out, ppar_in, dens, upar_e, upar_i, + moments.electron, collisions, composition, z, + z_spectral, scratch_dummy, dt, ir) + + # Define a function whose input is `electron_ppar`, so that when it's output + # `residual` is zero, electron_ppar is the result of a backward-Euler timestep: + # (f_new - f_old) / dt = RHS(f_new) + # ⇒ (f_new - f_old)/dt - RHS(f_new) = 0 + function residual_func!(residual, electron_ppar) + begin_z_region() + @loop_z iz begin + residual[iz] = ppar_in[iz] + end + electron_braginskii_conduction!(residual, electron_ppar, dens, upar_e, + upar_i, moments.electron, collisions, + composition, z, z_spectral, scratch_dummy, + dt, ir) + # Now + # residual = f_old + dt*RHS(f_new) + # so update to desired residual + begin_z_region() + @loop_z iz begin + residual[iz] = (electron_ppar[iz] - residual[iz]) + end + end + + # Shared-memory buffers + residual = @view scratch_dummy.buffer_zs_1[:,1] + delta_x = @view scratch_dummy.buffer_zs_2[:,1] + rhs_delta = @view scratch_dummy.buffer_zs_3[:,1] + v = @view scratch_dummy.buffer_zs_4[:,1] + w = @view scratch_dummy.buffer_zrs_1[:,1,1] + + success = newton_solve!(ppar_out, residual_func!, residual, delta_x, rhs_delta, v, + w, nl_solver_params; left_preconditioner=nothing, + right_preconditioner=nothing, coords=(z=z,)) + if !success + return success + end + end + + nl_solver_params.stage_counter[] += 1 + + return true +end + +""" +solve the electron force balance (parallel momentum) equation for the +parallel electric field, Epar: + Epar = -dphi/dz = (2/n_e) * (-dppar_e/dz + friction_force + n_e * m_e * n_n * R_en * (u_n - u_e)) + NB: in 1D only Epar is needed for update of ion pdf, so boundary phi is irrelevant +inputs: + dens_e = electron density + dppar_dz = zed derivative of electron parallel pressure + nu_ei = electron-ion collision frequency + friction = electron-ion parallel friction force + n_neutral_species = number of evolved neutral species + charge_exchange = electron-neutral charge exchange frequency + me_over_mi = electron-ion mass ratio + dens_n = neutral density + upar_n = neutral parallel flow + upar_e = electron parallel flow +output: + Epar = parallel electric field +""" +function calculate_Epar_from_electron_force_balance!(Epar, dens_e, dppar_dz, nu_ei, friction, n_neutral_species, + charge_exchange, me_over_mi, dens_n, upar_n, upar_e) + begin_r_z_region() + # get the contribution to Epar from the parallel pressure + @loop_r_z ir iz begin + Epar[iz,ir] = -(2/dens_e[iz,ir]) * dppar_dz[iz,ir] + end + # if electron-ion collisions are taken into account, include electron-ion parallel friction + if nu_ei > 0 + @loop_r_z ir iz begin + Epar[iz,ir] += (2/dens_e[iz,ir]) * friction[iz,ir] + end + end + # if there are neutral species evolved and accounting for charge exchange collisions with neutrals + if n_neutral_species > 0 && charge_exchange > 0 + @loop_sn_r_z isn ir iz begin + Epar[iz,ir] += 2 * me_over_mi * dens_n[iz,ir] * charge_exchange * (upar_n[iz,ir,isn] - upar_e[iz,ir]) + end + end + return nothing +end + +""" +""" +function calculate_electron_parallel_friction_force!(friction, dens_e, upar_e, upar_i, dTe_dz, + me_over_mi, nu_ei, electron_model) + begin_r_z_region() + if electron_model == braginskii_fluid + @loop_r_z ir iz begin + friction[iz,ir] = -(1/2) * 0.71 * dens_e[iz,ir] * dTe_dz[iz,ir] + end + @loop_s_r_z is ir iz begin + friction[iz,ir] += 0.51 * dens_e[iz,ir] * me_over_mi * nu_ei * (upar_i[iz,ir,is] - upar_e[iz,ir]) + end + else + @loop_r_z ir iz begin + friction[iz,ir] = 0.0 + end + end + return nothing +end + +""" +calculate the parallel component of the electron heat flux. +there are currently two supported options for the parallel heat flux: + Braginskii collisional closure - qpar_e = -3.16*ppar_e/(m_e*nu_ei)*dT/dz - 0.71*ppar_e*(upar_i-upar_e) + collisionless closure - d(qpar_e)/dz = 0 ==> qpar_e = constant +inputs: + qpar_e = parallel electron heat flux at the previous time level + qpar_updated = flag indicating whether qpar is updated already + pdf = electron pdf + ppar_e = electron parallel pressure + upar_e = electron parallel flow + vth_e = electron thermal speed + dTe_dz = zed derivative of electron temperature + upar_i = ion parallel flow + nu_ei = electron-ion collision frequency + me_over_mi = electron-to-ion mass ratio + electron_model = choice of model for electron physics + vpa = struct containing information about the vpa coordinate +output: + qpar_e = updated parallel electron heat flux + qpar_updated = flag indicating that the parallel electron heat flux is updated +""" +function calculate_electron_qpar!(electron_moments, pdf, ppar_e, upar_e, upar_i, nu_ei, + me_over_mi, electron_model, vpa) + # only calculate qpar_e if needs updating + qpar_updated = electron_moments.qpar_updated + #if !qpar_updated[] + qpar_e = electron_moments.qpar + vth_e = electron_moments.vth + dTe_dz = electron_moments.dT_dz + if electron_model == braginskii_fluid + begin_r_z_region() + # use the classical Braginskii expression for the electron heat flux + @loop_r_z ir iz begin + qpar_e[iz,ir] = 0.0 + @loop_s is begin + qpar_e[iz,ir] -= 0.71 * ppar_e[iz,ir] * (upar_i[iz,ir,is] - upar_e[iz,ir]) + end + end + if nu_ei > 0.0 + @loop_r_z ir iz begin + qpar_e[iz,ir] -= (1/2) * 3.16 * ppar_e[iz,ir] / (me_over_mi * nu_ei) * dTe_dz[iz,ir] + end + end + elseif electron_model ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + # use the modified electron pdf to calculate the electron heat flux + if isa(pdf, electron_pdf_substruct) + electron_pdf = pdf.norm + else + electron_pdf = pdf + end + calculate_electron_qpar_from_pdf!(qpar_e, ppar_e, vth_e, electron_pdf, vpa) + else + begin_r_z_region() + # qpar_e is not used. Initialize to 0.0 to avoid failure of + # @debug_track_initialized check + @loop_r_z ir iz begin + qpar_e[iz,ir] = 0.0 + end + end + #end + # qpar has been updated + qpar_updated[] = true + return nothing +end + +""" +calculate the parallel component of the electron heat flux, +defined as qpar = 2 * ppar * vth * int dwpa (pdf * wpa^3) +""" +function calculate_electron_qpar_from_pdf!(qpar, ppar, vth, pdf, vpa) + # specialise to 1D for now + begin_r_z_region() + ivperp = 1 + @loop_r_z ir iz begin + @views qpar[iz, ir] = 2*ppar[iz,ir]*vth[iz,ir]*integrate_over_vspace(pdf[:, ivperp, iz, ir], vpa.grid.^3, vpa.wgts) + end +end + +function calculate_electron_heat_source!(heat_source, ppar_e, dupar_dz, dens_n, ionization, ionization_energy, + dens_e, ppar_i, nu_ei, me_over_mi, T_wall, z) + begin_r_z_region() + # heat_source currently only used for testing + # @loop_r_z ir iz begin + # heat_source[iz,ir] = (2/3) * ppar_e[iz,ir] * dupar_dz[iz,ir] + # end + # if nu_ei > 0.0 + # @loop_s_r_z is ir iz begin + # heat_source[iz,ir] -= 2 * me_over_mi * nu_ei * (ppar_i[iz,ir,is] - ppar_e[iz,ir]) + # end + # end + # n_neutral_species = size(dens_n, 3) + # if n_neutral_species > 0 && ionization > 0.0 + # @loop_s_r_z is ir iz begin + # heat_source[iz,ir] += (2/3) * dens_n[iz,ir,is] * dens_e[iz,ir] * ionization * ionization_energy + # end + # end + # @loop_r ir begin + # heat_source[1,ir] += 20 * 0.5 * (T_wall * dens_e[1,ir] - ppar_e[1,ir]) + # heat_source[end,ir] += 20 * (0.5 * T_wall * dens_e[end,ir] - ppar_e[end,ir]) + # end + # Gaussian heat deposition profile, with a decay of 5 e-foldings at the ends of the domain in z + @loop_r_z ir iz begin + heat_source[iz,ir] = 50*exp(-5*(2.0*z.grid[iz]/z.L)^2) + end + return nothing +end + +#function enforce_parallel_BC_on_electron_pressure!(ppar, dens, T_wall, ppar_i) +# # assume T_e = T_i at boundaries in z +# @loop_r ir begin +# #ppar[1,ir] = 0.5 * dens[1,ir] * T_wall +# #ppar[end,ir] = 0.5 * dens[end,ir] * T_wall +# ppar[1,ir] = ppar_i[1,ir,1] +# ppar[end,ir] = ppar_i[end,ir,1] +# end +#end + +function update_electron_vth_temperature!(moments, ppar, dens, composition) + begin_r_z_region() + + temp = moments.electron.temp + vth = moments.electron.vth + @loop_r_z ir iz begin + p = max(ppar[iz,ir], 0.0) + temp[iz,ir] = 2 * p / dens[iz,ir] + vth[iz,ir] = sqrt(temp[iz,ir] / composition.me_over_mi) + end + moments.electron.temp_updated[] = true + + return nothing +end + +function update_electron_temperature!(temp, ppar, dens, composition) + begin_z_region() + + @loop_z iz begin + p = max(ppar[iz], 0.0) + temp[iz] = 2 * p / dens[iz] + end + + return nothing +end + +""" + electron_fluid_qpar_boundary_condition!(electron_moments, z) + +Impose fluid approximation to electron sheath boundary condition on the parallel heat +flux. See Stangeby textbook, equations (2.89) and (2.90). +""" +function electron_fluid_qpar_boundary_condition!(ppar, upar, dens, electron_moments, z) + if z.bc == "periodic" + # Nothing to do as z-derivative used to calculate qpar already imposed + # periodicity. + return nothing + end + + begin_r_region() + + if z.irank == 0 && (z.irank == z.nrank - 1) + z_indices = (1, z.n) + elseif z.irank == 0 + z_indices = (1,) + elseif z.irank == z.nrank - 1 + z_indices = (z.n,) + else + return nothing + end + + @loop_r ir begin + for iz ∈ z_indices + this_ppar = ppar[iz,ir] + this_upar = electron_moments.upar[iz,ir] + this_dens = electron_moments.dens[iz,ir] + particle_flux = this_dens * this_upar + T_e = electron_moments.temp[iz,ir] + + # Stangeby (2.90) + gamma_e = 5.5 + + # Stangeby (2.89) + total_heat_flux = gamma_e * T_e * particle_flux + + # E.g. Helander&Sigmar (2.14), neglecting electron viscosity and kinetic + # energy fluxes due to small mass ratio + conductive_heat_flux = total_heat_flux - 2.5 * this_ppar * this_upar + + electron_moments.qpar[iz,ir] = conductive_heat_flux + end + end + + return nothing +end + +end diff --git a/moment_kinetics/src/electron_kinetic_equation.jl b/moment_kinetics/src/electron_kinetic_equation.jl new file mode 100644 index 000000000..fe1aeeb1e --- /dev/null +++ b/moment_kinetics/src/electron_kinetic_equation.jl @@ -0,0 +1,2390 @@ +module electron_kinetic_equation + +using LinearAlgebra +using MPI + +export get_electron_critical_velocities + +using ..looping +using ..analysis: steady_state_residuals +using ..derivatives: derivative_z! +using ..boundary_conditions: enforce_v_boundary_condition_local!, + enforce_vperp_boundary_condition! +using ..calculus: derivative!, second_derivative!, integral +using ..communication +using ..interpolation: interpolate_to_grid_1d! +using ..type_definitions: mk_float, mk_int +using ..array_allocation: allocate_float +using ..electron_fluid_equations: calculate_electron_moments!, + update_electron_vth_temperature!, + calculate_electron_qpar_from_pdf!, + calculate_electron_parallel_friction_force! +using ..electron_fluid_equations: electron_energy_equation!, electron_energy_residual! +using ..electron_z_advection: electron_z_advection!, update_electron_speed_z! +using ..electron_vpa_advection: electron_vpa_advection!, update_electron_speed_vpa! +using ..em_fields: update_phi! +using ..external_sources: external_electron_source! +using ..file_io: get_electron_io_info, write_electron_state, finish_electron_io +using ..krook_collisions: electron_krook_collisions! +using ..moment_constraints: hard_force_moment_constraints!, + moment_constraints_on_residual! +using ..moment_kinetics_structs: scratch_pdf, scratch_electron_pdf, electron_pdf_substruct +using ..nonlinear_solvers: newton_solve! +using ..runge_kutta: rk_update_variable!, rk_loworder_solution!, local_error_norm, + adaptive_timestep_update_t_params! +using ..utils: get_minimum_CFL_z, get_minimum_CFL_vpa +using ..velocity_moments: integrate_over_vspace, calculate_electron_moment_derivatives! + +""" +update_electron_pdf is a function that uses the electron kinetic equation +to solve for the updated electron pdf + +The electron kinetic equation is: + zdot * d(pdf)/dz + wpadot * d(pdf)/dwpa = pdf * pre_factor + + INPUTS: + scratch = `scratch_pdf` struct used to store Runge-Kutta stages + pdf = modified electron pdf @ previous time level = (true electron pdf / dens_e) * vth_e + dens = electron density + vthe = electron thermal speed + ppar = electron parallel pressure + ddens_dz = z-derivative of the electron density + dppar_dz = z-derivative of the electron parallel pressure + dqpar_dz = z-derivative of the electron parallel heat flux + dvth_dz = z-derivative of the electron thermal speed + z = struct containing z-coordinate information + vpa = struct containing vpa-coordinate information + z_spectral = struct containing spectral information for the z-coordinate + vpa_spectral = struct containing spectral information for the vpa-coordinate + scratch_dummy = dummy arrays to be used for temporary storage + dt = time step size + max_electron_pdf_iterations = maximum number of iterations to use in the solution of the electron kinetic equation + ion_dt = if this is passed, the electron pressure is evolved in a form that results in + a backward-Euler update on the ion timestep (ion_dt) once the electron + pseudo-timestepping reaches steady state. +OUTPUT: + pdf = updated (modified) electron pdf +""" +function update_electron_pdf!(scratch, pdf, moments, phi, r, z, vperp, vpa, z_spectral, + vperp_spectral, vpa_spectral, z_advect, vpa_advect, scratch_dummy, t_params, + collisions, composition, external_source_settings, num_diss_params, + max_electron_pdf_iterations, max_electron_sim_time; io_electron=nothing, + initial_time=nothing, residual_tolerance=nothing, evolve_ppar=false, + ion_dt=nothing) + + # set the method to use to solve the electron kinetic equation + solution_method = "artificial_time_derivative" + #solution_method = "shooting_method" + #solution_method = "picard_iteration" + # solve the electron kinetic equation using the specified method + if solution_method == "artificial_time_derivative" + return update_electron_pdf_with_time_advance!(scratch, pdf, moments, phi, + collisions, composition, r, z, vperp, vpa, z_spectral, vperp_spectral, + vpa_spectral, z_advect, vpa_advect, scratch_dummy, t_params, + external_source_settings, num_diss_params, max_electron_pdf_iterations, + max_electron_sim_time; io_electron=io_electron, initial_time=initial_time, + residual_tolerance=residual_tolerance, evolve_ppar=evolve_ppar, ion_dt=ion_dt) + elseif solution_method == "shooting_method" + dens = moments.electron.dens + vthe = moments.electron.vth + ppar = moments.electron.ppar + qpar = moments.electron.qpar + qpar_updated = moments.electron.qpar_updated + ddens_dz = moments.electron.ddens_dz + dppar_dz = moments.electron.dppar_dz + dqpar_dz = moments.electron.dqpar_dz + dvth_dz = moments.electron.dvth_dz + return update_electron_pdf_with_shooting_method!(pdf, dens, vthe, ppar, qpar, + qpar_updated, phi, ddens_dz, dppar_dz, dqpar_dz, dvth_dz, z, vpa, + vpa_spectral, scratch_dummy, composition) + elseif solution_method == "picard_iteration" + dens = moments.electron.dens + vthe = moments.electron.vth + ppar = moments.electron.ppar + qpar = moments.electron.qpar + qpar_updated = moments.electron.qpar_updated + ddens_dz = moments.electron.ddens_dz + dppar_dz = moments.electron.dppar_dz + dqpar_dz = moments.electron.dqpar_dz + dvth_dz = moments.electron.dvth_dz + return update_electron_pdf_with_picard_iteration!(pdf, dens, vthe, ppar, ddens_dz, + dppar_dz, dqpar_dz, dvth_dz, z, vpa, vpa_spectral, scratch_dummy, + max_electron_pdf_iterations) + else + error("!!! invalid solution method specified !!!") + end + return nothing +end + +""" +update_electron_pdf_with_time_advance is a function that introduces an artifical time derivative to advance +the electron kinetic equation until a steady-state solution is reached. + +The electron kinetic equation is: + zdot * d(pdf)/dz + wpadot * d(pdf)/dwpa = pdf * pre_factor + + INPUTS: + pdf = modified electron pdf @ previous time level = (true electron pdf / dens_e) * vth_e + dens = electron density + vthe = electron thermal speed + ppar = electron parallel pressure + ddens_dz = z-derivative of the electron density + dppar_dz = z-derivative of the electron parallel pressure + dqpar_dz = z-derivative of the electron parallel heat flux + dvth_dz = z-derivative of the electron thermal speed + z = struct containing z-coordinate information + vpa = struct containing vpa-coordinate information + z_spectral = struct containing spectral information for the z-coordinate + vpa_spectral = struct containing spectral information for the vpa-coordinate + scratch_dummy = dummy arrays to be used for temporary storage + max_electron_pdf_iterations = maximum number of iterations to use in the solution of the electron kinetic equation + io_electron = info struct for binary file I/O + initial_time = initial value for the (pseudo-)time + ion_dt = if this is passed, the electron pressure is evolved in a form that results in + a backward-Euler update on the ion timestep (ion_dt) once the electron + pseudo-timestepping reaches steady state. +OUTPUT: + pdf = updated (modified) electron pdf +""" +function update_electron_pdf_with_time_advance!(scratch, pdf, moments, phi, collisions, + composition, r, z, vperp, vpa, z_spectral, vperp_spectral, vpa_spectral, z_advect, + vpa_advect, scratch_dummy, t_params, external_source_settings, num_diss_params, + max_electron_pdf_iterations, max_electron_sim_time; io_electron=nothing, + initial_time=nothing, residual_tolerance=nothing, evolve_ppar=false, + ion_dt=nothing) + + if max_electron_pdf_iterations !== nothing && max_electron_sim_time !== nothing + error("Cannot use both max_electron_pdf_iterations=$max_electron_pdf_iterations " + * "and max_electron_sim_time=$max_electron_sim_time at the same time") + end + if max_electron_pdf_iterations === nothing && max_electron_sim_time === nothing + error("Must set one of max_electron_pdf_iterations and max_electron_sim_time") + end + + begin_r_z_region() + + # create several (r) dimension dummy arrays for use in taking derivatives + buffer_r_1 = @view scratch_dummy.buffer_rs_1[:,1] + buffer_r_2 = @view scratch_dummy.buffer_rs_2[:,1] + buffer_r_3 = @view scratch_dummy.buffer_rs_3[:,1] + buffer_r_4 = @view scratch_dummy.buffer_rs_4[:,1] + buffer_r_5 = @view scratch_dummy.buffer_rs_5[:,1] + buffer_r_6 = @view scratch_dummy.buffer_rs_6[:,1] + + begin_r_z_region() + @loop_r_z ir iz begin + # update the electron thermal speed using the updated electron parallel pressure + moments.electron.vth[iz,ir] = sqrt(abs(2.0 * moments.electron.ppar[iz,ir] / + (moments.electron.dens[iz,ir] * + composition.me_over_mi))) + scratch[t_params.n_rk_stages+1].electron_ppar[iz,ir] = moments.electron.ppar[iz,ir] + end + calculate_electron_moment_derivatives!(moments, + (electron_density=moments.electron.dens, + electron_upar=moments.electron.upar, + electron_ppar=moments.electron.ppar), + scratch_dummy, z, z_spectral, + num_diss_params.electron.moment_dissipation_coefficient, + composition.electron_physics) + + if ion_dt !== nothing + evolve_ppar = true + + # Use forward-Euler step (with `ion_dt` as the timestep) as initial guess for + # updated electron_ppar + electron_energy_equation!(scratch[t_params.n_rk_stages+1].electron_ppar, + moments.electron.ppar, moments.electron.dens, + moments.electron.upar, moments.ion.dens, + moments.ion.upar, moments.ion.ppar, + moments.neutral.dens, moments.neutral.uz, + moments.neutral.pz, moments.electron, collisions, + ion_dt, composition, external_source_settings.electron, + num_diss_params, z) + end + + if !evolve_ppar + # ppar is not updated in the pseudo-timestepping loop below. So that we can read + # ppar from the scratch structs, copy moments.electron.ppar into all of them. + moments_ppar = moments.electron.ppar + for istage ∈ 1:t_params.n_rk_stages+1 + scratch_ppar = scratch[istage].electron_ppar + @loop_r_z ir iz begin + scratch_ppar[iz,ir] = moments_ppar[iz,ir] + end + end + end + + if initial_time !== nothing + @serial_region begin + t_params.t[] = initial_time + end + _block_synchronize() + # Make sure that output times are set relative to this initial_time (the values in + # t_params are set relative to 0.0). + moments_output_times = t_params.moments_output_times .+ initial_time + dfns_output_times = t_params.dfns_output_times .+ initial_time + else + initial_time = t_params.t[] + end + if io_electron === nothing && t_params.debug_io !== nothing + # Overwrite the debug output file with the output from this call to + # update_electron_pdf_with_time_advance!(). + io_electron = get_electron_io_info(t_params.debug_io[1], "electron_debug") + do_debug_io = true + debug_io_nwrite = t_params.debug_io[3] + else + do_debug_io = false + end + + #z_speedup_fac = 20.0 + #z_speedup_fac = 5.0 + z_speedup_fac = 1.0 + + text_output = false + + epsilon = 1.e-11 + # Store the initial number of iterations in the solution of the electron kinetic + # equation + initial_step_counter = t_params.step_counter[] + t_params.step_counter[] += 1 + # initialise the electron pdf convergence flag to false + electron_pdf_converged = false + + if text_output + if n_blocks[] == 1 + text_output_suffix = "" + else + text_output_suffix = "$(iblock_index[])" + end + begin_serial_region() + @serial_region begin + # open files to write the electron heat flux and pdf to file + io_upar = open("upar$text_output_suffix.txt", "w") + io_qpar = open("qpar$text_output_suffix.txt", "w") + io_ppar = open("ppar$text_output_suffix.txt", "w") + io_pdf = open("pdf$text_output_suffix.txt", "w") + io_vth = open("vth$text_output_suffix.txt", "w") + if !electron_pdf_converged + # need to exit or handle this appropriately + @loop_vpa ivpa begin + @loop_z iz begin + println(io_pdf, "z: ", z.grid[iz], " wpa: ", vpa.grid[ivpa], " pdf: ", scratch[t_params.n_rk_stages+1].pdf_electron[ivpa, 1, iz, 1], " time: ", t_params.t[], " residual: ", residual[ivpa, 1, iz, 1]) + end + println(io_pdf,"") + end + @loop_z iz begin + println(io_upar, "z: ", z.grid[iz], " upar: ", moments.electron.upar[iz,1], " dupar_dz: ", moments.electron.dupar_dz[iz,1], " time: ", t_params.t[], " iteration: ", t_params.step_counter[] - initial_step_counter) + println(io_qpar, "z: ", z.grid[iz], " qpar: ", moments.electron.qpar[iz,1], " dqpar_dz: ", moments.electron.dqpar_dz[iz,1], " time: ", t_params.t[], " iteration: ", t_params.step_counter[] - initial_step_counter) + println(io_ppar, "z: ", z.grid[iz], " ppar: ", scratch[t_params.n_rk_stages+1].electron_ppar[iz,1], " dppar_dz: ", moments.electron.dppar_dz[iz,1], " time: ", t_params.t[], " iteration: ", t_params.step_counter[] - initial_step_counter) + println(io_vth, "z: ", z.grid[iz], " vthe: ", moments.electron.vth[iz,1], " dvth_dz: ", moments.electron.dvth_dz[iz,1], " time: ", t_params.t[], " iteration: ", t_params.step_counter[] - initial_step_counter, " dens: ", dens[iz,1]) + end + println(io_upar,"") + println(io_qpar,"") + println(io_ppar,"") + println(io_vth,"") + end + io_pdf_stages = open("pdf_zright$text_output_suffix.txt", "w") + end + end + + begin_serial_region() + t_params.moments_output_counter[] += 1 + @serial_region begin + if io_electron !== nothing + write_electron_state(scratch, moments, t_params, io_electron, + t_params.moments_output_counter[], r, z, vperp, vpa) + end + end + # evolve (artificially) in time until the residual is less than the tolerance + while (!electron_pdf_converged + && (max_electron_pdf_iterations === nothing || t_params.step_counter[] - initial_step_counter < max_electron_pdf_iterations) + && (max_electron_sim_time === nothing || t_params.t[] - initial_time < max_electron_sim_time) + && t_params.dt[] > 0.0 && !isnan(t_params.dt[])) + + # Set the initial values for the next step to the final values from the previous + # step + begin_r_z_vperp_vpa_region() + new_pdf = scratch[1].pdf_electron + old_pdf = scratch[t_params.n_rk_stages+1].pdf_electron + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + new_pdf[ivpa,ivperp,iz,ir] = old_pdf[ivpa,ivperp,iz,ir] + end + if evolve_ppar + begin_r_z_region() + new_ppar = scratch[1].electron_ppar + old_ppar = scratch[t_params.n_rk_stages+1].electron_ppar + @loop_r_z ir iz begin + new_ppar[iz,ir] = old_ppar[iz,ir] + end + end + + for istage ∈ 1:t_params.n_rk_stages + # Set the initial values for this stage to the final values from the previous + # stage + begin_r_z_vperp_vpa_region() + new_pdf = scratch[istage+1].pdf_electron + old_pdf = scratch[istage].pdf_electron + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + new_pdf[ivpa,ivperp,iz,ir] = old_pdf[ivpa,ivperp,iz,ir] + end + if evolve_ppar + begin_r_z_region() + new_ppar = scratch[istage+1].electron_ppar + old_ppar = scratch[istage].electron_ppar + @loop_r_z ir iz begin + new_ppar[iz,ir] = old_ppar[iz,ir] + end + end + # Do a forward-Euler update of the electron pdf, and (if evove_ppar=true) the + # electron parallel pressure. + electron_kinetic_equation_euler_update!(scratch[istage+1], scratch[istage], + moments, z, vperp, vpa, z_spectral, + vpa_spectral, z_advect, vpa_advect, + scratch_dummy, collisions, + composition, external_source_settings, + num_diss_params, t_params.dt[]; + evolve_ppar=evolve_ppar, + ion_dt=ion_dt) + speedup_hack!(scratch[istage+1], scratch[istage], z_speedup_fac, z, vpa; + evolve_ppar=evolve_ppar) + + rk_update_variable!(scratch, nothing, :pdf_electron, t_params, istage) + + if evolve_ppar + rk_update_variable!(scratch, nothing, :electron_ppar, t_params, istage) + + update_electron_vth_temperature!(moments, scratch[istage+1].electron_ppar, + moments.electron.dens, composition) + end + + apply_electron_bc_and_constraints!(scratch[istage+1], phi, moments, z, vperp, + vpa, vperp_spectral, vpa_spectral, + vpa_advect, num_diss_params, composition) + + latest_pdf = scratch[istage+1].pdf_electron + + function update_derived_moments_and_derivatives(update_vth=false) + # update the electron heat flux + moments.electron.qpar_updated[] = false + calculate_electron_qpar_from_pdf!(moments.electron.qpar, + scratch[istage+1].electron_ppar, + moments.electron.vth, latest_pdf, vpa) + + if evolve_ppar + this_ppar = scratch[istage+1].electron_ppar + this_dens = moments.electron.dens + this_upar = moments.electron.upar + if update_vth + begin_r_z_region() + this_vth = moments.electron.vth + @loop_r_z ir iz begin + # update the electron thermal speed using the updated electron + # parallel pressure + this_vth[iz,ir] = sqrt(abs(2.0 * this_ppar[iz,ir] / + (this_dens[iz,ir] * + composition.me_over_mi))) + end + end + calculate_electron_moment_derivatives!( + moments, + (electron_density=this_dens, + electron_upar=this_upar, + electron_ppar=this_ppar), + scratch_dummy, z, z_spectral, + num_diss_params.electron.moment_dissipation_coefficient, + composition.electron_physics) + else + # compute the z-derivative of the parallel electron heat flux + @views derivative_z!(moments.electron.dqpar_dz, moments.electron.qpar, + buffer_r_1, buffer_r_2, buffer_r_3, buffer_r_4, + z_spectral, z) + end + end + update_derived_moments_and_derivatives() + + if t_params.adaptive && istage == t_params.n_rk_stages + if ion_dt === nothing + local_max_dt = Inf + else + # Ensure timestep is not too big, so that d(electron_ppar)/dt 'source + # term' is numerically stable. + local_max_dt = 0.5 * ion_dt + end + electron_adaptive_timestep_update!(scratch, t_params.t[], t_params, + moments, phi, z_advect, vpa_advect, + composition, r, z, vperp, vpa, + vperp_spectral, vpa_spectral, + external_source_settings, + num_diss_params; + evolve_ppar=evolve_ppar, + local_max_dt=local_max_dt) + # Re-do this in case electron_adaptive_timestep_update!() re-arranged the + # `scratch` vector + new_scratch = scratch[istage+1] + old_scratch = scratch[istage] + + if t_params.previous_dt[] == 0.0 + # Re-calculate moments and moment derivatives as the timstep needs to + # be re-done with a smaller dt, so scratch[t_params.n_rk_stages+1] has + # been reset to the values from the beginning of the timestep here. + update_derived_moments_and_derivatives(true) + end + end + end + + # update the time following the pdf update + @serial_region begin + t_params.t[] += t_params.previous_dt[] + end + _block_synchronize() + + residual = -1.0 + if t_params.previous_dt[] > 0.0 + # Calculate residuals to decide if iteration is converged. + # Might want an option to calculate the residual only after a certain number + # of iterations (especially during initialization when there are likely to be + # a large number of iterations required) to avoid the expense, and especially + # the global MPI.Bcast()? + begin_r_z_vperp_vpa_region() + residual = steady_state_residuals(scratch[t_params.n_rk_stages+1].pdf_electron, + scratch[1].pdf_electron, t_params.previous_dt[]; + use_mpi=true, only_max_abs=true) + if global_rank[] == 0 + residual = first(values(residual))[1] + end + if evolve_ppar + ppar_residual = + steady_state_residuals(scratch[t_params.n_rk_stages+1].electron_ppar, + scratch[1].electron_ppar, t_params.previous_dt[]; + use_mpi=true, only_max_abs=true) + if global_rank[] == 0 + ppar_residual = first(values(ppar_residual))[1] + residual = max(residual, ppar_residual) + end + end + if global_rank[] == 0 + if residual_tolerance === nothing + residual_tolerance = t_params.converged_residual_value + end + electron_pdf_converged = abs(residual) < residual_tolerance + end + electron_pdf_converged = MPI.Bcast(electron_pdf_converged, 0, comm_world) + end + + if text_output + if (mod(t_params.step_counter[] - initial_step_counter, t_params.nwrite_moments)==1) + begin_serial_region() + @serial_region begin + @loop_vpa ivpa begin + println(io_pdf_stages, "vpa: ", vpa.grid[ivpa], " pdf: ", new_pdf[ivpa,1,end,1], " iteration: ", t_params.step_counter[] - initial_step_counter, " flag: ", 1) + end + println(io_pdf_stages,"") + end + end + end + + if (mod(t_params.step_counter[] - initial_step_counter,100) == 0) + begin_serial_region() + @serial_region begin + if z.irank == 0 && z.irank == z.nrank - 1 + println("iteration: ", t_params.step_counter[] - initial_step_counter, " time: ", t_params.t[], " dt_electron: ", t_params.dt[], " phi_boundary: ", phi[[1,end],1], " residual: ", residual) + elseif z.irank == 0 + println("iteration: ", t_params.step_counter[] - initial_step_counter, " time: ", t_params.t[], " dt_electron: ", t_params.dt[], " phi_boundary_lower: ", phi[1,1], " residual: ", residual) + end + end + end + if ((t_params.adaptive && t_params.write_moments_output[]) + || (!t_params.adaptive && t_params.step_counter[] % t_params.nwrite_moments == 0) + || (do_debug_io && (t_params.step_counter[] % debug_io_nwrite == 0))) + + begin_serial_region() + @serial_region begin + if text_output + if (mod(t_params.moments_output_counter[], 100) == 0) + @loop_vpa ivpa begin + @loop_z iz begin + println(io_pdf, "z: ", z.grid[iz], " wpa: ", vpa.grid[ivpa], " pdf: ", new_pdf[ivpa, 1, iz, 1], " time: ", t_params.t[], " residual: ", residual[ivpa, 1, iz, 1]) + end + println(io_pdf,"") + end + println(io_pdf,"") + end + @loop_z iz begin + println(io_upar, "z: ", z.grid[iz], " upar: ", moments.electron.upar[iz,1], " dupar_dz: ", moments.electron.dupar_dz[iz,1], " time: ", t_params.t[], " iteration: ", t_params.step_counter[] - initial_step_counter) + println(io_qpar, "z: ", z.grid[iz], " qpar: ", moments.electron.qpar[iz,1], " dqpar_dz: ", moments.electron.dqpar_dz[iz,1], " time: ", t_params.t[], " iteration: ", t_params.step_counter[] - initial_step_counter) + println(io_ppar, "z: ", z.grid[iz], " ppar: ", scratch[t_params.n_rk_stages+1].electron_ppar[iz,1], " dppar_dz: ", moments.electron.dppar_dz[iz,1], " time: ", t_params.t[], " iteration: ", t_params.step_counter[] - initial_step_counter) + println(io_vth, "z: ", z.grid[iz], " vthe: ", moments.electron.vth[iz,1], " dvth_dz: ", moments.electron.dvth_dz[iz,1], " time: ", t_params.t[], " iteration: ", t_params.step_counter[] - initial_step_counter, " dens: ", dens[iz,1]) + end + println(io_upar,"") + println(io_qpar,"") + println(io_ppar,"") + println(io_vth,"") + end + end + t_params.moments_output_counter[] += 1 + @serial_region begin + if io_electron !== nothing + t_params.write_moments_output[] = false + write_electron_state(scratch, moments, t_params, io_electron, + t_params.moments_output_counter[], r, z, vperp, + vpa) + end + end + end + + # check to see if the electron pdf satisfies the electron kinetic equation to within the specified tolerance + + t_params.step_counter[] += 1 + if electron_pdf_converged + break + end + end + # Update the 'pdf' arrays with the final result + begin_r_z_vperp_vpa_region() + final_scratch_pdf = scratch[t_params.n_rk_stages+1].pdf_electron + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + pdf[ivpa,ivperp,iz,ir] = final_scratch_pdf[ivpa,ivperp,iz,ir] + end + if evolve_ppar + # Update `moments.electron.ppar` with the final electron pressure + begin_r_z_region() + scratch_ppar = scratch[t_params.n_rk_stages+1].electron_ppar + moments_ppar = moments.electron.ppar + @loop_r_z ir iz begin + moments_ppar[iz,ir] = scratch_ppar[iz,ir] + end + end + begin_serial_region() + @serial_region begin + if text_output + if !electron_pdf_converged + @loop_vpa ivpa begin + @loop_z iz begin + println(io_pdf, "z: ", z.grid[iz], " wpa: ", vpa.grid[ivpa], " pdf: ", pdf[ivpa, 1, iz, 1], " time: ", t_params.t[], " residual: ", residual[ivpa, 1, iz, 1]) + end + println(io_pdf,"") + end + end + close(io_upar) + close(io_qpar) + close(io_ppar) + close(io_vth) + close(io_pdf) + close(io_pdf_stages) + end + if !electron_pdf_converged || do_debug_io + if io_electron !== nothing && io_electron !== true + t_params.moments_output_counter[] += 1 + write_electron_state(scratch, moments, t_params, io_electron, + t_params.moments_output_counter[], r, z, vperp, vpa) + finish_electron_io(io_electron) + end + end + end + if !electron_pdf_converged + success = "kinetic-electrons" + else + success = "" + end + return success +end + +""" + implicit_electron_advance!() + +Do an implicit solve which finds: the steady-state electron shape function \$g_e\$; the +backward-Euler advanced electron pressure which is updated using \$g_e\$ at the new +time-level. + +Implicit electron solve includes r-dimension. For 1D runs this makes no difference. In 2D +it might or might not be necessary. If the r-dimension is not needed in the implicit +solve, we would need to work on the parallelisation. The simplest option would be a +non-parallelised outer loop over r, with each nonlinear solve being parallelised over +{z,vperp,vpa}. More efficient might be to add an equivalent to the 'anyv' parallelisation +used for the collision operator (e.g. 'anyzv'?) to allow the outer r-loop to be +parallelised. +""" +function implicit_electron_advance!(fvec_out, fvec_in, pdf, scratch_electron, moments, + fields, collisions, composition, geometry, + external_source_settings, num_diss_params, r, z, + vperp, vpa, r_spectral, z_spectral, vperp_spectral, + vpa_spectral, z_advect, vpa_advect, gyroavs, + scratch_dummy, dt, nl_solver_params) + + electron_ppar_out = fvec_out.electron_ppar + # Store the solved-for pdf in n_rk_stages+1, because this was the version that gets + # written to output for the explicit-electron-timestepping version. + pdf_electron_out = scratch_electron.pdf_electron + + # Do a forward-Euler step for electron_ppar to get the initial guess. + # No equivalent for f_electron, as f_electron obeys a steady-state equation. + calculate_electron_moment_derivatives!(moments, fvec_in, scratch_dummy, z, z_spectral, + num_diss_params.electron.moment_dissipation_coefficient, + composition.electron_physics) + electron_energy_equation!(electron_ppar_out, fvec_in.electron_ppar, + fvec_in.density, fvec_in.electron_upar, fvec_in.density, + fvec_in.upar, fvec_in.ppar, fvec_in.density_neutral, + fvec_in.uz_neutral, fvec_in.pz_neutral, moments.electron, + collisions, dt, composition, + external_source_settings.electron, num_diss_params, z) + + function residual_func!(residual, new_variables) + electron_ppar_residual, f_electron_residual = residual + electron_ppar_new, f_electron_new = new_variables + + new_scratch = scratch_pdf(fvec_in.pdf, fvec_in.density, fvec_in.upar, fvec_in.ppar, + fvec_in.pperp, fvec_in.temp_z_s, + fvec_in.electron_density, fvec_in.electron_upar, + electron_ppar_new, fvec_in.electron_pperp, + fvec_in.electron_temp, fvec_in.pdf_neutral, + fvec_in.density_neutral, fvec_in.uz_neutral, + fvec_in.pz_neutral) + new_scratch_electron = scratch_electron_pdf(f_electron_new, electron_ppar_new) + + apply_electron_bc_and_constraints!(new_scratch_electron, fields.phi, moments, z, + vperp, vpa, vperp_spectral, vpa_spectral, + vpa_advect, num_diss_params, composition) + + # Only the first entry in the `electron_pdf_substruct` will be used, so does not + # matter what we put in the second and third except that they have the right type. + new_pdf = (electron=electron_pdf_substruct(f_electron_new, f_electron_new, + f_electron_new,),) + # Calculate heat flux and derivatives using new_variables + calculate_electron_moments!(new_scratch, new_pdf, moments, composition, + collisions, r, z, vpa) + calculate_electron_moment_derivatives!(moments, new_scratch, scratch_dummy, z, + z_spectral, + num_diss_params.electron.moment_dissipation_coefficient, + composition.electron_physics) + + electron_energy_residual!(electron_ppar_residual, electron_ppar_new, fvec_in, + moments, collisions, composition, + external_source_settings, num_diss_params, z, dt) + + # electron_kinetic_equation_euler_update!() just adds dt*d(g_e)/dt to the + # electron_pdf member of the first argument, so if we set the electron_pdf member + # of the first argument to zero, and pass dt=1, then it will evaluate the time + # derivative, which is the residual for a steady-state solution. + begin_r_z_vperp_vpa_region() + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + f_electron_residual[ivpa,ivperp,iz,ir] = 0.0 + end + residual_scratch_electron = scratch_electron_pdf(f_electron_residual, + electron_ppar_residual) + new_scratch_electron = scratch_electron_pdf(f_electron_new, electron_ppar_new) + electron_kinetic_equation_euler_update!(residual_scratch_electron, + new_scratch_electron, moments, z, vperp, + vpa, z_spectral, vpa_spectral, z_advect, + vpa_advect, scratch_dummy, collisions, + composition, external_source_settings, + num_diss_params, 1.0) + + # Set residual to zero where pdf_electron is determined by boundary conditions. + if vpa.n > 1 + begin_r_z_vperp_region() + @loop_r_z_vperp ir iz ivperp begin + @views enforce_v_boundary_condition_local!(f_electron_residual[:,ivperp,iz,ir], vpa.bc, + vpa_advect[1].speed[:,ivperp,iz,ir], + num_diss_params.electron.vpa_dissipation_coefficient > 0.0, + vpa, vpa_spectral) + end + end + if vperp.n > 1 + begin_r_z_vpa_region() + enforce_vperp_boundary_condition!(f_electron_residual, vperp.bc, vperp, vperp_spectral, + vperp_adv, vperp_diffusion) + end + if z.bc == "wall" && (z.irank == 0 || z.irank == z.nrank - 1) + # Wall boundary conditions. Note that as density, upar, ppar do not + # change in this implicit step, f_new, f_old, and residual should all + # be zero at exactly the same set of grid points, so it is reasonable + # to zero-out `residual` to impose the boundary condition. We impose + # this after subtracting f_old in case rounding errors, etc. mean that + # at some point f_old had a different boundary condition cut-off + # index. + begin_r_vperp_vpa_region() + v_unnorm = vpa.scratch + zero = 1.0e-14 + if z.irank == 0 + iz = 1 + @loop_r ir begin + v_unnorm .= vpagrid_to_dzdt(vpa.grid, moments.electron.vth[iz,ir], + fvec_in.electron_upar[iz,ir], true, true) + @loop_vperp_vpa ivperp ivpa begin + if v_unnorm > -zero + f_electron_residual[ivpa,ivperp,iz,ir] .= 0.0 + end + end + end + end + if z.irank == z.nrank - 1 + iz = z.n + @loop_r ir begin + v_unnorm .= vpagrid_to_dzdt(vpa.grid, moments.electron.vth[iz,ir], + fvec_in.electron_upar[iz,ir], true, true) + @loop_vperp_vpa ivpa ivperp begin + if v_unnorm < zero + f_electron_residual[ivpa,ivperp,iz,ir] .= 0.0 + end + end + end + end + end + begin_r_z_region() + @loop_r_z ir iz begin + @views moment_constraints_on_residual!(f_electron_residual[:,:,iz,ir], f_electron_new[:,:,iz,ir], + (evolve_density=true, evolve_upar=true, evolve_ppar=true), + vpa) + end + return nothing + end + + residual = (scratch_dummy.implicit_buffer_zr_1, + scratch_dummy.implicit_buffer_vpavperpzr_1) + delta_x = (scratch_dummy.implicit_buffer_zr_2, + scratch_dummy.implicit_buffer_vpavperpzr_2) + rhs_delta = (scratch_dummy.implicit_buffer_zr_3, + scratch_dummy.implicit_buffer_vpavperpzr_3) + v = (scratch_dummy.implicit_buffer_zr_4, + scratch_dummy.implicit_buffer_vpavperpzr_4) + w = (scratch_dummy.implicit_buffer_zr_5, + scratch_dummy.implicit_buffer_vpavperpzr_5) + + newton_success = newton_solve!((electron_ppar_out, pdf_electron_out), residual_func!, + residual, delta_x, rhs_delta, v, w, nl_solver_params; + left_preconditioner=nothing, + right_preconditioner=nothing, + coords=(r=r, z=z, vperp=vperp, vpa=vpa)) + + # Fill pdf.electron.norm + non_scratch_pdf = pdf.electron.norm + begin_r_z_vperp_vpa_region() + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + non_scratch_pdf[ivpa,ivperp,iz,ir] = pdf_electron_out[ivpa,ivperp,iz,ir] + end + + # Update the electron parallel friction force. + # This does not actually do anything for kinetic electron runs at the moment, but + # include as a reminder to update this if/when we do include e-i collisions for + # kinetic electrons. + calculate_electron_parallel_friction_force!( + moments.electron.parallel_friction, fvec_out.electron_density, + fvec_out.electron_upar, fvec_out.upar, moments.electron.dT_dz, + composition.me_over_mi, collisions.nu_ei, composition.electron_physics) + + # Solve for EM fields now that electrons are updated. + update_phi!(fields, fvec_out, vperp, z, r, composition, collisions, moments, + geometry, z_spectral, r_spectral, scratch_dummy, gyroavs) + + if !newton_success + success = "kinetic-electrons" + else + success = "" + end + return success +end + +function speedup_hack!(fvec_out, fvec_in, z_speedup_fac, z, vpa; evolve_ppar=false) + # Divide by wpa to relax CFL condition at large wpa - only looking for steady + # state here, so does not matter that this makes time evolution incorrect. + # Also increase the effective timestep for z-values far from the sheath boundary - + # these have a less-limited timestep so letting them evolve faster speeds up + # convergence to the steady state. + + # Actually modify so that large wpa does go faster (to allow some phase mixing - maybe + # this makes things more stable?), but not by so much. + #vpa_fudge_factor = 1.0 + #vpa_fudge_factor = 0.8 + vpa_fudge_factor = 0.0 + + Lz = z.L + + if evolve_ppar + begin_r_z_region() + ppar_out = fvec_out.electron_ppar + ppar_in = fvec_in.electron_ppar + @loop_r_z ir iz begin + zval = z.grid[iz] + znorm = 2.0*zval/Lz + ppar_out[iz,ir] = ppar_in[iz,ir] + + (1.0 + z_speedup_fac*(1.0 - znorm^2)) * + (ppar_out[iz,ir] - ppar_in[iz,ir]) + end + end + + begin_r_z_vperp_vpa_region() + pdf_out = fvec_out.pdf_electron + pdf_in = fvec_in.pdf_electron + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + zval = z.grid[iz] + znorm = 2.0*zval/Lz + pdf_out[ivpa,ivperp,iz,ir] = pdf_in[ivpa,ivperp,iz,ir] + + (1.0 + z_speedup_fac*(1.0 - znorm^2)) / + sqrt(1.0 + vpa_fudge_factor * vpa.grid[ivpa]^2) * + (pdf_out[ivpa,ivperp,iz,ir] - pdf_in[ivpa,ivperp,iz,ir]) + end + return nothing +end + +function apply_electron_bc_and_constraints!(this_scratch, phi, moments, z, vperp, vpa, + vperp_spectral, vpa_spectral, vpa_advect, + num_diss_params, composition) + latest_pdf = this_scratch.pdf_electron + + begin_r_z_vperp_vpa_region() + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + latest_pdf[ivpa,ivperp,iz,ir] = max(latest_pdf[ivpa,ivperp,iz,ir], 0.0) + end + + # enforce the boundary condition(s) on the electron pdf + enforce_boundary_condition_on_electron_pdf!(latest_pdf, phi, moments.electron.vth, + moments.electron.upar, z, vperp, vpa, + vperp_spectral, vpa_spectral, vpa_advect, + moments, + num_diss_params.electron.vpa_dissipation_coefficient > 0.0, + composition.me_over_mi) + + begin_r_z_region() + A = moments.electron.constraints_A_coefficient + B = moments.electron.constraints_B_coefficient + C = moments.electron.constraints_C_coefficient + skip_first = z.irank == 0 && z.bc != "periodic" + skip_last = z.irank == z.nrank - 1 && z.bc != "periodic" + @loop_r_z ir iz begin + if (iz == 1 && skip_first) || (iz == z.n && skip_last) + continue + end + (A[iz,ir], B[iz,ir], C[iz,ir]) = + @views hard_force_moment_constraints!(latest_pdf[:,:,iz,ir], + (evolve_density=true, + evolve_upar=true, + evolve_ppar=true), vpa) + end +end + +function enforce_boundary_condition_on_electron_pdf!(pdf, phi, vthe, upar, z, vperp, vpa, + vperp_spectral, vpa_spectral, + vpa_adv, moments, vpa_diffusion, + me_over_mi) + + newton_tol = 1.0e-13 + + # Enforce velocity-space boundary conditions + if vpa.n > 1 + begin_r_z_vperp_region() + @loop_r_z_vperp ir iz ivperp begin + # enforce the vpa BC + # use that adv.speed independent of vpa + @views enforce_v_boundary_condition_local!(pdf[:,ivperp,iz,ir], vpa.bc, + vpa_adv[1].speed[:,ivperp,iz,ir], + vpa_diffusion, vpa, vpa_spectral) + end + end + if vperp.n > 1 + begin_r_z_vpa_region() + @views enforce_vperp_boundary_condition!(pdf, vperp.bc, vperp, vperp_spectral) + end + + if z.bc == "periodic" + # Nothing more to do for z-periodic boundary conditions + return nothing + end + + # first enforce the boundary condition at z_min. + # this involves forcing the pdf to be zero for electrons travelling faster than the max speed + # they could attain by accelerating in the electric field between the wall and the simulation boundary; + # for electrons with positive velocities less than this critical value, they must have the same + # pdf as electrons with negative velocities of the same magnitude. + # the electrostatic potential at the boundary, which determines the critical speed, is unknown a priori; + # use the constraint that the first moment of the normalised pdf be zero to choose the potential. + + begin_r_region() + + newton_max_its = 100 + reversed_pdf = vpa.scratch + + function get_residual_and_coefficients_for_bc(a1, a1prime, a2, a2prime, b1, b1prime, + c1, c1prime, c2, c2prime, d1, d1prime, + e1, e1prime, e2, e2prime, u_over_vt) + alpha = a1 + 2.0 * a2 + alphaprime = a1prime + 2.0 * a2prime + beta = c1 + 2.0 * c2 + betaprime = c1prime + 2.0 * c2prime + gamma = u_over_vt^2 * alpha - 2.0 * u_over_vt * b1 + beta + gammaprime = u_over_vt^2 * alphaprime - 2.0 * u_over_vt * b1prime + betaprime + delta = u_over_vt^2 * beta - 2.0 * u_over_vt * d1 + e1 + 2.0 * e2 + deltaprime = u_over_vt^2 * betaprime - 2.0 * u_over_vt * d1prime + e1prime + 2.0 * e2prime + + A = (0.5 * beta - delta) / (beta * gamma - alpha * delta) + Aprime = (0.5 * betaprime - deltaprime + - (0.5 * beta - delta) * (gamma * betaprime + beta * gammaprime - delta * alphaprime - alpha * deltaprime) + / (beta * gamma - alpha * delta) + ) / (beta * gamma - alpha * delta) + C = (1.0 - alpha * A) / beta + Cprime = -(A * alphaprime + alpha * Aprime) / beta - (1.0 - alpha * A) * betaprime / beta^2 + + epsilon = A * b1 + C * d1 - u_over_vt + epsilonprime = b1 * Aprime + A * b1prime + d1 * Cprime + C * d1prime + + return epsilon, epsilonprime, A, C + end + + if z.irank == 0 + if z.bc != "wall" + error("Options other than wall or z-periodic bc not implemented yet for electrons") + end + @loop_r ir begin + # Impose sheath-edge boundary condition, while also imposing moment + # constraints and determining the cut-off velocity (and therefore the sheath + # potential). + + # Delete the upar contribution here if ignoring the 'upar shift' + vpa_unnorm = @. vpa.scratch2 = vthe[1,ir] * vpa.grid + upar[1,ir] + + u_over_vt = upar[1,ir] / vthe[1,ir] + + # Initial guess for cut-off velocity is result from previous RK stage (which + # might be the previous timestep if this is the first stage). Recalculate this + # value from phi. + vcut = sqrt(phi[1,ir] / me_over_mi) + + # -vcut is between minus_vcut_ind-1 and minus_vcut_ind + minus_vcut_ind = searchsortedfirst(vpa_unnorm, -vcut) + if minus_vcut_ind < 2 + error("In lower-z electron bc, failed to find vpa=-vcut point, minus_vcut_ind=$minus_vcut_ind") + end + if minus_vcut_ind > vpa.n + error("In lower-z electron bc, failed to find vpa=-vcut point, minus_vcut_ind=$minus_vcut_ind") + end + + # sigma is the location we use for w_∥(v_∥=0) - set to 0 to ignore the 'upar + # shift' + sigma = -u_over_vt + + # sigma is between sigma_ind-1 and sigma_ind + sigma_ind = searchsortedfirst(vpa_unnorm, 0.0) + if sigma_ind < 2 + error("In lower-z electron bc, failed to find vpa=0 point, sigma_ind=$sigma_ind") + end + if sigma_ind > vpa.n + error("In lower-z electron bc, failed to find vpa=0 point, sigma_ind=$sigma_ind") + end + + # sigma_fraction is the fraction of the distance between sigma_ind-1 and + # sigma_ind where sigma is. + sigma_fraction = (sigma - vpa_unnorm[sigma_ind-1]) / (vpa_unnorm[sigma_ind] - vpa_unnorm[sigma_ind-1]) + + # Want to construct the w-grid corresponding to -vpa. + # wpa(vpa) = (vpa - upar)/vth + # ⇒ vpa = vth*wpa(vpa) + upar + # wpa(-vpa) = (-vpa - upar)/vth + # = (-(vth*wpa(vpa) + upar) - upar)/vth + # = (-vth*wpa - 2*upar)/vth + # = -wpa - 2*upar/vth + # [Note that `vpa.grid` is slightly mis-named here - it contains the values of + # wpa(+vpa) as we are using a 'moment kinetic' approach.] + # Need to reverse vpa.grid because the grid passed as the second argument of + # interpolate_to_grid_1d!() needs to be sorted in increasing order. + reversed_wpa_of_minus_vpa = @. vpa.scratch3 = -vpa.grid + 2.0 * sigma + #reversed_wpa_of_minus_vpa = vpa.scratch3 .= .-vpa.grid + reverse!(reversed_wpa_of_minus_vpa) + + # interpolate the pdf onto this grid + #@views interpolate_to_grid_1d!(interpolated_pdf, wpa_values, pdf[:,1,1,ir], vpa, vpa_spectral) + @views interpolate_to_grid_1d!(reversed_pdf, reversed_wpa_of_minus_vpa, pdf[:,1,1,ir], vpa, vpa_spectral) # Could make this more efficient by only interpolating to the points needed below, by taking an appropriate view of wpa_of_minus_vpa. Also, in the element containing vpa=0, this interpolation depends on the values that will be replaced by the reflected, interpolated values, which is not ideal (maybe this element should be treated specially first?). + reverse!(reversed_pdf) + pdf[sigma_ind:end,1,1,ir] .= reversed_pdf[sigma_ind:end] + + # Per-grid-point contributions to moment integrals + # Note that we need to include the normalisation factor of 1/sqrt(pi) that + # would be factored in by integrate_over_vspace(). This will need to + # change/adapt when we support 2V as well as 1V. + density_integral_pieces = @views @. vpa.scratch3 = pdf[:,1,1,ir] * vpa.wgts / sqrt(pi) + flow_integral_pieces = @views @. vpa.scratch4 = density_integral_pieces * vpa_unnorm / vthe[1,ir] + energy_integral_pieces = @views @. vpa.scratch5 = flow_integral_pieces * vpa_unnorm / vthe[1,ir] + cubic_integral_pieces = @views @. vpa.scratch6 = energy_integral_pieces * vpa_unnorm / vthe[1,ir] + quartic_integral_pieces = @views @. vpa.scratch7 = cubic_integral_pieces * vpa_unnorm / vthe[1,ir] + + function get_integrals_and_derivatives_lowerz(vcut, minus_vcut_ind) + # vcut_fraction is the fraction of the distance between minus_vcut_ind-1 and + # minus_vcut_ind where -vcut is. + vcut_fraction = (-vcut - vpa_unnorm[minus_vcut_ind-1]) / (vpa_unnorm[minus_vcut_ind] - vpa_unnorm[minus_vcut_ind-1]) + + function get_for_one_moment(integral_pieces) + # Integral contribution from the cell containing vcut + integral_vcut_cell = (0.5 * integral_pieces[minus_vcut_ind-1] + 0.5 * integral_pieces[minus_vcut_ind]) + + part1 = sum(integral_pieces[1:minus_vcut_ind-2]) + part1 += 0.5 * integral_pieces[minus_vcut_ind-1] + vcut_fraction * integral_vcut_cell + # part1prime is d(part1)/d(vcut) + part1prime = -integral_vcut_cell / (vpa_unnorm[minus_vcut_ind] - vpa_unnorm[minus_vcut_ind-1]) + + # Integral contribution from the cell containing sigma + integral_sigma_cell = (0.5 * integral_pieces[sigma_ind-1] + 0.5 * integral_pieces[sigma_ind]) + + part2 = sum(integral_pieces[minus_vcut_ind+1:sigma_ind-2]) + part2 += (1.0 - vcut_fraction) * integral_vcut_cell + 0.5 * integral_pieces[minus_vcut_ind] + 0.5 * integral_pieces[sigma_ind-1] + sigma_fraction * integral_sigma_cell + # part2prime is d(part2)/d(vcut) + part2prime = -part1prime + + return part1, part1prime, part2, part2prime + end + a1, a1prime, a2, a2prime = get_for_one_moment(density_integral_pieces) + b1, b1prime, b2, _ = get_for_one_moment(flow_integral_pieces) + c1, c1prime, c2, c2prime = get_for_one_moment(energy_integral_pieces) + d1, d1prime, d2, _ = get_for_one_moment(cubic_integral_pieces) + e1, e1prime, e2, e2prime = get_for_one_moment(quartic_integral_pieces) + + return get_residual_and_coefficients_for_bc(a1, a1prime, a2, a2prime, b1, + b1prime, c1, c1prime, c2, + c2prime, d1, d1prime, e1, + e1prime, e2, e2prime, + u_over_vt)..., + a2, b2, c2, d2 + end + + counter = 1 + A = 1.0 + C = 0.0 + # Always do at least one update of vcut + epsilon, epsilonprime, A, C, a2, b2, c2, d2 = get_integrals_and_derivatives_lowerz(vcut, minus_vcut_ind) + while true + # Newton iteration update. Note that primes denote derivatives with + # respect to vcut + delta_v = - epsilon / epsilonprime + + # Prevent the step size from getting too big, to make Newton iteration + # more robust. + delta_v = min(delta_v, 0.1 * vthe[1,ir]) + delta_v = max(delta_v, -0.1 * vthe[1,ir]) + + vcut = vcut + delta_v + minus_vcut_ind = searchsortedfirst(vpa_unnorm, -vcut) + + epsilon, epsilonprime, A, C, a2, b2, c2, d2 = get_integrals_and_derivatives_lowerz(vcut, minus_vcut_ind) + + if abs(epsilon) < newton_tol + break + end + + if counter ≥ newton_max_its + error("Newton iteration for electron lower-z boundary failed to " + * "converge after $counter iterations") + end + counter += 1 + end + + # Adjust pdf so that after reflecting and cutting off tail, it will obey the + # constraints. + @. pdf[:,1,1,ir] *= A + C * vpa_unnorm^2 / vthe[1,ir]^2 + + plus_vcut_ind = searchsortedlast(vpa_unnorm, vcut) + pdf[plus_vcut_ind+2:end,1,1,ir] .= 0.0 + # vcut_fraction is the fraction of the distance between plus_vcut_ind and + # plus_vcut_ind+1 where vcut is. + vcut_fraction = (vcut - vpa_unnorm[plus_vcut_ind]) / (vpa_unnorm[plus_vcut_ind+1] - vpa_unnorm[plus_vcut_ind]) + if vcut_fraction > 0.5 + pdf[plus_vcut_ind+1,1,1,ir] *= vcut_fraction - 0.5 + else + pdf[plus_vcut_ind+1,1,1,ir] = 0.0 + pdf[plus_vcut_ind+1,1,1,ir] *= vcut_fraction + 0.5 + end + + # update the electrostatic potential at the boundary to be the value corresponding to the updated cutoff velocity + phi[1,ir] = me_over_mi * vcut^2 + + moments.electron.constraints_A_coefficient[1,ir] = A + moments.electron.constraints_B_coefficient[1,ir] = 0.0 + moments.electron.constraints_C_coefficient[1,ir] = C + + # Ensure the part of f for 0≤v_∥≤vcut has its first 3 moments symmetric with + # vcut≤v_∥≤0 (i.e. even moments are the same, odd moments are equal but opposite + # sign). This should be true analytically because of the definition of the + # boundary condition, but would not be numerically true because of the + # interpolation. + + # Need to recalculate these with the updated distribution function + density_integral_pieces = @views @. vpa.scratch3 = pdf[:,1,1,ir] * vpa.wgts / sqrt(pi) + flow_integral_pieces = @views @. vpa.scratch4 = density_integral_pieces * vpa_unnorm / vthe[1,ir] + energy_integral_pieces = @views @. vpa.scratch5 = flow_integral_pieces * vpa_unnorm / vthe[1,ir] + cubic_integral_pieces = @views @. vpa.scratch6 = energy_integral_pieces * vpa_unnorm / vthe[1,ir] + quartic_integral_pieces = @views @. vpa.scratch7 = cubic_integral_pieces * vpa_unnorm / vthe[1,ir] + + # Update the part2 integrals since we've applied the A and C factors + _, _, _, _, a2, b2, c2, d2 = get_integrals_and_derivatives_lowerz(vcut, minus_vcut_ind) + + function get_part3_for_one_moment_lower(integral_pieces) + # Integral contribution from the cell containing sigma + integral_sigma_cell = (0.5 * integral_pieces[sigma_ind-1] + 0.5 * integral_pieces[sigma_ind]) + + @views part3 = sum(integral_pieces[sigma_ind+1:plus_vcut_ind+1]) + part3 += 0.5 * integral_pieces[sigma_ind] + (1.0 - sigma_fraction) * integral_sigma_cell + + return part3 + end + a3 = get_part3_for_one_moment_lower(density_integral_pieces) + b3 = get_part3_for_one_moment_lower(flow_integral_pieces) + c3 = get_part3_for_one_moment_lower(energy_integral_pieces) + d3 = get_part3_for_one_moment_lower(cubic_integral_pieces) + + correction0_integral_pieces = @views @. vpa.scratch3 = pdf[:,1,1,ir] * vpa.wgts / sqrt(pi) * vpa_unnorm^2 / vthe[1,ir]^2 / (1.0 + vpa_unnorm^2 / vthe[1,ir]^2) + for ivpa ∈ 1:sigma_ind + # We only add the corrections to 'part3', so zero them out for negative v_∥. + # I think this is only actually significant for `sigma_ind-1` and + # `sigma_ind`. Even though `sigma_ind` is part of the distribution + # function that we are correcting, for v_∥>0, it affects the integral in + # the 'sigma_cell' between `sigma_ind-1` and `sigma_ind`, which would + # affect the numerically calculated integrals for f(v_∥<0), so if we + # 'corrected' its value, those integrals would change and the constraints + # would not be exactly satisfied. The difference should be small, as the + # correction at that point is multiplied by + # v_∥^2/vth^2/(1+v_∥^2/vth^2)≈v_∥^2/vth^2≈0. + correction0_integral_pieces[ivpa] = 0.0 + end + correction1_integral_pieces = @views @. vpa.scratch4 = correction0_integral_pieces * vpa_unnorm / vthe[1,ir] + correction2_integral_pieces = @views @. vpa.scratch5 = correction1_integral_pieces * vpa_unnorm / vthe[1,ir] + correction3_integral_pieces = @views @. vpa.scratch6 = correction2_integral_pieces * vpa_unnorm / vthe[1,ir] + correction4_integral_pieces = @views @. vpa.scratch7 = correction3_integral_pieces * vpa_unnorm / vthe[1,ir] + correction5_integral_pieces = @views @. vpa.scratch8 = correction4_integral_pieces * vpa_unnorm / vthe[1,ir] + correction6_integral_pieces = @views @. vpa.scratch9 = correction5_integral_pieces * vpa_unnorm / vthe[1,ir] + + alpha = get_part3_for_one_moment_lower(correction0_integral_pieces) + beta = get_part3_for_one_moment_lower(correction1_integral_pieces) + gamma = get_part3_for_one_moment_lower(correction2_integral_pieces) + delta = get_part3_for_one_moment_lower(correction3_integral_pieces) + epsilon = get_part3_for_one_moment_lower(correction4_integral_pieces) + zeta = get_part3_for_one_moment_lower(correction5_integral_pieces) + eta = get_part3_for_one_moment_lower(correction6_integral_pieces) + + # Update the v_∥>0 part of f to correct the moments as + # f(0 vpa.n - 1 + error("In upper-z electron bc, failed to find vpa=vcut point, plus_vcut_ind=$plus_vcut_ind") + end + + # sigma is the location we use for w_∥(v_∥=0) - set to 0 to ignore the 'upar + # shift' + sigma = -u_over_vt + + # sigma is between sigma_ind and sigma_ind+1 + sigma_ind = searchsortedlast(vpa_unnorm, 0.0) + if sigma_ind < 1 + error("In upper-z electron bc, failed to find vpa=0 point, sigma_ind=$sigma_ind") + end + if sigma_ind > vpa.n - 1 + error("In upper-z electron bc, failed to find vpa=0 point, sigma_ind=$sigma_ind") + end + + # sigma_fraction is the fraction of the distance between sigma_ind+1 and + # sigma_ind where sigma is. + sigma_fraction = (sigma - vpa_unnorm[sigma_ind+1]) / (vpa_unnorm[sigma_ind] - vpa_unnorm[sigma_ind+1]) + + # Want to construct the w-grid corresponding to -vpa. + # wpa(vpa) = (vpa - upar)/vth + # ⇒ vpa = vth*wpa(vpa) + upar + # wpa(-vpa) = (-vpa - upar)/vth + # = (-(vth*wpa(vpa) + upar) - upar)/vth + # = (-vth*wpa - 2*upar)/vth + # = -wpa - 2*upar/vth + # [Note that `vpa.grid` is slightly mis-named here - it contains the values of + # wpa(+vpa) as we are using a 'moment kinetic' approach.] + # Need to reverse vpa.grid because the grid passed as the second argument of + # interpolate_to_grid_1d!() needs to be sorted in increasing order. + reversed_wpa_of_minus_vpa = @. vpa.scratch3 = -vpa.grid + 2.0 * sigma + #reversed_wpa_of_minus_vpa = vpa.scratch3 .= .-vpa.grid + reverse!(reversed_wpa_of_minus_vpa) + + # interpolate the pdf onto this grid + #@views interpolate_to_grid_1d!(interpolated_pdf, wpa_values, pdf[:,1,1,ir], vpa, vpa_spectral) + @views interpolate_to_grid_1d!(reversed_pdf, reversed_wpa_of_minus_vpa, pdf[:,1,end,ir], vpa, vpa_spectral) # Could make this more efficient by only interpolating to the points needed below, by taking an appropriate view of wpa_of_minus_vpa. Also, in the element containing vpa=0, this interpolation depends on the values that will be replaced by the reflected, interpolated values, which is not ideal (maybe this element should be treated specially first?). + reverse!(reversed_pdf) + pdf[1:sigma_ind,1,end,ir] .= reversed_pdf[1:sigma_ind] + + # Per-grid-point contributions to moment integrals + # Note that we need to include the normalisation factor of 1/sqrt(pi) that + # would be factored in by integrate_over_vspace(). This will need to + # change/adapt when we support 2V as well as 1V. + density_integral_pieces = @views @. vpa.scratch3 = pdf[:,1,end,ir] * vpa.wgts / sqrt(pi) + flow_integral_pieces = @views @. vpa.scratch4 = density_integral_pieces * vpa_unnorm / vthe[end,ir] + energy_integral_pieces = @views @. vpa.scratch5 = flow_integral_pieces * vpa_unnorm / vthe[end,ir] + cubic_integral_pieces = @views @. vpa.scratch6 = energy_integral_pieces * vpa_unnorm / vthe[end,ir] + quartic_integral_pieces = @views @. vpa.scratch7 = cubic_integral_pieces * vpa_unnorm / vthe[end,ir] + + function get_integrals_and_derivatives_upperz(vcut, plus_vcut_ind) + # vcut_fraction is the fraction of the distance between plus_vcut_ind and + # plus_vcut_ind+1 where vcut is. + vcut_fraction = (vcut - vpa_unnorm[plus_vcut_ind+1]) / (vpa_unnorm[plus_vcut_ind] - vpa_unnorm[plus_vcut_ind+1]) + + function get_for_one_moment(integral_pieces) + # Integral contribution from the cell containing vcut + integral_vcut_cell = (0.5 * integral_pieces[plus_vcut_ind] + 0.5 * integral_pieces[plus_vcut_ind+1]) + + part1 = sum(integral_pieces[plus_vcut_ind+2:end]) + part1 += 0.5 * integral_pieces[plus_vcut_ind+1] + vcut_fraction * integral_vcut_cell + # part1prime is d(part1)/d(vcut) + part1prime = integral_vcut_cell / (vpa_unnorm[plus_vcut_ind] - vpa_unnorm[plus_vcut_ind+1]) + + # Integral contribution from the cell containing sigma + integral_sigma_cell = (0.5 * integral_pieces[sigma_ind] + 0.5 * integral_pieces[sigma_ind+1]) + + part2 = sum(integral_pieces[sigma_ind+2:plus_vcut_ind-1]) + part2 += (1.0 - vcut_fraction) * integral_vcut_cell + 0.5 * integral_pieces[plus_vcut_ind] + 0.5 * integral_pieces[sigma_ind+1] + sigma_fraction * integral_sigma_cell + # part2prime is d(part2)/d(vcut) + part2prime = -part1prime + + return part1, part1prime, part2, part2prime + end + a1, a1prime, a2, a2prime = get_for_one_moment(density_integral_pieces) + b1, b1prime, b2, _ = get_for_one_moment(flow_integral_pieces) + c1, c1prime, c2, c2prime = get_for_one_moment(energy_integral_pieces) + d1, d1prime, d2, _ = get_for_one_moment(cubic_integral_pieces) + e1, e1prime, e2, e2prime = get_for_one_moment(quartic_integral_pieces) + + return get_residual_and_coefficients_for_bc(a1, a1prime, a2, a2prime, b1, + b1prime, c1, c1prime, c2, + c2prime, d1, d1prime, e1, + e1prime, e2, e2prime, + u_over_vt)..., + a2, b2, c2, d2 + end + + counter = 1 + # Always do at least one update of vcut + epsilon, epsilonprime, A, C, a2, b2, c2, d2 = get_integrals_and_derivatives_upperz(vcut, plus_vcut_ind) + while true + # Newton iteration update. Note that primes denote derivatives with + # respect to vcut + delta_v = - epsilon / epsilonprime + + # Prevent the step size from getting too big, to make Newton iteration + # more robust. + delta_v = min(delta_v, 0.1 * vthe[end,ir]) + delta_v = max(delta_v, -0.1 * vthe[end,ir]) + + vcut = vcut + delta_v + plus_vcut_ind = searchsortedlast(vpa_unnorm, vcut) + + epsilon, epsilonprime, A, C, a2, b2, c2, d2 = get_integrals_and_derivatives_upperz(vcut, plus_vcut_ind) + + if abs(epsilon) < newton_tol + break + end + + if counter ≥ newton_max_its + error("Newton iteration for electron upper-z boundary failed to " + * "converge after $counter iterations") + end + counter += 1 + end + + # Adjust pdf so that after reflecting and cutting off tail, it will obey the + # constraints. + @. pdf[:,1,end,ir] *= A + C * vpa_unnorm^2 / vthe[end,ir]^2 + + minus_vcut_ind = searchsortedfirst(vpa_unnorm, -vcut) + pdf[1:minus_vcut_ind-2,1,end,ir] .= 0.0 + # vcut_fraction is the fraction of the distance between minus_vcut_ind and + # minus_vcut_ind-1 where -vcut is. + vcut_fraction = (-vcut - vpa_unnorm[minus_vcut_ind]) / (vpa_unnorm[minus_vcut_ind-1] - vpa_unnorm[minus_vcut_ind]) + if vcut_fraction > 0.5 + pdf[minus_vcut_ind-1,1,end,ir] *= vcut_fraction - 0.5 + else + pdf[minus_vcut_ind-1,1,end,ir] = 0.0 + pdf[minus_vcut_ind,1,end,ir] *= vcut_fraction + 0.5 + end + + # update the electrostatic potential at the boundary to be the value corresponding to the updated cutoff velocity + phi[end,ir] = me_over_mi * vcut^2 + + moments.electron.constraints_A_coefficient[end,ir] = A + moments.electron.constraints_B_coefficient[end,ir] = 0.0 + moments.electron.constraints_C_coefficient[end,ir] = C + + # Ensure the part of f for -vcut≤v_∥≤0 has its first 3 moments symmetric with + # 0≤v_∥≤vcut (i.e. even moments are the same, odd moments are equal but opposite + # sign). This should be true analytically because of the definition of the + # boundary condition, but would not be numerically true because of the + # interpolation. + + # Need to recalculate these with the updated distribution function + density_integral_pieces = @views @. vpa.scratch3 = pdf[:,1,end,ir] * vpa.wgts / sqrt(pi) + flow_integral_pieces = @views @. vpa.scratch4 = density_integral_pieces * vpa_unnorm / vthe[end,ir] + energy_integral_pieces = @views @. vpa.scratch5 = flow_integral_pieces * vpa_unnorm / vthe[end,ir] + cubic_integral_pieces = @views @. vpa.scratch6 = energy_integral_pieces * vpa_unnorm / vthe[end,ir] + quartic_integral_pieces = @views @. vpa.scratch7 = cubic_integral_pieces * vpa_unnorm / vthe[end,ir] + + # Update the part2 integrals since we've applied the A and C factors + _, _, _, _, a2, b2, c2, d2 = get_integrals_and_derivatives_upperz(vcut, plus_vcut_ind) + + function get_part3_for_one_moment_upper(integral_pieces) + # Integral contribution from the cell containing sigma + integral_sigma_cell = (0.5 * integral_pieces[sigma_ind] + 0.5 * integral_pieces[sigma_ind+1]) + + @views part3 = sum(integral_pieces[minus_vcut_ind-1:sigma_ind-1]) + part3 += 0.5 * integral_pieces[sigma_ind] + (1.0 - sigma_fraction) * integral_sigma_cell + + return part3 + end + a3 = get_part3_for_one_moment_upper(density_integral_pieces) + b3 = get_part3_for_one_moment_upper(flow_integral_pieces) + c3 = get_part3_for_one_moment_upper(energy_integral_pieces) + d3 = get_part3_for_one_moment_upper(cubic_integral_pieces) + + correction0_integral_pieces = @views @. vpa.scratch3 = pdf[:,1,end,ir] * vpa.wgts / sqrt(pi) * vpa_unnorm^2 / vthe[end,ir]^2 / (1.0 + vpa_unnorm^2 / vthe[end,ir]^2) + for ivpa ∈ sigma_ind:vpa.n + # We only add the corrections to 'part3', so zero them out for positive v_∥. + # I think this is only actually significant for `sigma_ind` and + # `sigma_ind+1`. Even though `sigma_ind` is part of the distribution + # function that we are correcting, for v_∥<0, it affects the integral in + # the 'sigma_cell' between `sigma_ind` and `sigma_ind+1`, which would + # affect the numerically calculated integrals for f(v_∥>0), so if we + # 'corrected' its value, those integrals would change and the constraints + # would not be exactly satisfied. The difference should be small, as the + # correction at that point is multiplied by + # v_∥^2/vth^2/(1+v_∥^2/vth^2)≈v_∥^2/vth^2≈0. + correction0_integral_pieces[ivpa] = 0.0 + end + correction1_integral_pieces = @views @. vpa.scratch4 = correction0_integral_pieces * vpa_unnorm / vthe[end,ir] + correction2_integral_pieces = @views @. vpa.scratch5 = correction1_integral_pieces * vpa_unnorm / vthe[end,ir] + correction3_integral_pieces = @views @. vpa.scratch6 = correction2_integral_pieces * vpa_unnorm / vthe[end,ir] + correction4_integral_pieces = @views @. vpa.scratch7 = correction3_integral_pieces * vpa_unnorm / vthe[end,ir] + correction5_integral_pieces = @views @. vpa.scratch8 = correction4_integral_pieces * vpa_unnorm / vthe[end,ir] + correction6_integral_pieces = @views @. vpa.scratch9 = correction5_integral_pieces * vpa_unnorm / vthe[end,ir] + + alpha = get_part3_for_one_moment_upper(correction0_integral_pieces) + beta = get_part3_for_one_moment_upper(correction1_integral_pieces) + gamma = get_part3_for_one_moment_upper(correction2_integral_pieces) + delta = get_part3_for_one_moment_upper(correction3_integral_pieces) + epsilon = get_part3_for_one_moment_upper(correction4_integral_pieces) + zeta = get_part3_for_one_moment_upper(correction5_integral_pieces) + eta = get_part3_for_one_moment_upper(correction6_integral_pieces) + + # Update the v_∥>0 part of f to correct the moments as + # f(0 eps(mk_float) + for iz ∈ 1:z.n-1 + pdf[ivpa, ivperp, iz + 1, ir] = pdf[ivpa, ivperp, iz, ir] - (z.cell_width[iz] * rhs[ivpa, ivperp, iz, ir] + / (vpa.grid[ivpa] * vthe[iz, ir])) + end + # deal with case of negative z-advection speed + elseif vpa.grid[ivpa] < -eps(mk_float) + for iz ∈ z.n:-1:2 + pdf[ivpa, ivperp, iz - 1, ir] = pdf[ivpa, ivperp, iz, ir] + (z.cell_width[iz-1] * rhs[ivpa, ivperp, iz, ir] + / (vpa.grid[ivpa] * vthe[iz, ir])) + end + # deal with case of zero z-advection speed + else + # hack for now until I figure out how to deal with the singular case + @. pdf[ivpa, ivperp, :, ir] = 0.5 * (pdf[ivpa+1, ivperp, :, ir] + pdf[ivpa-1, ivperp, :, ir]) + end + end + # enforce the boundary condition at the walls + for ivpa ∈ 1:vpa.n + # find the ivpa index corresponding to the same magnitude + # of vpa but with opposite sign + ivpamod = vpa.n - ivpa + 1 + # deal with wall at z_min + if vpa.grid[ivpa] > eps(mk_float) + iz = 1 + # electrons travelling sufficiently fast in the negative z-direction are lost to the wall + # the maximum posative z-velocity that can be had is the amount by which electrons + # can be accelerated by the electric field between the wall and the simulation boundary + if vpa.grid[ivpa] > crit_speed_zmin + pdf[ivpa, ivperp, iz, ir] = 0.0 + else + pdf[ivpa, ivperp, iz, ir] = pdf[ivpamod, ivperp, iz, ir] + end + # deal with wall at z_max + elseif vpa.grid[ivpa] < -eps(mk_float) + iz = z.n + # electrons travelling sufficiently fast in the positive z-direction are lost to the wall + # the maximum negative z-velocity that can be had is the amount by which electrons + # can be accelerated by the electric field between the wall and the simulation boundary + if vpa.grid[ivpa] < crit_speed_zmax + pdf[ivpa, ivperp, iz, ir] = 0.0 + else + pdf[ivpa, ivperp, iz, ir] = pdf[ivpamod, ivperp, iz, ir] + end + end + end + end + # the electron parallel heat flux is no longer consistent with the electron pdf + qpar_updated = false + # calculate the updated electron parallel heat flux + calculate_electron_qpar_from_pdf!(qpar, ppar, vthe, pdf, vpa) + for iz ∈ 1:z.n + for ivpa ∈ 1:vpa.n + println("z: ", z.grid[iz], " vpa: ", vpa.grid[ivpa], " pdf: ", pdf[ivpa, 1, iz, 1], " qpar: ", qpar[iz, 1], + " dppardz: ", dppar_dz[iz,1], " vth: ", vthe[iz,1], " ppar: ", ppar[iz,1], " dqpar_dz: ", dqpar_dz[iz,1], + " dvth_dz: ", dvth_dz[iz,1], " ddens_dz: ", ddens_dz[iz,1]) + end + println() + end + return nothing +end + +""" +use Picard iteration to solve the electron kinetic equation + +The electron kinetic equation is: + zdot * d(pdf)/dz + wpadot * d(pdf)/dwpa = pdf * pre_factor +Picard iteration uses the previous iteration of the electron pdf to calculate the next iteration: + zdot * d(pdf^{i+1})/dz + wpadot^{i} * d(pdf^{i})/dwpa = pdf^{i} * prefactor^{i} + +INPUTS: + pdf = modified electron pdf @ previous time level = (true electron pdf / dens_e) * vth_e + dens = electron density + vthe = electron thermal speed + ppar = electron parallel pressure + ddens_dz = z-derivative of the electron density + dppar_dz = z-derivative of the electron parallel pressure + dqpar_dz = z-derivative of the electron parallel heat flux + dvth_dz = z-derivative of the electron thermal speed + z = struct containing z-coordinate information + vpa = struct containing vpa-coordinate information + z_spectral = struct containing spectral information for the z-coordinate + vpa_spectral = struct containing spectral information for the vpa-coordinate + scratch_dummy = dummy arrays to be used for temporary storage + max_electron_pdf_iterations = maximum number of iterations to use in the solution of the electron kinetic equation +OUTPUT: + pdf = updated (modified) electron pdf +""" +function update_electron_pdf_with_picard_iteration!(pdf, dens, vthe, ppar, ddens_dz, dppar_dz, dqpar_dz, dvth_dz, + z, vpa, vpa_spectral, scratch_dummy, max_electron_pdf_iterations) + + # it is convenient to create a pointer to a scratch array to store the RHS of the linear + # system that will be solved iteratively + rhs = scratch_dummy.buffer_vpavperpzr_1 + # define residual to point to a scratch array; + # to be filled with the difference between successive iterations of the electron pdf + residual = scratch_dummy.buffer_vpavperpzr_2 + # define pdf_new to point to a scratch array; + # to be filled with the updated electron pdf + pdf_new = scratch_dummy.buffer_vpavperpzr_3 + # initialise the electron pdf convergence flag to false + electron_pdf_converged = false + # initialise the number of iterations in the solution of the electron kinetic equation to be 1 + iteration = 1 + while !electron_pdf_converged && (iteration < max_electron_pdf_iterations) + # calculate the RHS of the linear system, consisting of all terms in which the + # pdf is evaluated at the previous iteration. + + # initialise the RHS to zero + rhs .= 0.0 + # add the contribution to rhs from the term proportional to the pdf (rather than its derivatives) + add_contribution_from_pdf_term!(rhs, pdf, ppar, vthe, dens, ddens_dz, upar, dvth_dz, dqpar_dz, vpa.grid, z, external_source_settings.electron) + # add the contribution to rhs from the wpa advection term + #add_contribution_from_wpa_advection!(rhs, pdf, vthe, ppar, dppar_dz, dqpar_dz, dvth_dz, vpa, vpa_spectral) + # loop over wpa locations, solving the linear system at each location; + # care must be taken when wpa = 0, as the linear system is singular in this case + @loop_r_vperp_vpa ir ivperp ivpa begin + # modify the rhs vector to account for the pre-factor multiplying d(pdf)/dz + if abs(vpa.grid[ivpa]) < eps() + # hack for now until I figure out how to deal with the singular linear system + rhs[ivpa, ivperp, :, ir] .= 0.0 + else + @loop_z iz begin + #rhs[ivpa, ivperp, iz, ir] /= -(vpa.grid[ivpa] * vthe[iz, ir]) + rhs[ivpa, ivperp, iz, ir] = -rhs[ivpa, ivperp, iz, ir] + end + end + # solve the linear system at this (wpa, wperp, r) location + pdf_new[ivpa, ivperp, :, ir] = z.differentiation_matrix \ rhs[ivpa, ivperp, :, ir] + end + # calculate the difference between successive iterations of the electron pdf + @. residual = pdf_new - pdf + # check to see if the electron pdf has converged to within the specified tolerance + average_residual, electron_pdf_converged = check_electron_pdf_convergence(residual) + # prepare for the next iteration by updating the pdf + @. pdf = pdf_new + # if converged, exit the loop; otherwise, increment the iteration counter + if electron_pdf_converged + break + else + iteration +=1 + end + end + # if the maximum number of iterations has been exceeded, print a warning + if !electron_pdf_converged + # need to exit or handle this appropriately + println("!!! max number of iterations for electron pdf update exceeded !!!") + @loop_z iz begin + println("z: ", z.grid[iz], " pdf: ", pdf[10, 1, iz, 1]) + end + end + + return nothing +end + +""" + electron_kinetic_equation_euler_update!(fvec, pdf, moments, z, vperp, vpa, + z_spectral, vpa_spectral, z_advect, + vpa_advect, scratch_dummy, collisions, + num_diss_params, dt; evolve_ppar=false) + +Do a forward-Euler update of the electron kinetic equation. + +When `evolve_ppar=true` is passed, also updates the electron parallel pressure. +""" +function electron_kinetic_equation_euler_update!(fvec_out, fvec_in, moments, z, vperp, + vpa, z_spectral, vpa_spectral, z_advect, + vpa_advect, scratch_dummy, collisions, + composition, external_source_settings, + num_diss_params, dt; evolve_ppar=false, + ion_dt=nothing) + # add the contribution from the z advection term + electron_z_advection!(fvec_out.pdf_electron, fvec_in.pdf_electron, + moments.electron.upar, moments.electron.vth, z_advect, z, + vpa.grid, z_spectral, scratch_dummy, dt) + + # add the contribution from the wpa advection term + electron_vpa_advection!(fvec_out.pdf_electron, fvec_in.pdf_electron, + moments.electron.dens, moments.electron.upar, + fvec_in.electron_ppar, moments, vpa_advect, vpa, vpa_spectral, + scratch_dummy, dt, external_source_settings.electron) + + # add in the contribution to the residual from the term proportional to the pdf + add_contribution_from_pdf_term!(fvec_out.pdf_electron, fvec_in.pdf_electron, + fvec_in.electron_ppar, moments.electron.dens, + moments.electron.upar, moments, vpa.grid, z, dt, + external_source_settings.electron) + + # add in numerical dissipation terms + add_dissipation_term!(fvec_out.pdf_electron, fvec_in.pdf_electron, scratch_dummy, + z_spectral, z, vpa, vpa_spectral, num_diss_params, dt) + + if collisions.krook.nuee0 > 0.0 || collisions.krook.nuei0 > 0.0 + # Add a Krook collision operator + # Set dt=-1 as we update the residual here rather than adding an update to + # 'fvec_out'. + electron_krook_collisions!(fvec_out.pdf_electron, fvec_in.pdf_electron, + moments.electron.dens, moments.electron.upar, + moments.ion.upar, moments.electron.vth, collisions, + vperp, vpa, dt) + end + + if external_source_settings.electron.active + external_electron_source!(fvec_out.pdf_electron, fvec_in.pdf_electron, + moments.electron.dens, moments.electron.upar, moments, + composition, external_source_settings.electron, vperp, + vpa, dt) + end + + if evolve_ppar + electron_energy_equation!(fvec_out.electron_ppar, fvec_in.electron_ppar, + moments.electron.dens, moments.electron.upar, + moments.ion.dens, moments.ion.upar, moments.ion.ppar, + moments.neutral.dens, moments.neutral.uz, + moments.neutral.pz, moments.electron, collisions, dt, + composition, external_source_settings.electron, + num_diss_params, z) + + if ion_dt !== nothing + # Add source term to turn steady state solution into a backward-Euler update of + # electron_ppar with the ion timestep `ion_dt`. + ppar_out = fvec_out.electron_ppar + ppar_previous_ion_step = moments.electron.ppar + begin_r_z_region() + @loop_r_z ir iz begin + # At this point, ppar_out = ppar_in + dt*RHS(ppar_in). Here we add a + # source/damping term so that in the steady state of the electron + # pseudo-timestepping iteration, + # RHS(ppar) - (ppar - ppar_previous_ion_step) / ion_dt = 0, + # resulting in a backward-Euler step (as long as the pseudo-timestepping + # loop converges). + ppar_out[iz,ir] += -dt * (ppar_out[iz,ir] - ppar_previous_ion_step[iz,ir]) / ion_dt + end + end + end + + return nothing +end + +""" +electron_kinetic_equation_residual! calculates the residual of the (time-independent) electron kinetic equation +INPUTS: + residual = dummy array to be filled with the residual of the electron kinetic equation +OUTPUT: + residual = updated residual of the electron kinetic equation +""" +function electron_kinetic_equation_residual!(residual, max_term, single_term, pdf, dens, upar, vth, ppar, upar_ion, + ddens_dz, dppar_dz, dqpar_dz, dvth_dz, + z, vperp, vpa, z_spectral, vpa_spectral, z_advect, vpa_advect, scratch_dummy, + collisions, external_source_settings, + num_diss_params, dt_electron) + + # initialise the residual to zero + begin_r_vperp_vpa_region() + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + residual[ivpa,ivperp,iz,ir] = 0.0 + end + # calculate the contribution to the residual from the z advection term + electron_z_advection!(residual, pdf, upar, vth, z_advect, z, vpa.grid, z_spectral, scratch_dummy, -1.0) + #dt_max_zadv = simple_z_advection!(residual, pdf, vth, z, vpa.grid, dt_electron) + #single_term .= residual + #max_term .= abs.(residual) + #println("z_adv residual = ", maximum(abs.(single_term))) + #println("z_advection: ", sum(residual), " dqpar_dz: ", sum(abs.(dqpar_dz))) + #calculate_contribution_from_z_advection!(residual, pdf, vth, z, vpa.grid, z_spectral, scratch_dummy) + # add in the contribution to the residual from the wpa advection term + electron_vpa_advection!(residual, pdf, ppar, vth, dppar_dz, dqpar_dz, dvth_dz, + vpa_advect, vpa, vpa_spectral, scratch_dummy, -1.0, + external_source_settings.electron) + #dt_max_vadv = simple_vpa_advection!(residual, pdf, ppar, vth, dppar_dz, dqpar_dz, dvth_dz, vpa, dt_electron) + #@. single_term = residual - single_term + #max_term .= max.(max_term, abs.(single_term)) + #@. single_term = residual + #println("v_adv residual = ", maximum(abs.(single_term))) + #add_contribution_from_wpa_advection!(residual, pdf, vth, ppar, dppar_dz, dqpar_dz, dvth_dz, vpa, vpa_spectral) + # add in the contribution to the residual from the term proportional to the pdf + add_contribution_from_pdf_term!(residual, pdf, ppar, dens, moments, vpa.grid, z, -1.0, + external_source_settings.electron) + #@. single_term = residual - single_term + #max_term .= max.(max_term, abs.(single_term)) + #@. single_term = residual + #println("pdf_term residual = ", maximum(abs.(single_term))) + # @loop_vpa ivpa begin + # @loop_z iz begin + # println("LHS: ", residual[ivpa,1,iz,1], " vpa: ", vpa.grid[ivpa], " z: ", z.grid[iz], " dvth_dz: ", dvth_dz[iz,1], " type: ", 1) + # end + # println("") + # end + # println("") + # add in numerical dissipation terms + add_dissipation_term!(residual, pdf, scratch_dummy, z_spectral, z, vpa, vpa_spectral, + num_diss_params, -1.0) + #@. single_term = residual - single_term + #println("dissipation residual = ", maximum(abs.(single_term))) + #max_term .= max.(max_term, abs.(single_term)) + # add in particle and heat source term(s) + #@. single_term = residual + #add_source_term!(residual, vpa.grid, z.grid, dvth_dz) + #@. single_term = residual - single_term + #max_term .= max.(max_term, abs.(single_term)) + #stop() + # @loop_vpa ivpa begin + # @loop_z iz begin + # println("total_residual: ", residual[ivpa,1,iz,1], " vpa: ", vpa.grid[ivpa], " z: ", z.grid[iz], " dvth_dz: ", dvth_dz[iz,1], " type: ", 2) + # end + # println("") + # end + # stop() + #dt_max = min(dt_max_zadv, dt_max_vadv) + + if collisions.krook_collision_frequency_prefactor_ee > 0.0 + # Add a Krook collision operator + # Set dt=-1 as we update the residual here rather than adding an update to + # 'fvec_out'. + electron_krook_collisions!(residual, pdf, dens, upar, upar_ion, vth, + collisions, vperp, vpa, -1.0) + end + + dt_max = dt_electron + #println("dt_max: ", dt_max, " dt_max_zadv: ", dt_max_zadv, " dt_max_vadv: ", dt_max_vadv) + return dt_max +end + +function simple_z_advection!(advection_term, pdf, vth, z, vpa, dt_max_in) + dt_max = dt_max_in + # take the z derivative of the input pdf + begin_r_vperp_vpa_region() + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + speed = vth[iz,ir] * vpa[ivpa] + dt_max = min(dt_max, 0.5*z.cell_width[iz]/(max(abs(speed),1e-3))) + if speed > 0 + if iz == 1 + #dpdf_dz = pdf[ivpa,ivperp,iz,ir]/z.cell_width[1] + dpdf_dz = 0.0 + else + dpdf_dz = (pdf[ivpa,ivperp,iz,ir] - pdf[ivpa,ivperp,iz-1,ir])/z.cell_width[iz-1] + end + elseif speed < 0 + if iz == z.n + #dpdf_dz = -pdf[ivpa,ivperp,iz,ir]/z.cell_width[z.n-1] + dpdf_dz = 0.0 + else + dpdf_dz = (pdf[ivpa,ivperp,iz+1,ir] - pdf[ivpa,ivperp,iz,ir])/z.cell_width[iz] + end + else + if iz == 1 + dpdf_dz = (pdf[ivpa,ivperp,iz+1,ir] - pdf[ivpa,ivperp,iz,ir])/z.cell_width[1] + elseif iz == z.n + dpdf_dz = (pdf[ivpa,ivperp,iz,ir] - pdf[ivpa,ivperp,iz-1,ir])/z.cell_width[z.n-1] + else + dpdf_dz = (pdf[ivpa,ivperp,iz+1,ir] - pdf[ivpa,ivperp,iz,ir])/z.cell_width[1] + end + end + advection_term[ivpa,ivperp,iz,ir] += speed * dpdf_dz + end + return dt_max +end + +################# +# NEED TO ADD PARTICLE/HEAT SOURCE INTO THE ELECTRON KINETIC EQUATION TO FIX amplitude +################# + + +function simple_vpa_advection!(advection_term, pdf, ppar, vth, dppar_dz, dqpar_dz, dvth_dz, vpa, dt_max_in) + dt_max = dt_max_in + # take the vpa derivative in the input pdf + begin_r_z_vperp_region() + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + speed = ((vth[iz,ir] * dppar_dz[iz,ir] + vpa.grid[ivpa] * dqpar_dz[iz,ir]) + / (2 * ppar[iz,ir]) - vpa.grid[ivpa]^2 * dvth_dz[iz,ir]) + dt_max = min(dt_max, 0.5*vpa.cell_width[ivpa]/max(abs(speed),1e-3)) + if speed > 0 + if ivpa == 1 + dpdf_dvpa = pdf[ivpa,ivperp,iz,ir]/vpa.cell_width[1] + else + dpdf_dvpa = (pdf[ivpa,ivperp,iz,ir] - pdf[ivpa-1,ivperp,iz,ir])/vpa.cell_width[ivpa-1] + end + elseif speed < 0 + if ivpa == vpa.n + dpdf_dvpa = -pdf[ivpa,ivperp,iz,ir]/vpa.cell_width[vpa.n-1] + else + dpdf_dvpa = (pdf[ivpa+1,ivperp,iz,ir] - pdf[ivpa,ivperp,iz,ir])/vpa.cell_width[ivpa] + end + else + if ivpa == 1 + dpdf_dvpa = (pdf[ivpa+1,ivperp,iz,ir] - pdf[ivpa,ivperp,iz,ir])/vpa.cell_width[1] + elseif ivpa == vpa.n + dpdf_dvpa = (pdf[ivpa,ivperp,iz,ir] - pdf[ivpa-1,ivperp,iz,ir])/vpa.cell_width[vpa.n-1] + else + dpdf_dvpa = (pdf[ivpa+1,ivperp,iz,ir] - pdf[ivpa,ivperp,iz,ir])/vpa.cell_width[1] + end + end + advection_term[ivpa,ivperp,iz,ir] += speed * dpdf_dvpa + end + return dt_max +end + +function add_source_term!(source_term, vpa, z, dvth_dz) + # add in particle and heat source term(s) + begin_r_z_vperp_vpa_region() + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + # source_term[ivpa,ivperp,iz,ir] -= 40*exp(-vpa[ivpa]^2)*exp(-z[iz]^2) + # source_term[ivpa,ivperp,iz,ir] -= vpa[ivpa]*exp(-vpa[ivpa]^2)*(2*vpa[ivpa]^2-3)*dvth_dz[iz,ir] + end + return nothing +end + +function add_dissipation_term!(pdf_out, pdf_in, scratch_dummy, z_spectral, z, vpa, + vpa_spectral, num_diss_params, dt) + dummy_zr1 = @view scratch_dummy.dummy_zrs[:,:,1] + dummy_zr2 = @view scratch_dummy.buffer_vpavperpzr_1[1,1,:,:] + buffer_r_1 = @view scratch_dummy.buffer_rs_1[:,1] + buffer_r_2 = @view scratch_dummy.buffer_rs_2[:,1] + buffer_r_3 = @view scratch_dummy.buffer_rs_3[:,1] + buffer_r_4 = @view scratch_dummy.buffer_rs_4[:,1] + # add in numerical dissipation terms + #@loop_vperp_vpa ivperp ivpa begin + # @views derivative_z!(dummy_zr1, pdf_in[ivpa,ivperp,:,:], buffer_r_1, buffer_r_2, buffer_r_3, + # buffer_r_4, z_spectral, z) + # @views derivative_z!(dummy_zr2, dummy_zr1, buffer_r_1, buffer_r_2, buffer_r_3, + # buffer_r_4, z_spectral, z) + # @. residual[ivpa,ivperp,:,:] -= num_diss_params.electron.z_dissipation_coefficient * dummy_zr2 + #end + begin_r_z_vperp_region() + @loop_r_z_vperp ir iz ivperp begin + #@views derivative!(vpa.scratch, pdf_in[:,ivperp,iz,ir], vpa, false) + #@views derivative!(vpa.scratch2, vpa.scratch, vpa, false) + #@. residual[:,ivperp,iz,ir] -= num_diss_params.electron.vpa_dissipation_coefficient * vpa.scratch2 + @views second_derivative!(vpa.scratch, pdf_in[:,ivperp,iz,ir], vpa, vpa_spectral) + @. pdf_out[:,ivperp,iz,ir] += dt * num_diss_params.electron.vpa_dissipation_coefficient * vpa.scratch + end + #stop() + return nothing +end + +""" +update_electron_pdf! iterates to find a solution for the electron pdf +from the electron kinetic equation: + zdot * d(pdf^{i})/dz + wpadot^{i} * d(pdf^{i})/dwpa + wpedot^{i} * d(pdf^{i})/dwpe = pdf^{i+1} * pre_factor^{i} +NB: zdot, wpadot, wpedot and pre-factor contain electron fluid quantities, that will have been updated +independently of the electron pdf; +NB: wpadot, wpedot and pre-factor all contain the electron parallel heat flux, +which itself depends on the electron pdf. + +INPUTS: + pdf = modified electron pdf @ previous time level = (true electron pdf / dens_e) * vth_e +OUTPUT: + pdf = updated (modified) electron pdf +""" +# function update_electron_pdf!(pdf, dens, vthe, ppar, ddens_dz, dppar_dz, dqpar_dz, dvth_dz, +# max_electron_pdf_iterations, z, vpa, z_spectral, vpa_spectral, scratch_dummy) +# # iterate the electron kinetic equation until the electron pdf is converged +# # or the specified maximum number of iterations is exceeded +# electron_pdf_converged = false +# iteration = 1 + +# println("pdf_update_electron_pdf: ", sum(abs.(pdf))) +# while !electron_pdf_converged && (iteration < max_electron_pdf_iterations) +# # calculate the contribution to the kinetic equation due to z advection +# # and store in the dummy array scratch_dummy.buffer_vpavperpzr_1 +# @views calculate_contribution_from_z_advection!(scratch_dummy.buffer_vpavperpzr_1, pdf, vthe, z, vpa.grid, z_spectral, scratch_dummy) +# # calculate the contribution to the kinetic equation due to w_parallel advection and add to the z advection term +# @views add_contribution_from_wpa_advection!(scratch_dummy.buffer_vpavperpzr_1, pdf, vthe, ppar, dppar_dz, dqpar_dz, dvth_dz, vpa, vpa_spectral) +# # calculate the pre-factor multiplying the modified electron pdf +# # and store in the dummy array scratch_dummy.buffer_vpavperpzr_2 +# @views calculate_pdf_dot_prefactor!(scratch_dummy.buffer_vpavperpzr_2, ppar, vthe, dens, ddens_dz, dvth_dz, dqpar_dz, vpa.grid) +# # update the electron pdf +# #pdf_new = scratch_dummy.buffer_vpavperpzr_1 +# #println("advection_terms: ", sum(abs.(advection_terms)), "pdf_dot_prefactor: ", sum(abs.(pdf_dot_prefactor))) +# @. scratch_dummy.buffer_vpavperpzr_1 /= scratch_dummy.buffer_vpavperpzr_2 +# # check to see if the electron pdf has converged to within the specified tolerance +# check_electron_pdf_convergence!(electron_pdf_converged, scratch_dummy.buffer_vpavperpzr_1, pdf) +# # prepare for the next iteration +# @. pdf = scratch_dummy.buffer_vpavperpzr_1 +# iteration += 1 +# end +# if !electron_pdf_converged +# # need to exit or handle this appropriately +# println("!!!max number of iterations for electron pdf update exceeded!!!") +# end +# return nothing +# end + + + +# """ +# use the biconjugate gradient stabilized method to solve the electron kinetic equation +# """ +# function update_electron_pdf_bicgstab!(pdf, dens, vthe, ppar, ddens_dz, dppar_dz, dqpar_dz, dvth_dz, +# max_electron_pdf_iterations, z, vpa, z_spectral, vpa_spectral, scratch_dummy) + +# for iz in 1:z.n +# println("z: ", z.grid[iz], " pdf: ", pdf[10, 1, iz, 1], " iteration: ", 1, " z.n: ", z.n) +# end + +# # set various arrays to point to a corresponding dummy array scratch_dummy.buffer_vpavperpzr_X +# # so that it is easier to understand what is going on +# residual = scratch_dummy.buffer_vpavperpzr_1 +# residual_hat = scratch_dummy.buffer_vpavperpzr_2 +# pvec = scratch_dummy.buffer_vpavperpzr_3 +# vvec = scratch_dummy.buffer_vpavperpzr_4 +# tvec = scratch_dummy.buffer_vpavperpzr_5 + +# # calculate the residual of the electron kinetic equation for the initial guess of the electron pdf +# electron_kinetic_equation_residual!(residual, pdf, dens, vthe, ppar, ddens_dz, dppar_dz, dqpar_dz, dvth_dz, +# z, vpa, z_spectral, vpa_spectral, scratch_dummy) +# @. residual = -residual + +# for iz in 1:z.n +# println("z: ", z.grid[iz], " residual: ", residual[10, 1, iz, 1], " iteration: ", -1, " z.n: ", z.n) +# end + +# residual_hat .= residual + pdf + +# # calculate the inner product of the residual and residual_hat +# rho = dot(residual_hat, residual) +# #@views rho = integral(residual_hat[10, 1, :, 1], residual[10,1,:,1], z.wgts) + +# println("rho: ", rho) + +# pvec .= residual + +# # iterate the electron kinetic equation until the electron pdf is converged +# # or the specified maximum number of iterations is exceeded +# electron_pdf_converged = false +# iteration = 1 + +# while !electron_pdf_converged && (iteration < max_electron_pdf_iterations) +# # calculate the residual of the electron kinetic equation with the residual of the previous iteration +# # as the input electron pdf +# electron_kinetic_equation_residual!(vvec, pvec, dens, vthe, ppar, ddens_dz, dppar_dz, dqpar_dz, dvth_dz, +# z, vpa, z_spectral, vpa_spectral, scratch_dummy) +# alpha = rho / dot(residual_hat, vvec) +# #alpha = rho / integral(residual_hat[10,1,:,1], vvec[10,1,:,1], z.wgts) +# println("alpha: ", alpha) +# @. pdf += alpha * pvec +# @. residual -= alpha * vvec +# # check to see if the electron pdf has converged to within the specified tolerance +# check_electron_pdf_convergence!(electron_pdf_converged, residual) +# if electron_pdf_converged +# break +# end +# electron_kinetic_equation_residual!(tvec, residual, dens, vthe, ppar, ddens_dz, dppar_dz, dqpar_dz, dvth_dz, +# z, vpa, z_spectral, vpa_spectral, scratch_dummy) +# omega = dot(tvec, residual) / dot(tvec, tvec) +# #omega = integral(tvec[10,1,:,1],residual[10,1,:,1],z.wgts)/integral(tvec[10,1,:,1],tvec[10,1,:,1],z.wgts) +# @. pdf += omega * residual +# @. residual -= omega * tvec +# check_electron_pdf_convergence!(electron_pdf_converged, residual) +# if electron_pdf_converged +# break +# end +# #rho_new = integral(residual_hat[10,1,:,1],residual[10,1,:,1],z.wgts) +# rho_new = dot(residual_hat, residual) +# beta = (rho_new / rho) * (alpha / omega) +# println("rho_new: ", rho_new, " beta: ", beta) +# #@. pvec -= omega * vvec +# #@. pvec *= beta +# #@. pvec += residual +# @. pvec = residual + beta * (pvec - omega * vvec) +# # prepare for the next iteration +# rho = rho_new +# iteration += 1 +# println() +# if iteration == 2 +# for iz in 1:z.n +# println("z: ", z.grid[iz], " pvec: ", pvec[10, 1, iz, 1], " iteration: ", -2, " z.n: ", z.n) +# end +# end +# for iz in 1:z.n +# println("z: ", z.grid[iz], " pdf: ", pdf[10, 1, iz, 1], " iteration: ", iteration, " z.n: ", z.n) +# end +# end +# if !electron_pdf_converged +# # need to exit or handle this appropriately +# println("!!!max number of iterations for electron pdf update exceeded!!!") +# end +# return nothing +# end + +""" +calculates the contribution to the residual of the electron kinetic equation from the z advection term: +residual = zdot * d(pdf)/dz + wpadot * d(pdf)/dwpa - pdf * pre_factor +INPUTS: + z_advection_term = dummy array to be filled with the contribution to the residual from the z advection term + pdf = modified electron pdf used in the kinetic equation = (true electron pdf / dens_e) * vth_e + vthe = electron thermal speed + z = z grid + vpa = v_parallel grid + z_spectral = spectral representation of the z grid + scratch_dummy = dummy array to be used for temporary storage +OUTPUT: + z_advection_term = updated contribution to the residual from the z advection term +""" +function calculate_contribution_from_z_advection!(z_advection_term, pdf, vthe, z, vpa, spectral, scratch_dummy) + # calculate the z-derivative of the electron pdf and store in the dummy array + # scratch_dummy.buffer_vpavperpzr_1 + derivative_z!(z_advection_term, pdf, + scratch_dummy.buffer_vpavperpr_1, + scratch_dummy.buffer_vpavperpr_2, scratch_dummy.buffer_vpavperpr_3, + scratch_dummy.buffer_vpavperpr_4, spectral, z) + # contribution from the z-advection term is wpa * vthe * d(pdf)/dz + begin_r_vperp_vpa_region() + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + @. z_advection_term[ivpa,ivperp,iz,ir] = z_advection_term[ivpa,ivperp,iz,ir] #* vpa[ivpa] * vthe[:, :] + end + return nothing +end + +function add_contribution_from_wpa_advection!(residual, pdf, vth, ppar, dppar_dz, dqpar_dz, dvth_dz, + vpa, vpa_spectral) + begin_r_z_vperp_region() + @loop_r_z_vperp ir iz ivperp begin + # calculate the wpa-derivative of the pdf and store in the scratch array vpa.scratch + @views derivative!(vpa.scratch, pdf[:, ivperp, iz, ir], vpa, vpa_spectral) + # contribution from the wpa-advection term is ( ) * d(pdf)/dwpa + @. residual[:,ivperp,iz,ir] += vpa.scratch * (0.5 * vth[iz,ir] / ppar[iz,ir] * dppar_dz[iz,ir] + + 0.5 * vpa.grid / ppar[iz,ir] * dqpar_dz[iz,ir] - vpa.grid^2 * dvth_dz[iz,ir]) + end + return nothing +end + +# calculate the pre-factor multiplying the modified electron pdf +function calculate_pdf_dot_prefactor!(pdf_dot_prefactor, ppar, vth, dens, ddens_dz, dvth_dz, dqpar_dz, vpa) + begin_r_z_vperp_vpa_region() + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + @. pdf_dot_prefactor[ivpa,ivperp,iz,ir] = 0.5 * dqpar_dz[iz,ir] / ppar[iz,ir] - vpa[ivpa] * vth[iz,ir] * + (ddens_dz[iz,ir] / dens[iz,ir] + dvth_dz[iz,ir] / vth[iz,ir]) + end + return nothing +end + +# add contribution to the residual coming from the term proporational to the pdf +function add_contribution_from_pdf_term!(pdf_out, pdf_in, ppar, dens, upar, moments, vpa, + z, dt, electron_source_settings) + vth = moments.electron.vth + ddens_dz = moments.electron.ddens_dz + dvth_dz = moments.electron.dvth_dz + dqpar_dz = moments.electron.dqpar_dz + begin_r_z_vperp_vpa_region() + @loop_r_z ir iz begin + this_dqpar_dz = dqpar_dz[iz,ir] + this_ppar = ppar[iz,ir] + this_vth = vth[iz,ir] + this_ddens_dz = ddens_dz[iz,ir] + this_dens = dens[iz,ir] + this_dvth_dz = dvth_dz[iz,ir] + this_vth = vth[iz,ir] + @loop_vperp_vpa ivperp ivpa begin + pdf_out[ivpa,ivperp,iz,ir] += + dt * (-0.5 * this_dqpar_dz / this_ppar - vpa[ivpa] * this_vth * + (this_ddens_dz / this_dens - this_dvth_dz / this_vth)) * + pdf_in[ivpa,ivperp,iz,ir] + #pdf_out[ivpa, ivperp, :, :] -= (-0.5 * dqpar_dz[:, :] / ppar[:, :]) * pdf_in[ivpa, ivperp, :, :] + end + end + + if electron_source_settings.active + source_density_amplitude = moments.electron.external_source_density_amplitude + source_momentum_amplitude = moments.electron.external_source_momentum_amplitude + source_pressure_amplitude = moments.electron.external_source_pressure_amplitude + @loop_r_z ir iz begin + term = dt * (1.5 * source_density_amplitude[iz,ir] / dens[iz,ir] - + (0.5 * source_pressure_amplitude[iz,ir] + + source_momentum_amplitude[iz,ir]) / ppar[iz,ir]) + @loop_vperp_vpa ivperp ivpa begin + pdf_out[ivpa,ivperp,iz,ir] -= term * pdf_in[ivpa,ivperp,iz,ir] + end + end + end + + return nothing +end + +# function check_electron_pdf_convergence!(electron_pdf_converged, pdf_new, pdf) +# # check to see if the electron pdf has converged to within the specified tolerance +# # NB: the convergence criterion is based on the average relative difference between the +# # new and old electron pdfs +# println("sum(abs(pdf)): ", sum(abs.(pdf)), " sum(abs(pdf_new)): ", sum(abs.(pdf_new))) +# println("sum(abs(pdfdiff)): ", sum(abs.(pdf_new - pdf)) / sum(abs.(pdf)), " length: ", length(pdf)) +# average_relative_error = sum(abs.(pdf_new - pdf)) / sum(abs.(pdf)) +# electron_pdf_converged = (average_relative_error < 1e-3) +# println("average_relative_error: ", average_relative_error, " electron_pdf_converged: ", electron_pdf_converged) +# return nothing +# end + +function get_electron_critical_velocities(phi, vthe, me_over_mi, z) + # get the critical velocities beyond which electrons are lost to the wall + if z.irank == 0 && block_rank[] == 0 + crit_speed_zmin = sqrt(phi[1, 1] / (me_over_mi * vthe[1, 1]^2)) + else + crit_speed_zmin = 0.0 + end + @serial_region begin + crit_speed_zmin = MPI.Bcast(crit_speed_zmin, 0, z.comm) + end + crit_speed_zmin = MPI.Bcast(crit_speed_zmin, 0, comm_block[]) + + if z.irank == z.nrank - 1 && block_rank[] == 0 + crit_speed_zmax = -sqrt(max(phi[end, 1],0.0) / (me_over_mi * vthe[end, 1]^2)) + else + crit_speed_zmin = 0.0 + end + @serial_region begin + crit_speed_zmax = MPI.Bcast(crit_speed_zmax, z.nrank-1, z.comm) + end + crit_speed_zmax = MPI.Bcast(crit_speed_zmax, 0, comm_block[]) + + return crit_speed_zmin, crit_speed_zmax +end + +function check_electron_pdf_convergence(residual, pdf, upar, vthe, z, vpa) + #average_residual = 0.0 + # @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + # if pdf[ivpa, ivperp, iz, ir] > eps(mk_float) + # average_residual += abs(residual[ivpa, ivperp, iz, ir]) / pdf[ivpa, ivperp, iz, ir] + # else + # average_residual += abs(residual[ivpa, ivperp, iz, ir]) + # end + # average_residual /= length(residual) + # end + + # Only sum residual over points that are not set by the sheath boundary condition, as + # those that are set by the sheath boundary condition are not used by the time + # advance, and so might not converge to 0. + # First, sum the contributions from the bulk of the domain + begin_r_z_vperp_region() + sum_residual = 0.0 + sum_pdf = 0.0 + @loop_r_z ir iz begin + if z.irank == 0 && iz == 1 + vpa_unnorm_lower = @. vpa.scratch3 = vthe[1,ir] * vpa.grid + upar[1,ir] + iv0_end = findfirst(x -> x>0.0, vpa_unnorm_lower) - 1 + else + iv0_end = vpa.n + end + if z.irank == z.nrank-1 && iz == z.n + vpa_unnorm_upper = @. vpa.scratch3 = vthe[end,ir] * vpa.grid + upar[end,ir] + iv0_start = findlast(x -> x>0.0, vpa_unnorm_upper) + 1 + else + iv0_start = 1 + end + @loop_vperp ivperp begin + sum_residual += sum(abs.(@view residual[iv0_start:iv0_end,ivperp,iz,ir])) + # account for the fact that we want dg/dt << vthe/L * g, but + # residual is normalized by c_ref/L_ref * g + sum_pdf += sum(abs.(@view pdf[iv0_start:iv0_end,ivperp,iz,ir]) * vthe[iz,ir]) + end + end + sum_residual, sum_pdf = MPI.Allreduce([sum_residual, sum_pdf], +, comm_world) + + average_residual = sum_residual / sum_pdf + + electron_pdf_converged = (average_residual < 1e-3) + + return average_residual, electron_pdf_converged +end + +function check_electron_pdf_convergence(residual) + begin_r_z_vperp_region() + sum_residual = 0.0 + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + sum_residual += abs.(residual[ivpa,ivperp,iz,ir]) + end + sum_residual, sum_length = MPI.Allreduce((sum_residual, length(residual) / block_size[]), +, comm_world) + average_residual = sum_residual / sum_length + electron_pdf_converged = (average_residual < 1e-3) + return average_residual, electron_pdf_converged +end + +end diff --git a/moment_kinetics/src/electron_vpa_advection.jl b/moment_kinetics/src/electron_vpa_advection.jl new file mode 100644 index 000000000..2d7968286 --- /dev/null +++ b/moment_kinetics/src/electron_vpa_advection.jl @@ -0,0 +1,88 @@ +""" +""" +module electron_vpa_advection + +export electron_vpa_advection! +export update_electron_speed_vpa! + +using ..looping +using ..calculus: derivative!, second_derivative! + +""" +calculate the wpa-advection term for the electron kinetic equation += (vthe / 2 ppare * dppare/dz + wpa / 2 ppare * dqpare/dz - wpa^2 * dvthe/dz) * df/dwpa +""" +function electron_vpa_advection!(pdf_out, pdf_in, density, upar, ppar, moments, advect, + vpa, spectral, scratch_dummy, dt, + electron_source_settings) + begin_r_z_vperp_region() + + # create a reference to a scratch_dummy array to store the wpa-derivative of the electron pdf + dpdf_dvpa = scratch_dummy.buffer_vpavperpzr_1 + #d2pdf_dvpa2 = scratch_dummy.buffer_vpavperpzr_2 + begin_r_z_vperp_region() + # get the updated speed along the wpa direction using the current pdf + @views update_electron_speed_vpa!(advect[1], density, upar, ppar, moments, vpa.grid, + electron_source_settings) + # update adv_fac -- note that there is no factor of dt here because + # in some cases the electron kinetic equation is solved as a steady-state equation iteratively + @loop_r_z_vperp ir iz ivperp begin + @views @. advect[1].adv_fac[:,ivperp,iz,ir] = -advect[1].speed[:,ivperp,iz,ir] + end + #calculate the upwind derivative of the electron pdf w.r.t. wpa + @loop_r_z_vperp ir iz ivperp begin + @views derivative!(dpdf_dvpa[:,ivperp,iz,ir], pdf_in[:,ivperp,iz,ir], vpa, + advect[1].adv_fac[:,ivperp,iz,ir], spectral) + end + #@loop_r_z_vperp ir iz ivperp begin + # @views second_derivative!(d2pdf_dvpa2[:,ivperp,iz,ir], pdf_in[:,ivperp,iz,ir], vpa, spectral) + #end + # calculate the advection term + @loop_r_z_vperp ir iz ivperp begin + @. pdf_out[:,ivperp,iz,ir] += dt * advect[1].adv_fac[:,ivperp,iz,ir] * dpdf_dvpa[:,ivperp,iz,ir] + #@. pdf_out[:,ivperp,iz,ir] -= advect[1].adv_fac[:,ivperp,iz,ir] * dpdf_dvpa[:,ivperp,iz,ir] + 0.0001*d2pdf_dvpa2[:,ivperp,iz,ir] + end + #@loop_vpa ivpa begin + # println("electron_vpa_advection: ", pdf_out[ivpa,1,10,1], " vpa: ", vpa.grid[ivpa], " dpdf_dvpa: ", dpdf_dvpa[ivpa,1,10,1], + # " pdf: ", pdf[ivpa,1,10,1]) + #end + #exit() + return nothing +end + +""" +calculate the electron advection speed in the wpa-direction at each grid point +""" +function update_electron_speed_vpa!(advect, density, upar, ppar, moments, vpa, + electron_source_settings) + vth = moments.electron.vth + dppar_dz = moments.electron.dppar_dz + dqpar_dz = moments.electron.dqpar_dz + dvth_dz = moments.electron.dvth_dz + # calculate the advection speed in wpa + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + # TMP FOR TESTING + #advect.speed[ivpa,ivperp,iz,ir] = vth[iz,ir] * dppar_dz[iz,ir] / (2 * ppar[iz,ir]) + advect.speed[ivpa,ivperp,iz,ir] = ((vth[iz,ir] * dppar_dz[iz,ir] + vpa[ivpa] * dqpar_dz[iz,ir]) + / (2 * ppar[iz,ir]) - vpa[ivpa]^2 * dvth_dz[iz,ir]) + end + if electron_source_settings.active + source_density_amplitude = moments.electron.external_source_density_amplitude + source_momentum_amplitude = moments.electron.external_source_momentum_amplitude + source_pressure_amplitude = moments.electron.external_source_pressure_amplitude + @loop_r_z ir iz begin + term1 = source_density_amplitude[iz,ir] * upar[iz,ir]/(density[iz,ir]*vth[iz,ir]) + term2_over_vpa = + -0.5 * (source_pressure_amplitude[iz,ir] + + 2.0 * upar[iz,ir] * source_momentum_amplitude[iz,ir]) / + ppar[iz,ir] + + 0.5 * source_density_amplitude[iz,ir] / density[iz,ir] + @loop_vperp_vpa ivperp ivpa begin + advect.speed[ivpa,ivperp,iz,ir] += term1 + vpa[ivpa] * term2_over_vpa + end + end + end + return nothing +end + +end diff --git a/moment_kinetics/src/electron_z_advection.jl b/moment_kinetics/src/electron_z_advection.jl new file mode 100644 index 000000000..b0b8f26aa --- /dev/null +++ b/moment_kinetics/src/electron_z_advection.jl @@ -0,0 +1,61 @@ +""" +""" +module electron_z_advection + +export electron_z_advection! +export update_electron_speed_z! + +using ..advection: advance_f_df_precomputed! +using ..chebyshev: chebyshev_info +using ..looping +using ..derivatives: derivative_z! +using ..calculus: second_derivative!, derivative! + +""" +calculate the z-advection term for the electron kinetic equation = wpa * vthe * df/dz +""" +function electron_z_advection!(pdf_out, pdf_in, upar, vth, advect, z, vpa, spectral, + scratch_dummy, dt) + begin_r_vperp_vpa_region() + + # create a pointer to a scratch_dummy array to store the z-derivative of the electron pdf + dpdf_dz = scratch_dummy.buffer_vpavperpzr_1 + d2pdf_dz2 = scratch_dummy.buffer_vpavperpzr_2 + begin_r_vperp_vpa_region() + # get the updated speed along the z direction using the current pdf + @views update_electron_speed_z!(advect[1], upar, vth, vpa) + # update adv_fac -- note that there is no factor of dt here because + # in some cases the electron kinetic equation is solved as a steady-state equation iteratively + @loop_r_vperp_vpa ir ivperp ivpa begin + @views advect[1].adv_fac[:,ivpa,ivperp,ir] = -advect[1].speed[:,ivpa,ivperp,ir] + end + #calculate the upwind derivative + derivative_z!(dpdf_dz, pdf_in, + advect, scratch_dummy.buffer_vpavperpr_1, + scratch_dummy.buffer_vpavperpr_2, scratch_dummy.buffer_vpavperpr_3, + scratch_dummy.buffer_vpavperpr_4, scratch_dummy.buffer_vpavperpr_5, + scratch_dummy.buffer_vpavperpr_6, spectral, z) + #@loop_r_vperp_vpa ir ivperp ivpa begin + # @views second_derivative!(d2pdf_dz2[ivpa,ivperp,:,ir], pdf[ivpa,ivperp,:,ir], z, spectral) + #end + # calculate the advection term + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + pdf_out[ivpa,ivperp,iz,ir] += dt * advect[1].adv_fac[iz,ivpa,ivperp,ir] * dpdf_dz[ivpa,ivperp,iz,ir] + #pdf_out[ivpa,ivperp,iz,ir] += dt * advect[1].adv_fac[iz,ivpa,ivperp,ir] * dpdf_dz[ivpa,ivperp,iz,ir] + 0.0001*d2pdf_dz2[ivpa,ivperp,iz,ir] + end + return nothing +end + +""" +calculate the electron advection speed in the z-direction at each grid point +""" +function update_electron_speed_z!(advect, upar, vth, vpa) + # the electron advection speed in z is v_par = w_par * v_the + @loop_r_vperp_vpa ir ivperp ivpa begin + #@. @views advect.speed[:,ivpa,ivperp,ir] = vpa[ivpa] * vth[:,ir] + @. @views advect.speed[:,ivpa,ivperp,ir] = vpa[ivpa] * vth[:,ir] + upar[:,ir] + end + return nothing +end + +end diff --git a/moment_kinetics/src/em_fields.jl b/moment_kinetics/src/em_fields.jl index 6591e9062..9f26f009c 100644 --- a/moment_kinetics/src/em_fields.jl +++ b/moment_kinetics/src/em_fields.jl @@ -14,7 +14,11 @@ using ..moment_kinetics_structs: em_fields_struct using ..velocity_moments: update_density! #using ..calculus: derivative! using ..derivatives: derivative_r!, derivative_z! +using ..electron_fluid_equations: calculate_Epar_from_electron_force_balance! using ..gyroaverages: gyro_operators, gyroaverage_field! + +using MPI + """ """ function setup_em_fields(nvperp, nz, nr, n_ion_species, force_phi, drive_amplitude, drive_frequency, force_Er_zero) @@ -31,8 +35,11 @@ end """ update_phi updates the electrostatic potential, phi """ -function update_phi!(fields, fvec, vperp, z, r, composition, z_spectral, r_spectral, scratch_dummy, gyroavs::gyro_operators) +function update_phi!(fields, fvec, vperp, z, r, composition, collisions, moments, + geometry, z_spectral, r_spectral, scratch_dummy, + gyroavs::gyro_operators) n_ion_species = composition.n_ion_species + # check bounds of fields and fvec arrays @boundscheck size(fields.phi,1) == z.n || throw(BoundsError(fields.phi)) @boundscheck size(fields.phi,2) == r.n || throw(BoundsError(fields.phi)) @boundscheck size(fields.phi0,1) == z.n || throw(BoundsError(fields.phi0)) @@ -50,9 +57,11 @@ function update_phi!(fields, fvec, vperp, z, r, composition, z_spectral, r_spect # when there is only one species. begin_serial_region()#(no_synchronize=true) + + dens_e = fvec.electron_density # in serial as both s, r and z required locally if (composition.n_ion_species > 1 || true || - composition.electron_physics == boltzmann_electron_response_with_simple_sheath) + composition.electron_physics ∈ (boltzmann_electron_response_with_simple_sheath)) # If there is more than 1 ion species, the ranks that handle species 1 have to # read density for all the other species, so need to synchronize here. # If composition.electron_physics == @@ -66,19 +75,17 @@ function update_phi!(fields, fvec, vperp, z, r, composition, z_spectral, r_spect # for each radial position in parallel if possible # TODO: parallelise this... - @serial_region begin - ne = @view scratch_dummy.dummy_zrs[:,:,1] - jpar_i = @view scratch_dummy.buffer_rs_1[:,:,1] - N_e = @view scratch_dummy.buffer_rs_2[:,:,1] - ne .= 0.0 - for is in 1:n_ion_species - @loop_r_z ir iz begin - ne[iz,ir] += fvec.density[iz,ir,is] - end - end - if composition.electron_physics == boltzmann_electron_response + ne = @view scratch_dummy.dummy_zrs[:,:,1] + jpar_i = @view scratch_dummy.buffer_rs_1[:,:,1] + N_e = @view scratch_dummy.buffer_rs_2[:,:,1] + if composition.electron_physics == boltzmann_electron_response + begin_serial_region() + @serial_region begin N_e .= 1.0 - elseif composition.electron_physics == boltzmann_electron_response_with_simple_sheath + end + elseif composition.electron_physics == boltzmann_electron_response_with_simple_sheath + begin_serial_region() + @serial_region begin # calculate Sum_{i} Z_i n_i u_i = J_||i at z = 0 jpar_i .= 0.0 for is in 1:n_ion_species @@ -101,11 +108,18 @@ function update_phi!(fields, fvec, vperp, z, r, composition, z_spectral, r_spect end # See P.C. Stangeby, The Plasma Boundary of Magnetic Fusion Devices, IOP Publishing, Chpt 2, p75 end - if composition.electron_physics ∈ (boltzmann_electron_response, boltzmann_electron_response_with_simple_sheath) - @loop_r_z ir iz begin - fields.phi[iz,ir] = composition.T_e * log(ne[iz,ir] / N_e[ir]) - end + end + if composition.electron_physics ∈ (boltzmann_electron_response, boltzmann_electron_response_with_simple_sheath) + @loop_r_z ir iz begin + fields.phi[iz,ir] = composition.T_e * log(dens_e[iz,ir] / N_e[ir]) end + elseif composition.electron_physics ∈ (braginskii_fluid, kinetic_electrons, + kinetic_electrons_with_temperature_equation) + calculate_Epar_from_electron_force_balance!(fields.Ez, dens_e, moments.electron.dppar_dz, + collisions.nu_ei, moments.electron.parallel_friction, + composition.n_neutral_species, collisions.charge_exchange_electron, composition.me_over_mi, + fvec.density_neutral, fvec.uz_neutral, fvec.electron_upar) + calculate_phi_from_Epar!(fields.phi, fields.Ez, r, z) end ## can calculate phi at z = L and hence phi_wall(z=L) using jpar_i at z =L if needed _block_synchronize() @@ -125,22 +139,22 @@ function update_phi!(fields, fvec, vperp, z, r, composition, z_spectral, r_spect end else @loop_r_z ir iz begin - fields.Er[iz,ir] = composition.Er_constant - # Er_constant defaults to 0.0 in moment_kinetics_input.jl + fields.Er[iz,ir] = geometry.input.Er_constant + # Er_constant defaults to 0.0 in geo.jl end end - #Ez = - d phi / dz - if z.n > 1 - derivative_z!(fields.Ez,-fields.phi, - scratch_dummy.buffer_r_1, scratch_dummy.buffer_r_2, - scratch_dummy.buffer_r_3, scratch_dummy.buffer_r_4, - z_spectral,z) - else - @serial_region begin - fields.Ez[:,:] .= 0.0 + # if advancing electron fluid equations, solve for Ez directly from force balance + if composition.electron_physics ∉ (braginskii_fluid, kinetic_electrons, + kinetic_electrons_with_temperature_equation) + if z.n > 1 + # Ez = - d phi / dz + @views derivative_z!(fields.Ez,-fields.phi, + scratch_dummy.buffer_rs_1[:,1], scratch_dummy.buffer_rs_2[:,1], + scratch_dummy.buffer_rs_3[:,1], scratch_dummy.buffer_rs_4[:,1], + z_spectral,z) end end - + # get gyroaveraged field arrays for distribution function advance gkions = composition.gyrokinetic_ions if gkions @@ -155,6 +169,52 @@ function update_phi!(fields, fvec, vperp, z, r, composition, z_spectral, r_spect end end + return nothing +end + +function calculate_phi_from_Epar!(phi, Epar, r, z) + # simple calculation of phi from Epar for now. Assume phi is already set at the + # lower-ze boundary, e.g. by the kinetic electron boundary condition. + begin_serial_region() + + dz = z.cell_width + @serial_region begin + # Need to broadcast the lower-z boundary value, because we only communicate + # delta_phi below, rather than passing the boundary values directly from block to + # block. + MPI.Bcast!(@view(phi[1,:]), z.comm; root=0) + + if z.irank == z.nrank - 1 + # Don't want to change the upper-z boundary value, so save it here so we can + # restore it at the end + @views @. r.scratch = phi[end,:] + end + + @loop_r ir begin + for iz ∈ 2:z.n + phi[iz,ir] = phi[iz-1,ir] - dz[iz-1]*Epar[iz,ir] + end + end + + # Add contributions to integral along z from processes at smaller z-values than + # this one. + this_delta_phi = r.scratch2 .= phi[end,:] .- phi[1,:] + for irank ∈ 0:z.nrank-2 + MPI.Bcast!(this_delta_phi, z.comm; root=irank) + if z.irank > irank + @loop_r_z ir iz begin + phi[iz,ir] += this_delta_phi[ir] + end + end + end + + if z.irank == z.nrank - 1 + # Restore the upper-z boundary value + @views @. phi[end,:] = r.scratch + end + end + + return nothing end end diff --git a/moment_kinetics/src/external_sources.jl b/moment_kinetics/src/external_sources.jl index 08b23bd5f..98fe4169c 100644 --- a/moment_kinetics/src/external_sources.jl +++ b/moment_kinetics/src/external_sources.jl @@ -20,7 +20,7 @@ using ..array_allocation: allocate_float, allocate_shared_float using ..calculus using ..communication using ..coordinates -using ..input_structs: set_defaults_and_check_section!, Dict_to_NamedTuple +using ..input_structs using ..looping using ..velocity_moments: get_density @@ -39,7 +39,7 @@ and z-coordinates. Returns a NamedTuple `(ion=ion_source_settings, neutral=neutral_source_settings)` containing two NamedTuples of settings. """ -function setup_external_sources!(input_dict, r, z) +function setup_external_sources!(input_dict, r, z, electron_physics) function get_settings(neutrals) input = set_defaults_and_check_section!( input_dict, neutrals ? "neutral_source" : "ion_source"; @@ -199,7 +199,43 @@ function setup_external_sources!(input_dict, r, z) PI_density_target_rank=PI_density_target_rank) end - return (ion=get_settings(false), neutral=get_settings(true)) + function get_electron_settings(ion_settings) + # Note most settings for the electron source are copied from the ion source, + # because we require that the particle sources are the same for ions and + # electrons. `source_T` can be set independently, and when using + # `source_type="energy"`, the `source_strength` could also be set. + input = set_defaults_and_check_section!( + input_dict, "electron_source"; + source_strength=ion_settings.source_strength, + source_T=ion_settings.source_T, + ) + if ion_settings.source_type != "energy" + # Need to keep same amplitude for ions and electrons so there is no charge + # source. + if input["source_strength"] != ion_settings.source_strength + println("When not using source_type=\"energy\", source_strength for " + * "electrons must be equal to source_strength for ions to ensure " + * "no charge is injected by the source. Overriding electron " + * "source_strength...") + end + input["source_strength"] = ion_settings.source_strength + end + return (; (Symbol(k)=>v for (k,v) ∈ input)..., active=ion_settings.active, + r_amplitude=ion_settings.r_amplitude, + z_amplitude=ion_settings.z_amplitude, + source_type=ion_settings.source_type) + end + + ion_settings = get_settings(false) + if electron_physics ∈ (braginskii_fluid, kinetic_electrons, + kinetic_electrons_with_temperature_equation) + electron_settings = get_electron_settings(ion_settings) + else + electron_settings = (active=false,) + end + neutral_settings = get_settings(true) + + return (ion=ion_settings, electron=electron_settings, neutral=neutral_settings) end """ @@ -241,6 +277,10 @@ Initialize the arrays `moments.ion.external_source_amplitude`, `moments.ion.external_source_density_amplitude`, `moments.ion.external_source_momentum_amplitude`, `moments.ion.external_source_pressure_amplitude`, +`moments.electron.external_source_amplitude`, +`moments.electron.external_source_density_amplitude`, +`moments.electron.external_source_momentum_amplitude`, +`moments.electron.external_source_pressure_amplitude`, `moments.neutral.external_source_amplitude`, `moments.neutral.external_source_density_amplitude`, `moments.neutral.external_source_momentum_amplitude`, and @@ -317,6 +357,67 @@ function initialize_external_source_amplitude!(moments, external_source_settings end end + electron_source_settings = external_source_settings.electron + if electron_source_settings.active + if electron_source_settings.source_type == "energy" + @loop_r_z ir iz begin + moments.electron.external_source_amplitude[iz,ir] = + electron_source_settings.source_strength * + electron_source_settings.r_amplitude[ir] * + electron_source_settings.z_amplitude[iz] + end + @loop_r_z ir iz begin + moments.electron.external_source_density_amplitude[iz,ir] = 0.0 + end + @loop_r_z ir iz begin + moments.electron.external_source_momentum_amplitude[iz,ir] = + - moments.electron.dens[iz,ir] * moments.electron.upar[iz,ir] * + electron_source_settings.source_strength * + electron_source_settings.r_amplitude[ir] * + electron_source_settings.z_amplitude[iz] + end + @loop_r_z ir iz begin + moments.electron.external_source_pressure_amplitude[iz,ir] = + (0.5 * electron_source_settings.source_T + + moments.electron.upar[iz,ir]^2 - moments.electron.ppar[iz,ir]) * + electron_source_settings.source_strength * + electron_source_settings.r_amplitude[ir] * + electron_source_settings.z_amplitude[iz] + end + else + @loop_r_z ir iz begin + moments.electron.external_source_amplitude[iz,ir] = + moments.ion.external_source_amplitude[iz,ir] + end + if moments.evolve_density + @loop_r_z ir iz begin + moments.electron.external_source_density_amplitude[iz,ir] = + moments.ion.external_source_density_amplitude[iz,ir] + end + else + @loop_r_z ir iz begin + # Note set this using *ion* settings to force electron density source + # to always be equal to ion density source (even when + # evolve_density=false) to ensure the source does not inject charge + # into the simulation. + moments.electron.external_source_density_amplitude[iz,ir] = + ion_source_settings.source_strength * + ion_source_settings.r_amplitude[ir] * + ion_source_settings.z_amplitude[iz] + end + end + @loop_r_z ir iz begin + moments.electron.external_source_momentum_amplitude[iz,ir] = 0.0 + end + @loop_r_z ir iz begin + moments.electron.external_source_pressure_amplitude[iz,ir] = + (0.5 * electron_source_settings.source_T + + moments.electron.upar[iz,ir]^2) * + moments.electron.external_source_amplitude[iz,ir] + end + end + end + if n_neutral_species > 0 neutral_source_settings = external_source_settings.neutral if neutral_source_settings.active @@ -508,10 +609,18 @@ function external_ion_source!(pdf, fvec, moments, ion_source_settings, vperp, vp end if source_type == "energy" - # Take particles out of pdf so source does not change density - @loop_s_r_z_vperp_vpa is ir iz ivperp ivpa begin - pdf[ivpa,ivperp,iz,ir,is] -= dt * source_amplitude[iz,ir] * - fvec.pdf[ivpa,ivperp,iz,ir,is] + if moments.evolve_density + # Take particles out of pdf so source does not change density + @loop_s_r_z_vperp_vpa is ir iz ivperp ivpa begin + pdf[ivpa,ivperp,iz,ir,is] -= dt * source_amplitude[iz,ir] * + fvec.pdf[ivpa,ivperp,iz,ir,is] + end + else + # Take particles out of pdf so source does not change density + @loop_s_r_z_vperp_vpa is ir iz ivperp ivpa begin + pdf[ivpa,ivperp,iz,ir,is] -= dt * source_amplitude[iz,ir] * + fvec.pdf[ivpa,ivperp,iz,ir,is] / fvec.density[iz,ir,is] + end end end elseif source_type == "alphas" || source_type == "alphas-with-losses" @@ -638,6 +747,57 @@ function external_ion_source!(pdf, fvec, moments, ion_source_settings, vperp, vp return nothing end +""" + external_electron_source!(pdf, fvec, moments, electron_source_settings, vperp, + vpa, dt) + +Add external source term to the electron kinetic equation. +""" +function external_electron_source!(pdf_out, pdf_in, electron_density, electron_upar, + moments, composition, electron_source_settings, vperp, + vpa, dt) + begin_r_z_vperp_region() + + me_over_mi = composition.me_over_mi + + source_amplitude = moments.electron.external_source_amplitude + source_T = electron_source_settings.source_T + if vperp.n == 1 + vth_factor = 1.0 / sqrt(source_T / me_over_mi) + else + vth_factor = 1.0 / (source_T / me_over_mi)^1.5 + end + vpa_grid = vpa.grid + vperp_grid = vperp.grid + + vth = moments.electron.vth + @loop_r_z ir iz begin + this_vth = vth[iz,ir] + this_upar = electron_upar[iz,ir] + this_prefactor = dt * this_vth / electron_density[iz,ir] * vth_factor * + source_amplitude[iz,ir] + @loop_vperp_vpa ivperp ivpa begin + # Factor of 1/sqrt(π) (for 1V) or 1/π^(3/2) (for 2V/3V) is absorbed by the + # normalisation of F + vperp_unnorm = vperp_grid[ivperp] * this_vth + vpa_unnorm = vpa_grid[ivpa] * this_vth + this_upar + pdf_out[ivpa,ivperp,iz,ir] += + this_prefactor * + exp(-(vperp_unnorm^2 + vpa_unnorm^2) * me_over_mi / source_T) + end + end + + if electron_source_settings.source_type == "energy" + # Take particles out of pdf so source does not change density + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + pdf_out[ivpa,ivperp,iz,ir] -= dt * source_amplitude[iz,ir] * + pdf_in[ivpa,ivperp,iz,ir] + end + end + + return nothing +end + """ external_neutral_source!(pdf, fvec, moments, neutral_source_settings, vzeta, vr, vz, dt) @@ -745,12 +905,15 @@ function external_ion_source_controller!(fvec_in, moments, ion_source_settings, is = 1 ion_moments = moments.ion + density = fvec_in.density + upar = fvec_in.upar + ppar = fvec_in.ppar if ion_source_settings.source_type == "Maxwellian" if moments.evolve_ppar @loop_r_z ir iz begin ion_moments.external_source_pressure_amplitude[iz,ir] = - (0.5 * ion_source_settings.source_T + fvec_in.upar[iz,ir,is]^2) * + (0.5 * ion_source_settings.source_T + upar[iz,ir,is]^2) * ion_moments.external_source_amplitude[iz,ir] end end @@ -758,7 +921,7 @@ function external_ion_source_controller!(fvec_in, moments, ion_source_settings, if moments.evolve_upar @loop_r_z ir iz begin ion_moments.external_source_momentum_amplitude[iz,ir] = - - ion_moments.density[iz,ir] * ion_moments.upar[iz,ir] * + - density[iz,ir] * upar[iz,ir] * ion_source_settings.source_strength * ion_source_settings.r_amplitude[ir] * ion_source_settings.z_amplitude[iz] @@ -767,8 +930,7 @@ function external_ion_source_controller!(fvec_in, moments, ion_source_settings, if moments.evolve_ppar @loop_r_z ir iz begin ion_moments.external_source_pressure_amplitude[iz,ir] = - (0.5 * ion_source_settings.source_T + ion_moments.upar[iz,ir]^2 - - ion_moments.ppar[iz,ir]) * + (0.5 * ion_source_settings.source_T + upar[iz,ir]^2 - ppar[iz,ir]) * ion_source_settings.source_strength * ion_source_settings.r_amplitude[ir] * ion_source_settings.z_amplitude[iz] @@ -784,8 +946,8 @@ function external_ion_source_controller!(fvec_in, moments, ion_source_settings, ion_source_settings.PI_density_target_iz !== nothing # This process has the target point - n_mid = fvec_in.density[ion_source_settings.PI_density_target_iz, - ion_source_settings.PI_density_target_ir, is] + n_mid = density[ion_source_settings.PI_density_target_iz, + ion_source_settings.PI_density_target_ir, is] n_error = ion_source_settings.PI_density_target - n_mid ion_moments.external_source_controller_integral[1,1] += @@ -820,14 +982,13 @@ function external_ion_source_controller!(fvec_in, moments, ion_source_settings, if moments.evolve_ppar @loop_r_z ir iz begin ion_moments.external_source_pressure_amplitude[iz,ir] = - (0.5 * ion_source_settings.source_T + fvec_in.upar[iz,ir,is]^2) * + (0.5 * ion_source_settings.source_T + upar[iz,ir,is]^2) * amplitude * ion_source_settings.controller_source_profile[iz,ir] end end elseif ion_source_settings.source_type == "density_profile_control" begin_r_z_region() - density = fvec_in.density target = ion_source_settings.PI_density_target P = ion_source_settings.PI_density_controller_P I = ion_source_settings.PI_density_controller_I @@ -847,7 +1008,7 @@ function external_ion_source_controller!(fvec_in, moments, ion_source_settings, if moments.evolve_ppar @loop_r_z ir iz begin ion_moments.external_source_pressure_amplitude[iz,ir] = - (0.5 * ion_source_settings.source_T + fvec_in.upar[iz,ir,is]^2) * + (0.5 * ion_source_settings.source_T + upar[iz,ir,is]^2) * amplitude[iz,ir] end end @@ -866,6 +1027,75 @@ function external_ion_source_controller!(fvec_in, moments, ion_source_settings, return nothing end +""" + external_electron_source_controller!(fvec_in, moments, electron_source_settings, dt) + +Calculate the amplitude, e.g. when using a PI controller for the density to set the +external source amplitude. + +As the electron density source must be equal to the ion density source in order not to +inject charge into the simulation, the electron source (at least in some modes of +operation) depends on the ion source, so [`external_ion_source_controller`](@ref) must be +called before this function is called so that `moments.ion.external_source_amplitude` is +up to date. +""" +function external_electron_source_controller!(fvec_in, moments, electron_source_settings, + dt) + begin_r_z_region() + + is = 1 + electron_moments = moments.electron + ion_source_amplitude = moments.ion.external_source_amplitude + + if electron_source_settings.source_type == "Maxwellian" + @loop_r_z ir iz begin + electron_moments.external_source_pressure_amplitude[iz,ir] = + (0.5 * electron_source_settings.source_T + + fvec_in.electron_upar[iz,ir,is]^2) * + electron_moments.external_source_amplitude[iz,ir] + end + elseif electron_source_settings.source_type == "energy" + @loop_r_z ir iz begin + electron_moments.external_source_momentum_amplitude[iz,ir] = + - electron_moments.density[iz,ir] * electron_moments.upar[iz,ir] * + electron_source_settings.source_strength * + electron_source_settings.r_amplitude[ir] * + electron_source_settings.z_amplitude[iz] + end + @loop_r_z ir iz begin + electron_moments.external_source_pressure_amplitude[iz,ir] = + (0.5 * electron_source_settings.source_T + electron_moments.upar[iz,ir]^2 - + electron_moments.ppar[iz,ir]) * + electron_source_settings.source_strength * + electron_source_settings.r_amplitude[ir] * + electron_source_settings.z_amplitude[iz] + end + else + @loop_r_z ir iz begin + electron_moments.external_source_amplitude[iz,ir] = ion_source_amplitude[iz,ir] + end + @loop_r_z ir iz begin + electron_moments.external_source_momentum_amplitude[iz,ir] = + - electron_moments.density[iz,ir] * electron_moments.upar[iz,ir] * + electron_moments.external_source_amplitude[iz,ir] + end + @loop_r_z ir iz begin + electron_moments.external_source_pressure_amplitude[iz,ir] = + (0.5 * electron_source_settings.source_T + electron_moments.upar[iz,ir]^2 - + electron_moments.ppar[iz,ir]) * + electron_moments.external_source_amplitude[iz,ir] + end + end + + # Density source is always the same as the ion one + @loop_r_z ir iz begin + electron_moments.external_source_density_amplitude[iz,ir] = + moments.ion.external_source_density_amplitude[iz,ir] + end + + return nothing +end + """ external_neutral_source_controller!(fvec_in, moments, neutral_source_settings, r, z, dt) diff --git a/moment_kinetics/src/file_io.jl b/moment_kinetics/src/file_io.jl index df91d308c..5f6adab0d 100644 --- a/moment_kinetics/src/file_io.jl +++ b/moment_kinetics/src/file_io.jl @@ -41,6 +41,7 @@ struct ascii_ios{T <: Union{IOStream,Nothing}} # corresponds to the ascii file to which velocity space moments of the # distribution function such as density and pressure are written moments_ion::T + moments_electron::T moments_neutral::T # corresponds to the ascii file to which electromagnetic fields # such as the electrostatic potential are written @@ -51,10 +52,12 @@ end structure containing the data/metadata needed for binary file i/o moments & fields only """ -struct io_moments_info{Tfile, Ttime, Tphi, Tmomi, Tmomn, Tchodura_lower, +struct io_moments_info{Tfile, Ttime, Tphi, Tmomi, Tmome, Tmomn, Tchodura_lower, Tchodura_upper, Texti1, Texti2, Texti3, Texti4, - Texti5, Textn1, Textn2, Textn3, Textn4, Textn5, Tconstri, Tconstrn, - Tint, Tfailcause, Tnldiagnostics} + Texti5, Textn1, Textn2, Textn3, Textn4, Textn5, Texte1, Texte2, + Texte3, Texte4, Tconstri, Tconstrn, Tconstre, Tint, Tfailcause, + Telectrontime, Telectronint, Telectronfailcause, Tnldiagnostics, + Tinput} # file identifier for the binary file to which data is written fid::Tfile # handle for the time variable @@ -67,12 +70,28 @@ struct io_moments_info{Tfile, Ttime, Tphi, Tmomi, Tmomn, Tchodura_lower, Ez::Tphi # handle for the ion species density density::Tmomi + # low-order approximation, used to diagnose timestepping error + density_loworder::Union{Tmomi,Nothing} + # start of the last timestep before output, used to measure steady state residual + density_start_last_timestep::Union{Tmomi,Nothing} # handle for the ion species parallel flow parallel_flow::Tmomi + # low-order approximation, used to diagnose timestepping error + parallel_flow_loworder::Union{Tmomi,Nothing} + # start of the last timestep before output, used to measure steady state residual + parallel_flow_start_last_timestep::Union{Tmomi,Nothing} # handle for the ion species parallel pressure parallel_pressure::Tmomi + # low-order approximation, used to diagnose timestepping error + parallel_pressure_loworder::Union{Tmomi,Nothing} + # start of the last timestep before output, used to measure steady state residual + parallel_pressure_start_last_timestep::Union{Tmomi,Nothing} # handle for the ion species perpendicular pressure perpendicular_pressure::Tmomi + # low-order approximation, used to diagnose timestepping error + perpendicular_pressure_loworder::Union{Tmomi,Nothing} + # start of the last timestep before output, used to measure steady state residual + perpendicular_pressure_start_last_timestep::Union{Tmomi,Nothing} # handle for the ion species parallel heat flux parallel_heat_flux::Tmomi # handle for the ion species thermal speed @@ -83,10 +102,45 @@ struct io_moments_info{Tfile, Ttime, Tphi, Tmomi, Tmomn, Tchodura_lower, chodura_integral_lower::Tchodura_lower # handle for chodura diagnostic (upper) chodura_integral_upper::Tchodura_upper + # handle for the electron species density + electron_density::Tmome + # low-order approximation, used to diagnose timestepping error + electron_density_loworder::Union{Tmome,Nothing} + # start of the last timestep before output, used to measure steady state residual + electron_density_start_last_timestep::Union{Tmome,Nothing} + # handle for the electron species parallel flow + electron_parallel_flow::Tmome + # low-order approximation, used to diagnose timestepping error + electron_parallel_flow_loworder::Union{Tmome,Nothing} + # start of the last timestep before output, used to measure steady state residual + electron_parallel_flow_start_last_timestep::Union{Tmome,Nothing} + # handle for the electron species parallel pressure + electron_parallel_pressure::Tmome + # low-order approximation, used to diagnose timestepping error + electron_parallel_pressure_loworder::Union{Tmome,Nothing} + # start of the last timestep before output, used to measure steady state residual + electron_parallel_pressure_start_last_timestep::Union{Tmome,Nothing} + # handle for the electron species parallel heat flux + electron_parallel_heat_flux::Tmome + # handle for the electron species thermal speed + electron_thermal_speed::Tmome + # handle for the neutral species density density_neutral::Tmomn + # low-order approximation, used to diagnose timestepping error + density_neutral_loworder::Union{Tmomn,Nothing} + # start of the last timestep before output, used to measure steady state residual + density_neutral_start_last_timestep::Union{Tmomn,Nothing} uz_neutral::Tmomn + # low-order approximation, used to diagnose timestepping error + uz_neutral_loworder::Union{Tmomn,Nothing} + # start of the last timestep before output, used to measure steady state residual + uz_neutral_start_last_timestep::Union{Tmomn,Nothing} pz_neutral::Tmomn + # low-order approximation, used to diagnose timestepping error + pz_neutral_loworder::Union{Tmomn,Nothing} + # start of the last timestep before output, used to measure steady state residual + pz_neutral_start_last_timestep::Union{Tmomn,Nothing} qz_neutral::Tmomn thermal_speed_neutral::Tmomn @@ -101,6 +155,10 @@ struct io_moments_info{Tfile, Ttime, Tphi, Tmomi, Tmomn, Tchodura_lower, external_source_neutral_momentum_amplitude::Textn3 external_source_neutral_pressure_amplitude::Textn4 external_source_neutral_controller_integral::Textn5 + external_source_electron_amplitude::Texte1 + external_source_electron_density_amplitude::Texte2 + external_source_electron_momentum_amplitude::Texte3 + external_source_electron_pressure_amplitude::Texte4 # handles for constraint coefficients ion_constraints_A_coefficient::Tconstri @@ -109,6 +167,9 @@ struct io_moments_info{Tfile, Ttime, Tphi, Tmomi, Tmomn, Tchodura_lower, neutral_constraints_A_coefficient::Tconstrn neutral_constraints_B_coefficient::Tconstrn neutral_constraints_C_coefficient::Tconstrn + electron_constraints_A_coefficient::Tconstre + electron_constraints_B_coefficient::Tconstre + electron_constraints_C_coefficient::Tconstre # cumulative wall clock time taken by the run time_for_run::Ttime @@ -116,6 +177,8 @@ struct io_moments_info{Tfile, Ttime, Tphi, Tmomi, Tmomn, Tchodura_lower, step_counter::Tint # current timestep size dt::Ttime + # size of last timestep before output, used for some diagnostics + previous_dt::Ttime # cumulative number of timestep failures failure_counter::Tint # cumulative count of which variable caused a timstep failure @@ -125,34 +188,134 @@ struct io_moments_info{Tfile, Ttime, Tphi, Tmomi, Tmomn, Tchodura_lower, # Last successful timestep before most recent timestep failure, used by adaptve # timestepping algorithm dt_before_last_fail::Ttime + # cumulative number of electron pseudo-timesteps taken + electron_step_counter::Telectronint + # cumulative electron pseudo-time + electron_cumulative_pseudotime::Telectrontime + # current electron pseudo-timestep size + electron_dt::Telectrontime + # size of last electron pseudo-timestep before the output was written + electron_previous_dt::Telectrontime + # cumulative number of electron pseudo-timestep failures + electron_failure_counter::Telectronint + # cumulative count of which variable caused a electron pseudo-timstep failure + electron_failure_caused_by::Telectronfailcause + # cumulative count of which factors limited the electron pseudo-timestep at each step + electron_limit_caused_by::Telectronfailcause + # Last successful timestep before most recent electron pseudo-timestep failure, used + # by adaptive timestepping algorithm + electron_dt_before_last_fail::Telectrontime # Variables recording diagnostic information about non-linear solvers (used for # implicit parts of timestep). These are stored in nested NamedTuples so that we can # write diagnostics generically for as many nonlinear solvers as are created. nl_solver_diagnostics::Tnldiagnostics - # Use parallel I/O? - parallel_io::Bool + # Settings for I/O + io_input::Tinput end """ structure containing the data/metadata needed for binary file i/o distribution function data only """ -struct io_dfns_info{Tfile, Tfi, Tfn, Tmoments} +struct io_dfns_info{Tfile, Tfi, Tfe, Tfn, Tmoments, Tinput} # file identifier for the binary file to which data is written fid::Tfile # handle for the ion species distribution function variable f::Tfi + # low-order approximation to ion species distribution function, used to diagnose timestepping error + f_loworder::Union{Tfi,Nothing} + # ion species distribution function at the start of the last timestep before output, used to measure steady state residual + f_start_last_timestep::Union{Tfi,Nothing} + # handle for the electron distribution function variable + f_electron::Tfe + # low-order approximation to electron distribution function, used to diagnose timestepping error + f_electron_loworder::Union{Tfe,Nothing} + # electron distribution function at the start of the last timestep before output, used to measure steady state residual + f_electron_start_last_timestep::Union{Tfe,Nothing} # handle for the neutral species distribution function variable f_neutral::Tfn + # low-order approximation to neutral species distribution function, used to diagnose timestepping error + f_neutral_loworder::Union{Tfn,Nothing} + # neutral species distribution function at the start of the last timestep before output, used to measure steady state residual + f_neutral_start_last_timestep::Union{Tfn,Nothing} - # Use parallel I/O? - parallel_io::Bool + # Settings for I/O + io_input::Tinput # Handles for moment variables io_moments::Tmoments end +""" +structure containing the data/metadata needed for binary file i/o +for electron initialization +""" +struct io_initial_electron_info{Tfile, Tfe, Tmom, Texte1, Texte2, Texte3, Texte4, + Tconstr, Telectrontime, Telectronint, Telectronfailcause, + Tinput} + # file identifier for the binary file to which data is written + fid::Tfile + time::Telectrontime + # handle for the electron distribution function variable + f_electron::Tfe + # low-order approximation, used to diagnose timestepping error + f_electron_loworder::Union{Tfe,Nothing} + # start of the last timestep before output, used to measure steady state residual + f_electron_start_last_timestep::Union{Tfe,Nothing} + # handle for the electron density variable + electron_density::Union{Tmom,Nothing} + # low-order approximation, used to diagnose timestepping error + electron_density_loworder::Union{Tmom,Nothing} + # start of the last timestep before output, used to measure steady state residual + electron_density_start_last_timestep::Union{Tmom,Nothing} + # handle for the electron parallel flow variable + electron_parallel_flow::Union{Tmom,Nothing} + # low-order approximation, used to diagnose timestepping error + electron_parallel_flow_loworder::Union{Tmom,Nothing} + # start of the last timestep before output, used to measure steady state residual + electron_parallel_flow_start_last_timestep::Union{Tmom,Nothing} + # handle for the electron parallel pressure variable + electron_parallel_pressure::Tmom + # low-order approximation, used to diagnose timestepping error + electron_parallel_pressure_loworder::Union{Tmom,Nothing} + # start of the last timestep before output, used to measure steady state residual + electron_parallel_pressure_start_last_timestep::Union{Tmom,Nothing} + # handle for the electron parallel heat flux variable + electron_parallel_heat_flux::Tmom + # handle for the electron thermal speed variable + electron_thermal_speed::Tmom + # handles for external source terms + external_source_electron_amplitude::Texte1 + external_source_electron_density_amplitude::Texte2 + external_source_electron_momentum_amplitude::Texte3 + external_source_electron_pressure_amplitude::Texte4 + # handles for constraint coefficients + electron_constraints_A_coefficient::Tconstr + electron_constraints_B_coefficient::Tconstr + electron_constraints_C_coefficient::Tconstr + # cumulative number of electron pseudo-timesteps taken + electron_step_counter::Telectronint + # cumulative electron pseudo-time + electron_cumulative_pseudotime::Telectrontime + # current electron pseudo-timestep size + electron_dt::Telectrontime + # size of last electron pseudo-timestep before the output was written + electron_previous_dt::Telectrontime + # cumulative number of electron pseudo-timestep failures + electron_failure_counter::Telectronint + # cumulative count of which variable caused a electron pseudo-timstep failure + electron_failure_caused_by::Telectronfailcause + # cumulative count of which factors limited the electron pseudo-timestep at each step + electron_limit_caused_by::Telectronfailcause + # Last successful timestep before most recent electron pseudo-timestep failure, used + # by adaptve timestepping algorithm + electron_dt_before_last_fail::Telectrontime + + # Settings for I/O + io_input::Tinput +end + """ io_has_parallel(Val(binary_format)) @@ -210,29 +373,28 @@ function setup_file_io(io_input, boundary_distributions, vz, vr, vzeta, vpa, vpe if io_input.ascii_output ff_io = open_ascii_output_file(out_prefix, "f_vs_t") mom_ion_io = open_ascii_output_file(out_prefix, "moments_ion_vs_t") + mom_eon_io = open_ascii_output_file(out_prefix, "moments_electron_vs_t") mom_ntrl_io = open_ascii_output_file(out_prefix, "moments_neutral_vs_t") fields_io = open_ascii_output_file(out_prefix, "fields_vs_t") - ascii = ascii_ios(ff_io, mom_ion_io, mom_ntrl_io, fields_io) + ascii = ascii_ios(ff_io, mom_ion_io, mom_eon_io, mom_ntrl_io, fields_io) else - ascii = ascii_ios(nothing, nothing, nothing, nothing) + ascii = ascii_ios(nothing, nothing, nothing, nothing, nothing) end run_id = io_input.run_id - io_moments = setup_moments_io(out_prefix, io_input.binary_format, vz, vr, vzeta, - vpa, vperp, r, z, composition, collisions, - evolve_density, evolve_upar, evolve_ppar, - external_source_settings, input_dict, - io_input.parallel_io, comm_inter_block[], run_id, + io_moments = setup_moments_io(out_prefix, io_input, vz, vr, vzeta, vpa, vperp, r, + z, composition, collisions, evolve_density, + evolve_upar, evolve_ppar, external_source_settings, + input_dict, comm_inter_block[], run_id, restart_time_index, previous_runs_info, time_for_setup, t_params, nl_solver_params) - io_dfns = setup_dfns_io(out_prefix, io_input.binary_format, - boundary_distributions, r, z, vperp, vpa, vzeta, vr, vz, - composition, collisions, evolve_density, evolve_upar, - evolve_ppar, external_source_settings, input_dict, - io_input.parallel_io, comm_inter_block[], run_id, - restart_time_index, previous_runs_info, time_for_setup, - t_params, nl_solver_params) + io_dfns = setup_dfns_io(out_prefix, io_input, boundary_distributions, r, z, vperp, + vpa, vzeta, vr, vz, composition, collisions, + evolve_density, evolve_upar, evolve_ppar, + external_source_settings, input_dict, comm_inter_block[], + run_id, restart_time_index, previous_runs_info, + time_for_setup, t_params, nl_solver_params) return ascii, io_moments, io_dfns end @@ -240,6 +402,182 @@ function setup_file_io(io_input, boundary_distributions, vz, vr, vzeta, vpa, vpe return nothing, nothing, nothing end +""" +open output file to save the initial electron pressure and distribution function +""" +function setup_electron_io(io_input, vpa, vperp, z, r, composition, collisions, + evolve_density, evolve_upar, evolve_ppar, + external_source_settings, t_params, input_dict, + restart_time_index, previous_runs_info, prefix_label) + begin_serial_region() + @serial_region begin + # Only read/write from first process in each 'block' + + # check to see if output_dir exists in the current directory + # if not, create it + isdir(io_input.output_dir) || mkdir(io_input.output_dir) + out_prefix = joinpath(io_input.output_dir, io_input.run_name) + + run_id = io_input.run_id + parallel_io = io_input.parallel_io + io_comm = comm_inter_block[] + + electrons_prefix = string(out_prefix, ".$prefix_label") + if !parallel_io + electrons_prefix *= ".$(iblock_index[])" + end + fid, file_info = open_output_file(electrons_prefix, io_input, io_comm) + + # write a header to the output file + add_attribute!(fid, "file_info", + "Output initial electron state from the moment_kinetics code") + add_attribute!(fid, "pdf_electron_converged", false) + + # write some overview information to the output file + write_overview!(fid, composition, collisions, parallel_io, evolve_density, + evolve_upar, evolve_ppar, -1.0) + + # write provenance tracking information to the output file + write_provenance_tracking_info!(fid, parallel_io, run_id, restart_time_index, + input_dict, previous_runs_info) + + # write the input settings + write_input!(fid, input_dict, parallel_io) + + ### define coordinate dimensions ### + define_io_coordinates!(fid, nothing, nothing, nothing, vpa, vperp, z, r, + parallel_io) + + ### create variables for time-dependent quantities ### + dynamic = create_io_group(fid, "dynamic_data", description="time evolving variables") + io_pseudotime = create_dynamic_variable!(dynamic, "time", mk_float; parallel_io=parallel_io, + description="pseudotime used for electron initialization") + io_f_electron = create_dynamic_variable!(dynamic, "f_electron", mk_float, vpa, + vperp, z, r; + parallel_io=parallel_io, + description="electron distribution function") + if io_input.write_electron_error_diagnostics + io_f_electron_loworder = + create_dynamic_variable!(dynamic, "f_electron_loworder", mk_float, + vpa, vperp, z, r; + n_ion_species=composition.n_ion_species, + parallel_io=parallel_io, + description="low-order approximation to electron distribution function, used to diagnose timestepping error") + else + io_f_electron_loworder = nothing + end + if io_input.write_electron_steady_state_diagnostics + io_f_electron_start_last_timestep = + create_dynamic_variable!(dynamic, "f_electron_start_last_timestep", + mk_float, vpa, vperp, z, r; + n_ion_species=composition.n_ion_species, + parallel_io=parallel_io, + description="electron distribution function at the start of the last electron pseudo-timestep before output, used to measure steady state residual") + else + io_f_electron_start_last_timestep = nothing + end + + io_electron_density, io_electron_density_loworder, + io_electron_density_start_last_timestep, io_electron_upar, + io_electron_upar_loworder, io_electron_upar_start_last_timestep, io_electron_ppar, + io_electron_ppar_loworder, io_electron_ppar_start_last_timestep, io_electron_qpar, + io_electron_vth, external_source_electron_amplitude, + external_source_electron_density_amplitude, + external_source_electron_momentum_amplitude, + external_source_electron_pressure_amplitude, + electron_constraints_A_coefficient, electron_constraints_B_coefficient, + electron_constraints_C_coefficient, io_electron_step_counter, io_electron_dt, + io_electron_previous_dt, io_electron_failure_counter, + io_electron_failure_caused_by, io_electron_limit_caused_by, + io_electron_dt_before_last_fail = + define_dynamic_electron_moment_variables!(fid, r, z, parallel_io, + external_source_settings, + evolve_density, evolve_upar, + evolve_ppar, kinetic_electrons, + t_params, + io_input.write_electron_error_diagnostics, + io_input.write_electron_steady_state_diagnostics; + electron_only_io=true) + + close(fid) + + return file_info + end + # For other processes in the block, return nothing + return nothing +end + +""" +Get the `file_info` for an existing electron I/O file +""" +function get_electron_io_info(io_input, prefix_label) + out_prefix = joinpath(io_input.output_dir, io_input.run_name) + electrons_prefix = string(out_prefix, ".$prefix_label") + if io_input.binary_format == hdf5 + filename = string(electrons_prefix, ".h5") + elseif io_input.binary_format == netcdf + filename = string(electrons_prefix, ".cdf") + else + error("Unrecognized binary_format=$(io_input.binary_format)") + end + + return (filename, io_input, comm_inter_block[]) +end + +""" +Reopen an existing initial electron output file to append more data +""" +function reopen_initial_electron_io(file_info) + @serial_region begin + filename, io_input, io_comm = file_info + fid = reopen_output_file(filename, io_input, io_comm) + dyn = get_group(fid, "dynamic_data") + + variable_list = get_variable_keys(dyn) + function getvar(name) + if name ∈ variable_list + return dyn[name] + else + return nothing + end + end + return io_initial_electron_info(fid, getvar("time"), getvar("f_electron"), + getvar("f_electron_loworder"), + getvar("f_electron_start_last_timestep"), + getvar("electron_density"), + getvar("electron_density_loworder"), + getvar("electron_density_start_last_timestep"), + getvar("electron_parallel_flow"), + getvar("electron_parallel_flow_loworder"), + getvar("electron_parallel_flow_start_last_timestep"), + getvar("electron_parallel_pressure"), + getvar("electron_parallel_pressure_loworder"), + getvar("electron_parallel_pressure_start_last_timestep"), + getvar("electron_parallel_heat_flux"), + getvar("electron_thermal_speed"), + getvar("external_source_electron_amplitude"), + getvar("external_source_electron_density_amplitude"), + getvar("external_source_electron_momentum_amplitude"), + getvar("external_source_electron_pressure_amplitude"), + getvar("electron_constraints_A_coefficient"), + getvar("electron_constraints_B_coefficient"), + getvar("electron_constraints_C_coefficient"), + getvar("electron_step_counter"), + getvar("electron_cumulative_pseudotime"), + getvar("electron_dt"), + getvar("electron_previous_dt"), + getvar("electron_failure_counter"), + getvar("electron_failure_caused_by"), + getvar("electron_limit_caused_by"), + getvar("electron_dt_before_last_fail"), + io_input) + end + + # For processes other than the root process of each shared-memory group... + return nothing +end + + """ Get a (sub-)group from a file or group """ @@ -268,6 +606,11 @@ attributes of the variable. """ function write_single_value! end +# Convert Enum values to String to be written to file +function write_single_value!(file_or_group, name, data::Enum; kwargs...) + return write_single_value!(file_or_group, name, string(data); kwargs...) +end + """ write some overview information for the simulation to the binary file """ @@ -646,11 +989,12 @@ function create_dynamic_variable! end define dynamic (time-evolving) moment variables for writing to the hdf5 file """ function define_dynamic_moment_variables!(fid, n_ion_species, n_neutral_species, - r::coordinate, z::coordinate, parallel_io, + r::coordinate, z::coordinate, io_input, external_source_settings, evolve_density, - evolve_upar, evolve_ppar, t_params, - nl_solver_params) + evolve_upar, evolve_ppar, electron_physics, + t_params, nl_solver_params) @serial_region begin + parallel_io = io_input.parallel_io dynamic = create_io_group(fid, "dynamic_data", description="time evolving variables") io_time = create_dynamic_variable!(dynamic, "time", mk_float; parallel_io=parallel_io, @@ -659,19 +1003,46 @@ function define_dynamic_moment_variables!(fid, n_ion_species, n_neutral_species, io_phi, io_Er, io_Ez = define_dynamic_em_field_variables!(fid, r, z, parallel_io) - io_density, io_upar, io_ppar, io_pperp, io_qpar, io_vth, io_dSdt, - external_source_amplitude, external_source_density_amplitude, - external_source_momentum_amplitude, external_source_pressure_amplitude, - external_source_controller_integral, io_chodura_lower, io_chodura_upper, - ion_constraints_A_coefficient, ion_constraints_B_coefficient, - ion_constraints_C_coefficient = + io_density, io_density_loworder, io_density_start_last_timestep, io_upar, + io_upar_loworder, io_upar_start_last_timestep, io_ppar, io_ppar_loworder, + io_ppar_start_last_timestep, io_pperp, io_pperp_loworder, + io_pperp_start_last_timestep, io_qpar, io_vth, io_dSdt, external_source_amplitude, + external_source_density_amplitude, external_source_momentum_amplitude, + external_source_pressure_amplitude, external_source_controller_integral, + io_chodura_lower, io_chodura_upper, ion_constraints_A_coefficient, + ion_constraints_B_coefficient, ion_constraints_C_coefficient = define_dynamic_ion_moment_variables!(fid, n_ion_species, r, z, parallel_io, external_source_settings, evolve_density, - evolve_upar, evolve_ppar) - - io_density_neutral, io_uz_neutral, io_pz_neutral, io_qz_neutral, - io_thermal_speed_neutral, external_source_neutral_amplitude, - external_source_neutral_density_amplitude, + evolve_upar, evolve_ppar, + io_input.write_error_diagnostics, + io_input.write_steady_state_diagnostics) + + io_electron_density, io_electron_density_loworder, + io_electron_density_start_last_timestep, io_electron_upar, + io_electron_upar_loworder, io_electron_upar_start_last_timestep, io_electron_ppar, + io_electron_ppar_loworder, io_electron_ppar_start_last_timestep, io_electron_qpar, + io_electron_vth, external_source_electron_amplitude, + external_source_electron_density_amplitude, + external_source_electron_momentum_amplitude, + external_source_electron_pressure_amplitude, + electron_constraints_A_coefficient, electron_constraints_B_coefficient, + electron_constraints_C_coefficient, io_electron_step_counter, + io_electron_cumulative_pseudotime, io_electron_dt, io_electron_previous_dt, + io_electron_failure_counter, io_electron_failure_caused_by, + io_electron_limit_caused_by, io_electron_dt_before_last_fail = + define_dynamic_electron_moment_variables!(fid, r, z, parallel_io, + external_source_settings, + evolve_density, evolve_upar, + evolve_ppar, electron_physics, + t_params.electron, + io_input.write_error_diagnostics, + io_input.write_steady_state_diagnostics) + + io_density_neutral, io_density_neutral_loworder, + io_density_neutral_start_last_timestep, io_uz_neutral, io_uz_neutral_loworder, + io_uz_neutral_start_last_timestep, io_pz_neutral, io_pz_neutral_loworder, + io_pz_neutral_start_last_timestep, io_qz_neutral, io_thermal_speed_neutral, + external_source_neutral_amplitude, external_source_neutral_density_amplitude, external_source_neutral_momentum_amplitude, external_source_neutral_pressure_amplitude, external_source_neutral_controller_integral, neutral_constraints_A_coefficient, @@ -680,7 +1051,9 @@ function define_dynamic_moment_variables!(fid, n_ion_species, n_neutral_species, parallel_io, external_source_settings, evolve_density, evolve_upar, - evolve_ppar) + evolve_ppar, + io_input.write_error_diagnostics, + io_input.write_steady_state_diagnostics) io_time_for_run = create_dynamic_variable!( dynamic, "time_for_run", mk_float; parallel_io=parallel_io, @@ -695,6 +1068,10 @@ function define_dynamic_moment_variables!(fid, n_ion_species, n_neutral_species, dynamic, "dt", mk_float; parallel_io=parallel_io, description="current timestep size") + io_previous_dt = create_dynamic_variable!( + dynamic, "previous_dt", mk_float; parallel_io=parallel_io, + description="size of the last timestep before the output") + io_failure_counter = create_dynamic_variable!( dynamic, "failure_counter", mk_int; parallel_io=parallel_io, description="cumulative number of timestep failures for the run") @@ -732,10 +1109,26 @@ function define_dynamic_moment_variables!(fid, n_ion_species, n_neutral_species, ) for (term, params) ∈ pairs(nl_solver_params) if params !== nothing) - return io_moments_info(fid, io_time, io_phi, io_Er, io_Ez, io_density, io_upar, - io_ppar, io_pperp, io_qpar, io_vth, io_dSdt, io_chodura_lower, io_chodura_upper, - io_density_neutral, io_uz_neutral, - io_pz_neutral, io_qz_neutral, io_thermal_speed_neutral, + return io_moments_info(fid, io_time, io_phi, io_Er, io_Ez, io_density, + io_density_loworder, io_density_start_last_timestep, + io_upar, io_upar_loworder, io_upar_start_last_timestep, + io_ppar, io_ppar_loworder, io_ppar_start_last_timestep, + io_pperp, io_pperp_loworder, io_pperp_start_last_timestep, + io_qpar, io_vth, io_dSdt, io_chodura_lower, + io_chodura_upper, + io_electron_density, io_electron_density_loworder, + io_electron_density_start_last_timestep, io_electron_upar, + io_electron_upar_loworder, + io_electron_upar_start_last_timestep, io_electron_ppar, + io_electron_ppar_loworder, + io_electron_ppar_start_last_timestep, io_electron_qpar, + io_electron_vth, + io_density_neutral, io_density_neutral_loworder, + io_density_neutral_start_last_timestep, io_uz_neutral, + io_uz_neutral_loworder, io_uz_neutral_start_last_timestep, + io_pz_neutral, io_pz_neutral_loworder, + io_pz_neutral_start_last_timestep, io_qz_neutral, + io_thermal_speed_neutral, external_source_amplitude, external_source_density_amplitude, external_source_momentum_amplitude, @@ -746,16 +1139,28 @@ function define_dynamic_moment_variables!(fid, n_ion_species, n_neutral_species, external_source_neutral_momentum_amplitude, external_source_neutral_pressure_amplitude, external_source_neutral_controller_integral, + external_source_electron_amplitude, + external_source_electron_density_amplitude, + external_source_electron_momentum_amplitude, + external_source_electron_pressure_amplitude, ion_constraints_A_coefficient, ion_constraints_B_coefficient, ion_constraints_C_coefficient, neutral_constraints_A_coefficient, neutral_constraints_B_coefficient, neutral_constraints_C_coefficient, - io_time_for_run, io_step_counter, io_dt, + electron_constraints_A_coefficient, + electron_constraints_B_coefficient, + electron_constraints_C_coefficient, + io_time_for_run, io_step_counter, io_dt, io_previous_dt, io_failure_counter, io_failure_caused_by, - io_limit_caused_by, io_dt_before_last_fail, io_nl_solver_diagnostics, - parallel_io) + io_limit_caused_by, io_dt_before_last_fail, + io_electron_step_counter, + io_electron_cumulative_pseudotime, io_electron_dt, + io_electron_previous_dt, io_electron_failure_counter, + io_electron_failure_caused_by, io_electron_limit_caused_by, + io_electron_dt_before_last_fail, io_nl_solver_diagnostics, + io_input) end # For processes other than the root process of each shared-memory group... @@ -795,7 +1200,7 @@ define dynamic (time-evolving) ion moment variables for writing to the hdf5 file """ function define_dynamic_ion_moment_variables!(fid, n_ion_species, r::coordinate, z::coordinate, parallel_io, external_source_settings, evolve_density, evolve_upar, - evolve_ppar) + evolve_ppar, write_error_diagnostics, write_steady_state_diagnostics) dynamic = get_group(fid, "dynamic_data") @@ -805,6 +1210,24 @@ function define_dynamic_ion_moment_variables!(fid, n_ion_species, r::coordinate, parallel_io=parallel_io, description="ion species density", units="n_ref") + if write_error_diagnostics + io_density_loworder = + create_dynamic_variable!(dynamic, "density_loworder", mk_float, z, r; + n_ion_species=n_ion_species, parallel_io=parallel_io, + description="low-order approximation to ion species density, used to diagnose timestepping error", + units="n_ref") + else + io_density_loworder = nothing + end + if write_steady_state_diagnostics + io_density_start_last_timestep = + create_dynamic_variable!(dynamic, "density_start_last_timestep", mk_float, z, r; + n_ion_species=n_ion_species, parallel_io=parallel_io, + description="ion species density at the start of the last timestep before output, used to measure steady state residual", + units="n_ref") + else + io_density_start_last_timestep = nothing + end # io_upar is the handle for the ion parallel flow density io_upar = create_dynamic_variable!(dynamic, "parallel_flow", mk_float, z, r; @@ -812,6 +1235,25 @@ function define_dynamic_ion_moment_variables!(fid, n_ion_species, r::coordinate, parallel_io=parallel_io, description="ion species parallel flow", units="c_ref = sqrt(2*T_ref/mi)") + if write_error_diagnostics + io_upar_loworder = + create_dynamic_variable!(dynamic, "parallel_flow_loworder", mk_float, z, r; + n_ion_species=n_ion_species, parallel_io=parallel_io, + description="low-order approximation to ion species parallel flow, used to diagnose timestepping error", + units="c_ref = sqrt(2*T_ref/mi)") + else + io_upar_loworder = nothing + end + if write_steady_state_diagnostics + io_upar_start_last_timestep = + create_dynamic_variable!(dynamic, "parallel_flow_start_last_timestep", + mk_float, z, r; n_ion_species=n_ion_species, + parallel_io=parallel_io, + description="ion species parallel flow at the start of the last timestep before output, used to measure steady state residual", + units="c_ref = sqrt(2*T_ref/mi)") + else + io_upar_start_last_timestep = nothing + end # io_ppar is the handle for the ion parallel pressure io_ppar = create_dynamic_variable!(dynamic, "parallel_pressure", mk_float, z, r; @@ -819,6 +1261,25 @@ function define_dynamic_ion_moment_variables!(fid, n_ion_species, r::coordinate, parallel_io=parallel_io, description="ion species parallel pressure", units="n_ref*T_ref") + if write_error_diagnostics + io_ppar_loworder = + create_dynamic_variable!(dynamic, "parallel_pressure_loworder", mk_float, z, r; + n_ion_species=n_ion_species, parallel_io=parallel_io, + description="low-order approximation to ion species parallel pressure, used to diagnose timestepping error", + units="n_ref*T_ref") + else + io_ppar_loworder = nothing + end + if write_steady_state_diagnostics + io_ppar_start_last_timestep = + create_dynamic_variable!(dynamic, "parallel_pressure_start_last_timestep", + mk_float, z, r; n_ion_species=n_ion_species, + parallel_io=parallel_io, + description="ion species parallel pressure at the start of the last timestep before output, used to measure steady state residual", + units="n_ref*T_ref") + else + io_ppar_start_last_timestep = nothing + end # io_pperp is the handle for the ion parallel pressure io_pperp = create_dynamic_variable!(dynamic, "perpendicular_pressure", mk_float, z, r; @@ -826,6 +1287,25 @@ function define_dynamic_ion_moment_variables!(fid, n_ion_species, r::coordinate, parallel_io=parallel_io, description="ion species perpendicular pressure", units="n_ref*T_ref") + if write_error_diagnostics + io_pperp_loworder = + create_dynamic_variable!(dynamic, "perpendicular_pressure_loworder", mk_float, z, + r; n_ion_species=n_ion_species, parallel_io=parallel_io, + description="low-order approximation to ion species perpendicular pressure, used to diagnose timestepping error", + units="n_ref*T_ref") + else + io_pperp_loworder = nothing + end + if write_steady_state_diagnostics + io_pperp_start_last_timestep = + create_dynamic_variable!(dynamic, "perpendicular_pressure_start_last_timestep", + mk_float, z, r; n_ion_species=n_ion_species, + parallel_io=parallel_io, + description="ion species perpendicular pressure at the start of the last timestep before output, used to measure steady state residual", + units="n_ref*T_ref") + else + io_pperp_start_last_timestep = nothing + end # io_qpar is the handle for the ion parallel heat flux io_qpar = create_dynamic_variable!(dynamic, "parallel_heat_flux", mk_float, z, r; @@ -945,7 +1425,10 @@ function define_dynamic_ion_moment_variables!(fid, n_ion_species, r::coordinate, ion_constraints_C_coefficient = nothing end - return io_density, io_upar, io_ppar, io_pperp, io_qpar, io_vth, io_dSdt, + return io_density, io_density_loworder, io_density_start_last_timestep, io_upar, + io_upar_loworder, io_upar_start_last_timestep, io_ppar, io_ppar_loworder, + io_ppar_start_last_timestep, io_pperp, io_pperp_loworder, + io_pperp_start_last_timestep, io_qpar, io_vth, io_dSdt, external_source_amplitude, external_source_density_amplitude, external_source_momentum_amplitude, external_source_pressure_amplitude, external_source_controller_integral, io_chodura_lower, io_chodura_upper, @@ -953,12 +1436,220 @@ function define_dynamic_ion_moment_variables!(fid, n_ion_species, r::coordinate, ion_constraints_C_coefficient end +""" +define dynamic (time-evolving) electron moment variables for writing to the hdf5 file +""" +function define_dynamic_electron_moment_variables!(fid, r::coordinate, z::coordinate, + parallel_io, external_source_settings, evolve_density, evolve_upar, evolve_ppar, + electron_physics, t_params, write_error_diagnostics, + write_steady_state_diagnostics; electron_only_io=false) + + dynamic = get_group(fid, "dynamic_data") + + if !electron_only_io + # io_density is the handle for the ion particle density + io_electron_density = create_dynamic_variable!(dynamic, "electron_density", mk_float, z, r; + parallel_io=parallel_io, + description="electron species density", + units="n_ref") + if write_error_diagnostics + io_electron_density_loworder = + create_dynamic_variable!(dynamic, "electron_density_loworder", mk_float, z, r; + parallel_io=parallel_io, + description="low-order approximation to electron species density, used to diagnose timestepping error", + units="n_ref") + else + io_electron_density_loworder = nothing + end + if write_steady_state_diagnostics + io_electron_density_start_last_timestep = + create_dynamic_variable!(dynamic, "electron_density_start_last_timestep", + mk_float, z, r; parallel_io=parallel_io, + description="electron species density at the start of the last timestep before output, used to measure steady state residual", + units="n_ref") + else + io_electron_density_start_last_timestep = nothing + end + + # io_electron_upar is the handle for the electron parallel flow density + io_electron_upar = create_dynamic_variable!(dynamic, "electron_parallel_flow", mk_float, z, r; + parallel_io=parallel_io, + description="electron species parallel flow", + units="c_ref = sqrt(2*T_ref/mi)") + if write_error_diagnostics + io_electron_upar_loworder = + create_dynamic_variable!(dynamic, "electron_parallel_flow_loworder", mk_float, z, + r; parallel_io=parallel_io, + description="low-order approximation to electron species parallel flow, used to diagnose timestepping error", + units="c_ref = sqrt(2*T_ref/mi)") + else + io_electron_upar_loworder = nothing + end + if write_steady_state_diagnostics + io_electron_upar_start_last_timestep = + create_dynamic_variable!(dynamic, "electron_parallel_flow_start_last_timestep", + mk_float, z, r; parallel_io=parallel_io, + description="electron species parallel flow at the start of the last timestep before output, used to measure steady state residual", + units="c_ref = sqrt(2*T_ref/mi)") + else + io_electron_upar_start_last_timestep = nothing + end + else + io_electron_density = nothing + io_electron_density_loworder = nothing + io_electron_density_start_last_timestep = nothing + io_electron_upar = nothing + io_electron_upar_loworder = nothing + io_electron_upar_start_last_timestep = nothing + end + + # io_electron_ppar is the handle for the electron parallel pressure + io_electron_ppar = create_dynamic_variable!(dynamic, "electron_parallel_pressure", mk_float, z, r; + parallel_io=parallel_io, + description="electron species parallel pressure", + units="n_ref*T_ref") + if write_error_diagnostics + io_electron_ppar_loworder = + create_dynamic_variable!(dynamic, "electron_parallel_pressure_loworder", mk_float, + z, r; parallel_io=parallel_io, + description="low-order approximation to electron species parallel pressure, used to diagnose timestepping error", + units="n_ref*T_ref") + else + io_electron_ppar_loworder = nothing + end + if write_steady_state_diagnostics + io_electron_ppar_start_last_timestep = + create_dynamic_variable!(dynamic, + "electron_parallel_pressure_start_last_timestep", + mk_float, z, r; parallel_io=parallel_io, + description="electron species parallel pressure at the start of the last timestep before output, used to measure steady state residual", + units="n_ref*T_ref") + else + io_electron_ppar_start_last_timestep = nothing + end + + # io_electron_qpar is the handle for the electron parallel heat flux + io_electron_qpar = create_dynamic_variable!(dynamic, "electron_parallel_heat_flux", mk_float, z, r; + parallel_io=parallel_io, + description="electron species parallel heat flux", + units="n_ref*T_ref*c_ref") + + # io_electron_vth is the handle for the electron thermal speed + io_electron_vth = create_dynamic_variable!(dynamic, "electron_thermal_speed", mk_float, z, r; + parallel_io=parallel_io, + description="electron species thermal speed", + units="c_ref") + + electron_source_settings = external_source_settings.electron + if electron_source_settings.active + external_source_electron_amplitude = create_dynamic_variable!( + dynamic, "external_source_electron_amplitude", mk_float, z, r; + parallel_io=parallel_io, description="Amplitude of the external source for electrons", + units="n_ref/c_ref^3*c_ref/L_ref") + external_source_electron_density_amplitude = create_dynamic_variable!( + dynamic, "external_source_electron_density_amplitude", mk_float, z, r; + parallel_io=parallel_io, description="Amplitude of the external density source for electrons", + units="n_ref*c_ref/L_ref") + external_source_electron_momentum_amplitude = create_dynamic_variable!( + dynamic, "external_source_electron_momentum_amplitude", mk_float, z, r; + parallel_io=parallel_io, description="Amplitude of the external momentum source for electrons", + units="m_ref*n_ref*c_ref*c_ref/L_ref") + external_source_electron_pressure_amplitude = create_dynamic_variable!( + dynamic, "external_source_electron_pressure_amplitude", mk_float, z, r; + parallel_io=parallel_io, description="Amplitude of the external pressure source for electrons", + units="m_ref*n_ref*c_ref^2*c_ref/L_ref") + else + external_source_electron_amplitude = nothing + external_source_electron_density_amplitude = nothing + external_source_electron_momentum_amplitude = nothing + external_source_electron_pressure_amplitude = nothing + end + + electron_constraints_A_coefficient = + create_dynamic_variable!(dynamic, "electron_constraints_A_coefficient", mk_float, z, r; + parallel_io=parallel_io, + description="'A' coefficient enforcing density constraint for electrons") + electron_constraints_B_coefficient = + create_dynamic_variable!(dynamic, "electron_constraints_B_coefficient", mk_float, z, r; + parallel_io=parallel_io, + description="'B' coefficient enforcing flow constraint for electrons") + electron_constraints_C_coefficient = + create_dynamic_variable!(dynamic, "electron_constraints_C_coefficient", mk_float, z, r; + parallel_io=parallel_io, + description="'C' coefficient enforcing pressure constraint for electrons") + + if electron_physics ∈ (kinetic_electrons, kinetic_electrons_with_temperature_equation) + io_electron_step_counter = create_dynamic_variable!( + dynamic, "electron_step_counter", mk_int; parallel_io=parallel_io, + description="cumulative number of electron pseudo-timesteps for the run") + + io_electron_cumulative_pseudotime = create_dynamic_variable!( + dynamic, "electron_cumulative_pseudotime", mk_float; parallel_io=parallel_io, + description="cumulative electron pseudo-time") + + io_electron_dt = create_dynamic_variable!( + dynamic, "electron_dt", mk_float; parallel_io=parallel_io, + description="current electron pseudo-timestep size") + + io_electron_previous_dt = create_dynamic_variable!( + dynamic, "electron_previous_dt", mk_float; parallel_io=parallel_io, + description="size of last electron pseudo-timestep before output was written") + + io_electron_failure_counter = create_dynamic_variable!( + dynamic, "electron_failure_counter", mk_int; parallel_io=parallel_io, + description="cumulative number of electron pseudo-timestep failures for the run") + + n_failure_vars = length(t_params.failure_caused_by) + io_electron_failure_caused_by = create_dynamic_variable!( + dynamic, "electron_failure_caused_by", mk_int; + diagnostic_var_size=n_failure_vars, parallel_io=parallel_io, + description="cumulative count of how many times each variable caused an " + * "electron pseudo-timestep failure for the run") + + n_limit_vars = length(t_params.limit_caused_by) + io_electron_limit_caused_by = create_dynamic_variable!( + dynamic, "electron_limit_caused_by", mk_int; diagnostic_var_size=n_limit_vars, + parallel_io=parallel_io, + description="cumulative count of how many times each factor limited the " + * "electron pseudo-timestep for the run") + + io_electron_dt_before_last_fail = create_dynamic_variable!( + dynamic, "electron_dt_before_last_fail", mk_float; parallel_io=parallel_io, + description="Last successful electron pseudo-timestep before most recent " + * "electron pseudo-timestep failure, used by adaptve " + * "timestepping algorithm") + else + io_electron_step_counter = nothing + io_electron_cumulative_pseudotime = nothing + io_electron_dt = nothing + io_electron_previous_dt = nothing + io_electron_failure_counter = nothing + io_electron_failure_caused_by = nothing + io_electron_limit_caused_by = nothing + io_electron_dt_before_last_fail = nothing + end + + return io_electron_density, io_electron_density_loworder, + io_electron_density_start_last_timestep, io_electron_upar, + io_electron_upar_loworder, io_electron_upar_start_last_timestep, + io_electron_ppar, io_electron_ppar_loworder, + io_electron_ppar_start_last_timestep, io_electron_qpar, io_electron_vth, + external_source_electron_amplitude, external_source_electron_density_amplitude, + external_source_electron_momentum_amplitude, + external_source_electron_pressure_amplitude, + electron_constraints_A_coefficient, electron_constraints_B_coefficient, + electron_constraints_C_coefficient, io_electron_step_counter, + io_electron_cumulative_pseudotime, io_electron_dt, io_electron_previous_dt, + io_electron_failure_counter, io_electron_failure_caused_by, + io_electron_limit_caused_by, io_electron_dt_before_last_fail +end + """ define dynamic (time-evolving) neutral moment variables for writing to the hdf5 file """ function define_dynamic_neutral_moment_variables!(fid, n_neutral_species, r::coordinate, z::coordinate, parallel_io, external_source_settings, evolve_density, evolve_upar, - evolve_ppar) + evolve_ppar, write_error_diagnostics, write_steady_state_diagnostics) dynamic = get_group(fid, "dynamic_data") @@ -968,6 +1659,26 @@ function define_dynamic_neutral_moment_variables!(fid, n_neutral_species, r::coo parallel_io=parallel_io, description="neutral species density", units="n_ref") + if write_error_diagnostics + io_density_neutral_loworder = + create_dynamic_variable!(dynamic, "density_neutral_loworder", mk_float, z, r; + n_neutral_species=n_neutral_species, + parallel_io=parallel_io, + description="low-order approximation to neutral species density, used to diagnose timestepping error", + units="n_ref") + else + io_density_neutral_loworder = nothing + end + if write_steady_state_diagnostics + io_density_neutral_start_last_timestep = + create_dynamic_variable!(dynamic, "density_neutral_start_last_timestep", mk_float, z, r; + n_neutral_species=n_neutral_species, + parallel_io=parallel_io, + description="neutral species density at the start of the last timestep before output, used to measure steady state residual", + units="n_ref") + else + io_density_neutral_start_last_timestep = nothing + end # io_uz_neutral is the handle for the neutral z momentum density io_uz_neutral = create_dynamic_variable!(dynamic, "uz_neutral", mk_float, z, r; @@ -975,6 +1686,26 @@ function define_dynamic_neutral_moment_variables!(fid, n_neutral_species, r::coo parallel_io=parallel_io, description="neutral species mean z velocity", units="c_ref = sqrt(2*T_ref/mi)") + if write_error_diagnostics + io_uz_neutral_loworder = + create_dynamic_variable!(dynamic, "uz_neutral_loworder", mk_float, z, r; + n_neutral_species=n_neutral_species, + parallel_io=parallel_io, + description="low-order approximation to neutral species mean z velocity, used to diagnose timestepping error", + units="c_ref = sqrt(2*T_ref/mi)") + else + io_uz_neutral_loworder = nothing + end + if write_steady_state_diagnostics + io_uz_neutral_start_last_timestep = + create_dynamic_variable!(dynamic, "uz_neutral_start_last_timestep", mk_float, z, r; + n_neutral_species=n_neutral_species, + parallel_io=parallel_io, + description="neutral species mean z velocity at the start of the last timestep before output, used to measure steady state residual", + units="c_ref = sqrt(2*T_ref/mi)") + else + io_uz_neutral_start_last_timestep = nothing + end # io_pz_neutral is the handle for the neutral species zz pressure io_pz_neutral = create_dynamic_variable!(dynamic, "pz_neutral", mk_float, z, r; @@ -982,6 +1713,26 @@ function define_dynamic_neutral_moment_variables!(fid, n_neutral_species, r::coo parallel_io=parallel_io, description="neutral species mean zz pressure", units="n_ref*T_ref") + if write_error_diagnostics + io_pz_neutral_loworder = + create_dynamic_variable!(dynamic, "pz_neutral_loworder", mk_float, z, r; + n_neutral_species=n_neutral_species, + parallel_io=parallel_io, + description="low-order approximation to neutral species mean zz pressure, used to diagnose timestepping error", + units="n_ref*T_ref") + else + io_pz_neutral_loworder = nothing + end + if write_steady_state_diagnostics + io_pz_neutral_start_last_timestep = + create_dynamic_variable!(dynamic, "pz_neutral_start_last_timestep", mk_float, z, r; + n_neutral_species=n_neutral_species, + parallel_io=parallel_io, + description="neutral species mean zz pressure at the start of the last timestep before output, used to measure steady state residual", + units="n_ref*T_ref") + else + io_pz_neutral_start_last_timestep = nothing + end # io_qz_neutral is the handle for the neutral z heat flux io_qz_neutral = create_dynamic_variable!(dynamic, "qz_neutral", mk_float, z, r; @@ -1073,9 +1824,11 @@ function define_dynamic_neutral_moment_variables!(fid, n_neutral_species, r::coo neutral_constraints_C_coefficient = nothing end - return io_density_neutral, io_uz_neutral, io_pz_neutral, io_qz_neutral, - io_thermal_speed_neutral, external_source_neutral_amplitude, - external_source_neutral_density_amplitude, + return io_density_neutral, io_density_neutral_loworder, + io_density_neutral_start_last_timestep, io_uz_neutral, io_uz_neutral_loworder, + io_uz_neutral_start_last_timestep, io_pz_neutral, io_pz_neutral_loworder, + io_pz_neutral_start_last_timestep, io_qz_neutral, io_thermal_speed_neutral, + external_source_neutral_amplitude, external_source_neutral_density_amplitude, external_source_neutral_momentum_amplitude, external_source_neutral_pressure_amplitude, external_source_neutral_controller_integral, neutral_constraints_A_coefficient, @@ -1087,17 +1840,19 @@ define dynamic (time-evolving) distribution function variables for writing to th file """ function define_dynamic_dfn_variables!(fid, r, z, vperp, vpa, vzeta, vr, vz, composition, - parallel_io, external_source_settings, + io_input, external_source_settings, evolve_density, evolve_upar, evolve_ppar, t_params, nl_solver_params) @serial_region begin + parallel_io = io_input.parallel_io io_moments = define_dynamic_moment_variables!(fid, composition.n_ion_species, composition.n_neutral_species, r, z, - parallel_io, + io_input, external_source_settings, evolve_density, evolve_upar, - evolve_ppar, t_params, + evolve_ppar, + composition.electron_physics, t_params, nl_solver_params) dynamic = get_group(fid, "dynamic_data") @@ -1107,14 +1862,88 @@ function define_dynamic_dfn_variables!(fid, r, z, vperp, vpa, vzeta, vr, vz, com n_ion_species=composition.n_ion_species, parallel_io=parallel_io, description="ion species distribution function") + if io_input.write_error_diagnostics + io_f_loworder = create_dynamic_variable!(dynamic, "f_loworder", mk_float, vpa, + vperp, z, r; + n_ion_species=composition.n_ion_species, + parallel_io=parallel_io, + description="low-order approximation to ion species distribution function, used to diagnose timestepping error") + else + io_f_loworder = nothing + end + if io_input.write_steady_state_diagnostics + io_f_start_last_timestep = + create_dynamic_variable!(dynamic, "f_start_last_timestep", mk_float, vpa, + vperp, z, r; + n_ion_species=composition.n_ion_species, + parallel_io=parallel_io, + description="ion species distribution function at the start of the last timestep before output, used to measure steady state residual") + else + io_f_start_last_timestep = nothing + end + + if composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + # io_f_electron is the handle for the electron pdf + io_f_electron = create_dynamic_variable!(dynamic, "f_electron", mk_float, vpa, + vperp, z, r; + parallel_io=parallel_io, + description="electron distribution function") + if io_input.write_error_diagnostics + io_f_electron_loworder = + create_dynamic_variable!(dynamic, "f_electron_loworder", mk_float, + vpa, vperp, z, r; + parallel_io=parallel_io, + description="low-order approximation to electron distribution function, used to diagnose timestepping error") + else + io_f_electron_loworder = nothing + end + if io_input.write_steady_state_diagnostics + io_f_electron_start_last_timestep = + create_dynamic_variable!(dynamic, "f_electron_start_last_timestep", + mk_float, vpa, vperp, z, r; + parallel_io=parallel_io, + description="electron distribution function at the start of the last electron pseudo-timestep before output, used to measure steady state residual") + else + io_f_electron_start_last_timestep = nothing + end + else + io_f_electron = nothing + io_f_electron_loworder = nothing + io_f_electron_start_last_timestep = nothing + end # io_f_neutral is the handle for the neutral pdf io_f_neutral = create_dynamic_variable!(dynamic, "f_neutral", mk_float, vz, vr, vzeta, z, r; n_neutral_species=composition.n_neutral_species, parallel_io=parallel_io, description="neutral species distribution function") + if io_input.write_error_diagnostics + io_f_neutral_loworder = + create_dynamic_variable!(dynamic, "f_neutral_loworder", mk_float, vz, vr, + vzeta, z, r; + n_ion_species=composition.n_ion_species, + parallel_io=parallel_io, + description="low-order approximation to neutral species distribution function, used to diagnose timestepping error") + else + io_f_neutral_loworder = nothing + end + if io_input.write_steady_state_diagnostics + io_f_neutral_start_last_timestep = + create_dynamic_variable!(dynamic, "f_neutral_start_last_timestep", + mk_float, vz, vr, vzeta, z, r; + n_ion_species=composition.n_ion_species, + parallel_io=parallel_io, + description="neutral species distribution function at the start of the last timestep before output, used to measure steady state residual") + else + io_f_neutral_start_last_timestep = nothing + end - return io_dfns_info(fid, io_f, io_f_neutral, parallel_io, io_moments) + return io_dfns_info(fid, io_f, io_f_loworder, io_f_start_last_timestep, + io_f_electron, io_f_electron_loworder, + io_f_electron_start_last_timestep, io_f_neutral, + io_f_neutral_loworder, io_f_neutral_start_last_timestep, + parallel_io, io_moments) end # For processes other than the root process of each shared-memory group... @@ -1126,6 +1955,11 @@ Add an attribute to a file, group or variable """ function add_attribute! end +""" +Modify an attribute to a file, group or variable +""" +function modify_attribute! end + """ Low-level function to open a binary output file @@ -1137,25 +1971,25 @@ function open_output_file_implementation end """ Open an output file, selecting the backend based on io_option """ -function open_output_file(prefix, binary_format, parallel_io, io_comm) - check_io_implementation(binary_format) +function open_output_file(prefix, io_input, io_comm) + check_io_implementation(io_input.binary_format) - return open_output_file_implementation(Val(binary_format), prefix, parallel_io, + return open_output_file_implementation(Val(io_input.binary_format), prefix, io_input, io_comm) end """ Re-open an existing output file, selecting the backend based on io_option """ -function reopen_output_file(filename, parallel_io, io_comm) +function reopen_output_file(filename, io_input, io_comm) prefix, format_string = splitext(filename) if format_string == ".h5" check_io_implementation(hdf5) - return open_output_file_implementation(Val(hdf5), prefix, parallel_io, io_comm, + return open_output_file_implementation(Val(hdf5), prefix, io_input, io_comm, "r+")[1] elseif format_string == ".cdf" check_io_implementation(netcdf) - return open_output_file_implementation(Val(netcdf), prefix, parallel_io, io_comm, + return open_output_file_implementation(Val(netcdf), prefix, io_input, io_comm, "a")[1] else error("Unsupported I/O format $binary_format") @@ -1165,17 +1999,18 @@ end """ setup file i/o for moment variables """ -function setup_moments_io(prefix, binary_format, vz, vr, vzeta, vpa, vperp, r, z, +function setup_moments_io(prefix, io_input, vz, vr, vzeta, vpa, vperp, r, z, composition, collisions, evolve_density, evolve_upar, - evolve_ppar, external_source_settings, input_dict, parallel_io, + evolve_ppar, external_source_settings, input_dict, io_comm, run_id, restart_time_index, previous_runs_info, time_for_setup, t_params, nl_solver_params) @serial_region begin moments_prefix = string(prefix, ".moments") + parallel_io = io_input.parallel_io if !parallel_io moments_prefix *= ".$(iblock_index[])" end - fid, file_info = open_output_file(moments_prefix, binary_format, parallel_io, io_comm) + fid, file_info = open_output_file(moments_prefix, io_input, io_comm) # write a header to the output file add_attribute!(fid, "file_info", "Output moments data from the moment_kinetics code") @@ -1198,8 +2033,8 @@ function setup_moments_io(prefix, binary_format, vz, vr, vzeta, vpa, vperp, r, z ### in a struct for later access ### io_moments = define_dynamic_moment_variables!( fid, composition.n_ion_species, composition.n_neutral_species, r, z, - parallel_io, external_source_settings, evolve_density, evolve_upar, - evolve_ppar, t_params, nl_solver_params) + io_input, external_source_settings, evolve_density, evolve_upar, + evolve_ppar, composition.electron_physics, t_params, nl_solver_params) close(fid) @@ -1215,8 +2050,8 @@ Reopen an existing moments output file to append more data """ function reopen_moments_io(file_info) @serial_region begin - filename, parallel_io, io_comm = file_info - fid = reopen_output_file(filename, parallel_io, io_comm) + filename, io_input, io_comm = file_info + fid = reopen_output_file(filename, io_input, io_comm) dyn = get_group(fid, "dynamic_data") variable_list = get_variable_keys(dyn) @@ -1237,14 +2072,41 @@ function reopen_moments_io(file_info) end end return io_moments_info(fid, getvar("time"), getvar("phi"), getvar("Er"), - getvar("Ez"), getvar("density"), getvar("parallel_flow"), - getvar("parallel_pressure"), getvar("perpendicular_pressure"), + getvar("Ez"), getvar("density"), + getvar("density_loworder"), + getvar("density_start_last_timestep"), + getvar("parallel_flow"), getvar("parallel_flow_loworder"), + getvar("parallel_flow_start_last_timestep"), + getvar("parallel_pressure"), + getvar("parallel_pressure_loworder"), + getvar("parallel_pressure_start_last_timestep"), + getvar("perpendicular_pressure"), + getvar("perpendicular_pressure_loworder"), + getvar("perpendicular_pressure_start_last_timestep"), getvar("parallel_heat_flux"), getvar("thermal_speed"), getvar("entropy_production"), getvar("chodura_integral_lower"), - getvar("chodura_integral_upper"), getvar("density_neutral"), - getvar("uz_neutral"), getvar("pz_neutral"), - getvar("qz_neutral"), getvar("thermal_speed_neutral"), + getvar("chodura_integral_upper"), + getvar("electron_density"), + getvar("electron_density_loworder"), + getvar("electron_density_start_last_timestep"), + getvar("electron_parallel_flow"), + getvar("electron_parallel_flow_loworder"), + getvar("electron_parallel_flow_start_last_timestep"), + getvar("electron_parallel_pressure"), + getvar("electron_parallel_pressure_loworder"), + getvar("electron_parallel_pressure_start_last_timestep"), + getvar("electron_parallel_heat_flux"), + getvar("electron_thermal_speed"), + getvar("density_neutral"), + getvar("density_neutral_loworder"), + getvar("density_neutral_start_last_timestep"), + getvar("uz_neutral"), getvar("uz_neutral_loworder"), + getvar("uz_neutral_start_last_timestep"), + getvar("pz_neutral"), getvar("pz_neutral_loworder"), + getvar("pz_neutral_start_last_timestep"), + getvar("qz_neutral"), + getvar("thermal_speed_neutral"), getvar("external_source_amplitude"), getvar("external_source_density_amplitude"), getvar("external_source_momentum_amplitude"), @@ -1255,17 +2117,30 @@ function reopen_moments_io(file_info) getvar("external_source_neutral_momentum_amplitude"), getvar("external_source_neutral_pressure_amplitude"), getvar("external_source_neutral_controller_integral"), + getvar("external_source_electron_amplitude"), + getvar("external_source_electron_density_amplitude"), + getvar("external_source_electron_momentum_amplitude"), + getvar("external_source_electron_pressure_amplitude"), getvar("ion_constraints_A_coefficient"), getvar("ion_constraints_B_coefficient"), getvar("ion_constraints_C_coefficient"), getvar("neutral_constraints_A_coefficient"), getvar("neutral_constraints_B_coefficient"), getvar("neutral_constraints_C_coefficient"), + getvar("electron_constraints_A_coefficient"), + getvar("electron_constraints_B_coefficient"), + getvar("electron_constraints_C_coefficient"), getvar("time_for_run"), getvar("step_counter"), - getvar("dt"), getvar("failure_counter"), + getvar("dt"), getvar("previous_dt"), getvar("failure_counter"), getvar("failure_caused_by"), getvar("limit_caused_by"), - getvar("dt_before_last_fail"), - getvar("nl_solver_diagnostics"), parallel_io) + getvar("dt_before_last_fail"),getvar("electron_step_counter"), + getvar("electron_cumulative_pseudotime"), + getvar("electron_dt"), getvar("electron_previous_dt"), + getvar("electron_failure_counter"), + getvar("electron_failure_caused_by"), + getvar("electron_limit_caused_by"), + getvar("electron_dt_before_last_fail"), + getvar("nl_solver_diagnostics"), io_input) end # For processes other than the root process of each shared-memory group... @@ -1275,18 +2150,19 @@ end """ setup file i/o for distribution function variables """ -function setup_dfns_io(prefix, binary_format, boundary_distributions, r, z, vperp, vpa, - vzeta, vr, vz, composition, collisions, evolve_density, - evolve_upar, evolve_ppar, external_source_settings, input_dict, - parallel_io, io_comm, run_id, restart_time_index, - previous_runs_info, time_for_setup, t_params, nl_solver_params) +function setup_dfns_io(prefix, io_input, boundary_distributions, r, z, vperp, vpa, vzeta, + vr, vz, composition, collisions, evolve_density, evolve_upar, + evolve_ppar, external_source_settings, input_dict, io_comm, run_id, + restart_time_index, previous_runs_info, time_for_setup, t_params, + nl_solver_params) @serial_region begin dfns_prefix = string(prefix, ".dfns") + parallel_io = io_input.parallel_io if !parallel_io dfns_prefix *= ".$(iblock_index[])" end - fid, file_info = open_output_file(dfns_prefix, binary_format, parallel_io, io_comm) + fid, file_info = open_output_file(dfns_prefix, io_input, io_comm) # write a header to the output file add_attribute!(fid, "file_info", @@ -1314,7 +2190,7 @@ function setup_dfns_io(prefix, binary_format, boundary_distributions, r, z, vper ### create variables for time-dependent quantities and store them ### ### in a struct for later access ### io_dfns = define_dynamic_dfn_variables!( - fid, r, z, vperp, vpa, vzeta, vr, vz, composition, parallel_io, + fid, r, z, vperp, vpa, vzeta, vr, vz, composition, io_input, external_source_settings, evolve_density, evolve_upar, evolve_ppar, t_params, nl_solver_params) @@ -1332,8 +2208,8 @@ Reopen an existing distribution-functions output file to append more data """ function reopen_dfns_io(file_info) @serial_region begin - filename, parallel_io, io_comm = file_info - fid = reopen_output_file(filename, parallel_io, io_comm) + filename, io_input, io_comm = file_info + fid = reopen_output_file(filename, io_input, io_comm) dyn = get_group(fid, "dynamic_data") variable_list = get_variable_keys(dyn) @@ -1355,12 +2231,40 @@ function reopen_dfns_io(file_info) end io_moments = io_moments_info(fid, getvar("time"), getvar("phi"), getvar("Er"), getvar("Ez"), getvar("density"), - getvar("parallel_flow"), getvar("parallel_pressure"), + getvar("density_loworder"), + getvar("density_start_last_timestep"), + getvar("parallel_flow"), + getvar("parallel_flow_loworder"), + getvar("parallel_flow_start_last_timestep"), + getvar("parallel_pressure"), + getvar("parallel_pressure_loworder"), + getvar("parallel_pressure_start_last_timestep"), getvar("perpendicular_pressure"), - getvar("parallel_heat_flux"), getvar("thermal_speed"), - getvar("entropy_production"), getvar("chodura_integral_lower"), - getvar("chodura_integral_upper"), getvar("density_neutral"), - getvar("uz_neutral"), getvar("pz_neutral"), + getvar("perpendicular_pressure_loworder"), + getvar("perpendicular_pressure_start_last_timestep"), + getvar("parallel_heat_flux"), + getvar("thermal_speed"), + getvar("entropy_production"), + getvar("chodura_integral_lower"), + getvar("chodura_integral_upper"), + getvar("electron_density"), + getvar("electron_density_loworder"), + getvar("electron_density_start_last_timestep"), + getvar("electron_parallel_flow"), + getvar("electron_parallel_flow_loworder"), + getvar("electron_parallel_flow_start_last_timestep"), + getvar("electron_parallel_pressure"), + getvar("electron_parallel_pressure_loworder"), + getvar("electron_parallel_pressure_start_last_timestep"), + getvar("electron_parallel_heat_flux"), + getvar("electron_thermal_speed"), + getvar("density_neutral"), + getvar("density_neutral_loworder"), + getvar("density_neutral_start_last_timestep"), + getvar("uz_neutral"), getvar("uz_neutral_loworder"), + getvar("uz_neutral_start_last_timestep"), + getvar("pz_neutral"),getvar("pz_neutral_loworder"), + getvar("pz_neutral_start_last_timestep"), getvar("qz_neutral"), getvar("thermal_speed_neutral"), getvar("external_source_amplitude"), @@ -1373,21 +2277,41 @@ function reopen_dfns_io(file_info) getvar("external_source_neutral_momentum_amplitude"), getvar("external_source_neutral_pressure_amplitude"), getvar("external_source_neutral_controller_integral"), + getvar("external_source_electron_amplitude"), + getvar("external_source_electron_density_amplitude"), + getvar("external_source_electron_momentum_amplitude"), + getvar("external_source_electron_pressure_amplitude"), getvar("ion_constraints_A_coefficient"), getvar("ion_constraints_B_coefficient"), getvar("ion_constraints_C_coefficient"), getvar("neutral_constraints_A_coefficient"), getvar("neutral_constraints_B_coefficient"), getvar("neutral_constraints_C_coefficient"), + getvar("electron_constraints_A_coefficient"), + getvar("electron_constraints_B_coefficient"), + getvar("electron_constraints_C_coefficient"), getvar("time_for_run"), getvar("step_counter"), - getvar("dt"), getvar("failure_counter"), + getvar("dt"), getvar("previous_dt"), + getvar("failure_counter"), getvar("failure_caused_by"), getvar("limit_caused_by"), getvar("dt_before_last_fail"), - getvar("nl_solver_diagnostics"), parallel_io) - - return io_dfns_info(fid, getvar("f"), getvar("f_neutral"), parallel_io, - io_moments) + getvar("electron_step_counter"), + getvar("electron_cumulative_pseudotime"), + getvar("electron_dt"), + getvar("electron_previous_dt"), + getvar("electron_failure_counter"), + getvar("electron_failure_caused_by"), + getvar("electron_limit_caused_by"), + getvar("electron_dt_before_last_fail"), + getvar("nl_solver_diagnostics"), io_input) + + return io_dfns_info(fid, getvar("f"), getvar("f_loworder"), + getvar("f_start_last_timestep"), getvar("f_electron"), + getvar("f_electron_loworder"), + getvar("f_electron_start_last_timestep"), getvar("f_neutral"), + getvar("f_neutral_loworder"), + getvar("f_neutral_start_last_timestep"), io_input, io_moments) end # For processes other than the root process of each shared-memory group... @@ -1409,6 +2333,11 @@ each file is only written by one process). """ function append_to_dynamic_var end +function append_to_dynamic_var(data::Nothing, args...; kwargs...) + # Variable was not created to save, so nothing to do. + return nothing +end + @debug_shared_array begin function append_to_dynamic_var(data::DebugMPISharedArray, args...; kwargs...) return append_to_dynamic_var(data.data, args...; kwargs...) @@ -1416,9 +2345,10 @@ function append_to_dynamic_var end end """ -write time-dependent moments data for ions and neutrals to the binary output file +write time-dependent moments data for ions, electrons and neutrals to the binary output +file """ -function write_all_moments_data_to_binary(moments, fields, t, n_ion_species, +function write_all_moments_data_to_binary(scratch, moments, fields, n_ion_species, n_neutral_species, io_or_file_info_moments, t_idx, time_for_run, t_params, nl_solver_params, r, z) @@ -1434,22 +2364,26 @@ function write_all_moments_data_to_binary(moments, fields, t, n_ion_species, closefile = true end - parallel_io = io_moments.parallel_io + parallel_io = io_moments.io_input.parallel_io # add the time for this time slice to the hdf5 file - append_to_dynamic_var(io_moments.time, t, t_idx, parallel_io) + append_to_dynamic_var(io_moments.time, t_params.t[], t_idx, parallel_io) write_em_fields_data_to_binary(fields, io_moments, t_idx, r, z) - write_ion_moments_data_to_binary(moments, n_ion_species, io_moments, - t_idx, r, z) + write_ion_moments_data_to_binary(scratch, moments, n_ion_species, t_params, + io_moments, t_idx, r, z) + + write_electron_moments_data_to_binary(scratch, moments, t_params, + t_params.electron, io_moments, t_idx, r, z) - write_neutral_moments_data_to_binary(moments, n_neutral_species, - io_moments, t_idx, r, z) + write_neutral_moments_data_to_binary(scratch, moments, n_neutral_species, + t_params, io_moments, t_idx, r, z) append_to_dynamic_var(io_moments.time_for_run, time_for_run, t_idx, parallel_io) append_to_dynamic_var(io_moments.step_counter, t_params.step_counter[], t_idx, parallel_io) append_to_dynamic_var(io_moments.dt, t_params.dt_before_output[], t_idx, parallel_io) + append_to_dynamic_var(io_moments.previous_dt, t_params.previous_dt[], t_idx, parallel_io) append_to_dynamic_var(io_moments.failure_counter, t_params.failure_counter[], t_idx, parallel_io) append_to_dynamic_var(io_moments.failure_caused_by, t_params.failure_caused_by, t_idx, parallel_io, length(t_params.failure_caused_by); @@ -1482,12 +2416,11 @@ write time-dependent EM fields data to the binary output file Note: should only be called from within a function that (re-)opens the output file. """ -function write_em_fields_data_to_binary(fields, io_moments::io_moments_info, t_idx, - r, z) +function write_em_fields_data_to_binary(fields, io_moments::io_moments_info, t_idx, r, z) @serial_region begin # Only read/write from first process in each 'block' - parallel_io = io_moments.parallel_io + parallel_io = io_moments.io_input.parallel_io # add the electrostatic potential and electric field components at this time slice to the hdf5 file append_to_dynamic_var(io_moments.phi, fields.phi, t_idx, parallel_io, z, r) @@ -1503,22 +2436,53 @@ write time-dependent moments data for ions to the binary output file Note: should only be called from within a function that (re-)opens the output file. """ -function write_ion_moments_data_to_binary(moments, n_ion_species, +function write_ion_moments_data_to_binary(scratch, moments, n_ion_species, t_params, io_moments::io_moments_info, t_idx, r, z) @serial_region begin # Only read/write from first process in each 'block' - parallel_io = io_moments.parallel_io + parallel_io = io_moments.io_input.parallel_io # add the density data at this time slice to the output file - append_to_dynamic_var(io_moments.density, moments.ion.dens, t_idx, - parallel_io, z, r, n_ion_species) - append_to_dynamic_var(io_moments.parallel_flow, moments.ion.upar, t_idx, - parallel_io, z, r, n_ion_species) - append_to_dynamic_var(io_moments.parallel_pressure, moments.ion.ppar, t_idx, + append_to_dynamic_var(io_moments.density, scratch[t_params.n_rk_stages+1].density, + t_idx, parallel_io, z, r, n_ion_species) + # If options were not set to select the following outputs, then the io variables + # will be `nothing` and nothing will be written. + append_to_dynamic_var(io_moments.density_loworder, scratch[2].density, t_idx, parallel_io, z, r, n_ion_species) - append_to_dynamic_var(io_moments.perpendicular_pressure, moments.ion.pperp, t_idx, + append_to_dynamic_var(io_moments.density_start_last_timestep, scratch[1].density, + t_idx, parallel_io, z, r, n_ion_species) + + append_to_dynamic_var(io_moments.parallel_flow, + scratch[t_params.n_rk_stages+1].upar, t_idx, parallel_io, z, + r, n_ion_species) + # If options were not set to select the following outputs, then the io variables + # will be `nothing` and nothing will be written. + append_to_dynamic_var(io_moments.parallel_flow_loworder, scratch[2].upar, t_idx, parallel_io, z, r, n_ion_species) + append_to_dynamic_var(io_moments.parallel_flow_start_last_timestep, + scratch[1].upar, t_idx, parallel_io, z, r, n_ion_species) + + append_to_dynamic_var(io_moments.parallel_pressure, + scratch[t_params.n_rk_stages+1].ppar, t_idx, parallel_io, z, + r, n_ion_species) + # If options were not set to select the following outputs, then the io variables + # will be `nothing` and nothing will be written. + append_to_dynamic_var(io_moments.parallel_pressure_loworder, scratch[2].ppar, + t_idx, parallel_io, z, r, n_ion_species) + append_to_dynamic_var(io_moments.parallel_pressure_start_last_timestep, + scratch[1].ppar, t_idx, parallel_io, z, r, n_ion_species) + + append_to_dynamic_var(io_moments.perpendicular_pressure, + scratch[t_params.n_rk_stages+1].pperp, t_idx, parallel_io, + z, r, n_ion_species) + # If options were not set to select the following outputs, then the io variables + # will be `nothing` and nothing will be written. + append_to_dynamic_var(io_moments.perpendicular_pressure_loworder, + scratch[2].pperp, t_idx, parallel_io, z, r, n_ion_species) + append_to_dynamic_var(io_moments.perpendicular_pressure_start_last_timestep, + scratch[1].pperp, t_idx, parallel_io, z, r, n_ion_species) + append_to_dynamic_var(io_moments.parallel_heat_flux, moments.ion.qpar, t_idx, parallel_io, z, r, n_ion_species) append_to_dynamic_var(io_moments.thermal_speed, moments.ion.vth, t_idx, @@ -1590,13 +2554,120 @@ function write_ion_moments_data_to_binary(moments, n_ion_species, return nothing end +""" +write time-dependent moments data for electrons to the binary output file + +Note: should only be called from within a function that (re-)opens the output file. +""" +function write_electron_moments_data_to_binary(scratch, moments, t_params, electron_t_params, + io_moments::Union{io_moments_info,io_initial_electron_info}, + t_idx, r, z) + @serial_region begin + # Only read/write from first process in each 'block' + + parallel_io = io_moments.io_input.parallel_io + + if io_moments.electron_density !== nothing + append_to_dynamic_var(io_moments.electron_density, + scratch[t_params.n_rk_stages+1].electron_density, t_idx, + parallel_io, z, r) + # If options were not set to select the following outputs, then the io variables + # will be `nothing` and nothing will be written. + append_to_dynamic_var(io_moments.electron_density_loworder, + scratch[2].electron_density, t_idx, parallel_io, z, r) + append_to_dynamic_var(io_moments.electron_density_start_last_timestep, + scratch[1].electron_density, t_idx, parallel_io, z, r) + end + + if io_moments.electron_parallel_flow !== nothing + append_to_dynamic_var(io_moments.electron_parallel_flow, + scratch[t_params.n_rk_stages+1].electron_upar, t_idx, + parallel_io, z, r) + # If options were not set to select the following outputs, then the io variables + # will be `nothing` and nothing will be written. + append_to_dynamic_var(io_moments.electron_parallel_flow_loworder, + scratch[2].electron_upar, t_idx, parallel_io, z, r) + append_to_dynamic_var(io_moments.electron_parallel_flow_start_last_timestep, + scratch[1].electron_upar, t_idx, parallel_io, z, r) + end + + append_to_dynamic_var(io_moments.electron_parallel_pressure, + scratch[t_params.n_rk_stages+1].electron_ppar, t_idx, + parallel_io, z, r) + # If options were not set to select the following outputs, then the io variables + # will be `nothing` and nothing will be written. + append_to_dynamic_var(io_moments.electron_parallel_pressure_loworder, + scratch[2].electron_ppar, t_idx, parallel_io, z, r) + append_to_dynamic_var(io_moments.electron_parallel_pressure_start_last_timestep, + scratch[1].electron_ppar, t_idx, parallel_io, z, r) + + append_to_dynamic_var(io_moments.electron_parallel_heat_flux, + moments.electron.qpar, t_idx, parallel_io, z, r) + append_to_dynamic_var(io_moments.electron_thermal_speed, moments.electron.vth, + t_idx, parallel_io, z, r) + if io_moments.external_source_electron_amplitude !== nothing + append_to_dynamic_var(io_moments.external_source_electron_amplitude, + moments.electron.external_source_amplitude, t_idx, + parallel_io, z, r) + append_to_dynamic_var(io_moments.external_source_electron_density_amplitude, + moments.electron.external_source_density_amplitude, + t_idx, parallel_io, z, r) + append_to_dynamic_var(io_moments.external_source_electron_momentum_amplitude, + moments.electron.external_source_momentum_amplitude, + t_idx, parallel_io, z, r) + append_to_dynamic_var(io_moments.external_source_electron_pressure_amplitude, + moments.electron.external_source_pressure_amplitude, + t_idx, parallel_io, z, r) + end + append_to_dynamic_var(io_moments.electron_constraints_A_coefficient, + moments.electron.constraints_A_coefficient, t_idx, + parallel_io, z, r) + append_to_dynamic_var(io_moments.electron_constraints_B_coefficient, + moments.electron.constraints_B_coefficient, t_idx, + parallel_io, z, r) + append_to_dynamic_var(io_moments.electron_constraints_C_coefficient, + moments.electron.constraints_C_coefficient, t_idx, + parallel_io, z, r) + + if electron_t_params !== nothing + # Save timestepping info + append_to_dynamic_var(io_moments.electron_step_counter, + electron_t_params.step_counter[], t_idx, parallel_io) + append_to_dynamic_var(io_moments.electron_cumulative_pseudotime, + electron_t_params.t[], t_idx, + parallel_io) + append_to_dynamic_var(io_moments.electron_dt, + electron_t_params.dt_before_output[], t_idx, + parallel_io) + append_to_dynamic_var(io_moments.electron_previous_dt, + electron_t_params.previous_dt[], t_idx, parallel_io) + append_to_dynamic_var(io_moments.electron_failure_counter, + electron_t_params.failure_counter[], t_idx, parallel_io) + append_to_dynamic_var(io_moments.electron_failure_caused_by, + electron_t_params.failure_caused_by, t_idx, parallel_io, + length(electron_t_params.failure_caused_by); + only_root=true) + append_to_dynamic_var(io_moments.electron_limit_caused_by, + electron_t_params.limit_caused_by, t_idx, parallel_io, + length(electron_t_params.limit_caused_by); + only_root=true) + append_to_dynamic_var(io_moments.electron_dt_before_last_fail, + electron_t_params.dt_before_last_fail[], t_idx, + parallel_io) + end + end + + return nothing +end + """ write time-dependent moments data for neutrals to the binary output file Note: should only be called from within a function that (re-)opens the output file. """ -function write_neutral_moments_data_to_binary(moments, n_neutral_species, - io_moments::io_moments_info, t_idx, r, z) +function write_neutral_moments_data_to_binary(scratch, moments, n_neutral_species, + t_params, io_moments::io_moments_info, + t_idx, r, z) if n_neutral_species ≤ 0 return nothing end @@ -1604,19 +2675,48 @@ function write_neutral_moments_data_to_binary(moments, n_neutral_species, @serial_region begin # Only read/write from first process in each 'block' - parallel_io = io_moments.parallel_io + parallel_io = io_moments.io_input.parallel_io - append_to_dynamic_var(io_moments.density_neutral, moments.neutral.dens, t_idx, + append_to_dynamic_var(io_moments.density_neutral, + scratch[t_params.n_rk_stages+1].density_neutral, t_idx, parallel_io, z, r, n_neutral_species) - append_to_dynamic_var(io_moments.uz_neutral, moments.neutral.uz, t_idx, + # If options were not set to select the following outputs, then the io variables + # will be `nothing` and nothing will be written. + append_to_dynamic_var(io_moments.density_neutral_loworder, + scratch[2].density_neutral, t_idx, parallel_io, z, r, + n_neutral_species) + append_to_dynamic_var(io_moments.density_neutral_start_last_timestep, + scratch[1].density_neutral, t_idx, parallel_io, z, r, + n_neutral_species) + + append_to_dynamic_var(io_moments.uz_neutral, + scratch[t_params.n_rk_stages+1].uz_neutral, t_idx, parallel_io, z, r, n_neutral_species) - append_to_dynamic_var(io_moments.pz_neutral, moments.neutral.pz, t_idx, + # If options were not set to select the following outputs, then the io variables + # will be `nothing` and nothing will be written. + append_to_dynamic_var(io_moments.uz_neutral_loworder, + scratch[2].uz_neutral, t_idx, parallel_io, z, r, + n_neutral_species) + append_to_dynamic_var(io_moments.uz_neutral_start_last_timestep, + scratch[1].uz_neutral, t_idx, parallel_io, z, r, + n_neutral_species) + + append_to_dynamic_var(io_moments.pz_neutral, + scratch[t_params.n_rk_stages+1].pz_neutral, t_idx, parallel_io, z, r, n_neutral_species) + # If options were not set to select the following outputs, then the io variables + # will be `nothing` and nothing will be written. + append_to_dynamic_var(io_moments.pz_neutral_loworder, + scratch[2].pz_neutral, t_idx, parallel_io, z, r, + n_neutral_species) + append_to_dynamic_var(io_moments.pz_neutral_start_last_timestep, + scratch[1].pz_neutral, t_idx, parallel_io, z, r, + n_neutral_species) + append_to_dynamic_var(io_moments.qz_neutral, moments.neutral.qz, t_idx, parallel_io, z, r, n_neutral_species) append_to_dynamic_var(io_moments.thermal_speed_neutral, moments.neutral.vth, t_idx, parallel_io, z, r, n_neutral_species) - if io_moments.external_source_neutral_amplitude !== nothing append_to_dynamic_var(io_moments.external_source_neutral_amplitude, moments.neutral.external_source_amplitude, t_idx, @@ -1665,13 +2765,14 @@ function write_neutral_moments_data_to_binary(moments, n_neutral_species, end """ -write time-dependent distribution function data for ions and neutrals to the +write time-dependent distribution function data for ions, electrons and neutrals to the binary output file """ -function write_all_dfns_data_to_binary(pdf, moments, fields, t, n_ion_species, - n_neutral_species, io_or_file_info_dfns, t_idx, - time_for_run, t_params, nl_solver_params, r, z, - vperp, vpa, vzeta, vr, vz) +function write_all_dfns_data_to_binary(scratch, scratch_electron, moments, fields, + n_ion_species, n_neutral_species, + io_or_file_info_dfns, t_idx, time_for_run, + t_params, nl_solver_params, r, z, vperp, vpa, + vzeta, vr, vz) @serial_region begin # Only read/write from first process in each 'block' @@ -1685,14 +2786,18 @@ function write_all_dfns_data_to_binary(pdf, moments, fields, t, n_ion_species, # Write the moments for this time slice to the output file. # This also updates the time. - write_all_moments_data_to_binary(moments, fields, t, n_ion_species, + write_all_moments_data_to_binary(scratch, moments, fields, n_ion_species, n_neutral_species, io_dfns.io_moments, t_idx, time_for_run, t_params, nl_solver_params, r, z) # add the distribution function data at this time slice to the output file - write_ion_dfns_data_to_binary(pdf.ion.norm, n_ion_species, io_dfns, t_idx, r, z, - vperp, vpa) - write_neutral_dfns_data_to_binary(pdf.neutral.norm, n_neutral_species, io_dfns, + write_ion_dfns_data_to_binary(scratch, t_params, n_ion_species, io_dfns, t_idx, r, + z, vperp, vpa) + if scratch_electron !== nothing + write_electron_dfns_data_to_binary(scratch_electron, t_params, io_dfns, t_idx, + r, z, vperp, vpa) + end + write_neutral_dfns_data_to_binary(scratch, t_params, n_neutral_species, io_dfns, t_idx, r, z, vzeta, vr, vz) closefile && close(io_dfns.fid) @@ -1705,15 +2810,57 @@ write time-dependent distribution function data for ions to the binary output fi Note: should only be called from within a function that (re-)opens the output file. """ -function write_ion_dfns_data_to_binary(ff, n_ion_species, io_dfns::io_dfns_info, - t_idx, r, z, vperp, vpa) +function write_ion_dfns_data_to_binary(scratch, t_params, n_ion_species, + io_dfns::io_dfns_info, t_idx, r, z, vperp, vpa) @serial_region begin # Only read/write from first process in each 'block' - parallel_io = io_dfns.parallel_io + parallel_io = io_dfns.io_input.parallel_io - append_to_dynamic_var(io_dfns.f, ff, t_idx, parallel_io, vpa, vperp, z, r, - n_ion_species) + append_to_dynamic_var(io_dfns.f, scratch[t_params.n_rk_stages+1].pdf, t_idx, + parallel_io, vpa, vperp, z, r, n_ion_species) + # If options were not set to select the following outputs, then the io variables + # will be `nothing` and nothing will be written. + append_to_dynamic_var(io_dfns.f_loworder, scratch[2].pdf, t_idx, + parallel_io, vpa, vperp, z, r, n_ion_species) + append_to_dynamic_var(io_dfns.f_start_last_timestep, scratch[1].pdf, t_idx, + parallel_io, vpa, vperp, z, r, n_ion_species) + end + return nothing +end + +""" +write time-dependent distribution function data for electrons to the binary output file + +Note: should only be called from within a function that (re-)opens the output file. +""" +function write_electron_dfns_data_to_binary(scratch_electron, t_params, + io_dfns::Union{io_dfns_info,io_initial_electron_info}, + t_idx, r, z, vperp, vpa) + @serial_region begin + # Only read/write from first process in each 'block' + + parallel_io = io_dfns.io_input.parallel_io + + if io_dfns.f_electron !== nothing + if t_params.electron === nothing + # t_params is the t_params for electron timestepping + n_rk_stages = t_params.n_rk_stages + else + n_rk_stages = t_params.electron.n_rk_stages + end + append_to_dynamic_var(io_dfns.f_electron, + scratch_electron[n_rk_stages+1].pdf_electron, + t_idx, parallel_io, vpa, vperp, z, r) + # If options were not set to select the following outputs, then the io + # variables will be `nothing` and nothing will be written. + append_to_dynamic_var(io_dfns.f_electron_loworder, + scratch_electron[2].pdf_electron, + t_idx, parallel_io, vpa, vperp, z, r) + append_to_dynamic_var(io_dfns.f_electron_start_last_timestep, + scratch_electron[1].pdf_electron, + t_idx, parallel_io, vpa, vperp, z, r) + end end return nothing end @@ -1723,19 +2870,74 @@ write time-dependent distribution function data for neutrals to the binary outpu Note: should only be called from within a function that (re-)opens the output file. """ -function write_neutral_dfns_data_to_binary(ff_neutral, n_neutral_species, +function write_neutral_dfns_data_to_binary(scratch, t_params, n_neutral_species, io_dfns::io_dfns_info, t_idx, r, z, vzeta, vr, vz) @serial_region begin # Only read/write from first process in each 'block' - parallel_io = io_dfns.parallel_io + parallel_io = io_dfns.io_input.parallel_io if n_neutral_species > 0 - append_to_dynamic_var(io_dfns.f_neutral, ff_neutral, t_idx, parallel_io, vz, - vr, vzeta, z, r, n_neutral_species) + append_to_dynamic_var(io_dfns.f_neutral, + scratch[t_params.n_rk_stages+1].pdf_neutral, t_idx, + parallel_io, vz, vr, vzeta, z, r, n_neutral_species) + # If options were not set to select the following outputs, then the io + # variables will be `nothing` and nothing will be written. + append_to_dynamic_var(io_dfns.f_neutral_loworder, scratch[2].pdf_neutral, + t_idx, parallel_io, vz, vr, vzeta, z, r, + n_neutral_species) + append_to_dynamic_var(io_dfns.f_neutral_start_last_timestep, + scratch[1].pdf_neutral, t_idx, parallel_io, vz, vr, + vzeta, z, r, n_neutral_species) + end + end + return nothing +end + +""" + write_electron_state(scratch_electron, moments, t_params, io_initial_electron, + t_idx, r, z, vperp, vpa; pdf_electron_converged=false) + +Write the electron state to an output file. +""" +function write_electron_state(scratch_electron, moments, t_params, + io_or_file_info_initial_electron, t_idx, r, z, vperp, vpa; + pdf_electron_converged=false) + + @serial_region begin + # Only read/write from first process in each 'block' + + if isa(io_or_file_info_initial_electron, io_dfns_info) + io_initial_electron = io_or_file_info_initial_electron + closefile = false + else + io_initial_electron = reopen_initial_electron_io(io_or_file_info_initial_electron) + closefile = true + end + + parallel_io = io_initial_electron.io_input.parallel_io + + # add the pseudo-time for this time slice to the hdf5 file + append_to_dynamic_var(io_initial_electron.time, + t_params.t[], t_idx, parallel_io) + append_to_dynamic_var(io_initial_electron.electron_cumulative_pseudotime, + t_params.t[], t_idx, parallel_io) + + write_electron_dfns_data_to_binary(scratch_electron, t_params, + io_initial_electron, t_idx, r, z, vperp, vpa) + + write_electron_moments_data_to_binary(scratch_electron, moments, t_params, + t_params, io_initial_electron, t_idx, r, z) + + if pdf_electron_converged + modify_attribute!(io_initial_electron.fid, "pdf_electron_converged", + pdf_electron_converged) end + + closefile && close(io_initial_electron.fid) end + return nothing end @@ -1768,6 +2970,24 @@ function finish_file_io(ascii_io::Union{ascii_ios,Nothing}, return nothing end +""" +close output files for electron initialization +""" +function finish_electron_io( + binary_initial_electron::Union{io_initial_electron_info,Tuple,Nothing,Bool}) + + @serial_region begin + # Only read/write from first process in each 'block' + + if (binary_initial_electron !== nothing && !isa(binary_initial_electron, Tuple) + && !isa(binary_initial_electron, Bool)) + + close(binary_initial_electron.fid) + end + end + return nothing +end + # Include the non-optional implementations of binary I/O functions include("file_io_hdf5.jl") @@ -1785,6 +3005,7 @@ function write_data_to_ascii(pdf, moments, fields, vpa, vperp, z, r, t, n_ion_sp write_f_ascii(pdf, z, vpa, t, ascii_io.ff) write_moments_ion_ascii(moments.ion, z, r, t, n_ion_species, ascii_io.moments_ion) + write_moments_electron_ascii(moments.electron, z, r, t, ascii_io.moments_electron) if n_neutral_species > 0 write_moments_neutral_ascii(moments.neutral, z, r, t, n_neutral_species, ascii_io.moments_neutral) end @@ -1842,6 +3063,27 @@ function write_moments_ion_ascii(mom, z, r, t, n_species, ascii_io) return nothing end +""" +write moments of the ion species distribution function f at this time slice +""" +function write_moments_electron_ascii(mom, z, r, t, ascii_io) + @serial_region begin + # Only read/write from first process in each 'block' + + @inbounds begin + for ir ∈ 1:r.n + for iz ∈ 1:z.n + println(ascii_io,"t: ", t, " r: ", r.grid[ir], " z: ", z.grid[iz], + " dens: ", mom.dens[iz,ir], " upar: ", mom.upar[iz,ir], + " ppar: ", mom.ppar[iz,ir], " qpar: ", mom.qpar[iz,ir]) + end + end + end + println(ascii_io,"") + end + return nothing +end + """ write moments of the neutral species distribution function f_neutral at this time slice """ @@ -1855,7 +3097,8 @@ function write_moments_neutral_ascii(mom, z, r, t, n_species, ascii_io) for iz ∈ 1:z.n println(ascii_io,"t: ", t, " species: ", is, " r: ", r.grid[ir], " z: ", z.grid[iz], " dens: ", mom.dens[iz,ir,is], " uz: ", mom.uz[iz,ir,is], - " ur: ", mom.ur[iz,ir,is], " uzeta: ", mom.uzeta[iz,ir,is]) + " ur: ", mom.ur[iz,ir,is], " uzeta: ", mom.uzeta[iz,ir,is], + " pz: ", mom.pz[iz,ir,is]) end end end @@ -1981,14 +3224,15 @@ function debug_dump(vz::coordinate, vr::coordinate, vzeta::coordinate, vpa::coor ### in a struct for later access ### io_moments = define_dynamic_moment_variables!(fid, composition.n_ion_species, composition.n_neutral_species, - r, z, false, + r, z, nothing, external_source_settings, evolve_density, evolve_upar, - evolve_ppar, t_params, + evolve_ppar, + composition.electron_physics, t_params, nl_solver_params) io_dfns = define_dynamic_dfn_variables!( fid, r, z, vperp, vpa, vzeta, vr, vz, composition.n_ion_species, - composition.n_neutral_species, false, external_source_settings, + composition.n_neutral_species, nothing, external_source_settings, evolve_density, evolve_upar, evolve_ppar, t_params, nl_solver_params) # create the "istage" variable, used to identify the rk stage where diff --git a/moment_kinetics/src/file_io_hdf5.jl b/moment_kinetics/src/file_io_hdf5.jl index c40dea9d5..271d79fee 100644 --- a/moment_kinetics/src/file_io_hdf5.jl +++ b/moment_kinetics/src/file_io_hdf5.jl @@ -7,7 +7,7 @@ function io_has_parallel(::Val{hdf5}) return HDF5.has_parallel() end -function open_output_file_implementation(::Val{hdf5}, prefix, parallel_io, io_comm, mode="cw") +function open_output_file_implementation(::Val{hdf5}, prefix, io_input, io_comm, mode="cw") # the hdf5 file will be given by output_dir/run_name with .h5 appended filename = string(prefix, ".h5") @@ -16,7 +16,7 @@ function open_output_file_implementation(::Val{hdf5}, prefix, parallel_io, io_co * "characters), which will cause an error in HDF5.") end # create the new HDF5 file - if parallel_io + if io_input.parallel_io # if a file with the requested name already exists, remove it if mode == "cw" && MPI.Comm_rank(io_comm) == 0 && isfile(filename) rm(filename) @@ -32,7 +32,7 @@ function open_output_file_implementation(::Val{hdf5}, prefix, parallel_io, io_co fid = h5open(filename, mode) end - return fid, (filename, parallel_io, io_comm) + return fid, (filename, io_input, io_comm) end # HDF5.H5DataStore is the supertype for HDF5.File and HDF5.Group @@ -54,6 +54,11 @@ function add_attribute!(var::HDF5.Dataset, name, value) attributes(var)[name] = value end +# HDF5.H5DataStore is the supertype for HDF5.File and HDF5.Group +function modify_attribute!(file_or_group_or_var::Union{HDF5.H5DataStore,HDF5.Dataset}, name, value) + attrs(file_or_group_or_var)[name] = value +end + function get_group(file_or_group::HDF5.H5DataStore, name::String) # This overload deals with cases where fid is an HDF5 `File` or `Group` (`H5DataStore` # is the abstract super-type for both diff --git a/moment_kinetics/src/fokker_planck.jl b/moment_kinetics/src/fokker_planck.jl index 1f520e9f6..fb9c6ef19 100644 --- a/moment_kinetics/src/fokker_planck.jl +++ b/moment_kinetics/src/fokker_planck.jl @@ -90,6 +90,7 @@ function setup_fkpl_collisions_input(toml_input::Dict, reference_params) nuii = -1.0, frequency_option = "reference_parameters", self_collisions = true, + use_conserving_corrections = true, slowing_down_test = false, sd_density = 1.0, sd_temp = 0.01, @@ -257,6 +258,7 @@ function explicit_fp_collisions_weak_form_Maxwellian_cross_species!(pdf_out,pdf_ @boundscheck r.n == size(dSdt,2) || throw(BoundsError(dSdt)) @boundscheck n_ion_species == size(dSdt,3) || throw(BoundsError(dSdt)) + use_conserving_corrections = collisions.fkpl.use_conserving_corrections fkin = collisions.fkpl # masses charge numbers and collision frequencies mref = 1.0 # generalise if multiple ions evolved @@ -282,8 +284,10 @@ function explicit_fp_collisions_weak_form_Maxwellian_cross_species!(pdf_out,pdf_ # enforce the boundary conditions on CC before it is used for timestepping enforce_vpavperp_BCs!(fkpl_arrays.CC,vpa,vperp,vpa_spectral,vperp_spectral) # make sure that the cross-species terms conserve density - density_conserving_correction!(fkpl_arrays.CC, pdf_in[:,:,iz,ir,is], vpa, vperp, + if use_conserving_corrections + density_conserving_correction!(fkpl_arrays.CC, pdf_in[:,:,iz,ir,is], vpa, vperp, fkpl_arrays.S_dummy) + end # advance this part of s,r,z with the resulting sum_s' C[Fs,Fs'] begin_anyv_vperp_vpa_region() CC = fkpl_arrays.CC @@ -326,6 +330,7 @@ function explicit_fokker_planck_collisions_weak_form!(pdf_out,pdf_in,dSdt,compos nuref = collisions.fkpl.nuii # generalise! Zi = collisions.fkpl.Zi # generalise! nussp = nuref*(Zi^4) # include charge number factor for self collisions + use_conserving_corrections = collisions.fkpl.use_conserving_corrections # N.B. parallelisation using special 'anyv' region begin_s_r_z_anyv_region() @loop_s_r_z is ir iz begin @@ -336,9 +341,10 @@ function explicit_fokker_planck_collisions_weak_form!(pdf_out,pdf_in,dSdt,compos # enforce the boundary conditions on CC before it is used for timestepping enforce_vpavperp_BCs!(fkpl_arrays.CC,vpa,vperp,vpa_spectral,vperp_spectral) # make ad-hoc conserving corrections - conserving_corrections!(fkpl_arrays.CC, pdf_in[:,:,iz,ir,is], vpa, vperp, + if use_conserving_corrections + conserving_corrections!(fkpl_arrays.CC, pdf_in[:,:,iz,ir,is], vpa, vperp, fkpl_arrays.S_dummy) - + end # advance this part of s,r,z with the resulting C[Fs,Fs] begin_anyv_vperp_vpa_region() CC = fkpl_arrays.CC diff --git a/moment_kinetics/src/fokker_planck_calculus.jl b/moment_kinetics/src/fokker_planck_calculus.jl index 300e0ca6e..f11dcf43c 100644 --- a/moment_kinetics/src/fokker_planck_calculus.jl +++ b/moment_kinetics/src/fokker_planck_calculus.jl @@ -2287,21 +2287,29 @@ function enforce_vpavperp_BCs!(pdf,vpa,vperp,vpa_spectral,vperp_spectral) D0 = vperp_spectral.radau.D0 # vpa boundary conditions # zero at infinity - begin_anyv_vperp_region() - @loop_vperp ivperp begin - pdf[1,ivperp] = 0.0 - pdf[nvpa,ivperp] = 0.0 + if vpa.bc == "zero" + begin_anyv_vperp_region() + @loop_vperp ivperp begin + pdf[1,ivperp] = 0.0 + pdf[nvpa,ivperp] = 0.0 + end end # vperp boundary conditions # zero boundary condition at infinity # set regularity condition d F / d vperp = 0 at vperp = 0 # adjust F(vperp = 0) so that d F / d vperp = 0 at vperp = 0 begin_anyv_vpa_region() - buffer = @view vperp.scratch[1:ngrid_vperp-1] - @loop_vpa ivpa begin - pdf[ivpa,nvperp] = 0.0 - @views @. buffer = D0[2:ngrid_vperp] * pdf[ivpa,2:ngrid_vperp] - pdf[ivpa,1] = -sum(buffer)/D0[1] + if vperp.bc in ("zero", "zero-impose-regularity") + @loop_vpa ivpa begin + pdf[ivpa,nvperp] = 0.0 + end + end + if vperp.bc == "zero-impose-regularity" + buffer = @view vperp.scratch[1:ngrid_vperp-1] + @loop_vpa ivpa begin + @views @. buffer = D0[2:ngrid_vperp] * pdf[ivpa,2:ngrid_vperp] + pdf[ivpa,1] = -sum(buffer)/D0[1] + end end end diff --git a/moment_kinetics/src/gauss_legendre.jl b/moment_kinetics/src/gauss_legendre.jl index 539a5fd86..9a0afb6b3 100644 --- a/moment_kinetics/src/gauss_legendre.jl +++ b/moment_kinetics/src/gauss_legendre.jl @@ -100,7 +100,7 @@ struct gausslegendre_info{TSparse, TLU} <: weak_discretization_info Qmat::Array{mk_float,2} end -function setup_gausslegendre_pseudospectral(coord; collision_operator_dim=true, dirichlet_bc=true) +function setup_gausslegendre_pseudospectral(coord; collision_operator_dim=true, dirichlet_bc=false) lobatto = setup_gausslegendre_pseudospectral_lobatto(coord,collision_operator_dim=collision_operator_dim) radau = setup_gausslegendre_pseudospectral_radau(coord,collision_operator_dim=collision_operator_dim) @@ -887,9 +887,12 @@ function setup_global_weak_form_matrix!(QQ_global::Array{mk_float,2}, if dirichlet_bc # Make matrix diagonal for first/last grid points so it does not change the values # there - if coord.irank == 0 - QQ_global[1,:] .= 0.0 - QQ_global[1,1] = 1.0 + if !(coord.name == "vperp") + # modify lower endpoint if not a radial/cylindrical coordinate + if coord.irank == 0 + QQ_global[1,:] .= 0.0 + QQ_global[1,1] = 1.0 + end end if coord.irank == coord.nrank - 1 QQ_global[end,:] .= 0.0 diff --git a/moment_kinetics/src/geo.jl b/moment_kinetics/src/geo.jl index 9ad6d4885..3635230dc 100644 --- a/moment_kinetics/src/geo.jl +++ b/moment_kinetics/src/geo.jl @@ -8,7 +8,7 @@ module geo export init_magnetic_geometry export setup_geometry_input -using ..input_structs: geometry_input +using ..input_structs: geometry_input, set_defaults_and_check_section! using ..file_io: input_option_error using ..array_allocation: allocate_float using ..type_definitions: mk_float, mk_int @@ -73,11 +73,26 @@ option = "" """ function setup_geometry_input(toml_input::Dict, reference_rhostar) - input_section = get(toml_input, "geometry", Dict{String,Any}()) - if !("rhostar" ∈ keys(input_section)) - # Set default rhostar with reference value - input_section["rhostar"] = get(input_section, "rhostar", reference_rhostar) - end + # read the input toml and specify a sensible default + input_section = set_defaults_and_check_section!(toml_input, "geometry", + # begin default inputs (as kwargs) + # rhostar ion (ref) + rhostar = reference_rhostar, #used to premultiply ExB drift terms + # magnetic geometry option + option = "constant-helical",# "1D-mirror" + # pitch ( = Bzed/Bmag if geometry_option == "constant-helical") + pitch = 1.0, + # DeltaB ( = (Bzed(z=L/2) - Bzed(0))/Bref if geometry_option == "1D-mirror") + DeltaB = 0.0, + # constant for testing nonzero Er when nr = 1 + Er_constant = 0.0, + # constant for testing nonzero Ez when nz = 1 + Ez_constant = 0.0, + # constant for testing nonzero dBdz when nz = 1 + dBdz_constant = 0.0, + # constant for testing nonzero dBdr when nr = 1 + dBdr_constant = 0.0) + input = Dict(Symbol(k)=>v for (k,v) in input_section) #println(input) return geometry_input(; input...) @@ -187,6 +202,31 @@ function init_magnetic_geometry(geometry_input_data::geometry_input,z,r) gbdriftz[iz,ir] = cvdriftz[iz,ir] end end + elseif option == "0D-Spitzer-test" + # a 0D configuration with certain geometrical factors + # set to be constants to enable testing of velocity + # space operators such as mirror or vperp advection terms + pitch = geometry_input_data.pitch + dBdz_constant = geometry_input_data.dBdz_constant + dBdr_constant = geometry_input_data.dBdr_constant + B0 = 1.0 # chose reference field strength to be Bzeta at r = 1 + for ir in 1:nr + for iz in 1:nz + Bmag[iz,ir] = B0 + bzed[iz,ir] = pitch + bzeta[iz,ir] = sqrt(1 - pitch^2) + Bzed[iz,ir] = bzed[iz,ir]*Bmag[iz,ir] + Bzeta[iz,ir] = bzeta[iz,ir]*Bmag[iz,ir] + dBdz[iz,ir] = dBdz_constant + dBdr[iz,ir] = dBdr_constant + jacobian[iz,ir] = 1.0 + + cvdriftr[iz,ir] = 0.0 + cvdriftz[iz,ir] = 0.0 + gbdriftr[iz,ir] = 0.0 + gbdriftz[iz,ir] = 0.0 + end + end else input_option_error("$option", option) end diff --git a/moment_kinetics/src/initial_conditions.jl b/moment_kinetics/src/initial_conditions.jl index da0841fa3..8d7db7598 100644 --- a/moment_kinetics/src/initial_conditions.jl +++ b/moment_kinetics/src/initial_conditions.jl @@ -4,12 +4,14 @@ module initial_conditions export allocate_pdf_and_moments export init_pdf_and_moments! +export initialize_electrons! # functional testing export create_boundary_distributions export create_pdf # package +using Dates using SpecialFunctions: erfc # modules using ..type_definitions: mk_float, mk_int @@ -20,14 +22,29 @@ using ..communication using ..external_sources using ..interpolation: interpolate_to_grid_1d! using ..looping -using ..moment_kinetics_structs: scratch_pdf, pdf_substruct, +using ..electron_kinetic_equation: implicit_electron_advance! +using ..em_fields: update_phi! +using ..file_io: setup_electron_io, write_electron_state, finish_electron_io +using ..load_data: reload_electron_data! +using ..moment_kinetics_structs: scratch_pdf, pdf_substruct, electron_pdf_substruct, pdf_struct, moments_struct, boundary_distributions_struct +using ..nonlinear_solvers: nl_solver_info using ..velocity_moments: integrate_over_vspace, integrate_over_neutral_vspace using ..velocity_moments: integrate_over_positive_vz, integrate_over_negative_vz -using ..velocity_moments: create_moments_ion, create_moments_neutral, update_qpar! +using ..velocity_moments: create_moments_ion, create_moments_electron, create_moments_neutral +using ..velocity_moments: update_qpar! using ..velocity_moments: update_neutral_density!, update_neutral_pz!, update_neutral_pr!, update_neutral_pzeta! using ..velocity_moments: update_neutral_uz!, update_neutral_ur!, update_neutral_uzeta!, update_neutral_qz! using ..velocity_moments: update_ppar!, update_upar!, update_density!, update_pperp!, update_vth!, reset_moments_status! +using ..electron_fluid_equations: calculate_electron_density! +using ..electron_fluid_equations: calculate_electron_upar_from_charge_conservation! +using ..electron_fluid_equations: calculate_electron_qpar!, electron_fluid_qpar_boundary_condition! +using ..electron_fluid_equations: calculate_electron_parallel_friction_force! +using ..electron_kinetic_equation: update_electron_pdf!, enforce_boundary_condition_on_electron_pdf! +using ..input_structs +using ..derivatives: derivative_z! +using ..utils: get_default_restart_filename, get_prefix_iblock_and_move_existing_file, + get_backup_filename using ..manufactured_solns: manufactured_solutions @@ -38,7 +55,7 @@ Creates the structs for the pdf and the velocity-space moments """ function allocate_pdf_and_moments(composition, r, z, vperp, vpa, vzeta, vr, vz, evolve_moments, collisions, external_source_settings, - num_diss_params) + num_diss_params, t_input) pdf = create_pdf(composition, r, z, vperp, vpa, vzeta, vr, vz) # create the 'moments' struct that contains various v-space moments and other @@ -50,6 +67,8 @@ function allocate_pdf_and_moments(composition, r, z, vperp, vpa, vzeta, vr, vz, evolve_moments.density, evolve_moments.parallel_flow, evolve_moments.parallel_pressure, external_source_settings.ion, num_diss_params) + electron = create_moments_electron(z.n, r.n, + composition.electron_physics, num_diss_params) neutral = create_moments_neutral(z.n, r.n, composition.n_neutral_species, evolve_moments.density, evolve_moments.parallel_flow, evolve_moments.parallel_pressure, external_source_settings.neutral, @@ -64,7 +83,7 @@ function allocate_pdf_and_moments(composition, r, z, vperp, vpa, vzeta, vr, vz, particle_number_conserved = true end - moments = moments_struct(ion, neutral, evolve_moments.density, + moments = moments_struct(ion, electron, neutral, evolve_moments.density, particle_number_conserved, evolve_moments.conservation, evolve_moments.parallel_flow, @@ -87,19 +106,36 @@ function create_pdf(composition, r, z, vperp, vpa, vzeta, vr, vz) pdf_neutral_norm = allocate_shared_float(vz.n, vr.n, vzeta.n, z.n, r.n, composition.n_neutral_species) # buffer array is for neutral-ion collisions, not for storing neutral pdf pdf_neutral_buffer = allocate_shared_float(vz.n, vr.n, vzeta.n, z.n, r.n, composition.n_ion_species) + if composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + pdf_electron_norm = allocate_shared_float(vpa.n, vperp.n, z.n, r.n) + # MB: not sure if pdf_electron_buffer will ever be needed, but create for now + # to emulate ion and neutral behaviour + pdf_electron_buffer = allocate_shared_float(vpa.n, vperp.n, z.n, r.n) + pdf_before_ion_timestep = allocate_shared_float(vpa.n, vperp.n, z.n, r.n) + electron_substruct = electron_pdf_substruct(pdf_electron_norm, + pdf_electron_buffer, + pdf_before_ion_timestep) + else + electron_substruct = nothing + end + return pdf_struct(pdf_substruct(pdf_ion_norm, pdf_ion_buffer), + electron_substruct, pdf_substruct(pdf_neutral_norm, pdf_neutral_buffer)) end """ -creates the normalised pdf and the velocity-space moments and populates them +creates the normalised pdfs and the velocity-space moments and populates them with a self-consistent initial condition """ -function init_pdf_and_moments!(pdf, moments, boundary_distributions, geometry, - composition, r, z, vperp, vpa, vzeta, vr, vz, - vpa_spectral, vz_spectral, species, - external_source_settings, manufactured_solns_input) +function init_pdf_and_moments!(pdf, moments, fields, boundary_distributions, geometry, composition, r, z, + vperp, vpa, vzeta, vr, vz, z_spectral, r_spectral, + vperp_spectral, vpa_spectral, vz_spectral, species, + collisions, external_source_settings, + manufactured_solns_input, t_input, num_diss_params, + advection_structs, io_input, input_dict) if manufactured_solns_input.use_for_init init_pdf_moments_manufactured_solns!(pdf, moments, vz, vr, vzeta, vpa, vperp, z, r, composition.n_ion_species, @@ -162,6 +198,7 @@ function init_pdf_and_moments!(pdf, moments, boundary_distributions, geometry, # when evolve_ppar = true. initialize_pdf!(pdf, moments, boundary_distributions, composition, r, z, vperp, vpa, vzeta, vr, vz, vpa_spectral, vz_spectral, species) + begin_s_r_z_region() # calculate the initial parallel heat flux from the initial un-normalised pdf update_qpar!(moments.ion.qpar, moments.ion.qpar_updated, @@ -169,6 +206,25 @@ function init_pdf_and_moments!(pdf, moments, boundary_distributions, geometry, pdf.ion.norm, vpa, vperp, z, r, composition, moments.evolve_density, moments.evolve_upar, moments.evolve_ppar) + begin_serial_region() + @serial_region begin + # If electrons are being used, they will be initialized properly later. Here + # we only set the values to avoid false positives from the debug checks + # (when @debug_track_initialized is active). + moments.electron.dens .= 0.0 + moments.electron.upar .= 0.0 + moments.electron.ppar .= 0.0 + moments.electron.qpar .= 0.0 + moments.electron.temp .= 0.0 + moments.electron.constraints_A_coefficient .= 1.0 + moments.electron.constraints_B_coefficient .= 0.0 + moments.electron.constraints_C_coefficient .= 0.0 + if composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + pdf.electron.norm .= 0.0 + end + end + initialize_external_source_amplitude!(moments, external_source_settings, vperp, vzeta, vr, n_neutral_species) initialize_external_source_controller_integral!(moments, external_source_settings, @@ -204,6 +260,225 @@ function init_pdf_and_moments!(pdf, moments, boundary_distributions, geometry, return nothing end +function initialize_electrons!(pdf, moments, fields, geometry, composition, r, z, + vperp, vpa, vzeta, vr, vz, z_spectral, r_spectral, + vperp_spectral, vpa_spectral, collisions, gyroavs, + external_source_settings, scratch_dummy, scratch, + scratch_electron, nl_solver_params, t_params, t_input, + num_diss_params, advection_structs, io_input, input_dict; + restart_electron_physics) + + moments.electron.dens_updated[] = false + # initialise the electron density profile + init_electron_density!(moments.electron.dens, moments.electron.dens_updated, moments.ion.dens) + # initialise the electron parallel flow profile + moments.electron.upar_updated[] = false + init_electron_upar!(moments.electron.upar, moments.electron.upar_updated, moments.electron.dens, + moments.ion.upar, moments.ion.dens, composition.electron_physics, r, z) + # different choices for initialization of electron temperature/pressure/vth depending on whether + # we are restarting from a previous simulation with Boltzmann electrons or not + if restart_electron_physics === nothing + # Not restarting, so create initial profiles + + # initialise the electron thermal speed profile + init_electron_vth!(moments.electron.vth, moments.ion.vth, composition.T_e, composition.me_over_mi, z.grid) + begin_r_z_region() + # calculate the electron temperature from the thermal speed + @loop_r_z ir iz begin + moments.electron.temp[iz,ir] = composition.me_over_mi * moments.electron.vth[iz,ir]^2 + end + # calculate the electron parallel pressure from the density and temperature + @loop_r_z ir iz begin + moments.electron.ppar[iz,ir] = 0.5 * moments.electron.dens[iz,ir] * moments.electron.temp[iz,ir] + end + elseif restart_electron_physics ∉ (braginskii_fluid, kinetic_electrons, + kinetic_electrons_with_temperature_equation) + # Restarting from Boltzmann electron simulation, so start with constant + # electron temperature + begin_serial_region() + @serial_region begin + # if restarting from a simulations where Boltzmann electrons were used, then the assumption is + # that the electron parallel temperature is constant along the field line and equal to T_e + moments.electron.temp .= composition.T_e + # the thermal speed is related to the temperature by vth_e / v_ref = sqrt((T_e/T_ref) / (m_e/m_ref)) + moments.electron.vth .= sqrt(composition.T_e / composition.me_over_mi) + # ppar = 0.5 * n * T, so we can calculate the parallel pressure from the density and T_e + moments.electron.ppar .= 0.5 * moments.electron.dens * composition.T_e + end + end # else, we are restarting from `braginskii_fluid` or `kinetic_electrons`, so keep the reloaded electron pressure/temperature profiles. + + # the electron temperature has now been updated + moments.electron.temp_updated[] = true + # the electron parallel pressure now been updated + moments.electron.ppar_updated[] = true + + # calculate the zed derivative of the initial electron density + @views derivative_z!(moments.electron.ddens_dz, moments.electron.dens, + scratch_dummy.buffer_rs_1[:,1], scratch_dummy.buffer_rs_2[:,1], scratch_dummy.buffer_rs_3[:,1], + scratch_dummy.buffer_rs_4[:,1], z_spectral, z) + # calculate the zed derivative of the initial electron temperature + @views derivative_z!(moments.electron.dT_dz, moments.electron.temp, + scratch_dummy.buffer_rs_1[:,1], scratch_dummy.buffer_rs_2[:,1], scratch_dummy.buffer_rs_3[:,1], + scratch_dummy.buffer_rs_4[:,1], z_spectral, z) + # calculate the zed derivative of the initial electron thermal speed + @views derivative_z!(moments.electron.dvth_dz, moments.electron.vth, + scratch_dummy.buffer_rs_1[:,1], scratch_dummy.buffer_rs_2[:,1], scratch_dummy.buffer_rs_3[:,1], + scratch_dummy.buffer_rs_4[:,1], z_spectral, z) + # calculate the zed derivative of the initial electron parallel pressure + @views derivative_z!(moments.electron.dppar_dz, moments.electron.ppar, + scratch_dummy.buffer_rs_1[:,1], scratch_dummy.buffer_rs_2[:,1], scratch_dummy.buffer_rs_3[:,1], + scratch_dummy.buffer_rs_4[:,1], z_spectral, z) + if composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + # Initialise the array for the electron pdf + begin_serial_region() + speed = @view scratch_dummy.buffer_vpavperpzrs_1[:,:,:,:,1] + @serial_region begin + speed .= 0.0 + end + init_electron_pdf_over_density_and_boundary_phi!( + pdf.electron.norm, fields.phi, moments.electron.dens, moments.electron.upar, + moments.electron.vth, z, vpa, vperp, vperp_spectral, vpa_spectral, + [(speed=speed,)], moments, num_diss_params, + composition.me_over_mi, scratch_dummy) + end + # calculate the initial electron parallel heat flux; + # if using kinetic electrons, this relies on the electron pdf, which itself relies on the electron heat flux + if composition.electron_physics == braginskii_fluid + electron_fluid_qpar_boundary_condition!( + moments.electron.ppar, moments.electron.upar, moments.electron.dens, + moments.electron, z) + if restart_electron_physics ∉ (nothing, braginskii_fluid, kinetic_electrons, + kinetic_electrons_with_temperature_equation) + # Restarting from Boltzmann. If we use an exactly constant T_e profile, + # qpar for the electrons will be non-zero only at the boundary points, + # which will crash the code unless the timestep is insanely small. Give + # T_e a cubic profile with gradients at the boundaries that match the + # boundary qpar. The boundary values are both the constant 'Boltzmann + # electron' temperature + begin_serial_region() + @serial_region begin + # Ignore the 0.71*p_e*(u_i-u_e) term here as it would vanish when u_i=u_e + # and this is only a rough initial condition anyway + # + # q at the boundaries tells us dTe/dz for Braginskii electrons + if z.irank == 0 + dTe_dz_lower = @. -moments.electron.qpar[1,:] * 2.0 / 3.16 / + moments.electron.ppar[1,:] * + composition.me_over_mi * collisions.nu_ei + else + dTe_dz_lower = nothing + end + dTe_dz_lower = MPI.bcast(dTe_dz_lower, z.comm; root=0) + + if z.irank == z.nrank - 1 + dTe_dz_upper = @. -moments.electron.qpar[end,:] * 2.0 / 3.16 / + moments.electron.ppar[end,:] * + composition.me_over_mi * collisions.nu_ei + else + dTe_dz_upper = nothing + end + dTe_dz_upper = MPI.bcast(dTe_dz_upper, z.comm; root=(z.nrank - 1)) + + # The temperature should already be equal to the 'Boltzmann electron' + # Te, so we just need to add a cubic that vanishes at ±Lz/2 + # δT = A + B*z + C*z^2 + D*z^3 + # ⇒ A - B*Lz/2 + C*Lz^2/4 - D*Lz^3/8 = 0 + # A + B*Lz/2 + C*Lz^2/4 + D*Lz^3/8 = 0 + # B - C*Lz + 3*D*Lz^2/4 = dTe/dz_lower + # B + C*Lz + 3*D*Lz^2/4 = dTe/dz_upper + # + # Adding the first pair together, and subtracting the second pair: + # A + C*Lz^2/4 = 0 + # 2*C*Lz = dT/dz_upper - dT/dz_lower + # + # Subtracting the first pair and adding the second instead: + # B*Lz/2 + D*Lz^3/8 = 0 ⇒ D*Lz^2/2 = -2*B + # 2*B + 3*D*Lz^2/2 = dTe/dz_upper - dTe/dz_lower + # + # 2*B - 3*2*B = -4*B = dTe/dz_upper + dTe/dz_lower + Lz = z.L + zg = z.grid + C = @. (dTe_dz_upper - dTe_dz_lower) / 2.0 / Lz + A = @. -C * Lz^2 / 4 + B = @. -(dTe_dz_lower + dTe_dz_upper) / 4.0 + D = @. -4.0 * B / Lz^2 + @loop_r ir begin + @. moments.electron.temp[:,ir] += A[ir] + B[ir]*zg + C[ir]*zg^2 + + D[ir]*zg^3 + end + + @. moments.electron.vth = sqrt(moments.electron.temp / + composition.me_over_mi) + @. moments.electron.ppar = 0.5 * moments.electron.dens * moments.electron.temp + end + @views derivative_z!(moments.electron.dT_dz, moments.electron.temp, + scratch_dummy.buffer_rs_1[:,1], + scratch_dummy.buffer_rs_2[:,1], + scratch_dummy.buffer_rs_3[:,1], + scratch_dummy.buffer_rs_4[:,1], z_spectral, z) + end + end + moments.electron.qpar_updated[] = false + calculate_electron_qpar!(moments.electron, pdf.electron, moments.electron.ppar, + moments.electron.upar, moments.ion.upar, collisions.nu_ei, composition.me_over_mi, + composition.electron_physics, vpa) + if composition.electron_physics == braginskii_fluid + electron_fluid_qpar_boundary_condition!( + moments.electron.ppar, moments.electron.upar, moments.electron.dens, + moments.electron, z) + end + # calculate the zed derivative of the initial electron parallel heat flux + @views derivative_z!(moments.electron.dqpar_dz, moments.electron.qpar, + scratch_dummy.buffer_rs_1[:,1], scratch_dummy.buffer_rs_2[:,1], scratch_dummy.buffer_rs_3[:,1], + scratch_dummy.buffer_rs_4[:,1], z_spectral, z) + # calculate the electron-ion parallel friction force + calculate_electron_parallel_friction_force!(moments.electron.parallel_friction, moments.electron.dens, + moments.electron.upar, moments.ion.upar, moments.electron.dT_dz, + composition.me_over_mi, collisions.nu_ei, composition.electron_physics) + + # initialize the scratch arrays containing pdfs and moments for the first RK stage + # the electron pdf is yet to be initialised but with the current code logic, the scratch + # arrays need to exist and be otherwise initialised in order to compute the initial + # electron pdf. The electron arrays will be updated as necessary by + # initialize_electron_pdf!(). + begin_serial_region() + @serial_region begin + scratch[1].electron_density .= moments.electron.dens + scratch[1].electron_upar .= moments.electron.upar + scratch[1].electron_ppar .= moments.electron.ppar + scratch[1].electron_pperp .= 0.0 #moments.electron.pperp + scratch[1].electron_temp .= moments.electron.temp + if t_params.electron === nothing + n_rk_stages = length(scratch) - 1 + else + n_rk_stages = t_params.electron.n_rk_stages + end + scratch[n_rk_stages+1].electron_density .= moments.electron.dens + scratch[n_rk_stages+1].electron_upar .= moments.electron.upar + scratch[n_rk_stages+1].electron_ppar .= moments.electron.ppar + scratch[n_rk_stages+1].electron_pperp .= 0.0 #moments.electron.pperp + scratch[n_rk_stages+1].electron_temp .= moments.electron.temp + end + if scratch_electron !== nothing + begin_serial_region() + @serial_region begin + scratch_electron[1].electron_ppar .= moments.electron.ppar + end + end + + # initialize the electron pdf that satisfies the electron kinetic equation + initialize_electron_pdf!(scratch, scratch_electron, pdf, moments, fields, r, z, vpa, + vperp, vzeta, vr, vz, r_spectral, z_spectral, vperp_spectral, + vpa_spectral, advection_structs.electron_z_advect, + advection_structs.electron_vpa_advect, scratch_dummy, + collisions, composition, geometry, external_source_settings, + num_diss_params, gyroavs, nl_solver_params, t_params, + t_input["electron_t_input"], io_input, input_dict) + + return nothing +end + """ """ function initialize_pdf!(pdf, moments, boundary_distributions, composition, r, z, vperp, @@ -258,7 +533,6 @@ function initialize_pdf!(pdf, moments, boundary_distributions, composition, r, z moments.evolve_upar, moments.evolve_ppar, wall_flux_0[ir,min(isn,composition.n_ion_species)], wall_flux_L[ir,min(isn,composition.n_ion_species)]) - @loop_z iz begin if moments.evolve_ppar @. pdf.neutral.norm[:,:,:,iz,ir,isn] *= moments.neutral.vth[iz,ir,isn] @@ -269,6 +543,272 @@ function initialize_pdf!(pdf, moments, boundary_distributions, composition, r, z end end + # @serial_region begin + # @loop_r ir begin + # # this is the initial guess for the electron pdf + # # it will be iteratively updated to satisfy the time-independent + # # electron kinetic equation + # @views init_electron_pdf_over_density!(pdf.electron.norm[:,:,:,ir], moments.electron.dens[:,ir], + # moments.electron.upar[:,ir], moments.electron.vth[:,ir], z, vpa, vperp) + # end + # # now that we have our initial guess for the electron pdf, we iterate + # # using the time-independent electron kinetic equation to find a self-consistent + # # solution for the electron pdf + # max_electron_pdf_iterations = 100 + # @views update_electron_pdf!(pdf.electron.norm, moments.electron.dens, moments.electron.vth, moments.electron.ppar, + # moments.electron.ddens_dz, moments.electron.dppar_dz, moments.electron.dqpar_dz, moments.electron.dvth_dz, + # max_electron_pdf_iterations, z, vpa, z_spectral, vpa_spectral, scratch_dummy) + # end + + + + return nothing +end + +function initialize_electron_pdf!(scratch, scratch_electron, pdf, moments, fields, r, z, + vpa, vperp, vzeta, vr, vz, r_spectral, z_spectral, + vperp_spectral, vpa_spectral, z_advect, vpa_advect, + scratch_dummy, collisions, composition, geometry, + external_source_settings, num_diss_params, gyroavs, + nl_solver_params, t_params, t_input, io_input, + input_dict) + + # now that the initial electron pdf is given, the electron parallel heat flux should be updated + # if using kinetic electrons + if composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + begin_serial_region() + if t_input["no_restart"] + restart_filename = nothing + else + restart_filename = get_default_restart_filename(io_input, "initial_electron"; + error_if_no_file_found=false) + # Synchronize to ensure that some processes do not detect the restart file + # when they should not, because this function gets called after the other + # processes create the file. + MPI.Barrier(comm_world) + end + if restart_filename === nothing + # No file to restart from + previous_runs_info = nothing + code_time = 0.0 + restart_time_index = -1 + pdf_electron_converged = false + else + # Previously-created electron distribution function exists, so use it as + # the initial guess. + backup_prefix_iblock, initial_electrons_filename, + backup_initial_electrons_filename = + get_prefix_iblock_and_move_existing_file(restart_filename, + io_input.output_dir) + + # Reload pdf and moments from an existing output file + code_time, pdf_electron_converged, previous_runs_info, restart_time_index = + reload_electron_data!(pdf, moments, t_params.electron, + backup_prefix_iblock, -1, geometry, r, z, vpa, + vperp, vzeta, vr, vz) + + # Broadcast code_time and pdf_electron_converged from the root process of each + # shared-memory block (on which it might have been loaded from a restart + # file). + code_time = MPI.Bcast(code_time, 0, comm_block[]) + pdf_electron_converged = MPI.Bcast(pdf_electron_converged, 0, comm_block[]) + + if pdf_electron_converged + if global_rank[] == 0 + println("Reading initial electron state from $restart_filename") + end + # Move the *.initial_electron.h5 file back to its original location, as we + # do not need to create a new output file. + MPI.Barrier(comm_world) + if global_rank[] == 0 + if initial_electrons_filename != backup_initial_electrons_filename + mv(backup_initial_electrons_filename, initial_electrons_filename) + end + end + MPI.Barrier(comm_world) + else + if global_rank[] == 0 + println("Restarting electron initialisation from $restart_filename") + end + end + end + + begin_serial_region() + @serial_region begin + # update the electron pdf in the last scratch_electron (which will be copied + # to the first entry as part of the pseudo-time-loop in + # update_electron_pdf!()). + scratch_electron[t_params.electron.n_rk_stages+1].pdf_electron .= pdf.electron.norm + end + + begin_r_z_region() + @loop_r_z ir iz begin + # update the electron thermal speed using the updated electron parallel pressure + moments.electron.vth[iz,ir] = sqrt(abs(2.0 * moments.electron.ppar[iz,ir] / (moments.electron.dens[iz,ir] * composition.me_over_mi))) + end + + moments.electron.qpar_updated[] = false + calculate_electron_qpar!(moments.electron, pdf.electron, moments.electron.ppar, + moments.electron.upar, moments.ion.upar, + collisions.nu_ei, composition.me_over_mi, + composition.electron_physics, vpa) + # update dqpar/dz for electrons + # calculate the zed derivative of the initial electron parallel heat flux + @views derivative_z!(moments.electron.dqpar_dz, moments.electron.qpar, + scratch_dummy.buffer_rs_1[:,1], scratch_dummy.buffer_rs_2[:,1], scratch_dummy.buffer_rs_3[:,1], + scratch_dummy.buffer_rs_4[:,1], z_spectral, z) + + # now that we have our initial guess for the electron pdf, we iterate + # using the time-independent electron kinetic equation to find a self-consistent + # solution for the electron pdf. + # First run with evolve_ppar=true to get electron_ppar close to steady state. + # electron_ppar does not have to be exactly steady state as it will be + # time-evolved along with the ions. + #max_electron_pdf_iterations = 2000000 + ##max_electron_pdf_iterations = 500000 + ##max_electron_pdf_iterations = 10000 + #max_electron_sim_time = nothing + max_electron_pdf_iterations = nothing + max_electron_sim_time = 2.0 + if t_params.electron.debug_io !== nothing + io_electron = setup_electron_io(t_params.electron.debug_io[1], vpa, vperp, z, + r, composition, collisions, + moments.evolve_density, moments.evolve_upar, + moments.evolve_ppar, external_source_settings, + t_params.electron, + t_params.electron.debug_io[2], -1, nothing, + "electron_debug") + end + if code_time > 0.0 + tind = searchsortedfirst(t_params.electron.moments_output_times, code_time) + n_truncated = length(t_params.electron.moments_output_times) - tind + truncated_times = t_params.electron.moments_output_times[tind+1:end] + resize!(t_params.electron.moments_output_times, n_truncated) + t_params.electron.moments_output_times .= truncated_times + resize!(t_params.electron.dfns_output_times, n_truncated) + t_params.electron.dfns_output_times .= truncated_times + end + if !pdf_electron_converged + if global_rank[] == 0 + println("Initializing electrons - evolving both pdf_electron and electron_ppar") + end + # Setup I/O for initial electron state + io_initial_electron = setup_electron_io(io_input, vpa, vperp, z, r, + composition, collisions, + moments.evolve_density, + moments.evolve_upar, + moments.evolve_ppar, + external_source_settings, + t_params.electron, input_dict, + restart_time_index, + previous_runs_info, + "initial_electron") + + # Can't let this counter stay set to 0 + t_params.electron.dfns_output_counter[] = max(t_params.electron.dfns_output_counter[], 1) + success = + @views update_electron_pdf!(scratch_electron, pdf.electron.norm, moments, + fields.phi, r, z, vperp, vpa, z_spectral, + vperp_spectral, vpa_spectral, z_advect, + vpa_advect, scratch_dummy, t_params.electron, + collisions, composition, + external_source_settings, num_diss_params, + max_electron_pdf_iterations, + max_electron_sim_time; + io_electron=io_initial_electron, + initial_time=code_time, + residual_tolerance=t_input["initialization_residual_value"], + evolve_ppar=true) + if success != "" + error("!!!max number of iterations for electron pdf update exceeded!!!\n" + * "Stopping at $(Dates.format(now(), dateformat"H:MM:SS"))") + end + + # Now run without evolve_ppar=true to get pdf_electron fully to steady state, + # ready for the start of the ion time advance. + if global_rank[] == 0 + println("Initializing electrons - evolving pdf_electron only to steady state") + end + if t_params.implicit_electron_advance + # Create new nl_solver_info ojbect with higher maximum iterations for + # initialisation. + initialisation_nl_solver_params = + nl_solver_info(nl_solver_params.electron_advance.rtol, + nl_solver_params.electron_advance.atol, + 100000, + nl_solver_params.electron_advance.linear_rtol, + nl_solver_params.electron_advance.linear_atol, + nl_solver_params.electron_advance.linear_restart, + nl_solver_params.electron_advance.linear_max_restarts, + nl_solver_params.electron_advance.H, + nl_solver_params.electron_advance.V, + nl_solver_params.electron_advance.linear_initial_guess, + nl_solver_params.electron_advance.n_solves, + nl_solver_params.electron_advance.nonlinear_iterations, + nl_solver_params.electron_advance.linear_iterations, + nl_solver_params.electron_advance.global_n_solves, + nl_solver_params.electron_advance.global_nonlinear_iterations, + nl_solver_params.electron_advance.global_linear_iterations, + nl_solver_params.electron_advance.stage_counter, + nl_solver_params.electron_advance.serial_solve, + nl_solver_params.electron_advance.max_nonlinear_iterations_this_step, + nl_solver_params.electron_advance.preconditioner_update_interval, + nl_solver_params.electron_advance.preconditioners, + ) + # Run implicit solve with dt=0 so that we don't update electron_ppar here + success = + implicit_electron_advance!(scratch[t_params.n_rk_stages+1], + scratch[1], pdf, + scratch_electron[t_params.electron.n_rk_stages+1], + moments, fields, collisions, composition, + geometry, external_source_settings, + num_diss_params, r, z, vperp, vpa, + r_spectral, z_spectral, vperp_spectral, + vpa_spectral, z_advect, vpa_advect, + gyroavs, scratch_dummy, 0.0, + initialisation_nl_solver_params) + else + success = + update_electron_pdf!(scratch_electron, pdf.electron.norm, moments, + fields.phi, r, z, vperp, vpa, z_spectral, + vperp_spectral, vpa_spectral, z_advect, + vpa_advect, scratch_dummy, t_params.electron, + collisions, composition, + external_source_settings, num_diss_params, + max_electron_pdf_iterations, + max_electron_sim_time; + io_electron=io_initial_electron) + end + if success != "" + error("!!!max number of iterations for electron pdf update exceeded!!!\n" + * "Stopping at $(Dates.format(now(), dateformat"H:MM:SS"))") + end + + # Write the converged initial state for the electrons to a file so that it can be + # re-used if the simulation is re-run. + t_params.electron.moments_output_counter[] += 1 + write_electron_state(scratch_electron, moments, t_params.electron, + io_initial_electron, + t_params.electron.moments_output_counter[], r, z, vperp, + vpa; pdf_electron_converged=true) + finish_electron_io(io_initial_electron) + end + + begin_r_z_vperp_vpa_region() + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + pdf.electron.pdf_before_ion_timestep[ivpa,ivperp,iz,ir] = + pdf.electron.norm[ivpa,ivperp,iz,ir] + end + + # No need to do electron I/O (apart from possibly debug I/O) any more, so if + # adaptive timestep is used, it does not need to adjust to output times. + resize!(t_params.electron.moments_output_times, 0) + resize!(t_params.electron.dfns_output_times, 0) + t_params.electron.moments_output_counter[] = 1 + t_params.electron.dfns_output_counter[] = 1 + + end return nothing end @@ -322,8 +862,8 @@ function init_vth!(vth, z, r, spec, n_species) end end end - @. vth = sqrt(vth) end + @. vth = sqrt(vth) return nothing end @@ -500,6 +1040,40 @@ function init_uzeta!(uzeta, z, r, spec, n_species) return nothing end +""" +initialise the electron density +""" +function init_electron_density!(electron_density, updated, ion_density) + # use quasineutrality to obtain the electron density from the initial + # densities of the various ion species + calculate_electron_density!(electron_density, updated, ion_density) + return nothing +end + +""" +initialise the electron parallel flow density +""" +function init_electron_upar!(upar_e, updated, dens_e, upar_i, dens_i, electron_model, r, z) + calculate_electron_upar_from_charge_conservation!(upar_e, updated, dens_e, upar_i, dens_i, electron_model, r, z) + return nothing +end + +""" +initialise the electron thermal speed profile. +for now the only initialisation option for the temperature is constant in z. +returns vth0 = sqrt(2*Ts/Te) +""" +function init_electron_vth!(vth_e, vth_i, T_e, me_over_mi, z) + begin_r_z_region() + # @loop_r_z ir iz begin + # vth_e[iz,ir] = sqrt(T_e) + # end + @loop_r_z ir iz begin + vth_e[iz,ir] = vth_i[iz,ir,1] / sqrt(me_over_mi) + #vth_e[iz,ir] = exp(-5*(z[iz]/z[end])^2)/sqrt(me_over_mi) + end +end + """ """ function init_ion_pdf_over_density!(pdf, spec, composition, vpa, vperp, z, @@ -709,6 +1283,8 @@ function init_neutral_pdf_over_density!(pdf, boundary_distributions, spec, compo vz, vr, vzeta, z, vz_spectral, density, uz, pz, vth, v_norm_fac, evolve_density, evolve_upar, evolve_ppar, wall_flux_0, wall_flux_L) + zero = 1.0e-14 + # Reduce the ion flux by `recycling_fraction` to account for ions absorbed by the # wall. wall_flux_0 *= composition.recycling_fraction @@ -775,6 +1351,11 @@ function init_neutral_pdf_over_density!(pdf, boundary_distributions, spec, compo end end else + # Normalise grid to thermal speed of `initial_temperature`, to avoid + # inaccuracy in moment-kinetic cases when `initial_temperature` is very + # small or large compared to the reference value. + vth_init = sqrt(spec.initial_temperature) + # First create distribution functions at the z-boundary points that obey the # boundary conditions. if z.irank == 0 && z.irank == z.nrank - 1 @@ -795,7 +1376,7 @@ function init_neutral_pdf_over_density!(pdf, boundary_distributions, spec, compo @. pdf[:,ivr,ivzeta,iz] = density[iz] * exp(-((vz.grid - uz[iz])^2 + vzeta.grid[ivzeta]^2 + vr.grid[ivr]^2) - / vth[iz]^2) / vth[iz] + * vth_init^2 / vth[iz]^2) / vth[iz] # Also ensure both species go to zero smoothly at v_z=0 at the # wall, where the boundary conditions require that distribution @@ -835,16 +1416,49 @@ function init_neutral_pdf_over_density!(pdf, boundary_distributions, spec, compo @views MPI.Bcast!(temp, z.nrank - 1, z.comm) wall_flux_L = temp[] - knudsen_pdf = boundary_distributions.knudsen + # Re-calculate Knudsen distribution instead of using + # `boundary_distributions.knudsen`, so that we can include vth_init here. + knudsen_pdf = allocate_float(vz.n, vr.n, vzeta.n) + knudsen_vtfac = sqrt(composition.T_wall * composition.mn_over_mi) + if vzeta.n > 1 && vr.n > 1 + # 3V specification of neutral wall emission distribution for boundary condition + # get the true Knudsen cosine distribution for neutral particle wall emission + for ivzeta in 1:vzeta.n + for ivr in 1:vr.n + for ivz in 1:vz.n + v_transverse = sqrt(vzeta.grid[ivzeta]^2 + vr.grid[ivr]^2) + v_normal = abs(vz.grid[ivz]) + v_tot = sqrt(v_normal^2 + v_transverse^2) + if v_tot > zero + prefac = v_normal/v_tot + else + prefac = 0.0 + end + knudsen_pdf[ivz,ivr,ivzeta] = (3.0*sqrt(pi)/knudsen_vtfac^4) * prefac * + exp(-((v_normal/knudsen_vtfac)^2 + (v_transverse/knudsen_vtfac)^2) * vth_init^2) + end + end + end + elseif vzeta.n == 1 && vr.n == 1 + # get the marginalised Knudsen cosine distribution after integrating over + # vperp appropriate for 1V model + @. knudsen_pdf[:,1,1] = (3.0*pi/knudsen_vtfac^3)*abs(vz.grid*vth_init)*erfc(abs(vz.grid) * vth_init / knudsen_vtfac) + end - zero = 1.0e-14 + if vzeta.n > 1 || vr.n > 1 + wgts_3V_vth_init = vth_init + else + wgts_3V_vth_init = 1.0 + end # add this species' contribution to the combined ion/neutral particle flux # out of the domain at z=-Lz/2 @views wall_flux_0 += integrate_over_negative_vz( - abs.(vz.grid) .* lower_z_pdf_buffer, vz.grid, vz.wgts, - vz.scratch3, vr.grid, vr.wgts, vzeta.grid, - vzeta.wgts) + vth_init .* abs.(vz.grid) .* lower_z_pdf_buffer, + vth_init .* vz.grid, vth_init .* vz.wgts, + vz.scratch3, vth_init .* vr.grid, + wgts_3V_vth_init .* vr.wgts, vth_init .* vzeta.grid, + wgts_3V_vth_init .* vzeta.wgts) # for left boundary in zed (z = -Lz/2), want # f_n(z=-Lz/2, v_z > 0) = Γ_0 * f_KW(v_z) * pdf_norm_fac(-Lz/2) @loop_vz ivz begin @@ -856,9 +1470,11 @@ function init_neutral_pdf_over_density!(pdf, boundary_distributions, spec, compo # add this species' contribution to the combined ion/neutral particle flux # out of the domain at z=-Lz/2 @views wall_flux_L += integrate_over_positive_vz( - abs.(vz.grid) .* upper_z_pdf_buffer, vz.grid, vz.wgts, - vz.scratch3, vr.grid, vr.wgts, vzeta.grid, - vzeta.wgts) + vth_init .* abs.(vz.grid) .* upper_z_pdf_buffer, + vth_init .* vz.grid, vth_init .* vz.wgts, + vz.scratch3, vth_init .* vr.grid, + wgts_3V_vth_init .* vr.wgts, vth_init .* vzeta.grid, + wgts_3V_vth_init .* vzeta.wgts) # for right boundary in zed (z = Lz/2), want # f_n(z=Lz/2, v_z < 0) = Γ_Lz * f_KW(v_z) * pdf_norm_fac(Lz/2) @loop_vz ivz begin @@ -888,8 +1504,9 @@ function init_neutral_pdf_over_density!(pdf, boundary_distributions, spec, compo # Get the unnormalised pdf and the moments of the constructed full-f # distribution function (which will be modified from the input moments). convert_full_f_neutral_to_normalised!(pdf, density, uz, pz, vth, vzeta, vr, - vz, vz_spectral, evolve_density, - evolve_upar, evolve_ppar) + vz, vz_spectral, vth_init, + evolve_density, evolve_upar, + evolve_ppar) if !evolve_density # Need to divide out density to return pdf/density @@ -922,13 +1539,101 @@ function init_neutral_pdf_over_density!(pdf, boundary_distributions, spec, compo return nothing end -function init_pdf_moments_manufactured_solns!(pdf, moments, vz, vr, vzeta, vpa, vperp, z, - r, n_ion_species, n_neutral_species, - geometry, composition, species, - manufactured_solns_input) - manufactured_solns_list = manufactured_solutions(manufactured_solns_input, r.L, z.L, - r.bc, z.bc, geometry, composition, - species, r.n, vperp.n) +""" +init_electron_pdf_over_density_and_boundary_phi initialises the normalised electron pdf = pdf_e * +vth_e / dens_e and the boundary values of the electrostatic potential phi; +care is taken to ensure that the parallel boundary condition is satisfied; +NB: as the electron pdf is obtained via a time-independent equation, +this 'initital' value for the electron will just be the first guess in an iterative solution +""" +function init_electron_pdf_over_density_and_boundary_phi!(pdf, phi, density, upar, vth, z, + vpa, vperp, vperp_spectral, vpa_spectral, vpa_advect, moments, num_diss_params, + me_over_mi, scratch_dummy; restart_from_boltzmann=false) + + if z.bc == "wall" + begin_r_region() + @loop_r ir begin + # Initialise an unshifted Maxwellian as a first step + @loop_z iz begin + vpa_over_vth = @. vpa.scratch3 = vpa.grid + upar[iz,ir] / vth[iz,ir] + @loop_vperp ivperp begin + @. pdf[:,ivperp,iz,ir] = exp(-vpa_over_vth^2) + end + end + end + # Apply the sheath boundary condition to get cut-off boundary distribution + # functions and boundary values of phi + enforce_boundary_condition_on_electron_pdf!(pdf, phi, vth, upar, z, vperp, vpa, + vperp_spectral, vpa_spectral, + vpa_advect, moments, + num_diss_params.electron.vpa_dissipation_coefficient > 0.0, + me_over_mi) + + # Distribute the z-boundary pdf values to every process + begin_serial_region() + pdf_lower = scratch_dummy.buffer_vpavperprs_1 + pdf_upper = scratch_dummy.buffer_vpavperprs_2 + @serial_region begin + if z.irank == 0 + pdf_lower .= pdf[:,:,1,:] + end + MPI.Bcast!(pdf_lower, z.comm; root=0) + if z.irank == z.nrank - 1 + pdf_upper .= pdf[:,:,end,:] + end + MPI.Bcast!(pdf_upper, z.comm; root=z.nrank-1) + end + + begin_r_z_region() + @loop_r ir begin + # get critical velocities beyond which electrons are lost to the wall + #vpa_crit_zmin, vpa_crit_zmax = get_electron_critical_velocities(phi, vth, me_over_mi, z) + #println("vpa_crit_zmin = ", vpa_crit_zmin, " vpa_crit_zmax = ", vpa_crit_zmax) + # Blend boundary distribution function into bulk of domain to avoid + # discontinuities (as much as possible) + blend_fac = 100 + @loop_z_vperp iz ivperp begin + #@. pdf[:,ivperp,iz] = exp(-30*z.grid[iz]^2) + #@. pdf[:,ivperp,iz] = (density[iz] / vth[iz]) * + #@. pdf[:,ivperp,iz] = exp(-vpa.grid[:]^2) + blend_fac_lower = exp(-blend_fac*(z.grid[iz] + 0.5*z.L)^2) + blend_fac_upper = exp(-blend_fac*(z.grid[iz] - 0.5*z.L)^2) + @. pdf[:,ivperp,iz,ir] = (1.0 - blend_fac_lower) * (1.0 - blend_fac_upper) * pdf[:,ivperp,iz,ir] + + blend_fac_lower * pdf_lower[:,ivperp,ir] + + blend_fac_upper * pdf_upper[:,ivperp,ir] + #@. pdf[:,ivperp,iz,ir] = exp(-vpa.grid^2) * ( + # (1 - exp(-blend_fac*(z.grid[iz] - z.grid[1])^2) * + # tanh(sharp_fac*(vpa.grid-vpa_crit_zmin))) * + # (1 - exp(-blend_fac*(z.grid[iz] - z.grid[end])^2) * + # tanh(-sharp_fac*(vpa.grid-vpa_crit_zmax)))) #/ + #(1 - exp(-blend_fac*(z.grid[iz] - z.grid[1])^2) * tanh(-sharp_fac*vpa_crit_zmin)) / + #(1 - exp(-blend_fac*(z.grid[iz] - z.grid[end])^2) * tanh(sharp_fac*vpa_crit_zmax))) + #exp(-((vpa.grid[:] - upar[iz])^2) / vth[iz]^2) + #exp(-((vpa.grid - upar[iz])^2 + vperp.grid[ivperp]^2) / vth[iz]^2) + + # ensure that the normalised electron pdf integrates to unity + norm_factor = integrate_over_vspace(pdf[:,ivperp,iz,ir], vpa.wgts) + @. pdf[:,ivperp,iz,ir] /= norm_factor + #println("TMP FOR TESTING -- init electron pdf") + #@. pdf[:,ivperp,iz] = exp(-2*vpa.grid[:]^2)*exp(-z.grid[iz]^2) + end + end + else + begin_r_z_region() + @loop_r ir begin + # Initialise an unshifted Maxwellian as a first step + @loop_z iz begin + vpa_over_vth = @. vpa.scratch3 = vpa.grid + upar[iz,ir] / vth[iz,ir] + @loop_vperp ivperp begin + @. pdf[:,ivperp,iz,ir] = exp(-vpa_over_vth^2) + end + end + end + end +end + +function init_pdf_moments_manufactured_solns!(pdf, moments, vz, vr, vzeta, vpa, vperp, z, r, n_ion_species, n_neutral_species, geometry,composition) + manufactured_solns_list = manufactured_solutions(r.L,z.L,r.bc,z.bc,geometry,composition,r.n) dfni_func = manufactured_solns_list.dfni_func densi_func = manufactured_solns_list.densi_func dfnn_func = manufactured_solns_list.dfnn_func @@ -970,42 +1675,13 @@ function init_pdf_moments_manufactured_solns!(pdf, moments, vz, vr, vzeta, vpa, end end # get consistent moments with manufactured solutions - update_neutral_density!(moments.neutral.dens, - moments.neutral.dens_updated, pdf.neutral.norm, - vz, vr, vzeta, z, r, composition) - # nb bad naming convention uz -> n uz below - update_neutral_uz!(moments.neutral.uz, moments.neutral.uz_updated, - moments.neutral.dens, moments.neutral.pz, - pdf.neutral.norm, vz, vr, vzeta, z, r, composition, - moments.evolve_density, moments.evolve_ppar) - update_neutral_ur!(moments.neutral.ur, moments.neutral.ur_updated, - moments.neutral.dens, pdf.neutral.norm, vz, vr, - vzeta, z, r, composition) - update_neutral_uzeta!(moments.neutral.uzeta, - moments.neutral.uzeta_updated, - moments.neutral.dens, pdf.neutral.norm, vz, vr, - vzeta, z, r, composition) - @loop_sn_r_z isn ir iz begin - moments.neutral.uz[iz,ir,isn] /= moments.neutral.dens[iz,ir,isn] - moments.neutral.ur[iz,ir,isn] /= moments.neutral.dens[iz,ir,isn] - moments.neutral.uzeta[iz,ir,isn] /= moments.neutral.dens[iz,ir,isn] - end - update_neutral_pz!(moments.neutral.pz, moments.neutral.pz_updated, - moments.neutral.dens, moments.neutral.uz, - pdf.neutral.norm, vz, vr, vzeta, z, r, composition, - moments.evolve_density, moments.evolve_upar) - update_neutral_pr!(moments.neutral.pr, moments.neutral.pr_updated, - pdf.neutral.norm, vz, vr, vzeta, z, r, composition) - update_neutral_pzeta!(moments.neutral.pzeta, - moments.neutral.pzeta_updated, pdf.neutral.norm, - vz, vr, vzeta, z, r, composition) - update_neutral_qz!(moments.neutral.qz, moments.neutral.qz_updated, - moments.neutral.dens, moments.neutral.uz, - moments.neutral.vth, pdf.neutral.norm, vz, vr, - vzeta, z, r, composition, moments.evolve_density, - moments.evolve_upar, moments.evolve_ppar) + update_neutral_density!(moments.neutral.dens, pdf.neutral.norm, vz, vr, vzeta, z, r, composition) + update_neutral_qz!(moments.neutral.qz, pdf.neutral.norm, vz, vr, vzeta, z, r, composition) + update_neutral_pz!(moments.neutral.pz, pdf.neutral.norm, vz, vr, vzeta, z, r, composition) + update_neutral_pr!(moments.neutral.pr, pdf.neutral.norm, vz, vr, vzeta, z, r, composition) + update_neutral_pzeta!(moments.neutral.pzeta, pdf.neutral.norm, vz, vr, vzeta, z, r, composition) #update ptot (isotropic pressure) - if vzeta.n > 1 || vr.n > 1 #if not using marginalised distribution function + if r.n > 1 #if 2D geometry begin_sn_r_z_region() @loop_sn_r_z isn ir iz begin moments.neutral.ptot[iz,ir,isn] = (moments.neutral.pz[iz,ir,isn] + moments.neutral.pr[iz,ir,isn] + moments.neutral.pzeta[iz,ir,isn])/3.0 @@ -1013,9 +1689,16 @@ function init_pdf_moments_manufactured_solns!(pdf, moments, vz, vr, vzeta, vpa, else #1D model moments.neutral.ptot .= moments.neutral.pz end + # nb bad naming convention uz -> n uz below + update_neutral_uz!(moments.neutral.uz, pdf.neutral.norm, vz, vr, vzeta, z, r, composition) + update_neutral_ur!(moments.neutral.ur, pdf.neutral.norm, vz, vr, vzeta, z, r, composition) + update_neutral_uzeta!(moments.neutral.uzeta, pdf.neutral.norm, vz, vr, vzeta, z, r, composition) # now convert from particle particle flux to parallel flow begin_sn_r_z_region() @loop_sn_r_z isn ir iz begin + moments.neutral.uz[iz,ir,isn] /= moments.neutral.dens[iz,ir,isn] + moments.neutral.ur[iz,ir,isn] /= moments.neutral.dens[iz,ir,isn] + moments.neutral.uzeta[iz,ir,isn] /= moments.neutral.dens[iz,ir,isn] # get vth for neutrals moments.neutral.vth[iz,ir,isn] = sqrt(2.0*moments.neutral.ptot[iz,ir,isn]/moments.neutral.dens[iz,ir,isn]) end @@ -1052,7 +1735,7 @@ function init_knudsen_cosine!(knudsen_cosine, vz, vr, vzeta, vpa, vperp, composi v_transverse = sqrt(vzeta.grid[ivzeta]^2 + vr.grid[ivr]^2) v_normal = abs(vz.grid[ivz]) v_tot = sqrt(v_normal^2 + v_transverse^2) - if v_normal > zero + if v_tot > zero prefac = v_normal/v_tot else prefac = 0.0 @@ -1198,19 +1881,30 @@ the moments of `f`. Inputs/outputs depend on z, vzeta, vr and vz (should be inside loops over species, r) """ function convert_full_f_neutral_to_normalised!(f, density, uz, pz, vth, vzeta, vr, vz, - vz_spectral, evolve_density, evolve_upar, evolve_ppar) + vz_spectral, vth_init, evolve_density, evolve_upar, evolve_ppar) + if vzeta.n > 1 || vr.n > 1 + wgts_3V_vth_init = vth_init + else + wgts_3V_vth_init = 1.0 + end @loop_z iz begin # Calculate moments - @views density[iz] = integrate_over_neutral_vspace(f[:,:,:,iz], vz.grid, 0, - vz.wgts, vr.grid, 0, vr.wgts, - vzeta.grid, 0, vzeta.wgts) - @views uz[iz] = integrate_over_neutral_vspace(f[:,:,:,iz], vz.grid, 1, vz.wgts, - vr.grid, 0, vr.wgts, vzeta.grid, 0, - vzeta.wgts) / density[iz] - @views pz[iz] = integrate_over_neutral_vspace(f[:,:,:,iz], vz.grid, 2, vz.wgts, - vr.grid, 0, vr.wgts, vzeta.grid, 0, - vzeta.wgts) - density[iz]*uz[iz]^2 + @views density[iz] = integrate_over_neutral_vspace( + f[:,:,:,iz], vth_init .* vth_init .* vz.grid, 0, + vth_init .* vz.wgts, vth_init .* vr.grid, 0, + wgts_3V_vth_init .* vr.wgts, vth_init .* vzeta.grid, 0, + wgts_3V_vth_init .* vzeta.wgts) + @views uz[iz] = integrate_over_neutral_vspace( + f[:,:,:,iz], vth_init .* vz.grid, 1, vth_init .* vz.wgts, + vth_init .* vr.grid, 0, wgts_3V_vth_init .* vr.wgts, + vth_init .* vzeta.grid, 0, wgts_3V_vth_init .* vzeta.wgts) / + density[iz] + @views pz[iz] = integrate_over_neutral_vspace( + f[:,:,:,iz], vth_init .* vz.grid, 2, vth_init .* vz.wgts, + vth_init .* vr.grid, 0, wgts_3V_vth_init .* vr.wgts, + vth_init .* vzeta.grid, 0, wgts_3V_vth_init .* vzeta.wgts) - + density[iz]*uz[iz]^2 vth[iz] = sqrt(2.0*pz[iz]/density[iz]) # Normalise f @@ -1225,11 +1919,11 @@ function convert_full_f_neutral_to_normalised!(f, density, uz, pz, vth, vzeta, v # The values to interpolate *to* are the v_parallel values corresponding to # the w_parallel grid vz.scratch .= vpagrid_to_dzdt(vz.grid, vth[iz], uz[iz], evolve_ppar, - evolve_upar) + evolve_upar) ./ vth_init @loop_vzeta_vr ivzeta ivr begin @views vz.scratch2 .= f[:,ivr,ivzeta,iz] # Copy to use as input to interpolation - @views interpolate_to_grid_1d!(f[:,ivr,ivzeta,iz], vz.scratch, vz.scratch2, - vz, vz_spectral) + @views interpolate_to_grid_1d!(f[:,ivr,ivzeta,iz], vz.scratch, + vz.scratch2, vz, vz_spectral) end end end diff --git a/moment_kinetics/src/input_structs.jl b/moment_kinetics/src/input_structs.jl index 47aea55ce..6ce812496 100644 --- a/moment_kinetics/src/input_structs.jl +++ b/moment_kinetics/src/input_structs.jl @@ -39,18 +39,24 @@ end an option but known at compile time when a `time_info` struct is passed as a function argument. """ -struct time_info{Terrorsum <: Real, Trkimp, Timpzero} +struct time_info{Terrorsum <: Real, T_debug_output, T_electron, Trkimp, Timpzero} n_variables::mk_int nstep::mk_int end_time::mk_float + t::MPISharedArray{mk_float,1} dt::MPISharedArray{mk_float,1} previous_dt::MPISharedArray{mk_float,1} next_output_time::MPISharedArray{mk_float,1} dt_before_output::MPISharedArray{mk_float,1} dt_before_last_fail::MPISharedArray{mk_float,1} CFL_prefactor::mk_float - step_to_output::MPISharedArray{Bool,1} + step_to_moments_output::MPISharedArray{Bool,1} + step_to_dfns_output::MPISharedArray{Bool,1} + write_moments_output::MPISharedArray{Bool,1} + write_dfns_output::MPISharedArray{Bool,1} step_counter::Ref{mk_int} + moments_output_counter::Ref{mk_int} + dfns_output_counter::Ref{mk_int} failure_counter::Ref{mk_int} failure_caused_by::Vector{mk_int} limit_caused_by::Vector{mk_int} @@ -75,8 +81,11 @@ struct time_info{Terrorsum <: Real, Trkimp, Timpzero} last_fail_proximity_factor::mk_float minimum_dt::mk_float maximum_dt::mk_float + implicit_braginskii_conduction::Bool + implicit_electron_advance::Bool implicit_ion_advance::Bool implicit_vpa_advection::Bool + implicit_electron_ppar::Bool write_after_fixed_step_count::Bool error_sum_zero::Terrorsum split_operators::Bool @@ -84,6 +93,8 @@ struct time_info{Terrorsum <: Real, Trkimp, Timpzero} converged_residual_value::mk_float use_manufactured_solns_for_advance::Bool stopfile::String + debug_io::T_debug_output # Currently only used by electrons + electron::T_electron end """ @@ -106,6 +117,8 @@ mutable struct advance_info neutral_ionization_collisions_1V::Bool ionization_source::Bool krook_collisions_ii::Bool + mxwl_diff_collisions_ii::Bool + mxwl_diff_collisions_nn::Bool explicit_weakform_fp_collisions::Bool external_source::Bool ion_numerical_dissipation::Bool @@ -114,6 +127,8 @@ mutable struct advance_info continuity::Bool force_balance::Bool energy::Bool + electron_energy::Bool + electron_conduction::Bool neutral_external_source::Bool neutral_source_terms::Bool neutral_continuity::Bool @@ -154,8 +169,19 @@ end """ """ -@enum electron_physics_type boltzmann_electron_response boltzmann_electron_response_with_simple_sheath -export electron_physics_type, boltzmann_electron_response, boltzmann_electron_response_with_simple_sheath +@enum electron_physics_type begin + boltzmann_electron_response + boltzmann_electron_response_with_simple_sheath + braginskii_fluid + kinetic_electrons + kinetic_electrons_with_temperature_equation +end +export electron_physics_type +export boltzmann_electron_response +export boltzmann_electron_response_with_simple_sheath +export braginskii_fluid +export kinetic_electrons +export kinetic_electrons_with_temperature_equation """ """ @@ -319,7 +345,6 @@ mutable struct species_composition # wall potential used if electron_physics=boltzmann_electron_response_with_simple_sheath phi_wall::mk_float # constant for testing nonzero Er - Er_constant::mk_float # ratio of the neutral particle mass to the ion mass mn_over_mi::mk_float # ratio of the electron particle mass to the ion mass @@ -360,11 +385,27 @@ struct drive_input end """ +Structs set up for the collision operators so far in use. These will each +be contained in the main collisions_input struct below, as substructs. """ +Base.@kwdef struct mxwl_diff_collisions_input + use_maxwell_diffusion::Bool + # different diffusion coefficients for each species, has units of + # frequency * velocity^2. Diffusion coefficients usually denoted D + D_ii::mk_float + D_nn::mk_float + # Setting to switch between different options for Krook collision operator + diffusion_coefficient_option::String # "reference_parameters" # "manual", +end + Base.@kwdef struct krook_collisions_input use_krook::Bool # Ion-ion Coulomb collision rate at the reference density and temperature nuii0::mk_float + # Electron-electron Coulomb collision rate at the reference density and temperature + nuee0::mk_float + # Electron-ion Coulomb collision rate at the reference density and temperature + nuei0::mk_float # Setting to switch between different options for Krook collision operator frequency_option::String # "reference_parameters" # "manual", end @@ -379,6 +420,8 @@ Base.@kwdef struct fkpl_collisions_input nuii::mk_float # option to determine if self collisions are used (for physics test) self_collisions::Bool + # option to determine if ad-hoc moment_kinetics-style conserving corrections are used + use_conserving_corrections::Bool # option to determine if cross-collisions against fixed Maxwellians are used slowing_down_test::Bool # Setting to switch between different options for Fokker-Planck collision frequency input @@ -401,18 +444,30 @@ Base.@kwdef struct fkpl_collisions_input end """ +Collisions input struct to contain all the different collisions substructs and overall +collision input parameters. """ struct collisions_input - # charge exchange collision frequency + # ion-neutral charge exchange collision frequency charge_exchange::mk_float + # electron-neutral charge exchange collision frequency + charge_exchange_electron::mk_float # ionization collision frequency ionization::mk_float + # ionization collision frequency for electrons (probably should be same as for ions) + ionization_electron::mk_float + # ionization energy cost + ionization_energy::mk_float # if constant_ionization_rate = true, use an ionization term that is constant in z constant_ionization_rate::Bool + # electron-ion collision frequency + nu_ei::mk_float # struct of parameters for the Krook operator krook::krook_collisions_input # struct of parameters for the Fokker-Planck operator fkpl::fkpl_collisions_input + # struct of parameters for the Maxwellian Diffusion operator + mxwl_diff::mxwl_diff_collisions_input end """ @@ -426,6 +481,14 @@ Base.@kwdef struct geometry_input pitch::mk_float = 1.0 # DeltaB ( = (Bzed(z=L/2) - Bzed(0))/Bref if geometry_option == "1D-mirror") DeltaB::mk_float = 0.0 + # constant for testing nonzero Er when nr = 1 + Er_constant::mk_float + # constant for testing nonzero Ez when nz = 1 + Ez_constant::mk_float + # constant for testing nonzero dBdz when nz = 1 + dBdz_constant::mk_float + # constant for testing nonzero dBdr when nr = 1 + dBdr_constant::mk_float end @enum binary_format_type hdf5 netcdf @@ -557,6 +620,8 @@ struct pp_input animate_parallel_flow_vs_r_z::Bool # if plot_parallel_pressure_vs_r0_z = true plot last timestep parallel_pressure[z,ir0] plot_parallel_pressure_vs_r0_z::Bool + # if plot_perpendicular_pressure_vs_r0_z = true plot last timestep perpendicular_pressure[z,ir0] + plot_perpendicular_pressure_vs_r0_z::Bool # if plot_wall_parallel_pressure_vs_r = true plot last timestep parallel_pressure[z_wall,r] plot_wall_parallel_pressure_vs_r::Bool # if plot_parallel_pressure_vs_r_z = true plot parallel_pressure vs r z at last timestep @@ -621,15 +686,17 @@ Utility method for converting a string to an Enum when getting from a Dict, base type of the default value """ function get(d::Dict, key, default::Enum) - valstring = get(d, key, nothing) - if valstring == nothing + val_maybe_string = get(d, key, nothing) + if val_maybe_string == nothing return default + elseif isa(val_maybe_string, Enum) + return val_maybe_string # instances(typeof(default)) gets the possible values of the Enum. Then convert to # Symbol, then to String. - elseif valstring ∈ (split(s, ".")[end] for s ∈ String.(Symbol.(instances(typeof(default))))) - return eval(Symbol(valstring)) + elseif val_maybe_string ∈ Tuple(split(s, ".")[end] for s ∈ string.(instances(typeof(default)))) + return eval(Symbol(val_maybe_string)) else - error("Expected a $(typeof(default)), but '$valstring' is not in " + error("Expected a $(typeof(default)), but '$val_maybe_string' is not in " * "$(instances(typeof(default)))") end end @@ -703,7 +770,6 @@ function set_defaults_and_check_section!(options::AbstractDict, section_name; end # Set default values if a key was not set explicitly - explicit_keys = keys(section) for (key_sym, default_value) ∈ kwargs key = String(key_sym) diff --git a/moment_kinetics/src/ionization.jl b/moment_kinetics/src/ionization.jl index babdd5d9b..9ea348da0 100644 --- a/moment_kinetics/src/ionization.jl +++ b/moment_kinetics/src/ionization.jl @@ -78,8 +78,7 @@ function ion_ionization_collisions_1V!(f_out, fvec_in, vz, vpa, vperp, z, r, vz_ @boundscheck r.n == size(f_out,4) || throw(BoundsError(f_out)) @boundscheck composition.n_ion_species == size(f_out,5) || throw(BoundsError(f_out)) - - begin_r_z_vpa_region() + begin_r_z_region() if moments.evolve_density # For now, assume species index `is` corresponds to the neutral @@ -146,9 +145,14 @@ function ion_ionization_collisions_1V!(f_out, fvec_in, vz, vpa, vperp, z, r, vz_ #NB: used quasineutrality to replace electron density n_e with ion density #NEEDS GENERALISATION TO n_ion_species > 1 (missing species charge: Sum_i Z_i n_i = n_e) isn = is - @loop_r_z_vpa ir iz ivpa begin - # apply ionization collisions to all ion species - f_out[ivpa,1,iz,ir,is] += dt*collisions.ionization*fvec_in.pdf_neutral[ivpa,1,1,iz,ir,isn]*fvec_in.density[iz,ir,is] + @loop_r_z ir iz begin + @views interpolate_to_grid_vpa!(vpa.scratch, vpa.grid, + fvec_in.pdf_neutral[:,1,1,iz,ir,isn], vz, + vz_spectral) + @loop_vpa ivpa begin + # apply ionization collisions to all ion species + f_out[ivpa,1,iz,ir,is] += dt*collisions.ionization*vpa.scratch[ivpa]*fvec_in.density[iz,ir,is] + end end end end @@ -157,10 +161,9 @@ end function neutral_ionization_collisions_1V!(f_neutral_out, fvec_in, vz, vpa, vperp, z, r, vz_spectral, moments, composition, collisions, dt) # This routine assumes a 1D model with: - # nvz = nvpa and identical vz and vpa grids # nvperp = nvr = nveta = 1 # constant charge_exchange_frequency independent of species - @boundscheck vpa.n == size(f_neutral_out,1) || throw(BoundsError(f_neutral_out)) + @boundscheck vz.n == size(f_neutral_out,1) || throw(BoundsError(f_neutral_out)) @boundscheck 1 == size(f_neutral_out,2) || throw(BoundsError(f_neutral_out)) @boundscheck 1 == size(f_neutral_out,3) || throw(BoundsError(f_neutral_out)) @boundscheck z.n == size(f_neutral_out,4) || throw(BoundsError(f_neutral_out)) @@ -168,18 +171,18 @@ function neutral_ionization_collisions_1V!(f_neutral_out, fvec_in, vz, vpa, vper @boundscheck composition.n_neutral_species == size(f_neutral_out,6) || throw(BoundsError(f_neutral_out)) if !moments.evolve_density - begin_r_z_vpa_region() + begin_sn_r_z_vz_region() - @loop_s is begin + @loop_sn isn begin # ion ionisation rate = < f_n > n_e R_ion # neutral "ionisation" (depopulation) rate = - f_n n_e R_ion # no gyroaverage here as 1V code #NB: used quasineutrality to replace electron density n_e with ion density #NEEDS GENERALISATION TO n_ion_species > 1 (missing species charge: Sum_i Z_i n_i = n_e) - isn = is - @loop_r_z_vpa ir iz ivpa begin + is = isn + @loop_r_z_vz ir iz ivz begin # apply ionization collisions to all neutral species - f_neutral_out[ivpa,1,1,iz,ir,isn] -= dt*collisions.ionization*fvec_in.pdf_neutral[ivpa,1,1,iz,ir,isn]*fvec_in.density[iz,ir,is] + f_neutral_out[ivz,1,1,iz,ir,isn] -= dt*collisions.ionization*fvec_in.pdf_neutral[ivz,1,1,iz,ir,isn]*fvec_in.density[iz,ir,is] end end end diff --git a/moment_kinetics/src/krook_collisions.jl b/moment_kinetics/src/krook_collisions.jl index feefcf9f4..1880610e9 100644 --- a/moment_kinetics/src/krook_collisions.jl +++ b/moment_kinetics/src/krook_collisions.jl @@ -2,11 +2,14 @@ """ module krook_collisions -export setup_krook_collisions_input, get_collision_frequency_ii, krook_collisions! +export setup_krook_collisions_input, get_collision_frequency_ii, get_collision_frequency_ee, + get_collision_frequency_ei, krook_collisions!, electron_krook_collisions! using ..looping using ..input_structs: krook_collisions_input, set_defaults_and_check_section! -using ..reference_parameters: get_reference_collision_frequency_ii +using ..reference_parameters: get_reference_collision_frequency_ii, + get_reference_collision_frequency_ee, + get_reference_collision_frequency_ei """ @@ -21,17 +24,23 @@ frequency_option = "manual" function setup_krook_collisions_input(toml_input::Dict, reference_params) # get reference collision frequency nuii_krook_default = get_reference_collision_frequency_ii(reference_params) + nuee_krook_default = get_reference_collision_frequency_ee(reference_params) + nuei_krook_default = get_reference_collision_frequency_ei(reference_params) # read the input toml and specify a sensible default input_section = input_section = set_defaults_and_check_section!(toml_input, "krook_collisions", # begin default inputs (as kwargs) use_krook = false, nuii0 = -1.0, + nuee0 = -1.0, + nuei0 = -1.0, frequency_option = "reference_parameters") # ensure that the collision frequency is consistent with the input option frequency_option = input_section["frequency_option"] if frequency_option == "reference_parameters" input_section["nuii0"] = nuii_krook_default + input_section["nuee0"] = nuee_krook_default + input_section["nuei0"] = nuei_krook_default elseif frequency_option == "manual" # use the frequency from the input file # do nothing @@ -43,6 +52,8 @@ function setup_krook_collisions_input(toml_input::Dict, reference_params) # so that prefactor > 0 is the only check required in the rest of the code if !input_section["use_krook"] input_section["nuii0"] = -1.0 + input_section["nuee0"] = -1.0 + input_section["nuei0"] = -1.0 end input = Dict(Symbol(k)=>v for (k,v) in input_section) #println(input) @@ -72,6 +83,85 @@ function get_collision_frequency_ii(collisions, n, vth) # returning a scalar from this branch but an array from the "reference_parameters" # branch). return @. nuii0 + 0.0 * n + elseif frequency_option == "none" + # Include 0.0*n so that the result gets promoted to an array if n is an array, + # which hopefully means this function will have a fixed return type given the + # types of the arguments (we don't want to be 'type unstable' for array inputs by + # returning a scalar from this branch but an array from the "reference_parameters" + # branch). + return @. 0.0 * n + else + error("Unrecognised option [krook_collisions] " + * "frequency_option=$(frequency_option)") + end +end + +""" + get_collision_frequency_ee(collisions, n, vthe) + +Calculate the electron-electron collision frequency, depending on the settings/parameters +in `collisions`, for the given density `n` and electron thermal speed `vthe`. + +`n` and `vthe` may be scalars or arrays, but should have shapes that can be broadcasted +together. +""" +function get_collision_frequency_ee(collisions, n, vthe) + # extract krook options from collisions struct + colk = collisions.krook + nuee0 = colk.nuee0 + frequency_option = colk.frequency_option + if frequency_option == "reference_parameters" + return @. nuee0 * n * vthe^(-3) + elseif frequency_option == "manual" + # Include 0.0*n so that the result gets promoted to an array if n is an array, + # which hopefully means this function will have a fixed return type given the + # types of the arguments (we don't want to be 'type unstable' for array inputs by + # returning a scalar from this branch but an array from the "reference_parameters" + # branch). + return @. nuee0 + 0.0 * n + elseif frequency_option == "none" + # Include 0.0*n so that the result gets promoted to an array if n is an array, + # which hopefully means this function will have a fixed return type given the + # types of the arguments (we don't want to be 'type unstable' for array inputs by + # returning a scalar from this branch but an array from the "reference_parameters" + # branch). + return @. 0.0 * n + else + error("Unrecognised option [krook_collisions] " + * "frequency_option=$(frequency_option)") + end +end + +""" + get_collision_frequency_ei(collisions, n, vthe) + +Calculate the electron-electron collision frequency, depending on the settings/parameters +in `collisions`, for the given density `n` and electron thermal speed `vthe`. + +`n` and `vthe` may be scalars or arrays, but should have shapes that can be broadcasted +together. +""" +function get_collision_frequency_ei(collisions, n, vthe) + # extract krook options from collisions struct + colk = collisions.krook + nuei0 = colk.nuei0 + frequency_option = colk.frequency_option + if frequency_option == "reference_parameters" + return @. nuei0 * n * vthe^(-3) + elseif frequency_option == "manual" + # Include 0.0*n so that the result gets promoted to an array if n is an array, + # which hopefully means this function will have a fixed return type given the + # types of the arguments (we don't want to be 'type unstable' for array inputs by + # returning a scalar from this branch but an array from the "reference_parameters" + # branch). + return @. nuei0 + 0.0 * n + elseif frequency_option == "none" + # Include 0.0*n so that the result gets promoted to an array if n is an array, + # which hopefully means this function will have a fixed return type given the + # types of the arguments (we don't want to be 'type unstable' for array inputs by + # returning a scalar from this branch but an array from the "reference_parameters" + # branch). + return @. 0.0 * n else error("Unrecognised option [krook_collisions] " * "frequency_option=$(frequency_option)") @@ -170,4 +260,158 @@ function krook_collisions!(pdf_out, fvec_in, moments, composition, collisions, v return nothing end +""" +Add Krook collision operator for electrons +""" +function electron_krook_collisions!(pdf_out, pdf_in, dens_in, upar_in, upar_ion_in, + vth_in, collisions, vperp, vpa, dt) + begin_r_z_region() + + # For now, electrons are always fully moment-kinetic + evolve_density = true + evolve_upar = true + evolve_ppar = true + + if vperp.n > 1 && (evolve_density || evolve_upar || evolve_ppar) + error("Krook collisions not implemented for 2V moment-kinetic cases yet") + end + + # Note: do not need 1/sqrt(pi) for the 'Maxwellian' term because the pdf is already + # normalized by sqrt(pi) (see velocity_moments.integrate_over_vspace). + if evolve_ppar && evolve_upar + # Compared to evolve_upar version, grid is already normalized by vth, and multiply + # through by vth, remembering pdf is already multiplied by vth + @loop_r_z ir iz begin + n = dens_in[iz,ir] + vth = vth_in[iz,ir] + nu_ee = get_collision_frequency_ee(collisions, n, vth) + nu_ei = get_collision_frequency_ei(collisions, n, vth) + + # e-i collisions push electrons towards a Maxwellian drifting at the ion + # parallel flow, so need a corresponding normalised parallel velocity + # coordinate. + # For now, assume there is only one ion species rather than bothering to + # calculate an average ion flow speed, or sum over ion species here. + @. vpa.scratch = vpa.grid + (upar_ion_in[iz,ir,1] - upar_in[iz,ir]) / vth + + @loop_vperp_vpa ivperp ivpa begin + pdf_out[ivpa,ivperp,iz,ir] -= dt * ( + nu_ee * (pdf_in[ivpa,ivperp,iz,ir] + - exp(-vpa.grid[ivpa]^2 - vperp.grid[ivperp]^2)) + + nu_ei * (pdf_in[ivpa,ivperp,iz,ir] + - exp(-vpa.scratch[ivpa]^2 - vperp.grid[ivperp]^2)) + ) + end + end + elseif evolve_ppar + # Compared to full-f collision operater, multiply through by vth, remembering pdf + # is already multiplied by vth, and grid is already normalized by vth + @loop_r_z ir iz begin + n = dens_in[iz,ir] + vth = vth_in[iz,ir] + nu_ee = get_collision_frequency_ee(collisions, n, vth) + nu_ei = get_collision_frequency_ei(collisions, n, vth) + + @loop_vperp_vpa ivperp ivpa begin + pdf_out[ivpa,ivperp,iz,ir] -= dt * ( + nu_ee * (pdf_in[ivpa,ivperp,iz,ir] + - exp(-((vpa.grid[ivpa] - upar_in[iz,ir])/vth)^2 + - (vperp.grid[ivperp]/vth)^2)) + # e-i collisions push electrons towards a Maxwellian drifting at the ion + # parallel flow, so need a corresponding normalised parallel velocity + # coordinate. + # For now, assume there is only one ion species rather than bothering to + # calculate an average ion flow speed, or sum over ion species here. + + nu_ei * (pdf_in[ivpa,ivperp,iz,ir] + - exp(-((vpa.grid[ivpa] - upar_ion_in[iz,ir,1])/vth)^2 + - (vperp.grid[ivperp]/vth)^2)) + ) + end + end + elseif evolve_upar + # Compared to evolve_density version, grid is already shifted by upar + @loop_r_z ir iz begin + n = dens_in[iz,ir] + vth = vth_in[iz,ir] + nu_ee = get_collision_frequency_ee(collisions, n, vth) + nu_ei = get_collision_frequency_ei(collisions, n, vth) + + # e-i collisions push electrons towards a Maxwellian drifting at the ion + # parallel flow, so need a corresponding normalised parallel velocity + # coordinate. + # For now, assume there is only one ion species rather than bothering to + # calculate an average ion flow speed, or sum over ion species here. + @. vpa.scratch = vpa.grid + (upar_ion_in[iz,ir,1] - upar_in[iz,ir]) + + @loop_vperp_vpa ivperp ivpa begin + pdf_out[ivpa,ivperp,iz,ir] -= dt * ( + nu_ee * (pdf_in[ivpa,ivperp,iz,ir] + - 1.0 / vth * exp(-(vpa.grid[ivpa] / vth)^2 + - (vperp.grid[ivperp] / vth)^2)) + + nu_ei * (pdf_in[ivpa,ivperp,iz,ir] + - 1.0 / vth * exp(-(vpa.scratch[ivpa] / vth)^2 + - (vperp.grid[ivperp] / vth)^2)) + ) + end + end + elseif evolve_density + # Compared to full-f collision operater, divide through by density, remembering + # that pdf is already normalized by density + @loop_r_z ir iz begin + n = dens_in[iz,ir] + vth = vth_in[iz,ir] + nu_ee = get_collision_frequency_ee(collisions, n, vth) + nu_ei = get_collision_frequency_ei(collisions, n, vth) + @loop_vperp_vpa ivperp ivpa begin + pdf_out[ivpa,ivperp,iz,ir] -= dt * ( + nu_ee * (pdf_in[ivpa,ivperp,iz,ir] + - 1.0 / vth + * exp(-((vpa.grid[ivpa] - upar_in[iz,ir]) / vth)^2 + - (vperp.grid[ivperp]/vth)^2)) + # e-i collisions push electrons towards a Maxwellian drifting at the ion + # parallel flow, so need a corresponding normalised parallel velocity + # coordinate. + # For now, assume there is only one ion species rather than bothering to + # calculate an average ion flow speed, or sum over ion species here. + + nu_ei * (pdf_in[ivpa,ivperp,iz,ir] + - 1.0 / vth + * exp(-((vpa.grid[ivpa] - upar_ion_in[iz,ir,1]) / vth)^2 + - (vperp.grid[ivperp]/vth)^2)) + ) + end + end + else + @loop_r_z ir iz begin + n = dens_in[iz,ir] + vth = vth_in[iz,ir] + if vperp.n == 1 + vth_prefactor = 1.0 / vth + else + vth_prefactor = 1.0 / vth^3 + end + nu_ee = get_collision_frequency_ee(collisions, n, vth) + nu_ei = get_collision_frequency_ei(collisions, n, vth) + @loop_vperp_vpa ivperp ivpa begin + pdf_out[ivpa,ivperp,iz,ir] -= dt * ( + nu_ee * (pdf_in[ivpa,ivperp,iz,ir] + - n * vth_prefactor + * exp(-((vpa.grid[ivpa] - upar_in[iz,ir])/vth)^2 + - (vperp.grid[ivperp]/vth)^2)) + # e-i collisions push electrons towards a Maxwellian drifting at the ion + # parallel flow, so need a corresponding normalised parallel velocity + # coordinate. + # For now, assume there is only one ion species rather than bothering to + # calculate an average ion flow speed, or sum over ion species here. + + nu_ee * (pdf_in[ivpa,ivperp,iz,ir] + - n * vth_prefactor + * exp(-((vpa.grid[ivpa] - upar_ion_in[iz,ir,1])/vth)^2 + - (vperp.grid[ivperp]/vth)^2)) + ) + end + end + end + + return nothing +end + end # krook_collisions diff --git a/moment_kinetics/src/load_data.jl b/moment_kinetics/src/load_data.jl index 430edb5a9..f4c887b84 100644 --- a/moment_kinetics/src/load_data.jl +++ b/moment_kinetics/src/load_data.jl @@ -4,7 +4,8 @@ module load_data export open_readonly_output_file export load_fields_data -export load_ion_particle_moments_data +export load_ion_moments_data +export load_electron_moments_data export load_neutral_particle_moments_data export load_pdf_data export load_neutral_pdf_data @@ -19,6 +20,8 @@ using ..array_allocation: allocate_float, allocate_int using ..calculus: derivative! using ..communication using ..coordinates: coordinate, define_coordinate +using ..electron_vpa_advection: update_electron_speed_vpa! +using ..electron_z_advection: update_electron_speed_z! using ..file_io: check_io_implementation, get_group, get_subgroup_keys, get_variable_keys using ..input_structs using ..interpolation: interpolate_to_grid_1d! @@ -29,7 +32,7 @@ using ..neutral_vz_advection: update_speed_neutral_vz! using ..neutral_z_advection: update_speed_neutral_z! using ..type_definitions: mk_float, mk_int using ..utils: get_CFL!, get_minimum_CFL_z, get_minimum_CFL_vpa, get_minimum_CFL_neutral_z, - get_minimum_CFL_neutral_vz + get_minimum_CFL_neutral_vz, enum_from_string using ..vpa_advection: update_speed_vpa! using ..z_advection: update_speed_z! @@ -41,20 +44,37 @@ const timestep_diagnostic_variables = ("time_for_run", "step_counter", "dt", "failure_counter", "failure_caused_by", "steps_per_output", "failures_per_output", "failure_caused_by_per_output", - "average_successful_dt") + "average_successful_dt", "electron_step_counter", + "electron_dt", "electron_failure_counter", + "electron_failure_caused_by", + "electron_steps_per_ion_step", + "electron_steps_per_output", + "electron_failures_per_output", + "electron_failure_caused_by_per_output", + "electron_average_successful_dt") const em_variables = ("phi", "Er", "Ez") const ion_moment_variables = ("density", "parallel_flow", "parallel_pressure", "thermal_speed", "temperature", "parallel_heat_flux", - "collision_frequency_ii", "sound_speed", "mach_number") + "collision_frequency_ii", "sound_speed", "mach_number", + "total_energy", "total_energy_flux") +const electron_moment_variables = ("electron_density", "electron_parallel_flow", + "electron_parallel_pressure", "electron_thermal_speed", + "electron_temperature", "electron_parallel_heat_flux", + "collision_frequency_ee", "collision_frequency_ei", + "electron_dudz", "electron_dpdz", "electron_dqdz") const neutral_moment_variables = ("density_neutral", "uz_neutral", "pz_neutral", "thermal_speed_neutral", "temperature_neutral", - "qz_neutral") + "qz_neutral", "total_energy_neutral", + "total_energy_flux_neutral") const all_moment_variables = tuple(em_variables..., ion_moment_variables..., + electron_moment_variables..., neutral_moment_variables...) const ion_dfn_variables = ("f",) +const electron_dfn_variables = ("f_electron",) const neutral_dfn_variables = ("f_neutral",) -const all_dfn_variables = tuple(ion_dfn_variables..., neutral_dfn_variables...) +const all_dfn_variables = tuple(ion_dfn_variables..., electron_dfn_variables..., + neutral_dfn_variables...) const ion_variables = tuple(ion_moment_variables..., ion_dfn_variables) const neutral_variables = tuple(neutral_moment_variables..., neutral_dfn_variables) @@ -65,6 +85,11 @@ function open_file_to_read(::Val{hdf5}, filename) return h5open(filename, "r") end +function get_attribute end +function get_attribute(file_or_group_or_var::Union{HDF5.H5DataStore,HDF5.Dataset}, name) + return attrs(file_or_group_or_var)[name] +end + """ """ function open_readonly_output_file(run_name, ext; iblock=0, printout=false) @@ -492,6 +517,31 @@ function load_ion_moments_data(fid; printout=false, extended_moments = false) end end +""" +""" +function load_electron_moments_data(fid; printout=false) + if printout + print("Loading electron velocity moments data...") + end + + group = get_group(fid, "dynamic_data") + + # Read electron parallel pressure + parallel_pressure = load_variable(group, "electron_parallel_pressure") + + # Read electron parallel heat flux + parallel_heat_flux = load_variable(group, "electron_parallel_heat_flux") + + # Read electron thermal speed + thermal_speed = load_variable(group, "electron_thermal_speed") + + if printout + println("done.") + end + + return parallel_pressure, parallel_heat_flux, thermal_speed +end + function load_neutral_particle_moments_data(fid; printout=false) if printout print("Loading neutral particle velocity moments data...") @@ -561,13 +611,16 @@ end """ Reload pdf and moments from an existing output file. """ -function reload_evolving_fields!(pdf, moments, boundary_distributions, +function reload_evolving_fields!(pdf, moments, fields, boundary_distributions, restart_prefix_iblock, time_index, composition, geometry, r, z, vpa, vperp, vzeta, vr, vz) code_time = 0.0 dt = nothing dt_before_last_fail = nothing + electron_dt = nothing + electron_dt_before_last_fail = nothing previous_runs_info = nothing + restart_electron_physics = nothing begin_serial_region() @serial_region begin fid = open_readonly_output_file(restart_prefix_iblock[1], "dfns"; @@ -582,6 +635,8 @@ function reload_evolving_fields!(pdf, moments, boundary_distributions, restart_evolve_density, restart_evolve_upar, restart_evolve_ppar = load_mk_options(fid) + restart_input = load_input(fid) + previous_runs_info = load_run_info_history(fid) restart_n_ion_species, restart_n_neutral_species = load_species_data(fid) @@ -621,6 +676,10 @@ function reload_evolving_fields!(pdf, moments, boundary_distributions, get_reload_ranges(parallel_io, restart_r, restart_z, restart_vperp, restart_vpa, restart_vzeta, restart_vr, restart_vz) + fields.phi .= reload_electron_moment("phi", dynamic, time_index, r, z, + r_range, z_range, restart_r, + restart_r_spectral, restart_z, + restart_z_spectral, interpolation_needed) moments.ion.dens .= reload_moment("density", dynamic, time_index, r, z, r_range, z_range, restart_r, restart_r_spectral, restart_z, @@ -636,6 +695,10 @@ function reload_evolving_fields!(pdf, moments, boundary_distributions, restart_r_spectral, restart_z, restart_z_spectral, interpolation_needed) moments.ion.ppar_updated .= true + moments.ion.pperp .= reload_moment("perpendicular_pressure", dynamic, + time_index, r, z, r_range, z_range, + restart_r, restart_r_spectral, restart_z, + restart_z_spectral, interpolation_needed) moments.ion.qpar .= reload_moment("parallel_heat_flux", dynamic, time_index, r, z, r_range, z_range, restart_r, restart_r_spectral, restart_z, @@ -736,6 +799,96 @@ function reload_evolving_fields!(pdf, moments, boundary_distributions, restart_evolve_density, restart_evolve_upar, restart_evolve_ppar) + moments.electron.dens .= reload_electron_moment("electron_density", dynamic, + time_index, r, z, r_range, + z_range, restart_r, + restart_r_spectral, restart_z, + restart_z_spectral, + interpolation_needed) + moments.electron.dens_updated[] = true + moments.electron.upar .= reload_electron_moment("electron_parallel_flow", + dynamic, time_index, r, z, + r_range, z_range, restart_r, + restart_r_spectral, restart_z, + restart_z_spectral, + interpolation_needed) + moments.electron.upar_updated[] = true + moments.electron.ppar .= reload_electron_moment("electron_parallel_pressure", + dynamic, time_index, r, z, + r_range, z_range, restart_r, + restart_r_spectral, restart_z, + restart_z_spectral, + interpolation_needed) + moments.electron.ppar_updated[] = true + moments.electron.qpar .= reload_electron_moment("electron_parallel_heat_flux", + dynamic, time_index, r, z, + r_range, z_range, restart_r, + restart_r_spectral, restart_z, + restart_z_spectral, + interpolation_needed) + moments.electron.qpar_updated[] = true + moments.electron.vth .= reload_electron_moment("electron_thermal_speed", + dynamic, time_index, r, z, + r_range, z_range, restart_r, + restart_r_spectral, restart_z, + restart_z_spectral, + interpolation_needed) + if "electron_constraints_A_coefficient" ∈ keys(dynamic) + moments.electron.constraints_A_coefficient .= + reload_electron_moment("electron_constraints_A_coefficient", dynamic, + time_index, r, z, r_range, z_range, restart_r, + restart_r_spectral, restart_z, + restart_z_spectral, interpolation_needed) + else + moments.electron.constraints_A_coefficient .= 0.0 + end + if "electron_constraints_B_coefficient" ∈ keys(dynamic) + moments.electron.constraints_B_coefficient .= + reload_electron_moment("electron_constraints_B_coefficient", dynamic, + time_index, r, z, r_range, z_range, restart_r, + restart_r_spectral, restart_z, + restart_z_spectral, interpolation_needed) + else + moments.electron.constraints_B_coefficient .= 0.0 + end + if "electron_constraints_C_coefficient" ∈ keys(dynamic) + moments.electron.constraints_C_coefficient .= + reload_electron_moment("electron_constraints_C_coefficient", dynamic, + time_index, r, z, r_range, z_range, restart_r, + restart_r_spectral, restart_z, + restart_z_spectral, interpolation_needed) + else + moments.electron.constraints_C_coefficient .= 0.0 + end + + # For now, electrons are always fully moment_kinetic + restart_electron_evolve_density, restart_electron_evolve_upar, + restart_electron_evolve_ppar = true, true, true + electron_evolve_density, electron_evolve_upar, electron_evolve_ppar = + true, true, true + if "electron_physics" ∈ keys(restart_input) + restart_electron_physics = enum_from_string(electron_physics_type, + restart_input["electron_physics"]) + else + restart_electron_physics = boltzmann_electron_response + end + if pdf.electron !== nothing && + restart_electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + pdf.electron.norm .= + reload_electron_pdf(dynamic, time_index, moments, r, z, vperp, vpa, + r_range, z_range, vperp_range, vpa_range, + restart_r, restart_r_spectral, restart_z, + restart_z_spectral, restart_vperp, + restart_vperp_spectral, restart_vpa, + restart_vpa_spectral, interpolation_needed, + restart_evolve_density, restart_evolve_upar, + restart_evolve_ppar) + elseif pdf.electron !== nothing + # The electron distribution function will be initialized later + pdf.electron.norm .= 0.0 + end + if composition.n_neutral_species > 0 moments.neutral.dens .= reload_moment("density_neutral", dynamic, time_index, r, z, r_range, z_range, @@ -860,12 +1013,118 @@ function reload_evolving_fields!(pdf, moments, boundary_distributions, dt_before_last_fail = load_slice(dynamic, "dt_before_last_fail", time_index) end + if "electron_dt" ∈ keys(dynamic) + electron_dt = load_slice(dynamic, "electron_dt", time_index) + end + if "electron_dt_before_last_fail" ∈ keys(dynamic) + electron_dt_before_last_fail = + load_slice(dynamic, "electron_dt_before_last_fail", time_index) + end + finally + close(fid) + end + end + + restart_electron_physics = MPI.bcast(restart_electron_physics, 0, comm_block[]) + + return code_time, dt, dt_before_last_fail, electron_dt, electron_dt_before_last_fail, + previous_runs_info, time_index, restart_electron_physics +end + +""" +Reload electron pdf and moments from an existing output file. +""" +function reload_electron_data!(pdf, moments, t_params, restart_prefix_iblock, time_index, + geometry, r, z, vpa, vperp, vzeta, vr, vz) + code_time = 0.0 + pdf_electron_converged = false + previous_runs_info = nothing + begin_serial_region() + @serial_region begin + fid = open_readonly_output_file(restart_prefix_iblock[1], "initial_electron"; + iblock=restart_prefix_iblock[2]) + try # finally to make sure to close file0 + overview = get_group(fid, "overview") + dynamic = get_group(fid, "dynamic_data") + parallel_io = load_variable(overview, "parallel_io") + if time_index < 0 + time_index, _, _ = load_time_data(fid) + end + #restart_evolve_density, restart_evolve_upar, restart_evolve_ppar = + # load_mk_options(fid) + # For now, electrons are always fully moment_kinetic + restart_evolve_density, restart_evolve_upar, restart_evolve_ppar = true, true, + true + evolve_density, evolve_upar, evolve_ppar = true, true, true + + previous_runs_info = load_run_info_history(fid) + + restart_n_ion_species, restart_n_neutral_species = load_species_data(fid) + restart_r, restart_r_spectral, restart_z, restart_z_spectral, restart_vperp, + restart_vperp_spectral, restart_vpa, restart_vpa_spectral, restart_vzeta, + restart_vzeta_spectral, restart_vr,restart_vr_spectral, restart_vz, + restart_vz_spectral = load_restart_coordinates(fid, r, z, vperp, vpa, + vzeta, vr, vz, parallel_io) + + # Test whether any interpolation is needed + interpolation_needed = Dict( + x.name => x.n != restart_x.n || !all(isapprox.(x.grid, restart_x.grid)) + for (x, restart_x) ∈ ((z, restart_z), (r, restart_r), + (vperp, restart_vperp), (vpa, restart_vpa))) + + code_time = load_slice(dynamic, "time", time_index) + + pdf_electron_converged = get_attribute(fid, "pdf_electron_converged") + + r_range, z_range, vperp_range, vpa_range, vzeta_range, vr_range, vz_range = + get_reload_ranges(parallel_io, restart_r, restart_z, restart_vperp, + restart_vpa, restart_vzeta, restart_vr, restart_vz) + + moments.electron.upar_updated[] = true + moments.electron.ppar .= + reload_electron_moment("electron_parallel_pressure", dynamic, time_index, + r, z, r_range, z_range, restart_r, + restart_r_spectral, restart_z, restart_z_spectral, + interpolation_needed) + moments.electron.ppar_updated[] = true + moments.electron.qpar .= + reload_electron_moment("electron_parallel_heat_flux", dynamic, time_index, + r, z, r_range, z_range, restart_r, + restart_r_spectral, restart_z, restart_z_spectral, + interpolation_needed) + moments.electron.qpar_updated[] = true + moments.electron.vth .= + reload_electron_moment("electron_thermal_speed", dynamic, time_index, r, + z, r_range, z_range, restart_r, restart_r_spectral, + restart_z, restart_z_spectral, + interpolation_needed) + + pdf.electron.norm .= + reload_electron_pdf(dynamic, time_index, moments, r, z, vperp, vpa, + r_range, z_range, vperp_range, vpa_range, restart_r, + restart_r_spectral, restart_z, restart_z_spectral, + restart_vperp, restart_vperp_spectral, restart_vpa, + restart_vpa_spectral, interpolation_needed, + restart_evolve_density, restart_evolve_upar, + restart_evolve_ppar) + + new_dt = load_slice(dynamic, "electron_dt", time_index) + if new_dt > 0.0 + # if the reloaded electron_dt was 0.0, then the previous run would not + # have been using kinetic electrons, so we only use the value if it is + # positive + t_params.dt[] = new_dt + end + t_params.previous_dt[] = t_params.dt[] + t_params.dt_before_output[] = t_params.dt[] + t_params.dt_before_last_fail[] = + load_slice(dynamic, "electron_dt_before_last_fail", time_index) finally close(fid) end end - return code_time, dt, dt_before_last_fail, previous_runs_info, time_index + return code_time, pdf_electron_converged, previous_runs_info, time_index end function load_restart_coordinates(fid, r, z, vperp, vpa, vzeta, vr, vz, parallel_io) @@ -885,15 +1144,20 @@ function load_restart_coordinates(fid, r, z, vperp, vpa, vzeta, vr, vz, parallel restart_vz, restart_vz_spectral, _ = load_coordinate_data(fid, "vz"; irank=vz.irank, nrank=vz.nrank) else - restart_z, restart_z_spectral, _ = load_coordinate_data(fid, "z") - restart_r, restart_r_spectral, _ = load_coordinate_data(fid, "r") + restart_z, restart_z_spectral, _ = + load_coordinate_data(fid, "z") + restart_r, restart_r_spectral, _ = + load_coordinate_data(fid, "r") restart_vperp, restart_vperp_spectral, _ = load_coordinate_data(fid, "vperp") - restart_vpa, restart_vpa_spectral, _ = load_coordinate_data(fid, "vpa") + restart_vpa, restart_vpa_spectral, _ = + load_coordinate_data(fid, "vpa") restart_vzeta, restart_vzeta_spectral, _ = load_coordinate_data(fid, "vzeta") - restart_vr, restart_vr_spectral, _ = load_coordinate_data(fid, "vr") - restart_vz, restart_vz_spectral, _ = load_coordinate_data(fid, "vz") + restart_vr, restart_vr_spectral, _ = + load_coordinate_data(fid, "vr") + restart_vz, restart_vz_spectral, _ = + load_coordinate_data(fid, "vz") if restart_r.nrank != r.nrank error("Not using parallel I/O, and distributed MPI layout has " @@ -998,6 +1262,30 @@ function reload_moment(var_name, dynamic, time_index, r, z, r_range, z_range, re return moment end +function reload_electron_moment(var_name, dynamic, time_index, r, z, r_range, z_range, + restart_r, restart_r_spectral, restart_z, + restart_z_spectral, interpolation_needed) + moment = load_slice(dynamic, var_name, z_range, r_range, time_index) + orig_nz, orig_nr = size(moment) + if interpolation_needed["r"] + new_moment = allocate_float(orig_nz, r.n) + for iz ∈ 1:orig_nz + @views interpolate_to_grid_1d!(new_moment[iz,:], r.grid, moment[iz,:], + restart_r, restart_r_spectral) + end + moment = new_moment + end + if interpolation_needed["z"] + new_moment = allocate_float(z.n, r.n) + for ir ∈ 1:r.n + @views interpolate_to_grid_1d!(new_moment[:,ir], z.grid, moment[:,ir], + restart_z, restart_z_spectral) + end + moment = new_moment + end + return moment +end + function reload_ion_pdf(dynamic, time_index, moments, r, z, vperp, vpa, r_range, z_range, vperp_range, vpa_range, restart_r, restart_r_spectral, restart_z, restart_z_spectral, restart_vperp, restart_vperp_spectral, @@ -1506,6 +1794,281 @@ function reload_ion_boundary_pdf(boundary_distributions_io, var_name, ir, moment return this_pdf end +function reload_electron_pdf(dynamic, time_index, moments, r, z, vperp, vpa, r_range, + z_range, vperp_range, vpa_range, restart_r, + restart_r_spectral, restart_z, restart_z_spectral, + restart_vperp, restart_vperp_spectral, restart_vpa, + restart_vpa_spectral, interpolation_needed, + restart_evolve_density, restart_evolve_upar, + restart_evolve_ppar) + + # Currently, electrons are always fully moment-kinetic + evolve_density = true + evolve_upar = true + evolve_ppar = true + + this_pdf = load_slice(dynamic, "f_electron", vpa_range, vperp_range, z_range, r_range, + time_index) + orig_nvpa, orig_nvperp, orig_nz, orig_nr = size(this_pdf) + if interpolation_needed["r"] + new_pdf = allocate_float(orig_nvpa, orig_nvperp, orig_nz, r.n) + for iz ∈ 1:orig_nz, ivperp ∈ 1:orig_nvperp, + ivpa ∈ 1:orig_nvpa + @views interpolate_to_grid_1d!( + new_pdf[ivpa,ivperp,iz,:], r.grid, + this_pdf[ivpa,ivperp,iz,:], restart_r, + restart_r_spectral) + end + this_pdf = new_pdf + end + if interpolation_needed["z"] + new_pdf = allocate_float(orig_nvpa, orig_nvperp, z.n, r.n) + for ir ∈ 1:r.n, ivperp ∈ 1:orig_nvperp, + ivpa ∈ 1:orig_nvpa + @views interpolate_to_grid_1d!( + new_pdf[ivpa,ivperp,:,ir], z.grid, + this_pdf[ivpa,ivperp,:,ir], restart_z, + restart_z_spectral) + end + this_pdf = new_pdf + end + + # Current moment-kinetic implementation is only 1V, so no need to handle a + # normalised vperp coordinate. This will need to change when 2V + # moment-kinetics is implemented. + if interpolation_needed["vperp"] + new_pdf = allocate_float(orig_nvpa, vperp.n, z.n, r.n) + for ir ∈ 1:r.n, iz ∈ 1:z.n, ivpa ∈ 1:orig_nvpa + @views interpolate_to_grid_1d!( + new_pdf[ivpa,:,iz,ir], vperp.grid, + this_pdf[ivpa,:,iz,ir], restart_vperp, + restart_vperp_spectral) + end + this_pdf = new_pdf + end + + if ( + (evolve_density == restart_evolve_density && + evolve_upar == restart_evolve_upar && evolve_ppar == + restart_evolve_ppar) + || (!evolve_upar && !restart_evolve_upar && + !evolve_ppar && !restart_evolve_ppar) + ) + # No chages to velocity-space coordinates, so just interpolate from + # one grid to the other + if interpolation_needed["vpa"] + new_pdf = allocate_float(vpa.n, vperp.n, z.n, r.n) + for ir ∈ 1:r.n, iz ∈ 1:z.n, ivperp ∈ 1:vperp.n + @views interpolate_to_grid_1d!( + new_pdf[:,ivperp,iz,ir], vpa.grid, + this_pdf[:,ivperp,iz,ir], restart_vpa, + restart_vpa_spectral) + end + this_pdf = new_pdf + end + elseif (!evolve_upar && !evolve_ppar && + restart_evolve_upar && !restart_evolve_ppar) + # vpa = new_wpa = old_wpa + upar + # => old_wpa = new_wpa - upar + new_pdf = allocate_float(vpa.n, vperp.n, z.n, r.n) + for ir ∈ 1:r.n, iz ∈ 1:z.n, ivperp ∈ 1:vperp.n + restart_vpa_vals = vpa.grid .- moments.electron.upar[iz,ir] + @views interpolate_to_grid_1d!( + new_pdf[:,ivperp,iz,ir], restart_vpa_vals, + this_pdf[:,ivperp,iz,ir], restart_vpa, + restart_vpa_spectral) + end + this_pdf = new_pdf + elseif (!evolve_upar && !evolve_ppar && + !restart_evolve_upar && restart_evolve_ppar) + # vpa = new_wpa = old_wpa*vth + # => old_wpa = new_wpa/vth + new_pdf = allocate_float(vpa.n, vperp.n, z.n, r.n) + for ir ∈ 1:r.n, iz ∈ 1:z.n, ivperp ∈ 1:vperp.n + restart_vpa_vals = vpa.grid ./ moments.electron.vth[iz,ir] + @views interpolate_to_grid_1d!( + new_pdf[:,ivperp,iz,ir], restart_vpa_vals, + this_pdf[:,ivperp,iz,ir], restart_vpa, + restart_vpa_spectral) + end + this_pdf = new_pdf + elseif (!evolve_upar && !evolve_ppar && + restart_evolve_upar && restart_evolve_ppar) + # vpa = new_wpa = old_wpa*vth + upar + # => old_wpa = (new_wpa - upar)/vth + new_pdf = allocate_float(vpa.n, vperp.n, z.n, r.n) + for ir ∈ 1:r.n, iz ∈ 1:z.n, ivperp ∈ 1:vperp.n + restart_vpa_vals = + @. (vpa.grid - moments.electron.upar[iz,ir]) / + moments.electron.vth[iz,ir] + @views interpolate_to_grid_1d!( + new_pdf[:,ivperp,iz,ir], restart_vpa_vals, + this_pdf[:,ivperp,iz,ir], restart_vpa, + restart_vpa_spectral) + end + this_pdf = new_pdf + elseif (evolve_upar && !evolve_ppar && + !restart_evolve_upar && !restart_evolve_ppar) + # vpa = new_wpa + upar = old_wpa + # => old_wpa = new_wpa + upar + new_pdf = allocate_float(vpa.n, vperp.n, z.n, r.n) + for ir ∈ 1:r.n, iz ∈ 1:z.n, ivperp ∈ 1:vperp.n + restart_vpa_vals = vpa.grid .+ moments.electron.upar[iz,ir] + @views interpolate_to_grid_1d!( + new_pdf[:,ivperp,iz,ir], restart_vpa_vals, + this_pdf[:,ivperp,iz,ir], restart_vpa, + restart_vpa_spectral) + end + this_pdf = new_pdf + elseif (evolve_upar && !evolve_ppar && + !restart_evolve_upar && restart_evolve_ppar) + # vpa = new_wpa + upar = old_wpa*vth + # => old_wpa = (new_wpa + upar)/vth + new_pdf = allocate_float(vpa.n, vperp.n, z.n, r.n) + for ir ∈ 1:r.n, iz ∈ 1:z.n, ivperp ∈ 1:vperp.n + restart_vpa_vals = + @. (vpa.grid + moments.electron.upar[iz,ir]) / + moments.electron.vth + @views interpolate_to_grid_1d!( + new_pdf[:,ivperp,iz,ir], restart_vpa_vals, + this_pdf[:,ivperp,iz,ir], restart_vpa, + restart_vpa_spectral) + end + this_pdf = new_pdf + elseif (evolve_upar && !evolve_ppar && + restart_evolve_upar && restart_evolve_ppar) + # vpa = new_wpa + upar = old_wpa*vth + upar + # => old_wpa = new_wpa/vth + new_pdf = allocate_float(vpa.n, vperp.n, z.n, r.n) + for ir ∈ 1:r.n, iz ∈ 1:z.n, ivperp ∈ 1:vperp.n + restart_vpa_vals = vpa.grid ./ moments.electron.vth[iz,ir] + @views interpolate_to_grid_1d!( + new_pdf[:,ivperp,iz,ir], restart_vpa_vals, + this_pdf[:,ivperp,iz,ir], restart_vpa, + restart_vpa_spectral) + end + this_pdf = new_pdf + elseif (!evolve_upar && evolve_ppar && + !restart_evolve_upar && !restart_evolve_ppar) + # vpa = new_wpa*vth = old_wpa + # => old_wpa = new_wpa*vth + new_pdf = allocate_float(vpa.n, vperp.n, z.n, r.n) + for ir ∈ 1:r.n, iz ∈ 1:z.n, ivperp ∈ 1:vperp.n + restart_vpa_vals = vpa.grid .* moments.electron.vth[iz,ir] + @views interpolate_to_grid_1d!( + new_pdf[:,ivperp,iz,ir], restart_vpa_vals, + this_pdf[:,ivperp,iz,ir], restart_vpa, + restart_vpa_spectral) + end + this_pdf = new_pdf + elseif (!evolve_upar && evolve_ppar && + restart_evolve_upar && !restart_evolve_ppar) + # vpa = new_wpa*vth = old_wpa + upar + # => old_wpa = new_wpa*vth - upar/vth + new_pdf = allocate_float(vpa.n, vperp.n, z.n, r.n) + for ir ∈ 1:r.n, iz ∈ 1:z.n, ivperp ∈ 1:vperp.n + restart_vpa_vals = @. vpa.grid * moments.electron.vth[iz,ir] - + moments.electron.upar[iz,ir] + @views interpolate_to_grid_1d!( + new_pdf[:,ivperp,iz,ir], restart_vpa_vals, + this_pdf[:,ivperp,iz,ir], restart_vpa, + restart_vpa_spectral) + end + this_pdf = new_pdf + elseif (!evolve_upar && evolve_ppar && + restart_evolve_upar && restart_evolve_ppar) + # vpa = new_wpa*vth = old_wpa*vth + upar + # => old_wpa = new_wpa - upar/vth + new_pdf = allocate_float(vpa.n, vperp.n, z.n, r.n) + for ir ∈ 1:r.n, iz ∈ 1:z.n, ivperp ∈ 1:vperp.n + restart_vpa_vals = + @. vpa.grid - + moments.electron.upar[iz,ir]/moments.electron.vth[iz,ir] + @views interpolate_to_grid_1d!( + new_pdf[:,ivperp,iz,ir], restart_vpa_vals, + this_pdf[:,ivperp,iz,ir], restart_vpa, + restart_vpa_spectral) + end + this_pdf = new_pdf + elseif (evolve_upar && evolve_ppar && + !restart_evolve_upar && !restart_evolve_ppar) + # vpa = new_wpa*vth + upar = old_wpa + # => old_wpa = new_wpa*vth + upar + new_pdf = allocate_float(vpa.n, vperp.n, z.n, r.n) + for ir ∈ 1:r.n, iz ∈ 1:z.n, ivperp ∈ 1:vperp.n + restart_vpa_vals = @. vpa.grid * moments.electron.vth[iz,ir] + + moments.electron.upar[iz,ir] + @views interpolate_to_grid_1d!( + new_pdf[:,ivperp,iz,ir], restart_vpa_vals, + this_pdf[:,ivperp,iz,ir], restart_vpa, + restart_vpa_spectral) + end + this_pdf = new_pdf + elseif (evolve_upar && evolve_ppar && + restart_evolve_upar && !restart_evolve_ppar) + # vpa = new_wpa*vth + upar = old_wpa + upar + # => old_wpa = new_wpa*vth + new_pdf = allocate_float(vpa.n, vperp.n, z.n, r.n) + for ir ∈ 1:r.n, iz ∈ 1:z.n, ivperp ∈ 1:vperp.n + restart_vpa_vals = vpa.grid .* moments.electron.vth[iz,ir] + @views interpolate_to_grid_1d!( + new_pdf[:,ivperp,iz,ir], restart_vpa_vals, + this_pdf[:,ivperp,iz,ir], restart_vpa, + restart_vpa_spectral) + end + this_pdf = new_pdf + elseif (evolve_upar && evolve_ppar && + !restart_evolve_upar && restart_evolve_ppar) + # vpa = new_wpa*vth + upar = old_wpa*vth + # => old_wpa = new_wpa + upar/vth + new_pdf = allocate_float(vpa.n, vperp.n, z.n, r.n) + for ir ∈ 1:r.n, iz ∈ 1:z.n, ivperp ∈ 1:vperp.n + restart_vpa_vals = + @. vpa.grid + + moments.electron.upar[iz,ir] / moments.electron.vth[iz,ir] + @views interpolate_to_grid_1d!( + new_pdf[:,ivperp,iz,ir], restart_vpa_vals, + this_pdf[:,ivperp,iz,ir], restart_vpa, + restart_vpa_spectral) + end + this_pdf = new_pdf + else + # This should never happen, as all combinations of evolve_* options + # should be handled above. + error("Unsupported combination of moment-kinetic options:" + * " evolve_density=", evolve_density + * " evolve_upar=", evolve_upar + * " evolve_ppar=", evolve_ppar + * " restart_evolve_density=", restart_evolve_density + * " restart_evolve_upar=", restart_evolve_upar + * " restart_evolve_ppar=", restart_evolve_ppar) + end + if evolve_density && !restart_evolve_density + # Need to normalise by density + for ir ∈ 1:r.n, iz ∈ 1:z.n + this_pdf[:,:,iz,ir] ./= moments.electron.dens[iz,ir] + end + elseif !evolve_density && restart_evolve_density + # Need to unnormalise by density + for ir ∈ 1:r.n, iz ∈ 1:z.n + this_pdf[:,:,iz,ir] .*= moments.electron.dens[iz,ir] + end + end + if evolve_ppar && !restart_evolve_ppar + # Need to normalise by vth + for ir ∈ 1:r.n, iz ∈ 1:z.n + this_pdf[:,:,iz,ir] .*= moments.electron.vth[iz,ir] + end + elseif !evolve_ppar && restart_evolve_ppar + # Need to unnormalise by vth + for ir ∈ 1:r.n, iz ∈ 1:z.n + this_pdf[:,:,iz,ir] ./= moments.electron.vth[iz,ir] + end + end + + return this_pdf +end + function reload_neutral_pdf(dynamic, time_index, moments, r, z, vzeta, vr, vz, r_range, z_range, vzeta_range, vr_range, vz_range, restart_r, restart_r_spectral, restart_z, restart_z_spectral, @@ -2091,10 +2654,10 @@ restarts (which are sequential in time), so concatenate the data from each entry The slice to take is specified by the keyword arguments. """ function load_distributed_ion_pdf_slice(run_names::Tuple, nblocks::Tuple, t_range, - n_species::mk_int, r::coordinate, - z::coordinate, vperp::coordinate, - vpa::coordinate; is=nothing, ir=nothing, - iz=nothing, ivperp=nothing, ivpa=nothing) + n_species::mk_int, r::coordinate, z::coordinate, + vperp::coordinate, vpa::coordinate; is=nothing, + ir=nothing, iz=nothing, ivperp=nothing, + ivpa=nothing) # dimension of pdf is [vpa,vperp,z,r,species,t] result_dims = mk_int[] @@ -2291,20 +2854,216 @@ function load_distributed_ion_pdf_slice(run_names::Tuple, nblocks::Tuple, t_rang end """ -Read a slice of a neutral distribution function +Read a slice of an electron distribution function run_names is a tuple. If it has more than one entry, this means that there are multiple restarts (which are sequential in time), so concatenate the data from each entry together. The slice to take is specified by the keyword arguments. """ -function load_distributed_neutral_pdf_slice(run_names::Tuple, nblocks::Tuple, t_range, - n_species::mk_int, r::coordinate, - z::coordinate, vzeta::coordinate, - vr::coordinate, vz::coordinate; isn=nothing, - ir=nothing, iz=nothing, ivzeta=nothing, - ivr=nothing, ivz=nothing) - # dimension of pdf is [vpa,vperp,z,r,species,t] +function load_distributed_electron_pdf_slice(run_names::Tuple, nblocks::Tuple, t_range, + n_species::mk_int, r::coordinate, + z::coordinate, vperp::coordinate, + vpa::coordinate; ir=nothing, iz=nothing, + ivperp=nothing, ivpa=nothing) + # dimension of pdf is [vpa,vperp,z,r,t] + + result_dims = mk_int[] + if ivpa === nothing + ivpa = 1:vpa.n_global + push!(result_dims, vpa.n_global) + elseif !isa(ivpa, mk_int) + push!(result_dims, length(ivpa)) + end + if ivperp === nothing + ivperp = 1:vperp.n_global + push!(result_dims, vperp.n_global) + elseif !isa(ivperp, mk_int) + push!(result_dims, length(ivperp)) + end + if iz === nothing + iz = 1:z.n_global + push!(result_dims, z.n_global) + elseif isa(iz, mk_int) + push!(result_dims, 1) + else + push!(result_dims, length(iz)) + end + if ir === nothing + ir = 1:r.n_global + push!(result_dims, r.n_global) + elseif isa(ir, mk_int) + push!(result_dims, 1) + else + push!(result_dims, length(ir)) + end + push!(result_dims, length(t_range)) + + f_global = allocate_float(result_dims...) + + local_tind_start = 1 + local_tind_end = -1 + global_tind_start = 1 + global_tind_end = -1 + for (run_name, nb) in zip(run_names, nblocks) + for iblock in 0:nb-1 + fid = open_readonly_output_file(run_name, "dfns", iblock=iblock, printout=false) + + z_irank, z_nrank, r_irank, r_nrank = load_rank_data(fid) + + # max index set to avoid double assignment of repeated points + # nr/nz if irank = nrank-1, (nr-1)/(nz-1) otherwise + imax_r = (r_irank == r.nrank - 1 ? r.n : r.n - 1) + imax_z = (z_irank == z.nrank - 1 ? z.n : z.n - 1) + local_r_range = 1:imax_r + local_z_range = 1:imax_z + global_r_range = iglobal_func(1, r_irank, r.n):iglobal_func(imax_r, r_irank, r.n) + global_z_range = iglobal_func(1, z_irank, z.n):iglobal_func(imax_z, z_irank, z.n) + + if ir !== nothing && !any(i ∈ global_r_range for i in ir) + # No data for the slice on this rank + continue + elseif isa(ir, StepRange) + # Note that `findfirst(test, array)` returns the index `i` of the first + # element of `array` for which `test(array[i])` is `true`. + # `findlast()` similarly finds the index of the last element... + start_ind = findfirst(i -> i>=ir.start, global_r_range) + start = global_r_range[start_ind] + stop_ind = findlast(i -> i<=ir.stop, global_r_range) + stop = global_r_range[stop_ind] + local_r_range = (local_r_range.start + start - global_r_range.start):ir.step:(local_r_range.stop + stop - global_r_range.stop) + global_r_range = findfirst(i->i ∈ global_r_range, ir):findlast(i->i ∈ global_r_range, ir) + elseif isa(ir, UnitRange) + start_ind = findfirst(i -> i>=ir.start, global_r_range) + start = global_r_range[start_ind] + stop_ind = findlast(i -> i<=ir.stop, global_r_range) + stop = global_r_range[stop_ind] + local_r_range = (local_r_range.start + start - global_r_range.start):(local_r_range.stop + stop - global_r_range.stop) + global_r_range = findfirst(i->i ∈ global_r_range, ir):findlast(i->i ∈ global_r_range, ir) + elseif isa(ir, mk_int) + local_r_range = ir - (global_r_range.start - 1) + global_r_range = ir + end + if iz !== nothing && !any(i ∈ global_z_range for i in iz) + # No data for the slice on this rank + continue + elseif isa(iz, StepRange) + # Note that `findfirst(test, array)` returns the index `i` of the first + # element of `array` for which `test(array[i])` is `true`. + # `findlast()` similarly finds the index of the last element... + start_ind = findfirst(i -> i>=iz.start, global_z_range) + start = global_z_range[start_ind] + stop_ind = findlast(i -> i<=iz.stop, global_z_range) + stop = global_z_range[stop_ind] + local_z_range = (local_z_range.start + start - global_z_range.start):iz.step:(local_z_range.stop + stop - global_z_range.stop) + global_z_range = findfirst(i->i ∈ global_z_range, iz):findlast(i->i ∈ global_z_range, iz) + elseif isa(iz, UnitRange) + start_ind = findfirst(i -> i>=iz.start, global_z_range) + start = global_z_range[start_ind] + stop_ind = findlast(i -> i<=iz.stop, global_z_range) + stop = global_z_range[stop_ind] + local_z_range = (local_z_range.start + start - global_z_range.start):(local_z_range.stop + stop - global_z_range.stop) + global_z_range = findfirst(i->i ∈ global_z_range, iz):findlast(i->i ∈ global_z_range, iz) + elseif isa(iz, mk_int) + local_z_range = iz - (global_z_range.start - 1) + global_z_range = iz + end + + f_local_slice = load_pdf_data(fid) + + if local_tind_start > 1 + # The run being loaded is a restart (as local_tind_start=1 for the first + # run), so skip the first point, as this is a duplicate of the last point + # of the previous restart + skip_first = 1 + else + skip_first = 0 + end + ntime_local = size(f_local_slice, ndims(f_local_slice)) - skip_first + local_tind_end = local_tind_start + ntime_local - 1 + local_t_range = collect(it - local_tind_start + 1 + skip_first + for it ∈ t_range + if local_tind_start <= it <= local_tind_end) + global_tind_end = global_tind_start + length(local_t_range) - 1 + + f_global_slice = selectdim(f_global, ndims(f_global), + global_tind_start:global_tind_end) + + # Note: use selectdim() and get the dimension from thisdim because the actual + # number of dimensions in f_global_slice, f_local_slice is different depending + # on which combination of ivpa, ivperp, iz, ir, and is was passed. + thisdim = ndims(f_local_slice) - 5 + f_local_slice = selectdim(f_local_slice, thisdim, ivpa) + + thisdim = ndims(f_local_slice) - 4 + f_local_slice = selectdim(f_local_slice, thisdim, ivperp) + + thisdim = ndims(f_local_slice) - 3 + if isa(iz, mk_int) + f_global_slice = selectdim(f_global_slice, thisdim, 1) + f_local_slice = selectdim(f_local_slice, thisdim, + ilocal_func(iz, z_irank, z.n)) + else + f_global_slice = selectdim(f_global_slice, thisdim, global_z_range) + f_local_slice = selectdim(f_local_slice, thisdim, local_z_range) + end + + thisdim = ndims(f_local_slice) - 2 + if isa(ir, mk_int) + f_global_slice = selectdim(f_global_slice, thisdim, 1) + f_local_slice = selectdim(f_local_slice, thisdim, + ilocal_func(ir, r_irank, r.n)) + else + f_global_slice = selectdim(f_global_slice, thisdim, global_r_range) + f_local_slice = selectdim(f_local_slice, thisdim, local_r_range) + end + + thisdim = ndims(f_local_slice) - 1 + f_global_slice = selectdim(f_global_slice, thisdim) + f_local_slice = selectdim(f_local_slice, thisdim) + + # Select time slice + thisdim = ndims(f_local_slice) + f_local_slice = selectdim(f_local_slice, thisdim, local_t_range) + + f_global_slice .= f_local_slice + close(fid) + end + local_tind_start = local_tind_end + 1 + global_tind_start = global_tind_end + 1 + end + + if isa(iz, mk_int) + thisdim = ndims(f_global) - 3 + f_global = selectdim(f_global, thisdim, 1) + end + if isa(ir, mk_int) + thisdim = ndims(f_global) - 2 + f_global = selectdim(f_global, thisdim, 1) + end + if isa(t_range, mk_int) + thisdim = ndims(f_global) + f_global = selectdim(f_global, thisdim, 1) + end + + return f_global +end + +""" +Read a slice of a neutral distribution function + +run_names is a tuple. If it has more than one entry, this means that there are multiple +restarts (which are sequential in time), so concatenate the data from each entry together. + +The slice to take is specified by the keyword arguments. +""" +function load_distributed_neutral_pdf_slice(run_names::Tuple, nblocks::Tuple, t_range, + n_species::mk_int, r::coordinate, + z::coordinate, vzeta::coordinate, + vr::coordinate, vz::coordinate; isn=nothing, + ir=nothing, iz=nothing, ivzeta=nothing, + ivr=nothing, ivz=nothing) + # dimension of pdf is [vpa,vperp,z,r,species,t] result_dims = mk_int[] if ivz === nothing @@ -2526,9 +3285,9 @@ end """ get_run_info_no_setup(run_dir...; itime_min=1, itime_max=0, itime_skip=1, dfns=false, - do_setup=true, setup_input_file=nothing) + initial_electron=false) get_run_info_no_setup((run_dir, restart_index)...; itime_min=1, itime_max=0, - itime_skip=1, dfns=false, do_setup=true, setup_input_file=nothing) + itime_skip=1, dfns=false, initial_electron=false) Get file handles and other info for a single run @@ -2550,7 +3309,8 @@ argument can be a String `run_dir` giving a directory to read output from or a T mix Strings and Tuples in a call). By default load data from moments files, pass `dfns=true` to load from distribution -functions files. +functions files, or `initial_electron=true` and `dfns=true` to load from initial electron +state files. The `itime_min`, `itime_max` and `itime_skip` options can be used to select only a slice of time points when loading data. In `makie_post_process` these options are read from the @@ -2561,14 +3321,19 @@ defaults for the remaining options. If either `itime_min` or `itime_max` are ≤ values are used as offsets from the final time index of the run. """ function get_run_info_no_setup(run_dir::Union{AbstractString,Tuple{AbstractString,Union{Int,Nothing}}}...; - itime_min=1, itime_max=0, itime_skip=1, dfns=false) + itime_min=1, itime_max=0, itime_skip=1, dfns=false, + initial_electron=false) if length(run_dir) == 0 error("No run_dir passed") end + if initial_electron && !dfns + error("When `initial_electron=true` is passed, `dfns=true` must also be passed") + end if length(run_dir) > 1 run_info = Tuple(get_run_info_no_setup(r; itime_min=itime_min, itime_max=itime_max, itime_skip=itime_skip, - dfns=dfns) + dfns=dfns, + initial_electron=initial_electron) for r ∈ run_dir) return run_info end @@ -2589,6 +3354,7 @@ function get_run_info_no_setup(run_dir::Union{AbstractString,Tuple{AbstractStrin * "`(String, Nothing)`. Got $run_dir") end + electron_debug = false if isfile(this_run_dir) # this_run_dir is actually a filename. Assume it is a moment_kinetics output file # and infer the directory and the run_name from the filename. @@ -2600,8 +3366,13 @@ function get_run_info_no_setup(run_dir::Union{AbstractString,Tuple{AbstractStrin run_name = split(filename, ".moments.")[1] elseif occursin(".dfns.", filename) run_name = split(filename, ".dfns.")[1] + elseif occursin(".initial_electron.", filename) + run_name = split(filename, ".initial_electron.")[1] + elseif occursin(".electron_debug.", filename) + run_name = split(filename, ".electron_debug.")[1] + electron_debug = true else - error("Cannot recognise '$this_run_dir' as a moment_kinetics output file") + error("Cannot recognise '$this_run_dir/$filename' as a moment_kinetics output file") end elseif isdir(this_run_dir) # Normalise by removing any trailing slash - with a slash basename() would return an @@ -2618,18 +3389,36 @@ function get_run_info_no_setup(run_dir::Union{AbstractString,Tuple{AbstractStrin # Find output files from all restarts in the directory counter = 1 run_prefixes = Vector{String}() - while true - # Test if output files exist for this value of counter - prefix_with_count = base_prefix * "_$counter" - if length(glob(basename(prefix_with_count) * ".*.h5", dirname(prefix_with_count))) > 0 || - length(glob(basename(prefix_with_count) * ".*.cdf", dirname(prefix_with_count))) > 0 - - push!(run_prefixes, prefix_with_count) - else - # No more output files found - break + if initial_electron + while true + # Test if output files exist for this value of counter + prefix_with_count = base_prefix * "_$counter" + if length(glob(basename(prefix_with_count) * ".initial_elctron*.h5", dirname(prefix_with_count))) > 0 || + length(glob(basename(prefix_with_count) * ".initial_elctron*.cdf", dirname(prefix_with_count))) > 0 + + push!(run_prefixes, prefix_with_count) + else + # No more output files found + break + end + counter += 1 + end + else + while true + # Test if output files exist for this value of counter + prefix_with_count = base_prefix * "_$counter" + if length(glob(basename(prefix_with_count) * ".dfns*.h5", dirname(prefix_with_count))) > 0 || + length(glob(basename(prefix_with_count) * ".dfns*.cdf", dirname(prefix_with_count))) > 0 || + length(glob(basename(prefix_with_count) * ".moments*.h5", dirname(prefix_with_count))) > 0 || + length(glob(basename(prefix_with_count) * ".moments*.cdf", dirname(prefix_with_count))) > 0 + + push!(run_prefixes, prefix_with_count) + else + # No more output files found + break + end + counter += 1 end - counter += 1 end # Add the final run which does not have a '_$counter' suffix push!(run_prefixes, base_prefix) @@ -2642,7 +3431,13 @@ function get_run_info_no_setup(run_dir::Union{AbstractString,Tuple{AbstractStrin error("Invalid restart_index=$restart_index") end - if dfns + if initial_electron + if electron_debug + ext = "electron_debug" + else + ext = "initial_electron" + end + elseif dfns ext = "dfns" else ext = "moments" @@ -2724,6 +3519,41 @@ function get_run_info_no_setup(run_dir::Union{AbstractString,Tuple{AbstractStrin # Get variable names just from the first restart, for simplicity variable_names = get_variable_keys(get_group(fids0[1], "dynamic_data")) + if initial_electron + evolving_variables = ("f_electron",) + else + evolving_variables = ["f"] + if evolve_density + push!(evolving_variables, "density") + end + if evolve_upar + push!(evolving_variables, "parallel_flow") + end + if evolve_ppar + push!(evolving_variables, "parallel_pressure") + end + if composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + push!(evolving_variables, "f_electron") + end + if composition.electron_physics ∈ (braginskii_fluid, kinetic_electrons, + kinetic_electrons_with_temperature_equation) + push!(evolving_variables, "electron_parallel_pressure") + end + if composition.n_neutral_species > 0 + push!(evolving_variables, "f_neutral") + if evolve_density + push!(evolving_variables, "density_neutral") + end + if evolve_upar + push!(evolving_variables, "uz_neutral") + end + if evolve_ppar + push!(evolving_variables, "pz_neutral") + end + end + evolving_variables = Tuple(evolving_variables) + end if parallel_io files = fids0 @@ -2753,7 +3583,8 @@ function get_run_info_no_setup(run_dir::Union{AbstractString,Tuple{AbstractStrin z_chunk_size=z_chunk_size, vperp_chunk_size=vperp_chunk_size, vpa_chunk_size=vpa_chunk_size, vzeta_chunk_size=vzeta_chunk_size, vr_chunk_size=vr_chunk_size, vz_chunk_size=vz_chunk_size, - variable_names=variable_names, dfns=dfns) + variable_names=variable_names, evolving_variables=evolving_variables, + dfns=dfns) return run_info end @@ -2967,6 +3798,15 @@ function postproc_load_variable(run_info, variable_name; it=nothing, is=nothing, end !isa(it, mk_int) && push!(dims, nt) result = allocate_float(dims...) + elseif nd == 5 + # electron distribution function variable with dimensions (vpa,vperp,z,r,t) + dims = Vector{mk_int}() + !isa(ivpa, mk_int) && push!(dims, nvpa) + !isa(ivperp, mk_int) && push!(dims, nvperp) + !isa(iz, mk_int) && push!(dims, nz) + !isa(ir, mk_int) && push!(dims, nr) + !isa(it, mk_int) && push!(dims, nt) + result = allocate_float(dims...) elseif nd == 6 # ion distribution function variable with dimensions (vpa,vperp,z,r,s,t) nspecies = size(variable[1], 5) @@ -2976,7 +3816,6 @@ function postproc_load_variable(run_info, variable_name; it=nothing, is=nothing, !isa(iz, mk_int) && push!(dims, nz) !isa(ir, mk_int) && push!(dims, nr) if is === (:) - nspecies = size(variable[1], 3) push!(dims, nspecies) elseif !isa(is, mk_int) push!(dims, nspecies) @@ -2993,7 +3832,6 @@ function postproc_load_variable(run_info, variable_name; it=nothing, is=nothing, !isa(iz, mk_int) && push!(dims, nz) !isa(ir, mk_int) && push!(dims, nr) if is === (:) - nspecies = size(variable[1], 3) push!(dims, nspecies) elseif !isa(is, mk_int) push!(dims, nspecies) @@ -3028,6 +3866,8 @@ function postproc_load_variable(run_info, variable_name; it=nothing, is=nothing, result .= v[iz,ir,tind] elseif nd == 4 result .= v[iz,ir,is,tind] + elseif nd == 5 + result .= v[ivpa,ivperp,iz,ir,tind] elseif nd == 6 result .= v[ivpa,ivperp,iz,ir,is,tind] elseif nd == 7 @@ -3062,6 +3902,8 @@ function postproc_load_variable(run_info, variable_name; it=nothing, is=nothing, selectdim(result, ndims(result), global_it_start:global_it_end) .= v[iz,ir,tinds] elseif nd == 4 selectdim(result, ndims(result), global_it_start:global_it_end) .= v[iz,ir,is,tinds] + elseif nd == 5 + selectdim(result, ndims(result), global_it_start:global_it_end) .= v[ivpa,ivperp,iz,ir,tinds] elseif nd == 6 selectdim(result, ndims(result), global_it_start:global_it_end) .= v[ivpa,ivperp,iz,ir,is,tinds] elseif nd == 7 @@ -3080,8 +3922,10 @@ function postproc_load_variable(run_info, variable_name; it=nothing, is=nothing, else # Use existing distributed I/O loading functions diagnostic_variable = false - if variable_name ∈ em_variables + if variable_name ∈ tuple(em_variables..., electron_moment_variables...) nd = 3 + elseif variable_name ∈ electron_dfn_variables + nd = 5 elseif variable_name ∈ ion_dfn_variables nd = 6 elseif variable_name ∈ neutral_dfn_variables @@ -3099,7 +3943,7 @@ function postproc_load_variable(run_info, variable_name; it=nothing, is=nothing, fid = open_readonly_output_file(run_info.files[1], run_info.ext, iblock=0) group = get_group(fid, "dynamic_data") result = load_variable(group, variable_name) - result = selectdim(result, ndims(result), global_it_start:global_it_end) + result = selectdim(result, ndims(result), it) elseif nd == 3 result = allocate_float(run_info.z.n, run_info.r.n, run_info.nt) read_distributed_zr_data!(result, variable_name, run_info.files, @@ -3117,6 +3961,13 @@ function postproc_load_variable(run_info, variable_name; it=nothing, is=nothing, run_info.ext, run_info.nblocks, run_info.z_local.n, run_info.r_local.n, run_info.itime_skip) result = result[iz,ir,is,it] + elseif nd === 5 + result = load_distributed_electron_pdf_slice(run_info.files, run_info.nblocks, + it, run_info.n_ion_species, + run_info.r_local, + run_info.z_local, run_info.vperp, + run_info.vpa; ir=ir, iz=iz, + ivperp=ivperp, ivpa=ivpa) elseif nd === 6 result = load_distributed_ion_pdf_slice(run_info.files, run_info.nblocks, it, run_info.n_ion_species, @@ -3201,6 +4052,30 @@ function get_variable(run_info, variable_name; normalize_advection_speed_shape=t return variable end + # Select a slice of an electron distribution function sized variable + function select_slice_of_variable(variable::AbstractArray{T,5} where T; it=nothing, + is=nothing, ir=nothing, iz=nothing, ivperp=nothing, + ivpa=nothing, ivzeta=nothing, ivr=nothing, + ivz=nothing) + if it !== nothing + variable = selectdim(variable, 5, kwargs[:it]) + end + if ir !== nothing + variable = selectdim(variable, 4, kwargs[:ir]) + end + if iz !== nothing + variable = selectdim(variable, 3, kwargs[:iz]) + end + if ivperp !== nothing + variable = selectdim(variable, 2, kwargs[:ivperp]) + end + if ivpa !== nothing + variable = selectdim(variable, 1, kwargs[:ivpa]) + end + + return variable + end + # Select a slice of a neutral distribution function sized variable function select_slice_of_variable(variable::AbstractArray{T,7} where T; it=nothing, is=nothing, ir=nothing, iz=nothing, ivperp=nothing, @@ -3253,14 +4128,72 @@ function get_variable(run_info, variable_name; normalize_advection_speed_shape=t end if variable_name == "temperature" - vth = postproc_load_variable(run_info, "thermal_speed"; kwargs...) + vth = get_variable(run_info, "thermal_speed"; kwargs...) variable = vth.^2 elseif variable_name == "collision_frequency_ii" - n = postproc_load_variable(run_info, "density"; kwargs...) - vth = postproc_load_variable(run_info, "thermal_speed"; kwargs...) + n = get_variable(run_info, "density"; kwargs...) + vth = get_variable(run_info, "thermal_speed"; kwargs...) variable = get_collision_frequency_ii(run_info.collisions, n, vth) + elseif variable_name == "collision_frequency_ee" + n = get_variable(run_info, "electron_density"; kwargs...) + vth = get_variable(run_info, "electron_thermal_speed"; kwargs...) + variable = get_collision_frequency_ee(run_info.collisions, n, vth) + elseif variable_name == "collision_frequency_ei" + n = get_variable(run_info, "electron_density"; kwargs...) + vth = get_variable(run_info, "electron_thermal_speed"; kwargs...) + variable = get_collision_frequency_ei(run_info.collisions, n, vth) + elseif variable_name == "electron_temperature" + vth = get_variable(run_info, "electron_thermal_speed"; kwargs...) + variable = run_info.composition.me_over_mi .* vth.^2 + elseif variable_name == "electron_dudz" + upar = get_variable(run_info, "electron_parallel_flow"; kwargs...) + variable = similar(upar) + if :iz ∈ keys(kwargs) && kwargs[:iz] !== nothing + error("Cannot take z-derivative when iz!==nothing") + end + if :ir ∈ keys(kwargs) && isa(kwargs[:ir], mk_int) + for it ∈ 1:size(variable, 2) + @views derivative!(variable[:,it], upar[:,it], run_info.z, run_info.z_spectral) + end + else + for it ∈ 1:size(variable, 3), ir ∈ 1:run_info.r.n + @views derivative!(variable[:,ir,it], upar[:,ir,it], run_info.z, run_info.z_spectral) + end + end + elseif variable_name == "electron_dpdz" + ppar = get_variable(run_info, "electron_parallel_pressure"; kwargs...) + variable = similar(ppar) + println("electron_dpdz kwargs ", kwargs) + if :iz ∈ keys(kwargs) && kwargs[:iz] !== nothing + error("Cannot take z-derivative when iz!==nothing") + end + if :ir ∈ keys(kwargs) && isa(kwargs[:ir], mk_int) + println("check range ", (kwargs[:it] === nothing ? (1:run_info.nt) : (1:length(kwargs[:it])))) + for it ∈ 1:size(variable, 2) + @views derivative!(variable[:,it], ppar[:,it], run_info.z, run_info.z_spectral) + end + else + for it ∈ 1:size(variable, 3), ir ∈ 1:run_info.r.n + @views derivative!(variable[:,ir,it], ppar[:,ir,it], run_info.z, run_info.z_spectral) + end + end + elseif variable_name == "electron_dqdz" + qpar = get_variable(run_info, "electron_parallel_heat_flux"; kwargs...) + variable = similar(qpar) + if :iz ∈ keys(kwargs) && kwargs[:iz] !== nothing + error("Cannot take z-derivative when iz!==nothing") + end + if :ir ∈ keys(kwargs) && isa(kwargs[:ir], mk_int) + for it ∈ 1:size(variable, 2) + @views derivative!(variable[:,it], qpar[:,it], run_info.z, run_info.z_spectral) + end + else + for it ∈ 1:size(variable, 3), ir ∈ 1:run_info.r.n + @views derivative!(variable[:,ir,it], qpar[:,ir,it], run_info.z, run_info.z_spectral) + end + end elseif variable_name == "temperature_neutral" - vth = postproc_load_variable(run_info, "thermal_speed_neutral"; kwargs...) + vth = get_variable(run_info, "thermal_speed_neutral"; kwargs...) variable = vth.^2 elseif variable_name == "sound_speed" T_e = run_info.composition.T_e @@ -3276,6 +4209,58 @@ function get_variable(run_info, variable_name; normalize_advection_speed_shape=t upar = get_variable(run_info, "parallel_flow"; kwargs...) cs = get_variable(run_info, "sound_speed"; kwargs...) variable = upar ./ cs + elseif variable_name == "total_energy" + if run_info.vperp.n > 1 + error("total_energy is so far only implemented for 1D1V case") + else + ppar = get_variable(run_info, "parallel_pressure"; kwargs...) + upar = get_variable(run_info, "parallel_flow"; kwargs...) + n = get_variable(run_info, "density"; kwargs...) + + variable = @. ppar + 0.5*n*upar^2 + end + elseif variable_name == "total_energy_neutral" + if run_info.vzeta.n > 1 || run_info.vr.n > 1 + error("total_energy_neutral is so far only implemented for 1D1V case") + else + ppar = get_variable(run_info, "pz_neutral"; kwargs...) + upar = get_variable(run_info, "uz_neutral"; kwargs...) + n = get_variable(run_info, "density_neutral"; kwargs...) + + # Factor of 3/2 in front of 1/2*n*vth^2*upar because this in 1V - would be 5/2 + # for 2V/3V cases. + variable = @. ppar + 0.5*n*upar^2 + end + elseif variable_name == "total_energy_flux" + if run_info.vperp.n > 1 + error("total_energy_flux is so far only implemented for 1D1V case") + else + qpar = get_variable(run_info, "parallel_heat_flux"; kwargs...) + vth = get_variable(run_info, "thermal_speed"; kwargs...) + upar = get_variable(run_info, "parallel_flow"; kwargs...) + n = get_variable(run_info, "density"; kwargs...) + + # Note factor of 0.5 in front of qpar because the definition of qpar (see e.g. + # `update_qpar_species!()`) is unconventional (i.e. missing a factor of 0.5). + # Factor of 3/2 in front of 1/2*n*vth^2*upar because this in 1V - would be 5/2 + # for 2V/3V cases. + variable = @. 0.5*qpar + 0.75*n*vth^2*upar + 0.5*n*upar^3 + end + elseif variable_name == "total_energy_flux_neutral" + if run_info.vzeta.n > 1 || run_info.vr.n > 1 + error("total_energy_flux_neutral is so far only implemented for 1D1V case") + else + qpar = get_variable(run_info, "qz_neutral"; kwargs...) + vth = get_variable(run_info, "thermal_speed_neutral"; kwargs...) + upar = get_variable(run_info, "uz_neutral"; kwargs...) + n = get_variable(run_info, "density_neutral"; kwargs...) + + # Note factor of 0.5 in front of qpar because the definition of qpar (see e.g. + # `update_qpar_species!()`) is unconventional (i.e. missing a factor of 0.5). + # Factor of 3/2 in front of 1/2*n*vth^2*upar because this in 1V - would be 5/2 + # for 2V/3V cases. + variable = @. 0.5*qpar + 0.75*n*vth^2*upar + 0.5*n*upar^3 + end elseif variable_name == "z_advect_speed" # update_speed_z!() requires all dimensions to be present, so do *not* pass kwargs # to get_variable() in this case. Instead select a slice of the result. @@ -3424,6 +4409,114 @@ function get_variable(run_info, variable_name; normalize_advection_speed_shape=t run_info.time[it], run_info.geometry) end + variable = speed + variable = select_slice_of_variable(variable; kwargs...) + elseif variable_name == "electron_z_advect_speed" + # update_speed_z!() requires all dimensions to be present, so do *not* pass kwargs + # to get_variable() in this case. Instead select a slice of the result. + upar = get_variable(run_info, "electron_parallel_flow") + vth = get_variable(run_info, "electron_thermal_speed") + nz, nr, nt = size(upar) + nvperp = run_info.vperp.n + nvpa = run_info.vpa.n + + speed = allocate_float(nz, nvpa, nvperp, nr, nt) + + setup_distributed_memory_MPI(1,1,1,1) + setup_loop_ranges!(0, 1; s=run_info.n_ion_species, sn=run_info.n_neutral_species, + r=nr, z=nz, + vperp=(run_info.vperp === nothing ? 1 : run_info.vperp.n), + vpa=(run_info.vpa === nothing ? 1 : run_info.vpa.n), + vzeta=(run_info.vzeta === nothing ? 1 : run_info.vzeta.n), + vr=(run_info.vr === nothing ? 1 : run_info.vr.n), + vz=(run_info.vz === nothing ? 1 : run_info.vz.n)) + for it ∈ 1:nt + begin_serial_region() + # Only need some struct with a 'speed' variable + advect = (speed=@view(speed[:,:,:,:,it]),) + @views update_electron_speed_z!(advect, upar[:,:,it], vth[:,:,it], + run_info.vpa.grid) + end + + # Horrible hack so that we can get the speed back without rearranging the + # dimensions, if we want that to pass it to a utility function from the main code + # (e.g. to calculate a CFL limit). + if normalize_advection_speed_shape + variable = allocate_float(nvpa, nvperp, nz, nr, nspecies, nt) + for it ∈ 1:nt, ir ∈ 1:nr, iz ∈ 1:nz, ivperp ∈ 1:nvperp, ivpa ∈ 1:nvpa + variable[ivpa,ivperp,iz,ir,it] = speed[iz,ivpa,ivperp,ir,it] + end + variable = select_slice_of_variable(variable; kwargs...) + else + variable = speed + if :it ∈ keys(kwargs) + variable = selectdim(variable, 5, kwargs[:it]) + end + if :ir ∈ keys(kwargs) + variable = selectdim(variable, 4, kwargs[:ir]) + end + if :ivperp ∈ keys(kwargs) + variable = selectdim(variable, 3, kwargs[:ivperp]) + end + if :ivpa ∈ keys(kwargs) + variable = selectdim(variable, 2, kwargs[:ivpa]) + end + if :iz ∈ keys(kwargs) + variable = selectdim(variable, 1, kwargs[:iz]) + end + end + elseif variable_name == "electron_vpa_advect_speed" + # update_speed_z!() requires all dimensions to be present, so do *not* pass kwargs + # to get_variable() in this case. Instead select a slice of the result. + density = get_variable(run_info, "electron_density") + upar = get_variable(run_info, "electron_parallel_flow") + ppar = get_variable(run_info, "electron_parallel_pressure") + vth = get_variable(run_info, "electron_thermal_speed") + dppar_dz = get_z_derivative(run_info, "electron_parallel_pressure") + dvth_dz = get_z_derivative(run_info, "electron_thermal_speed") + dqpar_dz = get_z_derivative(run_info, "electron_parallel_heat_flux") + if run_info.external_source_settings.electron.active + external_source_amplitude = get_variable(run_info, "external_source_electron_amplitude") + external_source_density_amplitude = get_variable(run_info, "external_source_electron_density_amplitude") + external_source_momentum_amplitude = get_variable(run_info, "external_source_electron_momentum_amplitude") + external_source_pressure_amplitude = get_variable(run_info, "external_source_electron_pressure_amplitude") + else + external_source_amplitude = zeros(0,0,run_info.nt) + external_source_density_amplitude = zeros(0,0,run_info.nt) + external_source_momentum_amplitude = zeros(0,0,run_info.nt) + external_source_pressure_amplitude = zeros(0,0,run_info.nt) + end + + nz, nr, nt = size(vth) + nvperp = run_info.vperp.n + nvpa = run_info.vpa.n + + speed=allocate_float(nvpa, nvperp, nz, nr, nt) + setup_distributed_memory_MPI(1,1,1,1) + setup_loop_ranges!(0, 1; s=run_info.n_ion_species, sn=run_info.n_neutral_species, + r=nr, z=nz, + vperp=(run_info.vperp === nothing ? 1 : run_info.vperp.n), + vpa=(run_info.vpa === nothing ? 1 : run_info.vpa.n), + vzeta=(run_info.vzeta === nothing ? 1 : run_info.vzeta.n), + vr=(run_info.vr === nothing ? 1 : run_info.vr.n), + vz=(run_info.vz === nothing ? 1 : run_info.vz.n)) + for it ∈ 1:nt + begin_serial_region() + # Only need some struct with a 'speed' variable + advect = (speed=@view(speed[:,:,:,:,it]),) + moments = (electron=(vth=vth[:,:,it], + dppar_dz=dppar_dz[:,:,it], + dqpar_dz=dqpar_dz[:,:,it], + dvth_dz=dvth_dz[:,:,it], + external_source_amplitude=external_source_amplitude[:,:,it], + external_source_density_amplitude=external_source_density_amplitude[:,:,it], + external_source_momentum_amplitude=external_source_momentum_amplitude[:,:,it], + external_source_pressure_amplitude=external_source_pressure_amplitude[:,:,it]),) + @views update_electron_speed_vpa!(advect, density[:,:,it], upar[:,:,it], + ppar[:,:,it], moments, run_info.vpa.grid, + run_info.external_source_settings.electron) + end + variable = speed variable = select_slice_of_variable(variable; kwargs...) elseif variable_name == "neutral_z_advect_speed" @@ -3595,6 +4688,39 @@ function get_variable(run_info, variable_name; normalize_advection_speed_shape=t # Don't want a meaningless Inf... variable[1] = 0.0 end + elseif variable_name == "electron_steps_per_ion_step" + electron_steps_per_output = get_variable(run_info, "electron_steps_per_output"; kwargs...) + ion_steps_per_output = get_variable(run_info, "steps_per_output"; kwargs...) + variable = electron_steps_per_output ./ ion_steps_per_output + elseif variable_name == "electron_steps_per_output" + variable = get_per_step_from_cumulative_variable(run_info, "electron_step_counter"; kwargs...) + elseif variable_name == "electron_failures_per_output" + variable = get_per_step_from_cumulative_variable(run_info, "electron_failure_counter"; kwargs...) + elseif variable_name == "electron_failure_caused_by_per_output" + variable = get_per_step_from_cumulative_variable(run_info, "electron_failure_caused_by"; kwargs...) + elseif variable_name == "electron_limit_caused_by_per_output" + variable = get_per_step_from_cumulative_variable(run_info, "electron_limit_caused_by"; kwargs...) + elseif variable_name == "electron_average_successful_dt" + electron_steps_per_output = get_variable(run_info, "electron_steps_per_output"; kwargs...) + electron_failures_per_output = get_variable(run_info, "electron_failures_per_output"; kwargs...) + electron_successful_steps_per_output = electron_steps_per_output - electron_failures_per_output + electron_pseudotime = get_variable("electron_cumulative_pseudotime"; kwargs...) + + delta_t = copy(electron_pseudotime) + for i ∈ length(delta_t):-1:2 + delta_t[i] -= delta_t[i-1] + end + + variable = delta_t ./ electron_successful_steps_per_output + for i ∈ eachindex(electron_successful_steps_per_output) + if electron_successful_steps_per_output[i] == 0 + variable[i] = 0.0 + end + end + if electron_successful_steps_per_output[1] == 0 + # Don't want a meaningless Inf... + variable[1] = 0.0 + end elseif variable_name == "CFL_ion_z" # update_speed_z!() requires all dimensions to be present, so do *not* pass kwargs # to get_variable() in this case. Instead select a slice of the result. @@ -3621,6 +4747,34 @@ function get_variable(run_info, variable_name; normalize_advection_speed_shape=t @views get_CFL!(CFL[:,:,:,:,:,it], speed[:,:,:,:,:,it], run_info.vpa) end + variable = CFL + variable = select_slice_of_variable(variable; kwargs...) + elseif variable_name == "CFL_electron_z" + # update_speed_z!() requires all dimensions to be present, so do *not* pass kwargs + # to get_variable() in this case. Instead select a slice of the result. + speed = get_variable(run_info, "electron_z_advect_speed"; + normalize_advection_speed_shape=false) + nz, nvpa, nvperp, nr, nt = size(speed) + CFL = similar(speed) + for it ∈ 1:nt + @views get_CFL!(CFL[:,:,:,:,it], speed[:,:,:,:,it], run_info.z) + end + + variable = allocate_float(nvpa, nvperp, nz, nr, nt) + for it ∈ 1:nt, ir ∈ 1:nr, iz ∈ 1:nz, ivperp ∈ 1:nvperp, ivpa ∈ 1:nvpa + variable[ivpa,ivperp,iz,ir,it] = CFL[iz,ivpa,ivperp,ir,it] + end + variable = select_slice_of_variable(variable; kwargs...) + elseif variable_name == "CFL_electron_vpa" + # update_speed_z!() requires all dimensions to be present, so do *not* pass kwargs + # to get_variable() in this case. Instead select a slice of the result. + speed = get_variable(run_info, "electron_vpa_advect_speed") + nt = size(speed, 5) + CFL = similar(speed) + for it ∈ 1:nt + @views get_CFL!(CFL[:,:,:,:,it], speed[:,:,:,:,it], run_info.vpa) + end + variable = CFL variable = select_slice_of_variable(variable; kwargs...) elseif variable_name == "CFL_neutral_z" @@ -3684,6 +4838,31 @@ function get_variable(run_info, variable_name; normalize_advection_speed_shape=t variable[it] = min_CFL end variable = select_slice_of_variable(variable; kwargs...) + elseif variable_name == "minimum_CFL_electron_z" + # update_speed_z!() requires all dimensions to be present, so do *not* pass kwargs + # to get_variable() in this case. Instead select a slice of the result. + speed = get_variable(run_info, "electron_z_advect_speed"; + normalize_advection_speed_shape=false) + nt = size(speed, 5) + variable = allocate_float(nt) + begin_serial_region() + for it ∈ 1:nt + min_CFL = get_minimum_CFL_z(@view(speed[:,:,:,:,it]), run_info.z) + variable[it] = min_CFL + end + variable = select_slice_of_variable(variable; kwargs...) + elseif variable_name == "minimum_CFL_electron_vpa" + # update_speed_z!() requires all dimensions to be present, so do *not* pass kwargs + # to get_variable() in this case. Instead select a slice of the result. + speed = get_variable(run_info, "electron_vpa_advect_speed") + nt = size(speed, 5) + variable = allocate_float(nt) + begin_serial_region() + for it ∈ 1:nt + min_CFL = get_minimum_CFL_vpa(@view(speed[:,:,:,:,it]), run_info.vpa) + variable[it] = min_CFL + end + variable = select_slice_of_variable(variable; kwargs...) elseif variable_name == "minimum_CFL_neutral_z" # update_speed_z!() requires all dimensions to be present, so do *not* pass kwargs # to get_variable() in this case. Instead select a slice of the result. @@ -3717,6 +4896,39 @@ function get_variable(run_info, variable_name; normalize_advection_speed_shape=t variable[it] = min_CFL end variable = select_slice_of_variable(variable; kwargs...) + elseif occursin("_timestep_error", variable_name) + prefix = split(variable_name, "_timestep_error")[1] + full_order = get_variable(run_info, prefix; kwargs...) + low_order = get_variable(run_info, prefix * "_loworder"; kwargs...) + variable = low_order .- full_order + elseif occursin("_timestep_residual", variable_name) + prefix = split(variable_name, "_timestep_residual")[1] + full_order = get_variable(run_info, prefix; kwargs...) + low_order = get_variable(run_info, prefix * "_loworder"; kwargs...) + if prefix == "pdf_electron" + rtol = run_info.input["electron_timestepping"]["rtol"] + atol = run_info.input["electron_timestepping"]["atol"] + else + rtol = run_info.input["timestepping"]["rtol"] + atol = run_info.input["timestepping"]["atol"] + end + variable = @. (low_order - full_order) / (rtol * abs(full_order) + atol) + elseif occursin("_steady_state_residual", variable_name) + prefix = split(variable_name, "_steady_state_residual")[1] + end_step = get_variable(run_info, prefix; kwargs...) + begin_step = get_variable(run_info, prefix * "_start_last_timestep"; kwargs...) + if prefix == "f_electron" + dt = get_variable(run_info, "electron_previous_dt"; kwargs...) + else + dt = get_variable(run_info, "previous_dt"; kwargs...) + end + dt = reshape(dt, ones(mk_int, ndims(end_step)-1)..., length(dt)) + for i ∈ eachindex(dt) + if dt[i] ≤ 0.0 + dt[i] = Inf + end + end + variable = (end_step .- begin_step) ./ dt elseif occursin("_nonlinear_iterations_per_solve", variable_name) prefix = split(variable_name, "_nonlinear_iterations_per_solve")[1] nl_nsolves = get_per_step_from_cumulative_variable( diff --git a/moment_kinetics/src/maxwell_diffusion.jl b/moment_kinetics/src/maxwell_diffusion.jl new file mode 100644 index 000000000..03b4daa96 --- /dev/null +++ b/moment_kinetics/src/maxwell_diffusion.jl @@ -0,0 +1,259 @@ +""" +Model Diffusion operator - this will act to push the distribution towards +Maxwellian, but purely based on velocity gradients. So need the second derivative of +the distribution function. Integration constants should not be a problem, i.e. pushing +the second derivatives of a Maxwellian and our function together should also move their +values towards each other, as the boundary conditions of both would be the same. + +This operator will mostly come into effect in places where there is ringing in the +distribution, which can be expected in the grid points near the walls. Fortunately this +region is also where the plasma is most collisional, so having such an operator is also +most valid here. +""" + +module maxwell_diffusion + +export setup_mxwl_diff_collisions_input, ion_vpa_maxwell_diffusion!, neutral_vz_maxwell_diffusion! + +using ..looping +using ..input_structs: mxwl_diff_collisions_input, set_defaults_and_check_section! +using ..calculus: second_derivative! +using ..reference_parameters: get_reference_collision_frequency_ii + +""" +Function for reading Maxwell diffusion operator input parameters. +Structure the namelist as follows. + +[maxwell_diffusion_collisions] +use_maxwell_diffusion = true +D_ii = 1.0 +diffusion_coefficient_option = "manual" +""" +function setup_mxwl_diff_collisions_input(toml_input::Dict, reference_params) + # get reference diffusion coefficient, made up of collision frequency and + # thermal speed for now. NOTE THAT THIS CONSTANT PRODUCES ERRORS. DO NOT USE + D_ii_mxwl_diff_default = get_reference_collision_frequency_ii(reference_params)# * + #2 * reference_params.Tref/reference_params.mref + D_nn_mxwl_diff_default = D_ii_mxwl_diff_default + # read the input toml and specify a sensible default + input_section = set_defaults_and_check_section!(toml_input, "maxwell_diffusion_collisions", + # begin default inputs (as kwargs) + use_maxwell_diffusion = false, + D_ii = -1.0, + D_nn = -1.0, + diffusion_coefficient_option = "reference_parameters") + + # ensure that the diffusion coefficient is consistent with the input option + diffusion_coefficient_option = input_section["diffusion_coefficient_option"] + if diffusion_coefficient_option == "reference_parameters" + input_section["D_ii"] = D_ii_mxwl_diff_default + input_section["D_nn"] = -1.0 #D_nn_mxwl_diff_default + elseif diffusion_coefficient_option == "manual" + # use the diffusion coefficient from the input file + # do nothing + else + error("Invalid option [maxwell_diffusion_collisions] " + * "diffusion_coefficient_option=$(diffusion_coefficient_option) passed") + end + # finally, ensure prefactor < 0 if use_maxwell_diffusion is false + # so that prefactor > 0 is the only check required in the rest of the code + if !input_section["use_maxwell_diffusion"] + input_section["D_ii"] = -1.0 + input_section["D_nn"] = -1.0 + end + input = Dict(Symbol(k)=>v for (k,v) in input_section) + + return mxwl_diff_collisions_input(; input...) +end + +""" +Calculate the Maxwellian associated with the current ion pdf moments, and then +subtract this from current pdf. Then take second derivative of this function +to act as the diffusion operator. +""" +function ion_vpa_maxwell_diffusion!(f_out, f_in, moments, vpa, vperp, spectral::T_spectral, + dt, diffusion_coefficient) where T_spectral + + # If negative input (should be -1.0), then none of this diffusion will happen. + # This number can be put in as some parameter in the input file called something + # like 'maxwellian_diffusion_coefficient' + if diffusion_coefficient <= 0.0 || vpa.n == 1 + return nothing + end + + if vperp.n > 1 && (moments.evolve_density || moments.evolve_upar || moments.evolve_ppar) + error("Maxwell diffusion not implemented for 2V moment-kinetic cases yet") + end + + # Otherwise, build the maxwellian function (which is going to be subtracted from + # the current distribution) using the moments of the distribution (so that the + # operator itself conserves the moments), and then this result will be the one + # whose second derivative will be added to the RHS (i.e. subtracted from the current) + begin_s_r_z_vperp_region() + + # In what follows, there are eight combinations of booleans (though not all have been + # fully implemented yet). In line with moment kinetics, the Maxwellian is normalised + # in the relevant ways: + # - density: normalise by n + # - upar: working in peculiar velocity space, so no upar subtraction from vpa + # - ppar: normalisation by vth, in 1D is 1/vth prefactor, and grid is normalised by vth, + # hence no 1/vth^2 term in the exponent. + if moments.evolve_density && moments.evolve_upar && moments.evolve_ppar + @loop_s_r_z_vperp is ir iz ivperp begin + @views @. vpa.scratch = f_in.pdf[:,ivperp,iz,ir,is] - + exp(-((vpa.grid[:])^2 + (vperp.grid[ivperp])^2) ) + second_derivative!(vpa.scratch2, vpa.scratch, vpa, spectral) + @views @. f_out[:,ivperp,iz,ir,is] += dt * diffusion_coefficient * vpa.scratch2 + end + elseif moments.evolve_density && moments.evolve_upar + @loop_s_r_z_vperp is ir iz ivperp begin + vth = moments.ion.vth[iz,ir,is] + @views @. vpa.scratch = f_in.pdf[:,ivperp,iz,ir,is] - + 1.0 / vth * exp(- ((vpa.grid[:])^2 + (vperp.grid[ivperp])^2)/(vth^2) ) + second_derivative!(vpa.scratch2, vpa.scratch, vpa, spectral) + @views @. f_out[:,ivperp,iz,ir,is] += dt * diffusion_coefficient * vpa.scratch2 + end + elseif moments.evolve_density && moments.evolve_ppar + @loop_s_r_z_vperp is ir iz ivperp begin + upar = f_in.upar[iz,ir,is] + @views @. vpa.scratch = f_in.pdf[:,ivperp,iz,ir,is] - + exp(- ((vpa.grid[:] - upar)^2 + (vperp.grid[ivperp])^2)) + second_derivative!(vpa.scratch2, vpa.scratch, vpa, spectral) + @views @. f_out[:,ivperp,iz,ir,is] += dt * diffusion_coefficient * vpa.scratch2 + end + elseif moments.evolve_upar && moments.evolve_ppar + @loop_s_r_z_vperp is ir iz ivperp begin + n = f_in.density[iz,ir,is] + @views @. vpa.scratch = f_in.pdf[:,ivperp,iz,ir,is] - + n * exp(- ((vpa.grid[:])^2 + (vperp.grid[ivperp])^2) ) + second_derivative!(vpa.scratch2, vpa.scratch, vpa, spectral) + @views @. f_out[:,ivperp,iz,ir,is] += dt * diffusion_coefficient * vpa.scratch2 + end + elseif moments.evolve_density + @loop_s_r_z_vperp is ir iz ivperp begin + vth = moments.ion.vth[iz,ir,is] + upar = f_in.upar[iz,ir,is] + @views @. vpa.scratch = f_in.pdf[:,ivperp,iz,ir,is] - + 1.0 / vth * exp(- ((vpa.grid[:] - upar)^2 + (vperp.grid[ivperp])^2)/(vth^2) ) + second_derivative!(vpa.scratch2, vpa.scratch, vpa, spectral) + @views @. f_out[:,ivperp,iz,ir,is] += dt * diffusion_coefficient * vpa.scratch2 + end + elseif moments.evolve_upar + @loop_s_r_z_vperp is ir iz ivperp begin + vth = moments.ion.vth[iz,ir,is] + n = f_in.density[iz,ir,is] + @views @. vpa.scratch = f_in.pdf[:,ivperp,iz,ir,is] - + n / vth * exp(- ((vpa.grid[:])^2 + (vperp.grid[ivperp])^2)/(vth^2) ) + second_derivative!(vpa.scratch2, vpa.scratch, vpa, spectral) + @views @. f_out[:,ivperp,iz,ir,is] += dt * diffusion_coefficient * vpa.scratch2 + end + elseif moments.evolve_ppar + @loop_s_r_z_vperp is ir iz ivperp begin + n = f_in.density[iz,ir,is] + upar = f_in.upar[iz,ir,is] + @views @. vpa.scratch = f_in.pdf[:,ivperp,iz,ir,is] - + n * exp(- ((vpa.grid[:] - upar)^2 + (vperp.grid[ivperp])^2) ) + second_derivative!(vpa.scratch2, vpa.scratch, vpa, spectral) + @views @. f_out[:,ivperp,iz,ir,is] += dt * diffusion_coefficient * vpa.scratch2 + end + else + # Drift kinetic version is the only one that currently can support 2V. + @loop_s_r_z_vperp is ir iz ivperp begin + n = f_in.density[iz,ir,is] + upar = f_in.upar[iz,ir,is] + vth = moments.ion.vth[iz,ir,is] + if vperp.n == 1 + vth_prefactor = 1.0 / vth + else + vth_prefactor = 1.0 / vth^3 + end + @views @. vpa.scratch = f_in.pdf[:,ivperp,iz,ir,is] - n * vth_prefactor * + exp(-( ((vpa.grid[:] - upar)^2) + (vperp.grid[ivperp])^2)/(vth^2) ) + second_derivative!(vpa.scratch2, vpa.scratch, vpa, spectral) + @views @. f_out[:,ivperp,iz,ir,is] += dt * diffusion_coefficient * vpa.scratch2 + end + end + return nothing +end + +""" +Calculate the Maxwellian associated with the current neutral pdf moments, and then +subtract this from current pdf. Then take second derivative of this function +to act as the diffusion operator. +""" +function neutral_vz_maxwell_diffusion!(f_out, f_in, moments, vzeta, vr, vz, spectral::T_spectral, + dt, diffusion_coefficient) where T_spectral + + # If negative input (should be -1.0), then none of this diffusion will happen. + # This number can be put in as some parameter in the input file called something + # like 'maxwellian_diffusion_coefficient' + if diffusion_coefficient <= 0.0 || vz.n == 1 + return nothing + end + + if vr.n > 1 && (moments.evolve_density || moments.evolve_upar || moments.evolve_ppar) + error("Maxwell diffusion not implemented for 2V moment-kinetic cases yet") + end + + # Otherwise, build the maxwellian function (which is going to be subtracted from + # the current distribution) using the moments of the distribution (so that the + # operator itself conserves the moments), and then this result will be the one + # whose second derivative will be added to the RHS (i.e. subtracted from the current pdf) + begin_sn_r_z_vzeta_vr_region() + + + if moments.evolve_ppar && moments.evolve_upar + # See similar comments in krook_collisions! function. + @loop_sn_r_z_vzeta_vr isn ir iz ivzeta ivr begin + @views @. vz.scratch = f_in.pdf_neutral[:,ivr,ivzeta,iz,ir,isn] - + exp(-((vz.grid[:])^2 + (vr.grid[ivr])^2 + (vzeta.grid[ivzeta])^2) ) + second_derivative!(vz.scratch2, vz.scratch, vz, spectral) + @views @. f_out[:,ivr,ivzeta,iz,ir,isn] += dt * diffusion_coefficient * vz.scratch2 + end + elseif moments.evolve_ppar + @loop_sn_r_z_vzeta_vr isn ir iz ivzeta ivr begin + vth = moments.neutral.vth[iz,ir,isn] + uz = f_in.uz_neutral[iz,ir,isn] + @views @. vz.scratch = f_in.pdf_neutral[:,ivr,ivzeta,iz,ir,isn] - + exp(- ((vz.grid[:] - uz)^2 + (vr.grid[ivr])^2 + (vzeta.grid[ivzeta])^2)/(vth^2) ) + second_derivative!(vz.scratch2, vz.scratch, vz, spectral) + @views @. f_out[:,ivr,ivzeta,iz,ir,isn] += dt * diffusion_coefficient * vz.scratch2 + end + elseif moments.evolve_upar + @loop_sn_r_z_vzeta_vr isn ir iz ivzeta ivr begin + vth = moments.neutral.vth[iz,ir,isn] + @views @. vz.scratch = f_in.pdf_neutral[:,ivr,ivzeta,iz,ir,isn] - + 1.0 / vth * exp(- ((vz.grid[:])^2 + (vr.grid[ivr])^2 + (vzeta.grid[ivzeta])^2)/(vth^2) ) + second_derivative!(vz.scratch2, vz.scratch, vz, spectral) + @views @. f_out[:,ivr,ivzeta,iz,ir,isn] += dt * diffusion_coefficient * vz.scratch2 + end + elseif moments.evolve_density + @loop_sn_r_z_vzeta_vr isn ir iz ivzeta ivr begin + vth = moments.neutral.vth[iz,ir,isn] + uz = f_in.uz_neutral[iz,ir,isn] + @views @. vz.scratch = f_in.pdf_neutral[:,ivr,ivzeta,iz,ir,isn] - + 1.0 / vth * exp(- ((vz.grid[:] - uz)^2 + + (vr.grid[ivr])^2 + (vzeta.grid[ivzeta])^2)/(vth^2) ) + second_derivative!(vz.scratch2, vz.scratch, vz, spectral) + @views @. f_out[:,ivr,ivzeta,iz,ir,isn] += dt * diffusion_coefficient * vz.scratch2 + end + else + @loop_sn_r_z_vzeta_vr isn ir iz ivzeta ivr begin + n = f_in.density_neutral[iz,ir,isn] + uz = f_in.uz_neutral[iz,ir,isn] + vth = moments.neutral.vth[iz,ir,isn] + if vr.n == 1 && vzeta.n == 1 + vth_prefactor = 1.0 / vth + else + vth_prefactor = 1.0 / vth^3 + end + @views @. vz.scratch = f_in.pdf_neutral[:,ivr,ivzeta,iz,ir,isn] - n * vth_prefactor * + exp(-( (vz.grid[:] - uz)^2 + (vr.grid[ivr])^2 + (vzeta.grid[ivzeta])^2)/(vth^2) ) + second_derivative!(vz.scratch2, vz.scratch, vz, spectral) + @views @. f_out[:,ivr,ivzeta,iz,ir,isn] += dt * diffusion_coefficient * vz.scratch2 + end + end + return nothing +end + +end # maxwell_diffusion \ No newline at end of file diff --git a/moment_kinetics/src/moment_kinetics.jl b/moment_kinetics/src/moment_kinetics.jl index 90fd7f1bd..3712ba994 100644 --- a/moment_kinetics/src/moment_kinetics.jl +++ b/moment_kinetics/src/moment_kinetics.jl @@ -39,6 +39,7 @@ include("geo.jl") include("gyroaverages.jl") include("velocity_moments.jl") include("velocity_grid_transforms.jl") +include("electron_fluid_equations.jl") include("em_fields.jl") include("bgk.jl") include("manufactured_solns.jl") # MRH Here? @@ -53,12 +54,15 @@ include("vpa_advection.jl") include("z_advection.jl") include("r_advection.jl") include("vperp_advection.jl") +include("electron_z_advection.jl") +include("electron_vpa_advection.jl") include("neutral_r_advection.jl") include("neutral_z_advection.jl") include("neutral_vz_advection.jl") include("charge_exchange.jl") include("ionization.jl") include("krook_collisions.jl") +include("maxwell_diffusion.jl") include("continuity.jl") include("energy_equation.jl") include("force_balance.jl") @@ -68,6 +72,7 @@ include("moment_kinetics_input.jl") include("utils.jl") include("load_data.jl") include("analysis.jl") +include("electron_kinetic_equation.jl") include("initial_conditions.jl") include("parameter_scans.jl") include("time_advance.jl") @@ -97,7 +102,6 @@ using .type_definitions: mk_int using .utils: to_minutes, get_default_restart_filename, get_prefix_iblock_and_move_existing_file using .em_fields: setup_em_fields -using .time_advance: setup_dummy_and_buffer_arrays using .time_advance: allocate_advection_structs @debug_detect_redundant_block_synchronize using ..communication: debug_detect_redundant_is_active @@ -260,31 +264,31 @@ function setup_moment_kinetics(input_dict::AbstractDict; pdf, moments, boundary_distributions = allocate_pdf_and_moments(composition, r, z, vperp, vpa, vzeta, vr, vz, evolve_moments, collisions, external_source_settings, - num_diss_params) + num_diss_params, t_input) # create structs containing the information needed to treat advection in z, r, vpa, vperp, and vz # for ions, electrons and neutrals # NB: the returned advection_structs are yet to be initialized advection_structs = allocate_advection_structs(composition, z, r, vpa, vperp, vz, vr, vzeta) - # setup dummy arrays & buffer arrays for z r MPI - n_neutral_species_alloc = max(1, composition.n_neutral_species) - scratch_dummy = setup_dummy_and_buffer_arrays(r.n, z.n, vpa.n, vperp.n, vz.n, vr.n, vzeta.n, - composition.n_ion_species, n_neutral_species_alloc) - if restart === false restarting = false # initialize f(z,vpa) and the lowest three v-space moments (density(z), upar(z) and ppar(z)), # each of which may be evolved separately depending on input choices. - init_pdf_and_moments!(pdf, moments, boundary_distributions, geometry, + init_pdf_and_moments!(pdf, moments, fields, boundary_distributions, geometry, composition, r, z, vperp, vpa, vzeta, vr, vz, - vpa_spectral, vz_spectral, species, - external_source_settings, manufactured_solns_input) + z_spectral, r_spectral, vperp_spectral, vpa_spectral, + vz_spectral, species, collisions, external_source_settings, + manufactured_solns_input, t_input, num_diss_params, + advection_structs, io_input, input_dict) # initialize time variable code_time = 0. dt = nothing dt_before_last_fail = nothing + electron_dt = nothing + electron_dt_before_last_fail = nothing previous_runs_info = nothing + restart_electron_physics = nothing else restarting = true @@ -294,16 +298,32 @@ function setup_moment_kinetics(input_dict::AbstractDict; restart_filename = restart end - backup_prefix_iblock = get_prefix_iblock_and_move_existing_file(restart_filename, - io_input.output_dir) + backup_prefix_iblock, _, _ = + get_prefix_iblock_and_move_existing_file(restart_filename, + io_input.output_dir) # Reload pdf and moments from an existing output file - code_time, dt, dt_before_last_fail, previous_runs_info, restart_time_index = - reload_evolving_fields!(pdf, moments, boundary_distributions, + code_time, dt, dt_before_last_fail, electron_dt, electron_dt_before_last_fail, + previous_runs_info, restart_time_index, restart_electron_physics = + reload_evolving_fields!(pdf, moments, fields, boundary_distributions, backup_prefix_iblock, restart_time_index, composition, geometry, r, z, vpa, vperp, vzeta, vr, vz) + begin_serial_region() + @serial_region begin + @. moments.electron.temp = composition.me_over_mi * moments.electron.vth^2 + @. moments.electron.ppar = 0.5 * moments.electron.dens * moments.electron.temp + end + if composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + begin_r_z_vperp_vpa_region() + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + pdf.electron.pdf_before_ion_timestep[ivpa,ivperp,iz,ir] = + pdf.electron.norm[ivpa,ivperp,iz,ir] + end + end + # Re-initialize the source amplitude here instead of loading it from the restart # file so that we can change the settings between restarts. initialize_external_source_amplitude!(moments, external_source_settings, vperp, @@ -319,14 +339,16 @@ function setup_moment_kinetics(input_dict::AbstractDict; # create arrays and do other work needed to setup # the main time advance loop -- including normalisation of f by density if requested - moments, spectral_objects, scratch, scratch_implicit, advance, advance_implicit, - t_params, fp_arrays, gyroavs, manufactured_source_list, nl_solver_params = + moments, spectral_objects, scratch, scratch_implicit, scratch_electron, scratch_dummy, + advance, advance_implicit, t_params, fp_arrays, gyroavs, manufactured_source_list, + nl_solver_params = setup_time_advance!(pdf, fields, vz, vr, vzeta, vpa, vperp, z, r, gyrophase, vz_spectral, vr_spectral, vzeta_spectral, vpa_spectral, vperp_spectral, z_spectral, r_spectral, composition, moments, t_input, code_time, dt, - dt_before_last_fail, collisions, species, geometry, boundary_distributions, - external_source_settings, num_diss_params, manufactured_solns_input, - advection_structs, scratch_dummy, restarting, input_dict) + dt_before_last_fail, electron_dt, electron_dt_before_last_fail, collisions, + species, geometry, boundary_distributions, external_source_settings, + num_diss_params, manufactured_solns_input, advection_structs, io_input, + restarting, restart_electron_physics, input_dict) # This is the closest we can get to the end time of the setup before writing it to the # output file @@ -339,25 +361,28 @@ function setup_moment_kinetics(input_dict::AbstractDict; restart_time_index, previous_runs_info, time_for_setup, t_params, nl_solver_params) # write initial data to ascii files - write_data_to_ascii(pdf, moments, fields, vpa, vperp, z, r, code_time, + write_data_to_ascii(pdf, moments, fields, vpa, vperp, z, r, t_params.t[], composition.n_ion_species, composition.n_neutral_species, ascii_io) # write initial data to binary files - write_all_moments_data_to_binary(moments, fields, code_time, - composition.n_ion_species, composition.n_neutral_species, io_moments, 1, 0.0, + t_params.moments_output_counter[] += 1 + write_all_moments_data_to_binary(scratch, moments, fields, composition.n_ion_species, + composition.n_neutral_species, io_moments, t_params.moments_output_counter[], 0.0, t_params, nl_solver_params, r, z) - write_all_dfns_data_to_binary(pdf, moments, fields, code_time, - composition.n_ion_species, composition.n_neutral_species, io_dfns, 1, 0.0, - t_params, nl_solver_params, r, z, vperp, vpa, vzeta, vr, vz) + t_params.dfns_output_counter[] += 1 + write_all_dfns_data_to_binary(scratch, scratch_electron, moments, fields, + composition.n_ion_species, composition.n_neutral_species, io_dfns, + t_params.dfns_output_counter[], 0.0, t_params, nl_solver_params, r, z, vperp, vpa, + vzeta, vr, vz) begin_s_r_z_vperp_region() - return pdf, scratch, scratch_implicit, code_time, t_params, vz, vr, vzeta, vpa, vperp, - gyrophase, z, r, moments, fields, spectral_objects, advection_structs, - composition, collisions, geometry, gyroavs, boundary_distributions, - external_source_settings, num_diss_params, nl_solver_params, advance, - advance_implicit, fp_arrays, scratch_dummy, manufactured_source_list, ascii_io, - io_moments, io_dfns + return pdf, scratch, scratch_implicit, scratch_electron, t_params, vz, vr, + vzeta, vpa, vperp, gyrophase, z, r, moments, fields, spectral_objects, + advection_structs, composition, collisions, geometry, gyroavs, + boundary_distributions, external_source_settings, num_diss_params, + nl_solver_params, advance, advance_implicit, fp_arrays, scratch_dummy, + manufactured_source_list, ascii_io, io_moments, io_dfns end """ diff --git a/moment_kinetics/src/moment_kinetics_input.jl b/moment_kinetics/src/moment_kinetics_input.jl index 82b8b00fb..57c1c8205 100644 --- a/moment_kinetics/src/moment_kinetics_input.jl +++ b/moment_kinetics/src/moment_kinetics_input.jl @@ -15,6 +15,7 @@ using ..coordinates: define_coordinate using ..external_sources using ..file_io: io_has_parallel, input_option_error, open_ascii_output_file using ..krook_collisions: setup_krook_collisions_input +using ..maxwell_diffusion: setup_mxwl_diff_collisions_input using ..fokker_planck: setup_fkpl_collisions_input using ..finite_differences: fd_check_option using ..input_structs @@ -23,7 +24,6 @@ using ..reference_parameters using ..geo: init_magnetic_geometry, setup_geometry_input using MPI -using Quadmath using TOML using UUIDs @@ -84,6 +84,7 @@ function mk_input(scan_input=Dict(); save_inputs_to_txt=false, ignore_MPI=true) # this is the directory where the simulation data will be stored base_directory = get(scan_input, "base_directory", "runs") output_dir = joinpath(base_directory, run_name) + # if evolve_moments.density = true, evolve density via continuity eqn # and g = f/n via modified drift kinetic equation evolve_moments.density = get(scan_input, "evolve_moments_density", false) @@ -103,7 +104,7 @@ function mk_input(scan_input=Dict(); save_inputs_to_txt=false, ignore_MPI=true) # if false use true Knudsen cosine for neutral wall bc composition.use_test_neutral_wall_pdf = get(scan_input, "use_test_neutral_wall_pdf", false) # constant to be used to test nonzero Er in wall boundary condition - composition.Er_constant = get(scan_input, "Er_constant", 0.0) + #composition.Er_constant = get(scan_input, "Er_constant", 0.0) # The ion flux reaching the wall that is recycled as neutrals is reduced by # `recycling_fraction` to account for ions absorbed by the wall. composition.recycling_fraction = get(scan_input, "recycling_fraction", 1.0) @@ -118,6 +119,9 @@ function mk_input(scan_input=Dict(); save_inputs_to_txt=false, ignore_MPI=true) # normalised values used in the code. reference_params = setup_reference_parameters(scan_input) + # Set me_over_mi here so we can use reference_params + composition.me_over_mi = reference_params.me / reference_params.mref + ## set geometry_input geometry_in = setup_geometry_input(scan_input, get_default_rhostar(reference_params)) @@ -180,14 +184,28 @@ function mk_input(scan_input=Dict(); save_inputs_to_txt=false, ignore_MPI=true) end #################### end specification of species inputs ##################### + # Build the main collisions struct using scan_input and reference parameters charge_exchange = get(scan_input, "charge_exchange_frequency", 2.0*sqrt(species.ion[1].initial_temperature)) + charge_exchange_electron = get(scan_input, "electron_charge_exchange_frequency", 0.0) ionization = get(scan_input, "ionization_frequency", charge_exchange) + ionization_electron = get(scan_input, "electron_ionization_frequency", ionization) + ionization_energy = get(scan_input, "ionization_energy", 0.0) constant_ionization_rate = get(scan_input, "constant_ionization_rate", false) + nu_ei = get(scan_input, "nu_ei", 0.0) # set up krook collision inputs krook_input = setup_krook_collisions_input(scan_input, reference_params) - # set up krook collision inputs + # set up Fokker-Planck collision inputs fkpl_input = setup_fkpl_collisions_input(scan_input, reference_params) - collisions = collisions_input(charge_exchange, ionization, constant_ionization_rate, krook_input, fkpl_input) + # set up maxwell diffusion collision inputs + mxwl_diff_input = setup_mxwl_diff_collisions_input(scan_input, reference_params) + # write total collision struct using the structs above, as each setup function + # for the collisions outputs itself a struct of the type of collision, which + # is a substruct of the overall collisions_input struct. + collisions = collisions_input(charge_exchange, charge_exchange_electron, ionization, + ionization_electron, ionization_energy, + constant_ionization_rate, + nu_ei, krook_input, + fkpl_input, mxwl_diff_input) # parameters related to the time stepping timestepping_section = set_defaults_and_check_section!( @@ -211,9 +229,14 @@ function mk_input(scan_input=Dict(); save_inputs_to_txt=false, ignore_MPI=true) last_fail_proximity_factor=1.05, minimum_dt=0.0, maximum_dt=Inf, - implicit_ion_advance=true, + implicit_braginskii_conduction=true, + implicit_electron_advance=true, + implicit_ion_advance=false, implicit_vpa_advection=false, + implicit_electron_ppar=false, write_after_fixed_step_count=false, + write_error_diagnostics=false, + write_steady_state_diagnostics=false, high_precision_error_sum=false, ) if timestepping_section["nwrite"] > timestepping_section["nstep"] @@ -227,6 +250,95 @@ function mk_input(scan_input=Dict(); save_inputs_to_txt=false, ignore_MPI=true) if timestepping_section["atol_upar"] === nothing timestepping_section["atol_upar"] = 1.0e-2 * timestepping_section["rtol"] end + + # parameters related to electron time stepping + electron_timestepping_section = set_defaults_and_check_section!( + scan_input, "electron_timestepping"; + nstep=50000, + dt=timestepping_section["dt"] * sqrt(composition.me_over_mi), + CFL_prefactor=timestepping_section["CFL_prefactor"], + nwrite=nothing, + nwrite_dfns=nothing, + type=timestepping_section["type"], + split_operators=false, + converged_residual_value=1.0e-3, + rtol=timestepping_section["rtol"], + atol=timestepping_section["atol"], + step_update_prefactor=timestepping_section["step_update_prefactor"], + max_increase_factor=timestepping_section["max_increase_factor"], + max_increase_factor_near_last_fail=timestepping_section["max_increase_factor_near_last_fail"], + last_fail_proximity_factor=timestepping_section["last_fail_proximity_factor"], + minimum_dt=timestepping_section["minimum_dt"] * sqrt(composition.me_over_mi), + maximum_dt=timestepping_section["maximum_dt"] * sqrt(composition.me_over_mi), + write_after_fixed_step_count=false, + write_error_diagnostics=false, + write_steady_state_diagnostics=false, + high_precision_error_sum=timestepping_section["high_precision_error_sum"], + initialization_residual_value=1.0, + no_restart=false, + debug_io=false, + ) + if electron_timestepping_section["nwrite"] === nothing + electron_timestepping_section["nwrite"] = electron_timestepping_section["nstep"] + elseif electron_timestepping_section["nwrite"] > electron_timestepping_section["nstep"] + electron_timestepping_section["nwrite"] = electron_timestepping_section["nstep"] + end + if electron_timestepping_section["nwrite_dfns"] === nothing + electron_timestepping_section["nwrite_dfns"] = electron_timestepping_section["nstep"] + elseif electron_timestepping_section["nwrite_dfns"] > electron_timestepping_section["nstep"] + electron_timestepping_section["nwrite_dfns"] = electron_timestepping_section["nstep"] + end + # Make a copy because "stopfile_name" is not a separate input for the electrons, so we + # do not want to add a value to the `input_dict`. We also add a few dummy inputs that + # are not actually used for electrons. + electron_timestepping_section = copy(electron_timestepping_section) + electron_timestepping_section["stopfile_name"] = timestepping_section["stopfile_name"] + electron_timestepping_section["atol_upar"] = NaN + electron_timestepping_section["steady_state_residual"] = true + if !(0.0 < electron_timestepping_section["step_update_prefactor"] < 1.0) + error("[electron_timestepping] step_update_prefactor=" + * "$(electron_timestepping_section["step_update_prefactor"]) must be between " + * "0.0 and 1.0.") + end + if electron_timestepping_section["max_increase_factor"] ≤ 1.0 + error("[electron_timestepping] max_increase_factor=" + * "$(electron_timestepping_section["max_increase_factor"]) must be greater than " + * "1.0.") + end + if electron_timestepping_section["max_increase_factor_near_last_fail"] ≤ 1.0 + error("[electron_timestepping] max_increase_factor_near_last_fail=" + * "$(electron_timestepping_section["max_increase_factor_near_last_fail"]) must " + * "be greater than 1.0.") + end + if !isinf(electron_timestepping_section["max_increase_factor_near_last_fail"]) && + electron_timestepping_section["max_increase_factor_near_last_fail"] > electron_timestepping_section["max_increase_factor"] + error("[electron_timestepping] max_increase_factor_near_last_fail=" + * "$(electron_timestepping_section["max_increase_factor_near_last_fail"]) should be " + * "less than max_increase_factor=" + * "$(electron_timestepping_section["max_increase_factor"]).") + end + if electron_timestepping_section["last_fail_proximity_factor"] ≤ 1.0 + error("[electron_timestepping] last_fail_proximity_factor=" + * "$(electron_timestepping_section["last_fail_proximity_factor"]) must be " + * "greater than 1.0.") + end + if electron_timestepping_section["minimum_dt"] > electron_timestepping_section["maximum_dt"] + error("[electron_timestepping] minimum_dt=" + * "$(electron_timestepping_section["minimum_dt"]) must be less than " + * "maximum_dt=$(electron_timestepping_section["maximum_dt"])") + end + if electron_timestepping_section["maximum_dt"] ≤ 0.0 + error("[electron_timestepping] maximum_dt=" + * "$(electron_timestepping_section["maximum_dt"]) must be positive") + end + + # Make a copy of `timestepping_section` here as we do not want to add + # `electron_timestepping_section` to the `input_dict` because there is already an + # "electron_timestepping" section containing the input info - we only want to put + # `electron_timestepping_section` into the Dict that is used to make + # `timestepping_input`, so that it becomes part of `timestepping_input`. + timestepping_section = copy(timestepping_section) + timestepping_section["electron_t_input"] = electron_timestepping_section if !(0.0 < timestepping_section["step_update_prefactor"] < 1.0) error("step_update_prefactor=$(timestepping_section["step_update_prefactor"]) must " * "be between 0.0 and 1.0.") @@ -365,7 +477,7 @@ function mk_input(scan_input=Dict(); save_inputs_to_txt=false, ignore_MPI=true) # supported options are "finite_difference_vperp" "chebyshev_pseudospectral" vperp.discretization = get(scan_input, "vperp_discretization", "chebyshev_pseudospectral") vperp.element_spacing_option = get(scan_input, "vperp_element_spacing_option", "uniform") - + # overwrite some default parameters related to the gyrophase grid # ngrid is the number of grid points per element gyrophase.ngrid = get(scan_input, "gyrophase_ngrid", 17) @@ -392,7 +504,7 @@ function mk_input(scan_input=Dict(); save_inputs_to_txt=false, ignore_MPI=true) # supported options are "chebyshev_pseudospectral" and "finite_difference" vz.discretization = get(scan_input, "vz_discretization", vpa.discretization) vz.element_spacing_option = get(scan_input, "vz_element_spacing_option", "uniform") - + # overwrite some default parameters related to the vr grid # ngrid is the number of grid points per element vr.ngrid = get(scan_input, "vr_ngrid", 1) @@ -481,14 +593,6 @@ function mk_input(scan_input=Dict(); save_inputs_to_txt=false, ignore_MPI=true) _block_synchronize() end - # Create output_dir if it does not exist. - if !ignore_MPI - if global_rank[] == 0 - mkpath(output_dir) - end - _block_synchronize() - end - # replace mutable structures with immutable ones to optimize performance # and avoid possible misunderstandings z_advection_immutable = advection_input(z.advection.option, z.advection.constant_speed, @@ -550,7 +654,7 @@ function mk_input(scan_input=Dict(); save_inputs_to_txt=false, ignore_MPI=true) species.ion[is].vpa_IC.upar_amplitude, species.ion[is].vpa_IC.upar_phase, species.ion[is].vpa_IC.temperature_amplitude, species.ion[is].vpa_IC.temperature_phase, species.ion[is].vpa_IC.monomial_degree, - get(scan_input, "vpa_IC_v0_$is", 0.5*sqrt(vperp.L^2 + (0.5*vpa.L)^2)), + get(scan_input, "vpa_IC_v0$is", 0.5*sqrt(vperp.L^2 + (0.5*vpa.L)^2)), get(scan_input, "vpa_IC_vth0$is", 0.1*sqrt(vperp.L^2 + (0.5*vpa.L)^2)), get(scan_input, "vpa_IC_vpa0$is", 0.25*0.5*abs(vpa.L)), get(scan_input, "vpa_IC_vperp0$is", 0.5*abs(vperp.L))) @@ -586,18 +690,44 @@ function mk_input(scan_input=Dict(); save_inputs_to_txt=false, ignore_MPI=true) species.neutral[is].initial_density, z_IC, r_IC, vpa_IC) end end - species_immutable = (ion = species_ion_immutable, neutral = species_neutral_immutable) + z_IC = initial_condition_input(species.electron.z_IC.initialization_option, + species.electron.z_IC.width, species.electron.z_IC.wavenumber, + species.electron.z_IC.density_amplitude, species.electron.z_IC.density_phase, + species.electron.z_IC.upar_amplitude, species.electron.z_IC.upar_phase, + species.electron.z_IC.temperature_amplitude, species.electron.z_IC.temperature_phase, + species.electron.z_IC.monomial_degree, 0.0, 0.0, 0.0, 0.0) + r_IC = initial_condition_input(species.electron.r_IC.initialization_option, + species.electron.r_IC.width, species.electron.r_IC.wavenumber, + species.electron.r_IC.density_amplitude, species.electron.r_IC.density_phase, + species.electron.r_IC.upar_amplitude, species.electron.r_IC.upar_phase, + species.electron.r_IC.temperature_amplitude, species.electron.r_IC.temperature_phase, + species.electron.r_IC.monomial_degree, 0.0, 0.0, 0.0, 0.0) + vpa_IC = initial_condition_input(species.electron.vpa_IC.initialization_option, + species.electron.vpa_IC.width, species.electron.vpa_IC.wavenumber, + species.electron.vpa_IC.density_amplitude, species.electron.vpa_IC.density_phase, + species.electron.vpa_IC.upar_amplitude, species.electron.vpa_IC.upar_phase, + species.electron.vpa_IC.temperature_amplitude, + species.electron.vpa_IC.temperature_phase, + species.electron.vpa_IC.monomial_degree, 0.0, 0.0, 0.0, 0.0) + species_electron_immutable = species_parameters("electron", species.electron.initial_temperature, + species.electron.initial_density, z_IC, r_IC, vpa_IC) + species_immutable = (ion = species_ion_immutable, electron = species_electron_immutable, neutral = species_neutral_immutable) force_Er_zero = get(scan_input, "force_Er_zero_at_wall", false) drive_immutable = drive_input(drive.force_phi, drive.amplitude, drive.frequency, force_Er_zero) # inputs for file I/O + io_settings = set_defaults_and_check_section!( + scan_input, "output"; + ascii_output=false, + binary_format=hdf5, + parallel_io=nothing, + ) + if io_settings["parallel_io"] === nothing + io_settings["parallel_io"] = io_has_parallel(Val(io_settings["binary_format"])) + end # Make copy of the section to avoid modifying the passed-in Dict - io_settings = copy(get(scan_input, "output", Dict{String,Any}())) - io_settings["ascii_output"] = get(io_settings, "ascii_output", false) - io_settings["binary_format"] = get(io_settings, "binary_format", hdf5) - io_settings["parallel_io"] = get(io_settings, "parallel_io", - io_has_parallel(Val(io_settings["binary_format"]))) + io_settings = copy(io_settings) run_id = string(uuid4()) if !ignore_MPI # Communicate run_id to all blocks @@ -607,8 +737,13 @@ function mk_input(scan_input=Dict(); save_inputs_to_txt=false, ignore_MPI=true) run_id = string(run_id_chars...) end io_settings["run_id"] = run_id - io_immutable = io_input(; output_dir=output_dir, run_name=run_name, - Dict(Symbol(k)=>v for (k,v) in io_settings)...) + io_settings["output_dir"] = output_dir + io_settings["run_name"] = run_name + io_settings["write_error_diagnostics"] = timestepping_section["write_error_diagnostics"] + io_settings["write_steady_state_diagnostics"] = timestepping_section["write_steady_state_diagnostics"] + io_settings["write_electron_error_diagnostics"] = timestepping_section["electron_t_input"]["write_error_diagnostics"] + io_settings["write_electron_steady_state_diagnostics"] = timestepping_section["electron_t_input"]["write_steady_state_diagnostics"] + io_immutable = Dict_to_NamedTuple(io_settings) # initialize z grid and write grid point locations to file if ignore_MPI @@ -647,7 +782,8 @@ function mk_input(scan_input=Dict(); save_inputs_to_txt=false, ignore_MPI=true) run_directory=run_directory, ignore_MPI=ignore_MPI) - external_source_settings = setup_external_sources!(scan_input, r, z) + external_source_settings = setup_external_sources!(scan_input, r, z, + composition.electron_physics) if global_rank[] == 0 && save_inputs_to_txt # Make file to log some information about inputs into. @@ -1007,7 +1143,7 @@ function load_defaults(n_ion_species, n_neutral_species, electron_physics) if electron_physics ∈ (boltzmann_electron_response, boltzmann_electron_response_with_simple_sheath) n_species = n_ion_species + n_neutral_species else - n_species = n_ion_speces + n_neutral_species + 1 + n_species = n_ion_species + n_neutral_species + 1 end use_test_neutral_wall_pdf = false # electron temperature over reference temperature @@ -1016,18 +1152,17 @@ function load_defaults(n_ion_species, n_neutral_species, electron_physics) T_wall = 1.0 # wall potential at z = 0 phi_wall = 0.0 - # constant to test nonzero Er - Er_constant = 0.0 # ratio of the neutral particle mass to the ion particle mass mn_over_mi = 1.0 - # ratio of the electron particle mass to the ion particle mass - me_over_mi = 1.0/1836.0 + # ratio of the electron particle mass to the ion particle mass - value set later using + # reference_params + me_over_mi = NaN # The ion flux reaching the wall that is recycled as neutrals is reduced by # `recycling_fraction` to account for ions absorbed by the wall. recycling_fraction = 1.0 gyrokinetic_ions = false composition = species_composition(n_species, n_ion_species, n_neutral_species, - electron_physics, use_test_neutral_wall_pdf, T_e, T_wall, phi_wall, Er_constant, + electron_physics, use_test_neutral_wall_pdf, T_e, T_wall, phi_wall, mn_over_mi, me_over_mi, recycling_fraction, gyrokinetic_ions, allocate_float(n_species)) species_ion = Array{species_parameters_mutable,1}(undef,n_ion_species) @@ -1113,7 +1248,9 @@ function load_defaults(n_ion_species, n_neutral_species, electron_physics) deepcopy(r_initial_conditions), deepcopy(vpa_initial_conditions)) end end - species = (ion = species_ion, neutral = species_neutral) + species_electron = species_parameters_mutable("electron", T_e, 1.0, deepcopy(z_initial_conditions), + deepcopy(r_initial_conditions), deepcopy(vpa_initial_conditions)) + species = (ion = species_ion, electron = species_electron, neutral = species_neutral) # if drive_phi = true, include external electrostatic potential of form # phi(z,t=0)*drive_amplitude*sinpi(time*drive_frequency) @@ -1198,6 +1335,8 @@ function check_coordinate_input(coord, coord_name, io) println(io,">$coord_name.bc = 'constant'. enforcing constant incoming BC in $coord_name.") elseif coord.bc == "zero" println(io,">$coord_name.bc = 'zero'. enforcing zero incoming BC in $coord_name. Enforcing zero at both boundaries if diffusion operator is present.") + elseif coord.bc == "zero-impose-regularity" + println(io,">$coord_name.bc = 'zero'. enforcing zero incoming BC in $coord_name. Enforcing zero at both boundaries if diffusion operator is present. Enforce dF/dcoord = 0 at origin if coord = vperp.") elseif coord.bc == "zero_gradient" println(io,">$coord_name.bc = 'zero_gradient'. enforcing zero gradients at both limits of $coord_name domain.") elseif coord.bc == "both_zero" @@ -1216,9 +1355,9 @@ function check_coordinate_input(coord, coord_name, io) coord.nelement_global, " elements across the $coord_name domain [", 0.0, ",", coord.L, "].") - if coord.bc != "zero" && coord.n_global > 1 && global_rank[] == 0 - println("WARNING: regularity condition (df/dvperp=0 at vperp=0) not being " - * "imposed. Collisions or vperp-diffusion will be unstable.") + if coord.bc == "zero-impose-regularity" && coord.n_global > 1 && global_rank[] == 0 + println("WARNING: regularity condition (df/dvperp=0 at vperp=0) being " + * "imposed explicitly.") end else println(io,">using ", coord.ngrid, " grid points per $coord_name element on ", diff --git a/moment_kinetics/src/moment_kinetics_structs.jl b/moment_kinetics/src/moment_kinetics_structs.jl index 5ac80b23c..acb1a378e 100644 --- a/moment_kinetics/src/moment_kinetics_structs.jl +++ b/moment_kinetics/src/moment_kinetics_structs.jl @@ -9,7 +9,8 @@ using ..type_definitions: mk_float """ """ -struct scratch_pdf{n_distribution_ion, n_moment, n_distribution_neutral,n_moment_neutral} +struct scratch_pdf{n_distribution_ion, n_moment, n_moment_electron, + n_distribution_neutral, n_moment_neutral} # ions pdf::MPISharedArray{mk_float, n_distribution_ion} density::MPISharedArray{mk_float, n_moment} @@ -17,6 +18,12 @@ struct scratch_pdf{n_distribution_ion, n_moment, n_distribution_neutral,n_moment ppar::MPISharedArray{mk_float, n_moment} pperp::MPISharedArray{mk_float, n_moment} temp_z_s::MPISharedArray{mk_float, n_moment} + # electrons + electron_density::MPISharedArray{mk_float, n_moment_electron} + electron_upar::MPISharedArray{mk_float, n_moment_electron} + electron_ppar::MPISharedArray{mk_float, n_moment_electron} + electron_pperp::MPISharedArray{mk_float, n_moment_electron} + electron_temp::MPISharedArray{mk_float, n_moment_electron} # neutral particles pdf_neutral::MPISharedArray{mk_float, n_distribution_neutral} density_neutral::MPISharedArray{mk_float, n_moment_neutral} @@ -24,6 +31,14 @@ struct scratch_pdf{n_distribution_ion, n_moment, n_distribution_neutral,n_moment pz_neutral::MPISharedArray{mk_float, n_moment_neutral} end +""" +""" +struct scratch_electron_pdf{n_distribution_electron, n_moment_electron} + # electrons + pdf_electron::MPISharedArray{mk_float, n_distribution_electron} + electron_ppar::MPISharedArray{mk_float, n_moment_electron} +end + """ """ struct em_fields_struct @@ -140,6 +155,78 @@ struct moments_ion_substruct constraints_C_coefficient::Union{MPISharedArray{mk_float,3},Nothing} end +""" +moments_electron_substruct is a struct that contains moment information for electrons +""" +struct moments_electron_substruct + # this is the particle density + dens::MPISharedArray{mk_float,2} + # flag that keeps track of if the density needs updating before use + dens_updated::Ref{Bool} + # this is the parallel flow + upar::MPISharedArray{mk_float,2} + # flag that keeps track of whether or not upar needs updating before use + upar_updated::Ref{Bool} + # this is the parallel pressure + ppar::MPISharedArray{mk_float,2} + # flag that keeps track of whether or not ppar needs updating before use + ppar_updated::Ref{Bool} + # this is the temperature + temp::MPISharedArray{mk_float,2} + # flag that keeps track of whether or not temp needs updating before use + temp_updated::Ref{Bool} + # this is the parallel heat flux + qpar::MPISharedArray{mk_float,2} + # flag that keeps track of whether or not qpar needs updating before use + qpar_updated::Ref{Bool} + # this is the thermal speed based on the parallel temperature Tpar = ppar/dens: vth = sqrt(2*Tpar/m) + vth::MPISharedArray{mk_float,2} + # this is the parallel friction force between ions and electrons + parallel_friction::MPISharedArray{mk_float,2} + # Spatially varying amplitude of the external source term + external_source_amplitude::MPISharedArray{mk_float,2} + # Spatially varying amplitude of the density moment of the external source term + external_source_density_amplitude::MPISharedArray{mk_float,2} + # Spatially varying amplitude of the parallel momentum moment of the external source + # term + external_source_momentum_amplitude::MPISharedArray{mk_float,2} + # Spatially varying amplitude of the parallel pressure moment of the external source + # term + external_source_pressure_amplitude::MPISharedArray{mk_float,2} + # if evolve_ppar = true, then the velocity variable is (vpa - upa)/vth, which introduces + # a factor of vth for each power of wpa in velocity space integrals. + # v_norm_fac accounts for this: it is vth if using the above definition for the parallel velocity, + # and it is one otherwise + v_norm_fac::Union{MPISharedArray{mk_float,2},Nothing} + # this is the z-derivative of the particle density + ddens_dz::Union{MPISharedArray{mk_float,2},Nothing} + # this is the z-derivative of the parallel flow + dupar_dz::Union{MPISharedArray{mk_float,2},Nothing} + # this is the z-derivative of the parallel pressure + dppar_dz::Union{MPISharedArray{mk_float,2},Nothing} + # this is the upwinded z-derivative of the parallel pressure + dppar_dz_upwind::Union{MPISharedArray{mk_float,2},Nothing} + # this is the second-z-derivative of the parallel pressure + d2ppar_dz2::Union{MPISharedArray{mk_float,2},Nothing} + # this is the z-derivative of the parallel heat flux + dqpar_dz::Union{MPISharedArray{mk_float,2},Nothing} + # this is the z-derivative of the parallel temperature Tpar = ppar/dens + dT_dz::Union{MPISharedArray{mk_float,2},Nothing} + # this is the upwinded z-derivative of the temperature Tpar = ppar/dens + dT_dz_upwind::Union{MPISharedArray{mk_float,2},Nothing} + # this is the z-derivative of the electron thermal speed vth = sqrt(2*Tpar/m) + dvth_dz::Union{MPISharedArray{mk_float,2},Nothing} + # Store coefficient 'A' from applying moment constraints so we can write it out as a + # diagnostic + constraints_A_coefficient::Union{MPISharedArray{mk_float,2},Nothing} + # Store coefficient 'B' from applying moment constraints so we can write it out as a + # diagnostic + constraints_B_coefficient::Union{MPISharedArray{mk_float,2},Nothing} + # Store coefficient 'C' from applying moment constraints so we can write it out as a + # diagnostic + constraints_C_coefficient::Union{MPISharedArray{mk_float,2},Nothing} +end + """ """ struct moments_neutral_substruct @@ -239,12 +326,22 @@ struct pdf_substruct{n_distribution} buffer::MPISharedArray{mk_float,n_distribution} # for collision operator terms when pdfs must be interpolated onto different velocity space grids, and for gyroaveraging end +""" +""" +struct electron_pdf_substruct{n_distribution} + norm::MPISharedArray{mk_float,n_distribution} + buffer::MPISharedArray{mk_float,n_distribution} # for collision operator terms when pdfs must be interpolated onto different velocity space grids + pdf_before_ion_timestep::MPISharedArray{mk_float,n_distribution} +end + # struct of structs neatly contains i+n info? """ """ struct pdf_struct #ion particles: s + r + z + vperp + vpa ion::pdf_substruct{5} + # electron particles: r + z + vperp + vpa + electron::Union{electron_pdf_substruct{4},Nothing} #neutral particles: s + r + z + vzeta + vr + vz neutral::pdf_substruct{6} end @@ -253,6 +350,7 @@ end """ struct moments_struct ion::moments_ion_substruct + electron::moments_electron_substruct neutral::moments_neutral_substruct # flag that indicates if the density should be evolved via continuity equation evolve_density::Bool diff --git a/moment_kinetics/src/nonlinear_solvers.jl b/moment_kinetics/src/nonlinear_solvers.jl index 79a87fc09..365b91295 100644 --- a/moment_kinetics/src/nonlinear_solvers.jl +++ b/moment_kinetics/src/nonlinear_solvers.jl @@ -39,6 +39,7 @@ using LinearAlgebra using MINPACK using MPI using SparseArrays +using StatsBase: mean struct nl_solver_info{TH,TV,Tlig,Tprecon} rtol::mk_float @@ -75,7 +76,7 @@ for example a preconditioner object for each point in that outer loop. """ function setup_nonlinear_solve(input_dict, coords, outer_coords=(); default_rtol=1.0e-5, default_atol=1.0e-12, serial_solve=false, - preconditioner_type=nothing) + electron_ppar_pdf_solve=false, preconditioner_type=nothing) nl_solver_section = set_defaults_and_check_section!( input_dict, "nonlinear_solver"; rtol=default_rtol, @@ -100,6 +101,19 @@ function setup_nonlinear_solve(input_dict, coords, outer_coords=(); default_rtol V = allocate_float(reverse(coord_sizes)..., linear_restart+1) H .= 0.0 V .= 0.0 + elseif electron_ppar_pdf_solve + H = allocate_shared_float(linear_restart + 1, linear_restart) + V_ppar = allocate_shared_float(coords.z.n, coords.r.n, linear_restart+1) + V_pdf = allocate_shared_float(reverse(coord_sizes)..., linear_restart+1) + + begin_serial_region() + @serial_region begin + H .= 0.0 + V_ppar .= 0.0 + V_pdf .= 0.0 + end + + V = (V_ppar, V_pdf) else H = allocate_shared_float(linear_restart + 1, linear_restart) V = allocate_shared_float(reverse(coord_sizes)..., linear_restart+1) @@ -379,6 +393,10 @@ function get_distributed_norm(coords, rtol, atol, x) this_norm = distributed_norm_z elseif dims == (:vpa,) this_norm = distributed_norm_vpa + elseif dims == (:r, :z, :vperp, :vpa) + # Intended for implicit solve combining electron_ppar and pdf_electron, so will + # not work for a single variable. + this_norm = distributed_norm_r_z_vperp_vpa elseif dims == (:s, :r, :z, :vperp, :vpa) this_norm = distributed_norm_s_r_z_vperp_vpa else @@ -439,6 +457,75 @@ function distributed_norm_vpa(residual::AbstractArray{mk_float, 1}; coords, rtol return residual_norm end +function distributed_norm_r_z_vperp_vpa(residual::Tuple{AbstractArray{mk_float, 2},AbstractArray{mk_float, 4}}; + coords, rtol, atol, x) + ppar_residual, pdf_residual = residual + x_ppar, x_pdf = x + r = coords.r + z = coords.z + vperp = coords.vperp + vpa = coords.vpa + + if r.irank < r.nrank - 1 + rend = r.n + else + rend = r.n + 1 + end + if z.irank < z.nrank - 1 + zend = z.n + else + zend = z.n + 1 + end + + begin_r_z_region() + + ppar_local_norm_square = 0.0 + @loop_r_z ir iz begin + if ir == rend || iz == zend + continue + end + ppar_local_norm_square += (ppar_residual[iz,ir] / (rtol * abs(x_ppar[iz,ir]) + atol))^2 + end + + _block_synchronize() + ppar_block_norm_square = MPI.Reduce(ppar_local_norm_square, +, comm_block[]) + + if block_rank[] == 0 + ppar_global_norm_square = MPI.Allreduce(ppar_block_norm_square, +, comm_inter_block[]) + ppar_global_norm_square = ppar_global_norm_square / (r.n_global * z.n_global) + else + ppar_global_norm_square = nothing + end + + begin_r_z_vperp_vpa_region() + + pdf_local_norm_square = 0.0 + @loop_r_z ir iz begin + if ir == rend || iz == zend + continue + end + @loop_vperp_vpa ivperp ivpa begin + pdf_local_norm_square += (pdf_residual[ivpa,ivperp,iz,ir] / (rtol * abs(x_pdf[ivpa,ivperp,iz,ir]) + atol))^2 + end + end + + _block_synchronize() + pdf_block_norm_square = MPI.Reduce(pdf_local_norm_square, +, comm_block[]) + + if block_rank[] == 0 + pdf_global_norm_square = MPI.Allreduce(pdf_block_norm_square, +, comm_inter_block[]) + pdf_global_norm_square = pdf_global_norm_square / (r.n_global * z.n_global * vperp.n_global * vpa.n_global) + + global_norm = sqrt(mean((ppar_global_norm_square, pdf_global_norm_square))) + else + global_norm = nothing + end + + global_norm = MPI.bcast(global_norm, comm_block[]; root=0) + + return global_norm +end + function distributed_norm_s_r_z_vperp_vpa(residual::AbstractArray{mk_float, 5}; coords, rtol, atol, x) n_ion_species = coords.s @@ -495,6 +582,10 @@ function get_distributed_dot(coords, rtol, atol, x) this_dot = distributed_dot_z elseif dims == (:vpa,) this_dot = distributed_dot_vpa + elseif dims == (:r, :z, :vperp, :vpa) + # Intended for implicit solve combining electron_ppar and pdf_electron, so will + # not work for a single variable. + this_dot = distributed_dot_r_z_vperp_vpa elseif dims == (:s, :r, :z, :vperp, :vpa) this_dot = distributed_dot_s_r_z_vperp_vpa else @@ -556,6 +647,74 @@ function distributed_dot_vpa(v::AbstractArray{mk_float, 1}, w::AbstractArray{mk_ return local_dot end +function distributed_dot_r_z_vperp_vpa(v::Tuple{AbstractArray{mk_float, 2},AbstractArray{mk_float, 4}}, + w::Tuple{AbstractArray{mk_float, 2},AbstractArray{mk_float, 4}}; + coords, atol, rtol, x) + v_ppar, v_pdf = v + w_ppar, w_pdf = w + x_ppar, x_pdf = x + + r = coords.r + z = coords.z + vperp = coords.vperp + vpa = coords.vpa + + if r.irank < r.nrank - 1 + rend = r.n + else + rend = r.n + 1 + end + if z.irank < z.nrank - 1 + zend = z.n + else + zend = z.n + 1 + end + + begin_r_z_region() + + ppar_local_dot = 0.0 + @loop_r_z ir iz begin + if ir == rend || iz == zend + continue + end + ppar_local_dot += v_ppar[iz,ir] * w_ppar[iz,ir] / (rtol * abs(x_ppar[iz,ir]) + atol)^2 + end + + _block_synchronize() + ppar_block_dot = MPI.Reduce(ppar_local_dot, +, comm_block[]) + + if block_rank[] == 0 + ppar_global_dot = MPI.Allreduce(ppar_block_dot, +, comm_inter_block[]) + ppar_global_dot = ppar_global_dot / (r.n_global * z.n_global * vperp.n_global * vpa.n_global) + else + ppar_global_dot = nothing + end + + begin_r_z_vperp_vpa_region() + + pdf_local_dot = 0.0 + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + if ir == rend || iz == zend + continue + end + pdf_local_dot += v_pdf[ivpa,ivperp,iz,ir] * w_pdf[ivpa,ivperp,iz,ir] / (rtol * abs(x_pdf[ivpa,ivperp,iz,ir]) + atol)^2 + end + + _block_synchronize() + pdf_block_dot = MPI.Reduce(pdf_local_dot, +, comm_block[]) + + if block_rank[] == 0 + pdf_global_dot = MPI.Allreduce(pdf_block_dot, +, comm_inter_block[]) + pdf_global_dot = pdf_global_dot / (r.n_global * z.n_global * vperp.n_global * vpa.n_global) + + global_dot = mean((ppar_global_dot, pdf_global_dot)) + else + global_dot = nothing + end + + return global_dot +end + function distributed_dot_s_r_z_vperp_vpa(v::AbstractArray{mk_float, 5}, w::AbstractArray{mk_float, 5}; coords, atol, rtol, x) @@ -611,6 +770,10 @@ function get_parallel_map(coords) return parallel_map_z elseif dims == (:vpa,) return parallel_map_vpa + elseif dims == (:r, :z, :vperp, :vpa) + # Intended for implicit solve combining electron_ppar and pdf_electron, so will + # not work for a single variable. + return parallel_map_r_z_vperp_vpa elseif dims == (:s, :r, :z, :vperp, :vpa) return parallel_map_s_r_z_vperp_vpa else @@ -678,6 +841,64 @@ function parallel_map_vpa(func, result::AbstractArray{mk_float, 1}, x1, x2) return nothing end +function parallel_map_r_z_vperp_vpa(func, result::Tuple{AbstractArray{mk_float, 2},AbstractArray{mk_float, 4}}) + + result_ppar, result_pdf = result + + begin_r_z_region() + + @loop_r_z ir iz begin + result_ppar[iz,ir] = func() + end + + begin_r_z_vperp_vpa_region() + + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + result_pdf[ivpa,ivperp,iz,ir] = func() + end + + return nothing +end +function parallel_map_r_z_vperp_vpa(func, result::Tuple{AbstractArray{mk_float, 2},AbstractArray{mk_float, 4}}, x1) + + result_ppar, result_pdf = result + x1_ppar, x1_pdf = x1 + + begin_r_z_region() + + @loop_r_z ir iz begin + result_ppar[iz,ir] = func(x1_ppar[iz,ir]) + end + + begin_r_z_vperp_vpa_region() + + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + result_pdf[ivpa,ivperp,iz,ir] = func(x1_pdf[ivpa,ivperp,iz,ir]) + end + + return nothing +end +function parallel_map_r_z_vperp_vpa(func, result::Tuple{AbstractArray{mk_float, 2},AbstractArray{mk_float, 4}}, x1, x2) + + result_ppar, result_pdf = result + x1_ppar, x1_pdf = x1 + x2_ppar, x2_pdf = x2 + + begin_r_z_region() + + @loop_r_z ir iz begin + result_ppar[iz,ir] = func(x1_ppar[iz,ir], x2_ppar[iz,ir]) + end + + begin_r_z_vperp_vpa_region() + + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + result_pdf[ivpa,ivperp,iz,ir] = func(x1_pdf[ivpa,ivperp,iz,ir], x2_pdf[ivpa,ivperp,iz,ir]) + end + + return nothing +end + function parallel_map_s_r_z_vperp_vpa(func, result::AbstractArray{mk_float, 5}) begin_s_r_z_vperp_vpa_region() @@ -721,6 +942,10 @@ function get_parallel_delta_x_calc(coords) return parallel_delta_x_calc_z elseif dims == (:vpa,) return parallel_delta_x_calc_vpa + elseif dims == (:r, :z, :vperp, :vpa) + # Intended for implicit solve combining electron_ppar and pdf_electron, so will + # not work for a single variable. + return parallel_delta_x_calc_r_z_vperp_vpa elseif dims == (:s, :r, :z, :vperp, :vpa) return parallel_delta_x_calc_s_r_z_vperp_vpa else @@ -755,6 +980,32 @@ function parallel_delta_x_calc_vpa(delta_x::AbstractArray{mk_float, 1}, V, y) return nothing end +function parallel_delta_x_calc_r_z_vperp_vpa(delta_x::Tuple{AbstractArray{mk_float, 2},AbstractArray{mk_float, 4}}, V, y) + + delta_x_ppar, delta_x_pdf = delta_x + V_ppar, V_pdf = V + + ny = length(y) + + begin_r_z_region() + + @loop_r_z ir iz begin + for iy ∈ 1:ny + delta_x_ppar[iz,ir] += y[iy] * V_ppar[iz,ir,iy] + end + end + + begin_r_z_vperp_vpa_region() + + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + for iy ∈ 1:ny + delta_x_pdf[ivpa,ivperp,iz,ir] += y[iy] * V_pdf[ivpa,ivperp,iz,ir,iy] + end + end + + return nothing +end + function parallel_delta_x_calc_s_r_z_vperp_vpa(delta_x::AbstractArray{mk_float, 5}, V, y) begin_s_r_z_vperp_vpa_region() @@ -769,6 +1020,14 @@ function parallel_delta_x_calc_s_r_z_vperp_vpa(delta_x::AbstractArray{mk_float, return nothing end +# Utility function for neatness handling that V may be an array or a Tuple of arrays +function select_from_V(V::Tuple, i) + return Tuple(selectdim(Vpart,ndims(Vpart),i) for Vpart ∈ V) +end +function select_from_V(V, i) + return selectdim(V,ndims(V),i) +end + """ Apply the GMRES algorithm to solve the 'linear problem' J.δx^n = R(x^n), which is needed at each step of the outer Newton iteration (in `newton_solve!()`). @@ -813,7 +1072,7 @@ function linear_solve!(x, residual_func!, residual0, delta_x, v, w; coords, rtol # Now we actually set 'w' as the first Krylov vector, and normalise it. parallel_map((residual0, v) -> -residual0 - v, w, residual0, v) beta = distributed_norm(w) - parallel_map((w) -> w/beta, selectdim(V,ndims(V),1), w) + parallel_map((w) -> w/beta, select_from_V(V, 1), w) # Set tolerance for GMRES iteration to rtol times the initial residual, unless this is # so small that it is smaller than atol, in which case use atol instead. @@ -829,12 +1088,12 @@ function linear_solve!(x, residual_func!, residual0, delta_x, v, w; coords, rtol #println("Linear ", counter) # Compute next Krylov vector - parallel_map((V) -> V, w, selectdim(V,ndims(V),i)) + parallel_map((V) -> V, w, select_from_V(V, i)) approximate_Jacobian_vector_product!(w) # Gram-Schmidt orthogonalization for j ∈ 1:i - parallel_map((V) -> V, v, selectdim(V,ndims(V),j)) + parallel_map((V) -> V, v, select_from_V(V, j)) w_dot_Vj = distributed_dot(w, v) if serial_solve H[j,i] = w_dot_Vj @@ -844,7 +1103,7 @@ function linear_solve!(x, residual_func!, residual0, delta_x, v, w; coords, rtol H[j,i] = w_dot_Vj end end - parallel_map((w, V) -> w - H[j,i] * V, w, w, selectdim(V,ndims(V),j)) + parallel_map((w, V) -> w - H[j,i] * V, w, w, select_from_V(V, j)) end norm_w = distributed_norm(w) if serial_solve @@ -855,7 +1114,7 @@ function linear_solve!(x, residual_func!, residual0, delta_x, v, w; coords, rtol H[i+1,i] = norm_w end end - parallel_map((w) -> w / H[i+1,i], selectdim(V,ndims(V),i+1), w) + parallel_map((w) -> w / H[i+1,i], select_from_V(V, i+1), w) function temporary_residual!(result, guess) #println("temporary residual ", size(result), " ", size(@view(H[1:i+1,1:i])), " ", size(guess)) @@ -921,9 +1180,9 @@ function linear_solve!(x, residual_func!, residual0, delta_x, v, w; coords, rtol parallel_map((residual0, v) -> -residual0 - v, v, residual0, v) beta = distributed_norm(v) for i ∈ 2:length(y) - parallel_map(() -> 0.0, selectdim(V,ndims(V),i)) + parallel_map(() -> 0.0, select_from_V(V, i)) end - parallel_map((v) -> v/beta, selectdim(V,ndims(V),1), v) + parallel_map((v) -> v/beta, select_from_V(V, 1), v) end return counter diff --git a/moment_kinetics/src/numerical_dissipation.jl b/moment_kinetics/src/numerical_dissipation.jl index 86e517229..f48a158c6 100644 --- a/moment_kinetics/src/numerical_dissipation.jl +++ b/moment_kinetics/src/numerical_dissipation.jl @@ -12,7 +12,7 @@ export setup_numerical_dissipation, vpa_boundary_buffer_decay!, using Base.Iterators: flatten using ..looping -using ..calculus: derivative!, second_derivative! +using ..calculus: derivative!, second_derivative!, laplacian_derivative! using ..derivatives: derivative_r!, derivative_z!, second_derivative_r!, second_derivative_z! using ..type_definitions: mk_float @@ -369,7 +369,7 @@ function vperp_dissipation!(f_out, f_in, vperp, spectral::T_spectral, dt, end @loop_s_r_z_vpa is ir iz ivpa begin - @views second_derivative!(vperp.scratch, f_in[ivpa,:,iz,ir,is], vperp, spectral) + @views laplacian_derivative!(vperp.scratch, f_in[ivpa,:,iz,ir,is], vperp, spectral) @views @. f_out[ivpa,:,iz,ir,is] += dt * diffusion_coefficient * vperp.scratch end diff --git a/moment_kinetics/src/reference_parameters.jl b/moment_kinetics/src/reference_parameters.jl index 869451cc4..97b771228 100644 --- a/moment_kinetics/src/reference_parameters.jl +++ b/moment_kinetics/src/reference_parameters.jl @@ -8,7 +8,8 @@ physical units of the simulation, and are needed for a few specific steps during module reference_parameters export setup_reference_parameters -export get_reference_collision_frequency_ii +export get_reference_collision_frequency_ii, get_reference_collision_frequency_ee, + get_reference_collision_frequency_ei using ..constants using ..input_structs @@ -39,6 +40,21 @@ function setup_reference_parameters(input_dict) # units of eV. reference_parameter_section["logLambda_ii"] = 23.0 - log(sqrt(2.0*Nref_per_cm3) / Tref^1.5) + # Coulomb logarithm at reference parameters for electron-electron collisions, using + # NRL formulary. Formula given for n in units of cm^-3 and T in units of eV. + reference_parameter_section["logLambda_ee"] = 23.5 - log(sqrt(Nref_per_cm3) / Tref^1.25) - sqrt(1.0e-5 + (log(Tref) -2.0)^2 / 16.0) + + # Coulomb logarithm at reference parameters for electron-ion collisions with + # singly-charged ions, using NRL formulary. Formula given for n in units of cm^-3 and + # T in units of eV. + # Note: assume reference temperature is the same for ions and electrons, so ignore + # case in NRL formulary where Te < Ti*me/mi. + if Tref < 10.0 + reference_parameter_section["logLambda_ei"] = 23.0 - log(sqrt(Nref_per_cm3) / Tref^1.5) + else + reference_parameter_section["logLambda_ei"] = 24.0 - log(sqrt(Nref_per_cm3) / Tref) + end + reference_params = Dict_to_NamedTuple(reference_parameter_section) return reference_params @@ -66,4 +82,54 @@ function get_reference_collision_frequency_ii(reference_params) return nu_ii0 end +""" +Calculate normalized electron-electron collision frequency at reference parameters for Coulomb collisions. +""" +function get_reference_collision_frequency_ee(reference_params) + Nref = reference_params.Nref + Tref = reference_params.Tref + me = reference_params.me + timeref = reference_params.timeref + cref = reference_params.cref + logLambda_ee = reference_params.logLambda_ee + + # Collision frequency, using \hat{\nu} from Appendix, p. 277 of Helander "Collisional + # Transport in Magnetized Plasmas" (2002). + # Note the electron thermal speed used in the code is normalised to cref, so we use + # cref in these two formulas rather than a reference electron thermal speed, so that + # when multiplied by the normalised electron thermal speed we get the correct + # normalised collision frequency. + nu_ee0_per_s = Nref * proton_charge^4 * logLambda_ee / + (4.0 * π * epsilon0^2 * me^2 * cref^3) # s^-1 + nu_ee0 = nu_ee0_per_s * timeref + + return nu_ee0 +end + +""" +Calculate normalized electron-ion collision frequency at reference parameters for Coulomb collisions. + +Currently valid only for hydrogenic ions (Z=1) +""" +function get_reference_collision_frequency_ei(reference_params) + Nref = reference_params.Nref + Tref = reference_params.Tref + me = reference_params.me + timeref = reference_params.timeref + cref = reference_params.cref + logLambda_ei = reference_params.logLambda_ei + + # Collision frequency, using \hat{\nu} from Appendix, p. 277 of Helander "Collisional + # Transport in Magnetized Plasmas" (2002). + # Note the electron thermal speed used in the code is normalised to cref, so we use + # cref in these two formulas rather than a reference electron thermal speed, so that + # when multiplied by the normalised electron thermal speed we get the correct + # normalised collision frequency. + nu_ei0_per_s = Nref * proton_charge^4 * logLambda_ei / + (4.0 * π * epsilon0^2 * me^2 * cref^3) # s^-1 + nu_ei0 = nu_ei0_per_s * timeref + + return nu_ei0 +end + end diff --git a/moment_kinetics/src/runge_kutta.jl b/moment_kinetics/src/runge_kutta.jl index d2767a90f..16d0ef600 100644 --- a/moment_kinetics/src/runge_kutta.jl +++ b/moment_kinetics/src/runge_kutta.jl @@ -9,6 +9,7 @@ export setup_runge_kutta_coefficients!, rk_update_evolved_moments!, using ..array_allocation: allocate_float using ..communication +using ..input_structs using ..looping using ..type_definitions: mk_float @@ -891,6 +892,41 @@ function local_error_norm(f_loworder::MPISharedArray{mk_float,3}, error("Unrecognized method '$method'") end end +function local_error_norm(f_loworder::MPISharedArray{mk_float,4}, + f::MPISharedArray{mk_float,4}, rtol, atol; method="Linf", + skip_r_inner=false, skip_z_lower=false, error_sum_zero=0.0) + if method == "Linf" + f_max = -Inf + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + error_norm = abs(f_loworder[ivpa,ivperp,iz,ir] - f[ivpa,ivperp,iz,ir]) / + (rtol*abs(f[ivpa,ivperp,iz,ir]) + atol) + f_max = max(f_max, error_norm) + end + return f_max + elseif method == "L2" + L2sum = error_sum_zero + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + if (skip_r_inner && ir == 1) || (skip_z_lower && iz == 1) + continue + end + error_norm = ((f_loworder[ivpa,ivperp,iz,ir] - f[ivpa,ivperp,iz,ir]) / + (rtol*abs(f[ivpa,ivperp,iz,ir]) + atol))^2 + L2sum += error_norm + end + # Will sum results from different processes in shared memory block after returning + # from this function. + nvpa, nvperp, nz, nr = size(f_loworder) + if skip_r_inner + nr -= 1 + end + if skip_z_lower + nz -= 1 + end + return L2sum + else + error("Unrecognized method '$method'") + end +end function local_error_norm(f_loworder::MPISharedArray{mk_float,5}, f::MPISharedArray{mk_float,5}, rtol, atol; method="Linf", skip_r_inner=false, skip_z_lower=false, error_sum_zero=0.0) @@ -962,9 +998,10 @@ end Use the calculated `CFL_limits` and `error_norms` to update the timestep in `t_params`. """ -function adaptive_timestep_update_t_params!(t_params, scratch, t, CFL_limits, error_norms, +function adaptive_timestep_update_t_params!(t_params, CFL_limits, error_norms, total_points, current_dt, error_norm_method, - success, nl_max_its_fraction) + success, nl_max_its_fraction, composition; + electron=false, local_max_dt::mk_float=Inf) # Get global minimum of CFL limits CFL_limit = nothing this_limit_caused_by = nothing @@ -1020,16 +1057,9 @@ function adaptive_timestep_update_t_params!(t_params, scratch, t, CFL_limits, er error("Unrecognized error_norm_method '$method'") end - just_completed_output_step = false - - if !success + if success != "" # Iteration failed in implicit part of timestep try decreasing timestep - # Set scratch[end] equal to scratch[1] to start the timestep over - scratch_temp = scratch[t_params.n_rk_stages+1] - scratch[t_params.n_rk_stages+1] = scratch[1] - scratch[1] = scratch_temp - @serial_region begin t_params.failure_counter[] += 1 @@ -1039,10 +1069,6 @@ function adaptive_timestep_update_t_params!(t_params, scratch, t, CFL_limits, er t_params.dt_before_last_fail[] = t_params.previous_dt[] end - # If we were trying to take a step to the output timestep, dt will be smaller on - # the re-try, so will not reach the output time. - t_params.step_to_output[] = false - # Decrease timestep by 1/2 - this factor should probably be settable! # Note when nonlinear solve iteration fails, we do not enforce # minimum_dt, as the timesolver must error if we do not decrease dt. @@ -1058,8 +1084,27 @@ function adaptive_timestep_update_t_params!(t_params, scratch, t, CFL_limits, er t_params.previous_dt[] = 0.0 # Call the 'cause' of the timestep failure the variable that has the biggest - # error norm here - t_params.failure_caused_by[end] += 1 + # error norm here. + # Could do with a better way to sort the different possible types of + # convergence failure... + if t_params.rk_coefs_implicit !== nothing && + composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + if success == "nonlinear-solver" + t_params.failure_caused_by[end-1] += 1 + elseif success == "kinetic-electrons" + t_params.failure_caused_by[end] += 1 + else + error("Unrecognised cause of convergence failure: \"$success\"") + end + else + t_params.failure_caused_by[end] += 1 + end + + # If we were trying to take a step to the output timestep, dt will be smaller on + # the re-try, so will not reach the output time. + t_params.step_to_moments_output[] = false + t_params.step_to_dfns_output[] = false end elseif (error_norm > 1.0 || isnan(error_norm)) && current_dt > t_params.minimum_dt * (1.0 + 1.0e-13) # (1.0 + 1.0e-13) fudge factor accounts for possible rounding errors when @@ -1070,11 +1115,6 @@ function adaptive_timestep_update_t_params!(t_params, scratch, t, CFL_limits, er # # Timestep failed, reduce timestep and re-try - # Set scratch[end] equal to scratch[1] to start the timestep over - scratch_temp = scratch[t_params.n_rk_stages+1] - scratch[t_params.n_rk_stages+1] = scratch[1] - scratch[1] = scratch_temp - @serial_region begin t_params.failure_counter[] += 1 @@ -1084,10 +1124,6 @@ function adaptive_timestep_update_t_params!(t_params, scratch, t, CFL_limits, er t_params.dt_before_last_fail[] = t_params.previous_dt[] end - # If we were trying to take a step to the output timestep, dt will be smaller on - # the re-try, so will not reach the output time. - t_params.step_to_output[] = false - # Get new timestep estimate using same formula as for a successful step, but # limit decrease to factor 1/2 - this factor should probably be settable! t_params.dt[] = max(t_params.dt[] / 2.0, @@ -1101,6 +1137,11 @@ function adaptive_timestep_update_t_params!(t_params, scratch, t, CFL_limits, er # error norm here t_params.failure_caused_by[max_error_variable_index] += 1 + # If we were trying to take a step to the output timestep, dt will be smaller on + # the re-try, so will not reach the output time. + t_params.step_to_moments_output[] = false + t_params.step_to_dfns_output[] = false + #println("t=$t, timestep failed, error_norm=$error_norm, error_norms=$error_norms, decreasing timestep to ", t_params.dt[]) end else @@ -1109,17 +1150,23 @@ function adaptive_timestep_update_t_params!(t_params, scratch, t, CFL_limits, er # simulation time. t_params.previous_dt[] = t_params.dt[] - if t_params.step_to_output[] + if t_params.step_to_moments_output[] || t_params.step_to_dfns_output[] # Completed an output step, reset dt to what it was before it was reduced to reach # the output time t_params.dt[] = t_params.dt_before_output[] - t_params.step_to_output[] = false + + if t_params.step_to_moments_output[] + t_params.step_to_moments_output[] = false + t_params.write_moments_output[] = true + end + if t_params.step_to_dfns_output[] + t_params.step_to_dfns_output[] = false + t_params.write_dfns_output[] = true + end if t_params.dt[] > CFL_limit t_params.dt[] = CFL_limit end - - just_completed_output_step = true else # Adjust timestep according to Fehlberg's suggestion # (https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta%E2%80%93Fehlberg_method). @@ -1171,8 +1218,9 @@ function adaptive_timestep_update_t_params!(t_params, scratch, t, CFL_limits, er end # Prevent timestep from going above maximum_dt - if t_params.dt[] > t_params.maximum_dt - t_params.dt[] = t_params.maximum_dt + max_dt = min(t_params.maximum_dt, local_max_dt) + if t_params.dt[] > max_dt + t_params.dt[] = max_dt this_limit_caused_by = 4 end @@ -1190,9 +1238,10 @@ function adaptive_timestep_update_t_params!(t_params, scratch, t, CFL_limits, er t_params.limit_caused_by[this_limit_caused_by] += 1 if (t_params.step_counter[] % 1000 == 0) && global_rank[] == 0 - println("step ", t_params.step_counter[], ": t=", - round(t, sigdigits=6), ", nfail=", t_params.failure_counter[], - ", dt=", t_params.dt[]) + prefix = electron ? "electron" : "ion" + println("$prefix step ", t_params.step_counter[], ": t=", + round(t_params.t[], sigdigits=6), ", nfail=", + t_params.failure_counter[], ", dt=", t_params.dt[]) end end end @@ -1202,18 +1251,44 @@ function adaptive_timestep_update_t_params!(t_params, scratch, t, CFL_limits, er minimum_dt = 1.e-14 if t_params.dt[] < minimum_dt println("Time advance failed: trying to set dt=$(t_params.dt[]) less than " - * "$minimum_dt at t=$t. Ending run.") + * "$minimum_dt at t=$(t_params.t[]). Ending run.") # Set dt negative to signal an error t_params.dt[] = -1.0 end - current_time = t + t_params.previous_dt[] - if (!t_params.write_after_fixed_step_count && !just_completed_output_step - && (current_time + t_params.dt[] >= t_params.next_output_time[])) + current_time = t_params.t[] + t_params.previous_dt[] + # Store here to ensure dt_before_output is set correctly when both moments and + # dfns are written at the same time. + current_dt = t_params.dt[] + if (!t_params.write_after_fixed_step_count + && !t_params.write_moments_output[] + && length(t_params.moments_output_times) > 0 + && (t_params.moments_output_counter[] ≤ length(t_params.moments_output_times)) + && (current_time + t_params.dt[] >= t_params.moments_output_times[t_params.moments_output_counter[]])) + + t_params.dt_before_output[] = current_dt + t_params.dt[] = t_params.moments_output_times[t_params.moments_output_counter[]] - current_time + t_params.step_to_moments_output[] = true - t_params.dt_before_output[] = t_params.dt[] - t_params.dt[] = t_params.next_output_time[] - current_time - t_params.step_to_output[] = true + if t_params.dt[] < 0.0 + error("When trying to step to next output time, made negative timestep " + * "dt=$(t_params.dt[])") + end + end + if (!t_params.write_after_fixed_step_count + && !t_params.write_dfns_output[] + && length(t_params.dfns_output_times) > 0 + && (t_params.dfns_output_counter[] ≤ length(t_params.dfns_output_times)) + && (current_time + t_params.dt[] >= t_params.dfns_output_times[t_params.dfns_output_counter[]])) + + t_params.dt_before_output[] = current_dt + t_params.dt[] = t_params.dfns_output_times[t_params.dfns_output_counter[]] - current_time + t_params.step_to_dfns_output[] = true + + if t_params.dt[] < 0.0 + error("When trying to step to next output time, made negative timestep " + * "dt=$(t_params.dt[])") + end end end diff --git a/moment_kinetics/src/time_advance.jl b/moment_kinetics/src/time_advance.jl index 2460dba15..db4464985 100644 --- a/moment_kinetics/src/time_advance.jl +++ b/moment_kinetics/src/time_advance.jl @@ -8,22 +8,26 @@ export allocate_advection_structs export setup_dummy_and_buffer_arrays using MPI +using Quadmath using ..type_definitions: mk_float, mk_int using ..array_allocation: allocate_float, allocate_shared_float, allocate_shared_int, allocate_shared_bool using ..communication using ..communication: _block_synchronize using ..debugging -using ..file_io: write_data_to_ascii, write_all_moments_data_to_binary, write_all_dfns_data_to_binary, debug_dump +using ..file_io: write_data_to_ascii, write_all_moments_data_to_binary, + write_all_dfns_data_to_binary, debug_dump, setup_electron_io +using ..initial_conditions: initialize_electrons! using ..looping -using ..moment_kinetics_structs: scratch_pdf +using ..moment_kinetics_structs: scratch_pdf, scratch_electron_pdf using ..velocity_moments: update_moments!, update_moments_neutral!, reset_moments_status!, update_derived_moments!, update_derived_moments_neutral! using ..velocity_moments: update_density!, update_upar!, update_ppar!, update_pperp!, update_qpar!, update_vth! using ..velocity_moments: update_neutral_density!, update_neutral_qz! using ..velocity_moments: update_neutral_uzeta!, update_neutral_uz!, update_neutral_ur! using ..velocity_moments: update_neutral_pzeta!, update_neutral_pz!, update_neutral_pr! using ..velocity_moments: calculate_ion_moment_derivatives!, calculate_neutral_moment_derivatives! +using ..velocity_moments: calculate_electron_moment_derivatives! using ..velocity_grid_transforms: vzvrvzeta_to_vpavperp!, vpavperp_to_vzvrvzeta! -using ..boundary_conditions: enforce_boundary_conditions! +using ..boundary_conditions: enforce_boundary_conditions!, get_ion_z_boundary_cutoff_indices using ..boundary_conditions: enforce_neutral_boundary_conditions! using ..boundary_conditions: vpagrid_to_dzdt, enforce_v_boundary_condition_local! using ..input_structs @@ -42,10 +46,12 @@ using ..charge_exchange: ion_charge_exchange_collisions_1V!, neutral_charge_exchange_collisions_1V!, ion_charge_exchange_collisions_3V!, neutral_charge_exchange_collisions_3V! +using ..electron_kinetic_equation: update_electron_pdf!, implicit_electron_advance! using ..ionization: ion_ionization_collisions_1V!, neutral_ionization_collisions_1V!, ion_ionization_collisions_3V!, neutral_ionization_collisions_3V!, constant_ionization_source! using ..krook_collisions: krook_collisions! +using ..maxwell_diffusion: ion_vpa_maxwell_diffusion!, neutral_vz_maxwell_diffusion! using ..external_sources using ..nonlinear_solvers using ..numerical_dissipation: vpa_boundary_buffer_decay!, @@ -71,6 +77,16 @@ using ..runge_kutta: rk_update_evolved_moments!, rk_update_evolved_moments_neutr adaptive_timestep_update_t_params! using ..utils: to_minutes, get_minimum_CFL_z, get_minimum_CFL_vpa, get_minimum_CFL_neutral_z, get_minimum_CFL_neutral_vz +using ..electron_fluid_equations: calculate_electron_moments! +using ..electron_fluid_equations: calculate_electron_density! +using ..electron_fluid_equations: calculate_electron_upar_from_charge_conservation! +using ..electron_fluid_equations: calculate_electron_qpar!, electron_fluid_qpar_boundary_condition! +using ..electron_fluid_equations: calculate_electron_parallel_friction_force! +using ..electron_fluid_equations: electron_energy_equation!, update_electron_vth_temperature!, + electron_braginskii_conduction!, + implicit_braginskii_conduction! +using ..input_structs: braginskii_fluid +using ..derivatives: derivative_z! @debug_detect_redundant_block_synchronize using ..communication: debug_detect_redundant_is_active using Dates @@ -119,7 +135,7 @@ struct scratch_dummy_arrays buffer_zrs_1::MPISharedArray{mk_float,3} buffer_zrs_2::MPISharedArray{mk_float,3} buffer_zrs_3::MPISharedArray{mk_float,3} - + buffer_vpavperpzs_1::MPISharedArray{mk_float,4} buffer_vpavperpzs_2::MPISharedArray{mk_float,4} buffer_vpavperpzs_3::MPISharedArray{mk_float,4} @@ -138,6 +154,20 @@ struct scratch_dummy_arrays # needs to be shared memory buffer_vpavperpzrs_1::MPISharedArray{mk_float,5} buffer_vpavperpzrs_2::MPISharedArray{mk_float,5} + # buffers to hold moment quantities for implicit solves + implicit_buffer_zr_1::MPISharedArray{mk_float,2} + implicit_buffer_zr_2::MPISharedArray{mk_float,2} + implicit_buffer_zr_3::MPISharedArray{mk_float,2} + implicit_buffer_zr_4::MPISharedArray{mk_float,2} + implicit_buffer_zr_5::MPISharedArray{mk_float,2} + implicit_buffer_zr_6::MPISharedArray{mk_float,2} + # buffers to hold electron for implicit solves + implicit_buffer_vpavperpzr_1::MPISharedArray{mk_float,4} + implicit_buffer_vpavperpzr_2::MPISharedArray{mk_float,4} + implicit_buffer_vpavperpzr_3::MPISharedArray{mk_float,4} + implicit_buffer_vpavperpzr_4::MPISharedArray{mk_float,4} + implicit_buffer_vpavperpzr_5::MPISharedArray{mk_float,4} + implicit_buffer_vpavperpzr_6::MPISharedArray{mk_float,4} # buffers to hold ion pdf for implicit solves implicit_buffer_vpavperpzrs_1::MPISharedArray{mk_float,5} implicit_buffer_vpavperpzrs_2::MPISharedArray{mk_float,5} @@ -145,7 +175,7 @@ struct scratch_dummy_arrays implicit_buffer_vpavperpzrs_4::MPISharedArray{mk_float,5} implicit_buffer_vpavperpzrs_5::MPISharedArray{mk_float,5} implicit_buffer_vpavperpzrs_6::MPISharedArray{mk_float,5} - + buffer_vzvrvzetazsn_1::MPISharedArray{mk_float,5} buffer_vzvrvzetazsn_2::MPISharedArray{mk_float,5} buffer_vzvrvzetazsn_3::MPISharedArray{mk_float,5} @@ -169,6 +199,19 @@ struct scratch_dummy_arrays buffer_vpavperp_2::MPISharedArray{mk_float,2} buffer_vpavperp_3::MPISharedArray{mk_float,2} + buffer_vpavperpzr_1::MPISharedArray{mk_float,4} + buffer_vpavperpzr_2::MPISharedArray{mk_float,4} + buffer_vpavperpzr_3::MPISharedArray{mk_float,4} + buffer_vpavperpzr_4::MPISharedArray{mk_float,4} + buffer_vpavperpzr_5::MPISharedArray{mk_float,4} + buffer_vpavperpzr_6::MPISharedArray{mk_float,4} + + buffer_vpavperpr_1::MPISharedArray{mk_float,3} + buffer_vpavperpr_2::MPISharedArray{mk_float,3} + buffer_vpavperpr_3::MPISharedArray{mk_float,3} + buffer_vpavperpr_4::MPISharedArray{mk_float,3} + buffer_vpavperpr_5::MPISharedArray{mk_float,3} + buffer_vpavperpr_6::MPISharedArray{mk_float,3} int_buffer_rs_1::MPISharedArray{mk_int,2} int_buffer_rs_2::MPISharedArray{mk_int,2} end @@ -178,6 +221,8 @@ struct advect_object_struct vperp_advect::Vector{advection_info{4,5}} z_advect::Vector{advection_info{4,5}} r_advect::Vector{advection_info{4,5}} + electron_z_advect::Vector{advection_info{4,5}} + electron_vpa_advect::Vector{advection_info{4,5}} neutral_z_advect::Vector{advection_info{5,6}} neutral_r_advect::Vector{advection_info{5,6}} neutral_vz_advect::Vector{advection_info{5,6}} @@ -223,6 +268,19 @@ function allocate_advection_structs(composition, z, r, vpa, vperp, vz, vr, vzeta # with advection in vperp begin_serial_region() vperp_advect = setup_advection(n_ion_species, vperp, vpa, z, r) + ## ## + # electron particle advection structs # + ## ## + # create structure electron_z_advect whose members are the arrays needed to compute + # the advection term(s) appearing in the part of the electron kinetic equation dealing + # with advection in z + begin_serial_region() + electron_z_advect = setup_advection(1, z, vpa, vperp, r) + # create structure vpa_advect whose members are the arrays needed to compute + # the advection term(s) appearing in the part of the electron kinetic equation dealing + # with advection in vpa + begin_serial_region() + electron_vpa_advect = setup_advection(1, vpa, vperp, z, r) ## ## # neutral particle advection structs # ## ## @@ -239,6 +297,7 @@ function allocate_advection_structs(composition, z, r, vpa, vperp, vz, vr, vzeta # construct named list of advection structs to compactify arguments # ## ## advection_structs = advect_object_struct(vpa_advect, vperp_advect, z_advect, r_advect, + electron_z_advect, electron_vpa_advect, neutral_z_advect, neutral_r_advect, neutral_vz_advect) return advection_structs end @@ -247,9 +306,13 @@ end setup_time_info(t_input; electrons=nothing) Create a [`input_structs.time_info`](@ref) struct using the settings in `t_input`. + +If something is passed in `electron`, it is stored in the `electron_t_params` member of +the returned `time_info`. """ function setup_time_info(t_input, n_variables, code_time, dt_reload, - dt_before_last_fail_reload, manufactured_solns_input, io_input) + dt_before_last_fail_reload, composition, + manufactured_solns_input, io_input, input_dict; electron=nothing) rk_coefs, rk_coefs_implicit, implicit_coefficient_is_zero, n_rk_stages, rk_order, adaptive, low_storage, CFL_prefactor = setup_runge_kutta_coefficients!(t_input["type"], @@ -260,21 +323,41 @@ function setup_time_info(t_input, n_variables, code_time, dt_reload, # No adaptive timestep, want to use the value from the input file even when we are # restarting dt_reload = nothing + + # Makes no sense to use write_error_diagnostics because non-adaptive schemes have + # no error estimate + input_dict["write_error_diagnostics"] = false end + if adaptive && t_input["write_error_diagnostics"] && !t_input["write_after_fixed_step_count"] + println("WARNING: using adaptive timestepping, so short, random-length timesteps " + * "before output is written will make diagnostics from " + * "`write_error_diagnostics=true` hard to interpret. If these " + * "diagnostics are important, suggest using " + * "`write_after_fixed_step_count=true`.") + end + + t_shared = allocate_shared_float(1) dt_shared = allocate_shared_float(1) previous_dt_shared = allocate_shared_float(1) next_output_time = allocate_shared_float(1) dt_before_output = allocate_shared_float(1) dt_before_last_fail = allocate_shared_float(1) - step_to_output = allocate_shared_bool(1) + step_to_moments_output = allocate_shared_bool(1) + step_to_dfns_output = allocate_shared_bool(1) + write_moments_output = allocate_shared_bool(1) + write_dfns_output = allocate_shared_bool(1) if block_rank[] == 0 + t_shared[] = code_time dt_shared[] = dt_reload === nothing ? t_input["dt"] : dt_reload previous_dt_shared[] = dt_reload === nothing ? t_input["dt"] : dt_reload next_output_time[] = 0.0 dt_before_output[] = dt_reload === nothing ? t_input["dt"] : dt_reload dt_before_last_fail[] = dt_before_last_fail_reload === nothing ? Inf : dt_before_last_fail_reload - step_to_output[] = false + step_to_moments_output[] = false + step_to_dfns_output[] = false + write_moments_output[] = false + write_dfns_output[] = false end _block_synchronize() @@ -307,8 +390,26 @@ function setup_time_info(t_input, n_variables, code_time, dt_reload, if rk_coefs_implicit === nothing # Not an IMEX scheme, so cannot have any implicit terms + t_input["implicit_braginskii_conduction"] = false + t_input["implicit_electron_advance"] = false t_input["implicit_ion_advance"] = false t_input["implicit_vpa_advection"] = false + t_input["implicit_electron_ppar"] = false + else + if composition.electron_physics != braginskii_fluid + t_input["implicit_braginskii_conduction"] = false + end + if composition.electron_physics ∉ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + t_input["implicit_electron_advance"] = false + t_input["implicit_electron_ppar"] = false + end + end + + if t_input["implicit_vpa_advection"] + error("implicit_vpa_advection does not work at the moment. Need to figure out " + * "what to do with constraints, as explicit and implicit parts would not " + * "preserve constaints separately.") end if t_input["high_precision_error_sum"] @@ -316,22 +417,50 @@ function setup_time_info(t_input, n_variables, code_time, dt_reload, else error_sum_zero = 0.0 end - return time_info(n_variables, t_input["nstep"], end_time, dt_shared, previous_dt_shared, - next_output_time, dt_before_output, dt_before_last_fail, - CFL_prefactor, step_to_output, Ref(0), Ref(0), mk_int[], mk_int[], - t_input["nwrite"], t_input["nwrite_dfns"], moments_output_times, - dfns_output_times, t_input["type"], rk_coefs, rk_coefs_implicit, + if electron === nothing + # Setting up time_info for electrons. + # Store io_input as the debug_io variable so we can use it to open the debug + # output file. + if t_input["debug_io"] !== false + if !isa(t_input["debug_io"], mk_int) + error("`debug_io` input should be an integer, giving the number of steps " + * "between writes, if it is passed") + end + debug_io = (io_input, input_dict, t_input["debug_io"]) + else + debug_io = nothing + end + electron_t_params = nothing + elseif electron === false + debug_io = nothing + electron_t_params = nothing + else + debug_io = nothing + electron_t_params = electron + end + return time_info(n_variables, t_input["nstep"], end_time, t_shared, dt_shared, + previous_dt_shared, next_output_time, dt_before_output, + dt_before_last_fail, CFL_prefactor, step_to_moments_output, + step_to_dfns_output, write_moments_output, write_dfns_output, Ref(0), + Ref(0), Ref(0), Ref(0), mk_int[], mk_int[], t_input["nwrite"], + t_input["nwrite_dfns"], moments_output_times, dfns_output_times, + t_input["type"], rk_coefs, rk_coefs_implicit, implicit_coefficient_is_zero, n_rk_stages, rk_order, adaptive, low_storage, t_input["rtol"], t_input["atol"], t_input["atol_upar"], t_input["step_update_prefactor"], t_input["max_increase_factor"], t_input["max_increase_factor_near_last_fail"], t_input["last_fail_proximity_factor"], t_input["minimum_dt"], - t_input["maximum_dt"], t_input["implicit_ion_advance"], - t_input["implicit_vpa_advection"], + t_input["maximum_dt"], + electron !== nothing && t_input["implicit_braginskii_conduction"], + electron !== nothing && t_input["implicit_electron_advance"], + electron !== nothing && t_input["implicit_ion_advance"], + electron !== nothing && t_input["implicit_vpa_advection"], + electron !== nothing && t_input["implicit_electron_ppar"], t_input["write_after_fixed_step_count"], error_sum_zero, t_input["split_operators"], t_input["steady_state_residual"], t_input["converged_residual_value"], - manufactured_solns_input.use_for_advance, t_input["stopfile_name"]) + manufactured_solns_input.use_for_advance, t_input["stopfile_name"], + debug_io, electron_t_params) end """ @@ -345,16 +474,56 @@ function setup_time_advance!(pdf, fields, vz, vr, vzeta, vpa, vperp, z, r, gyrop vz_spectral, vr_spectral, vzeta_spectral, vpa_spectral, vperp_spectral, z_spectral, r_spectral, composition, moments, t_input, code_time, dt_reload, - dt_before_last_fail_reload, collisions, species, geometry, - boundary_distributions, external_source_settings, - num_diss_params, manufactured_solns_input, advection_structs, - scratch_dummy, restarting, input_dict) + dt_before_last_fail_reload, electron_dt_reload, + electron_dt_before_last_fail_reload, collisions, species, + geometry, boundary_distributions, external_source_settings, + num_diss_params, manufactured_solns_input, + advection_structs, io_input, restarting, + restart_electron_physics, input_dict) # define some local variables for convenience/tidiness n_ion_species = composition.n_ion_species n_neutral_species = composition.n_neutral_species ion_mom_diss_coeff = num_diss_params.ion.moment_dissipation_coefficient + electron_mom_diss_coeff = num_diss_params.electron.moment_dissipation_coefficient neutral_mom_diss_coeff = num_diss_params.neutral.moment_dissipation_coefficient + if composition.electron_physics != restart_electron_physics + + # When restarting from a different electron physics type, and + # using an adaptive timestep, do not want to keep the `dt` from the previous + # simulation, in case the new electron physics requires a smaller ion timestep. + dt_reload = nothing + end + + if composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + electron_t_params = setup_time_info(t_input["electron_t_input"], 2, 0.0, + electron_dt_reload, + electron_dt_before_last_fail_reload, + composition, manufactured_solns_input, + io_input, input_dict) + # Make Vectors that count which variable caused timestep limits and timestep failures + # the right length. Do this setup even when not using adaptive timestepping, because + # it is easier than modifying the file I/O according to whether we are using adaptive + # timestepping. + # + # Entries for limit by accuracy (which is an average over all variables), + # max_increase_factor, max_increase_factor_near_last_fail, minimum_dt, maximum_dt + # and high_nl_iterations. + push!(electron_t_params.limit_caused_by, 0, 0, 0, 0, 0) + + # electron pdf + push!(electron_t_params.limit_caused_by, 0, 0, 0) # RK accuracy plus 2 CFL limits + push!(electron_t_params.failure_caused_by, 0) + + # electron ppar + push!(electron_t_params.limit_caused_by, 0) # RK accuracy + push!(electron_t_params.failure_caused_by, 0) + else + # Pass `false` rather than `nothing` to `setup_time_info()` call for ions, which + # indicates that 'debug_io' should never be set up for ions. + electron_t_params = false + end n_variables = 1 # pdf if moments.evolve_density # ion density @@ -368,6 +537,11 @@ function setup_time_advance!(pdf, fields, vz, vr, vzeta, vpa, vperp, z, r, gyrop # ion pressure n_variables += 1 end + if composition.electron_physics ∈ (braginskii_fluid, kinetic_electrons, + kinetic_electrons_with_temperature_equation) + # electron pressure + n_variables += 1 + end if composition.n_neutral_species > 0 # neutral pdf n_variables += 1 @@ -385,8 +559,9 @@ function setup_time_advance!(pdf, fields, vz, vr, vzeta, vpa, vperp, z, r, gyrop end end t_params = setup_time_info(t_input, n_variables, code_time, dt_reload, - dt_before_last_fail_reload, manufactured_solns_input, - io_input) + dt_before_last_fail_reload, composition, + manufactured_solns_input, io_input, input_dict; + electron=electron_t_params) # Make Vectors that count which variable caused timestep limits and timestep failures # the right length. Do this setup even when not using adaptive timestepping, because @@ -421,6 +596,16 @@ function setup_time_advance!(pdf, fields, vz, vr, vzeta, vpa, vperp, z, r, gyrop push!(t_params.limit_caused_by, 0) # RK accuracy push!(t_params.failure_caused_by, 0) end + if composition.electron_physics ∈ (braginskii_fluid, kinetic_electrons, + kinetic_electrons_with_temperature_equation) + # electron pressure + push!(t_params.limit_caused_by, 0) # RK accuracy + push!(t_params.failure_caused_by, 0) # RK accuracy for electron_ppar + if composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + push!(t_params.failure_caused_by, 0) # Convergence failure for kinetic electron solve + end + end if composition.n_neutral_species > 0 # neutral pdf push!(t_params.limit_caused_by, 0, 0, 0) # RK accuracy plus 2 CFL limits @@ -471,6 +656,26 @@ function setup_time_advance!(pdf, fields, vz, vr, vzeta, vpa, vperp, z, r, gyrop # Set up parameters for Jacobian-free Newton-Krylov solver used for implicit part of # timesteps. + if t_params.implicit_braginskii_conduction + # Should really have options to set solver tolerance, etc. + electron_conduction_nl_solve_parameters = setup_nonlinear_solve(input_dict, (z=z,); + default_rtol=t_params.rtol / 10.0, + default_atol=t_params.atol / 10.0) + else + electron_conduction_nl_solve_parameters = nothing + end + if t_params.implicit_electron_advance + nl_solver_electron_advance_params = + setup_nonlinear_solve(input_dict, + (r=r, z=z, vperp=vperp, vpa=vpa), + (); + default_rtol=t_params.rtol / 10.0, + default_atol=t_params.atol / 10.0, + electron_ppar_pdf_solve=true, + preconditioner_type="lu") + else + nl_solver_electron_advance_params = nothing + end if t_params.implicit_ion_advance # Implicit solve for vpa_advection term should be done in serial, as it will be # called within a parallelised s_r_z_vperp loop. @@ -502,22 +707,37 @@ function setup_time_advance!(pdf, fields, vz, vr, vzeta, vpa, vperp, z, r, gyrop error("Cannot use implicit_ion_advance and implicit_vpa_advection at the same " * "time") end - nl_solver_params = (ion_advance=nl_solver_ion_advance_params, + if nl_solver_electron_advance_params !== nothing && t_params.implicit_electron_ppar + error("Cannot use implicit_electron_advance and implicit_electron_ppar at the " + * "same time.") + end + nl_solver_params = (electron_conduction=electron_conduction_nl_solve_parameters, + electron_advance=nl_solver_electron_advance_params, + ion_advance=nl_solver_ion_advance_params, vpa_advection=nl_solver_vpa_advection_params,) begin_serial_region() # create an array of structs containing scratch arrays for the pdf and low-order moments # that may be evolved separately via fluid equations - n_rk_stages = t_params.n_rk_stages - scratch = setup_scratch_arrays(moments, pdf, n_rk_stages + 1) + scratch = setup_scratch_arrays(moments, pdf, t_params.n_rk_stages + 1) if t_params.rk_coefs_implicit !== nothing - scratch_implicit = setup_scratch_arrays(moments, pdf, n_rk_stages) + scratch_implicit = setup_scratch_arrays(moments, pdf, t_params.n_rk_stages) else scratch_implicit = nothing end + if composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + scratch_electron = setup_electron_scratch_arrays(moments, pdf, + t_params.electron.n_rk_stages+1) + else + scratch_electron = nothing + end # setup dummy arrays & buffer arrays for z r MPI n_neutral_species_alloc = max(1,composition.n_neutral_species) + scratch_dummy = setup_dummy_and_buffer_arrays(r.n, z.n, vpa.n, vperp.n, vz.n, vr.n, + vzeta.n, composition.n_ion_species, + n_neutral_species_alloc, t_params) # create arrays for Fokker-Planck collisions if advance.explicit_weakform_fp_collisions fp_arrays = init_fokker_planck_collisions_weak_form(vpa,vperp,vpa_spectral,vperp_spectral; precompute_weights=true) @@ -527,9 +747,60 @@ function setup_time_advance!(pdf, fields, vz, vr, vzeta, vpa, vperp, z, r, gyrop # create gyroaverage matrix arrays gyroavs = init_gyro_operators(vperp,z,r,gyrophase,geometry,composition) + # Now that `t_params` and `scratch` have been created, initialize electrons if + # necessary + if restarting && + composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) && + restart_electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + if t_params.electron.debug_io !== nothing + # Create *.electron_debug.h5 file so that it can be re-opened in + # update_electron_pdf!(). + io_electron = setup_electron_io(t_params.electron.debug_io[1], vpa, vperp, z, r, + composition, collisions, moments.evolve_density, + moments.evolve_upar, moments.evolve_ppar, + external_source_settings, t_params.electron, + t_params.electron.debug_io[2], -1, nothing, + "electron_debug") + end + + # No need to do electron I/O (apart from possibly debug I/O) any more, so if + # adaptive timestep is used, it does not need to adjust to output times. + resize!(t_params.electron.moments_output_times, 0) + resize!(t_params.electron.dfns_output_times, 0) + t_params.electron.moments_output_counter[] = 1 + t_params.electron.dfns_output_counter[] = 1 + elseif composition.electron_physics != restart_electron_physics + begin_serial_region() + @serial_region begin + # zero-initialise phi here, because the boundary points of phi are used as an + # effective 'cache' for the sheath-boundary cutoff speed for the electrons, so + # needs to be initialised to something, but phi cannot be calculated properly + # until after the electrons are initialised. + fields.phi .= 0.0 + end + initialize_electrons!(pdf, moments, fields, geometry, composition, r, z, + vperp, vpa, vzeta, vr, vz, z_spectral, r_spectral, + vperp_spectral, vpa_spectral, collisions, gyroavs, + external_source_settings, scratch_dummy, scratch, + scratch_electron, nl_solver_params, t_params, t_input, + num_diss_params, advection_structs, io_input, input_dict; + restart_electron_physics=restart_electron_physics) + end + + # update the derivatives of the electron moments as these may be needed when + # computing the electrostatic potential (and components of the electric field) + calculate_electron_moment_derivatives!(moments, scratch[1], scratch_dummy, z, z_spectral, + electron_mom_diss_coeff, composition.electron_physics) + # calculate the electron-ion parallel friction force + calculate_electron_parallel_friction_force!(moments.electron.parallel_friction, moments.electron.dens, + moments.electron.upar, moments.ion.upar, moments.electron.dT_dz, + composition.me_over_mi, collisions.nu_ei, composition.electron_physics) # initialize the electrostatic potential begin_serial_region() - update_phi!(fields, scratch[1], vperp, z, r, composition, z_spectral, r_spectral, scratch_dummy, gyroavs) + update_phi!(fields, scratch[1], vperp, z, r, composition, collisions, moments, + geometry, z_spectral, r_spectral, scratch_dummy, gyroavs) @serial_region begin # save the initial phi(z) for possible use later (e.g., if forcing phi) fields.phi0 .= fields.phi @@ -708,14 +979,73 @@ function setup_time_advance!(pdf, fields, vz, vr, vzeta, vpa, vperp, z, r, gyrop composition) end - # update scratch arrays in case they were affected by applying boundary conditions - # or constraints to the pdf + # Update scratch arrays in case they were affected by applying boundary conditions + # or constraints to the pdf. + # Also update scratch[t_params.n_rk_stages+1] as this will be used for the I/O at + # the initial time. begin_s_r_z_region() @loop_s_r_z is ir iz begin scratch[1].pdf[:,:,iz,ir,is] .= pdf.ion.norm[:,:,iz,ir,is] scratch[1].density[iz,ir,is] = moments.ion.dens[iz,ir,is] scratch[1].upar[iz,ir,is] = moments.ion.upar[iz,ir,is] scratch[1].ppar[iz,ir,is] = moments.ion.ppar[iz,ir,is] + scratch[1].pperp[iz,ir,is] = moments.ion.pperp[iz,ir,is] + scratch[t_params.n_rk_stages+1].pdf[:,:,iz,ir,is] .= pdf.ion.norm[:,:,iz,ir,is] + scratch[t_params.n_rk_stages+1].density[iz,ir,is] = moments.ion.dens[iz,ir,is] + scratch[t_params.n_rk_stages+1].upar[iz,ir,is] = moments.ion.upar[iz,ir,is] + scratch[t_params.n_rk_stages+1].ppar[iz,ir,is] = moments.ion.ppar[iz,ir,is] + scratch[t_params.n_rk_stages+1].pperp[iz,ir,is] = moments.ion.pperp[iz,ir,is] + end + + # update the electron density, parallel flow and parallel pressure (and temperature) + # in case the corresponding ion quantities have been changed by applying + # constraints to the ion pdf + calculate_electron_density!(moments.electron.dens, moments.electron.dens_updated, moments.ion.dens) + calculate_electron_upar_from_charge_conservation!(moments.electron.upar, moments.electron.upar_updated, + moments.electron.dens, moments.ion.upar, moments.ion.dens, + composition.electron_physics, r, z) + begin_serial_region() + # compute the updated electron temperature + # NB: not currently necessary, as initial vth is not directly dependent on ion quantities + @serial_region begin + @. moments.electron.temp = composition.me_over_mi * moments.electron.vth^2 + end + # as the electron temperature has now been updated, set the appropriate flag + moments.electron.temp_updated[] = true + # compute the updated electron parallel pressure + @serial_region begin + @. moments.electron.ppar = 0.5 * moments.electron.dens * moments.electron.temp + end + # as the electron ppar has now been updated, set the appropriate flag + moments.electron.ppar_updated[] = true + # calculate the zed derivative of the initial electron temperature, potentially + # needed in the following calculation of the electron parallel friction force and + # parallel heat flux + @views derivative_z!(moments.electron.dT_dz, moments.electron.temp, + scratch_dummy.buffer_rs_1[:,1], scratch_dummy.buffer_rs_2[:,1], scratch_dummy.buffer_rs_3[:,1], + scratch_dummy.buffer_rs_4[:,1], z_spectral, z) + # calculate the electron parallel heat flux + calculate_electron_qpar!(moments.electron, pdf.electron, moments.electron.ppar, + moments.electron.upar, moments.ion.upar, collisions.nu_ei, + composition.me_over_mi, composition.electron_physics, vpa) + if composition.electron_physics == braginskii_fluid + electron_fluid_qpar_boundary_condition!( + moments.electron.ppar, moments.electron.upar, moments.electron.dens, + moments.electron, z) + end + # Update the electron moment entries in the scratch array. + # Also update scratch[t_params.n_rk_stages+1] as this will be used for the I/O at + # the initial time. + begin_r_z_region() + @loop_r_z ir iz begin + scratch[1].electron_density[iz,ir] = moments.electron.dens[iz,ir] + scratch[1].electron_upar[iz,ir] = moments.electron.upar[iz,ir] + scratch[1].electron_ppar[iz,ir] = moments.electron.ppar[iz,ir] + scratch[1].electron_temp[iz,ir] = moments.electron.temp[iz,ir] + scratch[t_params.n_rk_stages+1].electron_density[iz,ir] = moments.electron.dens[iz,ir] + scratch[t_params.n_rk_stages+1].electron_upar[iz,ir] = moments.electron.upar[iz,ir] + scratch[t_params.n_rk_stages+1].electron_ppar[iz,ir] = moments.electron.ppar[iz,ir] + scratch[t_params.n_rk_stages+1].electron_temp[iz,ir] = moments.electron.temp[iz,ir] end begin_sn_r_z_region(no_synchronize=true) @@ -724,22 +1054,34 @@ function setup_time_advance!(pdf, fields, vz, vr, vzeta, vpa, vperp, z, r, gyrop scratch[1].density_neutral[iz,ir,isn] = moments.neutral.dens[iz,ir,isn] scratch[1].uz_neutral[iz,ir,isn] = moments.neutral.uz[iz,ir,isn] scratch[1].pz_neutral[iz,ir,isn] = moments.neutral.pz[iz,ir,isn] + scratch[t_params.n_rk_stages+1].pdf_neutral[:,:,:,iz,ir,isn] .= pdf.neutral.norm[:,:,:,iz,ir,isn] + scratch[t_params.n_rk_stages+1].density_neutral[iz,ir,isn] = moments.neutral.dens[iz,ir,isn] + scratch[t_params.n_rk_stages+1].uz_neutral[iz,ir,isn] = moments.neutral.uz[iz,ir,isn] + scratch[t_params.n_rk_stages+1].pz_neutral[iz,ir,isn] = moments.neutral.pz[iz,ir,isn] end end + # calculate the electron-ion parallel friction force + calculate_electron_parallel_friction_force!(moments.electron.parallel_friction, moments.electron.dens, + moments.electron.upar, moments.ion.upar, moments.electron.dT_dz, + composition.me_over_mi, collisions.nu_ei, composition.electron_physics) - update_phi!(fields, scratch[1], vperp, z, r, composition, z_spectral, r_spectral, - scratch_dummy, gyroavs) calculate_ion_moment_derivatives!(moments, scratch[1], scratch_dummy, z, z_spectral, ion_mom_diss_coeff) + calculate_electron_moment_derivatives!(moments, scratch[1], scratch_dummy, z, z_spectral, + electron_mom_diss_coeff, composition.electron_physics) calculate_neutral_moment_derivatives!(moments, scratch[1], scratch_dummy, z, z_spectral, neutral_mom_diss_coeff) + # update the electrostatic potential and components of the electric field, as pdfs and moments + # may have changed due to enforcing boundary/moment constraints + update_phi!(fields, scratch[1], vperp, z, r, composition, collisions, moments, + geometry, z_spectral, r_spectral, scratch_dummy, gyroavs) # Ensure all processes are synchronized at the end of the setup _block_synchronize() - return moments, spectral_objects, scratch, scratch_implicit, advance, - advance_implicit, t_params, fp_arrays, gyroavs, manufactured_source_list, - nl_solver_params + return moments, spectral_objects, scratch, scratch_implicit, scratch_electron, + scratch_dummy, advance, advance_implicit, t_params, fp_arrays, gyroavs, + manufactured_source_list, nl_solver_params end """ @@ -766,6 +1108,8 @@ function setup_advance_flags(moments, composition, t_params, collisions, advance_neutral_ionization_1V = false advance_ionization_source = false advance_krook_collisions_ii = false + advance_maxwell_diffusion_ii = false + advance_maxwell_diffusion_nn = false advance_external_source = false advance_ion_numerical_dissipation = false advance_neutral_numerical_dissipation = false @@ -773,6 +1117,8 @@ function setup_advance_flags(moments, composition, t_params, collisions, advance_continuity = false advance_force_balance = false advance_energy = false + advance_electron_energy = false + advance_electron_conduction = false advance_neutral_z_advection = false advance_neutral_r_advection = false advance_neutral_vz_advection = false @@ -792,8 +1138,8 @@ function setup_advance_flags(moments, composition, t_params, collisions, # default for non-split operators is to include both vpa and z advection together # If using an IMEX scheme and implicit vpa advection has been requested, then vpa # advection is not included in the explicit part of the timestep. - advance_vpa_advection = vpa.n > 1 && z.n > 1 && !(t_params.implicit_ion_advance || t_params.implicit_vpa_advection) - advance_vperp_advection = vperp.n > 1 && z.n > 1 && !t_params.implicit_ion_advance + advance_vpa_advection = vpa.n > 1 && !(t_params.implicit_ion_advance || t_params.implicit_vpa_advection) + advance_vperp_advection = vperp.n > 1 && !t_params.implicit_ion_advance advance_z_advection = z.n > 1 && !t_params.implicit_ion_advance advance_r_advection = r.n > 1 && !t_params.implicit_ion_advance if collisions.fkpl.nuii > 0.0 && vperp.n > 1 && !t_params.implicit_ion_advance @@ -814,7 +1160,7 @@ function setup_advance_flags(moments, composition, t_params, collisions, # if charge exchange collision frequency non-zero, # account for charge exchange collisions if abs(collisions.charge_exchange) > 0.0 - if vz.n == vpa.n && vperp.n == 1 && vr.n == 1 && vzeta.n == 1 + if vperp.n == 1 && vr.n == 1 && vzeta.n == 1 advance_ion_cx_1V = !t_params.implicit_ion_advance advance_neutral_cx_1V = true elseif vperp.n > 1 && vr.n > 1 && vzeta.n > 1 @@ -822,8 +1168,6 @@ function setup_advance_flags(moments, composition, t_params, collisions, advance_neutral_cx = true else error("If any perpendicular velocity has length>1 they all must. " - * "If all perpendicular velocities have length=1, then vpa and " - * "vz should be the same.\n" * "vperp.n=$(vperp.n), vr.n=$(vr.n), vzeta.n=$(vzeta.n), " * "vpa.n=$(vpa.n), vz.n=$(vz.n)") end @@ -831,7 +1175,7 @@ function setup_advance_flags(moments, composition, t_params, collisions, # if ionization collision frequency non-zero, # account for ionization collisions if abs(collisions.ionization) > 0.0 - if vz.n == vpa.n && vperp.n == 1 && vr.n == 1 && vzeta.n == 1 + if vperp.n == 1 && vr.n == 1 && vzeta.n == 1 advance_ion_ionization_1V = !t_params.implicit_ion_advance advance_neutral_ionization_1V = true elseif vperp.n > 1 && vr.n > 1 && vzeta.n > 1 @@ -839,8 +1183,6 @@ function setup_advance_flags(moments, composition, t_params, collisions, advance_neutral_ionization = true else error("If any perpendicular velocity has length>1 they all must. " - * "If all perpendicular velocities have length=1, then vpa and " - * "vz should be the same.\n" * "vperp.n=$(vperp.n), vr.n=$(vr.n), vzeta.n=$(vzeta.n), " * "vpa.n=$(vpa.n), vz.n=$(vz.n)") end @@ -850,9 +1192,17 @@ function setup_advance_flags(moments, composition, t_params, collisions, if collisions.ionization > 0.0 && collisions.constant_ionization_rate && !t_params.implicit_ion_advance advance_ionization_source = true end + # set flags for krook and maxwell diffusion collisions, and negative coefficient + # in both cases (as usual) will mean not employing that operator (flag remains false) if collisions.krook.nuii0 > 0.0 advance_krook_collisions_ii = !t_params.implicit_ion_advance end + if collisions.mxwl_diff.D_ii > 0.0 + advance_maxwell_diffusion_ii = true + end + if collisions.mxwl_diff.D_nn > 0.0 + advance_maxwell_diffusion_nn = true + end advance_external_source = external_source_settings.ion.active && !t_params.implicit_ion_advance advance_neutral_external_source = external_source_settings.neutral.active advance_ion_numerical_dissipation = !(t_params.implicit_ion_advance || t_params.implicit_vpa_advection) @@ -890,6 +1240,27 @@ function setup_advance_flags(moments, composition, t_params, collisions, advance_neutral_energy = true end end + # if treating the electrons as a fluid with Braginskii closure, or + # moment-kinetically then advance the electron energy equation + if composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + if !(t_params.implicit_electron_advance || t_params.implicit_electron_ppar) + advance_electron_energy = true + advance_electron_conduction = true + end + elseif composition.electron_physics == braginskii_fluid + if t_params.implicit_braginskii_conduction + # if treating the electrons as a fluid with Braginskii closure, and using + # an IMEX scheme, advance the conduction part of the electron energy + # equation implicitly. + advance_electron_energy = true + advance_electron_conduction = false + else + # If not using an IMEX scheme, treat the conduction explicitly. + advance_electron_energy = true + advance_electron_conduction = true + end + end # *_diffusion flags are set regardless of whether diffusion is included in explicit or # implicit part of timestep, because they are used for boundary conditions, not to @@ -900,9 +1271,9 @@ function setup_advance_flags(moments, composition, t_params, collisions, # flag to determine if a d^2/dvpa^2 operator is present # When using implicit_vpa_advection, the vpa diffusion is included in the implicit # step - vpa_diffusion = ((num_diss_params.ion.vpa_dissipation_coefficient > 0.0) || (collisions.fkpl.nuii > 0.0 && vperp.n > 1)) + vpa_diffusion = ((num_diss_params.ion.vpa_dissipation_coefficient > 0.0) || (collisions.fkpl.nuii > 0.0 && vperp.n > 1) || advance_maxwell_diffusion_ii) vperp_diffusion = ((num_diss_params.ion.vperp_dissipation_coefficient > 0.0) || (collisions.fkpl.nuii > 0.0 && vperp.n > 1)) - vz_diffusion = (num_diss_params.neutral.vz_dissipation_coefficient > 0.0) + vz_diffusion = (num_diss_params.neutral.vz_dissipation_coefficient > 0.0 || advance_maxwell_diffusion_nn) end manufactured_solns_test = manufactured_solns_input.use_for_advance @@ -914,14 +1285,17 @@ function setup_advance_flags(moments, composition, t_params, collisions, advance_neutral_ionization, advance_ion_ionization_1V, advance_neutral_ionization_1V, advance_ionization_source, advance_krook_collisions_ii, + advance_maxwell_diffusion_ii, advance_maxwell_diffusion_nn, explicit_weakform_fp_collisions, advance_external_source, advance_ion_numerical_dissipation, advance_neutral_numerical_dissipation, advance_sources, advance_continuity, advance_force_balance, advance_energy, - advance_neutral_external_source, advance_neutral_sources, - advance_neutral_continuity, advance_neutral_force_balance, - advance_neutral_energy, manufactured_solns_test, r_diffusion, - vpa_diffusion, vperp_diffusion, vz_diffusion) + advance_electron_energy, advance_electron_conduction, + advance_neutral_external_source, + advance_neutral_sources, advance_neutral_continuity, + advance_neutral_force_balance, advance_neutral_energy, + manufactured_solns_test, r_diffusion, vpa_diffusion, + vperp_diffusion, vz_diffusion) end """ @@ -948,6 +1322,8 @@ function setup_implicit_advance_flags(moments, composition, t_params, collisions advance_neutral_ionization_1V = false advance_ionization_source = false advance_krook_collisions_ii = false + advance_maxwell_diffusion_ii = false + advance_maxwell_diffusion_nn = false advance_external_source = false advance_ion_numerical_dissipation = false advance_neutral_numerical_dissipation = false @@ -955,6 +1331,8 @@ function setup_implicit_advance_flags(moments, composition, t_params, collisions advance_continuity = false advance_force_balance = false advance_energy = false + advance_electron_energy = false + advance_electron_conduction = false advance_neutral_z_advection = false advance_neutral_r_advection = false advance_neutral_vz_advection = false @@ -977,27 +1355,23 @@ function setup_implicit_advance_flags(moments, composition, t_params, collisions advance_z_advection = z.n > 1 advance_r_advection = r.n > 1 if abs(collisions.charge_exchange) > 0.0 - if vz.n == vpa.n && vperp.n == 1 && vr.n == 1 && vzeta.n == 1 + if vperp.n == 1 && vr.n == 1 && vzeta.n == 1 advance_ion_cx_1V = true elseif vperp.n > 1 && vr.n > 1 && vzeta.n > 1 advance_ion_cx = true else error("If any perpendicular velocity has length>1 they all must. " - * "If all perpendicular velocities have length=1, then vpa and " - * "vz should be the same.\n" * "vperp.n=$(vperp.n), vr.n=$(vr.n), vzeta.n=$(vzeta.n), " * "vpa.n=$(vpa.n), vz.n=$(vz.n)") end end if abs(collisions.ionization) > 0.0 - if vz.n == vpa.n && vperp.n == 1 && vr.n == 1 && vzeta.n == 1 + if vperp.n == 1 && vr.n == 1 && vzeta.n == 1 advance_ion_ionization_1V = true elseif vperp.n > 1 && vr.n > 1 && vzeta.n > 1 advance_ion_ionization = true else error("If any perpendicular velocity has length>1 they all must. " - * "If all perpendicular velocities have length=1, then vpa and " - * "vz should be the same.\n" * "vperp.n=$(vperp.n), vr.n=$(vr.n), vzeta.n=$(vzeta.n), " * "vpa.n=$(vpa.n), vz.n=$(vz.n)") end @@ -1025,6 +1399,18 @@ function setup_implicit_advance_flags(moments, composition, t_params, collisions vperp_diffusion = ((num_diss_params.ion.vperp_dissipation_coefficient > 0.0) || (collisions.fkpl.nuii > 0.0 && vperp.n > 1)) vz_diffusion = (num_diss_params.neutral.vz_dissipation_coefficient > 0.0) + if t_params.implicit_braginskii_conduction + # if treating the electrons as a fluid with Braginskii closure, and using an IMEX + # scheme, advance the conduction part of the electron energy equation implicitly. + advance_electron_energy = false + advance_electron_conduction = true + end + + if (t_params.implicit_electron_advance || t_params.implicit_electron_ppar) + advance_electron_energy = true + advance_electron_conduction = true + end + manufactured_solns_test = manufactured_solns_input.use_for_advance return advance_info(advance_vpa_advection, advance_vperp_advection, advance_z_advection, advance_r_advection, @@ -1034,17 +1420,20 @@ function setup_implicit_advance_flags(moments, composition, t_params, collisions advance_neutral_ionization, advance_ion_ionization_1V, advance_neutral_ionization_1V, advance_ionization_source, advance_krook_collisions_ii, + advance_maxwell_diffusion_ii, advance_maxwell_diffusion_nn, explicit_weakform_fp_collisions, advance_external_source, advance_ion_numerical_dissipation, advance_neutral_numerical_dissipation, advance_sources, advance_continuity, advance_force_balance, advance_energy, + advance_electron_energy, advance_electron_conduction, advance_neutral_external_source, advance_neutral_sources, advance_neutral_continuity, advance_neutral_force_balance, advance_neutral_energy, manufactured_solns_test, r_diffusion, vpa_diffusion, vperp_diffusion, vz_diffusion) end -function setup_dummy_and_buffer_arrays(nr,nz,nvpa,nvperp,nvz,nvr,nvzeta,nspecies_ion,nspecies_neutral) +function setup_dummy_and_buffer_arrays(nr, nz, nvpa, nvperp, nvz, nvr, nvzeta, + nspecies_ion, nspecies_neutral, t_params) dummy_s = allocate_float(nspecies_ion) dummy_sr = allocate_float(nr, nspecies_ion) @@ -1105,13 +1494,66 @@ function setup_dummy_and_buffer_arrays(nr,nz,nvpa,nvperp,nvz,nvr,nvzeta,nspecies buffer_vpavperpzrs_1 = allocate_shared_float(nvpa,nvperp,nz,nr,nspecies_ion) buffer_vpavperpzrs_2 = allocate_shared_float(nvpa,nvperp,nz,nr,nspecies_ion) - implicit_buffer_vpavperpzrs_1 = allocate_shared_float(nvpa,nvperp,nz,nr,nspecies_ion) - implicit_buffer_vpavperpzrs_2 = allocate_shared_float(nvpa,nvperp,nz,nr,nspecies_ion) - implicit_buffer_vpavperpzrs_3 = allocate_shared_float(nvpa,nvperp,nz,nr,nspecies_ion) - implicit_buffer_vpavperpzrs_4 = allocate_shared_float(nvpa,nvperp,nz,nr,nspecies_ion) - implicit_buffer_vpavperpzrs_5 = allocate_shared_float(nvpa,nvperp,nz,nr,nspecies_ion) - implicit_buffer_vpavperpzrs_6 = allocate_shared_float(nvpa,nvperp,nz,nr,nspecies_ion) + buffer_vpavperpzr_1 = allocate_shared_float(nvpa,nvperp,nz,nr) + buffer_vpavperpzr_2 = allocate_shared_float(nvpa,nvperp,nz,nr) + buffer_vpavperpzr_3 = allocate_shared_float(nvpa,nvperp,nz,nr) + buffer_vpavperpzr_4 = allocate_shared_float(nvpa,nvperp,nz,nr) + buffer_vpavperpzr_5 = allocate_shared_float(nvpa,nvperp,nz,nr) + buffer_vpavperpzr_6 = allocate_shared_float(nvpa,nvperp,nz,nr) + buffer_vpavperpr_1 = allocate_shared_float(nvpa,nvperp,nr) + buffer_vpavperpr_2 = allocate_shared_float(nvpa,nvperp,nr) + buffer_vpavperpr_3 = allocate_shared_float(nvpa,nvperp,nr) + buffer_vpavperpr_4 = allocate_shared_float(nvpa,nvperp,nr) + buffer_vpavperpr_5 = allocate_shared_float(nvpa,nvperp,nr) + buffer_vpavperpr_6 = allocate_shared_float(nvpa,nvperp,nr) + + if t_params.implicit_electron_advance + implicit_buffer_zr_1 = allocate_shared_float(nz,nr) + implicit_buffer_zr_2 = allocate_shared_float(nz,nr) + implicit_buffer_zr_3 = allocate_shared_float(nz,nr) + implicit_buffer_zr_4 = allocate_shared_float(nz,nr) + implicit_buffer_zr_5 = allocate_shared_float(nz,nr) + implicit_buffer_zr_6 = allocate_shared_float(nz,nr) + + implicit_buffer_vpavperpzr_1 = allocate_shared_float(nvpa,nvperp,nz,nr) + implicit_buffer_vpavperpzr_2 = allocate_shared_float(nvpa,nvperp,nz,nr) + implicit_buffer_vpavperpzr_3 = allocate_shared_float(nvpa,nvperp,nz,nr) + implicit_buffer_vpavperpzr_4 = allocate_shared_float(nvpa,nvperp,nz,nr) + implicit_buffer_vpavperpzr_5 = allocate_shared_float(nvpa,nvperp,nz,nr) + implicit_buffer_vpavperpzr_6 = allocate_shared_float(nvpa,nvperp,nz,nr) + else + implicit_buffer_zr_1 = allocate_shared_float(0,0) + implicit_buffer_zr_2 = allocate_shared_float(0,0) + implicit_buffer_zr_3 = allocate_shared_float(0,0) + implicit_buffer_zr_4 = allocate_shared_float(0,0) + implicit_buffer_zr_5 = allocate_shared_float(0,0) + implicit_buffer_zr_6 = allocate_shared_float(0,0) + + implicit_buffer_vpavperpzr_1 = allocate_shared_float(0,0,0,0) + implicit_buffer_vpavperpzr_2 = allocate_shared_float(0,0,0,0) + implicit_buffer_vpavperpzr_3 = allocate_shared_float(0,0,0,0) + implicit_buffer_vpavperpzr_4 = allocate_shared_float(0,0,0,0) + implicit_buffer_vpavperpzr_5 = allocate_shared_float(0,0,0,0) + implicit_buffer_vpavperpzr_6 = allocate_shared_float(0,0,0,0) + end + + if t_params.implicit_ion_advance + implicit_buffer_vpavperpzrs_1 = allocate_shared_float(nvpa,nvperp,nz,nr,nspecies_ion) + implicit_buffer_vpavperpzrs_2 = allocate_shared_float(nvpa,nvperp,nz,nr,nspecies_ion) + implicit_buffer_vpavperpzrs_3 = allocate_shared_float(nvpa,nvperp,nz,nr,nspecies_ion) + implicit_buffer_vpavperpzrs_4 = allocate_shared_float(nvpa,nvperp,nz,nr,nspecies_ion) + implicit_buffer_vpavperpzrs_5 = allocate_shared_float(nvpa,nvperp,nz,nr,nspecies_ion) + implicit_buffer_vpavperpzrs_6 = allocate_shared_float(nvpa,nvperp,nz,nr,nspecies_ion) + else + implicit_buffer_vpavperpzrs_1 = allocate_shared_float(0,0,0,0,0) + implicit_buffer_vpavperpzrs_2 = allocate_shared_float(0,0,0,0,0) + implicit_buffer_vpavperpzrs_3 = allocate_shared_float(0,0,0,0,0) + implicit_buffer_vpavperpzrs_4 = allocate_shared_float(0,0,0,0,0) + implicit_buffer_vpavperpzrs_5 = allocate_shared_float(0,0,0,0,0) + implicit_buffer_vpavperpzrs_6 = allocate_shared_float(0,0,0,0,0) + end + buffer_vzvrvzetazsn_1 = allocate_shared_float(nvz,nvr,nvzeta,nz,nspecies_neutral) buffer_vzvrvzetazsn_2 = allocate_shared_float(nvz,nvr,nvzeta,nz,nspecies_neutral) buffer_vzvrvzetazsn_3 = allocate_shared_float(nvz,nvr,nvzeta,nz,nspecies_neutral) @@ -1147,11 +1589,15 @@ function setup_dummy_and_buffer_arrays(nr,nz,nvpa,nvperp,nvz,nvr,nvzeta,nspecies buffer_vpavperpzs_1,buffer_vpavperpzs_2,buffer_vpavperpzs_3,buffer_vpavperpzs_4,buffer_vpavperpzs_5,buffer_vpavperpzs_6, buffer_vpavperprs_1,buffer_vpavperprs_2,buffer_vpavperprs_3,buffer_vpavperprs_4,buffer_vpavperprs_5,buffer_vpavperprs_6, buffer_vpavperpzrs_1,buffer_vpavperpzrs_2, + implicit_buffer_zr_1,implicit_buffer_zr_2,implicit_buffer_zr_3,implicit_buffer_zr_4,implicit_buffer_zr_5,implicit_buffer_zr_6, + implicit_buffer_vpavperpzr_1,implicit_buffer_vpavperpzr_2,implicit_buffer_vpavperpzr_3,implicit_buffer_vpavperpzr_4,implicit_buffer_vpavperpzr_5,implicit_buffer_vpavperpzr_6, implicit_buffer_vpavperpzrs_1,implicit_buffer_vpavperpzrs_2,implicit_buffer_vpavperpzrs_3,implicit_buffer_vpavperpzrs_4,implicit_buffer_vpavperpzrs_5,implicit_buffer_vpavperpzrs_6, buffer_vzvrvzetazsn_1,buffer_vzvrvzetazsn_2,buffer_vzvrvzetazsn_3,buffer_vzvrvzetazsn_4,buffer_vzvrvzetazsn_5,buffer_vzvrvzetazsn_6, buffer_vzvrvzetarsn_1,buffer_vzvrvzetarsn_2,buffer_vzvrvzetarsn_3,buffer_vzvrvzetarsn_4,buffer_vzvrvzetarsn_5,buffer_vzvrvzetarsn_6, buffer_vzvrvzetazrsn_1, buffer_vzvrvzetazrsn_2, buffer_vpavperp_1,buffer_vpavperp_2,buffer_vpavperp_3, + buffer_vpavperpzr_1, buffer_vpavperpzr_2,buffer_vpavperpzr_3,buffer_vpavperpzr_4,buffer_vpavperpzr_5,buffer_vpavperpzr_6, + buffer_vpavperpr_1, buffer_vpavperpr_2, buffer_vpavperpr_3, buffer_vpavperpr_4, buffer_vpavperpr_5, buffer_vpavperpr_6, int_buffer_rs_1,int_buffer_rs_2) end @@ -1185,14 +1631,16 @@ create an array of structs containing scratch arrays for the normalised pdf and that may be evolved separately via fluid equations """ function setup_scratch_arrays(moments, pdf, n) - # will create n_rk_stages+1 structs, each of which will contain one pdf, + # will create n structs, each of which will contain one pdf, # density, parallel flow, parallel pressure, and perpendicular pressure array for ions # (possibly) the same for electrons, and the same for neutrals. The actual array will # be created at the end of the first step of the loop below, once we have a # `scratch_pdf` object of the correct type. - scratch = Vector{scratch_pdf{5,3,6,3}}(undef, n) + scratch = Vector{scratch_pdf{5,3,2,6,3}}(undef, n) pdf_dims = size(pdf.ion.norm) moment_dims = size(moments.ion.dens) + moment_electron_dims = size(moments.electron.dens) + pdf_neutral_dims = size(pdf.neutral.norm) moment_neutral_dims = size(moments.neutral.dens) # populate each of the structs @@ -1204,7 +1652,13 @@ function setup_scratch_arrays(moments, pdf, n) upar_array = allocate_shared_float(moment_dims...) ppar_array = allocate_shared_float(moment_dims...) pperp_array = allocate_shared_float(moment_dims...) - temp_z_s_array = allocate_shared_float(moment_dims...) + temp_array = allocate_shared_float(moment_dims...) + + density_electron_array = allocate_shared_float(moment_electron_dims...) + upar_electron_array = allocate_shared_float(moment_electron_dims...) + ppar_electron_array = allocate_shared_float(moment_electron_dims...) + pperp_electron_array = allocate_shared_float(moment_electron_dims...) + temp_electron_array = allocate_shared_float(moment_electron_dims...) pdf_neutral_array = allocate_shared_float(pdf_neutral_dims...) density_neutral_array = allocate_shared_float(moment_neutral_dims...) @@ -1213,9 +1667,12 @@ function setup_scratch_arrays(moments, pdf, n) scratch[istage] = scratch_pdf(pdf_array, density_array, upar_array, - ppar_array, pperp_array, temp_z_s_array, - pdf_neutral_array, density_neutral_array, - uz_neutral_array, pz_neutral_array) + ppar_array, pperp_array, temp_array, + density_electron_array, upar_electron_array, + ppar_electron_array, pperp_electron_array, + temp_electron_array, pdf_neutral_array, + density_neutral_array, uz_neutral_array, + pz_neutral_array) @serial_region begin scratch[istage].pdf .= pdf.ion.norm scratch[istage].density .= moments.ion.dens @@ -1223,6 +1680,11 @@ function setup_scratch_arrays(moments, pdf, n) scratch[istage].ppar .= moments.ion.ppar scratch[istage].pperp .= moments.ion.pperp + scratch[istage].electron_density .= moments.electron.dens + scratch[istage].electron_upar .= moments.electron.upar + scratch[istage].electron_ppar .= moments.electron.ppar + scratch[istage].electron_pperp .= 0.0 #moments.electron.pperp + scratch[istage].pdf_neutral .= pdf.neutral.norm scratch[istage].density_neutral .= moments.neutral.dens scratch[istage].uz_neutral .= moments.neutral.uz @@ -1232,6 +1694,31 @@ function setup_scratch_arrays(moments, pdf, n) return scratch end +function setup_electron_scratch_arrays(moments, pdf, n) + # will create n structs, each of which will contain one pdf, and parallel pressure + # array for electrons. + # The actual array will be created at the end of the first step of the loop below, + # once we have a `scratch_electron_pdf` object of the correct type. + scratch = Vector{scratch_electron_pdf{4,2}}(undef, n) + pdf_dims = size(pdf.electron.norm) + moment_dims = size(moments.electron.dens) + + # populate each of the structs + for istage ∈ 1:n + # Allocate arrays in temporary variables so that we can identify them + # by source line when using @debug_shared_array + pdf_array = allocate_shared_float(pdf_dims...) + ppar_array = allocate_shared_float(moment_dims...) + + scratch[istage] = scratch_electron_pdf(pdf_array, ppar_array) + @serial_region begin + scratch[istage].pdf_electron .= pdf.electron.norm + scratch[istage].electron_ppar .= moments.electron.ppar + end + end + return scratch +end + """ solve ∂f/∂t + v(z,t)⋅∂f/∂z + dvpa/dt ⋅ ∂f/∂vpa= 0 define approximate characteristic velocity @@ -1240,13 +1727,13 @@ df/dt + δv⋅∂f/∂z = 0, with δv(z,t)=v(z,t)-v₀(z) for prudent choice of v₀, expect δv≪v so that explicit time integrator can be used without severe CFL condition """ -function time_advance!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzeta, vpa, - vperp, gyrophase, z, r, moments, fields, spectral_objects, - advect_objects, composition, collisions, geometry, gyroavs, - boundary_distributions, external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, fp_arrays, - scratch_dummy, manufactured_source_list, ascii_io, io_moments, - io_dfns) +function time_advance!(pdf, scratch, scratch_implicit, scratch_electron, t_params, vz, + vr, vzeta, vpa, vperp, gyrophase, z, r, moments, fields, + spectral_objects, advect_objects, composition, collisions, + geometry, gyroavs, boundary_distributions, + external_source_settings, num_diss_params, nl_solver_params, + advance, advance_implicit, fp_arrays, scratch_dummy, + manufactured_source_list, ascii_io, io_moments, io_dfns) @debug_detect_redundant_block_synchronize begin # Only want to check for redundant _block_synchronize() calls during the @@ -1277,23 +1764,11 @@ function time_advance!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzet start_time = now() epsilon = 1.e-11 - moments_output_counter = 1 - dfns_output_counter = 1 - @serial_region begin - if t_params.adaptive && !t_params.write_after_fixed_step_count - t_params.next_output_time[] = - min(t_params.moments_output_times[moments_output_counter], - t_params.dfns_output_times[dfns_output_counter]) - end - end - _block_synchronize() # main time advance loop - iwrite_moments = 2 - iwrite_dfns = 2 finish_now = false t_params.step_counter[] = 1 - if t ≥ t_params.end_time - epsilon + if t_params.t[] ≥ t_params.end_time - epsilon # User must have requested zero output steps, i.e. to just write out the initial # profiles return nothing @@ -1301,10 +1776,8 @@ function time_advance!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzet while true if t_params.adaptive && !t_params.write_after_fixed_step_count - maybe_write_moments = (t + t_params.dt[] ≥ t_params.moments_output_times[moments_output_counter] - epsilon - || t + t_params.dt[] ≥ t_params.end_time - epsilon) - maybe_write_dfns = (t + t_params.dt[] ≥ t_params.dfns_output_times[dfns_output_counter] - epsilon - || t + t_params.dt[] ≥ t_params.end_time - epsilon) + maybe_write_moments = t_params.step_to_moments_output[] + maybe_write_dfns = t_params.step_to_dfns_output[] else maybe_write_moments = (t_params.step_counter[] % t_params.nwrite_moments == 0 || t_params.step_counter[] >= t_params.nstep) @@ -1315,27 +1788,33 @@ function time_advance!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzet if t_params.split_operators # MRH NOT SUPPORTED - time_advance_split_operators!(pdf, scratch, scratch_implicit, t, t_params, - vpa, z, vpa_spectral, z_spectral, moments, - fields, vpa_advect, z_advect, composition, - collisions, external_source_settings, - num_diss_params, nl_solver_params, advance, - advance_implicit, t_params.step_counter[]) + time_advance_split_operators!(pdf, scratch, scratch_implicit, + scratch_electron, t_params, vpa, z, + vpa_spectral, z_spectral, moments, fields, + vpa_advect, z_advect, composition, collisions, + external_source_settings, num_diss_params, + nl_solver_params, advance, advance_implicit, + t_params.step_counter[]) else - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vz, - vr, vzeta, vpa, vperp, gyrophase, z, r, moments, - fields, spectral_objects, advect_objects, - composition, collisions, geometry, gyroavs, - boundary_distributions, external_source_settings, - num_diss_params, nl_solver_params, advance, - advance_implicit, fp_arrays, scratch_dummy, - manufactured_source_list, diagnostic_checks, - t_params.step_counter[]) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, scratch_electron, + t_params, vz, vr, vzeta, vpa, vperp, gyrophase, + z, r, moments, fields, spectral_objects, + advect_objects, composition, collisions, geometry, + gyroavs, boundary_distributions, + external_source_settings, num_diss_params, + nl_solver_params, advance, advance_implicit, + fp_arrays, scratch_dummy, manufactured_source_list, + diagnostic_checks, t_params.step_counter[]) end # update the time - t += t_params.previous_dt[] + @serial_region begin + t_params.t[] += t_params.previous_dt[] + end + _block_synchronize() - if t ≥ t_params.end_time - epsilon + if t_params.t[] ≥ t_params.end_time - epsilon || + (t_params.write_after_fixed_step_count && + t_params.step_counter[] >= t_params.nstep) # Ensure all output is written at the final step finish_now = true elseif t_params.dt[] < 0.0 || isnan(t_params.dt[]) || isinf(t_params.dt[]) @@ -1343,7 +1822,7 @@ function time_advance!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzet # write output. # t_params.dt[] should never be NaN or Inf, so if it is something has gone # wrong. - println("dt=", t_params.dt[], " at t=$t, terminating run.") + println("dt=", t_params.dt[], " at t=", t_params.t[], ", terminating run.") finish_now = true end @@ -1354,37 +1833,27 @@ function time_advance!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzet end if t_params.adaptive && !t_params.write_after_fixed_step_count - write_moments = (t ≥ t_params.moments_output_times[moments_output_counter] - epsilon - || t ≥ t_params.end_time - epsilon) - write_dfns = (t ≥ t_params.dfns_output_times[dfns_output_counter] - epsilon - || t ≥ t_params.end_time - epsilon) + write_moments = t_params.write_moments_output[] || finish_now + write_dfns = t_params.write_dfns_output[] || finish_now + + _block_synchronize() + @serial_region begin + t_params.write_moments_output[] = false + t_params.write_dfns_output[] = false + end else write_moments = (t_params.step_counter[] % t_params.nwrite_moments == 0 - || t_params.step_counter[] >= t_params.nstep) + || t_params.step_counter[] >= t_params.nstep + || finish_now) write_dfns = (t_params.step_counter[] % t_params.nwrite_dfns == 0 - || t_params.step_counter[] >= t_params.nstep) + || t_params.step_counter[] >= t_params.nstep + || finish_now) end if write_moments - moments_output_counter += 1 - if moments_output_counter ≤ length(t_params.moments_output_times) - @serial_region begin - t_params.next_output_time[] = - min(t_params.moments_output_times[moments_output_counter], - t_params.dfns_output_times[dfns_output_counter]) - end - end - write_moments = true + t_params.moments_output_counter[] += 1 end if write_dfns - dfns_output_counter += 1 - if dfns_output_counter ≤ length(t_params.dfns_output_times) - @serial_region begin - t_params.next_output_time[] = - min(t_params.moments_output_times[moments_output_counter], - t_params.dfns_output_times[dfns_output_counter]) - end - end - write_dfns = true + t_params.dfns_output_counter[] += 1 end if write_moments || write_dfns || finish_now @@ -1435,8 +1904,8 @@ function time_advance!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzet @serial_region begin if global_rank[] == 0 print("writing moments output ", - rpad(string(moments_output_counter - 1), 4), " ", - "t = ", rpad(string(round(t, sigdigits=6)), 7), " ", + rpad(string(t_params.moments_output_counter[]), 4), " ", + "t = ", rpad(string(round(t_params.t[], sigdigits=6)), 7), " ", "nstep = ", rpad(string(t_params.step_counter[]), 7), " ") if t_params.adaptive print("nfail = ", rpad(string(t_params.failure_counter[]), 7), " ", @@ -1445,13 +1914,13 @@ function time_advance!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzet print(Dates.format(now(), dateformat"H:MM:SS")) end end - write_data_to_ascii(pdf, moments, fields, vpa, vperp, z, r, t, + write_data_to_ascii(pdf, moments, fields, vpa, vperp, z, r, t_params.t[], composition.n_ion_species, composition.n_neutral_species, ascii_io) - write_all_moments_data_to_binary(moments, fields, t, + write_all_moments_data_to_binary(scratch, moments, fields, composition.n_ion_species, composition.n_neutral_species, io_moments, - iwrite_moments, time_for_run, t_params, + t_params.moments_output_counter[], time_for_run, t_params, nl_solver_params, r, z) if t_params.steady_state_residual @@ -1461,7 +1930,7 @@ function time_advance!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzet all_residuals = Vector{mk_float}() @loop_s is begin @views residual_ni = - steady_state_residuals(scratch[end].density[:,:,is], + steady_state_residuals(scratch[t_params.n_rk_stages+1].density[:,:,is], scratch[1].density[:,:,is], t_params.previous_dt[]; use_mpi=true, only_max_abs=true) if global_rank[] == 0 @@ -1474,7 +1943,7 @@ function time_advance!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzet if composition.n_neutral_species > 0 @loop_sn isn begin residual_nn = - steady_state_residuals(scratch[end].density_neutral[:,:,isn], + steady_state_residuals(scratch[t_params.n_rk_stages+1].density_neutral[:,:,isn], scratch[1].density_neutral[:,:,isn], t_params.previous_dt[]; use_mpi=true, only_max_abs=true) @@ -1508,7 +1977,6 @@ function time_advance!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzet end end - iwrite_moments += 1 begin_s_r_z_vperp_region() @debug_detect_redundant_block_synchronize begin # Reactivate check for redundant _block_synchronize() @@ -1525,20 +1993,19 @@ function time_advance!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzet @serial_region begin if global_rank[] == 0 println("writing distribution functions output ", - rpad(string(dfns_output_counter - 1), 4), " ", - "t = ", rpad(string(round(t, sigdigits=6)), 7), " ", + rpad(string(t_params.dfns_output_counter[]), 4), " ", + "t = ", rpad(string(round(t_params.t[], sigdigits=6)), 7), " ", "nstep = ", rpad(string(t_params.step_counter[]), 7), " ", Dates.format(now(), dateformat"H:MM:SS")) flush(stdout) end end - write_all_dfns_data_to_binary(pdf, moments, fields, t, + write_all_dfns_data_to_binary(scratch, scratch_electron, moments, fields, composition.n_ion_species, composition.n_neutral_species, io_dfns, - iwrite_dfns, time_for_run, t_params, - nl_solver_params, r, z, vperp, vpa, vzeta, vr, - vz) - iwrite_dfns += 1 + t_params.dfns_output_counter[], time_for_run, + t_params, nl_solver_params, r, z, vperp, vpa, + vzeta, vr, vz) begin_s_r_z_vperp_region() @debug_detect_redundant_block_synchronize begin # Reactivate check for redundant _block_synchronize() @@ -1546,11 +2013,29 @@ function time_advance!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzet end end + if t_params.previous_dt[] == 0.0 + # Timestep failed, so reset scratch[t_params.n_rk_stages+1] equal to + # scratch[1] to start the timestep over. + scratch_temp = scratch[t_params.n_rk_stages+1] + scratch[t_params.n_rk_stages+1] = scratch[1] + scratch[1] = scratch_temp + + # Re-update remaining velocity moments that are calculable from the evolved + # pdf These need to be re-calculated because `scratch[istage+1]` is now the + # state at the beginning of the timestep, because the timestep failed + apply_all_bcs_constraints_update_moments!( + scratch[t_params.n_rk_stages+1], pdf, moments, fields, nothing, nothing, vz, + vr, vzeta, vpa, vperp, z, r, spectral_objects, advect_objects, composition, + collisions, geometry, gyroavs, external_source_settings, num_diss_params, + t_params, advance, scratch_dummy, false; pdf_bc_constraints=false, + update_electrons=false) + end + if finish_now break end if t_params.adaptive - if t >= t_params.end_time - epsilon + if t_params.t[] >= t_params.end_time - epsilon break end else @@ -1566,11 +2051,12 @@ end """ """ -function time_advance_split_operators!(pdf, scratch, scratch_implicit, t, t_params, vpa, - z, vpa_spectral, z_spectral, moments, fields, - vpa_advect, z_advect, composition, collisions, - external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) +function time_advance_split_operators!(pdf, scratch, scratch_implicit, scratch_electron, + t_params, vpa, z, vpa_spectral, z_spectral, + moments, fields, vpa_advect, z_advect, composition, + collisions, external_source_settings, + num_diss_params, nl_solver_params, advance, + advance_implicit, istep) # define some abbreviated variables for tidiness n_ion_species = composition.n_ion_species @@ -1583,178 +2069,186 @@ function time_advance_split_operators!(pdf, scratch, scratch_implicit, t, t_para # advance the operator-split 1D advection equation in vpa # vpa-advection only applies for ion species advance.vpa_advection = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vpa, z, - vpa_spectral, z_spectral, moments, fields, vpa_advect, z_advect, - composition, collisions, external_source_settings, num_diss_params, + time_advance_no_splitting!(pdf, scratch, scratch_implicit, scratch_electron, + t_params, vpa, z, vpa_spectral, z_spectral, moments, fields, vpa_advect, + z_advect, composition, collisions, external_source_settings, num_diss_params, nl_solver_params, advance, advance_implicit, istep) advance.vpa_advection = false # z_advection! advances the operator-split 1D advection equation in z # apply z-advection operation to all species (ion and neutral) advance.z_advection = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vpa, z, - vpa_spectral, z_spectral, moments, fields, vpa_advect, z_advect, - composition, collisions, external_source_settings, num_diss_params, + time_advance_no_splitting!(pdf, scratch, scratch_implicit, scratch_electron, + t_params, vpa, z, vpa_spectral, z_spectral, moments, fields, vpa_advect, + z_advect, composition, collisions, external_source_settings, num_diss_params, nl_solver_params, advance, advance_implicit, istep) advance.z_advection = false # account for charge exchange collisions between ions and neutrals if composition.n_neutral_species > 0 if collisions.charge_exchange > 0.0 advance.ion_cx_collisions = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vpa, z, - vpa_spectral, z_spectral, moments, fields, vpa_advect, z_advect, - composition, collisions, external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, + scratch_electron, t_params, vpa, z, vpa_spectral, z_spectral, + moments, fields, vpa_advect, z_advect, composition, collisions, + external_source_settings, num_diss_params, nl_solver_params, advance, + advance_implicit, istep) advance.ion_cx_collisions = false advance.neutral_cx_collisions = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vpa, z, - vpa_spectral, z_spectral, moments, fields, vpa_advect, z_advect, - composition, collisions, external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, + scratch_electron, t_params, vpa, z, vpa_spectral, z_spectral, + moments, fields, vpa_advect, z_advect, composition, collisions, + external_source_settings, num_diss_params, nl_solver_params, advance, + advance_implicit, istep) advance.neutral_cx_collisions = false end if collisions.ionization > 0.0 advance.ion_ionization_collisions = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, z, vpa, - z_spectral, vpa_spectral, moments, fields, z_advect, vpa_advect, - composition, collisions, external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, + scratch_electron, t_params, z, vpa, z_spectral, vpa_spectral, + moments, fields, z_advect, vpa_advect, composition, collisions, + external_source_settings, num_diss_params, nl_solver_params, advance, + advance_implicit, istep) advance.ion_ionization_collisions = false advance.neutral_ionization_collisions = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, z, vpa, - z_spectral, vpa_spectral, moments, fields, z_advect, vpa_advect, - composition, collisions, external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, + scratch_electron, t_params, z, vpa, z_spectral, vpa_spectral, + moments, fields, z_advect, vpa_advect, composition, collisions, + external_source_settings, num_diss_params, nl_solver_params, advance, + advance_implicit, istep) advance.neutral_ionization_collisions = false end end if collisions.krook.nuii0 > 0.0 advance.krook_collisions_ii = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, z, vpa, - z_spectral, vpa_spectral, moments, fields, z_advect, vpa_advect, - z_SL, vpa_SL, composition, collisions, sources, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, scratch_electron, + t_params, z, vpa, z_spectral, vpa_spectral, moments, fields, z_advect, + vpa_advect, z_SL, vpa_SL, composition, collisions, sources, + num_diss_params, nl_solver_params, advance, advance_implicit, istep) advance.krook_collisions_ii = false end # and add the source terms associated with redefining g = pdf/density or pdf*vth/density # to the kinetic equation if moments.evolve_density || moments.evolve_upar || moments.evolve_ppar advance.source_terms = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vpa, z, - vpa_spectral, z_spectral, moments, fields, vpa_advect, z_advect, - composition, collisions, external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, scratch_electron, + t_params, vpa, z, vpa_spectral, z_spectral, moments, fields, + vpa_advect, z_advect, composition, collisions, external_source_settings, + num_diss_params, nl_solver_params, advance, advance_implicit, istep) advance.source_terms = false end # use the continuity equation to update the density if moments.evolve_density advance.continuity = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vpa, z, - vpa_spectral, z_spectral, moments, fields, vpa_advect, z_advect, - composition, collisions, external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, scratch_electron, + t_params, vpa, z, vpa_spectral, z_spectral, moments, fields, + vpa_advect, z_advect, composition, collisions, external_source_settings, + num_diss_params, nl_solver_params, advance, advance_implicit, istep) advance.continuity = false end # use force balance to update the parallel flow if moments.evolve_upar advance.force_balance = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vpa, z, - vpa_spectral, z_spectral, moments, fields, vpa_advect, z_advect, - composition, collisions, external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, scratch_electron, + t_params, vpa, z, vpa_spectral, z_spectral, moments, fields, + vpa_advect, z_advect, composition, collisions, external_source_settings, + num_diss_params, nl_solver_params, advance, advance_implicit, istep) advance.force_balance = false end # use the energy equation to update the parallel pressure if moments.evolve_ppar advance.energy = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vpa, z, - vpa_spectral, z_spectral, moments, fields, vpa_advect, z_advect, - composition, collisions, external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, scratch_electron, + t_params, vpa, z, vpa_spectral, z_spectral, moments, fields, + vpa_advect, z_advect, composition, collisions, external_source_settings, + num_diss_params, nl_solver_params, advance, advance_implicit, istep) advance.energy = false end else # use the energy equation to update the parallel pressure if moments.evolve_ppar advance.energy = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vpa, z, - vpa_spectral, z_spectral, moments, fields, vpa_advect, z_advect, - composition, collisions, external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, scratch_electron, + t_params, vpa, z, vpa_spectral, z_spectral, moments, fields, + vpa_advect, z_advect, composition, collisions, external_source_settings, + num_diss_params, nl_solver_params, advance, advance_implicit, istep) advance.energy = false end # use force balance to update the parallel flow if moments.evolve_upar advance.force_balance = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vpa, z, - vpa_spectral, z_spectral, moments, fields, vpa_advect, z_advect, - composition, collisions, external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, scratch_electron, + t_params, vpa, z, vpa_spectral, z_spectral, moments, fields, + vpa_advect, z_advect, composition, collisions, external_source_settings, + num_diss_params, nl_solver_params, advance, advance_implicit, istep) advance.force_balance = false end # use the continuity equation to update the density if moments.evolve_density advance.continuity = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vpa, z, - vpa_spectral, z_spectral, moments, fields, vpa_advect, z_advect, - composition, collisions, external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, scratch_electron, + t_params, vpa, z, vpa_spectral, z_spectral, moments, fields, + vpa_advect, z_advect, composition, collisions, external_source_settings, + num_diss_params, nl_solver_params, advance, advance_implicit, istep) advance.continuity = false end # and add the source terms associated with redefining g = pdf/density or pdf*vth/density # to the kinetic equation if moments.evolve_density || moments.evolve_upar || moments.evolve_ppar advance.source_terms = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vpa, z, - vpa_spectral, z_spectral, moments, fields, vpa_advect, z_advect, - composition, collisions, external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, scratch_electron, + t_params, vpa, z, vpa_spectral, z_spectral, moments, fields, + vpa_advect, z_advect, composition, collisions, external_source_settings, + num_diss_params, nl_solver_params, advance, advance_implicit, istep) advance.source_terms = false end # account for charge exchange collisions between ions and neutrals if composition.n_neutral_species > 0 if collisions.ionization > 0.0 advance.neutral_ionization = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, z, vpa, - z_spectral, vpa_spectral, moments, fields, z_advect, vpa_advect, - composition, collisions, external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, + scratch_electron, t_params, z, vpa, z_spectral, vpa_spectral, + moments, fields, z_advect, vpa_advect, composition, collisions, + external_source_settings, num_diss_params, nl_solver_params, advance, + advance_implicit, istep) advance.neutral_ionization = false advance.ion_ionization = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, z, vpa, - z_spectral, vpa_spectral, moments, fields, z_advect, vpa_advect, - composition, collisions, external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, + scratch_electron, t_params, z, vpa, z_spectral, vpa_spectral, + moments, fields, z_advect, vpa_advect, composition, collisions, + external_source_settings, num_diss_params, nl_solver_params, advance, + advance_implicit, istep) advance.ion_ionization = false end if collisions.charge_exchange > 0.0 advance.neutral_cx_collisions = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vpa, z, - vpa_spectral, z_spectral, moments, fields, vpa_advect, z_advect, - composition, collisions, external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, + scratch_electron, t_params, vpa, z, vpa_spectral, z_spectral, + moments, fields, vpa_advect, z_advect, composition, collisions, + external_source_settings, num_diss_params, nl_solver_params, advance, + advance_implicit, istep) advance.neutral_cx_collisions = false advance.ion_cx_collisions = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vpa, z, - vpa_spectral, z_spectral, moments, fields, vpa_advect, z_advect, - composition, collisions, external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, istep) + time_advance_no_splitting!(pdf, scratch, scratch_implicit, + scratch_electron, t_params, vpa, z, vpa_spectral, z_spectral, + moments, fields, vpa_advect, z_advect, composition, collisions, + external_source_settings, num_diss_params, nl_solver_params, advance, + advance_implicit, istep) advance.ion_cx_collisions = false end end # z_advection! advances the operator-split 1D advection equation in z # apply z-advection operation to all species (ion and neutral) advance.z_advection = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vpa, z, - vpa_spectral, z_spectral, moments, fields, vpa_advect, z_advect, - composition, collisions, external_source_settings, num_diss_params, + time_advance_no_splitting!(pdf, scratch, scratch_implicit, scratch_electron, + t_params, vpa, z, vpa_spectral, z_spectral, moments, fields, vpa_advect, + z_advect, composition, collisions, external_source_settings, num_diss_params, nl_solver_params, advance, advance_implicit, istep) advance.z_advection = false # advance the operator-split 1D advection equation in vpa # vpa-advection only applies for ion species advance.vpa_advection = true - time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vpa, z, - vpa_spectral, z_spectral, moments, fields, vpa_advect, z_advect, - composition, collisions, external_source_settings, num_diss_params, + time_advance_no_splitting!(pdf, scratch, scratch_implicit, scratch_electron, + t_params, vpa, z, vpa_spectral, z_spectral, moments, fields, vpa_advect, + z_advect, composition, collisions, external_source_settings, num_diss_params, nl_solver_params, advance, advance_implicit, istep) advance.vpa_advection = false end @@ -1763,21 +2257,21 @@ end """ """ -function time_advance_no_splitting!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, - vzeta, vpa, vperp, gyrophase, z, r, moments, fields, - spectral_objects, advect_objects, composition, - collisions, geometry, gyroavs, boundary_distributions, - external_source_settings, num_diss_params, - nl_solver_params, advance, advance_implicit, - fp_arrays, scratch_dummy, manufactured_source_list, - diagnostic_checks, istep) - - ssp_rk!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzeta, vpa, vperp, - gyrophase, z, r, moments, fields, spectral_objects, advect_objects, - composition, collisions, geometry, gyroavs, boundary_distributions, - external_source_settings, num_diss_params, nl_solver_params, advance, - advance_implicit, fp_arrays, scratch_dummy, manufactured_source_list, - diagnostic_checks, istep) +function time_advance_no_splitting!(pdf, scratch, scratch_implicit, scratch_electron, + t_params, vz, vr, vzeta, vpa, vperp, gyrophase, z, r, + moments, fields, spectral_objects, advect_objects, + composition, collisions, geometry, gyroavs, + boundary_distributions, external_source_settings, + num_diss_params, nl_solver_params, advance, + advance_implicit, fp_arrays, scratch_dummy, + manufactured_source_list, diagnostic_checks, istep) + + ssp_rk!(pdf, scratch, scratch_implicit, scratch_electron, t_params, vz, vr, vzeta, + vpa, vperp, gyrophase, z, r, moments, fields, spectral_objects, + advect_objects, composition, collisions, geometry, gyroavs, + boundary_distributions, external_source_settings, num_diss_params, + nl_solver_params, advance, advance_implicit, fp_arrays, scratch_dummy, + manufactured_source_list, diagnostic_checks, istep) return nothing end @@ -1802,6 +2296,11 @@ function rk_update!(scratch, scratch_implicit, moments, t_params, istage, compos # use Runge Kutta to update any velocity moments evolved separately from the pdf rk_update_evolved_moments!(scratch, scratch_implicit, moments, t_params, istage) + if composition.electron_physics ∈ (braginskii_fluid, kinetic_electrons, + kinetic_electrons_with_temperature_equation) + rk_update_variable!(scratch, scratch_implicit, :electron_ppar, t_params, istage) + end + if composition.n_neutral_species > 0 ## # update the neutral particle distribution and moments @@ -1817,17 +2316,22 @@ Apply boundary conditions and moment constraints to updated pdfs and calculate d moments and moment derivatives """ function apply_all_bcs_constraints_update_moments!( - this_scratch, moments, fields, boundary_distributions, vz, vr, vzeta, vpa, vperp, - z, r, spectral_objects, advect_objects, composition, geometry, gyroavs, - num_diss_params, advance, scratch_dummy, diagnostic_moments; pdf_bc_constraints=true) + this_scratch, pdf, moments, fields, boundary_distributions, scratch_electron, vz, + vr, vzeta, vpa, vperp, z, r, spectral_objects, advect_objects, composition, + collisions, geometry, gyroavs, external_source_settings, num_diss_params, + t_params, advance, scratch_dummy, diagnostic_moments; pdf_bc_constraints=true, + update_electrons=true) begin_s_r_z_region() z_spectral, r_spectral, vpa_spectral, vperp_spectral = spectral_objects.z_spectral, spectral_objects.r_spectral, spectral_objects.vpa_spectral, spectral_objects.vperp_spectral vzeta_spectral, vr_spectral, vz_spectral = spectral_objects.vzeta_spectral, spectral_objects.vr_spectral, spectral_objects.vz_spectral vpa_advect, vperp_advect, r_advect, z_advect = advect_objects.vpa_advect, advect_objects.vperp_advect, advect_objects.r_advect, advect_objects.z_advect + electron_z_advect, electron_vpa_advect = advect_objects.electron_z_advect, advect_objects.electron_vpa_advect neutral_z_advect, neutral_r_advect, neutral_vz_advect = advect_objects.neutral_z_advect, advect_objects.neutral_r_advect, advect_objects.neutral_vz_advect + success = "" + if pdf_bc_constraints # Ensure there are no negative values in the pdf before applying boundary # conditions, so that negative deviations do not mess up the integral-constraint @@ -1870,9 +2374,67 @@ function apply_all_bcs_constraints_update_moments!( calculate_ion_moment_derivatives!(moments, this_scratch, scratch_dummy, z, z_spectral, num_diss_params.ion.moment_dissipation_coefficient) + calculate_electron_moments!(this_scratch, pdf, moments, composition, collisions, r, z, + vpa) + calculate_electron_moment_derivatives!(moments, this_scratch, scratch_dummy, z, + z_spectral, + num_diss_params.electron.moment_dissipation_coefficient, + composition.electron_physics) + if composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + #max_electron_pdf_iterations = 1000 + #max_electron_sim_time = nothing + max_electron_pdf_iterations = nothing + max_electron_sim_time = 1.0e-3 + + # Copy ion and electron moments from `scratch` into `moments` to be used in + # electron kinetic equation update + begin_r_z_region() + @loop_s_r_z is ir iz begin + moments.ion.dens[iz,ir,is] = this_scratch.density[iz,ir,is] + moments.ion.upar[iz,ir,is] = this_scratch.upar[iz,ir,is] + moments.ion.ppar[iz,ir,is] = this_scratch.ppar[iz,ir,is] + end + @loop_sn_r_z isn ir iz begin + moments.neutral.dens[iz,ir,isn] = this_scratch.density_neutral[iz,ir,isn] + moments.neutral.uz[iz,ir,isn] = this_scratch.uz_neutral[iz,ir,isn] + moments.neutral.pz[iz,ir,isn] = this_scratch.pz_neutral[iz,ir,isn] + end + @loop_r_z ir iz begin + moments.electron.dens[iz,ir] = this_scratch.electron_density[iz,ir] + moments.electron.upar[iz,ir] = this_scratch.electron_upar[iz,ir] + moments.electron.ppar[iz,ir] = this_scratch.electron_ppar[iz,ir] + end + + # When we do not need to apply bc's and constraints to the ion/neutral pdf + # (because this function is being called after a failed timestep, to reset to the + # state at the beginning of the step), we also do not need to update the + # electrons. + # Note that if some solve for the implicit timestep already failed, we will reset + # to the beginning of the ion/neutral timestep, so the electron solution + # calculated here would be discarded - we might as well skip calculating it in + # that case. + if update_electrons && + !(t_params.implicit_electron_advance || t_params.implicit_electron_ppar) && + success == "" + kinetic_electron_success = update_electron_pdf!( + scratch_electron, pdf.electron.norm, moments, fields.phi, r, z, vperp, vpa, + z_spectral, vperp_spectral, vpa_spectral, electron_z_advect, + electron_vpa_advect, scratch_dummy, t_params.electron, collisions, + composition, external_source_settings, num_diss_params, + max_electron_pdf_iterations, max_electron_sim_time) + success = kinetic_electron_success + end + end + # update the electron parallel friction force + calculate_electron_parallel_friction_force!( + moments.electron.parallel_friction, this_scratch.electron_density, + this_scratch.electron_upar, this_scratch.upar, moments.electron.dT_dz, + composition.me_over_mi, collisions.nu_ei, composition.electron_physics) + # update the electrostatic potential phi - update_phi!(fields, this_scratch, vperp, z, r, composition, z_spectral, r_spectral, - scratch_dummy, gyroavs) + update_phi!(fields, this_scratch, vperp, z, r, composition, collisions, moments, + geometry, z_spectral, r_spectral, scratch_dummy, gyroavs) if composition.n_neutral_species > 0 if pdf_bc_constraints @@ -1931,11 +2493,14 @@ function apply_all_bcs_constraints_update_moments!( z_spectral, num_diss_params.neutral.moment_dissipation_coefficient) end + + return success end """ - adaptive_timestep_update!(scratch, scratch_implicit, t, t_params, moments, - fields, composition, collisions, geometry, + adaptive_timestep_update!(scratch, scratch_implicit, scratch_electron, + t_params, moments, fields, + composition, collisions, geometry, external_source_settings, spectral_objects, advect_objects, gyroavs, num_diss_params, advance, scratch_dummy, r, z, vperp, vpa, vzeta, vr, vz, @@ -1944,17 +2509,18 @@ end Check the error estimate for the embedded RK method and adjust the timestep if appropriate. """ -function adaptive_timestep_update!(scratch, scratch_implicit, t, t_params, moments, - fields, boundary_distributions, composition, - collisions, geometry, external_source_settings, - spectral_objects, advect_objects, gyroavs, - num_diss_params, advance, scratch_dummy, r, z, vperp, - vpa, vzeta, vr, vz, success, nl_max_its_fraction) +function adaptive_timestep_update!(scratch, scratch_implicit, scratch_electron, + t_params, pdf, moments, fields, boundary_distributions, + composition, collisions, geometry, + external_source_settings, spectral_objects, + advect_objects, gyroavs, num_diss_params, advance, + scratch_dummy, r, z, vperp, vpa, vzeta, vr, vz, + success, nl_max_its_fraction) #error_norm_method = "Linf" error_norm_method = "L2" error_coeffs = t_params.rk_coefs[:,end] - if length(scratch) < 3 + if t_params.n_rk_stages < 3 # This should never happen as an adaptive RK scheme needs at least 2 RHS evals so # (with the pre-timestep data) there must be at least 3 entries in `scratch`. error("adaptive timestep needs a buffer scratch array") @@ -1989,7 +2555,8 @@ function adaptive_timestep_update!(scratch, scratch_implicit, t, t_params, momen ion_z_CFL = Inf @loop_s is begin update_speed_z!(z_advect[is], moments.ion.upar, moments.ion.vth, evolve_upar, - evolve_ppar, fields, vpa, vperp, z, r, t, geometry, is) + evolve_ppar, fields, vpa, vperp, z, r, t_params.t[], geometry, + is) this_minimum = get_minimum_CFL_z(z_advect[is].speed, z) @serial_region begin ion_z_CFL = min(ion_z_CFL, this_minimum) @@ -2002,8 +2569,8 @@ function adaptive_timestep_update!(scratch, scratch_implicit, t, t_params, momen # ion vpa-advection begin_r_z_vperp_region() ion_vpa_CFL = Inf - update_speed_vpa!(vpa_advect, fields, scratch[end], moments, vpa, vperp, z, r, - composition, collisions, external_source_settings.ion, t, + update_speed_vpa!(vpa_advect, fields, scratch[t_params.n_rk_stages+1], moments, vpa, vperp, z, r, + composition, collisions, external_source_settings.ion, t_params.t[], geometry) @loop_s is begin this_minimum = get_minimum_CFL_vpa(vpa_advect[is].speed, vpa) @@ -2035,6 +2602,11 @@ function adaptive_timestep_update!(scratch, scratch_implicit, t, t_params, momen begin_s_r_z_region() rk_loworder_solution!(scratch, scratch_implicit, :ppar, t_params) end + if composition.electron_physics ∈ (braginskii_fluid, kinetic_electrons, + kinetic_electrons_with_temperature_equation) + begin_r_z_region() + rk_loworder_solution!(scratch, scratch_implicit, :electron_ppar, t_params) + end if n_neutral_species > 0 begin_sn_r_z_vzeta_vr_region() rk_loworder_solution!(scratch, scratch_implicit, :pdf_neutral, t_params; neutrals=true) @@ -2052,20 +2624,67 @@ function adaptive_timestep_update!(scratch, scratch_implicit, t, t_params, momen end end - # Apply boundary conditions and constraints + # Apply boundary conditions and constraints to the loworder approximation. + # Need to apply constraints using the high-order moments for consistency, to avoid + # potential for spurious error estimates at boundary points. + loworder_constraints_scratch = + scratch_pdf(scratch[2].pdf, scratch[t_params.n_rk_stages+1].density, + scratch[t_params.n_rk_stages+1].upar, + scratch[t_params.n_rk_stages+1].ppar, + scratch[t_params.n_rk_stages+1].pperp, + scratch[t_params.n_rk_stages+1].temp_z_s, + scratch[t_params.n_rk_stages+1].electron_density, + scratch[t_params.n_rk_stages+1].electron_upar, + scratch[t_params.n_rk_stages+1].electron_ppar, + scratch[t_params.n_rk_stages+1].electron_pperp, + scratch[t_params.n_rk_stages+1].electron_temp, + scratch[2].pdf_neutral, + scratch[t_params.n_rk_stages+1].density_neutral, + scratch[t_params.n_rk_stages+1].uz_neutral, + scratch[t_params.n_rk_stages+1].pz_neutral) apply_all_bcs_constraints_update_moments!( - scratch[2], moments, fields, boundary_distributions, vz, vr, vzeta, - vpa, vperp, z, r, spectral_objects, advect_objects, composition, geometry, - gyroavs, num_diss_params, advance, scratch_dummy, false) + loworder_constraints_scratch, pdf, moments, fields, boundary_distributions, + scratch_electron, vz, vr, vzeta, vpa, vperp, z, r, spectral_objects, + advect_objects, composition, collisions, geometry, gyroavs, + external_source_settings, num_diss_params, t_params, advance, scratch_dummy, + false; update_electrons=false) # Re-calculate moment derivatives in the `moments` struct, in case they were changed # by the previous call apply_all_bcs_constraints_update_moments!( - scratch[t_params.n_rk_stages+1], moments, fields, boundary_distributions, vz, vr, - vzeta, vpa, vperp, z, r, spectral_objects, advect_objects, composition, geometry, - gyroavs, num_diss_params, advance, scratch_dummy, false; pdf_bc_constraints=false) + scratch[t_params.n_rk_stages+1], pdf, moments, fields, boundary_distributions, + scratch_electron, vz, vr, vzeta, vpa, vperp, z, r, spectral_objects, + advect_objects, composition, collisions, geometry, gyroavs, + external_source_settings, num_diss_params, t_params, advance, scratch_dummy, + false; pdf_bc_constraints=false, update_electrons=false) # Calculate the timstep error estimates + if z.bc == "wall" && (moments.evolve_upar || moments.evolve_ppar) + # Set error on last/first non-zero point in ion distribution function to zero, as + # this this point may cause unhelpful timestep failures when the cutoff moves from + # one point to another. + if z.irank == 0 || z.irank == z.nrank - 1 + begin_s_r_region() + @loop_s_r is ir begin + density = @view scratch[t_params.n_rk_stages+1].density[:,ir,is] + upar = @view scratch[t_params.n_rk_stages+1].upar[:,ir,is] + ppar = @view scratch[t_params.n_rk_stages+1].ppar[:,ir,is] + last_negative_vpa_ind, first_positive_vpa_ind = + get_ion_z_boundary_cutoff_indices(density, upar, ppar, + moments.evolve_upar, + moments.evolve_ppar, z, vpa, + 1.0e-14) + if z.irank == 0 + scratch[2].pdf[last_negative_vpa_ind,:,1,ir,is] .= + scratch[t_params.n_rk_stages+1].pdf[last_negative_vpa_ind,:,1,ir,is] + end + if z.irank == z.nrank - 1 + scratch[2].pdf[first_positive_vpa_ind,:,end,ir,is] .= + scratch[t_params.n_rk_stages+1].pdf[first_positive_vpa_ind,:,end,ir,is] + end + end + end + end ion_pdf_error = local_error_norm(scratch[2].pdf, scratch[t_params.n_rk_stages+1].pdf, t_params.rtol, t_params.atol; method=error_norm_method, skip_r_inner=skip_r_inner, @@ -2108,6 +2727,20 @@ function adaptive_timestep_update!(scratch, scratch_implicit, t, t_params, momen push!(total_points, z.n_global * r.n_global * n_ion_species) end + if composition.electron_physics ∈ (braginskii_fluid, kinetic_electrons, + kinetic_electrons_with_temperature_equation) + begin_r_z_region() + electron_p_err = local_error_norm(scratch[2].electron_ppar, + scratch[t_params.n_rk_stages+1].electron_ppar, + t_params.rtol, t_params.atol; + method=error_norm_method, + skip_r_inner=skip_r_inner, + skip_z_lower=skip_z_lower, + error_sum_zero=t_params.error_sum_zero) + push!(error_norms, electron_p_err) + push!(total_points, z.n_global * r.n_global) + end + if n_neutral_species > 0 # neutral z-advection # Don't parallelise over species here, because get_minimum_CFL_*() does an MPI @@ -2118,7 +2751,7 @@ function adaptive_timestep_update!(scratch, scratch_implicit, t, t_params, momen @loop_sn isn begin update_speed_neutral_z!(neutral_z_advect[isn], moments.neutral.uz, moments.neutral.vth, evolve_upar, evolve_ppar, vz, vr, - vzeta, z, r, t) + vzeta, z, r, t_params.t[]) this_minimum = get_minimum_CFL_neutral_z(neutral_z_advect[isn].speed, z) @serial_region begin neutral_z_CFL = min(neutral_z_CFL, this_minimum) @@ -2129,9 +2762,10 @@ function adaptive_timestep_update!(scratch, scratch_implicit, t, t_params, momen # neutral vz-advection begin_r_z_vzeta_vr_region() neutral_vz_CFL = Inf - update_speed_neutral_vz!(neutral_vz_advect, fields, scratch[end], - moments, vz, vr, vzeta, z, r, composition, - collisions, external_source_settings.neutral) + update_speed_neutral_vz!(neutral_vz_advect, fields, + scratch[t_params.n_rk_stages+1], moments, vz, vr, vzeta, + z, r, composition, collisions, + external_source_settings.neutral) @loop_sn isn begin this_minimum = get_minimum_CFL_neutral_vz(neutral_vz_advect[isn].speed, vz) @serial_region begin @@ -2142,8 +2776,9 @@ function adaptive_timestep_update!(scratch, scratch_implicit, t, t_params, momen # Calculate error for neutral distribution functions neut_pdf_error = local_error_norm(scratch[2].pdf_neutral, - scratch[end].pdf_neutral, t_params.rtol, - t_params.atol; method=error_norm_method, + scratch[t_params.n_rk_stages+1].pdf_neutral, + t_params.rtol, t_params.atol; + method=error_norm_method, skip_r_inner=skip_r_inner, skip_z_lower=skip_z_lower, error_sum_zero=t_params.error_sum_zero) @@ -2156,8 +2791,9 @@ function adaptive_timestep_update!(scratch, scratch_implicit, t, t_params, momen if moments.evolve_density begin_sn_r_z_region() neut_n_err = local_error_norm(scratch[2].density_neutral, - scratch[end].density_neutral, t_params.rtol, - t_params.atol, true; method=error_norm_method, + scratch[t_params.n_rk_stages+1].density_neutral, + t_params.rtol, t_params.atol, true; + method=error_norm_method, skip_r_inner=skip_r_inner, skip_z_lower=skip_z_lower, error_sum_zero=t_params.error_sum_zero) @@ -2190,19 +2826,107 @@ function adaptive_timestep_update!(scratch, scratch_implicit, t, t_params, momen end end - adaptive_timestep_update_t_params!(t_params, scratch, t, CFL_limits, error_norms, - total_points, current_dt, error_norm_method, - success, nl_max_its_fraction) + adaptive_timestep_update_t_params!(t_params, CFL_limits, error_norms, total_points, + current_dt, error_norm_method, success, + nl_max_its_fraction, composition) + + if composition.electron_physics ∈ (kinetic_electrons, + kinetic_electrons_with_temperature_equation) + if t_params.previous_dt[] == 0.0 + # Reset electron pdf to its value at the beginning of this step. + begin_r_z_vperp_vpa_region() + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + pdf.electron.norm[ivpa,ivperp,iz,ir] = + pdf.electron.pdf_before_ion_timestep[ivpa,ivperp,iz,ir] + scratch_electron[1].pdf_electron[ivpa,ivperp,iz,ir] = + pdf.electron.pdf_before_ion_timestep[ivpa,ivperp,iz,ir] + end + else + # Store the current value, which will be the value at the beginning of the + # next step. + begin_r_z_vperp_vpa_region() + @loop_r_z_vperp_vpa ir iz ivperp ivpa begin + pdf.electron.pdf_before_ion_timestep[ivpa,ivperp,iz,ir] = + pdf.electron.norm[ivpa,ivperp,iz,ir] + end + end + + istage = t_params.n_rk_stages+1 + + # update the pdf.norm and moments arrays as needed + begin_s_r_z_region() + final_scratch = scratch[istage] + @loop_s_r_z_vperp_vpa is ir iz ivperp ivpa begin + pdf.ion.norm[ivpa,ivperp,iz,ir,is] = final_scratch.pdf[ivpa,ivperp,iz,ir,is] + end + @loop_s_r_z is ir iz begin + moments.ion.dens[iz,ir,is] = final_scratch.density[iz,ir,is] + moments.ion.upar[iz,ir,is] = final_scratch.upar[iz,ir,is] + moments.ion.ppar[iz,ir,is] = final_scratch.ppar[iz,ir,is] + moments.ion.pperp[iz,ir,is] = final_scratch.pperp[iz,ir,is] + end + # No need to synchronize here as we only change electron quantities and previous + # region only changed ion quantities. + begin_r_z_region(no_synchronize=true) + @loop_r_z ir iz begin + moments.electron.dens[iz,ir] = final_scratch.electron_density[iz,ir] + moments.electron.upar[iz,ir] = final_scratch.electron_upar[iz,ir] + moments.electron.ppar[iz,ir] = final_scratch.electron_ppar[iz,ir] + moments.electron.temp[iz,ir] = final_scratch.electron_temp[iz,ir] + end + if composition.n_neutral_species > 0 + # No need to synchronize here as we only change neutral quantities and previous + # region only changed plasma quantities. + begin_sn_r_z_region(no_synchronize=true) + @loop_sn_r_z_vzeta_vr_vz isn ir iz ivzeta ivr ivz begin + pdf.neutral.norm[ivz,ivr,ivzeta,iz,ir,isn] = final_scratch.pdf_neutral[ivz,ivr,ivzeta,iz,ir,isn] + end + @loop_sn_r_z isn ir iz begin + moments.neutral.dens[iz,ir,isn] = final_scratch.density_neutral[iz,ir,isn] + moments.neutral.uz[iz,ir,isn] = final_scratch.uz_neutral[iz,ir,isn] + moments.neutral.pz[iz,ir,isn] = final_scratch.pz_neutral[iz,ir,isn] + end + # for now update moments.neutral object directly for diagnostic moments + # that are not used in Runga-Kutta steps + update_neutral_pr!(moments.neutral.pr, moments.neutral.pr_updated, pdf.neutral.norm, vz, vr, vzeta, z, r, composition) + update_neutral_pzeta!(moments.neutral.pzeta, moments.neutral.pzeta_updated, pdf.neutral.norm, vz, vr, vzeta, z, r, composition) + # Update ptot (isotropic pressure) + if r.n > 1 #if 2D geometry + @loop_sn_r_z isn ir iz begin + moments.neutral.ptot[iz,ir,isn] = (moments.neutral.pz[iz,ir,isn] + moments.neutral.pr[iz,ir,isn] + moments.neutral.pzeta[iz,ir,isn])/3.0 + end + else # 1D model + @loop_sn_r_z isn ir iz begin + moments.neutral.ptot[iz,ir,isn] = moments.neutral.pz[iz,ir,isn] + end + end + # get particle fluxes (n.b. bad naming convention uz -> means -> n uz here) + update_neutral_ur!(moments.neutral.ur, moments.neutral.ur_updated, + moments.neutral.dens, pdf.neutral.norm, vz, vr, vzeta, z, r, + composition) + update_neutral_uzeta!(moments.neutral.uzeta, moments.neutral.uzeta_updated, + moments.neutral.dens, pdf.neutral.norm, vz, vr, vzeta, z, + r, composition) + try #below loop can cause DomainError if ptot < 0 or density < 0, so exit cleanly if possible + @loop_sn_r_z isn ir iz begin + # update density using last density from Runga-Kutta stages + moments.neutral.dens[iz,ir,isn] = final_scratch.density_neutral[iz,ir,isn] + # get vth for neutrals + moments.neutral.vth[iz,ir,isn] = sqrt(2.0*moments.neutral.ptot[iz,ir,isn]/moments.neutral.dens[iz,ir,isn]) + end + catch e + if global_size[] > 1 + println("ERROR: error at line 724 of time_advance.jl") + println(e) + display(stacktrace(catch_backtrace())) + flush(stdout) + flush(stderr) + MPI.Abort(comm_world, 1) + end + rethrow(e) + end + end - if t_params.previous_dt[] == 0.0 - # Re-update remaining velocity moments that are calculable from the evolved - # pdf These need to be re-calculated because `scratch[istage+1]` is now the - # state at the beginning of the timestep, because the timestep failed - apply_all_bcs_constraints_update_moments!( - scratch[t_params.n_rk_stages+1], moments, fields, nothing, vz, vr, vzeta, - vpa, vperp, z, r, spectral_objects, advect_objects, composition, geometry, - gyroavs, num_diss_params, advance, scratch_dummy, false; - pdf_bc_constraints=false) end return nothing @@ -2210,11 +2934,11 @@ end """ """ -function ssp_rk!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzeta, vpa, vperp, - gyrophase, z, r, moments, fields, spectral_objects, advect_objects, - composition, collisions, geometry, gyroavs, boundary_distributions, - external_source_settings, num_diss_params, nl_solver_params, advance, - advance_implicit, fp_arrays, scratch_dummy, +function ssp_rk!(pdf, scratch, scratch_implicit, scratch_electron, t_params, vz, vr, + vzeta, vpa, vperp, gyrophase, z, r, moments, fields, spectral_objects, + advect_objects, composition, collisions, geometry, gyroavs, + boundary_distributions, external_source_settings, num_diss_params, + nl_solver_params, advance, advance_implicit, fp_arrays, scratch_dummy, manufactured_source_list,diagnostic_checks, istep) begin_s_r_z_region() @@ -2232,6 +2956,14 @@ function ssp_rk!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzeta, vpa first_scratch.pperp[iz,ir,is] = moments.ion.pperp[iz,ir,is] end + begin_r_z_region() + @loop_r_z ir iz begin + first_scratch.electron_density[iz,ir] = moments.electron.dens[iz,ir] + first_scratch.electron_upar[iz,ir] = moments.electron.upar[iz,ir] + first_scratch.electron_ppar[iz,ir] = moments.electron.ppar[iz,ir] + first_scratch.electron_temp[iz,ir] = moments.electron.temp[iz,ir] + end + if composition.n_neutral_species > 0 begin_sn_r_z_region() @loop_sn_r_z_vzeta_vr_vz isn ir iz ivzeta ivr ivz begin @@ -2251,7 +2983,7 @@ function ssp_rk!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzeta, vpa end # success is set to false if an iteration failed to converge in an implicit solve - success = true + success = "" for istage ∈ 1:n_rk_stages if t_params.rk_coefs_implicit !== nothing update_solution_vector!(scratch_implicit[istage], scratch[istage], moments, @@ -2262,7 +2994,7 @@ function ssp_rk!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzeta, vpa # stage. euler_time_advance!(scratch_implicit[istage], scratch[istage], pdf, fields, moments, advect_objects, vz, vr, vzeta, - vpa, vperp, gyrophase, z, r, t, t_params.dt[], + vpa, vperp, gyrophase, z, r, t_params.t[], t_params.dt[], spectral_objects, composition, collisions, geometry, scratch_dummy, manufactured_source_list, external_source_settings, num_diss_params, @@ -2276,19 +3008,29 @@ function ssp_rk!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzeta, vpa # Note the timestep for this solve is rk_coefs_implict[istage,istage]*dt. # The diagonal elements are equal to the Butcher 'a' coefficients # rk_coefs_implicit[istage,istage]=a[istage,istage]. - success = backward_euler!(scratch_implicit[istage], scratch[istage], pdf, - fields, moments, advect_objects, vz, vr, vzeta, - vpa, vperp, gyrophase, z, r, t, t_params.dt[] * - t_params.rk_coefs_implicit[istage,istage], - spectral_objects, composition, collisions, - geometry, scratch_dummy, - manufactured_source_list, - external_source_settings, num_diss_params, - gyroavs, nl_solver_params, advance_implicit, - fp_arrays, istage) - success = MPI.Allreduce(success, &, comm_world) - if !success - # Break out of the istage loop, as passing `success = false` to the + if scratch_electron === nothing + this_scratch_electron = nothing + elseif t_params.implicit_electron_advance + this_scratch_electron = scratch_electron[t_params.electron.n_rk_stages+1] + else + this_scratch_electron = scratch_electron + end + nl_success = backward_euler!(scratch_implicit[istage], scratch[istage], + this_scratch_electron, + pdf, fields, moments, advect_objects, vz, vr, + vzeta, vpa, vperp, gyrophase, z, r, + t_params.dt[] * + t_params.rk_coefs_implicit[istage,istage], + t_params, spectral_objects, composition, + collisions, geometry, scratch_dummy, + manufactured_source_list, + external_source_settings, num_diss_params, + gyroavs, nl_solver_params, advance_implicit, + fp_arrays, istage) + nl_success = MPI.Allreduce(nl_success, &, comm_world) + if !nl_success + success = "nonlinear-solver" + # Break out of the istage loop, as passing `success != ""` to the # adaptive timestep update function will signal a failed timestep, so # that we restart this timestep with a smaller `dt`. break @@ -2296,11 +3038,18 @@ function ssp_rk!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzeta, vpa # The result of the implicit solve gives the state vector at 'istage' # which is used as input to the explicit part of the IMEX time step. old_scratch = scratch_implicit[istage] - apply_all_bcs_constraints_update_moments!( - scratch_implicit[istage], moments, fields, boundary_distributions, vz, - vr, vzeta, vpa, vperp, z, r, spectral_objects, advect_objects, - composition, geometry, gyroavs, num_diss_params, advance, - scratch_dummy, false) + success = apply_all_bcs_constraints_update_moments!( + scratch_implicit[istage], pdf, moments, fields, + boundary_distributions, scratch_electron, vz, vr, vzeta, vpa, vperp, + z, r, spectral_objects, advect_objects, composition, collisions, + geometry, gyroavs, external_source_settings, num_diss_params, + t_params, advance, scratch_dummy, false) + if success != "" + # Break out of the istage loop, as passing `success != ""` to the + # adaptive timestep update function will signal a failed timestep, so + # that we restart this timestep with a smaller `dt`. + break + end end else # Fully explicit method starts the forward-Euler step with the result from the @@ -2315,7 +3064,7 @@ function ssp_rk!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzeta, vpa # calculate f^{(1)} = fⁿ + Δt*G[fⁿ] = scratch[2].pdf euler_time_advance!(scratch[istage+1], old_scratch, pdf, fields, moments, advect_objects, vz, vr, vzeta, vpa, vperp, gyrophase, z, - r, t, t_params.dt[], spectral_objects, composition, + r, t_params.t[], t_params.dt[], spectral_objects, composition, collisions, geometry, scratch_dummy, manufactured_source_list, external_source_settings, num_diss_params, advance, fp_arrays, istage) @@ -2328,14 +3077,27 @@ function ssp_rk!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzeta, vpa # If `implicit_coefficient_is_zero` is true for the next stage, then this step is # explicit, so we need the bcs and constraints. apply_bc_constraints = (t_params.rk_coefs_implicit === nothing - || istage == n_rk_stages + || !t_params.implicit_ion_advance + || (istage == n_rk_stages && t_params.implicit_coefficient_is_zero[1]) || t_params.implicit_coefficient_is_zero[istage+1]) + update_electrons = (t_params.rk_coefs_implicit === nothing + || !(t_params.implicit_electron_advance || t_params.implicit_electron_ppar) + || t_params.implicit_coefficient_is_zero[istage+1] + || (istage == n_rk_stages && t_params.implicit_coefficient_is_zero[1])) diagnostic_moments = diagnostic_checks && istage == n_rk_stages - apply_all_bcs_constraints_update_moments!( - scratch[istage+1], moments, fields, boundary_distributions, vz, vr, vzeta, - vpa, vperp, z, r, spectral_objects, advect_objects, composition, geometry, - gyroavs, num_diss_params, advance, scratch_dummy, diagnostic_moments; - pdf_bc_constraints=apply_bc_constraints) + success = apply_all_bcs_constraints_update_moments!( + scratch[istage+1], pdf, moments, fields, boundary_distributions, + scratch_electron, vz, vr, vzeta, vpa, vperp, z, r, spectral_objects, + advect_objects, composition, collisions, geometry, gyroavs, + external_source_settings, num_diss_params, t_params, advance, scratch_dummy, + diagnostic_moments; pdf_bc_constraints=apply_bc_constraints, + update_electrons=update_electrons) + if success != "" + # Break out of the istage loop, as passing `success != ""` to the + # adaptive timestep update function will signal a failed timestep, so + # that we restart this timestep with a smaller `dt`. + break + end end if t_params.adaptive @@ -2347,83 +3109,99 @@ function ssp_rk!(pdf, scratch, scratch_implicit, t, t_params, vz, vr, vzeta, vpa nl_max_its_fraction) end end - adaptive_timestep_update!(scratch, scratch_implicit, t, t_params, moments, fields, + adaptive_timestep_update!(scratch, scratch_implicit, scratch_electron, + t_params, pdf, moments, fields, boundary_distributions, composition, collisions, geometry, external_source_settings, spectral_objects, - advect_objects, gyroavs, num_diss_params, advance, - scratch_dummy, r, z, vperp, vpa, vzeta, vr, vz, success, - nl_max_its_fraction) - elseif !success + advect_objects, gyroavs, num_diss_params, + advance, scratch_dummy, r, z, vperp, vpa, + vzeta, vr, vz, success, nl_max_its_fraction) + elseif success != "" error("Implicit part of timestep failed") end +#if global_rank[] == 0 +# println("loworder ", scratch[2].pdf[92:95,1,1,1,1]) +# println() +#end reset_nonlinear_per_stage_counters(nl_solver_params.ion_advance) reset_nonlinear_per_stage_counters(nl_solver_params.vpa_advection) - istage = n_rk_stages+1 + if t_params.previous_dt[] > 0.0 + istage = n_rk_stages+1 - # update the pdf.norm and moments arrays as needed - begin_s_r_z_region() - final_scratch = scratch[istage] - @loop_s_r_z_vperp_vpa is ir iz ivperp ivpa begin - pdf.ion.norm[ivpa,ivperp,iz,ir,is] = final_scratch.pdf[ivpa,ivperp,iz,ir,is] - end - @loop_s_r_z is ir iz begin - moments.ion.dens[iz,ir,is] = final_scratch.density[iz,ir,is] - moments.ion.upar[iz,ir,is] = final_scratch.upar[iz,ir,is] - moments.ion.ppar[iz,ir,is] = final_scratch.ppar[iz,ir,is] - moments.ion.pperp[iz,ir,is] = final_scratch.pperp[iz,ir,is] - end - if composition.n_neutral_species > 0 - # No need to synchronize here as we only change neutral quantities and previous - # region only changed plasma quantities. - begin_sn_r_z_region(no_synchronize=true) - @loop_sn_r_z_vzeta_vr_vz isn ir iz ivzeta ivr ivz begin - pdf.neutral.norm[ivz,ivr,ivzeta,iz,ir,isn] = final_scratch.pdf_neutral[ivz,ivr,ivzeta,iz,ir,isn] + # update the pdf.norm and moments arrays as needed + begin_s_r_z_region() + final_scratch = scratch[istage] + @loop_s_r_z_vperp_vpa is ir iz ivperp ivpa begin + pdf.ion.norm[ivpa,ivperp,iz,ir,is] = final_scratch.pdf[ivpa,ivperp,iz,ir,is] end - @loop_sn_r_z isn ir iz begin - moments.neutral.dens[iz,ir,isn] = final_scratch.density_neutral[iz,ir,isn] - moments.neutral.uz[iz,ir,isn] = final_scratch.uz_neutral[iz,ir,isn] - moments.neutral.pz[iz,ir,isn] = final_scratch.pz_neutral[iz,ir,isn] - end - # for now update moments.neutral object directly for diagnostic moments - # that are not used in Runga-Kutta steps - update_neutral_pr!(moments.neutral.pr, moments.neutral.pr_updated, pdf.neutral.norm, vz, vr, vzeta, z, r, composition) - update_neutral_pzeta!(moments.neutral.pzeta, moments.neutral.pzeta_updated, pdf.neutral.norm, vz, vr, vzeta, z, r, composition) - # Update ptot (isotropic pressure) - if r.n > 1 #if 2D geometry - @loop_sn_r_z isn ir iz begin - moments.neutral.ptot[iz,ir,isn] = (moments.neutral.pz[iz,ir,isn] + moments.neutral.pr[iz,ir,isn] + moments.neutral.pzeta[iz,ir,isn])/3.0 - end - else # 1D model - @loop_sn_r_z isn ir iz begin - moments.neutral.ptot[iz,ir,isn] = moments.neutral.pz[iz,ir,isn] - end + @loop_s_r_z is ir iz begin + moments.ion.dens[iz,ir,is] = final_scratch.density[iz,ir,is] + moments.ion.upar[iz,ir,is] = final_scratch.upar[iz,ir,is] + moments.ion.ppar[iz,ir,is] = final_scratch.ppar[iz,ir,is] + moments.ion.pperp[iz,ir,is] = final_scratch.pperp[iz,ir,is] + end + # No need to synchronize here as we only change electron quantities and previous + # region only changed ion quantities. + begin_r_z_region(no_synchronize=true) + @loop_r_z ir iz begin + moments.electron.dens[iz,ir] = final_scratch.electron_density[iz,ir] + moments.electron.upar[iz,ir] = final_scratch.electron_upar[iz,ir] + moments.electron.ppar[iz,ir] = final_scratch.electron_ppar[iz,ir] + moments.electron.temp[iz,ir] = final_scratch.electron_temp[iz,ir] end - # get particle fluxes (n.b. bad naming convention uz -> means -> n uz here) - update_neutral_ur!(moments.neutral.ur, moments.neutral.ur_updated, - moments.neutral.dens, pdf.neutral.norm, vz, vr, vzeta, z, r, - composition) - update_neutral_uzeta!(moments.neutral.uzeta, moments.neutral.uzeta_updated, - moments.neutral.dens, pdf.neutral.norm, vz, vr, vzeta, z, - r, composition) - try #below loop can cause DomainError if ptot < 0 or density < 0, so exit cleanly if possible + if composition.n_neutral_species > 0 + # No need to synchronize here as we only change neutral quantities and previous + # region only changed plasma quantities. + begin_sn_r_z_region(no_synchronize=true) + @loop_sn_r_z_vzeta_vr_vz isn ir iz ivzeta ivr ivz begin + pdf.neutral.norm[ivz,ivr,ivzeta,iz,ir,isn] = final_scratch.pdf_neutral[ivz,ivr,ivzeta,iz,ir,isn] + end @loop_sn_r_z isn ir iz begin - # update density using last density from Runga-Kutta stages moments.neutral.dens[iz,ir,isn] = final_scratch.density_neutral[iz,ir,isn] - # get vth for neutrals - moments.neutral.vth[iz,ir,isn] = sqrt(2.0*moments.neutral.ptot[iz,ir,isn]/moments.neutral.dens[iz,ir,isn]) + moments.neutral.uz[iz,ir,isn] = final_scratch.uz_neutral[iz,ir,isn] + moments.neutral.pz[iz,ir,isn] = final_scratch.pz_neutral[iz,ir,isn] end - catch e - if global_size[] > 1 - println("ERROR: error at line 724 of time_advance.jl") - println(e) - display(stacktrace(catch_backtrace())) - flush(stdout) - flush(stderr) - MPI.Abort(comm_world, 1) + # for now update moments.neutral object directly for diagnostic moments + # that are not used in Runga-Kutta steps + update_neutral_pr!(moments.neutral.pr, moments.neutral.pr_updated, pdf.neutral.norm, vz, vr, vzeta, z, r, composition) + update_neutral_pzeta!(moments.neutral.pzeta, moments.neutral.pzeta_updated, pdf.neutral.norm, vz, vr, vzeta, z, r, composition) + # Update ptot (isotropic pressure) + if r.n > 1 #if 2D geometry + @loop_sn_r_z isn ir iz begin + moments.neutral.ptot[iz,ir,isn] = (moments.neutral.pz[iz,ir,isn] + moments.neutral.pr[iz,ir,isn] + moments.neutral.pzeta[iz,ir,isn])/3.0 + end + else # 1D model + @loop_sn_r_z isn ir iz begin + moments.neutral.ptot[iz,ir,isn] = moments.neutral.pz[iz,ir,isn] + end + end + # get particle fluxes (n.b. bad naming convention uz -> means -> n uz here) + update_neutral_ur!(moments.neutral.ur, moments.neutral.ur_updated, + moments.neutral.dens, pdf.neutral.norm, vz, vr, vzeta, z, r, + composition) + update_neutral_uzeta!(moments.neutral.uzeta, moments.neutral.uzeta_updated, + moments.neutral.dens, pdf.neutral.norm, vz, vr, vzeta, z, + r, composition) + try #below loop can cause DomainError if ptot < 0 or density < 0, so exit cleanly if possible + @loop_sn_r_z isn ir iz begin + # update density using last density from Runga-Kutta stages + moments.neutral.dens[iz,ir,isn] = final_scratch.density_neutral[iz,ir,isn] + # get vth for neutrals + moments.neutral.vth[iz,ir,isn] = sqrt(2.0*moments.neutral.ptot[iz,ir,isn]/moments.neutral.dens[iz,ir,isn]) + end + catch e + if global_size[] > 1 + println("ERROR: error at line 724 of time_advance.jl") + println(e) + display(stacktrace(catch_backtrace())) + flush(stdout) + flush(stderr) + MPI.Abort(comm_world, 1) + end + rethrow(e) end - rethrow(e) end end @@ -2476,7 +3254,7 @@ function euler_time_advance!(fvec_out, fvec_in, pdf, fields, moments, end # r advection relies on derivatives in z to get ExB - if advance.r_advection && r.n > 1 + if advance.r_advection r_advection!(fvec_out.pdf, fvec_in, moments, fields, r_advect, r, z, vperp, vpa, dt, r_spectral, composition, geometry, scratch_dummy) end @@ -2484,7 +3262,8 @@ function euler_time_advance!(fvec_out, fvec_in, pdf, fields, moments, # so call vperp_advection! only after z and r advection routines if advance.vperp_advection vperp_advection!(fvec_out.pdf, fvec_in, vperp_advect, r, z, vperp, vpa, - dt, vperp_spectral, composition, z_advect, r_advect, geometry) + dt, vperp_spectral, composition, z_advect, r_advect, geometry, + moments, fields, t) end if advance.source_terms @@ -2497,7 +3276,7 @@ function euler_time_advance!(fvec_out, fvec_in, pdf, fields, moments, r, z, vzeta, vr, vz, dt, t, z_spectral, composition, scratch_dummy) end - if advance.neutral_r_advection && r.n > 1 + if advance.neutral_r_advection neutral_advection_r!(fvec_out.pdf_neutral, fvec_in, neutral_r_advect, r, z, vzeta, vr, vz, dt, r_spectral, composition, geometry, scratch_dummy) end @@ -2573,6 +3352,16 @@ function euler_time_advance!(fvec_out, fvec_in, pdf, fields, moments, krook_collisions!(fvec_out.pdf, fvec_in, moments, composition, collisions, vperp, vpa, dt) end + # Add maxwellian diffusion collision operator for ions + if advance.mxwl_diff_collisions_ii + ion_vpa_maxwell_diffusion!(fvec_out.pdf, fvec_in, moments, vpa, vperp, vpa_spectral, + dt, collisions.mxwl_diff.D_ii) + end + # Add maxwellian diffusion collision operator for neutrals + if advance.mxwl_diff_collisions_nn + neutral_vz_maxwell_diffusion!(fvec_out.pdf_neutral, fvec_in, moments, vzeta, vr, vz, vz_spectral, + dt, collisions.mxwl_diff.D_nn) + end if advance.external_source external_ion_source!(fvec_out.pdf, fvec_in, moments, external_source_settings.ion, @@ -2668,51 +3457,96 @@ function euler_time_advance!(fvec_out, fvec_in, pdf, fields, moments, z_spectral, composition, external_source_settings.neutral, num_diss_params) end + if advance.electron_energy + electron_energy_equation!(fvec_out.electron_ppar, fvec_in.electron_ppar, + fvec_in.density, fvec_in.electron_upar, fvec_in.density, + fvec_in.upar, fvec_in.ppar, fvec_in.density_neutral, + fvec_in.uz_neutral, fvec_in.pz_neutral, + moments.electron, collisions, dt, composition, + external_source_settings.electron, num_diss_params, z; + conduction=advance.electron_conduction) + elseif advance.electron_conduction + # Explicit version of the implicit part of the IMEX timestep, need to evaluate + # only the conduction term. + for ir ∈ 1:r.n + @views electron_braginskii_conduction!( + fvec_out.electron_ppar[:,ir], fvec_in.electron_ppar[:,ir], + fvec_in.electron_density[:,ir], fvec_in.electron_upar[:,ir], + fvec_in.upar[:,ir], moments.electron, collisions, composition, z, + z_spectral, scratch_dummy, dt, ir) + end + end # reset "xx.updated" flags to false since ff has been updated # and the corresponding moments have not reset_moments_status!(moments) return nothing end -function backward_euler!(fvec_out, fvec_in, pdf, fields, moments, advect_objects, vz, vr, - vzeta, vpa, vperp, gyrophase, z, r, t, dt, spectral_objects, - composition, collisions, geometry, scratch_dummy, - manufactured_source_list, external_source_settings, - num_diss_params, gyroavs, nl_solver_params, advance, fp_arrays, - istage) +function backward_euler!(fvec_out, fvec_in, scratch_electron, pdf, fields, moments, + advect_objects, vz, vr, vzeta, vpa, vperp, gyrophase, z, r, dt, + t_params, spectral_objects, composition, collisions, geometry, + scratch_dummy, manufactured_source_list, + external_source_settings, num_diss_params, gyroavs, + nl_solver_params, advance, fp_arrays, istage) vpa_spectral, vperp_spectral, r_spectral, z_spectral = spectral_objects.vpa_spectral, spectral_objects.vperp_spectral, spectral_objects.r_spectral, spectral_objects.z_spectral vz_spectral, vr_spectral, vzeta_spectral = spectral_objects.vz_spectral, spectral_objects.vr_spectral, spectral_objects.vzeta_spectral vpa_advect, vperp_advect, r_advect, z_advect = advect_objects.vpa_advect, advect_objects.vperp_advect, advect_objects.r_advect, advect_objects.z_advect + electron_z_advect, electron_vpa_advect = advect_objects.electron_z_advect, advect_objects.electron_vpa_advect neutral_z_advect, neutral_r_advect, neutral_vz_advect = advect_objects.neutral_z_advect, advect_objects.neutral_r_advect, advect_objects.neutral_vz_advect + if nl_solver_params.electron_advance !== nothing + success = implicit_electron_advance!(fvec_out, fvec_in, pdf, scratch_electron, + moments, fields, collisions, composition, + geometry, external_source_settings, + num_diss_params, r, z, vperp, vpa, + r_spectral, z_spectral, vperp_spectral, + vpa_spectral, electron_z_advect, + electron_vpa_advect, gyroavs, scratch_dummy, + dt, nl_solver_params.electron_advance) + elseif t_params.implicit_electron_ppar + #max_electron_pdf_iterations = 1000 + #max_electron_sim_time = nothing + max_electron_pdf_iterations = nothing + max_electron_sim_time = 1.0e-3 + electron_success = update_electron_pdf!(scratch_electron, pdf.electron.norm, + moments, fields.phi, r, z, vperp, vpa, + z_spectral, vperp_spectral, vpa_spectral, + electron_z_advect, electron_vpa_advect, + scratch_dummy, t_params.electron, + collisions, composition, + external_source_settings, num_diss_params, + max_electron_pdf_iterations, + max_electron_sim_time; ion_dt=dt) + success = (electron_success == "") + elseif advance.electron_conduction + success = implicit_braginskii_conduction!(fvec_out, fvec_in, moments, z, r, dt, + z_spectral, composition, collisions, + scratch_dummy, + nl_solver_params.electron_conduction) + end + if nl_solver_params.ion_advance !== nothing success = implicit_ion_advance!(fvec_out, fvec_in, pdf, fields, moments, advect_objects, vz, vr, vzeta, vpa, vperp, - gyrophase, z, r, t, dt, spectral_objects, - composition, collisions, geometry, scratch_dummy, - manufactured_source_list, + gyrophase, z, r, t_params.t[], dt, + spectral_objects, composition, collisions, + geometry, scratch_dummy, manufactured_source_list, external_source_settings, num_diss_params, gyroavs, nl_solver_params.ion_advance, advance, fp_arrays, istage) - if !success - return success - end elseif advance.vpa_advection success = implicit_vpa_advection!(fvec_out.pdf, fvec_in, fields, moments, - z_advect, vpa_advect, vpa, vperp, z, r, dt, t, - r_spectral, z_spectral, vpa_spectral, - composition, collisions, + z_advect, vpa_advect, vpa, vperp, z, r, dt, + t_params.t[], r_spectral, z_spectral, + vpa_spectral, composition, collisions, external_source_settings.ion, geometry, nl_solver_params.vpa_advection, advance.vpa_diffusion, num_diss_params, gyroavs, scratch_dummy) - if !success - return success - end end - return true + return success end """ @@ -2895,14 +3729,20 @@ function implicit_ion_advance!(fvec_out, fvec_in, pdf, fields, moments, advect_o # scratch_pdf struct containing the array passed as f_new new_scratch = scratch_pdf(f_new, fvec_out.density, fvec_out.upar, fvec_out.ppar, - fvec_out.pperp, fvec_out.temp_z_s, fvec_out.pdf_neutral, + fvec_out.pperp, fvec_out.temp_z_s, + fvec_out.electron_density, fvec_out.electron_upar, + fvec_out.electron_ppar, fvec_out.electron_pperp, + fvec_out.electron_temp, fvec_out.pdf_neutral, fvec_out.density_neutral, fvec_out.uz_neutral, fvec_out.pz_neutral) # scratch_pdf struct containing the array passed as residual residual_scratch = scratch_pdf(residual, fvec_out.density, fvec_out.upar, fvec_out.ppar, fvec_out.pperp, fvec_out.temp_z_s, - fvec_out.pdf_neutral, fvec_out.density_neutral, - fvec_out.uz_neutral, fvec_out.pz_neutral) + fvec_out.electron_density, fvec_out.electron_upar, + fvec_out.electron_ppar, fvec_out.electron_pperp, + fvec_out.electron_temp, fvec_out.pdf_neutral, + fvec_out.density_neutral, fvec_out.uz_neutral, + fvec_out.pz_neutral) # Ensure moments are consistent with f_new update_derived_moments!(new_scratch, moments, vpa, vperp, z, r, composition, @@ -2983,6 +3823,13 @@ function update_solution_vector!(new_evolved, old_evolved, moments, composition, new_evolved.upar[iz,ir,is] = old_evolved.upar[iz,ir,is] new_evolved.ppar[iz,ir,is] = old_evolved.ppar[iz,ir,is] end + begin_r_z_region() + @loop_r_z ir iz begin + new_evolved.electron_density[iz,ir] = old_evolved.electron_density[iz,ir] + new_evolved.electron_upar[iz,ir] = old_evolved.electron_upar[iz,ir] + new_evolved.electron_ppar[iz,ir] = old_evolved.electron_ppar[iz,ir] + new_evolved.electron_temp[iz,ir] = old_evolved.electron_temp[iz,ir] + end if composition.n_neutral_species > 0 begin_sn_r_z_region() @loop_sn_r_z_vzeta_vr_vz isn ir iz ivzeta ivr ivz begin diff --git a/moment_kinetics/src/utils.jl b/moment_kinetics/src/utils.jl index b2a434435..2118c0407 100644 --- a/moment_kinetics/src/utils.jl +++ b/moment_kinetics/src/utils.jl @@ -274,7 +274,7 @@ function get_prefix_iblock_and_move_existing_file(restart_filename, output_dir) # Ensure files have been moved before any process tries to read from them MPI.Barrier(comm_world) - return backup_prefix_iblock + return backup_prefix_iblock, dfns_filename, backup_dfns_filename end """ @@ -421,6 +421,17 @@ post-processing. """ function get_CFL end +function get_CFL!(CFL::AbstractArray{T,4}, speed::AbstractArray{T,4}, coord) where T + + nmain, n2, n3, n4 = size(speed) + + for i4 ∈ 1:n4, i3 ∈ 1:n3, i2 ∈ 1:n2, imain ∈ 1:nmain + CFL[imain,i2,i3,i4] = abs(coord.cell_width[imain] / speed[imain,i2,i3,i4]) + end + + return CFL +end + function get_CFL!(CFL::AbstractArray{T,5}, speed::AbstractArray{T,5}, coord) where T nmain, n2, n3, n4, n5 = size(speed) diff --git a/moment_kinetics/src/velocity_moments.jl b/moment_kinetics/src/velocity_moments.jl index 5b211e437..29684c26a 100644 --- a/moment_kinetics/src/velocity_moments.jl +++ b/moment_kinetics/src/velocity_moments.jl @@ -5,7 +5,7 @@ module velocity_moments export integrate_over_vspace export integrate_over_positive_vpa, integrate_over_negative_vpa export integrate_over_positive_vz, integrate_over_negative_vz -export create_moments_ion, create_moments_neutral +export create_moments_ion, create_moments_electron, create_moments_neutral export update_moments! export update_density! export update_upar! @@ -41,7 +41,8 @@ using ..derivatives: derivative_z!, second_derivative_z! using ..derivatives: derivative_r!, second_derivative_r! using ..looping using ..gyroaverages: gyro_operators, gyroaverage_pdf! -using ..moment_kinetics_structs: moments_ion_substruct, +using ..input_structs +using ..moment_kinetics_structs: moments_ion_substruct, moments_electron_substruct, moments_neutral_substruct #global tmpsum1 = 0.0 @@ -195,6 +196,84 @@ function create_moments_ion(nz, nr, n_species, evolve_density, evolve_upar, constraints_B_coefficient, constraints_C_coefficient) end +""" +create a moment struct containing information about the electron moments +""" +function create_moments_electron(nz, nr, electron_model, num_diss_params) + # allocate array used for the particle density + density = allocate_shared_float(nz, nr) + # initialise Bool variable that indicates if the density is updated for each species + density_updated = Ref(false) + # allocate array used for the parallel flow + parallel_flow = allocate_shared_float(nz, nr) + # allocate Bool variable that indicates if the parallel flow is updated for each species + parallel_flow_updated = Ref(false) + # allocate array used for the parallel pressure + parallel_pressure = allocate_shared_float(nz, nr) + # allocate Bool variable that indicates if the parallel pressure is updated for each species + parallel_pressure_updated = Ref(false) + # allocate array used for the temperature + temperature = allocate_shared_float(nz, nr) + # allocate Bool variable that indicates if the temperature is updated for each species + temperature_updated = Ref(false) + # allocate array used for the parallel flow + parallel_heat_flux = allocate_shared_float(nz, nr) + # allocate Bool variables that indicates if the parallel flow is updated for each species + parallel_heat_flux_updated = Ref(false) + # allocate array used for the election-ion parallel friction force + parallel_friction_force = allocate_shared_float(nz, nr) + # allocate arrays used for external sources + external_source_amplitude = allocate_shared_float(nz, nr) + external_source_density_amplitude = allocate_shared_float(nz, nr) + external_source_momentum_amplitude = allocate_shared_float(nz, nr) + external_source_pressure_amplitude = allocate_shared_float(nz, nr) + # allocate array used for the thermal speed + thermal_speed = allocate_shared_float(nz, nr) + # if evolving the electron pdf, it will be a function of the vth-normalised peculiar velocity + v_norm_fac = thermal_speed + # dn/dz is needed to obtain dT/dz (appearing in, e.g., Braginskii qpar) from dppar/dz + ddens_dz = allocate_shared_float(nz, nr) + # need dupar/dz to obtain, e.g., the updated electron temperature + dupar_dz = allocate_shared_float(nz, nr) + dppar_dz = allocate_shared_float(nz, nr) + if electron_model ∈ (braginskii_fluid, kinetic_electrons, + kinetic_electrons_with_temperature_equation) + dppar_dz_upwind = allocate_shared_float(nz, nr) + dT_dz_upwind = allocate_shared_float(nz, nr) + else + dppar_dz_upwind = nothing + dT_dz_upwind = nothing + end + if num_diss_params.electron.moment_dissipation_coefficient > 0.0 + d2ppar_dz2 = allocate_shared_float(nz, nr) + else + d2ppar_dz2 = nothing + end + dqpar_dz = allocate_shared_float(nz, nr) + dT_dz = allocate_shared_float(nz, nr) + dvth_dz = allocate_shared_float(nz, nr) + + constraints_A_coefficient = allocate_shared_float(nz, nr) + constraints_B_coefficient = allocate_shared_float(nz, nr) + constraints_C_coefficient = allocate_shared_float(nz, nr) + @serial_region begin + constraints_A_coefficient .= 1.0 + constraints_B_coefficient .= 0.0 + constraints_C_coefficient .= 0.0 + end + + # return struct containing arrays needed to update moments + return moments_electron_substruct(density, density_updated, parallel_flow, + parallel_flow_updated, parallel_pressure, parallel_pressure_updated, + temperature, temperature_updated, + parallel_heat_flux, parallel_heat_flux_updated, thermal_speed, + parallel_friction_force, external_source_amplitude, + external_source_density_amplitude, external_source_momentum_amplitude, + external_source_pressure_amplitude, v_norm_fac, ddens_dz, dupar_dz, dppar_dz, + dppar_dz_upwind, d2ppar_dz2, dqpar_dz, dT_dz, dT_dz_upwind, dvth_dz, + constraints_A_coefficient, constraints_B_coefficient, constraints_C_coefficient) +end + # neutral particles have natural mean velocities # uz, ur, uzeta =/= upar # and similarly for heat fluxes @@ -881,6 +960,54 @@ function calculate_ion_moment_derivatives!(moments, scratch, scratch_dummy, z, z end end +""" +Pre-calculate spatial derivatives of the electron moments that will be needed for the time advance +""" +function calculate_electron_moment_derivatives!(moments, scratch, scratch_dummy, z, z_spectral, + electron_mom_diss_coeff, electron_model) + begin_r_region() + + dens = scratch.electron_density + upar = scratch.electron_upar + ppar = scratch.electron_ppar + qpar = moments.electron.qpar + vth = moments.electron.vth + dummy_zr = @view scratch_dummy.dummy_zrs[:,:,1] + buffer_r_1 = @view scratch_dummy.buffer_rs_1[:,1] + buffer_r_2 = @view scratch_dummy.buffer_rs_2[:,1] + buffer_r_3 = @view scratch_dummy.buffer_rs_3[:,1] + buffer_r_4 = @view scratch_dummy.buffer_rs_4[:,1] + + @views derivative_z!(moments.electron.dupar_dz, upar, buffer_r_1, + buffer_r_2, buffer_r_3, buffer_r_4, z_spectral, z) + + # centred second derivative for dissipation + if electron_mom_diss_coeff > 0.0 + @views derivative_z!(dummy_zr, ppar, buffer_r_1, buffer_r_2, buffer_r_3, + buffer_r_4, z_spectral, z) + @views derivative_z!(moments.electron.d2ppar_dz2, dummy_zr, buffer_r_1, + buffer_r_2, buffer_r_3, buffer_r_4, z_spectral, z) + end + + @views derivative_z!(moments.electron.ddens_dz, dens, buffer_r_1, + buffer_r_2, buffer_r_3, buffer_r_4, z_spectral, z) + @views derivative_z!(moments.electron.dppar_dz, ppar, buffer_r_1, + buffer_r_2, buffer_r_3, buffer_r_4, z_spectral, z) + @views derivative_z!(moments.electron.dqpar_dz, qpar, buffer_r_1, + buffer_r_2, buffer_r_3, buffer_r_4, z_spectral, z) + @views derivative_z!(moments.electron.dvth_dz, vth, buffer_r_1, + buffer_r_2, buffer_r_3, buffer_r_4, z_spectral, z) + # calculate the zed derivative of the electron temperature + @loop_r_z ir iz begin + # store the temperature in dummy_zr + dummy_zr[iz,ir] = 2*ppar[iz,ir]/dens[iz,ir] + end + @views derivative_z!(moments.electron.dT_dz, dummy_zr, buffer_r_1, + buffer_r_2, buffer_r_3, buffer_r_4, z_spectral, z) + @views derivative_z!(moments.electron.dvth_dz, moments.electron.vth, buffer_r_1, + buffer_r_2, buffer_r_3, buffer_r_4, z_spectral, z) +end + """ update velocity moments of the evolved neutral pdf """ @@ -1743,6 +1870,10 @@ function reset_moments_status!(moments) moments.neutral.pzeta_updated .= false moments.neutral.pr_updated .= false moments.neutral.qz_updated .= false + moments.electron.dens_updated[] = false + moments.electron.upar_updated[] = false + moments.electron.ppar_updated[] = false + moments.electron.qpar_updated[] = false end end diff --git a/moment_kinetics/src/vperp_advection.jl b/moment_kinetics/src/vperp_advection.jl index 16c2697df..2dc6bd5c8 100644 --- a/moment_kinetics/src/vperp_advection.jl +++ b/moment_kinetics/src/vperp_advection.jl @@ -6,13 +6,21 @@ export update_speed_vperp! using ..advection: advance_f_local! using ..chebyshev: chebyshev_info using ..looping +using ..z_advection: update_speed_z! +using ..r_advection: update_speed_r! # do a single stage time advance (potentially as part of a multi-stage RK scheme) function vperp_advection!(f_out, fvec_in, vperp_advect, r, z, vperp, vpa, - dt, vperp_spectral, composition, z_advect, r_advect, geometry) + dt, vperp_spectral, composition, z_advect, r_advect, geometry, + moments, fields, t) + + # if appropriate, update z and r speeds + update_z_r_speeds!(z_advect, r_advect, fvec_in, moments, fields, + geometry, vpa, vperp, z, r, t) + begin_s_r_z_vpa_region() @loop_s is begin - # get the updated speed along the r direction using the current f + # get the updated speed along the vperp direction using the current f @views update_speed_vperp!(vperp_advect[is], vpa, vperp, z, r, z_advect[is], r_advect[is], geometry) @loop_r_z_vpa ir iz ivpa begin @views advance_f_local!(f_out[ivpa,:,iz,ir,is], fvec_in.pdf[ivpa,:,iz,ir,is], @@ -58,4 +66,34 @@ function update_speed_vperp!(vperp_advect, vpa, vperp, z, r, z_advect, r_advect, return nothing end +function update_z_r_speeds!(z_advect, r_advect, fvec_in, moments, fields, + geometry, vpa, vperp, z, r, t) + update_z_speed = (z.n == 1 && geometry.input.option == "0D-Spitzer-test") + if update_z_speed + # make sure z speed is physical despite + # 0D nature of simulation + begin_s_r_vperp_vpa_region() + @loop_s is begin + # get the updated speed along the z direction using the current f + @views update_speed_z!(z_advect[is], fvec_in.upar[:,:,is], + moments.ion.vth[:,:,is], moments.evolve_upar, + moments.evolve_ppar, fields, vpa, vperp, z, r, t, geometry, is) + end + end + + update_r_speed = (r.n == 1 && geometry.input.option == "0D-Spitzer-test") + if update_r_speed + # make sure r speed is physical despite + # 0D nature of simulation + begin_s_z_vperp_vpa_region() + @loop_s is begin + # get the updated speed along the r direction using the current f + update_speed_r!(r_advect[is], fvec_in.upar[:,:,is], + moments.ion.vth[:,:,is], fields, moments.evolve_upar, + moments.evolve_ppar, vpa, vperp, z, r, geometry, is) + end + end + return nothing +end + end diff --git a/moment_kinetics/test/Krook_collisions_tests.jl b/moment_kinetics/test/Krook_collisions_tests.jl index 91e6bb7de..9bb7197bf 100644 --- a/moment_kinetics/test/Krook_collisions_tests.jl +++ b/moment_kinetics/test/Krook_collisions_tests.jl @@ -228,7 +228,7 @@ function run_test(test_input, rtol, atol; args...) # load velocity moments data n_ion_zrst, upar_ion_zrst, ppar_ion_zrst, qpar_ion_zrst, v_t_ion_zrst = load_ion_moments_data(fid) n_neutral_zrst, upar_neutral_zrst, ppar_neutral_zrst, qpar_neutral_zrst, v_t_neutral_zrst = load_neutral_particle_moments_data(fid) - z, z_spectral = load_coordinate_data(fid, "z") + z, z_spectral = load_coordinate_data(fid, "z"; ignore_MPI=true) close(fid) @@ -238,7 +238,7 @@ function run_test(test_input, rtol, atol; args...) # load particle distribution function (pdf) data f_ion_vpavperpzrst = load_pdf_data(fid) f_neutral_vzvrvzetazrst = load_neutral_pdf_data(fid) - vpa, vpa_spectral = load_coordinate_data(fid, "vpa") + vpa, vpa_spectral = load_coordinate_data(fid, "vpa"; ignore_MPI=true) close(fid) diff --git a/moment_kinetics/test/braginskii_electrons_imex_tests.jl b/moment_kinetics/test/braginskii_electrons_imex_tests.jl new file mode 100644 index 000000000..f784e0cf2 --- /dev/null +++ b/moment_kinetics/test/braginskii_electrons_imex_tests.jl @@ -0,0 +1,316 @@ +module BraginskiiElectronsIMEX + +# Regression test using wall boundary conditions, with recycling fraction less than 1 and +# a plasma source. Runs for a while and then checks phi profile against saved reference +# output. + +include("setup.jl") + +using Base.Filesystem: tempname +using MPI + +using moment_kinetics.coordinates: define_coordinate +using moment_kinetics.input_structs: grid_input, advection_input +using moment_kinetics.interpolation: interpolate_to_grid_z +using moment_kinetics.load_data: get_run_info_no_setup, close_run_info, get_variable + +# default inputs for tests +test_input = Dict("n_ion_species" => 1, + "n_neutral_species" => 1, + "electron_physics" => "braginskii_fluid", + "run_name" => "braginskii-electrons-imex", + "evolve_moments_density" => true, + "evolve_moments_parallel_flow" => true, + "evolve_moments_parallel_pressure" => true, + "evolve_moments_conservation" => true, + "T_e" => 0.2, + "nu_ei" => 1.0e3, + "initial_density1" => 1.0, + "initial_temperature1" => 1.0, + "z_IC_option1" => "sinusoid", + "z_IC_density_amplitude1" => 0.1, + "z_IC_density_phase1" => 0.0, + "z_IC_upar_amplitude1" => 1.0, + "z_IC_upar_phase1" => 0.0, + "z_IC_temperature_amplitude1" => 0.1, + "z_IC_temperature_phase1" => 1.0, + "vpa_IC_option1" => "gaussian", + "vpa_IC_density_amplitude1" => 1.0, + "vpa_IC_density_phase1" => 0.0, + "vpa_IC_upar_amplitude1" => 0.0, + "vpa_IC_upar_phase1" => 0.0, + "vpa_IC_temperature_amplitude1" => 0.0, + "vpa_IC_temperature_phase1" => 0.0, + "initial_density2" => 1.0, + "initial_temperature2" => 1.0, + "z_IC_option2" => "sinusoid", + "z_IC_density_amplitude2" => 0.001, + "z_IC_density_phase2" => 0.0, + "z_IC_upar_amplitude2" => 0.0, + "z_IC_upar_phase2" => 0.0, + "z_IC_temperature_amplitude2" => 0.0, + "z_IC_temperature_phase2" => 0.0, + "vpa_IC_option2" => "gaussian", + "vpa_IC_density_amplitude2" => 1.0, + "vpa_IC_density_phase2" => 0.0, + "vpa_IC_upar_amplitude2" => 0.0, + "vpa_IC_upar_phase2" => 0.0, + "vpa_IC_temperature_amplitude2" => 0.0, + "vpa_IC_temperature_phase2" => 0.0, + "charge_exchange_frequency" => 0.75, + "ionization_frequency" => 0.5, + "constant_ionization_rate" => false, + "timestepping" => Dict{String,Any}("type" => "KennedyCarpenterARK324", + "implicit_ion_advance" => false, + "implicit_vpa_advection" => false, + "nstep" => 10000, + "dt" => 1.0e-6, + "minimum_dt" => 1.e-7, + "rtol" => 1.0e-7, + "nwrite" => 10000, + "high_precision_error_sum" => true), + "nonlinear_solver" => Dict{String,Any}("nonlinear_max_iterations" => 100), + "r_ngrid" => 1, + "r_nelement" => 1, + "z_ngrid" => 17, + "z_nelement" => 16, + "z_bc" => "periodic", + "z_discretization" => "chebyshev_pseudospectral", + "vpa_ngrid" => 6, + "vpa_nelement" => 31, + "vpa_L" => 12.0, + "vpa_bc" => "zero", + "vpa_discretization" => "chebyshev_pseudospectral", + "vz_ngrid" => 6, + "vz_nelement" => 31, + "vz_L" => 12.0, + "vz_bc" => "zero", + "vz_discretization" => "chebyshev_pseudospectral", + "ion_numerical_dissipation" => Dict{String,Any}("force_minimum_pdf_value" => 0.0, + "vpa_dissipation_coefficient" => 1e0), + "neutral_numerical_dissipation" => Dict{String,Any}("force_minimum_pdf_value" => 0.0, + "vz_dissipation_coefficient" => 1e-1)) + +if global_size[] > 2 && global_size[] % 2 == 0 + # Test using distributed-memory + test_input["z_nelement_local"] = test_input["z_nelement"] ÷ 2 +end + +""" +Run a test for a single set of parameters +""" +function run_test(test_input, expected_p, expected_q, expected_vt; rtol=1.e-6, + atol=1.e-8, args...) + # by passing keyword arguments to run_test, args becomes a Tuple of Pairs which can be + # used to update the default inputs + + # Make a copy to make sure nothing modifies the input Dicts defined in this test + # script. + test_input = deepcopy(test_input) + + # Convert keyword arguments to a unique name + name = test_input["run_name"] + if length(args) > 0 + name = string(name, "_", (string(k, "-", v, "_") for (k, v) in args)...) + + # Remove trailing "_" + name = chop(name) + end + + # Provide some progress info + println(" - testing ", name) + + # Convert dict from symbol keys to String keys + modified_inputs = Dict(String(k) => v for (k, v) in args) + + # Update default inputs with values to be changed + input = merge(test_input, modified_inputs) + + input["run_name"] = name + + # Suppress console output while running + p = undef + q = undef + vt = undef + quietoutput() do + # run simulation + run_moment_kinetics(input) + end + + if global_rank[] == 0 + quietoutput() do + # Load and analyse output + ######################### + + path = joinpath(realpath(input["base_directory"]), name) + + # open the output file + run_info = get_run_info_no_setup(path) + + parallel_pressure_zrt = get_variable(run_info, "electron_parallel_pressure") + parallel_heat_flux_zrt = get_variable(run_info, "electron_parallel_heat_flux") + thermal_speed_zrt = get_variable(run_info, "electron_thermal_speed") + + close_run_info(run_info) + + p = parallel_pressure_zrt[:,1,:] + q = parallel_heat_flux_zrt[:,1,:] + vt = thermal_speed_zrt[:,1,:] + end + + # Regression test + actual_p = p[begin:3:end, end] + actual_q = q[begin:3:end, end] + actual_vt = vt[begin:3:end, end] + if expected_p == nothing + # Error: no expected input provided + println("data tested would be: ", actual_p) + @test false + else + @test isapprox(actual_p, expected_p, rtol=rtol, atol=atol) + end + if expected_q == nothing + # Error: no expected input provided + println("data tested would be: ", actual_q) + @test false + else + @test isapprox(actual_q, expected_q, rtol=10.0*rtol, atol=atol) + end + if expected_vt == nothing + # Error: no expected input provided + println("data tested would be: ", actual_vt) + @test false + else + @test isapprox(actual_vt, expected_vt, rtol=rtol, atol=atol) + end + end +end + +function runtests() + # Create a temporary directory for test output + test_output_directory = get_MPI_tempdir() + + expected_p = [0.4495141154262702, 0.44810731623424865, 0.4446924860674211, + 0.44101474518047623, 0.43843729172365287, 0.43732257545500436, + 0.4369771896163651, 0.43585040736153463, 0.43476981731494, + 0.43442274081030574, 0.43458934725870224, 0.43468321808979, + 0.4350672174585142, 0.4362345707586601, 0.4382356226789203, + 0.44027341593371017, 0.44114469018060415, 0.4420726748472857, + 0.4448144055192906, 0.44890293982902774, 0.45302566125041793, + 0.455412309803846, 0.45627258226390244, 0.45973185720339665, + 0.46533867318637956, 0.47137720587404724, 0.4755972251834676, + 0.4767157199899437, 0.479982869902296, 0.48630210267423224, + 0.4937119745209616, 0.499632228117403, 0.5018851956608419, + 0.5041516147429022, 0.5102382404520471, 0.5181350935202635, + 0.5251274962417639, 0.528840009619943, 0.5301266410196889, + 0.5350545811435541, 0.5423140663994042, 0.5492817851159084, + 0.5536930382468159, 0.5548035497737663, 0.5579127105543162, + 0.5633811248630228, 0.5689228569091898, 0.5726991310447442, + 0.5739863254025046, 0.5751980350089889, 0.5780379005475736, + 0.5808090106835145, 0.5823710691200007, 0.5828440926355674, + 0.5829481350770388, 0.5830503832825662, 0.5822895468763042, + 0.5804173591751022, 0.5785672859272517, 0.5780103965926895, + 0.5762363699105814, 0.572237324021336, 0.5667183507722322, + 0.5617561268531523, 0.559752846313555, 0.5576782694282233, + 0.5518326820337154, 0.543711872849146, 0.5360739378273777, + 0.5318633271279509, 0.530380132078173, 0.5245886109720006, + 0.5157429370998954, 0.5068932335599383, 0.5010911857600961, + 0.49960342886504683, 0.4953735336353052, 0.4876627291240921, + 0.4793665506564848, 0.4732825693615421, 0.4710889716397071, + 0.46894821577973017, 0.4635186380600756, 0.45714663551681506, + 0.4521183598666343, 0.4496787838015138] + expected_q = [0.6827418582524897, 0.6759389228503324, 0.6546242399054403, + 0.6207644558519532, 0.5852347231333254, 0.564287918118347, + 0.556689983477969, 0.5259471969259888, 0.4756862865761334, + 0.4212550001972507, 0.3831394924057264, 0.37303276222778314, + 0.343503487668719, 0.28638988969921425, 0.2194175020090074, + 0.1658803337183493, 0.14548865444145112, 0.1249612385669573, + 0.06972848884953677, -0.0022814642388705414, -0.06656443212989245, + -0.10097001502687641, -0.11294895521598257, -0.15913617106073358, + -0.22827644490625998, -0.2963224914648622, -0.3405683906933329, + -0.3518831487295314, -0.3840082790031256, -0.44248719673145465, + -0.5054051728451866, -0.551574215204323, -0.5682200554009741, + -0.5844574843383253, -0.6255436186448009, -0.6733338973995404, + -0.7102497022619719, -0.7276721907765155, -0.7333397406079434, + -0.7531999175389356, -0.7766606922910467, -0.7916849353418282, + -0.7966732798546298, -0.7972935019748215, -0.7974998065347064, + -0.7914511420487961, -0.7742462932837018, -0.7531170349745917, + -0.7434154933841022, -0.732749549391789, -0.6996770392831854, + -0.6472925144162879, -0.5920338583314992, -0.5592837663247994, + -0.5473759749629427, -0.49905814442640656, -0.419796532987056, + -0.3339940482100009, -0.27421323483476967, -0.2584305361791927, + -0.21253786937371122, -0.12493437607128674, -0.024809724391701034, + 0.05267445297594558, 0.08149394898045487, 0.11008483580643007, + 0.18477109572661538, 0.27671771664185124, 0.353033924201842, + 0.3914632816914362, 0.4044296067758364, 0.4523545483111222, + 0.5176797539993653, 0.5739945138569089, 0.6061138463121605, + 0.6137350794737616, 0.6340122577148257, 0.6655474631299673, + 0.6911272362100365, 0.7037896304781194, 0.7069493638375064, + 0.7092542139170925, 0.7113077755769187, 0.7055330024527745, + 0.692930113700812, 0.6834744521342498] + expected_vt = [60.503243776596165, 60.46153023170041, 60.35196357745784, + 60.21534169442452, 60.099829334123406, 60.040677146084676, + 60.02054030308586, 59.945192607070005, 59.83956332270126, + 59.74466073286034, 59.6881815170369, 59.67443939896856, + 59.63705527455284, 59.5755966280989, 59.52003881563317, + 59.487303746230786, 59.47740469832934, 59.46882266803735, + 59.45242085810586, 59.44514055782754, 59.45162966582038, + 59.460023392631314, 59.4637436896616, 59.48193396567828, + 59.520607349859056, 59.57229303072903, 59.6134051525281, + 59.62490374169246, 59.65981194145, 59.73238824637004, + 59.82488582955655, 59.90379982182214, 59.93490096692693, + 59.96675491891175, 60.05500747114224, 60.17514126096532, + 60.28669920214769, 60.34792696001425, 60.36947746684328, + 60.453647521646644, 60.582668964615706, 60.71293949062846, + 60.79930544278526, 60.82159978880084, 60.88535831265987, + 61.003191250737984, 61.13271036566074, 61.22982940090206, + 61.26534648299963, 61.300283935289386, 61.390210936392684, + 61.49842988504546, 61.58617772618817, 61.6295965023691, + 61.64411705987245, 61.697148185872855, 61.767402554884846, + 61.8251039647873, 61.85602560032788, 61.863044731750065, + 61.880896838249456, 61.90494747307992, 61.91707206994147, + 61.91528656108256, 61.91206502126355, 61.90744824569871, + 61.88836065205279, 61.84936481884471, 61.80151925202637, + 61.77096817847411, 61.759530818105134, 61.711578242982426, + 61.628367571043206, 61.532972492870755, 61.46358631318027, + 61.444884310802, 61.38960250589494, 61.280335702777364, + 61.149196249162785, 61.04274069753091, 61.0019364120191, + 60.960757611821634, 60.849658312940456, 60.704804495390036, + 60.57626380073712, 60.50801542345454] + + @testset "Braginskii electron IMEX timestepping" verbose=use_verbose begin + println("Braginskii electron IMEX timestepping tests") + + if Sys.isapple() + @testset_skip "MINPACK is broken on macOS (https://github.com/sglyon/MINPACK.jl/issues/18)" "non-linear solvers" begin + end + else + @testset "Split 3" begin + test_input["base_directory"] = test_output_directory + run_test(test_input, expected_p, expected_q, expected_vt) + end + @long @testset "Check other timestep - $type" for + type ∈ ("KennedyCarpenterARK437",) + + timestep_check_input = deepcopy(test_input) + timestep_check_input["base_directory"] = test_output_directory + timestep_check_input["run_name"] = type + timestep_check_input["timestepping"]["type"] = type + run_test(timestep_check_input, expected_p, expected_q, expected_vt, + rtol=2.e-4, atol=1.e-10) + end + end + end + + if global_rank[] == 0 + # Delete output directory to avoid using too much disk space + rm(realpath(test_output_directory); recursive=true) + end +end + +end # BraginskiiElectronsIMEX + + +using .BraginskiiElectronsIMEX + +BraginskiiElectronsIMEX.runtests() diff --git a/moment_kinetics/test/calculus_tests.jl b/moment_kinetics/test/calculus_tests.jl index e439ac4cf..ef13b75bb 100644 --- a/moment_kinetics/test/calculus_tests.jl +++ b/moment_kinetics/test/calculus_tests.jl @@ -5,6 +5,7 @@ include("setup.jl") using moment_kinetics.input_structs: grid_input, advection_input using moment_kinetics.coordinates: define_coordinate using moment_kinetics.calculus: derivative!, second_derivative!, integral +using moment_kinetics.calculus: laplacian_derivative! using MPI using Random @@ -1355,6 +1356,101 @@ function runtests() end end + @testset "Chebyshev pseudospectral cylindrical laplacian derivatives (4 argument), zero" verbose=false begin + @testset "$nelement $ngrid" for (nelement, ngrid, rtol) ∈ + ( + (4, 7, 2.e-1), + (4, 8, 2.e-1), + (4, 9, 4.e-2), + (4, 10, 4.e-2), + (4, 11, 5.e-3), + (4, 12, 5.e-3), + (4, 13, 5.e-3), + (4, 14, 5.e-3), + (4, 15, 5.e-3), + (4, 16, 5.e-3), + (4, 17, 5.e-3), + (4, 18, 5.e-3), + (4, 19, 5.e-3), + (4, 20, 5.e-3), + (4, 21, 5.e-3), + (4, 22, 5.e-3), + (4, 23, 5.e-3), + (4, 24, 4.e-3), + (4, 25, 4.e-3), + (4, 26, 4.e-3), + (4, 27, 4.e-3), + (4, 28, 4.e-3), + (4, 29, 4.e-3), + (4, 30, 4.e-3), + (4, 31, 4.e-3), + (4, 32, 4.e-3), + (4, 33, 4.e-3), + + (5, 7, 2.e-1), + (5, 8, 8.e-2), + (5, 9, 5.e-2), + (5, 10, 8.e-3), + (5, 11, 8.e-3), + (5, 12, 8.e-3), + (5, 13, 8.e-3), + (5, 14, 8.e-3), + (5, 15, 8.e-3), + (5, 16, 2.e-3), + (5, 17, 2.e-3), + (5, 18, 2.e-3), + (5, 19, 2.e-3), + (5, 20, 2.e-3), + (5, 21, 2.e-3), + (5, 22, 2.e-3), + (5, 23, 2.e-3), + (5, 24, 2.e-3), + (5, 25, 2.e-3), + (5, 26, 2.e-3), + (5, 27, 2.e-3), + (5, 28, 2.e-3), + (5, 29, 2.e-3), + (5, 30, 2.e-3), + (5, 31, 2.e-3), + (5, 32, 2.e-3), + (5, 33, 2.e-3), + ), cheb_option in ("FFT","matrix") + + # define inputs needed for the test + L = 6.0 + bc = "zero" + # fd_option and adv_input not actually used so given values unimportant + fd_option = "" + adv_input = advection_input("default", 1.0, 0.0, 0.0) + # create the 'input' struct containing input info needed to create a + # coordinate + nelement_local = nelement + nrank_per_block = 0 # dummy value + irank = 0 # dummy value + comm = MPI.COMM_NULL # dummy value + element_spacing_option = "uniform" + input = grid_input("vperp", ngrid, nelement, + nelement_local, nrank_per_block, irank, L, + "chebyshev_pseudospectral", fd_option, cheb_option, bc, adv_input, comm, + element_spacing_option) + # create the coordinate struct 'x' and info for derivatives, etc. + # This test runs effectively in serial, so use `ignore_MPI=true` to avoid + # errors due to communicators not being fully set up. + x, spectral = define_coordinate(input; ignore_MPI=true) + + f = @. exp(-x.grid^2) + expected_d2f = @. 4.0*(x.grid^2 - 1.0)*exp(-x.grid^2) + # create array for the derivative d2f/dx2 + d2f = similar(f) + + # differentiate f + laplacian_derivative!(d2f, f, x, spectral) + + @test isapprox(d2f, expected_d2f, rtol=rtol, atol=1.e-10, + norm=maxabs_norm) + end + end + @testset "GaussLegendre pseudospectral second derivatives (4 argument), periodic" verbose=false begin @testset "$nelement $ngrid" for (nelement, ngrid, rtol) ∈ ( @@ -1460,6 +1556,111 @@ function runtests() # differentiate f second_derivative!(d2f, f, x, spectral) + @test isapprox(d2f, expected_d2f, rtol=rtol, atol=1.e-10, + norm=maxabs_norm) + end + end + + @testset "GaussLegendre pseudospectral cylindrical laplacian derivatives (4 argument), zero" verbose=false begin + @testset "$nelement $ngrid" for (nelement, ngrid, rtol) ∈ + ( + (1, 8, 5.e-2), + (1, 9, 1.e-1), + (1, 10, 2.e-1), + (1, 11, 5.e-2), + (1, 12, 5.e-2), + (1, 13, 5.e-2), + (1, 14, 5.e-2), + (1, 15, 5.e-3), + (1, 16, 5.e-2), + (1, 17, 5.e-3), + + (2, 6, 8.e-2), + (2, 7, 8.e-2), + (2, 8, 5.e-2), + (2, 9, 5.e-2), + (2, 10, 5.e-2), + (2, 11, 5.e-3), + (2, 12, 5.e-3), + (2, 13, 5.e-4), + (2, 14, 5.e-4), + (2, 15, 5.e-4), + (2, 16, 5.e-4), + (2, 17, 5.e-4), + + (3, 6, 5.e-2), + (3, 7, 5.e-3), + (3, 8, 5.e-2), + (3, 9, 5.e-4), + (3, 10, 5.e-3), + (3, 11, 5.e-4), + (3, 12, 5.e-5), + (3, 13, 5.e-5), + (3, 14, 5.e-6), + (3, 15, 5.e-6), + (3, 16, 5.e-6), + (3, 17, 5.e-8), + + (4, 5, 5.e-2), + (4, 6, 2.e-2), + (4, 7, 2.e-2), + (4, 8, 2.e-3), + (4, 9, 1.e-3), + (4, 10, 1.e-4), + (4, 11, 8.e-5), + (4, 12, 8.e-6), + (4, 13, 8.e-6), + (4, 14, 8.e-7), + (4, 15, 8.e-7), + (4, 16, 8.e-8), + (4, 17, 8.e-8), + + (5, 5, 4.e-2), + (5, 6, 8.e-3), + (5, 7, 5.e-3), + (5, 8, 5.e-4), + (5, 9, 8.e-5), + (5, 10, 5.e-6), + (5, 11, 8.e-6), + (5, 12, 4.e-7), + (5, 13, 2.e-7), + (5, 14, 2.e-7), + (5, 15, 8.e-7), + (5, 16, 8.e-10), + (5, 17, 8.e-10), + ) + + # define inputs needed for the test + L = 6.0 + bc = "zero" + # fd_option and adv_input not actually used so given values unimportant + fd_option = "" + adv_input = advection_input("default", 1.0, 0.0, 0.0) + cheb_option = "" + # create the 'input' struct containing input info needed to create a + # coordinate + nelement_local = nelement + nrank_per_block = 0 # dummy value + irank = 0 # dummy value + comm = MPI.COMM_NULL # dummy value + element_spacing_option = "uniform" + input = grid_input("vperp", ngrid, nelement, + nelement_local, nrank_per_block, irank, L, + "gausslegendre_pseudospectral", fd_option, cheb_option, bc, adv_input, comm, + element_spacing_option) + # create the coordinate struct 'x' and info for derivatives, etc. + # This test runs effectively in serial, so use `ignore_MPI=true` to avoid + # errors due to communicators not being fully set up. + x, spectral = define_coordinate(input; ignore_MPI=true, collision_operator_dim=false) + + f = @. exp(-x.grid^2) + expected_d2f = @. 4.0*(x.grid^2 - 1.0)*exp(-x.grid^2) + # create array for the derivative d2f/dx2 + d2f = similar(f) + + # differentiate f + laplacian_derivative!(d2f, f, x, spectral) + @test isapprox(d2f, expected_d2f, rtol=rtol, atol=1.e-10, norm=maxabs_norm) end diff --git a/moment_kinetics/test/fokker_planck_time_evolution_tests.jl b/moment_kinetics/test/fokker_planck_time_evolution_tests.jl index 08239c0f7..4ce363165 100644 --- a/moment_kinetics/test/fokker_planck_time_evolution_tests.jl +++ b/moment_kinetics/test/fokker_planck_time_evolution_tests.jl @@ -34,7 +34,7 @@ struct expected_data f_ion::Array{mk_float, 3} # vpa, vperp, time end -const expected = +const expected_zero_impose_regularity = expected_data( [-3.0, -2.5, -2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0], [0.155051025721682, 0.644948974278318, 1.000000000000000, 1.500000000000000, 2.000000000000000, 2.500000000000000, 3.000000000000000], @@ -81,34 +81,145 @@ const expected = 0.005877384425022965 0.00466291282312835 0.0028530945920350282 0.0008130106752860425 2.4399432485774198e-5 -9.743480904212476e-5 0.0; 0.00017108987342944414 7.097261590252536e-5 -7.822316408657793e-5 -0.0001534897546375058 -9.087984332447822e-5 -3.3079379573126077e-5 0.0; 0.0 0.0 0.0 0.0 0.0 0.0 0.0]) +const expected_zero = +expected_data( + [-3.0, -2.5, -2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0], + [0.155051025721682, 0.644948974278318, 1.000000000000000, 1.500000000000000, 2.000000000000000, 2.500000000000000, 3.000000000000000], + # Expected phi: + [-1.254259088243025, -1.254259088243286], + # Expected n_ion: + [0.285287142537587, 0.285287142537513], + # Expected upar_ion: + [0.0, 0.0], + # Expected ppar_ion: + [0.182220654804438, 0.156448883483764], + # Expected pperp_ion + [0.143306715174515, 0.156192600834786], + # Expected qpar_ion + [0.0, 0.0], + # Expected v_t_ion + [1.046701532502699, 1.046701532502689], + # Expected dSdt + [0.000000000000000, -0.000000000865997], + # Expected f_ion: + [0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 ; + 0.000706724195475 0.000477575434536 0.000266315395816 0.000076300638379 0.000013259062819 0.000001397494940 0.000000000000000 ; + 0.006705212475828 0.004531109564814 0.002526730124657 0.000723920301085 0.000125798485463 0.000013259062819 0.000000000000000 ; + 0.038585833650058 0.026074735222541 0.014540327934436 0.004165873701136 0.000723920300963 0.000076300638366 0.000000000000000 ; + 0.134671678398729 0.091005636629981 0.050748427134097 0.014539667807029 0.002526615411287 0.000266303305116 0.000000000000000 ; + 0.261709398369233 0.176852554997681 0.098620144126568 0.028255144359305 0.004910007858078 0.000517511020834 0.000000000000000 ; + 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 ; + 0.261709398369233 0.176852554997682 0.098620144126568 0.028255144359305 0.004910007858078 0.000517511020834 0.000000000000000 ; + 0.134671678398729 0.091005636629981 0.050748427134097 0.014539667807029 0.002526615411287 0.000266303305116 0.000000000000000 ; + 0.038585833650058 0.026074735222541 0.014540327934436 0.004165873701136 0.000723920300963 0.000076300638366 0.000000000000000 ; + 0.006705212475828 0.004531109564814 0.002526730124657 0.000723920301085 0.000125798485463 0.000013259062819 0.000000000000000 ; + 0.000706724195475 0.000477575434536 0.000266315395816 0.000076300638379 0.000013259062819 0.000001397494940 0.000000000000000 ; + 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 ;;; + 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 ; + 0.000473874379555 0.000190726186343 -0.000067413540342 -0.000219129923463 -0.000165609965457 -0.000069940808382 0.000000000000000 ; + 0.007980077110978 0.005701898246888 0.003327438535529 0.000925955494251 -0.000037963012435 -0.000174809935222 0.000000000000000 ; + 0.034010103035913 0.024541441396039 0.014741892515397 0.004975785949797 0.000869171347288 -0.000277723510529 0.000000000000000 ; + 0.095320265225675 0.069160937049138 0.041953067849510 0.014711859721633 0.003235165444330 -0.000199356792174 0.000000000000000 ; + 0.176443375955397 0.128736406466194 0.078839749105573 0.028150317244036 0.006528121645593 0.000119350436884 0.000000000000000 ; + 0.215552595150568 0.157471466895046 0.096656199821596 0.034660213647059 0.008120924795998 0.000303073103709 0.000000000000000 ; + 0.176443375955397 0.128736406466194 0.078839749105573 0.028150317244036 0.006528121645593 0.000119350436884 0.000000000000000 ; + 0.095320265225675 0.069160937049138 0.041953067849510 0.014711859721633 0.003235165444330 -0.000199356792174 0.000000000000000 ; + 0.034010103035913 0.024541441396039 0.014741892515397 0.004975785949797 0.000869171347288 -0.000277723510529 0.000000000000000 ; + 0.007980077110978 0.005701898246888 0.003327438535529 0.000925955494251 -0.000037963012435 -0.000174809935222 0.000000000000000 ; + 0.000473874379555 0.000190726186343 -0.000067413540342 -0.000219129923463 -0.000165609965457 -0.000069940808382 0.000000000000000 ; + 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000]) + +const expected_none_bc = +expected_data( + [-3.0, -2.5, -2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0], + [0.155051025721682, 0.644948974278318, 1.000000000000000, 1.500000000000000, 2.000000000000000, 2.500000000000000, 3.000000000000000], + # Expected phi: + [-1.254104813718096, -1.254104813718360], + # Expected n_ion: + [0.285331158471152, 0.285331158471077], + # Expected upar_ion: + [0.0, 0.0], + # Expected ppar_ion: + [0.182321263834348, 0.154595996906667], + # Expected pperp_ion + [0.143470130069393, 0.157332763533171], + # Expected qpar_ion + [0.0, 0.0], + # Expected v_t_ion + [1.047097792428007, 1.047097792428005], + # Expected dSdt + [0.000000000000000, 0.000000019115425], + # Expected f_ion: + [0.000045179366280 0.000030530376095 0.000017024973661 0.000004877736620 0.000000847623528 0.000000089338863 0.000000005711242 ; + 0.000706724195475 0.000477575434536 0.000266315395816 0.000076300638379 0.000013259062819 0.000001397494940 0.000000089338863 ; + 0.006705212475828 0.004531109564814 0.002526730124657 0.000723920301085 0.000125798485463 0.000013259062819 0.000000847623528 ; + 0.038585833650058 0.026074735222541 0.014540327934436 0.004165873701136 0.000723920300963 0.000076300638366 0.000004877736619 ; + 0.134671678398729 0.091005636629981 0.050748427134097 0.014539667807029 0.002526615411287 0.000266303305116 0.000017024200728 ; + 0.261709398369233 0.176852554997681 0.098620144126568 0.028255144359305 0.004910007858078 0.000517511020834 0.000033083372713 ; + 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 ; + 0.261709398369233 0.176852554997682 0.098620144126568 0.028255144359305 0.004910007858078 0.000517511020834 0.000033083372713 ; + 0.134671678398729 0.091005636629981 0.050748427134097 0.014539667807029 0.002526615411287 0.000266303305116 0.000017024200728 ; + 0.038585833650058 0.026074735222541 0.014540327934436 0.004165873701136 0.000723920300963 0.000076300638366 0.000004877736619 ; + 0.006705212475828 0.004531109564814 0.002526730124657 0.000723920301085 0.000125798485463 0.000013259062819 0.000000847623528 ; + 0.000706724195475 0.000477575434536 0.000266315395816 0.000076300638379 0.000013259062819 0.000001397494940 0.000000089338863 ; + 0.000045179366280 0.000030530376095 0.000017024973661 0.000004877736620 0.000000847623528 0.000000089338863 0.000000005711242 ;;; + 0.000447615535468 0.000364801746561 0.000270093752229 0.000138454732861 0.000052917434226 0.000014331973588 -0.000000022222325 ; + 0.000530676740860 0.000337644325418 0.000161654162384 0.000044642657051 0.000030848860156 0.000021605350900 0.000014496785601 ; + 0.006652401806692 0.004725322877613 0.002773765135978 0.000908975538425 0.000210924069594 0.000037798409136 0.000057517483301 ; + 0.030674916797523 0.021423584760802 0.012211671337997 0.003838328661082 0.000895825759878 0.000045643481270 0.000159762122747 ; + 0.097751022951371 0.068620792366922 0.039365625553724 0.012397445282748 0.002818297719268 0.000149127033057 0.000311439525732 ; + 0.194564361178625 0.137570544970062 0.079900546066037 0.025549350260855 0.005673491663727 0.000408759661293 0.000445141805523 ; + 0.242684098382075 0.171867841736931 0.100113917461427 0.032148650920148 0.007091277425658 0.000554833974109 0.000500018986081 ; + 0.194564361178625 0.137570544970062 0.079900546066037 0.025549350260855 0.005673491663727 0.000408759661293 0.000445141805523 ; + 0.097751022951371 0.068620792366922 0.039365625553724 0.012397445282748 0.002818297719268 0.000149127033057 0.000311439525732 ; + 0.030674916797523 0.021423584760802 0.012211671337997 0.003838328661082 0.000895825759878 0.000045643481270 0.000159762122747 ; + 0.006652401806692 0.004725322877613 0.002773765135978 0.000908975538425 0.000210924069594 0.000037798409136 0.000057517483301 ; + 0.000530676740860 0.000337644325418 0.000161654162384 0.000044642657051 0.000030848860156 0.000021605350900 0.000014496785601 ; + 0.000447615535468 0.000364801746561 0.000270093752229 0.000138454732861 0.000052917434226 0.000014331973588 -0.000000022222325]) ########################################################################################### # to modify the test, with a new expected f, print the new f using the following commands # in an interative Julia REPL. The path is the path to the .dfns file. ########################################################################################## """ +using moment_kinetics.load_data: open_readonly_output_file, load_pdf_data, load_ion_moments_data, load_fields_data fid = open_readonly_output_file(path, "dfns") f_ion_vpavperpzrst = load_pdf_data(fid) f_ion = f_ion_vpavperpzrst[:,:,1,1,1,:] ntind = 2 nvpa = 13 #subject to grid choices nvperp = 7 #subject to grid choices +# pdf for k in 1:ntind - for j in 1:nvperp-1 - for i in 1:nvpa-1 - @printf("%.15f ", f_ion[i,j,k]) - print("; ") - end - @printf("%.15f ", f_ion[nvpa,j,k]) - print(";;\n") - end - for i in 1:nvpa-1 - @printf("%.15f ", f_ion[i,nvperp,k]) - print("; ") - end - @printf("%.15f ", f_ion[nvpa,nvperp,k]) - if k < ntind - print(";;;\n") - end + for i in 1:nvpa-1 + for j in 1:nvperp-1 + @printf("%.15f ", f_ion[i,j,k]) + end + @printf("%.15f ", f_ion[i,nvperp,k]) + print(";\n") + end + for j in 1:nvperp-1 + @printf("%.15f ", f_ion[nvpa,j,k]) + end + @printf("%.15f ", f_ion[nvpa,nvperp,k]) + if k < ntind + print(";;;\n") + end +end +# a moment +n_ion_zrst, upar_ion_zrst, ppar_ion_zrst, pperp_ion_zrst, qpar_ion_zrst, v_t_ion_zrst, dSdt_zrst = load_ion_moments_data(fid,extended_moments=true) +for k in 1:ntind + @printf("%.15f", n_ion_zrst[1,1,1,k]) + if k < ntind + print(", ") + end +end +# a field +phi_zrt, Er_zrt, Ez_zrt = load_fields_data(fid) +for k in 1:ntind + @printf("%.15f", phi_zrt[1,1,k]) + if k < ntind + print(", ") + end end """ # default inputs for tests @@ -128,7 +239,7 @@ test_input_gauss_legendre = Dict("run_name" => "gausslegendre_pseudospectral", "vperp_nelement" => 3, "vperp_L" => 3.0, "vperp_discretization" => "gausslegendre_pseudospectral", - "split_operators" => false, + #"split_operators" => false, "ionization_frequency" => 0.0, "charge_exchange_frequency" => 0.0, "constant_ionization_rate" => false, @@ -175,14 +286,14 @@ test_input_gauss_legendre = Dict("run_name" => "gausslegendre_pseudospectral", Run a sound-wave test for a single set of parameters """ # Note 'name' should not be shared by any two tests in this file -function run_test(test_input, rtol, atol, upar_rtol=nothing; args...) +function run_test(test_input, expected, rtol, atol, upar_rtol=nothing; args...) # by passing keyword arguments to run_test, args becomes a Dict which can be used to # update the default inputs # Make a copy to make sure nothing modifies the input Dicts defined in this test # script. test_input = deepcopy(test_input) - + if upar_rtol === nothing upar_rtol = rtol end @@ -204,10 +315,8 @@ function run_test(test_input, rtol, atol, upar_rtol=nothing; args...) # Update default inputs with values to be changed input = merge(test_input, modified_inputs) - #input = test_input input["run_name"] = name - # Suppress console output while running quietoutput() do # run simulation @@ -255,8 +364,8 @@ function run_test(test_input, rtol, atol, upar_rtol=nothing; args...) # open the netcdf file containing pdf data fid = open_readonly_output_file(path, "dfns") # load coordinates - vpa, vpa_spectral = load_coordinate_data(fid, "vpa") - vperp, vperp_spectral = load_coordinate_data(fid, "vperp") + vpa, vpa_spectral = load_coordinate_data(fid, "vpa"; ignore_MPI=true) + vperp, vperp_spectral = load_coordinate_data(fid, "vperp"; ignore_MPI=true) # load particle distribution function (pdf) data f_ion_vpavperpzrst = load_pdf_data(fid) @@ -325,7 +434,26 @@ function runtests() # GaussLegendre pseudospectral # Benchmark data is taken from this run (GaussLegendre) @testset "Gauss Legendre base" begin - run_test(test_input_gauss_legendre, 1.e-14, 1.0e-14 ) + run_name = "gausslegendre_pseudospectral" + vperp_bc = "zero-impose-regularity" + run_test(test_input_gauss_legendre, + expected_zero_impose_regularity, 1.0e-14, 1.0e-14; + vperp_bc=vperp_bc) + end + @testset "Gauss Legendre no enforced regularity condition at vperp = 0" begin + run_name = "gausslegendre_pseudospectral_no_regularity" + vperp_bc = "zero" + run_test(test_input_gauss_legendre, + expected_zero, + 1.0e-14, 1.0e-14; vperp_bc=vperp_bc) + end + @testset "Gauss Legendre no (explicitly) enforced boundary conditions" begin + run_name = "gausslegendre_pseudospectral_none_bc" + vperp_bc = "none" + vpa_bc = "none" + run_test(test_input_gauss_legendre, + expected_none_bc, + 1.0e-14, 1.0e-14; vperp_bc=vperp_bc, vpa_bc=vpa_bc) end end end diff --git a/moment_kinetics/test/gyroaverage_tests.jl b/moment_kinetics/test/gyroaverage_tests.jl index 38a6c7085..21c43f0b7 100644 --- a/moment_kinetics/test/gyroaverage_tests.jl +++ b/moment_kinetics/test/gyroaverage_tests.jl @@ -10,7 +10,7 @@ using SpecialFunctions: besselj0 import moment_kinetics using moment_kinetics.input_structs using moment_kinetics.coordinates: define_coordinate -using moment_kinetics.geo: init_magnetic_geometry +using moment_kinetics.geo: init_magnetic_geometry, setup_geometry_input using moment_kinetics.communication using moment_kinetics.looping using moment_kinetics.array_allocation: allocate_float, allocate_shared_float @@ -142,11 +142,9 @@ function gyroaverage_test(absolute_error; rhostar=0.1, pitch=0.5, ngrid=5, kr=2, gyrophase, gyrophase_spectral = define_coordinate(gyrophase_input; collision_operator_dim=false, run_directory=test_output_directory) # create test geometry - #rhostar = 0.1 #rhostar of ions for ExB drift option = "constant-helical" - #pitch = 1.0 - DeltaB = 1.0 - geometry_in = geometry_input(rhostar,option,pitch,DeltaB) + inputdict = Dict("geometry" => Dict("option" => option, "rhostar" => rhostar, "pitch" => pitch)) + geometry_in = setup_geometry_input(inputdict, 0.1) geometry = init_magnetic_geometry(geometry_in,z,r) # create test composition @@ -243,7 +241,7 @@ function create_test_composition() # wall potential at z = 0 phi_wall = 0.0 # constant to test nonzero Er - Er_constant = 0.0 + #Er_constant = 0.0 # ratio of the neutral particle mass to the ion particle mass mn_over_mi = 1.0 # ratio of the electron particle mass to the ion particle mass @@ -253,7 +251,7 @@ function create_test_composition() recycling_fraction = 1.0 gyrokinetic_ions = true return composition = species_composition(n_species, n_ion_species, n_neutral_species, - electron_physics, use_test_neutral_wall_pdf, T_e, T_wall, phi_wall, Er_constant, + electron_physics, use_test_neutral_wall_pdf, T_e, T_wall, phi_wall, #Er_constant, mn_over_mi, me_over_mi, recycling_fraction, gyrokinetic_ions, allocate_float(n_species)) end diff --git a/moment_kinetics/test/harrisonthompson.jl b/moment_kinetics/test/harrisonthompson.jl index 4e4cada7d..06206a263 100644 --- a/moment_kinetics/test/harrisonthompson.jl +++ b/moment_kinetics/test/harrisonthompson.jl @@ -199,8 +199,8 @@ function run_test(test_input, analytic_rtol, analytic_atol, expected_phi, fid = open_readonly_output_file(path,"moments") # load space-time coordinate data - z, z_spectral, z_chunk_size = load_coordinate_data(fid, "z") - r, r_spectral, r_chunk_size = load_coordinate_data(fid, "r") + z, z_spectral, z_chunk_size = load_coordinate_data(fid, "z"; ignore_MPI=true) + r, r_spectral, r_chunk_size = load_coordinate_data(fid, "r"; ignore_MPI=true) ntime, time = load_time_data(fid) n_ion_species, n_neutral_species = load_species_data(fid) @@ -223,7 +223,11 @@ function run_test(test_input, analytic_rtol, analytic_atol, expected_phi, rtol=10.0*analytic_rtol, atol=analytic_atol) # Regression test - @test isapprox(phi[:, end], expected_phi, rtol=regression_rtol, atol=regression_atol) + if expected_phi === nothing + println("values tested would be ", phi[:,end]) + else + @test isapprox(phi[:, end], expected_phi, rtol=regression_rtol, atol=regression_atol) + end end end @@ -242,44 +246,44 @@ function runtests() @testset "Chebyshev" begin test_input_chebyshev["base_directory"] = test_output_directory run_test(test_input_chebyshev, 3.e-2, 3.e-3, - [-0.8270506701954182, -0.6647482038047513, -0.4359510242978734, - -0.2930090318306279, -0.19789542580389763, -0.14560099254974576, - -0.12410802135258239, -0.11657014257474364, -0.11761846656548933, - -0.11657014257474377, -0.12410802135258239, -0.1456009925497464, - -0.19789542580389616, -0.2930090318306262, -0.435951024297872, - -0.66474820380475, -0.8270506701954171], 5.0e-9, 1.e-15) + [-0.8270506736528097, -0.6647482045160528, -0.43595102198197894, + -0.2930090302314022, -0.19789542449264944, -0.14560099229503182, + -0.12410802088624982, -0.11657014266155726, -0.1176184662051167, + -0.11657014266155688, -0.1241080208862487, -0.14560099229503298, + -0.1978954244926481, -0.2930090302313995, -0.4359510219819795, + -0.6647482045160534, -0.8270506736528144], 5.0e-9, 1.e-15) end @testset "Chebyshev split 1" begin test_input_chebyshev_split1["base_directory"] = test_output_directory run_test(test_input_chebyshev_split1, 3.e-2, 3.e-3, - [-0.808956646073449, -0.6619131832543625, -0.4308291868843453, - -0.295820339728472, -0.19344190006125275, -0.1492514208442407, - -0.11977511930743077, -0.12060863604650167, -0.11342106824862994, - -0.12060863604649866, -0.11977511930742626, -0.14925142084423915, - -0.1934419000612479, -0.295820339728463, -0.4308291868843545, - -0.6619131832543678, -0.808956646073442], 5.0e-9, 1.e-15) + [-0.8089566460734486, -0.6619131832543634, -0.43082918688434424, + -0.29582033972847016, -0.1934419000612522, -0.14925142084423915, + -0.11977511930743077, -0.12060863604650167, -0.11342106824863019, + -0.1206086360464999, -0.11977511930742751, -0.14925142084423915, + -0.19344190006124898, -0.2958203397284666, -0.43082918688435656, + -0.6619131832543697, -0.808956646073445], 5.0e-9, 1.e-15) end @testset "Chebyshev split 2" begin test_input_chebyshev_split2["base_directory"] = test_output_directory - run_test(test_input_chebyshev_split2, 5.e-2, 3.e-3, - [-0.7667804422571606, -0.6128777083267765, -0.39031953439035494, - -0.27326504140885904, -0.15311275955907663, -0.11567486122959246, - -0.09226471519174786, -0.07477085120501512, -0.07206945428218994, - -0.07477085120545898, -0.09226471518828984, -0.11567486123016281, - -0.15311275955613904, -0.273265041412353, -0.3903195344134153, - -0.612877708320375, -0.766780442235556], 5.0e-9, 1.e-15) + run_test(test_input_chebyshev_split2, 6.e-2, 3.e-3, + [-0.7798736739831602, -0.661568214314525, -0.409872886370737, + -0.24444487132869974, -0.17244646306807737, -0.11761557291772232, + -0.09113439652298189, -0.09025928800454038, -0.08814925970784306, + -0.09025928800449955, -0.0911343965228694, -0.1176155729185088, + -0.1724464630676158, -0.24444487132881484, -0.40987288637069097, + -0.6615682143148902, -0.7798736739849054], 5.0e-9, 1.e-15) end # The 'split 3' test is pretty badly resolved, but don't want to increase # run-time! @testset "Chebyshev split 3" begin test_input_chebyshev_split3["base_directory"] = test_output_directory - run_test(test_input_chebyshev_split3, 2.1e-1, 3.e-3, - [-0.5535421015240105, -0.502816770781802, -0.3755477646148533, - -0.24212761527100635, -0.15737450156025806, -0.11242832417550296, - -0.09168434722655881, -0.08653015173768085, -0.0858195594227437, - -0.08653015173768933, -0.09168434722650211, -0.11242832417546023, - -0.15737450156026872, -0.24212761527101284, -0.3755477646149367, - -0.5028167707818142, -0.5535421015238932], 5.0e-9, 1.e-15) + run_test(test_input_chebyshev_split3, 2.5e-1, 3.e-3, + [-0.5012994554414933, -0.4624277373138882, -0.35356695432752266, + -0.22371207174875177, -0.14096934539193717, -0.10082423314545275, + -0.07938834260378662, -0.07480364283744717, -0.07316256734281283, + -0.07480364283744836, -0.07938834260380849, -0.10082423314551169, + -0.14096934539196504, -0.22371207174878788, -0.35356695432739504, + -0.4624277373114037, -0.5012994554370094], 5.0e-9, 1.e-15) end end diff --git a/moment_kinetics/test/nonlinear_solver_tests.jl b/moment_kinetics/test/nonlinear_solver_tests.jl index 5c0b08dfe..23d62acf9 100644 --- a/moment_kinetics/test/nonlinear_solver_tests.jl +++ b/moment_kinetics/test/nonlinear_solver_tests.jl @@ -57,7 +57,8 @@ function linear_test() zeros(mk_float, 0, 0), zeros(mk_float, 0), zeros(mk_float, 0), zeros(mk_float, 0), zeros(mk_float, 0), zeros(mk_float, 0), zeros(mk_float, 0), zeros(mk_float, 0), - zeros(mk_float, 0), zeros(mk_float, 0), + zeros(mk_float, 0), zeros(mk_float, 0), zeros(mk_float, 0), + zeros(mk_float, 0), zeros(mk_float, 0, 0), zeros(mk_float, 0, 0), advection_input("", 0.0, 0.0, 0.0), zeros(mk_float, 0), zeros(mk_float, 0), MPI.COMM_NULL, 1:n, 1:n, @@ -168,7 +169,8 @@ function nonlinear_test() zeros(mk_float, 0, 0), zeros(mk_float, 0), zeros(mk_float, 0), zeros(mk_float, 0), zeros(mk_float, 0), zeros(mk_float, 0), zeros(mk_float, 0), zeros(mk_float, 0), - zeros(mk_float, 0), zeros(mk_float, 0), + zeros(mk_float, 0), zeros(mk_float, 0), zeros(mk_float, 0), + zeros(mk_float, 0), zeros(mk_float, 0, 0), zeros(mk_float, 0, 0), advection_input("", 0.0, 0.0, 0.0), zeros(mk_float, 0), zeros(mk_float, 0), MPI.COMM_NULL, 1:n, 1:n, diff --git a/moment_kinetics/test/recycling_fraction_tests.jl b/moment_kinetics/test/recycling_fraction_tests.jl index 28a22c84f..fed259261 100644 --- a/moment_kinetics/test/recycling_fraction_tests.jl +++ b/moment_kinetics/test/recycling_fraction_tests.jl @@ -110,7 +110,9 @@ test_input_split3 = merge(test_input_split2, "ion_numerical_dissipation" => Dict{String,Any}("force_minimum_pdf_value" => 0.0, "vpa_dissipation_coefficient" => 1e-2), "neutral_numerical_dissipation" => Dict{String,Any}("force_minimum_pdf_value" => 0.0, "vz_dissipation_coefficient" => 1e-2))) test_input_split3["timestepping"] = merge(test_input_split3["timestepping"], - Dict("dt" => 1.0e-5)) + Dict("dt" => 1.0e-5, + "write_error_diagnostics" => true, + "write_steady_state_diagnostics" => true)) # default inputs for adaptive timestepping tests test_input_adaptive = merge(test_input, @@ -240,107 +242,110 @@ function runtests() @long @testset "Full-f" begin test_input["base_directory"] = test_output_directory run_test(test_input, - [-0.05499288668923642, -0.017610447066356092, -0.0014497230450292054, - 0.0015713106015958053, 0.0021153221201727283, 0.00135154586425295, - 0.0013626547300678799, 0.003653592144195716, 0.00672151562009703, - 0.014857207950835708, 0.03452385151240508, 0.03591016289984108, - 0.02229102871737884, 0.007447997216451657, 0.00505099606227552, - 0.0016937650707449176, 0.0013469420674100871, 0.0016965410643657965, - 0.002562353505582182, -6.33366212813045e-5, -0.00969571716777773, - -0.048688980279053266]) + [-0.05519530428547719, -0.017715187591731293, -0.0014094765667960986, + 0.0017408709303110715, 0.002364329303626605, 0.0015912944705669578, + 0.0015964146438650865, 0.003860702183595992, 0.0069126570648780075, + 0.01502802246799623, 0.034672817945651656, 0.03605806530524313, + 0.022451501088277735, 0.007636465002105951, 0.005249626321396431, + 0.0019202967839667788, 0.0015870957754252823, 0.0019420461064341924, + 0.0027769433764546388, 2.482219694607524e-5, -0.009668963817923988, + -0.04888254078430223]) end @long @testset "Split 1" begin test_input_split1["base_directory"] = test_output_directory run_test(test_input_split1, - [-0.054793853738618496, -0.017535475032013862, - -0.0014718402826481662, 0.0016368065803215382, 0.002097475822421603, - 0.001353447830403315, 0.001356138437924921, 0.0036537497347573, - 0.006719973928112565, 0.014855703760316889, 0.03452400419220982, - 0.03590889137214591, 0.022290971843531463, 0.007446918804323913, - 0.005048816472156039, 0.0016968661957691385, 0.0013266658105610114, - 0.0017028442360018413, 0.002534466861251151, - -0.00018703865529355897, -0.009661145065079906, - -0.0484483682752969]) + [-0.05499683305021959, -0.017648671323931234, -0.001435044896193346, + 0.0018073884147499259, 0.0023450881700708397, 0.0015955143089305631, + 0.001589815317774866, 0.003860937118209949, 0.006911057359417227, + 0.015026521129765527, 0.03467295602711963, 0.03605680131391841, + 0.022451426419324128, 0.007635385849475049, 0.005247408512658831, + 0.0019234631747149433, 0.001566853129890402, 0.001949947092934496, + 0.0027475042305556622, -0.00010906536252042205, + -0.00962961346763738, -0.04864884428378774]) end @long @testset "Split 2" begin test_input_split2["base_directory"] = test_output_directory run_test(test_input_split2, - [-0.05555568198447252, -0.020145183717956348, 0.001182118478411508, - 0.002193148323751635, 0.0019441188563940751, 0.0011789368818662881, - 0.0013514249605048384, 0.003735531583031493, 0.006723696092974834, - 0.014826903180374499, 0.03454936277756109, 0.03587040875737859, - 0.022277731154827392, 0.007403052912240603, 0.00512153431160143, - 0.0017463637584066217, 0.0011452779397062784, 0.0014049872146431029, - 0.0022755389057580316, 0.0016780234234311344, -0.008381041468024259, - -0.05005526194222513]) + [-0.05584608895693617, -0.020285311011810747, 0.0013246162852513857, + 0.002408198079080654, 0.002193404660625343, 0.0014165984310586497, + 0.0015838582299817442, 0.003942456292519074, 0.006915806487555444, + 0.014996822639406769, 0.034698460163972725, 0.03601812331030096, + 0.022438422037486003, 0.007592137358805067, 0.00532114704663586, + 0.001973382574270663, 0.0013804707387267182, 0.0016443777257862315, + 0.0025134094388913966, 0.0018832456170377893, -0.008404304571565805, + -0.05034925353831177]) end @long @testset "Split 3" begin test_input_split3["base_directory"] = test_output_directory run_test(test_input_split3, - [-0.036205375991650725, -0.030483334021285433, -0.028961568619094404, - -0.028550383934166465, -0.02551672335720456, -0.021976119708577647, - -0.01982001937014411, -0.01331564927923702, -0.00984100255121529, - -0.005266490060020825, 0.0021494114844098316, 0.004620296275317165, - 0.011509404776589328, 0.01757377252325957, 0.02014859036576961, - 0.027122126647315926, 0.030505809525427197, 0.034043759795000156, - 0.03932240322253646, 0.04089804092628224, 0.04436256082283185, - 0.04582461258085377, 0.0457025256980273, 0.04312136181903663, - 0.04054653135540802, 0.03787132328029428, 0.03223719811392133, - 0.030105212408583878, 0.024827994199332723, 0.018595982530248478, - 0.016209527148134187, 0.008754940562653064, 0.0040079860524162405, - 3.89264740137833e-5, -0.007642430261913982, -0.010258137085572222, - -0.015541799469166076, -0.021192018291797773, -0.022784703489569562, - -0.026873219344096318, -0.028749404798656616, -0.029220744790456707, - -0.032303083015072]) + [-0.03620705983288495, -0.030483526856225397, -0.028960441350906176, + -0.028549218778503995, -0.025515599608030678, -0.021975115062752498, + -0.019818943505064867, -0.013314608790987136, -0.009839994543852062, + -0.005265524793578627, 0.002150328580191541, 0.004621192779726743, + 0.011510249894025814, 0.017574569324021832, 0.020149366401796907, + 0.027122843852491103, 0.03050649889203747, 0.03404441833358536, + 0.039323018405068834, 0.04089864462026069, 0.04436314083820065, + 0.04582518382395237, 0.045703097564838854, 0.04312195015009901, + 0.04054713854267327, 0.0378719503058148, 0.03223787080558438, + 0.030105904373564214, 0.024828730387096765, 0.01859677066598083, + 0.01621033424329937, 0.008755805694319756, 0.004008885194932725, + 3.98551863685712e-5, -0.007641449438487893, -0.010257112218851392, + -0.01554078023638837, -0.021190999288843403, -0.022783488732253034, + -0.026872207781674047, -0.028748355604856834, -0.02921966520151288, + -0.03230397503173283]) end + fullf_expected_output = [-0.04413552960518834, -0.021828861958540703, + -0.012581752434237016, -0.010579192578141765, + -0.0068512759586887885, 2.5839426347419376e-5, + 0.006155432086970659, 0.011968120005188723, + 0.019681941313251225, 0.029904657995405693, + 0.03949771002617614, 0.04255717403165738, + 0.031939444000420925, 0.02124792913154375, + 0.015253505295222548, 0.00930078535463162, + 0.0012208189549702839, -0.005751247690841759, + -0.009217769053947429, -0.011407780388153488, + -0.020596064125157174, -0.03809484046514718] @testset "Adaptive timestep - full-f" begin test_input_adaptive["base_directory"] = test_output_directory run_test(test_input_adaptive, - [-0.04406302421567803, -0.021804698228788805, -0.012620074187743302, - -0.0106380090360855, -0.00691525568459642, -4.233879648051771e-5, - 0.006087337143759688, 0.011901298104136846, 0.019618466419394923, - 0.029848931238319366, 0.03944995729484389, 0.0425118861432023, - 0.031885423069787075, 0.02118545257675995, 0.015187813894182393, - 0.009233281236463076, 0.0011524769858418965, -0.005816337799653475, - -0.009278801135132173, -0.011463100731406845, -0.020577143329947718, - -0.03801740811142216], rtol=6.0e-4, atol=2.0e-12) + fullf_expected_output, rtol=6.0e-4, atol=2.0e-12) end @testset "Adaptive timestep - split 1" begin test_input_adaptive_split1["base_directory"] = test_output_directory run_test(test_input_adaptive_split1, - [-0.04404229340935816, -0.021715993643694146, -0.01254532739530461, - -0.010653532207501667, -0.006915890693912259, -4.387439665774011e-5, - 0.006085426633745894, 0.01190000270341187, 0.019617011480099204, - 0.02984682106369964, 0.03944638388660516, 0.042512679055667614, - 0.031887123203488764, 0.02118278590028187, 0.01518621987364583, - 0.009231972103916531, 0.001150127914745935, -0.005818837808004554, - -0.009271945665225503, -0.011554150887267762, -0.020405808046592103, - -0.03801003686546914], rtol=6.0e-4, atol=2.0e-12) + [-0.04411241514173686, -0.02173832960900953, -0.012507206800103619, + -0.010594605015685111, -0.006851937496868262, 2.4307749760781248e-5, + 0.006153573001701797, 0.011966908336801292, 0.019680600194907028, + 0.02990265026906549, 0.039494202889752195, 0.04255801055036077, + 0.031941240640856954, 0.02124538117173497, 0.0152520119972139, + 0.009299543598122585, 0.001218486350949803, -0.005753814631808573, + -0.009211138614098327, -0.011500666056267622, -0.020424831739003606, + -0.03808534246490289], rtol=6.0e-4, atol=2.0e-12) end @testset "Adaptive timestep - split 2" begin test_input_adaptive_split2["base_directory"] = test_output_directory run_test(test_input_adaptive_split2, - [-0.04459179157659319, -0.022746645577738103, -0.012946338116940815, - -0.010871883552302172, -0.006982172600237595, -6.051063734515426e-5, - 0.006026142648981889, 0.0119738448881976, 0.01962536485263761, - 0.02987382918222712, 0.03946868382103347, 0.0424964960343025, - 0.031848821225964274, 0.02115083095729825, 0.015157115049355067, - 0.00916355205311941, 0.0011734398171854166, -0.005841676293891676, - -0.009106806670666958, -0.011016868962010862, -0.02091990269514529, - -0.037555410636391916] , rtol=6.0e-4, atol=2.0e-12) + [-0.044658773996679106, -0.022770640350132876, -0.01291279676887995, + -0.010818472056813256, -0.00692137979236985, 8.260915374129437e-6, + 0.006095505380954945, 0.012043966021961394, 0.01969312249006842, + 0.02993329149668162, 0.03951863202813308, 0.04254329784045647, + 0.031905905757383245, 0.0212173464030042, 0.015225863469416798, + 0.00923409202948059, 0.0012431576067072942, -0.005777971895837924, + -0.009047333684784373, -0.010964221005155143, -0.020937032832434074, + -0.03762542957657465], rtol=6.0e-4, atol=2.0e-12) end @testset "Adaptive timestep - split 3" begin test_input_adaptive_split3["base_directory"] = test_output_directory run_test(test_input_adaptive_split3, - [-0.03462989367404872, -0.032011023958736944, -0.027146091249774885, - -0.020930812236514006, -0.010156481871960516, 0.0027832606290666053, - 0.012831753103964484, 0.022090130790633454, 0.03302848792727571, - 0.04152538920797896, 0.045375641995415654, 0.046239778021746426, - 0.04254552381542057, 0.0348087945161905, 0.02707439699253448, - 0.017880292949104492, 0.0047783374339709405, -0.007768102112509674, - -0.016299183329203885, -0.024140030225825285, -0.031567878963645234, - -0.03417555449482003], rtol=6.0e-4, atol=2.0e-12) + [-0.03462849854420647, -0.03201042148267427, -0.027145441768761876, + -0.020930163084065857, -0.010155888649583823, 0.0027838306741690046, + 0.012832285048924648, 0.022090624745954916, 0.033028935673319444, + 0.041525790733438206, 0.04537601028144605, 0.046240136469284064, + 0.042545918106145095, 0.03480923464250141, 0.02707487186844719, + 0.017880803893474347, 0.004778898480075153, -0.007767501511791003, + -0.016298557311046468, -0.0241393991660673, -0.03156718517969005, + -0.03417441352897386], rtol=6.0e-4, atol=2.0e-12) end @long @testset "Check other timestep - $type" for @@ -352,14 +357,7 @@ function runtests() timestep_check_input["run_name"] = type timestep_check_input["timestepping"]["type"] = type run_test(timestep_check_input, - [-0.04406302421567803, -0.021804698228788805, -0.012620074187743302, - -0.0106380090360855, -0.00691525568459642, -4.233879648051771e-5, - 0.006087337143759688, 0.011901298104136846, 0.019618466419394923, - 0.029848931238319366, 0.03944995729484389, 0.0425118861432023, - 0.031885423069787075, 0.02118545257675995, 0.015187813894182393, - 0.009233281236463076, 0.0011524769858418965, -0.005816337799653475, - -0.009278801135132173, -0.011463100731406845, -0.020577143329947718, - -0.03801740811142216], rtol=8.e-4, atol=1.e-10) + fullf_expected_output, rtol=8.e-4, atol=1.e-10) end end diff --git a/moment_kinetics/test/runtests.jl b/moment_kinetics/test/runtests.jl index fa0f5d64f..73d688f06 100644 --- a/moment_kinetics/test/runtests.jl +++ b/moment_kinetics/test/runtests.jl @@ -16,6 +16,7 @@ function runtests() include(joinpath(@__DIR__, "harrisonthompson.jl")) include(joinpath(@__DIR__, "wall_bc_tests.jl")) include(joinpath(@__DIR__, "recycling_fraction_tests.jl")) + include(joinpath(@__DIR__, "braginskii_electrons_imex_tests.jl")) include(joinpath(@__DIR__, "fokker_planck_tests.jl")) include(joinpath(@__DIR__, "fokker_planck_time_evolution_tests.jl")) include(joinpath(@__DIR__, "gyroaverage_tests.jl")) diff --git a/moment_kinetics/test/sound_wave_tests.jl b/moment_kinetics/test/sound_wave_tests.jl index 813ed5a99..f72cdf1c7 100644 --- a/moment_kinetics/test/sound_wave_tests.jl +++ b/moment_kinetics/test/sound_wave_tests.jl @@ -187,8 +187,8 @@ function run_test(test_input, analytic_frequency, analytic_growth_rate, fid = open_readonly_output_file(path,"moments") # load space-time coordinate data - z, z_spectral = load_coordinate_data(fid, "z") - r, r_spectral = load_coordinate_data(fid, "r") + z, z_spectral = load_coordinate_data(fid, "z"; ignore_MPI=true) + r, r_spectral = load_coordinate_data(fid, "r"; ignore_MPI=true) n_ion_species, n_neutral_species = load_species_data(fid) ntime, time = load_time_data(fid) diff --git a/moment_kinetics/test/wall_bc_tests.jl b/moment_kinetics/test/wall_bc_tests.jl index c1c40634d..ab09fec48 100644 --- a/moment_kinetics/test/wall_bc_tests.jl +++ b/moment_kinetics/test/wall_bc_tests.jl @@ -246,30 +246,30 @@ function runtests() @testset "Chebyshev uniform" begin test_input_chebyshev["base_directory"] = test_output_directory run_test(test_input_chebyshev, - [-1.1689445031600718, -0.7479504438063098, -0.6947559936893813, - -0.6917252442591313, -0.7180152498764835, -0.9980114095597415], + [-1.168944495073113, -0.747950464799219, -0.6947560093910274, + -0.6917252594440765, -0.7180152693147238, -0.9980114030684668], 2.e-3) end @testset "Chebyshev sqrt grid odd" begin test_input_chebyshev_sqrt_grid_odd["base_directory"] = test_output_directory run_test(test_input_chebyshev_sqrt_grid_odd, - [-1.2047298885671576, -0.9431378294506091, -0.8084332392927167, - -0.7812620422650213, -0.7233303514000929, -0.7003878610612269, - -0.69572751349158, -0.6933148921301019, -0.6992503992521327, - -0.7115787972775218, -0.7596015032228407, -0.795776514029509, - -0.876303297135126, -1.1471244425913258], + [-1.2047298844053338, -0.9431378244038217, -0.8084332486925859, + -0.7812620574297168, -0.7233303715713063, -0.700387877851292, + -0.695727529425101, -0.6933149075958859, -0.6992504158371133, + -0.7115788158947632, -0.7596015227027635, -0.7957765261319207, + -0.876303296785542, -1.1471244373220089], 2.e-3) end @testset "Chebyshev sqrt grid even" begin test_input_chebyshev_sqrt_grid_even["base_directory"] = test_output_directory run_test(test_input_chebyshev_sqrt_grid_even, - [-1.213617049279473, -1.0054529928344382, -0.871444761913497, - -0.836017699317097, -0.7552110924643832, -0.7264644073096705, - -0.7149147366621806, -0.6950077192395091, -0.6923364889119271, - -0.6950077192395089, -0.7149147366621814, -0.7264644073096692, - -0.7552110924643836, -0.8360176993170979, -0.8714447619134948, - -1.0054529928344376, -1.2136170492794727], + [-1.213617044609117, -1.0054529856551995, -0.8714447622540997, + -0.836017704148175, -0.7552111126205924, -0.7264644278204795, + -0.7149147557607726, -0.6950077350352664, -0.6923365041825125, + -0.6950077350352668, -0.7149147557607729, -0.7264644278204795, + -0.7552111126205917, -0.8360177041481733, -0.8714447622540994, + -1.0054529856552, -1.213617044609118], 2.e-3) end end diff --git a/plots_post_processing/plots_post_processing/src/plot_MMS_sequence.jl b/plots_post_processing/plots_post_processing/src/plot_MMS_sequence.jl index 84bfcf21b..92d780b27 100644 --- a/plots_post_processing/plots_post_processing/src/plot_MMS_sequence.jl +++ b/plots_post_processing/plots_post_processing/src/plot_MMS_sequence.jl @@ -255,7 +255,7 @@ function get_MMS_error_data(path_list,scan_type,scan_name) dfnn_func = manufactured_solns_list.dfnn_func densn_func = manufactured_solns_list.densn_func - manufactured_E_fields = manufactured_electric_fields(Lr_in,z.L,r_bc,z_bc,composition,r.n,manufactured_solns_input,species) + manufactured_E_fields = manufactured_electric_fields(Lr_in,z.L,r_bc,z_bc,composition,geo_in,r.n,manufactured_solns_input,species) Er_func = manufactured_E_fields.Er_func Ez_func = manufactured_E_fields.Ez_func phi_func = manufactured_E_fields.phi_func diff --git a/plots_post_processing/plots_post_processing/src/plots_post_processing.jl b/plots_post_processing/plots_post_processing/src/plots_post_processing.jl index 972b92620..00b8c7558 100644 --- a/plots_post_processing/plots_post_processing/src/plots_post_processing.jl +++ b/plots_post_processing/plots_post_processing/src/plots_post_processing.jl @@ -8,6 +8,7 @@ export compare_neutral_pdf_symbolic_test export compare_fields_symbolic_test export construct_global_zr_coords export allocate_global_zr_ion_moments +export allocate_global_zr_electron_moments export allocate_global_zr_neutral_moments export allocate_global_zr_fields export get_coords_nelement @@ -212,6 +213,15 @@ function allocate_global_zr_ion_dfns(nvpa_global, nvperp_global, nz_global, nr_g return f end +function allocate_global_zr_electron_moments(nz_global,nr_global,ntime) + density = allocate_float(nz_global,nr_global,ntime) + parallel_flow = allocate_float(nz_global,nr_global,ntime) + parallel_pressure = allocate_float(nz_global,nr_global,ntime) + parallel_heat_flux = allocate_float(nz_global,nr_global,ntime) + thermal_speed = allocate_float(nz_global,nr_global,ntime) + return density, parallel_flow, parallel_pressure, parallel_heat_flux, thermal_speed +end + function allocate_global_zr_neutral_dfns(nvz_global, nvr_global, nvzeta_global, nz_global, nr_global, n_ion_species, ntime) f = allocate_float(nvz_global, nvr_global, nvzeta_global, nz_global, nr_global, @@ -437,6 +447,12 @@ function analyze_and_plot_data(prefix...; run_index=nothing) Tuple(this_z.n_global for this_z ∈ z), Tuple(this_r.n_global for this_r ∈ r), n_ion_species, ntime) + electron_density, electron_parallel_flow, electron_parallel_pressure, + electron_parallel_heat_flux, electron_thermal_speed = + get_tuple_of_return_values(allocate_global_zr_electron_moments, + Tuple(this_z.n_global for this_z ∈ z), + Tuple(this_r.n_global for this_r ∈ r), + ntime) if any(n_neutral_species .> 0) neutral_density, neutral_uz, neutral_pz, neutral_qz, neutral_thermal_speed = get_tuple_of_return_values(allocate_global_zr_neutral_moments, @@ -497,6 +513,27 @@ function analyze_and_plot_data(prefix...; run_index=nothing) get_tuple_of_return_values(read_distributed_zwallr_data!, chodura_integral_upper, "chodura_integral_upper", run_names, "moments", nblocks, Tuple(this_r.n for this_r ∈ r), iskip, "upper") + # electron particle moments + get_tuple_of_return_values(read_distributed_zr_data!, electron_density, "electron_density", run_names, + "moments", nblocks, + Tuple(this_z.n for this_z ∈ z), + Tuple(this_r.n for this_r ∈ r), iskip) + get_tuple_of_return_values(read_distributed_zr_data!, electron_parallel_flow, "electron_parallel_flow", + run_names, "moments", nblocks, + Tuple(this_z.n for this_z ∈ z), + Tuple(this_r.n for this_r ∈ r), iskip) + get_tuple_of_return_values(read_distributed_zr_data!, electron_parallel_pressure, + "electron_parallel_pressure", run_names, "moments", nblocks, + Tuple(this_z.n for this_z ∈ z), + Tuple(this_r.n for this_r ∈ r), iskip) + get_tuple_of_return_values(read_distributed_zr_data!, electron_parallel_heat_flux, + "electron_parallel_heat_flux", run_names, "moments", nblocks, + Tuple(this_z.n for this_z ∈ z), + Tuple(this_r.n for this_r ∈ r), iskip) + get_tuple_of_return_values(read_distributed_zr_data!, electron_thermal_speed, "electron_thermal_speed", + run_names, "moments", nblocks, + Tuple(this_z.n for this_z ∈ z), + Tuple(this_r.n for this_r ∈ r), iskip) # neutral particle moments if has_neutrals get_tuple_of_return_values(read_distributed_zr_data!, neutral_density, @@ -692,6 +729,11 @@ function analyze_and_plot_data(prefix...; run_index=nothing) Tuple(qpar[:,ir0,:,:] for qpar ∈ parallel_heat_flux_at_pdf_times), Tuple(vth[:,ir0,:,:] for vth ∈ thermal_speed_at_pdf_times), Tuple(f[:,ivperp0,:,ir0,:,:] for f ∈ ff), + Tuple(n[:,ir0,:,:] for n ∈ electron_density), + Tuple(upar[:,ir0,:,:] for upar ∈ electron_parallel_flow), + Tuple(ppar[:,ir0,:,:] for ppar ∈ electron_parallel_pressure), + Tuple(qpar[:,ir0,:,:] for qpar ∈ electron_parallel_heat_flux), + Tuple(vth[:,ir0,:,:] for vth ∈ electron_thermal_speed), has_neutrals ? Tuple(neutral_n[:,ir0,:,:] for neutral_n ∈ neutral_density) : nothing, has_neutrals ? Tuple(uz[:,ir0,:,:] for uz ∈ neutral_uz) : nothing, has_neutrals ? Tuple(pz[:,ir0,:,:] for pz ∈ neutral_pz) : nothing, @@ -1097,7 +1139,7 @@ function analyze_and_plot_data(prefix...; run_index=nothing) densn_func = manufactured_solns_list.densn_func manufactured_E_fields = manufactured_electric_fields(Lr_in, z_global.L, r_global.bc, z_global.bc, - composition, r_global.n, + composition, geometry.input, r_global.n, manufactured_solns_input, species) Er_func = manufactured_E_fields.Er_func Ez_func = manufactured_E_fields.Ez_func @@ -1618,7 +1660,10 @@ function plot_1D_1V_diagnostics(run_name_labels, nwrite_movie, itime_min, itime_ density, parallel_flow, parallel_pressure, parallel_heat_flux, thermal_speed, phi_at_pdf_times, density_at_pdf_times, parallel_flow_at_pdf_times, parallel_pressure_at_pdf_times, parallel_heat_flux_at_pdf_times, - thermal_speed_at_pdf_times, ff, neutral_density, neutral_uz, neutral_pz, + thermal_speed_at_pdf_times, ff, + electron_density, electron_parallel_flow, electron_parallel_pressure, + electron_parallel_heat_flux, electron_thermal_speed, + neutral_density, neutral_uz, neutral_pz, neutral_qz, neutral_thermal_speed, neutral_density_at_pdf_times, neutral_uz_at_pdf_times, neutral_pz_at_pdf_times, neutral_qz_at_pdf_times, neutral_thermal_speed_at_pdf_times, neutral_ff, n_ion_species, n_neutral_species, @@ -1652,6 +1697,12 @@ function plot_1D_1V_diagnostics(run_name_labels, nwrite_movie, itime_min, itime_ parallel_heat_flux, delta_qpar, qpar_fldline_avg, pp, run_name_labels, time, itime_min, itime_max, nwrite_movie, z, iz0, n_ion_species, "ion") + + # create the requested plots of the electron moments + plot_electron_moments(electron_density, electron_parallel_flow, electron_parallel_pressure, + electron_parallel_heat_flux, electron_thermal_speed, pp, run_names, time, itime_min, + itime_max, nwrite_movie, z, iz0) + if maximum(n_neutral_species) > 0 # analyze the velocity neutral moments data neutral_density_fldline_avg, neutral_uz_fldline_avg, neutral_pz_fldline_avg, @@ -1664,7 +1715,7 @@ function plot_1D_1V_diagnostics(run_name_labels, nwrite_movie, itime_min, itime_ neutral_uz, delta_neutral_uz, neutral_uz_fldline_avg, neutral_pz, delta_neutral_pz, neutral_pz_fldline_avg, neutral_thermal_speed, delta_neutral_vth, neutral_vth_fldline_avg, neutral_qz, delta_neutral_qz, - neutral_qz_fldline_avg, pp, run_name_labels, time, itime_min, itime_max, + neutral_qz_fldline_avg, pp, run_names, time, itime_min, itime_max, nwrite_movie, z, iz0, n_ion_species, "neutral") end @@ -1776,6 +1827,105 @@ function plot_fields(phi, delta_phi, time, itime_min, itime_max, nwrite_movie, println("done.") end +function plot_electron_moments(density, parallel_flow, parallel_pressure, + parallel_heat_flux, thermal_speed, pp, run_names, time, itime_min, + itime_max, nwrite_movie, z, iz0) + + println("Plotting electron moments data...") + + # determine what to use for the legend depending on how many runs are being plotted + n_runs = length(run_names) + if n_runs == 1 + prefix = run_names[1] + legend = false + else + prefix = default_compare_prefix + legend = true + end + + moment_string = "electron_density" + if pp.plot_dens0_vs_t + plot_single_moment_z0(density, time, iz0, prefix, legend, run_names, moment_string) + end + # if pp.plot_dens_vs_z_t + # plot_single_moment_vs_z_t(density, time, z, run_names, prefix, moment_string) + # end + moment_string = "electron_upar" + if pp.plot_upar0_vs_t + plot_single_moment_z0(parallel_flow, time, iz0, prefix, legend, run_names, moment_string) + end + # if pp.plot_upar_vs_z_t + # plot_single_moment_vs_z_t(parallel_flow, time, z, run_names, prefix, moment_string) + # end + moment_string = "electron_ppar" + if pp.plot_ppar0_vs_t + plot_single_moment_z0(parallel_pressure, time, iz0, prefix, legend, run_names, moment_string) + end + # if pp.plot_ppar_vs_z_t + # plot_single_moment_vs_z_t(parallel_pressure, time, z, run_names, prefix, moment_string) + # end + moment_string = "electron_vth" + if pp.plot_vth0_vs_t + plot_single_moment_z0(thermal_speed, time, iz0, prefix, legend, run_names, moment_string) + end + moment_string = "electron_qpar" + if pp.plot_qpar0_vs_t + plot_single_moment_z0(parallel_heat_flux, time, iz0, prefix, legend, run_names, moment_string) + end + if pp.animate_phi_vs_z + pmin = minimum(minimum(p) for p ∈ parallel_pressure) + pmax = maximum(maximum(p) for p ∈ parallel_pressure) + # make a gif animation of vthe(z) at different times + anim = @animate for i ∈ itime_min:nwrite_movie:itime_max + plot(legend=legend) + for (t, this_z, p, run_label) ∈ zip(time, z, parallel_pressure, run_names) + @views plot!(this_z.grid, p[:,i], xlabel="z", ylabel="ppar", + ylims=(pmin, pmax), label=run_label) + end + end + outfile = string(prefix, "_electron_ppar_vs_z.gif") + gif(anim, outfile, fps=5) + + pmin = minimum(minimum(p) for p ∈ thermal_speed) + pmax = maximum(maximum(p) for p ∈ thermal_speed) + # make a gif animation of vthe(z) at different times + anim = @animate for i ∈ itime_min:nwrite_movie:itime_max + plot(legend=legend) + for (t, this_z, p, run_label) ∈ zip(time, z, thermal_speed, run_names) + @views plot!(this_z.grid, p[:,i], xlabel="z", ylabel="vth", + ylims=(pmin, pmax), label=run_label) + end + end + outfile = string(prefix, "_electron_vth_vs_z.gif") + gif(anim, outfile, fps=5) + end + # if pp.plot_qpar_vs_z_t + # plot_single_moment_vs_z_t(parallel_heat_flux, time, z, run_names, prefix, moment_string) + # end +end + +function plot_single_moment_z0(mom, time, iz0, prefix, legend, run_names, mom_string) + # plot the time trace of the moment at z=z0 + #plot(time, log.(phi[i,:]), yscale = :log10) + plot(legend=legend) + for (t, p, run_label) ∈ zip(time, mom, run_names) + @views plot!(t, p[iz0,:], label=run_label) + end + outfile = string(prefix, "_", mom_string, "0_vs_t.pdf") + savefig(outfile) +end + +function plot_single_moment_vs_z_t(mom, time, z, run_names, prefix, mom_string) + # make a heatmap plot of moment(z,t) + n_runs = length(run_names) + subplots = (heatmap(t, this_z.grid, p, xlabel="time", ylabel="z", title=run_label, + c = :deep) + for (t, this_z, p, run_label) ∈ zip(time, z, mom, run_names)) + plot(subplots..., layout=(1,n_runs), size=(600*n_runs, 400)) + outfile = string(prefix, "_", mom_string, "_vs_z_t.pdf") + savefig(outfile) +end + """ """ function plot_moments(density, delta_density, density_fldline_avg, @@ -1812,6 +1962,7 @@ function plot_moments(density, delta_density, density_fldline_avg, end outfile = string(prefix, "_$(label)_denstot_vs_t.pdf") trysavefig(outfile) + for is ∈ 1:maximum(n_species) spec_string = string(is) dens_min = minimum(minimum(n[:,is,:]) for n ∈ density) @@ -2806,13 +2957,13 @@ function plot_fields_rt(phi, delta_phi, time, itime_min, itime_max, nwrite_movie outfile = string(run_name, "_delta_phi(r0,z0)_vs_t.pdf") trysavefig(outfile) end - if pp.plot_phi_vs_z_t + if pp.plot_phi_vs_z_t && r.n > 1 # make a heatmap plot of ϕ(r,t) heatmap(time, r.grid, phi, xlabel="time", ylabel="r", title="ϕ", c = :deep) outfile = string(run_name, "_phi_vs_r_t.pdf") trysavefig(outfile) end - if pp.animate_phi_vs_z + if pp.animate_phi_vs_z && r.n > 1 # make a gif animation of ϕ(r) at different times anim = @animate for i ∈ itime_min:nwrite_movie:itime_max @views plot(r.grid, phi[:,i], xlabel="r", ylabel="ϕ", ylims = (phimin,phimax)) @@ -2826,10 +2977,11 @@ function plot_fields_rt(phi, delta_phi, time, itime_min, itime_max, nwrite_movie # plot!(exp.(-(phi[cld(nz,2),end] .- phi[izmid:end,end])) .* erfi.(sqrt.(abs.(phi[cld(nz,2),end] .- phi[izmid:end,end])))/sqrt(pi)/0.688, phi[izmid:end,end] .- phi[izmid,end], label = "analytical", linewidth=2) # outfile = string(run_name, "_harrison_comparison.pdf") # trysavefig(outfile) - plot(r.grid, phi[:,end], xlabel="r/Lr", ylabel="eϕ/Te", label="", linewidth=2) - outfile = string(run_name, "_phi(r)_final.pdf") - trysavefig(outfile) - + if r.n > 1 + plot(r.grid, phi[:,end], xlabel="r/Lr", ylabel="eϕ/Te", label="", linewidth=2) + outfile = string(run_name, "_phi(r)_final.pdf") + trysavefig(outfile) + end println("done.") end @@ -2858,7 +3010,7 @@ function plot_ion_pdf(run_name, run_name_label, vpa, vperp, z, r, z_local, r_loc spec_string = [string("_", spec_type)] end # make a gif animation of f(vpa,z,t) at a given (vperp,r) location - if pp.animate_f_vs_vpa_z + if pp.animate_f_vs_vpa_z && (vpa.n > 1 && z_local.n > 1) pdf = load_distributed_ion_pdf_slice(run_name, nblocks, itime_min:iskip:itime_max, n_species, r_local, z_local, vperp, vpa; @@ -2876,7 +3028,7 @@ function plot_ion_pdf(run_name, run_name_label, vpa, vperp, z, r, z_local, r_loc end end # make a gif animation of f(vpa,r,t) at a given (vperp,z) location - if pp.animate_f_vs_vpa_r + if pp.animate_f_vs_vpa_r && (vpa.n > 1 && r_local.n > 1) pdf = load_distributed_ion_pdf_slice(run_name, nblocks, itime_min:iskip:itime_max, n_species, r_local, z_local, vperp, vpa; @@ -2894,7 +3046,7 @@ function plot_ion_pdf(run_name, run_name_label, vpa, vperp, z, r, z_local, r_loc end end # make a gif animation of f(vperp,z,t) at a given (vpa,r) location - if pp.animate_f_vs_vperp_z + if pp.animate_f_vs_vperp_z && (vperp.n > 1 && z_local.n > 1) pdf = load_distributed_ion_pdf_slice(run_name, nblocks, itime_min:iskip:itime_max, n_species, r_local, z_local, vperp, vpa; ivpa=ivpa0, @@ -2908,7 +3060,7 @@ function plot_ion_pdf(run_name, run_name_label, vpa, vperp, z, r, z_local, r_loc end end # make a gif animation of f(vperp,r,t) at a given (vpa,z) location - if pp.animate_f_vs_vperp_r + if pp.animate_f_vs_vperp_r && (vperp.n > 1 && r_local.n > 1) pdf = load_distributed_ion_pdf_slice(run_name, nblocks, itime_min:iskip:itime_max, n_species, r_local, z_local, vperp, vpa; ivpa=ivpa0, @@ -2970,7 +3122,7 @@ function plot_ion_pdf(run_name, run_name_label, vpa, vperp, z, r, z_local, r_loc end end # make a gif animation of f(z,r,t) at a given (vpa,vperp) location - if pp.animate_f_vs_r_z + if pp.animate_f_vs_r_z && (z_local.n > 1 && r_local.n > 1) pdf = load_distributed_ion_pdf_slice(run_name, nblocks, itime_min:iskip:itime_max, n_species, r_local, z_local, vperp, vpa; ivpa=ivpa0, @@ -3067,15 +3219,16 @@ end function plot_fields_2D(phi, Ez, Er, time, z, r, iz0, ir0, itime_min, itime_max, nwrite_movie, run_name, pp, description) nr = size(r,1) + nz = size(z,1) print("Plotting fields data...") phimin = minimum(phi) phimax = maximum(phi) - if pp.plot_phi_vs_r0_z # plot last timestep phi[z,ir0] + if pp.plot_phi_vs_r0_z && nz > 1 # plot last timestep phi[z,ir0] @views plot(z, phi[:,ir0,end], xlabel=L"z/L_z", ylabel=L"\phi") outfile = string(run_name, "_phi"*description*"(r0,z)_vs_z.pdf") trysavefig(outfile) end - if pp.animate_phi_vs_r_z && nr > 1 + if pp.animate_phi_vs_r_z && nr > 1 && nz > 1 # make a gif animation of ϕ(z) at different times anim = @animate for i ∈ itime_min:nwrite_movie:itime_max @views heatmap(r, z, phi[:,:,i], xlabel="r", ylabel="z", c = :deep, interpolation = :cubic) @@ -3087,7 +3240,7 @@ function plot_fields_2D(phi, Ez, Er, time, z, r, iz0, ir0, end outfile = string(run_name, "_phi(zwall-)_vs_r.gif") trygif(anim, outfile, fps=5) - elseif pp.animate_phi_vs_r_z && nr == 1 # make a gif animation of ϕ(z) at different times + elseif pp.animate_phi_vs_r_z && nr == 1 && nz > 1 # make a gif animation of ϕ(z) at different times anim = @animate for i ∈ itime_min:nwrite_movie:itime_max @views plot(z, phi[:,1,i], xlabel="z", ylabel=L"\widetilde{\phi}", ylims = (phimin,phimax)) end @@ -3096,17 +3249,17 @@ function plot_fields_2D(phi, Ez, Er, time, z, r, iz0, ir0, end Ezmin = minimum(Ez) Ezmax = maximum(Ez) - if pp.plot_Ez_vs_r0_z # plot last timestep Ez[z,ir0] + if pp.plot_Ez_vs_r0_z && nz > 1 # plot last timestep Ez[z,ir0] @views plot(z, Ez[:,ir0,end], xlabel=L"z/L_z", ylabel=L"E_z") outfile = string(run_name, "_Ez"*description*"(r0,z)_vs_z.pdf") trysavefig(outfile) end - if pp.plot_wall_Ez_vs_r && nr > 1 # plot last timestep Ez[z_wall,r] + if pp.plot_wall_Ez_vs_r && nr > 1 && nz > 1 # plot last timestep Ez[z_wall,r] @views plot(r, Ez[1,:,end], xlabel=L"r/L_r", ylabel=L"E_z") outfile = string(run_name, "_Ez"*description*"(r,z_wall-)_vs_r.pdf") trysavefig(outfile) end - if pp.animate_Ez_vs_r_z && nr > 1 + if pp.animate_Ez_vs_r_z && nr > 1 && nz > 1 # make a gif animation of ϕ(z) at different times anim = @animate for i ∈ itime_min:nwrite_movie:itime_max @views heatmap(r, z, Ez[:,:,i], xlabel="r", ylabel="z", c = :deep, interpolation = :cubic) @@ -3118,7 +3271,7 @@ function plot_fields_2D(phi, Ez, Er, time, z, r, iz0, ir0, end outfile = string(run_name, "_Ez(zwall-)_vs_r.gif") trygif(anim, outfile, fps=5) - elseif pp.animate_Ez_vs_r_z && nr == 1 + elseif pp.animate_Ez_vs_r_z && nr == 1 && nz > 1 anim = @animate for i ∈ itime_min:nwrite_movie:itime_max @views plot(z, Ez[:,1,i], xlabel="z", ylabel=L"\widetilde{E}_z", ylims = (Ezmin,Ezmax)) end @@ -3127,12 +3280,12 @@ function plot_fields_2D(phi, Ez, Er, time, z, r, iz0, ir0, end Ermin = minimum(Er) Ermax = maximum(Er) - if pp.plot_Er_vs_r0_z # plot last timestep Er[z,ir0] + if pp.plot_Er_vs_r0_z && nz > 1 # plot last timestep Er[z,ir0] @views plot(z, Er[:,ir0,end], xlabel=L"z/L_z", ylabel=L"E_r") outfile = string(run_name, "_Er"*description*"(r0,z)_vs_z.pdf") trysavefig(outfile) end - if pp.plot_wall_Er_vs_r && nr > 1 # plot last timestep Er[z_wall,r] + if pp.plot_wall_Er_vs_r && nr > 1 && nz > 1 # plot last timestep Er[z_wall,r] @views plot(r, Er[1,:,end], xlabel=L"r/L_r", ylabel=L"E_r") outfile = string(run_name, "_Er"*description*"(r,z_wall-)_vs_r.pdf") trysavefig(outfile) @@ -3142,7 +3295,7 @@ function plot_fields_2D(phi, Ez, Er, time, z, r, iz0, ir0, outfile = string(run_name, "_Er(zwall-)_vs_r.gif") trygif(anim, outfile, fps=5) end - if pp.animate_Er_vs_r_z && nr > 1 + if pp.animate_Er_vs_r_z && nr > 1 && nz > 1 # make a gif animation of ϕ(z) at different times anim = @animate for i ∈ itime_min:nwrite_movie:itime_max @views heatmap(r, z, Er[:,:,i], xlabel="r", ylabel="z", c = :deep, interpolation = :cubic) @@ -3159,6 +3312,7 @@ function plot_ion_moments_2D(density, parallel_flow, parallel_pressure, time, z, r, iz0, ir0, n_ion_species, itime_min, itime_max, nwrite_movie, run_name, pp) nr = size(r,1) + nz = size(z,1) ntime = size(time,1) print("Plotting ion moments data...") for is in 1:n_ion_species @@ -3166,7 +3320,7 @@ function plot_ion_moments_2D(density, parallel_flow, parallel_pressure, # the density densitymin = minimum(density[:,:,is,:]) densitymax = maximum(density) - if pp.plot_density_vs_r0_z # plot last timestep density[z,ir0] + if pp.plot_density_vs_r0_z && nz > 1 # plot last timestep density[z,ir0] @views plot(z, density[:,ir0,is,end], xlabel=L"z/L_z", ylabel=L"n_i") outfile = string(run_name, "_density"*description*"(r0,z)_vs_z.pdf") trysavefig(outfile) @@ -3176,7 +3330,7 @@ function plot_ion_moments_2D(density, parallel_flow, parallel_pressure, outfile = string(run_name, "_density"*description*"(r,z_wall)_vs_r.pdf") trysavefig(outfile) end - if pp.animate_density_vs_r_z && nr > 1 + if pp.animate_density_vs_r_z && nr > 1 && nz > 1 # make a gif animation of ϕ(z) at different times anim = @animate for i ∈ itime_min:nwrite_movie:itime_max @views heatmap(r, z, density[:,:,is,i], xlabel="r", ylabel="z", c = :deep, interpolation = :cubic) @@ -3184,7 +3338,7 @@ function plot_ion_moments_2D(density, parallel_flow, parallel_pressure, outfile = string(run_name, "_density"*description*"_vs_r_z.gif") trygif(anim, outfile, fps=5) end - if pp.plot_density_vs_r_z && nr > 1 + if pp.plot_density_vs_r_z && nr > 1 && nz > 1 @views heatmap(r, z, density[:,:,is,end], xlabel=L"r", ylabel=L"z", c = :deep, interpolation = :cubic, windowsize = (360,240), margin = 15pt) outfile = string(run_name, "_density"*description*"_vs_r_z.pdf") @@ -3201,17 +3355,17 @@ function plot_ion_moments_2D(density, parallel_flow, parallel_pressure, # the parallel flow parallel_flowmin = minimum(parallel_flow[:,:,is,:]) parallel_flowmax = maximum(parallel_flow) - if pp.plot_parallel_flow_vs_r0_z # plot last timestep parallel_flow[z,ir0] + if pp.plot_parallel_flow_vs_r0_z && nz > 1 # plot last timestep parallel_flow[z,ir0] @views plot(z, parallel_flow[:,ir0,is,end], xlabel=L"z/L_z", ylabel=L"u_{i\|\|}") outfile = string(run_name, "_parallel_flow"*description*"(r0,z)_vs_z.pdf") trysavefig(outfile) end - if pp.plot_wall_parallel_flow_vs_r && nr > 1 # plot last timestep parallel_flow[z_wall,r] + if pp.plot_wall_parallel_flow_vs_r && nr > 1 && nz > 1# plot last timestep parallel_flow[z_wall,r] @views plot(r, parallel_flow[end,:,is,end], xlabel=L"r/L_r", ylabel=L"u_{i\|\|}") outfile = string(run_name, "_parallel_flow"*description*"(r,z_wall)_vs_r.pdf") trysavefig(outfile) end - if pp.animate_parallel_flow_vs_r_z && nr > 1 + if pp.animate_parallel_flow_vs_r_z && nr > 1 && nz > 1 # make a gif animation of ϕ(z) at different times anim = @animate for i ∈ itime_min:nwrite_movie:itime_max @views heatmap(r, z, parallel_flow[:,:,is,i], xlabel="r", ylabel="z", c = :deep, interpolation = :cubic) @@ -3219,7 +3373,7 @@ function plot_ion_moments_2D(density, parallel_flow, parallel_pressure, outfile = string(run_name, "_parallel_flow"*description*"_vs_r_z.gif") trygif(anim, outfile, fps=5) end - if pp.plot_parallel_flow_vs_r_z && nr > 1 + if pp.plot_parallel_flow_vs_r_z && nr > 1 && nz > 1 @views heatmap(r, z, parallel_flow[:,:,is,end], xlabel=L"r", ylabel=L"z", c = :deep, interpolation = :cubic, windowsize = (360,240), margin = 15pt) outfile = string(run_name, "_parallel_flow"*description*"_vs_r_z.pdf") @@ -3236,7 +3390,7 @@ function plot_ion_moments_2D(density, parallel_flow, parallel_pressure, # the parallel pressure parallel_pressuremin = minimum(parallel_pressure[:,:,is,:]) parallel_pressuremax = maximum(parallel_pressure) - if pp.plot_parallel_pressure_vs_r0_z # plot last timestep parallel_pressure[z,ir0] + if pp.plot_parallel_pressure_vs_r0_z && nz > 1 # plot last timestep parallel_pressure[z,ir0] @views plot(z, parallel_pressure[:,ir0,is,end], xlabel=L"z/L_z", ylabel=L"p_{i\|\|}") outfile = string(run_name, "_parallel_pressure"*description*"(r0,z)_vs_z.pdf") trysavefig(outfile) @@ -3246,7 +3400,7 @@ function plot_ion_moments_2D(density, parallel_flow, parallel_pressure, outfile = string(run_name, "_parallel_pressure"*description*"(r,z_wall)_vs_r.pdf") trysavefig(outfile) end - if pp.animate_parallel_pressure_vs_r_z && nr > 1 + if pp.animate_parallel_pressure_vs_r_z && nr > 1 && nz > 1 # make a gif animation of ϕ(z) at different times anim = @animate for i ∈ itime_min:nwrite_movie:itime_max @views heatmap(r, z, parallel_pressure[:,:,is,i], xlabel="r", ylabel="z", c = :deep, interpolation = :cubic) @@ -3254,7 +3408,7 @@ function plot_ion_moments_2D(density, parallel_flow, parallel_pressure, outfile = string(run_name, "_parallel_pressure"*description*"_vs_r_z.gif") trygif(anim, outfile, fps=5) end - if pp.plot_parallel_pressure_vs_r_z && nr > 1 + if pp.plot_parallel_pressure_vs_r_z && nr > 1 && nz > 1 @views heatmap(r, z, parallel_pressure[:,:,is,end], xlabel=L"r", ylabel=L"z", c = :deep, interpolation = :cubic, windowsize = (360,240), margin = 15pt) outfile = string(run_name, "_parallel_pressure"*description*"_vs_r_z.pdf") @@ -3264,17 +3418,17 @@ function plot_ion_moments_2D(density, parallel_flow, parallel_pressure, # Note factor of 2 here because currently temperatures are normalised by # Tref, while pressures are normalised by m*nref*c_ref^2=2*nref*Tref temperature = 2.0 * parallel_pressure ./ density - if pp.plot_parallel_temperature_vs_r0_z # plot last timestep parallel_temperature[z,ir0] + if pp.plot_parallel_temperature_vs_r0_z && nz > 1# plot last timestep parallel_temperature[z,ir0] @views plot(z, temperature[:,ir0,is,end], xlabel=L"z/L_z", ylabel=L"T_i") outfile = string(run_name, "_temperature"*description*"(r0,z)_vs_z.pdf") trysavefig(outfile) end - if pp.plot_wall_parallel_temperature_vs_r && nr > 1 # plot last timestep parallel_temperature[z_wall,r] + if pp.plot_wall_parallel_temperature_vs_r && nr > 1 && nz > 1 # plot last timestep parallel_temperature[z_wall,r] @views plot(r, temperature[end,:,is,end], xlabel=L"r/L_r", ylabel=L"T_i") outfile = string(run_name, "_temperature"*description*"(r,z_wall)_vs_r.pdf") trysavefig(outfile) end - if pp.animate_parallel_temperature_vs_r_z && nr > 1 + if pp.animate_parallel_temperature_vs_r_z && nr > 1 && nz > 1 # make a gif animation of T_||(z) at different times anim = @animate for i ∈ itime_min:nwrite_movie:itime_max @views heatmap(r, z, temperature[:,:,is,i], xlabel="r", ylabel="z", c = :deep, interpolation = :cubic) @@ -3282,7 +3436,7 @@ function plot_ion_moments_2D(density, parallel_flow, parallel_pressure, outfile = string(run_name, "_temperature"*description*"_vs_r_z.gif") trygif(anim, outfile, fps=5) end - if pp.plot_parallel_temperature_vs_r_z && nr > 1 + if pp.plot_parallel_temperature_vs_r_z && nr > 1 && nz > 1 @views heatmap(r, z, temperature[:,:,is,end], xlabel=L"r", ylabel=L"z", c = :deep, interpolation = :cubic, windowsize = (360,240), margin = 15pt) outfile = string(run_name, "_temperature"*description*"_vs_r_z.pdf") @@ -3305,33 +3459,47 @@ function plot_ion_moments_2D(density, parallel_flow, parallel_pressure, outfile = string(run_name, "_delta_perpendicular_pressure"*description*"(iz0,ir0)_vs_t.pdf") trysavefig(outfile) end + if pp.plot_perpendicular_pressure_vs_r0_z && nz > 1 # plot last timestep perpendicular_pressure[z,ir0] + @views plot(z, perpendicular_pressure[:,ir0,is,end], xlabel=L"z/L_z", ylabel=L"p_{i\perp}",label="") + outfile = string(run_name, "_perpendicular_pressure"*description*"(r0,z)_vs_z.pdf") + trysavefig(outfile) + end # the total pressure + total_pressure = copy(parallel_pressure) + @. total_pressure = (2.0/3.0)*perpendicular_pressure + (1.0/3.0)*parallel_pressure if pp.plot_ppar0_vs_t && pp.plot_pperp0_vs_t + @views plot([time, time, time] , [parallel_pressure[iz0,ir0,is,:], perpendicular_pressure[iz0,ir0,is,:], - (2.0/3.0).*perpendicular_pressure[iz0,ir0,is,:] .+ (1.0/3.0).*parallel_pressure[iz0,ir0,is,:]], - xlabel=L"t/ (L_{ref}/c_{ref})", ylabel="", label = [L"p_{i\|\|}(t)" L"p_{i\perp}(t)" L"p_{i}(t)"]) + total_pressure[iz0,ir0,is,:]], + xlabel=L"t/ (L_{ref}/c_{ref})", ylabel="", label = [L"p_{i\|\|}(t)" L"p_{i\perp}(t)" L"p_{i}(t)"], + foreground_color_legend = nothing, background_color_legend = nothing) outfile = string(run_name, "_pressures"*description*"(iz0,ir0)_vs_t.pdf") trysavefig(outfile) @views plot([time, time, time] , [parallel_pressure[iz0,ir0,is,:] .- parallel_pressure[iz0,ir0,is,1], perpendicular_pressure[iz0,ir0,is,:] .- perpendicular_pressure[iz0,ir0,is,1], - (2.0/3.0).*(perpendicular_pressure[iz0,ir0,is,:] .- perpendicular_pressure[iz0,ir0,is,1]).+ - (1.0/3.0).*(parallel_pressure[iz0,ir0,is,:] .- parallel_pressure[iz0,ir0,is,1])], - xlabel=L"t/ (L_{ref}/c_{ref})", ylabel="", label = [L"p_{i\|\|}(t) - p_{i\|\|}(0)" L"p_{i\perp}(t) - p_{i\perp}(0)" L"p_{i}(t) - p_{i}(0)"]) + total_pressure[iz0,ir0,is,:] .- total_pressure[iz0,ir0,is,1]], + xlabel=L"t/ (L_{ref}/c_{ref})", ylabel="", label = [L"p_{i\|\|}(t) - p_{i\|\|}(0)" L"p_{i\perp}(t) - p_{i\perp}(0)" L"p_{i}(t) - p_{i}(0)"], + foreground_color_legend = nothing, background_color_legend = nothing) outfile = string(run_name, "_delta_pressures"*description*"(iz0,ir0)_vs_t.pdf") trysavefig(outfile) @views plot([time] , - [(2.0/3.0).*perpendicular_pressure[iz0,ir0,is,:] .+ (1.0/3.0).*parallel_pressure[iz0,ir0,is,:]], + [total_pressure[iz0,ir0,is,:]], xlabel=L"t/ (L_{ref}/c_{ref})", ylabel=L"p_{i}(t)", label = "") outfile = string(run_name, "_pressure"*description*"(iz0,ir0)_vs_t.pdf") trysavefig(outfile) @views plot([time] , - [(2.0/3.0).*(perpendicular_pressure[iz0,ir0,is,:] .- perpendicular_pressure[iz0,ir0,is,1]).+ - (1.0/3.0).*(parallel_pressure[iz0,ir0,is,:] .- parallel_pressure[iz0,ir0,is,1])], + [total_pressure[iz0,ir0,is,:] .- total_pressure[iz0,ir0,is,1]], xlabel=L"t/ (L_{ref}/c_{ref})", ylabel=L"p_{i}(t) - p_{i}(0)", label = "") outfile = string(run_name, "_delta_pressure"*description*"(iz0,ir0)_vs_t.pdf") trysavefig(outfile) end + if pp.plot_perpendicular_pressure_vs_r0_z && pp.plot_parallel_pressure_vs_r0_z && nz > 1 # plot last timestep perpendicular_pressure[z,ir0] + @views plot([z,z,z], [parallel_pressure[:,ir0,is,end],perpendicular_pressure[:,ir0,is,end],total_pressure[:,ir0,is,end]], xlabel=L"z/L_z", ylabel="", + label = [L"p_{i\|\|}" L"p_{i\perp}" L"p_{i}"], foreground_color_legend = nothing, background_color_legend = nothing) + outfile = string(run_name, "_all_pressures"*description*"(r0,z)_vs_z.pdf") + trysavefig(outfile) + end # the thermal speed if pp.plot_vth0_vs_t @views plot(time, thermal_speed[iz0,ir0,is,:], xlabel=L"t / (L_{ref}/c_{ref})", ylabel=L"v_{i,th}(t)", label = "") diff --git a/plots_post_processing/plots_post_processing/src/post_processing_input.jl b/plots_post_processing/plots_post_processing/src/post_processing_input.jl index 5c3b6f58d..4da132ed5 100644 --- a/plots_post_processing/plots_post_processing/src/post_processing_input.jl +++ b/plots_post_processing/plots_post_processing/src/post_processing_input.jl @@ -42,15 +42,15 @@ const plot_Tpar_vs_z_t = false # if plot_qpar_vs_z_t = true, create heatmap of species parallel heat flux vs z and time const plot_qpar_vs_z_t = false # if animate_dens_vs_z = true, create animation of species density(z) at different time slices -const animate_dens_vs_z = false #ttrue +const animate_dens_vs_z = true #ttrue # if animate_upar_vs_z = true, create animation of species parallel flow(z) at different time slices -const animate_upar_vs_z = false +const animate_upar_vs_z = true # if animate_ppar_vs_z = true, create animation of species parallel pressure(z) at different time slices const animate_ppar_vs_z = false # if animate_Tpar_vs_z = true, create animation of species parallel pressure(z) at different time slices const animate_Tpar_vs_z = false # if animate_vth_vs_z = true, create animation of species thermal_velocity(z) at different time slices -const animate_vth_vs_z = false +const animate_vth_vs_z = true # if animate_qpar_vs_z = true, create animation of species parallel heat flux(z) at different time slices const animate_qpar_vs_z = false # if plot_f_unnormalized_vs_vpa_z = true, create heatmap of f_unnorm(v_parallel_unnorm,z) at @@ -102,6 +102,7 @@ const plot_wall_density_vs_r = true # plot last timestep density[z_wall,r] const plot_density_vs_r_z = true const animate_density_vs_r_z = true const plot_parallel_flow_vs_r0_z = true # plot last timestep parallel_flow[z,ir0] +const plot_perpendicular_flow_vs_r0_z = true # plot last timestep perpendicular_flow[z,ir0] const plot_wall_parallel_flow_vs_r = true # plot last timestep parallel_flow[z_wall,r] const plot_parallel_flow_vs_r_z = true const animate_parallel_flow_vs_r_z = true @@ -171,7 +172,8 @@ pp = pp_input(calculate_frequencies, plot_phi0_vs_t, plot_phi_vs_z_t, animate_ph animate_Er_vs_r_z, animate_Ez_vs_r_z, animate_phi_vs_r_z, plot_phi_vs_r0_z, plot_Ez_vs_r0_z, plot_wall_Ez_vs_r, plot_Er_vs_r0_z, plot_wall_Er_vs_r, plot_density_vs_r0_z, plot_wall_density_vs_r, plot_density_vs_r_z, - animate_density_vs_r_z, plot_parallel_flow_vs_r0_z, plot_wall_parallel_flow_vs_r, + animate_density_vs_r_z, plot_parallel_flow_vs_r0_z, plot_perpendicular_flow_vs_r0_z, + plot_wall_parallel_flow_vs_r, plot_parallel_flow_vs_r_z, animate_parallel_flow_vs_r_z, plot_parallel_pressure_vs_r0_z, plot_wall_parallel_pressure_vs_r, plot_parallel_pressure_vs_r_z, animate_parallel_pressure_vs_r_z, diff --git a/precompile-kinetic-electrons.jl b/precompile-kinetic-electrons.jl new file mode 100644 index 000000000..9922963aa --- /dev/null +++ b/precompile-kinetic-electrons.jl @@ -0,0 +1,16 @@ +using Pkg + +# Activate the moment_kinetics package +Pkg.activate(".") + +using PackageCompiler + +# Create the sysimage 'moment_kinetics.so' in the base moment_kinetics source directory +# with both moment_kinetics and the dependencies listed above precompiled. +# Warning: editing the code will not affect what runs when using this .so, you +# need to re-precompile if you change anything. +create_sysimage(; sysimage_path="moment_kinetics.so", + precompile_execution_file="util/precompile_run_kinetic-electrons.jl", + include_transitive_dependencies=false, # This is needed to make MPI work, see https://github.com/JuliaParallel/MPI.jl/issues/518 + sysimage_build_args=`-O3 --check-bounds=no`, # Assume if we are precompiling we want an optimized, production build + ) diff --git a/runs/1D-mirror_MMS_new_nel_r_1_z_16_vpa_16_vperp_8_diss.toml b/runs/1D-mirror_MMS_new_nel_r_1_z_16_vpa_16_vperp_8_diss.toml deleted file mode 100644 index 9a475346b..000000000 --- a/runs/1D-mirror_MMS_new_nel_r_1_z_16_vpa_16_vperp_8_diss.toml +++ /dev/null @@ -1,98 +0,0 @@ -n_ion_species = 1 -n_neutral_species = 0 -electron_physics = "boltzmann_electron_response" -#electron_physics = "boltzmann_electron_response_with_simple_sheath" -evolve_moments_density = false -evolve_moments_parallel_flow = false -evolve_moments_parallel_pressure = false -evolve_moments_conservation = false -force_Er_zero_at_wall = false #true -geometry_option="1D-mirror" -DeltaB=10.0 -#geometry_option="constant-helical" -#pitch=1.0 -Er_constant = 0.0 -T_e = 1.0 -T_wall = 1.0 -rhostar = 1.0 -initial_density1 = 0.5 -initial_temperature1 = 1.0 -initial_density2 = 0.5 -initial_temperature2 = 1.0 -z_IC_option1 = "sinusoid" -z_IC_density_amplitude1 = 0.001 -z_IC_density_phase1 = 0.0 -z_IC_upar_amplitude1 = 0.0 -z_IC_upar_phase1 = 0.0 -z_IC_temperature_amplitude1 = 0.0 -z_IC_temperature_phase1 = 0.0 -z_IC_option2 = "sinusoid" -z_IC_density_amplitude2 = 0.001 -z_IC_density_phase2 = 0.0 -z_IC_upar_amplitude2 = 0.0 -z_IC_upar_phase2 = 0.0 -z_IC_temperature_amplitude2 = 0.0 -z_IC_temperature_phase2 = 0.0 -charge_exchange_frequency = 0.0 -ionization_frequency = 0.0 -nstep = 2000 -dt = 0.0005 -nwrite = 200 -nwrite_dfns = 200 -use_semi_lagrange = false -n_rk_stages = 4 -split_operators = false -z_ngrid = 17 -z_nelement = 16 -z_nelement_local = 16 -z_bc = "wall" -z_discretization = "chebyshev_pseudospectral" -r_ngrid = 1 -r_nelement = 1 -r_nelement_local = 1 -r_bc = "periodic" -r_discretization = "chebyshev_pseudospectral" -vpa_ngrid = 17 -vpa_nelement = 16 -vpa_L = 12.0 -vpa_bc = "zero" -vpa_discretization = "chebyshev_pseudospectral" -vperp_ngrid = 17 -vperp_nelement = 8 -vperp_L = 6.0 -#vperp_discretization = "finite_difference" -vperp_discretization = "chebyshev_pseudospectral" -vperp_bc = "zero" - -vz_ngrid = 17 -vz_nelement = 4 -vz_L = 12.0 -vz_bc = "periodic" -vz_discretization = "chebyshev_pseudospectral" - -vr_ngrid = 17 -vr_nelement = 4 -vr_L = 12.0 -vr_bc = "periodic" -vr_discretization = "chebyshev_pseudospectral" - -vzeta_ngrid = 17 -vzeta_nelement = 4 -vzeta_L = 12.0 -vzeta_bc = "periodic" -vzeta_discretization = "chebyshev_pseudospectral" - -[manufactured_solns] - use_for_advance=true - use_for_init=true - # constant to be used to control Ez divergence in MMS tests - epsilon_offset=0.1 - # bool to control if dfni is a function of vpa or vpabar in MMS test - use_vpabar_in_mms_dfni=true - alpha_switch=1.0 - type="default" -[ion_numerical_dissipation] -vpa_dissipation_coefficient = 0.001 -vperp_dissipation_coefficient = 0.001 -#z_dissipation_coefficient = 0.1 -r_dissipation_coefficient = 0.0 diff --git a/runs/1D-mirror_MMS_new_nel_r_1_z_6_vpa_6_vperp_3_diss.toml b/runs/1D-mirror_MMS_new_nel_r_1_z_6_vpa_6_vperp_3_diss.toml deleted file mode 100644 index 347dd2b04..000000000 --- a/runs/1D-mirror_MMS_new_nel_r_1_z_6_vpa_6_vperp_3_diss.toml +++ /dev/null @@ -1,99 +0,0 @@ -n_ion_species = 1 -n_neutral_species = 0 -electron_physics = "boltzmann_electron_response" -#electron_physics = "boltzmann_electron_response_with_simple_sheath" -evolve_moments_density = false -evolve_moments_parallel_flow = false -evolve_moments_parallel_pressure = false -evolve_moments_conservation = false -force_Er_zero_at_wall = false #true -geometry_option="1D-mirror" -DeltaB=0.5 -#geometry_option="constant-helical" -#pitch=1.0 -Er_constant = 0.0 -T_e = 1.0 -T_wall = 1.0 -rhostar = 1.0 -initial_density1 = 0.5 -initial_temperature1 = 1.0 -initial_density2 = 0.5 -initial_temperature2 = 1.0 -z_IC_option1 = "sinusoid" -z_IC_density_amplitude1 = 0.001 -z_IC_density_phase1 = 0.0 -z_IC_upar_amplitude1 = 0.0 -z_IC_upar_phase1 = 0.0 -z_IC_temperature_amplitude1 = 0.0 -z_IC_temperature_phase1 = 0.0 -z_IC_option2 = "sinusoid" -z_IC_density_amplitude2 = 0.001 -z_IC_density_phase2 = 0.0 -z_IC_upar_amplitude2 = 0.0 -z_IC_upar_phase2 = 0.0 -z_IC_temperature_amplitude2 = 0.0 -z_IC_temperature_phase2 = 0.0 -charge_exchange_frequency = 0.0 -ionization_frequency = 0.0 -nstep = 2000 -dt = 0.0005 -nwrite = 200 -nwrite_dfns = 200 -use_semi_lagrange = false -n_rk_stages = 4 -split_operators = false -z_ngrid = 17 -z_nelement = 6 -z_nelement_local = 6 -z_bc = "wall" -z_discretization = "chebyshev_pseudospectral" -r_ngrid = 1 -r_nelement = 1 -r_nelement_local = 1 -r_bc = "periodic" -r_discretization = "chebyshev_pseudospectral" -vpa_ngrid = 17 -vpa_nelement = 6 -vpa_L = 12.0 -vpa_bc = "zero" -vpa_discretization = "chebyshev_pseudospectral" -vperp_ngrid = 17 -vperp_nelement = 3 -vperp_L = 6.0 -#vperp_discretization = "finite_difference" -#vperp_discretization = "chebyshev_pseudospectral" -vperp_discretization = "gausslegendre_pseudospectral" -vperp_bc = "zero" - -vz_ngrid = 17 -vz_nelement = 4 -vz_L = 12.0 -vz_bc = "periodic" -vz_discretization = "chebyshev_pseudospectral" - -vr_ngrid = 17 -vr_nelement = 4 -vr_L = 12.0 -vr_bc = "periodic" -vr_discretization = "chebyshev_pseudospectral" - -vzeta_ngrid = 17 -vzeta_nelement = 4 -vzeta_L = 12.0 -vzeta_bc = "periodic" -vzeta_discretization = "chebyshev_pseudospectral" - -[manufactured_solns] - use_for_advance=true - use_for_init=true - # constant to be used to control Ez divergence in MMS tests - epsilon_offset=0.1 - # bool to control if dfni is a function of vpa or vpabar in MMS test - use_vpabar_in_mms_dfni=true - alpha_switch=1.0 - type="default" -[ion_numerical_dissipation] -vpa_dissipation_coefficient = 0.001 -vperp_dissipation_coefficient = 0.001 -#z_dissipation_coefficient = 0.1 -r_dissipation_coefficient = 0.0 diff --git a/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_16_vpa_16_vperp_8_diss.toml b/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_16_vpa_16_vperp_8_diss.toml index 883a73275..f22e52894 100644 --- a/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_16_vpa_16_vperp_8_diss.toml +++ b/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_16_vpa_16_vperp_8_diss.toml @@ -7,7 +7,6 @@ evolve_moments_parallel_flow = false evolve_moments_parallel_pressure = false evolve_moments_conservation = false force_Er_zero_at_wall = false #true -Er_constant = 0.0 T_e = 1.0 T_wall = 1.0 initial_density1 = 0.5 @@ -99,3 +98,4 @@ DeltaB=0.5 #option="constant-helical" pitch=1.0 rhostar = 1.0 +Er_constant = 0.0 diff --git a/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_32_vpa_32_vperp_16_diss.toml b/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_32_vpa_32_vperp_16_diss.toml index 50e878cb3..fefea7811 100644 --- a/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_32_vpa_32_vperp_16_diss.toml +++ b/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_32_vpa_32_vperp_16_diss.toml @@ -7,7 +7,6 @@ evolve_moments_parallel_flow = false evolve_moments_parallel_pressure = false evolve_moments_conservation = false force_Er_zero_at_wall = false #true -Er_constant = 0.0 T_e = 1.0 T_wall = 1.0 initial_density1 = 0.5 @@ -99,3 +98,4 @@ DeltaB=0.5 #option="constant-helical" pitch=1.0 rhostar = 1.0 +Er_constant = 0.0 diff --git a/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_4_vpa_4_vperp_2_diss.toml b/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_4_vpa_4_vperp_2_diss.toml index 1624ca129..58051541c 100644 --- a/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_4_vpa_4_vperp_2_diss.toml +++ b/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_4_vpa_4_vperp_2_diss.toml @@ -7,7 +7,6 @@ evolve_moments_parallel_flow = false evolve_moments_parallel_pressure = false evolve_moments_conservation = false force_Er_zero_at_wall = false #true -Er_constant = 0.0 T_e = 1.0 T_wall = 1.0 initial_density1 = 0.5 @@ -99,3 +98,4 @@ DeltaB=0.5 #option="constant-helical" pitch=1.0 rhostar = 1.0 +Er_constant = 0.0 diff --git a/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_64_vpa_64_vperp_32_diss.toml b/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_64_vpa_64_vperp_32_diss.toml index 74e3af388..0cc6b6ef1 100644 --- a/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_64_vpa_64_vperp_32_diss.toml +++ b/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_64_vpa_64_vperp_32_diss.toml @@ -7,7 +7,6 @@ evolve_moments_parallel_flow = false evolve_moments_parallel_pressure = false evolve_moments_conservation = false force_Er_zero_at_wall = false #true -Er_constant = 0.0 T_e = 1.0 T_wall = 1.0 initial_density1 = 0.5 @@ -99,3 +98,4 @@ DeltaB=0.5 #option="constant-helical" pitch=1.0 rhostar = 1.0 +Er_constant = 0.0 diff --git a/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_8_vpa_8_vperp_4_diss.toml b/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_8_vpa_8_vperp_4_diss.toml index f4d4956ab..0f3a24c74 100644 --- a/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_8_vpa_8_vperp_4_diss.toml +++ b/runs/1D-mirror_MMS_ngrid_9_nel_r_1_z_8_vpa_8_vperp_4_diss.toml @@ -7,7 +7,6 @@ evolve_moments_parallel_flow = false evolve_moments_parallel_pressure = false evolve_moments_conservation = false force_Er_zero_at_wall = false #true -Er_constant = 0.0 T_e = 1.0 T_wall = 1.0 initial_density1 = 0.5 @@ -99,3 +98,4 @@ DeltaB=0.5 #option="constant-helical" pitch=1.0 rhostar = 1.0 +Er_constant = 0.0 diff --git a/runs/1D-wall_MMS_new_nel_r_1_z_16_vpa_16_vperp_1_diss.toml b/runs/1D-wall_MMS_new_nel_r_1_z_16_vpa_16_vperp_1_diss.toml index 8bf508ee1..96611183c 100644 --- a/runs/1D-wall_MMS_new_nel_r_1_z_16_vpa_16_vperp_1_diss.toml +++ b/runs/1D-wall_MMS_new_nel_r_1_z_16_vpa_16_vperp_1_diss.toml @@ -7,7 +7,6 @@ evolve_moments_parallel_flow = false evolve_moments_parallel_pressure = false evolve_moments_conservation = false force_Er_zero_at_wall = false #true -Er_constant = 0.0 T_e = 1.0 T_wall = 1.0 initial_density1 = 0.5 @@ -89,3 +88,5 @@ split_operators = false vpa_dissipation_coefficient = 0.01 #z_dissipation_coefficient = 0.1 #r_dissipation_coefficient = 0.0 +[geometry] +Er_constant = 0.0 diff --git a/runs/1D-wall_MMS_new_nel_r_1_z_16_vpa_16_vperp_1_krook.toml b/runs/1D-wall_MMS_new_nel_r_1_z_16_vpa_16_vperp_1_krook.toml index 1abe96001..346996f69 100644 --- a/runs/1D-wall_MMS_new_nel_r_1_z_16_vpa_16_vperp_1_krook.toml +++ b/runs/1D-wall_MMS_new_nel_r_1_z_16_vpa_16_vperp_1_krook.toml @@ -7,7 +7,6 @@ evolve_moments_parallel_flow = false evolve_moments_parallel_pressure = false evolve_moments_conservation = false force_Er_zero_at_wall = false #true -Er_constant = 0.0 T_e = 1.0 T_wall = 1.0 initial_density1 = 0.5 @@ -93,3 +92,5 @@ split_operators = false vpa_dissipation_coefficient = -1.0 z_dissipation_coefficient = -1.0 r_dissipation_coefficient = -1.0 +[geometry] +Er_constant = 0.0 diff --git a/runs/1D-wall_MMS_new_nel_r_1_z_16_vpa_8_vperp_8_krook.toml b/runs/1D-wall_MMS_new_nel_r_1_z_16_vpa_8_vperp_8_krook.toml index d9ddc1aeb..3378a93b1 100644 --- a/runs/1D-wall_MMS_new_nel_r_1_z_16_vpa_8_vperp_8_krook.toml +++ b/runs/1D-wall_MMS_new_nel_r_1_z_16_vpa_8_vperp_8_krook.toml @@ -7,7 +7,6 @@ evolve_moments_parallel_flow = false evolve_moments_parallel_pressure = false evolve_moments_conservation = false force_Er_zero_at_wall = false #true -Er_constant = 0.0 T_e = 1.0 T_wall = 1.0 initial_density1 = 0.5 @@ -96,3 +95,5 @@ split_operators = false vpa_dissipation_coefficient = -1.0 z_dissipation_coefficient = -1.0 r_dissipation_coefficient = -1.0 +[geometry] +Er_constant = 0.0 diff --git a/runs/2D-mirror_MMS_ngrid_5_nel_r_16_z_16_vpa_16_vperp_8_diss.toml b/runs/2D-mirror_MMS_ngrid_5_nel_r_16_z_16_vpa_16_vperp_8_diss.toml index a4a31ccbb..29542c43f 100644 --- a/runs/2D-mirror_MMS_ngrid_5_nel_r_16_z_16_vpa_16_vperp_8_diss.toml +++ b/runs/2D-mirror_MMS_ngrid_5_nel_r_16_z_16_vpa_16_vperp_8_diss.toml @@ -7,7 +7,6 @@ evolve_moments_parallel_flow = false evolve_moments_parallel_pressure = false evolve_moments_conservation = false force_Er_zero_at_wall = false #true -Er_constant = 0.0 T_e = 1.0 T_wall = 1.0 initial_density1 = 0.5 @@ -99,3 +98,4 @@ DeltaB=0.5 #option="constant-helical" pitch=0.5 rhostar = 1.0 +Er_constant = 0.0 diff --git a/runs/2D-mirror_MMS_ngrid_5_nel_r_32_z_32_vpa_32_vperp_16_diss.toml b/runs/2D-mirror_MMS_ngrid_5_nel_r_32_z_32_vpa_32_vperp_16_diss.toml index 01dd1b487..3561f1d11 100644 --- a/runs/2D-mirror_MMS_ngrid_5_nel_r_32_z_32_vpa_32_vperp_16_diss.toml +++ b/runs/2D-mirror_MMS_ngrid_5_nel_r_32_z_32_vpa_32_vperp_16_diss.toml @@ -7,7 +7,6 @@ evolve_moments_parallel_flow = false evolve_moments_parallel_pressure = false evolve_moments_conservation = false force_Er_zero_at_wall = false #true -Er_constant = 0.0 T_e = 1.0 T_wall = 1.0 initial_density1 = 0.5 @@ -99,3 +98,4 @@ DeltaB=0.5 #option="constant-helical" pitch=0.5 rhostar = 1.0 +Er_constant = 0.0 diff --git a/runs/2D-mirror_MMS_ngrid_5_nel_r_4_z_4_vpa_4_vperp_2_diss.toml b/runs/2D-mirror_MMS_ngrid_5_nel_r_4_z_4_vpa_4_vperp_2_diss.toml index b13205d70..a8add1f3e 100644 --- a/runs/2D-mirror_MMS_ngrid_5_nel_r_4_z_4_vpa_4_vperp_2_diss.toml +++ b/runs/2D-mirror_MMS_ngrid_5_nel_r_4_z_4_vpa_4_vperp_2_diss.toml @@ -7,7 +7,6 @@ evolve_moments_parallel_flow = false evolve_moments_parallel_pressure = false evolve_moments_conservation = false force_Er_zero_at_wall = false #true -Er_constant = 0.0 T_e = 1.0 T_wall = 1.0 initial_density1 = 0.5 @@ -99,3 +98,4 @@ DeltaB=0.5 #option="constant-helical" pitch=0.5 rhostar = 1.0 +Er_constant = 0.0 diff --git a/runs/2D-mirror_MMS_ngrid_5_nel_r_8_z_8_vpa_8_vperp_4_diss.toml b/runs/2D-mirror_MMS_ngrid_5_nel_r_8_z_8_vpa_8_vperp_4_diss.toml index f19b91e39..2eb57f6ee 100644 --- a/runs/2D-mirror_MMS_ngrid_5_nel_r_8_z_8_vpa_8_vperp_4_diss.toml +++ b/runs/2D-mirror_MMS_ngrid_5_nel_r_8_z_8_vpa_8_vperp_4_diss.toml @@ -7,7 +7,6 @@ evolve_moments_parallel_flow = false evolve_moments_parallel_pressure = false evolve_moments_conservation = false force_Er_zero_at_wall = false #true -Er_constant = 0.0 T_e = 1.0 T_wall = 1.0 initial_density1 = 0.5 @@ -99,3 +98,4 @@ DeltaB=0.5 #option="constant-helical" pitch=0.5 rhostar = 1.0 +Er_constant = 0.0 diff --git a/runs/2D-wall_MMS_nel_r_32_z_32_vpa_16_vperp_1_diss.toml b/runs/2D-wall_MMS_nel_r_32_z_32_vpa_16_vperp_1_diss.toml index 274fc59e6..fa1f6c220 100644 --- a/runs/2D-wall_MMS_nel_r_32_z_32_vpa_16_vperp_1_diss.toml +++ b/runs/2D-wall_MMS_nel_r_32_z_32_vpa_16_vperp_1_diss.toml @@ -7,7 +7,6 @@ evolve_moments_parallel_flow = false evolve_moments_parallel_pressure = false evolve_moments_conservation = false force_Er_zero_at_wall = false #true -Er_constant = 0.0 T_e = 1.0 T_wall = 1.0 initial_density1 = 0.5 @@ -95,3 +94,4 @@ r_dissipation_coefficient = 0.1 option="constant-helical" pitch=0.5 rhostar = 1.0 +Er_constant = 0.0 diff --git a/runs/wall+sheath-bc_boltzmann.toml b/runs/wall+sheath-bc_boltzmann.toml new file mode 100644 index 000000000..b49b5a328 --- /dev/null +++ b/runs/wall+sheath-bc_boltzmann.toml @@ -0,0 +1,86 @@ +n_ion_species = 1 +n_neutral_species = 1 +electron_physics = "boltzmann_electron_response_with_simple_sheath" +run_name = "sheath-bc_cheb_boltzmann" +evolve_moments_density = false +evolve_moments_parallel_flow = false +evolve_moments_parallel_pressure = false +evolve_moments_conservation = false +T_e = 1.0 +T_wall = 1.0 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "gaussian" +z_IC_density_amplitude1 = 0.001 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.0 +z_IC_temperature_phase1 = 0.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "gaussian" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = 0.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 2.0 +electron_charge_exchange_frequency = 0.0 +nu_ei = 0.0 +ionization_frequency = 2.0 +electron_ionization_frequency = 0.0 +ionization_energy = 0.0 +constant_ionization_rate = false +nstep = 40000 +dt = 0.0005 +nwrite = 200 +use_semi_lagrange = false +n_rk_stages = 4 +split_operators = false +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 9 +z_nelement = 16 +z_bc = "wall" +z_discretization = "chebyshev_pseudospectral" +vpa_ngrid = 17 +vpa_nelement = 10 +vpa_L = 8.0 +vpa_bc = "periodic" +vpa_discretization = "chebyshev_pseudospectral" +vz_ngrid = 17 +vz_nelement = 10 +vz_L = 8.0 +vz_bc = "periodic" +vz_discretization = "chebyshev_pseudospectral" + +[output] +ascii_output = true + +[electron_numerical_dissipation] +moment_dissipation_coefficient = 0.0001 +vpa_dissipation_coefficient = 0.01 + +[ion_numerical_dissipation] +moment_dissipation_coefficient = 0.0001 +vpa_dissipation_coefficient = 0.01 + +[neutral_numerical_dissipation] +moment_dissipation_coefficient = 0.0001 +vz_dissipation_coefficient = 0.01 \ No newline at end of file diff --git a/runs/wall+sheath-bc_braginskii.toml b/runs/wall+sheath-bc_braginskii.toml new file mode 100644 index 000000000..616873e38 --- /dev/null +++ b/runs/wall+sheath-bc_braginskii.toml @@ -0,0 +1,89 @@ +n_ion_species = 1 +n_neutral_species = 1 +#boltzmann_electron_response = false +electron_physics = "braginskii_fluid" +run_name = "sheath-bc_cheb_braginskii_Rion_test" +evolve_moments_density = false +evolve_moments_parallel_flow = false +evolve_moments_parallel_pressure = false +evolve_moments_conservation = false +T_e = 1.0 +T_wall = 1.0 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "gaussian" +z_IC_density_amplitude1 = 0.001 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.0 +z_IC_temperature_phase1 = 0.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "gaussian" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = 0.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 2.0 +electron_charge_exchange_frequency = 0.0 +nu_ei = 0.0 +ionization_frequency = 2.0 +electron_ionization_frequency = 2.0 +ionization_energy = 1.0 +constant_ionization_rate = false +nstep = 40000 +#nstep = 1 +dt = 0.0005 +nwrite = 200 +use_semi_lagrange = false +n_rk_stages = 4 +split_operators = false +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 9 +z_nelement = 16 +z_bc = "wall" +z_discretization = "chebyshev_pseudospectral" +vpa_ngrid = 17 +vpa_nelement = 10 +vpa_L = 8.0 +vpa_bc = "periodic" +vpa_discretization = "chebyshev_pseudospectral" +vz_ngrid = 17 +vz_nelement = 10 +vz_L = 8.0 +vz_bc = "periodic" +vz_discretization = "chebyshev_pseudospectral" + +[output] +ascii_output = true + +[electron_numerical_dissipation] +moment_dissipation_coefficient = 0.0001 +vpa_dissipation_coefficient = 0.002 + +[ion_numerical_dissipation] +moment_dissipation_coefficient = 0.0001 +vpa_dissipation_coefficient = 0.002 + +[neutral_numerical_dissipation] +moment_dissipation_coefficient = 0.0001 +vz_dissipation_coefficient = 0.002 + diff --git a/runs/wall+sheath-bc_braginskii_boltzmann_test.toml b/runs/wall+sheath-bc_braginskii_boltzmann_test.toml new file mode 100644 index 000000000..ce8b7af1f --- /dev/null +++ b/runs/wall+sheath-bc_braginskii_boltzmann_test.toml @@ -0,0 +1,87 @@ +n_ion_species = 1 +n_neutral_species = 1 +electron_physics = "braginskii_fluid" +run_name = "sheath-bc_cheb_braginskii_boltzmann_test" +evolve_moments_density = false +evolve_moments_parallel_flow = false +evolve_moments_parallel_pressure = false +evolve_moments_conservation = false +T_e = 1.0 +T_wall = 1.0 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "gaussian" +z_IC_density_amplitude1 = 0.001 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.0 +z_IC_temperature_phase1 = 0.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "gaussian" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = 0.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 2.0 +electron_charge_exchange_frequency = 0.0 +nu_ei = 0.0 +ionization_frequency = 2.0 +electron_ionization_frequency = 2.0 +ionization_energy = 1.0 +constant_ionization_rate = false +nstep = 40000 +dt = 0.0005 +nwrite = 200 +nwrite_dfns = 1000 +use_semi_lagrange = false +n_rk_stages = 4 +split_operators = false +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 9 +z_nelement = 16 +z_bc = "wall" +z_discretization = "chebyshev_pseudospectral" +vpa_ngrid = 17 +vpa_nelement = 10 +vpa_L = 8.0 +vpa_bc = "periodic" +vpa_discretization = "chebyshev_pseudospectral" +vz_ngrid = 17 +vz_nelement = 10 +vz_L = 8.0 +vz_bc = "periodic" +vz_discretization = "chebyshev_pseudospectral" + +[output] +ascii_output = true + +[electron_numerical_dissipation] +moment_dissipation_coefficient = 0.0001 +vpa_dissipation_coefficient = 0.01 + +[ion_numerical_dissipation] +moment_dissipation_coefficient = 0.0001 +vpa_dissipation_coefficient = 0.01 + +[neutral_numerical_dissipation] +moment_dissipation_coefficient = 0.0001 +vz_dissipation_coefficient = 0.01 \ No newline at end of file diff --git a/runs/wall+sheath-bc_braginskii_colls.toml b/runs/wall+sheath-bc_braginskii_colls.toml new file mode 100644 index 000000000..3267b2402 --- /dev/null +++ b/runs/wall+sheath-bc_braginskii_colls.toml @@ -0,0 +1,88 @@ +n_ion_species = 1 +n_neutral_species = 1 +#boltzmann_electron_response = false +electron_physics = "braginskii_fluid" +run_name = "sheath-bc_cheb_braginskii_zdiss0p01" +evolve_moments_density = false +evolve_moments_parallel_flow = false +evolve_moments_parallel_pressure = false +evolve_moments_conservation = false +T_e = 1.0 +T_wall = 1.0 +initial_density1 = 1.0 +initial_temperature1 = 1.0 +z_IC_option1 = "gaussian" +z_IC_density_amplitude1 = 0.001 +z_IC_density_phase1 = 0.0 +z_IC_upar_amplitude1 = 1.0 +z_IC_upar_phase1 = 0.0 +z_IC_temperature_amplitude1 = 0.0 +z_IC_temperature_phase1 = 0.0 +vpa_IC_option1 = "gaussian" +vpa_IC_density_amplitude1 = 1.0 +vpa_IC_density_phase1 = 0.0 +vpa_IC_upar_amplitude1 = 0.0 +vpa_IC_upar_phase1 = 0.0 +vpa_IC_temperature_amplitude1 = 0.0 +vpa_IC_temperature_phase1 = 0.0 +initial_density2 = 1.0 +initial_temperature2 = 1.0 +z_IC_option2 = "gaussian" +z_IC_density_amplitude2 = 0.001 +z_IC_density_phase2 = 0.0 +z_IC_upar_amplitude2 = 0.0 +z_IC_upar_phase2 = 0.0 +z_IC_temperature_amplitude2 = 0.0 +z_IC_temperature_phase2 = 0.0 +vpa_IC_option2 = "gaussian" +vpa_IC_density_amplitude2 = 1.0 +vpa_IC_density_phase2 = 0.0 +vpa_IC_upar_amplitude2 = 0.0 +vpa_IC_upar_phase2 = 0.0 +vpa_IC_temperature_amplitude2 = 0.0 +vpa_IC_temperature_phase2 = 0.0 +charge_exchange_frequency = 2.0 +electron_charge_exchange_frequency = 2.0 +nu_ei = 100000.0 +ionization_frequency = 2.0 +electron_ionization_frequency = 2.0 +ionization_energy = 1.0 +constant_ionization_rate = false +nstep = 40000 +dt = 0.0001 +nwrite = 50 +nwrite_dfns = 1000 +use_semi_lagrange = false +n_rk_stages = 4 +split_operators = false +r_ngrid = 1 +r_nelement = 1 +z_ngrid = 9 +z_nelement = 16 +z_bc = "wall" +z_discretization = "chebyshev_pseudospectral" +vpa_ngrid = 17 +vpa_nelement = 10 +vpa_L = 8.0 +vpa_bc = "periodic" +vpa_discretization = "chebyshev_pseudospectral" +vz_ngrid = 17 +vz_nelement = 10 +vz_L = 8.0 +vz_bc = "periodic" +vz_discretization = "chebyshev_pseudospectral" + +[output] +ascii_output = true + +[electron_numerical_dissipation] +moment_dissipation_coefficient = 0.0001 +vpa_dissipation_coefficient = 0.01 + +[ion_numerical_dissipation] +moment_dissipation_coefficient = 0.0001 +vpa_dissipation_coefficient = 0.01 + +[neutral_numerical_dissipation] +moment_dissipation_coefficient = 0.0001 +vz_dissipation_coefficient = 0.01 \ No newline at end of file diff --git a/util/precompile_run.jl b/util/precompile_run.jl index f5a7fb512..d4e16965e 100644 --- a/util/precompile_run.jl +++ b/util/precompile_run.jl @@ -27,7 +27,7 @@ base_input = Dict("run_name" => "precompilation", "vpa_ngrid" => 7, "vpa_nelement" => 3, "vpa_bc" => "zero", - "vpa_L" => 4.0, + "vpa_L" => 8.0, "vpa_discretization" => "finite_difference", "vzeta_ngrid" => 5, "vzeta_nelement" => 3, @@ -42,7 +42,7 @@ base_input = Dict("run_name" => "precompilation", "vz_ngrid" => 7, "vz_nelement" => 3, "vz_bc" => "zero", - "vz_L" => 4.0, + "vz_L" => 8.0, "vz_discretization" => "finite_difference", "timestepping" => Dict{String,Any}("nstep" => 1)) cheb_input = merge(base_input, Dict("r_discretization" => "chebyshev_pseudospectral", @@ -66,17 +66,44 @@ for input ∈ [base_input, cheb_input, wall_bc_input, wall_bc_cheb_input] push!(inputs_list, x) end -collisions_input = merge(wall_bc_cheb_input, Dict("n_neutral_species" => 0, +collisions_input1 = merge(wall_bc_cheb_input, Dict("n_neutral_species" => 0, "krook_collisions" => Dict{String,Any}("use_krook" => true), "fokker_planck_collisions" => Dict{String,Any}("use_fokker_planck" => true, "self_collisions" => true, "slowing_down_test" => true), "vperp_discretization" => "gausslegendre_pseudospectral", "vpa_discretization" => "gausslegendre_pseudospectral", )) +collisions_input2 = merge(wall_bc_cheb_input, Dict("n_neutral_species" => 0, + "krook_collisions" => Dict{String,Any}("use_krook" => true), + "fokker_planck_collisions" => Dict{String,Any}("use_fokker_planck" => true, "self_collisions" => true, "slowing_down_test" => true), + "vperp_discretization" => "gausslegendre_pseudospectral", + "vpa_discretization" => "gausslegendre_pseudospectral", + "vperp_bc" => "zero-impose-regularity", + )) # add an additional input for every geometry option available in addition to the default geo_input1 = merge(wall_bc_cheb_input, Dict("n_neutral_species" => 0, "geometry" => Dict{String,Any}("option" => "1D-mirror", "DeltaB" => 0.5, "pitch" => 0.5, "rhostar" => 1.0))) -push!(inputs_list, collisions_input, geo_input1) +kinetic_electron_input = merge(cheb_input, Dict("evolve_moments_density" => true, + "evolve_moments_parallel_flow" => true, + "evolve_moments_parallel_pressure" => true, + "r_ngrid" => 1, + "r_nelement" => 1, + "vperp_ngrid" => 1, + "vperp_nelement" => 1, + "vzeta_ngrid" => 1, + "vzeta_nelement" => 1, + "vr_ngrid" => 1, + "vr_nelement" => 1, + "electron_physics" => "kinetic_electrons", + "electron_timestepping" => Dict{String,Any}("nstep" => 1, + "dt" => 2.0e-11, + "initialization_residual_value" => 1.0e10, + "converged_residual_value" => 1.0e10, + "rtol" => 1.0e10, + "no_restart" => true), + )) + +push!(inputs_list, collisions_input1, collisions_input2, geo_input1, kinetic_electron_input) for input in inputs_list run_moment_kinetics(input) diff --git a/util/precompile_run_kinetic-electrons.jl b/util/precompile_run_kinetic-electrons.jl new file mode 100644 index 000000000..b605fcf16 --- /dev/null +++ b/util/precompile_run_kinetic-electrons.jl @@ -0,0 +1,61 @@ +# provide option of running from command line via 'julia moment_kinetics.jl' +using Pkg +Pkg.activate(".") + +using moment_kinetics + +# Create a temporary directory for test output +test_output_directory = tempname() +mkpath(test_output_directory) + +input = Dict("run_name" => "precompilation", + "base_directory" => test_output_directory, + "dt" => 0.0, + "evolve_moments_density" => true, + "evolve_moments_parallel_flow" => true, + "evolve_moments_parallel_pressure" => true, + "electron_physics" => "kinetic_electrons", + "r_ngrid" => 1, + "r_nelement" => 1, + "r_bc" => "periodic", + "r_discretization" => "chebyshev_pseudospectral", + "z_ngrid" => 5, + "z_nelement" => 4, + "z_bc" => "wall", + "z_discretization" => "chebyshev_pseudospectral", + "vperp_ngrid" => 1, + "vperp_nelement" => 1, + "vperp_bc" => "zero", + "vperp_L" => 4.0, + "vperp_discretization" => "chebyshev_pseudospectral", + "vpa_ngrid" => 7, + "vpa_nelement" => 8, + "vpa_bc" => "zero", + "vpa_L" => 8.0, + "vpa_discretization" => "chebyshev_pseudospectral", + "vzeta_ngrid" => 1, + "vzeta_nelement" => 1, + "vzeta_bc" => "zero", + "vzeta_L" => 4.0, + "vzeta_discretization" => "chebyshev_pseudospectral", + "vr_ngrid" => 1, + "vr_nelement" => 1, + "vr_bc" => "zero", + "vr_L" => 4.0, + "vr_discretization" => "chebyshev_pseudospectral", + "vz_ngrid" => 7, + "vz_nelement" => 8, + "vz_bc" => "zero", + "vz_L" => 8.0, + "vz_discretization" => "chebyshev_pseudospectral", + "timestepping" => Dict{String,Any}("nstep" => 1, + "dt" => 2.0e-11), + "electron_timestepping" => Dict{String,Any}("nstep" => 1, + "dt" => 2.0e-11, + "initialization_residual_value" => 1.0e10, + "converged_residual_value" => 1.0e10, + "rtol" => 1.0e10, + "no_restart" => true)) + + +run_moment_kinetics(input)