[
  {
    "path": ".buildkite/docs-pipeline.yml",
    "content": "env:\n  JULIA_VERSION: \"1.5.4\"\n  GKSwstype: nul\n  OPENBLAS_NUM_THREADS: 1\n  CLIMATEMACHINE_SETTINGS_DISABLE_GPU: \"true\"\n  CLIMATEMACHINE_SETTINGS_FIX_RNG_SEED: \"true\"\n  CLIMATEMACHINE_SETTINGS_DISABLE_CUSTOM_LOGGER: \"true\"\n\nsteps:\n  - label: \"Build project\"\n    command:\n      - \"julia --project --color=yes -e 'using Pkg; Pkg.instantiate()'\"\n      - \"julia --project=docs/ --color=yes -e 'using Pkg; Pkg.instantiate()'\"\n      - \"julia --project=docs/ --color=yes -e 'using Pkg; Pkg.precompile()'\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_cpus_per_task: 1\n      slurm_mem_per_cpu: 6000\n\n  - wait\n\n  - label: \"Build docs\"\n    command:\n      # this extracts out the PR number from the bors message on the trying branch\n      # to force documenter to deploy the PR branch number gh-preview\n      - \"if [ $$BUILDKITE_BRANCH == \\\"trying\\\" ]; then \\\n            export BUILDKITE_PULL_REQUEST=\\\"$${BUILDKITE_MESSAGE//[!0-9]/}\\\"; \\\n         fi\"\n      - \"if [[ ! -z \\\"$${PULL_REQUEST}\\\" ]]; then \\\n            export BUILDKITE_PULL_REQUEST=\\\"$${PULL_REQUEST}\\\"; \\\n         fi\"\n      - \"julia --project=docs/ --color=yes --procs=10 docs/make.jl\"\n    env:\n      JULIA_PROJECT: \"docs/\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_time: 120\n      slurm_nodes: 1\n      slurm_ntasks: 10\n      slurm_cpus_per_task: 1\n      slurm_mem_per_cpu: 6000\n\n"
  },
  {
    "path": ".buildkite/pipeline.yml",
    "content": "env:\n  JULIA_VERSION: \"1.5.4\"\n  OPENMPI_VERSION: \"4.0.4\"\n  CUDA_VERSION: \"10.2\"\n  OPENBLAS_NUM_THREADS: 1\n  CLIMATEMACHINE_SETTINGS_FIX_RNG_SEED: \"true\"\n\nsteps:\n  - label: \"init cpu env\"\n    key: \"init_cpu_env\"\n    command:\n      - \"echo $JULIA_DEPOT_PATH\"\n      - \"julia --project -e 'using Pkg; Pkg.instantiate(;verbose=true)'\"\n      - \"julia --project -e 'using Pkg; Pkg.precompile()'\"\n      - \"julia --project -e 'using Pkg; Pkg.status()'\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"init gpu env\"\n    key: \"init_gpu_env\"\n    command:\n      - \"echo $JULIA_DEPOT_PATH\"\n      - \"julia --project -e 'using Pkg; Pkg.instantiate(;verbose=true)'\"\n      - \"julia --project -e 'using Pkg; Pkg.precompile()'\"\n      # force the initialization of the CUDA runtime\n      # as it is lazily loaded by default\n      - \"julia --project -e 'using CUDA; CUDA.versioninfo()'\"\n      # force the initialization of the CUDA Compiler runtime\n      # as it is lazily generated by default\n      - \"julia --project -e 'using CUDA; CUDA.precompile_runtime()'\"\n      - \"julia --project -e 'using Pkg; Pkg.status()'\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - wait\n\n  - label: \"cpu_advection_diffusion_model_1dimex_bgmres\"\n    key: \"cpu_advection_diffusion_model_1dimex_bgmres\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/advection_diffusion_model_1dimex_bgmres.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_fvm_advection_diffusion_model_1dimex_bjfnks\"\n    key: \"cpu_fvm_advection_diffusion_model_1dimex_bjfnks\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/fvm_advection_diffusion_model_1dimex_bjfnks.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_pseudo1D_advection_diffusion\"\n    key: \"cpu_pseudo1D_advection_diffusion\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/pseudo1D_advection_diffusion.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_pseudo1D_advection_diffusion_1dimex\"\n    key: \"cpu_pseudo1D_advection_diffusion_1dimex\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/pseudo1D_advection_diffusion_1dimex.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_pseudo1D_advection_diffusion_mrigark_implicit\"\n    key: \"cpu_pseudo1D_advection_diffusion_mrigark_implicit\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/pseudo1D_advection_diffusion_mrigark_implicit.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_pseudo1D_heat_eqn\"\n    key: \"cpu_pseudo1D_heat_eqn\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/pseudo1D_heat_eqn.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_bickley_jet_2D\"\n    key: \"cpu_bickley_jet_2D\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/compressible_navier_stokes_equations/two_dimensional/test_bickley_jet.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_periodic_3D_hyperdiffusion\"\n    key: \"cpu_periodic_3D_hyperdiffusion\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/periodic_3D_hyperdiffusion.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_hyperdiffusion_bc\"\n    key: \"cpu_hyperdiffusion_bc\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/hyperdiffusion_bc.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_diffusion_hyperdiffusion_sphere\"\n    key: \"cpu_diffusion_hyperdiffusion_sphere\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/diffusion_hyperdiffusion_sphere.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_mpi_connect_1d\"\n    key: \"cpu_mpi_connect_1d\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/mpi_connect_1d.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 5\n\n  - label: \"cpu_mpi_connect_sphere\"\n    key: \"cpu_mpi_connect_sphere\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/mpi_connect_sphere.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 5\n\n  - label: \"cpu_mpi_getpartition\"\n    key: \"cpu_mpi_getpartition\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/mpi_getpartition.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 5\n\n  - label: \"cpu_mpi_sortcolumns4\"\n    key: \"cpu_mpi_sortcolumns4\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/mpi_sortcolumns.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 4\n\n  - label: \"cpu_ode_tests_convergence\"\n    key: \"cpu_ode_tests_convergence\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/ODESolvers/ode_tests_convergence.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_ode_tests_basic\"\n    key: \"cpu_ode_tests_basic\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/ODESolvers/ode_tests_basic.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_varsindex\"\n    key: \"cpu_varsindex\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Arrays/varsindex.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_diagnostic_fields_test\"\n    key: \"cpu_diagnostic_fields_test\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Diagnostics/diagnostic_fields_test.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_vars_test\"\n    key: \"cpu_vars_test\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/vars_test.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_grad_test\"\n    key: \"cpu_grad_test\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/grad_test.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_grad_test_sphere\"\n    key: \"cpu_grad_test_sphere\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/grad_test_sphere.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_horizontal_integral_test\"\n    key: \"cpu_horizontal_integral_test\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/horizontal_integral_test.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_integral_test\"\n    key: \"cpu_integral_test\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/integral_test.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_integral_test_sphere\"\n    key: \"cpu_integral_test_sphere\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/integral_test_sphere.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_custom_filter\"\n    key: \"cpu_custom_filter\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/custom_filter.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_fv_reconstruction_test\"\n    key: \"cpu_fv_reconstruction_test\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/fv_reconstruction_test.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_remainder_model\"\n    key: \"cpu_remainder_model\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/remainder_model.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 2\n\n  - label: \"cpu_isentropicvortex\"\n    key: \"cpu_isentropicvortex\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/isentropicvortex.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_isentropicvortex_imex\"\n    key: \"cpu_isentropicvortex_imex\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/isentropicvortex_imex.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n  \n  - label: \"cpu_isentropicvortex_lmars\"\n    key: \"cpu_isentropicvortex_lmars\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/isentropicvortex_lmars.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_isentropicvortex_multirate\"\n    key: \"cpu_isentropicvortex_multirate\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/isentropicvortex_multirate.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_isentropicvortex_mrigark\"\n    key: \"cpu_isentropicvortex_mrigark\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/isentropicvortex_mrigark.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_isentropicvortex_mrigark_implicit\"\n    key: \"cpu_isentropicvortex_mrigark_implicit\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/isentropicvortex_mrigark_implicit.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_acousticwave_1d_imex\"\n    key: \"cpu_acousticwave_1d_imex\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/acousticwave_1d_imex.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_acousticwave_mrigark\"\n    key: \"cpu_acousticwave_mrigark\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/acousticwave_mrigark.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_acousticwave_variable_degree\"\n    key: \"cpu_acousticwave_variable_degree\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/acousticwave_variable_degree.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_fvm_balance\"\n    key: \"cpu_fvm_balance\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/fvm_balance.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_mms_bc_atmos\"\n    key: \"cpu_mms_bc_atmos\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/compressible_Navier_Stokes/mms_bc_atmos.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_mms_bc_dgmodel\"\n    key: \"cpu_mms_bc_dgmodel\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/compressible_Navier_Stokes/mms_bc_dgmodel.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_density_current_model\"\n    key: \"cpu_density_current_model\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/compressible_Navier_Stokes/density_current_model.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_direction_splitting_advection_diffusion\"\n    key: \"cpu_direction_splitting_advection_diffusion\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/direction_splitting_advection_diffusion.jl --fix-rng-seed\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_variable_degree_advection_diffusion\"\n    key: \"cpu_variable_degree_advection_diffusion\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/variable_degree_advection_diffusion.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_sphere\"\n    key: \"cpu_sphere\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/conservation/sphere.jl --fix-rng-seed\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_advection_sphere\"\n    key: \"cpu_advection_sphere\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/advection_sphere.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 2\n\n  - label: \"cpu_fvm_advection_sphere\"\n    key: \"cpu_fvm_advection_sphere\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/fvm_advection_sphere.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 2\n\n  - label: \"cpu_fvm_swirl\"\n    key: \"cpu_fvm_swirl\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/fvm_swirl.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 2\n\n  - label: \"cpu_fvm_advection\"\n    key: \"cpu_fvm_advection\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/fvm_advection.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 2\n\n  - label: \"cpu_fvm_advection_diffusion\"\n    key: \"cpu_fvm_advection_diffusion\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/fvm_advection_diffusion.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 2\n\n  - label: \"cpu_fvm_advection_diffusion_periodic\"\n    key: \"cpu_fvm_advection_diffusion_periodic\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/fvm_advection_diffusion_periodic.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 2\n\n  - label: \"cpu_fvm_isentropicvortex\"\n    key: \"cpu_fvm_isentropicvortex\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/fvm_isentropicvortex.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 2\n\n  - label: \"cpu_gcm_driver_test\"\n    key: \"cpu_gcm_driver_test\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Driver/gcm_driver_test.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_poisson\"\n    key: \"cpu_poisson\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/SystemSolvers/poisson.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 2\n\n  - label: \"cpu_columnwiselu\"\n    key: \"cpu_columnwiselu\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/SystemSolvers/columnwiselu.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_cg\"\n    key: \"cpu_cg\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/SystemSolvers/cg.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_bgmres\"\n    key: \"cpu_bgmres\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/SystemSolvers/bgmres.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_bandedsystem\"\n    key: \"cpu_bandedsystem\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/SystemSolvers/bandedsystem.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_interpolation\"\n    key: \"cpu_interpolation\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/interpolation.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_dss_mpi\"\n    key: \"cpu_dss_mpi\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/DSS_mpi.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_dss\"\n    key: \"cpu_dss\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/DSS.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_GyreDriver\"\n    key: \"cpu_GyreDriver\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/ShallowWater/GyreDriver.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_test_windstress_short\"\n    key: \"cpu_test_windstress_short\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/HydrostaticBoussinesq/test_windstress_short.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_test_ocean_gyre_short\"\n    key: \"cpu_test_ocean_gyre_short\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/HydrostaticBoussinesq/test_ocean_gyre_short.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_test_2D_spindown\"\n    key: \"cpu_test_2D_spindown\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/ShallowWater/test_2D_spindown.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_test_3D_spindown\"\n    key: \"cpu_test_3D_spindown\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/HydrostaticBoussinesq/test_3D_spindown.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_test_vertical_integral_model\"\n    key: \"cpu_test_vertical_integral_model\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/SplitExplicit/test_vertical_integral_model.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_test_spindown_long\"\n    key: \"cpu_test_spindown_long\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/SplitExplicit/test_spindown_long.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_test_restart\"\n    key: \"cpu_test_restart\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/SplitExplicit/test_restart.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_test_coriolis\"\n    key: \"cpu_test_coriolis\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/SplitExplicit/test_coriolis.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_KM_saturation_adjustment\"\n    key: \"cpu_KM_saturation_adjustment\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Atmos/Parameterizations/Microphysics/KM_saturation_adjustment.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_KM_warm_rain\"\n    key: \"cpu_KM_warm_rain\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Atmos/Parameterizations/Microphysics/KM_warm_rain.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_haverkamp_test\"\n    key: \"cpu_haverkamp_test\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Land/Model/haverkamp_test.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_soil_params\"\n    key: \"cpu_soil_params\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Land/Model/soil_heterogeneity.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_heat_analytic_unit_test\"\n    key: \"cpu_heat_analytic_unit_test\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Land/Model/heat_analytic_unit_test.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_discrete_hydrostatic_balance\"\n    key: \"cpu_discrete_hydrostatic_balance\"\n    command:\n     - \"mpiexec julia --color=yes --project test/Atmos/Model/discrete_hydrostatic_balance.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_soil_test_bc\"\n    key: \"cpu_soil_test_bc\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Land/Model/test_bc.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_soil_test_bc_3d\"\n    key: \"cpu_soil_test_bc_3d\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Land/Model/test_bc_3d.jl \"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"gpu_varsindex\"\n    key: \"gpu_varsindex\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Arrays/varsindex.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_variable_templates\"\n    key: \"gpu_variable_templates\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Utilities/VariableTemplates/runtests_gpu.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"cpu_land_overland_flow_vcatchment\"\n    key: \"cpu_land_overland_flow_vcatchment\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Land/Model/test_overland_flow_vcatchment.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"gpu_diagnostic_fields_test\"\n    key: \"gpu_diagnostic_fields_test\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Diagnostics/diagnostic_fields_test.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_vars_test\"\n    key: \"gpu_vars_test\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/vars_test.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_custom_filter\"\n    key: \"gpu_custom_filter\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/custom_filter.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_remainder_model\"\n    key: \"gpu_remainder_model\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/remainder_model.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 2\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_isentropicvortex\"\n    key: \"gpu_isentropicvortex\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/isentropicvortex.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_isentropicvortex_imex\"\n    key: \"gpu_isentropicvortex_imex\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/isentropicvortex_imex.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_isentropicvortex_lmars\"\n    key: \"gpu_isentropicvortex_lmars\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/isentropicvortex_lmars.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_isentropicvortex_multirate\"\n    key: \"gpu_isentropicvortex_multirate\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/isentropicvortex_multirate.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_isentropicvortex_mrigark\"\n    key: \"gpu_isentropicvortex_mrigark\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/isentropicvortex_mrigark.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_isentropicvortex_mrigark_implicit\"\n    key: \"gpu_isentropicvortex_mrigark_implicit\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/isentropicvortex_mrigark_implicit.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_acousticwave_1d_imex\"\n    key: \"gpu_acousticwave_1d_imex\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/acousticwave_1d_imex.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_acousticwave_mrigark\"\n    key: \"gpu_acousticwave_mrigark\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/acousticwave_mrigark.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_acousticwave_variable_degree\"\n    key: \"gpu_acousticwave_variable_degree\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/acousticwave_variable_degree.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_fvm_balance\"\n    key: \"gpu_fvm_balance\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/fvm_balance.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_mms_bc_atmos\"\n    key: \"gpu_mms_bc_atmos\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/compressible_Navier_Stokes/mms_bc_atmos.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_mms_bc_dgmodel\"\n    key: \"gpu_mms_bc_dgmodel\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/compressible_Navier_Stokes/mms_bc_dgmodel.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_density_current_model\"\n    key: \"gpu_density_current_model\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/compressible_Navier_Stokes/density_current_model.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_direction_splitting_advection_diffusion\"\n    key: \"gpu_direction_splitting_advection_diffusion\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/direction_splitting_advection_diffusion.jl --fix-rng-seed\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_variable_degree_advection_diffusion\"\n    key: \"gpu_variable_degree_advection_diffusion\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/variable_degree_advection_diffusion.jl\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_sphere\"\n    key: \"gpu_sphere\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/conservation/sphere.jl --fix-rng-seed\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_advection_sphere\"\n    key: \"gpu_advection_sphere\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/advection_sphere.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 2\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_fvm_advection_sphere\"\n    key: \"gpu_fvm_advection_sphere\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/fvm_advection_sphere.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 2\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_fvm_swirl\"\n    key: \"gpu_fvm_swirl\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/fvm_swirl.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 2\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_fvm_advection\"\n    key: \"gpu_fvm_advection\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/fvm_advection.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 2\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_fvm_advection_diffusion\"\n    key: \"gpu_fvm_advection_diffusion\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/fvm_advection_diffusion.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 2\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_fvm_advection_diffusion_periodic\"\n    key: \"gpu_fvm_advection_diffusion_periodic\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/fvm_advection_diffusion_periodic.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 2\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_fvm_isentropicvortex\"\n    key: \"gpu_fvm_isentropicvortex\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/Euler/fvm_isentropicvortex.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 2\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_gcm_driver_test\"\n    key: \"gpu_gcm_driver_test\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Driver/gcm_driver_test.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_poisson\"\n    key: \"gpu_poisson\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/SystemSolvers/poisson.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 2\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_columnwiselu\"\n    key: \"gpu_columnwiselu\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/SystemSolvers/columnwiselu.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_bandedsystem\"\n    key: \"gpu_bandedsystem\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/SystemSolvers/bandedsystem.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"cpu_courant\"\n    key: \"cpu_courant\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/courant.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 2\n\n  - label: \"cpu_brickmesh\"\n    key: \"cpu_brickmesh\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/BrickMesh.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_elements\"\n    key: \"cpu_elements\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/Elements.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_metrics\"\n    key: \"cpu_metrics\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/Metrics.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_grids\"\n    key: \"cpu_grids\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/Grids.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_topology\"\n    key: \"cpu_topology\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/topology.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_grid_integral\"\n    key: \"cpu_grid_integral\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/grid_integral.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_filter\"\n    key: \"cpu_filter\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/filter.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_filter_tmar\"\n    key: \"cpu_filter_tmar\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/filter_TMAR.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"cpu_mpi_centroid\"\n    key: \"cpu_mpi_centroid\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/mpi_centroid.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_mpi_connect_ell\"\n    key: \"cpu_mpi_connect_ell\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/mpi_connect_ell.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 2\n\n  - label: \"cpu_mpi_connect\"\n    key: \"cpu_mpi_connect\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/mpi_connect.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_mpi_connectfull\"\n    key: \"cpu_mpi_connectfull\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/mpi_connectfull.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_mpi_connect_stacked\"\n    key: \"cpu_mpi_connect_stacked\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/mpi_connect_stacked.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_mpi_connect_stacked_3d\"\n    key: \"cpu_mpi_connect_stacked_3d\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/mpi_connect_stacked_3d.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 2\n\n  - label: \"cpu_mpi_getpartition3\"\n    key: \"cpu_mpi_getpartition3\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/mpi_getpartition.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_mpi_partition\"\n    key: \"cpu_mpi_partition\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/mpi_partition.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 3\n\n  - label: \"cpu_mpi_sortcolumns\"\n    key: \"cpu_mpi_sortcolumns\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/mpi_sortcolumns.jl\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"gpu_interpolation\"\n    key: \"gpu_interpolation\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/interpolation.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_dss_mpi\"\n    key: \"gpu_dss_mpi\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/DSS_mpi.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_dss\"\n    key: \"gpu_dss\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/Mesh/DSS.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_GyreDriver\"\n    key: \"gpu_GyreDriver\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/ShallowWater/GyreDriver.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_test_windstress_short\"\n    key: \"gpu_test_windstress_short\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/HydrostaticBoussinesq/test_windstress_short.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_test_ocean_gyre_short\"\n    key: \"gpu_test_ocean_gyre_short\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/HydrostaticBoussinesq/test_ocean_gyre_short.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_test_2D_spindown\"\n    key: \"gpu_test_2D_spindown\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/ShallowWater/test_2D_spindown.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_test_3D_spindown\"\n    key: \"gpu_test_3D_spindown\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/HydrostaticBoussinesq/test_3D_spindown.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_test_vertical_integral_model\"\n    key: \"gpu_test_vertical_integral_model\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/SplitExplicit/test_vertical_integral_model.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_test_spindown_long\"\n    key: \"gpu_test_spindown_long\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/SplitExplicit/test_spindown_long.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_test_restart\"\n    key: \"gpu_test_restart\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/SplitExplicit/test_restart.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_test_coriolis\"\n    key: \"gpu_test_coriolis\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/SplitExplicit/test_coriolis.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_KM_saturation_adjustment\"\n    key: \"gpu_KM_saturation_adjustment\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Atmos/Parameterizations/Microphysics/KM_saturation_adjustment.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_KM_warm_rain\"\n    key: \"gpu_KM_warm_rain\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Atmos/Parameterizations/Microphysics/KM_warm_rain.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_KM_ice\"\n    key: \"gpu_KM_ice\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Atmos/Parameterizations/Microphysics/KM_ice.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_pseudo1D_advection_diffusion\"\n    key: \"gpu_pseudo1D_advection_diffusion\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/pseudo1D_advection_diffusion.jl --integration-testing\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_pseudo1D_advection_diffusion_1dimex\"\n    key: \"gpu_pseudo1D_advection_diffusion_1dimex\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/pseudo1D_advection_diffusion_1dimex.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_pseudo1D_advection_diffusion_mrigark_implicit\"\n    key: \"gpu_pseudo1D_advection_diffusion_mrigark_implicit\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/pseudo1D_advection_diffusion_mrigark_implicit.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_pseudo1D_heat_eqn\"\n    key: \"gpu_pseudo1D_heat_eqn\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/pseudo1D_heat_eqn.jl --integration-testing\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_bickley_jet_2D\"\n    key: \"gpu_bickley_jet_2D\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/compressible_navier_stokes_equations/two_dimensional/test_bickley_jet.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_bickley_jet_3D\"\n    key: \"gpu_bickley_jet_3D\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/compressible_navier_stokes_equations/three_dimensional/test_bickley_jet.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_cnse_buoyancy_3D\"\n    key: \"gpu_cnse_buoyancy_3D\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/compressible_navier_stokes_equations/three_dimensional/test_buoyancy.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_cnse_sphere_3D\"\n    key: \"gpu_cnse_sphere_3D\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/compressible_navier_stokes_equations/sphere/test_sphere.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_cnse_sphere_heat_3D\"\n    key: \"gpu_cnse_sphere_heat_3D\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/compressible_navier_stokes_equations/sphere/test_heat_equation.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_cnse_sphere_balance_3D\"\n    key: \"gpu_cnse_sphere_balance_3D\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/compressible_navier_stokes_equations/sphere/test_hydrostatic_balance.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_periodic_3D_hyperdiffusion\"\n    key: \"gpu_periodic_3D_hyperdiffusion\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/periodic_3D_hyperdiffusion.jl --integration-testing\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_hyperdiffusion_bc\"\n    key: \"gpu_hyperdiffusion_bc\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/hyperdiffusion_bc.jl --integration-testing\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_diffusion_hyperdiffusion_sphere\"\n    key: \"gpu_diffusion_hyperdiffusion_sphere\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/DGMethods/advection_diffusion/diffusion_hyperdiffusion_sphere.jl --integration-testing\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_esdg_baroclinic_wave\"\n    key: \"gpu_esdg_baroclinic_wave\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Numerics/ESDGMethods/DryAtmos/baroclinic_wave.jl\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_dry_rayleigh_benard\"\n    key: \"gpu_dry_rayleigh_benard\"\n    command:\n      - \"mpiexec julia --color=yes --project tutorials/Atmos/dry_rayleigh_benard.jl --fix-rng-seed\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_dry_risingbubble\"\n    key: \"gpu_dry_risingbubble\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/TestCase/risingbubble.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n  \n  - label: \"gpu_dry_risingbubble_fvm\"\n    key: \"gpu_dry_risingbubble_fvm\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/TestCase/risingbubble_fvm.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_moist_risingbubble\"\n    key: \"gpu_moist_risingbubble\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/TestCase/risingbubble.jl --with-moisture \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_solid_body_rotation\"\n    key: \"gpu_solid_body_rotation\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/TestCase/solid_body_rotation.jl --diagnostics=default \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n  \n  - label: \"gpu_solid_body_rotation_fvm\"\n    key: \"gpu_solid_body_rotation_fvm\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/TestCase/solid_body_rotation_fvm.jl --diagnostics=default \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_solid_body_rotation_mountain\"\n    key: \"gpu_solid_body_rotation_mountain\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/TestCase/solid_body_rotation_mountain.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_dry_baroclinic_wave\"\n    key: \"gpu_dry_baroclinic_wave\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/TestCase/baroclinic_wave.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n  \n  - label: \"gpu_dry_baroclinic_wave_fvm\"\n    key: \"gpu_dry_baroclinic_wave_fvm\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/TestCase/baroclinic_wave_fvm.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_moist_baroclinic_wave\"\n    key: \"gpu_moist_baroclinic_wave\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/TestCase/baroclinic_wave.jl --with-moisture \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_heldsuarez\"\n    key: \"gpu_heldsuarez\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosGCM/heldsuarez.jl --diagnostics=default --fix-rng-seed \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_nonhydrostatic_gravity_wave\"\n    key: \"gpu_nonhydrostatic_gravity_wave\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosGCM/nonhydrostatic_gravity_wave.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_isothermal_zonal_flow\"\n    key: \"gpu_isothermal_zonal_flow\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/TestCase/isothermal_zonal_flow.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_surfacebubble\"\n    key: \"gpu_surfacebubble\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/surfacebubble.jl --diagnostics=default\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_dycoms\"\n    key: \"gpu_dycoms\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/dycoms.jl --fix-rng-seed\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_dycoms_with_precip\"\n    key: \"gpu_dycoms_with_precip\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/dycoms.jl --fix-rng-seed --moisture-model nonequilibrium --precipitation-model rain --sim-time 120 --check-asserts yes\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_squall_eq_no_precip\"\n    key: \"gpu_squall_eq_no_precip\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/squall_line.jl --fix-rng-seed --sim-time 600 --check-asserts yes --moisture-model equilibrium --precipitation-model noprecipitation\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_squall_eq_rain\"\n    key: \"gpu_squall_eq_rain\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/squall_line.jl --fix-rng-seed --sim-time 600 --check-asserts yes --moisture-model equilibrium --precipitation-model rain\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_squall_neq_rain_snow\"\n    key: \"gpu_squall_neq_rain_snow\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/squall_line.jl --fix-rng-seed --sim-time 600 --check-asserts yes --moisture-model nonequilibrium --precipitation-model rainsnow\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_bomex_les\"\n    key: \"gpu_bomex_les\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/bomex_les.jl --diagnostics=default --fix-rng-seed\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_sbl_les\"\n    key: \"gpu_sbl_les\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/stable_bl_les.jl --fix-rng-seed\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_cbl_les\"\n    key: \"gpu_cbl_les\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/convective_bl_les.jl --fix-rng-seed\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_cfsite\"\n    key: \"gpu_cfsite\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/cfsite_hadgem2-a_07_amip.jl --fix-rng-seed\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_bomex_single_stack\"\n    key: \"gpu_bomex_single_stack\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/bomex_single_stack.jl --fix-rng-seed\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_bomex_single_stack_nonequil\"\n    key: \"gpu_bomex_single_stack_nonequil\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/bomex_single_stack.jl --moisture-model nonequilibrium --fix-rng-seed\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_bomex_edmf\"\n    key: \"gpu_bomex_edmf\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Atmos/EDMF/bomex_edmf.jl --diagnostics=default --fix-rng-seed\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_ekman\"\n    key: \"gpu_ekman\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Atmos/EDMF/ekman_layer.jl --fix-rng-seed\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_sbl_edmf\"\n    key: \"gpu_sbl_edmf\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Atmos/EDMF/stable_bl_edmf.jl --fix-rng-seed\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"cpu_sbl_edmf_fvm\"\n    key: \"cpu_sbl_edmf_fvm\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Atmos/EDMF/stable_bl_edmf_fvm.jl --fix-rng-seed\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"gpu_sbl_an1d\"\n    key: \"gpu_sbl_an1d\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Atmos/EDMF/stable_bl_anelastic1d.jl --fix-rng-seed\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"cpu_sbl_edmf_coupled\"\n    key: \"cpu_sbl_edmf_coupled\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Atmos/EDMF/stable_bl_coupled_edmf_an1d.jl --fix-rng-seed\"\n    agents:\n      config: cpu\n      queue: central\n      slurm_ntasks: 1\n\n  - label: \"gpu_bomex_bulk_sfc_flux\"\n    key: \"gpu_bomex_bulk_sfc_flux\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/bomex_les.jl --surface-flux=bulk --fix-rng-seed\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_taylor_green\"\n    key: \"gpu_taylor_green\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/taylor_green.jl --diagnostics=default --fix-rng-seed\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_rising_bubble_theta_formulation\"\n    key: \"gpu_rising_bubble_theta_formulation\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/rising_bubble_theta_formulation.jl\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_rising_bubble_bryan_mrrk\"\n    key: \"gpu_rising_bubble_bryan_mrrk\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/rising_bubble_bryan.jl  --fast-method=MultirateRungeKutta \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_rising_bubble_bryan_ark\"\n    key: \"gpu_rising_bubble_bryan_ark\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/rising_bubble_bryan.jl  --fast-method=AdditiveRungeKutta \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_rising_bubble_bryan_mis\"\n    key: \"gpu_rising_bubble_bryan_mis\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/rising_bubble_bryan.jl  --fast-method=MultirateInfinitesimalStep \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_schar_scalar_advection\"\n    key: \"gpu_schar_scalar_advection\"\n    command:\n      - \"mpiexec julia --color=yes --project experiments/AtmosLES/schar_scalar_advection.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_test_windstress_long\"\n    key: \"gpu_test_windstress_long\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/HydrostaticBoussinesq/test_windstress_long.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_test_ocean_gyre_long\"\n    key: \"gpu_test_ocean_gyre_long\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/HydrostaticBoussinesq/test_ocean_gyre_long.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_simple_box_ivd\"\n    key: \"gpu_simple_box_ivd\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/SplitExplicit/simple_box_ivd.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_simple_dbl_gyre\"\n    key: \"gpu_simple_dbl_gyre\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Ocean/SplitExplicit/simple_dbl_gyre.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_discrete_hydrostatic_balance\"\n    key: \"gpu_discrete_hydrostatic_balance\"\n    command:\n     - \"mpiexec julia --color=yes --project test/Atmos/Model/discrete_hydrostatic_balance.jl\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 3\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_haverkamp_test\"\n    key: \"gpu_haverkamp_test\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Land/Model/haverkamp_test.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_soil_params\"\n    key: \"gpu_soil_params\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Land/Model/soil_heterogeneity.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_stable_bl_edmf_implicit_test\"\n    key: \"gpu_stable_bl_edmf_implicit_test\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Atmos/EDMF/stable_bl_single_stack_implicit.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_heat_analytic_unit_test\"\n    key: \"gpu_heat_analytic_unit_test\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Land/Model/heat_analytic_unit_test.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_soil_test_bc\"\n    key: \"gpu_soil_test_bc\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Land/Model/test_bc.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_soil_test_bc_3d\"\n    key: \"gpu_soil_test_bc_3d\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Land/Model/test_bc_3d.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_land_overland_flow_vcatchment\"\n    key: \"gpu_land_overland_flow_vcatchment\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Land/Model/test_overland_flow_vcatchment.jl\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n\n  - label: \"gpu_unittests\"\n    key: \"gpu_unittests\"\n    command:\n      - \"julia --color=yes --project test/runtests_gpu.jl\"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n"
  },
  {
    "path": ".codecov.yml",
    "content": "comment: off\n"
  },
  {
    "path": ".dev/.gitignore",
    "content": "Manifest.toml\n"
  },
  {
    "path": ".dev/Project.toml",
    "content": "[deps]\nJuliaFormatter = \"98e50ef6-434e-11e9-1051-2b60c6c9e899\"\n\n[compat]\nJuliaFormatter = \"0.10\"\n"
  },
  {
    "path": ".dev/clima_formatter_default_image.jl",
    "content": "#!/usr/bin/env julia\n#! format: off\n#\n# Called with no arguments will replace the default system image with one that\n# includes the JuliaFormatter\n#\n# Called with a single argument the system image for the formatter will be\n# placed in the path specified by the argument (relative to the callers path)\n\nusing Pkg\nPkg.add(\"PackageCompiler\")\nusing PackageCompiler\n\nif isfile(PackageCompiler.backup_default_sysimg_path())\n    @error \"\"\"\n    A custom default system image already exists.\n    Either restore default with:\n        julia -e \"using PackageCompiler; PackageCompiler.restore_default_sysimage()\"\n    or use the script\n        $(abspath(joinpath(@__DIR__, \"..\", \".dev\", \"clima_formatter_image.jl\")))\n    which will use a custom path for the system image\n    \"\"\"\n    exit(1)\nend\n\n# If a current Manifest exist for the formatter we remove it so that we have the\n# latest version\nrm(joinpath(@__DIR__, \"Manifest.toml\"); force = true)\n\nPkg.activate(joinpath(@__DIR__))\nPackageCompiler.create_sysimage(\n    :JuliaFormatter;\n    precompile_execution_file = joinpath(@__DIR__, \"precompile.jl\"),\n    replace_default = true,\n)\n"
  },
  {
    "path": ".dev/clima_formatter_image.jl",
    "content": "#!/usr/bin/env julia\n#\n# Called with no arguments will build the formattter system image \n#   PATH_TO_CLIMATEMACHINE/.git/hooks/JuliaFormatterSysimage.so\n#\n# Called with a single argument the system image for the formatter will be\n# placed in the path specified by the argument (relative to the callers path)\n\nsysimage_path = abspath(\n    isempty(ARGS) ?\n    joinpath(@__DIR__, \"..\", \".git\", \"hooks\", \"JuliaFormatterSysimage.so\") :\n    ARGS[1],\n)\n\n@info \"\"\"\nCreating system image object file at:\n    $(sysimage_path)\n\"\"\"\n\nusing Pkg\nPkg.add(\"PackageCompiler\")\nusing PackageCompiler\n\n# If a current Manifest exist for the formatter we remove it so that we have the\n# latest version\nrm(joinpath(@__DIR__, \"Manifest.toml\"); force = true)\n\nPkg.activate(joinpath(@__DIR__))\nPackageCompiler.create_sysimage(\n    :JuliaFormatter;\n    precompile_execution_file = joinpath(@__DIR__, \"precompile.jl\"),\n    sysimage_path = sysimage_path,\n)\n"
  },
  {
    "path": ".dev/clima_formatter_options.jl",
    "content": "clima_formatter_options = (\n    indent = 4,\n    margin = 80,\n    always_for_in = true,\n    whitespace_typedefs = true,\n    whitespace_ops_in_indices = true,\n    remove_extra_newlines = false,\n)\n"
  },
  {
    "path": ".dev/climaformat.jl",
    "content": "#!/usr/bin/env julia\n#\n# This is an adapted version of format.jl from JuliaFormatter with the\n# following license:\n#\n#    MIT License\n#    Copyright (c) 2019 Dominique Luna\n#\n#    Permission is hereby granted, free of charge, to any person obtaining a\n#    copy of this software and associated documentation files (the\n#    \"Software\"), to deal in the Software without restriction, including\n#    without limitation the rights to use, copy, modify, merge, publish,\n#    distribute, sublicense, and/or sell copies of the Software, and to permit\n#    persons to whom the Software is furnished to do so, subject to the\n#    following conditions:\n#\n#    The above copyright notice and this permission notice shall be included\n#    in all copies or substantial portions of the Software.\n#\n#    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n#    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n#    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n#    NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n#    DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n#    OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n#    USE OR OTHER DEALINGS IN THE SOFTWARE.\n#\nusing Pkg\nPkg.activate(@__DIR__)\nPkg.instantiate()\n\nusing JuliaFormatter\n\ninclude(\"clima_formatter_options.jl\")\n\nhelp = \"\"\"\nUsage: climaformat.jl [flags] [FILE/PATH]...\n\nFormats the given julia files using the CLIMA formatting options.  If paths\nare given it will format the julia files in the paths. Otherwise, it will\nformat all changed julia files.\n\n    -v, --verbose\n        Print the name of the files being formatted with relevant details.\n\n    -h, --help\n        Print this message.\n\"\"\"\n\nfunction parse_opts!(args::Vector{String})\n    i = 1\n    opts = Dict{Symbol, Union{Int, Bool}}()\n    while i ≤ length(args)\n        arg = args[i]\n        if arg[1] != '-'\n            i += 1\n            continue\n        end\n        if arg == \"-v\" || arg == \"--verbose\"\n            opt = :verbose\n        elseif arg == \"-h\" || arg == \"--help\"\n            opt = :help\n        else\n            error(\"invalid option $arg\")\n        end\n        if opt in (:verbose, :help)\n            opts[opt] = true\n            deleteat!(args, i)\n        end\n    end\n    return opts\nend\n\nopts = parse_opts!(ARGS)\nif haskey(opts, :help)\n    write(stdout, help)\n    exit(0)\nend\nif isempty(ARGS)\n    filenames = readlines(`git ls-files \"*.jl\"`)\nelse\n    filenames = ARGS\nend\n\nformat(filenames; clima_formatter_options..., opts...)\n"
  },
  {
    "path": ".dev/hooks/pre-commit",
    "content": "#!/usr/bin/env julia\n#\n# Called by git-commit with no arguments.  This checks to make sure that all\n# .jl files are indented correctly before a commit is made.\n#\n# To enable this hook, make this file executable and copy it in\n# $GIT_DIR/hooks.\n\ntoplevel_directory = chomp(read(`git rev-parse --show-toplevel`, String))\n\nusing Pkg\nPkg.activate(joinpath(toplevel_directory, \".dev\"))\nPkg.instantiate()\n\nusing JuliaFormatter\n\ninclude(joinpath(toplevel_directory, \".dev\", \"clima_formatter_options.jl\"))\n\nneeds_format = false\n\nfor diffoutput in split.(readlines(`git diff --name-status --cached`))\n    status = diffoutput[1]\n    filename = diffoutput[end]\n    (!startswith(status, \"D\") && endswith(filename, \".jl\")) || continue\n\n    a = read(`git show :$filename`, String)\n    b = format_text(a; clima_formatter_options...)\n\n    if a != b\n        fullfilename = joinpath(toplevel_directory, filename)\n\n        @error \"\"\"File $filename needs to be indented with:\n            julia $(joinpath(toplevel_directory, \".dev\", \"climaformat.jl\")) $fullfilename\n        and added to the git index via\n            git add $fullfilename\n        \"\"\"\n        global needs_format = true\n    end\nend\n\nexit(needs_format ? 1 : 0)\n"
  },
  {
    "path": ".dev/hooks/pre-commit.sysimage",
    "content": "#!/usr/bin/env -S julia -J.git/hooks/JuliaFormatterSysimage.so\n#\n# Called by git-commit with no arguments.  This checks to make sure that all\n# .jl files are indented correctly before a commit is made.\n#\n# To enable this hook, make this file executable and copy it in\n# $GIT_DIR/hooks.\n\ntoplevel_directory = chomp(read(`git rev-parse --show-toplevel`, String))\n\nusing Pkg\nPkg.activate(joinpath(toplevel_directory, \".dev\"))\nPkg.instantiate()\n\nusing JuliaFormatter\n\ninclude(joinpath(toplevel_directory, \".dev\", \"clima_formatter_options.jl\"))\n\nneeds_format = false\n\nfor diffoutput in split.(readlines(`git diff --name-status --cached`))\n    status = diffoutput[1]\n    filename = diffoutput[end]\n    (!startswith(status, \"D\") && endswith(filename, \".jl\")) || continue\n\n    a = read(`git show :$filename`, String)\n    b = format_text(a; clima_formatter_options...)\n\n    if a != b\n        fullfilename = joinpath(toplevel_directory, filename)\n\n        @error \"\"\"File $filename needs to be indented with:\n            julia -J$(joinpath(toplevel_directory, \".git/hooks\", \"JuliaFormatterSysimage.so\")) $(joinpath(toplevel_directory, \".dev\", \"climaformat.jl\"))  $fullfilename\n        and added to the git index via\n            git add $fullfilename\n        \"\"\"\n        global needs_format = true\n    end\nend\n\nexit(needs_format ? 1 : 0)\n"
  },
  {
    "path": ".dev/precompile.jl",
    "content": "using JuliaFormatter\n\ninclude(\"clima_formatter_options.jl\")\n\nformat(@__FILE__; clima_formatter_options...)\n"
  },
  {
    "path": ".dev/systemimage/climate_machine_image.jl",
    "content": "#!/usr/bin/env julia\n#\n# Called with no arguments will create the system image\n#     ClimateMachine.so\n# in the `@__DIR__` directory.\n#\n# Called with a single argument the system image will be placed in the path\n# specified by the argument (relative to the callers path)\n#\n# Called with a specified systemimg path and `true`, the system image will\n# compile the climate machine package module (useful for CI)\n\nsysimage_path =\n    isempty(ARGS) ? joinpath(@__DIR__, \"ClimateMachine.so\") : abspath(ARGS[1])\n\nclimatemachine_pkg = get(ARGS, 2, \"false\") == \"true\"\n\n@info \"Creating system image object file at: '$(sysimage_path)'\"\n@info \"Building ClimateMachine into system image: $(climatemachine_pkg)\"\n\nstart_time = time()\n\nusing Pkg\nPkg.add(\"PackageCompiler\")\n\nPkg.activate(joinpath(@__DIR__, \"..\", \"..\"))\nPkg.instantiate(verbose = true)\n\npkgs = Symbol[]\nif climatemachine_pkg\n    push!(pkgs, :ClimateMachine)\nelse\n    append!(\n        pkgs,\n        [Symbol(v.name) for v in values(Pkg.dependencies()) if v.is_direct_dep],\n    )\nend\n\n# use package compiler\nusing PackageCompiler\nPackageCompiler.create_sysimage(\n    pkgs,\n    sysimage_path = sysimage_path,\n    precompile_execution_file = joinpath(\n        @__DIR__,\n        \"..\",\n        \"..\",\n        \"test\",\n        \"Numerics\",\n        \"DGMethods\",\n        \"Euler\",\n        \"isentropicvortex.jl\",\n    ),\n)\n\ntot_secs = Int(floor(time() - start_time))\n@info \"Created system image object file at: $(sysimage_path)\"\n@info \"System image build time: $tot_secs sec\"\n"
  },
  {
    "path": ".github/issue_template.md",
    "content": "### Description\n\n<!-- Provide a clear description of the issue including any relevant logs or screenshots. Add `bug`, `enhancement` or other labels as appropriate. -->\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "### Description\n\n<!-- Provide a clear description of the content -->\n\n<!-- Check all the boxes below before taking the PR out of draft -->\n\n- [ ] Code follows the [style guidelines](https://clima.github.io/ClimateMachine.jl/latest/DevDocs/CodeStyle/) OR N/A.\n- [ ] Unit tests are included OR N/A.\n- [ ] Code is exercised in an integration test OR N/A.\n- [ ] Documentation has been added/updated OR N/A.\n"
  },
  {
    "path": ".github/workflows/CompatHelper.yml",
    "content": "name: CompatHelper\n\non:\n  schedule:\n    - cron: '00 * * * *'\n\njobs:\n  CompatHelper:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: julia-actions/setup-julia@latest\n        with:\n          version: 1.5.4\n      - name: Pkg.add(\"CompatHelper\")\n        run: julia -e 'using Pkg; Pkg.add(\"CompatHelper\")'\n      - name: CompatHelper.main()\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: julia -e 'using CompatHelper; CompatHelper.main()'\n"
  },
  {
    "path": ".github/workflows/Coverage.yaml",
    "content": "name: Coverage\n\non:\n  schedule:\n    # * is a special character in YAML so you have to quote this string\n    # Run at 2am every day:\n    - cron:  '0 2 * * *'\n\njobs:\n  coverage:\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v2.2.0\n\n    - name: Set up Julia\n      uses: julia-actions/setup-julia@latest\n      with:\n        version: 1.5.4\n   \n    - name: Test with coverage\n      run: |\n        julia --project -e 'using Pkg; Pkg.instantiate()'\n        julia --project -e 'using Pkg; Pkg.test(coverage=true)'\n\n    - name: Generate coverage file\n      run: julia --project -e 'using Pkg; Pkg.add(\"Coverage\");\n                               using Coverage;\n                               LCOV.writefile(\"coverage-lcov.info\", Codecov.process_folder())'\n      if: success()\n\n    - name: Submit coverage\n      uses: codecov/codecov-action@v1.0.7\n      with:\n        token: ${{secrets.CODECOV_TOKEN}}\n      if: success()"
  },
  {
    "path": ".github/workflows/DocCleanup.yml",
    "content": "name: Doc Preview Cleanup\n\non:\n  pull_request:\n    types: [closed]\n\njobs:\n  doc-preview-cleanup:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout gh-pages branch\n        uses: actions/checkout@v2\n        with:\n          ref: gh-pages\n\n      - name: Delete preview and history\n        run: |\n            git config user.name \"Documenter.jl\"\n            git config user.email \"documenter@juliadocs.github.io\"\n            git rm -rf \"previews/PR$PRNUM\"\n            git commit -m \"delete preview\"\n            git branch gh-pages-new $(echo \"delete history\" | git commit-tree HEAD^{tree})\n        env:\n            PRNUM: ${{ github.event.number }}\n\n      - name: Push changes\n        run: |\n            git push --force origin gh-pages-new:gh-pages\n"
  },
  {
    "path": ".github/workflows/Documenter.yaml",
    "content": "name: Documentation\n\non:\n  pull_request:\n    paths:\n      - 'docs/**'\n      - 'tutorials/**'\n      - 'src/**'\n      - 'Project.toml'\n      - 'Manifest.toml'\n\njobs:\n  docs-build:\n    runs-on: ubuntu-latest\n    timeout-minutes: 90\n    steps:\n      - name: Cancel Previous Runs\n        uses: styfle/cancel-workflow-action@0.4.0\n        with:\n          access_token: ${{ github.token }}\n      - uses: actions/checkout@v2.2.0\n      - name: Install System Dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get -qq install libxt6 libxrender1 libxext6 libgl1-mesa-glx libqt5widgets5 xvfb\n\n      - uses: julia-actions/setup-julia@latest\n        with:\n          version: 1.5.4\n\n      # https://discourse.julialang.org/t/recommendation-cache-julia-artifacts-in-ci-services/35484\n      - name: Cache artifacts\n        uses: actions/cache@v1\n        env:\n          cache-name: cache-artifacts\n        with:\n          path: ~/.julia/artifacts\n          key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}\n          restore-keys: |\n            ${{ runner.os }}-test-${{ env.cache-name }}-\n            ${{ runner.os }}-test-\n            ${{ runner.os }}-\n\n      - name: Install Julia dependencies\n        env:\n          JULIA_PROJECT: \"docs/\"\n        run: |\n          julia --project -e 'using Pkg; Pkg.instantiate()'\n          julia --project=docs/ -e 'using Pkg; Pkg.instantiate()'\n          julia --project=docs/ -e 'using Pkg; Pkg.precompile()'\n      - name: Build and deploy\n        # Run with X virtual frame buffer as GR (default backend for Plots.jl) needs\n        # an X session to run without warnings\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          XDG_RUNTIME_DIR: \"/home/runner\"\n          JULIA_PROJECT: \"docs/\"\n          CLIMATEMACHINE_DOCS_GENERATE_TUTORIALS: \"false\"\n          ClIMATEMACHINE_SETTINGS_DISABLE_GPU: \"true\"\n          CLIMATEMACHINE_SETTINGS_DISABLE_CUSTOM_LOGGER: \"true\"\n          CLIMATEMACHINE_SETTINGS_FIX_RNG_SEED: \"true\"\n        run: xvfb-run -- julia --project=docs/ --color=yes docs/make.jl\n      - name: Help! Documenter Failed\n        run: |\n          cat .github/workflows/doc_build_common_error_messages.md\n        if: failure()\n"
  },
  {
    "path": ".github/workflows/JuliaFormatter.yml",
    "content": "name: JuliaFormatter\n\non:\n  push:\n    branches:\n      - master\n      - trying\n      - staging\n    tags: '*'\n  pull_request:\n\njobs:\n  format:\n    runs-on: ubuntu-latest\n    timeout-minutes: 30\n    steps:\n    - name: Cancel Previous Runs\n      uses: styfle/cancel-workflow-action@0.4.0\n      with:\n        access_token: ${{ github.token }}\n\n    - uses: actions/checkout@v2.2.0\n\n    - uses: dorny/paths-filter@v2.9.1\n      id: filter\n      with:\n        filters: |\n          julia_file_change:\n            - added|modified: '**.jl'\n \n    - uses: julia-actions/setup-julia@latest\n      if: steps.filter.outputs.julia_file_change == 'true'\n      with:\n        version: 1.5.4\n\n    - name: Apply JuliaFormatter\n      if: steps.filter.outputs.julia_file_change == 'true'\n      run: |\n        julia --project=.dev .dev/climaformat.jl .\n\n    - name: Check formatting diff\n      if: steps.filter.outputs.julia_file_change == 'true'\n      run: |\n        git diff --color=always --exit-code\n"
  },
  {
    "path": ".github/workflows/Linux-UnitTests.yml",
    "content": "name: Unit Tests\n\non:\n  pull_request:\n    paths:\n      - 'src/**'\n      - 'test/**'\n      - 'Project.toml'\n      - 'Manifest.toml'\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    timeout-minutes: 60\n    strategy:\n      fail-fast: true\n      matrix:\n          test-modules: [\"Atmos,Common,InputOutput,Utilities\",\n                         \"Driver\",\n                         \"Diagnostics\",\n                         \"Arrays,Numerics/ODESolvers,Numerics/SystemSolvers\",\n                         \"Ocean\",\n                         \"Land\",]\n\n    env:\n      CLIMATEMACHINE_SETTINGS_FIX_RNG_SEED: \"true\"\n\n    steps:\n    - name: Cancel Previous Runs\n      uses: styfle/cancel-workflow-action@0.4.0\n      with:\n        access_token: ${{ github.token }}\n\n    - name: Checkout\n      uses: actions/checkout@v2.2.0\n\n    - name: Set up Julia\n      uses: julia-actions/setup-julia@latest\n      with:\n        version: 1.5.4\n\n    # https://discourse.julialang.org/t/recommendation-cache-julia-artifacts-in-ci-services/35484\n    - name: Cache artifacts\n      uses: actions/cache@v1\n      env:\n        cache-name: cache-artifacts\n      with:\n        path: ~/.julia/artifacts \n        key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}\n        restore-keys: |\n          ${{ runner.os }}-test-${{ env.cache-name }}-\n          ${{ runner.os }}-test-\n          ${{ runner.os }}-\n\n    - name: Install Project Packages\n      run: |\n        julia --project=@. -e 'using Pkg; Pkg.instantiate()'\n        julia --project=@. -e 'using Pkg; Pkg.precompile()'\n\n    - name: Run Unit Tests\n      env:\n        TEST_MODULES: ${{ matrix.test-modules }}\n      run: |\n        julia --project=@. -e 'using Pkg; Pkg.test(test_args=map(String, split(ENV[\"TEST_MODULES\"], \",\")))'\n"
  },
  {
    "path": ".github/workflows/OS-UnitTests.yml",
    "content": "name: OS Unit Tests\n\non:\n  push:\n    branches:\n      - staging\n      - trying\n\njobs:\n  test-os:\n    timeout-minutes: 210\n    strategy:\n      fail-fast: true\n      matrix:\n        os: [ubuntu-latest, windows-latest, macos-latest]\n\n    runs-on: ${{ matrix.os }}\n\n    # Workaround for OSX MPICH issue:\n    # https://github.com/pmodels/mpich/issues/4710\n    env:\n      MPICH_INTERFACE_HOSTNAME: \"localhost\"\n      CLIMATEMACHINE_TEST_RUNMPI_LOCALHOST: \"true\"\n      CLIMATEMACHINE_SETTINGS_FIX_RNG_SEED: \"true\"\n\n    steps:\n    - name: Cancel Previous Runs\n      uses: styfle/cancel-workflow-action@0.4.0\n      with:\n        access_token: ${{ github.token }}\n\n    - name: Checkout\n      uses: actions/checkout@v2.2.0\n    \n    # Setup a filter and only run if src/ test/ folder content changes\n    # or project depedencies\n    - uses: dorny/paths-filter@v2\n      id: filter\n      with:\n        filters: |\n          run_test:\n            - 'src/**'\n            - 'test/**'\n            - 'Project.toml'\n            - 'Manifest.toml'\n\n\n    - name: Set up Julia\n      uses: julia-actions/setup-julia@latest\n      if: steps.filter.outputs.run_test == 'true'\n      with:\n        version: 1.5.4\n\n    # https://discourse.julialang.org/t/recommendation-cache-julia-artifacts-in-ci-services/35484\n    - name: Cache artifacts\n      uses: actions/cache@v1\n      if: steps.filter.outputs.run_test == 'true'\n      env:\n        cache-name: cache-artifacts\n      with:\n        path: ~/.julia/artifacts \n        key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}\n        restore-keys: |\n          ${{ runner.os }}-test-${{ env.cache-name }}-\n          ${{ runner.os }}-test-\n          ${{ runner.os }}-\n\n    - name: Install Project Packages\n      if: steps.filter.outputs.run_test == 'true'\n      run: |\n        julia --project=@. -e 'using Pkg; Pkg.instantiate()'\n\n    - name: Build System Image\n      if: steps.filter.outputs.run_test == 'true'\n      continue-on-error: true\n      run: |\n        julia --project .dev/systemimage/climate_machine_image.jl ClimateMachine.so true\n\n    - name: Run Unit Tests\n      if: steps.filter.outputs.run_test == 'true'\n      run: |\n        julia --project -J ClimateMachine.so -e 'using Pkg; Pkg.test()'\n"
  },
  {
    "path": ".github/workflows/PR-Comment.yml",
    "content": "name: Trigger action on PR comment\non:\n  issue_comment:\n    types: [created]\n\njobs:\n  trigger-doc-build:\n    if: ${{ github.event.issue.pull_request &&\n            ( github.event.comment.author_association == 'OWNER' ||\n              github.event.comment.author_association == 'MEMBER' ||\n              github.event.comment.author_association == 'COLLABORATOR' ) &&\n            contains(github.event.comment.body, '@climabot build docs') }}\n    runs-on: ubuntu-latest\n    steps:\n      - uses: octokit/request-action@v2.0.26\n        id: get_pr # need sha of commit\n        with:\n          route: GET /repos/{repository}/pulls/{pull_number}\n          repository: ${{ github.repository }}\n          pull_number: ${{ github.event.issue.number }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n \n      - name: Trigger Buildkite Pipeline\n        id: buildkite\n        uses: CliMA/buildkite-pipeline-action@master\n        with:\n          access_token: ${{ secrets.BUILDKITE }}\n          pipeline: 'clima/climatemachine-docs'\n          branch: ${{ fromJson(steps.get_pr.outputs.data).head.ref }}\n          commit: ${{ fromJson(steps.get_pr.outputs.data).head.sha }}\n          message: \":github: Triggered by comment on PR #${{ github.event.issue.number }}\"\n          env: '{\"PULL_REQUEST\": ${{ github.event.issue.number }} }'\n          async: true\n\n      - name: Create comment\n        uses: peter-evans/create-or-update-comment@v1\n        with:\n          issue-number: ${{ github.event.issue.number }}\n          body: |\n            Docs build created: ${{ steps.buildkite.outputs.web_url }}\n            Preview link: https://clima.github.io/ClimateMachine.jl/previews/PR${{ github.event.issue.number}}\n        \n"
  },
  {
    "path": ".github/workflows/doc_build_common_error_messages.md",
    "content": "# Documenter common warning/error messages:\n\n## General notes\n - Changing order of API/HowToGuides does not fix unresolved path issues\n\n## no doc found for reference\n```\n┌ Warning: no doc found for reference '[`BatchedGeneralizedMinimalResidual`](@ref)' in src/HowToGuides/Numerics/SystemSolvers/IterativeSolvers.md.\n└ @ Documenter.CrossReferences ~/.julia/packages/Documenter/PLD7m/src/CrossReferences.jl:160\n```\n - Missing entry in ```@docs ``` in API\n\n\n## Reference could not be found\n```\n┌ Warning: reference for 'ClimateMachine.ODESolvers.solve!' could not be found in src\\APIs\\Driver\\index.md.\n└ @ Documenter.CrossReferences C:\\Users\\kawcz\\.julia\\packages\\Documenter\\PLD7m\\src\\CrossReferences.jl:104\n```\n - Missing doc string?\n\n## invalid local link: unresolved path\n```\n┌ Warning: invalid local link: unresolved path in APIs/Atmos/AtmosModel.md\n│   link.text =\n│    1-element Array{Any,1}:\n│     Markdown.Code(\"\", \"FlatOrientation\")\n│   link.url = \"@ref\"\n```\n - Missing entry in ```@docs ``` for FlatOrientation in API OR\n - The \"code\" in the reference must be to actual code and not arbitrary text\n\n## unable to get the binding\n```\n┌ Warning: unable to get the binding for 'ClimateMachine.Atmos.AtmosModel.NoOrientation' in `@docs` block in src/APIs/Atmos/AtmosModel.md:9-14 from expression ':(ClimateMachine.Atmos.AtmosModel.NoOrientation)' in module ClimateMachine\n│ ```@docs\n│ ClimateMachine.Atmos.AtmosModel.NoOrientation\n...\n│ ```\n...\n```\n - `ClimateMachine.Atmos.AtmosModel.NoOrientation` should be `ClimateMachine.Atmos.NoOrientation`\n\n## Other useful tips\n- The syntax is white space sensitive: Do not leave any extra new line between the end of the doc string (denoted by triple double-quotes `\"\"\"`) and the code of the defined method / type / module name that you are describing.\n- In the doc string, indent the method / type / module signature and do not indent the descriptive text.\n- Any method name and the corresponding signature in the doc string have to match 1:1 (be careful of missing/extra exclamation points `!`)\n"
  },
  {
    "path": ".gitignore",
    "content": "# Temporary\n*.DS_Store\n*.swp\n*.jl.cov\n*.jl.*.cov\n*.jl.mem\n*.DS_Store\n*~\nTAGS\n\n# Docs\ndocs/build/\ndocs/site/\ndocs/src/tutorials/\ndocs/src/generated/\ndocs/transient_dir/generated/\n!docs/src/assets/*.png\n!docs/src/assets/*.svg\n\n# Deps\nsrc/**/Manifest.toml\n\n# Data\n*.vtk\n*.dat\n*.vtu\n*.pvtu\n*.nc\n*.jld2\n\n# Figs\n*.png\n*.jpg\n*.jpeg\n*.svg\n\n# Movies\n*.gif\n*.mp4\n\n# Julia System Images\n*.so\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright 2019 Climate Modeling Alliance\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "Manifest.toml",
    "content": "# This file is machine-generated - editing it directly is not advised\n\n[[AbstractFFTs]]\ndeps = [\"LinearAlgebra\"]\ngit-tree-sha1 = \"485ee0867925449198280d4af84bdb46a2a404d0\"\nuuid = \"621f4979-c628-5d54-868e-fcf4e3e8185c\"\nversion = \"1.0.1\"\n\n[[Adapt]]\ndeps = [\"LinearAlgebra\"]\ngit-tree-sha1 = \"ffcfa2d345aaee0ef3d8346a073d5dd03c983ebe\"\nuuid = \"79e6a3ab-5dfb-504d-930d-738a2a938a0e\"\nversion = \"3.2.0\"\n\n[[ArgParse]]\ndeps = [\"Logging\", \"TextWrap\"]\ngit-tree-sha1 = \"e928ca0a49f7b0564044b39108c70c160f03e05a\"\nuuid = \"c7e460c6-2fb9-53a9-8c5b-16f535851c63\"\nversion = \"1.1.2\"\n\n[[ArgTools]]\ngit-tree-sha1 = \"bdf73eec6a88885256f282d48eafcad25d7de494\"\nuuid = \"0dad84c5-d112-42e6-8d28-ef12dabb789f\"\nversion = \"1.1.1\"\n\n[[ArnoldiMethod]]\ndeps = [\"LinearAlgebra\", \"Random\", \"StaticArrays\"]\ngit-tree-sha1 = \"f87e559f87a45bece9c9ed97458d3afe98b1ebb9\"\nuuid = \"ec485272-7323-5ecc-a04f-4719b315124d\"\nversion = \"0.1.0\"\n\n[[ArrayInterface]]\ndeps = [\"IfElse\", \"LinearAlgebra\", \"Requires\", \"SparseArrays\", \"Static\"]\ngit-tree-sha1 = \"ce17bad65d0842b34a15fffc8879a9f68f08a67f\"\nuuid = \"4fba245c-0d91-5ea0-9b3e-6abc04ee57a9\"\nversion = \"3.1.6\"\n\n[[ArrayLayouts]]\ndeps = [\"FillArrays\", \"LinearAlgebra\", \"SparseArrays\"]\ngit-tree-sha1 = \"9aa9647b58147a81f7359eacc7d6249ac3a3e3d4\"\nuuid = \"4c555306-a7a7-4459-81d9-ec55ddd5c99a\"\nversion = \"0.6.4\"\n\n[[ArtifactWrappers]]\ndeps = [\"DocStringExtensions\", \"Downloads\", \"Pkg\"]\ngit-tree-sha1 = \"e9b52e63e3ea81a504412807c9426566e26c232d\"\nuuid = \"a14bc488-3040-4b00-9dc1-f6467924858a\"\nversion = \"0.1.1\"\n\n[[Artifacts]]\ndeps = [\"Pkg\"]\ngit-tree-sha1 = \"c30985d8821e0cd73870b17b0ed0ce6dc44cb744\"\nuuid = \"56f22d72-fd6d-98f1-02f0-08ddc0907c33\"\nversion = \"1.3.0\"\n\n[[BFloat16s]]\ndeps = [\"LinearAlgebra\", \"Test\"]\ngit-tree-sha1 = \"4af69e205efc343068dc8722b8dfec1ade89254a\"\nuuid = \"ab4f0b2a-ad5b-11e8-123f-65d77653426b\"\nversion = \"0.1.0\"\n\n[[Base64]]\nuuid = \"2a0f44e3-6c83-55bd-87e4-b1978d98bd5f\"\n\n[[BenchmarkTools]]\ndeps = [\"JSON\", \"Logging\", \"Printf\", \"Statistics\", \"UUIDs\"]\ngit-tree-sha1 = \"9e62e66db34540a0c919d72172cc2f642ac71260\"\nuuid = \"6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf\"\nversion = \"0.5.0\"\n\n[[CEnum]]\ngit-tree-sha1 = \"215a9aa4a1f23fbd05b92769fdd62559488d70e9\"\nuuid = \"fa961155-64e5-5f13-b03f-caf6b980ea82\"\nversion = \"0.4.1\"\n\n[[CFTime]]\ndeps = [\"Dates\", \"Printf\"]\ngit-tree-sha1 = \"bca6cb6ee746e6485ca4535f6cc29cf3579a0f20\"\nuuid = \"179af706-886a-5703-950a-314cd64e0468\"\nversion = \"0.1.1\"\n\n[[CLIMAParameters]]\ndeps = [\"Test\"]\ngit-tree-sha1 = \"0801216ee1670a1e5280cdb0e5fda60cc4b992ca\"\nuuid = \"6eacf6c3-8458-43b9-ae03-caf5306d3d53\"\nversion = \"0.2.0\"\n\n[[CUDA]]\ndeps = [\"AbstractFFTs\", \"Adapt\", \"BFloat16s\", \"CEnum\", \"CompilerSupportLibraries_jll\", \"DataStructures\", \"ExprTools\", \"GPUArrays\", \"GPUCompiler\", \"LLVM\", \"Libdl\", \"LinearAlgebra\", \"Logging\", \"MacroTools\", \"NNlib\", \"Pkg\", \"Printf\", \"Random\", \"Reexport\", \"Requires\", \"SparseArrays\", \"Statistics\", \"TimerOutputs\"]\ngit-tree-sha1 = \"6ccc73b2d8b671f7a65c92b5f08f81422ebb7547\"\nuuid = \"052768ef-5323-5732-b1bb-66c8b64840ba\"\nversion = \"2.4.1\"\n\n[[Cassette]]\ngit-tree-sha1 = \"742fbff99a2798f02bd37d25087efb5615b5a207\"\nuuid = \"7057c7e9-c182-5462-911a-8362d720325c\"\nversion = \"0.3.5\"\n\n[[ChainRulesCore]]\ndeps = [\"Compat\", \"LinearAlgebra\", \"SparseArrays\"]\ngit-tree-sha1 = \"644c24cd6344348f1c645efab24b707088be526a\"\nuuid = \"d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4\"\nversion = \"0.9.34\"\n\n[[CloudMicrophysics]]\ndeps = [\"CLIMAParameters\", \"DocStringExtensions\", \"SpecialFunctions\", \"Thermodynamics\"]\ngit-tree-sha1 = \"52c85d99a842a1d9ebf6f36e8dd34d55e903b0b5\"\nuuid = \"6a9e3e04-43cd-43ba-94b9-e8782df3c71b\"\nversion = \"0.3.1\"\n\n[[CodecZlib]]\ndeps = [\"TranscodingStreams\", \"Zlib_jll\"]\ngit-tree-sha1 = \"ded953804d019afa9a3f98981d99b33e3db7b6da\"\nuuid = \"944b1d66-785c-5afd-91f1-9de20f533193\"\nversion = \"0.7.0\"\n\n[[Combinatorics]]\ngit-tree-sha1 = \"08c8b6831dc00bfea825826be0bc8336fc369860\"\nuuid = \"861a8166-3701-5b0c-9a16-15d98fcdc6aa\"\nversion = \"1.0.2\"\n\n[[CommonSolve]]\ngit-tree-sha1 = \"68a0743f578349ada8bc911a5cbd5a2ef6ed6d1f\"\nuuid = \"38540f10-b2f7-11e9-35d8-d573e4eb0ff2\"\nversion = \"0.2.0\"\n\n[[CommonSubexpressions]]\ndeps = [\"MacroTools\", \"Test\"]\ngit-tree-sha1 = \"7b8a93dba8af7e3b42fecabf646260105ac373f7\"\nuuid = \"bbf7d656-a473-5ed7-a52c-81e309532950\"\nversion = \"0.3.0\"\n\n[[Compat]]\ndeps = [\"Base64\", \"Dates\", \"DelimitedFiles\", \"Distributed\", \"InteractiveUtils\", \"LibGit2\", \"Libdl\", \"LinearAlgebra\", \"Markdown\", \"Mmap\", \"Pkg\", \"Printf\", \"REPL\", \"Random\", \"SHA\", \"Serialization\", \"SharedArrays\", \"Sockets\", \"SparseArrays\", \"Statistics\", \"Test\", \"UUIDs\", \"Unicode\"]\ngit-tree-sha1 = \"919c7f3151e79ff196add81d7f4e45d91bbf420b\"\nuuid = \"34da2185-b29b-5c13-b0c7-acf172513d20\"\nversion = \"3.25.0\"\n\n[[CompilerSupportLibraries_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"8e695f735fca77e9708e795eda62afdb869cbb70\"\nuuid = \"e66e0078-7015-5450-92f7-15fbd957f2ae\"\nversion = \"0.3.4+0\"\n\n[[ConstructionBase]]\ndeps = [\"LinearAlgebra\"]\ngit-tree-sha1 = \"48920211c95a6da1914a06c44ec94be70e84ffff\"\nuuid = \"187b0558-2788-49d3-abe0-74a17ed4e7c9\"\nversion = \"1.1.0\"\n\n[[Coverage]]\ndeps = [\"CoverageTools\", \"HTTP\", \"JSON\", \"LibGit2\", \"MbedTLS\"]\ngit-tree-sha1 = \"a485b62b1ce53368e3a1a3a8dc0c39a61b9d3d9c\"\nuuid = \"a2441757-f6aa-5fb2-8edb-039e3f45d037\"\nversion = \"1.2.0\"\n\n[[CoverageTools]]\ndeps = [\"LibGit2\"]\ngit-tree-sha1 = \"08b72d2f2154e33dc2aeb1bfcd4a83cb283abd4f\"\nuuid = \"c36e975a-824b-4404-a568-ef97ca766997\"\nversion = \"1.2.2\"\n\n[[Crayons]]\ngit-tree-sha1 = \"3f71217b538d7aaee0b69ab47d9b7724ca8afa0d\"\nuuid = \"a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f\"\nversion = \"4.0.4\"\n\n[[CubedSphere]]\ndeps = [\"Elliptic\", \"Printf\", \"Rotations\", \"TaylorSeries\", \"Test\"]\ngit-tree-sha1 = \"f66fabd1ee5df59a7ba47c7873a6332c19e0c03f\"\nuuid = \"7445602f-e544-4518-8976-18f8e8ae6cdb\"\nversion = \"0.2.0\"\n\n[[DataAPI]]\ngit-tree-sha1 = \"dfb3b7e89e395be1e25c2ad6d7690dc29cc53b1d\"\nuuid = \"9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a\"\nversion = \"1.6.0\"\n\n[[DataStructures]]\ndeps = [\"Compat\", \"InteractiveUtils\", \"OrderedCollections\"]\ngit-tree-sha1 = \"4437b64df1e0adccc3e5d1adbc3ac741095e4677\"\nuuid = \"864edb3b-99cc-5e75-8d2d-829cb0a9cfe8\"\nversion = \"0.18.9\"\n\n[[DataValueInterfaces]]\ngit-tree-sha1 = \"bfc1187b79289637fa0ef6d4436ebdfe6905cbd6\"\nuuid = \"e2d170a0-9d28-54be-80f0-106bbe20a464\"\nversion = \"1.0.0\"\n\n[[Dates]]\ndeps = [\"Printf\"]\nuuid = \"ade2ca70-3891-5945-98fb-dc099432e06a\"\n\n[[DelimitedFiles]]\ndeps = [\"Mmap\"]\nuuid = \"8bb1440f-4735-579b-a4ab-409b98df4dab\"\n\n[[Dierckx]]\ndeps = [\"Dierckx_jll\"]\ngit-tree-sha1 = \"5fefbe52e9a6e55b8f87cb89352d469bd3a3a090\"\nuuid = \"39dd38d3-220a-591b-8e3c-4c3a8c710a94\"\nversion = \"0.5.1\"\n\n[[Dierckx_jll]]\ndeps = [\"CompilerSupportLibraries_jll\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"a580560f526f6fc6973e8bad2b036514a4e3b013\"\nuuid = \"cd4c43a9-7502-52ba-aa6d-59fb2a88580b\"\nversion = \"0.0.1+0\"\n\n[[DiffEqBase]]\ndeps = [\"ArrayInterface\", \"ChainRulesCore\", \"DataStructures\", \"DocStringExtensions\", \"FunctionWrappers\", \"IterativeSolvers\", \"LabelledArrays\", \"LinearAlgebra\", \"Logging\", \"MuladdMacro\", \"NonlinearSolve\", \"Parameters\", \"Printf\", \"RecursiveArrayTools\", \"RecursiveFactorization\", \"Reexport\", \"Requires\", \"SciMLBase\", \"SparseArrays\", \"StaticArrays\", \"Statistics\", \"SuiteSparse\", \"ZygoteRules\"]\ngit-tree-sha1 = \"c2ff625248a0967adff1dc1f79c6a41e2531f081\"\nuuid = \"2b5f629d-d688-5b77-993f-72d75c75574e\"\nversion = \"6.57.8\"\n\n[[DiffResults]]\ndeps = [\"StaticArrays\"]\ngit-tree-sha1 = \"c18e98cba888c6c25d1c3b048e4b3380ca956805\"\nuuid = \"163ba53b-c6d8-5494-b064-1a9d43ac40c5\"\nversion = \"1.0.3\"\n\n[[DiffRules]]\ndeps = [\"NaNMath\", \"Random\", \"SpecialFunctions\"]\ngit-tree-sha1 = \"214c3fcac57755cfda163d91c58893a8723f93e9\"\nuuid = \"b552c78f-8df3-52c6-915a-8e097449b14b\"\nversion = \"1.0.2\"\n\n[[DispatchedTuples]]\ngit-tree-sha1 = \"4ccc236f2e2f6e4a15b093d76184ffded23d211f\"\nuuid = \"508c55e1-51b4-41fd-a5ca-7eb0327d070d\"\nversion = \"0.2.0\"\n\n[[Distances]]\ndeps = [\"LinearAlgebra\", \"Statistics\"]\ngit-tree-sha1 = \"366715149014943abd71aa647a07a43314158b2d\"\nuuid = \"b4f34e82-e78d-54a5-968a-f98e89d6e8f7\"\nversion = \"0.10.2\"\n\n[[Distributed]]\ndeps = [\"Random\", \"Serialization\", \"Sockets\"]\nuuid = \"8ba89e20-285c-5b6f-9357-94700520ee1b\"\n\n[[Distributions]]\ndeps = [\"FillArrays\", \"LinearAlgebra\", \"PDMats\", \"Printf\", \"QuadGK\", \"Random\", \"SparseArrays\", \"SpecialFunctions\", \"Statistics\", \"StatsBase\", \"StatsFuns\"]\ngit-tree-sha1 = \"e64debe8cd174cc52d7dd617ebc5492c6f8b698c\"\nuuid = \"31c24e10-a181-5473-b8eb-7969acd0382f\"\nversion = \"0.24.15\"\n\n[[DocStringExtensions]]\ndeps = [\"LibGit2\", \"Markdown\", \"Pkg\", \"Test\"]\ngit-tree-sha1 = \"50ddf44c53698f5e784bbebb3f4b21c5807401b1\"\nuuid = \"ffbed154-4ef7-542d-bbb7-c09d3a79fcae\"\nversion = \"0.8.3\"\n\n[[DoubleFloats]]\ndeps = [\"GenericSVD\", \"GenericSchur\", \"LinearAlgebra\", \"Polynomials\", \"Printf\", \"Quadmath\", \"Random\", \"Requires\", \"SpecialFunctions\"]\ngit-tree-sha1 = \"d5cb090c9f59e5024e0c94be0714c5de8ff5dc99\"\nuuid = \"497a8b3b-efae-58df-a0af-a86822472b78\"\nversion = \"1.1.18\"\n\n[[Downloads]]\ndeps = [\"ArgTools\", \"LibCURL\", \"NetworkOptions\"]\ngit-tree-sha1 = \"5de8c54d269fd7ab430656c27de73e63eb07a979\"\nuuid = \"f43a241f-c20a-4ad4-852c-f6b1247861c6\"\nversion = \"1.4.0\"\n\n[[Elliptic]]\ngit-tree-sha1 = \"71c79e77221ab3a29918aaf6db4f217b89138608\"\nuuid = \"b305315f-e792-5b7a-8f41-49f472929428\"\nversion = \"1.0.1\"\n\n[[ExponentialUtilities]]\ndeps = [\"LinearAlgebra\", \"Printf\", \"Requires\", \"SparseArrays\"]\ngit-tree-sha1 = \"712cb5af8db62836913970ee035a5fa742986f00\"\nuuid = \"d4d017d3-3776-5f7e-afef-a10c40355c18\"\nversion = \"1.8.1\"\n\n[[ExprTools]]\ngit-tree-sha1 = \"10407a39b87f29d47ebaca8edbc75d7c302ff93e\"\nuuid = \"e2ba6199-217a-4e67-a87a-7c52f15ade04\"\nversion = \"0.1.3\"\n\n[[EzXML]]\ndeps = [\"Printf\", \"XML2_jll\"]\ngit-tree-sha1 = \"0fa3b52a04a4e210aeb1626def9c90df3ae65268\"\nuuid = \"8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615\"\nversion = \"1.1.0\"\n\n[[FFTW]]\ndeps = [\"AbstractFFTs\", \"FFTW_jll\", \"IntelOpenMP_jll\", \"Libdl\", \"LinearAlgebra\", \"MKL_jll\", \"Reexport\"]\ngit-tree-sha1 = \"1b48dbde42f307e48685fa9213d8b9f8c0d87594\"\nuuid = \"7a1cc6ca-52ef-59f5-83cd-3a7055c09341\"\nversion = \"1.3.2\"\n\n[[FFTW_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"3676abafff7e4ff07bbd2c42b3d8201f31653dcc\"\nuuid = \"f5851436-0d7a-5f13-b9de-f02708fd171a\"\nversion = \"3.3.9+8\"\n\n[[FastClosures]]\ngit-tree-sha1 = \"acebe244d53ee1b461970f8910c235b259e772ef\"\nuuid = \"9aa1b823-49e4-5ca5-8b0f-3971ec8bab6a\"\nversion = \"0.3.2\"\n\n[[FileIO]]\ndeps = [\"Pkg\", \"Requires\", \"UUIDs\"]\ngit-tree-sha1 = \"9cdfbf5c0ed88ad0dcdb02544416c8e5a73addef\"\nuuid = \"5789e2e9-d7fb-5bc7-8068-2c6fae9b9549\"\nversion = \"1.6.4\"\n\n[[FillArrays]]\ndeps = [\"LinearAlgebra\", \"Random\", \"SparseArrays\"]\ngit-tree-sha1 = \"31939159aeb8ffad1d4d8ee44d07f8558273120a\"\nuuid = \"1a297f60-69ca-5386-bcde-b61e274b549b\"\nversion = \"0.11.7\"\n\n[[FiniteDiff]]\ndeps = [\"ArrayInterface\", \"LinearAlgebra\", \"Requires\", \"SparseArrays\", \"StaticArrays\"]\ngit-tree-sha1 = \"f6f80c8f934efd49a286bb5315360be66956dfc4\"\nuuid = \"6a86dc24-6348-571c-b903-95158fe2bd41\"\nversion = \"2.8.0\"\n\n[[Formatting]]\ndeps = [\"Printf\"]\ngit-tree-sha1 = \"8339d61043228fdd3eb658d86c926cb282ae72a8\"\nuuid = \"59287772-0a20-5a39-b81b-1366585eb4c0\"\nversion = \"0.4.2\"\n\n[[ForwardDiff]]\ndeps = [\"CommonSubexpressions\", \"DiffResults\", \"DiffRules\", \"NaNMath\", \"Random\", \"SpecialFunctions\", \"StaticArrays\"]\ngit-tree-sha1 = \"c68fb7481b71519d313114dca639b35262ff105f\"\nuuid = \"f6369f11-7733-5829-9624-2563aa707210\"\nversion = \"0.10.17\"\n\n[[FunctionWrappers]]\ngit-tree-sha1 = \"241552bc2209f0fa068b6415b1942cc0aa486bcc\"\nuuid = \"069b7b12-0de2-55c6-9aab-29f3d0a68a2e\"\nversion = \"1.1.2\"\n\n[[Future]]\ndeps = [\"Random\"]\nuuid = \"9fa8497b-333b-5362-9e8d-4d0656e87820\"\n\n[[GPUArrays]]\ndeps = [\"AbstractFFTs\", \"Adapt\", \"LinearAlgebra\", \"Printf\", \"Random\", \"Serialization\"]\ngit-tree-sha1 = \"f99a25fe0313121f2f9627002734c7d63b4dd3bd\"\nuuid = \"0c68f7d7-f131-5f86-a1c3-88cf8149b2d7\"\nversion = \"6.2.0\"\n\n[[GPUCompiler]]\ndeps = [\"DataStructures\", \"InteractiveUtils\", \"LLVM\", \"Libdl\", \"Scratch\", \"Serialization\", \"TimerOutputs\", \"UUIDs\"]\ngit-tree-sha1 = \"c853c810b52a80f9aad79ab109207889e57f41ef\"\nuuid = \"61eb1bfa-7361-4325-ad38-22787b887f55\"\nversion = \"0.8.3\"\n\n[[GaussQuadrature]]\ndeps = [\"SpecialFunctions\"]\ngit-tree-sha1 = \"ce3079d0172eaa258f31c30dec9ae045092447d9\"\nuuid = \"d54b0c1a-921d-58e0-8e36-89d8069c0969\"\nversion = \"0.5.5\"\n\n[[GenericSVD]]\ndeps = [\"LinearAlgebra\"]\ngit-tree-sha1 = \"62909c3eda8a25b5673a367d1ad2392ebb265211\"\nuuid = \"01680d73-4ee2-5a08-a1aa-533608c188bb\"\nversion = \"0.3.0\"\n\n[[GenericSchur]]\ndeps = [\"LinearAlgebra\", \"Printf\"]\ngit-tree-sha1 = \"372e48d7f3ced17fdc888a841bcce77be417ce57\"\nuuid = \"c145ed77-6b09-5dd9-b285-bf645a82121e\"\nversion = \"0.5.0\"\n\n[[HDF5_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"LibCURL_jll\", \"Libdl\", \"OpenSSL_jll\", \"Pkg\", \"Zlib_jll\"]\ngit-tree-sha1 = \"fd83fa0bde42e01952757f01149dd968c06c4dba\"\nuuid = \"0234f1f7-429e-5d53-9886-15a909be8d59\"\nversion = \"1.12.0+1\"\n\n[[HTTP]]\ndeps = [\"Base64\", \"Dates\", \"IniFile\", \"MbedTLS\", \"NetworkOptions\", \"Sockets\", \"URIs\"]\ngit-tree-sha1 = \"c9f380c76d8aaa1fa7ea9cf97bddbc0d5b15adc2\"\nuuid = \"cd3eb016-35fb-5094-929b-558a96fad6f3\"\nversion = \"0.9.5\"\n\n[[Hwloc]]\ndeps = [\"Hwloc_jll\"]\ngit-tree-sha1 = \"ffdcd4272a7cc36442007bca41aa07ca3cc5fda4\"\nuuid = \"0e44f5e4-bd66-52a0-8798-143a42290a1d\"\nversion = \"1.3.0\"\n\n[[Hwloc_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"aac91e34ef4c166e0857e3d6052a3467e5732ceb\"\nuuid = \"e33a78d0-f292-5ffc-b300-72abe9b543c8\"\nversion = \"2.4.1+0\"\n\n[[IOCapture]]\ndeps = [\"Logging\"]\ngit-tree-sha1 = \"1868e4e7ad2f93d8de0904d89368c527b46aa6a1\"\nuuid = \"b5f81e59-6552-4d32-b1f0-c071b021bf89\"\nversion = \"0.2.1\"\n\n[[IfElse]]\ngit-tree-sha1 = \"28e837ff3e7a6c3cdb252ce49fb412c8eb3caeef\"\nuuid = \"615f187c-cbe4-4ef1-ba3b-2fcf58d6d173\"\nversion = \"0.1.0\"\n\n[[Inflate]]\ngit-tree-sha1 = \"f5fc07d4e706b84f72d54eedcc1c13d92fb0871c\"\nuuid = \"d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9\"\nversion = \"0.1.2\"\n\n[[IniFile]]\ndeps = [\"Test\"]\ngit-tree-sha1 = \"098e4d2c533924c921f9f9847274f2ad89e018b8\"\nuuid = \"83e8ac13-25f8-5344-8a64-a9f2b223428f\"\nversion = \"0.5.0\"\n\n[[IntelOpenMP_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"d979e54b71da82f3a65b62553da4fc3d18c9004c\"\nuuid = \"1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0\"\nversion = \"2018.0.3+2\"\n\n[[InteractiveUtils]]\ndeps = [\"Markdown\"]\nuuid = \"b77e0a4c-d291-57a0-90e8-8db25a27a240\"\n\n[[Intervals]]\ndeps = [\"Dates\", \"Printf\", \"RecipesBase\", \"Serialization\", \"TimeZones\"]\ngit-tree-sha1 = \"323a38ed1952d30586d0fe03412cde9399d3618b\"\nuuid = \"d8418881-c3e1-53bb-8760-2df7ec849ed5\"\nversion = \"1.5.0\"\n\n[[IterativeSolvers]]\ndeps = [\"LinearAlgebra\", \"Printf\", \"Random\", \"RecipesBase\", \"SparseArrays\"]\ngit-tree-sha1 = \"6f5ef3206d9dc6510a8b8e2334b96454a2ade590\"\nuuid = \"42fd0dbc-a981-5370-80f2-aaf504508153\"\nversion = \"0.9.0\"\n\n[[IteratorInterfaceExtensions]]\ngit-tree-sha1 = \"a3f24677c21f5bbe9d2a714f95dcd58337fb2856\"\nuuid = \"82899510-4779-5014-852e-03e436cf321d\"\nversion = \"1.0.0\"\n\n[[JLD2]]\ndeps = [\"CodecZlib\", \"DataStructures\", \"MacroTools\", \"Mmap\", \"Pkg\", \"Printf\", \"Requires\", \"UUIDs\"]\ngit-tree-sha1 = \"b8343a7f96591404ade118b3a7014e1a52062465\"\nuuid = \"033835bb-8acc-5ee8-8aae-3f567f8a3819\"\nversion = \"0.4.2\"\n\n[[JLLWrappers]]\ngit-tree-sha1 = \"a431f5f2ca3f4feef3bd7a5e94b8b8d4f2f647a0\"\nuuid = \"692b3bcd-3c85-4b1f-b108-f13ce0eb3210\"\nversion = \"1.2.0\"\n\n[[JSON]]\ndeps = [\"Dates\", \"Mmap\", \"Parsers\", \"Unicode\"]\ngit-tree-sha1 = \"81690084b6198a2e1da36fcfda16eeca9f9f24e4\"\nuuid = \"682c06a0-de6a-54ab-a142-c8b1cf79cde6\"\nversion = \"0.21.1\"\n\n[[KernelAbstractions]]\ndeps = [\"Adapt\", \"CUDA\", \"Cassette\", \"InteractiveUtils\", \"MacroTools\", \"SpecialFunctions\", \"StaticArrays\", \"UUIDs\"]\ngit-tree-sha1 = \"ee7f03c23d874c8353813a44315daf82a1e82046\"\nuuid = \"63c18a36-062a-441e-b654-da1e3ab1ce7c\"\nversion = \"0.5.3\"\n\n[[LLVM]]\ndeps = [\"CEnum\", \"Libdl\", \"Printf\", \"Unicode\"]\ngit-tree-sha1 = \"b616937c31337576360cb9fb872ec7633af7b194\"\nuuid = \"929cbde3-209d-540e-8aea-75f648917ca0\"\nversion = \"3.6.0\"\n\n[[LabelledArrays]]\ndeps = [\"ArrayInterface\", \"LinearAlgebra\", \"MacroTools\", \"StaticArrays\"]\ngit-tree-sha1 = \"5e288800819c323de5897fa6d5a002bdad54baf7\"\nuuid = \"2ee39098-c373-598a-b85f-a56591580800\"\nversion = \"1.5.0\"\n\n[[LambertW]]\ngit-tree-sha1 = \"2d9f4009c486ef676646bca06419ac02061c088e\"\nuuid = \"984bce1d-4616-540c-a9ee-88d1112d94c9\"\nversion = \"0.4.5\"\n\n[[LazyArrays]]\ndeps = [\"ArrayLayouts\", \"FillArrays\", \"LinearAlgebra\", \"MacroTools\", \"MatrixFactorizations\", \"SparseArrays\", \"StaticArrays\"]\ngit-tree-sha1 = \"91abe45baaaf05f855215b3221d02f06f96734e1\"\nuuid = \"5078a376-72f3-5289-bfd5-ec5146d43c02\"\nversion = \"0.20.9\"\n\n[[LazyArtifacts]]\ndeps = [\"Pkg\"]\ngit-tree-sha1 = \"4bb5499a1fc437342ea9ab7e319ede5a457c0968\"\nuuid = \"4af54fe1-eca0-43a8-85a7-787d91b784e3\"\nversion = \"1.3.0\"\n\n[[LibCURL]]\ndeps = [\"LibCURL_jll\", \"MozillaCACerts_jll\"]\ngit-tree-sha1 = \"cdbe7465ab7b52358804713a53c7fe1dac3f8a3f\"\nuuid = \"b27032c2-a3e7-50c8-80cd-2d36dbcbfd21\"\nversion = \"0.6.3\"\n\n[[LibCURL_jll]]\ndeps = [\"LibSSH2_jll\", \"Libdl\", \"MbedTLS_jll\", \"Pkg\", \"Zlib_jll\", \"nghttp2_jll\"]\ngit-tree-sha1 = \"897d962c20031e6012bba7b3dcb7a667170dad17\"\nuuid = \"deac9b47-8bc7-5906-a0fe-35ac56dc84c0\"\nversion = \"7.70.0+2\"\n\n[[LibGit2]]\ndeps = [\"Printf\"]\nuuid = \"76f85450-5226-5b5a-8eaa-529ad045b433\"\n\n[[LibSSH2_jll]]\ndeps = [\"Libdl\", \"MbedTLS_jll\", \"Pkg\"]\ngit-tree-sha1 = \"717705533148132e5466f2924b9a3657b16158e8\"\nuuid = \"29816b5a-b9ab-546f-933c-edad1886dfa8\"\nversion = \"1.9.0+3\"\n\n[[Libdl]]\nuuid = \"8f399da3-3557-5675-b5ff-fb832c97cbdb\"\n\n[[Libiconv_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"cba7b560fcc00f8cd770fa85a498cbc1d63ff618\"\nuuid = \"94ce4f54-9a6c-5748-9c1c-f9c7231a4531\"\nversion = \"1.16.0+8\"\n\n[[LightGraphs]]\ndeps = [\"ArnoldiMethod\", \"DataStructures\", \"Distributed\", \"Inflate\", \"LinearAlgebra\", \"Random\", \"SharedArrays\", \"SimpleTraits\", \"SparseArrays\", \"Statistics\"]\ngit-tree-sha1 = \"432428df5f360964040ed60418dd5601ecd240b6\"\nuuid = \"093fc24a-ae57-5d10-9952-331d41423f4d\"\nversion = \"1.3.5\"\n\n[[LightXML]]\ndeps = [\"Libdl\", \"XML2_jll\"]\ngit-tree-sha1 = \"e129d9391168c677cd4800f5c0abb1ed8cb3794f\"\nuuid = \"9c8b4983-aa76-5018-a973-4c85ecc9e179\"\nversion = \"0.9.0\"\n\n[[LineSearches]]\ndeps = [\"LinearAlgebra\", \"NLSolversBase\", \"NaNMath\", \"Parameters\", \"Printf\"]\ngit-tree-sha1 = \"f27132e551e959b3667d8c93eae90973225032dd\"\nuuid = \"d3d80556-e9d4-5f37-9878-2ab0fcc64255\"\nversion = \"7.1.1\"\n\n[[LinearAlgebra]]\ndeps = [\"Libdl\"]\nuuid = \"37e2e46d-f89d-539d-b4ee-838fcccc9c8e\"\n\n[[Literate]]\ndeps = [\"Base64\", \"IOCapture\", \"JSON\", \"REPL\"]\ngit-tree-sha1 = \"501a1a74a0c825037860d36d87d703e987d39dbc\"\nuuid = \"98b081ad-f1c9-55d3-8b20-4c87d4299306\"\nversion = \"2.8.1\"\n\n[[Logging]]\nuuid = \"56ddb016-857b-54e1-b83d-db4d58db5568\"\n\n[[LoopVectorization]]\ndeps = [\"ArrayInterface\", \"DocStringExtensions\", \"IfElse\", \"LinearAlgebra\", \"OffsetArrays\", \"Requires\", \"SLEEFPirates\", \"ThreadingUtilities\", \"UnPack\", \"VectorizationBase\"]\ngit-tree-sha1 = \"5684e4aafadaf668dce27f12d67df4888fa58181\"\nuuid = \"bdcacae8-1622-11e9-2a5c-532679323890\"\nversion = \"0.11.2\"\n\n[[MKL_jll]]\ndeps = [\"Artifacts\", \"IntelOpenMP_jll\", \"JLLWrappers\", \"LazyArtifacts\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"5455aef09b40e5020e1520f551fa3135040d4ed0\"\nuuid = \"856f044c-d86e-5d09-b602-aeab76dc8ba7\"\nversion = \"2021.1.1+2\"\n\n[[MPI]]\ndeps = [\"Distributed\", \"DocStringExtensions\", \"Libdl\", \"MPICH_jll\", \"MicrosoftMPI_jll\", \"OpenMPI_jll\", \"Pkg\", \"Random\", \"Requires\", \"Serialization\", \"Sockets\"]\ngit-tree-sha1 = \"38d0d0255db2316077f7d5dcf8f40c3940e8d534\"\nuuid = \"da04e1cc-30fd-572f-bb4f-1f8673147195\"\nversion = \"0.17.0\"\n\n[[MPICH_jll]]\ndeps = [\"CompilerSupportLibraries_jll\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"4d37f1e07b4e2a74462eebf9ee48c626d15ffdac\"\nuuid = \"7cb0a576-ebde-5e09-9194-50597f1243b4\"\nversion = \"3.3.2+10\"\n\n[[MacroTools]]\ndeps = [\"Markdown\", \"Random\"]\ngit-tree-sha1 = \"6a8a2a625ab0dea913aba95c11370589e0239ff0\"\nuuid = \"1914dd2f-81c6-5fcd-8719-6d5c9610ff09\"\nversion = \"0.5.6\"\n\n[[Markdown]]\ndeps = [\"Base64\"]\nuuid = \"d6f4376e-aef5-505a-96c1-9c027394607a\"\n\n[[MatrixFactorizations]]\ndeps = [\"ArrayLayouts\", \"LinearAlgebra\", \"Printf\", \"Random\"]\ngit-tree-sha1 = \"4154951579535cfba4d716b96dedd9d0beaefcb9\"\nuuid = \"a3b82374-2e81-5b9e-98ce-41277c0e4c87\"\nversion = \"0.8.2\"\n\n[[MbedTLS]]\ndeps = [\"Dates\", \"MbedTLS_jll\", \"Random\", \"Sockets\"]\ngit-tree-sha1 = \"1c38e51c3d08ef2278062ebceade0e46cefc96fe\"\nuuid = \"739be429-bea8-5141-9913-cc70e7f3736d\"\nversion = \"1.0.3\"\n\n[[MbedTLS_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"0eef589dd1c26a3ac9d753fe1a8bcad63f956fa6\"\nuuid = \"c8ffd9c3-330d-5841-b78e-0817d7145fa1\"\nversion = \"2.16.8+1\"\n\n[[MicrosoftMPI_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"e5c90234b3967684c9c6f87b4a54549b4ce21836\"\nuuid = \"9237b28f-5490-5468-be7b-bb81f5f5e6cf\"\nversion = \"10.1.3+0\"\n\n[[Missings]]\ndeps = [\"DataAPI\"]\ngit-tree-sha1 = \"f8c673ccc215eb50fcadb285f522420e29e69e1c\"\nuuid = \"e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28\"\nversion = \"0.4.5\"\n\n[[Mmap]]\nuuid = \"a63ad114-7e13-5084-954f-fe012c677804\"\n\n[[Mocking]]\ndeps = [\"ExprTools\"]\ngit-tree-sha1 = \"916b850daad0d46b8c71f65f719c49957e9513ed\"\nuuid = \"78c3b35d-d492-501b-9361-3d52fe80e533\"\nversion = \"0.7.1\"\n\n[[MozillaCACerts_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"bbcac5afd9049834366c3b68d792971e3d981799\"\nuuid = \"14a3606d-f60d-562e-9121-12d972cd8159\"\nversion = \"2020.10.14+0\"\n\n[[MuladdMacro]]\ngit-tree-sha1 = \"c6190f9a7fc5d9d5915ab29f2134421b12d24a68\"\nuuid = \"46d2c3a1-f734-5fdb-9937-b9b9aeba4221\"\nversion = \"0.2.2\"\n\n[[NCDatasets]]\ndeps = [\"CFTime\", \"DataStructures\", \"Dates\", \"NetCDF_jll\", \"Printf\"]\ngit-tree-sha1 = \"b71d83c87d80f5c54c55a7a9a3aa42bf931c72aa\"\nuuid = \"85f8d34a-cbdd-5861-8df4-14fed0d494ab\"\nversion = \"0.11.3\"\n\n[[NLSolversBase]]\ndeps = [\"DiffResults\", \"Distributed\", \"FiniteDiff\", \"ForwardDiff\"]\ngit-tree-sha1 = \"50608f411a1e178e0129eab4110bd56efd08816f\"\nuuid = \"d41bc354-129a-5804-8e4c-c37616107c6c\"\nversion = \"7.8.0\"\n\n[[NLsolve]]\ndeps = [\"Distances\", \"LineSearches\", \"LinearAlgebra\", \"NLSolversBase\", \"Printf\", \"Reexport\"]\ngit-tree-sha1 = \"019f12e9a1a7880459d0173c182e6a99365d7ac1\"\nuuid = \"2774e3e8-f4cf-5e23-947b-6d7e65073b56\"\nversion = \"4.5.1\"\n\n[[NNlib]]\ndeps = [\"ChainRulesCore\", \"Compat\", \"LinearAlgebra\", \"Pkg\", \"Requires\", \"Statistics\"]\ngit-tree-sha1 = \"ab1d43fead2ecb9aa5ae460d3d547c2cf8d89461\"\nuuid = \"872c559c-99b0-510c-b3b7-b6c96a88d5cd\"\nversion = \"0.7.17\"\n\n[[NaNMath]]\ngit-tree-sha1 = \"bfe47e760d60b82b66b61d2d44128b62e3a369fb\"\nuuid = \"77ba4419-2d1f-58cd-9bb1-8ffee604a2e3\"\nversion = \"0.3.5\"\n\n[[NetCDF_jll]]\ndeps = [\"Artifacts\", \"HDF5_jll\", \"JLLWrappers\", \"LibCURL_jll\", \"LibSSH2_jll\", \"Libdl\", \"MbedTLS_jll\", \"Pkg\", \"Zlib_jll\", \"nghttp2_jll\"]\ngit-tree-sha1 = \"d5835f95aea3b93965a1a7c06de9aace8cb82d99\"\nuuid = \"7243133f-43d8-5620-bbf4-c2c921802cf3\"\nversion = \"400.701.400+0\"\n\n[[NetworkOptions]]\ngit-tree-sha1 = \"ed3157f48a05543cce9b241e1f2815f7e843d96e\"\nuuid = \"ca575930-c2e3-43a9-ace4-1e988b2c1908\"\nversion = \"1.2.0\"\n\n[[NonlinearSolve]]\ndeps = [\"ArrayInterface\", \"FiniteDiff\", \"ForwardDiff\", \"IterativeSolvers\", \"LinearAlgebra\", \"RecursiveArrayTools\", \"RecursiveFactorization\", \"Reexport\", \"SciMLBase\", \"Setfield\", \"StaticArrays\", \"UnPack\"]\ngit-tree-sha1 = \"ef18e47df4f3917af35be5e5d7f5d97e8a83b0ec\"\nuuid = \"8913a72c-1f9b-4ce2-8d82-65094dcecaec\"\nversion = \"0.3.8\"\n\n[[NonlinearSolvers]]\ndeps = [\"CUDA\", \"DocStringExtensions\", \"ForwardDiff\"]\ngit-tree-sha1 = \"13de2bfa3716485129b59d2fdc1c48904c2cfa15\"\nuuid = \"f4b8ab15-8e73-4e04-9661-b5912071d22b\"\nversion = \"0.1.0\"\n\n[[OffsetArrays]]\ndeps = [\"Adapt\"]\ngit-tree-sha1 = \"b3dfef5f2be7d7eb0e782ba9146a5271ee426e90\"\nuuid = \"6fe1bfb0-de20-5000-8ca7-80f57d26f881\"\nversion = \"1.6.2\"\n\n[[OpenMPI_jll]]\ndeps = [\"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"41b983e26a7ab8c9bf05f7d70c274b817d541b46\"\nuuid = \"fe0851c0-eecd-5654-98d4-656369965a5c\"\nversion = \"4.0.2+2\"\n\n[[OpenSSL_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"71bbbc616a1d710879f5a1021bcba65ffba6ce58\"\nuuid = \"458c3c95-2e84-50aa-8efc-19380b2a3a95\"\nversion = \"1.1.1+6\"\n\n[[OpenSpecFun_jll]]\ndeps = [\"Artifacts\", \"CompilerSupportLibraries_jll\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"9db77584158d0ab52307f8c04f8e7c08ca76b5b3\"\nuuid = \"efe28fd5-8261-553b-a9e1-b2916fc3738e\"\nversion = \"0.5.3+4\"\n\n[[OrderedCollections]]\ngit-tree-sha1 = \"4fa2ba51070ec13fcc7517db714445b4ab986bdf\"\nuuid = \"bac558e1-5e72-5ebc-8fee-abe8a469f55d\"\nversion = \"1.4.0\"\n\n[[OrdinaryDiffEq]]\ndeps = [\"Adapt\", \"ArrayInterface\", \"DataStructures\", \"DiffEqBase\", \"ExponentialUtilities\", \"FastClosures\", \"FiniteDiff\", \"ForwardDiff\", \"LinearAlgebra\", \"Logging\", \"MacroTools\", \"MuladdMacro\", \"NLsolve\", \"RecursiveArrayTools\", \"Reexport\", \"SparseArrays\", \"SparseDiffTools\", \"StaticArrays\", \"UnPack\"]\ngit-tree-sha1 = \"d22a75b8ae5b77543c4e1f8eae1ff01ce1f64453\"\nuuid = \"1dea7af3-3e70-54e6-95c3-0bf5283fa5ed\"\nversion = \"5.52.2\"\n\n[[PDMats]]\ndeps = [\"LinearAlgebra\", \"SparseArrays\", \"SuiteSparse\"]\ngit-tree-sha1 = \"f82a0e71f222199de8e9eb9a09977bd0767d52a0\"\nuuid = \"90014a1f-27ba-587c-ab20-58faa44d9150\"\nversion = \"0.11.0\"\n\n[[PackageCompiler]]\ndeps = [\"Libdl\", \"Pkg\", \"UUIDs\"]\ngit-tree-sha1 = \"d448727c4b86be81b225b738c88d30334fda6779\"\nuuid = \"9b87118b-4619-50d2-8e1e-99f35a4d4d9d\"\nversion = \"1.2.5\"\n\n[[Parameters]]\ndeps = [\"OrderedCollections\", \"UnPack\"]\ngit-tree-sha1 = \"2276ac65f1e236e0a6ea70baff3f62ad4c625345\"\nuuid = \"d96e819e-fc66-5662-9728-84c9c7592b0a\"\nversion = \"0.12.2\"\n\n[[Parsers]]\ndeps = [\"Dates\"]\ngit-tree-sha1 = \"c8abc88faa3f7a3950832ac5d6e690881590d6dc\"\nuuid = \"69de0a69-1ddd-5017-9359-2bf0b02dc9f0\"\nversion = \"1.1.0\"\n\n[[Pkg]]\ndeps = [\"Dates\", \"LibGit2\", \"Libdl\", \"Logging\", \"Markdown\", \"Printf\", \"REPL\", \"Random\", \"SHA\", \"UUIDs\"]\nuuid = \"44cfe95a-1eb2-52ea-b672-e2afdf69b78f\"\n\n[[Polynomials]]\ndeps = [\"Intervals\", \"LinearAlgebra\", \"RecipesBase\"]\ngit-tree-sha1 = \"c6b8b87670b9e765db3001ffe640e0583a5ec317\"\nuuid = \"f27b6e38-b328-58d1-80ce-0feddd5e7a45\"\nversion = \"2.0.3\"\n\n[[PrettyTables]]\ndeps = [\"Crayons\", \"Formatting\", \"Markdown\", \"Reexport\", \"Tables\"]\ngit-tree-sha1 = \"574a6b3ea95f04e8757c0280bb9c29f1a5e35138\"\nuuid = \"08abe8d2-0d0c-5749-adfa-8a2ac140af0d\"\nversion = \"0.11.1\"\n\n[[Printf]]\ndeps = [\"Unicode\"]\nuuid = \"de0858da-6303-5e67-8744-51eddeeeb8d7\"\n\n[[QuadGK]]\ndeps = [\"DataStructures\", \"LinearAlgebra\"]\ngit-tree-sha1 = \"12fbe86da16df6679be7521dfb39fbc861e1dc7b\"\nuuid = \"1fd47b50-473d-5c70-9696-f719f8f3bcdc\"\nversion = \"2.4.1\"\n\n[[Quadmath]]\ndeps = [\"Printf\", \"Random\", \"Requires\"]\ngit-tree-sha1 = \"5a8f74af8eae654086a1d058b4ec94ff192e3de0\"\nuuid = \"be4d8f0f-7fa4-5f49-b795-2f01399ab2dd\"\nversion = \"0.5.5\"\n\n[[REPL]]\ndeps = [\"InteractiveUtils\", \"Markdown\", \"Sockets\"]\nuuid = \"3fa0cd96-eef1-5676-8a61-b3b8758bbffb\"\n\n[[Random]]\ndeps = [\"Serialization\"]\nuuid = \"9a3f8284-a2c9-5f02-9a11-845980a1fd5c\"\n\n[[RecipesBase]]\ngit-tree-sha1 = \"b3fb709f3c97bfc6e948be68beeecb55a0b340ae\"\nuuid = \"3cdcf5f2-1ef4-517c-9805-6587b60abb01\"\nversion = \"1.1.1\"\n\n[[RecursiveArrayTools]]\ndeps = [\"ArrayInterface\", \"LinearAlgebra\", \"RecipesBase\", \"Requires\", \"StaticArrays\", \"Statistics\", \"ZygoteRules\"]\ngit-tree-sha1 = \"271a36e18c8806332b7bd0f57e50fcff0d428b11\"\nuuid = \"731186ca-8d62-57ce-b412-fbd966d074cd\"\nversion = \"2.11.0\"\n\n[[RecursiveFactorization]]\ndeps = [\"LinearAlgebra\", \"LoopVectorization\"]\ngit-tree-sha1 = \"20f0ad1b2760da770d31be71f777740d25807631\"\nuuid = \"f2c3362d-daeb-58d1-803e-2bc74f2840b4\"\nversion = \"0.1.11\"\n\n[[Reexport]]\ngit-tree-sha1 = \"57d8440b0c7d98fc4f889e478e80f268d534c9d5\"\nuuid = \"189a3867-3050-52da-a836-e630ba90ab69\"\nversion = \"1.0.0\"\n\n[[Requires]]\ndeps = [\"UUIDs\"]\ngit-tree-sha1 = \"4036a3bd08ac7e968e27c203d45f5fff15020621\"\nuuid = \"ae029012-a4dd-5104-9daa-d747884805df\"\nversion = \"1.1.3\"\n\n[[Rmath]]\ndeps = [\"Random\", \"Rmath_jll\"]\ngit-tree-sha1 = \"86c5647b565873641538d8f812c04e4c9dbeb370\"\nuuid = \"79098fc4-a85e-5d69-aa6a-4863f24498fa\"\nversion = \"0.6.1\"\n\n[[Rmath_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"1b7bf41258f6c5c9c31df8c1ba34c1fc88674957\"\nuuid = \"f50d1b31-88e8-58de-be2c-1cc44531875f\"\nversion = \"0.2.2+2\"\n\n[[RootSolvers]]\ndeps = [\"DocStringExtensions\", \"ForwardDiff\", \"Test\"]\ngit-tree-sha1 = \"0e5b394adc5c6fb39b3964bce2a259a44cc312d3\"\nuuid = \"7181ea78-2dcb-4de3-ab41-2b8ab5a31e74\"\nversion = \"0.2.0\"\n\n[[Rotations]]\ndeps = [\"LinearAlgebra\", \"StaticArrays\", \"Statistics\"]\ngit-tree-sha1 = \"2ed8d8a16d703f900168822d83699b8c3c1a5cd8\"\nuuid = \"6038ab10-8711-5258-84ad-4b1120ba62dc\"\nversion = \"1.0.2\"\n\n[[SHA]]\nuuid = \"ea8e919c-243c-51af-8825-aaa63cd721ce\"\n\n[[SLEEFPirates]]\ndeps = [\"IfElse\", \"Libdl\", \"VectorizationBase\"]\ngit-tree-sha1 = \"ab6194c92dcf38036cd9513e4ab12cd76a613da1\"\nuuid = \"476501e8-09a2-5ece-8869-fb82de89a1fa\"\nversion = \"0.6.10\"\n\n[[SciMLBase]]\ndeps = [\"ArrayInterface\", \"CommonSolve\", \"Distributed\", \"DocStringExtensions\", \"IteratorInterfaceExtensions\", \"LinearAlgebra\", \"Logging\", \"RecipesBase\", \"RecursiveArrayTools\", \"StaticArrays\", \"Statistics\", \"Tables\", \"TreeViews\"]\ngit-tree-sha1 = \"617d5ade740dc628884b6a33e1b02b9bb950e9b3\"\nuuid = \"0bca4576-84f4-4d90-8ffe-ffa030f20462\"\nversion = \"1.9.1\"\n\n[[Scratch]]\ndeps = [\"Dates\"]\ngit-tree-sha1 = \"ad4b278adb62d185bbcb6864dc24959ab0627bf6\"\nuuid = \"6c6a2e73-6563-6170-7368-637461726353\"\nversion = \"1.0.3\"\n\n[[Serialization]]\nuuid = \"9e88b42a-f829-5b0c-bbe9-9e923198166b\"\n\n[[Setfield]]\ndeps = [\"ConstructionBase\", \"Future\", \"MacroTools\", \"Requires\"]\ngit-tree-sha1 = \"d5640fc570fb1b6c54512f0bd3853866bd298b3e\"\nuuid = \"efcf1570-3423-57d1-acb7-fd33fddbac46\"\nversion = \"0.7.0\"\n\n[[SharedArrays]]\ndeps = [\"Distributed\", \"Mmap\", \"Random\", \"Serialization\"]\nuuid = \"1a1011a3-84de-559e-8e89-a11a2f7dc383\"\n\n[[SimpleTraits]]\ndeps = [\"InteractiveUtils\", \"MacroTools\"]\ngit-tree-sha1 = \"daf7aec3fe3acb2131388f93a4c409b8c7f62226\"\nuuid = \"699a6c99-e7fa-54fc-8d76-47d257e15c1d\"\nversion = \"0.9.3\"\n\n[[Sockets]]\nuuid = \"6462fe0b-24de-5631-8697-dd941f90decc\"\n\n[[SortingAlgorithms]]\ndeps = [\"DataStructures\", \"Random\", \"Test\"]\ngit-tree-sha1 = \"03f5898c9959f8115e30bc7226ada7d0df554ddd\"\nuuid = \"a2af1166-a08f-5f64-846c-94a0d3cef48c\"\nversion = \"0.3.1\"\n\n[[SparseArrays]]\ndeps = [\"LinearAlgebra\", \"Random\"]\nuuid = \"2f01184e-e22b-5df5-ae63-d93ebab69eaf\"\n\n[[SparseDiffTools]]\ndeps = [\"Adapt\", \"ArrayInterface\", \"Compat\", \"DataStructures\", \"FiniteDiff\", \"ForwardDiff\", \"LightGraphs\", \"LinearAlgebra\", \"Requires\", \"SparseArrays\", \"VertexSafeGraphs\"]\ngit-tree-sha1 = \"d05bc362e3fa1b0e2361594a706fc63ffbd140f3\"\nuuid = \"47a9eef4-7e08-11e9-0b38-333d64bd3804\"\nversion = \"1.13.0\"\n\n[[SpecialFunctions]]\ndeps = [\"ChainRulesCore\", \"OpenSpecFun_jll\"]\ngit-tree-sha1 = \"5919936c0e92cff40e57d0ddf0ceb667d42e5902\"\nuuid = \"276daf66-3868-5448-9aa4-cd146d93841b\"\nversion = \"1.3.0\"\n\n[[Static]]\ndeps = [\"IfElse\"]\ngit-tree-sha1 = \"ddec5466a1d2d7e58adf9a427ba69763661aacf6\"\nuuid = \"aedffcd0-7271-4cad-89d0-dc628f76c6d3\"\nversion = \"0.2.4\"\n\n[[StaticArrays]]\ndeps = [\"LinearAlgebra\", \"Random\", \"Statistics\"]\ngit-tree-sha1 = \"9da72ed50e94dbff92036da395275ed114e04d49\"\nuuid = \"90137ffa-7385-5640-81b9-e52037218182\"\nversion = \"1.0.1\"\n\n[[StaticNumbers]]\ndeps = [\"Requires\"]\ngit-tree-sha1 = \"a0df7d5ade3fd0f0e6c93ad63facc05b12c40e6a\"\nuuid = \"c5e4b96a-f99f-5557-8ed2-dc63ef9b5131\"\nversion = \"0.3.3\"\n\n[[Statistics]]\ndeps = [\"LinearAlgebra\", \"SparseArrays\"]\nuuid = \"10745b16-79ce-11e8-11f9-7d13ad32a3b2\"\n\n[[StatsBase]]\ndeps = [\"DataAPI\", \"DataStructures\", \"LinearAlgebra\", \"Missings\", \"Printf\", \"Random\", \"SortingAlgorithms\", \"SparseArrays\", \"Statistics\"]\ngit-tree-sha1 = \"a83fa3021ac4c5a918582ec4721bc0cf70b495a9\"\nuuid = \"2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91\"\nversion = \"0.33.4\"\n\n[[StatsFuns]]\ndeps = [\"Rmath\", \"SpecialFunctions\"]\ngit-tree-sha1 = \"ced55fd4bae008a8ea12508314e725df61f0ba45\"\nuuid = \"4c63d2b9-4356-54db-8cca-17b64c39e42c\"\nversion = \"0.9.7\"\n\n[[SuiteSparse]]\ndeps = [\"Libdl\", \"LinearAlgebra\", \"Serialization\", \"SparseArrays\"]\nuuid = \"4607b0f0-06f3-5cda-b6b1-a6196a1729e9\"\n\n[[SurfaceFluxes]]\ndeps = [\"CLIMAParameters\", \"DocStringExtensions\", \"KernelAbstractions\", \"NonlinearSolvers\", \"StaticArrays\"]\ngit-tree-sha1 = \"0f2685b633ebf751e50842ee3525880f7d043162\"\nuuid = \"49b00bb7-8bd4-4f2b-b78c-51cd0450215f\"\nversion = \"0.1.1\"\n\n[[TableTraits]]\ndeps = [\"IteratorInterfaceExtensions\"]\ngit-tree-sha1 = \"b1ad568ba658d8cbb3b892ed5380a6f3e781a81e\"\nuuid = \"3783bdb8-4a98-5b6b-af9a-565f29a5fe9c\"\nversion = \"1.0.0\"\n\n[[Tables]]\ndeps = [\"DataAPI\", \"DataValueInterfaces\", \"IteratorInterfaceExtensions\", \"LinearAlgebra\", \"TableTraits\", \"Test\"]\ngit-tree-sha1 = \"a9ff3dfec713c6677af435d6a6d65f9744feef67\"\nuuid = \"bd369af6-aec1-5ad0-b16a-f7cc5008161c\"\nversion = \"1.4.1\"\n\n[[TaylorSeries]]\ndeps = [\"InteractiveUtils\", \"LinearAlgebra\", \"Markdown\", \"Requires\", \"SparseArrays\"]\ngit-tree-sha1 = \"66f4d1993bae49eeba21a1634b5f65782585a42c\"\nuuid = \"6aa5eb33-94cf-58f4-a9d0-e4b2c4fc25ea\"\nversion = \"0.10.13\"\n\n[[Test]]\ndeps = [\"Distributed\", \"InteractiveUtils\", \"Logging\", \"Random\"]\nuuid = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n\n[[TextWrap]]\ngit-tree-sha1 = \"9250ef9b01b66667380cf3275b3f7488d0e25faf\"\nuuid = \"b718987f-49a8-5099-9789-dcd902bef87d\"\nversion = \"1.0.1\"\n\n[[Thermodynamics]]\ndeps = [\"CLIMAParameters\", \"DocStringExtensions\", \"ExprTools\", \"KernelAbstractions\", \"Random\", \"RootSolvers\"]\ngit-tree-sha1 = \"c7d73eae235caffdab85778d8b6c371691ad1b3e\"\nuuid = \"b60c26fb-14c3-4610-9d3e-2d17fe7ff00c\"\nversion = \"0.5.1\"\n\n[[ThreadingUtilities]]\ndeps = [\"VectorizationBase\"]\ngit-tree-sha1 = \"e3032c97b183e6e2baf4d2cc4fe60c4292a4a707\"\nuuid = \"8290d209-cae3-49c0-8002-c8c24d57dab5\"\nversion = \"0.2.5\"\n\n[[TimeZones]]\ndeps = [\"Dates\", \"EzXML\", \"Mocking\", \"Pkg\", \"Printf\", \"RecipesBase\", \"Serialization\", \"Unicode\"]\ngit-tree-sha1 = \"4ba8a9579a243400db412b50300cd61d7447e583\"\nuuid = \"f269a46b-ccf7-5d73-abea-4c690281aa53\"\nversion = \"1.5.3\"\n\n[[TimerOutputs]]\ndeps = [\"Printf\"]\ngit-tree-sha1 = \"32cdbe6cd2d214c25a0b88f985c9e0092877c236\"\nuuid = \"a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f\"\nversion = \"0.5.8\"\n\n[[TranscodingStreams]]\ndeps = [\"Random\", \"Test\"]\ngit-tree-sha1 = \"7c53c35547de1c5b9d46a4797cf6d8253807108c\"\nuuid = \"3bb67fe8-82b1-5028-8e26-92a6c54297fa\"\nversion = \"0.9.5\"\n\n[[TreeViews]]\ndeps = [\"Test\"]\ngit-tree-sha1 = \"8d0d7a3fe2f30d6a7f833a5f19f7c7a5b396eae6\"\nuuid = \"a2a6695c-b41b-5b7d-aed9-dbfdeacea5d7\"\nversion = \"0.3.0\"\n\n[[URIs]]\ngit-tree-sha1 = \"7855809b88d7b16e9b029afd17880930626f54a2\"\nuuid = \"5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4\"\nversion = \"1.2.0\"\n\n[[UUIDs]]\ndeps = [\"Random\", \"SHA\"]\nuuid = \"cf7118a7-6976-5b1a-9a39-7adc72f591a4\"\n\n[[UnPack]]\ngit-tree-sha1 = \"387c1f73762231e86e0c9c5443ce3b4a0a9a0c2b\"\nuuid = \"3a884ed6-31ef-47d7-9d2a-63182c4928ed\"\nversion = \"1.0.2\"\n\n[[Unicode]]\nuuid = \"4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5\"\n\n[[VectorizationBase]]\ndeps = [\"ArrayInterface\", \"Hwloc\", \"IfElse\", \"Libdl\", \"LinearAlgebra\"]\ngit-tree-sha1 = \"486842a62c4a1bc23f7c8457d64e683a00d6d0e9\"\nuuid = \"3d5dd08c-fd9d-11e8-17fa-ed2836048c2f\"\nversion = \"0.18.14\"\n\n[[VertexSafeGraphs]]\ndeps = [\"LightGraphs\"]\ngit-tree-sha1 = \"b9b450c99a3ca1cc1c6836f560d8d887bcbe356e\"\nuuid = \"19fa3120-7c27-5ec5-8db8-b0b0aa330d6f\"\nversion = \"0.1.2\"\n\n[[WriteVTK]]\ndeps = [\"Base64\", \"CodecZlib\", \"FillArrays\", \"LightXML\", \"Random\", \"TranscodingStreams\"]\ngit-tree-sha1 = \"37eef911a9c5211e0ae4362dc0477cfe6c537ffa\"\nuuid = \"64499a7a-5c06-52f2-abe2-ccb03c286192\"\nversion = \"1.9.1\"\n\n[[XML2_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Libiconv_jll\", \"Pkg\", \"Zlib_jll\"]\ngit-tree-sha1 = \"be0db24f70aae7e2b89f2f3092e93b8606d659a6\"\nuuid = \"02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a\"\nversion = \"2.9.10+3\"\n\n[[Zlib_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"320228915c8debb12cb434c59057290f0834dbf6\"\nuuid = \"83775a58-1f1d-513f-b197-d71354ab007a\"\nversion = \"1.2.11+18\"\n\n[[ZygoteRules]]\ndeps = [\"MacroTools\"]\ngit-tree-sha1 = \"9e7a1e8ca60b742e508a315c17eef5211e7fbfd7\"\nuuid = \"700de1a5-db45-46bc-99cf-38207098b444\"\nversion = \"0.2.1\"\n\n[[nghttp2_jll]]\ndeps = [\"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"8e2c44ab4d49ad9518f359ed8b62f83ba8beede4\"\nuuid = \"8e850ede-7688-5339-a07c-302acd2aaf8d\"\nversion = \"1.40.0+2\"\n"
  },
  {
    "path": "Project.toml",
    "content": "name = \"ClimateMachine\"\nuuid = \"777c4786-024f-11e9-21a3-85d5d4106250\"\nauthors = [\"Climate Modeling Alliance\"]\nversion = \"0.3.0-DEV\"\n\n[deps]\nAdapt = \"79e6a3ab-5dfb-504d-930d-738a2a938a0e\"\nArgParse = \"c7e460c6-2fb9-53a9-8c5b-16f535851c63\"\nArtifactWrappers = \"a14bc488-3040-4b00-9dc1-f6467924858a\"\nBenchmarkTools = \"6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf\"\nCLIMAParameters = \"6eacf6c3-8458-43b9-ae03-caf5306d3d53\"\nCUDA = \"052768ef-5323-5732-b1bb-66c8b64840ba\"\nCloudMicrophysics = \"6a9e3e04-43cd-43ba-94b9-e8782df3c71b\"\nCombinatorics = \"861a8166-3701-5b0c-9a16-15d98fcdc6aa\"\nCoverage = \"a2441757-f6aa-5fb2-8edb-039e3f45d037\"\nCubedSphere = \"7445602f-e544-4518-8976-18f8e8ae6cdb\"\nDates = \"ade2ca70-3891-5945-98fb-dc099432e06a\"\nDelimitedFiles = \"8bb1440f-4735-579b-a4ab-409b98df4dab\"\nDierckx = \"39dd38d3-220a-591b-8e3c-4c3a8c710a94\"\nDiffEqBase = \"2b5f629d-d688-5b77-993f-72d75c75574e\"\nDispatchedTuples = \"508c55e1-51b4-41fd-a5ca-7eb0327d070d\"\nDistributions = \"31c24e10-a181-5473-b8eb-7969acd0382f\"\nDocStringExtensions = \"ffbed154-4ef7-542d-bbb7-c09d3a79fcae\"\nDoubleFloats = \"497a8b3b-efae-58df-a0af-a86822472b78\"\nDownloads = \"f43a241f-c20a-4ad4-852c-f6b1247861c6\"\nFFTW = \"7a1cc6ca-52ef-59f5-83cd-3a7055c09341\"\nFileIO = \"5789e2e9-d7fb-5bc7-8068-2c6fae9b9549\"\nFormatting = \"59287772-0a20-5a39-b81b-1366585eb4c0\"\nForwardDiff = \"f6369f11-7733-5829-9624-2563aa707210\"\nGaussQuadrature = \"d54b0c1a-921d-58e0-8e36-89d8069c0969\"\nInteractiveUtils = \"b77e0a4c-d291-57a0-90e8-8db25a27a240\"\nJLD2 = \"033835bb-8acc-5ee8-8aae-3f567f8a3819\"\nKernelAbstractions = \"63c18a36-062a-441e-b654-da1e3ab1ce7c\"\nLambertW = \"984bce1d-4616-540c-a9ee-88d1112d94c9\"\nLazyArrays = \"5078a376-72f3-5289-bfd5-ec5146d43c02\"\nLinearAlgebra = \"37e2e46d-f89d-539d-b4ee-838fcccc9c8e\"\nLiterate = \"98b081ad-f1c9-55d3-8b20-4c87d4299306\"\nLogging = \"56ddb016-857b-54e1-b83d-db4d58db5568\"\nMPI = \"da04e1cc-30fd-572f-bb4f-1f8673147195\"\nMacroTools = \"1914dd2f-81c6-5fcd-8719-6d5c9610ff09\"\nNCDatasets = \"85f8d34a-cbdd-5861-8df4-14fed0d494ab\"\nNLsolve = \"2774e3e8-f4cf-5e23-947b-6d7e65073b56\"\nNonlinearSolvers = \"f4b8ab15-8e73-4e04-9661-b5912071d22b\"\nOrderedCollections = \"bac558e1-5e72-5ebc-8fee-abe8a469f55d\"\nOrdinaryDiffEq = \"1dea7af3-3e70-54e6-95c3-0bf5283fa5ed\"\nPackageCompiler = \"9b87118b-4619-50d2-8e1e-99f35a4d4d9d\"\nPkg = \"44cfe95a-1eb2-52ea-b672-e2afdf69b78f\"\nPrettyTables = \"08abe8d2-0d0c-5749-adfa-8a2ac140af0d\"\nPrintf = \"de0858da-6303-5e67-8744-51eddeeeb8d7\"\nRandom = \"9a3f8284-a2c9-5f02-9a11-845980a1fd5c\"\nRootSolvers = \"7181ea78-2dcb-4de3-ab41-2b8ab5a31e74\"\nRotations = \"6038ab10-8711-5258-84ad-4b1120ba62dc\"\nSpecialFunctions = \"276daf66-3868-5448-9aa4-cd146d93841b\"\nStaticArrays = \"90137ffa-7385-5640-81b9-e52037218182\"\nStaticNumbers = \"c5e4b96a-f99f-5557-8ed2-dc63ef9b5131\"\nStatistics = \"10745b16-79ce-11e8-11f9-7d13ad32a3b2\"\nSurfaceFluxes = \"49b00bb7-8bd4-4f2b-b78c-51cd0450215f\"\nThermodynamics = \"b60c26fb-14c3-4610-9d3e-2d17fe7ff00c\"\nUnPack = \"3a884ed6-31ef-47d7-9d2a-63182c4928ed\"\nWriteVTK = \"64499a7a-5c06-52f2-abe2-ccb03c286192\"\n\n[compat]\nAdapt = \"2.0.2, 3.2\"\nArgParse = \"1.1\"\nArtifactWrappers = \"0.1.1\"\nBenchmarkTools = \"0.5\"\nCLIMAParameters = \"0.2\"\nCUDA = \"2.0\"\nCloudMicrophysics = \"0.3\"\nCombinatorics = \"1.0\"\nCoverage = \"1.0\"\nDierckx = \"0.4, 0.5\"\nDiffEqBase = \"6.47\"\nDispatchedTuples = \"0.2\"\nDistributions = \"0.22, 0.23, 0.24\"\nDocStringExtensions = \"0.8\"\nDoubleFloats = \"1.1\"\nFFTW = \"1.2\"\nFileIO = \"1.2\"\nFormatting = \"0.4\"\nForwardDiff = \"0.10\"\nGaussQuadrature = \"0.5\"\nJLD2 = \"0.1, 0.2, 0.3, 0.4\"\nKernelAbstractions = \"0.4.1, 0.5\"\nLambertW = \"0.4\"\nLazyArrays = \"0.15, 0.16, 0.18, 0.19, 0.20\"\nLiterate = \"2.2\"\nMPI = \"0.16, 0.17\"\nNCDatasets = \"0.10, 0.11\"\nNLsolve = \"4.4\"\nNonlinearSolvers = \"0.1\"\nOrderedCollections = \"1.1\"\nOrdinaryDiffEq = \"5.41.0\"\nPackageCompiler = \"1.2\"\nPrettyTables = \"0.9, 0.10, 0.11\"\nRootSolvers = \"0.1, 0.2\"\nSpecialFunctions = \"0.10, 1.0\"\nStaticArrays = \"0.12, 1.0\"\nStaticNumbers = \"0.3.2\"\nSurfaceFluxes = \"0.1\"\nThermodynamics = \"0.5.1\"\nUnPack = \"1.0\"\nWriteVTK = \"1.7\"\njulia = \"1.5\"\n\n[extras]\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n\n[targets]\ntest = [\"Test\"]\n"
  },
  {
    "path": "README.md",
    "content": "# ClimateMachine.jl\n\n***NOTE THAT THIS REPO IS NOT CURRENTLY BEING MAINTAINED. PLEASE SEE THE FOLLOWING REPOSITORIES:***\n\n - [CliMA/ClimaCore.jl](http://github.com/CliMA/ClimaCore.jl)\n - [CliMA/ClimaAtmos.jl](http://github.com/CliMA/ClimaAtmos.jl)\n - [CliMA/Thermodynamics.jl](http://github.com/CliMA/Thermodynamics.jl)\n - [CliMA/CloudMicrophysics.jl](http://github.com/CliMA/CloudMicrophysics.jl)\n - [CliMA/SurfaceFluxes.jl](http://github.com/CliMA/SurfaceFluxes.jl)\n - [CliMA/ClimaLSM.jl](http://github.com/CliMA/ClimaLSM.jl)\n - [CliMA/Oceananigans.jl](http://github.com/CliMA/Oceananigans.jl)\n - [CliMA/ClimaCoupler.jl](http://github.com/CliMA/ClimaCoupler.jl)\n\nThe Climate Machine is a new Earth system model that leverages recent advances in the computational and data sciences to learn directly from a wealth of Earth observations from space and the ground. The Climate Machine will harness more data than ever before, providing a new level of accuracy to predictions of droughts, heat waves, and rainfall extremes.\n\n| **Documentation**    | [![dev][docs-latest-img]][docs-latest-url]       |\n|----------------------|--------------------------------------------------|\n| **Docs Build**       | [![docs build][docs-bld-img]][docs-bld-url]      |\n| **Unit tests**       | [![unit tests][unit-tests-img]][unit-tests-url]  |\n| **Code Coverage**    | [![codecov][codecov-img]][codecov-url]           |\n| **Bors**             | [![Bors enabled][bors-img]][bors-url]            |\n| **DOI**              | [![Zenodo][zenodo-img]][zenodo-url]              |\n\n[docs-bld-img]: https://github.com/CliMA/ClimateMachine.jl/workflows/Documentation/badge.svg\n[docs-bld-url]: https://github.com/CliMA/ClimateMachine.jl/actions?query=workflow%3ADocumentation\n\n[docs-latest-img]: https://img.shields.io/badge/docs-latest-blue.svg\n[docs-latest-url]: https://CliMA.github.io/ClimateMachine.jl/latest/\n\n[unit-tests-img]: https://github.com/CliMA/ClimateMachine.jl/workflows/OS%20Unit%20Tests/badge.svg\n[unit-tests-url]: https://github.com/CliMA/ClimateMachine.jl/actions?query=workflow%3A%22OS+Unit+Tests%22\n\n[codecov-img]: https://codecov.io/gh/CliMA/ClimateMachine.jl/branch/master/graph/badge.svg\n[codecov-url]: https://codecov.io/gh/CliMA/ClimateMachine.jl\n\n[bors-img]: https://bors.tech/images/badge_small.svg\n[bors-url]: https://app.bors.tech/repositories/11521\n\n[zenodo-img]: https://zenodo.org/badge/162166244.svg\n[zenodo-url]: https://zenodo.org/badge/latestdoi/162166244\n\nFor installation instructions and explanations on how to use the Climate Machine, please look at the [Documentation](https://clima.github.io/ClimateMachine.jl/latest/GettingStarted/Installation/).\n"
  },
  {
    "path": "bors.toml",
    "content": "status = [\n  \"test-os (ubuntu-latest)\",\n  \"test-os (windows-latest)\",\n  \"test-os (macos-latest)\",\n  \"buildkite/climatemachine-ci\",\n  \"buildkite/climatemachine-docs\",\n  \"format\"\n]\ndelete_merged_branches = true\ntimeout_sec = 12600\nblock_labels = [ \"do-not-merge-yet\" ]\ncut_body_after = \"<!--\"\nrequired_approvals = 1\n"
  },
  {
    "path": "docs/Manifest.toml",
    "content": "# This file is machine-generated - editing it directly is not advised\n\n[[AbstractFFTs]]\ndeps = [\"LinearAlgebra\"]\ngit-tree-sha1 = \"485ee0867925449198280d4af84bdb46a2a404d0\"\nuuid = \"621f4979-c628-5d54-868e-fcf4e3e8185c\"\nversion = \"1.0.1\"\n\n[[Adapt]]\ndeps = [\"LinearAlgebra\"]\ngit-tree-sha1 = \"ffcfa2d345aaee0ef3d8346a073d5dd03c983ebe\"\nuuid = \"79e6a3ab-5dfb-504d-930d-738a2a938a0e\"\nversion = \"3.2.0\"\n\n[[ArgParse]]\ndeps = [\"Logging\", \"TextWrap\"]\ngit-tree-sha1 = \"e928ca0a49f7b0564044b39108c70c160f03e05a\"\nuuid = \"c7e460c6-2fb9-53a9-8c5b-16f535851c63\"\nversion = \"1.1.2\"\n\n[[ArgTools]]\ngit-tree-sha1 = \"bdf73eec6a88885256f282d48eafcad25d7de494\"\nuuid = \"0dad84c5-d112-42e6-8d28-ef12dabb789f\"\nversion = \"1.1.1\"\n\n[[ArnoldiMethod]]\ndeps = [\"LinearAlgebra\", \"Random\", \"StaticArrays\"]\ngit-tree-sha1 = \"f87e559f87a45bece9c9ed97458d3afe98b1ebb9\"\nuuid = \"ec485272-7323-5ecc-a04f-4719b315124d\"\nversion = \"0.1.0\"\n\n[[ArrayInterface]]\ndeps = [\"IfElse\", \"LinearAlgebra\", \"Requires\", \"SparseArrays\", \"Static\"]\ngit-tree-sha1 = \"ce17bad65d0842b34a15fffc8879a9f68f08a67f\"\nuuid = \"4fba245c-0d91-5ea0-9b3e-6abc04ee57a9\"\nversion = \"3.1.6\"\n\n[[Artifacts]]\ndeps = [\"Pkg\"]\ngit-tree-sha1 = \"c30985d8821e0cd73870b17b0ed0ce6dc44cb744\"\nuuid = \"56f22d72-fd6d-98f1-02f0-08ddc0907c33\"\nversion = \"1.3.0\"\n\n[[Automa]]\ndeps = [\"Printf\", \"ScanByte\", \"TranscodingStreams\"]\ngit-tree-sha1 = \"d50976f217489ce799e366d9561d56a98a30d7fe\"\nuuid = \"67c07d97-cdcb-5c2c-af73-a7f9c32a568b\"\nversion = \"0.8.2\"\n\n[[BFloat16s]]\ndeps = [\"LinearAlgebra\", \"Test\"]\ngit-tree-sha1 = \"4af69e205efc343068dc8722b8dfec1ade89254a\"\nuuid = \"ab4f0b2a-ad5b-11e8-123f-65d77653426b\"\nversion = \"0.1.0\"\n\n[[Base64]]\nuuid = \"2a0f44e3-6c83-55bd-87e4-b1978d98bd5f\"\n\n[[BibInternal]]\ngit-tree-sha1 = \"96bd77118a0998d374ca0e14d79518bb03e72e15\"\nuuid = \"2027ae74-3657-4b95-ae00-e2f7d55c3e64\"\nversion = \"0.2.5\"\n\n[[BibParser]]\ndeps = [\"Automa\", \"BibInternal\", \"DataStructures\"]\ngit-tree-sha1 = \"ca965e4614fe0d674c6c385c69c6dda9de191c6b\"\nuuid = \"13533e5b-e1c2-4e57-8cef-cac5e52f6474\"\nversion = \"0.1.10\"\n\n[[Bibliography]]\ndeps = [\"BibInternal\", \"BibParser\", \"DataStructures\"]\ngit-tree-sha1 = \"f291657f8bc98e6756a2082733c36a3c5b3c5cc0\"\nuuid = \"f1be7e48-bf82-45af-a471-ae754a193061\"\nversion = \"0.2.6\"\n\n[[BinaryProvider]]\ndeps = [\"Libdl\", \"Logging\", \"SHA\"]\ngit-tree-sha1 = \"ecdec412a9abc8db54c0efc5548c64dfce072058\"\nuuid = \"b99e7846-7c00-51b0-8f62-c81ae34c0232\"\nversion = \"0.5.10\"\n\n[[Bzip2_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"c3598e525718abcc440f69cc6d5f60dda0a1b61e\"\nuuid = \"6e34b625-4abd-537c-b88f-471c36dfa7a0\"\nversion = \"1.0.6+5\"\n\n[[CEnum]]\ngit-tree-sha1 = \"215a9aa4a1f23fbd05b92769fdd62559488d70e9\"\nuuid = \"fa961155-64e5-5f13-b03f-caf6b980ea82\"\nversion = \"0.4.1\"\n\n[[CFTime]]\ndeps = [\"Dates\", \"Printf\"]\ngit-tree-sha1 = \"bca6cb6ee746e6485ca4535f6cc29cf3579a0f20\"\nuuid = \"179af706-886a-5703-950a-314cd64e0468\"\nversion = \"0.1.1\"\n\n[[CLIMAParameters]]\ndeps = [\"Test\"]\ngit-tree-sha1 = \"0801216ee1670a1e5280cdb0e5fda60cc4b992ca\"\nuuid = \"6eacf6c3-8458-43b9-ae03-caf5306d3d53\"\nversion = \"0.2.0\"\n\n[[CUDA]]\ndeps = [\"AbstractFFTs\", \"Adapt\", \"BFloat16s\", \"CEnum\", \"CompilerSupportLibraries_jll\", \"DataStructures\", \"ExprTools\", \"GPUArrays\", \"GPUCompiler\", \"LLVM\", \"Libdl\", \"LinearAlgebra\", \"Logging\", \"MacroTools\", \"NNlib\", \"Pkg\", \"Printf\", \"Random\", \"Reexport\", \"Requires\", \"SparseArrays\", \"Statistics\", \"TimerOutputs\"]\ngit-tree-sha1 = \"6ccc73b2d8b671f7a65c92b5f08f81422ebb7547\"\nuuid = \"052768ef-5323-5732-b1bb-66c8b64840ba\"\nversion = \"2.4.1\"\n\n[[Cairo_jll]]\ndeps = [\"Artifacts\", \"Bzip2_jll\", \"Fontconfig_jll\", \"FreeType2_jll\", \"Glib_jll\", \"JLLWrappers\", \"LZO_jll\", \"Libdl\", \"Pixman_jll\", \"Pkg\", \"Xorg_libXext_jll\", \"Xorg_libXrender_jll\", \"Zlib_jll\", \"libpng_jll\"]\ngit-tree-sha1 = \"e2f47f6d8337369411569fd45ae5753ca10394c6\"\nuuid = \"83423d85-b0ee-5818-9007-b63ccbeb887a\"\nversion = \"1.16.0+6\"\n\n[[Cassette]]\ngit-tree-sha1 = \"742fbff99a2798f02bd37d25087efb5615b5a207\"\nuuid = \"7057c7e9-c182-5462-911a-8362d720325c\"\nversion = \"0.3.5\"\n\n[[CategoricalArrays]]\ndeps = [\"DataAPI\", \"Future\", \"JSON\", \"Missings\", \"Printf\", \"Statistics\", \"StructTypes\", \"Unicode\"]\ngit-tree-sha1 = \"dbfddfafb75fae5356e00529ce67454125935945\"\nuuid = \"324d7699-5711-5eae-9e2f-1d82baa6b597\"\nversion = \"0.9.3\"\n\n[[ChainRulesCore]]\ndeps = [\"Compat\", \"LinearAlgebra\", \"SparseArrays\"]\ngit-tree-sha1 = \"644c24cd6344348f1c645efab24b707088be526a\"\nuuid = \"d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4\"\nversion = \"0.9.34\"\n\n[[CodecZlib]]\ndeps = [\"TranscodingStreams\", \"Zlib_jll\"]\ngit-tree-sha1 = \"ded953804d019afa9a3f98981d99b33e3db7b6da\"\nuuid = \"944b1d66-785c-5afd-91f1-9de20f533193\"\nversion = \"0.7.0\"\n\n[[ColorSchemes]]\ndeps = [\"ColorTypes\", \"Colors\", \"FixedPointNumbers\", \"Random\", \"StaticArrays\"]\ngit-tree-sha1 = \"3141757b5832ee7a0386db87997ee5a23ff20f4d\"\nuuid = \"35d6a980-a343-548e-a6ea-1d62b119f2f4\"\nversion = \"3.10.2\"\n\n[[ColorTypes]]\ndeps = [\"FixedPointNumbers\", \"Random\"]\ngit-tree-sha1 = \"32a2b8af383f11cbb65803883837a149d10dfe8a\"\nuuid = \"3da002f7-5984-5a60-b8a6-cbb66c0b333f\"\nversion = \"0.10.12\"\n\n[[Colors]]\ndeps = [\"ColorTypes\", \"FixedPointNumbers\", \"InteractiveUtils\", \"Reexport\"]\ngit-tree-sha1 = \"ac5f2213e56ed8a34a3dd2f681f4df1166b34929\"\nuuid = \"5ae59095-9a9b-59fe-a467-6f913c188581\"\nversion = \"0.12.6\"\n\n[[CommonSolve]]\ngit-tree-sha1 = \"68a0743f578349ada8bc911a5cbd5a2ef6ed6d1f\"\nuuid = \"38540f10-b2f7-11e9-35d8-d573e4eb0ff2\"\nversion = \"0.2.0\"\n\n[[CommonSubexpressions]]\ndeps = [\"MacroTools\", \"Test\"]\ngit-tree-sha1 = \"7b8a93dba8af7e3b42fecabf646260105ac373f7\"\nuuid = \"bbf7d656-a473-5ed7-a52c-81e309532950\"\nversion = \"0.3.0\"\n\n[[Compat]]\ndeps = [\"Base64\", \"Dates\", \"DelimitedFiles\", \"Distributed\", \"InteractiveUtils\", \"LibGit2\", \"Libdl\", \"LinearAlgebra\", \"Markdown\", \"Mmap\", \"Pkg\", \"Printf\", \"REPL\", \"Random\", \"SHA\", \"Serialization\", \"SharedArrays\", \"Sockets\", \"SparseArrays\", \"Statistics\", \"Test\", \"UUIDs\", \"Unicode\"]\ngit-tree-sha1 = \"919c7f3151e79ff196add81d7f4e45d91bbf420b\"\nuuid = \"34da2185-b29b-5c13-b0c7-acf172513d20\"\nversion = \"3.25.0\"\n\n[[CompilerSupportLibraries_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"8e695f735fca77e9708e795eda62afdb869cbb70\"\nuuid = \"e66e0078-7015-5450-92f7-15fbd957f2ae\"\nversion = \"0.3.4+0\"\n\n[[ConstructionBase]]\ndeps = [\"LinearAlgebra\"]\ngit-tree-sha1 = \"48920211c95a6da1914a06c44ec94be70e84ffff\"\nuuid = \"187b0558-2788-49d3-abe0-74a17ed4e7c9\"\nversion = \"1.1.0\"\n\n[[Contour]]\ndeps = [\"StaticArrays\"]\ngit-tree-sha1 = \"9f02045d934dc030edad45944ea80dbd1f0ebea7\"\nuuid = \"d38c429a-6771-53c6-b99e-75d170b6e991\"\nversion = \"0.5.7\"\n\n[[Crayons]]\ngit-tree-sha1 = \"3f71217b538d7aaee0b69ab47d9b7724ca8afa0d\"\nuuid = \"a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f\"\nversion = \"4.0.4\"\n\n[[DataAPI]]\ngit-tree-sha1 = \"dfb3b7e89e395be1e25c2ad6d7690dc29cc53b1d\"\nuuid = \"9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a\"\nversion = \"1.6.0\"\n\n[[DataFrames]]\ndeps = [\"CategoricalArrays\", \"Compat\", \"DataAPI\", \"Future\", \"InvertedIndices\", \"IteratorInterfaceExtensions\", \"LinearAlgebra\", \"Markdown\", \"Missings\", \"PooledArrays\", \"PrettyTables\", \"Printf\", \"REPL\", \"Reexport\", \"SortingAlgorithms\", \"Statistics\", \"TableTraits\", \"Tables\", \"Unicode\"]\ngit-tree-sha1 = \"b0db5579803eabb33f1274ca7ca2f472fdfb7f2a\"\nuuid = \"a93c6f00-e57d-5684-b7b6-d8193f3e46c0\"\nversion = \"0.22.5\"\n\n[[DataStructures]]\ndeps = [\"Compat\", \"InteractiveUtils\", \"OrderedCollections\"]\ngit-tree-sha1 = \"4437b64df1e0adccc3e5d1adbc3ac741095e4677\"\nuuid = \"864edb3b-99cc-5e75-8d2d-829cb0a9cfe8\"\nversion = \"0.18.9\"\n\n[[DataValueInterfaces]]\ngit-tree-sha1 = \"bfc1187b79289637fa0ef6d4436ebdfe6905cbd6\"\nuuid = \"e2d170a0-9d28-54be-80f0-106bbe20a464\"\nversion = \"1.0.0\"\n\n[[Dates]]\ndeps = [\"Printf\"]\nuuid = \"ade2ca70-3891-5945-98fb-dc099432e06a\"\n\n[[DelimitedFiles]]\ndeps = [\"Mmap\"]\nuuid = \"8bb1440f-4735-579b-a4ab-409b98df4dab\"\n\n[[Dierckx]]\ndeps = [\"Dierckx_jll\"]\ngit-tree-sha1 = \"5fefbe52e9a6e55b8f87cb89352d469bd3a3a090\"\nuuid = \"39dd38d3-220a-591b-8e3c-4c3a8c710a94\"\nversion = \"0.5.1\"\n\n[[Dierckx_jll]]\ndeps = [\"CompilerSupportLibraries_jll\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"a580560f526f6fc6973e8bad2b036514a4e3b013\"\nuuid = \"cd4c43a9-7502-52ba-aa6d-59fb2a88580b\"\nversion = \"0.0.1+0\"\n\n[[DiffEqBase]]\ndeps = [\"ArrayInterface\", \"ChainRulesCore\", \"DataStructures\", \"DocStringExtensions\", \"FunctionWrappers\", \"IterativeSolvers\", \"LabelledArrays\", \"LinearAlgebra\", \"Logging\", \"MuladdMacro\", \"NonlinearSolve\", \"Parameters\", \"Printf\", \"RecursiveArrayTools\", \"RecursiveFactorization\", \"Reexport\", \"Requires\", \"SciMLBase\", \"SparseArrays\", \"StaticArrays\", \"Statistics\", \"SuiteSparse\", \"ZygoteRules\"]\ngit-tree-sha1 = \"c2ff625248a0967adff1dc1f79c6a41e2531f081\"\nuuid = \"2b5f629d-d688-5b77-993f-72d75c75574e\"\nversion = \"6.57.8\"\n\n[[DiffResults]]\ndeps = [\"StaticArrays\"]\ngit-tree-sha1 = \"c18e98cba888c6c25d1c3b048e4b3380ca956805\"\nuuid = \"163ba53b-c6d8-5494-b064-1a9d43ac40c5\"\nversion = \"1.0.3\"\n\n[[DiffRules]]\ndeps = [\"NaNMath\", \"Random\", \"SpecialFunctions\"]\ngit-tree-sha1 = \"214c3fcac57755cfda163d91c58893a8723f93e9\"\nuuid = \"b552c78f-8df3-52c6-915a-8e097449b14b\"\nversion = \"1.0.2\"\n\n[[Distances]]\ndeps = [\"LinearAlgebra\", \"Statistics\"]\ngit-tree-sha1 = \"366715149014943abd71aa647a07a43314158b2d\"\nuuid = \"b4f34e82-e78d-54a5-968a-f98e89d6e8f7\"\nversion = \"0.10.2\"\n\n[[Distributed]]\ndeps = [\"Random\", \"Serialization\", \"Sockets\"]\nuuid = \"8ba89e20-285c-5b6f-9357-94700520ee1b\"\n\n[[Distributions]]\ndeps = [\"FillArrays\", \"LinearAlgebra\", \"PDMats\", \"Printf\", \"QuadGK\", \"Random\", \"SparseArrays\", \"SpecialFunctions\", \"Statistics\", \"StatsBase\", \"StatsFuns\"]\ngit-tree-sha1 = \"e64debe8cd174cc52d7dd617ebc5492c6f8b698c\"\nuuid = \"31c24e10-a181-5473-b8eb-7969acd0382f\"\nversion = \"0.24.15\"\n\n[[DocStringExtensions]]\ndeps = [\"LibGit2\", \"Markdown\", \"Pkg\", \"Test\"]\ngit-tree-sha1 = \"50ddf44c53698f5e784bbebb3f4b21c5807401b1\"\nuuid = \"ffbed154-4ef7-542d-bbb7-c09d3a79fcae\"\nversion = \"0.8.3\"\n\n[[Documenter]]\ndeps = [\"Base64\", \"Dates\", \"DocStringExtensions\", \"IOCapture\", \"InteractiveUtils\", \"JSON\", \"LibGit2\", \"Logging\", \"Markdown\", \"REPL\", \"Test\", \"Unicode\"]\ngit-tree-sha1 = \"3ebb967819b284dc1e3c0422229b58a40a255649\"\nuuid = \"e30172f5-a6a5-5a46-863b-614d45cd2de4\"\nversion = \"0.26.3\"\n\n[[DocumenterCitations]]\ndeps = [\"Bibliography\", \"Documenter\", \"Markdown\", \"Unicode\"]\ngit-tree-sha1 = \"e8f1cf4d6c23d1fac65faca1932940d9edb4602e\"\nuuid = \"daee34ce-89f3-4625-b898-19384cb65244\"\nversion = \"0.2.1\"\n\n[[DocumenterTools]]\ndeps = [\"Base64\", \"DocStringExtensions\", \"Documenter\", \"FileWatching\", \"LibGit2\", \"Sass\"]\ngit-tree-sha1 = \"9b40fd93f54ba5ef9d364981124a8ed389fd634e\"\nuuid = \"35a29f4d-8980-5a13-9543-d66fff28ecb8\"\nversion = \"0.1.9\"\n\n[[DoubleFloats]]\ndeps = [\"GenericSVD\", \"GenericSchur\", \"LinearAlgebra\", \"Polynomials\", \"Printf\", \"Quadmath\", \"Random\", \"Requires\", \"SpecialFunctions\"]\ngit-tree-sha1 = \"d5cb090c9f59e5024e0c94be0714c5de8ff5dc99\"\nuuid = \"497a8b3b-efae-58df-a0af-a86822472b78\"\nversion = \"1.1.18\"\n\n[[Downloads]]\ndeps = [\"ArgTools\", \"LibCURL\", \"NetworkOptions\"]\ngit-tree-sha1 = \"5de8c54d269fd7ab430656c27de73e63eb07a979\"\nuuid = \"f43a241f-c20a-4ad4-852c-f6b1247861c6\"\nversion = \"1.4.0\"\n\n[[EarCut_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"92d8f9f208637e8d2d28c664051a00569c01493d\"\nuuid = \"5ae413db-bbd1-5e63-b57d-d24a61df00f5\"\nversion = \"2.1.5+1\"\n\n[[Expat_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"1402e52fcda25064f51c77a9655ce8680b76acf0\"\nuuid = \"2e619515-83b5-522b-bb60-26c02a35a201\"\nversion = \"2.2.7+6\"\n\n[[ExponentialUtilities]]\ndeps = [\"LinearAlgebra\", \"Printf\", \"Requires\", \"SparseArrays\"]\ngit-tree-sha1 = \"712cb5af8db62836913970ee035a5fa742986f00\"\nuuid = \"d4d017d3-3776-5f7e-afef-a10c40355c18\"\nversion = \"1.8.1\"\n\n[[ExprTools]]\ngit-tree-sha1 = \"10407a39b87f29d47ebaca8edbc75d7c302ff93e\"\nuuid = \"e2ba6199-217a-4e67-a87a-7c52f15ade04\"\nversion = \"0.1.3\"\n\n[[EzXML]]\ndeps = [\"Printf\", \"XML2_jll\"]\ngit-tree-sha1 = \"0fa3b52a04a4e210aeb1626def9c90df3ae65268\"\nuuid = \"8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615\"\nversion = \"1.1.0\"\n\n[[FFMPEG]]\ndeps = [\"FFMPEG_jll\", \"x264_jll\"]\ngit-tree-sha1 = \"9a73ffdc375be61b0e4516d83d880b265366fe1f\"\nuuid = \"c87230d0-a227-11e9-1b43-d7ebe4e7570a\"\nversion = \"0.4.0\"\n\n[[FFMPEG_jll]]\ndeps = [\"Artifacts\", \"Bzip2_jll\", \"FreeType2_jll\", \"FriBidi_jll\", \"JLLWrappers\", \"LAME_jll\", \"LibVPX_jll\", \"Libdl\", \"Ogg_jll\", \"OpenSSL_jll\", \"Opus_jll\", \"Pkg\", \"Zlib_jll\", \"libass_jll\", \"libfdk_aac_jll\", \"libvorbis_jll\", \"x264_jll\", \"x265_jll\"]\ngit-tree-sha1 = \"3cc57ad0a213808473eafef4845a74766242e05f\"\nuuid = \"b22a6f82-2f65-5046-a5b2-351ab43fb4e5\"\nversion = \"4.3.1+4\"\n\n[[FastClosures]]\ngit-tree-sha1 = \"acebe244d53ee1b461970f8910c235b259e772ef\"\nuuid = \"9aa1b823-49e4-5ca5-8b0f-3971ec8bab6a\"\nversion = \"0.3.2\"\n\n[[FileIO]]\ndeps = [\"Pkg\", \"Requires\", \"UUIDs\"]\ngit-tree-sha1 = \"9cdfbf5c0ed88ad0dcdb02544416c8e5a73addef\"\nuuid = \"5789e2e9-d7fb-5bc7-8068-2c6fae9b9549\"\nversion = \"1.6.4\"\n\n[[FileWatching]]\nuuid = \"7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee\"\n\n[[FillArrays]]\ndeps = [\"LinearAlgebra\", \"Random\", \"SparseArrays\"]\ngit-tree-sha1 = \"31939159aeb8ffad1d4d8ee44d07f8558273120a\"\nuuid = \"1a297f60-69ca-5386-bcde-b61e274b549b\"\nversion = \"0.11.7\"\n\n[[FiniteDiff]]\ndeps = [\"ArrayInterface\", \"LinearAlgebra\", \"Requires\", \"SparseArrays\", \"StaticArrays\"]\ngit-tree-sha1 = \"f6f80c8f934efd49a286bb5315360be66956dfc4\"\nuuid = \"6a86dc24-6348-571c-b903-95158fe2bd41\"\nversion = \"2.8.0\"\n\n[[FixedPointNumbers]]\ndeps = [\"Statistics\"]\ngit-tree-sha1 = \"335bfdceacc84c5cdf16aadc768aa5ddfc5383cc\"\nuuid = \"53c48c17-4a7d-5ca2-90c5-79b7896eea93\"\nversion = \"0.8.4\"\n\n[[Fontconfig_jll]]\ndeps = [\"Artifacts\", \"Bzip2_jll\", \"Expat_jll\", \"FreeType2_jll\", \"JLLWrappers\", \"Libdl\", \"Libuuid_jll\", \"Pkg\", \"Zlib_jll\"]\ngit-tree-sha1 = \"35895cf184ceaab11fd778b4590144034a167a2f\"\nuuid = \"a3f928ae-7b40-5064-980b-68af3947d34b\"\nversion = \"2.13.1+14\"\n\n[[Formatting]]\ndeps = [\"Printf\"]\ngit-tree-sha1 = \"8339d61043228fdd3eb658d86c926cb282ae72a8\"\nuuid = \"59287772-0a20-5a39-b81b-1366585eb4c0\"\nversion = \"0.4.2\"\n\n[[ForwardDiff]]\ndeps = [\"CommonSubexpressions\", \"DiffResults\", \"DiffRules\", \"NaNMath\", \"Random\", \"SpecialFunctions\", \"StaticArrays\"]\ngit-tree-sha1 = \"c68fb7481b71519d313114dca639b35262ff105f\"\nuuid = \"f6369f11-7733-5829-9624-2563aa707210\"\nversion = \"0.10.17\"\n\n[[FreeType2_jll]]\ndeps = [\"Artifacts\", \"Bzip2_jll\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Zlib_jll\"]\ngit-tree-sha1 = \"cbd58c9deb1d304f5a245a0b7eb841a2560cfec6\"\nuuid = \"d7e528f0-a631-5988-bf34-fe36492bcfd7\"\nversion = \"2.10.1+5\"\n\n[[FriBidi_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"0d20aed5b14dd4c9a2453c1b601d08e1149679cc\"\nuuid = \"559328eb-81f9-559d-9380-de523a88c83c\"\nversion = \"1.0.5+6\"\n\n[[FunctionWrappers]]\ngit-tree-sha1 = \"241552bc2209f0fa068b6415b1942cc0aa486bcc\"\nuuid = \"069b7b12-0de2-55c6-9aab-29f3d0a68a2e\"\nversion = \"1.1.2\"\n\n[[Future]]\ndeps = [\"Random\"]\nuuid = \"9fa8497b-333b-5362-9e8d-4d0656e87820\"\n\n[[GLFW_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Libglvnd_jll\", \"Pkg\", \"Xorg_libXcursor_jll\", \"Xorg_libXi_jll\", \"Xorg_libXinerama_jll\", \"Xorg_libXrandr_jll\"]\ngit-tree-sha1 = \"bd1dbf065d7a4a0bdf7e74dd26cf932dda22b929\"\nuuid = \"0656b61e-2033-5cc2-a64a-77c0f6c09b89\"\nversion = \"3.3.3+0\"\n\n[[GPUArrays]]\ndeps = [\"AbstractFFTs\", \"Adapt\", \"LinearAlgebra\", \"Printf\", \"Random\", \"Serialization\"]\ngit-tree-sha1 = \"f99a25fe0313121f2f9627002734c7d63b4dd3bd\"\nuuid = \"0c68f7d7-f131-5f86-a1c3-88cf8149b2d7\"\nversion = \"6.2.0\"\n\n[[GPUCompiler]]\ndeps = [\"DataStructures\", \"InteractiveUtils\", \"LLVM\", \"Libdl\", \"Scratch\", \"Serialization\", \"TimerOutputs\", \"UUIDs\"]\ngit-tree-sha1 = \"c853c810b52a80f9aad79ab109207889e57f41ef\"\nuuid = \"61eb1bfa-7361-4325-ad38-22787b887f55\"\nversion = \"0.8.3\"\n\n[[GR]]\ndeps = [\"Base64\", \"DelimitedFiles\", \"GR_jll\", \"HTTP\", \"JSON\", \"LinearAlgebra\", \"Pkg\", \"Printf\", \"Random\", \"Serialization\", \"Sockets\", \"Test\", \"UUIDs\"]\ngit-tree-sha1 = \"12d971c928b7ecf19b748a2c7df6a365690dbf2c\"\nuuid = \"28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71\"\nversion = \"0.55.0\"\n\n[[GR_jll]]\ndeps = [\"Artifacts\", \"Bzip2_jll\", \"Cairo_jll\", \"FFMPEG_jll\", \"Fontconfig_jll\", \"GLFW_jll\", \"JLLWrappers\", \"JpegTurbo_jll\", \"Libdl\", \"Libtiff_jll\", \"Pixman_jll\", \"Pkg\", \"Qt_jll\", \"Zlib_jll\", \"libpng_jll\"]\ngit-tree-sha1 = \"16df4fd019bf1245bfe97adbae7f2b7db6fb56bf\"\nuuid = \"d2c73de3-f751-5644-a686-071e5b155ba9\"\nversion = \"0.56.1+0\"\n\n[[GenericSVD]]\ndeps = [\"LinearAlgebra\"]\ngit-tree-sha1 = \"62909c3eda8a25b5673a367d1ad2392ebb265211\"\nuuid = \"01680d73-4ee2-5a08-a1aa-533608c188bb\"\nversion = \"0.3.0\"\n\n[[GenericSchur]]\ndeps = [\"LinearAlgebra\", \"Printf\"]\ngit-tree-sha1 = \"372e48d7f3ced17fdc888a841bcce77be417ce57\"\nuuid = \"c145ed77-6b09-5dd9-b285-bf645a82121e\"\nversion = \"0.5.0\"\n\n[[GeometryBasics]]\ndeps = [\"EarCut_jll\", \"IterTools\", \"LinearAlgebra\", \"StaticArrays\", \"StructArrays\", \"Tables\"]\ngit-tree-sha1 = \"c7f81b22b6c255861be4007a16bfdeb60e1c7f9b\"\nuuid = \"5c1252a2-5f33-56bf-86c9-59e7332b4326\"\nversion = \"0.3.11\"\n\n[[Gettext_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Libiconv_jll\", \"Pkg\", \"XML2_jll\"]\ngit-tree-sha1 = \"8c14294a079216000a0bdca5ec5a447f073ddc9d\"\nuuid = \"78b55507-aeef-58d4-861c-77aaff3498b1\"\nversion = \"0.20.1+7\"\n\n[[Glib_jll]]\ndeps = [\"Artifacts\", \"Gettext_jll\", \"JLLWrappers\", \"Libdl\", \"Libffi_jll\", \"Libiconv_jll\", \"Libmount_jll\", \"PCRE_jll\", \"Pkg\", \"Zlib_jll\"]\ngit-tree-sha1 = \"04690cc5008b38ecbdfede949220bc7d9ba26397\"\nuuid = \"7746bdde-850d-59dc-9ae8-88ece973131d\"\nversion = \"2.59.0+4\"\n\n[[Grisu]]\ngit-tree-sha1 = \"03d381f65183cb2d0af8b3425fde97263ce9a995\"\nuuid = \"42e2da0e-8278-4e71-bc24-59509adca0fe\"\nversion = \"1.0.0\"\n\n[[HDF5_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"LibCURL_jll\", \"Libdl\", \"OpenSSL_jll\", \"Pkg\", \"Zlib_jll\"]\ngit-tree-sha1 = \"fd83fa0bde42e01952757f01149dd968c06c4dba\"\nuuid = \"0234f1f7-429e-5d53-9886-15a909be8d59\"\nversion = \"1.12.0+1\"\n\n[[HTTP]]\ndeps = [\"Base64\", \"Dates\", \"IniFile\", \"MbedTLS\", \"NetworkOptions\", \"Sockets\", \"URIs\"]\ngit-tree-sha1 = \"c9f380c76d8aaa1fa7ea9cf97bddbc0d5b15adc2\"\nuuid = \"cd3eb016-35fb-5094-929b-558a96fad6f3\"\nversion = \"0.9.5\"\n\n[[IOCapture]]\ndeps = [\"Logging\"]\ngit-tree-sha1 = \"377252859f740c217b936cebcd918a44f9b53b59\"\nuuid = \"b5f81e59-6552-4d32-b1f0-c071b021bf89\"\nversion = \"0.1.1\"\n\n[[IfElse]]\ngit-tree-sha1 = \"28e837ff3e7a6c3cdb252ce49fb412c8eb3caeef\"\nuuid = \"615f187c-cbe4-4ef1-ba3b-2fcf58d6d173\"\nversion = \"0.1.0\"\n\n[[Inflate]]\ngit-tree-sha1 = \"f5fc07d4e706b84f72d54eedcc1c13d92fb0871c\"\nuuid = \"d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9\"\nversion = \"0.1.2\"\n\n[[IniFile]]\ndeps = [\"Test\"]\ngit-tree-sha1 = \"098e4d2c533924c921f9f9847274f2ad89e018b8\"\nuuid = \"83e8ac13-25f8-5344-8a64-a9f2b223428f\"\nversion = \"0.5.0\"\n\n[[InteractiveUtils]]\ndeps = [\"Markdown\"]\nuuid = \"b77e0a4c-d291-57a0-90e8-8db25a27a240\"\n\n[[Intervals]]\ndeps = [\"Dates\", \"Printf\", \"RecipesBase\", \"Serialization\", \"TimeZones\"]\ngit-tree-sha1 = \"323a38ed1952d30586d0fe03412cde9399d3618b\"\nuuid = \"d8418881-c3e1-53bb-8760-2df7ec849ed5\"\nversion = \"1.5.0\"\n\n[[InvertedIndices]]\ndeps = [\"Test\"]\ngit-tree-sha1 = \"15732c475062348b0165684ffe28e85ea8396afc\"\nuuid = \"41ab1584-1d38-5bbf-9106-f11c6c58b48f\"\nversion = \"1.0.0\"\n\n[[IterTools]]\ngit-tree-sha1 = \"05110a2ab1fc5f932622ffea2a003221f4782c18\"\nuuid = \"c8e1da08-722c-5040-9ed9-7db0dc04731e\"\nversion = \"1.3.0\"\n\n[[IterativeSolvers]]\ndeps = [\"LinearAlgebra\", \"Printf\", \"Random\", \"RecipesBase\", \"SparseArrays\"]\ngit-tree-sha1 = \"6f5ef3206d9dc6510a8b8e2334b96454a2ade590\"\nuuid = \"42fd0dbc-a981-5370-80f2-aaf504508153\"\nversion = \"0.9.0\"\n\n[[IteratorInterfaceExtensions]]\ngit-tree-sha1 = \"a3f24677c21f5bbe9d2a714f95dcd58337fb2856\"\nuuid = \"82899510-4779-5014-852e-03e436cf321d\"\nversion = \"1.0.0\"\n\n[[JLD2]]\ndeps = [\"CodecZlib\", \"DataStructures\", \"MacroTools\", \"Mmap\", \"Pkg\", \"Printf\", \"Requires\", \"UUIDs\"]\ngit-tree-sha1 = \"b8343a7f96591404ade118b3a7014e1a52062465\"\nuuid = \"033835bb-8acc-5ee8-8aae-3f567f8a3819\"\nversion = \"0.4.2\"\n\n[[JLLWrappers]]\ngit-tree-sha1 = \"a431f5f2ca3f4feef3bd7a5e94b8b8d4f2f647a0\"\nuuid = \"692b3bcd-3c85-4b1f-b108-f13ce0eb3210\"\nversion = \"1.2.0\"\n\n[[JSON]]\ndeps = [\"Dates\", \"Mmap\", \"Parsers\", \"Unicode\"]\ngit-tree-sha1 = \"81690084b6198a2e1da36fcfda16eeca9f9f24e4\"\nuuid = \"682c06a0-de6a-54ab-a142-c8b1cf79cde6\"\nversion = \"0.21.1\"\n\n[[JpegTurbo_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"9aff0587d9603ea0de2c6f6300d9f9492bbefbd3\"\nuuid = \"aacddb02-875f-59d6-b918-886e6ef4fbf8\"\nversion = \"2.0.1+3\"\n\n[[KernelAbstractions]]\ndeps = [\"Adapt\", \"CUDA\", \"Cassette\", \"InteractiveUtils\", \"MacroTools\", \"SpecialFunctions\", \"StaticArrays\", \"UUIDs\"]\ngit-tree-sha1 = \"ee7f03c23d874c8353813a44315daf82a1e82046\"\nuuid = \"63c18a36-062a-441e-b654-da1e3ab1ce7c\"\nversion = \"0.5.3\"\n\n[[LAME_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"df381151e871f41ee86cee4f5f6fd598b8a68826\"\nuuid = \"c1c5ebd0-6772-5130-a774-d5fcae4a789d\"\nversion = \"3.100.0+3\"\n\n[[LLVM]]\ndeps = [\"CEnum\", \"Libdl\", \"Printf\", \"Unicode\"]\ngit-tree-sha1 = \"b616937c31337576360cb9fb872ec7633af7b194\"\nuuid = \"929cbde3-209d-540e-8aea-75f648917ca0\"\nversion = \"3.6.0\"\n\n[[LZO_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"f128cd6cd05ffd6d3df0523ed99b90ff6f9b349a\"\nuuid = \"dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac\"\nversion = \"2.10.0+3\"\n\n[[LaTeXStrings]]\ngit-tree-sha1 = \"c7f1c695e06c01b95a67f0cd1d34994f3e7db104\"\nuuid = \"b964fa9f-0449-5b57-a5c2-d3ea65f4040f\"\nversion = \"1.2.1\"\n\n[[LabelledArrays]]\ndeps = [\"ArrayInterface\", \"LinearAlgebra\", \"MacroTools\", \"StaticArrays\"]\ngit-tree-sha1 = \"5e288800819c323de5897fa6d5a002bdad54baf7\"\nuuid = \"2ee39098-c373-598a-b85f-a56591580800\"\nversion = \"1.5.0\"\n\n[[LambertW]]\ngit-tree-sha1 = \"2d9f4009c486ef676646bca06419ac02061c088e\"\nuuid = \"984bce1d-4616-540c-a9ee-88d1112d94c9\"\nversion = \"0.4.5\"\n\n[[Latexify]]\ndeps = [\"Formatting\", \"InteractiveUtils\", \"LaTeXStrings\", \"MacroTools\", \"Markdown\", \"Printf\", \"Requires\"]\ngit-tree-sha1 = \"7c72983c6daf61393ee8a0b29a419c709a06cede\"\nuuid = \"23fbe1c1-3f47-55db-b15f-69d7ec21a316\"\nversion = \"0.14.12\"\n\n[[LibCURL]]\ndeps = [\"LibCURL_jll\", \"MozillaCACerts_jll\"]\ngit-tree-sha1 = \"cdbe7465ab7b52358804713a53c7fe1dac3f8a3f\"\nuuid = \"b27032c2-a3e7-50c8-80cd-2d36dbcbfd21\"\nversion = \"0.6.3\"\n\n[[LibCURL_jll]]\ndeps = [\"LibSSH2_jll\", \"Libdl\", \"MbedTLS_jll\", \"Pkg\", \"Zlib_jll\", \"nghttp2_jll\"]\ngit-tree-sha1 = \"897d962c20031e6012bba7b3dcb7a667170dad17\"\nuuid = \"deac9b47-8bc7-5906-a0fe-35ac56dc84c0\"\nversion = \"7.70.0+2\"\n\n[[LibGit2]]\ndeps = [\"Printf\"]\nuuid = \"76f85450-5226-5b5a-8eaa-529ad045b433\"\n\n[[LibSSH2_jll]]\ndeps = [\"Libdl\", \"MbedTLS_jll\", \"Pkg\"]\ngit-tree-sha1 = \"717705533148132e5466f2924b9a3657b16158e8\"\nuuid = \"29816b5a-b9ab-546f-933c-edad1886dfa8\"\nversion = \"1.9.0+3\"\n\n[[LibVPX_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"85fcc80c3052be96619affa2fe2e6d2da3908e11\"\nuuid = \"dd192d2f-8180-539f-9fb4-cc70b1dcf69a\"\nversion = \"1.9.0+1\"\n\n[[Libdl]]\nuuid = \"8f399da3-3557-5675-b5ff-fb832c97cbdb\"\n\n[[Libffi_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"a2cd088a88c0d37eef7d209fd3d8712febce0d90\"\nuuid = \"e9f186c6-92d2-5b65-8a66-fee21dc1b490\"\nversion = \"3.2.1+4\"\n\n[[Libgcrypt_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Libgpg_error_jll\", \"Pkg\"]\ngit-tree-sha1 = \"b391a18ab1170a2e568f9fb8d83bc7c780cb9999\"\nuuid = \"d4300ac3-e22c-5743-9152-c294e39db1e4\"\nversion = \"1.8.5+4\"\n\n[[Libglvnd_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Xorg_libX11_jll\", \"Xorg_libXext_jll\"]\ngit-tree-sha1 = \"7739f837d6447403596a75d19ed01fd08d6f56bf\"\nuuid = \"7e76a0d4-f3c7-5321-8279-8d96eeed0f29\"\nversion = \"1.3.0+3\"\n\n[[Libgpg_error_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"ec7f2e8ad5c9fa99fc773376cdbc86d9a5a23cb7\"\nuuid = \"7add5ba3-2f88-524e-9cd5-f83b8a55f7b8\"\nversion = \"1.36.0+3\"\n\n[[Libiconv_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"cba7b560fcc00f8cd770fa85a498cbc1d63ff618\"\nuuid = \"94ce4f54-9a6c-5748-9c1c-f9c7231a4531\"\nversion = \"1.16.0+8\"\n\n[[Libmount_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"51ad0c01c94c1ce48d5cad629425035ad030bfd5\"\nuuid = \"4b2f31a3-9ecc-558c-b454-b3730dcb73e9\"\nversion = \"2.34.0+3\"\n\n[[Libtiff_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"JpegTurbo_jll\", \"Libdl\", \"Pkg\", \"Zlib_jll\", \"Zstd_jll\"]\ngit-tree-sha1 = \"291dd857901f94d683973cdf679984cdf73b56d0\"\nuuid = \"89763e89-9b03-5906-acba-b20f662cd828\"\nversion = \"4.1.0+2\"\n\n[[Libuuid_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"f879ae9edbaa2c74c922e8b85bb83cc84ea1450b\"\nuuid = \"38a345b3-de98-5d2b-a5d3-14cd9215e700\"\nversion = \"2.34.0+7\"\n\n[[LightGraphs]]\ndeps = [\"ArnoldiMethod\", \"DataStructures\", \"Distributed\", \"Inflate\", \"LinearAlgebra\", \"Random\", \"SharedArrays\", \"SimpleTraits\", \"SparseArrays\", \"Statistics\"]\ngit-tree-sha1 = \"432428df5f360964040ed60418dd5601ecd240b6\"\nuuid = \"093fc24a-ae57-5d10-9952-331d41423f4d\"\nversion = \"1.3.5\"\n\n[[LineSearches]]\ndeps = [\"LinearAlgebra\", \"NLSolversBase\", \"NaNMath\", \"Parameters\", \"Printf\"]\ngit-tree-sha1 = \"f27132e551e959b3667d8c93eae90973225032dd\"\nuuid = \"d3d80556-e9d4-5f37-9878-2ab0fcc64255\"\nversion = \"7.1.1\"\n\n[[LinearAlgebra]]\ndeps = [\"Libdl\"]\nuuid = \"37e2e46d-f89d-539d-b4ee-838fcccc9c8e\"\n\n[[Literate]]\ndeps = [\"Base64\", \"JSON\", \"REPL\"]\ngit-tree-sha1 = \"32b517d4d8219d3bbab199de3416ace45010bdb3\"\nuuid = \"98b081ad-f1c9-55d3-8b20-4c87d4299306\"\nversion = \"2.8.0\"\n\n[[Logging]]\nuuid = \"56ddb016-857b-54e1-b83d-db4d58db5568\"\n\n[[MPI]]\ndeps = [\"Distributed\", \"DocStringExtensions\", \"Libdl\", \"MPICH_jll\", \"MicrosoftMPI_jll\", \"OpenMPI_jll\", \"Pkg\", \"Random\", \"Requires\", \"Serialization\", \"Sockets\"]\ngit-tree-sha1 = \"38d0d0255db2316077f7d5dcf8f40c3940e8d534\"\nuuid = \"da04e1cc-30fd-572f-bb4f-1f8673147195\"\nversion = \"0.17.0\"\n\n[[MPICH_jll]]\ndeps = [\"CompilerSupportLibraries_jll\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"4d37f1e07b4e2a74462eebf9ee48c626d15ffdac\"\nuuid = \"7cb0a576-ebde-5e09-9194-50597f1243b4\"\nversion = \"3.3.2+10\"\n\n[[MacroTools]]\ndeps = [\"Markdown\", \"Random\"]\ngit-tree-sha1 = \"6a8a2a625ab0dea913aba95c11370589e0239ff0\"\nuuid = \"1914dd2f-81c6-5fcd-8719-6d5c9610ff09\"\nversion = \"0.5.6\"\n\n[[Markdown]]\ndeps = [\"Base64\"]\nuuid = \"d6f4376e-aef5-505a-96c1-9c027394607a\"\n\n[[MbedTLS]]\ndeps = [\"Dates\", \"MbedTLS_jll\", \"Random\", \"Sockets\"]\ngit-tree-sha1 = \"1c38e51c3d08ef2278062ebceade0e46cefc96fe\"\nuuid = \"739be429-bea8-5141-9913-cc70e7f3736d\"\nversion = \"1.0.3\"\n\n[[MbedTLS_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"0eef589dd1c26a3ac9d753fe1a8bcad63f956fa6\"\nuuid = \"c8ffd9c3-330d-5841-b78e-0817d7145fa1\"\nversion = \"2.16.8+1\"\n\n[[Measures]]\ngit-tree-sha1 = \"e498ddeee6f9fdb4551ce855a46f54dbd900245f\"\nuuid = \"442fdcdd-2543-5da2-b0f3-8c86c306513e\"\nversion = \"0.3.1\"\n\n[[MicrosoftMPI_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"e5c90234b3967684c9c6f87b4a54549b4ce21836\"\nuuid = \"9237b28f-5490-5468-be7b-bb81f5f5e6cf\"\nversion = \"10.1.3+0\"\n\n[[Missings]]\ndeps = [\"DataAPI\"]\ngit-tree-sha1 = \"f8c673ccc215eb50fcadb285f522420e29e69e1c\"\nuuid = \"e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28\"\nversion = \"0.4.5\"\n\n[[Mmap]]\nuuid = \"a63ad114-7e13-5084-954f-fe012c677804\"\n\n[[Mocking]]\ndeps = [\"ExprTools\"]\ngit-tree-sha1 = \"916b850daad0d46b8c71f65f719c49957e9513ed\"\nuuid = \"78c3b35d-d492-501b-9361-3d52fe80e533\"\nversion = \"0.7.1\"\n\n[[MozillaCACerts_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"bbcac5afd9049834366c3b68d792971e3d981799\"\nuuid = \"14a3606d-f60d-562e-9121-12d972cd8159\"\nversion = \"2020.10.14+0\"\n\n[[MuladdMacro]]\ngit-tree-sha1 = \"c6190f9a7fc5d9d5915ab29f2134421b12d24a68\"\nuuid = \"46d2c3a1-f734-5fdb-9937-b9b9aeba4221\"\nversion = \"0.2.2\"\n\n[[NCDatasets]]\ndeps = [\"CFTime\", \"DataStructures\", \"Dates\", \"NetCDF_jll\", \"Printf\"]\ngit-tree-sha1 = \"b71d83c87d80f5c54c55a7a9a3aa42bf931c72aa\"\nuuid = \"85f8d34a-cbdd-5861-8df4-14fed0d494ab\"\nversion = \"0.11.3\"\n\n[[NLSolversBase]]\ndeps = [\"DiffResults\", \"Distributed\", \"FiniteDiff\", \"ForwardDiff\"]\ngit-tree-sha1 = \"50608f411a1e178e0129eab4110bd56efd08816f\"\nuuid = \"d41bc354-129a-5804-8e4c-c37616107c6c\"\nversion = \"7.8.0\"\n\n[[NLsolve]]\ndeps = [\"Distances\", \"LineSearches\", \"LinearAlgebra\", \"NLSolversBase\", \"Printf\", \"Reexport\"]\ngit-tree-sha1 = \"019f12e9a1a7880459d0173c182e6a99365d7ac1\"\nuuid = \"2774e3e8-f4cf-5e23-947b-6d7e65073b56\"\nversion = \"4.5.1\"\n\n[[NNlib]]\ndeps = [\"ChainRulesCore\", \"Compat\", \"LinearAlgebra\", \"Pkg\", \"Requires\", \"Statistics\"]\ngit-tree-sha1 = \"ab1d43fead2ecb9aa5ae460d3d547c2cf8d89461\"\nuuid = \"872c559c-99b0-510c-b3b7-b6c96a88d5cd\"\nversion = \"0.7.17\"\n\n[[NaNMath]]\ngit-tree-sha1 = \"bfe47e760d60b82b66b61d2d44128b62e3a369fb\"\nuuid = \"77ba4419-2d1f-58cd-9bb1-8ffee604a2e3\"\nversion = \"0.3.5\"\n\n[[NetCDF_jll]]\ndeps = [\"Artifacts\", \"HDF5_jll\", \"JLLWrappers\", \"LibCURL_jll\", \"LibSSH2_jll\", \"Libdl\", \"MbedTLS_jll\", \"Pkg\", \"Zlib_jll\", \"nghttp2_jll\"]\ngit-tree-sha1 = \"d5835f95aea3b93965a1a7c06de9aace8cb82d99\"\nuuid = \"7243133f-43d8-5620-bbf4-c2c921802cf3\"\nversion = \"400.701.400+0\"\n\n[[NetworkOptions]]\ngit-tree-sha1 = \"ed3157f48a05543cce9b241e1f2815f7e843d96e\"\nuuid = \"ca575930-c2e3-43a9-ace4-1e988b2c1908\"\nversion = \"1.2.0\"\n\n[[NonlinearSolve]]\ndeps = [\"ArrayInterface\", \"FiniteDiff\", \"ForwardDiff\", \"IterativeSolvers\", \"LinearAlgebra\", \"RecursiveArrayTools\", \"RecursiveFactorization\", \"Reexport\", \"SciMLBase\", \"Setfield\", \"StaticArrays\", \"UnPack\"]\ngit-tree-sha1 = \"ef18e47df4f3917af35be5e5d7f5d97e8a83b0ec\"\nuuid = \"8913a72c-1f9b-4ce2-8d82-65094dcecaec\"\nversion = \"0.3.8\"\n\n[[Ogg_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"a42c0f138b9ebe8b58eba2271c5053773bde52d0\"\nuuid = \"e7412a2a-1a6e-54c0-be00-318e2571c051\"\nversion = \"1.3.4+2\"\n\n[[OpenMPI_jll]]\ndeps = [\"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"41b983e26a7ab8c9bf05f7d70c274b817d541b46\"\nuuid = \"fe0851c0-eecd-5654-98d4-656369965a5c\"\nversion = \"4.0.2+2\"\n\n[[OpenSSL_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"71bbbc616a1d710879f5a1021bcba65ffba6ce58\"\nuuid = \"458c3c95-2e84-50aa-8efc-19380b2a3a95\"\nversion = \"1.1.1+6\"\n\n[[OpenSpecFun_jll]]\ndeps = [\"Artifacts\", \"CompilerSupportLibraries_jll\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"9db77584158d0ab52307f8c04f8e7c08ca76b5b3\"\nuuid = \"efe28fd5-8261-553b-a9e1-b2916fc3738e\"\nversion = \"0.5.3+4\"\n\n[[Opus_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"f9d57f4126c39565e05a2b0264df99f497fc6f37\"\nuuid = \"91d4177d-7536-5919-b921-800302f37372\"\nversion = \"1.3.1+3\"\n\n[[OrderedCollections]]\ngit-tree-sha1 = \"4fa2ba51070ec13fcc7517db714445b4ab986bdf\"\nuuid = \"bac558e1-5e72-5ebc-8fee-abe8a469f55d\"\nversion = \"1.4.0\"\n\n[[OrdinaryDiffEq]]\ndeps = [\"Adapt\", \"ArrayInterface\", \"DataStructures\", \"DiffEqBase\", \"ExponentialUtilities\", \"FastClosures\", \"FiniteDiff\", \"ForwardDiff\", \"LinearAlgebra\", \"Logging\", \"MacroTools\", \"MuladdMacro\", \"NLsolve\", \"RecursiveArrayTools\", \"Reexport\", \"SparseArrays\", \"SparseDiffTools\", \"StaticArrays\", \"UnPack\"]\ngit-tree-sha1 = \"d22a75b8ae5b77543c4e1f8eae1ff01ce1f64453\"\nuuid = \"1dea7af3-3e70-54e6-95c3-0bf5283fa5ed\"\nversion = \"5.52.2\"\n\n[[PCRE_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"1b556ad51dceefdbf30e86ffa8f528b73c7df2bb\"\nuuid = \"2f80f16e-611a-54ab-bc61-aa92de5b98fc\"\nversion = \"8.42.0+4\"\n\n[[PDMats]]\ndeps = [\"LinearAlgebra\", \"SparseArrays\", \"SuiteSparse\"]\ngit-tree-sha1 = \"f82a0e71f222199de8e9eb9a09977bd0767d52a0\"\nuuid = \"90014a1f-27ba-587c-ab20-58faa44d9150\"\nversion = \"0.11.0\"\n\n[[Parameters]]\ndeps = [\"OrderedCollections\", \"UnPack\"]\ngit-tree-sha1 = \"2276ac65f1e236e0a6ea70baff3f62ad4c625345\"\nuuid = \"d96e819e-fc66-5662-9728-84c9c7592b0a\"\nversion = \"0.12.2\"\n\n[[Parsers]]\ndeps = [\"Dates\"]\ngit-tree-sha1 = \"c8abc88faa3f7a3950832ac5d6e690881590d6dc\"\nuuid = \"69de0a69-1ddd-5017-9359-2bf0b02dc9f0\"\nversion = \"1.1.0\"\n\n[[Pixman_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"6a20a83c1ae86416f0a5de605eaea08a552844a3\"\nuuid = \"30392449-352a-5448-841d-b1acce4e97dc\"\nversion = \"0.40.0+0\"\n\n[[Pkg]]\ndeps = [\"Dates\", \"LibGit2\", \"Libdl\", \"Logging\", \"Markdown\", \"Printf\", \"REPL\", \"Random\", \"SHA\", \"UUIDs\"]\nuuid = \"44cfe95a-1eb2-52ea-b672-e2afdf69b78f\"\n\n[[PlotThemes]]\ndeps = [\"PlotUtils\", \"Requires\", \"Statistics\"]\ngit-tree-sha1 = \"a3a964ce9dc7898193536002a6dd892b1b5a6f1d\"\nuuid = \"ccf2f8ad-2431-5c83-bf29-c5338b663b6a\"\nversion = \"2.0.1\"\n\n[[PlotUtils]]\ndeps = [\"ColorSchemes\", \"Colors\", \"Dates\", \"Printf\", \"Random\", \"Reexport\", \"Statistics\"]\ngit-tree-sha1 = \"ae9a295ac761f64d8c2ec7f9f24d21eb4ffba34d\"\nuuid = \"995b91a9-d308-5afd-9ec6-746e21dbc043\"\nversion = \"1.0.10\"\n\n[[Plots]]\ndeps = [\"Base64\", \"Contour\", \"Dates\", \"FFMPEG\", \"FixedPointNumbers\", \"GR\", \"GeometryBasics\", \"JSON\", \"Latexify\", \"LinearAlgebra\", \"Measures\", \"NaNMath\", \"PlotThemes\", \"PlotUtils\", \"Printf\", \"REPL\", \"Random\", \"RecipesBase\", \"RecipesPipeline\", \"Reexport\", \"Requires\", \"Scratch\", \"Showoff\", \"SparseArrays\", \"Statistics\", \"StatsBase\", \"UUIDs\"]\ngit-tree-sha1 = \"40031283dbb5ae602aa132f423daedfc18c82069\"\nuuid = \"91a5bcdd-55d7-5caf-9e0b-520d859cae80\"\nversion = \"1.11.0\"\n\n[[Polynomials]]\ndeps = [\"Intervals\", \"LinearAlgebra\", \"RecipesBase\"]\ngit-tree-sha1 = \"c6b8b87670b9e765db3001ffe640e0583a5ec317\"\nuuid = \"f27b6e38-b328-58d1-80ce-0feddd5e7a45\"\nversion = \"2.0.3\"\n\n[[PooledArrays]]\ndeps = [\"DataAPI\", \"Future\"]\ngit-tree-sha1 = \"cde4ce9d6f33219465b55162811d8de8139c0414\"\nuuid = \"2dfb63ee-cc39-5dd5-95bd-886bf059d720\"\nversion = \"1.2.1\"\n\n[[PrettyTables]]\ndeps = [\"Crayons\", \"Formatting\", \"Markdown\", \"Reexport\", \"Tables\"]\ngit-tree-sha1 = \"574a6b3ea95f04e8757c0280bb9c29f1a5e35138\"\nuuid = \"08abe8d2-0d0c-5749-adfa-8a2ac140af0d\"\nversion = \"0.11.1\"\n\n[[Printf]]\ndeps = [\"Unicode\"]\nuuid = \"de0858da-6303-5e67-8744-51eddeeeb8d7\"\n\n[[Qt_jll]]\ndeps = [\"Artifacts\", \"CompilerSupportLibraries_jll\", \"Fontconfig_jll\", \"Glib_jll\", \"JLLWrappers\", \"Libdl\", \"Libglvnd_jll\", \"OpenSSL_jll\", \"Pkg\", \"Xorg_libXext_jll\", \"Xorg_libxcb_jll\", \"Xorg_xcb_util_image_jll\", \"Xorg_xcb_util_keysyms_jll\", \"Xorg_xcb_util_renderutil_jll\", \"Xorg_xcb_util_wm_jll\", \"Zlib_jll\", \"xkbcommon_jll\"]\ngit-tree-sha1 = \"32d763e4624ff5df9ad399aa29feeda5a5f5c43f\"\nuuid = \"ede63266-ebff-546c-83e0-1c6fb6d0efc8\"\nversion = \"5.15.2+3\"\n\n[[QuadGK]]\ndeps = [\"DataStructures\", \"LinearAlgebra\"]\ngit-tree-sha1 = \"12fbe86da16df6679be7521dfb39fbc861e1dc7b\"\nuuid = \"1fd47b50-473d-5c70-9696-f719f8f3bcdc\"\nversion = \"2.4.1\"\n\n[[Quadmath]]\ndeps = [\"Printf\", \"Random\", \"Requires\"]\ngit-tree-sha1 = \"5a8f74af8eae654086a1d058b4ec94ff192e3de0\"\nuuid = \"be4d8f0f-7fa4-5f49-b795-2f01399ab2dd\"\nversion = \"0.5.5\"\n\n[[REPL]]\ndeps = [\"InteractiveUtils\", \"Markdown\", \"Sockets\"]\nuuid = \"3fa0cd96-eef1-5676-8a61-b3b8758bbffb\"\n\n[[Random]]\ndeps = [\"Serialization\"]\nuuid = \"9a3f8284-a2c9-5f02-9a11-845980a1fd5c\"\n\n[[RecipesBase]]\ngit-tree-sha1 = \"b3fb709f3c97bfc6e948be68beeecb55a0b340ae\"\nuuid = \"3cdcf5f2-1ef4-517c-9805-6587b60abb01\"\nversion = \"1.1.1\"\n\n[[RecipesPipeline]]\ndeps = [\"Dates\", \"NaNMath\", \"PlotUtils\", \"RecipesBase\"]\ngit-tree-sha1 = \"c4d54a78e287de7ec73bbc928ce5eb3c60f80b24\"\nuuid = \"01d81517-befc-4cb6-b9ec-a95719d0359c\"\nversion = \"0.3.1\"\n\n[[RecursiveArrayTools]]\ndeps = [\"ArrayInterface\", \"LinearAlgebra\", \"RecipesBase\", \"Requires\", \"StaticArrays\", \"Statistics\", \"ZygoteRules\"]\ngit-tree-sha1 = \"271a36e18c8806332b7bd0f57e50fcff0d428b11\"\nuuid = \"731186ca-8d62-57ce-b412-fbd966d074cd\"\nversion = \"2.11.0\"\n\n[[RecursiveFactorization]]\ndeps = [\"LinearAlgebra\"]\ngit-tree-sha1 = \"6761a5d1f9646affb2a369ff932841fff77934a3\"\nuuid = \"f2c3362d-daeb-58d1-803e-2bc74f2840b4\"\nversion = \"0.1.0\"\n\n[[Reexport]]\ngit-tree-sha1 = \"57d8440b0c7d98fc4f889e478e80f268d534c9d5\"\nuuid = \"189a3867-3050-52da-a836-e630ba90ab69\"\nversion = \"1.0.0\"\n\n[[Requires]]\ndeps = [\"UUIDs\"]\ngit-tree-sha1 = \"4036a3bd08ac7e968e27c203d45f5fff15020621\"\nuuid = \"ae029012-a4dd-5104-9daa-d747884805df\"\nversion = \"1.1.3\"\n\n[[Rmath]]\ndeps = [\"Random\", \"Rmath_jll\"]\ngit-tree-sha1 = \"86c5647b565873641538d8f812c04e4c9dbeb370\"\nuuid = \"79098fc4-a85e-5d69-aa6a-4863f24498fa\"\nversion = \"0.6.1\"\n\n[[Rmath_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"1b7bf41258f6c5c9c31df8c1ba34c1fc88674957\"\nuuid = \"f50d1b31-88e8-58de-be2c-1cc44531875f\"\nversion = \"0.2.2+2\"\n\n[[SHA]]\nuuid = \"ea8e919c-243c-51af-8825-aaa63cd721ce\"\n\n[[SIMD]]\ngit-tree-sha1 = \"2318299b4c8e8fe06f6f9114fb4404bd1461ae48\"\nuuid = \"fdea26ae-647d-5447-a871-4b548cad5224\"\nversion = \"3.3.0\"\n\n[[Sass]]\ndeps = [\"BinaryProvider\", \"Libdl\", \"Test\"]\ngit-tree-sha1 = \"de11179555c6363c5a61c4c94376db3498983734\"\nuuid = \"322a6be2-4ae8-5d68-aaf1-3e960788d1d9\"\nversion = \"0.1.0\"\n\n[[ScanByte]]\ndeps = [\"Libdl\", \"SIMD\"]\ngit-tree-sha1 = \"9cc2955f2a254b18be655a4ee70bc4031b2b189e\"\nuuid = \"7b38b023-a4d7-4c5e-8d43-3f3097f304eb\"\nversion = \"0.3.0\"\n\n[[SciMLBase]]\ndeps = [\"ArrayInterface\", \"CommonSolve\", \"Distributed\", \"DocStringExtensions\", \"IteratorInterfaceExtensions\", \"LinearAlgebra\", \"Logging\", \"RecipesBase\", \"RecursiveArrayTools\", \"StaticArrays\", \"Statistics\", \"Tables\", \"TreeViews\"]\ngit-tree-sha1 = \"617d5ade740dc628884b6a33e1b02b9bb950e9b3\"\nuuid = \"0bca4576-84f4-4d90-8ffe-ffa030f20462\"\nversion = \"1.9.1\"\n\n[[Scratch]]\ndeps = [\"Dates\"]\ngit-tree-sha1 = \"ad4b278adb62d185bbcb6864dc24959ab0627bf6\"\nuuid = \"6c6a2e73-6563-6170-7368-637461726353\"\nversion = \"1.0.3\"\n\n[[Serialization]]\nuuid = \"9e88b42a-f829-5b0c-bbe9-9e923198166b\"\n\n[[Setfield]]\ndeps = [\"ConstructionBase\", \"Future\", \"MacroTools\", \"Requires\"]\ngit-tree-sha1 = \"d5640fc570fb1b6c54512f0bd3853866bd298b3e\"\nuuid = \"efcf1570-3423-57d1-acb7-fd33fddbac46\"\nversion = \"0.7.0\"\n\n[[SharedArrays]]\ndeps = [\"Distributed\", \"Mmap\", \"Random\", \"Serialization\"]\nuuid = \"1a1011a3-84de-559e-8e89-a11a2f7dc383\"\n\n[[Showoff]]\ndeps = [\"Dates\", \"Grisu\"]\ngit-tree-sha1 = \"ee010d8f103468309b8afac4abb9be2e18ff1182\"\nuuid = \"992d4aef-0814-514b-bc4d-f2e9a6c4116f\"\nversion = \"0.3.2\"\n\n[[SimpleTraits]]\ndeps = [\"InteractiveUtils\", \"MacroTools\"]\ngit-tree-sha1 = \"daf7aec3fe3acb2131388f93a4c409b8c7f62226\"\nuuid = \"699a6c99-e7fa-54fc-8d76-47d257e15c1d\"\nversion = \"0.9.3\"\n\n[[Sockets]]\nuuid = \"6462fe0b-24de-5631-8697-dd941f90decc\"\n\n[[SortingAlgorithms]]\ndeps = [\"DataStructures\", \"Random\", \"Test\"]\ngit-tree-sha1 = \"03f5898c9959f8115e30bc7226ada7d0df554ddd\"\nuuid = \"a2af1166-a08f-5f64-846c-94a0d3cef48c\"\nversion = \"0.3.1\"\n\n[[SparseArrays]]\ndeps = [\"LinearAlgebra\", \"Random\"]\nuuid = \"2f01184e-e22b-5df5-ae63-d93ebab69eaf\"\n\n[[SparseDiffTools]]\ndeps = [\"Adapt\", \"ArrayInterface\", \"Compat\", \"DataStructures\", \"FiniteDiff\", \"ForwardDiff\", \"LightGraphs\", \"LinearAlgebra\", \"Requires\", \"SparseArrays\", \"VertexSafeGraphs\"]\ngit-tree-sha1 = \"d05bc362e3fa1b0e2361594a706fc63ffbd140f3\"\nuuid = \"47a9eef4-7e08-11e9-0b38-333d64bd3804\"\nversion = \"1.13.0\"\n\n[[SpecialFunctions]]\ndeps = [\"ChainRulesCore\", \"OpenSpecFun_jll\"]\ngit-tree-sha1 = \"5919936c0e92cff40e57d0ddf0ceb667d42e5902\"\nuuid = \"276daf66-3868-5448-9aa4-cd146d93841b\"\nversion = \"1.3.0\"\n\n[[Static]]\ndeps = [\"IfElse\"]\ngit-tree-sha1 = \"ddec5466a1d2d7e58adf9a427ba69763661aacf6\"\nuuid = \"aedffcd0-7271-4cad-89d0-dc628f76c6d3\"\nversion = \"0.2.4\"\n\n[[StaticArrays]]\ndeps = [\"LinearAlgebra\", \"Random\", \"Statistics\"]\ngit-tree-sha1 = \"9da72ed50e94dbff92036da395275ed114e04d49\"\nuuid = \"90137ffa-7385-5640-81b9-e52037218182\"\nversion = \"1.0.1\"\n\n[[Statistics]]\ndeps = [\"LinearAlgebra\", \"SparseArrays\"]\nuuid = \"10745b16-79ce-11e8-11f9-7d13ad32a3b2\"\n\n[[StatsBase]]\ndeps = [\"DataAPI\", \"DataStructures\", \"LinearAlgebra\", \"Missings\", \"Printf\", \"Random\", \"SortingAlgorithms\", \"SparseArrays\", \"Statistics\"]\ngit-tree-sha1 = \"a83fa3021ac4c5a918582ec4721bc0cf70b495a9\"\nuuid = \"2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91\"\nversion = \"0.33.4\"\n\n[[StatsFuns]]\ndeps = [\"Rmath\", \"SpecialFunctions\"]\ngit-tree-sha1 = \"ced55fd4bae008a8ea12508314e725df61f0ba45\"\nuuid = \"4c63d2b9-4356-54db-8cca-17b64c39e42c\"\nversion = \"0.9.7\"\n\n[[StructArrays]]\ndeps = [\"Adapt\", \"DataAPI\", \"Tables\"]\ngit-tree-sha1 = \"26ea43b4be7e919a2390c3c0f824e7eb4fc19a0a\"\nuuid = \"09ab397b-f2b6-538f-b94a-2f83cf4a842a\"\nversion = \"0.5.0\"\n\n[[StructTypes]]\ndeps = [\"Dates\", \"UUIDs\"]\ngit-tree-sha1 = \"89b390141d2fb2ef3ac2dc32e336f7a5c4810751\"\nuuid = \"856f2bd8-1eba-4b0a-8007-ebc267875bd4\"\nversion = \"1.5.0\"\n\n[[SuiteSparse]]\ndeps = [\"Libdl\", \"LinearAlgebra\", \"Serialization\", \"SparseArrays\"]\nuuid = \"4607b0f0-06f3-5cda-b6b1-a6196a1729e9\"\n\n[[TableTraits]]\ndeps = [\"IteratorInterfaceExtensions\"]\ngit-tree-sha1 = \"b1ad568ba658d8cbb3b892ed5380a6f3e781a81e\"\nuuid = \"3783bdb8-4a98-5b6b-af9a-565f29a5fe9c\"\nversion = \"1.0.0\"\n\n[[Tables]]\ndeps = [\"DataAPI\", \"DataValueInterfaces\", \"IteratorInterfaceExtensions\", \"LinearAlgebra\", \"TableTraits\", \"Test\"]\ngit-tree-sha1 = \"a9ff3dfec713c6677af435d6a6d65f9744feef67\"\nuuid = \"bd369af6-aec1-5ad0-b16a-f7cc5008161c\"\nversion = \"1.4.1\"\n\n[[Test]]\ndeps = [\"Distributed\", \"InteractiveUtils\", \"Logging\", \"Random\"]\nuuid = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n\n[[TextWrap]]\ngit-tree-sha1 = \"9250ef9b01b66667380cf3275b3f7488d0e25faf\"\nuuid = \"b718987f-49a8-5099-9789-dcd902bef87d\"\nversion = \"1.0.1\"\n\n[[TimeZones]]\ndeps = [\"Dates\", \"EzXML\", \"Mocking\", \"Pkg\", \"Printf\", \"RecipesBase\", \"Serialization\", \"Unicode\"]\ngit-tree-sha1 = \"4ba8a9579a243400db412b50300cd61d7447e583\"\nuuid = \"f269a46b-ccf7-5d73-abea-4c690281aa53\"\nversion = \"1.5.3\"\n\n[[TimerOutputs]]\ndeps = [\"Printf\"]\ngit-tree-sha1 = \"32cdbe6cd2d214c25a0b88f985c9e0092877c236\"\nuuid = \"a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f\"\nversion = \"0.5.8\"\n\n[[TranscodingStreams]]\ndeps = [\"Random\", \"Test\"]\ngit-tree-sha1 = \"7c53c35547de1c5b9d46a4797cf6d8253807108c\"\nuuid = \"3bb67fe8-82b1-5028-8e26-92a6c54297fa\"\nversion = \"0.9.5\"\n\n[[TreeViews]]\ndeps = [\"Test\"]\ngit-tree-sha1 = \"8d0d7a3fe2f30d6a7f833a5f19f7c7a5b396eae6\"\nuuid = \"a2a6695c-b41b-5b7d-aed9-dbfdeacea5d7\"\nversion = \"0.3.0\"\n\n[[URIs]]\ngit-tree-sha1 = \"7855809b88d7b16e9b029afd17880930626f54a2\"\nuuid = \"5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4\"\nversion = \"1.2.0\"\n\n[[UUIDs]]\ndeps = [\"Random\", \"SHA\"]\nuuid = \"cf7118a7-6976-5b1a-9a39-7adc72f591a4\"\n\n[[UnPack]]\ngit-tree-sha1 = \"387c1f73762231e86e0c9c5443ce3b4a0a9a0c2b\"\nuuid = \"3a884ed6-31ef-47d7-9d2a-63182c4928ed\"\nversion = \"1.0.2\"\n\n[[Unicode]]\nuuid = \"4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5\"\n\n[[VertexSafeGraphs]]\ndeps = [\"LightGraphs\"]\ngit-tree-sha1 = \"b9b450c99a3ca1cc1c6836f560d8d887bcbe356e\"\nuuid = \"19fa3120-7c27-5ec5-8db8-b0b0aa330d6f\"\nversion = \"0.1.2\"\n\n[[Wayland_jll]]\ndeps = [\"Artifacts\", \"Expat_jll\", \"JLLWrappers\", \"Libdl\", \"Libffi_jll\", \"Pkg\", \"XML2_jll\"]\ngit-tree-sha1 = \"dc643a9b774da1c2781413fd7b6dcd2c56bb8056\"\nuuid = \"a2964d1f-97da-50d4-b82a-358c7fce9d89\"\nversion = \"1.17.0+4\"\n\n[[Wayland_protocols_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Wayland_jll\"]\ngit-tree-sha1 = \"2839f1c1296940218e35df0bbb220f2a79686670\"\nuuid = \"2381bf8a-dfd0-557d-9999-79630e7b1b91\"\nversion = \"1.18.0+4\"\n\n[[XML2_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Libiconv_jll\", \"Pkg\", \"Zlib_jll\"]\ngit-tree-sha1 = \"be0db24f70aae7e2b89f2f3092e93b8606d659a6\"\nuuid = \"02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a\"\nversion = \"2.9.10+3\"\n\n[[XSLT_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Libgcrypt_jll\", \"Libgpg_error_jll\", \"Pkg\", \"XML2_jll\"]\ngit-tree-sha1 = \"2b3eac39df218762d2d005702d601cd44c997497\"\nuuid = \"aed1982a-8fda-507f-9586-7b0439959a61\"\nversion = \"1.1.33+4\"\n\n[[Xorg_libX11_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Xorg_libxcb_jll\", \"Xorg_xtrans_jll\"]\ngit-tree-sha1 = \"5be649d550f3f4b95308bf0183b82e2582876527\"\nuuid = \"4f6342f7-b3d2-589e-9d20-edeb45f2b2bc\"\nversion = \"1.6.9+4\"\n\n[[Xorg_libXau_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"4e490d5c960c314f33885790ed410ff3a94ce67e\"\nuuid = \"0c0b7dd1-d40b-584c-a123-a41640f87eec\"\nversion = \"1.0.9+4\"\n\n[[Xorg_libXcursor_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Xorg_libXfixes_jll\", \"Xorg_libXrender_jll\"]\ngit-tree-sha1 = \"12e0eb3bc634fa2080c1c37fccf56f7c22989afd\"\nuuid = \"935fb764-8cf2-53bf-bb30-45bb1f8bf724\"\nversion = \"1.2.0+4\"\n\n[[Xorg_libXdmcp_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"4fe47bd2247248125c428978740e18a681372dd4\"\nuuid = \"a3789734-cfe1-5b06-b2d0-1dd0d9d62d05\"\nversion = \"1.1.3+4\"\n\n[[Xorg_libXext_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Xorg_libX11_jll\"]\ngit-tree-sha1 = \"b7c0aa8c376b31e4852b360222848637f481f8c3\"\nuuid = \"1082639a-0dae-5f34-9b06-72781eeb8cb3\"\nversion = \"1.3.4+4\"\n\n[[Xorg_libXfixes_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Xorg_libX11_jll\"]\ngit-tree-sha1 = \"0e0dc7431e7a0587559f9294aeec269471c991a4\"\nuuid = \"d091e8ba-531a-589c-9de9-94069b037ed8\"\nversion = \"5.0.3+4\"\n\n[[Xorg_libXi_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Xorg_libXext_jll\", \"Xorg_libXfixes_jll\"]\ngit-tree-sha1 = \"89b52bc2160aadc84d707093930ef0bffa641246\"\nuuid = \"a51aa0fd-4e3c-5386-b890-e753decda492\"\nversion = \"1.7.10+4\"\n\n[[Xorg_libXinerama_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Xorg_libXext_jll\"]\ngit-tree-sha1 = \"26be8b1c342929259317d8b9f7b53bf2bb73b123\"\nuuid = \"d1454406-59df-5ea1-beac-c340f2130bc3\"\nversion = \"1.1.4+4\"\n\n[[Xorg_libXrandr_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Xorg_libXext_jll\", \"Xorg_libXrender_jll\"]\ngit-tree-sha1 = \"34cea83cb726fb58f325887bf0612c6b3fb17631\"\nuuid = \"ec84b674-ba8e-5d96-8ba1-2a689ba10484\"\nversion = \"1.5.2+4\"\n\n[[Xorg_libXrender_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Xorg_libX11_jll\"]\ngit-tree-sha1 = \"19560f30fd49f4d4efbe7002a1037f8c43d43b96\"\nuuid = \"ea2f1a96-1ddc-540d-b46f-429655e07cfa\"\nversion = \"0.9.10+4\"\n\n[[Xorg_libpthread_stubs_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"6783737e45d3c59a4a4c4091f5f88cdcf0908cbb\"\nuuid = \"14d82f49-176c-5ed1-bb49-ad3f5cbd8c74\"\nversion = \"0.1.0+3\"\n\n[[Xorg_libxcb_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"XSLT_jll\", \"Xorg_libXau_jll\", \"Xorg_libXdmcp_jll\", \"Xorg_libpthread_stubs_jll\"]\ngit-tree-sha1 = \"daf17f441228e7a3833846cd048892861cff16d6\"\nuuid = \"c7cfdc94-dc32-55de-ac96-5a1b8d977c5b\"\nversion = \"1.13.0+3\"\n\n[[Xorg_libxkbfile_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Xorg_libX11_jll\"]\ngit-tree-sha1 = \"926af861744212db0eb001d9e40b5d16292080b2\"\nuuid = \"cc61e674-0454-545c-8b26-ed2c68acab7a\"\nversion = \"1.1.0+4\"\n\n[[Xorg_xcb_util_image_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Xorg_xcb_util_jll\"]\ngit-tree-sha1 = \"0fab0a40349ba1cba2c1da699243396ff8e94b97\"\nuuid = \"12413925-8142-5f55-bb0e-6d7ca50bb09b\"\nversion = \"0.4.0+1\"\n\n[[Xorg_xcb_util_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Xorg_libxcb_jll\"]\ngit-tree-sha1 = \"e7fd7b2881fa2eaa72717420894d3938177862d1\"\nuuid = \"2def613f-5ad1-5310-b15b-b15d46f528f5\"\nversion = \"0.4.0+1\"\n\n[[Xorg_xcb_util_keysyms_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Xorg_xcb_util_jll\"]\ngit-tree-sha1 = \"d1151e2c45a544f32441a567d1690e701ec89b00\"\nuuid = \"975044d2-76e6-5fbe-bf08-97ce7c6574c7\"\nversion = \"0.4.0+1\"\n\n[[Xorg_xcb_util_renderutil_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Xorg_xcb_util_jll\"]\ngit-tree-sha1 = \"dfd7a8f38d4613b6a575253b3174dd991ca6183e\"\nuuid = \"0d47668e-0667-5a69-a72c-f761630bfb7e\"\nversion = \"0.3.9+1\"\n\n[[Xorg_xcb_util_wm_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Xorg_xcb_util_jll\"]\ngit-tree-sha1 = \"e78d10aab01a4a154142c5006ed44fd9e8e31b67\"\nuuid = \"c22f9ab0-d5fe-5066-847c-f4bb1cd4e361\"\nversion = \"0.4.1+1\"\n\n[[Xorg_xkbcomp_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Xorg_libxkbfile_jll\"]\ngit-tree-sha1 = \"4bcbf660f6c2e714f87e960a171b119d06ee163b\"\nuuid = \"35661453-b289-5fab-8a00-3d9160c6a3a4\"\nversion = \"1.4.2+4\"\n\n[[Xorg_xkeyboard_config_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Xorg_xkbcomp_jll\"]\ngit-tree-sha1 = \"5c8424f8a67c3f2209646d4425f3d415fee5931d\"\nuuid = \"33bec58e-1273-512f-9401-5d533626f822\"\nversion = \"2.27.0+4\"\n\n[[Xorg_xtrans_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"79c31e7844f6ecf779705fbc12146eb190b7d845\"\nuuid = \"c5fb5394-a638-5e4d-96e5-b29de1b5cf10\"\nversion = \"1.4.0+3\"\n\n[[Zlib_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"320228915c8debb12cb434c59057290f0834dbf6\"\nuuid = \"83775a58-1f1d-513f-b197-d71354ab007a\"\nversion = \"1.2.11+18\"\n\n[[Zstd_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"2c1332c54931e83f8f94d310fa447fd743e8d600\"\nuuid = \"3161d3a3-bdf6-5164-811a-617609db77b4\"\nversion = \"1.4.8+0\"\n\n[[ZygoteRules]]\ndeps = [\"MacroTools\"]\ngit-tree-sha1 = \"9e7a1e8ca60b742e508a315c17eef5211e7fbfd7\"\nuuid = \"700de1a5-db45-46bc-99cf-38207098b444\"\nversion = \"0.2.1\"\n\n[[libass_jll]]\ndeps = [\"Artifacts\", \"Bzip2_jll\", \"FreeType2_jll\", \"FriBidi_jll\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Zlib_jll\"]\ngit-tree-sha1 = \"acc685bcf777b2202a904cdcb49ad34c2fa1880c\"\nuuid = \"0ac62f75-1d6f-5e53-bd7c-93b484bb37c0\"\nversion = \"0.14.0+4\"\n\n[[libfdk_aac_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"7a5780a0d9c6864184b3a2eeeb833a0c871f00ab\"\nuuid = \"f638f0a6-7fb0-5443-88ba-1cc74229b280\"\nversion = \"0.1.6+4\"\n\n[[libpng_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Zlib_jll\"]\ngit-tree-sha1 = \"6abbc424248097d69c0c87ba50fcb0753f93e0ee\"\nuuid = \"b53b4c65-9356-5827-b1ea-8c7a1a84506f\"\nversion = \"1.6.37+6\"\n\n[[libvorbis_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Ogg_jll\", \"Pkg\"]\ngit-tree-sha1 = \"fa14ac25af7a4b8a7f61b287a124df7aab601bcd\"\nuuid = \"f27f6e37-5d2b-51aa-960f-b287f2bc3b7a\"\nversion = \"1.3.6+6\"\n\n[[nghttp2_jll]]\ndeps = [\"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"8e2c44ab4d49ad9518f359ed8b62f83ba8beede4\"\nuuid = \"8e850ede-7688-5339-a07c-302acd2aaf8d\"\nversion = \"1.40.0+2\"\n\n[[x264_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"d713c1ce4deac133e3334ee12f4adff07f81778f\"\nuuid = \"1270edf5-f2f9-52d2-97e9-ab00b5d0237a\"\nversion = \"2020.7.14+2\"\n\n[[x265_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\"]\ngit-tree-sha1 = \"487da2f8f2f0c8ee0e83f39d13037d6bbf0a45ab\"\nuuid = \"dfaa095f-4041-5dcd-9319-2fabd8486b76\"\nversion = \"3.0.0+3\"\n\n[[xkbcommon_jll]]\ndeps = [\"Artifacts\", \"JLLWrappers\", \"Libdl\", \"Pkg\", \"Wayland_jll\", \"Wayland_protocols_jll\", \"Xorg_libxcb_jll\", \"Xorg_xkeyboard_config_jll\"]\ngit-tree-sha1 = \"ece2350174195bb31de1a63bea3a41ae1aa593b6\"\nuuid = \"d8fb68d0-12a3-5cfd-a85a-d49703b185fd\"\nversion = \"0.9.1+5\"\n"
  },
  {
    "path": "docs/Project.toml",
    "content": "[deps]\nArgParse = \"c7e460c6-2fb9-53a9-8c5b-16f535851c63\"\nCLIMAParameters = \"6eacf6c3-8458-43b9-ae03-caf5306d3d53\"\nDataFrames = \"a93c6f00-e57d-5684-b7b6-d8193f3e46c0\"\nDates = \"ade2ca70-3891-5945-98fb-dc099432e06a\"\nDierckx = \"39dd38d3-220a-591b-8e3c-4c3a8c710a94\"\nDiffEqBase = \"2b5f629d-d688-5b77-993f-72d75c75574e\"\nDistributions = \"31c24e10-a181-5473-b8eb-7969acd0382f\"\nDocStringExtensions = \"ffbed154-4ef7-542d-bbb7-c09d3a79fcae\"\nDocumenter = \"e30172f5-a6a5-5a46-863b-614d45cd2de4\"\nDocumenterCitations = \"daee34ce-89f3-4625-b898-19384cb65244\"\nDocumenterTools = \"35a29f4d-8980-5a13-9543-d66fff28ecb8\"\nDoubleFloats = \"497a8b3b-efae-58df-a0af-a86822472b78\"\nDownloads = \"f43a241f-c20a-4ad4-852c-f6b1247861c6\"\nFileIO = \"5789e2e9-d7fb-5bc7-8068-2c6fae9b9549\"\nGR = \"28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71\"\nJLD2 = \"033835bb-8acc-5ee8-8aae-3f567f8a3819\"\nKernelAbstractions = \"63c18a36-062a-441e-b654-da1e3ab1ce7c\"\nLaTeXStrings = \"b964fa9f-0449-5b57-a5c2-d3ea65f4040f\"\nLambertW = \"984bce1d-4616-540c-a9ee-88d1112d94c9\"\nLinearAlgebra = \"37e2e46d-f89d-539d-b4ee-838fcccc9c8e\"\nLiterate = \"98b081ad-f1c9-55d3-8b20-4c87d4299306\"\nLogging = \"56ddb016-857b-54e1-b83d-db4d58db5568\"\nMPI = \"da04e1cc-30fd-572f-bb4f-1f8673147195\"\nNCDatasets = \"85f8d34a-cbdd-5861-8df4-14fed0d494ab\"\nNLsolve = \"2774e3e8-f4cf-5e23-947b-6d7e65073b56\"\nOrderedCollections = \"bac558e1-5e72-5ebc-8fee-abe8a469f55d\"\nOrdinaryDiffEq = \"1dea7af3-3e70-54e6-95c3-0bf5283fa5ed\"\nPlots = \"91a5bcdd-55d7-5caf-9e0b-520d859cae80\"\nPrintf = \"de0858da-6303-5e67-8744-51eddeeeb8d7\"\nRandom = \"9a3f8284-a2c9-5f02-9a11-845980a1fd5c\"\nSpecialFunctions = \"276daf66-3868-5448-9aa4-cd146d93841b\"\nStaticArrays = \"90137ffa-7385-5640-81b9-e52037218182\"\nStatistics = \"10745b16-79ce-11e8-11f9-7d13ad32a3b2\"\n\n[compat]\nDocumenter = \">= 0.25.4\"\njulia = \"1.5\"\n"
  },
  {
    "path": "docs/bibliography.bib",
    "content": "# The citation keys have been formatted as:\n#    Last author name (titlecase), followed by\n#    (no characters in-between) the year.\n\n@article{Arabas2015,\n  title = {libcloudph++ 1.0: a single-moment bulk, double-moment bulk, and particle-based warm-rain microphysics library in C++},\n  author = {Arabas, Sylwester and Jaruga, Anna and Pawlowska, Hanna and Grabowski, Wojciech W},\n  journal = {Geoscientific Model Development},\n  volume = {8},\n  number = {6},\n  doi = {10.5194/gmd-8-1677-2015},\n  pages = {1677--1707},\n  year = {2015},\n  publisher = {Copernicus GmbH}\n}\n\n@book{Atkinson2011,\n\ttitle={Numerical solution of ordinary differential equations},\n\tauthor={Atkinson, Kendall and Han, Weimin and Stewart, David E},\n\tvolume={108},\n\tyear={2011},\n\tpublisher={John Wiley \\& Sons}\n}\n\n@article{Baer1972,\n  title = {An alternate scale representation of atmospheric energy spectra},\n  author = {Baer, Ferdinand},\n  journal = {Journal of the Atmospheric Sciences},\n  volume = {29},\n  number = {4},\n  doi = {10.1175/1520-0469(1972)029<0649:AASROA>2.0.CO;2},\n  pages = {649--664},\n  year = {1972}\n}\n\n@article{Bank1985,\n  title = {Transient simulation of silicon devices and circuits},\n  author = {R. E. Bank and W. M. Coughran and W. Fichtner and E. H. Grosse and D. J. Rose and R. K. Smith},\n  journal = {IEEE Transactions on Computer-Aided Design of Integrated Circuits and Systems},\n  volume = {4},\n  number = {4},\n  pages = {436-451},\n  year = {1985},\n  publisher = {IEEE},\n  doi = {10.1109/TCAD.1985.1270142}\n}\n\n@article{Beare2006,\n  title = {An intercomparison of large-eddy simulations of the stable boundary layer},\n  author = {Beare, Robert J and Macvean, Malcolm K and Holtslag, Albert AM and Cuxart, Joan and Esau, Igor and Golaz, Jean-Christophe and Jimenez, Maria A and Khairoutdinov, Marat and Kosovic, Branko and Lewellen, David and others},\n  journal = {Boundary-Layer Meteorology},\n  volume = {118},\n  number = {2},\n  doi = {10.1175/1520-0469(2000)057<1052:ALESSO>2.0.CO;2},\n  pages = {247--272},\n  month = {04},\n  year = {2006},\n  publisher = {Springer}\n}\n\n@article{Berrut2004,\n  title = {Barycentric lagrange interpolation},\n  author = {Berrut, Jean-Paul and Trefethen, Lloyd N},\n  journal = {SIAM review},\n  volume = {46},\n  number = {3},\n  pages = {501--517},\n  year = {2004},\n  doi = {10.1137/S0036144502417715},\n  publisher = {SIAM}\n}\n\n@inproceedings{Boyd1996,\n  title = {The erfc-log filter and the asymptotics of the Euler and Vandeven sequence accelerations},\n  author = {Boyd, JP},\n  booktitle = {Proceedings of the Third International Conference on Spectral and High Order Methods},\n  pages = {267--276},\n  year = {1996},\n  organization = {Houston Math. J}\n}\n\n@article{Bull2014,\n  author = {Bull, J.R. and Jameson, A.},\n  title = {Simulation of the Compressible {Taylor Green} Vortex using High-Order Flux Reconstruction Schemes},\n  journal = {AIAA Aviation 7th AIAA theoretical fluid mechanics conference},\n  year = {2014},\n  doi = {10.2514/6.2014-3210},\n}\n\n@article{Businger1971,\n  title = {Flux-profile relationships in the atmospheric surface layer},\n  author = {Businger, Joost A and Wyngaard, John C and Izumi, Yꎬ and Bradley, Edward F},\n  journal = {Journal of the atmospheric Sciences},\n  volume = {28},\n  number = {2},\n  doi = {10.1175/1520-0469(1971)028<0181:FPRITA>2.0.CO;2},\n  pages = {181--189},\n  year = {1971}\n}\n\n@article{Byun1990,\n  title = {On the analytical solutions of flux-profile relationships for the atmospheric surface layer},\n  author = {Byun, Daewon W},\n  journal = {Journal of Applied Meteorology},\n  volume = {29},\n  number = {7},\n  doi = {10.1175/1520-0450(1990)029<0652:OTASOF>2.0.CO;2},\n  pages = {652--657},\n  year = {1990}\n}\n\n@article {Chen2013,\n  author = \"Xi Chen and Natalia Andronova and Bram Van Leer and Joyce E. Penner and John P. Boyd and Christiane Jablonowski and Shian-Jiann Lin\",\n  title = \"A Control-Volume Model of the Compressible Euler Equations with a Vertical Lagrangian Coordinate\",\n  journal = \"Monthly Weather Review\",\n  year = \"01 Jul. 2013\",\n  publisher = \"American Meteorological Society\",\n  address = \"Boston MA, USA\",\n  volume = \"141\",\n  number = \"7\",\n  doi = \"10.1175/MWR-D-12-00129.1\",\n  pages=      \"2526 - 2544\",\n  url = \"https://journals.ametsoc.org/view/journals/mwre/141/7/mwr-d-12-00129.1.xml\"\n}\n\n\n@article{Desai2019,\n  title = {Aerosol-Mediated Glaciation of Mixed-Phase Clouds: Steady-State Laboratory Measurements},\n  author = {Desai, Neel and Chandrakar, KK and Kinney, G and Cantrell, W and Shaw, RA},\n  journal = {Geophysical Research Letters},\n  volume = {46},\n  number = {15},\n  pages = {9154--9162},\n  year = {2019},\n  doi = {10.1029/2019GL083503},\n  publisher = {Wiley Online Library}\n}\n\n@article{Doms2011,\n  title = {A Description of the Nonhydrostatic Regional COSMO model. Part I: Dynamics and Numerics},\n  author = {Doms, G{\\\"u}nther and Baldauf, M},\n  journal = {Deutscher Wetterdienst, Offenbach},\n  volume = {-1},\n  year = {2011}\n}\n\n@article{Durran1982,\n  title = {On the effects of moisture on the Brunt-V{\\\"a}is{\\\"a}l{\\\"a} frequency},\n  author = {Durran, Dale R and Klemp, Joseph B},\n  journal = {Journal of the Atmospheric Sciences},\n  volume = {39},\n  number = {10},\n  doi = {10.1175/1520-0469(1982)039<2152:OTEOMO>2.0.CO;2},\n  pages = {2152--2158},\n  year = {1982}\n}\n\n@article{Carpenter1990,\n  title = {Application of the piecewise parabolic method (PPM) to meteorological modeling},\n  author = {Carpenter Jr, Richard L and Droegemeier, Kelvin K and Woodward, Paul R and Hane, Carl E},\n  journal = {Monthly Weather Review},\n  volume = {118},\n  number = {3},\n  doi = {10.1175/1520-0493(1990)118<0586:AOTPPM>2.0.CO;2},\n  pages = {586--612},\n  year = {1990}\n}\n\n@article{Eisenstat1983,\n  title = {Variational iterative methods for nonsymmetric systems of linear equations},\n  author = {Eisenstat, Stanley C and Elman, Howard C and Schultz, Martin H},\n  journal = {SIAM Journal on Numerical Analysis},\n  volume = {20},\n  number = {2},\n  pages = {345--357},\n  year = {1983},\n  doi = {10.1137/0720023},\n  publisher = {SIAM}\n}\n\n@book{GolubVanLoan2013,\n  title = {Matrix Computations},\n  author = {Gene H. Golub and Charles F. Van Loan},\n  edition = {4th},\n  isbn = {9781421407944},\n  publisher = {Johns Hopkins University Press},\n  address = {Baltimore, MD, USA},\n  url = {http://www.cs.cornell.edu/cv/GVL4/golubandvanloan.htm},\n  year = 2013\n}\n\n@article{giraldoRestelli2008a,\n  author = {{Giraldo},F.~X. and {Restelli},M.},\n  title = {A study of spectral element and discontinuous {G}alerkin methods for the {Navier-Stokes} equations in nonhydrostatic mesoscale atmospheric modeling: {E}quation sets and test cases},\n  journal = {J. Comput. Phys.},\n  year  = {2008},\n  volume = {227},\n  pages  = {3849-3877},\n  doi = {10.1016/j.jcp.2007.12.009},\n},\n\n@article{Giraldo2013,\n  title = {Implicit-explicit formulations of a three-dimensional nonhydrostatic unified model of the atmosphere ({NUMA})},\n  author = {Giraldo, Francis X and Kelly, James F and Constantinescu, Emil M},\n  journal = {SIAM Journal on Scientific Computing},\n  volume = {35},\n  number = {5},\n  doi = {10.1137/120876034},\n  pages = {B1162--B1194},\n  year = {2013},\n  publisher = {SIAM}\n}\n\n@article{Grabowski1996,\n  title = {Two-time-level semi-Lagrangian modeling of precipitating clouds},\n  author = {Grabowski, Wojciech W and Smolarkiewicz, Piotr K},\n  journal = {Monthly weather review},\n  volume = {124},\n  number = {3},\n  doi = {10.1175/1520-0493(1996)124<0487:TTLSLM>2.0.CO;2},\n  pages = {487--497},\n  year = {1996}\n}\n\n@article{Grabowski1998,\n  title = {Toward cloud resolving modeling of large-scale tropical circulations: A simple cloud microphysics parameterization},\n  author = {Grabowski, Wojciech W},\n  journal = {Journal of the Atmospheric Sciences},\n  volume = {55},\n  number = {21},\n  pages = {3283--3298},\n  doi = {10.1175/1520-0469(1998)055<3283:TCRMOL>2.0.CO;2},\n  year = {1998}\n}\n\n@article{Grachev2007,\n  title = {SHEBA flux--profile relationships in the stable atmospheric boundary layer},\n  author = {Grachev, Andrey A and Andreas, Edgar L and Fairall, Christopher W and Guest, Peter S and Persson, P Ola G},\n  journal = {Boundary-layer meteorology},\n  volume = {124},\n  number = {3},\n  pages = {315--333},\n  year = {2007},\n  doi = {10.1007/s10546-007-9177-6},\n  publisher = {Springer}\n}\n\n@article{Gryanik2020,\n  title = {New modified and extended stability functions for the stable boundary layer based on SHEBA and parametrizations of bulk transfer coefficients for climate models},\n  author = {Gryanik, Vladimir M and L{\\\"u}pkes, Christof and Grachev, Andrey and Sidorenko, Dmitry},\n  journal = {Journal of the Atmospheric Sciences},\n  volume = {-1},\n  doi = {10.1175/JAS-D-19-0255.1},\n  year = {2020}\n}\n\n@article{Harrington1995,\n  title = {Parameterization of ice crystal conversion processes due to vapor deposition for mesoscale models using double-moment basis functions. Part I: Basic formulation and parcel model results},\n  author = {Harrington, Jerry Y and Meyers, Michael P and Walko, Robert L and Cotton, William R},\n  journal = {Journal of the atmospheric sciences},\n  volume = {52},\n  number = {23},\n  doi = {10.1175/1520-0469(1995)052<4344:POICCP>2.0.CO;2},\n  pages = {4344--4366},\n  year = {1995}\n}\n\n@article{Held1994,\n  title = {A proposal for the intercomparison of the dynamical cores of atmospheric general circulation models},\n  author = {Held, Isaac M and Suarez, Max J},\n  journal = {Bulletin of the American Meteorological society},\n  volume = {75},\n  number = {10},\n  pages = {1825--1830},\n  year = {1994},\n  doi = {10.1175/1520-0477(1994)075<1825:APFTIO>2.0.CO;2},\n  publisher = {American Meteorological Society}\n}\n\n@article{Heun1900,\n  title = {Neue Methoden zur approximativen Integration der\n  Differentialgleichungen einer unabh\\\"{a}ngigen Ver\\\"{a}nderlichen},\n  author = {Heun, Karl},\n  journal = {Z. Math. Phys},\n  volume = {45},\n  pages = {23--38},\n  year = {1900}\n}\n\n@article{Kaul2015,\n  title = {Sensitivities in large-eddy simulations of mixed-phase Arctic stratocumulus clouds using a simple microphysics approach},\n  author = {Kaul, Colleen M and Teixeira, Jo{\\~a}o and Suzuki, Kentaroh},\n  journal = {Monthly Weather Review},\n  volume = {143},\n  number = {11},\n  doi = {10.1175/MWR-D-14-00319.1},\n  pages = {4393--4421},\n  year = {2015}\n}\n\n@article{Karrer2020,\n  title = {Ice Particle Properties Inferred from Aggregation Modelling},\n  author = {Karrer, M and Seifert, A and Siewert, C and Ori, D and von Lerber, A and Kneifel, S},\n  journal = {Journal of Advances in Modeling Earth Systems},\n  pages = {e2020MS002066},\n  volume = {-1},\n  doi = {10.1029/2020MS002066},\n  year = {2020},\n  publisher = {Wiley Online Library}\n}\n\n@book{Kennedy2001,\n\ttitle={Additive Runge-Kutta schemes for convection-diffusion-reaction equations},\n\tauthor={Kennedy, Christopher Alan},\n\tyear={2001},\n\tpublisher={National Aeronautics and Space Administration, Langley Research Center}\n}\n\n@article{Kennedy2019,\n  title = {Higher-order additive Runge--Kutta schemes for ordinary differential equations},\n  author = {Kennedy, Christopher A and Carpenter, Mark H},\n  journal = {Applied Numerical Mathematics},\n  volume = {136},\n  pages = {183--205},\n  doi = {10.1016/j.apnum.2018.10.007},\n  year = {2019},\n  publisher = {Elsevier}\n}\n\n@techreport{KennedyCarpenter2016,\n  title = {Diagonally implicit Runge-Kutta methods for ordinary differential equations. A review},\n  author = {C. A. Kennedy and M. H. Carpenter},\n  institution = {National Aeronautics and Space Administration},\n  year = {2016},\n  number = {NASA/TM–2016–219173},\n  address = {Langley Research Center, Hampton, VA}\n}\n\n@article{Kessler1995,\n  title = {On the continuity and distribution of water substance in atmospheric circulations},\n  author = {Kessler, Edwin},\n  journal = {Atmospheric research},\n  volume = {38},\n  number = {1-4},\n  pages = {109--145},\n  year = {1995},\n  doi = {10.1016/0169-8095(94)00090-Z},\n  publisher = {Elsevier}\n}\n\n@article{Khvorostyanov2002,\n  title = {Terminal velocities of droplets and crystals: Power laws with continuous parameters over the size spectrum},\n  author = {Khvorostyanov, Vitaly I and Curry, Judith A},\n  journal = {Journal of the atmospheric sciences},\n  volume = {59},\n  number = {11},\n  doi = {10.1175/1520-0469(2002)059<1872:TVODAC>2.0.CO;2},\n  pages = {1872--1884},\n  year = {2002}\n}\n\n@article{KnothWensch2014,\n  title = {Generalized split-explicit Runge--Kutta methods for the compressible Euler equations},\n  author = {Knoth, Oswald and Wensch, Joerg},\n  journal = {Monthly Weather Review},\n  volume = {142},\n  number = {5},\n  doi = {10.1175/MWR-D-13-00068.1},\n  pages = {2067--2081},\n  year = {2014}\n}\n\n@article{KnothWolke1998,\n  title = \"Implicit-explicit Runge-Kutta methods for computing atmospheric reactive flows\",\n  journal = \"Applied Numerical Mathematics\",\n  volume = \"28\",\n  number = \"2\",\n  pages = \"327--341\",\n  year = \"1998\",\n  doi = \"https://doi.org/10.1016/S0168-9274(98)00051-8\",\n  author = \"Oswald Knoth and Ralf Wolke\"\n}\n\n@article{Kopriva2006,\n  title = {Metric identities and the discontinuous spectral element method on curvilinear meshes},\n  author = {Kopriva, David A},\n  journal = {Journal of Scientific Computing},\n  volume = {26},\n  number = {3},\n  pages = {301},\n  year = {2006},\n  doi = {10.1007/s10915-005-9070-8},\n  publisher = {Springer}\n}\n\n@article{Koshyk2001,\n  title = {The horizontal kinetic energy spectrum and spectral budget simulated by a high-resolution troposphere--stratosphere--mesosphere GCM},\n  author = {Koshyk, John N and Hamilton, Kevin},\n  journal = {Journal of the atmospheric sciences},\n  volume = {58},\n  number = {4},\n  doi = {10.1175/1520-0469(2001)058<0329:THKESA>2.0.CO;2},\n  pages = {329--348},\n  year = {2001}\n}\n\n@article{Kosovic2000,\n  title = {A large eddy simulation study of a quasi-steady, stably stratified atmospheric boundary layer},\n  author = {Kosovi{\\'c}, Branko and Curry, Judith A},\n  journal = {Journal of the atmospheric sciences},\n  volume = {57},\n  number = {8},\n  doi = {10.1175/1520-0469(2000)057<1052:ALESSO>2.0.CO;2},\n  pages = {1052--1068},\n  year = {2000}\n}\n\n@article{Lauritzen2012,\n  title = {A standard test case suite for two-dimensional linear transport on the sphere},\n  author = {Lauritzen, Peter Hjort and Skamarock, William C and Prather, MJ and Taylor, MA},\n  journal = {Geoscientific Model Development},\n  volume = {5},\n  number = {3},\n  doi = {10.5194/gmd-5-887-2012},\n  pages = {887--901},\n  year = {2012}\n}\n\n@article{Light2016,\n  title = {Preserving nonnegativity in discontinuous Galerkin approximations to scalar transport via truncation and mass aware rescaling (TMAR)},\n  author = {Light, Devin and Durran, Dale},\n  journal = {Monthly Weather Review},\n  volume = {144},\n  number = {12},\n  doi = {10.1175/MWR-D-16-0220.1},\n  pages = {4771--4786},\n  year = {2016}\n}\n\n@article{Lilly1962,\n  title = {On the numerical simulation of buoyant convection},\n  author = {Lilly, Douglas K},\n  journal = {Tellus},\n  volume = {14},\n  number = {2},\n  doi = {10.1111/j.2153-3490.1962.tb00128.x},\n  pages = {148--172},\n  year = {1962},\n  publisher = {Wiley Online Library}\n}\n\n@article{LockWoodWeller2014,\n  title = {Numerical analyses of Runge–Kutta implicit–explicit schemes\n           for horizontally explicit, vertically implicit solutions of\n           atmospheric models},\n  author = {S.-J. Lock and N. Wood and H. Weller},\n  volume = {140},\n  number = {682},\n  pages = {1654-1669},\n  year = {2014},\n  journal = {Quarterly Journal of the Royal Meteorological Society},\n  publisher = {{RMetS}}\n}\n\n@article{Marshall1948,\n  title = {The distribution of raindrops with size},\n  author = {Marshall, John S and Palmer, W Mc K},\n  journal = {Journal of meteorology},\n  volume = {5},\n  number = {4},\n  pages = {165--166},\n  year = {1948}\n}\n\n@book{Mason2010,\n  title = {Physics of clouds},\n  author = {Mason, Basil John},\n  year = {2010},\n  publisher = {Clarendon Press}\n}\n\n@article{Morrison2008,\n  title = {A new two-moment bulk stratiform cloud microphysics scheme in the Community Atmosphere Model, version 3 (CAM3). Part I: Description and numerical tests},\n  author = {Morrison, Hugh and Gettelman, Andrew},\n  journal = {Journal of Climate},\n  volume = {21},\n  number = {15},\n  doi = {10.1175/2008JCLI2105.1},\n  pages = {3642--3659},\n  year = {2008}\n}\n\n@article{Muhlbauer2013,\n  title = {Reexamination of the state of the art of cloud modeling shows real improvements},\n  author = {Muhlbauer, Andreas and Grabowski, Wojciech W and Malinowski, Szymon P and Ackerman, Thomas P and Bryan, George H and Lebo, Zachary J and Milbrandt, Jason A and Morrison, Hugh and Ovchinnikov, Mikhail and Tessendorf, Sarah and others},\n  journal = {Bulletin of the American Meteorological Society},\n  volume = {94},\n  number = {5},\n  doi = {doi:10.1175/BAMS-D-12-00188.1},\n  pages = {ES45--ES48},\n  year = {2013},\n  publisher = {American Meteorological Society}\n}\n\n@article{Nair2005,\n  title = {A Discontinuous Galerkin Transport Scheme on the Cubed Sphere},\n  author = {Nair, Ramachandran D and Thomas, Stephen J and Loft, Richard D},\n  journal = {Monthly Weather Review},\n  volume = {133},\n  number = {4},\n  doi = {doi:10.1175/MWR2890.1},\n  pages = {814--828},\n  year = {2005},\n  publisher = {American Meteorological Society}\n}\n\n@article{Niegemann2012,\n  title = {Efficient low-storage Runge--Kutta schemes with optimized stability regions},\n  author = {Niegemann, Jens and Diehl, Richard and Busch, Kurt},\n  journal = {Journal of Computational Physics},\n  volume = {231},\n  number = {2},\n  pages = {364--372},\n  year = {2012},\n  doi = {10.1016/j.jcp.2011.09.003},\n  publisher = {Elsevier}\n}\n\n@article{Nishizawa2018,\n  title = {A Surface Flux Scheme Based on the Monin-Obukhov Similarity for Finite Volume Models},\n  author = {Nishizawa, S and Kitamura, Y},\n  journal = {Journal of Advances in Modeling Earth Systems},\n  volume = {10},\n  number = {12},\n  doi = {10.1029/2018MS001534},\n  pages = {3159--3175},\n  year = {2018},\n  publisher = {Wiley Online Library}\n}\n\n@article{Ogura1971,\n  title = {Numerical simulation of the life cycle of a thunderstorm cell},\n  author = {Ogura, Yoshimitsu and Takahashi, Tsutomu},\n  journal = {Mon. Wea. Rev},\n  volume = {99},\n  number = {12},\n  pages = {895--911},\n  year = {1971},\n  publisher = {Citeseer}\n}\n\n@article{Rafei2018,\n  author = {Rafei, M.E. and K\\\"on\\\"oszy, L. and Rana, Z.},\n  title = {Investigation of Numerical Dissipation in Classicaland Implicit Large Eddy Simulations},\n  journal = {Aerospace},\n  year = {2018},\n}\n\n@article{Ralston1962,\n  title = {Runge-Kutta methods with minimum error bounds},\n  author = {Ralston, Anthony},\n  journal = {Mathematics of computation},\n  volume = {16},\n  number = {80},\n  pages = {431--437},\n  year = {1962},\n  doi = {10.1090/S0025-5718-1962-0150954-0}\n}\n\n@article{Rancic1996,\n  title = {A global shallow-water model using an expanded spherical cube: Gnomonic versus conformal coordinates},\n  author = {Ran\\v{c}i\\'{c}, M. and Purser, R. J. and Mesinger, F.},\n  journal = {Quarterly Journal of the Royal Meteorological Society},\n  volume = {122},\n  number = {532},\n  pages = {959--982},\n  year = {1996},\n  doi = {10.1002/qj.49712253209}\n}\n\n@article{Roberts2018,\n  title = {Coupled Multirate Infinitesimal GARK Schemes for Stiff Systems with Multiple Time Scales},\n  author = {Roberts, Steven and Sarshar, Arash and Sandu, Adrian},\n  journal = {arXiv preprint arXiv:1812.00808},\n  volume = {-1},\n  doi = {10.1137/19M1266952},\n  year = {2019}\n}\n\n@article{Ronchi1996,\n  title = {The ``cubed sphere``: a new method for the solution of partial differential equations in spherical geometry},\n  author = {Ronchi, C. and Iacono, R. and Paolucci, P. S.},\n  journal = {Journal of Computational Physics},\n  volume = {124},\n  number = {1},\n  pages = {93--114},\n  year = {1996},\n  doi = {10.1006/jcph.1996.0047}\n}\n\n@article{Rutledge1983,\n  title = {The mesoscale and microscale structure and organization of clouds and precipitation in midlatitude cyclones. VIII: A model for the “seeder-feeder” process in warm-frontal rainbands},\n  author = {Rutledge, Steven A and Hobbs, Peterv},\n  journal = {Journal of the Atmospheric Sciences},\n  volume = {40},\n  number = {5},\n  doi = {10.1175/1520-0469(1983)040<1185:TMAMSA>2.0.CO;2},\n  pages = {1185--1206},\n  year = {1983}\n}\n\n@article{Rutledge1984,\n  title = {The mesoscale and microscale structure and organization of clouds and precipitation in midlatitude cyclones. XII: A diagnostic modeling study of precipitation development in narrow cold-frontal rainbands},\n  author = {Rutledge, Steven A and Hobbs, Peter V},\n  journal = {Journal of the Atmospheric Sciences},\n  volume = {41},\n  number = {20},\n  doi = {10.1175/1520-0469(1984)041<2949:TMAMSA>2.0.CO;2},\n  pages = {2949--2972},\n  year = {1984}\n}\n\n@article{Saad1986,\n  title = {GMRES: A generalized minimal residual algorithm for solving nonsymmetric linear systems},\n  author = {Saad, Youcef and Schultz, Martin H},\n  journal = {SIAM Journal on scientific and statistical computing},\n  volume = {7},\n  number = {3},\n  pages = {856--869},\n  year = {1986},\n  doi = {10.1137/0907058},\n  publisher = {SIAM}\n}\n\n@article{Sandu2019,\n  title = {A class of multirate infinitesimal gark methods},\n  author = {Sandu, Adrian},\n  journal = {SIAM Journal on Numerical Analysis},\n  volume = {57},\n  number = {5},\n  pages = {2300--2327},\n  year = {2019},\n  publisher = {SIAM},\n  doi = {10.1137/18M1205492}\n}\n\n@article{Schar2002,\n  title = {A new terrain-following vertical coordinate formulation for atmospheric prediction models},\n  author = {Sch{\\\"a}r, Christoph and Leuenberger, Daniel and Fuhrer, Oliver and L{\\\"u}thi, Daniel and Girard, Claude},\n  journal = {Monthly Weather Review},\n  volume = {130},\n  number = {10},\n  issn = {0027-0644},\n  doi = {10.1175/1520-0493(2002)130<2459:ANTFVC>2.0.CO;2},\n  pages = {2459--2480},\n  month = {10},\n  year = {2002}\n}\n\n@article{Schlegel2012,\n  title = {Implementation of multirate time integration methods for air pollution modelling},\n  author = {Schlegel, M and Knoth, O and Arnold, M and Wolke, R},\n  journal = {Geoscientific Model Development},\n  volume = {5},\n  number = {6},\n  pages = {1395--1405},\n  year = {2012},\n  doi = {10.5194/gmd-5-1395-2012},\n  publisher = {Copernicus GmbH}\n}\n\n@article{Schlegel2009,\n  title = {Multirate Runge-Kutta schemes for advection equations},\n  author = {Martin Schlegel and Oswald Knoth and Martin Arnold and Ralf Wolke},\n  journal = {Journal of Computational and Applied Mathematics},\n  year = {2009},\n  month = {apr},\n  publisher = {Elsevier {BV}},\n  volume = {226},\n  number = {2},\n  doi = {10.1016/j.cam.2008.08.009},\n  pages = {345--357}\n}\n\n@article{Seifert2006,\n  title = {A two-moment cloud microphysics parameterization for mixed-phase clouds. Part 1: Model description},\n  author = {Seifert, Axel and Beheng, Klaus Dieter},\n  journal = {Meteorology and atmospheric physics},\n  volume = {92},\n  number = {1-2},\n  pages = {45--66},\n  year = {2006},\n  doi = {10.1007/s00703-005-0112-4},\n  publisher = {Springer}\n}\n\n@article{Shu1988,\n  title = {Efficient implementation of essentially non-oscillatory shock-capturing schemes},\n  author = {Shu, Chi-Wang and Osher, Stanley},\n  journal = {Journal of computational physics},\n  volume = {77},\n  number = {2},\n  pages = {439--471},\n  year = {1988},\n  doi = {10.1016/0021-9991(88)90177-5},\n  publisher = {Elsevier}\n}\n\n@article{Siebesma2003,\n  title = {A large eddy simulation intercomparison study of shallow cumulus convection},\n  author = {Siebesma, A Pier and Bretherton, Christopher S and Brown, Andrew and Chlond, Andreas and Cuxart, Joan and Duynkerke, Peter G and Jiang, Hongli and Khairoutdinov, Marat and Lewellen, David and Moeng, Chin-Hoh and others},\n  journal = {Journal of the Atmospheric Sciences},\n  volume = {60},\n  number = {10},\n  doi = {10.1175/1520-0469(2003)60<1201:ALESIS>2.0.CO;2},\n  pages = {1201--1219},\n  year = {2003}\n}\n\n@inproceedings{Skilling2004,\n  title = {Programming the Hilbert curve},\n  author = {Skilling, John},\n  booktitle = {AIP Conference Proceedings},\n  volume = {707},\n  number = {1},\n  pages = {381--387},\n  year = {2004},\n  doi = {10.1063/1.1751381},\n  organization = {American Institute of Physics}\n}\n\n@article{Smagorinsky1963,\n  title = {General circulation experiments with the primitive equations: I. The basic experiment},\n  author = {Smagorinsky, Joseph},\n  journal = {Monthly weather review},\n  volume = {91},\n  number = {3},\n  doi = {10.1175/1520-0493(1963)091<0099:GCEWTP>2.3.CO;2},\n  pages = {99--164},\n  year = {1963}\n}\n\n@article{Spiteri2002,\n  title = {A new class of optimal high-order strong-stability-preserving time discretization methods},\n  author = {Spiteri, Raymond J and Ruuth, Steven J},\n  journal = {SIAM Journal on Numerical Analysis},\n  volume = {40},\n  number = {2},\n  pages = {469--491},\n  year = {2002},\n  doi = {10.1137/S0036142901389025},\n  publisher = {SIAM}\n}\n\n@article{Stevens2005,\n  title = {Evaluation of large-eddy simulations via observations of nocturnal marine stratocumulus},\n  author = {Stevens, Bjorn and Moeng, Chin-Hoh and Ackerman, Andrew S and Bretherton, Christopher S and Chlond, Andreas and de Roode, Stephan and Edwards, James and Golaz, Jean-Christophe and Jiang, Hongli and Khairoutdinov, Marat and others},\n  journal = {Monthly weather review},\n  volume = {133},\n  number = {6},\n  doi = {10.1175/MWR2930.1},\n  pages = {1443--1462},\n  year = {2005}\n}\n\n@article{Straka1993,\n  title = {Numerical solutions of a non-linear density current: A benchmark solution and comparisons},\n  author = {Straka, Jerry M and Wilhelmson, Robert B and Wicker, Louis J and Anderson, John R and Droegemeier, Kelvin K},\n  journal = {International Journal for Numerical Methods in Fluids},\n  volume = {17},\n  number = {1},\n  doi = {10.1002/fld.1650170103},\n  pages = {1--22},\n  year = {1993},\n  publisher = {Wiley Online Library}\n}\n\n@article{Taylor1937,\n  author = {Taylor, G. I. and Green, A. E.},\n  title = {Mechanisms of production of small eddies from large ones},\n  journal = {Proc. Roy. Soc. A},\n  volume = {158},\n  number = {895},\n  year = {1937},\n  doi = {doi.org/10.1098/rspa.1937.0036},\n}\n\n@article{Tomita2008,\n  title = {A New Approach to Atmospheric General Circulation Model: Global Cloud Resolving Model {NICAM} and its Computational Performance},\n  author = {Hirofumi Tomita and Koji Goto and Masaki Satoh},\n  journal = {{SIAM} Journal on Scientific Computing},\n  year = {2008},\n  month = {jan},\n  publisher = {Society for Industrial and Applied Mathematics ({SIAM})},\n  volume = {30},\n  number = {6},\n  doi = {10.1137/070692273},\n  pages = {2755--2776}\n}\n\n@book{Toro2013,\n  title = {Riemann solvers and numerical methods for fluid dynamics: a practical introduction},\n  author = {Toro, Eleuterio F},\n  year = {2013},\n  publisher = {Springer Science & Business Media}\n}\n\n@article{Ullrich2014,\n  title = {A proposed baroclinic wave test case for deep-and shallow-atmosphere dynamical cores},\n  author = {Ullrich, Paul A and Melvin, Thomas and Jablonowski, Christiane and Staniforth, Andrew},\n  journal = {Quarterly Journal of the Royal Meteorological Society},\n  volume = {140},\n  number = {682},\n  pages = {1590--1602},\n  year = {2014},\n  doi = {10.1002/qj.2241},\n  publisher = {Wiley Online Library}\n}\n\n@article{Vreman2004,\n  title = {An eddy-viscosity subgrid-scale model for turbulent shear flow: Algebraic theory and applications},\n  author = {Vreman, AW},\n  journal = {Physics of fluids},\n  volume = {16},\n  number = {10},\n  pages = {3670--3681},\n  year = {2004},\n  doi = {10.1063/1.1785131},\n  publisher = {AIP}\n}\n\n@article{Vreugdenhil2018,\n  title = {Large-eddy simulations of stratified plane Couette flow using the anisotropic minimum-dissipation model},\n  author = {Vreugdenhil, Catherine A and Taylor, John R},\n  journal = {Physics of Fluids},\n  volume = {30},\n  number = {8},\n  pages = {085104},\n  year = {2018},\n  doi = {10.1063/1.5037039},\n  publisher = {AIP Publishing LLC}\n}\n\n@article{Wang2006,\n  title = {Probability distributions of angle of approach and relative velocity for colliding droplets in a turbulent flow},\n  author = {Wang, Lian-Ping and Franklin, Charmaine N and Ayala, Orlando and Grabowski, Wojciech W},\n  journal = {Journal of the atmospheric sciences},\n  volume = {63},\n  number = {3},\n  pages = {881--900},\n  doi = {10.1175/JAS3655.1},\n  year = {2006}\n}\n\n@article{Webb2017,\n  author = {Webb, M. J. and Andrews, T. and Bodas-Salcedo, A. and Bony, S. and Bretherton, C. S. and Chadwick, R. and Chepfer, H. and Douville, H. and Good, P. and Kay, J. E. and Klein, S. A. and Marchand, R. and Medeiros, B. and Siebesma, A. P. and Skinner, C. B. and Stevens, B. and Tselioudis, G. and Tsushima, Y. and Watanabe, M.},\n  title = {The Cloud Feedback Model Intercomparison Project (CFMIP) contribution to CMIP6},\n  journal = {Geoscientific Model Development},\n  volume = {10},\n  year = {2017},\n  number = {1},\n  pages = {359--384},\n  url = {https://www.geosci-model-dev.net/10/359/2017/},\n  doi = {10.5194/gmd-10-359-2017},\n}\n\n@article{WenschKnothGalant2009,\n  author = {Wensch, J. and Knoth O., and Galant, A.},\n  title = {Multirate infinitesimal step methods for atmospheric flow simulation},\n  journal = {BIT Numerical Mathematics},\n  year = {2009},\n  volume = {49},\n  number = {2},\n  pages = {449--473},\n  doi = {10.1007/s10543-009-0222-3}\n}\n\n@article {WickerSkamarock2002,\n  author = \"Louis J. Wicker and William C. Skamarock\",\n  title = \"Time-Splitting Methods for Elastic Models Using Forward Time Schemes\",\n  journal = \"Monthly Weather Review\",\n  year = \"2002\",\n  publisher = \"American Meteorological Society\",\n  volume = \"130\",\n  number = \"8\",\n  doi = \"10.1175/1520-0493(2002)130<2088:TSMFEM>2.0.CO;2\",\n  pages= \"2088--2097\",\n}\n\n@article{Wiin1967,\n  title = {On the annual variation and spectral distribution of atmospheric energy 1},\n  author = {WIIN-NIELSEN, Aksel},\n  journal = {Tellus},\n  volume = {19},\n  number = {4},\n  doi = {10.3402/tellusa.v19i4.9822},\n  pages = {540--559},\n  year = {1967},\n  publisher = {Wiley Online Library}\n}\n\n@article{Williamson1992,\n  title = {A standard test set for numerical approximations to the shallow water equations in spherical geometry},\n  author = {Williamson, David L and Drake, John B and Hack, James J and Jakob, R{\\\"u}diger and Swarztrauber, Paul N},\n  journal = {Journal of Computational Physics},\n  volume = {102},\n  number = {1},\n  doi = {10.1016/S0021-9991(05)80016-6},\n  pages = {211--224},\n  year = {1992},\n  publisher = {Elsevier}\n}\n\n@article{Wood2005,\n  title = {Drizzle in stratiform boundary layer clouds. Part II: Microphysical aspects},\n  author = {Wood, R},\n  journal = {Journal of the atmospheric sciences},\n  volume = {62},\n  number = {9},\n  pages = {3034--3050},\n  doi = {10.1175/JAS3530.1},\n  year = {2005}\n}\n\n@article{Wyngaard1975,\n  title = {Modeling the planetary boundary layer—extension to the stable case},\n  author = {Wyngaard, John C},\n  journal = {Boundary-Layer Meteorology},\n  volume = {9},\n  number = {4},\n  pages = {441--460},\n  year = {1975},\n  doi = {10.1007/BF00223393},\n  publisher = {Springer}\n}\n\n@article{Vogl2019,\n  title={Evaluation of Implicit-Explicit Additive Runge-Kutta Integrators for the HOMME-NH Dynamical Core},\n  author={Vogl, Christopher J and Steyer, Andrew and Reynolds, Daniel R and Ullrich, Paul A and Woodward, Carol S},\n  journal={Journal of Advances in Modeling Earth Systems},\n  volume={11},\n  number={12},\n  pages={4228--4244},\n  year={2019},\n  publisher={Wiley Online Library}\n}\n\n@book{Bonan19a,\naddress = {Cambridge, UK, and New York, NY, USA},\nauthor = {G. Bonan},\npublisher = {Cambridge Univ. Press},\ntitle = {Climate Change and Terrestrial Ecoystem Modeling},\nyear = {2019},\n}\n\n@article{BallandArp2005,\nauthor = {V. Balland and P. A. Arp},\njournal = {J. Env. Eng. Sci.},\npages = {549--558},\ntitle = {Modeling soil thermal conductivities over a wide range of conditions},\ndoi = {10.1139/s05-007},\nvolume = {4},\nyear = {2005}\n}\n\t\n@article{Dai2019a,\nauthor = {Yongjiu Dai et al.},\ntitle = {Evaluation of Soil Thermal Conductivity Schemes for Use in Land Surface Modeling},\njournal = {Journal of Advances in Modeling Earth Systems},\nkeywords = {Land surface modeling, Soil thermal conductivity schemes, Performance evaluation, Uncertainties analyses, Physics - Atmospheric and Oceanic Physics},\nyear = 2019,\nmonth = nov,\nvolume = {11},\nnumber = {11},\npages = {3454-3473},\ndoi = {10.1029/2019MS001723},\narchivePrefix = {arXiv},\neprint = {1908.04579},\nprimaryClass = {physics.ao-ph},\nadsurl = {https://ui.adsabs.harvard.edu/abs/2019JAMES..11.3454D},\nadsnote = {Provided by the SAO/NASA Astrophysics Data System}\n}\n\n@article{Cosby1984,\n       author = {B.J. Cosby et al.},\n        title = {A Statistical Exploration of the Relationships of Soil Moisture Characteristics to the Physical Properties of Soils},\n      journal = {Water Resources Research},\n         year = 1984,\n        month = jun,\n       volume = {20},\n       number = {6},\n        pages = {682-690},\n          doi = {10.1029/WR020i006p00682},\n       adsurl = {https://ui.adsabs.harvard.edu/abs/1984WRR....20..682C},\n      adsnote = {Provided by the SAO/NASA Astrophysics Data System}\n}\n@article{Johanson1975,\n\tauthor = {O. Johanson},\n\tjournal = {Ph.D Thesis, Cold Regions Research and Engineering Laboratory},\n\tpages = {682-690},\n\ttitle = {Thermal conductivity of soils},\n\tyear = {1977}\n\t}\n@ARTICLE{vanGenuchten1980,\n       author = {M. Th. van Genuchten},\n        title = {A Closed form Equation for Predicting the Hydraulic Conductivity of Unsaturated Soils},\n      journal = {Soil Science Society of America Journal},\n         year = 1980,\n        month = jan,\n       volume = {44},\n       number = {5},\n        pages = {892},\n          doi = {10.2136/sssaj1980.03615995004400050002x},\n       adsurl = {https://ui.adsabs.harvard.edu/abs/1980SSASJ..44..892V},\n      adsnote = {Provided by the SAO/NASA Astrophysics Data System}\n}\n\n@article{BrooksCorey1964,\n\tauthor = {R. J. Brooks and A. T. Corey},\n\tjournal = {Hydrology Papers},\n\ttitle = {Hydraulic properties of porous media},\n\tvolume = {3},\n\tyear = {1964}}\n\n@book{Corey1977,\n\taddress = {Fort Collins, CO},\n\tauthor = {A. T. Corey},\n\tpublisher = {Water Resources Publication},\n\ttitle = {Mechanics of Heterogeneous Fluids in Porous Media},\n\tyear = {1977}}\n\t\n@article{Lundin1990,\n\tauthor = {L.C. Lundin},\n\tjournal = {J. Hydrol.},\n\tpages = {289--310},\n\ttitle = {Hydraulic properties in an operational model of frozen soil },\n\tvolume = {118},\n\tyear = {1990}}\n\n@article{Woodward00a,\n\tauthor = {C. S. Woodward and C. N. Dawson},\n\tjournal = {SIAM J. Numer. Anal.},\n\tpages = {701--724},\n\ttitle = {Analysis of expanded mixed finite element methods for a nonlinear parabolic equation modeling flow into variably saturated porous media},\n\tvolume = {37},\n\tyear = {2000}}\n\n@article{Mizoguchi1990,\n\tauthor = {M. Mizoguchi},\n\tjournal = {Ph.D Thesis, University of Tokyo},\n\ttitle = {Water, heat and salt transport in freezing soil},\n\tyear = {1990}\n\t}\n@article{Hansson2004,\n\tauthor = {K. Hansson et al.},\n\tjournal = {Vadose Zone Journal},\n\tpages = {693-704},\n\ttitle = {Water Flow and Heat Transport in Frozen Soil: Numerical Solution and Freeze–Thaw Applications},\n\tvolume = {3},\n\tyear = {2004}}\n\n@article{DallAmico2011,\n\tauthor = {M. Dall'Amico et al.},\n\tjournal = {The Cryosphere},\n\tpages = {469-484},\n\ttitle = {A robust and energy-conserving model of freezing variably-saturated soil.},\n\tvolume = {5},\n\tyear = {2011}}\n@article{KurylykWatanabe2013,\n\tauthor = {B. Kurylyk and K. Watanabe},\n\tjournal = {Advances in Water Resources},\n\tpages = {160-177},\n\ttitle = {Review: The mathematical representation of freezing and thawing processes in variably-saturated, non-deformable soils.},\n\tvolume = {60},\n\tyear = {2013}}\n@article{Watanabe2011,\n\tauthor = {K. Watanabe et al.},\n\tjournal = {Annals of Glaciology},\n\ttitle = {Freezing experiments on unsaturated sand, loam, and silt loam.},\n\tvolume = {52},\n\tyear = {2011}}\n@article{Painter2011,\n\tauthor = {S. L. Painter},\n\ttitle = {Three-phase numerical model of water migration in partially frozen geological media: model formulation, validation, and application},\n\tpages = {69-85},\n\tjournal = {Computational Geosci},\n\tvolume = {15},\n\tyear = {2011}}\n\n@book{CarslawJaeger,\n\tauthor = {H. Carslaw and J. Jaeger},\n\ttitle = {Conduction of heat in solids},\n\tpublisher = {Clarendon Press Oxford},\n\tyear = {1959}}"
  },
  {
    "path": "docs/clean_build_folder.jl",
    "content": "#####\n##### make sure there are no temporary files files (e.g., *.vtu) left around from the build in `GENERATED_DIR`\n#####\n\nfile_extensions_to_remove = [\".vtu\", \".pvtu\", \".csv\", \".vtk\", \".dat\", \".nc\"]\ngenerated_files = [\n    joinpath(root, f)\n    for (root, dirs, files) in Base.Filesystem.walkdir(GENERATED_DIR)\n    for f in files\n]\nprintln(\"Generated files: $(generated_files)\")\n\nfilter!(\n    x -> any([endswith(x, y) for y in file_extensions_to_remove]),\n    generated_files,\n)\n\nprintln(\"Deleting files: $(generated_files)\")\nfor f in generated_files\n    rm(f)\nend\n"
  },
  {
    "path": "docs/list_of_apis.jl",
    "content": "####\n#### Defines list of Application Programming Interface (APIs)\n####\n\napis = [\n    \"Home\" => \"APIs/index.md\",\n    \"Driver\" => [\n        \"Top level interface\" => \"APIs/Driver/index.md\",\n        \"Checkpoint\" => \"APIs/Driver/Checkpoint.md\",\n    ],\n    \"Atmos\" => [\"AtmosModel\" => \"APIs/Atmos/AtmosModel.md\"],\n    \"Ocean\" => \"APIs/Ocean/Ocean.md\",\n    \"Land\" => [\n        \"Land Model\" => \"APIs/Land/LandModel.md\",\n        \"Runoff Parameterizations\" => \"APIs/Land/Runoff.md\",\n        \"Soil Water Parameterizations\" =>\n            \"APIs/Land/SoilWaterParameterizations.md\",\n        \"Soil Heat Parameterizations\" =>\n            \"APIs/Land/SoilHeatParameterizations.md\",\n        \"Surface Flow\" => \"APIs/Land/SurfaceFlow.md\",\n        \"Radiative Boundary Conditions\" =>\n            \"APIs/Land/RadiativeEnergyFlux.md\",\n    ],\n    \"Common\" => [\n        \"Orientations\" => \"APIs/Common/Orientations.md\",\n        \"Cartesian Domains\" => \"APIs/Common/CartesianDomains.md\",\n        \"Cartesian Fields\" => \"APIs/Common/CartesianFields.md\",\n        \"Spectra\" => \"APIs/Common/Spectra.md\",\n        \"Turbulence Closures\" => \"APIs/Common/TurbulenceClosures.md\",\n        \"Turbulence Convection\" => \"APIs/Common/TurbulenceConvection.md\",\n    ],\n    \"Balance Laws\" => [\n        \"Balance Laws\" => \"APIs/BalanceLaws/BalanceLaws.md\",\n        \"Problems\" => \"APIs/BalanceLaws/Problems.md\",\n    ],\n    \"Arrays\" => \"APIs/Arrays/Arrays.md\",\n    \"Diagnostics\" => [\n        \"Diagnostics groups\" => \"APIs/Diagnostics/Diagnostics.md\",\n        \"DiagnosticsMachine\" => \"APIs/Diagnostics/DiagnosticsMachine.md\",\n        \"StdDiagnostics\" => \"APIs/Diagnostics/StdDiagnostics.md\",\n        \"State Check\" => \"APIs/Diagnostics/StateCheck.md\",\n    ],\n    \"Input/Output\" => [\"Input/Output\" => \"APIs/InputOutput/index.md\"],\n    \"Numerics\" => [\n        \"Meshes\" => \"APIs/Numerics/Meshes/Mesh.md\",\n        \"SystemSolvers\" => \"APIs/Numerics/SystemSolvers/SystemSolvers.md\",\n        \"ODESolvers\" => \"APIs/Numerics/ODESolvers/ODESolvers.md\",\n        \"DG Methods\" => \"APIs/Numerics/DGMethods/DGMethods.md\",\n        \"Courant\" => \"APIs/Numerics/DGMethods/Courant.md\",\n        \"Numerical Fluxes\" => \"APIs/Numerics/DGMethods/NumericalFluxes.md\",\n        \"Finite Volume Reconstructions\" =>\n            \"APIs/Numerics/DGMethods/FVReconstructions.md\",\n    ],\n    \"Utilities\" => [\n        \"Variable Templates\" => \"APIs/Utilities/VariableTemplates.md\",\n        \"Single Stack Utilities\" => \"APIs/Utilities/SingleStackUtils.md\",\n        \"Tic Toc\" => \"APIs/Utilities/TicToc.md\",\n    ],\n]\n"
  },
  {
    "path": "docs/list_of_dev_docs.jl",
    "content": "####\n#### Defines list of developer documents\n####\n\ndev_docs = Any[\n    \"Coding style\" => \"DevDocs/CodeStyle.md\",\n    \"Acceptable Unicode\" => \"DevDocs/AcceptableUnicode.md\",\n    \"Model variable list\" => \"DevDocs/ModelVariableList.md\",\n    \"Model output\" => \"DevDocs/ModelOutput.md\",\n    \"Diagnostic variable list\" => \"DevDocs/DiagnosticVariableList.md\",\n    \"Custom System Image\" => \"DevDocs/SystemImage.md\",\n]\n"
  },
  {
    "path": "docs/list_of_getting_started_docs.jl",
    "content": "####\n#### Defines list of getting started documents\n####\n\ngetting_started_docs = Any[\n    \"Installation\" => \"GettingStarted/Installation.md\",\n    \"Terminology\" => \"GettingStarted/Terminology.md\",\n    \"Running\" => \"GettingStarted/RunningClimateMachine.md\",\n    \"Defaults\" => Any[\"Atmosphere model configurations\" => \"GettingStarted/Atmos.md\",],\n]\n"
  },
  {
    "path": "docs/list_of_how_to_guides.jl",
    "content": "####\n#### Defines list of how-to-guides\n####\n\nhow_to_guides = Any[\n    \"Balance Laws\" => Any[\"How to make a balance law\" => \"HowToGuides/BalanceLaws/how_to_make_a_balance_law.md\",],\n    \"Atmos\" => Any[\n        \"Reference profiles\" => \"HowToGuides/Atmos/AtmosReferenceState.md\",\n        \"Moisture model\" => \"HowToGuides/Atmos/MoistureModelChoices.md\",\n        \"Precipitation model\" => \"HowToGuides/Atmos/PrecipitationModelChoices.md\",\n        \"How to create an experiment with moisture and precipitation\" => \"HowToGuides/Atmos/MoistureAndPrecip.md\",\n    ],\n    \"Ocean\" => Any[\n    # \"Home\" => \"HowToGuides/Ocean/index.md\"\n    ],\n    \"Land\" => Any[\n    # \"Home\" => \"HowToGuides/Land/index.md\"\n    ],\n    \"Numerics\" => Any[\n        \"Meshes\" => Any[\n        # \"Home\" => \"HowToGuides/Numerics/Meshes/index.md\",\n        ],\n        \"ODE Solvers\" => Any[\"Time-integration\" => \"HowToGuides/Numerics/ODESolvers/Timestepping.md\",],\n        \"System Solvers\" => Any[\"Iterative Solvers\" => \"HowToGuides/Numerics/SystemSolvers/IterativeSolvers.md\",],\n    ],\n    \"Diagnostics\" => Any[\"Using Diagnostics\" => \"HowToGuides/Diagnostics/UsingDiagnostics.md\",],\n]\n"
  },
  {
    "path": "docs/list_of_theory_docs.jl",
    "content": "####\n#### Defines list of theory documents\n####\n\ntheory_docs = Any[\n    \"Common\" => Any[\"Turbulence Closures\" => \"Theory/Common/Turbulence.md\",],\n    \"Atmos\" => Any[\n        \"AtmosModel\" => \"Theory/Atmos/AtmosModel.md\",\n        \"EDMF Model\" => \"Theory/Atmos/EDMF_plots.md\",\n        \"Microphysics_0M\" => \"Theory/Atmos/Microphysics_0M.md\",\n        \"Microphysics_1M\" => \"Theory/Atmos/Microphysics_1M.md\",\n        \"EDMF equations\" => \"Theory/Atmos/EDMFEquations.md\",\n        \"Tracers\" => \"Theory/Atmos/Model/tracers.md\",\n    ],\n]\n"
  },
  {
    "path": "docs/list_of_tutorials.jl",
    "content": "####\n#### Defines list of tutorials given `GENERATED_DIR`\n####\n\ngenerate_tutorials =\n    parse(Bool, get(ENV, \"CLIMATEMACHINE_DOCS_GENERATE_TUTORIALS\", \"true\"))\n\ntutorials = []\n\n\n# Allow flag to skip generated\n# tutorials since this is by\n# far the slowest part of the\n# docs build.\nif generate_tutorials\n\n    # generate tutorials\n\n    include(\"pages_helper.jl\")\n\n    tutorials = [\n        \"Home\" => \"TutorialList.jl\",\n        \"BalanceLaws\" => [\n            \"Tendency specification\" =>\n                \"BalanceLaws/tendency_specification_layer.jl\",\n        ],\n        \"Atmos\" => [\n            \"Dry Idealized GCM (Held-Suarez)\" => \"Atmos/heldsuarez.jl\",\n            \"Single Element Stack Experiment (Burgers Equation)\" =>\n                \"Atmos/burgers_single_stack.jl\",\n            \"Finite Volume Single Element Stack Experiment (Burgers Equation)\" =>\n                \"Atmos/burgers_single_stack_fvm.jl\",\n            \"HEVI Single Element Stack Experiment (Burgers Equation)\" =>\n                \"Atmos/burgers_single_stack_bjfnk.jl\",\n            \"LES Experiment (Density Current)\" => \"Atmos/densitycurrent.jl\",\n            \"LES Experiment (Rising Thermal Bubble)\" =>\n                \"Atmos/risingbubble.jl\",\n            \"Linear Hydrostatic Mountain (Topography)\" =>\n                \"Atmos/agnesi_hs_lin.jl\",\n            \"Linear Non-Hydrostatic Mountain (Topography)\" =>\n                \"Atmos/agnesi_nh_lin.jl\",\n        ],\n        \"Land\" => [\n            \"Heat\" => [\"Heat Equation\" => \"Land/Heat/heat_equation.jl\"],\n            \"Soil\" => [\n                \"Hydraulic Functions\" =>\n                    \"Land/Soil/Water/hydraulic_functions.jl\",\n                \"Soil Heat Equation\" =>\n                    \"Land/Soil/Heat/bonan_heat_tutorial.jl\",\n                \"Richards Equation\" =>\n                    \"Land/Soil/Water/equilibrium_test.jl\",\n                \"Coupled Water and Heat\" =>\n                    \"Land/Soil/Coupled/equilibrium_test.jl\",\n                \"Phase Change I\" =>\n                    \"Land/Soil/PhaseChange/freezing_front.jl\",\n                \"Phase Change II\" =>\n                    \"Land/Soil/PhaseChange/phase_change_analytic_test.jl\",\n            ],\n        ],\n        \"Ocean\" => [\n            \"One-dimensional geostrophic adjustment\" =>\n                \"Ocean/geostrophic_adjustment.jl\",\n            \"Propagating mode-1 internal wave\" => \"Ocean/internal_wave.jl\",\n            \"Shear instability\" => \"Ocean/shear_instability.jl\",\n        ],\n        \"Numerics\" => [\n            \"System Solvers\" => [\n                \"Conjugate Gradient\" => \"Numerics/SystemSolvers/cg.jl\",\n                \"Batched Generalized Minimal Residual\" =>\n                    \"Numerics/SystemSolvers/bgmres.jl\",\n            ],\n            \"DG Methods\" =>\n                [\"Filters\" => \"Numerics/DGMethods/showcase_filters.jl\"],\n            \"Time-Stepping\" => [\n                \"Introduction\" => \"Numerics/TimeStepping/ts_intro.jl\",\n                \"Explicit Runge-Kutta methods\" => [\n                    \"Numerics/TimeStepping/explicit_lsrk.jl\",\n                    \"Numerics/TimeStepping/tutorial_risingbubble_config.jl\",\n                ],\n                \"Implicit-Explicit (IMEX) Additive Runge-Kutta methods\" =>\n                    [\n                        \"Numerics/TimeStepping/imex_ark.jl\",\n                        \"Numerics/TimeStepping/tutorial_acousticwave_config.jl\",\n                    ],\n                \"Multirate Runge-Kutta methods\" => [\n                    \"Numerics/TimeStepping/multirate_rk.jl\",\n                    \"Numerics/TimeStepping/tutorial_risingbubble_config.jl\",\n                ],\n                \"MIS methods\" => [\n                    \"Numerics/TimeStepping/mis.jl\",\n                    \"Numerics/TimeStepping/tutorial_acousticwave_config.jl\",\n                ],\n            ],\n        ],\n        \"Diagnostics\" => [\n            \"Debug\" => [\n                \"State Statistics Regression\" =>\n                    \"Diagnostics/Debug/StateCheck.jl\",\n            ],\n        ],\n    ]\n\n    # Prepend tutorials_dir\n    tutorials_jl = flatten_to_array_of_strings(get_second(tutorials))\n    if run_single_tutorial\n        tutorials_jl =\n            filter(x -> occursin(tutorial_string_match, x), tutorials_jl)\n        tutorials = [\"Single tutorial mode\" => tutorials_jl[1]]\n    end\n    println(\"Building literate tutorials...\")\n\n    @everywhere function generate_tutorial(tutorials_dir, tutorial)\n        rpath = relpath(dirname(tutorial), tutorials_dir)\n        rpath = rpath == \".\" ? \"\" : rpath\n        gen_dir = joinpath(GENERATED_DIR, rpath)\n        mkpath(gen_dir)\n\n        cd(gen_dir) do\n            # change the Edit on GitHub link:\n            path = relpath(clima_dir, pwd())\n            content = \"\"\"\n            # ```@meta\n            # EditURL = \"https://github.com/CliMA/ClimateMachine.jl/$(path)\"\n            # ```\n            \"\"\"\n            mdpre(str) = content * str\n            input = abspath(tutorial)\n            Literate.markdown(\n                input;\n                execute = true,\n                documenter = false,\n                preprocess = mdpre,\n            )\n        end\n    end\n\n    tutorials_dir = joinpath(@__DIR__, \"..\", \"tutorials\")\n    tutorials_jl = map(x -> joinpath(tutorials_dir, x), tutorials_jl)\n    pmap(t -> generate_tutorial(tutorials_dir, t), tutorials_jl)\n\n    # update list of rendered markdown tutorial output for mkdocs\n    ext_jl2md(x) = joinpath(basename(GENERATED_DIR), replace(x, \".jl\" => \".md\"))\n    tutorials = transform_second(x -> ext_jl2md(x), tutorials)\nend\n"
  },
  {
    "path": "docs/make.jl",
    "content": "# https://github.com/jheinen/GR.jl/issues/278#issuecomment-587090846\nENV[\"GKSwstype\"] = \"nul\"\n# some of the tutorials cannot be run on the GPU\nENV[\"CLIMATEMACHINE_SETTINGS_DISABLE_GPU\"] = true\n# avoid problems with Documenter/Literate when using `global_logger()`\nENV[\"CLIMATEMACHINE_SETTINGS_DISABLE_CUSTOM_LOGGER\"] = true\n\nusing Distributed\nusing DocumenterCitations\n\nrun_single_tutorial = false && isempty(get(ENV, \"CI\", \"\")) # never use remotely\n@everywhere source_dir = joinpath(@__DIR__, \"src\")\nif run_single_tutorial\n    tutorial_string_match = \"<tutorial_name>\"\n    single_tutorial_dir = joinpath(@__DIR__, \"transient_dir\")\n    source_dir = single_tutorial_dir\nend\nbib = CitationBibliography(joinpath(@__DIR__, \"bibliography.bib\"))\n\n@everywhere push!(LOAD_PATH, joinpath(@__DIR__, \"..\"))\n@everywhere using ClimateMachine\n@everywhere using Documenter, Literate\n\n@everywhere const clima_dir = dirname(dirname(pathof(ClimateMachine)));\n\n@everywhere GENERATED_DIR = joinpath(source_dir, \"generated\") # generated files directory\nrm(GENERATED_DIR, force = true, recursive = true)\nmkpath(GENERATED_DIR)\n\ninclude(\"list_of_getting_started_docs.jl\")      # defines `getting_started_docs`\n\ninclude(\"list_of_tutorials.jl\")                 # defines `tutorials`\n\ninclude(\"list_of_how_to_guides.jl\")             # defines `how_to_guides`\ninclude(\"list_of_apis.jl\")                      # defines `apis`\ninclude(\"list_of_theory_docs.jl\")               # defines `theory_docs`\ninclude(\"list_of_dev_docs.jl\")                  # defines `dev_docs`\n\npages = Any[\n    \"Home\" => \"index.md\",\n    \"Getting started\" => getting_started_docs,\n    \"Tutorials\" => tutorials,\n    \"How-to-guides\" => how_to_guides,\n    \"APIs\" => apis,\n    \"Contribution guide\" => \"Contributing.md\",\n    \"Theory\" => theory_docs,\n    \"Developer docs\" => dev_docs,\n    \"References\" => \"References.md\",\n]\n\nrun_single_tutorial && (pages = Any[\"Tutorials\" => tutorials])\n\nmathengine = MathJax(Dict(\n    :TeX => Dict(\n        :equationNumbers => Dict(:autoNumber => \"AMS\"),\n        :Macros => Dict(),\n    ),\n))\n\nformat = Documenter.HTML(\n    prettyurls = get(ENV, \"CI\", \"\") != \"\" ? true : false,\n    mathengine = mathengine,\n    collapselevel = 1,\n    analytics = get(ENV, \"CI\", \"\") != \"\" ? \"UA-191640394-1\" : \"\",\n)\n\nmakedocs(\n    bib,\n    source = source_dir,\n    sitename = \"ClimateMachine\",\n    doctest = false,\n    strict = !run_single_tutorial,\n    format = format,\n    checkdocs = :exports,\n    clean = true,\n    modules = [ClimateMachine],\n    pages = pages,\n)\n\ninclude(\"clean_build_folder.jl\")\n\ndeploydocs(\n    repo = \"github.com/CliMA/ClimateMachine.jl.git\",\n    target = \"build\",\n    push_preview = true,\n    forcepush = true,\n)\n"
  },
  {
    "path": "docs/pages_helper.jl",
    "content": "\"\"\"\n    get_second\n\nGets `second` in nested array of `Pair`s\nand filters empty entries.\n\"\"\"\nfunction get_second end\n\nget_second(x::String) = x\nget_second(x::Pair{String, String}) = x.second\nget_second(x::Pair{String, T}) where {T} = get_second(x.second)\nget_second(A::Array{T}) where {T} =\n    filter(y -> !isempty(y), [get_second(x) for x in A if !isempty(x)])\n\n\"\"\"\n    flatten_to_array_of_strings(A)\n\nRecursive `flatten` of nested array of strings.\n\"\"\"\nfunction flatten_to_array_of_strings(A)\n    V = String[]\n    for a in A\n        if a isa String\n            push!(V, a)\n        else\n            push!(V, flatten_to_array_of_strings(a)...)\n        end\n    end\n    return V\nend\n\n\"\"\"\n    transform_second\n\nTransform `second` in nested array of `Pair`s.\n\"\"\"\nfunction transform_second end\n\ntransform_second(transform, x::String) = transform(x)\ntransform_second(transform, x::Pair{String, String}) =\n    Pair(x.first, transform_second(transform, x.second))\ntransform_second(transform, x::Pair{String, T}) where {T} =\n    Pair(x.first, transform_second(transform, x.second))\ntransform_second(transform, A::Array{T}) where {T} =\n    Any[transform_second(transform, x) for x in A]\n"
  },
  {
    "path": "docs/plothelpers.jl",
    "content": "using Plots\nusing KernelAbstractions: CPU\nusing ClimateMachine.MPIStateArrays: array_device\nusing ClimateMachine.BalanceLaws: Prognostic, Auxiliary, GradientFlux\n\n\"\"\"\n    plot_friendly_name(ϕ)\n\nGet plot-friendly string, since many Unicode\ncharacters do not render in plot labels.\n\"\"\"\nfunction plot_friendly_name(ϕ)\n    s = ϕ\n    s = replace(s, \"ρ\" => \"rho\")\n    s = replace(s, \"α\" => \"alpha\")\n    s = replace(s, \"∂\" => \"partial\")\n    s = replace(s, \"∇\" => \"nabla\")\n    return s\nend\n\n\"\"\"\n    export_plot(\n        z,\n        time_data,\n        dons_arr::Array,\n        ϕ_all,\n        filename;\n        xlabel,\n        ylabel,\n        time_units = \"[s]\",\n        round_digits = 2,\n        horiz_layout = false,\n        kwargs...\n    )\n\nExport plot of all variables, or all\navailable time-steps in `dons_arr`.\n\"\"\"\nfunction export_plot(\n    z,\n    time_data,\n    dons_arr::Array,\n    ϕ_all,\n    filename;\n    xlabel,\n    ylabel,\n    time_units = \"[s]\",\n    round_digits = 2,\n    sample_rate = 1,\n    horiz_layout = false,\n    kwargs...,\n)\n    ϕ_all isa Tuple || (ϕ_all = (ϕ_all,))\n    single_var = ϕ_all[1] == xlabel || length(ϕ_all) == 1\n    p = plot()\n    sample = 1:sample_rate:length(time_data)\n    for (t, data) in zip(time_data[sample], dons_arr[sample])\n        for ϕ in ϕ_all\n            ϕ_string = String(ϕ)\n            ϕ_data = data[ϕ_string][:]\n            t_label = \"t=$(round(t, digits=round_digits)) $time_units\"\n            label = single_var ? t_label : \"$ϕ_string, $t_label\"\n            args = horiz_layout ? (z, ϕ_data) : (ϕ_data, z)\n            plot!(\n                args...;\n                xlabel = xlabel,\n                ylabel = ylabel,\n                label = label,\n                kwargs...,\n            )\n        end\n    end\n    savefig(filename)\nend\n\n\"\"\"\n    export_contour(\n        z,\n        time_data,\n        dons_arr::Array,\n        ϕ,\n        filename;\n        xlabel = \"time [s]\",\n        ylabel = \"z [m]\",\n        label = String(ϕ),\n        kwargs...\n    )\n\nExport contour plots given\n - `z` Array of altitude. Note: this must not include duplicate nodal points.\n - `time_data` array of time data\n - `dons_arr` an array whose elements are populated by `dict_of_nodal_states`\n - `ϕ` variable to contour\n - `filename` file name to export to.\n - `xlabel` x-label\n - `ylabel` y-label\n - `label` contour labels\n\"\"\"\nfunction export_contour(\n    z,\n    time_data,\n    dons_arr::Array,\n    ϕ,\n    filename;\n    xlabel = \"time [s]\",\n    ylabel = \"z [m]\",\n    label = String(ϕ),\n    kwargs...,\n)\n    ϕ_string = String(ϕ)\n    ϕ_data = hcat([data[ϕ_string][:] for data in dons_arr]...)\n    args = (time_data, z, ϕ_data)\n    try\n        contourf(\n            args...;\n            xlabel = xlabel,\n            ylabel = ylabel,\n            label = label,\n            c = :viridis,\n            kwargs...,\n        )\n        savefig(filename)\n    catch\n        @warn \"Contour plot $label failed. Perhaps the field is all zeros\"\n    end\nend\n\nfunction save_binned_surface_plots(\n    x,\n    y,\n    z,\n    title,\n    filename,\n    n_plots = (3, 3),\n    z_label_prefix = \"z\",\n    n_digits = 5,\n)\n    n_z_partitions = prod(n_plots)\n    z_min_global = min(z...)\n    z_max_global = max(z...)\n    Δz = (z_max_global - z_min_global) / n_z_partitions\n    z_min = ntuple(i -> z_min_global + (i - 1) * Δz, n_z_partitions)\n    z_max = ntuple(i -> z_min_global + (i) * Δz, n_z_partitions)\n    p = []\n    for i in 1:n_z_partitions\n        mask = z_min[i] .<= z .<= z_max[i]\n        x_i = x[mask]\n        y_i = y[mask]\n        sz_min = string(z_min[i])[1:min(n_digits, length(string(z_min[i])))]\n        sz_max = string(z_max[i])[1:min(n_digits, length(string(z_max[i])))]\n        p_i = plot(\n            x_i,\n            y_i,\n            title = \"$(title), in ($sz_min, $sz_max)\",\n            seriestype = :scatter,\n            markersize = 5,\n        )\n        push!(p, p_i)\n    end\n    plot(p..., layout = n_plots, legend = false)\n    savefig(filename)\nend;\n\nstate_prefix(::Prognostic) = \"prog_\"\nstate_prefix(::Auxiliary) = \"aux_\"\nstate_prefix(::GradientFlux) = \"grad_flux_\"\n\n\"\"\"\n    export_state_plots(\n        solver_config,\n        dons_arr,\n        time_data,\n        output_dir;\n        state_types = (Prognostic(), Auxiliary()),\n        z = Array(get_z(solver_config.dg.grid)),\n        sample_rate = 1,\n        time_units = \"[s]\",\n        ylabel = \"z [m]\",\n        kwargs...\n    )\n\nExport line plots of states given\n - `solver_config` a `SolverConfiguration`\n - `dons_arr` an array of dictionaries, returned from `dict_of_nodal_states`\n - `time_data` an array of time values\n - `output_dir` output directory\n\"\"\"\nfunction export_state_plots(\n    solver_config,\n    dons_arr,\n    time_data,\n    output_dir;\n    state_types = (Prognostic(), Auxiliary()),\n    z = Array(get_z(solver_config.dg.grid)),\n    sample_rate = 1,\n    time_units = \"[s]\",\n    ylabel = \"z [m]\",\n    kwargs...,\n)\n    FT = eltype(solver_config.Q)\n    mkpath(output_dir)\n    for st in state_types\n        vs = vars_state(solver_config.dg.balance_law, st, FT)\n        for fn in flattenednames(vs)\n            base_name = state_prefix(st) * replace(fn, \".\" => \"_\")\n            file_name = joinpath(output_dir, \"$(base_name).png\")\n            export_plot(\n                z,\n                time_data,\n                dons_arr,\n                (fn,),\n                file_name;\n                xlabel = fn,\n                sample_rate = sample_rate,\n                ylabel = ylabel,\n                time_units = time_units,\n                round_digits = 5,\n                kwargs...,\n            )\n        end\n    end\nend\n\n\"\"\"\n    export_state_contours(\n        solver_config,\n        dons_arr,\n        time_data,\n        output_dir;\n        state_types = (Prognostic(),),\n        xlabel = \"time [s]\",\n        ylabel = \"z [m]\",\n        z = Array(get_z(solver_config.dg.grid; rm_dupes=true)),\n        kwargs...\n    )\n\nCall `export_contour` for every\nstate variable given `state_types`.\n\"\"\"\nfunction export_state_contours(\n    solver_config,\n    dons_arr,\n    time_data,\n    output_dir;\n    state_types = (Prognostic(),),\n    xlabel = \"time [s]\",\n    ylabel = \"z [m]\",\n    z = Array(get_z(solver_config.dg.grid; rm_dupes = true)),\n    kwargs...,\n)\n    FT = eltype(solver_config.Q)\n    mkpath(output_dir)\n    for st in state_types\n        vs = vars_state(solver_config.dg.balance_law, st, FT)\n        for fn in flattenednames(vs)\n            base_name = state_prefix(st) * replace(fn, \".\" => \"_\")\n            filename = joinpath(output_dir, \"cnt_$(base_name).png\")\n            label = string(replace(fn, \".\" => \"_\"))\n            args = (z, time_data, dons_arr, fn, filename)\n            export_contour(\n                args...;\n                xlabel = xlabel,\n                ylabel = ylabel,\n                label = label,\n                kwargs...,\n            )\n        end\n    end\nend\n"
  },
  {
    "path": "docs/src/APIs/Arrays/Arrays.md",
    "content": "# Arrays\n\n```@meta\nCurrentModule = ClimateMachine.MPIStateArrays\n```\n## MPI State Arrays\n\nStorage for the state of a discretization.\n\n```@docs\nMPIStateArray\nbegin_ghost_exchange!\nend_ghost_exchange!\nweightedsum\n```\n\n## Buffers\n\n```@docs\nCMBuffers.CMBuffer\n```\n\n## Helpers\n\n```@docs\nshow_not_finite_fields\nchecked_wait\n```\n"
  },
  {
    "path": "docs/src/APIs/Atmos/AtmosModel.md",
    "content": "# [AtmosModel](@id AtmosModel-docs)\n\n```@meta\nCurrentModule = ClimateMachine\n```\n\n## AtmosProblem\n\n```@docs\nClimateMachine.Atmos.AtmosProblem\n```\n\n## AtmosModel balance law\n\n```@docs\nClimateMachine.Atmos.AtmosPhysics\nClimateMachine.Atmos.AtmosModel\n```\n\n## AtmosModel methods\n\n```@docs\nClimateMachine.BalanceLaws.flux_first_order!(m::AtmosModel, flux::Grad, state::Vars, aux::Vars, t::Real, direction)\nClimateMachine.BalanceLaws.flux_second_order!(atmos::AtmosModel, flux::Grad, state::Vars, diffusive::Vars, hyperdiffusive::Vars, aux::Vars, t::Real)\nClimateMachine.BalanceLaws.init_state_auxiliary!(m::AtmosModel, state_auxiliary::MPIStateArray, grid, direction)\nClimateMachine.BalanceLaws.source!(m::AtmosModel, source::Vars, state::Vars, diffusive::Vars, aux::Vars, t::Real, direction)\nClimateMachine.BalanceLaws.init_state_prognostic!(m::AtmosModel, state::Vars, aux::Vars, localgeo, t, args...)\n```\n\n## Compressibility\n\n```@docs\nClimateMachine.Atmos.Compressible\nClimateMachine.Atmos.Anelastic1D\n```\n\n## Reference states\n\n```@docs\nClimateMachine.Atmos.HydrostaticState\nClimateMachine.Atmos.InitStateBC\nClimateMachine.Atmos.ReferenceState\nClimateMachine.Atmos.NoReferenceState\n```\n\n## Thermodynamics\n\n```@docs\nClimateMachine.Atmos.recover_thermo_state\nClimateMachine.Atmos.new_thermo_state\nClimateMachine.Atmos.recover_thermo_state_anelastic\nClimateMachine.Atmos.new_thermo_state_anelastic\n```\n\n## Moisture and Precipitation\n\n```@docs\nClimateMachine.Atmos.DryModel\nClimateMachine.Atmos.EquilMoist\nClimateMachine.Atmos.NonEquilMoist\nClimateMachine.Atmos.NoPrecipitation\nClimateMachine.Atmos.RainModel\nClimateMachine.Atmos.RainSnowModel\n```\n\n## Stabilization\n\n```@docs\nClimateMachine.Atmos.RayleighSponge\n```\n\n## BCs\n\n```@docs\nClimateMachine.Atmos.AtmosBC\nClimateMachine.Atmos.DragLaw\nClimateMachine.Atmos.Impermeable\nClimateMachine.Atmos.PrescribedMoistureFlux\nClimateMachine.Atmos.BulkFormulaMoisture\nClimateMachine.Atmos.FreeSlip\nClimateMachine.Atmos.PrescribedTemperature\nClimateMachine.Atmos.PrescribedEnergyFlux\nClimateMachine.Atmos.NishizawaEnergyFlux\nClimateMachine.Atmos.Adiabaticθ\nClimateMachine.Atmos.BulkFormulaEnergy\nClimateMachine.Atmos.OutflowPrecipitation\nClimateMachine.Atmos.ImpermeableTracer\nClimateMachine.Atmos.Impenetrable\nClimateMachine.Atmos.Insulating\nClimateMachine.Atmos.NoSlip\nClimateMachine.Atmos.average_density\n```\n\n## Sources\n\n```@docs\nClimateMachine.Atmos.RemovePrecipitation\nClimateMachine.Atmos.CreateClouds\nClimateMachine.Atmos.WarmRain_1M\nClimateMachine.Atmos.RainSnow_1M\n```\n\n## Large-scale forcing\n```@docs\nClimateMachine.Atmos.NoLSForcing\nClimateMachine.Atmos.HadGEMVertical\n``` \n"
  },
  {
    "path": "docs/src/APIs/BalanceLaws/BalanceLaws.md",
    "content": "# Balance Laws\n\n```@meta\nCurrentModule = ClimateMachine.BalanceLaws\n```\n\n## The balance law\n\n```@docs\nBalanceLaw\n```\n\n## Tendency types and methods\n\n```@docs\nAbstractPrognosticVariable\nAbstractOrder\nFirstOrder\nSecondOrder\nAbstractTendencyType\nFlux\nSource\nTendencyDef\neq_tends\nprognostic_vars\nget_prog_state\nprojection\nprecompute\nprognostic_var_source_map\nshow_tendencies\n```\n\n## Methods for fluxes and sources\n\n```@docs\nflux\nsource\nΣfluxes\nΣsources\n```\n\n## State variable types\n\n```@docs\nAbstractStateType\nPrognostic\nPrimitive\nEntropy\nAuxiliary\nGradient\nGradientFlux\nGradientLaplacian\nHyperdiffusive\nUpwardIntegrals\nDownwardIntegrals\n```\n\n## Interface\n\n```@docs\nsub_model\n```\n\n## Variable specification methods\n\n```@docs\nvars_state\n```\n\n## Initial condition methods\n\n```@docs\ninit_state_prognostic!\ninit_state_auxiliary!\nnodal_init_state_auxiliary!\n```\n\n## Source term kernels\n\n```@docs\nflux_first_order!\nflux_second_order!\nsource!\n```\n\n## Integral kernels\n\n```@docs\nindefinite_stack_integral!\nreverse_indefinite_stack_integral!\nintegral_load_auxiliary_state!\nintegral_set_auxiliary_state!\nreverse_integral_load_auxiliary_state!\nreverse_integral_set_auxiliary_state!\n```\n\n## Gradient/Laplacian kernels\n\n```@docs\ncompute_gradient_flux!\ncompute_gradient_argument!\ntransform_post_gradient_laplacian!\n```\n\n## Boundary conditions\n\n```@docs\nboundary_conditions\nboundary_state!\n```\n\n## Auxiliary kernels\n\n```@docs\nwavespeed\nupdate_auxiliary_state!\nupdate_auxiliary_state_gradient!\nnodal_update_auxiliary_state!\n```\n"
  },
  {
    "path": "docs/src/APIs/BalanceLaws/Problems.md",
    "content": "# Problems\n\n```@meta\nCurrentModule = ClimateMachine.Problems\n```\n\n## The problem\n\n```@docs\nAbstractProblem\n```\n\n## Initial condition methods\n\n```@docs\ninit_state_prognostic!\ninit_state_auxiliary!\n```\n"
  },
  {
    "path": "docs/src/APIs/Common/CartesianDomains.md",
    "content": "# Cartesian Domains\n\n```@meta\nCurrentModule = ClimateMachine.CartesianDomains\n```\n\n```@docs\nRectangularDomain\n```\n"
  },
  {
    "path": "docs/src/APIs/Common/CartesianFields.md",
    "content": "# Cartesian Fields\n\n```@meta\nCurrentModule = ClimateMachine.CartesianFields\n```\n\n```@docs\nSpectralElementField\nRectangularElement\nassemble\n```\n"
  },
  {
    "path": "docs/src/APIs/Common/Orientations.md",
    "content": "# Orientations\n\n```@meta\nCurrentModule = ClimateMachine.Orientations\n```\n\n```@docs\nOrientations\n```\n\n## Types\n\n```@docs\nNoOrientation\nFlatOrientation\nSphericalOrientation\nsphr_to_cart_vec\ncart_to_sphr_vec\n```\n"
  },
  {
    "path": "docs/src/APIs/Common/Spectra.md",
    "content": "# Spectra\n\n```@meta\nCurrentModule = ClimateMachine.Spectra\n```\n\n## Methods\n\n```@docs\npower_spectrum_1d\npower_spectrum_2d\npower_spectrum_3d\n```\n"
  },
  {
    "path": "docs/src/APIs/Common/TurbulenceClosures.md",
    "content": "# TurbulenceClosures\n\n```@meta\nCurrentModule = ClimateMachine.TurbulenceClosures\n```\n\n```@docs\nTurbulenceClosures\n```\n\n## Turbulence Closure Model Constructors\n\n```@docs\nTurbulenceClosureModel\nWithDivergence\nWithoutDivergence\nConstantViscosity\nConstantDynamicViscosity\nConstantKinematicViscosity\nSmagorinskyLilly\nVreman\nAnisoMinDiss\nHyperDiffusion\nNoHyperDiffusion\nDryBiharmonic\nEquilMoistBiharmonic\nViscousSponge\nNoViscousSponge\nUpperAtmosSponge\n```\n\n## Supporting Methods\n\n```@docs\nturbulence_tensors\ninit_aux_turbulence!\nprincipal_invariants\nsymmetrize\nnorm2\nstrain_rate_magnitude\n```\n"
  },
  {
    "path": "docs/src/APIs/Common/TurbulenceConvection.md",
    "content": "# Turbulence Convection\n\n```@meta\nCurrentModule = ClimateMachine\n```\n\n```@docs\nTurbulenceConvection\n```\n\n## Models\n\n```@docs\nTurbulenceConvection.NoTurbConv\n```\n\n## Boundary conditions\n\n```@docs\nTurbulenceConvection.NoTurbConvBC\n```\n"
  },
  {
    "path": "docs/src/APIs/Diagnostics/Diagnostics.md",
    "content": "# [Diagnostics](@id Diagnostics-docs)\n\n```@meta\nCurrentModule = ClimateMachine.Diagnostics\n```\n\n```@docs\nDiagnostics\n```\n\n### Types\n\n```@docs\nDiagnostics.DiagnosticsGroup\n```\n\n### [Diagnostics groups](@id Diagnostics-groups)\n\nA `ClimateMachine` driver may use any number of the methods described below\nto create `DiagnosticsGroup`s which must be specified to the `ClimateMachine`\nin a `DiagnosticsConfiguration` in order to be used.\n\n```@docs\nDiagnostics.setup_atmos_default_diagnostics\nDiagnostics.setup_atmos_core_diagnostics\nDiagnostics.setup_atmos_default_perturbations\nDiagnostics.setup_atmos_refstate_perturbations\nDiagnostics.setup_atmos_turbulence_stats\nDiagnostics.setup_atmos_mass_energy_loss\nDiagnostics.setup_atmos_spectra_diagnostics\nDiagnostics.setup_dump_state_diagnostics\nDiagnostics.setup_dump_aux_diagnostics\nDiagnostics.setup_dump_tendencies_diagnostics\n```\n"
  },
  {
    "path": "docs/src/APIs/Diagnostics/DiagnosticsMachine.md",
    "content": "# [DiagnosticsMachine](@id DiagnosticsMachine)\n\n```@meta\nCurrentModule = ClimateMachine.DiagnosticsMachine\n```\n\n```@docs\nDiagnosticsMachine\nDiagnosticsMachine.init\n```\n\n### Diagnostic variables\n\n```@docs\nDiagnosticsMachine.DiagnosticVar\n```\n\n### Kinds of diagnostic variables\n\n```@docs\nDiagnosticsMachine.PointwiseDiagnostic\nDiagnosticsMachine.HorizontalAverage\n```\n\n### Creating diagnostic variables\n\n```@docs\nDiagnosticsMachine.@pointwise_diagnostic\nDiagnosticsMachine.@horizontal_average\n```\n\nThese use:\n\n```@docs\nDiagnosticsMachine.generate_dv_interface\nDiagnosticsMachine.generate_dv_function\nDiagnosticsMachine.generate_dv_scale\nDiagnosticsMachine.generate_dv_project\n```\n\nTo generate:\n\n```@docs\nDiagnosticsMachine.dv_name\nDiagnosticsMachine.dv_attrib\nDiagnosticsMachine.dv_args\nDiagnosticsMachine.dv_scale\nDiagnosticsMachine.dv_project\n```\n\nDiagnostic variable implementations must use the following type in\ntheir arguments:\n\n```@docs\nDiagnosticsMachine.States\n```\n\n### Creating diagnostics groups\n\n```@docs\nDiagnosticsMachine.@diagnostics_group\n```\n\n### Defining new diagnostic variable kinds\n\nNew diagnostic variable kinds are being added. Currently, these\nmust define the following functions (this list and the semantics\nof these functions are subject to change).\n\n```@docs\nDiagnosticsMachine.dv_dg_points_length\nDiagnosticsMachine.dv_dg_points_index\nDiagnosticsMachine.dv_dg_elems_length\nDiagnosticsMachine.dv_dg_elems_index\nDiagnosticsMachine.dv_dg_dimnames\nDiagnosticsMachine.dv_dg_dimranges\nDiagnosticsMachine.dv_op\nDiagnosticsMachine.dv_reduce\n```\n"
  },
  {
    "path": "docs/src/APIs/Diagnostics/StateCheck.md",
    "content": "# State Check\n\n```@meta\nCurrentModule = ClimateMachine\n```\n\n```@docs\nClimateMachine.StateCheck\n```\n\n## Methods\n\n```@docs\nClimateMachine.StateCheck.sccreate\nClimateMachine.StateCheck.scdocheck\nClimateMachine.StateCheck.scprintref\n```\n"
  },
  {
    "path": "docs/src/APIs/Diagnostics/StdDiagnostics.md",
    "content": "# [StdDiagnostics](@id StdDiagnostics)\n\n```@meta\nCurrentModule = ClimateMachine.StdDiagnostics\n```\n\n```@docs\nStdDiagnostics\n```\n\n### Available groups\n\n#### StdDiagnostics.AtmosLESDefault\n\nNote that this group does not currently density-average the output.\n\nIncluded diagnostic variables:\n- `u`: x-velocity\n- `v`: y-velocity\n- `w`: z-velocity\n- `avg\\_rho`: air density (_not_ density-averaged)\n- `rho`: air density\n- `temp`: air temperature\n- `pres`: air pressure\n- `thd`: dry potential temperature\n- `et`: total specific energy\n- `ei`: specific internal energy\n- `ht`: specific enthalpy based on total energy\n- `hi`: specific enthalpy based on internal energy\n- `w\\_ht\\_sgs`: vertical sgs flux of total specific enthalpy\n\nWhen a non-`DryModel` moisture model is used:\n- `qt`: mass fraction of total water in air\n- `ql`: mass fraction of liquid water in air\n- `qi`: mass fraction of ice in air\n- `qv`: mass fraction of water vapor in air\n- `thv`: virtual potential temperature\n- `thl`: liquid-ice potential temperature\n- `w\\_qt\\_sgs`: vertical sgs flux of total specific humidity\n\nSome variable products, to allow computing variances and co-variances:\n- `uu`\n- `vv`\n- `ww`\n- `www`\n- `eiei`\n- `wu`\n- `wv`\n- `wrho`\n- `wthd`\n- `wei`\n- `qtqt`\n- `thlthl`\n- `wqt`\n- `wql`\n- `wqi`\n- `wqv`\n- `wthv`\n- `wthl`\n- `qtthl`\n- `qtei`\n\n#### StdDiagnostics.AtmosGCMDefault\n\nIncluded diagnostic variables:\n- `u`: zonal wind\n- `v`: meridional wind\n- `w`: vertical wind\n- `rho`: air density\n- `temp`: air temperature\n- `pres`: air pressure\n- `thd`: dry potential temperature\n- `et`: total specific energy\n- `ei`: specific internal energy\n- `ht`: specific enthalpy based on total energy\n- `hi`: specific enthalpy based on internal energy\n- `vort`: vertical component of relative vorticity\n- `vort2`: vertical component of relative vorticity from DGModel kernels via a mini balance law\n\nWhen a non-`DryModel` moisture model is used:\n- `qt`: mass fraction of total water in air\n- `ql`: mass fraction of liquid water in air\n- `qv`: mass fraction of water vapor in air\n- `qi`: mass fraction of ice in air\n- `thv`: virtual potential temperature\n- `thl`: liquid-ice potential temperature\n"
  },
  {
    "path": "docs/src/APIs/Driver/Checkpoint.md",
    "content": "# Checkpoint\n\n```@meta\nCurrentModule = ClimateMachine.Checkpoint\n```\n\n## Methods\n\n```@docs\nrm_checkpoint\nread_checkpoint\nwrite_checkpoint\n```\n"
  },
  {
    "path": "docs/src/APIs/Driver/index.md",
    "content": "# Driver\n\n```@meta\nCurrentModule = ClimateMachine\n```\n\n## Solver types and functions\n\n```@docs\nHEVISplitting\nMISSolverType\nMultirateSolverType\nAbstractSolverType\nDiscreteSplittingType\nExplicitSolverType\nImplicitSolverType\nSplitExplicitSolverType\nConfigTypes\nIMEXSolverType\nHEVISolverType\ngetdtmodel\n```\n\n## Configurations\n\n```@docs\nDriverConfiguration\nSolverConfiguration\nInterpolationConfiguration\nDiagnosticsConfiguration\nConservationCheck\n```\n\n## Initialize / solve\n\n```@docs\narray_type\ndatetime\ninit\ninvoke!\n```\n"
  },
  {
    "path": "docs/src/APIs/InputOutput/index.md",
    "content": "# Input/Output\n\n```@meta\nCurrentModule = ClimateMachine\n```\n\n## VTK\n\n```@docs\nVTK.writevtk\nVTK.writepvtu\nVTK.VTKFieldWriter\n```\n\n## Writers\n\n```@docs\nWriters.init_data\nWriters.full_name\nWriters.append_data\n```\n"
  },
  {
    "path": "docs/src/APIs/Land/LandModel.md",
    "content": "# Land Model\n\n```@meta\nCurrentModule = ClimateMachine\n```\n## Land Model\n\n```@docs\nClimateMachine.Land.LandModel\n```\n\n## Soil\n```@docs\nClimateMachine.Land.SoilModel\nClimateMachine.Land.SoilWaterModel\nClimateMachine.Land.PrescribedWaterModel\nClimateMachine.Land.SoilHeatModel\nClimateMachine.Land.PrescribedTemperatureModel\nClimateMachine.Land.SoilWaterParameterizations\nClimateMachine.Land.SoilHeatParameterizations\nClimateMachine.Land.SoilParamFunctions\nClimateMachine.Land.WaterParamFunctions\nClimateMachine.Land.get_water_content\nClimateMachine.Land.get_temperature\nClimateMachine.Land.PhaseChange\n```\n\n## Boundary Conditions\n```@docs\nClimateMachine.Land.LandDomainBC\nClimateMachine.Land.LandComponentBC\nClimateMachine.Land.NoBC\nClimateMachine.Land.SurfaceDrivenWaterBoundaryConditions\nClimateMachine.Land.SurfaceDrivenHeatBoundaryConditions\nClimateMachine.Land.Dirichlet\nClimateMachine.Land.Neumann\n```\n"
  },
  {
    "path": "docs/src/APIs/Land/RadiativeEnergyFlux.md",
    "content": "# RadiativeEnergyFlux\n\n```@meta\nCurrentModule = ClimateMachine.Land.RadiativeEnergyFlux\n```\n\n## RadiativeEnergyFlux types and functions\n```@docs\nAbstractNetSwFluxModel\nPrescribedSwFluxAndAlbedo\nPrescribedNetSwFlux\ncompute_net_sw_flux\ncompute_net_radiative_energy_flux\n```"
  },
  {
    "path": "docs/src/APIs/Land/Runoff.md",
    "content": "# Runoff\n\n```@meta\nCurrentModule = ClimateMachine.Land.Runoff\n```\n\n## Runoff types and functions\n```@docs\nAbstractPrecipModel\nDrivenConstantPrecip\nAbstractSurfaceRunoffModel\nNoRunoff\nCoarseGridRunoff\ncompute_surface_grad_bc\n```"
  },
  {
    "path": "docs/src/APIs/Land/SoilHeatParameterizations.md",
    "content": "# Soil Heat Parameterizations\n\n```@meta\nCurrentModule = ClimateMachine.Land.SoilHeatParameterizations\n```\n\n## Heat functions\n```@docs\nvolumetric_heat_capacity\nvolumetric_internal_energy\nsaturated_thermal_conductivity\nthermal_conductivity\nrelative_saturation\nkersten_number\nk_solid\nk_dry\nksat_unfrozen\nksat_frozen\nvolumetric_internal_energy_liq\ntemperature_from_ρe_int\n```"
  },
  {
    "path": "docs/src/APIs/Land/SoilWaterParameterizations.md",
    "content": "# Soil Water Parameterizations\n\n```@meta\nCurrentModule = ClimateMachine.Land.SoilWaterParameterizations\n```\n\n## Water functions\n```@docs\nAbstractImpedanceFactor\nNoImpedance\nIceImpedance\nimpedance_factor\nAbstractViscosityFactor\nConstantViscosity    \nTemperatureDependentViscosity\nviscosity_factor\nAbstractMoistureFactor\nMoistureDependent\nMoistureIndependent\nmoisture_factor\nAbstractHydraulicsModel\nvanGenuchten\nBrooksCorey\nHaverkamp\nhydraulic_conductivity\neffective_saturation\npressure_head\nhydraulic_head\nmatric_potential\nvolumetric_liquid_fraction\ninverse_matric_potential\n```"
  },
  {
    "path": "docs/src/APIs/Land/SurfaceFlow.md",
    "content": "# SurfaceFlow\n\n```@meta\nCurrentModule = ClimateMachine.Land.SurfaceFlow\n```\n\n## SurfaceFlow types and functions\n```@docs\nOverlandFlowModel\nNoSurfaceFlowModel\nsurface_boundary_flux!\nsurface_boundary_state!\ncalculate_velocity\nPrecip\nVolumeAdvection\nSurfaceWaterHeight\n```"
  },
  {
    "path": "docs/src/APIs/Numerics/DGMethods/Courant.md",
    "content": "# Discontinuous Galerkin Methods\n\n```@meta\nCurrentModule = ClimateMachine\n```\n\n## Functions\n\n```@docs\nCourant\nCourant.advective_courant\nCourant.diffusive_courant\nCourant.nondiffusive_courant\n```\n"
  },
  {
    "path": "docs/src/APIs/Numerics/DGMethods/DGMethods.md",
    "content": "# Discontinuous Galerkin Methods\n\n```@meta\nCurrentModule = ClimateMachine.DGMethods\n```\n\n```@docs\nSpaceDiscretization\nDGModel\nESDGModel\nDGFVModel\nremainder_DGModel\nauxiliary_field_gradient!\ncourant\n```\n\n## Mathematical Formulation\n\nto be filled\n\n## Examples\n\n!!! attribution\n    The style of examples we use here is heavily inspired by\n    [`JuAFEM.jl`](https://github.com/KristofferC/JuAFEM.jl)\n\nto be filled\n\n## Custom filter\n\n```@docs\nAbstractCustomFilter\ncustom_filter!\n```\n"
  },
  {
    "path": "docs/src/APIs/Numerics/DGMethods/FVReconstructions.md",
    "content": "# Numerical Fluxes\n\n```@meta\nCurrentModule = ClimateMachine.DGMethods.FVReconstructions\n```\n\n## Types\n\n```@docs\n    AbstractReconstruction\n    AbstractSlopeLimiter\n    width\n    FVConstant\n    FVLinear\n    VanLeer\n```\n"
  },
  {
    "path": "docs/src/APIs/Numerics/DGMethods/NumericalFluxes.md",
    "content": "# Numerical Fluxes\n\n```@meta\nCurrentModule = ClimateMachine.DGMethods.NumericalFluxes\n```\n\n## Types\n\n```@docs\nNumericalFluxGradient\nRusanovNumericalFlux\nRoeNumericalFlux\nHLLCNumericalFlux\nRoeNumericalFluxMoist\nNumericalFluxFirstOrder\nNumericalFluxSecondOrder\nCentralNumericalFluxSecondOrder\nCentralNumericalFluxFirstOrder\nCentralNumericalFluxGradient\nLMARSNumericalFlux\n```\n"
  },
  {
    "path": "docs/src/APIs/Numerics/Meshes/Mesh.md",
    "content": "# Meshing Stuff\n\n```@meta\nCurrentModule = ClimateMachine.Mesh\n```\n\n## Topologies\n\nTopologies encode the connectivity of the elements, spatial domain interval and MPI\ncommunication.\n\n### Types\n\n```@docs\nTopologies.AbstractTopology\nTopologies.BoxElementTopology\nTopologies.BrickTopology\nTopologies.StackedBrickTopology\nTopologies.CubedShellTopology\nTopologies.AnalyticalTopography\nTopologies.NoTopography\nTopologies.DCMIPMountain\nTopologies.StackedCubedSphereTopology\nTopologies.SingleExponentialStretching\n```\n\n### Constructors\n\n```@docs\nTopologies.BrickTopology(mpicomm, Nelems)\nTopologies.StackedBrickTopology(mpicomm, elemrange)\nTopologies.CubedShellTopology(mpicomm, Neside, T)\nTopologies.StackedCubedSphereTopology(mpicomm, Nhorz, Rrange)\n```\n\n### Functions\n\n```@docs\nTopologies.cubedshellmesh\nTopologies.cubed_sphere_warp\nTopologies.cubed_sphere_unwarp\nTopologies.equiangular_cubed_sphere_warp\nTopologies.equiangular_cubed_sphere_unwarp\nTopologies.equidistant_cubed_sphere_warp\nTopologies.equidistant_cubed_sphere_unwarp\nTopologies.conformal_cubed_sphere_warp\nTopologies.conformal_cubed_sphere_unwarp\nTopologies.hasboundary\nTopologies.compute_lat_long\nTopologies.cubed_sphere_topo_warp\nTopologies.grid1d\n```\n\n## Geometry\n```@docs\nGeometry.LocalGeometry\nGeometry.lengthscale\nGeometry.resolutionmetric\nGeometry.lengthscale_horizontal\n```\n\n## Brick Mesh\n\n```@docs\nBrickMesh.partition\nBrickMesh.brickmesh\nBrickMesh.connectmesh\nBrickMesh.connectmeshfull\nBrickMesh.centroidtocode\n```\n\n## GeometricFactors\nGeometricFactors groups data structures that collect geometric terms data needed at each quadrature point, in each element.\n### Types\n```@docs\nGeometricFactors.VolumeGeometry\nGeometricFactors.SurfaceGeometry\n```\n\n## Metrics\n\nMetrics encode the computation of metric terms defined at each quadrature point, in each element.\n\n### Functions\n```@docs\nMetrics.creategrid!\nMetrics.compute_reference_to_physical_coord_jacobian!\nMetrics.computemetric!\n```\n\n## Grids\n\nGrids specify the approximation within each element, and any necessary warping.\n\n### Functions\n```@docs\nGrids.get_z\nGrids.referencepoints\nGrids.min_node_distance\nGrids.DiscontinuousSpectralElementGrid\nGrids.computegeometry\n```\n\n## DSS\n\nComputes the direct stiffness summation of fields in the MPIStateArray.\n\n```@docs\nDSS.dss!\nDSS.dss_vertex!\nDSS.dss_edge!\nDSS.dss_face!\n```\n\n## Filters\n\nThere are methods used to cleanup state vectors.\n\n```@docs\nFilters.CutoffFilter\nFilters.MassPreservingCutoffFilter\nFilters.BoydVandevenFilter\nFilters.ExponentialFilter\nFilters.TMARFilter\nFilters.apply!\nFilters.apply_async!\n```\n\n## Interpolation\n\n### Types\n\n```@docs\nInterpolation.InterpolationBrick\nInterpolation.InterpolationCubedSphere\n```\n\n### Functions\n\n```@docs\nInterpolation.interpolate_local!\nInterpolation.project_cubed_sphere!\nInterpolation.accumulate_interpolated_data!\n```\n"
  },
  {
    "path": "docs/src/APIs/Numerics/ODESolvers/ODESolvers.md",
    "content": "# [ODESolvers](@id ODESolvers-docs)\n\n```@meta\nCurrentModule = ClimateMachine\n```\n\n```@docs\nODESolvers\n```\n\n## Low Storage Runge Kutta methods\n\n```@docs\nODESolvers.LowStorageRungeKutta2N\nODESolvers.LSRK54CarpenterKennedy\nODESolvers.LSRK144NiegemannDiehlBusch\n```\n\n## Low Storage (3N) Runge Kutta methods\n\n```@docs\nODESolvers.LowStorageRungeKutta3N\nODESolvers.LS3NRK44Classic\nODESolvers.LS3NRK33Heuns\n```\n\n## Strong Stability Preserving RungeKutta methods\n\n```@docs\nODESolvers.StrongStabilityPreservingRungeKutta\nODESolvers.SSPRK33ShuOsher\nODESolvers.SSPRK34SpiteriRuuth\n```\n\n## Additive Runge Kutta methods\n\n```@docs\nODESolvers.AdditiveRungeKutta\nODESolvers.ARK1ForwardBackwardEuler\nODESolvers.ARK2ImplicitExplicitMidpoint\nODESolvers.ARK2GiraldoKellyConstantinescu\nODESolvers.ARK548L2SA2KennedyCarpenter\nODESolvers.ARK437L2SA1KennedyCarpenter\nODESolvers.Trap2LockWoodWeller\nODESolvers.DBM453VoglEtAl\nODESolvers.SSPRK22Ralstons\nODESolvers.SSPRK22Heuns\nODESolvers.LSRKEulerMethod\n```\n\n## Multi-rate Runge Kutta Methods\n\n```@docs\nODESolvers.MultirateRungeKutta\n```\n\n## Multi-rate Infinitesimal Step Methods\n\n```@docs\nODESolvers.TimeScaledRHS\nODESolvers.MultirateInfinitesimalStep\nODESolvers.MISRK1\nODESolvers.MIS2\nODESolvers.MISRK2a\nODESolvers.MISRK2b\nODESolvers.MIS3C\nODESolvers.MISRK3\nODESolvers.MIS4\nODESolvers.MIS4a\nODESolvers.MISKWRK43\nODESolvers.TVDMISA\nODESolvers.TVDMISB\n```\n\n## Split-explicit methods\n\n```@docs\nODESolvers.SplitExplicitSolver\n```\n\n## GARK methods\n\n```@docs\nODESolvers.MRIGARKESDIRK46aSandu\nODESolvers.MRIGARKIRK21aSandu\nODESolvers.MRIGARKESDIRK24LSA\nODESolvers.MRIGARKESDIRK34aSandu\nODESolvers.MRIGARKERK45aSandu\nODESolvers.MRIGARKExplicit\nODESolvers.MRIGARKESDIRK23LSA\nODESolvers.MRIGARKERK33aSandu\nODESolvers.MRIGARKDecoupledImplicit\n```\n\n## Euler methods\n\n```@docs\nODESolvers.LinearBackwardEulerSolver\nODESolvers.AbstractBackwardEulerSolver\nODESolvers.NonLinearBackwardEulerSolver\n```\n\n## Differential Equations\n\n```@docs\nODESolvers.DiffEqJLIMEXSolver\nODESolvers.DiffEqJLSolver\n```\n\n## ODE Solvers\n\n```@docs\nODESolvers.solve!\nODESolvers.updatedt!\nODESolvers.gettime\nODESolvers.getsteps\n```\n\n## Generic Callbacks\n\n```@docs\nGenericCallbacks\nGenericCallbacks.AtInit\nGenericCallbacks.AtInitAndFini\nGenericCallbacks.EveryXWallTimeSeconds\nGenericCallbacks.EveryXSimulationTime\nGenericCallbacks.EveryXSimulationSteps\n```\n"
  },
  {
    "path": "docs/src/APIs/Numerics/SystemSolvers/SystemSolvers.md",
    "content": "# System Solvers\n\n```@meta\nCurrentModule = ClimateMachine.SystemSolvers\n```\n\n## Non-linear solvers\n\n```@docs\nLSOnly\nJacobianAction\nJacobianFreeNewtonKrylovSolver\n```\n\n## Linear solvers\n\n### Generalized Conjugate Residual Method\n\n```@docs\nGeneralizedConjugateResidual\n```\n\n### Generalized Minimal Residual Method\n\n```@docs\nGeneralizedMinimalResidual\n```\n\n### Batched Generalized Minimal Residual Method\n\n```@docs\nBatchedGeneralizedMinimalResidual\n```\n\n### Conjugate Gradient Solver Method\n```@docs\nConjugateGradient\ninitialize!\ndoiteration!\n```\n\n### LU Decomposition\n\n```@docs\nManyColumnLU\nSingleColumnLU\n```\n\n## Preconditioners\n\n```@docs\nNoPreconditioner\nColumnwiseLUPreconditioner\n```\n\n## Shared components\n\n```@docs\nAbstractSystemSolver\nAbstractNonlinearSolver\nAbstractIterativeSystemSolver\nAbstractPreconditioner\nnonlinearsolve!\nlinearsolve!\nsettolerance!\nprefactorize\npreconditioner_update!\npreconditioner_solve!\npreconditioner_counter_update!\n```\n"
  },
  {
    "path": "docs/src/APIs/Ocean/Ocean.md",
    "content": "# Ocean Base Module\n\n```@meta\nCurrentModule = ClimateMachine.Ocean\n```\n\n# Hydrostatic Boussinesq\n\n```@meta\nCurrentModule = ClimateMachine.Ocean.HydrostaticBoussinesq\n```\n\n## Models\n\n```@docs\nHydrostaticBoussinesqModel\nLinearHBModel\n```\n\n## BCs\n\n```@docs\nOceanBC\nPenetrable\nImpenetrable\nNoSlip\nFreeSlip\nKinematicStress\nInsulating\nTemperatureFlux\n```\n# ShallowWater\n\n```@meta\nCurrentModule = ClimateMachine.Ocean.ShallowWater\n```\n\n## Models\n\n```@docs\nShallowWaterModel\n```\n\n# OceanProblems\n\n```@meta\nCurrentModule = ClimateMachine.Ocean.OceanProblems\n```\n\n## Problems\n\n```@docs\nSimpleBox\nHomogeneousBox\nOceanGyre\n```\n\n## Other (development)\n\n```@meta\nCurrentModule = ClimateMachine.Ocean\n```\n\n```@docs\nHydrostaticBoussinesqSuperModel\n```\n\n```@meta\nCurrentModule = ClimateMachine.Ocean.JLD2Writers\n```\n\n```@docs\nJLD2Writer\n```\n\n```@meta\nCurrentModule = ClimateMachine.Ocean.SplitExplicit01\n```\n\n```@docs\nSplitExplicitLSRK2nSolver\nSplitExplicitLSRK3nSolver\nOceanDGModel\n```\n"
  },
  {
    "path": "docs/src/APIs/Utilities/SingleStackUtils.md",
    "content": "# Single Stack Utils\n\n```@meta\nCurrentModule = ClimateMachine.SingleStackUtils\n```\n\n## Functions\n\n```@docs\nget_vars_from_nodal_stack\nget_vars_from_element_stack\nget_horizontal_mean\nget_horizontal_variance\nreduce_nodal_stack\nreduce_element_stack\nhorizontally_average!\ndict_of_nodal_states\nNodalStack\nsingle_stack_diagnostics\n```\n"
  },
  {
    "path": "docs/src/APIs/Utilities/TicToc.md",
    "content": "# Tic Toc\n\n```@meta\nCurrentModule = ClimateMachine\n```\n\n```@docs\nClimateMachine.TicToc\n```\n\n## Methods\n\n```@docs\nTicToc.@tic\nTicToc.@toc\nTicToc.tictoc\n```\n\n"
  },
  {
    "path": "docs/src/APIs/Utilities/VariableTemplates.md",
    "content": "# Variable Templates\n\n```@meta\nCurrentModule = ClimateMachine.VariableTemplates\n```\n\n## Types\n\n```@docs\nGrad\nVars\n```\n\n## Index methods\n\n```@docs\n@vars\nvuntuple\nunroll_map\nflattened_tup_chain\nflattened_named_tuple\nFlattenArr\nRetainArr\nvarsize\nvarsindices\nvarsindex\n```\n"
  },
  {
    "path": "docs/src/APIs/index.md",
    "content": "# Application Programming Interface (APIs)\n\nHere, references are provided for ClimateMachine's programming interface. These references aim to describe what functions do, their arguments, what is returned, and the composition of structs.\n"
  },
  {
    "path": "docs/src/Contributing.md",
    "content": "# Contributing\n\nThank you for considering contributing to the `ClimateMachine`! We encourage\nPull Requests (PRs). Please do not hesitate to ask as questions if you're\nunsure about how to help.\n\n## What to contribute?\n\n- The easiest way to contribute is by running the `ClimateMachine`, identifying\n  problems and opening issues.\n\n- You can tackle an existing issue. We have a list of good [first\n  issues](https://github.com/CliMA/ClimateMachine.jl/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).\n\n- Write an example or tutorial.\n\n- Improve documentation or comments if you found something hard to use.\n\n- Implement a new feature if you need it to use the `ClimateMachine`.\n\n## Using `git`\n\nIf you are unfamiliar with `git` and version control, the following guides\nwill be helpful:\n\n- [Atlassian (bitbucket) `git`\n  tutorials](https://www.atlassian.com/git/tutorials). A set of tips and tricks\n  for getting started with `git`.\n- [GitHub's `git` tutorials](https://try.github.io/). A set of resources from\n  GitHub to learn `git`.\n\nWe provide a brief guide here.\n\n### Identity\n\nFirst make sure `git` knows your name and email address:\n\n```\n$ git config --global user.name \"A. Climate Developer\"\n$ git config --global user.email \"a.climate.developer@eg.com\"\n```\n\n### Forks and branches\n\nCreate your own fork of the `ClimateMachine` [on\nGitHub](https://github.com/CliMA/ClimateMachine.jl) and check out your copy:\n\n```\n$ git clone https://github.com/<username>/ClimateMachine.jl.git\n$ cd ClimateMachine.jl\n$ git remote add upstream https://github.com/CliMA/ClimateMachine.jl\n```\n\nNow you have two remote repositories: `origin`, which is your fork of the\n`ClimateMachine`, and `upstream`, which is the main `ClimateMachine.jl`\nrepository.\n\nCreate a branch for your feature; this will hold your contribution:\n\n```\n$ git checkout -b <branchname>\n```\n\n#### Some useful tips\n- When you start working on a new feature branch, make sure you start from\n  master by running: `git checkout master`.\n- When you create a new branch and check it out, as in `git checkout -b <branchname>`,\n  a common convention is to make `branchname` something along the lines of\n  `<loginname>/<affected-module>-<short-description>`.\n\n### Develop your feature\n\nFollow the [Coding conventions](@ref) we use. Make sure you add tests\nfor your code in `test/` and appropriate documentation in the code and/or\nin `docs/`.\n\n#### Some useful tips\n- Once you have written some code, inspect changes by running `git status`.\n- Commit all files changed: `git commit -a` or\n- Commit selected files: `git commit <file1> <file2>` or\n- Add new files to be committed: `git add <file1> <file2>` followed by `git commit`.\n  Modified files can be added to a commit in the same way.\n- Push feature branch to the remote for review: `git push origin <branchname>`\n\nWhen your PR is ready for review, clean up your commit history by squashing\nand make sure your code is current with `ClimateMachine` master by rebasing.\n\n### Squash and rebase\n\nUse `git rebase` (not `git merge`) to sync your work:\n\n```\n$ git fetch upstream\n$ git rebase upstream/master\n```\n\nYou might find it easier to [squash your\ncommits](https://github.com/edx/edx-platform/wiki/How-to-Rebase-a-Pull-Request#squash-your-changes)\nfirst.\n\n#### Some useful tips\nWhen cleaning up your local branches, some of the following commands might be\nuseful:\n- Show local and remote-tracking branches: `git branch -a`.\n- Show available remotes: `git remote -v`.\n- Show all branches available on remote: `git ls-remote`.\nUse `git remote show origin` for a complete summary.\n- Delete a local branch: `git branch -D <branchname>` (only after merge to\n  `master` is complete).\n- Delete remote branch: `git push origin :<branchname>` (mind the colon in\n  front of the branch name).\n\nAdditionally, when debugging or inspecting the code for some potentially\nproblematic changes introduced, some of the following commands can be used:\n- Show logs: `git log`. (A more powerful version of this that can track all\n  changes, even after potential rebases, is [`git reflog`](https://git-scm.com/docs/git-reflog)).\n- Show logs for file or folder: `git log <file>`.\n- Show changes for each log: `git log -p` (add file or folder name if required).\n- Show diff with current working tree: `git diff path/to/file`.\n- Show diff with other commit: `git diff <SHA1> path/to/file`.\n- Compare version of file in two commits: `git diff <SHA1> <SHA1> path/to/file`.\n- Show changes that are in `master`, but not yet in my current branch:\n  `git log..master`.\n- Discard changes to a file which are not yet committed: `git checkout <file>`.\n  (If the file was aready staged via `git add <file>`, then use `git restore <file>`\n  first, and then `git checkout <file>` to discard local changes).\n- Discard all changes to the current working tree: `git checkout -f`.\n\n## Continuous integration\n\nIt's time to click the button to open your PR! Fill out the template and\nprovide a clear summary of what your PR does. When a PR is created or\nupdated, a set of automated tests are run on the PR in our continuous\nintegration (CI) system.\n\nA `ClimateMachine` developer will look at your PR and provide feedback!\n\n### Unit testing\n\nCurrently a number of checks are run per commit for a given PR.\n\n- `JuliaFormatter` checks if the PR is formatted with `.dev/climaformat.jl`.\n- `Documentation` rebuilds the documentation for the PR and checks if the docs\n  are consistent and generate valid output.\n- `Unit Tests` run subsets of the unit tests defined in `tests/`, using `Pkg.test()`.\n  The tests are run in parallel to ensure that they finish in a reasonable time.\n  The tests only run the latest commit for a PR, branch and will kill any stale jobs on push.\n  These tests are only run on linux (Ubuntu LTS).\n\nUnit tests are run against every new commit for a given PR,\nthe status of the unit-tests are not checked during the merge\nprocess but act as a sanity check for developers and reviewers.\nDepending on the content changed in the PR, some CI checks that\nare not necessary will be skipped.  For example doc only changes\ndo not require the unit tests to be run.\n\n### The merge process\n\nWe use [`bors`](https://bors.tech/) to manage merging PR's in the the `ClimateMachine` repo.\nIf you're a collaborator and have the necessary permissions, you can type\n`bors try` in a comment on a PR to have integration test suite run on that\nPR, or `bors r+` to try and merge the code.  Bors ensures that all integration tests\nfor a given PR always pass before merging into `master`.\n\n### Integration testing\n\nCurrently a number of checks are run during integration testing before being\nmerged into master.\n\n- `JuliaFormatter` checks if the PR is formatted with `.dev/climaformat.jl`.\n- `Documentation` checks that the documentation correctly builds for the merged PR.\n- `OS Unit Tests` checks that ClimateMachine.jl package unit tests can pass\n   on every OS supported with a pre-compiled system image (Linux, macOS, Windows).\n- `ClimateMachine-CI` computationally expensive integration testing on CPU and\n  GPU hardware using HPC cluster resources.\n\nIntegration tests are run when triggered by a reviewer through `bors`.\nIntegration tests are more computationally heavyweight than unit-tests and can\nexercise tests using accelerator hardware (GPUs).\n\nCurrently HPC cluster integration tests are run using the [Buildkite CI service](https://buildkite.com/clima/climatemachine-ci).\nTests are parallelized and run as individual [Slurm](https://slurm.schedmd.com/documentation.html)\nbatch jobs on the HPC cluster and defined in `.buildkite/pipeline.yml`.\n\nAn example integration test definition is outlined below:\n```\n  - label: \"gpu_soil_test_bc\"\n    key: \"gpu_soil_test_bc\"\n    command:\n      - \"mpiexec julia --color=yes --project test/Land/Model/test_bc.jl \"\n    agents:\n      config: gpu\n      queue: central\n      slurm_ntasks: 1\n      slurm_gres: \"gpu:1\"\n```\n\n* label / key: unique test defintion strings\n* command(s): list of one or more bash commands to run.\n* agent block:\n    - `config`: Defines `cpu` or `gpu` test environments.\n    - `queue`: HPC queue to submit the job (default `central`).\n    - `slurm_`: All `slurm_` definitions are passed through as\n       [slurm batch job cli options](https://slurm.schedmd.com/sbatch.html).\n       Ex. for the above the `slurm_ntasks: 1` is eqv. to `--ntasks=1`.\n       Flags are defined with an empty value.\n\n## Contributing Documentation\n\nDocumentation is written in Julia-flavored markdown and generated from two sources:\n```\n$CLIMATEMACHINE_HOME/docs/src\n```\nAnd [Literate.jl](https://fredrikekre.github.io/Literate.jl/v2/) tutorials:\n```\n$CLIMATEMACHINE_HOME/tutorials\n```\n\nTo locally build the documentation you need to create a new `docs` project\nto build and install the documentation related dependencies:\n\n```\ncd $CLIMATEMACHINE_HOME\njulia --project=docs/ -e 'using Pkg; Pkg.instantiate()'\njulia --project=docs docs/make.jl\n```\n\nThe makefile script will generate the appropriate markdown files and\nstatic html from both the `docs/src` and `tutorials/` directories,\nsaving the output in `docs/src/generated`.\n\n### How to generate a literate tutorial file\n\nTo create a tutorial using ClimateMachine, please use\n[Literate.jl](https://github.com/fredrikekre/Literate.jl),\nand consult the [Literate documentation](https://fredrikekre.github.io/Literate.jl/stable/)\nfor questions. For now, all literate tutorials are held in\nthe `tutorials` directory.\n\nWith Literate, all comments turn into markdown text and any\nJulia code is read and run *as if it is in the Julia REPL*.\nAs a small caveat to this, you might need to suppress the\noutput of certain commands. For example, if you define and\nrun the following function\n\n```\nfunction f()\n    return x = [i * i for i in 1:10]\nend\n\nx = f()\n```\n\nThe entire list will be output, while\n\n```\nf();\n```\n\ndoes not (because of the `;`).\n\nTo show plots, you may do something like the following:\n\n```\nusing Plots\nplot(x)\n```\n\nPlease consider writing the comments in your tutorial as if they are meant to\nbe read as an *article explaining the topic the tutorial is meant to explain.*\nIf there are any specific nuances to writing Literate documentation for ClimateMachine, please let us know!\n\n\n### Speeding up the documentation build process\nBuilding the tutorials can take a long time so there is an environment variable\nswitch to toggle on / off building the tutorials (`true` deafult):\n\n```\nCLIMATEMACHINE_DOCS_GENERATE_TUTORIALS=false julia --project=docs/ docs/make.jl\n```\n"
  },
  {
    "path": "docs/src/DevDocs/AcceptableUnicode.md",
    "content": "# Acceptable Unicode characters\n\nUsing Unicode seems to be irresistible. However, we must ensure avoiding\nproblematic Unicode usage.\n\nBelow is a list of acceptable Unicode characters.  **All characters not\nlisted below are forbidden**. We forbid the use of accents (dot, hat, vec,\netc.), because this can lead to visually ambiguous characters.\n\n## Acceptable lower-case Greek letters\n\n```\nα # (alpha)\nβ # (beta)\nδ # (delta)\nϵ # (epsilon)\nε # (varepsilon)\nγ # (gamma)\nκ # (kappa)\nλ # (lambda)\nμ # (mu)\nν # (nu)\nη # (eta)\nω # (omega)\nπ # (pi)\nρ # (rho)\nσ # (sigma)\nθ # (theta)\nχ # (chi)\nξ # (xi)\nζ # (zeta)\nϕ # (psi)\nφ # (varphi)\n```\n\n## Acceptable upper-case Greek letters\n\n```\nΔ # (Delta)\n∑ # (Sigma)\nΓ # (Gamma)\nΩ # (Omega)\nΨ # (Psi)\nΦ # (Phi)\n```\n\n## Acceptable mathematical symbols\n\n```\n∫ # (int)\n∬ # (iint)\n∭ # (iiint)\n∞ # (infinity)\n≈ # (approx)\n∂ # (partial)\n∇ # (nabla/del), note that nabla and del are indistinguishable\n∀ # (forall)\n≥ # (greater than equal to)\n≤ # (less than equal to)\n```\n"
  },
  {
    "path": "docs/src/DevDocs/CodeStyle.md",
    "content": "# Coding conventions\n\nFor the most part, we follow the\n[YASGuide](https://github.com/jrevels/YASGuide). Some key considerations:\n\n- Limit use of Unicode as described in\n  [Acceptable Unicode characters](@ref).\n\n- Modules and struct names should follow TitleCase convention.\n\n- Function names should be lowercase with words separated by underscores as\n  necessary to improve readability.\n\n- Variable names follow the format used in the [ClimateMachine Variable List](@ref).\n  In addition, follow CMIP conventions where possible and practicable.\n\n- Document design and purpose rather than mechanics and implementation\n  (document interfaces and embed documentation in code).\n\n- Avoid variable names that coincide with module and struct names, as well as\n  function/variable names that are natively supported.\n\n- Never use the characters `l` (lowercase letter 'el'), `O` (uppercase letter\n  'oh'), or `I` (uppercase letter 'eye') as single character variable names.\n\n- Try to limit all lines to a maximum of 78 characters.\n\n- `import`/`using` should be grouped in the following order:\n  - Standard library imports.\n  - Related third party imports.\n  - Local application/library specific imports.\n  - Use a blank line between each group of imports.\n\n## Use `JuliaFormatter`\n\nOnce you are happy with your PR, apply our\n[JuliaFormatter.jl](https://domluna.github.io/JuliaFormatter.jl/stable/) settings to\nall changed files in the repository from the top-level `ClimateMachine`\ndirectory:\n```\njulia .dev/climaformat.jl <list of changed files>\n```\nThis is easiest done by installing our formatting `githook`.\n\n### Formatting `githook`\n\nA `pre-commit` script can be placed in `$GIT_DIR/hooks/*` which will prevent\ncommits of incorrectly formatted Julia code.  It will also provide\ninstructions on how to format the code correctly.\n\nInstall the script with:\n\n```\n$ ln -s ../../.dev/hooks/pre-commit .git/hooks\n```\n\nThen, when you run `git commit`, an error message will be shown for staged\nJulia files that are not formatted correctly. For example, if you try to commit\nchanges to `src/Arrays/MPIStateArrays.jl` that are not formatted correctly:\n\n```\n❯ git commit                                                                                                           │\nActivating environment at `~/research/code/ClimateMachine.jl/.dev/Project.toml`                                        │\n┌ Error: File src/Arrays/MPIStateArrays.jl needs to be indented with:                                                  │\n│     julia /home/lucas/research/code/ClimateMachine.jl/.dev/climaformat.jl /home/lucas/research/code/ClimateMachine.jl│\n/src/Arrays/MPIStateArrays.jl                                                                                          │\n│ and added to the git index via                                                                                       │\n│     git add /home/lucas/research/code/ClimateMachine.jl/src/Arrays/MPIStateArrays.jl                                 │\n└ @ Main ~/research/code/ClimateMachine.jl/.git/hooks/pre-commit:30\n```\nLearn more about [`git hooks`](https://www.atlassian.com/git/tutorials/git-hooks).\n\n### Precompiling `JuliaFormatter`\n\nTo speed up the formatter and the githook, a custom system image can be\nbuilt with the [`PackageCompiler`]. That said, the following [drawback]\nfrom the `PackageCompiler` repository should be noted:\n\n> It should be clearly stated that there are some drawbacks to using a custom\n> sysimage, thereby sidestepping the standard Julia package precompilation\n> system. The biggest drawback is that packages that are compiled into a\n> sysimage (including their dependencies!) are \"locked\" to the version they\n> where at when the sysimage was created. This means that no matter what package\n> version you have installed in your current project, the one in the sysimage\n> will take precedence. This can lead to bugs where you start with a project\n> that needs a specific version of a package, but you have another one compiled\n> into the sysimage.\n\nThere are two helper scripts for doing this. The first will replace your default\nsystem image and can be run (from the top-level directory of a clone of\n`ClimateMachine`) with\n\n```\njulia .dev/clima_formatter_default_image.jl\n```\n\nIf you cannot or do not want to modify the default system image, use the\nfollowing instead:\n\n```\njulia .dev/clima_formatter_image.jl\n```\n\nwhich will put the precompile image in `.git/hooks/JuliaFormatterSysimage.so`.\nIn this case, use the `pre-commit.sysimage` `git hook` with:\n\n```\n$ ln -s ../../.dev/hooks/pre-commit.sysimage .git/hooks/pre-commit\n```\n\n!!! tip\n\n  Putting the system image in `.git/hooks` protects it from calls to `git clean -x`.\n\n!!! Note\n\n  The script `.dev/clima_formatter_image.jl` can also take a second arguement\n  with will specify a different path for the system image. If this is used along\n  with the `git hook` the path in `pre-commet.sysimage` will need to be updated.\n\n!!! warning\n\n    If you use a system image for the formatter, a new system image must be\n    built in order to update the `JuliaFormatter` package.\n"
  },
  {
    "path": "docs/src/DevDocs/DiagnosticVariableList.md",
    "content": "# [Diagnostic Variable List](@id Diagnostics-vars)\n\nThis is a list of all the diagnostic variables that the `ClimateMachine`\ncan produce, partitioned into groups.\n\n## LES Diagnostics\n\nUnless other noted, these fields are density-weighted using horizontally\naveraged density.\n\n### default\n\n#### 1D fields (time, z)\n\n| short name | description                                                                |\n|:-----------|:---------------------------------------------------------------------------|\n| u          | x-velocity                                                                 |\n| v          | y-velocity                                                                 |\n| w          | z-velocity                                                                 |\n| avg_rho    | density                                                                    |\n| rho        | (density-averaged) density                                                 |\n| qt         | total specific humidity                                                    |\n| ql         | liquid water specific humidity                                             |\n| qv         | water vapor specific humidity                                              |\n| thd        | potential temperature                                                      |\n| thv        | virtual potential temperature                                              |\n| thl        | liquid-ice potential temperature                                           |\n| et         | total specific energy                                                      |\n| ei         | specific internal energy                                                   |\n| ht         | specific enthalpy based on total energy                                    |\n| hi         | specific enthalpy based on internal energy                                 |\n| var\\_u      | variance of x-velocity                                                     |\n| var\\_v      | variance of y-velocity                                                     |\n| var\\_w      | variance of z-velocity                                                     |\n| w3         | the third moment of z-velocity                                             |\n| tke        | turbulence kinetic energy                                                  |\n| var\\_qt     | variance of total specific humidity                                        |\n| var\\_thl    | variance of liquid-ice potential temperature                               |\n| var\\_ei     | variance of specific internal energy                                       |\n| cov\\_w\\_u    | vertical eddy flux of x-velocity                                           |\n| cov\\_w\\_v    | vertical eddy flux of y-velocity                                           |\n| cov\\_w\\_rho  | vertical eddy flux of mass                                                 |\n| cov\\_w\\_qt   | vertical eddy flux of total specific humidity                              |\n| cov\\_w\\_ql   | vertical eddy flux of liuqid water specific humidity                       |\n| cov\\_w\\_qv   | vertical eddy flux of water vapor specific humidity                        |\n| cov\\_w\\_thd  | vertical eddy flux of potential temperature                                |\n| cov\\_w\\_thv  | vertical eddy flux of virtual temperature                                  |\n| cov\\_w\\_thl  | vertical eddy flux of liquid-ice potential temperature                     |\n| cov\\_w\\_ei   | vertical eddy flux of specific internal energy                             |\n| cov\\_qt\\_thl | covariance of total specific humidity and liquid-ice potential temperature |\n| cov\\_qt\\_ei  | covariance of total specific humidity and specific internal energy         |\n| w\\_qt\\_sgs   | vertical sgs flux of total specific humidity                               |\n| w\\_ht\\_sgs   | vertical sgs flux of total specific enthalpy                               |\n| cld\\_frac   | cloud fraction                                                             |\n| lwp        | liquid water path                                                          |\n\n#### Scalars (time)\n\n| short name      | description                                                                           |\n|:----------------|:--------------------------------------------------------------------------------------|\n| cld\\_cover  | cloud cover                                                                |\n| cld\\_top    | cloud top                                                                  |\n| cld\\_base   | cloud base                                                                 |\n\n\n### core\n\n#### 1D fields (time, z)\n\n| short name      | description                                                                           |\n|:----------------|:--------------------------------------------------------------------------------------|\n| u\\_core          | cloud core x-velocity                                                                 |\n| v\\_core          | cloud core y-velocity                                                                 |\n| w\\_core          | cloud core z-velocity                                                                 |\n| avg\\_rho\\_core    | cloud core density                                                                    |\n| rho\\_core        | cloud core (density-averaged) density                                                 |\n| qt\\_core         | cloud core total specific humidity                                                    |\n| ql\\_core         | cloud core liquid water specific humidity                                             |\n| thv\\_core        | cloud core virtual potential temperature                                              |\n| thl\\_core        | cloud core liquid-ice potential temperature                                           |\n| ei\\_core         | cloud core specific internal energy                                                   |\n| var\\_u\\_core      | cloud core variance of x-velocity                                                     |\n| var\\_v\\_core      | cloud core variance of y-velocity                                                     |\n| var\\_w\\_core      | cloud core variance of z-velocity                                                     |\n| var\\_qt\\_core     | cloud core variance of total specific humidity                                        |\n| var\\_thl\\_core    | cloud core variance of liquid-ice potential temperature                               |\n| var\\_ei\\_core     | cloud core variance of specific internal energy                                       |\n| cov\\_w\\_rho\\_core  | cloud core vertical eddy flux of mass                                                 |\n| cov\\_w\\_qt\\_core   | cloud core vertical eddy flux of total specific humidity                              |\n| cov\\_w\\_thl\\_core  | cloud core vertical eddy flux of liquid-ice potential temperature                     |\n| cov\\_w\\_ei\\_core   | cloud core vertical eddy flux of specific internal energy                             |\n| cov\\_qt\\_thl\\_core | cloud core covariance of total specific humidity and liquid-ice potential temperature |\n| cov\\_qt\\_ei\\_core  | cloud core covariance of total specific humidity and specific internal energy         |\n| core\\_frac       | cloud core (q\\_liq > 0 and w > 0) fraction                                             |\n\n### 3D Spectral decomposition (time, wavenumber)\n\n| short name | description                                                                |\n|:-----------|:---------------------------------------------------------------------------|\n| spectrum   | 3D power spectrum using the 3 velocity components                          |\n\n## GCM Diagnostics\n\n- Based on [this issue](https://github.com/CliMA/ClimateMachine.jl/issues/214).\n- GCM diagnostic variables are not currently density weigted.\n\n### default (for Dry Held-Suarez)\n\n#### 3D fields (time, long, lat, level)\n\n| short name | description                                                                |\n|:-----------|:---------------------------------------------------------------------------|\n| u          | zonal velocity (along longitude)                                           |\n| v          | meridional velocity (along latitude)                                       |\n| w          | vertical velocity (along altitude)                                         |\n| rho        | density                                                                    |\n|                                                                                         |\n| temp       | air temperature                                                            |\n| pres       | air pressure                                                               |\n| thd        | dry potential temperature                                                  |\n|                                                                                         |\n| et         | total specific energy                                                      |\n| ei         | specific internal energy                                                   |\n| ht         | specific enthalpy from total energy                                        |\n| hi         | specific enthalpy from internal energy                                     |\n|                                                                                         |\n| vort       | relative vorticity                                                         |\n| vort2      | relative vorticity from DGmodel kernels                                    |\n\n\n#### 1D spectral decomposition using the Fourier transform (time, level, latitude, zonal wavenumber)\n\n| short name | description                                                                |\n|:-----------|:---------------------------------------------------------------------------|\n| spectrum_1d  | kinetic energy power spectrum                                   |\n\n#### 2D spectral decomposition using the spherical harmonics (time, level, spherical wavenumber, zonal wavenumber)\n\n| short name | description                                                                |\n|:-----------|:---------------------------------------------------------------------------|\n| spectrum_2d  | kinetic energy power spectrum\n\n\n### default (for Moist Aquaplanet)\n\nThese are in addition to Held-Suarez.\n\n### 3D fields (time, long, lat, level)\n\n| short name | description                                                                |\n|:-----------|:---------------------------------------------------------------------------|\n| qt         | total specific humidity                                                    |\n| ql         | liquid water specific humidity                                             |\n| qv         | water vapour specific humidity                                             |\n| qi         | ice specific humidity                                                      |\n| thv        | virtual potential temperature                                              |\n| thl        | liquid-ice potential temperature                                           |\n\n### To be implemented (for Dry Held-Suarez)\n\n#### 2D fields (time, lat, level)\n\n| short name | description                                                                |\n|:-----------|:---------------------------------------------------------------------------|\n| stream_euler | Eulerian meridional streamfunction                                        |\n\n#### 2D fields (time, long, lat)                                       \n\n| short name | description                                                                |\n|:-----------|:---------------------------------------------------------------------------|\n\n#### 3D fields (long, lat, level)\n\n| short name | description                                                                |\n|:-----------|:---------------------------------------------------------------------------|\n| stream     | horizontal streamfunction (Laplacian of vort)                              |\n| pv\\_qg     | potential vorticity (f + vort + f/N d2/dz2 stream)                         |\n| pv\\_ertel  | Ertel potential vorticity                                                  |\n| div        | divergence                                                                 |\n|                                                                                         |\n| var\\_uu\\_zonal  | variances using zonal mean (also for vv, ww, TT, option for others)   |\n| cov\\_uv\\_zonal  | covariances using zonal mean (also for uw, vw, uT, vT, wT, option for others) |\n|                                                                                         |\n| var\\_uu\\_time   | variances using time mean (also for vv, ww, TT, option for others)    |\n| cov\\_uv\\_time   | covariances using time mean (also for uw, vw, uT, vT, wT, option for others) |\n|                                                                                         |\n| var\\_uu\\_bandpass  | (co)variances using a Lanczos filter (also for vv, ww, TT, option for others) |\n| cov\\_uv\\_bandpass  | (co)variances using a Lanczos filter (also for uw, vw, uT, vT, wT, option for others) |\n\n\n### To be implemented (for Moist Aquaplanet)\n\nThis are in addition to Held-Suarez.\n\n#### 2D fields (time, long, lat)\n\n| short name | description                                                                |\n|:-----------|:---------------------------------------------------------------------------|\n| toa\\_sw\\_do    | top of atmosphere (TOA) downwelling shortwave flux                     |\n| toa\\_sw_up     | TOA Upwelling shortwave flux                                           |\n| toa\\_lw\\_up    | TOA Upwelling longwave flux                                            |\n| toa\\_sw\\_sfc   | up- and downwelling shortwave flux at surface                          |\n| toa\\_lw\\_sfc   | up- and downwelling longwave flux at surface                           |\n| sensible\\_sfc  | sensible heat flux at surface                                          |\n| latent\\_sfc    | latent heat flux at surface                                            |\n| T\\_sfc         | surface air temperature                                                |\n| rain\\_sfc      | rain rate at surface                                                   |\n| snow\\_sfc      | snow rate at surface                                                   |\n\n#### 3D fields (time, long, lat, level)                            \n\n| short name | description                                                                |\n|:-----------|:---------------------------------------------------------------------------|\n| rh           | relative humidity                                                        |\n| cld\\_frac    | cloud fraction                                                           |\n| mse          | moist static energy                                                      |\n\n#### More complex diagnostics, e.g. extremes\n\n| short name | description                                                                |\n|:-----------|:---------------------------------------------------------------------------|\n| rain_thres    | frequency with which a given rain rate threshold at the surface is exceeded   |\n| temp_thres    | frequency with which a given temperature threshold at the surface is exceeded |\n| track_loc     | frequency of tracked features (e.g. cyclones, blocking)                       |\n| track_int     | intensity of tracked features (e.g. cyclones, blocking)                       |\n\n### To be implemented (for full GCM, additional to all above)\n\n| short name | description                                                                |\n|:-----------|:---------------------------------------------------------------------------|\n| xx            | (Later: sea ice cover, leaf temperature, soil temperature, ...)         |\n"
  },
  {
    "path": "docs/src/DevDocs/ModelOutput.md",
    "content": "# [ClimateMachine Output](@id Model-output)\n\nThese are the output data types currently supported by the `ClimateMachine`.\n\n1. **Diagnostics**\n    = user-selected variables calculated using the Diagnostics module\n    - NetCDF4 with hdf5 format\n        - this format allows compression of large datasets with metadata (following the CF convention). It is currently the mainstream format for geospatial data, so using it facilitates use of existing third party analysis packages.\n        - this format does not currently support storing connectivity information, so dumping on the DG grid is impractical. The Interpolation module enables the 3D fields to be saved on a user-defined diagnostic grid.\n    - a diagnostics group has to be pre-defined and selected by the user (see the [how to guide](@ref How-to-diagnostics)).\n    - command line argument: `[--diagnostics <interval>]`\n2. **Model variable dump**\n    = dump of prognostic and auxiliary variables on the model's DG grid\n    - VTK format\n        - this format retains connectivity information, so visualisation packages (e.g., [ParaView](https://www.paraview.org) or [VisIt](https://visitusers.org/index.php?title=Main_Page)) can approximately reconstruct the mesh. This is particularly useful for quick visual checks.\n    - command line argument: `[--vtk <interval>]`\n3. **Checkpoints**\n    = prognostic and auxiliary variables, saved separately by each MPI rank (see [this script](https://github.com/CliMA/ClimateMachine.jl/wiki/Assemble-checkpoints) to combine them) for checkpointing and restart purposes\n    - binary format (currently JLD2)\n    - command line arguments: `[--checkpoint <interval>]`, `[--checkpoint-keep-all]`, `[--checkpoint-at-end]`, `[--checkpoint-dir <path>]`\n"
  },
  {
    "path": "docs/src/DevDocs/ModelVariableList.md",
    "content": "# ClimateMachine Variable List\n\nThis document is currently for collaborators within the project with\naccess to the Overleaf CliMA-Atmos docs. The purpose of this page is to\nunify the naming conventions used in the Overleaf document in a manner\nuseful for coding. This document suggests 'reserved' variable names in\n`<property>_<species>` format with the default working fluid (no-subscript)\nbeing moist air. Contributors to the CliMA repository are welcome to\nsuggest changes when necessary.\n\n## Type parameters\n\nThe Julia code typically uses `T` as a type parameter, however this\nconflicts with the typical usage for temperature. Instead, good choices are:\n- `FT` for floating point values\n\n### Names reserved for debug variables\n```\ndummy\nscratch\n```\n\n### 2.1  Working Fluid and Equation of State\n```\nq_dry = dry air mass fraction\nq_vap = specific humidity, vapour\nq_liq = specific humidity, liquid\nq_ice = specific humidity, ice\nq_con = specific humidity, condensate\nq_tot = specific humidity, total\n\nP_<species>     = pressure, species (no subscript == default working fluid moist air)\nρ_<species>     = density, species (no subscript == default working fluid moist air)\nR_m             = gas constant, moist\nR_d             = gas constant, dry\nR_v             = gas constant, water vapour\nT               = temperature, moist air\nT_<species>     = temperature, species\n```\n\n### 2.2 Mass Balance\n```\ndt              = time increment\nu               = x-velocity\nv               = y-velocity\nw               = z-velocity\nU               = x-momentum\nV               = y-momentum\nW               = z=momentum\n```\n### 2.3 Moisture balances\n```\nsource_qt           = local source/sink of water mass [S_qt]\ndiffusiveflux_vap   = diffusive flux, water vapour\ndiffusiveflux_liq   = diffusive flux, cloud liquid\ndiffusiveflux_ice   = diffusive flux, cloud ice\ndiffusiveflux_tot   = diffusive flux, total\n```\n\n### 2.4 Momentum balances\n```\nU               = x-momentum\nV               = y-momentum\nW               = z-momentum (2D/3D: this is the vertical coordinate)\nΩ_x             = x-angular momentum\nΩ_y             = y-angular momentum\nΩ_z             = z-angular momentum\nτ_xx            = stress tensor ((1,1) component)\nτ_<ij>          = replace ij with combination of x/y/z to recover appropriate value\nλ_stokes        = Stokes parameter\n```\n\n### 2.5 Energy balance\n```\n<Lower case e_<type> suggests specific (per unit mass) quantities>\ne_kin_<spe>      = specific energy per unit volume, kinetic\ne_pot_<spe>      = specific energy per unit volume, potential\ne_int_<spe>      = specific energy per unit volume, internal\ne_tot_<spe>      = specific energy per unit volume, total\n\nE_kin_<spe>      = energy, kinetic\nE_pot_<spe>      = energy, potential\nE_int_<spe>      = energy, internal\nE_tot_<spe>      = energy, total\n\ncv_m             = isochoric specific heat, moist air\ncv_d             = isochoric specific heat, dry air\ncv_l             = isochoric specific heat, liquid water\ncv_v             = isochoric specific heat, water vapour\ncv_i             = isochoric specific heat, ice\n\ncp_m             = isobaric specific heat, moist air\ncp_d             = isobaric specific heat, dry air\ncp_l             = isobaric specific heat, liquid water\ncp_v             = isobaric specific heat, water vapour\ncp_i             = isobaric specific heat, ice\n```\n\n### 2.6 Microphysics\n```\nq_rai = specific humidity, rain [kg/kg]\n\nterminal_velocity = mass weighted average rain fall speed [m/s]\n\nconv_q_vap_to_q_liq      = tendency to q_liq and q_ice due to\n                           condensation/evaporation and\n                           sublimation/resublimation from q_vap [1/s]\nconv_q_liq_to_q_rai_acnv = tendency to q_rai due to autoconversion from q_liq [1/s]\nconv_q_liq_to_q_rai_accr = tendency to q_rai due to accretion from q_liq [1/s]\nconv_q_rai_to_q_vap      = tendency to q_vap due to evaporation from q_rai [1/s]\n```\n\n### 2.7 Diagnostics\n\nPlease see the [diagnostic variable list](@ref Diagnostics-vars).\n\n### TODO\n```\nUpdate with list of additional parameters / source terms as necessary\n```\n"
  },
  {
    "path": "docs/src/DevDocs/SystemImage.md",
    "content": "# System Image\n\nTo speed up the start time of `ClimateMachine` a custom system image can be\nbuilt with the\n[`PackageCompiler`](https://github.com/JuliaLang/PackageCompiler.jl).\n\nA helper script for doing this is provided at\n[`.dev/systemimage/climate_machine_image.jl`](https://github.com/CliMA/ClimateMachine.jl/blob/master/.dev/systemimage/climate_machine_image.jl)\n\nIf called with\n\n - **no arguments**: will create the system image `ClimateMachine.so` in the\n   `@__DIR__` directory (i.e., the directory in which the script is located).\n   ```\n   .dev/systemimage/climate_machine_image.jl\n   ```\n\n - **a single argument**: the system image will be placed in the path specified\n   by the argument (relative to the callers path). For example:\n   ```\n   .dev/systemimage/climate_machine_image.jl .git/ClimateMachine.so\n   ```\n\n - **a specified systemimg path and `true`**: the system image will compile the\n   climate machine package module (useful for CI). This option should not be\n   used when actually developing the climate machine package; see the\n   [drawback](https://julialang.github.io/PackageCompiler.jl/dev/sysimages/#Drawbacks-to-custom-sysimages-1)\n   from the `PackageCompiler` repository. For example:\n   ```\n   .dev/systemimage/climate_machine_image.jl ClimateMachine.so true\n   ```\n\nTo run julia using the newly created system image use:\n```\njulia -J/PATH/TO/SYSTEM/IMAGE/ClimateMachine.so --project\n```\n\n!!! tip\n\n    If you put the system image in your `.git` directory, your system image will\n    not be remove by calls to `git clean`.\n\n!!! warning\n\n    If the climate machine `Manifest.toml` is updated you must build a new\n    system image for these changes to be seen.\n"
  },
  {
    "path": "docs/src/GettingStarted/Atmos.md",
    "content": "# Atmosphere model configurations\n\nThe struct `AtmosModel` defines a specific subtype of a balance law\n(i.e. conservation equations) specific to atmospheric modeling. A\ncomplete description of a `model` is provided by the fields listed\nbelow. In this implementation of the `AtmosModel` we concern ourselves\nwith the conservative form of the compressible equations of moist fluid\nmotion given a set of initial, boundary and forcing(source) conditions.\n\nFirst, we construct the atmospheric physics via `AtmosPhysics`:\n\n```\nphysics = AtmosPhysics{FT}(;\n    param_set::AbstractParameterSet;\n    ref_state = HydrostaticState(DecayingTemperatureProfile{FT}(param_set),)\n    turbulence = SmagorinskyLilly{FT}(0.21),\n    hyperdiffusion = NoHyperDiffusion(),\n    moisture = EquilMoist(),\n    precipitation = NoPrecipitation(),\n    radiation = NoRadiation(),\n    tracers = NoTracers(),\n)\n```\n\n### LES Configuration (with defaults)\nDefault field values for the LES `AtmosModel` definition are included\nbelow. Users are directed to the model subcomponent pages to view the\npossible options for each subcomponent.\n```\natmos = AtmosModel{FT}(\n    ::Type{AtmosLESConfigType},\n    physics::AtmosPhysics;\n    orientation = FlatOrientation(),\n    source = (Gravity(), Coriolis(), GeostrophicForcing{FT}(7.62e-5, 0, 0)),\n    problem = AtmosBC(physics),\n    init_state_prognostic = nothing,\n    data_config = nothing,\n)\n```\n\n!!! note\n\n    Most AtmosModel subcomponents are common to both LES / GCM\n    configurations.  Equation sets are written in vector-invariant form and\n    solved in Cartesian coordinates.  The component `orientation` determines\n    whether the problem is solved in a `box (LES)` or a `sphere (GCM)`)\n\n\n### GCM Configuration (with defaults)\nDefault field values for the GCM `AtmosModel` definition are included\nbelow. Users are directed to the model subcomponent pages to view the\npossible options for each subcomponent.\n\n```\n    ::Type{AtmosGCMConfigType},\n    physics::AtmosPhysics;\n    source::S = (Gravity(), Coriolis()),\n    boundarycondition::BC = AtmosBC(physics),\n    init_state_prognostic::IS = nothing,\n    data_config::DC = nothing,\n```\n"
  },
  {
    "path": "docs/src/GettingStarted/Installation.md",
    "content": "# Installing ClimateMachine\n\n## Install Julia\n\nThe Climate Machine (CLIMA) uses the Julia programming language and has been tested for the latest Julia release version 1.5, which can be downloaded via your package manager or from the [Julia website](https://julialang.org/downloads/#current_stable_release).\n\n## Install MPI (optional)\n\nThe `ClimateMachine` uses the Message Passing Interface (MPI) for\ndistributed processing via the\n[MPI.jl](https://github.com/JuliaParallel/MPI.jl) package. This package\ndownloads and installs an MPI implementation for your platform. However, on\nhigh-performance computing systems, you will probably want to configure this\npackage to use the system-provided MPI implementation. You can do so by setting\nthe environment variable `JULIA_MPI_BINARY=system`.\n\nInstall `MPI.jl` in Julia using the built-in package manager (press `]` at\nthe Julia prompt):\n\n```julia\njulia> ]\n(v1.5) pkg> add MPI\n```\n\nThe package should be installed and built without errors. You can verify\nthat all is well with:\n\n```julia\njulia> ]\n(v1.5) pkg> test MPI\n```\n\nIf you are having problems, see the [`MPI.jl`\ndocumentation](https://juliaparallel.github.io/MPI.jl/stable/configuration/)\nfor help.\n\n## Install the `ClimateMachine`\n\nDownload the `ClimateMachine`\n[source](https://github.com/CliMA/ClimateMachine.jl) (you will need\n[`Git`](https://git-scm.com/)):\n\n```\n$ git clone https://github.com/CliMA/ClimateMachine.jl\n```\n\nNow change into the `ClimateMachine.jl` directory with \n\n```\n$ cd ClimateMachine.jl\n```\n\nand install all the required packages with:\n\n```\n$ julia --project -e 'using Pkg; pkg\"instantiate\"; pkg\"build MPI\"'\n```\n\nPre-compile the packages to allow the `ClimateMachine` to start faster:\n\n```\n$ julia --project -e 'using Pkg; pkg\"precompile\"'\n```\n\nYou can verify your installation with:\n\n```\n$ julia --project test/runtests.jl\n```\n\nThis will take a while!\n\nYou are now ready to run one of the tutorials. For instance, the dry\nRayleigh Benard tutorial:\n\n```\n$ julia --project tutorials/Atmos/dry_rayleigh_benard.jl\n```\n\nThe `ClimateMachine` is CUDA-enabled and will use GPU(s) if available. To run\non the CPU, set the environment variable `CLIMATEMACHINE_SETTINGS_DISABLE_GPU` to `true`.\nThis can either be done inline with the Julia launch command using\n\n```\nCLIMATEMACHINE_SETTINGS_DISABLE_GPU=true julia --project\n```\n\nor for the whole shell session, for example with `bash` this would be\n\n```\nexport CLIMATEMACHINE_SETTINGS_DISABLE_GPU=true\n```\n"
  },
  {
    "path": "docs/src/GettingStarted/RunningClimateMachine.md",
    "content": "# Running the `ClimateMachine`\n\nThe `ClimateMachine` is composed of three models for the Earth system,\na dynamical core, and a number of other components. These are put together\nto set up a simulation by a [_driver_](./Terminology.md), for example the\nHeld-Suarez atmospheric GCM, or the Rising Bubble atmospheric LES. The\ndriver specifies:\n- the dimensions and resolution of the simulation domain,\n- the duration of the simulation,\n- boundary conditions,\n- source terms,\n- a reference state,\n- the turbulence model,\n- the moisture model,\n- diagnostics of interest,\n- initial conditions,\n- etc.\n\nAdditionally, the driver chooses the time integrator to be used to run the\nsimulation and may specify the Courant number used to compute the timestep.\n\nThus, running the `ClimateMachine` requires a driver. For example, the\nHeld-Suarez atmospheric GCM is run with:\n\n```\n$ julia --project experiments/AtmosGCM/heldsuarez.jl\n```\n\nSimpler examples of driver files can be found in the tutorials.\nDriver files in experiments show more complex examples.\n\n## Input and output\n\nThe `ClimateMachine` uses [ArtifactWrappers.jl](https://github.com/CliMA/ArtifactWrappers.jl)\nto assist a driver in sourcing input data\nfor a simulation, but any mechanism may be used.\n\nOutput takes the form of various [groups of diagnostic variables](@ref\nDiagnostics-groups) that are written to NetCDF files at user-specified\nintervals by the `ClimateMachine` when configured to do so by a driver\n(see the [how to guide](@ref How-to-diagnostics)).\n\nThe `ClimateMachine` can also output\n[prognostic](../APIs/BalanceLaws/BalanceLaws.md#ClimateMachine.BalanceLaws.Prognostic)\n and\n [auxiliary](../APIs/BalanceLaws/BalanceLaws.md#ClimateMachine.BalanceLaws.Auxiliary)\n state variables\nto VTK files at specified intervals.\n\nWhether or not output is generated, and if so, at what interval, is a\n`ClimateMachine` _setting_.\n\nMore information on output data formats and diagnostics can be found\n[here](@ref Model-output).\n\n## `ClimateMachine` settings\n\nSome aspects of the `ClimateMachine`'s behavior can be controlled via\nits _settings_ such as use of the GPU, diagnostics output and frequency,\ncheckpointing/restarting, etc. There are 3 ways in which these settings\ncan be changed:\n\n1. [Command line arguments](@ref ClimateMachine-clargs) have the highest\n   precedence, but it is possible for a driver to disable parsing of\n   command line arguments. In such a case, only the next two ways can\n   be used to change settings.\n\n3. [Programmatic settings](@ref ClimateMachine-kwargs) have the next\n   highest precedence.\n\n2. [Environment variables](@ref ClimateMachine-envvars) have the lowest\n   precedence.\n\n### [Command line arguments](@id ClimateMachine-clargs)\n\nIf a driver configures the `ClimateMachine` to parse command line\narguments (by passing `parse_clargs = true` to `ClimateMachine.init()`),\nyou can query the list of arguments understood, for example:\n\n```\n$ julia --project experiments/AtmosGCM/heldsuarez.jl --help\nusage: experiments/AtmosGCM/heldsuarez.jl [--disable-gpu]\n                        [--show-updates <interval>]\n                        [--diagnostics <interval>] [--no-overwrite]\n                        [--vtk <interval>]\n                        [--vtk-number-sample-points <number>]\n                        [--monitor-timestep-duration <interval>]\n                        [--monitor-courant-numbers <interval>]\n                        [--adapt-timestep <interval>]\n                        [--checkpoint <interval>]\n                        [--checkpoint-keep-all] [--checkpoint-at-end]\n                        [--checkpoint-dir <path>]\n                        [--restart-from-num <number>] [--fix-rng-seed]\n                        [--disable-custom-logger]\n                        [--log-level <level>] [--output-dir <path>]\n                        [--debug-init] [--integration-testing]\n                        [--sim-time <number>]\n                        [--fixed-number-of-steps <number>]\n                        [--degree <horizontal>,<vertical>]\n                        [--nelems <nelem_1>[,<nelem_2>[,<nelem_3>]]]\n                        [--domain-height <number>]\n                        [--resolution <Δx>,<Δy>,<Δz>]\n                        [--domain-min <xmin>,<ymin>,<zmin>]\n                        [--domain-max <xmax>,<ymax>,<zmax>]\n                        [--number-of-tracers <number>] [-h]\n\nClimate Machine: an Earth System Model that automatically learns from data\n\noptional arguments:\n  --number-of-tracers <number>\n                        Number of dummy tracers (type: Int64, default:\n                        0)\n  -h, --help            show this help message and exit\n\nClimateMachine:\n  --disable-gpu         do not use the GPU\n  --show-updates <interval>\n                        interval at which to show simulation updates\n                        (default: \"60secs\")\n  --diagnostics <interval>\n                        interval at which to collect diagnostics\n                        (default: \"never\")\n  --no-overwrite        throw an error if an output file would be\n                        overwritten\n  --vtk <interval>      interval at which to output VTK (default:\n                        \"never\")\n  --vtk-number-sample-points <number>\n                        number of sampling points in each element for\n                        VTK output (type: Int64, default: 0)\n  --monitor-timestep-duration <interval>\n                        interval in time-steps at which to output\n                        wall-clock time per time-step (default:\n                        \"never\")\n  --monitor-courant-numbers <interval>\n                        interval at which to output acoustic,\n                        advective, and diffusive Courant numbers\n                        (default: \"never\")\n  --adapt-timestep <interval>\n                        interval at which to update the timestep\n                        (default: \"never\")\n  --checkpoint <interval>\n                        interval at which to create a checkpoint\n                        (default: \"never\")\n  --checkpoint-keep-all\n                        keep all checkpoints (instead of just the most\n                        recent)\n  --checkpoint-at-end   create a checkpoint at the end of the\n                        simulation\n  --checkpoint-on-crash \n                        create a checkpoint on a kernel crash (hurts\n                        performance!)\n  --checkpoint-dir <path>\n                        the directory in which to store checkpoints\n                        (default: \"checkpoint\")\n  --restart-from-num <number>\n                        checkpoint number from which to restart (in\n                        <checkpoint-dir>) (type: Int64, default: -1)\n  --fix-rng-seed        set RNG seed to a fixed value for\n                        reproducibility\n  --disable-custom-logger\n                        do not use a custom logger\n  --log-level <level>   set the log level to one of\n                        debug/info/warn/error (default: \"INFO\")\n  --output-dir <path>   directory for output data (default: \"output\")\n  --debug-init          fill state arrays with NaNs and dump them\n                        post-initialization\n  --integration-testing\n                        enable integration testing\n  --sim-time <number>   run for the specified time (in simulation\n                        seconds) (type: Float64, default: NaN)\n  --fixed-number-of-steps <number>\n                        if `≥0` perform specified number of steps\n                        (type: Int64, default: -1)\n  --degree <horizontal>,<vertical>\n                        tuple of horizontal and vertical polynomial\n                        degrees for spatial discretization order (no\n                        space before/after comma) (type:\n                        Tuple{Int64,Int64}, default: (-1, -1))\n  --nelems <nelem_1>[,<nelem_2>[,<nelem_3>]]\n                        number of elements in each direction: 3 for\n                        Ocean GCM, 2 for Atmos GCM or 1 for Atmos\n                        single-stack (no space before/after comma)\n                        (type: Tuple{Int64,Int64,Int64}, default: (-1,\n                        -1, -1))\n  --domain-height <number>\n                        domain height (in meters) for GCM or\n                        single-stack configurations (type: Float64,\n                        default: -1.0)\n  --resolution <Δx>,<Δy>,<Δz>\n                        tuple of three element resolutions (in meters)\n                        for LES and MultiColumnLandModel\n                        configurations (type:\n                        Tuple{Float64,Float64,Float64}, default:\n                        (-1.0, -1.0, -1.0))\n  --domain-min <xmin>,<ymin>,<zmin>\n                        tuple of three minima for the domain size (in\n                        meters) for LES and MultiColumnLandModel\n                        configurations (type:\n                        Tuple{Float64,Float64,Float64}, default:\n                        (-1.0, -1.0, -1.0))\n  --domain-max <xmax>,<ymax>,<zmax>\n                        tuple of three maxima for the domain size (in\n                        meters) for LES and MultiColumnLandModel\n                        configurations (type:\n                        Tuple{Float64,Float64,Float64}, default:\n                        (-1.0, -1.0, -1.0))\n\nAny <interval> unless otherwise stated may be specified as:\n    - 2hours or 10mins or 30secs => wall-clock time\n    - 9.5smonths or 3.3sdays or 1.5shours => simulation time\n    - 1000steps => simulation steps\n    - never => disable\n    - default => use experiment specified interval (only for diagnostics at present)\n```\n\nThere may also be driver-specific command line arguments.\n\n### [Programmatic control](@id ClimateMachine-kwargs)\n\nEvery `ClimateMachine` setting can also be controlled via\nkeyword arguments to the `ClimateMachine` initialization function,\n`ClimateMachine.init()`. For instance, a driver can specify that VTK\noutput should occur every 5 simulation minutes with:\n\n```julia\nClimateMachine.init(vtk = \"5smins\")\n```\n\nThis can be overridden by by passing `--vtk=never` on the command line,\n_if_ the `ClimateMachine` is parsing command line arguments.\n\n!!! note\n\n    The `ClimateMachine` will only process command line arguments if a\n    driver requests that it do so with:\n    ```julia\n    ClimateMachine.init(parse_clargs = true)\n    ```\n\n### [Environment variables](@id ClimateMachine-envvars)\n\nEvery `ClimateMachine` command line argument has an equivalent environment\nvariable that takes the form `CLIMATEMACHINE_SETTINGS_<SETTING_NAME>`,\nhowever command line arguments and programmatic control have higher\nprecedence.\n\n## Running with MPI\n\nUse MPI to start a distributed run of the `ClimateMachine`. For example:\n\n```\nmpiexec -np 4 julia --project experiments/AtmosGCM/heldsuarez.jl\n```\n\nwill run the Held-Suarez experiment with four MPI ranks. If you are\nrunning on a cluster, you would use this command within a SLURM batch\nscript (or the equivalent) that allocates four tasks. On a stand-alone\nmachine, MPI will likely require that you have at least four cores.\n\nNote that unless GPU use is disabled (by changing the setting in one of\nthe ways described above), each `ClimateMachine` process will use GPU\nacceleration.  If there are insufficient GPUs (four in the example above),\nthe `ClimateMachine` processes will share the GPU resources available.\n\n## Scripts for end-to-end runs, logging and visualization\n\nThe `ClimateMachine`\n[wiki](https://github.com/CliMA/ClimateMachine.jl/wiki)\ncontains detailed examples of [Slurm\nscripts](https://github.com/CliMA/ClimateMachine.jl/wiki/Bash-Run-Scripts)\nthat run the `ClimateMachine`, record specified performance metrics and\nproduce basic visualization output.\n"
  },
  {
    "path": "docs/src/GettingStarted/Terminology.md",
    "content": "# Terminology\n\nThe `ClimateMachine` documentation uses terminology from several disciplines. Below are some definitions of some of these terms. Please let us know via a github issue if there are other terms that are unclear or confusing to you.\n\n## Software terms\n\n* __Driver file__: a julia script which (a) specifies which\n  [`BalanceLaw`](../APIs/BalanceLaws/BalanceLaws.md#ClimateMachine.BalanceLaws.BalanceLaw)\n  is being used, (b) specifies changes to the default version\n  of that model, (c) specifies initial and boundary conditions,\n  (d) specifies numerical details related to timestep, grid,\n  and frequency of output, and (e) runs the integration.\n  Examples can be found in tutorials and experiments.\n* __Source file__: a julia file containing code involved\n  in implementing the choices made in a driver file. This\n  is where the numerical methods related to solving the\n  equations (e.g. the ODE time steppers, or the creation of\n  the grid) are defined. Users typically only interact with\n  functions defined in source code within a driver file by\n  using arguments to those functions, unless they wish to\n  develop the source code itself.\n* __Kernel__: Functions which are launched on the compute device (i.e., the CPU or\n  GPU) and run with an array of workitems or threads.\n  `ClimateMachine.jl` uses\n  [`KernelAbstractions.jl`](https://github.com/JuliaGPU/KernelAbstractions.jl)\n  as its kernel language.\n* __Callback__: Functions executed by the ODE integrator after each time step; see\n  [`solve!`](../APIs/Numerics/ODESolvers/ODESolvers.md#ClimateMachine.ODESolvers.solve!).\n  This allows the ability to inject custom behavior into the ODE\n  integrators, such as diagnostic output, visualization, and apply\n  filtering to the numerical solution.\n  Several convenience functions exists specifying\n  [callback](../APIs/Numerics/ODESolvers/ODESolvers.md#ClimateMachine.GenericCallbacks)\n  frequency: number of simulation steps, every X simulation time, and every X wall clock seconds.\n  The callback mechanism is inspired by the [callback mechanism of\n  `DifferentialEquations.jl`.](https://diffeq.sciml.ai/stable/features/callback_functions/)\n\n## Numerics\n\n* __Balance Law__: The system of PDEs being solved. Please see the\n  [how-to-guide](../HowToGuides/BalanceLaws/how_to_make_a_balance_law.md)\n  or the [API](../APIs/BalanceLaws/BalanceLaws.md#ClimateMachine.BalanceLaws.BalanceLaw).\n* __Courant number__: The ratio of the distance sound waves, diffusion, and other physical processes in your\n  model travel or carry information in a timestep, relative to the resolution of your\n  spatial discretization. This can typically be written in terms of physical constants,\n  the grid size, and the time step, and is used for determining stability of\n  ODE time steppers.\n* __CFL limit__: A maximum value for the Courant number in order to guarantee convergence to the true solution.\n  Given a spatial discretization, this determines the maximum timestep that can be used. The value\n  of the CFL limit depends on the ODE solving algorithm used.\n\n\n\n## Physics\n\n* __Diurnal variation__: A periodic variation in Earth processes driven by the rotation of the Earth. Examples include\n  heating of the Earth's surface due to solar radiation, or the semi-diurnal tidal cycle.\n* __Shortwave radiation__: This refers to solar radiation, with intensity peaking in the visible part of the spectrum.\n* __Longwave radiation__: This refers to the emitted infrared radiation of the Earth surface or atmosphere.\n* __Advection/advective flux__: Advection is movement of some material/quantity by the bulk velocity of a fluid.\n  In the `BalanceLaw` language, the advective flux of a prognostic variable is a first order flux.\n* __Diffusion/diffusive flux__: Diffusion describes a process in which a material or quantity is moved, \n  or approximated as moved, due to random motion of particles. More generally, a diffusive flux is one\n  generated by a gradient in\n  concentration, temperature, or another quantity. A diffusive flux is a second order flux in the `BalanceLaw`\n  framework.\n* __Hyperdiffusion__: an explicit higher-order flux term representing horizontal diffusion. The `ClimateMachine`\n  hyperdiffusive flux uses a fourth-order derivative, but it is included in a second order flux. It enforces the flow\n  of enstrophy absorption at the smallest resolution, and models dissipation effects.\n\n## Documentation\n\n* __APIs__: This section details the parts of the source code that are visible\n  to users wanting to run models and explains how to interact with\n  and call them. API stands for\n  [application programming interface](https://en.wikipedia.org/wiki/API);\n  from Wikipedia, \"It defines the kinds of calls or requests that can be \n  made, how to make them, the data formats that should be used, the \n  conventions to follow, etc.\"  \n* __Tutorials__: Driver files with concrete examples showing how to run simple land,\n  ocean, and atmosphere models, or how to use certain numerical functions.\n* __Experiments__: Driver files with more complex models. These could be considered\n  the starting point for research; they are not a part of the docs\n  as they are constantly being updated.\n* __How-to-guide__: Code and explanations for components used in models.\n"
  },
  {
    "path": "docs/src/HowToGuides/Atmos/AtmosReferenceState.md",
    "content": "# Atmospheric temperature profiles\n\nHere, we plot the atmospheric reference state profiles for a few different polynomial orders and number of elements.\n\n```@example\nusing ClimateMachine\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\nusing Plots\ninclude(joinpath(clima_dir, \"docs\", \"plothelpers.jl\"));\ninclude(joinpath(clima_dir, \"test\", \"Atmos\", \"Model\", \"get_atmos_ref_states.jl\"));\n\nfunction export_ref_state_plot(nelem_vert, N_poly)\n    solver_config = get_atmos_ref_states(nelem_vert, N_poly, 0.5)\n    z = get_z(solver_config.dg.grid)\n    dons_arr = dict_of_nodal_states(solver_config)\n    T = dons_arr[\"ref_state.T\"]\n    ρ = dons_arr[\"ref_state.ρ\"]\n    p = dons_arr[\"ref_state.p\"]\n    ρe = dons_arr[\"ref_state.ρe\"]\n    p1 = plot(T, z./10^3, xlabel=\"Temperature [K]\");\n    p2 = plot(ρ, z./10^3, xlabel=\"Density [kg/m^3]\");\n    p3 = plot(p./10^3, z./10^3, xlabel=\"Pressure [kPa]\");\n    p4 = plot(ρe./10^3, z./10^3, xlabel=\"Total energy [kJ]\");\n    plot(p1, p2, p3, p4, layout=(1,4), ylabel=\"z [km]\")\n    savefig(\"N_poly_$(N_poly).png\")\nend\n\nexport_ref_state_plot(80, 1)\nexport_ref_state_plot(40, 2)\nexport_ref_state_plot(20, 4)\n```\n## Polynomial order 1, 80 elements\n![](N_poly_1.png)\n\n## Polynomial order 2, 40 elements\n![](N_poly_2.png)\n\n## Polynomial order 4, 20 elements\n![](N_poly_4.png)\n"
  },
  {
    "path": "docs/src/HowToGuides/Atmos/MoistureAndPrecip.md",
    "content": "## Representing moisture and precipitation\n\nThis tutorial shows how to setup an Atmos experiment with moisture\n(i.e. water vapor, cloud liquid water, and cloud ice)\nand precipitation (i.e. rain and/or snow).\nIt expands on the LES and GCM tutorials already available in the\n[Atmos tutorials section](https://clima.github.io/ClimateMachine.jl/latest/generated/TutorialList/#Atmos).\nTherefore, this tutorial only discusses additional steps that are needed\nin order to define an experiment with moisture and precipitation.\nFor a tutorial on how to setup basic Atmos experiment see the\n[Held-Suarez](https://clima.github.io/ClimateMachine.jl/latest/generated/Atmos/heldsuarez/)\nor the [Rising thermal bubble](https://clima.github.io/ClimateMachine.jl/latest/generated/Atmos/risingbubble/)\ntutorials.\nFor a full experiment setup with moisture and precipitation\nsee the [DyCOMS](https://github.com/CliMA/ClimateMachine.jl/blob/master/experiments/AtmosLES/dycoms.jl)\nor the [Squall line](https://github.com/CliMA/ClimateMachine.jl/blob/master/experiments/AtmosLES/squall_line.jl)\nsetups that are part of the Atmos model CI.\n\nWhen setting up a moist and precipitating Atmos experiment,\nthe user has to decide between two moisture and three precipitation models.\nThe differences between the two moisture models are described in the\n[Moisture model](https://clima.github.io/ClimateMachine.jl/latest/HowToGuides/Atmos/MoistureModelChoices/) section.\nIn general, the `EquilMoist` model solves for only one additional state variable\nand diagnoses the cloud condensate partitioning between liquid and ice\nbased on equilibrium assumptions and an iterative search algorithm.\nThe `NonEquilMoist` model solves equations for all three cloud condensate state variables\nBecause the condensation and deposition timescales are fast,\nin many cases, the `EquilMoist` model is a good approximation.\nHowever, it is obviously not sufficient when modeling nonequilibrium\nprocesses, such as supercooled clouds.\nBecause of the lack of need for iterative search to perform partitioning\nof cloud condensate, the `NonEquilMoist` model might be more robust,\nif a little slower than the `EquilMoist` model.\nThe differences between the three precipitation models are described in the\n[Precipitation model](https://clima.github.io/ClimateMachine.jl/latest/HowToGuides/Atmos/PrecipitationModelChoices/) section.\nIn general, the `NoPrecipitation` option can be used in simulations without\nprecipitation or in simulations when the effect of precipitation\nis modeled by instantly removing cloud condensate.\nThis option, coupled with either of the moisture models, is a good start\nwhen running in moist `AtmosGCM` configuration.\nThe parameterization of instantaneous precipitation removal is described by the\n[0-moment microphysics](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_0M/)\nscheme.\nThe `RainModel` encapsulates the \"warm rain\" processes, i.e.\nthe formation of precipitation in temperatures above freezing.\nThe `RainSnowModel` describes the full set of microphysical processes\nleading to the formation of precipitation.\nBoth models are based on\n[1-moment microphysics](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_1M/)\nscheme, which is a simple representation of cloud microphysics.\nIt is a good starting point for the moist `AtmosLES` configurations,\ncoupling to the subgrid scale parameterizations, and testing the machine learning pipelines.\nIn the future, a more sophisticated 2-moment microphysics scheme will most likely be needed.\n\n\n## Using CLIMAParameters.jl\n\nThe microphysics parameterizations used to compute the sources and sinks for\nmoisture and precipitation variables depends on\n[CLIMAParameters.jl](https://github.com/CliMA/CLIMAParameters.jl).\nA set with all the parameter values needs to be created and passed in as one\nof the arguments.\n\n```julia\nusing CLIMAParameters\nusing CLIMAParameters.Planet\nusing CLIMAParameters.Atmos.Microphysics\n\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n```\n\nAfter this is done, the created `param_set` should be passed\nin the same way as in the \"dry\" tutorials.\n\n\n## Initial condition\n\nAs discussed in the documentation on the\n[moisture](https://clima.github.io/ClimateMachine.jl/latest/HowToGuides/Atmos/MoistureModelChoices/)\nand\n[precipitation](https://clima.github.io/ClimateMachine.jl/latest/HowToGuides/Atmos/PrecipitationModelChoices/)\nchoices, different model configurations result in different state variables.\nWhen using the `EquilMoist` model, the initial condition on total water\nis the only thing that is needed:\n\n```julia\nstate.moisture.ρq_tot = ρ * 0.0042\n```\n\nWhen using the `NonEquilMoist` model, the initial condition on total water,\ncloud liquid water and cloud ice is needed:\n\n```julia\nstate.moisture.ρq_tot = ρ * 0.0042\nstate.moisture.ρq_liq = FT(0)\nstate.moisture.ρq_ice = FT(0)\n```\n\nSimilarly for the `RainModel` we need to define the initial condition for\nrain:\n\n```julia\nstate.precipitation.ρq_rai = FT(0)\n```\n\nAnd for the `RainSnowModel` the initial condition for both rain and snow is needed:\n\n```julia\nstate.precipitation.ρq_rai = FT(0)\nstate.precipitation.ρq_sno = FT(0)\n```\n\nAs in the previous tutorials `FT()` is the float type chosen for the simulation.\n\nThe moisture and precipitation state variables are grouped together\ninto `state.moisture` and `state.precipitation` fields.\nAs shown when specifying the initial condition,\nthis hierarchy has to be observed when accessing the members\nof moisture or precipitation state variables.\n\n\n## Choosing the moisture and precipitation models and sources\n\nWhen opting to use the `EquilMoist` moisture model, we need to specify\nthe maximum number of iterations and the tolerance allowed\nin the iterative search performed to diagnose cloud condensate\nphase partitioning.\nThe cloud liquid water and cloud ice are stored in the auxiliary\nstate variables.\nBecause the partitioning is diagnosed, no additional source terms\nhave to be specified:\n\n```julia\nmoisture = EquilMoist(; maxiter = 8, tolerance = FT(1e-1))\n```\n\nAlternatively, when using the `NonEquilMoist` model, an additional source term\n`CreateClouds` is needed.\nIt is assumed here that `source` lists all the previously\ndefined source terms.\nWe are splatting to it the additional\ncloud condensate sources for cloud liquid water and cloud ice.\n\n```julia\nmoisture = NonEquilMoist()\nsource = (source..., CreateClouds())\n```\n\nIf choosing the `NoPrecipitation` model we can either define no additional\nsource terms (a true simulation without any representation of precipitation),\nor use the instant precipitation removal source term `RemovePrecipitation`.\nThe boolean flag passed to it as argument chooses between two definitions\nof the threshold above which cloud condensate is removed,\nsee [here](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_0M/#Moisture-sink-due-to-precipitation).\nThe flag set to `true` results in cloud condensate based threshold.\nThe flag set to `false` results in saturation excess threshold.\n\n```julia\nprecipitation = NoPrecipitation()\nsource = (source..., RemovePrecipitation(true))\n```\n\nIf using the `RainModel`, the `WarmRain_1M` source terms have to be chosen:\n\n```julia\nprecipitation = RainModel()\nsource = (source..., WarmRain_1M())\n```\n\nAlternatively, if using the `RainSnowModel`, the `RainSnow_1M` source terms\nhave to be chosen:\n\n```julia\nprecipitation = RainSnowModel()\nsource = (source..., RainSnow_1M())\n```\n\nAs in the previous tutorials, all of the `source`, `moisture`, `precipitation`\nchoices, along with the `param_set` `struct` have to be passed to\nthe Atmos model configuration:\n\n```julia\nphysics = AtmosPhysics{FT}(\n    param_set;\n    ref_state = ref_state,\n    moisture = moisture,\n    precipitation = precipitation,\n    turbulence = SmagorinskyLilly{FT}(C_smag),\n)\nmodel = AtmosModel{FT}(\n    AtmosLESConfigType,\n    physics;\n    problem = problem,\n    source = source,\n)\n```\n\n\n## Boundary conditions\n\nBoundary condition specification is currently undergoing a re-write.\nMore details on boundary conditions will follow soon.\nBy default no additional moisture or precipitation fluxes are applied.\n\n\n## Using filters\n\nAll moisture and precipitation variables are positive definite.\nHowever, due to numerical errors, they can turn negative during the simulation.\nTo mitigate that behavior it is advisable to use the `TMAR filter`.\nThe filter adjust the nodal points inside the DG element.\nIt truncates the negative nodal points to zero\nand adjusts the values of the remaining points\nto preserve the element average.\nTo conserve mass of the advected tracers the `TMAR filter` should be combined\nwith a flux-correction to those DG elements whose average value is negative.\nThe flux-corrected option is currently being implemented.\nIf mass conservation is deemed more important than positive sign of advected tracers,\none can run the simulation without the `TMAR filter`.\nThe microphysics functions are implemented such that `f(negative_argument) = f(0)`.\n\nThe list of state variables to be filtered\ndepends on the moisture and precipitation model choices.\nFor example:\n\n```julia\nfilter_vars = (\"moisture.ρq_tot\",)\nfilter_vars = (filter_vars..., \"moisture.ρq_liq\", \"moisture.ρq_ice\")\nfilter_vars = (filter_vars..., \"precipitation.ρq_rai\")\nfilter_vars = (filter_vars..., \"precipitation.ρq_rai\", \"precipitation.ρq_sno\")\n```\n\nThe list of variables to be filtered should then be passed to the `TMAR filter` `callback`:\n\n```julia\ncbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n    Filters.apply!(\n        solver_config.Q,\n        filter_vars,\n        solver_config.dg.grid,\n        TMARFilter(),\n    )\n    nothing\nend\n```\n\nAnd the filter `callback` should be passed to the final `invoke` method:\n\n```julia\nresult = ClimateMachine.invoke!(\n    solver_config;\n    diagnostics_config = dgn_config,\n    user_callbacks = (cbtmarfilter,),\n    check_euclidean_distance = true,\n)\n```\n\n## Accessing moisture and precipitation variables\n\nTo access moisture and precipitation variables at the end of the simulation\n(for example for some debugging or assert checking in the CI)\nwe need to use the `varsindex` and `vars_state` functions:\n\n```julia\nm = driver_config.bl\nQ = solver_config.Q\nρ_ind = varsindex(vars_state(m, Prognostic(), FT), :ρ)\n\nρq_tot_ind = varsindex(vars_state(m, Prognostic(), FT), :moisture, :ρq_tot)\nρq_sno_ind = varsindex(vars_state(m, Prognostic(), FT), :precipitation, :ρq_sno)\n\nmin_q_tot = minimum(abs.(Array(Q[:, ρq_tot_ind, :]) ./ Array(Q[:, ρ_ind, :]),))\nmax_q_sno = maximum(abs.(Array(Q[:, ρq_sno_ind, :]) ./ Array(Q[:, ρ_ind, :]),))\n\n@info(min_q_tot, max_q_sno)\n```\n\nThe vertical profiles (horizontal averages) of moisture and precipitation\nvariables, their variances and covariances,\nas well as the liquid, ice rain and snow water paths\nare available in the output netcdf file when using the\n`setup_atmos_default_diagnostics` group.\nAdditionally one can choose to save into the netcdf output all of the state\nvariables and auxiliary variables using the `setup_dump_state_diagnostics`\nand `setup_dump_aux_diagnostics` groups.\n"
  },
  {
    "path": "docs/src/HowToGuides/Atmos/MoistureModelChoices.md",
    "content": "# Moisture model choices in Atmos.jl\n\nThe moisture model in `Atmos.jl` describes the behavior\n  of suspended water in the atmosphere (i.e. water vapor,\n  cloud liquid water and cloud ice).\nThere are three options available: `DryModel`, `EquilMoist` and `NonEquilMoist`.\n\n\n## DryModel\n\nThe `DryModel` assumes that air is dry and there is no water available\n  in any form.\nIt does not add any water related variables to state variables.\n\n\n## EquilMoist\n\nThe `EquilMoist` assumes that water is present in the air and that\n  all of its phases are always in thermodynamic equilibrium.\nIt employs an iterative search algorithm called `saturation adjustment` to find\n  the temperature at which all water phases are in equilibrium\n  and the corresponding `q_liq` and `q_ice`\n  (cloud liquid water cloud ice specific humidities).\nIt adds `ρ q_tot` (air density times total water specific humidity)\n   to state variables.\nThe `q_liq` and `q_ice` are diagnosed during output based on the temperature\n  stored in the auxiliary state.\n\n\n## NonEquilMoist\n\nThe `NonEquilMoist` assumes that water is present in the air,\n  its phases are in thermal equilibrium (i.e., they have the same temperature),\n  but it does not assume that the partitioning of water\n  into its vapor, liquid, and ice phases is in equilibrium.\nAt each time step, the cloud liquid water and cloud ice source/sink terms\n  are obtained as relaxation towards equilibrium over time scale\n  that may eventually depend on factors such as the number of cloud condensation nuclei.\n  (for details see [here](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_1M/#Cloud-water-condensation/evaporation).)\nThis approach does not require employing iterative algorithm and\n  allows for supersaturation to be present in the computational domain.\nIt adds `ρ q_tot`, `ρ q_liq` and `ρ q_ice` to the state variables\n  (air density times total water, cloud liquid water and cloud ice specific humidities).\nBecause the assumed relaxation timescale for condensation/evaporation and\n  deposition/sublimation is small, it requires care when choosing the\n  model timestep length.\n\n\n## Implementation notes\n\n!!! warn\n    While `recover_thermo_state` is an ideal long-term solution,\n    right now we are directly calling new_thermo_state to avoid\n    inconsistent aux states in kernels where the aux states are\n    out of sync with the boundary state.\n\nThe moisture models rely on the\n  [new\\_thermo\\_state](https://clima.github.io/ClimateMachine.jl/latest/APIs/Atmos/AtmosModel/#ClimateMachine.Atmos.new_thermo_state)\n  and\n  [recover\\_thermo\\_state](https://clima.github.io/ClimateMachine.jl/latest/APIs/Atmos/AtmosModel/#ClimateMachine.Atmos.recover_thermo_state)\n  convenience functions to create a `struct` that stores\n  air properties needed to uniquely define air thermodynamic state.\nFor the `DryModel` it is the\n  [PhaseDry](https://clima.github.io/Thermodynamics.jl/dev/API/#Thermodynamics.PhaseDry)\n  `struct` that has three fields:\n  parameter set used by the `Atmos.jl` model, internal energy and air density.\nFor the `EquilMoist` model it is the\n  [PhaseEquil](https://clima.github.io/Thermodynamics.jl/dev/API/#Thermodynamics.PhaseEquil)\n  `struct` that has five fields:\n  parameter set used by the `Atmos.jl` model, internal energy, air density,\n  total water specific humidity and temperature at which all water phases\n  are in equilibrium.\nFor the `NonEquilMoist` model it is the\n  [PhaseNonEquil](https://clima.github.io/Thermodynamics.jl/dev/API/#Thermodynamics.PhaseNonEquil)\n  `struct` that has four fields:\n  parameter set used by the `Atmos.jl` model, internal energy,\n  air density and phase partition `struct`.\nAll other properties, such as the speed of sound in the air,\n  water vapor specific humidity,\n  etc, should be computed based on the thermodynamic state `struct`.\n\nThe `new_thermo_state` function is called at the beginning of each time step\n  in the `atmos_nodal_update_auxiliary_state` function.\nFor the `EquilMoist` model the `new_thermo_state` function calls\n  the `saturation_adjustment` to find the equilibrium temperature.\nIt populates the fields of the `PhaseEquil` `struct`\n  and saves the equilibrium air temperature into the auxiliary state.\nFor the `DryModel` and `NonEquilMoist` model the thermodynamic state `struct`\n  is created based on the current state variables.\nThe `recover_thermo_state` function should be used throughout the code to create\n  an instance of the thermodynamic state `struct`.\nFor the `EquilMoist` model it populates the `PhaseEquil` fields based on the\n  current state variables and the temperature stored in the auxiliary state.\nThis avoids executing unnecessarily the `saturation_adjustemnt` algorithm.\nFor the `DryModel` and `NonEquilMoist` model the `recover_thermo_state`\n  function is the same as the `new_thermo_state` function\n  and populates the corresponding `struct` fields based on\n  the current state variables.\n"
  },
  {
    "path": "docs/src/HowToGuides/Atmos/PrecipitationModelChoices.md",
    "content": "# Precipitation model choices in Atmos.jl\n\nThe precipitation model in `Atmos.jl` describes the behavior\n  of precipitating water in the atmosphere (i.e. rain and snow).\nThere are two options available: `NoPrecipitation` and `RainModel`.\n\n## NoPrecipitation\n\nThe `NoPrecipitation` model assumes there is no precipitating water present\n  in any form in the simulation.\nIt does not add any precipitation related variables to state variables.\nThis model can be used (i) without defining any precipitation related source terms,\n  (ii) or using the [0-moment microphysics scheme](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_0M/).\nIn the first case `ρ q_tot` (total water specific humidity) is not removed\n  during the simulation.\nIn the second case `ρ q_tot` is removed if it exceeds a threshold.\n\n\n## RainModel\n\nThe `RainModel` assumes that precipitating water is present but only in the\n  form of rain (liquid-phase precipitation).\nIt adds `ρ q_rai` (air density times total rain water specific humidity)\n  to state variables.\nIt uses a subset of source terms from the [1-moment microphysics scheme](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_1M/)\n  that parameterize processes relevant to liquid-phase clouds\n  (autoconverion, accretion, and rain evaporation).\n\n## RainSnowModel\n\nThe `RainSnowModel` assumes that precipitating water is present in the\n  form of rain (liquid-phase precipitation) and\n  snow (ice-phase precipitation).\nIt adds `ρ q_rai` (air density times total rain water specific humidity)\n  and `ρ q_sno` (air density times total snow water specific humidity)\n  to state variables.\nIt uses source terms from the [1-moment microphysics scheme](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_1M/).\n"
  },
  {
    "path": "docs/src/HowToGuides/BalanceLaws/how_to_make_a_balance_law.md",
    "content": "# How to make a Balance law\n\n```@meta\nCurrentModule = ClimateMachine.BalanceLaws\n```\n\nDefining the set of solved PDEs in ClimateMachine revolve around defining a\n[`BalanceLaw`](@ref). A balance law solves equations of the form:\n\n```\n∂Y\n-- = - ∇ • ( F_{first_order}(Y) + F_{second_order}(Y, Σ) ) + S_{non_conservative}(Y, Σ)\n∂t\n```\n\nHere, `Y`, `Σ`, `F_{first_order}`, `F_{second_order}`, and\n`S_{non_conservative}` can be thought of column vectors[^1] expressing:\n\n - `Y` the prognostic state variables, or unknowns of the PDEs to be solved\n\n - `Σ` the gradients of functions of the prognostic state variables\n\n - `F_{first-order}` contains all first-order fluxes (e.g. **not** functions\n of gradients of any variables)\n\n - `F_{second-order}` contains all second-order and higher-order fluxes\n (e.g. functions of gradients of any variables)\n\n - `S_{non_conservative}` non-conservative sources[^2]\n\nIn order to alleviate users from being concerned with the burden of\nspatial discretization, users must provide their own implementations of\nthe following methods, which are computed locally at each nodal point:\n\n## Variable name specification methods\n\nThe `vars_state` method is used to specify the names of the variables for the following state variable types:\n\n| **Type** | Purpose |\n|:-----|:----|\n| [`Prognostic`](@ref) | the variables in the prognostic state vector, typically mass, momentum, and various tracers. |\n| [`Auxiliary`](@ref) | any variables required for the balance law that aren't related to derivatives of the state variables (e.g. spatial coordinates or various integrals) or those needed to solve expensive auxiliary equations (e.g., temperature via a non-linear equation solve)     |\n| [`Gradient`](@ref) | the gradients of functions of the prognostic state variables. used to represent values before **and** after differentiation |\n| [`GradientFlux`](@ref) | the gradient fluxes necessary to impose Neumann boundary conditions. typically the product of a diffusivity tensor with a gradient state variable, potentially equivalent to the second-order flux for a prognostic state variable |\n| [`UpwardIntegrals`](@ref) | any one-dimensional vertical integrals from **bottom to top** of the domain required for the balance law. used to represent both the integrand **and** the resulting indefinite integral |\n| [`DownwardIntegrals`](@ref) | any one-dimensional vertical integral from **top to bottom** of the domain required for the balance law. each variable here must also exist in `vars_state` since the reverse integral kernels use subtraction to reverse the integral instead of performing a new integral. use to represent the value before **and** after reversing direction |\n\n## Methods to compute gradients and integrals\n| **Method** |  Purpose |\n|:-----|:-----|\n| [`compute_gradient_argument!`](@ref) | specify how to compute the arguments to the gradients. can be functions of prognostic state  and auxiliary variables. |\n| [`compute_gradient_flux!`](@ref) | specify how to compute gradient fluxes. can be a functions of the gradient state, the prognostic state, and auxiliary variables.|\n| [`integral_load_auxiliary_state!`](@ref) | specify how to compute integrands. can be functions of the prognostic state and auxiliary variables. |\n| [`integral_set_auxiliary_state!`](@ref) | specify which auxiliary variables are used to store the output of the integrals. |\n| [`reverse_integral_load_auxiliary_state!`](@ref) | specify auxiliary variables need their integrals reversed. |\n| [`reverse_integral_set_auxiliary_state!`](@ref) | specify which auxiliary variables are used to store the output of the reversed integrals. |\n| [`update_auxiliary_state!`](@ref) | perform any updates to the auxiliary variables needed at the beginning of each time-step. Can be used to solve non-linear equations, calculate integrals, and apply filters. |\n| [`update_auxiliary_state_gradient!`](@ref) | same as above, but after computing gradients and gradient fluxes in case these variables are needed during the update. |\n\n\n## Methods to compute fluxes and sources\n| **Method** | Purpose |\n|:-----|:-----|\n| [`flux_first_order!`](@ref) |  specify `F_{first_order}` for each prognostic state variable. can be functions of the prognostic state and auxiliary variables. |\n| [`flux_second_order!`](@ref)    |  specify `F_{second_order}` for each prognostic state variable. can be functions of the prognostic state, gradient flux state, and auxiliary variables. |\n| [`source!`](@ref)            |  specify `S_{non_conservative}`  for each prognostic state variable. can be functions of the prognostic state, gradient flux state, and auxiliary variables. |\n\n## Methods to compute numerical fluxes\n| **Method** | Purpose |\n|:-----|:-----|\n| [`wavespeed`](@ref) | specify how to compute the local wavespeed if using the `RusanovNumericalFlux`. |\n| [`boundary_state!`](@ref) | define exterior nodal values of the prognostic state and gradient flux state used to compute the numerical boundary fluxes. |\n\n## Methods to set initial conditions\n| **Method** | Purpose |\n|:-----|:-----|\n| [`init_state_prognostic!`](@ref) | provide initial values for the prognostic state as a function of time and space. |\n| [`init_state_auxiliary!`](@ref) | provide initial values for the auxiliary variables as a function of the geometry. |\n\n\n## General Remarks\n\nWhile `Y` can be thought of a column vector (each _row_ of which corresponds\nto each state variable and its prognostic equation), the _second_ function\nargument inside these methods behave as dictionaries, for example:\n\n```julia\nstruct MyModel <: BalanceLaw end\n\nfunction vars_state(m::MyModel, ::Prognostic, FT)\n    @vars begin\n        ρ::FT\n        T::FT\n    end\nend\n\nfunction source!(m::MyModel, source::Vars, args...)\n    source.ρ = 1 # adds a source of 1 to RHS of ρ equation\n    source.T = 1 # adds a source of 1 to RHS of T equation\nend\n```\n\nAll equations are marched simultaneously in time.\n\n\n# Reference links\n\n[^1]: [Column Vectors](https://en.wikipedia.org/wiki/Row_and_column_vectors)\n\n[^2]: Note that using non-conservative sources should be a final resort,\nas this can leak conservation of the unknowns and lead to numerical\ninstabilities. It is recommended to use either `F_{diffusive}` or\n`F_{non_diffusive}`, as these fluxes are communicated across elements[^3]\n\n[^3]: MPI communication occurs only across elements, not within each element,\nwhere there may be many [Gauss-Lobatto][^4] points\n\n[^4]: [Gauss-Lobatto](https://en.wikipedia.org/wiki/Gaussian_quadrature#Gauss%E2%80%93Lobatto_rules)\n"
  },
  {
    "path": "docs/src/HowToGuides/Diagnostics/UsingDiagnostics.md",
    "content": "# [Using Diagnostics](@id How-to-diagnostics)\n\n```@meta\nCurrentModule = ClimateMachine\n```\n\nAn experiment can configure ClimateMachine to output various diagnostic\nvariables to NetCDF files at specifiable intervals during a simulation.\nTo do so, it must create a\n[`ClimateMachine.DiagnosticsConfiguration`](@ref) which is passed to\n[`ClimateMachine.invoke!`](@ref) with the `diagnostics_config` keyword.\n\n```@meta\nCurrentModule = ClimateMachine.Diagnostics\n```\n\nA `DiagnosticsConfiguration` is constructed with a list of the\n[`DiagnosticsGroup`](@ref)s of interest. The `DiagnosticsGroup`s\ncurrently defined together with the functions used to construct them\nare:\n\n- `AtmosGCMDefault` and `AtmosLESDefault` -- [`setup_atmos_default_diagnostics`](@ref)\n- `AtmosLESCore` -- [`setup_atmos_core_diagnostics`](@ref)\n- `AtmosLESDefaultPerturbations` -- [`setup_atmos_default_perturbations`](@ref)\n- `AtmosRefStatePerturbations` -- [`setup_atmos_refstate_perturbations`](@ref)\n- `AtmosTurbulenceStats` -- [`setup_atmos_turbulence_stats`](@ref)\n- `AtmosMassEnergyLoss` -- [`setup_atmos_mass_energy_loss`](@ref)\n- `AtmosLESSpectra` and `AtmosGCMSpectra` -- [`setup_atmos_spectra_diagnostics`](@ref)\n- `DumpState` -- [`setup_dump_state_diagnostics`](@ref)\n- `DumpAux` -- [`setup_dump_aux_diagnostics`](@ref)\n- `DumpTendencies` -- [`setup_dump_tendencies_diagnostics`](@ref)\n\nEach of these diagnostics groups contains a set of diagnostic\nvariables.\n\n```@meta\nCurrentModule = ClimateMachine\n```\n\nUsers can define their own diagnostics groups, and the\n[`ClimateMachine.DiagnosticsMachine`](@ref), currently in development,\nprovides functionality to simplify doing so.\n\n```@meta\nCurrentModule = ClimateMachine.Diagnostics\n```\n\nHere is a code snippet adapted from the Taylor-Green vortex experiment,\nshowing the creation of three diagnostics groups and their use:\n```julia\n...\nusing ClimateMachine.Diagnostics\n...\n    ts_dgngrp = setup_atmos_turbulence_stats(\n        AtmosLESConfigType(),\n        \"360steps\",\n        driver_config.name,\n        tnor,\n        titer,\n    )\n\n    boundaries = [\n        xmin ymin zmin\n        xmax ymax zmax\n    ]\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries,\n        resolution,\n    )\n    ds_dgngrp = setup_atmos_spectra_diagnostics(\n        AtmosLESConfigType(),\n        \"0.06ssecs\",\n        driver_config.name,\n        interpol = interpol,\n        snor,\n    )\n\n    me_dgngrp = setup_atmos_mass_energy_loss(\n        AtmosLESConfigType(),\n        \"0.02ssecs\",\n        driver_config.name,\n    )\n\n    dgn_config = ClimateMachine.DiagnosticsConfiguration([\n        ts_dgngrp,\n        ds_dgngrp,\n        me_dgngrp,\n    ],)\n...\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        check_cons = check_cons,\n        check_euclidean_distance = true,\n    )\n```\n\nWhen this experiment is run with the command line argument\n`--diagnostics=default`, three NetCDF files are created, one for each\ngroup. The `AtmosLESSpectra` diagnostic variables are output on an\ninterpolated grid, as specified above. Each group has a different\ninterval specified, thus the number of entries in each NetCDF file\n(along the unlimited `time` dimension) will differ.\n\nWhen designing customized diagnostics groups, please use the above\nexample as a template and refer to the [list of current diagnostics\nvariables](@ref Diagnostics-vars).\n"
  },
  {
    "path": "docs/src/HowToGuides/Land/index.md",
    "content": "# Land\n\n"
  },
  {
    "path": "docs/src/HowToGuides/Numerics/Meshes/index.md",
    "content": "# Meshes\n"
  },
  {
    "path": "docs/src/HowToGuides/Numerics/ODESolvers/Timestepping.md",
    "content": "# Contribution Guide for Abstract Time-stepping Algorithms\n\n```@meta\nCurrentModule = ClimateMachine.ODESolvers\n```\n\nThis guide gives a brief overview on how time-stepping methods are\nimplemented in [ClimateMachine](https://github.com/CliMA/ClimateMachine.jl),\nand how one might contribute a new time-stepping method.\n\nCurrently, ClimateMachine supports a variety of time-stepping methods\nwithin the Runge-Kutta framework. For purely explicit time-integration,\nClimateMachine supports the following methods:\n\n1. [`LSRK54CarpenterKennedy`](@ref)\n2. [`LSRK144NiegemannDiehlBusch`](@ref)\n3. [`SSPRK33ShuOsher`](@ref)\n4. [`SSPRK34SpiteriRuuth`](@ref)\n\nMethods 1 and 2 are implemented as low-storage Runge-Kutta methods,\nwhich uses a 2N storage scheme for the coefficient arrays of the given\ntime-stepping method (known as the Butcher Tableau). All time-integration\nmethods are part of a single **module**: **ODESolvers**.  Each Runge-Kutta\nmethod requires **one struct**, with a **constructor**.\n\n## Basic Template for an explicit Runge-Kutta Method\n\nA basic template for an explicit Runge-Kutta method is as follows:\n\n```julia\nexport MyExplicitRKMethod\n\nstruct MyExplicitRKMethod{T, RT, AT, Nstages} <: AbstractODESolver\n    \"time step size\"\n    dt::RT\n    \"rhs function\"\n    rhs!\n    \"Storage for the stage vectors\"\n    Qstage::AT\n    \"RK coefficient vector A (rhs scaling)\"\n    RKA::Array{RT, 2}\n    \"RK coefficient vector B (rhs accumulation scaling)\"\n    RKB::Array{RT, 1}\n    \"RK coefficient vector C (temporal scaling)\"\n    RKC::Array{RT, 1}\n    # May require more attributes depending on the type of RK method\n\n    # Constructor\n    function MyExplicitRKMethod(args...)\n        # Body of the constructor\n        ...\n        return MyExplicitRKMethod(dt, rhs, Qstage, RKA, RKB, RKC)\n    end\nend\n```\n\nOnce `MyExplicitRKMethod` is defined, we require to implement an appropriate\n`dostep!` function, which defines how to step the state vector `Q` forward\nin time:\n\n```julia\nfunction ODESolver.dostep!(Q, rkmethod::MyExplicitRKMethod, p,\n                           time::Real,...)\n    # Function body\nend\n```\nOnce `dostep!` is implemented, `MyExplicitRKMethod` should be ready for\nuse in ClimateMachine.\n\n## Basic Template for an IMEX/Additive Runge-Kutta Method\n\nIMEX, or IMplicit-EXplicit, Runge-Kutta methods require a bit more\nattention. IMEX methods are typically constructed from Additively-partitioned\nRunge-Kutta (ARK) methods. For IMEX methods, the standard way is to consider\nan ARK method with **two** partitions: one explicit part, and one implicit\npart. The implicit part will require a linear solver.\n\n\nAn ARK method with an explicit and implicit component will require **two**\nButcher Tableaus: one for each of the partitioned components.  Additionally,\na linear solver is required.  Currently, ClimateMachine supports the follow\nset of ARK methods for IMEX-based timestepping:\n\n1. [`ARK1ForwardBackwardEuler`](@ref)\n2. [`ARK2ImplicitExplicitMidpoint`](@ref)\n3. [`ARK2GiraldoKellyConstantinescu`](@ref)\n4. [`ARK548L2SA2KennedyCarpenter`](@ref)\n5. [`ARK437L2SA1KennedyCarpenter`](@ref)\n\nFor example, consider the following:\n\n```julia\nexport MyIMEXMethod\n\nstruct MyIMEXMethod{T, RT, AT, LT, V, VS, Nstages, Nstages_sq} <: AbstractODESolver\n    \"time step\"\n    dt::RT\n    \"rhs function\"\n    rhs!\n    \"rhs linear operator\"\n    rhs_linear!\n    \"implicit operator, pre-factorized\"\n    implicitoperator!\n    \"linear solver\"\n    linearsolver::LT\n    \"Stage vectors for the ARK method\"\n    Qstages::NTuple{Nstages, AT}\n    \"RK coefficient matrix A for the explicit scheme\"\n    RKA_explicit::SArray{NTuple{2, Nstages}, RT, 2, Nstages_sq}\n    \"RK coefficient matrix A for the implicit scheme\"\n    RKA_implicit::SArray{NTuple{2, Nstages}, RT, 2, Nstages_sq}\n    \"RK coefficient vector B (rhs accumulation scaling)\"\n    RKB::SArray{Tuple{Nstages}, RT, 1, Nstages}\n    \"RK coefficient vector C (temporal scaling)\"\n    RKC::SArray{Tuple{Nstages}, RT, 1, Nstages}\n\n    # May have more attributes depending on the method\n\n    # Constructor\n    function MyIMEXMethod(args...)\n        # Body of the constructor\n        ...\n        return MyIMEXMethod(dt, rhs, rhs_linear, implicitoperator,\n                            Qstages, RKA_explicit, RKA_implicit,\n                            RKB, RKC)\n    end\n```\n\nIn addition to a `dostep!` function, IMEX methods also require functions\nrelated to the `implicitoperator`, which should be interpreted as a matrix\noperator representing the implicit components. Depending on the coefficients\nin `RKA_implicit`, a linear solve may be required at each stage of the ARK\nmethod, or only a subset of the total stages. If the implicit operator is\nchanging with each stage, then it will need to be updated via a **function**\n`updatedt!`:\n\n```julia\nfunction updatedt!(ark::MyIMEXMethod, dt::Real)\n    # Function body\n    ark.implicitoperator! = prefactorize(...)\nend\n```\nFor information on the function `prefactorize`, see\nthe **module** `ClimateMachine.SystemSolvers`.\n\n## The Struct and its Constructor\n\nThe `Struct` defining important quantities for a given time-integrator is\na subset of an `AbstractODESolver`. For simplicity, we assume a standard\nRunge-Kutta method:\n\n```julia\nstruct MyRKMethod{T, RT, AT, Nstages} <: AbstractODESolver\n    \"time step size\"\n    dt::RT\n    \"rhs function\"\n    rhs!\n    \"Storage for the stage vectors\"\n    Qstage::AT\n    \"RK coefficient vector A (rhs scaling)\"\n    RKA::Array{RT, 2}\n    \"RK coefficient vector B (rhs accumulation scaling)\"\n    RKB::Array{RT, 1}\n    \"RK coefficient vector C (temporal scaling)\"\n    RKC::Array{RT, 1}\n    # May require more attributes depending on the type of RK method\n\n    # Constructor\n    function MyRKMethod(args...)\n        # Body of the constructor\n        ...\n        return MyRKMethod(constructor_args...)\n    end\nend\n```\nSince time-integration methods are often complex and drastically different\nfrom one another, the `Struct` and its `Constructor`, `MyRKMethod(args...)`,\nwill often look quite different, i.e. explicit and IMEX time-integrators\nhave different `Struct` attributes and `Constructor` arguments.\n\nAs a general rule of thumb, all Runge-Kutta-based methods will need\nto keep track of the time-step size `dt` as wells as the Butcher\ntableau coefficients. If your time-integrator has an implicit component\n(semi-implicit) or is fully implicit, the `Struct` will need to know about\nthe `implicitoperator` and the corresponding `linearsolver`.\n\n## The `dostep!` function\n\nNo matter the type of time-integration method, **all** time-steppers\nrequire the implementation of the `dostep!` function. Suppose we have some\ntime-stepper, say `MyTimeIntegrator`. Then the arguments to the `dostep!`\nfunction will be:\n\n```julia\nfunction dostep!(\n    Q,\n    rkmethod::MyTimeIntegrator,\n    p,\n    time,\n    slow_δ = nothing,\n    slow_rv_dQ = nothing,\n    in_slow_scaling = nothing,\n)\n    # Function body\nend\n```\nWhere `Q` is the state vector, `time` denotes the time for the\nnext time-step, the time-integrator, and `slow_δ`, `slow_rv_dQ`,\n`in_slow_scaling` are optional arguments contributing to additional terms\nin the ODE right-hand side. More information on those argument will be\ncovered in a later section. Note that the argument `p` should be interpreted\nas a context manager for more sophisticated time-stepping methods (for\nexample, schemes with *multiple* RK methods); typical Runge-Kutta schemes\nwill generally not need to worry about the argument `p`.  The argument\n`rkmethod` is used for multiple dispatch, and `Q` is an array that gets\noverwritten with field values at the next time-step.\n\n## Multirate Runge-Kutta Methods\n\nMultirate time-integration is a popular approach for weather and climate\nsimulations. The core idea is that the ODE in question can be expressed\nas the sum of a `fast` and `slow` component. In the atmosphere, `fast`\ndynamics include the modes associated with acoustic waves (assuming a\ncompressible or pseudo-compressible model of the atmosphere), typically on\nthe order of 300 m/s, while dynamics associated with advection, diffusion,\nand radiation represent `slow` dynamics. The core idea behind a multirate\nmethod is to step each component (fast and slow) forward in time at a\ndifferent rate (hence the name \"Multi-Rate\").\n\nThere are several different approaches for multirate\nmethods. In ClimateMachine, a multirate time-stepper is provided as\n[`MultirateRungeKutta`](@ref), which takes a given number\nof Runge-Kutta methods (one for each rate).\n\n### Implementation Considerations\nGenerally speaking, a multirate method requires composing several different\ntime-stepping methods for different components of the ODE. Therefore, the\n`Struct` and its `Constructor` may look something like:\n\n```julia\nexport MyMultirateMethod\n\nstruct MyMultirateMethod{SS, FS, RT} <: AbstractODESolver\n    \"slow solver\"\n    slow_solver::SS\n    \"fast solver\"\n    fast_solver::FS\n    # May require more attributes depending on implementation\n\n    # Constructor\n    function MyMultirateMethod(args...)\n        # Body of constructor\n        ...\n        return MyMultirateMethod(constructor_args...)\n    end\nend\n```\n\nOne can imagine a scenario where several rates are operating in tandem. There\nare a number of possible approaches for handling this. One example is to\n*recursively* nest multiple `MyMultirateMethod` instances:\n\n```julia\nfunction MyMultirateMethod(solvers::Tuple, Q; dt::Real)\n    # Take a tuple of solvers and defined a nested\n    # multirate method\n    fast_solver = MyMultirateMethod(solvers[2:end], Q; dt = dt)\n    slow_solver = solver[1]\n    return MyMultirateMethod(slow_solver, fast_solver, Q; dt = dt)\nend\n```\nNote that this example assumes the solvers `Tuple` is ordered in such a\nway that the first element is the *slowest* solver, while all subsequent\nsolvers are faster than the previous.\n\nJust like all other previously mentioned time-integrators, the `dostep!`\nfunction will need to be implemented, taking into account the nesting of\nseveral solvers.\n\n## Writing Tests\n\nTesting is critical for the success and sustainability of any software\nproject. Therefore, it is absolutely *imperative* for all newly added\ntime-integrator to have a corresponding regression test.\n\nThe standard way is to consider an ODE with an analytic solution.\nA given time-integrator will have a known convergence rate, and thus\na good regression test would be to verify temporal convergence\nin the computed solution. Several examples can be found in\n`ClimateMachine.jl/test/ODESolvers`.\n\n\n## Performance Checks\n\nTiming performance of a time-integrator can be done using standard guidelines\nfor CPU and GPU architectures. Certain factors that impact the performance\nof a time-integrator includes the following:\n\n1. Memory management -- how much memory is a given method using, in\nparticular, storing stage vectors for RK methods. For IMEX methods, using\ndirect solvers (LU factorization, for example) often has a significant\nimpact on memory usage.\n\n2. Right-hand side evaluations -- for explicit methods, the total number\nof function evaluations contributes to most of the arithmetic intensity\nof the time-integrator. More evaluates require more compute resources.\n\n3. Solving linear systems -- for IMEX or implicit methods, solving a linear\nsystem of equations is required. This is arguably the most expensive\npart of any IMEX/implicit time-integrator. Things to consider include:\niterative methods, preconditioning, and parallel scalability.\n\n"
  },
  {
    "path": "docs/src/HowToGuides/Numerics/SystemSolvers/IterativeSolvers.md",
    "content": "# Contribution Guide for Abstract Iterative Solvers\n\n```@meta\nCurrentModule = ClimateMachine.SystemSolvers\n```\n\nAn abstract iterative solver requires **one struct**, **one constructor**,\nand **two functions** in order to interface with the rest of [ClimateMachine](https://github.com/CliMA/ClimateMachine.jl).\nIn what follows we will describe in detail the function signatures,\nreturn values, and struct properties necessary to build with\n[ClimateMachine](https://github.com/CliMA/ClimateMachine.jl).\n\nWe have the following concrete implementations:\n1. [`GeneralizedMinimalResidual`](@ref)\n2. [`GeneralizedConjugateResidual`](@ref)\n3. [`ConjugateGradient`](@ref)\n4. [`BatchedGeneralizedMinimalResidual`](@ref)\n\n## Basic Template for an Iterative Solver\n\nA basic template of an iterative solver could be as follows:\n\n```julia\n\nexport MyIterativeMethod\n\n# struct\nstruct MyIterativeMethod{FT} <: AbstractIterativeSystemSolver\n    # minimum\n    rtol::FT\n    atol::FT\n    # Add more structure if necessary\nend\n\n# constructor\nfunction MyIterativeMethod(args...)\n    # body of constructor\n    return MyIterativeMethod(contructor_args...)\nend\n\n# initialize function (1)\nfunction initialize!(linearoperator!, Q, Qrhs, solver::MyIterativeMethod, args...)\n    # body of initialize function in abstract iterative solver\n    return Bool, Int\nend\n\n# iteration function (2)\nfunction doiteration!(linearoperator!, Q, Qrhs, solver::MyIterativeMethod, threshold, args...)\n    # body of iteration\n    return Bool, Int, Float\nend\n\n```\nMyIterativeMethod and function bodies would need to be replaced appropriately\nfor a particular implementation. We will describe each component in detail\nin subsequent sections.\n\n### Struct\n\nA subset of AbstractIterativeSystemSolver needs at least two members:\natol and rtol. The former represents an absolute tolerance and the latter\nis a relative tolerance. Both can be used to terminate the iteration to\ndetermine the convergence criteria. An example struct could be\n\n```julia\nstruct MyIterativeMethod{FT} <: AbstractIterativeSystemSolver\n    atol::FT\n    rtol::FT\nend\n```\nbut often has more depending on the kind of iterative solver\nbeing used.  For example, in a Krylov subspace method one would\nneed to store a number of vectors which constitute the [Krylov\nsubspace](https://en.wikipedia.org/wiki/Krylov_subspace).\n\n### Constructor\n\nThe constructor for the struct can be defined any number of ways depending\non the needs of the struct itself. Often times this is just used to allocate\nmemory or convergence thresholds. This can also be a good place to define\nstructures that make the iterative solver easier to work with. For example,\nfor a columnwise solver one would want an easy array structure to work\nwith vectors in a columnwise fashion.\n\nIn [Basic Template for an Iterative Solver](@ref) we used an outer\nconstructor, e.g.,\n\n```julia\n# constructor\nfunction MyIterativeMethod(args...)\n    # body of constructor\n    return MyIterativeMethod(contructor_args...)\nend\n```\nbut we could have also used an inner constructor if desired.\n\n### Initialize Function\n\nThe initialize function needs the following signature\n```julia\nfunction initialize!(linearoperator!, Q, Qrhs, solver::MyIterativeMethod, args...)\n    # body of initialize function in abstract iterative solver\n    return Bool, Int\nend\n```\n\n\nThe intialize function has the following **arguments**:\n1. ```linearoperator!``` (function)\n1. ```Q```    (array) [OVERWRITTEN]\n1. ```Qrhs``` (array)\n1. ```solver``` (struct) used for dispatch\n1. ```args...``` passed to ```linearoperator!``` function\n\nThe ```linearoperator!``` function is assumed to have the following signature:\n```julia\nlinearoperator!(y, x, args...)\n    # body of linear operator\n    return nothing\nend\n```\nIt represents action of a linear operator ``L`` on a vector ``x``, that\nstores the value in the vector ``y``, i.e. ``Lx = y``. The last argument\n(the args...) is necessary due to how linear operators are defined within\nClimateMachine.\n\nThe ``` Q ``` and ```Qrhs``` function arguments are supposed to represent\nthe solution of the linear system `LQ = Qrhs` where `L` is the linear\noperator implicitly defined by ```linearoperator!```.\n\nThe initialize function must have **2 return values**:\n1. ```convergence``` (bool)\n1. ```iterations``` (int)\n\nThe return values keep track of whether or not the iterative algorithm\nhas converged as well as how many times the linear operator was applied.\n\n### Iteration Function\n\nThe iteration function needs the following signature\n\n```julia\nfunction doiteration!(linearoperator!, Q, Qrhs, solver::MyIterativeMethod, threshold, args...)\n    # body of iteration\n    return Bool, Int, Float\nend\n```\n\nThe iteration function has the following **arguments**:\n1. ```linearoperator!``` (function)\n1. ```Q``` (array) [OVERWRITTEN]\n1. ```Qrhs``` (array)\n1. ```solver``` (struct). used for dispatch\n1. ```threshold``` (float). for the convergence criteria\n1. ```args...``` passed into the ```linearoperator!``` function\n\nThe ```linearoperator!``` function is assumed to have the following signature:\n```julia\nlinearoperator!(y, x, args...)\n    # body of linear operator\n    return nothing\nend\n```\nIt represents action of a linear operator ``L`` on a vector ``x``, that\nstores the value in the vector ``y``, i.e. ``Lx = y``. The last argument\n(the args...) is necessary due to how linear operators are defined within\nClimateMachine.\n\nThe ``` Q ``` and ```Qrhs``` function arguments are supposed to represent\nthe solution of the linear system `LQ = Qrhs` where `L` is the linear\noperator implicitly defined by ```linearoperator!```.\n\nThe iteration function must have **3 return values**:\n1. ```converged``` (bool)\n1. ```iterations``` (int)\n1. ```residual_norm``` (float64)\n\nThe return values keep track of whether or not the iterative algorithm has\nconverged as well as how many times the linear operator was applied. The\nresidual norm is useful since it is often used to determine a stopping\ncriteria.\n\n## ClimateMachine Specific Considerations\nAn MPIStateArray ```Q``` in 3D, has the following structure by default:\n```julia\nsize(Q) = (n_ijk, n_s, n_e)\n```\nwhere\n1. ```n_ijk``` is the number of Gauss-Lobatto points per element\n1. ```n_s``` is the number of states\n1. ```n_e``` is the number of elements\n\nIn three dimensions, if one wants to operate in a column-wise fashion\n(with a stacked-brick topology) it is easiest to reshape the array into\nthe following form\n\n```julia\nalias_Q = reshape(Q, (n_i, n_j, n_k, n_s, n_ev, n_eh))\n```\nwhere\n1. ```n_i``` is the number of Gauss-Lobatto points per element within\nelement that are aligned with one of the horizontal directions.\n1. ```n_j``` is the number of Gauss-Lobatto points per element within\nelement that are aligned with another one of the horizontal directions.\n1. ```n_k``` is the number of Gauss-Lobatto points within element that\nare aligned with the vertical direction.\n1. ```n_s``` is the number of states\n1. ```n_ev``` is the number of elements in the vertical direction\n1. ```n_eh``` is the number of elements in the horizontal direction\n\nNote: ```n_i x n_j x n_k = n_ijk``` and ```n_ev x n_eh = n_e```.\n\nThus if one wants to operate on a column for a fixed state index (let's say\nthe int ```s```) and a fixed horizontal coordinate (let's say fixed ints\n```i```, ```j```, ```eh```), then one could operator on the state:\n\n```julia\none_column = alias_Q[i, j, :, s, :, eh]\n```\n\nwhich are the third and fifth argument in the MPIStateArray.\n\nSome extra tips are:\n- Since GPUs have limited memory, don't take up too much memory.\n- If possible define a preconditioner. Iterative methods are typically\nvery slow otherwise.\n\n## Preconditioners\n\nThe code needs to be slightly restructured to allow for preconditioners.\n\n## Writing Tests\n\nTest on small systems where answers can be checked analytically. Check with\nmatrices with easily computable inverses, i.e., the identity matrix or a\ndiagonal matrix. Test with diverse matrix structures. Test with different\narray types: Arrays, CuArrays, MPIStateArrays, etc. Also test with balance\nlaws to make sure that it can actually be run with IMEX solvers on the\nCPU/GPU and their distributed analogues.\n\n## Performance Checks\n\nTiming performance can be done with general CPU/GPU guidelines\n\n## Conventions\n\n- Q refers to the initial guess for the iterative solver that gets\noverwritten with the final solution\n- Qrhs refers to the right hand side of the linear system\n"
  },
  {
    "path": "docs/src/HowToGuides/Ocean/index.md",
    "content": "# Ocean\n\n"
  },
  {
    "path": "docs/src/References.md",
    "content": "# References\n\n```@bibliography\n```\n\n"
  },
  {
    "path": "docs/src/Theory/Atmos/AtmosModel.md",
    "content": "# Atmos Model\n\nThis page provides a summary of a specific type of balance law within the\n`ClimateMachine` source code, the `AtmosModel`. This documentation aims to\nintroduce a user to the properties of the `AtmosModel`, including the balance\nlaw equations and default model configurations. Both LES and GCM configurations\nare included.\n\n## Conservation Equations\nThe conservation equations specific to this implementation of `AtmosModel`\nare included below.\n\n### Mass\n```math\n\\frac{\\partial \\rho}{\\partial t} + \\nabla\\cdot (\\rho\\vec{u}) = \\rho \\mathcal{\\hat S}_{q_t}.\n```\n\n### Momentum\n```math\n\\frac{(\\partial \\rho\\vec{u})}{\\partial t} + \\nabla\\cdot \\left[ \\rho\\vec{u} \\otimes \\vec{u} + (p - p_r) \\vec{I}_3\\right] =\n- (\\rho - \\rho_r) \\nabla\\Phi - 2\\vec{\\Omega} \\times \\rho\\vec{u} \\\\\n- \\nabla\\cdot (\\rho \\vec{\\tau}) - \\nabla\\cdot\\left( \\vec{d}_{q_t} \\otimes \\rho\\vec{u} \\right) + \\nabla\\cdot \\left( q_c w_c \\vec{\\hat k} \\otimes \\rho \\vec{u} \\right) + \\rho \\vec{F}_{\\vec{u}}\n```\n\n### Energy\n```math\n \\frac{\\partial(\\rho e^\\mathrm{tot})}{\\partial t} + \\nabla\\cdot \\left( (\\rho e^\\mathrm{tot} + p)\\vec{u} \\right)\n = -\\nabla\\cdot (\\rho \\vec{F}_R) - \\nabla\\cdot \\bigl[\\rho (\\vec{J} + \\vec{D})\\bigr] + \\rho Q  \\\\\n  +\\nabla\\cdot \\left(\\rho W_c \\vec{\\hat k} \\right)  - \\nabla\\cdot (\\vec{u} \\cdot \\rho\\vec{\\tau)} %+ \\rho \\vec{u} \\cdot \\vec{F}_{\\vec{u}} \\\\\n   - \\sum_{j\\in\\{v,l,i\\}}(I_j + \\Phi)  \\rho C(q_j \\rightarrow q_p) - M\n```\n\n### Moisture\n```math\n\\frac{\\partial (\\rho q_t)}{\\partial t} + \\nabla\\cdot (\\rho q_t \\vec{u})\n= \\rho \\mathcal{S}_{q_t} - \\nabla\\cdot (\\rho \\vec{d}_{q_t}) + \\nabla\\cdot \\bigl(\\rho q_c w_c \\vec{\\hat k}  \\bigr)\n\\equiv \\rho \\mathcal{\\hat S}_{q_t}\n```\n\n### Precipitating Species\n```math\n\\frac{\\partial (\\rho q_{p,i})}{\\partial t} + \\nabla\\cdot \\left[\\rho q_{p,i} (\\vec{u} - w_{p,i} \\vec{\\hat k}) \\right] =\\\\\n\\rho \\left[C(q_t \\rightarrow q_{p,i}) + C(q_{p,k} \\rightarrow q_{p,i}) \\right] -\\nabla\\cdot (\\rho \\vec{d}_{q_{p, i}})\n```\n\n### Tracer Species\n```math\n\\frac{(\\partial \\rho \\chi_i)}{\\partial t} + \\nabla\\cdot \\left(\\rho \\chi_i \\vec{u} \\right) = \\rho \\mathcal{S}_{\\chi_i} - \\nabla\\cdot (\\rho \\vec{d}_{\\chi_i}) + \\nabla\\cdot (\\rho \\chi_{i} w_{\\chi, i} \\vec{\\hat k})\n```\n\n## Equation Abstractions\n\n```math\n\\frac{\\partial \\vec{Y}}{\\partial t} = - \\nabla \\cdot (\\vec{F}_{nondiff} + \\vec{F}_{diff} + \\vec{F}_{rad} + \\vec{F}_{precip}) + \\vec{S}\n```\n\n### State Variables\n```math\n\\vec{Y}=\\left( \\begin{array}{c}\n\\rho \\\\\n\\rho\\vec{u} \\\\\n\\rho e^{\\mathrm{tot}}\\\\\n\\rho q_k\\\\\n\\rho q_{p,i}\\\\\n\\rho \\chi_j\n\\end{array}\n\\right).\n```\n\n### Fluxes\n\n#### Nondiffusive Fluxes\n\n```math\n \\mathrm{F}_{nondiff}=\\left( \\begin{array}{c}\n \\rho \\vec{u} \\\\\n \\rho \\vec{u} \\otimes \\vec{u} + (p - p_r) \\vec{I}_3 \\\\\n \\rho e^{\\mathrm{tot}} \\vec{u} + p \\vec{u}\\\\\n \\rho q_k \\vec{u}\\\\\n \\rho q_{p,i} \\vec{u} \\\\\n \\rho \\chi_j \\vec{u}\n\\end{array}\n\\right).\n```\n\n#### Diffusive Fluxes\n\n```math\n\\mathrm{F}_{diff}=\\left( \\begin{array}{c}\n\\rho\\vec{d}_{q_t} \\\\\n\\rho\\vec{\\tau} + \\rho\\vec{d}_{q_t} \\otimes \\vec{u}\\\\\n\\vec{u} \\cdot \\rho\\vec{\\tau} + \\rho (\\vec{J} + \\vec{D}) \\\\\n\\rho\\vec{d}_{q_k}\\\\\n\\rho \\vec{d}_{q_{p, i}}\\\\\n\\rho \\vec{d}_{\\chi_j}\n\\end{array}\n\\right).\n```\n\n#### Radiation Fluxes\n```math\n\\mathrm{F}_{rad} =\n\\left( \\begin{array}{c}\n\\vec{0} \\\\\n\\vec{0} \\\\\n\\rho \\vec{F}_R \\\\\n\\vec{0} \\\\\n\\vec{0} \\\\\n\\vec{0}\n\\end{array}\n\\right)\n```\n\n### Fluxes of precipitating species\n```math\n\\mathrm{F}_{fall} =\n- \\left( \\begin{array}{c}\n\\rho q_{c} w_{c} \\vec{\\hat k}  \\\\\nq_c w_c \\vec{\\hat k} \\otimes \\rho \\vec{u}  \\\\\n\\rho W_c \\vec{\\hat k} \\\\\n\\rho q_{k} w_{k} \\vec{\\hat k}  \\\\\n\\rho q_{p,i} w_{p, i} \\vec{\\hat k} \\\\\n\\rho \\chi_{i} w_{\\chi, i} \\vec{\\hat k}\n\\end{array} \\right)\n```\n\n#### Sources\n\n```math\n\\mathrm{S}(\\vec{Y}, \\nabla\\vec{Y})=\n \\left( \\begin{array}{c}\n -\\rho C(q_t \\rightarrow q_p) \\\\\n  -(\\rho - \\rho_r) \\nabla\\Phi - 2 \\vec{\\Omega} \\times \\rho\\vec{u}  + \\rho \\vec{F}_{\\vec{u}} \\\\\n \\rho Q - \\sum_{j\\in\\{v,l,i\\}} (I_j + \\Phi)  \\rho C(q_j \\rightarrow q_p) - M \\\\% + \\rho \\vec{u} \\cdot \\vec{F}_{\\vec{u}}  \\\\\n\\rho C(q_p \\rightarrow q_k) + \\rho \\sum_j \\rho C(q_j \\rightarrow q_k) \\\\\n    \\rho \\sum_k C(q_k \\rightarrow q_{p, i}) - \\rho \\sum_j C(q_{p, i} \\rightarrow q_{p, j})\\\\\n\\rho \\mathcal{S}_{\\chi_i}\n\\end{array}\n\\right)\n```\n"
  },
  {
    "path": "docs/src/Theory/Atmos/EDMFEquations.md",
    "content": "```math\n\\newcommand{\\paramT}[1]{       \\text{#1}}\n\\newcommand{\\hyperparamT}[1]{\\text{#1}}\n\\newcommand{\\simparamT}[1]{  \\text{#1}}\n\n\\newcommand{\\exp}[1]{\\mathrm{exp}\\left(#1\\right)}\n\\newcommand{\\atan}[1]{\\mathrm{atan}\\left(#1\\right)}\n\\newcommand{\\sign}[1]{\\mathrm{sign}\\left(#1\\right)}\n\\newcommand{\\erf}[1]{\\mathrm{erf}\\left(#1\\right)}\n\\newcommand{\\erfinv}[1]{\\mathrm{erfinv}\\left(#1\\right)}\n\n\\newcommand{\\param}[1]{     #1}\n\\newcommand{\\hyperparam}[1]{#1}\n\\newcommand{\\simparam}[1]{  #1}\n\n\\newcommand{\\CROSS}{\\times}\n\\newcommand{\\GRAD}{\\nabla}\n\\newcommand{\\DOT}{\\bullet}\n\\newcommand{\\PD}{\\partial}\n\\newcommand{\\PDFz}{\\frac{\\PD}{\\PD z}}\n\\newcommand{\\DM}[1]{\\langle #1 \\rangle}\n\\newcommand{\\iEnv}{e}\n\\newcommand{\\SD}[2]{{\\overline{#1}}_{#2}}\n\\newcommand{\\SDi}[1]{{\\SD{#1}{i}}}\n\\newcommand{\\SDj}[1]{{\\SD{#1}{j}}}\n\\newcommand{\\SDe}[1]{{\\SD{#1}{\\iEnv}}}\n\\newcommand{\\SDiog}[2]{#1_{#2}}\n\\newcommand{\\SDio}[1]{{\\SDiog{#1}{i}}}\n\\newcommand{\\SDjo}[1]{{\\SDiog{#1}{j}}}\n\\newcommand{\\SDeo}[1]{{\\SDiog{#1}{\\iEnv}}}\n\\newcommand{\\aSD}[2]{{#1}_{#2}}\n\\newcommand{\\aSDi}[1]{\\aSD{#1}{i}}\n\\newcommand{\\aSDj}[1]{\\aSD{#1}{j}}\n\\newcommand{\\aSDe}[1]{\\aSD{#1}{\\iEnv}}\n\\newcommand{\\otherDefs}{where additional variable definitions are in:}\n\n\\newcommand{\\IntraCVSDi}[2]{\\overline{{#1}_{i      }'{#2}_{i      }'}}\n\\newcommand{\\IntraCVSDj}[2]{\\overline{{#1}_{j      }'{#2}_{j      }'}}\n\\newcommand{\\IntraCVSDe}[2]{\\overline{{#1}_{\\iEnv{}}'{#2}_{\\iEnv{}}'}}\n\n\\newcommand{\\InterCVSDi}[2]{\\overline{{#1}_{i      }'}~\\overline{{#2}_{i      }'}}\n\\newcommand{\\InterCVSDj}[2]{\\overline{{#1}_{j      }'}~\\overline{{#2}_{j      }'}}\n\\newcommand{\\InterCVSDe}[2]{\\overline{{#1}_{\\iEnv{}}'}~\\overline{{#2}_{\\iEnv{}}'}}\n\n\\newcommand{\\TCV}[2]{\\langle {#1}^*{#2}^* \\rangle}\n\n\\newcommand{\\BC}[1]{{#1|_{z_{min}}}}\n\\newcommand{\\BCT}[1]{{#1|_{z_{max}}}}\n\\newcommand{\\BCB}[1]{{#1|_{z_{min}}}}\n\\newcommand{\\BCG}[1]{{#1|_{z_{boundary}}}}\n\n\\newcommand{\\Km}{K^m}\n\\newcommand{\\Kh}{K^h}\n\\newcommand{\\TEquilib}{T_{\\mathrm{iterated}}}\n\\newcommand{\\PhasePartition}{q}\n\\newcommand{\\ExnerD}{\\Pi_{dry}}\n\\newcommand{\\ExnerM}{\\Pi_{moist}}\n\\newcommand{\\WindSpeed}{|u|}\n\\newcommand{\\LayerThickness}{\\param{\\Delta z}}\n\\newcommand{\\SurfaceRoughness}[1]{\\param{z_{0#1}}}\n\\newcommand{\\SensibleSurfaceHeatFlux}{F_{\\mathrm{sensible}''}}\n\\newcommand{\\LatentSurfaceHeatFlux}{F_{\\mathrm{latent}''}}\n\\newcommand{\\FrictionVelocity}{u_*}\n\\newcommand{\\Buoyancy}{b}\n\\newcommand{\\BuoyancyGrad}{\\PD_z \\Buoyancy}\n\\newcommand{\\BuoyancyFlux}{\\IntraCVSDi{w}{b}}\n\\newcommand{\\TemperatureScale}{\\theta_*}\n\\newcommand{\\SurfaceMomentumFlux}{\\BC{\\overline{w'u'}}}\n\\newcommand{\\SurfaceHeatFlux}{\\BC{\\overline{w'\\theta'}}}\n\\newcommand{\\SurfaceBuoyancyFlux}{\\BC{\\IntraCVSDi{w}{\\theta}}}\n\\newcommand{\\ConvectiveVelocity}{{w_*}} % Convective velocity near the surface\n\\newcommand{\\InversionHeight}{{z_*}}\n\\newcommand{\\MOLen}{\\Lambda_{M-O}}\n\\newcommand{\\zLL}{\\param{z_{||}}} % z at the first surface level (we should make this grid-independent)\n\n\\newcommand{\\qt}{q_{\\mathrm{tot}}}\n\\newcommand{\\qr}{q_{\\mathrm{rain}}}\n\\newcommand{\\ql}{q_{\\mathrm{liq}}}\n\\newcommand{\\qi}{q_{\\mathrm{ice}}}\n\\newcommand{\\qv}{q_{\\mathrm{vap}}}\n\\newcommand{\\qvsat}{q_{\\mathrm{vap}}^*}\n\\newcommand{\\pvsat}{p_{\\mathrm{vap}}^*}\n\\newcommand{\\qc}{q_{\\mathrm{con}}}\n\\newcommand{\\ThetaVap}{{\\theta_{\\mathrm{vap}}}}\n\\newcommand{\\ThetaVirt}{{\\theta_{\\mathrm{virt}}}}\n\\newcommand{\\ThetaRho}{{\\theta_{\\rho}}}\n\\newcommand{\\ThetaLiq}{{\\theta_{\\mathrm{liq}}}}\n\\newcommand{\\ThetaLiqIce}{{\\theta_{\\mathrm{liq-ice}}}}\n\\newcommand{\\ThetaLiqIceSat}{{\\theta^*_{\\mathrm{liq-ice}}}}\n\\newcommand{\\ThetaDry}{{\\theta_{\\mathrm{dry}}}}\n\\newcommand{\\TDry}{{T_{dry}}}\n\\newcommand{\\eint}{e_{\\mathrm{int}}}\n\\newcommand{\\etot}{e_{\\mathrm{tot}}}\n\n\\newcommand{\\TRef}{{T}_0}\n\\newcommand{\\alphaRef}{{\\alpha}_0}\n\\newcommand{\\rhoRef}{{\\rho}_0}\n\\newcommand{\\pRef}{{p}_0}\n\\newcommand{\\Heaviside}{\\mathcal H}\n\n\\newcommand{\\alphaLL}{\\alphaRef|_{\\zLL}}\n\\newcommand{\\uH}{\\simparam{\\mathbf{u}_h}}\n\n\\newcommand{\\CoriolisParam}{\\hyperparam{\\mathrm{coriolis\\_param}}}\n\\newcommand{\\SubsidenceParam}{\\hyperparam{\\mathrm{subsidence}}}\n\\newcommand{\\betaM}{\\hyperparam{\\beta_m}}\n\\newcommand{\\betaH}{\\hyperparam{\\beta_h}}\n\\newcommand{\\gammaM}{\\hyperparam{\\gamma_m}}\n\\newcommand{\\gammaH}{\\hyperparam{\\gamma_h}}\n\n\\newcommand{\\PTilde}{\\param{\\tilde{p}}}\n\\newcommand{\\VKConst}{\\param{\\kappa_{\\mathrm{Von-Karman}}}}\n\\newcommand{\\Nsd}{\\hyperparam{N_{sd}}}\n\\newcommand{\\grav}{\\param{g}}\n\\newcommand{\\TZero}{\\param{T_{0}}}\n\\newcommand{\\RefHintV}{\\param{{\\eint}_{v,0}}}\n\\newcommand{\\RefHintI}{\\param{{\\eint}_{i,0}}}\n\n\\newcommand{\\EpsDV}{\\param{\\varepsilon_{dv}}}\n\\newcommand{\\EpsVD}{\\param{\\varepsilon_{vd}}}\n\\newcommand{\\Rm}{R_m}\n\\newcommand{\\Cpm}{c_{pm}}\n\\newcommand{\\Cvm}{c_{vm}}\n\\newcommand{\\Rd}{\\param{R_d}}\n\\newcommand{\\Rv}{\\param{R_v}}\n\\newcommand{\\Cp}[1]{\\param{c_{p#1}}}\n\\newcommand{\\Cv}[1]{\\param{c_{v#1}}}\n\\newcommand{\\Cvd}{\\Cv{d}}\n\\newcommand{\\Cvv}{\\Cv{v}}\n\\newcommand{\\Cvl}{\\Cv{l}}\n\\newcommand{\\Cvi}{\\Cv{i}}\n\n\\newcommand{\\DeltaCp}{\\param{\\Delta c_p}}\n\\newcommand{\\TTriple}{\\param{T_{\\mathrm{tr}}}}\n\\newcommand{\\PTriple}{\\param{p_{\\mathrm{tr}}}}\n\\newcommand{\\TFreeze}{\\param{T_{\\mathrm{freeze}}}}\n\n\\newcommand{\\RefLHv}{\\param{L_{v,0}}}\n\\newcommand{\\RefLHs}{\\param{L_{s,0}}}\n\\newcommand{\\RefLHf}{\\param{L_{f,0}}}\n\\newcommand{\\LatentHeatV}[1]{L_{vap}(#1)}\n\\newcommand{\\LatentHeatS}[1]{L_{sub}(#1)}\n\\newcommand{\\LatentHeatF}[1]{L_{fus}(#1)}\n```\n\n# Eddy-Diffusivity Mass-Flux (EDMF) parameterization\n\nThis document describes the Eddy-Diffusivity Mass-Flux (EDMF)\nparameterization for subgrid-scale (SGS) turbulence and convection.\nThe model equations and rationale are based on:\nTan et al. (2018); Cohen et al. (2020); Lopez-Gomez et al. (2020).\nThe key predictands of the EDMF parameterization are the SGS vertical fluxes of heat, moisture\nand momentum, and the cloud fraction in the host model grid.\nThe EDMF parameterization divides the host model's grid into $N \\ge 2$ subdomains\nthat represent an isotropic turbulent enviroment and coherent updraft(s)\nand or downdraft(s). The parameterization solves prognostic equations\nfor first and second moments in the subdomains to provide both the\nabovementioned SGS vertical fluxes and cloud fraction in the host model grid.\n\nIn this document, color-coding is used to indicate:\n - $\\paramT{Constant parameters that are fixed in space and time (e.g., those defined in CLIMAParameters.jl)}$\n\n - $\\simparamT{Single column (SC) inputs (e.g., variables that are fed into the SC model from the dynamical core (e.g., horizontal velocity))}$\n\n - $\\hyperparamT{Tunable hyper-parameters that will need to be changeable, but will only include single numbers (e.g., Float64)}$\n\n## Subdomain decomposition\n\nThe EDMF is 1D model in $z$ that solves for the statistical distribution in the grid box of the host model. As such in the EDMF there is no spatial discretization in the horizontal directions ($x$ and $y$), the horizontal space is broken into $\\Nsd$ ($\\sim$ 1-5) \"subdomains\" (SDs), denoted by subscript $i$, where $1 \\le i \\le \\Nsd$. One of the subdomains, the \"environment\", is treated different compared to others, termed \"updrafts\" (and or \"downdrafts\"). This environment subdomain is denoted with a special index $\\iEnv{}$ (which we usually set to 0). For dummy variables $\\phi$ and $\\psi$, we use several domain and SD representations of interest:\n\n```math\n\\begin{align}\n  \\SDi{\\phi}                                                                                   \\quad & \\text{horizontal mean of variable $\\phi$ over subdomain $i$}, \\\\\n  \\SDi{\\phi}' = \\phi_i - \\SDi{\\phi}                                                            \\quad & \\text{fluctuations of $\\phi$ about the subdomain mean}, \\\\\n  \\IntraCVSDi{\\phi}{\\psi}                                                                      \\quad & \\text{intra subdomain covariance}, \\\\\n  \\DM{\\phi} = \\sum_i \\aSDi{a} \\SDi{\\phi}                                                       \\quad & \\text{horizontal mean of $\\phi$ over the total domain}, \\\\\n  \\SDi{\\phi}^* = \\SDi{\\phi} - \\DM{\\phi}                                                        \\quad & \\text{difference between subdomain & domain means}, \\\\\n  \\InterCVSDi{\\phi}{\\psi}                                                                      \\quad & \\text{inter subdomain covariance among subdomain means}, \\\\\n  \\phi^* = \\phi - \\DM{\\phi}                                                                    \\quad & \\text{difference between subdomain & domain means}, \\\\\n  \\TCV{\\phi}{\\psi} = \\sum_{\\forall i} a_i \\IntraCVSDi{\\phi}{\\psi} +\n  \\sum_{\\forall i} \\sum_{\\forall j}  \\aSDi{a} \\aSDj{a} \\SDi{\\phi}(\\SDi{\\psi} - \\SDj{\\psi})     \\quad & \\text{total covariance}.\n\\end{align}\n```\n\nHere, $\\SDi{\\phi}$ and $\\SDi{\\psi}$ are a dummy variables for the following 7 unknowns:\n```math\n\\begin{align}\n  \\SDi{w}                   & \\quad \\text{vertical velocity}, \\\\\n  \\SDi{\\eint}               & \\quad \\text{internal energy},  \\\\\n  \\SDi{\\qt}                 & \\quad \\text{total water specific humidity},  \\\\\n  \\SDi{TKE}                 & \\quad \\text{turbulent kinetic energy ($0.5(\\IntraCVSDi{u}{u}+\\IntraCVSDi{v}{v}+\\IntraCVSDi{w}{w})$)},  \\\\\n  \\IntraCVSDi{\\eint}{\\eint} & \\quad \\text{intra subdomain covariance of $\\eint'$ and $\\eint'$},  \\\\\n  \\IntraCVSDi{\\qt}{\\qt}     & \\quad \\text{intra subdomain covariance of $\\qt'$ and $\\qt'$}, \\\\\n  \\IntraCVSDi{\\eint}{\\qt}   & \\quad \\text{intra subdomain covariance of $\\eint'$ and $\\qt'$}.\n\\end{align}\n```\n\nFrom the large-scale model perspective, $\\DM{\\phi}$ represents the resolved grid mean, and $\\TCV{\\phi}{\\psi}$ represents the SGS fluxes and (co)-variances of scalars that need to be parameterized. Equations in the following sections, \\eqref{eq:AreaFracGov}, \\eqref{eq:1stMoment} and \\eqref{eq:2ndMoment}, are solved on $z_{min} \\le z \\le z_{max}$ and $t \\ge 0$. There are $8 \\Nsd$ equations in total.\n\n## Domain averaged equations\nThe EDMF model can be used in the context of a stand-alone single column, or integrated with a dynamical core. Either way, the EDMF model relies on grid mean variables, which may be prescribed or solved for. Taking an area fraction-weighted average of the subdomain equations yields the domain-averaged equations (which should be consistent with variables in the dynamical core).\n\nThe domain-averaged equations for $\\DM{\\phi} \\in [w, \\qt, \\eint, \\uH]$ are:\n\n```math\n\\begin{align}\n\\PD_t (\\rhoRef{} \\DM{\\phi})\n+ \\PD_z (\\rhoRef{} \\DM{w} \\DM{\\phi})\n+ \\nabla_h \\DOT \\left( \\rhoRef{} \\DM{\\phi} \\otimes \\DM{\\phi} \\right)\n= \\\\\n  \\DM{S}_{\\text{diff}}^{\\DM{\\phi}}\n+ \\DM{S}_{\\text{press}}\n+ \\DM{S}_{\\text{coriolis}}\n+ \\DM{S}_{\\text{subsidence}},\n\\end{align}\n```\nwhere\n```math\n\\begin{align}\n\\DM{S}_{\\text{diff}}^{\\DM{\\phi}} & = \\PD_z (\\rhoRef{} \\aSDe{a} \\SDe{\\Km} \\PD_z \\DM{\\phi}),   \\label{eq:gm_diffusion} \\\\\n\\DM{S}_{\\text{diff}}^{w}         & = \\PD_z (\\rhoRef{} \\aSDe{a} \\SDe{\\Km} \\PD_z \\DM{w})   ,   \\label{eq:gm_diffusion_w} \\\\\n\\DM{S}_{\\text{press}}            & = - \\GRAD_h \\DM{p},                                       \\label{eq:gm_pressure} \\\\\n\\DM{S}_{\\text{coriolis}}         & = \\CoriolisParam \\DM{\\phi} \\CROSS \\mathbf{k},             \\label{eq:gm_coriolis} \\\\\n\\DM{S}_{\\text{subsidence}}       & = - \\SubsidenceParam \\GRAD \\phi,                          \\label{eq:gm_subsidence} \\\\\n\\end{align}\n```\n\n## Sub-domain equations: Area fraction\n\nThe EDMF equations take the form of advection-diffusion equations. The subdomain area fraction is given by the mass continuity equation in the $i$th subdomain ($\\aSDi{a}$):\n\n```math\n\\begin{gather}\n  \\PD_t (\\rhoRef{} \\aSDi{a})\n  + \\PD_z (\\rhoRef{} \\aSDi{a} \\SDi{w})\n  + \\GRAD_h \\DOT\n  (\\rhoRef{} \\aSDi{a} \\DM{\\uH})\n  =\n  \\SDi{S}^a\n  , \\quad i \\ne \\iEnv, \\label{eq:AreaFracGov} \\\\\n  \\aSDi{a} = 1 - \\sum_{j\\ne\\iEnv} \\aSDj{a}, \\quad i = \\iEnv, \\label{eq:AreaFracConserve} \\\\\n  \\qquad 0 < \\aSDi{a} < 1. \\label{eq:AreaFracConstraint}\n\\end{gather}\n```\n\nHere, $\\rhoRef{}, \\SDi{w}, \\uH$ is fluid density, mean vertical velocity along $z$, and domain-mean of the horizontal velocity respectively. The area fraction constraints are necessary to ensure the system of equations is well-posed. All source terms ($\\SDi{S}^a$) will be discussed in later sections.\n\n!!! note\n\n    The greater than zero constraint must be satisfied at every step of the solution process, since it is necessary to avoid division by zero in the mean field equations.\n\n```math\n\\begin{align}\n\\SDi{S}^a = \\SDi{S_{\\epsilon\\delta}}^a.\n\\end{align}\n```\n\n### Source term definitions\nWe note that the net exchange is zero $\\sum_i \\SDi{S_{\\epsilon\\delta}}^a = 0$. Therefore, we may define the environment source term as the negative sum of all updraft source terms. The entrainment-detrainment source is:\n\n```math\n\\begin{align}\n\\SDi{S_{\\epsilon\\delta}}^a =\n\\begin{cases}\n  \\rho a_i \\SDi{w} \\left( -\\delta_i + \\epsilon_{i} \\right) & i \\ne \\iEnv{} \\\\\n  0 - \\sum_{j \\ne \\iEnv{}} \\SDj{S_{\\epsilon\\delta}}^a & i = \\iEnv{} \\\\\n\\end{cases},\n\\end{align}\n```\nwhere additional variable definitions are in:\n\n - [Reference state profiles](@ref) ($\\pRef{}$, $\\rhoRef{}$, and $\\alphaRef{}$).\n\n - [Entrainment-Detrainment](@ref) ($\\epsilon_{i}$) and ($\\delta_i$).\n\n## Sub-domain equations: 1st moment\n\nThe 1st moment sub-domain equations are:\n\n```math\n\\begin{align}\\label{eq:1stMoment}\n  \\PD_t (\\rhoRef{} \\aSDi{a} \\SDi{\\phi})\n  + \\PD_z (\\rhoRef{} \\aSDi{a} \\SDi{w} \\SDi{\\phi})\n  + \\GRAD_h \\DOT\n  (\\rhoRef{} \\aSDi{a} \\DM{\\uH} \\SDi{\\phi})\n  =\n  \\SDi{S}^\\phi\n  , \\quad \\forall i. \\\\\n\\end{align}\n```\n\nHere, $\\SDi{S}^{\\phi}$ are source terms, including diffusion, and many other sub-grid-scale (SGS) physics. In general, $\\SDi{S}^{\\phi}$ and $\\SDi{S}^{a}$ may depend on $\\SDj{\\phi}$ and or $\\aSDj{a}$ for any $j$.\n\n### Source terms per equation\nThe source terms common to all unknowns are:\n\n```math\n\\begin{align}\n\\SDi{S}^{\\phi} =\n  \\SDi{S_{\\epsilon\\delta}}^{\\phi}\n+ \\SDi{S_{\\text{turb-transp}}}^{\\phi}, \\quad \\forall \\phi\n\\end{align}\n```\nAdditional source terms exist in other equations:\n```math\n\\begin{align}\n\\SDi{S}^{w} &=\n  \\SDi{S_{\\epsilon\\delta}}^w\n+ \\SDi{S_{\\text{turb-transp}}}^w\n+ \\SDi{S_{\\text{buoy}}}\n+ \\SDi{S_{\\text{nh-press}}}\n+ \\SDi{S_{\\text{coriolis}}}, \\\\\n\\SDi{S}^{\\eint} &=\n  \\SDi{S_{\\epsilon\\delta}}^{\\eint}\n+ \\SDi{S_{\\text{turb-transp}}}^{\\eint}\n+ \\SDi{S_{\\text{MP-MSS}}}^{\\eint}\n+ \\SDi{S_{\\text{rad}}}, \\\\\n\\SDi{S}^{\\qt} &=\n  \\SDi{S_{\\epsilon\\delta}}^{\\qt}\n+ \\SDi{S_{\\text{turb-transp}}}^{\\qt}\n+ \\SDi{S_{\\text{MP-MSS}}}^{\\qt}.\n\\end{align}\n```\n\n### Source term definitions\n\nNote: The sum of the total pressure and gravity are recast into the sum of the non-hydrostatic pressure and buoyancy sources.\n\n```math\n\\begin{align}\n\\SDi{S_{\\epsilon\\delta}}^{\\phi} &=\n\\begin{cases}\n  \\rhoRef{} a_i \\SDi{w} \\left( -\\delta_i \\SDi{\\phi} + \\epsilon_{i} \\SDe{\\phi} \\right) & i \\ne \\iEnv{} \\\\\n  0 - \\sum_{j \\ne \\iEnv{}} \\SDj{S_{\\epsilon\\delta}}^{\\phi} & i=\\iEnv{} \\\\\n\\end{cases} \\\\\n\\SDi{S_{\\text{turb-transp}}}^{\\phi} & =  -\\PD_z (\\rhoRef{} a_i \\IntraCVSDi{w}{\\phi}) \\\\\n & = \\PD_z (\\rhoRef{} a_i \\SDi{\\Km} \\PD_z \\SDi{\\phi}) \\\\\n\\SDi{S_{\\text{nh-press}}} &= -\\rhoRef{} \\aSDi{a} \\left( \\alpha_b \\SDi{b}  + \\alpha_d \\frac{(\\SDi{w} - \\SDe{w}) | \\SDi{w} - \\SDe{w} | }{r_d \\aSDi{a}^{1/2}} \\right) \\\\\n\\alpha_b &= 1/3, \\quad \\alpha_d = 0.375, \\quad r_d      = 500 [m] \\\\\n\\SDi{S_{\\text{buoy}}} &= \\rhoRef{} \\aSDi{a} \\SDi{b} \\\\\n\\SDi{S_{\\text{coriolis}}} & = f(\\SDi{\\mathbf{u}} - {\\SDi{\\mathbf{u}_{\\text{geo-wind}}}}) \\\\\n\\SDi{S_{\\text{rad}}}  &= \\left( \\PD_t {\\SDi{\\eint}} \\right)_{radiation} \\\\\n\\SDi{S_{\\text{MP-MSS}}}^{\\qt} & = \\\\\n\\SDi{S_{\\text{MP-MSS}}}^{\\eint} & = \\\\\n\\end{align}\n```\n\nwhere additional variable definitions are in:\n\n - [Reference state profiles](@ref) ($\\pRef{}$, $\\rhoRef{}$, and $\\alphaRef{}$).\n\n - [Entrainment-Detrainment](@ref) ($\\epsilon_{i}$) and ($\\delta_i$).\n\n - [Buoyancy](@ref) ($\\Buoyancy$).\n\n - [Eddy diffusivity](@ref) ($\\Km, \\Kh$).\n\n\n## Sub-domain equations: 2nd moment\n\nThe 2nd moment sub-domain equations are of the exact same form as the 1st moment equations (equation \\eqref{eq:1stMoment}):\n\n```math\n\\begin{align}\\label{eq:2ndMoment}\n  \\PD_t (\\rhoRef{} \\aSDi{a} \\SDi{\\phi})\n  + \\PD_z (\\rhoRef{} \\aSDi{a} \\SDi{w} \\SDi{\\phi})\n  + \\GRAD_h \\DOT\n  (\\rhoRef{} \\aSDi{a} \\DM{\\uH} \\SDi{\\phi})\n  =\n  \\SDi{S}^\\phi\n  , \\quad \\forall i. \\\\\n\\end{align}\n```\nHere, $\\SDi{S}^{\\phi}$ are source terms, including diffusion, and many other sub-grid-scale (SGS) physics. In general, $\\SDi{S}^{\\phi}$ and $\\SDi{S}^{a}$ may depend on $\\SDj{\\phi}$ and or $\\aSDj{a}$ for any $j$.\n\n### Source terms per equation\nThe source terms common to all unknowns are:\n\n```math\n\\begin{align}\n\\SDi{S}^{\\phi\\psi} =\n  \\SDi{S_{\\epsilon\\delta}}^{\\phi\\psi}\n+ \\SDi{S_{\\text{x-grad flux}}}^{\\phi\\psi}\n+ \\SDi{S_{\\text{turb-transp}}}^{\\phi\\psi}\n\\quad \\forall \\phi \\psi,\n\\end{align}\n```\nAdditional source terms exist in other equations:\n\n```math\n\\begin{align}\n\\SDi{S}^{TKE} &=\n  \\SDi{S_{\\epsilon\\delta}}^{TKE}\n+ \\SDi{S_{\\text{x-grad flux}}}^{TKE}\n+ \\SDi{S_{\\text{turb-transp}}}^{TKE}\n+ \\SDi{S_{\\text{dissip}}}\n+ \\SDi{S_{\\text{press}}},\n+ \\SDi{S_{\\text{buoyancy}}}, \\\\\n\\SDi{S}^{\\phi\\psi} &=\n  \\SDi{S_{\\epsilon\\delta}}^{\\phi\\psi}\n+ \\SDi{S_{\\text{x-grad flux}}}^{\\phi\\psi}\n+ \\SDi{S_{\\text{turb-transp}}}^{\\phi\\psi}\n+ \\SDi{S_{\\text{dissip}}}^{\\phi\\psi}\n+ \\SDi{S_{\\text{MP-MSS}}}^{\\phi\\psi}.\n\\quad \\phi\\psi \\in [\\qt\\qt, \\eint\\eint, \\eint \\qt].\n\\end{align}\n```\n\n### Source term definitions\n\n```math\n\\begin{align}\n\\SDi{S_{\\epsilon\\delta}}^{\\phi\\psi} &=\n\\begin{cases}\n  \\rhoRef{} a_i \\SDi{w} \\left[ -\\delta_i \\IntraCVSDi{\\phi}{\\psi} + \\epsilon_{i}\n\\left(\n\\IntraCVSDe{\\phi}{\\psi} + (\\SDe{\\phi} - \\SDi{\\phi})(\\SDe{\\psi} - \\SDi{\\psi})\n\\right) \\right] & i \\ne \\iEnv \\\\\n  0 - \\sum_{j\\ne \\iEnv} \\SDj{S_{\\epsilon\\delta}}^{\\phi\\psi} & i=\\iEnv \\\\\n\\end{cases} \\\\\n\\SDi{S_{\\epsilon\\delta}}^{TKE} &=\n\\begin{cases}\n  \\rhoRef{} a_i \\SDi{w} \\left[ -\\delta_i \\SDi{TKE} + \\epsilon_{i}\n\\left(\n\\SDe{TKE} + \\frac{1}{2} (\\SDe{w} - \\SDi{w})^2\n\\right) \\right] & i \\ne \\iEnv \\\\\n  0 - \\sum_{j\\ne \\iEnv} \\SDj{S_{\\epsilon\\delta}}^{TKE} & i=\\iEnv \\\\\n\\end{cases} \\\\\n\\SDi{S_{\\text{x-grad flux}}}^{\\phi\\psi}\n& =\n- \\rhoRef{} a_i \\IntraCVSDi{w}{\\psi} \\PD_z \\SDi{\\phi}\n- \\rhoRef{} a_i \\IntraCVSDi{w}{\\phi} \\PD_z \\SDi{\\psi} \\\\\n& =\n 2 \\rhoRef{} a_i \\SDi{\\Kh} \\PD_z \\SDi{\\psi} \\PD_z \\SDi{\\phi} \\\\\n\\SDi{S_{\\text{x-grad flux}}}^{TKE}\n& =\n\\rhoRef{} a_i \\SDi{\\Km} \\left[ \\left(\\PD_z\\DM{u}\\right)^2 + \\left(\\PD_z\\DM{v}\\right)^2 + \\left(\\PD_z\\DM{w}\\right)^2 \\right] \\\\\n\\SDi{S_{\\text{turb-transp}}}^{\\phi\\psi} & = - \\PD_z (\\rhoRef{} a_i \\overline{w'_i\\phi'_i\\psi'_i}) \\\\\n& = \\PD_z (\\rhoRef{} a_i \\SDi{\\Kh} \\PD_z \\IntraCVSDi{\\phi}{\\psi}) \\\\\n\\SDi{S_{\\text{turb-transp}}}^{TKE} & = \\PD_z (\\rhoRef{} a_i \\SDi{\\Km} \\PD_z \\SDi{TKE}) \\\\\n\\SDi{S_{\\text{dissip}}}\n& = - \\rhoRef{} a_i c_e \\IntraCVSDi{\\phi}{\\psi} \\frac{\\SDi{TKE}^{1/2}}{\\SDio{{l_{mix}}}}, \\quad \\text{Equation 38 in Tan et al.} \\\\\nc_e & = 2 \\\\\n\\SDi{S_{\\text{press}}}\n& = - \\aSDi{a} \\left[ \\IntraCVSDi{u}{(\\partial_x p^{\\dagger})} +\n                      \\IntraCVSDi{v}{(\\partial_y p^{\\dagger})} +\n                      \\IntraCVSDi{w}{(\\partial_z p^{\\dagger})}\\right]  \\\\\n& = 0, \\qquad \\text{for now, need to derive correct formulation} \\\\\n\\SDi{S_{\\text{buoyancy}}}^{TKE} & = \\rhoRef{} \\aSDi{a} \\BuoyancyFlux \\\\\n\\SDi{S_{\\text{MP-MSSP}}}^{\\qt\\qt}\n& = \\\\\n\\SDi{S_{\\text{MP-MSSP}}}^{\\eint\\eint}\n& = \\\\\n\\end{align}\n```\nwhere additional variable definitions are in:\n\n - [Reference state profiles](@ref) ($\\pRef{}$, $\\rhoRef{}$, and $\\alphaRef{}$).\n\n - [Entrainment-Detrainment](@ref) ($\\epsilon_{i}$) and ($\\delta_i$).\n\n - [Eddy diffusivity](@ref) ($\\Km, \\Kh$).\n\n - [Mixing length](@ref) ($l_{mix}$).\n\n - [Buoyancy flux](@ref) ($\\BuoyancyFlux$).\n\n# EDMF variable definitions\n\nThe following definitions are ordered in a dependency fashion; all variables are defined from variables already defined in previous subsections.\n\n## Constants\n\n```math\n\\begin{align}\nc_K & = 0.1 \\\\\n\\text{tol}_{\\InversionHeight\\mathrm{-stable}} & = 0.01 \\\\\n\\end{align}\n```\n\n## Phase partition\n\n```math\n\\begin{align}\n\\PhasePartition &= \\{\\qv, \\ql, \\qi\\} \\\\\n\\qv &= \\qt - \\ql - \\qi \\\\\n\\pvsat(T) &= \\PTriple \\left( \\frac{T}{\\TTriple} \\right)^{\\frac{\\DeltaCp}{\\Rv}} \\exp{\\frac{\\RefLHv - \\DeltaCp \\TZero}{\\Rv} \\left( \\frac{1}{\\TTriple} - \\frac{1}{T} \\right)} \\label{eq:pvsat} \\\\\n\\qvsat(T, \\rho) &= \\frac{\\pvsat(T)}{\\rho \\Rv T}                                                                                                                            \\label{eq:qvsat} \\\\\n\\qc &= \\max(\\qt - \\qvsat, 0)                                                                                                                                               \\label{eq:qc} \\\\\n\\ql &= \\lambda \\qc                                                                                                                                                         \\label{eq:ql} \\\\\n\\qi &= (1-\\lambda) \\qc                                                                                                                                                     \\label{eq:qi} \\\\\n\\lambda(T) &= \\Heaviside(T-\\TFreeze)                                                                                                                                       \\label{eq:lambda} \\\\\n\\Heaviside &= \\text{Heaviside function} \\\\\n\\end{align}\n```\n\nFunctionally,\n\n```math\n\\begin{align}\n\\PhasePartition & = \\PhasePartition(\\qt, T, \\rho) \\\\\n\\qvsat & = \\qvsat(T, \\rho) \\\\\n\\end{align}\n```\n\n## Gas constants\n\n```math\n\\begin{align}\n\\EpsDV & = \\frac{\\Rv}{\\Rd} \\approx 1.61 \\\\\n\\EpsVD & = \\frac{\\Rd}{\\Rv} \\approx 0.62 \\\\\n\\Rm & = \\Rd \\left[1 + (\\EpsDV-1) \\qt - \\EpsDV (\\ql+\\qi) \\right] \\\\\n\\end{align}\n```\n\n## Specific heats\n\n```math\n\\begin{align}\n\\Cvm &= (1 - \\SDi{\\qt}) \\Cv{d} + \\SDi{\\qv} \\Cv{v} + \\SDi{\\ql} \\Cv{l} + \\SDi{\\qi} \\Cv{i} \\\\\n\\Cpm &= (1 - \\SDi{\\qt}) \\Cp{d} + \\SDi{\\qt} \\Cp{v} \\\\\n\\end{align}\n```\n\n## Latent heat\n\n```math\n\\begin{align}\n\\LatentHeatV{T} &= \\RefLHv + (\\Cp{v} - \\Cp{l}) (T - \\TTriple) \\\\\n\\LatentHeatS{T} &= \\RefLHs + (\\Cp{v} - \\Cp{i}) (T - \\TTriple) \\\\\n\\LatentHeatF{T} &= \\RefLHf + (\\Cp{l} - \\Cp{i}) (T - \\TTriple) \\\\\n\\end{align}\n```\n\n## Exner functions\n\n```math\n\\begin{align}\n\\ExnerD(\\pRef{})    = \\left(\\frac{\\pRef{}}{\\PTilde{}} \\right)^{\\Rd/\\Cp{d}} \\\\\n\\ExnerM(\\pRef{}, \\PhasePartition) = \\left(\\frac{\\pRef{}}{\\PTilde{}} \\right)^{\\Rm/\\Cpm} \\\\\n\\end{align}\n```\nwhere additional variable definitions are in:\n\n - [Specific heats](@ref) $\\Cpm$ and $\\Cvm$.\n\n - [Gas constants](@ref) ($\\Rm$).\n\n - [Phase partition](@ref) $\\PhasePartition, \\qt, \\qv, \\ql, \\qi, \\qvsat$.\n\n## Temperature\n\nNote that, while temperature may be computed using different\nthermodynamic formulations, [ThermodynamicState](https://clima.github.io/Thermodynamics.jl/dev/API/#Thermodynamics.ThermodynamicState)'s are immediately\nconverted to the ($\\qt, \\eint, \\rhoRef{}$)-formulation.\n\n### Dry temperature\n\n```math\n\\begin{align}\n\\TDry & = \\ThetaLiqIce \\ExnerD \\\\\n\\end{align}\n```\nwhere additional variable definitions are in:\n\n - [Exner functions](@ref) $\\ExnerD$ and $\\ExnerM$.\n\nFunctionally,\n\n```math\n\\begin{align}\n\\TDry & = \\TDry(\\ThetaLiqIce, \\pRef) \\\\\n\\end{align}\n```\n\n### ($\\qt, \\eint, \\rhoRef{}$)-formulation\n\nHere, $T$ conditionally satisfies the non-linear set of equations, which\ncan be solved using a standard root solver (e.g., Secant method):\n\n```math\n\\begin{align}\nT =\n\\begin{cases}\n\\mathrm{satisfies} & \\eint(T) = \\Cvm (T - \\TZero)  + \\qv \\RefHintV - \\qi \\RefHintI & \\qt > \\qvsat(T, \\rhoRef{}) \\\\\n& \\TZero + \\frac{\\eint(T)}{(1-\\qt)\\Cv{d} + \\qt \\Cv{v}} + \\qt \\RefHintV & \\text{otherwise} \\\\\n\\end{cases}\n\\end{align}\n```\n\nwhere additional variable definitions are in:\n\n - [Phase partition](@ref) $\\PhasePartition, \\qt, \\qv, \\ql, \\qi, \\qvsat$.\n\n - [Specific heats](@ref) $\\Cpm$ and $\\Cvm$.\n\n - [Reference state profiles](@ref) ($\\pRef{}$, $\\rhoRef{}$, and $\\alphaRef{}$).\n\nFunctionally,\n\n```math\n\\begin{align}\nT & = T(\\qt, \\eint, \\rhoRef) \\\\\n\\end{align}\n```\n\n### ($\\qt, \\ThetaLiqIce, \\rhoRef, \\pRef$)-formulation\n\nHere, $T$ conditionally satisfies the non-linear set of equations, which\ncan be solved using a standard root solver (e.g., Secant method):\n\n```math\n\\begin{align}\nT =\n\\begin{cases}\n\\mathrm{satisfies} & \\ThetaLiqIce \\ExnerM = T \\left(1 - \\frac{ \\RefLHv \\ql + \\RefLHs \\qi}{\\Cpm T} \\right) & \\qt > \\qvsat(T, \\rhoRef{}) \\\\\n& \\TDry & \\text{otherwise} \\\\\n\\end{cases}\n\\end{align}\n```\nwhere additional variable definitions are in:\n\n - [Dry temperature](@ref) $\\TDry$.\n\n - [Phase partition](@ref) $\\PhasePartition, \\qt, \\qv, \\ql, \\qi, \\qvsat$.\n\n - [Specific heats](@ref) $\\Cpm$ and $\\Cvm$.\n\n - [Exner functions](@ref) $\\ExnerD$ and $\\ExnerM$.\n\nFunctionally,\n\n```math\n\\begin{align}\nT & = T(\\qt, \\ThetaLiqIce, \\rhoRef, \\pRef) \\\\\n\\end{align}\n```\n\n## Reference state profiles\nUsing the hydrostatic balance, $\\PD_z \\pRef = - \\rhoRef{} \\grav$, and the\nideal gas law, $\\pRef = \\rhoRef{} \\Rm \\TRef$, the reference state profiles\nare computed as:\n\n```math\n\\begin{align}\n\\PD_z \\pRef & = - \\grav \\frac{\\pRef}{\\TRef \\Rm} \\\\\n\\int_{\\BC{\\pRef}}^{\\pRef} \\frac{\\TDry(\\BC{\\DM{\\ThetaLiqIce}}, \\pRef)}{\\pRef} \\PD \\pRef & = - \\frac{\\grav}{\\BC{\\DM{\\Rm}}} \\int_{z_{min}}^{z} \\PD z \\\\\n\\rhoRef{}(\\pRef) & = \\frac{\\pRef{}}{\\TDry(\\BC{\\DM{\\ThetaLiqIce}}, \\pRef) \\BC{\\DM{\\Rm}}} \\\\\n\\alphaRef & = \\frac{1}{\\rhoRef{}(\\pRef)} \\\\\n\\end{align}\n```\nwhere additional variable definitions are in:\n\n - [Temperature](@ref) ($T$ and $\\TDry$).\n\n - [Gas constants](@ref) ($\\Rm$).\n\n - [Specific heats](@ref) $\\Cpm$ and $\\Cvm$.\n\n## Mixing ratios\n```math\n\\begin{align}\\label{eq:MixingRatios}\nr_{con} & = \\frac{\\qt+\\ql}{1 - \\qt} \\\\\nr_{vap} & = \\frac{\\qt-\\ql    - \\qi}{1 - \\qt} \\\\\n\\end{align}\n```\n\n## Potential temperatures\nFix: which virtual potential temperature is used\n```math\n\\begin{align}\\label{eq:Theta}\n\\ThetaDry    & = T/\\ExnerD \\\\\n\\ThetaLiqIce & = \\ThetaDry (1 - (\\RefLHv \\ql + \\RefLHs \\qi)/(\\Cpm T)) \\\\\n\\ThetaVirt   & = \\ThetaDry (1 - r_{con} + 0.61 r_{vap}) \\\\\n\\ThetaVirt   & = \\theta \\left(1 + 0.61 \\qr - \\ql \\right) \\\\\n\\ThetaRho    & = T \\Rm/\\ExnerD \\\\\n\\end{align}\n```\nwhere additional variable definitions are in:\n\n - [Reference state profiles](@ref) ($\\pRef{}$, $\\rhoRef{}$, and $\\alphaRef{}$).\n\n - [Exner functions](@ref) ($\\ExnerM$).\n\n - [Mixing ratios](@ref) ($r_{con}$, $r_{vap}$).\n\n## Shear production\n\n```math\n\\begin{align}\\label{eq:ShearProduction}\n|S|^2 &= (\\PD_z \\DM{u})^2 + (\\PD_z \\DM{v})^2 + (\\PD_z \\SDe{w})^2 \\\\\n\\end{align}\n```\n\n## Buoyancy\n```math\n\\begin{align}\\label{eq:Buoyancy}\n\\SDi{b}^{\\dagger} & = \\grav (\\SDi{\\alpha} - \\alphaRef)/\\alphaRef \\\\\n\\SDi{b} & = \\SDi{b}^{\\dagger} - \\sum_j a_j \\SDj{b}^{\\dagger} \\\\\n\\alpha_i & = \\frac{\\SDi{\\Rm} \\SDi{T}}{\\pRef} \\\\\n\\end{align}\n```\nwhere additional variable definitions are in:\n\n - [Reference state profiles](@ref) ($\\pRef{}$, $\\rhoRef{}$, and $\\alphaRef{}$).\n\n - [Phase partition](@ref) $\\PhasePartition, \\qt, \\qv, \\ql, \\qi, \\qvsat$.\n\n - [Temperature](@ref) ($T$ and $\\TDry$).\n\n## Buoyancy gradient\n\n### ($\\qt, \\ThetaLiqIce, \\pRef, \\rhoRef$)-formulation\n\n```math\n\\begin{align}\\label{eq:BuoyancyGradLong}\n\\SDi{\\BuoyancyGrad} & = \\PD_z \\SDi{\\ThetaLiqIce}\n\\left[ (1-f_c) \\PD_{\\ThetaLiqIce} b |_d  + f_c \\PD_{\\ThetaLiqIce} b |_s \\right] +\n\\PD_z \\SDi{\\qt}      \\left[ (1-f_c) \\PD_{\\qt} b |_d + f_c \\PD_{\\qt} b |_s \\right] \\\\\nf_c &= 0 \\qquad \\text{good for simple cases, need to confirm for more complex cases} \\\\\n\\PD_{\\ThetaLiqIce} b |_d & = \\frac{\\grav}{\\DM{\\ThetaVirt}} \\left[ 1 + \\left( \\frac{\\Rv}{\\Rd} - 1 \\right) \\SDi{\\qt} \\right] \\\\\n\\PD_{\\ThetaLiqIce} b |_s &= \\frac{\\grav}{\\DM{\\ThetaVirt}} \\left[ 1 + \\frac{\\Rv}{\\Rd} \\left(1 + \\frac{\\LatentHeatV{\\SDi{T}}}{\\Rv \\SDi{T}} \\right) \\SDi{\\qvsat} - \\SDi{\\qt} \\right] \\left( 1 + \\frac{{\\LatentHeatV{\\SDi{T}}}^2}{\\Cpm \\Rv \\SDi{T}^2} \\SDi{\\qvsat} \\right)^{-1} \\\\\n\\PD_{\\qt} b |_d &= \\frac{\\grav}{\\DM{\\ThetaVirt}} \\left( \\frac{\\Rv}{\\Rd} - 1 \\right) \\SDi{\\ThetaDry} \\\\\n\\PD_{\\qt} b |_s &= \\left( \\frac{{\\LatentHeatV{\\SDi{T}}}}{\\Cpm \\SDi{T}} \\PD_{\\ThetaLiqIce} b |_s - \\frac{\\grav}{\\DM{\\ThetaVirt}} \\right) \\SDi{\\ThetaDry} \\\\\n\\end{align}\n```\nwhere additional variable definitions are in:\n\n - [Reference state profiles](@ref) ($\\pRef{}$, $\\rhoRef{}$, and $\\alphaRef{}$).\n\n - [Potential temperatures](@ref) ($\\ThetaDry$, $\\ThetaVirt$).\n\n - [Phase partition](@ref) $\\PhasePartition, \\qt, \\qv, \\ql, \\qi, \\qvsat$.\n\n - [Latent heat](@ref) ($\\LatentHeatV{T}$).\n\n### ($\\qt, \\eint, \\rhoRef{}$)-formulation\n\nPending.\n\n## Surface fluxes\n\n!!! todo\n\n    Add definitions for universal functions (e.g., $\\Psi_m$).\n\nVariables in this section must be computed simultaneously because it\nrequires the solution of a non-linear equation.\n\n### Monin-Obhukov length\nNOTE: All variables (Monin-Obhukov length, friction velocity, temperature\nscale) in [Surface fluxes](@ref) must be solved simultaneously\n```math\n\\begin{align}\\label{eq:MOLen}\n\\MOLen = \\begin{cases}\n- \\frac{\\FrictionVelocity^3 \\theta}{\\VKConst \\grav \\SurfaceHeatFlux} & \\SurfaceHeatFlux > 0 \\\\\n0 & \\text{otherwise} \\\\\n\\end{cases} \\\\\n\\end{align}\n```\n\n### Friction velocity\nNOTE: All variables (Monin-Obhukov length, friction velocity, temperature\nscale) in [Surface fluxes](@ref) must be solved simultaneously\n\n - Knowns: $u_{\\mathrm{ave}} = \\sqrt{\\DM{u}^2+\\DM{v}^2}, \\LayerThickness, \\SurfaceRoughness{m}$\n\n - Unknowns: $\\FrictionVelocity, \\MOLen$, and $\\SurfaceMomentumFlux$\n\n```math\n\\begin{align}\\label{eq:FrictionVelocity}\nu_{\\mathrm{ave}}     & = \\frac{\\FrictionVelocity}{\\VKConst}    \\left[ \\log\\left(\\frac{\\LayerThickness}{\\SurfaceRoughness{m}}\\right) - \\Psi_m\\left(\\frac{\\LayerThickness}{\\MOLen}\\right) + \\frac{\\SurfaceRoughness{m}}{\\LayerThickness} \\Psi_m\\left(\\frac{\\SurfaceRoughness{m}}{\\MOLen}\\right) + R_{z0m} \\left\\{ \\psi_m\\left(\\frac{\\SurfaceRoughness{m}}{\\MOLen}\\right) - 1 \\right\\} \\right] \\\\\nR_{z0m}              & = 1 - \\SurfaceRoughness{h}/\\LayerThickness \\\\\n\\SurfaceMomentumFlux & = -\\FrictionVelocity^2                , \\label{eq:SurfaceMomentumFlux}  \\\\\n\\end{align}\n```\nwhere $\\Psi_m$ is defined in Appendix A, equations A6 in Nishizawa, S., and Y. Kitamura. \"A Surface Flux Scheme Based on the Monin‐Obukhov Similarity for Finite Volume Models.\" Journal of Advances in Modeling Earth Systems 10.12 (2018): 3159-3175.\n\n### Temperature scale\nNOTE: All variables (Monin-Obhukov length, friction velocity, temperature\nscale) in [Surface fluxes](@ref) must be solved simultaneously\n\n - Knowns: $\\theta_{\\mathrm{ave}}, \\theta_s, \\LayerThickness, \\SurfaceRoughness{h}$\n\n - Unknowns: $\\FrictionVelocity, \\MOLen$, and $\\SurfaceHeatFlux$\n```math\n\\begin{align}\\label{eq:TemperatureScale}\n\\theta_{\\mathrm{ave}} - \\theta_s & = \\frac{Pr \\TemperatureScale}{\\VKConst} \\left[ \\log\\left(\\frac{\\LayerThickness}{\\SurfaceRoughness{h}}\\right) - \\Psi_h\\left(\\frac{\\LayerThickness}{\\MOLen}\\right) + \\frac{\\SurfaceRoughness{h}}{\\LayerThickness} \\Psi_m\\left(\\frac{\\SurfaceRoughness{h}}{\\MOLen}\\right) + R_{z0h} \\left\\{ \\psi_h\\left(\\frac{\\SurfaceRoughness{h}}{\\MOLen}\\right) - 1 \\right\\} \\right] \\\\\nR_{z0h}                          & = 1 - \\SurfaceRoughness{h}/\\LayerThickness \\\\\n\\SurfaceHeatFlux                 & = -\\FrictionVelocity\\TemperatureScale , \\label{eq:SurfaceHeatFlux}  \\\\\n\\end{align}\n```\nwhere $\\Psi_h$ is defined in Appendix A, equation A6 in Nishizawa, S.,\nand Y. Kitamura. \"A Surface Flux Scheme Based on the Monin‐Obukhov\nSimilarity for Finite Volume Models.\" Journal of Advances in Modeling\nEarth Systems 10.12 (2018): 3159-3175.\n\n## Prandtl number\n\n```math\n\\begin{align}\\label{eq:PrandtlNumber}\nPr_{neut} &= 0.74 \\\\\nPr(z) &= \\begin{cases}\n    Pr_{neut} & \\MOLen < 0 \\\\\n    Pr_{neut} \\left[ \\frac{1 + \\omega_2 R_g - \\sqrt{-4 R_g + (1+\\omega_2 R_g)^2}}{2 R_g} \\right] & \\text{otherwise} \\\\\n\\end{cases} \\\\\n\\omega_2 &= \\omega_1+1 \\\\\n\\omega_1 &= \\frac{40}{13} \\\\\nR_g &= \\frac{\\BuoyancyGrad}{|S|^2} \\\\\n\\end{align}\n```\nwhere additional variable definitions are in:\n\n - [Shear production](@ref) ($S$).\n\n - [Monin-Obhukov length](@ref) ($\\MOLen$).\n\n - [Buoyancy gradient](@ref) ($\\BuoyancyGrad$).\n\n## Mixing length\n\n!!! note\n\n    These mixing length have been tested for the environment, not the updrafts\n\n```math\n\\begin{align}\\label{eq:MixingLength}\n\\SDio{{l_{mix}^m,}} &= \\frac{\\sum_j l_j e^{-l_j}}{\\sum_j e^{-l_j}}, \\qquad j = 1,2,3 \\\\\nl_1 &= \\frac{\\sqrt{c_w\\SDe{TKE}}}{\\SDe{N}} \\\\\nc_w &= 0.4 \\\\\n\\SDe{N} &= \\frac{\\grav \\PD_z \\SDe{\\ThetaVirt}}{\\SDe{\\ThetaVirt}} , \\qquad \\text{(buoyancy frequency of environment)} \\\\\nl_2 &= \\frac{\\VKConst z}{c_K \\kappa^* \\phi_m(z/\\MOLen)} \\\\\n\\phi_m(\\xi) &= \\left( 1 + a_l \\xi \\right)^{-b_l} \\\\\n(a_l, b_l) &=\n\\begin{cases}\n  (-100, 0.2) & \\MOLen < 0 \\\\\n  (2.7, -1) & \\text{otherwise} \\\\\n\\end{cases} \\\\\n\\kappa^* &= \\frac{\\FrictionVelocity}{\\sqrt{\\SDe{TKE}}} \\\\\nl_3 &= \\sqrt{\\frac{c_{\\varepsilon}}{c_K}} \\sqrt{\\SDe{TKE}}\n\\left[ \\max(|S|^2 - \\frac{1}{Pr(z)} \\BuoyancyGrad, 0) \\right]^{-1/2} \\\\\n\\end{align}\n```\nwhere additional variable definitions are in:\n\n - [Constants](@ref).\n\n - [Shear production](@ref) ($S$).\n\n - [Monin-Obhukov length](@ref) ($\\MOLen$).\n\n - [Friction velocity](@ref) ($\\FrictionVelocity$).\n\n - [Buoyancy gradient](@ref) ($\\BuoyancyGrad$).\n\n - [Potential temperatures](@ref) ($\\ThetaDry$, $\\ThetaVirt$).\n\n - [Prandtl number](@ref) ($Pr$).\n\nSmoothing function is provided in python file. The Prandtl number was used\nfrom Eq. 75 in Dan Li 2019 \"Turbulent Prandtl number in the atmospheric BL -\nwhere are we now\".\n\n## Eddy diffusivity\n\n```math\n\\begin{align}\\label{eq:EddyDiffusivity}\n\\SDi{\\Km} & = \\begin{cases}\nc_K \\SDio{{l_{mix},}} \\sqrt{\\SDi{TKE}} & i = \\iEnv \\\\\n0 & \\text{otherwise}\n\\end{cases} \\\\\n\\SDi{\\Kh} & = \\frac{\\SDi{\\Km}}{Pr} \\\\\n\\end{align}\n```\nwhere additional variable definitions are in:\n\n - [Constants](@ref).\n\n - [Mixing length](@ref) ($l_{mix}$).\n\n - [Prandtl number](@ref) ($Pr$).\n\n## Buoyancy flux\n\n!!! todo\n\n    Currently, $\\BuoyancyFlux$ is hard-coded from the first expression\n    (which was used in SCAMPy), however, this value should be computed\n    from the SurfaceFluxes section.\n\n```math\n\\begin{align}\\label{eq:BuoyancyFlux}\n\\SurfaceBuoyancyFlux & = \\frac{\\grav \\BC{\\alphaRef}}{\\Cpm \\BC{\\SDi{T}}} (\\SensibleSurfaceHeatFlux + (\\EpsDV - 1) \\Cpm \\BC{\\SDi{T}} \\LatentSurfaceHeatFlux / \\LatentHeatV{\\BC{\\SDi{T}}}) \\\\\n\\BuoyancyFlux & = - \\SDi{\\Kh} \\SDi{\\BuoyancyGrad} \\\\\n\\end{align}\n```\n - [Eddy diffusivity](@ref) ($\\Km, \\Kh$).\n\n - [Latent heat](@ref) ($\\LatentHeatV{T}$).\n\n - [Buoyancy gradient](@ref) ($\\BuoyancyGrad$).\n\n - [Specific heats](@ref) $\\Cpm$ and $\\Cvm$.\n\n## Entrainment-Detrainment\n\nEntrainment ($\\epsilon_{i}$)\n```math\n\\begin{align}\\label{eq:Entrainment}\n\\epsilon_{i} &= c_{\\epsilon} \\frac{\\max(\\SDi{b}, 0)}{\\SDi{w}^2} \\\\\nc_{\\epsilon} &= 0.12 \\\\\n\\end{align}\n```\n\nDetrainment ($\\delta_{j}$):\n```math\n\\begin{align}\\label{eq:Detrainment}\n\\delta_{i} &= c_{\\delta} \\frac{|\\min(\\SDi{b}, 0)|}{\\SDi{w}^2} + \\delta_{B} \\Heaviside(\\SDi{\\ql}) \\\\\nc_{\\delta} &= c_{\\delta,0} + \\Gamma(\\aSDi{a}) \\\\\n\\Gamma(\\aSDi{a}) &= 0 \\\\\nc_{\\delta,0} &= c_{\\epsilon} = 0.12 \\\\\n\\delta_B &= 0.004 [m^{-1}] \\\\\n\\Heaviside &= \\text{Heaviside function} \\\\\n\\end{align}\n```\nwhere additional variable definitions are in:\n\n - [Reference state profiles](@ref) ($\\pRef{}$, $\\rhoRef{}$, and $\\alphaRef{}$).\n\n - [Temperature](@ref) ($T$ and $\\TDry$).\n\n - [Buoyancy](@ref) ($\\Buoyancy$).\n\n## Inversion height\n\n```math\n\\begin{align}\\label{eq:InversionHeight}\n\\SDio{\\InversionHeight} &=\n\\begin{cases}\n  \\left[ (\\PD_z \\ThetaRho)^{-1} (\\BC{\\ThetaRho} - \\ThetaRho|_{z_1}) + z_1 \\right] & \\simparam{\\BC{\\DM{u}}}^2 + \\simparam{\\BC{\\DM{v}}}^2 <= \\text{tol}_{\\InversionHeight\\mathrm{-stable}} \\\\\n  \\left[ (\\PD_z Ri_{bulk})^{-1} (\\hyperparam{Ri_{bulk, crit}} - Ri_{bulk}|_{z_2}) + z_2 \\right] & \\text{otherwise} \\\\\n\\end{cases} \\\\\nz_1 &= \\min_z (\\ThetaRho(z) > \\BC{\\ThetaRho}) \\\\\nz_2 &= \\min_z (Ri_{bulk}(z) > \\hyperparam{Ri_{bulk, crit}}) \\\\\nRi_{bulk} &= \\grav z \\frac{(\\ThetaRho/\\BC{\\ThetaRho} - 1)}{\\simparam{\\DM{u}}^2 + \\simparam{\\DM{v}}^2} \\\\\n\\end{align}\n```\nwhere additional variable definitions are in:\n\n - [Potential temperatures](@ref) ($\\theta$).\n\n## Convective velocity\n\n```math\n\\begin{align}\\label{eq:ConvectiveVelocity}\n\\SDio{\\ConvectiveVelocity} &= (\\max(\\BuoyancyFlux \\SDio{\\InversionHeight}, 0))^{1/3} \\\\\n\\end{align}\n```\nwhere additional variable definitions are in:\n\n - [Inversion height](@ref) ($\\SDio{\\InversionHeight}$).\n\n - [Buoyancy flux](@ref) ($\\BuoyancyFlux$).\n\n## Non-local mixing length\n\n```math\n\\begin{align}\\label{eq:MixingLengthOld}\n\\SDio{{l_{mix},}} &= (l_A^{-1} + l_B^{-1})^{-1} \\\\\nl_A &= \\VKConst z \\left( 1 + a_l \\frac{z}{\\MOLen} \\right)^{b_l} \\\\\n\\SDio{{l_B}} &= \\SDio{\\tau} \\SDi{TKE} \\\\\n(a_l, b_l) &=\n\\begin{cases}\n  (-100, 0.2) & \\MOLen < 0 \\\\\n  (2.7, -1) & \\text{otherwise} \\\\\n\\end{cases} \\\\\n\\SDio{\\tau} &= \\SDio{\\InversionHeight}/\\SDio{\\ConvectiveVelocity} \\\\\n\\end{align}\n```\nwhere additional variable definitions are in:\n\n - [Inversion height](@ref) ($\\SDio{\\InversionHeight}$).\n\n - [Monin-Obhukov length](@ref) ($\\MOLen$).\n\n - [Convective velocity](@ref) ($\\SDio{\\ConvectiveVelocity}$).\n\n# Boundary Conditions\n\nHere, we specify boundary conditions (BCs) by their type, Dirichlet (D) or Neumann (N), and their value.\n\n## BC functions\n\n```math\n\\begin{align}\n\\Gamma_{\\phi}(F_1, F_2)\n& = \\begin{cases}\n    4 \\frac{F_1 F_2}{\\FrictionVelocity^2} (1 - 8.3\\zLL/\\MOLen)^{-2/3} & \\MOLen < 0 \\\\\n    4 \\frac{F_1 F_2}{\\FrictionVelocity^2} & \\text{otherwise}\n\\end{cases} \\\\\n\\Gamma_{TKE}\n& = \\begin{cases}\n    3.75 {\\FrictionVelocity}^2 + 0.2 {\\ConvectiveVelocity}^2 + {\\FrictionVelocity}^2 (-\\zLL/\\MOLen)^{2/3} & \\MOLen < 0 \\\\\n    3.75 {\\FrictionVelocity}^2 & \\text{otherwise}\n\\end{cases} \\\\\n\\SensibleSurfaceHeatFlux & = \\BC{\\TCV{w}{\\eint}} \\Cpm \\rhoRef \\\\\n\\LatentSurfaceHeatFlux   & = \\BC{\\TCV{w}{\\qt}}  \\LatentHeatV{T} \\rhoRef \\\\\nF_{\\eint}(\\SensibleSurfaceHeatFlux)  & = \\frac{\\SensibleSurfaceHeatFlux}{\\Cpm}       = \\BC{\\TCV{w}{\\eint}} \\rhoRef \\\\\nF_{\\qt}(\\LatentSurfaceHeatFlux)      & = \\frac{\\LatentSurfaceHeatFlux}{\\LatentHeatV{T}} = \\BC{\\TCV{w}{\\qt}}   \\rhoRef \\\\\n\\end{align}\n```\nwhere additional variable definitions are in:\n\n - [Reference state profiles](@ref) ($\\pRef{}$, $\\rhoRef{}$, and $\\alphaRef{}$).\n\n - [Monin-Obhukov length](@ref) ($\\MOLen$).\n\n - [Convective velocity](@ref) ($\\SDio{\\ConvectiveVelocity}$).\n\n - [Friction velocity](@ref) ($\\FrictionVelocity$).\n\n - [Latent heat](@ref) ($\\LatentHeatV{T}$).\n\nand equation \\eqref{eq:TopPercentile} represents the mean of the top\n$x$-fraction of a standard normal distribution (Neggers et al., 2009).\n\n```math\n\\begin{align}\n\\Phi^{-1}(x)  &= \\text{inverse cumulative distribution function}, \\label{eq:InverseCDF} \\\\\n\\mathcal D(x) &= \\frac{1}{\\sqrt{2\\pi x}} \\exp{- \\frac{1}{2} (\\Phi^{-1}(1-x))^2 } , \\label{eq:TopPercentile} \\\\\n\\end{align}\n```\n\n## Area fraction\n\n```math\n\\begin{align}\nc_{frac} = 0.1, \\quad\n\\BCB{\\aSDi{a}} =\n\\begin{cases}\n    1-c_{frac}, & i = \\iEnv{} \\\\\n  \\frac{c_{frac}}{\\Nsd}, & i \\ne \\iEnv{}\n\\end{cases}, \\quad\n\\BCT{\\aSDi{a}} =\n\\begin{cases}\n    1-c_{frac}, & i = \\iEnv{} \\\\\n  \\frac{c_{frac}}{\\Nsd}, & i \\ne \\iEnv{}\n\\end{cases}\n\\end{align}\n```\n\n## 1st order moments\n\nTop boundary\n```math\n\\begin{align}\n\\BCT{\\SDi{w}}           &= 0 \\\\\n\\PD_z \\BCT{\\SDi{\\qt}}   &= 0 \\\\\n\\PD_z \\BCT{\\SDi{\\eint}} &= 0 \\\\\n\\end{align}\n```\nBottom boundary\n!!! todo\n\n    Need value for $C_{\\eint}$.\n\n```math\n\\begin{align}\n\\BCB{\\SDi{w}}     &= 0 \\\\\n- \\SDi{\\Kh} \\PD_z \\BCB{\\SDi{\\qt}}   &= \\TCV{w}{\\qt}   + \\mathcal D(\\aSDi{a}) \\sqrt{C_{\\qt}^2   \\WindSpeed^2\\Gamma_{\\phi}(\\TCV{w}{\\qt}  , \\TCV{w}{\\qt}   )} \\\\\n- \\SDi{\\Kh} \\PD_z \\BCB{\\SDi{\\eint}} &= \\TCV{w}{\\eint} + \\mathcal D(\\aSDi{a}) \\sqrt{C_{\\eint}^2 \\WindSpeed^2\\Gamma_{\\phi}(\\TCV{w}{\\eint}, \\TCV{w}{\\eint} )} \\\\\nC_{\\qt} &= 0.001133 \\\\\nC_{\\ThetaLiqIce} &= 0.001094 \\\\\nC_{\\eint} &=  \\\\\n\\end{align}\n```\nwhere additional variable/function definitions are in:\n\n - [BC functions](@ref) $\\mathcal D$.\n\n## 2nd order moments\n\nTop boundary\n```math\n\\begin{align}\n\\BCT{\\SDi{TKE}}                         & = 0 \\\\\n\\PD_z \\BCT{\\IntraCVSDi{\\qt}{\\qt}}       & = 0 \\\\\n\\PD_z \\BCT{\\IntraCVSDi{\\eint}{\\eint}}   & = 0 \\\\\n\\PD_z \\BCT{\\IntraCVSDi{\\eint}{\\qt}}     & = 0 \\\\\n\\end{align}\n```\n\n!!! todo\n\n    Currently, we only account for the _intra_ sub-domain covariance, but\n    we would like to also account for the _inter_ sub-domain covariance\n    for all but the $TKE$.\n\nBottom boundary\n```math\n\\begin{align}\n\\BCB{\\SDi{TKE}}                   & = \\Gamma_{TKE} \\\\\n\\BCB{\\IntraCVSDi{\\qt}{\\qt}}       & = \\Gamma_{\\phi}(\\TCV{w}{\\qt}  , \\TCV{w}{\\qt}   ) \\\\\n\\BCB{\\IntraCVSDi{\\eint}{\\eint}}   & = \\Gamma_{\\phi}(\\TCV{w}{\\qt}  , \\TCV{w}{\\eint} ) \\\\\n\\BCB{\\IntraCVSDi{\\eint}{\\qt}}     & = \\Gamma_{\\phi}(\\TCV{w}{\\eint}, \\TCV{w}{\\eint} ) \\\\\n\\end{align}\n```\nwhere additional variable/function definitions are in:\n\n - [BC functions](@ref) $\\Gamma_{TKE}$, $\\Gamma_{\\phi}$, $F_{\\eint}$, $\\SensibleSurfaceHeatFlux$, $F_{\\qt}$, $\\LatentSurfaceHeatFlux$.\n"
  },
  {
    "path": "docs/src/Theory/Atmos/EDMF_plots.md",
    "content": "# Atmospheric EDMF parameterization profiles\n\n```@meta\nCurrentModule = ClimateMachine\n```\n\nSeveral EDMF related profiles are to be plotted here\n\n## Usage\n\nUsing a profile involves passing two arguments:\n\n - `param_set` a parameter set, from [CLIMAParameters.jl](https://github.com/CliMA/CLIMAParameters.jl)\n - `max_Grad_Ri` maximum gradient Richarson Number (indepndent, non-dimensional variable for the turbulent Prantl number)\n\nto one of the EDMF profile constructors.\n\n### TurbulentPrantlNumberProfile\n\n```@example\nusing UnPack\nusing ClimateMachine\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\nusing ClimateMachine.Atmos: AtmosModel\nusing ClimateMachine.VariableTemplates\nusing Thermodynamics\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.TurbulenceConvection\nusing CLIMAParameters: AbstractEarthParameterSet\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\ninclude(joinpath(clima_dir, \"test\", \"Atmos\", \"EDMF\", \"closures\", \"turbulence_functions.jl\"))\ninclude(joinpath(clima_dir, \"test\", \"Atmos\", \"EDMF\", \"edmf_model.jl\"))\nusing Plots\nFT = Float64;\nGrad_Ri = range(FT(-1), stop = 10 , length = 100);\nml = MixingLengthModel{FT}(param_set);\nPr_t = turbulent_Prandtl_number.(Ref(ml.Pr_n), Grad_Ri, Ref(ml.ω_pr))\np1 = plot(Grad_Ri, Pr_t, xlabel=\" gradient Richardson number\");\nplot(p1, title=\"turbulent Prantl number\")\nsavefig(\"Pr_t.svg\")\n```\n![](Pr_t.svg)\n"
  },
  {
    "path": "docs/src/Theory/Atmos/Microphysics_0M.md",
    "content": "# Microphysics_0M\n\nWe are using the `Microphysics_0M.jl` module\n  from the [CloudMicrophysics.jl](https://github.com/CliMA/CloudMicrophysics.jl) package.\nSee the [documentation](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_0M/)\n  for further comments on the scheme derivation.\n\n## Coupling to the state variables\n\nFollowing the conservation equations for\n[moisture](https://clima.github.io/ClimateMachine.jl/latest/Theory/Atmos/AtmosModel/#Moisture)\nand [mass](https://clima.github.io/ClimateMachine.jl/latest/Theory/Atmos/AtmosModel/#Mass),\nthe ``\\mathcal{S}_{q_{tot}}`` sink has to be multiplied by ``\\rho`` before\n  adding it as one of the sink terms to both moisture and mass state variables.\nFor the conservation equation for\n[total energy](https://clima.github.io/ClimateMachine.jl/latest/Theory/Atmos/AtmosModel/#Energy),\n  no additional source/sink terms $M$ are considered, and the\n  the sink due to removing ``q_{tot}`` is computed as:\n```math\n\\begin{equation}\n\\left. \\mathcal{S}_{\\rho e} \\right|_{precip} =\n  \\left. \\sum_{j\\in\\{v,l,i\\}}(I_j + \\Phi)  \\rho C(q_j \\rightarrow q_p) \\right|_{precip} =\n  \\left[\\lambda I_l + (1 - \\lambda) I_i + \\Phi \\right]\n  \\rho \\, \\left.\\mathcal{S}_{q_{tot}} \\right|_{precip}\n\\end{equation}\n```\nwhere:\n - ``\\lambda`` is the liquid fraction\n - ``I_l = c_{vl} (T - T_0)`` is the internal energy of liquid water\n - ``I_i = c_{vi} (T - T_0) - I_{i0}`` is the internal energy of ice\n - ``T`` is the temperature,\n - ``T_0`` is the thermodynamic reference temperature (which is unrelated to the reference temperature used in hydrostatic reference states used in the momentum equations),\n - ``I_{i0}`` is the specific internal energy of ice at ``T_0``\n - ``c_{vl}`` and ``c_{vi}`` are the isochoric specific heats\n     of liquid water, and ice.\n - ``\\Phi`` is the effective gravitational potential.\n\nThis assumes that the ``\\mathcal{S}_{q_{tot}}`` sink is partitioned between the\n  cloud liquid water and cloud ice sinks\n  ``\\mathcal{S}_{q_{liq}}`` and ``\\mathcal{S}_{q_{ice}}`` based on the\n  cloud liquid water and cloud ice fractions.\n"
  },
  {
    "path": "docs/src/Theory/Atmos/Microphysics_1M.md",
    "content": "# Microphysics\n\nWe are using the `Microphysics_1M.jl` module\n  from the [CloudMicrophysics.jl](https://github.com/CliMA/CloudMicrophysics.jl) package.\nSee the [documentation](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_1M/)\n  for further comments on the scheme derivation.\n\n## Coupling to the state variables\n\n### Warm Rain\n\nThe source of rain ``\\mathcal{S}_{q_{rai}}`` is a sum of the\n  [autoconversion](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_1M/#Rain-autoconversion),\n  [accretion](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_1M/#Accretion), and\n  [rain evaporation](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_1M/#Rain-evaporation-and-snow-sublimation)\n  processes.\nThe sink of total water is equal to the source of rain:\n  ``\\mathcal{S}_{q_{tot}}`` = -``\\mathcal{S}_{q_{rai}}``.\nThe sink of cloud liquid water is the sum of rain autoconversion\n  and rain accretion processes.\nFollowing the conservation equations for\n[mass](https://clima.github.io/ClimateMachine.jl/latest/Theory/Atmos/AtmosModel/#Mass),\n[moisture](https://clima.github.io/ClimateMachine.jl/latest/Theory/Atmos/AtmosModel/#Moisture), and\n[precipitation](https://clima.github.io/ClimateMachine.jl/latest/Theory/Atmos/AtmosModel/#Precipitating-Species)\nthe ``\\mathcal{S}_{q_{tot}}`` sink has to be multiplied by ``\\rho`` before\n  adding it as one of the sink terms to both ``\\rho`` and ``\\rho q_{tot}``\n  state variables.\nThe ``\\mathcal{S}_{q_{liq}}``, ``\\mathcal{S}_{q_{rai}}`` sources have to be multiplied by ``\\rho``\n  before adding them as one of the source terms to ``\\rho q_{liq}`` and\n  ``\\rho q_{rai}``state variables.\nFor the conservation equation for\n[total energy](https://clima.github.io/ClimateMachine.jl/latest/Theory/Atmos/AtmosModel/#Energy),\n  the sink due to removing ``q_{tot}`` is computed as:\n```math\n\\begin{equation}\n\\left. \\mathcal{S}_{\\rho e} \\right|_{precip} =\n  \\left. \\sum_{j\\in\\{v,l,i\\}}(I_j + \\Phi)  \\rho C(q_j \\rightarrow q_p) \\right|_{precip} =\n  (I_l + \\Phi) \\rho \\, \\mathcal{S}_{q_{tot}}\n\\end{equation}\n```\nwhere:\n - ``I_l = c_{vl} (T - T_0)`` is the internal energy of liquid water,\n - ``T`` is the temperature,\n - ``T_0`` is the thermodynamic reference temperature (which is unrelated to the reference temperature used in hydrostatic reference states used in the momentum equations),\n - ``c_{vl}`` is the isochoric specific heat of liquid water,\n - ``\\Phi`` is the effective gravitational potential.\n\n### Rain and Snow\n\nThe source of rain ``\\mathcal{S}_{q_{rai}}`` is a sum of the\n  [autoconversion](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_1M/#Rain-autoconversion),\n  [accretion](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_1M/#Accretion),\n  [rain evaporation](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_1M/#Rain-evaporation-and-snow-sublimation), and\n  [snow melt](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_1M/#Snow-melt)\n  processes.\nSimilarily, the source of snow ``\\mathcal{S}_{q_{sno}}`` is a sum of the\n  [autoconversion](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_1M/#Snow-autoconversion),\n  [accretion](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_1M/#Accretion),\n  [snow deposition/sublimation](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_1M/#Rain-evaporation-and-snow-sublimation), and\n  [snow melt](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics_1M/#Snow-melt)\n  processes.\nThe sink of total water is equal to the sum of the rain and snow sources:\n  ``\\mathcal{S}_{q_{tot}}`` = -``\\mathcal{S}_{q_{rai}}`` - ``\\mathcal{S}_{q_{sno}}``.\nThe sink of cloud liquid water ``\\mathcal{S}_{q_{liq}}`` is the sum of\n  rain autoconversion,\n  rain accretion with cloud liquid water, and\n  snow accretion with cloud liquid water processes.\nThe sink of cloud ice ``\\mathcal{S}_{q_{ice}}`` is the sum of\n  snow autoconversion,\n  snow accretion with cloud ice, and\n  rain accretion with cloud ice processes.\nFollowing the conservation equations for\n[mass](https://clima.github.io/ClimateMachine.jl/latest/Theory/Atmos/AtmosModel/#Mass),\n[moisture](https://clima.github.io/ClimateMachine.jl/latest/Theory/Atmos/AtmosModel/#Moisture), and\n[precipitation](https://clima.github.io/ClimateMachine.jl/latest/Theory/Atmos/AtmosModel/#Precipitating-Species)\nthe ``\\mathcal{S}_{q_{tot}}`` sink has to be multiplied by ``\\rho`` before\n  adding it as one of the sink terms to both ``\\rho`` and ``\\rho q_{tot}``\n  state variables.\nThe ``\\mathcal{S}_{q_{liq}}``, ``\\mathcal{S}_{q_{ice}}``,\n  ``\\mathcal{S}_{q_{rai}}``, and ``\\mathcal{S}_{q_{sno}}`` sources\n  have to be multiplied by ``\\rho`` before adding them as one of the\n  source terms to ``\\rho q_{liq}``, ``\\rho q_{ice}``, ``\\rho q_{rai}``, and\n  ``\\rho q_{sno}``state variables.\nFor the conservation equation for\n[total energy](https://clima.github.io/ClimateMachine.jl/latest/Theory/Atmos/AtmosModel/#Energy)\n  the source term due to microphysics processes is caused by\n  either removing cloud condensate outside of the working fluid\n  or by changing phase and releasing latent heat outside of the working fluid.\nBelow, contributions from different microphysics processes are listed:\\\\\nThe contribution from\n  cloud liquid water to rain autoconversion,\n  cloud liquid water accretion by rain,\n  and rain evaporation\n  is computed as:\n```math\n\\begin{equation}\n\\left. \\mathcal{S}_{\\rho e} \\right|_{precip} =\n  - (I_l + \\Phi) \\rho \\, \\mathcal{S}_{q_{rai}}\n\\end{equation}\n```\nwhere:\n - ``I_l = c_{vl} (T - T_0)`` is the internal energy of liquid water,\n - ``T`` is the temperature,\n - ``T_0`` is the thermodynamic reference temperature (which is unrelated to the reference temperature used in hydrostatic reference states used in the momentum equations),\n - ``c_{vl}`` is the isochoric specific heat of liquid water,\n - ``\\Phi`` is the effective gravitational potential.\n - ``\\mathcal{S}_{q_{rai}}`` is the source of rain from the above three processes.\nThe contribution from\n  cloud ice to snow autoconversion,\n  cloud ice accretion by snow,\n  cloud liquid accretion by snow in temperatures below freezing,\n  and snow deposition/sublimation\n  is computed as:\n```math\n\\begin{equation}\n\\left. \\mathcal{S}_{\\rho e} \\right|_{precip} =\n  - (I_i + \\Phi) \\rho \\, \\mathcal{S}_{q_{sno}}\n\\end{equation}\n```\nwhere:\n - ``I_i = c_{vi} (T - T_0) - I_{i0}`` is the internal energy of ice,\n - ``c_{vi}`` is the isochoric specific heat of ice,\n - ``I_{i0}`` is the difference in specific internal energy between ice and liquid at ``T_0``,\n - ``\\mathcal{S}_{q_{sno}}`` is the source of snow from the above four processes.\nThe contribution from\n  accretion of cloud liquid water by snow in temperatures above freezing\n  is computed as:\n```math\n\\begin{equation}\n\\left. \\mathcal{S}_{\\rho e} \\right|_{precip} =\n  ((1 + \\alpha) I_l - \\alpha I_i + \\Phi) \\rho \\, \\mathcal{S}_{q_{liq}}\n\\end{equation}\n```\nwhere:\n - ``\\alpha = \\frac{c_{vl}}{L_f}(T - T_{freeze})``\n - ``\\mathcal{S}_{q_{liq}}`` is the source of cloud liquid water from\n  accretion of cloud liquid water by snow in temperatures above freezing.\nThe contribution from cloud ice accretion by rain (the result is snow)\n  is computed as:\n```math\n\\begin{equation}\n\\left. \\mathcal{S}_{\\rho e} \\right|_{precip} =\n   (I_i + \\Phi) \\rho \\, \\mathcal{S}_{q_{ice}} - \\rho L_f \\mathcal{S}_{q_{rai}}\n\\end{equation}\n```\n where:\n - ``\\mathcal{S}_{q_{ice}}`` and ``\\mathcal{S}_{q_{rai}}``\n  are the sinks of cloud ice and rain due to accretion..\nFinally, the contribution from accretion between rain and snow\n  as well as snow melting into rain is computed as:\n```math\n\\begin{equation}\n\\left. \\mathcal{S}_{\\rho e} \\right|_{precip} =\n   \\rho L_f \\mathcal{S}_{q_{sno}}\n\\end{equation}\n```\n where:\n - ``\\mathcal{S}_{q_{sno}}`` is the source of snow in those two processes.\n"
  },
  {
    "path": "docs/src/Theory/Atmos/Model/tracers.md",
    "content": "# [Tracers](@id Tracers-docs)\n\n!!! note\n\n    Usage: Enable tracers using a keyword argument in the AtmosModel\n    specification\\\n    - `tracers = NoTracer()`\\\n    - `tracers = NTracers{N, FT}(δ_χ)` where N is the number of tracers\n    required.\\\n    FT is the float-type and $\\delta_{\\chi}$ is an SVector of diffusivity\n    scaling coefficients\n\n!!! note\n    Hyperdiffusion is currently not supported with tracers. Laplacian\n    diffusion coefficients may still be specified. (See above)\n\n\nIn `tracers.jl`, we define the equation sets governing tracer\ndynamics. Specifically, we address the the equations of tracer motion in\nconservation form,\n\n```julia\nexport NoTracers, NTracers\n```\n\n### [Equations](@id tracer-eqns)\n```math\n\\frac{\\partial \\rho\\chi}{\\partial t} +  \\nabla \\cdot ( \\rho\\chi u) = \\nabla \\cdot (\\rho\\delta_{D\\chi}\\mathrm{D_{T}}\\nabla\\chi) + \\rho \\mathrm{S}\n```\nwhere  $$\\chi$$ represents the tracer species, $$\\mathrm{S}$$ represents\nthe tracer source terms and $$\\delta_{D\\chi} \\mathrm{D_{T}}$$ represents\nthe scaled turbulent eddy diffusivity for each tracer.  Currently a default\nscaling of `1` is supported.  The equation as written above corresponds to\na single scalar tracer, but can be extended to include multiple independent\ntracer species.\n\nWe first define an abstract tracer type, and define the default function\nsignatures. Two options are currently supported. [`NoTracers`](@ref\nno-tracers), and [`NTracers`](@ref multiple-tracers).\n\n## [Abstract Tracer Type](@id abstract-tracer-type)\n\nDefault stub functions for a generic tracer type are defined here.\n\n```julia\nabstract type TracerModel <: BalanceLaw end\n\nvars_state(::TracerModel, ::AbstractStateType, FT) = @vars()\n\nfunction atmos_init_aux!(\n    ::TracerModel,\n    ::AtmosModel,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    nothing\nend\nfunction atmos_nodal_update_auxiliary_state!(\n    ::TracerModel,\n    m::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    nothing\nend\nfunction flux_tracers!(\n    ::TracerModel,\n    atmos::AtmosModel,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    nothing\nend\nfunction compute_gradient_flux!(\n    ::TracerModel,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    nothing\nend\nfunction flux_second_order!(\n    ::TracerModel,\n    flux::Grad,\n    state::Vars,\n    diffusive::Vars,\n    aux::Vars,\n    t::Real,\n    D_t,\n)\n    nothing\nend\nfunction compute_gradient_argument!(\n    ::TracerModel,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    nothing\nend\n```\n\n## [NoTracers](@id no-tracers)\nThe default tracer type in both the LES and GCM configurations is the no\ntracer model. (This means no state variables for tracers are being carried\naround). For the purposes of this model, moist variables are considered\nseparately in `moisture.jl`.\n\n```@docs\nClimateMachine.Atmos.NoTracers\n```\n\n## [NTracers](@id multiple-tracers)\nAllows users to specify an integer corresponding to the number of\ntracers required.  Note that tracer naming is not currently supported,\ni.e. the user must track each tracer variable based on its numerical\nindex. Sources can be added to each tracer based on the corresponding\nnumerical vector index. Initial profiles must be specified using the\n`init_state_prognostic!` hook at the experiment level.\n\n```@docs\nClimateMachine.Atmos.NTracers{N,FT}\n```\n"
  },
  {
    "path": "docs/src/Theory/Common/Turbulence.md",
    "content": "## [Turbulence Closures](@id Turbulence-Closures-docs)\nModule `TurbulenceClosures.jl` currently supports \npointwise models of the eddy viscosity/eddy diffusivity type.\n\nSupported constructors include are:\\\n[`ConstantDynamicViscosity`](@ref constant-viscosity)\\\n[`SmagorinskyLilly`](@ref smagorinsky-lilly)\\\n[`Vreman`](@ref vreman)\\\n[`AnisoMinDiss`](@ref aniso-min-diss)\\\n\n!!! note\n    Usage: This is a quick-ref guide to using turbulence models as a\n    subcomponent of `AtmosModel` \\\n    $\\nu$ is the kinematic viscosity, $C_smag$ is the Smagorinsky Model coefficient,\n    - `turbulence=ConstantDynamicViscosity(ν)`\\\n    - `turbulence=SmagorinskyLilly(C_smag)`\\\n    - `turbulence=Vreman(C_smag)`\\\n    - `turbulence=AnisoMinDiss(C_poincare)`\n\n```julia\nusing DocStringExtensions\nusing CLIMAParameters.Atmos.SubgridScale: inv_Pr_turb\nexport ConstantDynamicViscosity, SmagorinskyLilly, Vreman, AnisoMinDiss\nexport turbulence_tensors\n```\n\n## Abstract Type\nWe define a `TurbulenceClosure` abstract type and default functions for the\ngeneric turbulence closure which will be overloaded with model specific\nfunctions. Minimally, overloaded functions for the following stubs must\nbe defined for a turbulence model.\n\n```julia\nabstract type TurbulenceClosure end\n\n\nvars_state(::TurbulenceClosure, ::AbstractStateType, FT) = @vars()\n\nfunction atmos_init_aux!(\n    ::TurbulenceClosure,\n    ::AtmosModel,\n    aux::Vars,\n    geom::LocalGeometry,\n) end\nfunction compute_gradient_argument!(\n    ::TurbulenceClosure,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) end\nfunction compute_gradient_flux!(\n    ::TurbulenceClosure,\n    ::Orientation,\n    diffusive,\n    ∇transform,\n    state,\n    aux,\n    t,\n) end\n```\n\nThe following may need to be addressed if turbulence models require\nadditional state variables or auxiliary variable updates (e.g. TKE based\nmodels)\n\n```julia\nvars_state(::TurbulenceClosure, ::Prognostic, FT) = @vars()\nfunction atmos_nodal_update_auxiliary_state!(\n    ::TurbulenceClosure,\n    ::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) end\n```\n\n## Eddy-viscosity Models\nThe following function provides an example of a stub for an eddy-viscosity\nmodel.  Currently, scalar and diagonal tensor viscosities and diffusivities\nare supported.\n\nGeneric math functions for use within the turbulence closures such as\nthe [principal tensor invariants](@ref tensor-invariants), [symmetric\ntensors](@ref symmetric-tensors) and [tensor norms](@ref tensor-norms)\nhave been included.\n\n### [Pricipal Invariants](@id tensor-invariants)\n```math\n\\textit{I}_{1} = \\mathrm{tr(X)} \\\\\n\\textit{I}_{2} = (\\mathrm{tr(X)}^2 - \\mathrm{tr(X)^2}) / 2 \\\\\n\\textit{I}_{3} = \\mathrm{det(X)} \\\\\n```\n\n### [Symmetrize](@id symmetric-tensors)\n```math\n\\frac{\\mathrm{X} + \\mathrm{X}^{T}}{2} \\\\\n```\n### [2-Norm](@id tensor-norms)\nGiven a tensor X, return the tensor dot product\n```math\n\\sum_{i,j} S_{ij}^2\n```\n### [Strain-rate Magnitude](@id strain-rate-magnitude)\nBy definition, the strain-rate magnitude, as defined in standard turbulence\nmodelling is computed such that\n\n```math\n|\\mathrm{S}| = \\sqrt{2 \\sum_{i,j} \\mathrm{S}_{ij}^2}\n```\nwhere\n```math\n\\vec{S}(\\vec{u}) = \\frac{1}{2}  \\left(\\nabla\\vec{u} +  \\left( \\nabla\\vec{u} \\right)^T \\right)\n```\n\\mathrm{S} is the rate-of-strain tensor. (Symmetric component of the\nvelocity gradient). Note that the skew symmetric component (rate-of-rotation)\nis not currently computed.\n\n```julia\n\"\"\"\n    strain_rate_magnitude(S)\nGiven the rate-of-strain tensor `S`, computes its magnitude.\n\"\"\"\nfunction strain_rate_magnitude(S::SHermitianCompact{3, FT, 6}) where {FT}\n    return sqrt(2 * norm2(S))\nend\n```\n\n### [Constant Viscosity Model](@id constant-viscosity)\n`ConstantDynamicViscosity` requires a user to specify the constant\nviscosity (kinematic) and appropriately computes the turbulent stress\ntensor based on this term. Diffusivity can be computed using the turbulent\nPrandtl number for the appropriate problem regime.\n\n```math\n\\tau =\n    \\begin{cases}\n    - 2 \\nu \\mathrm{S} & \\mathrm{WithoutDivergence},\\\\\n    - 2 \\nu \\mathrm{S} + \\frac{2}{3} \\nu \\mathrm{tr(S)} I_3 & \\mathrm{WithDivergence}.\n    \\end{cases}\n```\n\n## [Smagorinsky-Lilly](@id smagorinsky-lilly)\nThe Smagorinsky turbulence model, with Lilly's correction to\nstratified atmospheric flows, is included in ClimateMachine.\nThe input parameter to this model is the Smagorinsky coefficient.\nFor atmospheric flows, the coefficient `C_smag` typically takes values between\n0.15 and 0.23. Flow dependent `C_smag` are currently not supported (e.g. Germano's\nextension). The Smagorinsky-Lilly model does not contain explicit filtered terms.\n\n#### Equations\n\n```math\n\\nu = (C_{s} \\mathrm{f}_{b} \\Delta)^2 \\sqrt{|\\mathrm{S}|}\n```\nwith the stratification correction term\n```math\nf_{b} =\n   \\begin{cases}\n   1 & \\mathrm{Ri} \\leq 0 ,\\\\\n   \\max(0, 1 - \\mathrm{Ri} / \\mathrm{Pr}_{t})^{1/4} & \\mathrm{Ri} > 0 .\n   \\end{cases}\n```\n```math\n\\mathrm{Ri} =  \\frac{N^2}{{|S|}^2}\n```\n```math\nN = \\left( \\frac{g}{\\theta_v} \\frac{\\partial \\theta_v}{\\partial z}\\right)^{1/2}\n```\nHere, $\\mathrm{Ri}$ and $\\mathrm{Pr}_{t}$ are the Richardson and turbulent\nPrandtl numbers respectively.  $\\Delta$ is the mixing length in the relevant\ncoordinate direction. We use the DG metric terms to determine the local\neffective resolution (see `src/Mesh/Geometry.jl`), and modify the vertical\nlengthscale by the stratification correction factor $\\mathrm{f}_{b}$\nso that $\\Delta_{vert} = \\Delta z f_b$.\n\n## [Vreman Model](@id vreman)\nVreman's turbulence model for anisotropic flows, which provides a less\ndissipative solution (specifically in the near-wall and transitional regions)\nthan the Smagorinsky-Lilly method. This model relies of first derivatives\nof the velocity vector (i.e., the gradient tensor).  By design, the Vreman\nmodel handles transitional as well as fully turbulent flows adequately.\nThe input parameter to this model is the Smagorinsky coefficient -\nthe coefficient is modified within the model functions to account for\ndifferences in model construction.\n\n#### Equations\n```math\n\\nu_{t} = 2.5 C_{s}^2 \\sqrt{\\frac{B_{\\beta}}{u_{i,j}u_{i,j}}},\n```\nwhere ($i,j, m = (1,2,3)$)\n```math\n\\begin{align}\nB_{\\beta} &= \\beta_{11}\\beta_{22} + \\beta_{11}\\beta_{33} + \\beta_{22}\\beta_{33} - (\\beta_{13}^2 + \\beta_{12}^2 + \\beta_{23}^2) \\\\\n\\beta_{ij} &= \\Delta_{m}^2 u_{i, m} u_{j, m} \\\\\nu_{i,j} &= \\frac{\\partial u_{i}}{\\partial x_{j}}.\n\\end{align}\n```\n\n## [Anisotropic Minimum Dissipation](@id aniso-min-diss)\nThis method is based Vreugdenhil and Taylor's minimum-dissipation\neddy-viscosity model.  The principles of the Rayleigh quotient minimizer\nare applied to the energy dissipation terms in the conservation equations,\nresulting in a maximum dissipation bound, and a model for eddy viscosity\nand eddy diffusivity.\n\n```math\n\\nu_e = (\\mathrm{C}\\delta)^2  \\mathrm{max}\\left[0, - \\frac{\\hat{\\partial}_k \\hat{u}_{i} \\hat{\\partial}_k \\hat{u}_{j} \\mathrm{\\hat{S}}_{ij}}{\\hat{\\partial}_p \\hat{u}_{q} \\hat{\\partial}_p \\hat{u}_{q}} \\right]\n```\n"
  },
  {
    "path": "docs/src/index.md",
    "content": "# ClimateMachine\n\n```@meta\nCurrentModule = ClimateMachine\n```\n\nThe `ClimateMachine` is a software package that models the evolution of the\nEarth system over weeks to centuries. The `ClimateMachine` solves\nthree-dimensional partial differential equations for the distributions of\nwater, momentum, energy, and tracers such as carbon in the atmosphere, oceans,\nand on land.\n\nThe `ClimateMachine` will harness a wide range of Earth observations and data\ngenerated computationally to predict the evolution of Earth’s climate and\nfeatures such as droughts, rainfall extremes, and high-impact storms.\n\n## Subcomponents\n\nThe `ClimateMachine` currently consists of three models for the subcomponents\nof the Earth system:\n\n* `ClimateMachine.Atmos`: A model of the fluid\n  mechanics of the atmosphere and its interaction with solar radiation and\n  phase changes of water that occur, for example, in clouds.\n\n* `ClimateMachine.Ocean`: A model for the fluid mechanics of the ocean\n  and its distributions of heat, salinity, carbon, and other tracers.\n\n* `ClimateMachine.Land`: A model for the flow of energy and water in\n  soils and on the land surface, for the biophysics of vegetation on land,\n  and for the transfer and storage of carbon in the land biosphere.\n\nThe subcomponents will be coupled by exchanging water, momentum, energy,\nand tracers such as carbon dioxide across their boundaries.\n\n## Dynamical core\n\nA dynamical core based on discontinuous Galerkin numerical methods is\nused to discretize the physical conservation laws that underlie each of\nthe `ClimateMachine`'s subcomponents.\n\n## How to use the documentation\n\n| If you want to...                     | Look here                   |\n| :-------------------------------------|  -------------------------: |\n| install `ClimateMachine.jl`           | Getting started |\n| learn about default configurations | Getting started |\n| look up common terminology | Getting started |\n| run simple `Atmos`, `Ocean`, or `Land` models | Tutorials |\n| run code fragments detailing numerical methods | Tutorials |\n| get more detail about a model subcomponent or a numerical method |  How-to-guides|\n| learn how to call functions in source code | APIs |\n| help develop `ClimateMachine.jl` software | Contribution guide |\n\n## Authors\n\nThe `ClimateMachine` is being developed by [the Climate Modeling\nAlliance](https://clima.caltech.edu).\n"
  },
  {
    "path": "experiments/AtmosGCM/GCMDriver/GCMDriver.jl",
    "content": "#!/usr/bin/env julia --project\n\n# This file is the entry point for a generalized GCM driver that is used\n# to run a variety of GCM experiments.\n# Currently available default experiments are:\n#   1. `baroclinicwave_problem`: initial value problem (no sources to\n#      maintain equilibration)\n#   2. `heldsuarez_problem`: runs to equilibration\n#\n# Experiment name is required to be specified in the command line:\n#   e.g.: julia --project experiments/AtmosGCM/GCMDriver/jl --experiment=heldsuarez_problem\n#\n# The initial / boundary conditions of each experiment (defaults of which are\n# defined in the experiment`_problem.jl` files) can be mixed and matched, as long\n# as they are specified in:\n#   - gcm_bcs.jl (use `--surface-flux`)\n#   - gcm_perturbations.jl (use `--init-perturbation`)\n#   - gcm_base_states.jl (use `--init-base-state`)\n#   - gcm_moisture_profiles.jl (use `--init-moisture-profile`)\n# Default sources cannot be currently overriden from command line and are defined in `gcm_sources.jl`\n\nusing ArgParse\nusing LinearAlgebra\nusing StaticArrays\nusing Test\nusing UnPack\n\nusing ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.Atmos: recover_thermo_state\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Orientations\nusing ClimateMachine.SystemSolvers\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Spectra: compute_gaussian!\n\nusing ClimateMachine.BalanceLaws\nimport ClimateMachine.BalanceLaws: source, prognostic_vars\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet\nusing CLIMAParameters.Atmos.SubgridScale\n\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n# Menu for initial conditions, boundary conditions and sources\ninclude(\"gcm_bcs.jl\")                # Boundary conditions\ninclude(\"gcm_perturbations.jl\")      # Initial perturbation\ninclude(\"gcm_base_states.jl\")        # Initial base state\ninclude(\"gcm_moisture_profiles.jl\")  # Initial moisture profile\ninclude(\"gcm_sources.jl\")            # GCM-specific sources and parametrizations\n\n# Set default initial conditions, boundary conditions and sources for each experiment type\ninclude(\"baroclinicwave_problem.jl\") # initial value problem (no sources to maintain equilibration)\ninclude(\"heldsuarez_problem.jl\")     # runs to equilibration\n\n# Initial conditions (common to all GCM experiments)\nfunction init_gcm_experiment!(problem, bl, state, aux, coords, t)\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n    # General parameters\n    # constant for virtual temperature conversion\n    # - FIX: this assumes no initial liq/ice\n    M_v::FT = molmass_ratio(param_set) - 1\n\n    # Select initial perturbation\n    u′, v′, w′, rand_pert =\n        init_perturbation(problem.perturbation, bl, state, aux, coords, t)\n\n    # Select initial base state\n    T_v, p, u_ref, v_ref, w_ref =\n        init_base_state(problem.base_state, bl, state, aux, coords, t)\n\n    # Select initial moisture profile\n    q_tot = init_moisture_profile(\n        problem.moisture_profile,\n        bl,\n        state,\n        aux,\n        coords,\n        t,\n        p,\n    )\n\n    # Calculate initial total winds\n    u_sphere = SVector{3, FT}(u_ref + u′, v_ref + v′, w_ref + w′)\n    u_cart = sphr_to_cart_vec(bl.orientation, u_sphere, aux)\n\n    # Calculate initial temperature and density\n    phase_partition = PhasePartition(q_tot)\n    T::FT = T_v / (1 + M_v * q_tot) # this needs to be adapted for ice and liq\n\n    ρ::FT = air_density(param_set, T, p, phase_partition)\n\n    ## potential & kinetic energy\n    e_pot::FT = gravitational_potential(bl.orientation, aux)\n    e_kin::FT = 0.5 * u_cart' * u_cart\n    e_tot::FT = total_energy(param_set, e_kin, e_pot, T, phase_partition)\n\n    ## Assign state variables\n    state.ρ = ρ\n    state.ρu = ρ * u_cart\n    state.energy.ρe = ρ * e_tot * rand_pert\n\n    if moisture_model(bl) isa EquilMoist\n        state.moisture.ρq_tot = ρ * q_tot\n    end\n\n    return nothing\nend\n\nlowercasearg(arg::Nothing) = nothing\nlowercasearg(arg) = lowercase(arg)\n\n# Helper for parsing `--experiment` command line argument to initialize defaults for the specified problem\nfunction parse_experiment_arg(arg)\n    if arg == \"baroclinic_wave\"\n        problem_type = BaroclinicWaveProblem\n    elseif arg == \"heldsuarez\"\n        problem_type = HeldSuarezProblem\n    else\n        error(\"unknown experiment: \" * arg)\n    end\n\n    return problem_type\nend\n\n# General GCM configuration setup\nfunction config_gcm_experiment(\n    ::Type{FT},\n    poly_order,\n    resolution,\n    experiment_arg,\n    perturbation_arg,\n    base_state_arg,\n    moisture_profile_arg,\n    surface_flux_arg,\n) where {FT}\n    # Set up a reference state for linearization of equations\n    temp_profile_ref =\n        DecayingTemperatureProfile{FT}(param_set, FT(290), FT(220), FT(8e3))\n    ref_state = HydrostaticState(temp_profile_ref)\n\n    # Distance between surface and top of atmosphere (m)\n    domain_height::FT = 30e3\n\n    # make the orientation explicit (for surface fluxes)\n    orientation = SphericalOrientation()\n\n    # Determine the problem type\n    problem_type = parse_experiment_arg(experiment_arg)\n\n    # Set up problem components\n    perturbation = parse_perturbation_arg(perturbation_arg)\n    base_state = parse_base_state_arg(base_state_arg)\n    moisture_profile = parse_moisture_profile_arg(moisture_profile_arg)\n\n    # Choose the moisture model\n    if moisture_profile isa NoMoistureProfile\n        hyperdiffusion = DryBiharmonic(FT(8 * 3600))\n        moisture = DryModel()\n    else\n        hyperdiffusion = EquilMoistBiharmonic(FT(8 * 3600))\n        moisture = EquilMoist()\n    end\n\n    # Define the Atmosphere physics\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = ConstantKinematicViscosity(FT(0)),\n        hyperdiffusion = hyperdiffusion,\n        moisture = moisture,\n    )\n\n    # Set up the boundary conditions\n    boundaryconditions = parse_surface_flux_arg(\n        physics,\n        surface_flux_arg,\n        FT,\n        param_set,\n        orientation,\n        moisture,\n    )\n\n    # Set up the problem\n    problem = problem_type(\n        physics;\n        boundaryconditions = boundaryconditions,\n        perturbation = perturbation,\n        base_state = base_state,\n        moisture_profile = moisture_profile,\n    )\n\n    # Create the Atmosphere model\n    model = AtmosModel{FT}(\n        AtmosGCMConfigType,\n        physics;\n        problem = problem,\n        source = setup_source(problem),\n    )\n\n    # Create the GCM driver configuration\n    config = ClimateMachine.AtmosGCMConfiguration(\n        problem_name(problem),\n        poly_order,\n        resolution,\n        domain_height,\n        param_set,\n        init_gcm_experiment!;\n        model = model,\n    )\n\n    return config\nend\n\n# Diagnostics configuration setup\nfunction config_diagnostics(::Type{FT}, driver_config) where {FT}\n    interval = \"40000steps\"\n\n    _planet_radius = planet_radius(param_set)::FT\n\n    info = driver_config.config_info\n\n    # Setup diagnostic grid(s)\n    nlats = 32\n\n    sinθ, wts = compute_gaussian!(nlats)\n    lats = asin.(sinθ) .* 180 / π\n    lons = 180.0 ./ nlats * collect(FT, 1:1:(2nlats))[:] .- 180.0\n\n    boundaries = [\n        FT(lats[1]) FT(lons[1]) _planet_radius\n        FT(lats[end]) FT(lons[end]) FT(_planet_radius + info.domain_height)\n    ]\n\n    lvls = collect(range(\n        boundaries[1, 3],\n        boundaries[2, 3],\n        step = FT(1000), # in m\n    ))\n\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries;\n        axes = [lats, lons, lvls],\n    )\n\n    # Setup diagnostics group(s)\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosGCMConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n\n    ds_dgngrp = setup_atmos_spectra_diagnostics(\n        AtmosGCMConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp, ds_dgngrp])\nend\n\n# Entry point\nfunction main()\n    # Helper for command-line arguments to specify the experiment and\n    # the override experiment defaults\n    # various options\n    # TODO: some of this must move to a future namelist functionality\n    exp_args = ArgParseSettings(autofix_names = true) # hyphens replaced with underscores in cl_args\n    add_arg_group!(exp_args, \"GCMDriver\")\n    @add_arg_table! exp_args begin\n        \"--experiment\"\n        help = \"\"\"\n            baroclinic_wave defaults:\n                init-perturbation: deterministic,\n                init-base-state: bc_wave,\n                init-moisture-profile: moist_low_tropics;\n            heldsuarez defaults:\n                init-perturbation: deterministic,\n                init-base-state: heldsuarez,\n                init-moisture-profile: moist_low_tropics\n            \"\"\"\n        metavar = \"baroclinic_wave|heldsuarez\"\n        arg_type = String\n        required = true\n        \"--init-perturbation\"\n        help = \"select the perturbation to use\"\n        metavar = \"deterministic|random|zero\"\n        arg_type = String\n        \"--init-base-state\"\n        help = \"select the initial state to use\"\n        metavar = \"bc_wave|heldsuarez|zero\"\n        arg_type = String\n        \"--init-moisture-profile\"\n        help = \"specify the moisture profile\"\n        metavar = \"moist_low_tropics|zero|dry\"\n        arg_type = String\n        \"--surface-flux\"\n        help = \"specify surface flux for energy and moisture\"\n        metavar = \"default|bulk\"\n        arg_type = String\n        default = \"default\"\n    end\n\n    cl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = exp_args)\n    experiment_arg = lowercasearg(cl_args[\"experiment\"])\n    perturbation_arg = lowercasearg(cl_args[\"init_perturbation\"]) # use \"_\" not \"-\" in cl_args\n    base_state_arg = lowercasearg(cl_args[\"init_base_state\"])\n    moisture_profile_arg = lowercasearg(cl_args[\"init_moisture_profile\"])\n    surface_flux_arg = lowercasearg(cl_args[\"surface_flux\"])\n\n    # Driver configuration parameters\n    FT = Float64                             # floating type precision\n    poly_order = 5                           # discontinuous Galerkin polynomial order\n    n_horz = 8                              # horizontal element number\n    n_vert = 4                               # vertical element number\n    n_days::FT = 0.1\n    timestart::FT = 0                        # start time (s)\n    timeend::FT = n_days * day(param_set)    # end time (s)\n\n    # Set up driver configuration\n    driver_config = config_gcm_experiment(\n        FT,\n        poly_order,\n        (n_horz, n_vert),\n        experiment_arg,\n        perturbation_arg,\n        base_state_arg,\n        moisture_profile_arg,\n        surface_flux_arg,\n    )\n\n    # Choose time stepper\n    ode_solver_type = ClimateMachine.IMEXSolverType(\n        implicit_model = AtmosAcousticGravityLinearModel,\n        implicit_solver = ManyColumnLU,\n        solver_method = ARK2GiraldoKellyConstantinescu,\n        split_explicit_implicit = true,\n        discrete_splitting = false,\n    )\n\n    # The target acoustic CFL number; time step is computed such that the\n    # horizontal acoustic Courant number is CFL.\n    CFL = FT(0.1)\n\n    # Set up the solver\n    solver_config = ClimateMachine.SolverConfiguration(\n        timestart,\n        timeend,\n        driver_config,\n        Courant_number = CFL,\n        ode_solver_type = ode_solver_type,\n        CFL_direction = HorizontalDirection(),\n        diffdir = HorizontalDirection(),\n    )\n\n    # Set up diagnostics\n    dgn_config = config_diagnostics(FT, driver_config)\n\n    # Set up filters as user-defined callbacks\n    filterorder = 20\n    filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)\n    cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            AtmosFilterPerturbations(driver_config.bl),\n            solver_config.dg.grid,\n            filter,\n            state_auxiliary = solver_config.dg.state_auxiliary,\n        )\n        nothing\n    end\n\n    cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            (\"moisture.ρq_tot\",),\n            solver_config.dg.grid,\n            TMARFilter(),\n        )\n        nothing\n    end\n\n    check_cons = (\n        ClimateMachine.ConservationCheck(\"ρ\", \"100steps\", FT(0.0001)),\n        ClimateMachine.ConservationCheck(\"energy.ρe\", \"100steps\", FT(0.0025)),\n    )\n\n    # Run the model\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        check_cons = check_cons,\n        user_callbacks = (cbtmarfilter, cbfilter),\n        check_euclidean_distance = true,\n    )\n\n    return result, solver_config\nend\n\nresult, solver_config = main()\n"
  },
  {
    "path": "experiments/AtmosGCM/GCMDriver/baroclinicwave_problem.jl",
    "content": "# This file establishes the default initial conditions, boundary conditions and sources\n# for the baroclinicwave_problem experiment, following [Ullrich2014](@cite)\n\n# Override default CLIMAParameters for consistency with literature on this case\nCLIMAParameters.Planet.press_triple(::EarthParameterSet) = 610.78\n\nstruct BaroclinicWaveProblem{BCS, ISP, ISA, WP, BS, MP} <: AbstractAtmosProblem\n    boundaryconditions::BCS\n    init_state_prognostic::ISP\n    init_state_auxiliary::ISA\n    perturbation::WP\n    base_state::BS\n    moisture_profile::MP\nend\nfunction BaroclinicWaveProblem(\n    physics::AtmosPhysics;\n    boundaryconditions = (AtmosBC(physics;), AtmosBC(physics;)),\n    perturbation = nothing,\n    base_state = nothing,\n    moisture_profile = nothing,\n)\n    # Set up defaults\n    if isnothing(perturbation)\n        perturbation = DeterministicPerturbation()\n    end\n    if isnothing(base_state)\n        base_state = BCWaveBaseState()\n    end\n    if isnothing(moisture_profile)\n        moisture_profile = MoistLowTropicsMoistureProfile()\n    end\n\n    problem = (\n        boundaryconditions,\n        init_gcm_experiment!,\n        (_...) -> nothing,\n        perturbation,\n        base_state,\n        moisture_profile,\n    )\n    return BaroclinicWaveProblem{typeof.(problem)...}(problem...)\nend\n\nproblem_name(::BaroclinicWaveProblem) = \"BaroclinicWave\"\n\nsetup_source(::BaroclinicWaveProblem) = (Gravity(), Coriolis())\n"
  },
  {
    "path": "experiments/AtmosGCM/GCMDriver/gcm_base_states.jl",
    "content": "# GCM Initial Base State\n# This file contains helpers and lists currely avaiable options\n\nabstract type AbstractBaseState end\nstruct ZeroBaseState <: AbstractBaseState end\nstruct BCWaveBaseState <: AbstractBaseState end\nstruct HeldSuarezBaseState <: AbstractBaseState end\n\n# Helper for parsing `--init-base-state`` command line argument\nfunction parse_base_state_arg(arg)\n    if arg === nothing\n        base_state = nothing\n    elseif arg == \"bc_wave\"\n        base_state = BCWaveBaseState()\n    elseif arg == \"heldsuarez\"\n        base_state = HeldSuarezBaseState()\n    elseif arg == \"zero\"\n        base_state = ZeroBaseState()\n    else\n        error(\"unknown base state: \" * arg)\n    end\n\n    return base_state\nend\n\n# Initial base state from rest, independent of the model reference state\nfunction init_base_state(::ZeroBaseState, bl, state, aux, coords, t)\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n    _R_d::FT = R_d(param_set)\n    _grav::FT = grav(param_set)\n    _a::FT = planet_radius(param_set)\n    _p_0::FT = MSLP(param_set)\n    T_initial = FT(255)\n    r = norm(coords, 2)\n    h = r - _a\n\n    scale_height = _R_d * FT(T_initial) / _grav\n    p = FT(_p_0) * exp(-h / scale_height)\n    u_ref, v_ref, w_ref = (FT(0), FT(0), FT(0))\n    return T_initial, p, u_ref, v_ref, w_ref\nend\n\n# Initial base state from rest, consistent with the model reference state\nfunction init_base_state(::HeldSuarezBaseState, bl, state, aux, coords, t)\n    FT = eltype(state)\n\n    # T_v is dry ref state\n    T_v = aux.ref_state.T\n    p = aux.ref_state.p\n    u_ref, v_ref, w_ref = (FT(0), FT(0), FT(0))\n\n    return T_v, p, u_ref, v_ref, w_ref\nend\n\n# Initial base state following\n# Ullrich et al. (2016) Dynamical Core Model Intercomparison Project (DCMIP2016) Test Case Document\nfunction init_base_state(::BCWaveBaseState, bl, state, aux, coords, t)\n    FT = eltype(state)\n\n    # general parameters\n    param_set = parameter_set(bl)\n    _grav::FT = grav(param_set)\n    _R_d::FT = R_d(param_set)\n    _Ω::FT = Omega(param_set)\n    _a::FT = planet_radius(param_set)\n    _p_0::FT = MSLP(param_set)\n\n    # grid\n    φ = latitude(bl, aux)\n    z = altitude(bl, aux)\n    γ::FT = 1 # set to 0 for shallow-atmosphere case and to 1 for deep atmosphere case\n\n    # base state parameters\n    k::FT = 3\n    T_E::FT = 310\n    T_P::FT = 240\n    T_0::FT = 0.5 * (T_E + T_P)\n    Γ::FT = 0.005\n    A::FT = 1 / Γ\n    B::FT = (T_0 - T_P) / T_0 / T_P\n    C::FT = 0.5 * (k + 2) * (T_E - T_P) / T_E / T_P\n    b::FT = 2\n    H::FT = _R_d * T_0 / _grav\n    z_t::FT = 15e3\n    λ_c::FT = π / 9\n    φ_c::FT = 2 * π / 9\n    d_0::FT = _a / 6\n    V_p::FT = 1\n\n    # convenience functions for temperature and pressure\n    τ_z_1::FT = exp(Γ * z / T_0)\n    τ_z_2::FT = 1 - 2 * (z / b / H)^2\n    τ_z_3::FT = exp(-(z / b / H)^2)\n    τ_1::FT = 1 / T_0 * τ_z_1 + B * τ_z_2 * τ_z_3\n    τ_2::FT = C * τ_z_2 * τ_z_3\n    τ_int_1::FT = A * (τ_z_1 - 1) + B * z * τ_z_3\n    τ_int_2::FT = C * z * τ_z_3\n    I_T::FT =\n        (cos(φ) * (1 + γ * z / _a))^k -\n        k / (k + 2) * (cos(φ) * (1 + γ * z / _a))^(k + 2)\n\n    # base state virtual temperature and pressure\n    T_v::FT = (τ_1 - τ_2 * I_T)^(-1)\n    p::FT = _p_0 * exp(-_grav / _R_d * (τ_int_1 - τ_int_2 * I_T))\n\n    # base state velocity\n    U::FT =\n        _grav * k / _a *\n        τ_int_2 *\n        T_v *\n        (\n            (cos(φ) * (1 + γ * z / _a))^(k - 1) -\n            (cos(φ) * (1 + γ * z / _a))^(k + 1)\n        )\n    u_ref::FT =\n        -_Ω * (_a + γ * z) * cos(φ) +\n        sqrt((_Ω * (_a + γ * z) * cos(φ))^2 + (_a + γ * z) * cos(φ) * U)\n    v_ref::FT = 0\n    w_ref::FT = 0\n\n    return T_v, p, u_ref, v_ref, w_ref\nend\n"
  },
  {
    "path": "experiments/AtmosGCM/GCMDriver/gcm_bcs.jl",
    "content": "# GCM Boundary Conditions\n# This file contains helpers and lists currely avaiable options\n\n# Helper for parsing `--surface-flux` command line argument\nfunction parse_surface_flux_arg(\n    physics::AtmosPhysics,\n    arg,\n    ::Type{FT},\n    param_set,\n    orientation,\n    moisture,\n) where {FT}\n    if arg === nothing || arg == \"default\"\n        boundaryconditions = (AtmosBC(physics;), AtmosBC(physics;))\n    elseif arg == \"bulk\"\n        if !isa(moisture, EquilMoist)\n            error(\"need a moisture model for surface-flux: bulk\")\n        end\n        _C_drag = C_drag(param_set)::FT\n        bulk_flux = Varying_SST_TJ16(param_set, orientation, moisture) # GCM-specific function for T_sfc, q_sfc = f(latitude, height)\n        #bulk_flux = (T_sfc, q_sfc) # prescribed constant T_sfc, q_sfc\n        boundaryconditions = (\n            AtmosBC(\n                physics;\n                energy = BulkFormulaEnergy(\n                    (bl, state, aux, t, normPu_int) -> _C_drag,\n                    (bl, state, aux, t) -> bulk_flux(state, aux, t),\n                ),\n                moisture = BulkFormulaMoisture(\n                    (state, aux, t, normPu_int) -> _C_drag,\n                    (state, aux, t) -> begin\n                        _, q_tot = bulk_flux(state, aux, t)\n                        q_tot\n                    end,\n                ),\n            ),\n            AtmosBC(physics;),\n        )\n    else\n        error(\"unknown surface flux: \" * arg)\n    end\n\n    return boundaryconditions\nend\n\n# Current options for GCM boundary conditions:\n\n\"\"\"\nstruct Varying_SST_TJ16{PS, O, MM}\n    param_set::PS\n    orientation::O\n    moisture::MM\n\nDefines analytical function for prescribed T_sfc and q_sfc, following\nThatcher and Jablonowski (2016), used to calculate bulk surface fluxes.\nT_sfc_pole = SST at the poles (default: 271 K), specified above\n\"\"\"\nstruct Varying_SST_TJ16{PS, O, MM}\n    param_set::PS\n    orientation::O\n    moisture::MM\nend\nfunction (st::Varying_SST_TJ16)(state, aux, t)\n    FT = eltype(state)\n    φ = latitude(st.orientation, aux)\n\n    T_sfc_pole = FT(271.0)              # surface polar temperature\n    Δφ = FT(26) * FT(π) / FT(180)       # latitudinal width of Gaussian function\n    ΔSST = FT(29)                       # Eq-pole SST difference in K\n    T_sfc = ΔSST * exp(-φ^2 / (2 * Δφ^2)) + T_sfc_pole\n\n    eps = FT(0.622)\n    ρ = state.ρ\n\n    q_tot = state.moisture.ρq_tot / ρ\n    q = PhasePartition(q_tot)\n    param_set = st.param_set\n    e_int = internal_energy(st.orientation, state, aux)\n    T = air_temperature(param_set, e_int, q)\n    p = air_pressure(param_set, T, ρ, q)\n\n    _T_triple::FT = T_triple(param_set)          # triple point of water\n    _press_triple::FT = press_triple(param_set)  # sat water pressure at T_triple\n    _LH_v0::FT = LH_v0(param_set)                # latent heat of vaporization at T_triple\n    _R_v::FT = R_v(param_set)                    # gas constant for water vapor\n\n    q_sfc =\n        eps / p *\n        _press_triple *\n        exp(-_LH_v0 / _R_v * (FT(1) / T_sfc - FT(1) / _T_triple))\n\n    return T_sfc, q_sfc\nend\n"
  },
  {
    "path": "experiments/AtmosGCM/GCMDriver/gcm_moisture_profiles.jl",
    "content": "# GCM Initial Moisture Profiles\n# This file contains helpers and lists currely avaiable options\n\nabstract type AbstractMoistureProfile end\nstruct NoMoistureProfile <: AbstractMoistureProfile end\nstruct ZeroMoistureProfile <: AbstractMoistureProfile end\nstruct MoistLowTropicsMoistureProfile <: AbstractMoistureProfile end\n\n# Helper for parsing `--init-moisture-profile` command line argument\nfunction parse_moisture_profile_arg(arg)\n    if arg === nothing\n        moisture_profile = nothing\n    elseif arg == \"moist_low_tropics\"\n        moisture_profile = MoistLowTropicsMoistureProfile()\n    elseif arg == \"zero\"\n        moisture_profile = ZeroMoistureProfile()\n    elseif arg == \"dry\"\n        moisture_profile = NoMoistureProfile()\n    else\n        error(\"unknown moisture profile: \" * arg)\n    end\n\n    return moisture_profile\nend\n\n# Initial moisture profile for a dry model setup\nfunction init_moisture_profile(\n    ::NoMoistureProfile,\n    bl,\n    state,\n    aux,\n    coords,\n    t,\n    p,\n)\n    FT = eltype(state)\n    return FT(0)\nend\n\n# Initial moisture profile for a moist model setup with 0 initial moisture\nfunction init_moisture_profile(\n    ::ZeroMoistureProfile,\n    bl,\n    state,\n    aux,\n    coords,\n    t,\n    p,\n)\n    FT = eltype(state)\n    return FT(0)\nend\n\n# Initial moisture profile following\n# Ullrich et al. (2016) Dynamical Core Model Intercomparison Project (DCMIP2016) Test Case Document\nfunction init_moisture_profile(\n    ::MoistLowTropicsMoistureProfile,\n    bl,\n    state,\n    aux,\n    coords,\n    t,\n    p,\n)\n    FT = eltype(state)\n\n    param_set = parameter_set(bl)\n    _p_0::FT = MSLP(param_set)\n\n    φ = latitude(bl, aux)\n\n    # Humidity parameters\n    p_w::FT = 34e3              # Pressure width parameter for specific humidity\n    η_crit::FT = p_w / _p_0     # Critical pressure coordinate\n    q_0::FT = 0.018             # Maximum specific humidity (default: 0.018)\n    q_t::FT = 1e-12             # Specific humidity above artificial tropopause\n    φ_w::FT = 2π / 9            # Specific humidity latitude wind parameter\n\n    # get q_tot profile if needed\n    η = p / _p_0                # Pressure coordinate η\n    if η > η_crit\n        q_tot = q_0 * exp(-(φ / φ_w)^4) * exp(-((η - 1) * _p_0 / p_w)^2)\n    else\n        q_tot = q_t\n    end\n\n    return q_tot\nend\n"
  },
  {
    "path": "experiments/AtmosGCM/GCMDriver/gcm_perturbations.jl",
    "content": "# GCM Initial Perturbation\n# This file contains helpers and lists currely avaiable options\n\nusing Distributions\nusing Random\nusing Distributions: Uniform\nusing Random: rand\n\nabstract type AbstractPerturbation end\nstruct NoPerturbation <: AbstractPerturbation end\nstruct DeterministicPerturbation <: AbstractPerturbation end\nstruct RandomPerturbation <: AbstractPerturbation end\n\n# Helper for parsing `--init-perturbation` command line argument\nfunction parse_perturbation_arg(arg)\n    if arg === nothing\n        perturbation = nothing\n    elseif arg == \"deterministic\"\n        perturbation = DeterministicPerturbation()\n    elseif arg == \"zero\"\n        perturbation = NoPerturbation()\n    elseif arg == \"random\"\n        perturbation = RandomPerturbation()\n    else\n        error(\"unknown perturbation: \" * arg)\n    end\n\n    return perturbation\nend\n\nfunction init_perturbation(::NoPerturbation, bl, state, aux, coords, t)\n    FT = eltype(state)\n\n    u′, v′, w′ = (FT(0), FT(0), FT(0))\n    rand_pert = FT(1)\n\n    return u′, v′, w′, rand_pert\nend\n\n# Velocity perturbation following\n# Ullrich et al. (2016) Dynamical Core Model Intercomparison Project (DCMIP2016) Test Case Document\nfunction init_perturbation(\n    ::DeterministicPerturbation,\n    bl,\n    state,\n    aux,\n    coords,\n    t,\n)\n    FT = eltype(state)\n\n    # get parameters\n    param_set = parameter_set(bl)\n    _a::FT = planet_radius(param_set)\n\n    φ = latitude(bl, aux)\n    λ = longitude(bl, aux)\n    z = altitude(bl, aux)\n\n    # perturbation specific parameters\n    z_t::FT = 15e3\n    λ_c::FT = π / 9\n    φ_c::FT = 2 * π / 9\n    d_0::FT = _a / 6\n    V_p::FT = 10\n\n    F_z::FT = 1 - 3 * (z / z_t)^2 + 2 * (z / z_t)^3\n    if z > z_t\n        F_z = FT(0)\n    end\n    d::FT = _a * acos(sin(φ) * sin(φ_c) + cos(φ) * cos(φ_c) * cos(λ - λ_c))\n    c3::FT = cos(π * d / 2 / d_0)^3\n    s1::FT = sin(π * d / 2 / d_0)\n    if 0 < d < d_0 && d != FT(_a * π)\n        u′::FT =\n            -16 * V_p / 3 / sqrt(3) *\n            F_z *\n            c3 *\n            s1 *\n            (-sin(φ_c) * cos(φ) + cos(φ_c) * sin(φ) * cos(λ - λ_c)) /\n            sin(d / _a)\n        v′::FT =\n            16 * V_p / 3 / sqrt(3) * F_z * c3 * s1 * cos(φ_c) * sin(λ - λ_c) /\n            sin(d / _a)\n    else\n        u′ = FT(0)\n        v′ = FT(0)\n    end\n    w′ = FT(0)\n    rand_pert = FT(1)\n\n    return u′, v′, w′, rand_pert\nend\n\nfunction init_perturbation(::RandomPerturbation, bl, state, aux, coords, t)\n    FT = eltype(state)\n    u′, v′, w′ = (FT(0), FT(0), FT(0))\n    rand_pert = FT(1.0 + rand(Uniform(-1e-3, 1e-3)))\n\n    return u′, v′, w′, rand_pert\nend\n"
  },
  {
    "path": "experiments/AtmosGCM/GCMDriver/gcm_sources.jl",
    "content": "# GCM-specific Sources\n# This file contains helpers and lists currently available options\n\n# Current options for GCM-specific sources:\n\n\"\"\"\n    HeldSuarezForcing <: TendencyDef{Source}\n\nDefines a forcing that parametrises radiative and frictional effects using\nNewtonian relaxation and Rayleigh friction, following Held and Suarez (1994)\n\"\"\"\nstruct HeldSuarezForcing <: TendencyDef{Source} end\n\nprognostic_vars(::HeldSuarezForcing) = (Momentum(), Energy())\n\nfunction held_suarez_forcing_coefficients(bl, args)\n    @unpack state, aux = args\n    @unpack ts = args.precomputed\n    FT = eltype(state)\n\n    # Parameters\n    T_ref = FT(255)\n\n    param_set = parameter_set(bl)\n    _R_d = FT(R_d(param_set))\n    _day = FT(day(param_set))\n    _grav = FT(grav(param_set))\n    _cp_d = FT(cp_d(param_set))\n    _p0 = FT(MSLP(param_set))\n\n    # Held-Suarez parameters\n    k_a = FT(1 / (40 * _day))\n    k_f = FT(1 / _day)\n    k_s = FT(1 / (4 * _day))\n    ΔT_y = FT(60)\n    Δθ_z = FT(10)\n    T_equator = FT(315)\n    T_min = FT(200)\n    σ_b = FT(7 / 10)\n\n    # Held-Suarez forcing\n    φ = latitude(bl, aux)\n    p = air_pressure(ts)\n\n    #TODO: replace _p0 with dynamic surface pressure in Δσ calculations to account\n    #for topography, but leave unchanged for calculations of σ involved in T_equil\n    σ = p / _p0\n    exner_p = σ^(_R_d / _cp_d)\n    Δσ = (σ - σ_b) / (1 - σ_b)\n    height_factor = max(0, Δσ)\n    T_equil = (T_equator - ΔT_y * sin(φ)^2 - Δθ_z * log(σ) * cos(φ)^2) * exner_p\n    T_equil = max(T_min, T_equil)\n    k_T = k_a + (k_s - k_a) * height_factor * cos(φ)^4\n    k_v = k_f * height_factor\n    return (k_v = k_v, k_T = k_T, T_equil = T_equil)\nend\n\nfunction source(::Energy, s::HeldSuarezForcing, m, args)\n    @unpack state = args\n    @unpack ts = args.precomputed\n    nt = held_suarez_forcing_coefficients(m, args)\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n    _cv_d = FT(cv_d(param_set))\n    @unpack k_T, T_equil = nt\n    T = air_temperature(ts)\n    return -k_T * state.ρ * _cv_d * (T - T_equil)\nend\n\nfunction source(::Momentum, s::HeldSuarezForcing, m, args)\n    nt = held_suarez_forcing_coefficients(m, args)\n    return -nt.k_v * projection_tangential(m, args.aux, args.state.ρu)\nend\n"
  },
  {
    "path": "experiments/AtmosGCM/GCMDriver/heldsuarez_problem.jl",
    "content": "# This file establishes the default initial conditions, boundary conditions and sources\n# for the heldsuarez_problem experiment, following:\n#\n# - Held, I. M. and Suarez, M. J.: A proposal for the intercomparison\n# of the dynamical cores of atmospheric general circulation models,\n# B. Am. Meteorol. Soc., 75, 1825–1830, 1994.\n#\n# - Thatcher, D. R. and Jablonowski, C.: A moist aquaplanet variant of the\n# Held–Suarez test for atmospheric model dynamical cores, Geosci. Model Dev.,\n# 9, 1263–1292, 2016.\n\n# Override default CLIMAParameters for consistency with literature on this case\nusing Thermodynamics\n\nusing CLIMAParameters.Planet\n\nstruct HeldSuarezProblem{BC, ISP, ISA, WP, BS, MP} <: AbstractAtmosProblem\n    boundaryconditions::BC\n    init_state_prognostic::ISP\n    init_state_auxiliary::ISA\n    perturbation::WP\n    base_state::BS\n    moisture_profile::MP\nend\nfunction HeldSuarezProblem(\n    physics::AtmosPhysics;\n    boundaryconditions = (AtmosBC(physics;), AtmosBC(physics;)),\n    perturbation = nothing,\n    base_state = nothing,\n    moisture_profile = nothing,\n)\n    # Set up defaults\n    if isnothing(perturbation)\n        perturbation = DeterministicPerturbation()\n    end\n    if isnothing(base_state)\n        base_state = HeldSuarezBaseState()\n    end\n    if isnothing(moisture_profile)\n        moisture_profile = MoistLowTropicsMoistureProfile()\n    end\n\n    problem = (\n        boundaryconditions,\n        init_gcm_experiment!,\n        (_...) -> nothing,\n        perturbation,\n        base_state,\n        moisture_profile,\n    )\n    return HeldSuarezProblem{typeof.(problem)...}(problem...)\nend\n\nproblem_name(::HeldSuarezProblem) = \"HeldSuarez\"\n\nsetup_source(::HeldSuarezProblem) = (Gravity(), Coriolis(), HeldSuarezForcing())\n"
  },
  {
    "path": "experiments/AtmosGCM/heldsuarez.jl",
    "content": "#!/usr/bin/env julia --project\nusing ClimateMachine\nusing ArgParse\nusing UnPack\n\ns = ArgParseSettings()\n@add_arg_table! s begin\n    \"--number-of-tracers\"\n    help = \"Number of dummy tracers\"\n    metavar = \"<number>\"\n    arg_type = Int\n    default = 0\nend\n\nparsed_args = ClimateMachine.init(parse_clargs = true, custom_clargs = s)\nconst number_of_tracers = parsed_args[\"number-of-tracers\"]\n\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.StdDiagnostics\nusing ClimateMachine.SystemSolvers: ManyColumnLU\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Grids\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics:\n    air_pressure, air_density, air_temperature, total_energy, internal_energy\nusing ClimateMachine.VariableTemplates\n\nusing ClimateMachine.BalanceLaws\nimport ClimateMachine.BalanceLaws: source, prognostic_vars\n\nusing LinearAlgebra\nusing StaticArrays\nusing Test\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet:\n    MSLP, R_d, day, cp_d, cv_d, grav, Omega, planet_radius\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nfunction init_heldsuarez!(problem, bl, state, aux, localgeo, t)\n    FT = eltype(state)\n\n    param_set = parameter_set(bl)\n    # parameters\n    _a::FT = planet_radius(param_set)\n\n    z_t::FT = 15e3\n    λ_c::FT = π / 9\n    φ_c::FT = 2 * π / 9\n    d_0::FT = _a / 6\n    V_p::FT = 10\n\n    # grid\n    φ = latitude(bl.orientation, aux)\n    λ = longitude(bl.orientation, aux)\n    z = altitude(bl.orientation, param_set, aux)\n\n    # deterministic velocity perturbation\n    F_z::FT = 1 - 3 * (z / z_t)^2 + 2 * (z / z_t)^3\n    if z > z_t\n        F_z = FT(0)\n    end\n    d::FT = _a * acos(sin(φ) * sin(φ_c) + cos(φ) * cos(φ_c) * cos(λ - λ_c))\n    c3::FT = cos(π * d / 2 / d_0)^3\n    s1::FT = sin(π * d / 2 / d_0)\n    if 0 < d < d_0 && d != FT(_a * π)\n        u′::FT =\n            -16 * V_p / 3 / sqrt(3) *\n            F_z *\n            c3 *\n            s1 *\n            (-sin(φ_c) * cos(φ) + cos(φ_c) * sin(φ) * cos(λ - λ_c)) /\n            sin(d / _a)\n        v′::FT =\n            16 * V_p / 3 / sqrt(3) * F_z * c3 * s1 * cos(φ_c) * sin(λ - λ_c) /\n            sin(d / _a)\n    else\n        u′ = FT(0)\n        v′ = FT(0)\n    end\n    w′::FT = 0\n    u_sphere = SVector{3, FT}(u′, v′, w′)\n    u_cart = sphr_to_cart_vec(bl.orientation, u_sphere, aux)\n\n    ## potential & kinetic energy\n    e_kin::FT = 0.5 * u_cart' * u_cart\n\n    ## Assign state variables\n    state.ρ = aux.ref_state.ρ\n    state.ρu = state.ρ * u_cart\n    state.energy.ρe = aux.ref_state.ρe + state.ρ * e_kin\n    if number_of_tracers > 0\n        state.tracers.ρχ = @SVector [FT(ii) for ii in 1:number_of_tracers]\n    end\n\n    nothing\nend\n\n\"\"\"\n    HeldSuarezForcing <: TendencyDef{Source}\n\nDefines a forcing that parametrises radiative and frictional effects using\nNewtonian relaxation and Rayleigh friction, following Held and Suarez (1994)\n\"\"\"\nstruct HeldSuarezForcing <: TendencyDef{Source} end\n\nprognostic_vars(::HeldSuarezForcing) = (Momentum(), Energy())\n\nfunction held_suarez_forcing_coefficients(bl, args)\n    @unpack state, aux = args\n    @unpack ts = args.precomputed\n    FT = eltype(state)\n\n    # Parameters\n    T_ref = FT(255)\n    param_set = parameter_set(bl)\n    _R_d = FT(R_d(param_set))\n    _day = FT(day(param_set))\n    _grav = FT(grav(param_set))\n    _cp_d = FT(cp_d(param_set))\n    _p0 = FT(MSLP(param_set))\n\n    # Held-Suarez parameters\n    k_a = FT(1 / (40 * _day))\n    k_f = FT(1 / _day)\n    k_s = FT(1 / (4 * _day))\n    ΔT_y = FT(60)\n    Δθ_z = FT(10)\n    T_equator = FT(315)\n    T_min = FT(200)\n    σ_b = FT(7 / 10)\n\n    # Held-Suarez forcing\n    φ = latitude(bl, aux)\n    p = air_pressure(ts)\n\n    #TODO: replace _p0 with dynamic surface pressure in Δσ calculations to account\n    #for topography, but leave unchanged for calculations of σ involved in T_equil\n    σ = p / _p0\n    exner_p = σ^(_R_d / _cp_d)\n    Δσ = (σ - σ_b) / (1 - σ_b)\n    height_factor = max(0, Δσ)\n    T_equil = (T_equator - ΔT_y * sin(φ)^2 - Δθ_z * log(σ) * cos(φ)^2) * exner_p\n    T_equil = max(T_min, T_equil)\n    k_T = k_a + (k_s - k_a) * height_factor * cos(φ)^4\n    k_v = k_f * height_factor\n    return (k_v = k_v, k_T = k_T, T_equil = T_equil)\nend\n\nfunction source(::Energy, s::HeldSuarezForcing, m, args)\n    @unpack state = args\n    @unpack ts = args.precomputed\n    nt = held_suarez_forcing_coefficients(m, args)\n    FT = eltype(state)\n    param_set = parameter_set(m)\n    _cv_d = FT(cv_d(param_set))\n    @unpack k_T, T_equil = nt\n    T = air_temperature(ts)\n    return -k_T * state.ρ * _cv_d * (T - T_equil)\nend\n\nfunction source(::Momentum, s::HeldSuarezForcing, m, args)\n    nt = held_suarez_forcing_coefficients(m, args)\n    return -nt.k_v * projection_tangential(m, args.aux, args.state.ρu)\nend\n\nfunction config_heldsuarez(FT, poly_order, resolution)\n    # Set up a reference state for linearization of equations\n    temp_profile_ref =\n        DecayingTemperatureProfile{FT}(param_set, FT(290), FT(220), FT(8e3))\n    ref_state = HydrostaticState(temp_profile_ref)\n\n    # Set up the atmosphere model\n    exp_name = \"HeldSuarez\"\n    domain_height::FT = 30e3 # distance between surface and top of atmosphere (m)\n\n    if number_of_tracers > 0\n        δ_χ = @SVector [FT(ii) for ii in 1:number_of_tracers]\n        tracers = NTracers{number_of_tracers, FT}(δ_χ)\n    else\n        tracers = NoTracers()\n    end\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = ConstantKinematicViscosity(FT(0)),\n        hyperdiffusion = DryBiharmonic(FT(8 * 3600)),\n        moisture = DryModel(),\n        tracers = tracers,\n    )\n\n    model = AtmosModel{FT}(\n        AtmosGCMConfigType,\n        physics;\n        init_state_prognostic = init_heldsuarez!,\n        source = (Gravity(), Coriolis(), HeldSuarezForcing()),\n    )\n\n    config = ClimateMachine.AtmosGCMConfiguration(\n        exp_name,\n        poly_order,\n        resolution,\n        domain_height,\n        param_set,\n        init_heldsuarez!;\n        model = model,\n    )\n\n    return config\nend\n\nfunction main()\n    # Driver configuration parameters\n    FT = Float64                             # floating type precision\n    poly_order = 5                           # discontinuous Galerkin polynomial order\n    n_horz = 8                              # horizontal element number\n    n_vert = 4                               # vertical element number\n    n_days::FT = 1\n    timestart::FT = 0                        # start time (s)\n    timeend::FT = n_days * day(param_set)    # end time (s)\n\n    # Set up driver configuration\n    driver_config = config_heldsuarez(FT, poly_order, (n_horz, n_vert))\n\n    # Set up experiment\n    ode_solver_type = ClimateMachine.IMEXSolverType(\n        implicit_model = AtmosAcousticGravityLinearModel,\n        implicit_solver = ManyColumnLU,\n        solver_method = ARK2GiraldoKellyConstantinescu,\n        split_explicit_implicit = true,\n        discrete_splitting = false,\n    )\n\n    CFL = FT(0.1) # target acoustic CFL number\n\n    # time step is computed such that the horizontal acoustic Courant number is CFL\n    solver_config = ClimateMachine.SolverConfiguration(\n        timestart,\n        timeend,\n        driver_config,\n        Courant_number = CFL,\n        ode_solver_type = ode_solver_type,\n        CFL_direction = HorizontalDirection(),\n        diffdir = HorizontalDirection(),\n    )\n\n    # Set up diagnostics\n    dgn_ssecs = cld(timeend, 2) + 30\n    dgn_interval = \"$(dgn_ssecs)ssecs\"\n    dgn_config = config_diagnostics(FT, driver_config, dgn_interval)\n\n    # Set up user-defined callbacks\n    filterorder = 20\n    filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)\n    cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            AtmosFilterPerturbations(driver_config.bl),\n            solver_config.dg.grid,\n            filter,\n            state_auxiliary = solver_config.dg.state_auxiliary,\n        )\n        nothing\n    end\n\n    # Run the model\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (cbfilter,),\n        check_euclidean_distance = true,\n    )\nend\n\nfunction config_diagnostics(FT, driver_config, interval)\n    _planet_radius = FT(planet_radius(param_set))\n\n    info = driver_config.config_info\n    boundaries = [\n        FT(-90.0) FT(-180.0) _planet_radius\n        FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)\n    ]\n    resolution = (FT(1), FT(1), FT(1000)) # in (deg, deg, m)\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries,\n        resolution,\n    )\n\n    dgngrp = StdDiagnostics.AtmosGCMDefault(\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp])\nend\n\nmain()\n"
  },
  {
    "path": "experiments/AtmosGCM/moist_baroclinic_wave_bulksfcflux.jl",
    "content": "#!/usr/bin/env julia --project\n\nusing ArgParse\nusing LinearAlgebra\nusing StaticArrays\nusing Test\n\nusing ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.SystemSolvers: ManyColumnLU\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Grids\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics:\n    air_density,\n    air_temperature,\n    total_energy,\n    internal_energy,\n    PhasePartition,\n    air_pressure\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet\nusing CLIMAParameters.Atmos.SubgridScale\n\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n# from Thatcher and Jablonowski (2016)\nCLIMAParameters.Planet.press_triple(::EarthParameterSet) = 610.78\n\n# driver-specific parameters added here\nT_sfc_pole(::EarthParameterSet) = 271.0\n\nfunction init_baroclinic_wave!(problem, bl, state, aux, localgeo, t)\n    FT = eltype(state)\n\n    # parameters\n    param_set = parameter_set(bl)\n    _grav::FT = grav(param_set)\n    _R_d::FT = R_d(param_set)\n    _Ω::FT = Omega(param_set)\n    _a::FT = planet_radius(param_set)\n    _p_0::FT = MSLP(param_set)\n\n    k::FT = 3\n    T_E::FT = 310\n    T_P::FT = 240\n    T_0::FT = 0.5 * (T_E + T_P)\n    Γ::FT = 0.005\n    A::FT = 1 / Γ\n    B::FT = (T_0 - T_P) / T_0 / T_P\n    C::FT = 0.5 * (k + 2) * (T_E - T_P) / T_E / T_P\n    b::FT = 2\n    H::FT = _R_d * T_0 / _grav\n    z_t::FT = 15e3\n    λ_c::FT = π / 9\n    φ_c::FT = 2 * π / 9\n    d_0::FT = _a / 6\n    V_p::FT = 1\n    M_v::FT = 0.608\n    p_w::FT = 34e3             ## Pressure width parameter for specific humidity\n    η_crit::FT = p_w / _p_0 ## Critical pressure coordinate\n    q_0::FT = 1e-4                ## Maximum specific humidity (default: 0.018)\n    q_t::FT = 1e-12            ## Specific humidity above artificial tropopause\n    φ_w::FT = 2π / 9           ## Specific humidity latitude wind parameter\n\n    # grid\n    φ = latitude(bl.orientation, aux)\n    λ = longitude(bl.orientation, aux)\n    z = altitude(bl.orientation, param_set, aux)\n    r::FT = z + _a\n    γ::FT = 1 # set to 0 for shallow-atmosphere case and to 1 for deep atmosphere case\n\n    # convenience functions for temperature and pressure\n    τ_z_1::FT = exp(Γ * z / T_0)\n    τ_z_2::FT = 1 - 2 * (z / b / H)^2\n    τ_z_3::FT = exp(-(z / b / H)^2)\n    τ_1::FT = 1 / T_0 * τ_z_1 + B * τ_z_2 * τ_z_3\n    τ_2::FT = C * τ_z_2 * τ_z_3\n    τ_int_1::FT = A * (τ_z_1 - 1) + B * z * τ_z_3\n    τ_int_2::FT = C * z * τ_z_3\n    I_T::FT =\n        (cos(φ) * (1 + γ * z / _a))^k -\n        k / (k + 2) * (cos(φ) * (1 + γ * z / _a))^(k + 2)\n\n    # base state virtual temperature, pressure, specific humidity, density\n    T_v::FT = (τ_1 - τ_2 * I_T)^(-1)\n    p::FT = _p_0 * exp(-_grav / _R_d * (τ_int_1 - τ_int_2 * I_T))\n\n    # base state velocity\n    U::FT =\n        _grav * k / _a *\n        τ_int_2 *\n        T_v *\n        (\n            (cos(φ) * (1 + γ * z / _a))^(k - 1) -\n            (cos(φ) * (1 + γ * z / _a))^(k + 1)\n        )\n    u_ref::FT =\n        -_Ω * (_a + γ * z) * cos(φ) +\n        sqrt((_Ω * (_a + γ * z) * cos(φ))^2 + (_a + γ * z) * cos(φ) * U)\n    v_ref::FT = 0\n    w_ref::FT = 0\n\n    # velocity perturbations\n    F_z::FT = 1 - 3 * (z / z_t)^2 + 2 * (z / z_t)^3\n    if z > z_t\n        F_z = FT(0)\n    end\n    d::FT = _a * acos(sin(φ) * sin(φ_c) + cos(φ) * cos(φ_c) * cos(λ - λ_c))\n    c3::FT = cos(π * d / 2 / d_0)^3\n    s1::FT = sin(π * d / 2 / d_0)\n    if 0 < d < d_0 && d != FT(_a * π)\n        u′::FT =\n            -16 * V_p / 3 / sqrt(3) *\n            F_z *\n            c3 *\n            s1 *\n            (-sin(φ_c) * cos(φ) + cos(φ_c) * sin(φ) * cos(λ - λ_c)) /\n            sin(d / _a)\n        v′::FT =\n            16 * V_p / 3 / sqrt(3) * F_z * c3 * s1 * cos(φ_c) * sin(λ - λ_c) /\n            sin(d / _a)\n    else\n        u′ = FT(0)\n        v′ = FT(0)\n    end\n    w′::FT = 0\n    u_sphere = SVector{3, FT}(u_ref + u′, v_ref + v′, w_ref + w′)\n    u_cart = sphr_to_cart_vec(bl.orientation, u_sphere, aux)\n\n    ## Compute ure profile\n    ## Pressure coordinate η\n    ## η_crit = p_t / p_w ; p_t = 10000 hPa, p_w = 340 hPa\n    η = p / _p_0\n    if η > η_crit\n        q_tot = q_0 * exp(-(φ / φ_w)^4) * exp(-((η - 1) * _p_0 / p_w)^2)\n    else\n        q_tot = q_t\n    end\n    phase_partition = PhasePartition(q_tot)\n\n    ## temperature & density\n    T::FT = T_v / (1 + M_v * q_tot)\n    ρ::FT = air_density(param_set, T, p, phase_partition)\n\n    ## potential & kinetic energy\n    e_pot::FT = gravitational_potential(bl.orientation, aux)\n    e_kin::FT = 0.5 * u_cart' * u_cart\n    e_tot::FT = total_energy(param_set, e_kin, e_pot, T, phase_partition)\n\n    ## Assign state variables\n    state.ρ = ρ\n    state.ρu = ρ * u_cart\n    state.energy.ρe = ρ * e_tot\n\n    if moisture_model(bl) isa EquilMoist\n        state.moisture.ρq_tot = ρ * q_tot\n    end\n\n    nothing\nend\n\n\"\"\"\nDefines analytical function for prescribed T_sfc and q_sfc, following\nThatcher and Jablonowski (2016), used to calculate bulk surface fluxes.\n\nT_sfc_pole = SST at the poles (default: 271 K), specified above\n\"\"\"\nstruct Varying_SST_TJ16{PS, O, MM}\n    param_set::PS\n    orientation::O\n    moisture::MM\nend\nfunction (st::Varying_SST_TJ16)(state, aux, t)\n    FT = eltype(state)\n    φ = latitude(st.orientation, aux)\n\n    param_set = st.param_set\n    _T_sfc_pole = T_sfc_pole(param_set)::FT\n\n    Δφ = FT(26) * FT(π) / FT(180)       # latitudinal width of Gaussian function\n    ΔSST = FT(29)                       # Eq-pole SST difference in K\n    T_sfc = ΔSST * exp(-φ^2 / (2 * Δφ^2)) + _T_sfc_pole\n\n    eps = FT(0.622)\n    ρ = state.ρ\n\n    q_tot = state.moisture.ρq_tot / ρ\n    q = PhasePartition(q_tot)\n\n    e_int = internal_energy(st.orientation, state, aux)\n    T = air_temperature(param_set, e_int, q)\n    p = air_pressure(param_set, T, ρ, q)\n\n    _T_triple = T_triple(param_set)::FT          # triple point of water\n    _press_triple = press_triple(param_set)::FT  # sat water pressure at T_triple\n    _LH_v0 = LH_v0(param_set)::FT                # latent heat of vaporization at T_triple\n    _R_v = R_v(param_set)::FT                    # gas constant for water vapor\n\n    q_sfc =\n        eps / p *\n        _press_triple *\n        exp(-_LH_v0 / _R_v * (FT(1) / T_sfc - FT(1) / _T_triple))\n\n    return T_sfc, q_sfc\nend\n\nfunction config_baroclinic_wave(\n    FT,\n    param_set,\n    poly_order,\n    resolution,\n    with_moisture,\n)\n    # Set up a reference state for linearization of equations\n    temp_profile_ref =\n        DecayingTemperatureProfile{FT}(param_set, FT(290), FT(220), FT(8e3))\n    ref_state = HydrostaticState(temp_profile_ref)\n\n    # Set up the atmosphere model\n    exp_name = \"BaroclinicWave\"\n    domain_height::FT = 30e3 # distance between surface and top of atmosphere (m)\n    if with_moisture\n        hyperdiffusion = EquilMoistBiharmonic(FT(8 * 3600))\n        moisture = EquilMoist()\n        source = (Gravity(), Coriolis(), RemovePrecipitation(true)) # precipitation is default to NoPrecipitation() as 0M microphysics\n    else\n        hyperdiffusion = DryBiharmonic(FT(8 * 3600))\n        moisture = DryModel()\n        source = (Gravity(), Coriolis())\n    end\n\n    _C_drag = C_drag(param_set)::FT\n    bulk_flux = Varying_SST_TJ16(param_set, SphericalOrientation(), moisture)\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = ConstantKinematicViscosity(FT(0)),\n        hyperdiffusion = hyperdiffusion,\n        moisture = moisture,\n    )\n\n    problem = AtmosProblem(\n        boundaryconditions = (\n            AtmosBC(\n                physics;\n                energy = BulkFormulaEnergy(\n                    (bl, state, aux, t, normPu_int) -> _C_drag,\n                    (bl, state, aux, t) -> bulk_flux(state, aux, t),\n                ),\n                moisture = BulkFormulaMoisture(\n                    (state, aux, t, normPu_int) -> _C_drag,\n                    (state, aux, t) -> begin\n                        _, q_tot = bulk_flux(state, aux, t)\n                        q_tot\n                    end,\n                ),\n            ),\n            AtmosBC(physics;),\n        ),\n        init_state_prognostic = init_baroclinic_wave!,\n    )\n\n    model = AtmosModel{FT}(\n        AtmosGCMConfigType,\n        physics;\n        problem = problem,\n        source = source,\n    )\n\n    config = ClimateMachine.AtmosGCMConfiguration(\n        exp_name,\n        poly_order,\n        resolution,\n        domain_height,\n        param_set,\n        init_baroclinic_wave!;\n        model = model,\n    )\n\n    return config\nend\n\nfunction main()\n    # add a command line argument to specify whether to use a moist setup\n    # TODO: this will move to the future namelist functionality\n    bw_args = ArgParseSettings(autofix_names = true)\n    add_arg_group!(bw_args, \"BaroclinicWave\")\n    @add_arg_table! bw_args begin\n        \"--with-moisture\"\n        help = \"use a moist setup\"\n        metavar = \"yes|no\"\n        arg_type = String\n        default = \"yes\"\n    end\n\n    cl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = bw_args)\n    moisture_arg = lowercase(cl_args[\"with_moisture\"])\n    if moisture_arg == \"yes\"\n        with_moisture = true\n    elseif moisture_arg == \"no\"\n        with_moisture = false\n    else\n        error(\"invalid argument to --with-moisture: \" * moisture_arg)\n    end\n\n    # Driver configuration parameters\n    FT = Float64                             # floating type precision\n    poly_order = 5                           # discontinuous Galerkin polynomial order\n    n_horz = 8                              # horizontal element number\n    n_vert = 4                               # vertical element number\n    n_days::FT = 1\n    timestart::FT = 0                        # start time (s)\n    timeend::FT = n_days * day(param_set)    # end time (s)\n\n    # Set up driver configuration\n    driver_config = config_baroclinic_wave(\n        FT,\n        param_set,\n        poly_order,\n        (n_horz, n_vert),\n        with_moisture,\n    )\n\n    # Set up experiment\n    ode_solver_type = ClimateMachine.IMEXSolverType(\n        implicit_model = AtmosAcousticGravityLinearModel,\n        implicit_solver = ManyColumnLU,\n        solver_method = ARK2GiraldoKellyConstantinescu,\n        split_explicit_implicit = true,\n        discrete_splitting = false,\n    )\n\n    CFL = FT(0.1) # target acoustic CFL number\n\n    # time step is computed such that the horizontal acoustic Courant number is CFL\n    solver_config = ClimateMachine.SolverConfiguration(\n        timestart,\n        timeend,\n        driver_config,\n        Courant_number = CFL,\n        ode_solver_type = ode_solver_type,\n        CFL_direction = HorizontalDirection(),\n        diffdir = HorizontalDirection(),\n    )\n\n    # Set up diagnostics\n    dgn_config = config_diagnostics(FT, driver_config)\n\n    # Set up user-defined callbacks\n    filterorder = 20\n    filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)\n    cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            AtmosFilterPerturbations(driver_config.bl),\n            solver_config.dg.grid,\n            filter,\n            state_auxiliary = solver_config.dg.state_auxiliary,\n        )\n        nothing\n    end\n\n    cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            (\"moisture.ρq_tot\",),\n            solver_config.dg.grid,\n            TMARFilter(),\n        )\n        nothing\n    end\n\n    # Run the model\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (cbfilter,),\n        #user_callbacks = (cbtmarfilter, cbfilter),\n        check_euclidean_distance = true,\n    )\nend\n\nfunction config_diagnostics(FT, driver_config)\n    interval = \"40000steps\" # chosen to allow a single diagnostics collection\n\n    _planet_radius = FT(planet_radius(param_set))\n\n    info = driver_config.config_info\n    boundaries = [\n        FT(-90.0) FT(-180.0) _planet_radius\n        FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)\n    ]\n    resolution = (FT(1), FT(1), FT(1000)) # in (deg, deg, m)\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries,\n        resolution,\n    )\n\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosGCMConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp])\nend\n\nmain()\n"
  },
  {
    "path": "experiments/AtmosGCM/nonhydrostatic_gravity_wave.jl",
    "content": "#!/usr/bin/env julia --project\nusing ClimateMachine\nClimateMachine.init(parse_clargs = true)\n\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.SystemSolvers: ManyColumnLU\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Interpolation\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics: total_energy, air_density\nusing ClimateMachine.VariableTemplates\n\nusing Distributions: Uniform\nusing LinearAlgebra\nusing StaticArrays\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet:\n    R_d, day, grav, cp_d, planet_radius, Omega, kappa_d, MSLP\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nimport CLIMAParameters\nCLIMAParameters.Planet.Omega(::EarthParameterSet) = 0.0\nCLIMAParameters.Planet.planet_radius(::EarthParameterSet) = 6.371e6 / 125.0\nCLIMAParameters.Planet.MSLP(::EarthParameterSet) = 1e5\n\n\nfunction init_nonhydrostatic_gravity_wave!(problem, bl, state, aux, localgeo, t)\n    FT = eltype(state)\n\n    # grid\n    φ = latitude(bl, aux)\n    λ = longitude(bl, aux)\n    z = altitude(bl, aux)\n\n    # parameters\n    param_set = parameter_set(bl)\n    _grav::FT = grav(param_set)\n    _cp::FT = cp_d(param_set)\n    _Ω::FT = Omega(param_set)\n    _a::FT = planet_radius(param_set)\n    _R_d::FT = R_d(param_set)\n    _kappa::FT = kappa_d(param_set)\n    _p_eq::FT = MSLP(param_set)\n\n    N::FT = 0.01\n    u_0::FT = 0.0\n    G::FT = _grav^2 / N^2 / _cp\n    T_eq::FT = 300\n    Δθ::FT = 0.0\n    d::FT = 5e3\n    λ_c::FT = 2 * π / 3\n    φ_c::FT = 0\n    L_z::FT = 20e3\n\n    # initial velocity profile (we need to transform the vector into the Cartesian\n    # coordinate system)\n    u_sphere = SVector{3, FT}(u_0 * cos(φ), 0, 0)\n    u_init = sphr_to_cart_vec(bl.orientation, u_sphere, aux)\n\n    # background temperature\n    T_s::FT =\n        G +\n        (T_eq - G) *\n        exp(-u_0 * N^2 / 4 / _grav^2 * (u_0 + 2 * _Ω * _a) * (cos(2 * φ) - 1))\n    T_b::FT = G * (1 - exp(N^2 / _grav * z)) + T_s * exp(N^2 / _grav * z)\n\n    # pressure\n    p_s::FT =\n        _p_eq *\n        exp(u_0 / 4 / G / _R_d * (u_0 + 2 * _Ω * _a) * (cos(2 * φ) - 1)) *\n        (T_s / T_eq)^(1 / _kappa)\n    p::FT = p_s * (G / T_s * exp(-N^2 / _grav * z) + 1 - G / T_s)^(1 / _kappa)\n\n    # background potential temperature\n    θ_b::FT = T_b * (_p_eq / p)^_kappa\n\n    # potential temperature perturbation\n    r::FT = _a * acos(sin(φ_c) * sin(φ) + cos(φ_c) * cos(φ) * cos(λ - λ_c))\n    s::FT = d^2 / (d^2 + r^2)\n    θ′::FT = Δθ * s * sin(2 * π * z / L_z)\n\n    # temperature perturbation\n    T′::FT = θ′ * (p / _p_eq)^_kappa\n\n    # temperature\n    T::FT = T_b + T′\n\n    # density\n    ρ = air_density(param_set, T_b, p)\n\n    # potential & kinetic energy\n    e_pot = gravitational_potential(bl.orientation, aux)\n    e_kin::FT = 0.5 * sum(abs2.(u_init))\n\n    state.ρ = ρ\n    state.ρu = ρ * u_init\n    state.energy.ρe = ρ * total_energy(param_set, e_kin, e_pot, T)\n\n    nothing\nend\n\nfunction config_nonhydrostatic_gravity_wave(FT, poly_order, resolution)\n    # Set up a reference state for linearization of equations\n    temp_profile_ref =\n        DecayingTemperatureProfile{FT}(param_set, FT(300), FT(100), FT(27.5e3))\n    ref_state = HydrostaticState(temp_profile_ref)\n\n    domain_height::FT = 10e3               # distance between surface and top of atmosphere (m)\n\n    # Set up the atmosphere model\n    exp_name = \"NonhydrostaticGravityWave\"\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = ConstantKinematicViscosity(FT(0)),\n        moisture = DryModel(),\n    )\n\n    model = AtmosModel{FT}(\n        AtmosGCMConfigType,\n        physics;\n        init_state_prognostic = init_nonhydrostatic_gravity_wave!,\n        source = (Gravity(),),\n    )\n\n    config = ClimateMachine.AtmosGCMConfiguration(\n        exp_name,\n        poly_order,\n        resolution,\n        domain_height,\n        param_set,\n        init_nonhydrostatic_gravity_wave!;\n        model = model,\n    )\n\n    return config\nend\n\nfunction config_diagnostics(FT, driver_config)\n    interval = \"40000steps\" # chosen to allow a single diagnostics collection\n\n    _planet_radius = FT(planet_radius(param_set))\n\n    info = driver_config.config_info\n    boundaries = [\n        FT(-90.0) FT(-180.0) _planet_radius\n        FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)\n    ]\n    resolution = (FT(10), FT(10), FT(100)) # in (deg, deg, m)\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries,\n        resolution,\n    )\n\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosGCMConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp])\nend\n\nfunction main()\n    # Driver configuration parameters\n    FT = Float64                             # floating type precision\n    poly_order = 5                           # discontinuous Galerkin polynomial order\n    n_horz = 8                               # horizontal element number\n    n_vert = 4                               # vertical element number\n    timestart = FT(0)                        # start time (s)\n    timeend = FT(3600)                       # end time (s)\n\n    # Set up driver configuration\n    driver_config =\n        config_nonhydrostatic_gravity_wave(FT, poly_order, (n_horz, n_vert))\n\n    # Set up experiment\n    CFL = FT(0.4)\n    solver_config = ClimateMachine.SolverConfiguration(\n        timestart,\n        timeend,\n        driver_config,\n        Courant_number = CFL,\n        CFL_direction = HorizontalDirection(),\n    )\n\n    # Set up diagnostics\n    dgn_config = config_diagnostics(FT, driver_config)\n\n    # Set up user-defined callbacks\n    filterorder = 64\n    filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)\n    cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            AtmosFilterPerturbations(driver_config.bl),\n            solver_config.dg.grid,\n            filter,\n            state_auxiliary = solver_config.dg.state_auxiliary,\n        )\n        nothing\n    end\n\n    # Run the model\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (cbfilter,),\n        check_euclidean_distance = true,\n    )\nend\n\nmain()\n"
  },
  {
    "path": "experiments/AtmosLES/Artifacts.toml",
    "content": "[lsforcing]\ngit-tree-sha1 = \"6ac99861047fc1b2c16caa81506bde25e197dc0a\"\n\n[soundings]\ngit-tree-sha1 = \"54fd0eef5f47c4f32662a9f3fa56accc00a822fc\"\n"
  },
  {
    "path": "experiments/AtmosLES/bomex_les.jl",
    "content": "using Random\ninclude(\"bomex_model.jl\")\n\nfunction add_perturbations!(state, localgeo)\n    FT = eltype(state)\n    z = localgeo.coord[3]\n    if z <= FT(400) # Add random perturbations to bottom 400m of model\n        state.energy.ρe += (rand() - 0.5) * state.energy.ρe / 100\n        state.moisture.ρq_tot += (rand() - 0.5) * state.moisture.ρq_tot / 100\n    end\nend\n\nfunction main()\n    # add a command line argument to specify the kind of surface flux\n    # TODO: this will move to the future namelist functionality\n    bomex_args = ArgParseSettings(autofix_names = true)\n    add_arg_group!(bomex_args, \"BOMEX\")\n    @add_arg_table! bomex_args begin\n        \"--surface-flux\"\n        help = \"specify surface flux for energy and moisture\"\n        metavar = \"prescribed|bulk\"\n        arg_type = String\n        default = \"prescribed\"\n        \"--moisture-model\"\n        help = \"specify cloud condensate model\"\n        metavar = \"equilibrium|nonequilibrium\"\n        arg_type = String\n        default = \"equilibrium\"\n    end\n\n    cl_args =\n        ClimateMachine.init(parse_clargs = true, custom_clargs = bomex_args)\n\n    surface_flux = cl_args[\"surface_flux\"]\n    moisture_model = cl_args[\"moisture_model\"]\n\n    FT = Float64\n    config_type = AtmosLESConfigType\n\n    # DG polynomial order\n    N = 4\n    # Domain resolution and size\n    Δh = FT(100)\n    Δv = FT(40)\n\n    resolution = (Δh, Δh, Δv)\n\n    # Prescribe domain parameters\n    xmax = FT(6400)\n    ymax = FT(6400)\n    zmax = FT(3000)\n\n    t0 = FT(0)\n\n    # For a full-run, please set the timeend to 3600*6 seconds\n    # and change the values in ConservationCheck\n    # For the test we set this to == 20 minutes\n    timeend = FT(1200)\n    #timeend = FT(3600 * 6)\n    CFLmax = FT(0.35)\n\n    # Choose default IMEX solver\n    ode_solver_type = ClimateMachine.IMEXSolverType()\n\n    model = bomex_model(\n        FT,\n        config_type,\n        zmax,\n        surface_flux,\n        moisture_model = moisture_model,\n    )\n    ics = model.problem.init_state_prognostic\n    # Assemble configuration\n    driver_config = ClimateMachine.AtmosLESConfiguration(\n        \"BOMEX\",\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        ics;\n        model = model,\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        init_on_cpu = true,\n        Courant_number = CFLmax,\n        CFL_direction = HorizontalDirection(),\n    )\n    dgn_config =\n        config_diagnostics(driver_config, timeend, xmax, ymax, zmax, resolution)\n\n    if moisture_model == \"equilibrium\"\n        filter_vars = (\"moisture.ρq_tot\",)\n    elseif moisture_model == \"nonequilibrium\"\n        filter_vars = (\"moisture.ρq_tot\", \"moisture.ρq_liq\", \"moisture.ρq_ice\")\n    end\n\n    cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            filter_vars,\n            solver_config.dg.grid,\n            TMARFilter(),\n        )\n        nothing\n    end\n\n    check_cons = (\n        ClimateMachine.ConservationCheck(\"ρ\", \"3000steps\", FT(0.0001)),\n        ClimateMachine.ConservationCheck(\"energy.ρe\", \"3000steps\", FT(0.0025)),\n    )\n\n    result = ClimateMachine.invoke!(\n        solver_config;\n        user_callbacks = (cbtmarfilter,),\n        diagnostics_config = dgn_config,\n        check_cons = check_cons,\n        check_euclidean_distance = true,\n    )\nend\n\nfunction config_diagnostics(\n    driver_config,\n    timeend,\n    xmax::FT,\n    ymax::FT,\n    zmax::FT,\n    resolution,\n) where {FT}\n    default_interval = \"$(cld(timeend, 2) + 10)ssecs\"\n    default_dgngrp = setup_atmos_default_diagnostics(\n        AtmosLESConfigType(),\n        default_interval,\n        driver_config.name,\n    )\n    core_interval = \"$(cld(timeend, 4) + 10)ssecs\"\n    core_dgngrp = setup_atmos_core_diagnostics(\n        AtmosLESConfigType(),\n        core_interval,\n        driver_config.name,\n    )\n    boundaries = [\n        FT(0) FT(0) FT(0)\n        xmax ymax zmax\n    ]\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries,\n        resolution,\n    )\n    dt_dgngrp = setup_dump_tendencies_diagnostics(\n        AtmosLESConfigType(),\n        default_interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([\n        default_dgngrp,\n        core_dgngrp,\n        dt_dgngrp,\n    ])\nend\n\nmain()\n"
  },
  {
    "path": "experiments/AtmosLES/bomex_model.jl",
    "content": "#!/usr/bin/env julia --project\n#=\n# This experiment file establishes the initial conditions, boundary conditions,\n# source terms and simulation parameters (domain size + resolution) for the\n# BOMEX LES case. The set of parameters presented in the `master` branch copy\n# include those that have passed offline tests at the full simulation time of\n# 6 hours. Suggested offline tests included plotting horizontal-domain averages\n# of key properties (see AtmosDiagnostics). The timestepper configuration is in\n# `src/Driver/solver_configs.jl` while the `AtmosModel` defaults can be found in\n# `src/Atmos/Model/AtmosModel.jl` and `src/Driver/driver_configs.jl`\n#\n# To simulate the full 6 hour experiment, change `timeend` to (3600*6) and type in\n#\n# julia --project experiments/AtmosLES/bomex.jl\n#\n# See `src/Driver/driver_configs.jl` for additional flags (e.g. VTK, diagnostics,\n# update-interval, output directory settings)\n#\n# Upcoming changes:\n# 1) Atomic sources\n# 2) Improved boundary conditions\n# 3) Collapsed experiment design\n# 4) Updates to generally keep this in sync with master\n\n[Siebesma2003](@cite)\n=#\n\nusing ArgParse\nusing Distributions\nusing DocStringExtensions\nusing LinearAlgebra\nusing Printf\nusing StaticArrays\nusing Test\n\nusing ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.ODESolvers\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.TurbulenceConvection\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.BalanceLaws\nimport ClimateMachine.BalanceLaws: prognostic_vars\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: e_int_v0, grav, day\nusing CLIMAParameters.Atmos.Microphysics\n\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nimport ClimateMachine.BalanceLaws: source, prognostic_vars\nusing ClimateMachine.Atmos: altitude, recover_thermo_state\nusing UnPack\n\n\"\"\"\n  Bomex Geostrophic Forcing (Source)\n\"\"\"\nstruct BomexGeostrophic{FT} <: TendencyDef{Source}\n    \"Coriolis parameter [s⁻¹]\"\n    f_coriolis::FT\n    \"Eastward geostrophic velocity `[m/s]` (Base)\"\n    u_geostrophic::FT\n    \"Eastward geostrophic velocity `[m/s]` (Slope)\"\n    u_slope::FT\n    \"Northward geostrophic velocity `[m/s]`\"\n    v_geostrophic::FT\nend\n\nprognostic_vars(::BomexGeostrophic) = (Momentum(),)\n\nfunction source(::Momentum, s::BomexGeostrophic, m, args)\n    @unpack state, aux = args\n    @unpack f_coriolis, u_geostrophic, u_slope, v_geostrophic = s\n\n    z = altitude(m, aux)\n    # Note z dependence of eastward geostrophic velocity\n    u_geo = SVector(u_geostrophic + u_slope * z, v_geostrophic, 0)\n    ẑ = vertical_unit_vector(m, aux)\n    fkvector = f_coriolis * ẑ\n    # Accumulate sources\n    return -fkvector × (state.ρu .- state.ρ * u_geo)\nend\n\n\"\"\"\n  Bomex Sponge (Source)\n\"\"\"\nstruct BomexSponge{FT} <: TendencyDef{Source}\n    \"Maximum domain altitude (m)\"\n    z_max::FT\n    \"Altitude at with sponge starts (m)\"\n    z_sponge::FT\n    \"Sponge Strength 0 ⩽ α_max ⩽ 1\"\n    α_max::FT\n    \"Sponge exponent\"\n    γ::FT\n    \"Eastward geostrophic velocity `[m/s]` (Base)\"\n    u_geostrophic::FT\n    \"Eastward geostrophic velocity `[m/s]` (Slope)\"\n    u_slope::FT\n    \"Northward geostrophic velocity `[m/s]`\"\n    v_geostrophic::FT\nend\n\nprognostic_vars(::BomexSponge) = (Momentum(),)\n\nfunction source(::Momentum, s::BomexSponge, m, args)\n    @unpack state, aux = args\n    @unpack z_max, z_sponge, α_max, γ = s\n    @unpack u_geostrophic, u_slope, v_geostrophic = s\n\n    z = altitude(m, aux)\n    u_geo = SVector(u_geostrophic + u_slope * z, v_geostrophic, 0)\n    ẑ = vertical_unit_vector(m, aux)\n    # Accumulate sources\n    if z_sponge <= z\n        r = (z - z_sponge) / (z_max - z_sponge)\n        β_sponge = α_max * sinpi(r / 2)^s.γ\n        return -β_sponge * (state.ρu .- state.ρ * u_geo)\n    else\n        FT = eltype(state)\n        return SVector{3, FT}(0, 0, 0)\n    end\nend\n\n\"\"\"\n    BomexTendencies (Source)\n\nMoisture, Temperature and Subsidence tendencies\n\"\"\"\nstruct BomexTendencies{FT} <: TendencyDef{Source}\n    \"Advection tendency in total moisture `[s⁻¹]`\"\n    ∂qt∂t_peak::FT\n    \"Lower extent of piecewise profile (moisture term) `[m]`\"\n    zl_moisture::FT\n    \"Upper extent of piecewise profile (moisture term) `[m]`\"\n    zh_moisture::FT\n    \"Cooling rate `[K/s]`\"\n    ∂θ∂t_peak::FT\n    \"Lower extent of piecewise profile (subsidence term) `[m]`\"\n    zl_sub::FT\n    \"Upper extent of piecewise profile (subsidence term) `[m]`\"\n    zh_sub::FT\n    \"Subsidence peak velocity\"\n    w_sub::FT\n    \"Max height in domain\"\n    z_max::FT\nend\n\nprognostic_vars(::BomexTendencies) = (Mass(), Energy(), TotalMoisture())\n\nfunction compute_bomex_tend_params(s, m, args)\n    @unpack state, aux = args\n    FT = eltype(state)\n    ρ = state.ρ\n    z = altitude(m, aux)\n\n    # Moisture tendencey (sink term)\n    # Temperature tendency (Radiative cooling)\n    # Large scale subsidence\n    # Unpack struct\n    @unpack zl_moisture, zh_moisture, z_max, zl_sub, zh_sub = s\n    @unpack w_sub, ∂qt∂t_peak, ∂θ∂t_peak = s\n    zl_temperature = zl_sub\n    k̂ = vertical_unit_vector(m, aux)\n\n    # Piecewise term for moisture tendency\n    linscale_moisture = (z - zl_moisture) / (zh_moisture - zl_moisture)\n    if z <= zl_moisture\n        ρ∂qt∂t = ρ * ∂qt∂t_peak\n    elseif zl_moisture < z <= zh_moisture\n        ρ∂qt∂t = ρ * (∂qt∂t_peak - ∂qt∂t_peak * linscale_moisture)\n    else\n        ρ∂qt∂t = -zero(FT)\n    end\n\n    # Piecewise term for internal energy tendency\n    linscale_temp = (z - zl_sub) / (z_max - zl_sub)\n    if z <= zl_sub\n        ρ∂θ∂t = ρ * ∂θ∂t_peak\n    elseif zl_temperature < z <= z_max\n        ρ∂θ∂t = ρ * (∂θ∂t_peak - ∂θ∂t_peak * linscale_temp)\n    else\n        ρ∂θ∂t = -zero(FT)\n    end\n\n    # Piecewise terms for subsidence\n    linscale_sub = (z - zl_sub) / (zh_sub - zl_sub)\n    w_s = -zero(FT)\n    if z <= zl_sub\n        w_s = -zero(FT) + z * (w_sub) / (zl_sub)\n    elseif zl_sub < z <= zh_sub\n        w_s = w_sub - (w_sub) * linscale_sub\n    else\n        w_s = -zero(FT)\n    end\n    return (w_s = w_s, ρ∂qt∂t = ρ∂qt∂t, ρ∂θ∂t = ρ∂θ∂t, k̂ = k̂)\nend\n\nfunction source(::Mass, s::BomexTendencies, m, args)\n    @unpack state, diffusive = args\n    params = compute_bomex_tend_params(s, m, args)\n    @unpack ρ∂qt∂t, w_s, k̂ = params\n    ρ = state.ρ\n    return ρ∂qt∂t - state.ρ * w_s * dot(k̂, diffusive.moisture.∇q_tot)\nend\nfunction source(::Energy, s::BomexTendencies, m, args)\n    @unpack state, diffusive = args\n    @unpack ts = args.precomputed\n    params = compute_bomex_tend_params(s, m, args)\n    FT = eltype(state)\n    @unpack ρ∂qt∂t, ρ∂θ∂t, w_s, k̂ = params\n    cvm = cv_m(ts)\n    param_set = parameter_set(m)\n    _e_int_v0 = FT(e_int_v0(param_set))\n    term1 = cvm * ρ∂θ∂t * exner(ts) + _e_int_v0 * ρ∂qt∂t\n    term2 = state.ρ * w_s * dot(k̂, diffusive.energy.∇h_tot)\n    return term1 - term2\nend\nfunction source(::TotalMoisture, s::BomexTendencies, m, args)\n    @unpack state, diffusive = args\n    params = compute_bomex_tend_params(s, m, args)\n    @unpack ρ∂qt∂t, w_s, k̂ = params\n    return ρ∂qt∂t - state.ρ * w_s * dot(k̂, diffusive.moisture.∇q_tot)\nend\n\nadd_perturbations!(state, localgeo) = nothing\n\n\"\"\"\n  Initial Condition for BOMEX LES\n\"\"\"\nfunction init_bomex!(problem, bl, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n    # This experiment runs the BOMEX LES Configuration\n    # (Shallow cumulus cloud regime)\n    # x,y,z imply eastward, northward and altitude coordinates in `[m]`\n\n    # Problem floating point precision\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n\n    P_sfc::FT = 1.015e5 # Surface air pressure\n    qg::FT = 22.45e-3 # Total moisture at surface\n    q_pt_sfc = PhasePartition(qg) # Surface moisture partitioning\n    Rm_sfc = gas_constant_air(param_set, q_pt_sfc) # Moist gas constant\n    θ_liq_sfc = FT(299.1) # Prescribed θ_liq at surface\n    T_sfc = FT(300.4) # Surface temperature\n    _grav = FT(grav(param_set))\n\n    # Initialise speeds [u = Eastward, v = Northward, w = Vertical]\n    u::FT = 0\n    v::FT = 0\n    w::FT = 0\n\n    # Prescribed altitudes for piece-wise profile construction\n    zl1::FT = 520\n    zl2::FT = 1480\n    zl3::FT = 2000\n    zl4::FT = 3000\n\n    # Assign piecewise quantities to θ_liq and q_tot\n    θ_liq::FT = 0\n    q_tot::FT = 0\n\n    # Piecewise functions for potential temperature and total moisture\n    if FT(0) <= z <= zl1\n        # Well mixed layer\n        θ_liq = 298.7\n        q_tot = 17.0 + (z / zl1) * (16.3 - 17.0)\n    elseif z > zl1 && z <= zl2\n        # Conditionally unstable layer\n        θ_liq = 298.7 + (z - zl1) * (302.4 - 298.7) / (zl2 - zl1)\n        q_tot = 16.3 + (z - zl1) * (10.7 - 16.3) / (zl2 - zl1)\n    elseif z > zl2 && z <= zl3\n        # Absolutely stable inversion\n        θ_liq = 302.4 + (z - zl2) * (308.2 - 302.4) / (zl3 - zl2)\n        q_tot = 10.7 + (z - zl2) * (4.2 - 10.7) / (zl3 - zl2)\n    else\n        θ_liq = 308.2 + (z - zl3) * (311.85 - 308.2) / (zl4 - zl3)\n        q_tot = 4.2 + (z - zl3) * (3.0 - 4.2) / (zl4 - zl3)\n    end\n\n    # Set velocity profiles - piecewise profile for u\n    zlv::FT = 700\n    if z <= zlv\n        u = -8.75\n    else\n        u = -8.75 + (z - zlv) * (-4.61 + 8.75) / (zl4 - zlv)\n    end\n\n    # Convert total specific humidity to kg/kg\n    q_tot /= 1000\n    # Scale height based on surface parameters\n    H = Rm_sfc * T_sfc / _grav\n    # Pressure based on scale height\n    P = P_sfc * exp(-z / H)\n\n    # Establish thermodynamic state and moist phase partitioning\n    ts = PhaseEquil_pθq(param_set, P, θ_liq, q_tot)\n    T = air_temperature(ts)\n    ρ = air_density(ts)\n\n    # Compute momentum contributions\n    ρu = ρ * u\n    ρv = ρ * v\n    ρw = ρ * w\n\n    # Compute energy contributions\n    e_kin = FT(1 // 2) * (u^2 + v^2 + w^2)\n    e_pot = _grav * z\n    ρe_tot = ρ * total_energy(e_kin, e_pot, ts)\n\n    # Assign initial conditions for prognostic state variables\n    state.ρ = ρ\n    state.ρu = SVector(ρu, ρv, ρw)\n    state.energy.ρe = ρe_tot\n    state.moisture.ρq_tot = ρ * q_tot\n    if moisture_model(bl) isa NonEquilMoist\n        state.moisture.ρq_liq = FT(0)\n        state.moisture.ρq_ice = FT(0)\n    end\n\n    add_perturbations!(state, localgeo)\n    init_state_prognostic!(turbconv_model(bl), bl, state, aux, localgeo, t)\nend\n\nfunction bomex_model(\n    ::Type{FT},\n    config_type,\n    zmax,\n    surface_flux;\n    turbconv = NoTurbConv(),\n    moisture_model = \"equilibrium\",\n) where {FT}\n\n    ics = init_bomex!     # Initial conditions\n\n    C_smag = FT(0.23)     # Smagorinsky coefficient\n\n    u_star = FT(0.28)     # Friction velocity\n    C_drag = FT(0.0011)   # Bulk transfer coefficient\n\n    T_sfc = FT(300.4)     # Surface temperature `[K]`\n    q_sfc = FT(22.45e-3)  # Surface specific humiity `[kg/kg]`\n    LHF = FT(147.2)       # Latent heat flux `[W/m²]`\n    SHF = FT(9.5)         # Sensible heat flux `[W/m²]`\n    moisture_flux = LHF / latent_heat_vapor(param_set, T_sfc)\n\n    ∂qt∂t_peak = FT(-1.2e-8)  # Moisture tendency (energy source)\n    zl_moisture = FT(300)     # Low altitude limit for piecewise function (moisture source)\n    zh_moisture = FT(500)     # High altitude limit for piecewise function (moisture source)\n    ∂θ∂t_peak = FT(-2 / FT(day(param_set)))  # Potential temperature tendency (energy source)\n\n    z_sponge = FT(2400)     # Start of sponge layer\n    α_max = FT(0.75)        # Strength of sponge layer (timescale)\n    γ = 2              # Strength of sponge layer (exponent)\n\n    u_geostrophic = FT(-10)        # Eastward relaxation speed\n    u_slope = FT(1.8e-3)     # Slope of altitude-dependent relaxation speed\n    v_geostrophic = FT(0)          # Northward relaxation speed\n\n    zl_sub = FT(1500)         # Low altitude for piecewise function (subsidence source)\n    zh_sub = FT(2100)         # High altitude for piecewise function (subsidence source)\n    w_sub = FT(-0.65e-2)     # Subsidence velocity peak value\n\n    f_coriolis = FT(0.376e-4) # Coriolis parameter\n\n    # Assemble source components\n    source_default = (\n        Gravity(),\n        BomexTendencies{FT}(\n            ∂qt∂t_peak,\n            zl_moisture,\n            zh_moisture,\n            ∂θ∂t_peak,\n            zl_sub,\n            zh_sub,\n            w_sub,\n            zmax,\n        ),\n        BomexSponge{FT}(\n            zmax,\n            z_sponge,\n            α_max,\n            γ,\n            u_geostrophic,\n            u_slope,\n            v_geostrophic,\n        ),\n        BomexGeostrophic{FT}(f_coriolis, u_geostrophic, u_slope, v_geostrophic),\n        turbconv_sources(turbconv)...,\n    )\n    if moisture_model == \"equilibrium\"\n        source = source_default\n        moisture = EquilMoist(; maxiter = 5, tolerance = FT(0.1))\n    elseif moisture_model == \"nonequilibrium\"\n        source = (source_default..., CreateClouds())\n        moisture = NonEquilMoist()\n    else\n        @warn @sprintf(\n            \"\"\"\n%s: unrecognized moisture_model, using the defaults\"\"\",\n            moisture_model,\n        )\n        source = source_default\n        moisture = EquilMoist(; maxiter = 5, tolerance = FT(0.1))\n    end\n\n    # Set up problem initial and boundary conditions\n    if surface_flux == \"prescribed\"\n        energy_bc = PrescribedEnergyFlux((state, aux, t) -> LHF + SHF)\n        moisture_bc = PrescribedMoistureFlux((state, aux, t) -> moisture_flux)\n    elseif surface_flux == \"bulk\"\n        energy_bc = BulkFormulaEnergy(\n            (bl, state, aux, t, normPu_int) -> C_drag,\n            (bl, state, aux, t) -> (T_sfc, q_sfc),\n        )\n        moisture_bc = BulkFormulaMoisture(\n            (state, aux, t, normPu_int) -> C_drag,\n            (state, aux, t) -> q_sfc,\n        )\n    else\n        @warn @sprintf(\n            \"\"\"\n%s: unrecognized surface flux; using 'prescribed'\"\"\",\n            surface_flux,\n        )\n    end\n\n    # Assemble model components\n    physics = AtmosPhysics{FT}(\n        param_set;\n        turbulence = SmagorinskyLilly{FT}(C_smag),\n        moisture = moisture,\n        turbconv = turbconv,\n    )\n\n    problem = AtmosProblem(\n        boundaryconditions = (\n            AtmosBC(\n                physics;\n                momentum = Impenetrable(DragLaw(\n                    # normPu_int is the internal horizontal speed\n                    # P represents the projection onto the horizontal\n                    (state, aux, t, normPu_int) -> (u_star / normPu_int)^2,\n                )),\n                energy = energy_bc,\n                moisture = moisture_bc,\n                turbconv = turbconv_bcs(turbconv)[1],\n            ),\n            AtmosBC(physics; turbconv = turbconv_bcs(turbconv)[2]),\n        ),\n        init_state_prognostic = ics,\n    )\n\n    # Assemble model components\n    model =\n        AtmosModel{FT}(config_type, physics; problem = problem, source = source)\n\n    return model\nend\n"
  },
  {
    "path": "experiments/AtmosLES/bomex_single_stack.jl",
    "content": "include(\"bomex_model.jl\")\n\nfunction main()\n    # add a command line argument to specify the kind of surface flux\n    # TODO: this will move to the future namelist functionality\n    bomex_args = ArgParseSettings(autofix_names = true)\n    add_arg_group!(bomex_args, \"BOMEX\")\n    @add_arg_table! bomex_args begin\n        \"--moisture-model\"\n        help = \"specify cloud condensate model\"\n        metavar = \"equilibrium|nonequilibrium\"\n        arg_type = String\n        default = \"equilibrium\"\n        \"--surface-flux\"\n        help = \"specify surface flux for energy and moisture\"\n        metavar = \"prescribed|bulk\"\n        arg_type = String\n        default = \"prescribed\"\n    end\n\n    cl_args =\n        ClimateMachine.init(parse_clargs = true, custom_clargs = bomex_args)\n\n    surface_flux = cl_args[\"surface_flux\"]\n    moisture_model = cl_args[\"moisture_model\"]\n\n    FT = Float64\n    config_type = SingleStackConfigType\n\n    # DG polynomial order\n    N = 1\n\n    # Prescribe domain parameters\n    nelem_vert = 50\n    zmax = FT(3000)\n\n    t0 = FT(0)\n\n    # For a full-run, please set the timeend to 3600*6 seconds\n    # For the test we set this to == 30 minutes\n    timeend = FT(1800)\n    #timeend = FT(3600 * 6)\n    CFLmax = FT(0.90)\n\n    # Choose default IMEX solver\n    ode_solver_type = ClimateMachine.IMEXSolverType()\n\n    model = bomex_model(\n        FT,\n        config_type,\n        zmax,\n        surface_flux;\n        moisture_model = moisture_model,\n    )\n    ics = model.problem.init_state_prognostic\n\n    # Assemble configuration\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"BOMEX_SINGLE_STACK\",\n        N,\n        nelem_vert,\n        zmax,\n        param_set,\n        model,\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFLmax,\n    )\n    dgn_config = config_diagnostics(driver_config, timeend)\n\n    cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            (\"moisture.ρq_tot\",),\n            solver_config.dg.grid,\n            TMARFilter(),\n        )\n        nothing\n    end\n\n    check_cons = (\n        ClimateMachine.ConservationCheck(\"ρ\", \"3000steps\", FT(0.0001)),\n        ClimateMachine.ConservationCheck(\"energy.ρe\", \"3000steps\", FT(0.0025)),\n    )\n\n    result = ClimateMachine.invoke!(\n        solver_config;\n        user_callbacks = (cbtmarfilter,),\n        diagnostics_config = dgn_config,\n        check_cons = check_cons,\n        check_euclidean_distance = true,\n    )\nend\n\nfunction config_diagnostics(driver_config, timeend)\n    FT = eltype(driver_config.grid)\n    info = driver_config.config_info\n    interval = \"$(cld(timeend, 2) + 10)ssecs\"\n\n    boundaries = [\n        FT(0) FT(0) FT(0)\n        FT(info.hmax) FT(info.hmax) FT(info.zmax)\n    ]\n    axes = (\n        [FT(1)],\n        [FT(1)],\n        collect(range(boundaries[1, 3], boundaries[2, 3], step = FT(50)),),\n    )\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries;\n        axes = axes,\n    )\n    dgngrp = setup_dump_state_diagnostics(\n        SingleStackConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp])\nend\n\nmain()\n"
  },
  {
    "path": "experiments/AtmosLES/cfsite_hadgem2-a_07_amip.jl",
    "content": "# General Julia modules\nusing ArgParse\nusing Dierckx\nusing Distributions\nusing DocStringExtensions\nusing LinearAlgebra\nusing NCDatasets\nusing Pkg.Artifacts\nusing Printf\nusing Random\nusing StaticArrays\nusing Test\nusing UnPack\n\n# ClimateMachine Modules\nusing ClimateMachine\n\nusing ArtifactWrappers\nusing ClimateMachine.Atmos\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Orientations\nusing Thermodynamics\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Writers\n\n# Planet parameters\nusing CLIMAParameters\nusing CLIMAParameters.Planet: e_int_v0, grav, day\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n# Physics specific imports\nusing ClimateMachine.Atmos: altitude, recover_thermo_state\nimport ClimateMachine.BalanceLaws: source, eq_tends, prognostic_vars\n\n# Citation for problem setup\n## CMIP6 Test Dataset - cfsites\n## [Webb2017](@cite)\n\n\"\"\"\n    GCMRelaxation{FT} <: TendencyDef{Source}\n\n\"\"\"\nstruct GCMRelaxation{FT} <: TendencyDef{Source}\n    τ_relax::FT\nend\nprognostic_vars(::GCMRelaxation) = (Mass(), TotalMoisture())\n\nfunction source(::Mass, s::GCMRelaxation, m, args)\n    # TODO: write correct tendency\n    return 0\nend\n\nfunction source(::TotalMoisture, s::GCMRelaxation, m, args)\n    # TODO: write correct tendency\n    return 0\nend\n\n\"\"\"\n    LargeScaleProcess <: TendencyDef{Source}\n\n# Energy tendency ∂_t ρe\n\nTemperature tendency for the LES configuration based on quantities\nfrom a GCM. Quantities are included in standard CMIP naming format.\nTendencies included here are\n\n    tntha = temperature tendency due to horizontal advection\n    tntva = temperature tendency due to vertical advection\n    tntr = temperature tendency due to radiation fluxes\n    ∂T∂z = temperature vertical gradient from GCM values\n\n# Moisture tendency ∂_t ρq_tot\n\nMoisture tendency for the LES configuration based on quantities\nfrom a GCM. Quantities are included in standard CMIP naming format.\nTendencies included here are\n\n    tnhusha = moisture tendency due to horizontal advection\n    tnhusva = moisture tendency due to vertical advection\n    ∂qt∂z = moisture vertical gradient from GCM values\n\"\"\"\nstruct LargeScaleProcess <: TendencyDef{Source} end\n\nprognostic_vars(::LargeScaleProcess) = (Mass(), Energy(), TotalMoisture())\n\nfunction source(::Energy, s::LargeScaleProcess, m, args)\n    @unpack state, aux, diffusive = args\n    @unpack ts = args.precomputed\n    # Establish problem float-type\n    FT = eltype(state)\n    # Establish vertical orientation\n    k̂ = vertical_unit_vector(m, aux)\n    param_set = parameter_set(m)\n    _e_int_v0 = e_int_v0(param_set)\n    # Unpack vertical gradients\n    ∂qt∂z = diffusive.lsforcing.∇ᵥhus\n    ∂T∂z = diffusive.lsforcing.∇ᵥta\n    w_s = aux.lsforcing.w_s\n    cvm = cv_m(ts)\n    # Compute tendency terms\n    # Temperature contribution\n    T_tendency = aux.lsforcing.Σtemp_tendency + ∂T∂z * w_s\n    # Moisture contribution\n    q_tot_tendency = compute_q_tot_tend(m, args)\n\n    return cvm * state.ρ * T_tendency + _e_int_v0 * state.ρ * q_tot_tendency\nend\n\nfunction compute_q_tot_tend(m, args)\n    @unpack aux, diffusive = args\n    # Establish problem float-type\n    FT = eltype(aux)\n    k̂ = vertical_unit_vector(m, aux)\n    # Establish vertical orientation\n    ∂qt∂z = diffusive.lsforcing.∇ᵥhus\n    w_s = aux.lsforcing.w_s\n    # Compute tendency terms\n    return aux.lsforcing.Σqt_tendency + ∂qt∂z * w_s\nend\n\nfunction source(::Mass, s::LargeScaleProcess, m, args)\n    @unpack state = args\n    q_tot_tendency = compute_q_tot_tend(m, args)\n    return state.ρ * q_tot_tendency\nend\n\nfunction source(::TotalMoisture, s::LargeScaleProcess, m, args)\n    @unpack state, aux = args\n    q_tot_tendency = compute_q_tot_tend(m, args)\n    return state.ρ * q_tot_tendency\nend\n\n\"\"\"\n    LargeScaleSubsidence <: TendencyDef{Source}\n\nLarge-scale subsidence tendency, given a vertical velocity\nat the large scale, obtained from the GCM data.\n\n```\nwap = GCM vertical velocity [Pa s⁻¹]. Note the conversion required\n```\n\"\"\"\nstruct LargeScaleSubsidence <: TendencyDef{Source} end\n\nprognostic_vars(::LargeScaleSubsidence) = (Mass(), Energy(), TotalMoisture())\n\nfunction source(::Mass, s::LargeScaleSubsidence, m, args)\n    @unpack state, aux, diffusive = args\n    # Establish vertical orientation\n    k̂ = vertical_unit_vector(m, aux)\n    # Establish subsidence velocity\n    w_s = aux.lsforcing.w_s\n    return -state.ρ * w_s * dot(k̂, diffusive.moisture.∇q_tot)\nend\nfunction source(::Energy, s::LargeScaleSubsidence, m, args)\n    @unpack state, aux, diffusive = args\n    # Establish vertical orientation\n    k̂ = vertical_unit_vector(m, aux)\n    # Establish subsidence velocity\n    w_s = aux.lsforcing.w_s\n    return -state.ρ * w_s * dot(k̂, diffusive.energy.∇h_tot)\nend\nfunction source(::TotalMoisture, s::LargeScaleSubsidence, m, args)\n    @unpack state, aux, diffusive = args\n    # Establish vertical orientation\n    k̂ = vertical_unit_vector(m, aux)\n    # Establish subsidence velocity\n    w_s = aux.lsforcing.w_s\n    return -state.ρ * w_s * dot(k̂, diffusive.moisture.∇q_tot)\nend\n\n# Sponge relaxation\n\"\"\"\n    LinearSponge{FT} <: TendencyDef{Source}\n\nTwo parameter sponge (α_max, γ) for velocity relaxation to a reference\nstate.\n    α_max = Sponge strength (can be interpreted as timescale)\n    γ = Sponge exponent\n    z_max = Domain height\n    z_sponge = Altitude at which sponge layer starts\n\"\"\"\nstruct LinearSponge{FT} <: TendencyDef{Source}\n    \"Maximum domain altitude (m)\"\n    z_max::FT\n    \"Altitude at with sponge starts (m)\"\n    z_sponge::FT\n    \"Sponge Strength 0 ⩽ α_max ⩽ 1\"\n    α_max::FT\n    \"Sponge exponent\"\n    γ::FT\nend\n\nprognostic_vars(::LinearSponge) = (Momentum(),)\n\nfunction source(::Momentum, s::LinearSponge, m, args)\n    @unpack state, aux = args\n    #Unpack sponge parameters\n    FT = eltype(state)\n    @unpack z_max, z_sponge, α_max, γ = s\n    # Establish sponge relaxation velocity\n    u_geo = SVector(aux.lsforcing.ua, aux.lsforcing.va, 0)\n    z = altitude(m, aux)\n    # Accumulate sources\n    if z_sponge <= z\n        r = (z - z_sponge) / (z_max - z_sponge)\n        #ZS: different sponge formulation?\n        β_sponge = α_max .* sinpi(r / FT(2)) .^ γ\n        return -β_sponge * (state.ρu .- state.ρ * u_geo)\n    else\n        return SVector{3, FT}(0, 0, 0)\n    end\nend\n\n# We first specify the NetCDF file from which we wish to read our\n# GCM values.\n# Utility function to read and store variables directly from the\n# NetCDF file\n\"\"\"\n    str2var(str::String, var::Any)\n\nHelper function allowing variables read in from the GCM file\nto be made available to the LES simulation.\n\"\"\"\nfunction str2var(str::String, var::Any)\n    str = Symbol(str)\n    @eval(($str) = ($var))\nend\n\n# Define the get_gcm_info function\nconst forcingfile = \"HadGEM2-A_amip.2004-2008.07\"\n\"\"\"\n    get_gcm_info(group_id)\n\nFor a specific global site, establish and store the GCM state\nfor each available vertical level. `group_id` refers to the integer\nindex of the specific global site that we are interested in.\n\"\"\"\nfunction get_gcm_info(group_id)\n\n    @printf(\"--------------------------------------------------\\n\")\n    @info @sprintf(\"\"\"\\n\n     Experiment: GCM(HadGEM2-A) driven LES(ClimateMachine)\n     \"\"\")\n\n    @printf(\"\\n\")\n    @printf(\"HadGEM2-A_LES = %s\\n\", group_id)\n    @printf(\"--------------------------------------------------\\n\")\n\n    lsforcing_dataset = ArtifactWrapper(\n        @__DIR__,\n        isempty(get(ENV, \"CI\", \"\")),\n        \"lsforcing\",\n        ArtifactFile[ArtifactFile(\n            url = \"https://caltech.box.com/shared/static/dszfbqzwgc9a55vhxd43yenvebcb6bcj.nc\",\n            filename = forcingfile,\n        ),],\n    )\n    lsforcing_dataset_path = get_data_folder(lsforcing_dataset)\n    data_file = joinpath(lsforcing_dataset_path, forcingfile)\n\n    req_varnames = (\n        \"zg\",\n        \"ta\",\n        \"hus\",\n        \"ua\",\n        \"va\",\n        \"pfull\",\n        \"tntha\",\n        \"tntva\",\n        \"tntr\",\n        \"tnhusha\",\n        \"tnhusva\",\n        \"wap\",\n        \"hfls\",\n        \"hfss\",\n        \"ts\",\n        \"alpha\",\n    )\n    # Load NETCDF dataset (HadGEM2-A information)\n    # Load the NCDataset (currently we assume all time-stamps are\n    # in the same NCData file). We store this information in `data`.\n    data = NCDataset(data_file)\n    # To assist the user / inform them of the data processing step\n    # we print out some useful information, such as groupnames\n    # and a list of available variables\n    @printf(\"Storing information for group %s ...\", group_id)\n    for (varname, var) in data.group[group_id]\n        for reqvar in req_varnames\n            reqvar ≠ varname && continue\n            # Get average over time dimension\n            var = mean(var, dims = 2)\n            if any(varname == y for y in (\"hfls\", \"hfss\", \"ts\"))\n                var = mean(var, dims = 1)[1]\n            end\n            # Assign the variable values to the appropriate converted string\n            str2var(varname, var)\n        end\n        # Store key variables\n    end\n    @printf(\"Complete\\n\")\n    @printf(\"--------------------------------------------------\\n\")\n    @printf(\"Group data storage complete\\n\")\n    return (\n        zg = zg,\n        ta = ta,\n        hus = hus,\n        ua = ua,\n        va = va,\n        pfull = pfull,\n        tntha = tntha,\n        tntva = tntva,\n        tntr = tntr,\n        tnhusha = tnhusha,\n        tnhusva = tnhusva,\n        wap = wap,\n        hfls = hfls,\n        hfss = hfss,\n        ts = ts,\n        alpha = alpha,\n    )\n\nend\n\n# Initialise the CFSite experiment :D!\nconst seed = MersenneTwister(0)\nfunction init_cfsites!(problem, bl, state, aux, localgeo, t, spl)\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n    (x, y, z) = localgeo.coord\n    _grav = FT(grav(param_set))\n\n    # Unpack splines, interpolate to z coordinate at\n    # present grid index. (Functions are all pointwise)\n    ta = FT(spl.spl_ta(z))\n    hus = FT(spl.spl_hus(z))\n    ua = FT(spl.spl_ua(z))\n    va = FT(spl.spl_va(z))\n    pfull = FT(spl.spl_pfull(z))\n    tntha = FT(spl.spl_tntha(z))\n    tntva = FT(spl.spl_tntva(z))\n    tntr = FT(spl.spl_tntr(z))\n    tnhusha = FT(spl.spl_tnhusha(z))\n    tnhusva = FT(spl.spl_tnhusva(z))\n    wap = FT(spl.spl_wap(z))\n    ρ_gcm = FT(1 / spl.spl_alpha(z))\n\n    param_set = parameter_set(bl)\n    # Compute field properties based on interpolated data\n    ρ = air_density(param_set, ta, pfull, PhasePartition(hus))\n    e_int = internal_energy(param_set, ta, PhasePartition(hus))\n    e_kin = (ua^2 + va^2) / 2\n    e_pot = _grav * z\n    # Assignment of state variables\n    state.ρ = ρ\n    state.ρu = ρ * SVector(ua, va, 0)\n    state.energy.ρe = ρ * (e_kin + e_pot + e_int)\n    state.moisture.ρq_tot = ρ * hus\n    if z <= FT(400)\n        state.energy.ρe += rand(seed) * FT(1 / 100) * (state.energy.ρe)\n        state.moisture.ρq_tot +=\n            rand(seed) * FT(1 / 100) * (state.moisture.ρq_tot)\n    end\n\n    # Assign and store the ref variable for sources\n    aux.lsforcing.ta = ta\n    aux.lsforcing.hus = hus\n    aux.lsforcing.Σtemp_tendency = tntha + tntva + tntr\n    aux.lsforcing.ua = ua\n    aux.lsforcing.va = va\n    aux.lsforcing.Σqt_tendency = tnhusha + tnhusva\n    aux.lsforcing.w_s = -wap / ρ_gcm / _grav\n    return nothing\nend\n\nfunction config_cfsites(\n    ::Type{FT},\n    N,\n    resolution,\n    xmax,\n    ymax,\n    zmax,\n    hfls,\n    hfss,\n    ts,\n    group_id,\n) where {FT}\n    # Boundary Conditions\n    u_star = FT(0.28)\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        turbulence = Vreman{FT}(0.23),\n        moisture = EquilMoist(; maxiter = 5, tolerance = FT(2)),\n        lsforcing = HadGEMVertical(),\n    )\n\n    problem = AtmosProblem(\n        boundaryconditions = (\n            AtmosBC(\n                physics;\n                momentum = Impenetrable(DragLaw(\n                    (state, aux, t, normPu_int) -> (u_star / normPu_int)^2,\n                )),\n                energy = PrescribedEnergyFlux((state, aux, t) -> hfls + hfss),\n                moisture = PrescribedMoistureFlux(\n                    (state, aux, t) ->\n                        hfls / latent_heat_vapor(param_set, ts),\n                ),\n            ),\n            AtmosBC(physics;),\n        ),\n        init_state_prognostic = init_cfsites!,\n    )\n\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        problem = problem,\n        source = (\n            Gravity(),\n            LinearSponge{FT}(zmax, zmax * 0.85, 1, 4),\n            LargeScaleProcess(),\n            LargeScaleSubsidence(),\n        ),\n    )\n\n    # Timestepper options\n\n    # Explicit Solver\n    ex_solver = ClimateMachine.ExplicitSolverType()\n\n    # Multirate Explicit Solver\n    mrrk_solver = ClimateMachine.MultirateSolverType(\n        fast_model = AtmosAcousticGravityLinearModel,\n        slow_method = LSRK144NiegemannDiehlBusch,\n        fast_method = LSRK144NiegemannDiehlBusch,\n        timestep_ratio = 10,\n    )\n\n    # IMEX Solver Type\n    imex_solver = ClimateMachine.IMEXSolverType()\n\n    # Configuration\n    config = ClimateMachine.AtmosLESConfiguration(\n        forcingfile * \"_$group_id\",\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        init_cfsites!,\n        #ZS: multi-rate?\n        model = model,\n    )\n    return config, imex_solver\nend\n\n# Define the diagnostics configuration (Atmos-Default)\nfunction config_diagnostics(driver_config)\n    default_dgngrp = setup_atmos_default_diagnostics(\n        AtmosLESConfigType(),\n        \"2500steps\",\n        driver_config.name,\n    )\n    core_dgngrp = setup_atmos_core_diagnostics(\n        AtmosLESConfigType(),\n        \"2500steps\",\n        driver_config.name,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([\n        default_dgngrp,\n        core_dgngrp,\n    ])\nend\n\nfunction main()\n\n    # Provision for custom command line arguments\n    # Convenience args for slurm-array launches\n    cfsite_args = ArgParseSettings(autofix_names = true)\n    add_arg_group!(cfsite_args, \"HadGEM2-A_SiteInfo\")\n    @add_arg_table! cfsite_args begin\n        \"--group-id\"\n        help = \"Specify cfSite-ID for forcing data\"\n        metavar = \"site<number>\"\n        arg_type = String\n        default = \"site17\"\n    end\n    cl_args = ClimateMachine.init(\n        parse_clargs = true,\n        custom_clargs = cfsite_args,\n        fix_rng_seed = true,\n    )\n    group_id = cl_args[\"group_id\"]\n\n    # Working precision\n    FT = Float64\n    # DG polynomial order\n    N = 4\n    # Domain resolution and size\n    Δh = FT(75)\n    Δv = FT(20)\n    resolution = (Δh, Δh, Δv)\n    # Domain extents\n    xmax = FT(1800)\n    ymax = FT(1800)\n    zmax = FT(4000)\n    # Simulation time\n    t0 = FT(0)\n    timeend = FT(600)\n    #timeend = FT(3600 * 6)\n    # Courant number\n    CFL = FT(0.2)\n\n    # Execute the get_gcm_info function\n    ls_forcing = get_gcm_info(group_id)\n\n    # Drop dimensions for compatibility with Dierckx\n    z = dropdims(ls_forcing.zg; dims = 2)\n    # Create spline objects and pass them into a named tuple\n    splines = (\n        spl_ta = Spline1D(z, view(ls_forcing.ta, :, 1)),\n        spl_pfull = Spline1D(z, view(ls_forcing.pfull, :, 1)),\n        spl_ua = Spline1D(z, view(ls_forcing.ua, :, 1)),\n        spl_va = Spline1D(z, view(ls_forcing.va, :, 1)),\n        spl_hus = Spline1D(z, view(ls_forcing.hus, :, 1)),\n        spl_tntha = Spline1D(z, view(ls_forcing.tntha, :, 1)),\n        spl_tntva = Spline1D(z, view(ls_forcing.tntva, :, 1)),\n        spl_tntr = Spline1D(z, view(ls_forcing.tntr, :, 1)),\n        spl_tnhusha = Spline1D(z, view(ls_forcing.tnhusha, :, 1)),\n        spl_tnhusva = Spline1D(z, view(ls_forcing.tnhusva, :, 1)),\n        spl_wap = Spline1D(z, view(ls_forcing.wap, :, 1)),\n        spl_alpha = Spline1D(z, view(ls_forcing.alpha, :, 1)),\n    )\n\n    # Set up driver configuration\n    driver_config, ode_solver_type = config_cfsites(\n        FT,\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        hfls,\n        hfss,\n        ts,\n        group_id,\n    )\n    # Set up solver configuration\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        splines;\n        init_on_cpu = true,\n        ode_solver_type = ode_solver_type,\n        Courant_number = CFL,\n        CFL_direction = HorizontalDirection(),\n    )\n    # Set up diagnostic configuration\n    dgn_config = config_diagnostics(driver_config)\n\n    cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            (\"moisture.ρq_tot\",),\n            solver_config.dg.grid,\n            TMARFilter(),\n        )\n        nothing\n    end\n\n    #ZS: cutoff filter?\n    filterorder = 2 * N\n    filter = BoydVandevenFilter(solver_config.dg.grid, 0, filterorder)\n    cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            AtmosFilterPerturbations(driver_config.bl),\n            solver_config.dg.grid,\n            filter,\n        )\n        nothing\n    end\n\n    cutoff_filter = CutoffFilter(solver_config.dg.grid, N - 1)\n    cbcutoff = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(solver_config.Q, 1:6, solver_config.dg.grid, cutoff_filter)\n        nothing\n    end\n\n    # Invoke solver (calls solve! function for time-integrator)\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        #ZS: only tmar?\n        user_callbacks = (cbtmarfilter,),\n        check_euclidean_distance = true,\n    )\n\nend\nmain()\n"
  },
  {
    "path": "experiments/AtmosLES/convective_bl_les.jl",
    "content": "include(\"convective_bl_model.jl\")\nfunction main()\n\n    # TODO: this will move to the future namelist functionality\n    cbl_args = ArgParseSettings(autofix_names = true)\n    add_arg_group!(cbl_args, \"ConvectiveBoundaryLayer\")\n    @add_arg_table! cbl_args begin\n        \"--surface-flux\"\n        help = \"specify surface flux for energy and moisture\"\n        metavar = \"prescribed|bulk\"\n        arg_type = String\n        default = \"bulk\"\n    end\n\n    cl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = cbl_args)\n\n    surface_flux = cl_args[\"surface_flux\"]\n\n    FT = Float64\n    config_type = AtmosLESConfigType\n\n    # DG polynomial order\n    N = 4\n    # Domain resolution and size\n    Δh = FT(80)\n    Δv = FT(80)\n\n    resolution = (Δh, Δh, Δv)\n\n    # Prescribe domain parameters\n    xmax = FT(4800)\n    ymax = FT(4800)\n    zmax = FT(3200)\n\n    t0 = FT(0)\n\n    # Full simulation requires 16+ hours of simulated time\n    timeend = FT(3600 * 0.1)\n    CFLmax = FT(0.4)\n\n    # Choose default Explicit solver\n    ode_solver_type = ClimateMachine.ExplicitSolverType()\n\n    model = convective_bl_model(FT, config_type, zmax, surface_flux)\n    ics = model.problem.init_state_prognostic\n\n    # Assemble configuration\n    driver_config = ClimateMachine.AtmosLESConfiguration(\n        \"ConvectiveBoundaryLayer\",\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        ics,\n        model = model,\n    )\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFLmax,\n    )\n    dgn_config = config_diagnostics(driver_config)\n\n    check_cons = (\n        ClimateMachine.ConservationCheck(\"ρ\", \"1mins\", FT(0.0001)),\n        ClimateMachine.ConservationCheck(\"energy.ρe\", \"1mins\", FT(0.0025)),\n    )\n\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        check_cons = check_cons,\n        check_euclidean_distance = true,\n    )\nend\n\nmain()\n"
  },
  {
    "path": "experiments/AtmosLES/convective_bl_model.jl",
    "content": "#!/usr/bin/env julia --project\n# This experiment file establishes the initial conditions, boundary conditions,\n# source terms and simulation parameters (domain size + resolution) for the\n\n# Convective Boundary Layer LES case (Kitamura et al, 2016).\n\n## ### Convective Boundary Layer LES\n## [Nishizawa2018](@cite)\n#\n# To simulate the experiment, type in\n#\n# julia --project experiments/AtmosLES/convective_bl_les.jl\n\nusing ArgParse\nusing Distributions\nusing DocStringExtensions\nusing LinearAlgebra\nusing Printf\nusing Random\nusing StaticArrays\nusing UnPack\nusing Test\n\nusing ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Orientations\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.TurbulenceConvection\nusing ClimateMachine.VariableTemplates\n\nusing ClimateMachine.BalanceLaws\nimport ClimateMachine.BalanceLaws: source, prognostic_vars\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing ClimateMachine.Atmos: altitude, recover_thermo_state\n\n\"\"\"\n    ConvectiveBL Geostrophic Forcing (Source)\n\"\"\"\nstruct ConvectiveBLGeostrophic{FT} <: TendencyDef{Source}\n    \"Coriolis parameter [s⁻¹]\"\n    f_coriolis::FT\n    \"Eastward geostrophic velocity `[m/s]` (Base)\"\n    u_geostrophic::FT\n    \"Eastward geostrophic velocity `[m/s]` (Slope)\"\n    u_slope::FT\n    \"Northward geostrophic velocity `[m/s]`\"\n    v_geostrophic::FT\nend\nprognostic_vars(::ConvectiveBLGeostrophic) = (Momentum(),)\n\nfunction source(::Momentum, s::ConvectiveBLGeostrophic, m, args)\n    @unpack state, aux = args\n    @unpack f_coriolis, u_geostrophic, u_slope, v_geostrophic = s\n\n    z = altitude(m, aux)\n    # Note z dependence of eastward geostrophic velocity\n    u_geo = SVector(u_geostrophic + u_slope * z, v_geostrophic, 0)\n    ẑ = vertical_unit_vector(m, aux)\n    fkvector = f_coriolis * ẑ\n    # Accumulate sources\n    return -fkvector × (state.ρu .- state.ρ * u_geo)\nend\n\n\"\"\"\n  ConvectiveBL Sponge (Source)\n\"\"\"\nstruct ConvectiveBLSponge{FT} <: TendencyDef{Source}\n    \"Maximum domain altitude (m)\"\n    z_max::FT\n    \"Altitude at with sponge starts (m)\"\n    z_sponge::FT\n    \"Sponge Strength 0 ⩽ α_max ⩽ 1\"\n    α_max::FT\n    \"Sponge exponent\"\n    γ::FT\n    \"Eastward geostrophic velocity `[m/s]` (Base)\"\n    u_geostrophic::FT\n    \"Eastward geostrophic velocity `[m/s]` (Slope)\"\n    u_slope::FT\n    \"Northward geostrophic velocity `[m/s]`\"\n    v_geostrophic::FT\nend\nprognostic_vars(::ConvectiveBLSponge) = (Momentum(),)\n\nfunction source(::Momentum, s::ConvectiveBLSponge, m, args)\n    @unpack state, aux = args\n\n    @unpack z_max, z_sponge, α_max, γ = s\n    @unpack u_geostrophic, u_slope, v_geostrophic = s\n\n    z = altitude(m, aux)\n    u_geo = SVector(u_geostrophic + u_slope * z, v_geostrophic, 0)\n    ẑ = vertical_unit_vector(m, aux)\n    # Accumulate sources\n    if z_sponge <= z\n        r = (z - z_sponge) / (z_max - z_sponge)\n        β_sponge = α_max * sinpi(r / 2)^s.γ\n        return -β_sponge * (state.ρu .- state.ρ * u_geo)\n    else\n        FT = eltype(state)\n        return SVector{3, FT}(0, 0, 0)\n    end\nend\n\n\"\"\"\n  Initial Condition for ConvectiveBoundaryLayer LES\n\"\"\"\nfunction init_problem!(problem, bl, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n\n    # Problem floating point precision\n    param_set = parameter_set(bl)\n    FT = eltype(state)\n    R_gas::FT = R_d(param_set)\n    c_p::FT = cp_d(param_set)\n    c_v::FT = cv_d(param_set)\n    p0::FT = MSLP(param_set)\n    _grav::FT = grav(param_set)\n    γ::FT = c_p / c_v\n    # Initialise speeds [u = Eastward, v = Northward, w = Vertical]\n    u::FT = 4\n    v::FT = 0\n    w::FT = 0\n    # Assign piecewise quantities to θ_liq and q_tot\n    θ_liq::FT = 0\n    q_tot::FT = 0\n    # functions for potential temperature\n\n    θ_liq = FT(288) + FT(4 / 1000) * z\n    θ = θ_liq\n    π_exner = FT(1) - _grav / (c_p * θ) * z # exner pressure\n    ρ = p0 / (R_gas * θ) * (π_exner)^(c_v / R_gas) # density\n    # Establish thermodynamic state and moist phase partitioning\n    TS = PhaseEquil_ρθq(param_set, ρ, θ_liq, q_tot)\n\n    # Compute momentum contributions\n    ρu = ρ * u\n    ρv = ρ * v\n    ρw = ρ * w\n\n    # Compute energy contributions\n    e_kin = FT(1 // 2) * (u^2 + v^2 + w^2)\n    e_pot = _grav * z\n    ρe_tot = ρ * total_energy(e_kin, e_pot, TS)\n\n    # Assign initial conditions for prognostic state variables\n    state.ρ = ρ\n    state.ρu = SVector(ρu, ρv, ρw)\n    state.energy.ρe = ρe_tot\n    if !(moisture_model(bl) isa DryModel)\n        state.moisture.ρq_tot = ρ * q_tot\n    end\n\n    if z <= FT(400) # Add random perturbations to bottom 400m of model\n        state.energy.ρe += rand() * ρe_tot / 100\n    end\n    init_state_prognostic!(turbconv_model(bl), bl, state, aux, localgeo, t)\nend\n\nfunction surface_temperature_variation(bl, state, t)\n    FT = eltype(state)\n    ρ = state.ρ\n    θ_liq_sfc = FT(291.15) + FT(20) * sinpi(FT(t / 12 / 3600))\n    param_set = parameter_set(bl)\n    if moisture_model(bl) isa DryModel\n        TS = PhaseDry_ρθ(param_set, ρ, θ_liq_sfc)\n    else\n        q_tot = state.moisture.ρq_tot / ρ\n        TS = PhaseEquil_ρθq(param_set, ρ, θ_liq_sfc, q_tot)\n    end\n    return air_temperature(TS)\nend\n\nfunction convective_bl_model(\n    ::Type{FT},\n    config_type,\n    zmax,\n    surface_flux;\n    turbconv = NoTurbConv(),\n    moisture_model = \"dry\",\n) where {FT}\n\n    ics = init_problem!     # Initial conditions\n\n    C_smag = FT(0.23)     # Smagorinsky coefficient\n    C_drag = FT(0.001)    # Momentum exchange coefficient\n    z_sponge = FT(2560)     # Start of sponge layer\n\n    α_max = FT(0.75)       # Strength of sponge layer (timescale)\n\n    γ = 2                  # Strength of sponge layer (exponent)\n    u_geostrophic = FT(4)        # Eastward relaxation speed\n    u_slope = FT(0)              # Slope of altitude-dependent relaxation speed\n    v_geostrophic = FT(0)        # Northward relaxation speed\n    f_coriolis = FT(1.031e-4) # Coriolis parameter\n    u_star = FT(0.3)\n    q_sfc = FT(0)\n    moisture_flux = FT(0)\n\n    # Assemble source components\n    source_default = (\n        Gravity(),\n        ConvectiveBLSponge{FT}(\n            zmax,\n            z_sponge,\n            α_max,\n            γ,\n            u_geostrophic,\n            u_slope,\n            v_geostrophic,\n        ),\n        ConvectiveBLGeostrophic{FT}(\n            f_coriolis,\n            u_geostrophic,\n            u_slope,\n            v_geostrophic,\n        ),\n        turbconv_sources(turbconv)...,\n    )\n\n    if moisture_model == \"dry\"\n        source = source_default\n        moisture = DryModel()\n    elseif moisture_model == \"equilibrium\"\n        source = source_default\n        moisture = EquilMoist(; maxiter = 5, tolerance = FT(0.1))\n    elseif moisture_model == \"nonequilibrium\"\n        source = (source_default..., CreateClouds())\n        moisture = NonEquilMoist()\n    else\n        @warn @sprintf(\n            \"\"\"\n%s: unrecognized moisture_model in source terms, using the defaults\"\"\",\n            moisture_model,\n        )\n        source = source_default\n    end\n    # Set up problem initial and boundary conditions\n    if surface_flux == \"prescribed\"\n        energy_bc = PrescribedEnergyFlux((state, aux, t) -> LHF + SHF)\n        moisture_bc = PrescribedMoistureFlux((state, aux, t) -> moisture_flux)\n    elseif surface_flux == \"bulk\"\n        energy_bc = BulkFormulaEnergy(\n            (bl, state, aux, t, normPu_int) -> C_drag,\n            (bl, state, aux, t) ->\n                (surface_temperature_variation(bl, state, t), q_sfc),\n        )\n        moisture_bc = BulkFormulaMoisture(\n            (state, aux, t, normPu_int) -> C_drag,\n            (state, aux, t) -> q_sfc,\n        )\n    else\n        @warn @sprintf(\n            \"\"\"\n%s: unrecognized surface flux; using 'prescribed'\"\"\",\n            surface_flux,\n        )\n    end\n\n    # Define the physics\n    physics = AtmosPhysics{FT}(\n        param_set;\n        turbulence = SmagorinskyLilly{FT}(C_smag),\n        moisture = moisture,\n        turbconv = turbconv,\n    )\n\n    moisture_bcs = moisture_model == \"dry\" ? () : (; moisture = moisture_bc)\n    boundary_conditions = (\n        AtmosBC(\n            physics;\n            momentum = Impenetrable(DragLaw(\n                # normPu_int is the internal horizontal speed\n                # P represents the projection onto the horizontal\n                (state, aux, t, normPu_int) -> (u_star / normPu_int)^2,\n            )),\n            energy = energy_bc,\n            moisture_bcs...,\n            turbconv = turbconv_bcs(turbconv)[1],\n        ),\n        AtmosBC(physics; turbconv = turbconv_bcs(turbconv)[2]),\n    )\n\n    problem = AtmosProblem(\n        init_state_prognostic = ics,\n        boundaryconditions = boundary_conditions,\n    )\n\n    # Assemble model components\n    model =\n        AtmosModel{FT}(config_type, physics; problem = problem, source = source)\n\n    return model\nend\n\nfunction config_diagnostics(driver_config)\n    default_dgngrp = setup_atmos_default_diagnostics(\n        AtmosLESConfigType(),\n        \"2500steps\",\n        driver_config.name,\n    )\n    core_dgngrp = setup_atmos_core_diagnostics(\n        AtmosLESConfigType(),\n        \"2500steps\",\n        driver_config.name,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([\n        default_dgngrp,\n        core_dgngrp,\n    ])\nend\n"
  },
  {
    "path": "experiments/AtmosLES/dycoms.jl",
    "content": "#!/usr/bin/env julia --project\nusing ClimateMachine\n\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Mesh.Filters\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\n\nusing ClimateMachine.BalanceLaws\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    prognostic_vars,\n    flux,\n    eq_tends,\n    integral_load_auxiliary_state!,\n    integral_set_auxiliary_state!,\n    reverse_integral_load_auxiliary_state!,\n    reverse_integral_set_auxiliary_state!\n\nusing ArgParse\nusing UnPack\nusing Distributions\nusing Random\nusing StaticArrays\nusing Test\nusing DocStringExtensions\nusing LinearAlgebra\nusing Printf\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: cp_d, MSLP, grav, LH_v0\n\nusing CLIMAParameters.Atmos.Microphysics\n\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n# ------------------------ Begin Radiation Model ---------------------- #\n\n\"\"\"\n    DYCOMSRadiationModel <: RadiationModel\n\n## References\n - [Stevens2005](@cite)\n\"\"\"\nstruct DYCOMSRadiationModel{FT} <: RadiationModel\n    \"mass absorption coefficient `[m^2/kg]`\"\n    κ::FT\n    \"Troposphere cooling parameter `[m^(-4/3)]`\"\n    α_z::FT\n    \"Inversion height `[m]`\"\n    z_i::FT\n    \"Density\"\n    ρ_i::FT\n    \"Large scale divergence `[s^(-1)]`\"\n    D_subsidence::FT\n    \"Radiative flux parameter `[W/m^2]`\"\n    F_0::FT\n    \"Radiative flux parameter `[W/m^2]`\"\n    F_1::FT\n    \"is AtmosLES moisture model an equilibrium model\"\n    equilibrium_moisture_model::Bool\nend\n\nstruct DYCOMSRadiation <: TendencyDef{Flux{FirstOrder}} end\n\neq_tends(pv::Energy, ::DYCOMSRadiationModel, ::Flux{FirstOrder}) =\n    (DYCOMSRadiation(),)\n\nfunction flux(::Energy, ::DYCOMSRadiation, atmos, args)\n    @unpack state, aux = args\n    m = radiation_model(atmos)\n    FT = eltype(state)\n    z = altitude(atmos, aux)\n    Δz_i = max(z - m.z_i, -zero(FT))\n    # Constants\n    upward_flux_from_cloud = m.F_0 * exp(-aux.∫dnz.radiation.attenuation_coeff)\n    upward_flux_from_sfc = m.F_1 * exp(-aux.∫dz.radiation.attenuation_coeff)\n    param_set = parameter_set(atmos)\n    free_troposphere_flux =\n        m.ρ_i *\n        FT(cp_d(param_set)) *\n        m.D_subsidence *\n        m.α_z *\n        cbrt(Δz_i) *\n        (Δz_i / 4 + m.z_i)\n    F_rad =\n        upward_flux_from_sfc + upward_flux_from_cloud + free_troposphere_flux\n    ẑ = vertical_unit_vector(atmos, aux)\n    return F_rad * ẑ\nend\n\nvars_state(m::DYCOMSRadiationModel, ::Auxiliary, FT) = @vars(Rad_flux::FT)\n\nvars_state(m::DYCOMSRadiationModel, ::UpwardIntegrals, FT) =\n    @vars(attenuation_coeff::FT)\nfunction integral_load_auxiliary_state!(\n    m::DYCOMSRadiationModel,\n    integrand::Vars,\n    state::Vars,\n    aux::Vars,\n)\n    FT = eltype(state)\n\n    if m.equilibrium_moisture_model\n        integrand.radiation.attenuation_coeff =\n            state.ρ * m.κ * aux.moisture.q_liq\n    else\n        integrand.radiation.attenuation_coeff = m.κ * state.moisture.ρq_liq\n    end\nend\nfunction integral_set_auxiliary_state!(\n    m::DYCOMSRadiationModel,\n    aux::Vars,\n    integral::Vars,\n)\n    integral = integral.radiation.attenuation_coeff\n    aux.∫dz.radiation.attenuation_coeff = integral\nend\n\nvars_state(m::DYCOMSRadiationModel, ::DownwardIntegrals, FT) =\n    @vars(attenuation_coeff::FT)\nfunction reverse_integral_load_auxiliary_state!(\n    m::DYCOMSRadiationModel,\n    integrand::Vars,\n    state::Vars,\n    aux::Vars,\n)\n    FT = eltype(state)\n    integrand.radiation.attenuation_coeff = aux.∫dz.radiation.attenuation_coeff\nend\nfunction reverse_integral_set_auxiliary_state!(\n    m::DYCOMSRadiationModel,\n    aux::Vars,\n    integral::Vars,\n)\n    aux.∫dnz.radiation.attenuation_coeff = integral.radiation.attenuation_coeff\nend\n\n# -------------------------- End Radiation Model ------------------------ #\n\n\"\"\"\n  Initial Condition for DYCOMS_RF01 LES\n\n## References\n - [Stevens2005](@cite)\n\"\"\"\nfunction init_dycoms!(problem, bl, state, aux, localgeo, t)\n    FT = eltype(state)\n\n    (x, y, z) = localgeo.coord\n    param_set = parameter_set(bl)\n\n    z = altitude(bl, aux)\n\n    # These constants are those used by Stevens et al. (2005)\n    qref = FT(9.0e-3)\n    q_pt_sfc = PhasePartition(qref)\n    Rm_sfc = gas_constant_air(param_set, q_pt_sfc)\n    T_sfc = FT(290.4)\n    _MSLP = FT(MSLP(param_set))\n    _grav = FT(grav(param_set))\n\n    # Specify moisture profiles\n    q_liq = FT(0)\n    q_ice = FT(0)\n    zb = FT(600)         # initial cloud bottom\n    zi = FT(840)         # initial cloud top\n\n    if z <= zi\n        θ_liq = FT(289.0)\n        q_tot = qref\n    else\n        θ_liq = FT(297.0) + (z - zi)^(FT(1 / 3))\n        q_tot = FT(1.5e-3)\n    end\n\n    ugeo = FT(7)\n    vgeo = FT(-5.5)\n    u, v, w = ugeo, vgeo, FT(0)\n\n    # Perturb initial state to break symmetry and trigger turbulent convection\n    r1 = FT(rand(Uniform(-0.001, 0.001)))\n    if z <= 200.0\n        θ_liq += r1 * θ_liq\n    end\n\n    # Pressure\n    H = Rm_sfc * T_sfc / _grav\n    p = _MSLP * exp(-z / H)\n\n    # Density, Temperature\n\n    ts = PhaseEquil_pθq(param_set, p, θ_liq, q_tot)\n    ρ = air_density(ts)\n\n    e_kin = FT(1 / 2) * FT((u^2 + v^2 + w^2))\n    e_pot = gravitational_potential(bl.orientation, aux)\n    E = ρ * total_energy(e_kin, e_pot, ts)\n\n    state.ρ = ρ\n    state.ρu = SVector(ρ * u, ρ * v, ρ * w)\n    state.energy.ρe = E\n\n    state.moisture.ρq_tot = ρ * q_tot\n\n    if moisture_model(bl) isa NonEquilMoist\n        q_init = PhasePartition(ts)\n        state.moisture.ρq_liq = q_init.liq\n        state.moisture.ρq_ice = q_init.ice\n    end\n    if precipitation_model(bl) isa RainModel\n        state.precipitation.ρq_rai = FT(0)\n    end\n\n    return nothing\nend\n\nfunction config_dycoms(\n    ::Type{FT},\n    N,\n    resolution,\n    xmax,\n    ymax,\n    zmax,\n    moisture_model = \"equilibrium\",\n    precipitation_model = \"noprecipitation\",\n) where {FT}\n    # Reference state\n    T_profile = DecayingTemperatureProfile{FT}(param_set)\n    ref_state = HydrostaticState(T_profile)\n\n    # Radiation model\n    κ = FT(85)\n    α_z = FT(1)\n    z_i = FT(840)\n    ρ_i = FT(1.13)\n\n    D_subsidence = FT(3.75e-6)\n\n    F_0 = FT(70)\n    F_1 = FT(22)\n    if moisture_model == \"equilibrium\"\n        equilibrium_moisture_model = true\n    else\n        equilibrium_moisture_model = false\n    end\n    radiation = DYCOMSRadiationModel{FT}(\n        κ,\n        α_z,\n        z_i,\n        ρ_i,\n        D_subsidence,\n        F_0,\n        F_1,\n        equilibrium_moisture_model,\n    )\n\n    # Sources\n    f_coriolis = FT(0.762e-4)\n    u_geostrophic = FT(7.0)\n    v_geostrophic = FT(-5.5)\n    w_ref = FT(0)\n    u_relaxation = SVector(u_geostrophic, v_geostrophic, w_ref)\n    # Sponge\n    c_sponge = 1\n    # Rayleigh damping\n    zsponge = FT(1000.0)\n    rayleigh_sponge =\n        RayleighSponge{FT}(zmax, zsponge, c_sponge, u_relaxation, 2)\n    # Geostrophic forcing\n    geostrophic_forcing =\n        GeostrophicForcing{FT}(f_coriolis, u_geostrophic, v_geostrophic)\n\n    # Boundary conditions\n    # SGS Filter constants\n    C_smag = FT(0.21) # 0.21 for stable testing, 0.18 in practice\n    C_drag = FT(0.0011)\n    LHF = FT(115)\n    SHF = FT(15)\n    moisture_flux = LHF / FT(LH_v0(param_set))\n\n    source = (\n        Gravity(),\n        rayleigh_sponge,\n        Subsidence{FT}(D_subsidence),\n        geostrophic_forcing,\n    )\n\n    # moisture model and its sources\n    if moisture_model == \"equilibrium\"\n        moisture = EquilMoist(; maxiter = 4, tolerance = FT(1))\n    elseif moisture_model == \"nonequilibrium\"\n        source = (source..., CreateClouds())\n        moisture = NonEquilMoist()\n    else\n        @warn @sprintf(\n            \"\"\"\n%s: unrecognized moisture_model in source terms, using the defaults\"\"\",\n            moisture_model,\n        )\n        moisture = EquilMoist(; maxiter = 4, tolerance = FT(1))\n    end\n\n    # precipitation model and its sources\n    if precipitation_model == \"noprecipitation\"\n        precipitation = NoPrecipitation()\n    elseif precipitation_model == \"rain\"\n        source = (source..., WarmRain_1M())\n        precipitation = RainModel()\n    else\n        @warn @sprintf(\n            \"\"\"\n%s: unrecognized precipitation_model in source terms, using the defaults\"\"\",\n            precipitation_model,\n        )\n        precipitation = NoPrecipitation()\n    end\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = Vreman{FT}(C_smag),\n        moisture = moisture,\n        precipitation = precipitation,\n        radiation = radiation,\n    )\n\n    problem = AtmosProblem(\n        boundaryconditions = (\n            AtmosBC(\n                physics;\n                momentum = Impenetrable(DragLaw(\n                    (state, aux, t, normPu) -> C_drag,\n                )),\n                energy = PrescribedEnergyFlux((state, aux, t) -> LHF + SHF),\n                moisture = PrescribedMoistureFlux(\n                    (state, aux, t) -> moisture_flux,\n                ),\n            ),\n            AtmosBC(physics;),\n        ),\n        init_state_prognostic = init_dycoms!,\n    )\n\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        problem = problem,\n        source = source,\n    )\n\n    ode_solver = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK144NiegemannDiehlBusch,\n    )\n\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"DYCOMS\",\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        init_dycoms!,\n        model = model,\n    )\n    return config, ode_solver\nend\n\nfunction config_diagnostics(driver_config, timeend)\n    ssecs = cld(timeend, 2) + 10\n    interval = \"$(ssecs)ssecs\"\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosLESConfigType(),\n        interval,\n        driver_config.name,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp])\nend\n\nfunction main()\n    # add a command line argument to specify the kind of\n    # moisture and precipitation model you want\n    # TODO: this will move to the future namelist functionality\n    dycoms_args = ArgParseSettings(autofix_names = true)\n    add_arg_group!(dycoms_args, \"DYCOMS\")\n    @add_arg_table! dycoms_args begin\n        \"--moisture-model\"\n        help = \"specify cloud condensate model\"\n        metavar = \"equilibrium|nonequilibrium\"\n        arg_type = String\n        default = \"equilibrium\"\n        \"--precipitation-model\"\n        help = \"specify precipitation model\"\n        metavar = \"noprecipitation|rain\"\n        arg_type = String\n        default = \"noprecipitation\"\n        \"--check-asserts\"\n        help = \"should asserts be checked at the end of the simulation\"\n        metavar = \"yes|no\"\n        arg_type = String\n        default = \"no\"\n    end\n\n    cl_args =\n        ClimateMachine.init(parse_clargs = true, custom_clargs = dycoms_args)\n    moisture_model = cl_args[\"moisture_model\"]\n    precipitation_model = cl_args[\"precipitation_model\"]\n    check_asserts = cl_args[\"check_asserts\"]\n\n    FT = Float64\n\n    # DG polynomial order\n    N = 4\n\n    # Domain resolution and size\n    Δh = FT(40)\n    Δv = FT(20)\n    resolution = (Δh, Δh, Δv)\n\n    xmax = FT(1000)\n    ymax = FT(1000)\n    zmax = FT(1500)\n\n    t0 = FT(0)\n    timeend = FT(100) #FT(4 * 60 * 60)\n    Cmax = FT(1.7)     # use this for single-rate explicit LSRK144\n\n    driver_config, ode_solver_type = config_dycoms(\n        FT,\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        moisture_model,\n        precipitation_model,\n    )\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = Cmax,\n    )\n    dgn_config = config_diagnostics(driver_config, timeend)\n\n    if moisture_model == \"equilibrium\"\n        filter_vars = (\"moisture.ρq_tot\",)\n    elseif moisture_model == \"nonequilibrium\"\n        filter_vars = (\"moisture.ρq_tot\", \"moisture.ρq_liq\", \"moisture.ρq_ice\")\n    end\n    if precipitation_model == \"rain\"\n        filter_vars = (filter_vars..., \"precipitation.ρq_rai\")\n    end\n\n    cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            filter_vars,\n            solver_config.dg.grid,\n            TMARFilter(),\n        )\n        nothing\n    end\n\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (cbtmarfilter,),\n        check_euclidean_distance = true,\n    )\n\n    # some simple checks to ensure that rain and clouds exist in the CI runs\n    if check_asserts == \"yes\"\n\n        m = driver_config.bl\n        Q = solver_config.Q\n        ρ_ind = varsindex(vars_state(m, Prognostic(), FT), :ρ)\n\n        if moisture_model == \"nonequilibrium\"\n\n            ρq_liq_ind =\n                varsindex(vars_state(m, Prognostic(), FT), :moisture, :ρq_liq)\n            ρq_ice_ind =\n                varsindex(vars_state(m, Prognostic(), FT), :moisture, :ρq_ice)\n\n            min_q_liq = minimum(abs.(\n                Array(Q[:, ρq_liq_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n            max_q_liq = maximum(abs.(\n                Array(Q[:, ρq_liq_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n\n            min_q_ice = minimum(abs.(\n                Array(Q[:, ρq_ice_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n            max_q_ice = maximum(abs.(\n                Array(Q[:, ρq_ice_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n\n            @info(min_q_liq, max_q_liq)\n            @info(min_q_ice, max_q_ice)\n\n            # test that cloud condensate variables exist and are not NaN\n            @test !isnan(max_q_liq)\n            @test !isnan(max_q_ice)\n\n            # test that there is reasonable amount of cloud water...\n            @test abs(max_q_liq) > FT(5e-4)\n\n            # ...and that there is no cloud ice\n            @test isequal(min_q_ice, FT(0))\n            @test isequal(max_q_ice, FT(0))\n\n\n        end\n        if precipitation_model == \"rain\"\n            ρq_rai_ind = varsindex(\n                vars_state(m, Prognostic(), FT),\n                :precipitation,\n                :ρq_rai,\n            )\n\n            min_q_rai = minimum(abs.(\n                Array(Q[:, ρq_rai_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n            max_q_rai = maximum(abs.(\n                Array(Q[:, ρq_rai_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n\n            @info(min_q_rai, max_q_rai)\n\n            # test that rain variable exists and is not NaN\n            @test !isnan(max_q_rai)\n\n            # test that there is reasonable amount of rain water...\n            @test abs(max_q_rai) > FT(1e-6)\n        end\n    end\nend\n\nmain()\n"
  },
  {
    "path": "experiments/AtmosLES/ekman_layer_model.jl",
    "content": "#!/usr/bin/env julia --project\n#=\n# This experiment file establishes the initial conditions, boundary conditions,\n# source terms and simulation parameters (domain size + resolution) for\n# a dry neutrally stratified Ekman layer.\n# \n# The initial conditions are given by constant horizontal velocity of 1 m/s,\n# and a constant potential temperature profile. The bottom boundary condition\n# results in momentum drag, and there is no exchange of heat from the surface\n# since the fluxes are zero and the temperature is homogeneous.\n#\n=#\n\nusing ArgParse\nusing Distributions\nusing StaticArrays\nusing Test\nusing DocStringExtensions\nusing LinearAlgebra\nusing Printf\nusing UnPack\n\nusing ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing Thermodynamics.TemperatureProfiles\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.ODESolvers\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.TurbulenceConvection\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.BalanceLaws\nimport ClimateMachine.BalanceLaws: source, prognostic_vars\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: cp_d, cv_d, grav, T_surf_ref\nusing CLIMAParameters.Atmos.SubgridScale: C_smag, C_drag\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\nimport CLIMAParameters\n\nusing ClimateMachine.Atmos: altitude, recover_thermo_state, density\n\n\"\"\"\n  EkmanLayer Geostrophic Forcing (Source)\n\"\"\"\nstruct EkmanLayerGeostrophic{FT} <: TendencyDef{Source}\n    \"Coriolis parameter [s⁻¹]\"\n    f_coriolis::FT\n    \"Eastward geostrophic velocity `[m/s]` (Base)\"\n    u_geostrophic::FT\n    \"Eastward geostrophic velocity `[m/s]` (Slope)\"\n    u_slope::FT\n    \"Northward geostrophic velocity `[m/s]`\"\n    v_geostrophic::FT\nend\nprognostic_vars(::EkmanLayerGeostrophic) = (Momentum(),)\n\nfunction source(::Momentum, s::EkmanLayerGeostrophic, m, args)\n    @unpack state, aux = args\n    @unpack f_coriolis, u_geostrophic, u_slope, v_geostrophic = s\n\n    z = altitude(m, aux)\n    # Note z dependence of eastward geostrophic velocity\n    u_geo = SVector(u_geostrophic + u_slope * z, v_geostrophic, 0)\n    ẑ = vertical_unit_vector(m, aux)\n    fkvector = f_coriolis * ẑ\n    # Accumulate sources\n    return -fkvector × (state.ρu .- state.ρ * u_geo)\nend\n\n\"\"\"\n  EkmanLayer Sponge (Source)\n\"\"\"\nstruct EkmanLayerSponge{FT} <: TendencyDef{Source}\n    \"Maximum domain altitude (m)\"\n    z_max::FT\n    \"Altitude at with sponge starts (m)\"\n    z_sponge::FT\n    \"Sponge Strength 0 ⩽ α_max ⩽ 1\"\n    α_max::FT\n    \"Sponge exponent\"\n    γ::FT\n    \"Eastward geostrophic velocity `[m/s]` (Base)\"\n    u_geostrophic::FT\n    \"Eastward geostrophic velocity `[m/s]` (Slope)\"\n    u_slope::FT\n    \"Northward geostrophic velocity `[m/s]`\"\n    v_geostrophic::FT\nend\nprognostic_vars(::EkmanLayerSponge) = (Momentum(),)\n\nfunction source(::Momentum, s::EkmanLayerSponge, m, args)\n    @unpack state, aux = args\n\n    @unpack z_max, z_sponge, α_max, γ = s\n    @unpack u_geostrophic, u_slope, v_geostrophic = s\n\n    z = altitude(m, aux)\n    u_geo = SVector(u_geostrophic + u_slope * z, v_geostrophic, 0)\n    ẑ = vertical_unit_vector(m, aux)\n    # Accumulate sources\n    if z_sponge <= z\n        r = (z - z_sponge) / (z_max - z_sponge)\n        β_sponge = α_max * sinpi(r / 2)^s.γ\n        return -β_sponge * (state.ρu .- state.ρ * u_geo)\n    else\n        FT = eltype(state)\n        return SVector{3, FT}(0, 0, 0)\n    end\nend\n\nadd_perturbations!(state, localgeo) = nothing\n\n\"\"\"\n  Initial Condition for EkmanLayer simulation\n\"\"\"\nfunction init_problem!(problem, bl, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n    # Problem floating point precision\n    param_set = parameter_set(bl)\n    FT = eltype(state)\n    c_p::FT = cp_d(param_set)\n    c_v::FT = cv_d(param_set)\n    _grav::FT = grav(param_set)\n    γ::FT = c_p / c_v\n    # Initialise speeds [u = Eastward, v = Northward, w = Vertical]\n    u::FT = 1\n    v::FT = 0\n    w::FT = 0\n    # Assign constant θ profile and equal to surface temperature\n    θ::FT = T_surf_ref(param_set)\n\n    p = aux.ref_state.p\n    TS = PhaseDry_pθ(param_set, p, θ)\n\n    compress = compressibility_model(bl) isa Compressible\n    ρ = compress ? air_density(TS) : aux.ref_state.ρ\n    # Compute momentum contributions\n    ρu = ρ * u\n    ρv = ρ * v\n    ρw = ρ * w\n\n    # Compute energy contributions\n    e_kin = FT(1 // 2) * (u^2 + v^2 + w^2)\n    e_pot = _grav * z\n    ρe_tot = ρ * total_energy(e_kin, e_pot, TS)\n\n    # Assign initial conditions for prognostic state variables\n    state.ρ = ρ\n    state.ρu = SVector(ρu, ρv, ρw)\n    state.energy.ρe = ρe_tot\n    add_perturbations!(state, localgeo)\n    init_state_prognostic!(turbconv_model(bl), bl, state, aux, localgeo, t)\nend\n\nfunction ekman_layer_model(\n    ::Type{FT},\n    config_type,\n    zmax,\n    surface_flux;\n    turbulence = ConstantKinematicViscosity(FT(0.1)),\n    turbconv = NoTurbConv(),\n    compressibility = Compressible(),\n    ref_state = HydrostaticState(DryAdiabaticProfile{FT}(param_set),),\n) where {FT}\n\n    ics = init_problem!     # Initial conditions\n\n    C_drag_::FT = C_drag(param_set) # FT(0.001)    # Momentum exchange coefficient\n    u_star = FT(0.30)\n    z_0 = FT(0.1)          # Roughness height\n\n    z_sponge = FT(300)     # Start of sponge layer\n    α_max = FT(0.75)       # Strength of sponge layer (timescale)\n    γ = 2                  # Strength of sponge layer (exponent)\n\n    u_geostrophic = FT(1)        # Eastward relaxation speed\n    u_slope = FT(0)              # Slope of altitude-dependent relaxation speed\n    v_geostrophic = FT(0)        # Northward relaxation speed\n    f_coriolis = FT(1.39e-4) # Coriolis parameter at 73N\n\n    q_sfc = FT(0)\n    θ_sfc = T_surf_ref(param_set)\n    g = compressibility isa Compressible ? (Gravity(),) : ()\n\n    # Assemble source components\n    source_default = (\n        g...,\n        EkmanLayerSponge{FT}(\n            zmax,\n            z_sponge,\n            α_max,\n            γ,\n            u_geostrophic,\n            u_slope,\n            v_geostrophic,\n        ),\n        EkmanLayerGeostrophic(\n            f_coriolis,\n            u_geostrophic,\n            u_slope,\n            v_geostrophic,\n        ),\n        turbconv_sources(turbconv)...,\n    )\n    source = source_default\n\n    # Set up problem initial and boundary conditions\n    if surface_flux == \"prescribed\"\n        energy_bc = PrescribedEnergyFlux((state, aux, t) -> FT(0))\n    elseif surface_flux == \"bulk\"\n        energy_bc = BulkFormulaEnergy(\n            (bl, state, aux, t, normPu_int) -> C_drag_,\n            (bl, state, aux, t) -> (θ_sfc, q_sfc),\n        )\n    elseif surface_flux == \"custom_sbl\"\n        energy_bc = PrescribedTemperature((state, aux, t) -> θ_sfc)\n    elseif surface_flux == \"Nishizawa2018\"\n        energy_bc = NishizawaEnergyFlux(\n            (bl, state, aux, t, normPu_int) -> z_0,\n            (bl, state, aux, t) -> (θ_sfc, q_sfc),\n        )\n    else\n        @warn @sprintf(\n            \"\"\"\n%s: unrecognized surface flux; using 'prescribed'\"\"\",\n            surface_flux,\n        )\n    end\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = turbulence,\n        moisture = DryModel(),\n        turbconv = turbconv,\n        compressibility = compressibility,\n    )\n\n    moisture_bcs = ()\n    boundary_conditions = (\n        AtmosBC(\n            physics;\n            momentum = Impenetrable(DragLaw(\n                # normPu_int is the internal horizontal speed\n                # P represents the projection onto the horizontal\n                (state, aux, t, normPu_int) -> (u_star / normPu_int)^2,\n            )),\n            energy = energy_bc,\n            moisture_bcs...,\n            turbconv = turbconv_bcs(turbconv)[1],\n        ),\n        AtmosBC(physics; turbconv = turbconv_bcs(turbconv)[2]),\n    )\n\n    problem = AtmosProblem(\n        init_state_prognostic = ics,\n        boundaryconditions = boundary_conditions,\n    )\n\n    # Assemble model components\n    model =\n        AtmosModel{FT}(config_type, physics; problem = problem, source = source)\n\n    return model\nend\n\nfunction config_diagnostics(driver_config)\n    default_dgngrp = setup_atmos_default_diagnostics(\n        AtmosLESConfigType(),\n        \"60ssecs\",\n        driver_config.name,\n    )\n    core_dgngrp = setup_atmos_core_diagnostics(\n        AtmosLESConfigType(),\n        \"60ssecs\",\n        driver_config.name,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([\n        default_dgngrp,\n        core_dgngrp,\n    ])\nend\n"
  },
  {
    "path": "experiments/AtmosLES/rising_bubble_bryan.jl",
    "content": "using ClimateMachine\n\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.Mesh.Filters\nusing Thermodynamics\nusing Thermodynamics.TemperatureProfiles\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.NumericalFluxes\nusing ClimateMachine.VTK\n\nusing StaticArrays\nusing Test\nusing Printf\nusing MPI\nusing ArgParse\n\nusing CLIMAParameters\nusing CLIMAParameters.Atmos.SubgridScale: C_smag\nusing CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n# ------------------------ Description ------------------------- #\n# 1) Dry Rising Bubble (circular potential temperature perturbation)\n# 2) Boundaries - `All Walls` : Impenetrable(FreeSlip())\n#                               Laterally periodic\n# 3) Domain - 20000m[horizontal] x 10000m[vertical] (2-dimensional)\n# 4) Timeend - 1000s\n# 5) Mesh Aspect Ratio (Effective resolution) 2:1\n# 7) Overrides defaults for\n#               `init_on_cpu`\n#               `solver_type`\n#               `sources`\n#               `C_smag`\n# 8) Default settings can be found in `src/Driver/Configurations.jl`\n# ------------------------ Description ------------------------- #\nfunction init_risingbubble!(problem, bl, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n    R_gas::FT = R_d(param_set)\n    c_p::FT = cp_d(param_set)\n    c_v::FT = cv_d(param_set)\n    γ::FT = c_p / c_v\n    p0::FT = MSLP(param_set)\n    _grav::FT = grav(param_set)\n\n    xc::FT = 10000\n    zc::FT = 2000\n    r = sqrt((x - xc)^2 + (z - zc)^2)\n    rc::FT = 2000\n    θ_ref::FT = 300\n    Δθ::FT = 0\n\n    if r <= rc\n        Δθ = FT(2) * cospi(0.5 * r / rc)^2\n    end\n\n    # Perturbed state:\n    θ = θ_ref + Δθ # potential temperature\n    π_exner = FT(1) - _grav / (c_p * θ) * z # exner pressure\n    ρ = p0 / (R_gas * θ) * (π_exner)^(c_v / R_gas) # density\n    q_tot = FT(0)\n    ts = PhaseEquil_ρθq(param_set, ρ, θ, q_tot)\n    q_pt = PhasePartition(ts)\n\n    ρu = SVector(FT(0), FT(0), FT(0))\n\n    # State (prognostic) variable assignment\n    e_kin = FT(0)\n    e_pot = gravitational_potential(bl.orientation, aux)\n    ρe_tot = ρ * total_energy(e_kin, e_pot, ts)\n    state.ρ = ρ\n    state.ρu = ρu\n    state.energy.ρe = ρe_tot\n    state.moisture.ρq_tot = ρ * q_pt.tot\nend\n\nfunction config_risingbubble(FT, N, resolution, xmax, ymax, zmax, fast_method)\n\n    # Choose fast solver\n    if fast_method == \"LowStorageRungeKutta2N\"\n        ode_solver = ClimateMachine.MISSolverType(\n            splitting_type = ClimateMachine.SlowFastSplitting(),\n            fast_model = AtmosAcousticGravityLinearModel,\n            mis_method = MIS2,\n            fast_method = LSRK54CarpenterKennedy,\n            nsubsteps = (50,),\n        )\n    elseif fast_method == \"StrongStabilityPreservingRungeKutta\"\n        ode_solver = ClimateMachine.MISSolverType(\n            splitting_type = ClimateMachine.SlowFastSplitting(),\n            fast_model = AtmosAcousticGravityLinearModel,\n            mis_method = MIS2,\n            fast_method = SSPRK33ShuOsher,\n            nsubsteps = (12,),\n        )\n    elseif fast_method == \"MultirateInfinitesimalStep\"\n        ode_solver = ClimateMachine.MISSolverType(\n            splitting_type = ClimateMachine.HEVISplitting(),\n            fast_model = AtmosAcousticGravityLinearModel,\n            mis_method = MIS2,\n            fast_method = (dg, Q, nsubsteps) -> MultirateInfinitesimalStep(\n                MISKWRK43,\n                dg,\n                (dgi, Qi) -> LSRK54CarpenterKennedy(dgi, Qi),\n                Q,\n                nsubsteps = nsubsteps,\n            ),\n            nsubsteps = (12, 2),\n        )\n    elseif fast_method == \"MultirateRungeKutta\"\n        ode_solver = ClimateMachine.MISSolverType(\n            splitting_type = ClimateMachine.HEVISplitting(),\n            fast_model = AtmosAcousticGravityLinearModel,\n            mis_method = MIS2,\n            fast_method = (dg, Q, nsubsteps) -> MultirateRungeKutta(\n                LSRK144NiegemannDiehlBusch,\n                dg,\n                Q,\n                steps = nsubsteps,\n            ),\n            nsubsteps = (12, 4),\n        )\n    elseif fast_method == \"AdditiveRungeKutta\"\n        ode_solver = ClimateMachine.MISSolverType(\n            splitting_type = ClimateMachine.HEVISplitting(),\n            fast_model = AtmosAcousticGravityLinearModel,\n            mis_method = MISRK3,\n            fast_method = (dg, Q, dt, nsubsteps) -> AdditiveRungeKutta(\n                ARK548L2SA2KennedyCarpenter,\n                dg,\n                LinearBackwardEulerSolver(ManyColumnLU(), isadjustable = true),\n                Q,\n                dt = dt,\n                nsubsteps = nsubsteps,\n            ),\n            nsubsteps = (12,),\n        )\n    else\n        error(\"Invalid --fast_method=$fast_method\")\n    end\n\n    # Set up the model\n    C_smag = FT(0.23)\n    ref_state =\n        HydrostaticState(DryAdiabaticProfile{FT}(param_set, FT(300), FT(0)))\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        turbulence = SmagorinskyLilly{FT}(C_smag),\n        ref_state = ref_state,\n    )\n\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        source = (Gravity(),),\n        init_state_prognostic = init_risingbubble!,\n    )\n\n    # Problem configuration\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"DryRisingBubbleMIS\",\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        init_risingbubble!,\n        model = model,\n    )\n    return config, ode_solver\nend\n\nfunction config_diagnostics(driver_config)\n    interval = \"10000steps\"\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosLESConfigType(),\n        interval,\n        driver_config.name,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp])\nend\n\nfunction main()\n\n    rbb_args = ArgParseSettings(autofix_names = true)\n    add_arg_group!(rbb_args, \"RisingBubbleBryan\")\n    @add_arg_table! rbb_args begin\n        \"--fast_method\"\n        help = \"Choice of fast solver for the MIS method\"\n        metavar = \"<name>\"\n        arg_type = String\n        default = \"AdditiveRungeKutta\"\n    end\n\n    cl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = rbb_args)\n    fast_method = cl_args[\"fast_method\"]\n\n    # Working precision\n    FT = Float64\n    # DG polynomial order\n    N = 3\n    # Domain resolution and size\n    Δx = FT(125)\n    Δy = FT(125)\n    Δz = FT(125)\n    resolution = (Δx, Δy, Δz)\n    # Domain extents\n    xmax = FT(20000)\n    ymax = FT(1000)\n    zmax = FT(10000)\n    # Simulation time\n    t0 = FT(0)\n    timeend = FT(20)\n\n    # Time-step size (s)\n    Δt = FT(0.4)\n\n    driver_config, ode_solver_type =\n        config_risingbubble(FT, N, resolution, xmax, ymax, zmax, fast_method)\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        ode_dt = Δt,\n    )\n    dgn_config = config_diagnostics(driver_config)\n\n    # Invoke solver (calls solve! function for time-integrator)\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        check_euclidean_distance = true,\n    )\n\n    @test isapprox(result, FT(1); atol = 1.5e-3)\nend\n\nmain()\n"
  },
  {
    "path": "experiments/AtmosLES/rising_bubble_theta_formulation.jl",
    "content": "# # Rising Thermal Bubble\n#\n# In this example, we demonstrate the usage of the `ClimateMachine`\n# [AtmosModel](@ref AtmosModel-docs) machinery to solve the fluid\n# dynamics of a thermal perturbation in a neutrally stratified background state\n# defined by its uniform potential temperature. We solve a flow in a box configuration -\n# this is representative of a large-eddy simulation. Several versions of the problem\n# setup may be found in literature, but the general idea is to examine the\n# vertical ascent of a thermal bubble (we can interpret these as simple\n# representation of convective updrafts).\n#\n# ## Description of experiment\n# 1) Dry Rising Bubble (circular potential temperature perturbation)\n# 2) Boundaries\n#    Top and Bottom boundaries:\n#    - `Impenetrable(FreeSlip())` - Top and bottom: no momentum flux, no mass flux through\n#      walls.\n#    - `Impermeable()` - non-porous walls, i.e. no diffusive fluxes through\n#       walls.\n#    Lateral boundaries\n#    - Laterally periodic\n# 3) Domain - 2500m (horizontal) x 2500m (horizontal) x 2500m (vertical)\n# 4) Resolution - 50m effective resolution\n# 5) Total simulation time - 1000s\n# 6) Mesh Aspect Ratio (Effective resolution) 1:1\n# 7) Overrides defaults for\n#    - CPU Initialisation\n#    - Time integrator\n#    - Sources\n#    - Smagorinsky Coefficient\n\n#md # !!! note\n#md #     This experiment setup assumes that you have installed the\n#md #     `ClimateMachine` according to the instructions on the landing page.\n#md #     We assume the users' familiarity with the conservative form of the\n#md #     equations of motion for a compressible fluid (see the\n#md #     [AtmosModel](@ref AtmosModel-docs) page).\n#md #\n#md #     The following topics are covered in this example\n#md #     - Package requirements\n#md #     - Defining a `model` subtype for the set of conservation equations\n#md #     - Defining the initial conditions\n#md #     - Applying source terms\n#md #     - Choosing a turbulence model\n#md #     - Adding tracers to the model\n#md #     - Choosing a time-integrator\n#md #     - Choosing diagnostics (output) configurations\n#md #\n#md #     The following topics are not covered in this example\n#md #     - Defining new boundary conditions\n#md #     - Defining new turbulence models\n#md #     - Building new time-integrators\n#md #     - Adding diagnostic variables (beyond a standard pre-defined list of\n#md #       variables)\n\n# ## [Loading code](@id Loading-code-rtb)\n\n# Before setting up our experiment, we recognize that we need to import some\n# pre-defined functions from other packages. Julia allows us to use existing\n# modules (variable workspaces), or write our own to do so.  Complete\n# documentation for the Julia module system can be found\n# [here](https://docs.julialang.org/en/v1/manual/modules/#).\n\n# We need to use the `ClimateMachine` module! This imports all functions\n# specific to atmospheric and ocean flow modeling.\n\nusing ClimateMachine\nClimateMachine.init(; parse_clargs = true)\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Mesh.Geometry\nusing ClimateMachine.Mesh.Grids\n# In ClimateMachine we use `StaticArrays` for our variable arrays.\n# We also use the `Test` package to help with unit tests and continuous\n# integration systems to design sensible tests for our experiment to ensure new\n# / modified blocks of code don't damage the fidelity of the physics. The test\n# defined within this experiment is not a unit test for a specific\n# subcomponent, but ensures time-integration of the defined problem conditions\n# within a reasonable tolerance. Immediately useful macros and functions from\n# this include `@test` and `@testset` which will allow us to define the testing\n# parameter sets.\nusing StaticArrays\nusing Test\nusing CLIMAParameters\nusing CLIMAParameters.Atmos.SubgridScale: C_smag\nusing CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet();\n\n# ## [Initial Conditions](@id init-rtb)\n# This example demonstrates the use of functions defined\n# in the [`Thermodynamics`](@ref Thermodynamics) package to\n# generate the appropriate initial state for our problem.\n\n#md # !!! note\n#md #     The following variables are assigned in the initial condition\n#md #     - `state.ρ` = Scalar quantity for initial density profile\n#md #     - `state.ρu`= 3-component vector for initial momentum profile\n#md #     - `state.energy.ρe`= Scalar quantity for initial total-energy profile\n#md #       humidity\n#md #     - `state.tracers.ρχ` = Vector of four tracers (here, for demonstration\n#md #       only; we can interpret these as dye injections for visualization\n#md #       purposes)\nfunction init_risingbubble!(problem, bl, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n\n    ## Problem float-type\n    FT = eltype(state)\n\n    ## Unpack constant parameters\n    param_set = parameter_set(bl)\n    R_gas::FT = R_d(param_set)\n    c_p::FT = cp_d(param_set)\n    c_v::FT = cv_d(param_set)\n    p0::FT = MSLP(param_set)\n    _grav::FT = grav(param_set)\n    γ::FT = c_p / c_v\n\n    ## Define bubble center and background potential temperature\n    xc::FT = 5000\n    yc::FT = 1000\n    zc::FT = 2000\n    r = sqrt((x - xc)^2 + (z - zc)^2)\n    rc::FT = 2000\n    θamplitude::FT = 2\n\n    ## This is configured in the reference hydrostatic state\n    θ_ref::FT = 300\n\n    ## Add the thermal perturbation:\n    Δθ::FT = 0\n    if r <= rc\n        Δθ = θamplitude * (1.0 - r / rc)\n    end\n\n    ## Compute perturbed thermodynamic state:\n    θ = θ_ref + Δθ                                      ## potential temperature\n    π_exner = FT(1) - _grav / (c_p * θ) * z             ## exner pressure\n    ρ = p0 / (R_gas * θ) * (π_exner)^(c_v / R_gas)      ## density\n    T = θ * π_exner\n    e_int = internal_energy(param_set, T)\n    ts = PhaseDry(param_set, e_int, ρ)\n    ρu = SVector(FT(0), FT(0), FT(0))                   ## momentum\n    ## State (prognostic) variable assignment\n    e_kin = FT(0)                                       ## kinetic energy\n    e_pot = gravitational_potential(bl, aux)            ## potential energy\n    ρe_tot = ρ * total_energy(e_kin, e_pot, ts)         ## total energy\n\n    ρχ = FT(0)                                          ## tracer\n\n    ## We inject tracers at the initial condition at some specified z coordinates\n    if 500 < z <= 550\n        ρχ += FT(0.05)\n    end\n\n    ## We want 4 tracers\n    ntracers = 4\n\n    ## Define 4 tracers, (arbitrary scaling for this demo problem)\n    ρχ = SVector{ntracers, FT}(ρχ, ρχ / 2, ρχ / 3, ρχ / 4)\n\n    ## Assign State Variables\n    state.ρ = ρ\n    state.ρu = ρu\n    state.energy.ρθ_liq_ice = ρ * θ\n    state.tracers.ρχ = ρχ\nend\n\n# ## [Model Configuration](@id config-helper)\n# We define a configuration function to assist in prescribing the physical\n# model. The purpose of this is to populate the\n# `ClimateMachine.AtmosLESConfiguration` with arguments\n# appropriate to the problem being considered.\nfunction config_risingbubble(\n    ::Type{FT},\n    N,\n    resolution,\n    xmax,\n    ymax,\n    zmax,\n) where {FT}\n    ## Since we want four tracers, we specify this and include the appropriate\n    ## diffusivity scaling coefficients (normally these would be physically\n    ## informed but for this demonstration we use integers corresponding to the\n    ## tracer index identifier)\n    ntracers = 4\n    δ_χ = SVector{ntracers, FT}(1, 2, 3, 4)\n    ## To assemble `AtmosModel` with no tracers, set `tracers = NoTracers()`.\n\n    ## The model coefficient for the turbulence closure is defined via the\n    ## [CLIMAParameters\n    ## package](https://CliMA.github.io/CLIMAParameters.jl/latest/) A reference\n    ## state for the linearisation step is also defined.\n    T_surface = FT(300)\n    T_min_ref = FT(0)\n    T_profile = DryAdiabaticProfile{FT}(param_set, T_surface, T_min_ref)\n\n    ## Here we define the physics:\n    _C_smag = FT(0)\n    physics = AtmosPhysics{FT}(\n        param_set;                                     ## Parameter set corresponding to earth parameters\n        ref_state = NoReferenceState(),                ## Reference state\n        energy = θModel(),                             ## Energy model\n        turbulence = SmagorinskyLilly(_C_smag),        ## Turbulence closure model\n        moisture = DryModel(),                         ## Exclude moisture variables\n        tracers = NTracers{ntracers, FT}(δ_χ),         ## Tracer model with diffusivity coefficients\n    )\n\n    problem = AtmosProblem(\n        boundaryconditions = (\n            AtmosBC(\n                physics;\n                momentum = Impenetrable(FreeSlip()),\n                energy = Adiabaticθ((state, aux, t) -> FT(0)),\n            ),\n            AtmosBC(\n                physics;\n                momentum = Impenetrable(FreeSlip()),\n                energy = Adiabaticθ((state, aux, t) -> FT(0)),\n            ),\n        ),\n        init_state_prognostic = init_risingbubble!,\n    )\n\n    ## Here we assemble the `AtmosModel`.\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,                            ## Flow in a box, requires the AtmosLESConfigType\n        physics;                                       ## Atmos physics\n        init_state_prognostic = init_risingbubble!,    ## Apply the initial condition\n        problem = problem,                             ## Problem (boundary conditions)\n        source = (Gravity(),),                         ## Gravity is the only source term here\n    )\n\n    ## Finally, we pass a `Problem Name` string, the mesh information, and the\n    ## model type to  the [`AtmosLESConfiguration`] object.\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"DryRisingBubble\",       ## Problem title [String]\n        N,                       ## Polynomial order [Int]\n        resolution,              ## (Δx, Δy, Δz) effective resolution [m]\n        xmax,                    ## Domain maximum size [m]\n        ymax,                    ## Domain maximum size [m]\n        zmax,                    ## Domain maximum size [m]\n        param_set,               ## Parameter set.\n        init_risingbubble!,      ## Function specifying initial condition\n        model = model,           ## Model type\n    )\n    return config\nend\n\n#md # !!! note\n#md #     `Keywords` are used to specify some arguments (see appropriate source\n#md #     files).\n\n# ## [Diagnostics](@id config_diagnostics)\n# Here we define the diagnostic configuration specific to this problem.\nfunction config_diagnostics(driver_config)\n    interval = \"10000steps\"\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosLESConfigType(),\n        interval,\n        driver_config.name,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp])\nend\n\nfunction main()\n    ## These are essentially arguments passed to the\n    ## [`config_risingbubble`](@ref config-helper) function.  For type\n    ## consistency we explicitly define the problem floating-precision.\n    FT = Float64\n    ## We need to specify the polynomial order for the DG discretization,\n    ## effective resolution, simulation end-time, the domain bounds, and the\n    ## courant-number for the time-integrator. Note how the time-integration\n    ## components `solver_config` are distinct from the spatial / model\n    ## components in `driver_config`. `init_on_cpu` is a helper keyword argument\n    ## that forces problem initialization on CPU (thereby allowing the use of\n    ## random seeds, spline interpolants and other special functions at the\n    ## initialization step.)\n    N = 4\n    Δh = FT(125)\n    Δv = FT(125)\n    resolution = (Δh, Δh, Δv)\n    xmax = FT(10000)\n    ymax = FT(500)\n    zmax = FT(10000)\n    t0 = FT(0)\n    timeend = FT(1000)\n    ## For full simulation set `timeend = 1000`\n\n    ## Use up to 1.7 if ode_solver is the single rate LSRK144.\n    CFL = FT(1.7)\n\n    ## Assign configurations so they can be passed to the `invoke!` function\n    driver_config = config_risingbubble(FT, N, resolution, xmax, ymax, zmax)\n\n    ## Choose an Explicit Single-rate Solver from the existing [`ODESolvers`](@ref ClimateMachine.ODESolvers) options.\n    ## Apply the outer constructor to define the `ode_solver`.\n    ## The 1D-IMEX method is less appropriate for the problem given the current\n    ## mesh aspect ratio (1:1).\n\n    ## ode_solver = ClimateMachine.IMEXSolverType()\n    ## If the user prefers a multi-rate explicit time integrator,\n    ## the ode_solver above can be replaced with\n    ##\n    ## `ode_solver = ClimateMachine.MultirateSolverType(\n    ##    fast_model = AtmosAcousticGravityLinearModel,\n    ##    slow_method = LSRK144NiegemannDiehlBusch,\n    ##    fast_method = LSRK144NiegemannDiehlBusch,\n    ##    timestep_ratio = 10,\n    ## )`\n    ## See [ODESolvers](@ref ODESolvers-docs) for all of the available solvers.\n\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK144NiegemannDiehlBusch,\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        init_on_cpu = true,\n        ode_solver_type = ode_solver_type,\n        Courant_number = CFL,\n        CFL_direction = HorizontalDirection(),\n    )\n    dgn_config = config_diagnostics(driver_config)\n\n    ## Invoke solver (calls `solve!` function for time-integrator), pass the driver,\n    ## solver and diagnostic config information.\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (),\n        check_euclidean_distance = true,\n    )\n\n    ## Check that the solution norm is reasonable.\n    @test isapprox(result, FT(1); atol = 1.5e-3)\nend\n\n# The experiment definition is now complete. Time to run it.\n\n# ## Running the file\n# `julia --project tutorials/Atmos/risingbubble.jl` will run the\n# experiment from the main ClimateMachine.jl directory, with diagnostics output\n# at the intervals specified in [`config_diagnostics`](@ref\n# config_diagnostics).  You can also prescribe command line arguments for\n# simulation update and output specifications.  For\n# rapid turnaround, we recommend that you run this experiment on a GPU.\n\n# VTK output can be controlled via command line by\n# setting `parse_clargs=true` in the `ClimateMachine.init`\n# arguments, and then using `--vtk=<interval>`.\n\n# ## [Output Visualisation](@id output-viz)\n# See the `ClimateMachine` API interface documentation\n# for generating output.\n#\n#\n# - [VisIt](https://wci.llnl.gov/simulation/computer-codes/visit/)\n# - [Paraview](https://wci.llnl.gov/simulation/computer-codes/visit/)\n# are two commonly used programs for `.vtu` files.\n#\n# For NetCDF or JLD2 diagnostics you may use any of the following tools:\n# Julia's\n# [`NCDatasets`](https://github.com/Alexander-Barth/NCDatasets.jl) and\n# [`JLD2`](https://github.com/JuliaIO/JLD2.jl) packages with a suitable\n#\n# or the known and quick NCDF visualization tool:\n# [`ncview`](http://meteora.ucsd.edu/~pierce/ncview_home_page.html)\n# plotting program.\n\nmain()\n"
  },
  {
    "path": "experiments/AtmosLES/schar_scalar_advection.jl",
    "content": "using ClimateMachine\nClimateMachine.init(parse_clargs = true)\n\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing StaticArrays\nusing Test\n\nusing CLIMAParameters\nusing CLIMAParameters.Atmos.SubgridScale: C_smag\nusing CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n### Citation\n# [Schar2002](@cite)\n\n# ## [Initial Conditions]\nfunction init_schar!(problem, bl, state, aux, localgeo, t)\n    ## Problem float-type\n    FT = eltype(state)\n\n    (x, y, z) = localgeo.coord\n\n    ## Unpack constant parameters\n    param_set = parameter_set(bl)\n    R_gas::FT = R_d(param_set)\n    c_p::FT = cp_d(param_set)\n    c_v::FT = cv_d(param_set)\n    p0::FT = MSLP(param_set)\n    _grav::FT = grav(param_set)\n    γ::FT = c_p / c_v\n\n    c::FT = c_v / R_gas\n    c2::FT = R_gas / c_p\n\n    Tiso::FT = 250.0\n    θ0::FT = Tiso\n\n    ## Calculate the Brunt-Vaisaila frequency for an isothermal field\n    ## Hydrostatic background state\n    Brunt::FT = _grav / sqrt(c_p * Tiso)\n    Brunt2::FT = Brunt * Brunt\n    g2::FT = _grav * _grav\n\n    π_exner::FT = exp(-_grav * z / (c_p * Tiso))\n    θ::FT = θ0 * exp(Brunt2 * z / _grav)\n    ρ::FT = p0 / (R_gas * θ) * (π_exner)^c\n\n    ## Compute perturbed thermodynamic state:\n    T = θ * π_exner\n    e_int = internal_energy(param_set, T)\n    ts = PhaseDry(param_set, e_int, ρ)\n\n    ## Initial velocity\n    z₁::FT = 4000\n    z₂::FT = 5000\n    u₀::FT = 10\n    zscale = (z - z₁) / (z₂ - z₁)\n    if z₂ <= z\n        u = FT(1)\n    elseif z₁ <= z < z₂\n        u = (sinpi(zscale / 2))^2\n    elseif z <= z₁\n        u = FT(0)\n    end\n    u *= u₀\n\n    ## Initial scalar anomaly profile\n    ## Equivalent to a nondiffusive tracer\n    Ax::FT = 25000\n    Az::FT = 3000\n    x₀::FT = 25000\n    z₀::FT = 9000\n    r = ((x - x₀) / Ax)^2 + ((z - z₀) / Az)^2\n    if r <= 1\n        χ = (cospi(r / 2))^2\n    else\n        χ = 0\n    end\n\n    ## State (prognostic) variable assignment\n    e_kin = FT(1 / 2) * u^2                               # kinetic energy\n    e_pot = gravitational_potential(bl.orientation, aux)# potential energy\n    ρe_tot = ρ * total_energy(e_kin, e_pot, ts)         # total energy\n\n    state.ρ = ρ\n    state.ρu = SVector{3, FT}(ρ * u, 0, 0)\n    state.energy.ρe = ρe_tot\n    state.tracers.ρχ = ρ * SVector{1, FT}(χ)\nend\n\n# Define a `setmax` method\nfunction setmax(f, xmax, ymax, zmax)\n    function setmaxima(xin, yin, zin)\n        return f(xin, yin, zin; xmax = xmax, ymax = ymax, zmax = zmax)\n    end\n    return setmaxima\nend\n\nfunction warp_schar(\n    xin,\n    yin,\n    zin;\n    xmax = 150000.0,\n    ymax = 5000.0,\n    zmax = 25000.0,\n)\n    FT = eltype(xin)\n    a::FT = 25000 ## Half-width parameter [m]\n    r = sqrt(xin^2 + yin^2)\n    h₀::FT = 3000 ## Peak height [m]\n    λ::FT = 8000 ## Wavelength\n    h_star =\n        abs(xin - xmax / 2) <= a ? h₀ * (cospi((xin - xmax / 2) / 2a))^2 : FT(0)\n    h = h_star * (cospi((xin - xmax / 2) / λ))^2\n    x, y, z = xin, yin, zin + h * (zmax - zin) / zmax\n    return x, y, z\nend\n\nfunction config_schar(FT, N, resolution, xmax, ymax, zmax)\n    u_relaxation = SVector(FT(10), FT(0), FT(0))\n\n    ## Wave damping coefficient (1/s)\n    sponge_ampz = FT(0.5)\n\n    ## Vertical level where the absorbing layer starts\n    z_s = FT(20000.0)\n\n    ## Pass the sponge parameters to the sponge calculator\n    rayleigh_sponge =\n        RayleighSponge{FT}(zmax, z_s, sponge_ampz, u_relaxation, 2)\n\n    ## Setup the source terms for this problem:\n    source = (Gravity(), rayleigh_sponge)\n\n    ## Define the reference state:\n    T_virt = FT(250)\n    temp_profile_ref = IsothermalProfile(param_set, T_virt)\n    ref_state = HydrostaticState(temp_profile_ref)\n    nothing # hide\n\n    # Define a warping function to build an analytic topography:\n\n    _C_smag = FT(0.21)\n    _δχ = SVector{1, FT}(0)\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = Vreman(_C_smag),\n        moisture = DryModel(),\n        tracers = NTracers{1, FT}(_δχ),\n    )\n\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        init_state_prognostic = init_schar!,\n        source = source,\n    )\n\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"ScharScalarAdvection\",  # Problem title [String]\n        N,                       # Polynomial order [Int]\n        resolution,              # (Δx, Δy, Δz) effective resolution [m]\n        xmax,                    # Domain maximum size [m]\n        ymax,                    # Domain maximum size [m]\n        zmax,                    # Domain maximum size [m]\n        param_set,               # Parameter set.\n        init_schar!,             # Function specifying initial condition\n        model = model,           # Model type\n        meshwarp = setmax(warp_schar, xmax, ymax, zmax),\n    )\n\n    return config\nend\n\n# Define a `main` method (entry point)\nfunction main()\n\n    FT = Float64\n\n    ## Define the polynomial order and effective grid spacings:\n    N = 4\n\n    ## Define the domain size and spatial resolution\n    xmax = FT(150000)\n    ymax = FT(2500)\n    zmax = FT(25000)\n    Δx = FT(500)\n    Δy = FT(500)\n    Δz = FT(500)\n    resolution = (Δx, Δy, Δz)\n\n    t0 = FT(0)\n    timeend = FT(10000)\n\n    ## Define the max Courant for the time time integrator (ode_solver).\n    ## The default value is 1.7 for LSRK144:\n    CFL = FT(1.5)\n\n    ## Assign configurations so they can be passed to the `invoke!` function\n    driver_config = config_schar(FT, N, resolution, xmax, ymax, zmax)\n\n    ## Define the time integrator:\n    ## We chose an explicit single-rate LSRK144 for this problem\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK144NiegemannDiehlBusch,\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFL,\n    )\n\n    ## State Conservation Callback\n    # State variable\n    Q = solver_config.Q\n    # Volume geometry information\n    vgeo = driver_config.grid.vgeo\n    M = vgeo[:, Grids._M, :]\n    # Unpack prognostic vars\n    ρχ₀ = Q[:, 6, :]\n    # DG variable sums\n    Σρχ₀ = sum(ρχ₀ .* M)\n    cb_check_tracer = GenericCallbacks.EveryXSimulationSteps(1000) do\n        Q = solver_config.Q\n        δρχ = (sum(Q[:, 6, :] .* M) .- Σρχ₀) ./ Σρχ₀\n        @show (abs(δρχ))\n        nothing\n    end\n\n    ## Set up the spectral filter to remove the solutions spurious modes\n    ## Define the order of the exponential filter: use 32 or 64 for this problem.\n    ## The larger the value, the less dissipation you get:\n    filterorder = 64\n    filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)\n    cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            AtmosFilterPerturbations(driver_config.bl),\n            solver_config.dg.grid,\n            filter,\n            state_auxiliary = solver_config.dg.state_auxiliary,\n        )\n        nothing\n    end\n    ## End exponential filter\n\n    ## Invoke solver (calls `solve!` function for time-integrator),\n    ## pass the driver, solver and diagnostic config information.\n    result = ClimateMachine.invoke!(\n        solver_config;\n        user_callbacks = (cbfilter, cb_check_tracer),\n        check_euclidean_distance = true,\n    )\n\n    ## Check that the solution norm is reasonable.\n    @test isapprox(result, FT(1); atol = 1.5e-4)\nend\n\n# Call `main`\nmain()\n"
  },
  {
    "path": "experiments/AtmosLES/squall_line.jl",
    "content": "using ArgParse\nusing Random\nusing StaticArrays\nusing NCDatasets\nusing Test\nusing DocStringExtensions\nusing LinearAlgebra\nusing DelimitedFiles\nusing Dierckx\nusing Printf\n\nusing ClimateMachine\nusing ClimateMachine.Atmos\nusing ArtifactWrappers\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Mesh.Filters\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.BalanceLaws: vars_state, Prognostic\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet\nusing CLIMAParameters.Atmos.Microphysics\n\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n\"\"\"\n  Define initial conditions based on sounding data\n\"\"\"\nfunction init_squall_line!(problem, bl, state, aux, localgeo, t, args...)\n\n    FT = eltype(state)\n\n    spl_tinit, spl_qinit, spl_uinit, spl_vinit, spl_pinit = args[1]\n    # interpolate data\n    (x, y, z) = localgeo.coord\n    data_t = FT(spl_tinit(z))\n    data_q = FT(spl_qinit(z))\n    data_u = FT(spl_uinit(z))\n    data_v = FT(spl_vinit(z))\n    data_p = FT(spl_pinit(z))\n    u = data_u\n    v = data_v\n    w = FT(0)\n    if z >= 14000\n        data_q = FT(0)\n    end\n    θ_c = 3.0\n    rx = 10000.0\n    ry = 1500.0\n    rz = 1500.0\n    xc = 0.5 * (FT(0) + FT(0))\n    yc = 0.5 * (FT(0) + FT(5000))\n    zc = 2000.0\n    cylinder_flg = 0.0\n    r = sqrt(\n        (x - xc)^2 / rx^2 +\n        cylinder_flg * (y - yc)^2 / ry^2 +\n        (z - zc)^2 / rz^2,\n    )\n    Δθ = 0.0\n    if r <= 1.0\n        Δθ = θ_c * (cospi(0.5 * r))^2\n    end\n    θ_liq = data_t + Δθ\n    ts = PhaseNonEquil_pθq(param_set, data_p, θ_liq, PhasePartition(FT(data_q)))\n    T = air_temperature(ts)\n    ρ = air_density(ts)\n    e_kin = FT(1 / 2) * FT((u^2 + v^2 + w^2))\n    e_pot = gravitational_potential(bl.orientation, aux)\n    E = ρ * total_energy(e_kin, e_pot, ts)\n\n    state.ρ = ρ\n    state.ρu = SVector(ρ * u, ρ * v, FT(0))\n    state.energy.ρe = E\n\n    state.moisture.ρq_tot = ρ * data_q\n    if moisture_model(bl) isa NonEquilMoist\n        state.moisture.ρq_liq = FT(0)\n        state.moisture.ρq_ice = FT(0)\n    end\n\n    if precipitation_model(bl) isa RainModel\n        state.precipitation.ρq_rai = FT(0)\n    end\n    if precipitation_model(bl) isa RainSnowModel\n        state.precipitation.ρq_rai = FT(0)\n        state.precipitation.ρq_sno = FT(0)\n    end\n\n    return nothing\nend\n\n\"\"\"\n  Read the original squall sounding\n\"\"\"\nfunction read_sounding()\n\n    # Artifact creation is not thread-safe\n    #      https://github.com/JuliaLang/Pkg.jl/issues/1219\n    # To avoid race conditions from multiple jobs running this\n    # driver at the same time, we must store artifacts in a\n    # separate folder.\n\n    soundings_dataset = ArtifactWrapper(\n        @__DIR__,\n        isempty(get(ENV, \"CI\", \"\")),\n        \"soundings\",\n        ArtifactFile[ArtifactFile(\n            url = \"https://caltech.box.com/shared/static/rjnvt2dlw7etm1c7mmdfrkw5gnfds5lx.nc\",\n            filename = \"sounding_gabersek.nc\",\n        ),],\n    )\n    data_folder = get_data_folder(soundings_dataset)\n    fsounding = joinpath(data_folder, \"sounding_gabersek.nc\")\n    sounding = Dataset(fsounding, \"r\")\n    height = sounding[\"height [m]\"][:]\n    θ = sounding[\"theta [K]\"][:]\n    q_vap = sounding[\"qv [g kg⁻¹]\"][:]\n    u = sounding[\"u [m s⁻¹]\"][:]\n    v = sounding[\"v [m s⁻¹]\"][:]\n    p = sounding[\"pressure [Pa]\"][:]\n    close(sounding)\n    return (height = height, θ = θ, q_vap = q_vap, u = u, v = v, p = p)\nend\n\n\"\"\"\n  Get data from interpolated array onto vectors.\n  Accepts data in 6 column format\n\"\"\"\nfunction spline_int()\n\n    nt = read_sounding()\n\n    # WARNING: Not all sounding data is formatted/scaled the same.\n    spl_tinit = Spline1D(nt.height, nt.θ; k = 1)\n    spl_qinit = Spline1D(nt.height, nt.q_vap .* 0.001; k = 1)\n    spl_uinit = Spline1D(nt.height, nt.u; k = 1)\n    spl_vinit = Spline1D(nt.height, nt.v; k = 1)\n    spl_pinit = Spline1D(nt.height, nt.p; k = 1)\n    return (\n        t = spl_tinit,\n        q = spl_qinit,\n        u = spl_uinit,\n        v = spl_vinit,\n        p = spl_pinit,\n    )\nend\n\nfunction config_squall_line(\n    FT,\n    N,\n    resolution,\n    xmax,\n    ymax,\n    zmax,\n    xmin,\n    ymin,\n    moisture_model = \"nonequilibrium\",\n    precipitation_model = \"rainsnow\",\n)\n    # Reference state\n    nt = read_sounding()\n\n    zinit = nt.height\n    tinit = nt.θ\n    qinit = nt.q_vap .* 0.001\n    u_init = nt.u\n    v_init = nt.v\n    p_init = nt.p\n\n    maxz = length(zinit)\n    thinit = zeros(maxz)\n    piinit = zeros(maxz)\n    thinit[1] = tinit[1] / (1 + 0.61 * qinit[1])\n    piinit[1] = 1\n    for k in 2:maxz\n        thinit[k] = tinit[k] / (1 + 0.61 * qinit[k])\n        piinit[k] =\n            piinit[k - 1] -\n            9.81 / (1004 * 0.5 * (tinit[k] + tinit[k - 1])) *\n            (zinit[k] - zinit[k - 1])\n    end\n    T_min = FT(thinit[maxz] * piinit[maxz])\n    T_s = FT(thinit[1] * piinit[1])\n    Γ_lapse = FT(9.81 / 1004)\n    tvmax = T_s * (1 + 0.61 * qinit[1])\n    deltatv = -(T_min - tvmax)\n    tvmin = T_min * (1 + 0.61 * qinit[maxz])\n    htv = 8000.0\n    #T = DecayingTemperatureProfile(T_min, T_s, Γ_lapse)\n    Tv = DecayingTemperatureProfile{FT}(param_set, tvmax, tvmin, htv)\n    rel_hum = FT(0)\n    ref_state = HydrostaticState(Tv, rel_hum)\n\n    # Sponge\n    c_sponge = FT(0.5)\n    # Rayleigh damping\n    u_relaxation = SVector(FT(0), FT(0), FT(0))\n    zsponge = FT(15000.0)\n    rayleigh_sponge =\n        RayleighSponge{FT}(zmax, zsponge, c_sponge, u_relaxation, 2)\n\n    # Boundary conditions\n    # SGS Filter constants\n    C_smag = FT(0.18) # 0.21 for stable testing, 0.18 in practice\n    C_drag = FT(0.0011)\n    LHF = FT(50)\n    SHF = FT(10)\n    ics = init_squall_line!\n\n    source = (Gravity(), rayleigh_sponge)\n\n    # moisture model and its sources\n    if moisture_model == \"equilibrium\"\n        moisture = EquilMoist(; maxiter = 20, tolerance = FT(1))\n    elseif moisture_model == \"nonequilibrium\"\n        source = (source..., CreateClouds())\n        moisture = NonEquilMoist()\n    else\n        @warn @sprintf(\n            \"\"\"\n%s: unrecognized moisture_model in source terms, using the defaults\"\"\",\n            moisture_model,\n        )\n        source = (source..., CreateClouds())\n        moisture = NonEquilMoist()\n    end\n\n    # precipitation model and its sources\n    if precipitation_model == \"noprecipitation\"\n        precipitation = NoPrecipitation()\n        source = (source..., RemovePrecipitation(true))\n    elseif precipitation_model == \"rain\"\n        source = (source..., WarmRain_1M())\n        precipitation = RainModel()\n    elseif precipitation_model == \"rainsnow\"\n        source = (source..., RainSnow_1M())\n        precipitation = RainSnowModel()\n    else\n        @warn @sprintf(\n            \"\"\"\n%s: unrecognized precipitation_model in source terms, using the defaults\"\"\",\n            precipitation_model,\n        )\n        source = (source..., RainSnow_1M())\n        precipitation = RainSnowModel()\n    end\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        moisture = moisture,\n        precipitation = precipitation,\n        turbulence = SmagorinskyLilly{FT}(C_smag),\n    )\n\n    problem = AtmosProblem(\n        boundaryconditions = (AtmosBC(physics), AtmosBC(physics)),\n        init_state_prognostic = ics,\n    )\n\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        problem = problem,\n        source = source,\n    )\n\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"Squall_line\",\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        init_squall_line!,\n        xmin = xmin,\n        ymin = ymin,\n        model = model,\n        periodicity = (true, true, false),\n        boundary = ((2, 2), (2, 2), (1, 2)),\n    )\n    return config\nend\n\nfunction config_diagnostics(driver_config, boundaries, resolution)\n\n    interval = \"3600ssecs\"\n\n    dgngrp_profiles = setup_atmos_default_diagnostics(\n        AtmosLESConfigType(),\n        interval,\n        driver_config.name,\n    )\n\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries,\n        resolution,\n    )\n    dgngrp_state = setup_dump_state_diagnostics(\n        AtmosLESConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n    dgngrp_aux = setup_dump_aux_diagnostics(\n        AtmosLESConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n\n    return ClimateMachine.DiagnosticsConfiguration([\n        dgngrp_profiles,\n        dgngrp_state,\n        dgngrp_aux,\n    ])\nend\n\nfunction main()\n    # TODO: this will move to the future namelist functionality\n    squall_args = ArgParseSettings(autofix_names = true)\n    add_arg_group!(squall_args, \"SQUALL_LINE\")\n    @add_arg_table! squall_args begin\n        \"--moisture-model\"\n        help = \"specify cloud condensate model\"\n        metavar = \"equilibrium|nonequilibrium\"\n        arg_type = String\n        default = \"nonequilibrium\"\n        \"--precipitation-model\"\n        help = \"specify precipitation model\"\n        metavar = \"noprecipitation|rain|rainsnow\"\n        arg_type = String\n        default = \"rainsnow\"\n        \"--check-asserts\"\n        help = \"should asserts be checked at the end of the simulation\"\n        metavar = \"yes|no\"\n        arg_type = String\n        default = \"no\"\n    end\n\n    cl_args =\n        ClimateMachine.init(parse_clargs = true, custom_clargs = squall_args)\n    moisture_model = cl_args[\"moisture_model\"]\n    precipitation_model = cl_args[\"precipitation_model\"]\n    check_asserts = cl_args[\"check_asserts\"]\n\n    FT = Float64\n\n    # DG polynomial order\n    N = 4\n\n    # Domain resolution and size\n    Δx = FT(250)\n    Δy = FT(1000)\n    Δv = FT(200)\n    resolution = (Δx, Δy, Δv)\n\n    xmax = FT(30000)\n    ymax = FT(5000)\n    zmax = FT(24000)\n    xmin = FT(-30000)\n    ymin = FT(0)\n    # for diagnostics\n    boundaries = [\n        xmin ymin FT(0)\n        xmax ymax zmax\n    ]\n\n    t0 = FT(0)\n    timeend = FT(9000)\n    spl_tinit, spl_qinit, spl_uinit, spl_vinit, spl_pinit = spline_int()\n    Cmax = FT(0.4)\n\n    # driver, solver and diagnostics configs\n    driver_config = config_squall_line(\n        FT,\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        xmin,\n        ymin,\n        moisture_model,\n        precipitation_model,\n    )\n    ode_solver_type = ClimateMachine.IMEXSolverType()\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        (spl_tinit, spl_qinit, spl_uinit, spl_vinit, spl_pinit);\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = Cmax,\n    )\n    dgn_config = config_diagnostics(driver_config, boundaries, resolution)\n\n    filter_vars = (\"moisture.ρq_tot\",)\n    if moisture_model == \"nonequilibrium\"\n        filter_vars = (filter_vars..., \"moisture.ρq_liq\", \"moisture.ρq_ice\")\n    end\n    if precipitation_model == \"rain\"\n        filter_vars = (filter_vars..., \"precipitation.ρq_rai\")\n    end\n    if precipitation_model == \"rainsnow\"\n        filter_vars =\n            (filter_vars..., \"precipitation.ρq_rai\", \"precipitation.ρq_rai\")\n    end\n\n    cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            filter_vars,\n            solver_config.dg.grid,\n            TMARFilter(),\n        )\n        nothing\n    end\n\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (cbtmarfilter,),\n        check_euclidean_distance = true,\n    )\n\n    if check_asserts == \"yes\"\n\n        m = driver_config.bl\n        Q = solver_config.Q\n        ρ_ind = varsindex(vars_state(m, Prognostic(), FT), :ρ)\n\n        if moisture_model == \"equilibrium\"\n            ρq_tot_ind =\n                varsindex(vars_state(m, Prognostic(), FT), :moisture, :ρq_tot)\n\n            min_q_tot = minimum(abs.(\n                Array(Q[:, ρq_tot_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n            max_q_tot = maximum(abs.(\n                Array(Q[:, ρq_tot_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n\n            @info(min_q_tot, max_q_tot)\n\n            # test that moisture exists and is not NaN\n            @test !isnan(max_q_tot)\n\n            # test that there is reasonable amount of moisture\n            @test abs(max_q_tot) > FT(1e-2)\n        end\n        if moisture_model == \"nonequilibrium\"\n            ρq_tot_ind =\n                varsindex(vars_state(m, Prognostic(), FT), :moisture, :ρq_tot)\n            ρq_liq_ind =\n                varsindex(vars_state(m, Prognostic(), FT), :moisture, :ρq_liq)\n            ρq_ice_ind =\n                varsindex(vars_state(m, Prognostic(), FT), :moisture, :ρq_ice)\n\n            min_q_tot = minimum(abs.(\n                Array(Q[:, ρq_tot_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n            max_q_tot = maximum(abs.(\n                Array(Q[:, ρq_tot_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n            min_q_liq = minimum(abs.(\n                Array(Q[:, ρq_liq_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n            max_q_liq = maximum(abs.(\n                Array(Q[:, ρq_liq_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n            min_q_ice = minimum(abs.(\n                Array(Q[:, ρq_ice_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n            max_q_ice = maximum(abs.(\n                Array(Q[:, ρq_ice_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n            @info(min_q_tot, max_q_tot)\n            @info(min_q_liq, max_q_liq)\n            @info(min_q_ice, max_q_ice)\n\n            # test that moisture exists and is not NaN\n            @test !isnan(max_q_tot)\n            @test !isnan(max_q_liq)\n            @test !isnan(max_q_ice)\n\n            # test that there is reasonable amount of moisture\n            @test abs(max_q_tot) > FT(1e-2)\n            @test abs(max_q_liq) > FT(1e-3)\n            @test abs(max_q_ice) > FT(1e-3)\n        end\n\n        if precipitation_model == \"rain\"\n            ρq_rai_ind = varsindex(\n                vars_state(m, Prognostic(), FT),\n                :precipitation,\n                :ρq_rai,\n            )\n\n            min_q_rai = minimum(abs.(\n                Array(Q[:, ρq_rai_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n            max_q_rai = maximum(abs.(\n                Array(Q[:, ρq_rai_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n\n            @info(min_q_rai, max_q_rai)\n\n            # test that rain variable exists and is not NaN\n            @test !isnan(max_q_rai)\n\n            # test that there is reasonable amount of rain water...\n            @test abs(max_q_rai) > FT(1e-4)\n        end\n        if precipitation_model == \"rainsnow\"\n            ρq_rai_ind = varsindex(\n                vars_state(m, Prognostic(), FT),\n                :precipitation,\n                :ρq_rai,\n            )\n            ρq_sno_ind = varsindex(\n                vars_state(m, Prognostic(), FT),\n                :precipitation,\n                :ρq_sno,\n            )\n            min_q_rai = minimum(abs.(\n                Array(Q[:, ρq_rai_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n            max_q_rai = maximum(abs.(\n                Array(Q[:, ρq_rai_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n            min_q_sno = minimum(abs.(\n                Array(Q[:, ρq_sno_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n            max_q_sno = maximum(abs.(\n                Array(Q[:, ρq_sno_ind, :]) ./ Array(Q[:, ρ_ind, :]),\n            ))\n\n            @info(min_q_rai, max_q_rai)\n            @info(min_q_sno, max_q_sno)\n\n            # test that rain and snow variables exists and are not NaN\n            @test !isnan(max_q_rai)\n            @test !isnan(max_q_sno)\n\n            # test that there is reasonable amount of precipitation\n            @test abs(max_q_rai) > FT(1e-4)\n            @test abs(max_q_sno) > FT(1e-6)\n        end\n    end\nend\n\nmain()\n"
  },
  {
    "path": "experiments/AtmosLES/stable_bl_les.jl",
    "content": "using Random\n\ninclude(\"stable_bl_model.jl\")\n\nfunction add_perturbations!(state, localgeo)\n    FT = eltype(state)\n    z = localgeo.coord[3]\n    if z <= FT(50) # Add random perturbations to bottom 50m of model\n        state.energy.ρe += (rand() - 0.5) * state.energy.ρe / 100\n    end\nend\n\nfunction set_clima_parameters(filename)\n    eval(:(include($filename)))\nend\n\nfunction main(cl_args)\n\n    surface_flux = cl_args[\"surface_flux\"]\n\n    FT = Float64\n    config_type = AtmosLESConfigType\n    # DG polynomial order\n    N = 4\n    # Domain resolution and size\n    Δh = FT(20)\n    Δv = FT(20)\n\n    resolution = (Δh, Δh, Δv)\n\n    # Prescribe domain parameters\n    xmax = FT(100)\n    ymax = FT(100)\n    zmax = FT(400)\n\n    t0 = FT(0)\n\n    # Required simulation time == 9hours\n    timeend = FT(3600 * 0.1)\n    CFLmax = FT(0.4)\n\n    C_smag_ = C_smag(param_set) #FT(0.23)\n\n    model = stable_bl_model(\n        FT,\n        config_type,\n        zmax,\n        surface_flux;\n        turbulence = SmagorinskyLilly{FT}(C_smag_),\n    )\n\n    ics = model.problem.init_state_prognostic\n\n    # Assemble configuration\n    driver_config = ClimateMachine.AtmosLESConfiguration(\n        \"StableBoundaryLayer\",\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        init_problem!,\n        model = model,\n    )\n\n    # Choose default IMEX solver\n    ode_solver_type = ClimateMachine.ExplicitSolverType()\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFLmax,\n    )\n    dgn_config = config_diagnostics(driver_config)\n\n    check_cons = (ClimateMachine.ConservationCheck(\"ρ\", \"1mins\", FT(0.0001)),)\n\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        check_cons = check_cons,\n        check_euclidean_distance = true,\n    )\nend\n\n# ArgParse in global scope to modify Clima Parameters\nsbl_args = ArgParseSettings(autofix_names = true)\nadd_arg_group!(sbl_args, \"StableBoundaryLayer\")\n@add_arg_table! sbl_args begin\n    \"--cparam-file\"\n    help = \"specify CLIMAParameters file\"\n    arg_type = Union{String, Nothing}\n    default = nothing\n\n    \"--surface-flux\"\n    help = \"specify surface flux for energy and moisture\"\n    metavar = \"prescribed|bulk|custom_sbl\"\n    arg_type = String\n    default = \"custom_sbl\"\nend\n\ncl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = sbl_args)\nif !isnothing(cl_args[\"cparam_file\"])\n    filename = cl_args[\"cparam_file\"]\n    set_clima_parameters(filename)\nend\n\nmain(cl_args)\n"
  },
  {
    "path": "experiments/AtmosLES/stable_bl_model.jl",
    "content": "#!/usr/bin/env julia --project\n#=\n# This experiment file establishes the initial conditions, boundary conditions,\n# source terms and simulation parameters (domain size + resolution) for the\n# GABLS LES case ([Beare2006](@cite); [Kosovic2000](@cite)).\n#\n## [Kosovic2000](@cite)\n#\n# To simulate the experiment, type in\n#\n# julia --project experiments/AtmosLES/stable_bl_les.jl\n=#\n\nusing ArgParse\nusing Distributions\nusing StaticArrays\nusing Test\nusing DocStringExtensions\nusing LinearAlgebra\nusing Printf\nusing UnPack\n\nusing ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing Thermodynamics.TemperatureProfiles\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.ODESolvers\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.TurbulenceConvection\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.BalanceLaws\nimport ClimateMachine.BalanceLaws: source, prognostic_vars\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav, day\nusing CLIMAParameters.Atmos.SubgridScale: C_smag, C_drag\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\nimport CLIMAParameters\n\nusing ClimateMachine.Atmos: altitude, recover_thermo_state, density\n\n\"\"\"\n  StableBL Geostrophic Forcing (Source)\n\"\"\"\nstruct StableBLGeostrophic{FT} <: TendencyDef{Source}\n    \"Coriolis parameter [s⁻¹]\"\n    f_coriolis::FT\n    \"Eastward geostrophic velocity `[m/s]` (Base)\"\n    u_geostrophic::FT\n    \"Eastward geostrophic velocity `[m/s]` (Slope)\"\n    u_slope::FT\n    \"Northward geostrophic velocity `[m/s]`\"\n    v_geostrophic::FT\nend\nprognostic_vars(::StableBLGeostrophic) = (Momentum(),)\n\nfunction source(::Momentum, s::StableBLGeostrophic, m, args)\n    @unpack state, aux = args\n    @unpack f_coriolis, u_geostrophic, u_slope, v_geostrophic = s\n\n    z = altitude(m, aux)\n    # Note z dependence of eastward geostrophic velocity\n    u_geo = SVector(u_geostrophic + u_slope * z, v_geostrophic, 0)\n    ẑ = vertical_unit_vector(m, aux)\n    fkvector = f_coriolis * ẑ\n    # Accumulate sources\n    return -fkvector × (state.ρu .- state.ρ * u_geo)\nend\n\n\"\"\"\n  StableBL Sponge (Source)\n\"\"\"\nstruct StableBLSponge{FT} <: TendencyDef{Source}\n    \"Maximum domain altitude (m)\"\n    z_max::FT\n    \"Altitude at with sponge starts (m)\"\n    z_sponge::FT\n    \"Sponge Strength 0 ⩽ α_max ⩽ 1\"\n    α_max::FT\n    \"Sponge exponent\"\n    γ::FT\n    \"Eastward geostrophic velocity `[m/s]` (Base)\"\n    u_geostrophic::FT\n    \"Eastward geostrophic velocity `[m/s]` (Slope)\"\n    u_slope::FT\n    \"Northward geostrophic velocity `[m/s]`\"\n    v_geostrophic::FT\nend\n\nprognostic_vars(::StableBLSponge) = (Momentum(),)\n\nfunction source(::Momentum, s::StableBLSponge, m, args)\n    @unpack state, aux = args\n\n    @unpack z_max, z_sponge, α_max, γ = s\n    @unpack u_geostrophic, u_slope, v_geostrophic = s\n\n    z = altitude(m, aux)\n    u_geo = SVector(u_geostrophic + u_slope * z, v_geostrophic, 0)\n    ẑ = vertical_unit_vector(m, aux)\n    # Accumulate sources\n    if z_sponge <= z\n        r = (z - z_sponge) / (z_max - z_sponge)\n        β_sponge = α_max * sinpi(r / 2)^s.γ\n        return -β_sponge * (state.ρu .- state.ρ * u_geo)\n    else\n        FT = eltype(state)\n        return SVector{3, FT}(0, 0, 0)\n    end\nend\n\nadd_perturbations!(state, localgeo) = nothing\n\n\"\"\"\n  Initial Condition for StableBoundaryLayer LES\n\"\"\"\nfunction init_problem!(problem, bl, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n    # Problem floating point precision\n    param_set = parameter_set(bl)\n    FT = eltype(state)\n    R_gas::FT = R_d(param_set)\n    c_p::FT = cp_d(param_set)\n    c_v::FT = cv_d(param_set)\n    p0::FT = MSLP(param_set)\n    _grav::FT = grav(param_set)\n    γ::FT = c_p / c_v\n    # Initialise speeds [u = Eastward, v = Northward, w = Vertical]\n    u::FT = 8\n    v::FT = 0\n    w::FT = 0\n    # Assign piecewise quantities to θ_liq and q_tot\n    θ_liq::FT = 0\n    q_tot::FT = 0\n    # Piecewise functions for potential temperature and total moisture\n    z1 = FT(100)\n    if z <= z1\n        θ_liq = FT(265)\n    else\n        θ_liq = FT(265) + FT(0.01) * (z - z1)\n    end\n    θ = θ_liq\n    p = aux.ref_state.p\n    # Establish thermodynamic state and moist phase partitioning\n    if moisture_model(bl) isa DryModel\n        TS = PhaseDry_pθ(param_set, p, θ)\n    else\n        TS = PhaseEquil_pθq(param_set, p, θ_liq, q_tot)\n    end\n\n    compress = compressibility_model(bl) isa Compressible\n    ρ = compress ? air_density(TS) : aux.ref_state.ρ\n    # Compute momentum contributions\n    ρu = ρ * u\n    ρv = ρ * v\n    ρw = ρ * w\n\n    # Compute energy contributions\n    e_kin = FT(1 // 2) * (u^2 + v^2 + w^2)\n    e_pot = _grav * z\n    ρe_tot = ρ * total_energy(e_kin, e_pot, TS)\n\n    # Assign initial conditions for prognostic state variables\n    state.ρ = ρ\n    state.ρu = SVector(ρu, ρv, ρw)\n    state.energy.ρe = ρe_tot\n    if !(moisture_model(bl) isa DryModel)\n        state.moisture.ρq_tot = ρ * q_tot\n    end\n    add_perturbations!(state, localgeo)\n    init_state_prognostic!(turbconv_model(bl), bl, state, aux, localgeo, t)\nend\n\nfunction surface_temperature_variation(state, t)\n    FT = eltype(state)\n    return FT(265) - FT(1 / 4) * (t / 3600)\nend\n\nfunction stable_bl_model(\n    ::Type{FT},\n    config_type,\n    zmax,\n    surface_flux;\n    turbulence = ConstantKinematicViscosity(FT(0)),\n    turbconv = NoTurbConv(),\n    compressibility = Compressible(),\n    moisture_model = \"dry\",\n    ref_state = HydrostaticState(DecayingTemperatureProfile{FT}(param_set),),\n) where {FT}\n\n    ics = init_problem!     # Initial conditions\n\n    C_drag_::FT = C_drag(param_set) # FT(0.001)    # Momentum exchange coefficient\n    u_star = FT(0.30)\n    z_0 = FT(0.1)          # Roughness height\n\n    z_sponge = FT(300)     # Start of sponge layer\n    α_max = FT(0.75)       # Strength of sponge layer (timescale)\n    γ = 2                  # Strength of sponge layer (exponent)\n\n    u_geostrophic = FT(8)        # Eastward relaxation speed\n    u_slope = FT(0)              # Slope of altitude-dependent relaxation speed\n    v_geostrophic = FT(0)        # Northward relaxation speed\n    f_coriolis = FT(1.39e-4) # Coriolis parameter at 73N\n\n    q_sfc = FT(0)\n\n    g = compressibility isa Compressible ? (Gravity(),) : ()\n\n    # Assemble source components\n    source_default = (\n        g...,\n        StableBLSponge{FT}(\n            zmax,\n            z_sponge,\n            α_max,\n            γ,\n            u_geostrophic,\n            u_slope,\n            v_geostrophic,\n        ),\n        StableBLGeostrophic{FT}(\n            f_coriolis,\n            u_geostrophic,\n            u_slope,\n            v_geostrophic,\n        ),\n        turbconv_sources(turbconv)...,\n    )\n    if moisture_model == \"dry\"\n        source = source_default\n        moisture = DryModel()\n    elseif moisture_model == \"equilibrium\"\n        source = source_default\n        moisture = EquilMoist(; maxiter = 5, tolerance = FT(0.1))\n    elseif moisture_model == \"nonequilibrium\"\n        source = (source_default..., CreateClouds())\n        moisture = NonEquilMoist()\n    else\n        @warn @sprintf(\n            \"\"\"\n%s: unrecognized moisture_model in source terms, using the defaults\"\"\",\n            moisture_model,\n        )\n        source = source_default\n    end\n    # Set up problem initial and boundary conditions\n    if surface_flux == \"prescribed\"\n        energy_bc = PrescribedEnergyFlux((state, aux, t) -> LHF + SHF)\n        moisture_bc = PrescribedMoistureFlux((state, aux, t) -> moisture_flux)\n    elseif surface_flux == \"bulk\"\n        energy_bc = BulkFormulaEnergy(\n            (bl, state, aux, t, normPu_int) -> C_drag_,\n            (bl, state, aux, t) ->\n                (surface_temperature_variation(state, t), q_sfc),\n        )\n        moisture_bc = BulkFormulaMoisture(\n            (state, aux, t, normPu_int) -> C_drag_,\n            (state, aux, t) -> q_sfc,\n        )\n    elseif surface_flux == \"custom_sbl\"\n        energy_bc = PrescribedTemperature(\n            (state, aux, t) -> surface_temperature_variation(state, t),\n        )\n        moisture_bc = BulkFormulaMoisture(\n            (state, aux, t, normPu_int) -> C_drag_,\n            (state, aux, t) -> q_sfc,\n        )\n    elseif surface_flux == \"Nishizawa2018\"\n        energy_bc = NishizawaEnergyFlux(\n            (bl, state, aux, t, normPu_int) -> z_0,\n            (bl, state, aux, t) ->\n                (surface_temperature_variation(state, t), q_sfc),\n        )\n        moisture_bc = PrescribedMoistureFlux((state, aux, t) -> moisture_flux)\n    else\n        @warn @sprintf(\n            \"\"\"\n%s: unrecognized surface flux; using 'prescribed'\"\"\",\n            surface_flux,\n        )\n    end\n\n    # Define the physics\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = turbulence,\n        moisture = moisture,\n        turbconv = turbconv,\n        compressibility = compressibility,\n    )\n\n    moisture_bcs = moisture_model == \"dry\" ? () : (; moisture = moisture_bc)\n    boundary_conditions = (\n        AtmosBC(\n            physics;\n            momentum = Impenetrable(DragLaw(\n                # normPu_int is the internal horizontal speed\n                # P represents the projection onto the horizontal\n                (state, aux, t, normPu_int) -> (u_star / normPu_int)^2,\n            )),\n            energy = energy_bc,\n            moisture_bcs...,\n            turbconv = turbconv_bcs(turbconv)[1],\n        ),\n        AtmosBC(physics; turbconv = turbconv_bcs(turbconv)[2]),\n    )\n\n    moisture_flux = FT(0)\n    problem = AtmosProblem(\n        init_state_prognostic = ics,\n        boundaryconditions = boundary_conditions,\n    )\n\n    # Assemble model components\n\n    model =\n        AtmosModel{FT}(config_type, physics; problem = problem, source = source)\n\n    return model\nend\n\nfunction config_diagnostics(driver_config)\n    default_dgngrp = setup_atmos_default_diagnostics(\n        AtmosLESConfigType(),\n        \"2500steps\",\n        driver_config.name,\n    )\n    core_dgngrp = setup_atmos_core_diagnostics(\n        AtmosLESConfigType(),\n        \"2500steps\",\n        driver_config.name,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([\n        default_dgngrp,\n        core_dgngrp,\n    ])\nend\n"
  },
  {
    "path": "experiments/AtmosLES/surfacebubble.jl",
    "content": "#!/usr/bin/env julia --project\nusing ClimateMachine\nClimateMachine.init(parse_clargs = true)\n\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.StdDiagnostics\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\n\nusing Distributions\nusing StaticArrays\nusing Test\nusing DocStringExtensions\nusing LinearAlgebra\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n# -------------------- Surface Driven Bubble ----------------- #\n# Rising thermals driven by a prescribed surface heat flux.\n# 1) Boundary Conditions:\n#       Laterally periodic with no flow penetration through top\n#       and bottom wall boundaries.\n#       Momentum: Impenetrable(FreeSlip())\n#       Energy:   Spatially varying non-zero heat flux up to time t₁\n# 2) Domain: 1250m × 1250m × 1000m\n# Configuration defaults are in `src/Driver/Configurations.jl`\n\n\n\"\"\"\n  Surface Driven Thermal Bubble\n\"\"\"\nfunction init_surfacebubble!(problem, bl, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n    param_set = parameter_set(bl)\n    FT = eltype(state)\n    R_gas::FT = R_d(param_set)\n    c_p::FT = cp_d(param_set)\n    c_v::FT = cv_d(param_set)\n    p0::FT = MSLP(param_set)\n    _grav::FT = grav(param_set)\n    γ::FT = c_p / c_v\n\n    xc::FT = 1250\n    yc::FT = 1250\n    zc::FT = 1250\n    θ_ref::FT = 300\n    Δθ::FT = 0\n\n    #Perturbed state:\n    θ = θ_ref + Δθ # potential temperature\n    π_exner = FT(1) - _grav / (c_p * θ) * z # exner pressure\n    ρ = p0 / (R_gas * θ) * (π_exner)^(c_v / R_gas) # density\n\n    q_tot = FT(0)\n    ts = PhaseEquil_ρθq(param_set, ρ, θ, q_tot)\n    q_pt = PhasePartition(ts)\n\n    ρu = SVector(FT(0), FT(0), FT(0))\n    # energy definitions\n    e_kin = FT(0)\n    e_pot = gravitational_potential(bl.orientation, aux)\n    ρe_tot = ρ * total_energy(e_kin, e_pot, ts)\n    state.ρ = ρ\n    state.ρu = ρu\n    state.energy.ρe = ρe_tot\n    state.moisture.ρq_tot = ρ * q_pt.tot\nend\n\nfunction config_surfacebubble(FT, N, resolution, xmax, ymax, zmax)\n    # Boundary conditions\n    # Heat Flux Peak Magnitude\n    F₀ = FT(100)\n    # Time [s] at which `heater` turns off\n    t₁ = FT(500)\n    # Plume wavelength scaling\n    x₀ = xmax\n    function energyflux(state, aux, t)\n        x = aux.coord[1]\n        y = aux.coord[2]\n        MSEF = F₀ * (cospi(2 * x / x₀))^2 * (cospi(2 * y / x₀))^2\n        t < t₁ ? MSEF : zero(MSEF)\n    end\n\n    C_smag = FT(0.23)\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        turbulence = SmagorinskyLilly{FT}(C_smag),\n        moisture = EquilMoist(),\n    )\n\n    problem = AtmosProblem(\n        boundaryconditions = (\n            AtmosBC(physics; energy = PrescribedEnergyFlux(energyflux)),\n            AtmosBC(physics;),\n        ),\n        init_state_prognostic = init_surfacebubble!,\n    )\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        problem = problem,\n        source = (Gravity(),),\n    )\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"SurfaceDrivenBubble\",\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        init_surfacebubble!,\n        model = model,\n    )\n\n    return config\nend\n\nfunction config_diagnostics(\n    driver_config,\n    xmax::FT,\n    ymax::FT,\n    zmax::FT,\n    resolution,\n    interval,\n) where {FT}\n    dgngrp = StdDiagnostics.AtmosLESDefault(interval, driver_config.name)\n    boundaries = [\n        FT(0) FT(0) FT(0)\n        xmax ymax zmax\n    ]\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries,\n        resolution,\n    )\n    pdgngrp = setup_atmos_default_perturbations(\n        AtmosLESConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp, pdgngrp])\nend\n\nfunction main()\n    FT = Float64\n    # DG polynomial order\n    N = 4\n    # Domain resolution and size\n    Δh = FT(50)\n    Δv = FT(50)\n    resolution = (Δh, Δh, Δv)\n    xmax = FT(2000)\n    ymax = FT(2000)\n    zmax = FT(2000)\n    t0 = FT(0)\n    timeend = FT(2000)\n\n    driver_config = config_surfacebubble(FT, N, resolution, xmax, ymax, zmax)\n\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK144NiegemannDiehlBusch,\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        init_on_cpu = true,\n        ode_solver_type = ode_solver_type,\n    )\n\n    dgn_ssecs = (timeend / 2) + 10\n    dgn_interval = \"$(dgn_ssecs)ssecs\"\n    dgn_config = config_diagnostics(\n        driver_config,\n        xmax,\n        ymax,\n        zmax,\n        resolution,\n        dgn_interval,\n    )\n\n    cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            (\"moisture.ρq_tot\",),\n            solver_config.dg.grid,\n            TMARFilter(),\n        )\n        nothing\n    end\n\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (cbtmarfilter,),\n        check_euclidean_distance = true,\n    )\n\n    @test isapprox(result, FT(1); atol = 1.5e-3)\nend\n\nmain()\n"
  },
  {
    "path": "experiments/AtmosLES/taylor_green.jl",
    "content": "#!/usr/bin/env julia --project\n\nusing ClimateMachine\nClimateMachine.init(parse_clargs = true)\n\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Mesh.Filters\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\n\nusing Distributions\nusing StaticArrays\nusing Test\nusing DocStringExtensions\nusing LinearAlgebra\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: R_d, cv_d, cp_d, MSLP, grav, LH_v0\nusing CLIMAParameters.Atmos.SubgridScale: C_smag\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    indefinite_stack_integral!,\n    reverse_indefinite_stack_integral!,\n    integral_load_auxiliary_state!,\n    integral_set_auxiliary_state!,\n    reverse_integral_load_auxiliary_state!,\n    reverse_integral_set_auxiliary_state!\n\nimport ClimateMachine.BalanceLaws: boundary_state!\nimport ClimateMachine.Atmos: flux_second_order!\n\n\"\"\"\n    Initial Condition for Taylor-Green vortex (LES)\n\n## References:\n - [Taylor1937](@cite)\n - [Rafei2018](@cite)\n - [Bull2014](@cite)\n\"\"\"\nfunction init_greenvortex!(problem, bl, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n\n    # Problem float-type\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n\n    # Unpack constant parameters\n    R_gas::FT = R_d(param_set)\n    c_p::FT = cp_d(param_set)\n    c_v::FT = cv_d(param_set)\n    p0::FT = MSLP(param_set)\n    _grav::FT = grav(param_set)\n    γ::FT = c_p / c_v\n\n    # Compute perturbed thermodynamic state:\n    ρ = FT(1.178)      # density\n    ρu = SVector(FT(0), FT(0), FT(0))  # momentum\n    #State (prognostic) variable assignment\n    e_pot = FT(0)# potential energy\n    Pinf = 101325\n    Uzero = FT(100)\n    p = Pinf + (ρ * Uzero^2 / 16) * (2 + cos(2 * z)) * (cos(2 * x) + cos(2 * y))\n    u = Uzero * sin(x) * cos(y) * cos(z)\n    v = -Uzero * cos(x) * sin(y) * cos(z)\n    e_kin = 0.5 * (u^2 + v^2)\n    T = p / (ρ * R_gas)\n    e_int = internal_energy(param_set, T, PhasePartition(FT(0)))\n    ρe_tot = ρ * (e_kin + e_pot + e_int)\n    # Assign State Variables\n    state.ρ = ρ\n    state.ρu = SVector(FT(ρ * u), FT(ρ * v), FT(0))\n    state.energy.ρe = ρe_tot\nend\n\n# Set up AtmosLESConfiguration for the experiment\nfunction config_greenvortex(\n    ::Type{FT},\n    N,\n    (xmin, xmax, ymin, ymax, zmin, zmax),\n    resolution,\n) where {FT}\n\n    _C_smag = FT(C_smag(param_set))\n    physics = AtmosPhysics{FT}(\n        param_set;                          # Parameter set corresponding to earth parameters\n        ref_state = NoReferenceState(),\n        turbulence = Vreman(_C_smag),       # Turbulence closure model\n        moisture = DryModel(),\n    )\n\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,                 # Flow in a box, requires the AtmosLESConfigType\n        physics;                            # Atmos physics\n        init_state_prognostic = init_greenvortex!,\n        orientation = NoOrientation(),\n        source = (),\n    )\n\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"GreenVortex\",          # Problem title [String]\n        N,                      # Polynomial order [Int]\n        resolution,             # (Δx, Δy, Δz) effective resolution [m]\n        xmax,                   # Domain maximum size [m]\n        ymax,                   # Domain maximum size [m]\n        zmax,                   # Domain maximum size [m]\n        param_set,              # Parameter set.\n        init_greenvortex!,      # Function specifying initial condition\n        boundary = ((0, 0), (0, 0), (0, 0)),\n        periodicity = (true, true, true),\n        xmin = xmin,\n        ymin = ymin,\n        zmin = zmin,\n        model = model,                  # Model type\n    )\n    return config\nend\n\n# Define the diagnostics configuration for this experiment\nfunction config_diagnostics(\n    driver_config,\n    (xmin, xmax, ymin, ymax, zmin, zmax),\n    resolution,\n    tnor,\n    titer,\n    snor,\n)\n    ts_dgngrp = setup_atmos_turbulence_stats(\n        AtmosLESConfigType(),\n        \"360steps\",\n        driver_config.name,\n        tnor,\n        titer,\n    )\n\n    boundaries = [\n        xmin ymin zmin\n        xmax ymax zmax\n    ]\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries,\n        resolution,\n    )\n    ds_dgngrp = setup_atmos_spectra_diagnostics(\n        AtmosLESConfigType(),\n        \"0.06ssecs\",\n        driver_config.name,\n        interpol = interpol,\n        snor,\n    )\n    me_dgngrp = setup_atmos_mass_energy_loss(\n        AtmosLESConfigType(),\n        \"0.02ssecs\",\n        driver_config.name,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([\n        ts_dgngrp,\n        ds_dgngrp,\n        me_dgngrp,\n    ],)\nend\n\n# Entry point\nfunction main()\n    FT = Float64\n    # DG polynomial order\n    N = 4\n    # Domain resolution and size\n    Ncellsx = 64\n    Ncellsy = 64\n    Ncellsz = 64\n    Δx = FT(2 * pi / Ncellsx)\n    Δy = Δx\n    Δz = Δx\n    resolution = (Δx, Δy, Δz)\n    xmin = FT(-pi)\n    xmax = FT(pi)\n    ymin = FT(-pi)\n    ymax = FT(pi)\n    zmin = FT(-pi)\n    zmax = FT(pi)\n    # Simulation time\n    t0 = FT(0)\n    timeend = FT(0.1)\n    CFL = FT(1.8)\n\n    driver_config = config_greenvortex(\n        FT,\n        N,\n        (xmin, xmax, ymin, ymax, zmin, zmax),\n        resolution,\n    )\n\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK144NiegemannDiehlBusch,\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFL,\n    )\n\n    tnor = FT(100)\n    titer = FT(0.01)\n    snor = FT(10000.0)\n    dgn_config = config_diagnostics(\n        driver_config,\n        (xmin, xmax, ymin, ymax, zmin, zmax),\n        resolution,\n        tnor,\n        titer,\n        snor,\n    )\n\n    check_cons = (\n        ClimateMachine.ConservationCheck(\"ρ\", \"100steps\", FT(0.0001)),\n        ClimateMachine.ConservationCheck(\"energy.ρe\", \"100steps\", FT(0.0025)),\n    )\n\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        check_cons = check_cons,\n        check_euclidean_distance = true,\n    )\n\nend\n\nmain()\n"
  },
  {
    "path": "experiments/OceanBoxGCM/homogeneous_box.jl",
    "content": "#!/usr/bin/env julia --project\n\ninclude(\"simple_box.jl\")\nClimateMachine.init(parse_clargs = true)\n\n# Float type\nconst FT = Float64\n\n# simulation time\nconst timestart = FT(0)      # s\nconst timestep = FT(55)     # s\nconst timeend = FT(6 * 3600) # s\ntimespan = (timestart, timeend)\n\n# DG polynomial order\nconst N = Int(4)\n\n# Domain resolution\nconst Nˣ = Int(20)\nconst Nʸ = Int(20)\nconst Nᶻ = Int(50)\nresolution = (N, Nˣ, Nʸ, Nᶻ)\n\n# Domain size\nconst Lˣ = 4e6    # m\nconst Lʸ = 4e6    # m\nconst H = 400   # m\ndimensions = (Lˣ, Lʸ, H)\n\nBC = (\n    OceanBC(Impenetrable(NoSlip()), Insulating()),\n    OceanBC(Impenetrable(NoSlip()), Insulating()),\n    OceanBC(Penetrable(KinematicStress()), Insulating()),\n)\n\nrun_simple_box(\n    \"homogeneous_box\",\n    resolution,\n    dimensions,\n    timespan,\n    HomogeneousBox,\n    imex = true,\n    Δt = timestep,\n    BC = BC,\n)\n"
  },
  {
    "path": "experiments/OceanBoxGCM/ocean_gyre.jl",
    "content": "#!/usr/bin/env julia --project\n\ninclude(\"simple_box.jl\")\nClimateMachine.init(parse_clargs = true)\n\n# Float type\nconst FT = Float64\n\n# simulation time\nconst timestart = FT(0)      # s\nconst timestep = FT(240)     # s\nconst timeend = FT(86400) # s\ntimespan = (timestart, timeend)\n\n# DG polynomial order\nconst N = Int(4)\n\n# Domain resolution\nconst Nˣ = Int(20)\nconst Nʸ = Int(20)\nconst Nᶻ = Int(20)\nresolution = (N, Nˣ, Nʸ, Nᶻ)\n\n# Domain size\nconst Lˣ = 4e6    # m\nconst Lʸ = 4e6    # m\nconst H = 1000   # m\ndimensions = (Lˣ, Lʸ, H)\n\nBC = (\n    OceanBC(Impenetrable(NoSlip()), Insulating()),\n    OceanBC(Impenetrable(NoSlip()), Insulating()),\n    OceanBC(Penetrable(KinematicStress()), TemperatureFlux()),\n)\n\nrun_simple_box(\n    \"ocean_gyre\",\n    resolution,\n    dimensions,\n    timespan,\n    OceanGyre,\n    imex = false,\n    Δt = timestep,\n    BC = BC,\n)\n"
  },
  {
    "path": "experiments/OceanBoxGCM/simple_box.jl",
    "content": "using Test\nusing ClimateMachine\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Mesh.Grids: polynomialorders\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.Ocean\nusing ClimateMachine.Ocean.HydrostaticBoussinesq\nusing ClimateMachine.Ocean.OceanProblems\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nfunction config_simple_box(name, resolution, dimensions, problem; BC = nothing)\n    if BC == nothing\n        problem = problem{FT}(dimensions..., τₒ = 0.2)\n    else\n        problem = problem{FT}(dimensions...; BC = BC)\n    end\n\n    _grav::FT = grav(param_set)\n    cʰ = sqrt(_grav * problem.H) # m/s\n    model = HydrostaticBoussinesqModel{FT}(param_set, problem, cʰ = cʰ)\n\n    N, Nˣ, Nʸ, Nᶻ = resolution\n    resolution = (Nˣ, Nʸ, Nᶻ)\n\n    solver_type = ExplicitSolverType(solver_method = LSRK144NiegemannDiehlBusch)\n    config = ClimateMachine.OceanBoxGCMConfiguration(\n        name,\n        N,\n        resolution,\n        param_set,\n        model,\n    )\n\n    return config, solver_type\nend\n\nfunction run_simple_box(\n    name,\n    resolution,\n    dimensions,\n    timespan,\n    problem;\n    imex::Bool = false,\n    BC = nothing,\n    Δt = nothing,\n    refDat = (),\n)\n    if imex\n        ode_solver_type =\n            ClimateMachine.IMEXSolverType(implicit_model = LinearHBModel)\n        Courant_number = 0.1\n    else\n        ode_solver_type = ClimateMachine.ExplicitSolverType(\n            solver_method = LSRK144NiegemannDiehlBusch,\n        )\n        Courant_number = 0.4\n    end\n\n    driver_config, solver_type_driver_config =\n        config_simple_box(name, resolution, dimensions, problem; BC = BC)\n\n    grid = driver_config.grid\n    # XXX: Needs updating for multiple polynomial orders\n    N = polynomialorders(grid)\n    # Currently only support single polynomial order\n    @assert all(N[1] .== N)\n    N = N[1]\n    vert_filter = CutoffFilter(grid, N - 1)\n    exp_filter = ExponentialFilter(grid, 1, 8)\n    modeldata = (vert_filter = vert_filter, exp_filter = exp_filter)\n\n    timestart, timeend = timespan\n    solver_config = ClimateMachine.SolverConfiguration(\n        timestart,\n        timeend,\n        driver_config,\n        init_on_cpu = true,\n        ode_solver_type = ode_solver_type,\n        ode_dt = Δt,\n        modeldata = modeldata,\n        Courant_number = Courant_number,\n    )\n\n    ## Create a callback to report state statistics for main MPIStateArrays\n    ## every ntFreq timesteps.\n    nt_freq = floor(Int, 1 // 10 * solver_config.timeend / solver_config.dt)\n    cb = ClimateMachine.StateCheck.sccreate(\n        [(solver_config.Q, \"Q\"), (solver_config.dg.state_auxiliary, \"s_aux\")],\n        nt_freq;\n        prec = 12,\n    )\n\n    result = ClimateMachine.invoke!(solver_config; user_callbacks = [cb])\n\n    ## Check results against reference if present\n    ClimateMachine.StateCheck.scprintref(cb)\n    if length(refDat) > 0\n        @test ClimateMachine.StateCheck.scdocheck(cb, refDat)\n    end\nend\n"
  },
  {
    "path": "experiments/OceanSplitExplicit/simple_box.jl",
    "content": "using Test\nusing MPI\n\nusing ClimateMachine\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\n\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Topologies\n\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.SystemSolvers\n\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.BalanceLaws\n\nusing ClimateMachine.Ocean\nusing ClimateMachine.Ocean.SplitExplicit01\nusing ClimateMachine.Ocean.OceanProblems\n\nusing ClimateMachine:\n    ConfigSpecificInfo, DriverConfiguration, OceanSplitExplicitConfigType\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: grav\n\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nstruct OceanSplitExplicitSpecificInfo <: ConfigSpecificInfo\n    model_2D::BalanceLaw\n    grid_2D::DiscontinuousSpectralElementGrid\n    dg::DGModel\nend\n\nfunction OceanSplitExplicitConfiguration(\n    name::String,\n    N::Union{Int, NTuple{2, Int}},\n    (Nˣ, Nʸ, Nᶻ)::NTuple{3, Int},\n    param_set::AbstractParameterSet,\n    model_3D;\n    FT = Float64,\n    array_type = ClimateMachine.array_type(),\n    solver_type = SplitExplicitSolverType{FT}(90.0 * 60.0, 240.0),\n    mpicomm = MPI.COMM_WORLD,\n    numerical_flux_first_order = RusanovNumericalFlux(),\n    numerical_flux_second_order = CentralNumericalFluxSecondOrder(),\n    numerical_flux_gradient = CentralNumericalFluxGradient(),\n    fv_reconstruction = nothing,\n    periodicity = (false, false, false),\n    boundary = ((1, 1), (1, 1), (2, 3)),\n)\n\n    (polyorder_horz, polyorder_vert) = isa(N, Int) ? (N, N) : N\n\n    xrange = range(FT(0); length = Nˣ + 1, stop = model_3D.problem.Lˣ)\n    yrange = range(FT(0); length = Nʸ + 1, stop = model_3D.problem.Lʸ)\n    zrange = range(FT(-model_3D.problem.H); length = Nᶻ + 1, stop = 0)\n\n    brickrange_2D = (xrange, yrange)\n    brickrange_3D = (xrange, yrange, zrange)\n\n    topology_2D = BrickTopology(\n        mpicomm,\n        brickrange_2D;\n        periodicity = (periodicity[1], periodicity[2]),\n        boundary = (boundary[1], boundary[2]),\n    )\n    topology_3D = StackedBrickTopology(\n        mpicomm,\n        brickrange_3D;\n        periodicity = periodicity,\n        boundary = boundary,\n    )\n\n    grid_2D = DiscontinuousSpectralElementGrid(\n        topology_2D,\n        FloatType = FT,\n        DeviceArray = array_type,\n        polynomialorder = polyorder_horz,\n    )\n    grid_3D = DiscontinuousSpectralElementGrid(\n        topology_3D,\n        FloatType = FT,\n        DeviceArray = array_type,\n        polynomialorder = (polyorder_horz, polyorder_vert),\n    )\n\n    model_2D = BarotropicModel(model_3D)\n\n    dg_2D = DGModel(\n        model_2D,\n        grid_2D,\n        numerical_flux_first_order,\n        numerical_flux_second_order,\n        numerical_flux_gradient,\n    )\n\n    Q_2D = init_ode_state(dg_2D, FT(0); init_on_cpu = true)\n\n    vert_filter = CutoffFilter(grid_3D, polyorder_vert - 1)\n    exp_filter = ExponentialFilter(grid_3D, 1, 8)\n\n    flowintegral_dg = DGModel(\n        ClimateMachine.Ocean.SplitExplicit01.FlowIntegralModel(model_3D),\n        grid_3D,\n        numerical_flux_first_order,\n        numerical_flux_second_order,\n        numerical_flux_gradient,\n    )\n\n    tendency_dg = DGModel(\n        ClimateMachine.Ocean.SplitExplicit01.TendencyIntegralModel(model_3D),\n        grid_3D,\n        numerical_flux_first_order,\n        numerical_flux_second_order,\n        numerical_flux_gradient,\n    )\n\n    conti3d_dg = DGModel(\n        ClimateMachine.Ocean.SplitExplicit01.Continuity3dModel(model_3D),\n        grid_3D,\n        numerical_flux_first_order,\n        numerical_flux_second_order,\n        numerical_flux_gradient,\n    )\n    conti3d_Q = init_ode_state(conti3d_dg, FT(0); init_on_cpu = true)\n\n    ivdc_dg = DGModel(\n        ClimateMachine.Ocean.SplitExplicit01.IVDCModel(model_3D),\n        grid_3D,\n        numerical_flux_first_order,\n        numerical_flux_second_order,\n        numerical_flux_gradient;\n        direction = VerticalDirection(),\n    )\n    # Not sure this is needed since we set values later,\n    # but we'll do it just in case!\n    ivdc_Q = init_ode_state(ivdc_dg, FT(0); init_on_cpu = true)\n    ivdc_RHS = init_ode_state(ivdc_dg, FT(0); init_on_cpu = true)\n\n    ivdc_bgm_solver = BatchedGeneralizedMinimalResidual(\n        ivdc_dg,\n        ivdc_Q;\n        max_subspace_size = 10,\n    )\n\n    modeldata = (\n        dg_2D = dg_2D,\n        Q_2D = Q_2D,\n        vert_filter = vert_filter,\n        exp_filter = exp_filter,\n        flowintegral_dg = flowintegral_dg,\n        tendency_dg = tendency_dg,\n        conti3d_dg = conti3d_dg,\n        conti3d_Q = conti3d_Q,\n        ivdc_dg = ivdc_dg,\n        ivdc_Q = ivdc_Q,\n        ivdc_RHS = ivdc_RHS,\n        ivdc_bgm_solver = ivdc_bgm_solver,\n    )\n\n    dg_3D = DGModel(\n        model_3D,\n        grid_3D,\n        numerical_flux_first_order,\n        numerical_flux_second_order,\n        numerical_flux_gradient;\n        modeldata = modeldata,\n    )\n\n\n    return DriverConfiguration(\n        OceanSplitExplicitConfigType(),\n        name,\n        (polyorder_horz, polyorder_vert),\n        FT,\n        array_type,\n        param_set,\n        model_3D,\n        mpicomm,\n        grid_3D,\n        numerical_flux_first_order,\n        numerical_flux_second_order,\n        numerical_flux_gradient,\n        fv_reconstruction,\n        nothing, # filter\n        OceanSplitExplicitSpecificInfo(model_2D, grid_2D, dg_3D),\n    )\nend\n\n\nfunction config_simple_box(\n    name,\n    resolution,\n    dimensions,\n    boundary_conditions;\n    dt_slow = 90.0 * 60.0,\n    dt_fast = 240.0,\n)\n\n    problem = OceanGyre{FT}(\n        dimensions...;\n        τₒ = 0.1,\n        λʳ = 10 // 86400,\n        θᴱ = 10,\n        BC = boundary_conditions,\n    )\n\n    add_fast_substeps = 2\n    numImplSteps = 5\n    numImplSteps > 0 ? ivdc_dt = dt_slow / FT(numImplSteps) : ivdc_dt = dt_slow\n    model_3D = OceanModel{FT}(\n        param_set,\n        problem;\n        cʰ = 1,\n        κᶜ = FT(0.1),\n        add_fast_substeps = add_fast_substeps,\n        numImplSteps = numImplSteps,\n        ivdc_dt = ivdc_dt,\n    )\n\n    N, Nˣ, Nʸ, Nᶻ = resolution\n    resolution = (Nˣ, Nʸ, Nᶻ)\n\n    config = OceanSplitExplicitConfiguration(\n        name,\n        N,\n        resolution,\n        param_set,\n        model_3D,\n    )\n    solver_type = SplitExplicitSolverType{FT}(dt_slow, dt_fast)\n\n    return config, solver_type\nend\n\nfunction run_simple_box(driver_config, timespan, dt_slow; refDat = ())\n\n    timestart, timeend = timespan\n    solver_config = ClimateMachine.SolverConfiguration(\n        timestart,\n        timeend,\n        driver_config,\n        init_on_cpu = true,\n        ode_dt = dt_slow,\n    )\n\n    ## Create a callback to report state statistics for main MPIStateArrays\n    ## every ntFreq timesteps.\n    nt_freq = 1 # floor(Int, 1 // 10 * solver_config.timeend / solver_config.dt)\n    cb = ClimateMachine.StateCheck.sccreate(\n        [\n            (solver_config.Q, \"oce Q_3D\"),\n            (solver_config.dg.state_auxiliary, \"oce aux\"),\n            (solver_config.dg.modeldata.Q_2D, \"baro Q_2D\"),\n            (solver_config.dg.modeldata.dg_2D.state_auxiliary, \"baro aux\"),\n        ],\n        nt_freq;\n        prec = 12,\n    )\n\n    result = ClimateMachine.invoke!(solver_config; user_callbacks = [cb])\n\n    ## Check results against reference if present\n    ClimateMachine.StateCheck.scprintref(cb)\n    if length(refDat) > 0\n        @test ClimateMachine.StateCheck.scdocheck(cb, refDat)\n    end\nend\n"
  },
  {
    "path": "experiments/TestCase/baroclinic_wave.jl",
    "content": "#!/usr/bin/env julia --project\n\nusing ArgParse\nusing LinearAlgebra\nusing StaticArrays\nusing Test\n\nusing ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.SystemSolvers: ManyColumnLU\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Grids\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics:\n    air_density, air_temperature, total_energy, internal_energy, PhasePartition\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Spectra: compute_gaussian!\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: MSLP, R_d, day, grav, Omega, planet_radius\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nfunction init_baroclinic_wave!(problem, bl, state, aux, localgeo, t)\n    FT = eltype(state)\n\n    # parameters\n    param_set = parameter_set(bl)\n    _grav::FT = grav(param_set)\n    _R_d::FT = R_d(param_set)\n    _Ω::FT = Omega(param_set)\n    _a::FT = planet_radius(param_set)\n    _p_0::FT = MSLP(param_set)\n\n    k::FT = 3\n    T_E::FT = 310\n    T_P::FT = 240\n    T_0::FT = 0.5 * (T_E + T_P)\n    Γ::FT = 0.005\n    A::FT = 1 / Γ\n    B::FT = (T_0 - T_P) / T_0 / T_P\n    C::FT = 0.5 * (k + 2) * (T_E - T_P) / T_E / T_P\n    b::FT = 2\n    H::FT = _R_d * T_0 / _grav\n    z_t::FT = 15e3\n    λ_c::FT = π / 9\n    φ_c::FT = 2 * π / 9\n    d_0::FT = _a / 6\n    V_p::FT = 1\n    M_v::FT = 0.608\n    p_w::FT = 34e3                 # Pressure width parameter for specific humidity\n    η_crit::FT = p_w / _p_0        # Critical pressure coordinate\n    q_0::FT = 0.018                # Maximum specific humidity (default: 0.018)\n    q_t::FT = 1e-12                # Specific humidity above artificial tropopause\n    φ_w::FT = 2π / 9               # Specific humidity latitude wind parameter\n\n    # grid\n    φ = latitude(bl.orientation, aux)\n    λ = longitude(bl.orientation, aux)\n    z = altitude(bl.orientation, param_set, aux)\n    r::FT = z + _a\n    γ::FT = 1 # set to 0 for shallow-atmosphere case and to 1 for deep atmosphere case\n\n    # convenience functions for temperature and pressure\n    τ_z_1::FT = exp(Γ * z / T_0)\n    τ_z_2::FT = 1 - 2 * (z / b / H)^2\n    τ_z_3::FT = exp(-(z / b / H)^2)\n    τ_1::FT = 1 / T_0 * τ_z_1 + B * τ_z_2 * τ_z_3\n    τ_2::FT = C * τ_z_2 * τ_z_3\n    τ_int_1::FT = A * (τ_z_1 - 1) + B * z * τ_z_3\n    τ_int_2::FT = C * z * τ_z_3\n    I_T::FT =\n        (cos(φ) * (1 + γ * z / _a))^k -\n        k / (k + 2) * (cos(φ) * (1 + γ * z / _a))^(k + 2)\n\n    # base state virtual temperature, pressure, specific humidity, density\n    T_v::FT = (τ_1 - τ_2 * I_T)^(-1)\n    p::FT = _p_0 * exp(-_grav / _R_d * (τ_int_1 - τ_int_2 * I_T))\n\n    # base state velocity\n    U::FT =\n        _grav * k / _a *\n        τ_int_2 *\n        T_v *\n        (\n            (cos(φ) * (1 + γ * z / _a))^(k - 1) -\n            (cos(φ) * (1 + γ * z / _a))^(k + 1)\n        )\n    u_ref::FT =\n        -_Ω * (_a + γ * z) * cos(φ) +\n        sqrt((_Ω * (_a + γ * z) * cos(φ))^2 + (_a + γ * z) * cos(φ) * U)\n    v_ref::FT = 0\n    w_ref::FT = 0\n\n    # velocity perturbations\n    F_z::FT = 1 - 3 * (z / z_t)^2 + 2 * (z / z_t)^3\n    if z > z_t\n        F_z = FT(0)\n    end\n    d::FT = _a * acos(sin(φ) * sin(φ_c) + cos(φ) * cos(φ_c) * cos(λ - λ_c))\n    c3::FT = cos(π * d / 2 / d_0)^3\n    s1::FT = sin(π * d / 2 / d_0)\n    if 0 < d < d_0 && d != FT(_a * π)\n        u′::FT =\n            -16 * V_p / 3 / sqrt(3) *\n            F_z *\n            c3 *\n            s1 *\n            (-sin(φ_c) * cos(φ) + cos(φ_c) * sin(φ) * cos(λ - λ_c)) /\n            sin(d / _a)\n        v′::FT =\n            16 * V_p / 3 / sqrt(3) * F_z * c3 * s1 * cos(φ_c) * sin(λ - λ_c) /\n            sin(d / _a)\n    else\n        u′ = FT(0)\n        v′ = FT(0)\n    end\n    w′::FT = 0\n    u_sphere = SVector{3, FT}(u_ref + u′, v_ref + v′, w_ref + w′)\n    u_cart = sphr_to_cart_vec(bl.orientation, u_sphere, aux)\n\n    if moisture_model(bl) isa DryModel\n        q_tot = FT(0)\n    else\n        ## Compute moisture profile\n        ## Pressure coordinate η\n        ## η_crit = p_t / p_w ; p_t = 10000 hPa, p_w = 340 hPa\n        η = p / _p_0\n        if η > η_crit\n            q_tot = q_0 * exp(-(φ / φ_w)^4) * exp(-((η - 1) * _p_0 / p_w)^2)\n        else\n            q_tot = q_t\n        end\n    end\n    phase_partition = PhasePartition(q_tot)\n\n    ## temperature & density\n    T::FT = T_v / (1 + M_v * q_tot)\n    ρ::FT = air_density(param_set, T, p, phase_partition)\n\n    ## potential & kinetic energy\n    e_pot::FT = gravitational_potential(bl.orientation, aux)\n    e_kin::FT = 0.5 * u_cart' * u_cart\n    e_tot::FT = total_energy(param_set, e_kin, e_pot, T, phase_partition)\n\n    ## Assign state variables\n    state.ρ = ρ\n    state.ρu = ρ * u_cart\n    state.energy.ρe = ρ * e_tot\n\n    if !(moisture_model(bl) isa DryModel)\n        state.moisture.ρq_tot = ρ * q_tot\n    end\n\n    nothing\nend\n\nfunction config_baroclinic_wave(FT, poly_order, resolution, with_moisture)\n    # Set up a reference state for linearization of equations\n    temp_profile_ref =\n        DecayingTemperatureProfile{FT}(param_set, FT(290), FT(220), FT(8e3))\n    ref_state = HydrostaticState(temp_profile_ref)\n\n    # Set up the atmosphere model\n    exp_name = \"BaroclinicWave\"\n    domain_height::FT = 30e3 # distance between surface and top of atmosphere (m)\n    if with_moisture\n        hyperdiffusion = EquilMoistBiharmonic(FT(8 * 3600))\n        moisture = EquilMoist()\n        source = (Gravity(), Coriolis(), RemovePrecipitation(true)) # precipitation is default to NoPrecipitation() as 0M microphysics\n    else\n        hyperdiffusion = DryBiharmonic(FT(8 * 3600))\n        moisture = DryModel()\n        source = (Gravity(), Coriolis())\n    end\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = ConstantKinematicViscosity(FT(0)),\n        hyperdiffusion = hyperdiffusion,\n        moisture = moisture,\n    )\n    model = AtmosModel{FT}(\n        AtmosGCMConfigType,\n        physics;\n        init_state_prognostic = init_baroclinic_wave!,\n        source = source,\n    )\n\n    config = ClimateMachine.AtmosGCMConfiguration(\n        exp_name,\n        poly_order,\n        resolution,\n        domain_height,\n        param_set,\n        init_baroclinic_wave!;\n        model = model,\n    )\n\n    return config\nend\n\nfunction main()\n    # add a command line argument to specify whether to use a moist setup\n    # TODO: this will move to the future namelist functionality\n    bw_args = ArgParseSettings(autofix_names = true)\n    add_arg_group!(bw_args, \"BaroclinicWave\")\n    @add_arg_table! bw_args begin\n        \"--with-moisture\"\n        help = \"use a moist setup\"\n        action = :store_const\n        constant = true\n        default = false\n    end\n\n    cl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = bw_args)\n    with_moisture = cl_args[\"with_moisture\"]\n\n    # Driver configuration parameters\n    FT = Float64                             # floating type precision\n    poly_order = (5, 6)                      # discontinuous Galerkin polynomial order\n    n_horz = 8                               # horizontal element number\n    n_vert = 3                               # vertical element number\n    n_days::FT = 1\n    timestart::FT = 0                        # start time (s)\n    timeend::FT = n_days * day(param_set)    # end time (s)\n\n    # Set up driver configuration\n    driver_config =\n        config_baroclinic_wave(FT, poly_order, (n_horz, n_vert), with_moisture)\n\n    # Set up experiment\n    ode_solver_type = ClimateMachine.IMEXSolverType(\n        implicit_model = AtmosAcousticGravityLinearModel,\n        implicit_solver = ManyColumnLU,\n        solver_method = ARK2GiraldoKellyConstantinescu,\n        split_explicit_implicit = true,\n        discrete_splitting = false,\n    )\n\n    CFL = FT(0.1) # target acoustic CFL number\n\n    # time step is computed such that the horizontal acoustic Courant number is CFL\n    solver_config = ClimateMachine.SolverConfiguration(\n        timestart,\n        timeend,\n        driver_config,\n        Courant_number = CFL,\n        ode_solver_type = ode_solver_type,\n        CFL_direction = HorizontalDirection(),\n        diffdir = HorizontalDirection(),\n    )\n\n    # Set up diagnostics\n    dgn_config = config_diagnostics(FT, driver_config)\n\n    # Set up user-defined callbacks\n    filterorder = 20\n    filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)\n    cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            AtmosFilterPerturbations(driver_config.bl),\n            solver_config.dg.grid,\n            filter,\n            state_auxiliary = solver_config.dg.state_auxiliary,\n        )\n        nothing\n    end\n\n    cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            (\"moisture.ρq_tot\",),\n            solver_config.dg.grid,\n            TMARFilter(),\n        )\n        nothing\n    end\n\n    # Run the model\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (cbfilter,),\n        #user_callbacks = (cbtmarfilter, cbfilter),\n        check_euclidean_distance = true,\n    )\nend\n\nfunction config_diagnostics(FT, driver_config)\n    interval = \"12shours\" # chosen to allow diagnostics every 12 simulated hours\n\n    _planet_radius = FT(planet_radius(param_set))\n\n    info = driver_config.config_info\n\n    # Setup diagnostic grid(s)\n    nlats = 128\n\n    sinθ, wts = compute_gaussian!(nlats)\n    lats = asin.(sinθ) .* 180 / π\n    lons = 180.0 ./ nlats * collect(FT, 1:1:(2nlats))[:] .- 180.0\n\n    boundaries = [\n        FT(lats[1]) FT(lons[1]) _planet_radius\n        FT(lats[end]) FT(lons[end]) FT(_planet_radius + info.domain_height)\n    ]\n\n    lvls = collect(range(\n        boundaries[1, 3],\n        boundaries[2, 3],\n        step = FT(1000), # in m\n    ))\n\n    #boundaries = [\n    #    FT(-90.0) FT(-180.0) _planet_radius\n    #    FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)\n    #]\n\n    #resolution = (FT(2), FT(2), FT(1000)) # in (deg, deg, m)\n\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries;\n        axes = [lats, lons, lvls],\n    )\n\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosGCMConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n\n    ds_dgngrp = setup_atmos_spectra_diagnostics(\n        AtmosGCMConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp, ds_dgngrp])\nend\n\nmain()\n"
  },
  {
    "path": "experiments/TestCase/baroclinic_wave_fvm.jl",
    "content": "#!/usr/bin/env julia --project\n\nusing ArgParse\nusing LinearAlgebra\nusing StaticArrays\nusing Test\n\nusing ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.SystemSolvers: ManyColumnLU\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Grids\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics:\n    air_density, air_temperature, total_energy, internal_energy, PhasePartition\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nimport ClimateMachine.DGMethods.FVReconstructions: FVLinear\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: MSLP, R_d, day, grav, Omega, planet_radius\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nfunction init_baroclinic_wave!(problem, bl, state, aux, localgeo, t)\n    FT = eltype(state)\n\n    # parameters\n    param_set = parameter_set(bl)\n    _grav::FT = grav(param_set)\n    _R_d::FT = R_d(param_set)\n    _Ω::FT = Omega(param_set)\n    _a::FT = planet_radius(param_set)\n    _p_0::FT = MSLP(param_set)\n\n    k::FT = 3\n    T_E::FT = 310\n    T_P::FT = 240\n    T_0::FT = 0.5 * (T_E + T_P)\n    Γ::FT = 0.005\n    A::FT = 1 / Γ\n    B::FT = (T_0 - T_P) / T_0 / T_P\n    C::FT = 0.5 * (k + 2) * (T_E - T_P) / T_E / T_P\n    b::FT = 2\n    H::FT = _R_d * T_0 / _grav\n    z_t::FT = 15e3\n    λ_c::FT = π / 9\n    φ_c::FT = 2 * π / 9\n    d_0::FT = _a / 6\n    V_p::FT = 1\n    M_v::FT = 0.608\n    p_w::FT = 34e3                 # Pressure width parameter for specific humidity\n    η_crit::FT = p_w / _p_0        # Critical pressure coordinate\n    q_0::FT = 0.018                # Maximum specific humidity (default: 0.018)\n    q_t::FT = 1e-12                # Specific humidity above artificial tropopause\n    φ_w::FT = 2π / 9               # Specific humidity latitude wind parameter\n\n    # grid\n    φ = latitude(bl.orientation, aux)\n    λ = longitude(bl.orientation, aux)\n    z = altitude(bl.orientation, param_set, aux)\n    r::FT = z + _a\n    γ::FT = 1 # set to 0 for shallow-atmosphere case and to 1 for deep atmosphere case\n\n    # convenience functions for temperature and pressure\n    τ_z_1::FT = exp(Γ * z / T_0)\n    τ_z_2::FT = 1 - 2 * (z / b / H)^2\n    τ_z_3::FT = exp(-(z / b / H)^2)\n    τ_1::FT = 1 / T_0 * τ_z_1 + B * τ_z_2 * τ_z_3\n    τ_2::FT = C * τ_z_2 * τ_z_3\n    τ_int_1::FT = A * (τ_z_1 - 1) + B * z * τ_z_3\n    τ_int_2::FT = C * z * τ_z_3\n    I_T::FT =\n        (cos(φ) * (1 + γ * z / _a))^k -\n        k / (k + 2) * (cos(φ) * (1 + γ * z / _a))^(k + 2)\n\n    # base state virtual temperature, pressure, specific humidity, density\n    T_v::FT = (τ_1 - τ_2 * I_T)^(-1)\n    p::FT = _p_0 * exp(-_grav / _R_d * (τ_int_1 - τ_int_2 * I_T))\n\n    # base state velocity\n    U::FT =\n        _grav * k / _a *\n        τ_int_2 *\n        T_v *\n        (\n            (cos(φ) * (1 + γ * z / _a))^(k - 1) -\n            (cos(φ) * (1 + γ * z / _a))^(k + 1)\n        )\n    u_ref::FT =\n        -_Ω * (_a + γ * z) * cos(φ) +\n        sqrt((_Ω * (_a + γ * z) * cos(φ))^2 + (_a + γ * z) * cos(φ) * U)\n    v_ref::FT = 0\n    w_ref::FT = 0\n\n    # velocity perturbations\n    F_z::FT = 1 - 3 * (z / z_t)^2 + 2 * (z / z_t)^3\n    if z > z_t\n        F_z = FT(0)\n    end\n    d::FT = _a * acos(sin(φ) * sin(φ_c) + cos(φ) * cos(φ_c) * cos(λ - λ_c))\n    c3::FT = cos(π * d / 2 / d_0)^3\n    s1::FT = sin(π * d / 2 / d_0)\n    if 0 < d < d_0 && d != FT(_a * π)\n        u′::FT =\n            -16 * V_p / 3 / sqrt(3) *\n            F_z *\n            c3 *\n            s1 *\n            (-sin(φ_c) * cos(φ) + cos(φ_c) * sin(φ) * cos(λ - λ_c)) /\n            sin(d / _a)\n        v′::FT =\n            16 * V_p / 3 / sqrt(3) * F_z * c3 * s1 * cos(φ_c) * sin(λ - λ_c) /\n            sin(d / _a)\n    else\n        u′ = FT(0)\n        v′ = FT(0)\n    end\n    w′::FT = 0\n    u_sphere = SVector{3, FT}(u_ref + u′, v_ref + v′, w_ref + w′)\n    u_cart = sphr_to_cart_vec(bl.orientation, u_sphere, aux)\n\n    if moisture_model(bl) isa DryModel\n        q_tot = FT(0)\n    else\n        ## Compute moisture profile\n        ## Pressure coordinate η\n        ## η_crit = p_t / p_w ; p_t = 10000 hPa, p_w = 340 hPa\n        η = p / _p_0\n        if η > η_crit\n            q_tot = q_0 * exp(-(φ / φ_w)^4) * exp(-((η - 1) * _p_0 / p_w)^2)\n        else\n            q_tot = q_t\n        end\n    end\n    phase_partition = PhasePartition(q_tot)\n\n    ## temperature & density\n    T::FT = T_v / (1 + M_v * q_tot)\n    ρ::FT = air_density(param_set, T, p, phase_partition)\n\n    ## potential & kinetic energy\n    e_pot::FT = gravitational_potential(bl.orientation, aux)\n    e_kin::FT = 0.5 * u_cart' * u_cart\n    e_tot::FT = total_energy(param_set, e_kin, e_pot, T, phase_partition)\n\n    ## Assign state variables\n    state.ρ = ρ\n    state.ρu = ρ * u_cart\n    state.energy.ρe = ρ * e_tot\n\n    if !(moisture_model(bl) isa DryModel)\n        state.moisture.ρq_tot = ρ * q_tot\n    end\n\n    nothing\nend\n\nfunction config_baroclinic_wave(\n    FT,\n    poly_order,\n    fv_reconstruction,\n    resolution,\n    with_moisture,\n)\n    # Set up a reference state for linearization of equations\n    temp_profile_ref =\n        DecayingTemperatureProfile{FT}(param_set, FT(290), FT(220), FT(8e3))\n    ref_state = HydrostaticState(temp_profile_ref; subtract_off = false)\n\n    # Set up the atmosphere model\n    exp_name = \"BaroclinicWave\"\n    domain_height::FT = 30e3 # distance between surface and top of atmosphere (m)\n    if with_moisture\n        # hyperdiffusion = EquilMoistBiharmonic(FT(8 * 3600))\n        moisture = EquilMoist()\n        source = (Gravity(), Coriolis())\n    else\n        # hyperdiffusion = DryBiharmonic(FT(8 * 3600))\n        moisture = DryModel()\n        source = (Gravity(), Coriolis())\n    end\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = ConstantKinematicViscosity(FT(0)),\n        # hyperdiffusion = hyperdiffusion,\n        moisture = moisture,\n    )\n    model = AtmosModel{FT}(\n        AtmosGCMConfigType,\n        physics;\n        init_state_prognostic = init_baroclinic_wave!,\n        source = source,\n    )\n\n    config = ClimateMachine.AtmosGCMConfiguration(\n        exp_name,\n        poly_order,\n        resolution,\n        domain_height,\n        param_set,\n        init_baroclinic_wave!;\n        model = model,\n        numerical_flux_first_order = RoeNumericalFlux(),\n        fv_reconstruction = HBFVReconstruction(model, fv_reconstruction),\n    )\n\n    return config\nend\n\nfunction main()\n    # add a command line argument to specify whether to use a moist setup\n    # TODO: this will move to the future namelist functionality\n    bw_args = ArgParseSettings(autofix_names = true)\n    add_arg_group!(bw_args, \"BaroclinicWave\")\n    @add_arg_table! bw_args begin\n        \"--with-moisture\"\n        help = \"use a moist setup\"\n        action = :store_const\n        constant = true\n        default = false\n    end\n\n    cl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = bw_args)\n    with_moisture = cl_args[\"with_moisture\"]\n\n    # Driver configuration parameters\n    FT = Float64                             # floating type precision\n    poly_order = (5, 0)                      # discontinuous Galerkin polynomial order\n    fv_reconstruction = FVLinear()           # finite volume reconstruction scheme\n    n_horz = 8                               # horizontal element number\n    n_vert = 20                              # vertical element number\n    timestart::FT = 0                        # start time (s)\n    timeend::FT = 3600                       # end time (s)\n\n    # Set up driver configuration\n    driver_config = config_baroclinic_wave(\n        FT,\n        poly_order,\n        fv_reconstruction,\n        (n_horz, n_vert),\n        with_moisture,\n    )\n\n    # Set up experiment\n\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK54CarpenterKennedy,\n    )\n\n    CFL = FT(0.2) # target acoustic CFL number\n\n    # time step is computed such that the horizontal acoustic Courant number is CFL\n    solver_config = ClimateMachine.SolverConfiguration(\n        timestart,\n        timeend,\n        driver_config,\n        Courant_number = CFL,\n        ode_solver_type = ode_solver_type,\n        CFL_direction = EveryDirection(),\n        diffdir = HorizontalDirection(),\n    )\n\n    # Set up diagnostics\n    dgn_config = config_diagnostics(FT, driver_config)\n\n    #TODO enable filter\n    # # Set up user-defined callbacks\n    filterorder = 20\n    filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)\n    cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            AtmosFilterPerturbations(driver_config.bl),\n            solver_config.dg.grid,\n            filter,\n            state_auxiliary = solver_config.dg.state_auxiliary,\n            direction = HorizontalDirection(),\n        )\n        nothing\n    end\n\n    # cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n    #     Filters.apply!(\n    #         solver_config.Q,\n    #         (\"moisture.ρq_tot\",),\n    #         solver_config.dg.grid,\n    #         TMARFilter(),\n    #     )\n    #     nothing\n    # end\n\n    # Run the model\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (cbfilter,),\n        #user_callbacks = (cbtmarfilter, cbfilter),\n        check_euclidean_distance = true,\n    )\nend\n\nfunction config_diagnostics(FT, driver_config)\n    interval = \"12shours\" # chosen to allow diagnostics every 12 simulated hours\n\n    _planet_radius = FT(planet_radius(param_set))\n\n    info = driver_config.config_info\n    boundaries = [\n        FT(-90.0) FT(-180.0) _planet_radius\n        FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)\n    ]\n    resolution = (FT(2), FT(2), FT(1000)) # in (deg, deg, m)\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries,\n        resolution,\n    )\n\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosGCMConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp])\nend\n\nmain()\n"
  },
  {
    "path": "experiments/TestCase/isothermal_zonal_flow.jl",
    "content": "#!/usr/bin/env julia --project\nusing ClimateMachine\nClimateMachine.init(parse_clargs = true)\n\nusing ClimateMachine.Atmos\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.NumericalFluxes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.Orientations\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.SystemSolvers: ManyColumnLU\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Interpolation\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics: total_energy, air_density\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\n\nusing Distributions: Uniform\nusing LinearAlgebra\nusing StaticArrays\nusing Test\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet:\n    R_d, day, grav, cp_d, planet_radius, Omega, kappa_d, MSLP\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nimport CLIMAParameters\nCLIMAParameters.Planet.Omega(::EarthParameterSet) = 0.0\nCLIMAParameters.Planet.planet_radius(::EarthParameterSet) = 6.371e6 / 125.0\nCLIMAParameters.Planet.MSLP(::EarthParameterSet) = 1e5\n\nfunction init_isothermal_zonal_flow!(problem, bl, state, aux, localgeo, t)\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n    φ = latitude(bl.orientation, aux)\n    z = altitude(bl.orientation, param_set, aux)\n\n    _grav::FT = grav(param_set)\n    _a::FT = planet_radius(param_set)\n    _R_d::FT = R_d(param_set)\n    _MSLP::FT = MSLP(param_set)\n\n    u₀ = FT(20)\n    T₀ = FT(300)\n\n    shallow_atmos = false\n    if shallow_atmos\n        f1 = z\n        f2 = FT(0)\n        shear = FT(1)\n    else\n        f1 = z\n        f2 = z / _a + z^2 / (2 * _a^2)\n        shear = 1 + z / _a\n    end\n\n    u_sphere = SVector{3, FT}(u₀ * shear * cos(φ), 0, 0)\n    u_init = sphr_to_cart_vec(bl.orientation, u_sphere, aux)\n\n    prefac = u₀^2 / (_R_d * T₀)\n    fac1 = prefac * f2 * cos(φ)^2\n    fac2 = prefac * sin(φ)^2 / 2\n    fac3 = _grav * f1 / (_R_d * T₀)\n    exparg = fac1 - fac2 - fac3\n    p = _MSLP * exp(exparg)\n\n    ρ = air_density(param_set, T₀, p)\n\n    e_pot = gravitational_potential(bl.orientation, aux)\n    e_kin = u_init' * u_init / 2\n\n    state.ρ = ρ\n    state.ρu = ρ * u_init\n    state.energy.ρe = ρ * total_energy(param_set, e_kin, e_pot, T₀)\nend\n\nfunction config_isothermal_zonal_flow(\n    FT,\n    poly_order,\n    cutoff_order,\n    resolution,\n    ref_state,\n)\n    # Set up a reference state for linearization of equations\n\n    domain_height = FT(10e3)\n\n    # Set up the atmosphere model\n    exp_name = \"IsothermalZonalFlow\"\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = ConstantKinematicViscosity(FT(0)),\n        moisture = DryModel(),\n    )\n    model = AtmosModel{FT}(\n        AtmosGCMConfigType,\n        physics;\n        init_state_prognostic = init_isothermal_zonal_flow!,\n        source = (Gravity(),),\n    )\n\n    config = ClimateMachine.AtmosGCMConfiguration(\n        exp_name,\n        poly_order,\n        resolution,\n        domain_height,\n        param_set,\n        init_isothermal_zonal_flow!;\n        model = model,\n        numerical_flux_first_order = RoeNumericalFlux(),\n        Ncutoff = cutoff_order,\n    )\n\n    return config\nend\n\nfunction config_diagnostics(FT, driver_config)\n    interval = \"40000steps\" # chosen to allow a single diagnostics collection\n\n    _planet_radius = FT(planet_radius(param_set))\n\n    info = driver_config.config_info\n    boundaries = [\n        FT(-90.0) FT(-180.0) _planet_radius\n        FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)\n    ]\n    resolution = (FT(10), FT(10), FT(100)) # in (deg, deg, m)\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries,\n        resolution,\n    )\n\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosGCMConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp])\nend\n\nfunction main()\n    # Driver configuration parameters\n    FT = Float64                             # floating type precision\n    poly_order = 5                           # discontinuous Galerkin polynomial order\n    cutoff_order = 4\n    n_horz = 10                              # horizontal element number\n    n_vert = 5                               # vertical element number\n    timestart = FT(0)                        # start time (s)\n    timeend = FT(3600)                       # end time (s)\n\n    # set up the reference state for linearization of equations\n    temp_profile_ref = IsothermalProfile(param_set, FT(300))\n    ref_state = HydrostaticState(temp_profile_ref)\n\n    # Set up driver configuration\n    driver_config = config_isothermal_zonal_flow(\n        FT,\n        poly_order,\n        cutoff_order,\n        (n_horz, n_vert),\n        ref_state,\n    )\n\n    # Set up experiment\n    ode_solver_type = ClimateMachine.IMEXSolverType(\n        implicit_model = AtmosAcousticGravityLinearModel,\n        implicit_solver = ManyColumnLU,\n        solver_method = ARK2GiraldoKellyConstantinescu,\n        split_explicit_implicit = false,\n        discrete_splitting = true,\n    )\n    CFL = FT(0.4)\n    solver_config = ClimateMachine.SolverConfiguration(\n        timestart,\n        timeend,\n        driver_config,\n        Courant_number = CFL,\n        init_on_cpu = true,\n        ode_solver_type = ode_solver_type,\n        CFL_direction = HorizontalDirection(),\n    )\n\n    # save the initial condition for testing\n    Q0 = copy(solver_config.Q)\n\n    # Set up diagnostics\n    dgn_config = config_diagnostics(FT, driver_config)\n\n    # Set up user-defined callbacks\n    filterorder = 64\n    filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)\n    cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            AtmosFilterPerturbations(driver_config.bl),\n            solver_config.dg.grid,\n            filter,\n            state_auxiliary = solver_config.dg.state_auxiliary,\n        )\n        nothing\n    end\n\n    # Run the model\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (cbfilter,),\n        check_euclidean_distance = true,\n    )\n\n    relative_error = norm(solver_config.Q .- Q0) / norm(Q0)\n    @info \"Relative error = $relative_error\"\n    @test relative_error < 1e-7\n\nend\n\nmain()\n"
  },
  {
    "path": "experiments/TestCase/risingbubble.jl",
    "content": "#!/usr/bin/env julia --project\nusing ArgParse\n\nusing ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing StaticArrays\nusing Test\nusing CLIMAParameters\nusing CLIMAParameters.Atmos.SubgridScale: C_smag\nusing CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet();\n\nfunction init_risingbubble!(problem, bl, state, aux, localgeo, t)\n    ## Problem float-type\n    FT = eltype(state)\n\n    (x, y, z) = localgeo.coord\n    param_set = parameter_set(bl)\n\n    ## Unpack constant parameters\n    R_gas::FT = R_d(param_set)\n    c_p::FT = cp_d(param_set)\n    c_v::FT = cv_d(param_set)\n    p0::FT = MSLP(param_set)\n    _grav::FT = grav(param_set)\n    γ::FT = c_p / c_v\n\n    ## Define bubble center and background potential temperature\n    xc::FT = 5000\n    yc::FT = 1000\n    zc::FT = 2000\n    r = sqrt((x - xc)^2 + (z - zc)^2)\n    rc::FT = 2000\n    θamplitude::FT = 2\n    q_tot_amplitude::FT = 1e-3\n\n    ## TODO: clean this up, or add convenience function:\n    ## This is configured in the reference hydrostatic state\n    ref_state = reference_state(bl)\n    θ_ref::FT = ref_state.virtual_temperature_profile.T_surface\n\n    ## Add the thermal perturbation:\n    Δθ::FT = 0\n    Δq_tot::FT = 0\n    if r <= rc\n        Δθ = θamplitude * (1.0 - r / rc)\n        Δq_tot = q_tot_amplitude * (1.0 - r / rc)\n    end\n\n    ## Compute perturbed thermodynamic state:\n    θ = θ_ref + Δθ                                      # potential temperature\n    q_pt = PhasePartition(Δq_tot)\n    R_m = gas_constant_air(param_set, q_pt)\n    _cp_m = cp_m(param_set, q_pt)\n    _cv_m = cv_m(param_set, q_pt)\n    π_exner = FT(1) - _grav / (_cp_m * θ) * z           # exner pressure\n    ρ = p0 / (R_gas * θ) * (π_exner)^(_cv_m / R_m)      # density\n    T = θ * π_exner\n\n    if moisture_model(bl) isa EquilMoist\n        e_int = internal_energy(param_set, T, q_pt)\n        ts = PhaseEquil_ρeq(param_set, ρ, e_int, Δq_tot)\n    else\n        e_int = internal_energy(param_set, T)\n        ts = PhaseDry(param_set, e_int, ρ)\n    end\n    ρu = SVector(FT(0), FT(0), FT(0))                   # momentum\n    ## State (prognostic) variable assignment\n    e_kin = FT(0)                                       # kinetic energy\n    e_pot = gravitational_potential(bl, aux)            # potential energy\n    ρe_tot = ρ * total_energy(e_kin, e_pot, ts)         # total energy\n    ρq_tot = ρ * Δq_tot                                 # total water specific humidity\n\n    ## Assign State Variables\n    state.ρ = ρ\n    state.ρu = ρu\n    state.energy.ρe = ρe_tot\n    if !(moisture_model(bl) isa DryModel)\n        state.moisture.ρq_tot = ρq_tot\n    end\nend\n\nfunction config_risingbubble(FT, N, resolution, xmax, ymax, zmax, with_moisture)\n\n    T_surface = FT(300)\n    T_min_ref = FT(0)\n    T_profile = DryAdiabaticProfile{FT}(param_set, T_surface, T_min_ref)\n    ref_state = HydrostaticState(T_profile)\n\n    _C_smag = FT(C_smag(param_set))\n\n    if with_moisture\n        moisture = EquilMoist()\n    else\n        moisture = DryModel()\n    end\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = SmagorinskyLilly(_C_smag),\n        moisture = moisture,\n        tracers = NoTracers(),\n    )\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        init_state_prognostic = init_risingbubble!,\n        source = (Gravity(),),\n    )\n\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"RisingBubble\",\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        init_risingbubble!,\n        model = model,\n    )\n    return config\nend\n\nfunction config_diagnostics(driver_config)\n    FT = Float64\n    interval = \"50ssecs\"\n    boundaries = [\n        FT(0.0) FT(0.0) FT(0.0)\n        FT(10000) FT(500) FT(10000)\n    ]\n    resolution = (FT(100), FT(100), FT(100))\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries,\n        resolution,\n    )\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosLESConfigType(),\n        interval,\n        driver_config.name,\n    )\n    state_dgngrp = setup_dump_state_diagnostics(\n        AtmosLESConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n    aux_dgngrp = setup_dump_aux_diagnostics(\n        AtmosLESConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([\n        dgngrp,\n        state_dgngrp,\n        aux_dgngrp,\n    ])\nend\n\nfunction main()\n    # add a command line argument to specify whether to use a moist setup\n    rb_args = ArgParseSettings(autofix_names = true)\n    add_arg_group!(rb_args, \"RisingBubble\")\n    @add_arg_table! rb_args begin\n        \"--with-moisture\"\n        help = \"use a moist setup\"\n        action = :store_const\n        constant = true\n        default = false\n    end\n    cl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = rb_args)\n    with_moisture = cl_args[\"with_moisture\"]\n\n    FT = Float64\n    N = 4\n    Δh = FT(125)\n    Δv = FT(125)\n    resolution = (Δh, Δh, Δv)\n    xmax = FT(10000)\n    ymax = FT(500)\n    zmax = FT(10000)\n    t0 = FT(0)\n    timeend = FT(1000)\n\n    CFL = FT(1.7)\n\n    driver_config =\n        config_risingbubble(FT, N, resolution, xmax, ymax, zmax, with_moisture)\n\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK144NiegemannDiehlBusch,\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFL,\n    )\n    dgn_config = config_diagnostics(driver_config)\n\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (),\n        check_euclidean_distance = true,\n    )\n\n    @test isapprox(result, FT(1); atol = 1.5e-3)\nend\n\nmain()\n"
  },
  {
    "path": "experiments/TestCase/risingbubble_fvm.jl",
    "content": "#!/usr/bin/env julia --project\nusing ArgParse\n\nusing ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nimport ClimateMachine.DGMethods.FVReconstructions: FVLinear\n\nusing StaticArrays\nusing Test\nusing CLIMAParameters\nusing CLIMAParameters.Atmos.SubgridScale: C_smag\nusing CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet();\n\nfunction init_risingbubble!(problem, bl, state, aux, localgeo, t)\n    ## Problem float-type\n    FT = eltype(state)\n\n    (x, y, z) = localgeo.coord\n    param_set = parameter_set(bl)\n\n    ## Unpack constant parameters\n    R_gas::FT = R_d(param_set)\n    c_p::FT = cp_d(param_set)\n    c_v::FT = cv_d(param_set)\n    p0::FT = MSLP(param_set)\n    _grav::FT = grav(param_set)\n    γ::FT = c_p / c_v\n\n    ## Define bubble center and background potential temperature\n    xc::FT = 5000\n    yc::FT = 1000\n    zc::FT = 2000\n    r = sqrt((x - xc)^2 + (z - zc)^2)\n    rc::FT = 2000\n    θamplitude::FT = 2\n    q_tot_amplitude::FT = 1e-3\n\n    ## TODO: clean this up, or add convenience function:\n    ## This is configured in the reference hydrostatic state\n    ref_state = reference_state(bl)\n    θ_ref::FT = ref_state.virtual_temperature_profile.T_surface\n\n    ## Add the thermal perturbation:\n    Δθ::FT = 0\n    Δq_tot::FT = 0\n    if r <= rc\n        Δθ = θamplitude * (1.0 - r / rc)\n        Δq_tot = q_tot_amplitude * (1.0 - r / rc)\n    end\n\n    ## Compute perturbed thermodynamic state:\n    θ = θ_ref + Δθ                                      # potential temperature\n    q_pt = PhasePartition(Δq_tot)\n    R_m = gas_constant_air(param_set, q_pt)\n    _cp_m = cp_m(param_set, q_pt)\n    _cv_m = cv_m(param_set, q_pt)\n    π_exner = FT(1) - _grav / (_cp_m * θ) * z           # exner pressure\n    ρ = p0 / (R_gas * θ) * (π_exner)^(_cv_m / R_m)      # density\n    T = θ * π_exner\n\n    if moisture_model(bl) isa EquilMoist\n        e_int = internal_energy(param_set, T, q_pt)\n        ts = PhaseEquil_ρeq(param_set, ρ, e_int, Δq_tot)\n    else\n        e_int = internal_energy(param_set, T)\n        ts = PhaseDry(param_set, e_int, ρ)\n    end\n    ρu = SVector(FT(0), FT(0), FT(0))                   # momentum\n    ## State (prognostic) variable assignment\n    e_kin = FT(0)                                       # kinetic energy\n    e_pot = gravitational_potential(bl, aux)            # potential energy\n    ρe_tot = ρ * total_energy(e_kin, e_pot, ts)         # total energy\n    ρq_tot = ρ * Δq_tot                                 # total water specific humidity\n\n    ## Assign State Variables\n    state.ρ = ρ\n    state.ρu = ρu\n    state.energy.ρe = ρe_tot\n    if !(moisture_model(bl) isa DryModel)\n        state.moisture.ρq_tot = ρq_tot\n    end\nend\n\nfunction config_risingbubble(\n    FT,\n    N,\n    fv_reconstruction,\n    resolution,\n    xmax,\n    ymax,\n    zmax,\n    with_moisture,\n)\n\n    T_surface = FT(300)\n    T_min_ref = FT(0)\n    T_profile = DryAdiabaticProfile{FT}(param_set, T_surface, T_min_ref)\n    ref_state = HydrostaticState(T_profile)\n\n    _C_smag = FT(C_smag(param_set))\n\n    if with_moisture\n        moisture = EquilMoist()\n    else\n        moisture = DryModel()\n    end\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = ConstantKinematicViscosity(FT(0)),\n        moisture = moisture,\n        tracers = NoTracers(),\n    )\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        init_state_prognostic = init_risingbubble!,\n        source = (Gravity(),),\n    )\n\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"RisingBubble\",\n        (N, 0),\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        init_risingbubble!,\n        model = model,\n        numerical_flux_first_order = RoeNumericalFlux(),\n        fv_reconstruction = HBFVReconstruction(model, fv_reconstruction),\n    )\n    return config\nend\n\nfunction config_diagnostics(driver_config)\n    FT = Float64\n    interval = \"50ssecs\"\n    boundaries = [\n        FT(0.0) FT(0.0) FT(0.0)\n        FT(10000) FT(500) FT(10000)\n    ]\n    resolution = (FT(100), FT(100), FT(100))\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries,\n        resolution,\n    )\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosLESConfigType(),\n        interval,\n        driver_config.name,\n    )\n    state_dgngrp = setup_dump_state_diagnostics(\n        AtmosLESConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n    aux_dgngrp = setup_dump_aux_diagnostics(\n        AtmosLESConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([\n        dgngrp,\n        state_dgngrp,\n        aux_dgngrp,\n    ])\nend\n\nfunction main()\n    # add a command line argument to specify whether to use a moist setup\n    rb_args = ArgParseSettings(autofix_names = true)\n    add_arg_group!(rb_args, \"RisingBubble\")\n    @add_arg_table! rb_args begin\n        \"--with-moisture\"\n        help = \"use a moist setup\"\n        action = :store_const\n        constant = true\n        default = false\n    end\n    cl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = rb_args)\n    with_moisture = cl_args[\"with_moisture\"]\n\n    FT = Float64\n    N = 4\n    # effective mesh size\n    Δh = FT(125)\n    Δv = FT(125)\n    resolution = (Δh, Δh, Δv)\n    xmax = FT(10000)\n    ymax = FT(500)\n    zmax = FT(10000)\n    t0 = FT(0)\n    timeend = FT(1000)\n    fv_reconstruction = FVLinear()         # finite volume reconstruction scheme\n\n    CFL = FT(0.2)\n\n    driver_config = config_risingbubble(\n        FT,\n        N,\n        fv_reconstruction,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        with_moisture,\n    )\n\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK54CarpenterKennedy,\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFL,\n    )\n    dgn_config = config_diagnostics(driver_config)\n\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (),\n        check_euclidean_distance = true,\n    )\n\n    @test isapprox(result, FT(1); atol = 1.5e-3)\nend\n\nmain()\n"
  },
  {
    "path": "experiments/TestCase/solid_body_rotation.jl",
    "content": "#!/usr/bin/env julia --project\nusing ClimateMachine\nClimateMachine.init(parse_clargs = true)\n\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.NumericalFluxes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.SystemSolvers: ManyColumnLU\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Interpolation\nusing Thermodynamics.TemperatureProfiles\nusing ClimateMachine.VariableTemplates\nusing Thermodynamics: air_density, total_energy\n\nusing LinearAlgebra\nusing StaticArrays\nusing Test\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: day, planet_radius\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nfunction init_solid_body_rotation!(problem, bl, state, aux, localgeo, t)\n    FT = eltype(state)\n\n    # initial velocity profile (we need to transform the vector into the Cartesian\n    # coordinate system)\n    u_0::FT = 0\n    u_sphere = SVector{3, FT}(u_0, 0, 0)\n    u_init = sphr_to_cart_vec(bl.orientation, u_sphere, aux)\n    e_kin::FT = 0.5 * sum(abs2.(u_init))\n\n    # Assign state variables\n    state.ρ = aux.ref_state.ρ\n    state.ρu = u_init\n    state.energy.ρe = aux.ref_state.ρe + state.ρ * e_kin\n\n    nothing\nend\n\nfunction config_solid_body_rotation(FT, poly_order, resolution, ref_state)\n\n    # Set up the atmosphere model\n    exp_name = \"SolidBodyRotation\"\n    domain_height::FT = 30e3 # distance between surface and top of atmosphere (m)\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = ConstantKinematicViscosity(FT(0)),\n        #hyperdiffusion = DryBiharmonic(FT(8 * 3600)),\n        moisture = DryModel(),\n    )\n    model = AtmosModel{FT}(\n        AtmosGCMConfigType,\n        physics;\n        init_state_prognostic = init_solid_body_rotation!,\n        source = (Gravity(), Coriolis()),\n    )\n\n    config = ClimateMachine.AtmosGCMConfiguration(\n        exp_name,\n        poly_order,\n        resolution,\n        domain_height,\n        param_set,\n        init_solid_body_rotation!;\n        model = model,\n        numerical_flux_first_order = RoeNumericalFlux(),\n    )\n\n    return config\nend\n\nfunction main()\n    # Driver configuration parameters\n    FT = Float64                             # floating type precision\n    poly_order = (5, 4)                     # discontinuous Galerkin polynomial order\n    n_horz = 8                              # horizontal element number\n    n_vert = 4                               # vertical element number\n    timestart::FT = 0                        # start time (s)\n    timeend::FT = 7200\n\n    # Set up a reference state for linearization of equations\n    temp_profile_ref =\n        DecayingTemperatureProfile{FT}(param_set, FT(290), FT(220), FT(8e3))\n    ref_state = HydrostaticState(temp_profile_ref)\n\n    # Set up driver configuration\n    driver_config =\n        config_solid_body_rotation(FT, poly_order, (n_horz, n_vert), ref_state)\n\n    # Set up experiment\n    ode_solver_type = ClimateMachine.IMEXSolverType(\n        implicit_model = AtmosAcousticGravityLinearModel,\n        implicit_solver = ManyColumnLU,\n        solver_method = ARK2GiraldoKellyConstantinescu,\n        split_explicit_implicit = false,\n        discrete_splitting = true,\n    )\n\n    CFL = FT(0.2) # target acoustic CFL number\n\n    # time step is computed such that the horizontal acoustic Courant number is CFL\n    solver_config = ClimateMachine.SolverConfiguration(\n        timestart,\n        timeend,\n        driver_config,\n        Courant_number = CFL,\n        ode_solver_type = ode_solver_type,\n        CFL_direction = HorizontalDirection(),\n        diffdir = HorizontalDirection(),\n    )\n\n    # initialize using a different ref state (mega-hack)\n    temp_profile_init =\n        DecayingTemperatureProfile{FT}(param_set, FT(280), FT(230), FT(9e3))\n    init_ref_state = HydrostaticState(temp_profile_init)\n\n    init_driver_config = config_solid_body_rotation(\n        FT,\n        poly_order,\n        (n_horz, n_vert),\n        init_ref_state,\n    )\n    init_solver_config = ClimateMachine.SolverConfiguration(\n        timestart,\n        timeend,\n        init_driver_config,\n        Courant_number = CFL,\n        ode_solver_type = ode_solver_type,\n        CFL_direction = HorizontalDirection(),\n        diffdir = HorizontalDirection(),\n    )\n\n    # initialization\n    solver_config.Q .= init_solver_config.Q\n\n    # Set up diagnostics\n    dgn_config = config_diagnostics(FT, driver_config)\n\n    # Set up user-defined callbacks\n    filterorder = 20\n    filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)\n    cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            AtmosFilterPerturbations(driver_config.bl),\n            solver_config.dg.grid,\n            filter,\n            # filter perturbations from the initial state\n            state_auxiliary = init_solver_config.dg.state_auxiliary,\n        )\n        nothing\n    end\n\n    # Run the model\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (cbfilter,),\n        check_euclidean_distance = false,\n    )\n\n    relative_error =\n        norm(solver_config.Q .- init_solver_config.Q) /\n        norm(init_solver_config.Q)\n    @info \"Relative error = $relative_error\"\n    @test relative_error < 1e-9\nend\n\nfunction config_diagnostics(FT, driver_config)\n    interval = \"0.5shours\" # chosen to allow diagnostics every 30 simulated minutes\n\n    _planet_radius = FT(planet_radius(param_set))\n\n    info = driver_config.config_info\n    boundaries = [\n        FT(-90.0) FT(-180.0) _planet_radius\n        FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)\n    ]\n    resolution = (FT(2), FT(2), FT(1000)) # in (deg, deg, m)\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries,\n        resolution,\n    )\n\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosGCMConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp])\nend\n\nmain()\n"
  },
  {
    "path": "experiments/TestCase/solid_body_rotation_fvm.jl",
    "content": "#!/usr/bin/env julia --project\nusing ClimateMachine\nClimateMachine.init(parse_clargs = true)\n\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.NumericalFluxes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.SystemSolvers: ManyColumnLU\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Interpolation\nusing ClimateMachine.Mesh.Topologies\nusing Thermodynamics.TemperatureProfiles\nusing ClimateMachine.VariableTemplates\nusing Thermodynamics: air_density, total_energy\nimport ClimateMachine.DGMethods.FVReconstructions: FVLinear\n\nusing LinearAlgebra\nusing StaticArrays\nusing Test\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: day, planet_radius\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nfunction init_solid_body_rotation!(problem, bl, state, aux, localgeo, t)\n    FT = eltype(state)\n\n    # initial velocity profile (we need to transform the vector into the Cartesian\n    # coordinate system)\n    u_0::FT = 0\n    u_sphere = SVector{3, FT}(u_0, 0, 0)\n    u_init = sphr_to_cart_vec(bl.orientation, u_sphere, aux)\n    e_kin::FT = 0.5 * sum(abs2.(u_init))\n\n    # Assign state variables\n    state.ρ = aux.ref_state.ρ\n    state.ρu = u_init\n    state.energy.ρe = aux.ref_state.ρe + state.ρ * e_kin\n\n    nothing\nend\n\nfunction config_solid_body_rotation(\n    FT,\n    poly_order,\n    fv_reconstruction,\n    resolution,\n    ref_state,\n)\n\n    # Set up the atmosphere model\n    exp_name = \"SolidBodyRotation\"\n    domain_height::FT = 30e3 # distance between surface and top of atmosphere (m)\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = ConstantKinematicViscosity(FT(0)),\n        #hyperdiffusion = DryBiharmonic(FT(8 * 3600)),\n        moisture = DryModel(),\n    )\n    model = AtmosModel{FT}(\n        AtmosGCMConfigType,\n        physics;\n        init_state_prognostic = init_solid_body_rotation!,\n        source = (Gravity(), Coriolis()),\n    )\n\n    config = ClimateMachine.AtmosGCMConfiguration(\n        exp_name,\n        poly_order,\n        resolution,\n        domain_height,\n        param_set,\n        init_solid_body_rotation!;\n        model = model,\n        numerical_flux_first_order = RoeNumericalFlux(),\n        fv_reconstruction = HBFVReconstruction(model, fv_reconstruction),\n        #grid_stretching = (SingleExponentialStretching(FT(2.0)),),\n    )\n\n    return config\nend\n\nfunction main()\n    # Driver configuration parameters\n    FT = Float64                             # floating type precision\n    poly_order = (5, 0)                     # discontinuous Galerkin polynomial order\n    n_horz = 8                              # horizontal element number\n    n_vert = 20                               # vertical element number\n    timestart::FT = 0                        # start time (s)\n    timeend::FT = 3600    # end time (s)\n    fv_reconstruction = FVLinear()\n\n    # Set up a reference state for linearization of equations\n    temp_profile_ref =\n        DecayingTemperatureProfile{FT}(param_set, FT(290), FT(220), FT(8e3))\n    ref_state = HydrostaticState(temp_profile_ref; subtract_off = false)\n\n    # Set up driver configuration\n    driver_config = config_solid_body_rotation(\n        FT,\n        poly_order,\n        fv_reconstruction,\n        (n_horz, n_vert),\n        ref_state,\n    )\n\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK54CarpenterKennedy,\n    )\n\n    CFL = FT(0.5) # target acoustic CFL number\n\n    # time step is computed such that the horizontal acoustic Courant number is CFL\n    solver_config = ClimateMachine.SolverConfiguration(\n        timestart,\n        timeend,\n        driver_config,\n        Courant_number = CFL,\n        ode_solver_type = ode_solver_type,\n        CFL_direction = EveryDirection(),\n        diffdir = HorizontalDirection(),\n    )\n\n    # initialize using a different ref state (mega-hack)\n    temp_profile_init =\n        DecayingTemperatureProfile{FT}(param_set, FT(280), FT(230), FT(9e3))\n    init_ref_state = HydrostaticState(temp_profile_init; subtract_off = false)\n\n    init_driver_config = config_solid_body_rotation(\n        FT,\n        poly_order,\n        fv_reconstruction,\n        (n_horz, n_vert),\n        init_ref_state,\n    )\n    init_solver_config = ClimateMachine.SolverConfiguration(\n        timestart,\n        timeend,\n        init_driver_config,\n        Courant_number = CFL,\n        ode_solver_type = ode_solver_type,\n        CFL_direction = EveryDirection(),\n        diffdir = HorizontalDirection(),\n    )\n\n    # initialization\n    solver_config.Q .= init_solver_config.Q\n\n    # Set up diagnostics\n    dgn_config = config_diagnostics(FT, driver_config)\n\n    # Set up user-defined callbacks\n    filterorder = 20\n    filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)\n    cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            AtmosFilterPerturbations(driver_config.bl),\n            # driver_config.bl,\n            solver_config.dg.grid,\n            filter,\n            # filter perturbations from the initial state\n            state_auxiliary = init_solver_config.dg.state_auxiliary,\n            direction = HorizontalDirection(),\n        )\n        nothing\n    end\n\n    # Run the model\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (cbfilter,),\n        check_euclidean_distance = false,\n    )\n\n    relative_error =\n        norm(solver_config.Q .- init_solver_config.Q) /\n        norm(init_solver_config.Q)\n    @info \"Relative error = $relative_error\"\n    @test relative_error < 1e-9\nend\n\nfunction config_diagnostics(FT, driver_config)\n    interval = \"0.5shours\" # chosen to allow diagnostics every 30 simulated minutes\n\n    _planet_radius = FT(planet_radius(param_set))\n\n    info = driver_config.config_info\n\n    # Setup diagnostic grid(s)\n\n    boundaries = [\n        FT(-90.0) FT(-180.0) _planet_radius\n        FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)\n    ]\n\n    lats = collect(range(boundaries[1, 1], boundaries[2, 1], step = FT(2)))\n\n    lons = collect(range(boundaries[1, 2], boundaries[2, 2], step = FT(2)))\n\n    lvls = collect(range(\n        boundaries[1, 3],\n        boundaries[2, 3],\n        step = FT(1000), # in m\n    ))\n\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config.grid.topology,\n        driver_config,\n        boundaries,\n        [lats, lons, lvls];\n        nr_toler = FT(1e-7),\n    )\n\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosGCMConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp])\nend\n\nmain()\n"
  },
  {
    "path": "experiments/TestCase/solid_body_rotation_mountain.jl",
    "content": "#!/usr/bin/env julia --project\nusing ClimateMachine\nClimateMachine.init(parse_clargs = true)\n\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.NumericalFluxes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.SystemSolvers: ManyColumnLU\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Interpolation\nusing Thermodynamics.TemperatureProfiles\nusing ClimateMachine.VariableTemplates\nusing Thermodynamics: air_density, total_energy\n\nusing LinearAlgebra\nusing StaticArrays\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: day, planet_radius\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nfunction set_topofun(f, r_inner, r_outer, topography)\n    function wrapper_topo(a, b, c)\n        return f(\n            a,\n            b,\n            c,\n            max(abs(a), abs(b), abs(c));\n            r_inner = r_inner,\n            r_outer = r_outer,\n            topography = topography,\n        )\n    end\n    return wrapper_topo\nend\n\nfunction init_solid_body_rotation!(problem, bl, state, aux, localgeo, t)\n    FT = eltype(state)\n\n    # initial velocity profile (we need to transform the vector into the Cartesian\n    # coordinate system)\n    u_0::FT = 0\n    u_sphere = SVector{3, FT}(u_0, 0, 0)\n    u_init = sphr_to_cart_vec(bl.orientation, u_sphere, aux)\n    e_kin::FT = 0.5 * sum(abs2.(u_init))\n\n    # Assign state variables\n    state.ρ = aux.ref_state.ρ\n    state.ρu = u_init\n    state.energy.ρe = aux.ref_state.ρe + state.ρ * e_kin\n\n    nothing\nend\n\nfunction config_solid_body_rotation(FT, poly_order, resolution, ref_state)\n\n    # Set up the atmosphere model\n    exp_name = \"SolidBodyRotation\"\n    domain_height::FT = 30e3 # distance between surface and top of atmosphere (m)\n\n    _planet_radius = FT(planet_radius(param_set))\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = ConstantKinematicViscosity(FT(0)),\n        #hyperdiffusion = DryBiharmonic(FT(8 * 3600)),\n        moisture = DryModel(),\n    )\n    model = AtmosModel{FT}(\n        AtmosGCMConfigType,\n        physics;\n        init_state_prognostic = init_solid_body_rotation!,\n        source = (Gravity(), Coriolis()),\n    )\n\n    config = ClimateMachine.AtmosGCMConfiguration(\n        exp_name,\n        poly_order,\n        resolution,\n        domain_height,\n        param_set,\n        init_solid_body_rotation!;\n        model = model,\n        numerical_flux_first_order = RoeNumericalFlux(),\n        meshwarp = set_topofun(\n            cubed_sphere_topo_warp,                   ## Topography warp function\n            _planet_radius,                       ## Domain inner radius\n            _planet_radius + domain_height,       ## Domain outer radius\n            DCMIPMountain(),                      ## Problem specific dispatch\n        ),\n    )\n\n    return config\nend\n\nfunction main()\n    # Driver configuration parameters\n    FT = Float64                             # floating type precision\n    poly_order = 5                           # discontinuous Galerkin polynomial order\n    n_horz = 8                               # horizontal element number\n    n_vert = 4                               # vertical element number\n    timestart::FT = 0                        # start time (s)\n    timeend::FT = 7200\n\n    # Set up a reference state for linearization of equations\n    temp_profile_ref =\n        DecayingTemperatureProfile{FT}(param_set, FT(290), FT(220), FT(8e3))\n    ref_state = HydrostaticState(temp_profile_ref)\n\n    # Set up driver configuration\n    driver_config =\n        config_solid_body_rotation(FT, poly_order, (n_horz, n_vert), ref_state)\n\n    # Set up experiment\n    ode_solver_type = ClimateMachine.IMEXSolverType(\n        implicit_model = AtmosAcousticGravityLinearModel,\n        implicit_solver = ManyColumnLU,\n        solver_method = ARK2GiraldoKellyConstantinescu,\n        split_explicit_implicit = false,\n        discrete_splitting = true,\n    )\n\n    CFL = FT(0.2) # target acoustic CFL number\n\n    # time step is computed such that the horizontal acoustic Courant number is CFL\n    solver_config = ClimateMachine.SolverConfiguration(\n        timestart,\n        timeend,\n        driver_config,\n        Courant_number = CFL,\n        ode_solver_type = ode_solver_type,\n        CFL_direction = HorizontalDirection(),\n        diffdir = HorizontalDirection(),\n    )\n\n    # initialize using a different ref state (mega-hack)\n    temp_profile_init =\n        DecayingTemperatureProfile{FT}(param_set, FT(280), FT(230), FT(9e3))\n    init_ref_state = HydrostaticState(temp_profile_init)\n\n    init_driver_config = config_solid_body_rotation(\n        FT,\n        poly_order,\n        (n_horz, n_vert),\n        init_ref_state,\n    )\n    init_solver_config = ClimateMachine.SolverConfiguration(\n        timestart,\n        timeend,\n        init_driver_config,\n        Courant_number = CFL,\n        ode_solver_type = ode_solver_type,\n        CFL_direction = HorizontalDirection(),\n        diffdir = HorizontalDirection(),\n    )\n\n    # initialization\n    solver_config.Q .= init_solver_config.Q\n\n    # Set up diagnostics\n    dgn_config = config_diagnostics(FT, driver_config)\n\n    # Set up user-defined callbacks\n    filterorder = 20\n    filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)\n    cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            AtmosFilterPerturbations(driver_config.bl),\n            solver_config.dg.grid,\n            filter,\n            # filter perturbations from the initial state\n            state_auxiliary = init_solver_config.dg.state_auxiliary,\n        )\n        nothing\n    end\n\n    # Run the model\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (cbfilter,),\n        check_euclidean_distance = false,\n    )\n\n    relative_error =\n        norm(solver_config.Q .- init_solver_config.Q) /\n        norm(init_solver_config.Q)\n    @info \"Relative error = $relative_error\"\nend\n\nfunction config_diagnostics(FT, driver_config)\n    interval = \"0.5shours\" # chosen to allow diagnostics every 30 simulated minutes\n\n    _planet_radius = FT(planet_radius(param_set))\n\n    info = driver_config.config_info\n    boundaries = [\n        FT(-90.0) FT(-180.0) _planet_radius\n        FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)\n    ]\n    resolution = (FT(2), FT(2), FT(1000)) # in (deg, deg, m)\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries,\n        resolution,\n    )\n\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosGCMConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp])\nend\n\nmain()\n"
  },
  {
    "path": "src/Arrays/CMBuffers.jl",
    "content": "module CMBuffers\n\nimport CUDA\n\nusing MPI\nusing KernelAbstractions\nusing StaticArrays\n\nexport CMBuffer\nexport SingleCMBuffer, DoubleCMBuffer\nexport get_stage, get_transfer, prepare_transfer!, prepare_stage!\n\n@enum CMBufferKind begin\n    SingleCMBuffer\n    DoubleCMBuffer\nend\n\ndevice(::Union{Array, SArray, MArray}) = CPU()\ndevice(::CUDA.CuArray) = CUDADevice()\n\n###\n# Note: We use pinned and device-mapped hostbuffers in double staging\n# Potential improvements\n# - Investigate if we need to device-map\n# - Implement single buffered with pinned + device-mapped hostbuffers\n\n\"\"\"\n    CMBuffer{T}(::Type{Arr}, kind, dims...; pinned = true)\n\nCUDA/MPI buffer abstracts storage for MPI communication. The buffer is\nused for staging data and for MPI transfers. When running on:\n\n  - CPU -- a single buffer is used for staging and MPI transfers can be\n    initiated directly to/from it.\n  - CUDA -- either:\n    - MPI is CUDA-aware: a single buffer on the device for staging and MPI\n      transfers can be initiated directly to/from it, or\n    - MPI is not CUDA-aware: a double buffering scheme with the staging\n      buffer on the device and a transfer buffer on the host\n\n# Arguments\n- `T`: element type\n- `Arr::Type`: what kind of array to allocate for `stage`\n- `kind::CMBufferKind`: either `SingleCMBuffer` or `DoubleCMBuffer`\n- `dims...`: dimensions of the array\n\"\"\"\nstruct CMBuffer{T, Arr, Buff}\n    stage::Arr     # Same type as Q.data, used for staging\n    transfer::Buff # Union{Nothing,Buff}\n\n    function CMBuffer{T}(::Type{Arr}, kind, dims...) where {T, Arr}\n        if kind == SingleCMBuffer\n            transfer = nothing\n        elseif kind == DoubleCMBuffer\n            transfer = zeros(T, dims...)\n        else\n            error(\"CMBufferkind $kind is not implemented yet\")\n        end\n\n        stage = similar(Arr, T, dims...)\n        buffer = new{T, typeof(stage), typeof(transfer)}(stage, transfer)\n\n        return buffer\n    end\nend\n\nfunction get_stage(buf::CMBuffer)\n    return buf.stage\nend\n\nfunction get_transfer(buf::CMBuffer)\n    if buf.transfer === nothing\n        return buf.stage\n    else\n        return buf.transfer\n    end\nend\n\nfunction prepare_transfer!(\n    buf::CMBuffer;\n    dependencies = nothing,\n    progress = yield,\n)\n    if buf.transfer === nothing || length(buf.transfer) == 0\n        return MultiEvent(dependencies)\n    end\n\n    event = async_copy!(\n        device(buf.stage),\n        buf.transfer,\n        buf.stage;\n        dependencies = dependencies,\n        progress = progress,\n    )\n\n    return event\nend\n\nfunction prepare_stage!(buf::CMBuffer; dependencies = nothing, progress = yield)\n    if buf.transfer === nothing || length(buf.stage) == 0\n        return MultiEvent(dependencies)\n    end\n\n    event = async_copy!(\n        device(buf.stage),\n        buf.stage,\n        buf.transfer;\n        dependencies = dependencies,\n        progress = progress,\n    )\n\n    return event\nend\n\nend # module\n"
  },
  {
    "path": "src/Arrays/MPIStateArrays.jl",
    "content": "module MPIStateArrays\n\nusing CUDA\nusing DoubleFloats\nusing KernelAbstractions\nusing LazyArrays\nusing LinearAlgebra\nusing MPI\nusing StaticArrays\n\nusing ..TicToc\nusing ..VariableTemplates:\n    @vars, varsindex, varsindices, varsize, wrap_val, flattened_tup_chain\n\nusing Base.Broadcast: Broadcasted, BroadcastStyle, ArrayStyle\n\nstruct ErrorOnRemoteNode <: Exception end\n\n# This is so we can do things like\n#   similar(Array{Float64}, Int, 3, 4)\nBase.similar(::Type{A}, ::Type{FT}, dims...) where {A <: Array, FT} =\n    similar(Array{FT}, dims...)\n\nBase.similar(::Type{A}, ::Type{FT}, dims...) where {A <: CuArray, FT} =\n    similar(CuArray{FT}, dims...)\n\ninclude(\"CMBuffers.jl\")\nusing .CMBuffers\n\ncpuify(x::AbstractArray) = convert(Array, x)\ncpuify(x::Real) = x\n\nexport MPIStateArray,\n    euclidean_distance,\n    weightedsum,\n    array_device,\n    vars,\n    show_not_finite_fields,\n    ErrorOnRemoteNode,\n    checked_wait\n\n\"\"\"\n    MPIStateArray{FT, DATN<:AbstractArray{FT,3}, DAI1, DAV,\n                  DAT2<:AbstractArray{FT,2}} <: AbstractArray{FT, 3}\n\"\"\"\nmutable struct MPIStateArray{\n    FT,\n    V,\n    DATN <: AbstractArray{FT, 3},\n    DAI1,\n    DAV,\n    Buf <: CMBuffer,\n} <: AbstractArray{FT, 3}\n    mpicomm::MPI.Comm\n    data::DATN\n    realdata::DAV\n\n    realelems::UnitRange{Int64}\n    ghostelems::UnitRange{Int64}\n\n    vmaprecv::DAI1\n    vmapsend::DAI1\n\n    sendreq::Array{MPI.Request, 1}\n    recvreq::Array{MPI.Request, 1}\n\n    send_buffer::Buf\n    recv_buffer::Buf\n\n    nabrtorank::Array{Int64, 1}\n    nabrtovmaprecv::Array{UnitRange{Int64}, 1}\n    nabrtovmapsend::Array{UnitRange{Int64}, 1}\n\n    weights::DATN\n\n    function MPIStateArray{FT, V}(\n        mpicomm,\n        DA,\n        Np,\n        nstate,\n        numelem,\n        realelems,\n        ghostelems,\n        vmaprecv,\n        vmapsend,\n        nabrtorank,\n        nabrtovmaprecv,\n        nabrtovmapsend,\n        weights;\n        mpi_knows_cuda = nothing,\n    ) where {FT, V}\n        data = similar(DA, FT, Np, nstate, numelem)\n\n\n        if varsize(V) > 0 && varsize(V) != nstate\n            error(\"var sizes and numbers of states do not match\")\n        end\n\n        if isnothing(mpi_knows_cuda)\n            mpi_knows_cuda = MPI.has_cuda()\n        end\n\n        if data isa Array || mpi_knows_cuda\n            kind = SingleCMBuffer\n        else\n            kind = DoubleCMBuffer\n        end\n        recv_buffer = CMBuffer{FT}(DA, kind, nstate, length(vmaprecv))\n        send_buffer = CMBuffer{FT}(DA, kind, nstate, length(vmapsend))\n\n        realdata =\n            view(data, ntuple(i -> Colon(), ndims(data) - 1)..., realelems)\n        DAV = typeof(realdata)\n\n        nnabr = length(nabrtorank)\n        sendreq = fill(MPI.REQUEST_NULL, nnabr)\n        recvreq = fill(MPI.REQUEST_NULL, nnabr)\n\n        # If vmap is not on the device we need to copy it up (we also do not want to\n        # put it up everytime, so if it's already on the device then we do not do\n        # anything).\n        #\n        # Better way than checking the type names?\n        # XXX: Use Adapt.jl vmaprecv = adapt(DA, vmaprecv)\n        if typeof(vmaprecv).name != typeof(data).name\n            vmaprecv =\n                copyto!(similar(DA, eltype(vmaprecv), size(vmaprecv)), vmaprecv)\n        end\n        if typeof(vmapsend).name != typeof(data).name\n            vmapsend =\n                copyto!(similar(DA, eltype(vmapsend), size(vmapsend)), vmapsend)\n        end\n        if typeof(weights).name != typeof(data).name\n            weights = copyto!(\n                similar(DA, eltype(weights), size(weights)),\n                Array(weights),\n            )\n        end\n\n        DAI1 = typeof(vmaprecv)\n        Buf = typeof(send_buffer)\n        Q = new{FT, V, typeof(data), DAI1, DAV, Buf}(\n            # Make sure that each MPIStateArray has its own MPI context.  This\n            # allows multiple MPIStateArrays to be communicating asynchronously\n            # at the same time without having to explicitly manage tags.\n            MPI.Comm_dup(mpicomm),\n            data,\n            realdata,\n            realelems,\n            ghostelems,\n            vmaprecv,\n            vmapsend,\n            sendreq,\n            recvreq,\n            send_buffer,\n            recv_buffer,\n            nabrtorank,\n            nabrtovmaprecv,\n            nabrtovmapsend,\n            weights,\n        )\n        # Make sure that we have finished all outstanding data for halo\n        # exchanges before the MPIStateArray is finalize.\n        finalizer(Q) do x\n            if !MPI.Finalized()\n                MPI.Waitall!(x.recvreq)\n                MPI.Waitall!(x.sendreq)\n            end\n        end\n        return Q\n    end\nend\n\nfunction Base.fill!(Q::MPIStateArray, x)\n    fill!(Q.data, x)\n    return Q\nend\n\nvars(Q::MPIStateArray{FT, V}) where {FT, V} = V\nfunction Base.getproperty(Q::MPIStateArray{FT, V}, sym::Symbol) where {FT, V}\n    if sym ∈ V.names\n        varrange = varsindex(V, sym)\n        return view(realview(Q), :, varrange, :)\n    else\n        return getfield(Q, sym)\n    end\nend\n\n\"\"\"\n    getstateview(Q, fieldname)\n\nReturns of real element view of the state `fieldname`. The MPI state array `Q`\nmust have been generated in a `Vars`-aware fashion. This is mainly to allow accessing fields which line inside of the nested `Vars` state.\n\nThe state specified by 'fieldname' can be a 'String', 'Symbol', or 'Expr'\n\n# Examples\n```julia-repl\njulia> S = @vars(x::Float64, y::@vars(α::Float64, β::SVector{3, Float64}))\njulia> Q = MPIStateArray{Float64, S}(MPI.COMM_WORLD, Array, 2, 5, 3)\njulia> β = MPIStateArrays.getstateview(Q, \"y.β\")\njulia> β = MPIStateArrays.getstateview(Q, :(y.β))\njulia> x = MPIStateArrays.getstateview(Q, :x)\n```\n\"\"\"\nfunction getstateview(\n    Q::MPIStateArray{FT, V},\n    fieldname::Union{String, Symbol, Expr},\n) where {FT, V}\n    varrange = varsindices(V, fieldname)\n    return view(realview(Q), :, varrange[1]:varrange[end], :)\nend\n\n\"\"\"\n   MPIStateArray{FT, V}(mpicomm, DA, Np, nstate, numelem; realelems=1:numelem,\n                        ghostelems=numelem:numelem-1,\n                        vmaprecv=1:0,\n                        vmapsend=1:0,\n                        nabrtorank=Array{Int64}(undef, 0),\n                        nabrtovmaprecv=Array{UnitRange{Int64}}(undef, 0),\n                        nabrtovmapsend=Array{UnitRange{Int64}}(undef, 0),\n                        weights)\n\nConstruct an `MPIStateArray` over the communicator `mpicomm` with `numelem`\nelements, using array type `DA` with element type `FT`. `V` is an optional type\nto associate the `MPIStateArray` with a given `NamedTuple` used for `Vars` (e.g,\ngenerated by macro `@vars`). The arrays that are held in this created\n`MPIStateArray` will be of size `(Np, nstate, numelem)`.\n\nThe range `realelems` is the number of elements that this mpirank owns, whereas\nthe range `ghostelems` is the elements that are owned by other mpiranks.\nElements are stored as 'realelems` followed by `ghostelems`.\n\n  * `vmaprecv` is an ordered array of elements to be received from neighboring\n     mpiranks.  This is a vmap index into the MPIStateArray `A[:,*,:]`.\n  * `vmapsend` is an ordered array of elements to be sent to neighboring\n     mpiranks.  This is a vmap index into the MPIStateArray `A[:,*,:]`.\n  * `nabrtorank` is the list of neighboring mpiranks\n  * `nabrtovmaprecv` is an `Array` of `UnitRange` that give the ghost data to be\n    received from neighboring mpiranks (indexes into `vmaprecv`)\n  * `nabrtovmapsend` is an `Array` of `UnitRange` for which elements to send to\n    which neighboring mpiranks indexing into the `vmapsend`\n  * `weights` is an optional array which gives weight for each degree of freedom\n    to be used when computing the 2-norm of the array\n\"\"\"\nMPIStateArray{FT}(args...; kwargs...) where {FT} =\n    MPIStateArray{FT, @vars()}(args...; kwargs...)\n\nfunction MPIStateArray{FT, V}(\n    mpicomm,\n    DA,\n    Np,\n    nstate,\n    numelem;\n    realelems = 1:numelem,\n    ghostelems = numelem:(numelem - 1),\n    vmaprecv = 1:0,\n    vmapsend = 1:0,\n    nabrtorank = Array{Int64}(undef, 0),\n    nabrtovmaprecv = Array{UnitRange{Int64}}(undef, 0),\n    nabrtovmapsend = Array{UnitRange{Int64}}(undef, 0),\n    weights = nothing,\n    mpi_knows_cuda = nothing,\n) where {FT, V}\n\n    if weights == nothing\n        weights = similar(DA, FT, ntuple(j -> 0, 3))\n    end\n    MPIStateArray{FT, V}(\n        mpicomm,\n        DA,\n        Np,\n        nstate,\n        numelem,\n        realelems,\n        ghostelems,\n        vmaprecv,\n        vmapsend,\n        nabrtorank,\n        nabrtovmaprecv,\n        nabrtovmapsend,\n        weights,\n        mpi_knows_cuda = mpi_knows_cuda,\n    )\nend\n\n# FIXME: should general cases be handled?\nfunction Base.similar(\n    Q::MPIStateArray,\n    ::Type{A},\n    ::Type{FT};\n    vars::Type{V} = vars(Q),\n    nstate = size(Q.data)[2],\n) where {A <: AbstractArray, FT <: Number, V}\n    MPIStateArray{FT, V}(\n        Q.mpicomm,\n        A,\n        size(Q.data)[1],\n        nstate,\n        size(Q.data)[3],\n        Q.realelems,\n        Q.ghostelems,\n        Q.vmaprecv,\n        Q.vmapsend,\n        Q.nabrtorank,\n        Q.nabrtovmaprecv,\n        Q.nabrtovmapsend,\n        Q.weights,\n    )\nend\nfunction Base.similar(\n    Q::MPIStateArray{FT},\n    ::Type{A};\n    vars::Type{V} = vars(Q),\n    nstate = size(Q.data)[2],\n) where {A <: AbstractArray, FT <: Number, V}\n    similar(Q, A, FT; vars = V, nstate = nstate)\nend\nfunction Base.similar(\n    Q::MPIStateArray,\n    ::Type{FT};\n    vars::Type{V} = vars(Q),\n    nstate = size(Q.data)[2],\n) where {FT <: Number, V}\n    similar(Q, typeof(Q.data), FT; vars = V, nstate = nstate)\nend\nfunction Base.similar(\n    Q::MPIStateArray{FT};\n    vars::Type{V} = vars(Q),\n    nstate = size(Q.data)[2],\n) where {FT, V}\n    similar(Q, FT; vars = V, nstate = nstate)\nend\n\nBase.size(Q::MPIStateArray, x...; kw...) = size(Q.realdata, x...; kw...)\n\nBase.getindex(Q::MPIStateArray, x...; kw...) = getindex(Q.realdata, x...; kw...)\n\nBase.setindex!(Q::MPIStateArray, x...; kw...) =\n    setindex!(Q.realdata, x...; kw...)\n\nBase.eltype(Q::MPIStateArray, x...; kw...) = eltype(Q.data, x...; kw...)\n\nBase.Array(Q::MPIStateArray) = Array(Q.data)\n\n# broadcasting stuff\n\n# find the first MPIStateArray among `bc` arguments\n# based on https://docs.julialang.org/en/v1/manual/interfaces/#Selecting-an-appropriate-output-array-1\nfind_mpisa(bc::Broadcasted) = find_mpisa(bc.args)\nfind_mpisa(args::Tuple) = find_mpisa(find_mpisa(args[1]), Base.tail(args))\nfind_mpisa(x) = x\nfind_mpisa(a::MPIStateArray, rest) = a\nfind_mpisa(::Any, rest) = find_mpisa(rest)\n\nBase.BroadcastStyle(::Type{<:MPIStateArray}) = ArrayStyle{MPIStateArray}()\nfunction Base.similar(\n    bc::Broadcasted{ArrayStyle{MPIStateArray}},\n    ::Type{FT},\n) where {FT}\n    similar(find_mpisa(bc), FT)\nend\n\n# transform all arguments of `bc` from MPIStateArrays to Arrays\nfunction transform_broadcasted(bc::Broadcasted, ::Array)\n    transform_array(bc)\nend\nfunction transform_array(bc::Broadcasted)\n    Broadcasted(bc.f, transform_array.(bc.args), bc.axes)\nend\ntransform_array(mpisa::MPIStateArray) = mpisa.realdata\ntransform_array(x) = x\n\nBase.copyto!(dest::Array, src::MPIStateArray) = copyto!(dest, src.data)\n\nfunction Base.copyto!(dest::MPIStateArray, src::MPIStateArray)\n    copyto!(dest.realdata, src.realdata)\n    dest\nend\n\n@inline function Base.copyto!(dest::MPIStateArray, bc::Broadcasted{Nothing})\n    # check for the case a .= b, where b is an array\n    if bc.f === identity && bc.args isa Tuple{AbstractArray}\n        if bc.args isa Tuple{MPIStateArray}\n            realindices = CartesianIndices((\n                axes(dest.data)[1:(end - 1)]...,\n                dest.realelems,\n            ))\n            copyto!(dest.data, realindices, bc.args[1].data, realindices)\n        else\n            copyto!(dest.data, bc.args[1])\n        end\n    else\n        copyto!(dest.realdata, transform_broadcasted(bc, dest.data))\n    end\n    dest\nend\n\n@inline function Base.zero(Q::MPIStateArray{FT}) where {FT}\n    S = similar(Q)\n    fill!(S, 0)\n    return S\nend\n\n\"\"\"\n    begin_ghost_exchange!(Q::MPIStateArray; dependencies = nothing)\n\nBegin the MPI halo exchange of the data stored in `Q`.  A KernelAbstractions\n`Event` is returned that can be used as a dependency to end the exchange.\n\"\"\"\nfunction begin_ghost_exchange!(Q::MPIStateArray; dependencies = nothing)\n    if !all(r -> r == MPI.REQUEST_NULL, Q.recvreq)\n        error(\"The currrent ghost exchange must end before another begins.\")\n    end\n\n    __Irecv!(Q)\n\n    # wait on (prior) MPI sends\n    @tic mpi_sendwait\n    MPI.Waitall!(Q.sendreq)\n    fill!(Q.sendreq, MPI.REQUEST_NULL)\n    @toc mpi_sendwait\n\n    @tic mpi_sendcopy\n    stage = get_stage(Q.send_buffer)\n    progress = () -> iprobe_and_yield(Q.mpicomm)\n    event = fillsendbuf!(\n        stage,\n        Q.data,\n        Q.vmapsend;\n        dependencies = dependencies,\n        progress = progress,\n    )\n    event = prepare_transfer!(\n        Q.send_buffer;\n        dependencies = event,\n        progress = progress,\n    )\n    @toc mpi_sendcopy\n\n    return event\nend\n\n\"\"\"\n    end_ghost_exchange!(Q::MPIStateArray; dependencies = nothing)\n\nThis function blocks on the host until the ghost halo is received from MPI.  A\nKernelAbstractions `Event` is returned that can be waited on to indicate when\nthe data is ready on the device.\n\"\"\"\nfunction end_ghost_exchange!(\n    Q::MPIStateArray;\n    dependencies = nothing,\n    check_for_crashes = false,\n)\n    if any(r -> r == MPI.REQUEST_NULL, Q.recvreq)\n        error(\"A ghost exchange must begin before it ends.\")\n    end\n\n    progress = () -> iprobe_and_yield(Q.mpicomm)\n    checked_wait(CPU(), MultiEvent(dependencies), progress, check_for_crashes)\n\n    __Isend!(Q)\n\n    @tic mpi_recvwait\n    MPI.Waitall!(Q.recvreq)\n    fill!(Q.recvreq, MPI.REQUEST_NULL)\n    @toc mpi_recvwait\n\n    @tic mpi_recvcopy\n    event = prepare_stage!(Q.recv_buffer; progress = progress)\n    stage = get_stage(Q.recv_buffer)\n    event = transferrecvbuf!(\n        Q.data,\n        stage,\n        Q.vmaprecv;\n        dependencies = event,\n        progress = progress,\n    )\n    @toc mpi_recvcopy\n\n    return event\nend\n\nfunction __Irecv!(Q)\n    nnabr = length(Q.nabrtorank)\n    transfer = get_transfer(Q.recv_buffer)\n\n    for n in 1:nnabr\n        # If this fails we haven't waited on previous recv!\n        @assert Q.recvreq[n].buffer == nothing\n\n        Q.recvreq[n] = MPI.Irecv!(\n            (@view transfer[:, Q.nabrtovmaprecv[n]]),\n            Q.nabrtorank[n],\n            666,\n            Q.mpicomm,\n        )\n    end\nend\n\nfunction __Isend!(Q)\n    nnabr = length(Q.nabrtorank)\n    transfer = get_transfer(Q.send_buffer)\n\n    for n in 1:nnabr\n        Q.sendreq[n] = MPI.Isend(\n            (@view transfer[:, Q.nabrtovmapsend[n]]),\n            Q.nabrtorank[n],\n            666,\n            Q.mpicomm,\n        )\n    end\nend\n\nfunction iprobe_and_yield(comm)\n    MPI.Iprobe(MPI.MPI_ANY_SOURCE, MPI.MPI_ANY_TAG, comm)\n    yield()\nend\n\n# {{{ MPI Buffer handling\nfunction fillsendbuf!(\n    sendbuf,\n    buf,\n    vmapsend;\n    dependencies = nothing,\n    progress = yield,\n)\n    if length(vmapsend) == 0\n        return MultiEvent(dependencies)\n    end\n\n    Np = size(buf, 1)\n    nvar = size(buf, 2)\n\n    event = kernel_fillsendbuf!(array_device(buf), 256)(\n        Val(Np),\n        Val(nvar),\n        sendbuf,\n        buf,\n        vmapsend,\n        length(vmapsend);\n        ndrange = length(vmapsend),\n        dependencies = dependencies,\n        progress = progress,\n    )\n\n    return event\nend\n\nfunction transferrecvbuf!(\n    buf,\n    recvbuf,\n    vmaprecv;\n    dependencies = nothing,\n    progress = yield,\n)\n    if length(vmaprecv) == 0\n        return MultiEvent(dependencies)\n    end\n\n    Np = size(buf, 1)\n    nvar = size(buf, 2)\n\n    event = kernel_transferrecvbuf!(array_device(buf), 256)(\n        Val(Np),\n        Val(nvar),\n        buf,\n        recvbuf,\n        vmaprecv,\n        length(vmaprecv);\n        ndrange = length(vmaprecv),\n        dependencies = dependencies,\n        progress = progress,\n    )\n\n    return event\nend\n\n# }}}\n\n# Integral based metrics\nfunction LinearAlgebra.norm(\n    Q::MPIStateArray,\n    p::Real = 2,\n    weighted::Bool = true;\n    dims = :,\n)\n    if weighted && ~isempty(Q.weights) && isfinite(p)\n        W = @view Q.weights[:, :, Q.realelems]\n        locnorm = weighted_norm_impl(Q.realdata, W, Val(p), dims)\n    else\n        locnorm = norm_impl(Q.realdata, Val(p), dims)\n    end\n\n    mpiop = isfinite(p) ? (+) : max\n    if locnorm isa AbstractArray\n        locnorm = convert(Array, locnorm)\n    end\n    @tic mpi_norm\n    r = MPI.Allreduce(cpuify(locnorm), mpiop, Q.mpicomm)\n    @toc mpi_norm\n    isfinite(p) ? r .^ (1 // p) : r\nend\nLinearAlgebra.norm(Q::MPIStateArray, weighted::Bool; dims = :) =\n    norm(Q, 2, weighted; dims = dims)\n\nfunction LinearAlgebra.dot(\n    Q1::MPIStateArray,\n    Q2::MPIStateArray,\n    weighted::Bool = true,\n)\n    @assert length(Q1.realdata) == length(Q2.realdata)\n\n    if weighted && ~isempty(Q1.weights)\n        W = @view Q1.weights[:, :, Q1.realelems]\n        locnorm = weighted_dot_impl(Q1.realdata, Q2.realdata, W)\n    else\n        locnorm = dot_impl(Q1.realdata, Q2.realdata)\n    end\n\n    @tic mpi_dot\n    r = MPI.Allreduce(locnorm, +, Q1.mpicomm)\n    @toc mpi_dot\n    return r\nend\n\nfunction euclidean_distance(A::MPIStateArray, B::MPIStateArray)\n    # work around https://github.com/JuliaArrays/LazyArrays.jl/issues/66\n    ArealQ = A.realdata\n    BrealQ = B.realdata\n    E = @~ (ArealQ .- BrealQ) .^ 2\n\n    if ~isempty(A.weights)\n        w = @view A.weights[:, :, A.realelems]\n        E = @~ E .* w\n    end\n\n    locnorm = mapreduce(identity, +, E, init = zero(eltype(A)))\n    @tic mpi_euclidean_distance\n    r = sqrt(MPI.Allreduce(locnorm, +, A.mpicomm))\n    @toc mpi_euclidean_distance\n    return r\nend\n\n\"\"\"\n    weightedsum(A[, states])\n\nCompute the weighted sum of the `MPIStateArray` `A`. If `states` is specified on\nthe listed states are summed, otherwise all the states in `A` are used.\n\nA typical use case for this is when the weights have been initialized with\nquadrature weights from a grid, thus this becomes an integral approximation.\n\"\"\"\nfunction weightedsum(A::MPIStateArray, states = 1:size(A, 2))\n    isempty(A.weights) && error(\"`weightedsum` requires weights\")\n\n    FT = eltype(A)\n    states = SVector{length(states)}(states)\n\n    C = @view A.data[:, states, A.realelems]\n    w = @view A.weights[:, :, A.realelems]\n    init = zero(DoubleFloat{FT})\n\n    E = @~ DoubleFloat{FT}.(C) .* DoubleFloat{FT}.(w)\n\n    locwsum = mapreduce(identity, +, E, init = init)\n\n    @tic mpi_weightedsum\n    # Need to use anomous function version of sum else MPI.jl using MPI_SUM\n    r = FT(MPI.Allreduce(locwsum, (x, y) -> x + y, A.mpicomm))\n    @toc mpi_weightedsum\n    return r\nend\n\n# fast CPU local norm & dot implementations\nfunction norm_impl(\n    Q::SubArray{FT, N, A},\n    ::Val{p},\n    dims::Colon,\n) where {FT, N, A <: Array, p}\n    accum = isfinite(p) ? -zero(FT) : typemin(FT)\n    @inbounds @simd for i in eachindex(Q)\n        if isfinite(p)\n            accum += abs(Q[i])^p\n        else\n            aQ_i = abs(Q[i])\n            accum = ifelse(aQ_i > accum, aQ_i, accum)\n        end\n    end\n    accum\nend\n\nfunction weighted_norm_impl(\n    Q::SubArray{FT, N, A},\n    W,\n    ::Val{p},\n    dims::Colon,\n) where {FT, N, A <: Array, p}\n    @assert isfinite(p)\n    nq, ns, ne = size(Q)\n    accum = -zero(FT)\n    @inbounds for k in 1:ne, j in 1:ns\n        @simd for i in 1:nq\n            accum += W[i, 1, k] * abs(Q[i, j, k])^p\n        end\n    end\n    accum\nend\n\nfunction dot_impl(\n    Q1::SubArray{FT, N, A},\n    Q2::SubArray{FT, N, A},\n) where {FT, N, A <: Array}\n    accum = -zero(FT)\n    @inbounds @simd for i in eachindex(Q1)\n        accum += Q1[i] * Q2[i]\n    end\n    accum\nend\n\n\nfunction weighted_dot_impl(\n    Q1::SubArray{FT, N, A},\n    Q2::SubArray{FT, N, A},\n    W,\n) where {FT, N, A <: Array}\n    nq, ns, ne = size(Q1)\n    accum = -zero(FT)\n    @inbounds for k in 1:ne, j in 1:ns\n        @simd for i in 1:nq\n            accum += W[i, 1, k] * Q1[i, j, k] * Q2[i, j, k]\n        end\n    end\n    accum\nend\n\n# GPU/generic local norm & dot implementations\nfunction norm_impl(Q, ::Val{p}, dims = :) where {p}\n    FT = eltype(Q)\n    if !isfinite(p)\n        f, op = abs, max\n    elseif p == 1\n        f, op = abs, +\n    elseif p == 2\n        f, op = abs2, +\n    else\n        f, op = x -> abs(x)^p, +\n    end\n    mapreduce(f, op, Q, dims = dims)\nend\n\nfunction weighted_norm_impl(Q, W, ::Val{p}, dims = :) where {p}\n    @assert isfinite(p)\n    FT = eltype(Q)\n    if p == 1\n        E = @~ @. W * abs(Q)\n    elseif p == 2\n        E = @~ @. W * abs2(Q)\n    else\n        E = @~ @. W * abs(Q)^p\n    end\n    op, init = +, zero(FT)\n    reduce(op, E, init = init, dims = dims)\nend\n\nfunction dot_impl(Q1, Q2)\n    FT = eltype(Q1)\n    E = @~ @. Q1 * Q2\n    mapreduce(identity, +, E, init = zero(FT))\nend\n\nweighted_dot_impl(Q1, Q2, W) = dot_impl(@~ @. W * Q1, Q2)\n\nfunction Base.mapreduce(f, op, Q::MPIStateArray; kw...)\n    locreduce = mapreduce(f, op, realview(Q); kw...)\n    MPI.Allreduce(cpuify(locreduce), op, Q.mpicomm)\nend\n\n# Arrays and CuArrays have different reduction machinery\n# until we can figure this out, add special cases to make common functions work\nfunction Base.mapreduce(\n    ::typeof(identity),\n    ::Union{typeof(+), typeof(Base.add_sum)},\n    Q::MPIStateArray;\n    kw...,\n)\n    locreduce = sum(realview(Q); kw...)\n    MPI.Allreduce(cpuify(locreduce), +, Q.mpicomm)\nend\nfunction Base.mapreduce(\n    ::typeof(identity),\n    ::typeof(min),\n    Q::MPIStateArray;\n    kw...,\n)\n    locreduce = minimum(realview(Q); kw...)\n    MPI.Allreduce(cpuify(locreduce), min, Q.mpicomm)\nend\nfunction Base.mapreduce(\n    ::typeof(identity),\n    ::typeof(max),\n    Q::MPIStateArray;\n    kw...,\n)\n    locreduce = maximum(realview(Q); kw...)\n    MPI.Allreduce(cpuify(locreduce), max, Q.mpicomm)\nend\n\n# helpers: `array_device` and `realview`\narray_device(::Union{Array, SArray, MArray}) = CPU()\narray_device(::CuArray) = CUDADevice()\narray_device(s::SubArray) = array_device(parent(s))\narray_device(Q::MPIStateArray) = array_device(Q.data)\narray_device(ra::Base.ReshapedArray{T, N, A}) where {T, N, A <: MPIStateArray} =\n    array_device(parent(ra))\n\nrealview(Q::Union{Array, SArray, MArray}) = Q\nrealview(Q::MPIStateArray) = Q.realdata\nrealview(Q::CuArray) = Q\n\n# transform all arguments of `bc` from MPIStateArrays to CuArrays\n# and replace CPU function with GPU variants\nfunction transform_broadcasted(bc::Broadcasted, ::CuArray)\n    transform_cuarray(bc)\nend\nfunction transform_cuarray(bc::Broadcasted)\n    Broadcasted(CUDA.cufunc(bc.f), transform_cuarray.(bc.args), bc.axes)\nend\ntransform_cuarray(mpisa::MPIStateArray) = mpisa.realdata\ntransform_cuarray(x) = x\n\n# @init tictoc()\n\nusing KernelAbstractions.Extras: @unroll\n\n@kernel function kernel_fillsendbuf!(\n    ::Val{Np},\n    ::Val{nvar},\n    sendbuf,\n    buf,\n    vmapsend,\n    nvmapsend,\n) where {Np, nvar}\n\n    i = @index(Global, Linear)\n    @inbounds begin\n        e, n = fldmod1(vmapsend[i], Np)\n        @unroll for s in 1:nvar\n            sendbuf[s, i] = buf[n, s, e]\n        end\n    end\nend\n\n@kernel function kernel_transferrecvbuf!(\n    ::Val{Np},\n    ::Val{nvar},\n    buf,\n    recvbuf,\n    vmaprecv,\n    nvmaprecv,\n) where {Np, nvar}\n\n    i = @index(Global, Linear)\n    @inbounds begin\n        e, n = fldmod1(vmaprecv[i], Np)\n        @unroll for s in 1:nvar\n            buf[n, s, e] = recvbuf[s, i]\n        end\n    end\nend\n\nbracket_index(i::Int) = \"[$i]\"\nbracket_index(i) = string(i)\n\"\"\"\n    show_not_finite_fields(Q::MPIStateArray)\n\nPrints a warning of which fields are not finite.\n\n!!! warn\n    This is an expensive method, which calls `mapreduce`\n    on `Q`.\n\"\"\"\nfunction show_not_finite_fields(Q::MPIStateArray)\n    vs = vars(Q)\n    not_finite_fields = []\n    not_finite =\n        Array(reshape(mapreduce(x -> !isfinite(x), |, Q; dims = (1, 3)), :))\n    for ftc in flattened_tup_chain(vs)\n        i_vars = varsindex(vs, wrap_val.(ftc)...)\n        if not_finite[i_vars[1]]\n            s = join(bracket_index.(ftc), \".\")\n            s = replace(s, \".[\" => \"[\")\n            push!(not_finite_fields, s)\n        end\n    end\n    if !isempty(not_finite_fields)\n        @warn \"Field(s) ($(join(not_finite_fields, \", \", \", and \"))) are not finite (has NaNs or Inf)\"\n        flush(stdout)\n    end\nend\n\n\"\"\"\n    checked_wait(device, event, progress = nothing, check = false)\n\nIf `check` is `false`, simply perform a `wait(device, event, progress)`,\notherwise, check for exceptions and synchronize with all other ranks, so\nthat all throw an exception.\n\"\"\"\nfunction checked_wait(device::CPU, event, progress = nothing, check = false)\n    err = false\n    try\n        wait(device, event, progress)\n        if check\n            err = MPI.Allreduce(false, MPI.MAX, MPI.COMM_WORLD)\n        end\n    catch exc\n        if check\n            err = MPI.Allreduce(true, MPI.MAX, MPI.COMM_WORLD)\n        end\n        rethrow()\n    finally\n        if check && err\n            throw(ErrorOnRemoteNode())\n        end\n    end\nend\nfunction checked_wait(\n    device::CUDADevice,\n    event,\n    progress = nothing,\n    check = false,\n)\n    wait(device, event, progress)\nend\n\nend\n"
  },
  {
    "path": "src/Atmos/Model/AtmosModel.jl",
    "content": "module Atmos\n\nexport AtmosModel,\n    AtmosPhysics,\n    AtmosAcousticLinearModel,\n    AtmosAcousticGravityLinearModel,\n    HLLCNumericalFlux,\n    RoeNumericalFlux,\n    RoeNumericalFluxMoist,\n    LMARSNumericalFlux,\n    Compressible,\n    Anelastic1D,\n    reference_state,\n    energy_model,\n    moisture_model,\n    compressibility_model,\n    turbulence_model,\n    turbconv_model,\n    hyperdiffusion_model,\n    viscoussponge_model,\n    precipitation_model,\n    radiation_model,\n    tracer_model,\n    lsforcing_model,\n    parameter_set\n\nusing UnPack\nusing DispatchedTuples\nusing CLIMAParameters\nusing CLIMAParameters.Planet: grav, cp_d, R_v, LH_v0, e_int_v0\nusing CLIMAParameters.Atmos.SubgridScale: C_smag\nusing DocStringExtensions\nusing LinearAlgebra, StaticArrays\nusing ..ConfigTypes\nusing ..Orientations\nimport ..Orientations:\n    vertical_unit_vector,\n    altitude,\n    latitude,\n    longitude,\n    projection_normal,\n    gravitational_potential,\n    ∇gravitational_potential,\n    projection_tangential\n\nusing ..VariableTemplates\nusing Thermodynamics\nusing Thermodynamics.TemperatureProfiles\n\nusing ..TurbulenceClosures\nimport ..TurbulenceClosures: turbulence_tensors\nusing ..TurbulenceConvection\n\nimport Thermodynamics: internal_energy, soundspeed_air\nconst TD = Thermodynamics\nusing ..MPIStateArrays: MPIStateArray\nusing ..Mesh.Grids:\n    VerticalDirection,\n    HorizontalDirection,\n    min_node_distance,\n    EveryDirection,\n    Direction\n\nusing ..Mesh.Filters: AbstractFilterTarget\nimport ..Mesh.Filters:\n    vars_state_filtered, compute_filter_argument!, compute_filter_result!\n\nusing ..BalanceLaws\nusing ClimateMachine.Problems\n\nimport ..BalanceLaws:\n    vars_state,\n    projection,\n    sub_model,\n    prognostic_vars,\n    get_prog_state,\n    get_specific_state,\n    flux_first_order!,\n    flux_second_order!,\n    source!,\n    eq_tends,\n    flux,\n    precompute,\n    parameter_set,\n    source,\n    wavespeed,\n    boundary_conditions,\n    boundary_state!,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    transform_post_gradient_laplacian!,\n    prognostic_to_primitive!,\n    primitive_to_prognostic!,\n    init_state_auxiliary!,\n    construct_face_auxiliary_state!,\n    init_state_prognostic!,\n    update_auxiliary_state!,\n    indefinite_stack_integral!,\n    reverse_indefinite_stack_integral!,\n    integral_load_auxiliary_state!,\n    integral_set_auxiliary_state!,\n    reverse_integral_load_auxiliary_state!,\n    reverse_integral_set_auxiliary_state!\n\nimport ClimateMachine.DGMethods:\n    LocalGeometry, lengthscale, resolutionmetric, DGModel\n\nimport ..DGMethods.NumericalFluxes:\n    boundary_state!,\n    boundary_flux_second_order!,\n    normal_boundary_flux_second_order!,\n    NumericalFluxFirstOrder,\n    NumericalFluxGradient,\n    NumericalFluxSecondOrder,\n    CentralNumericalFluxHigherOrder,\n    CentralNumericalFluxDivergence,\n    CentralNumericalFluxFirstOrder,\n    numerical_flux_first_order!,\n    NumericalFluxFirstOrder\nusing ..DGMethods.NumericalFluxes:\n    RoeNumericalFlux,\n    HLLCNumericalFlux,\n    RusanovNumericalFlux,\n    RoeNumericalFluxMoist,\n    LMARSNumericalFlux\n\nimport ..Courant: advective_courant, nondiffusive_courant, diffusive_courant\n\n\"\"\"\n    AtmosPhysics\n\nAn `AtmosPhysics` for atmospheric physics\n\n# Usage\n\n    AtmosPhysics(\n        param_set,\n        ref_state,\n        energy,\n        moisture,\n        compressibility,\n        turbulence,\n        turbconv,\n        hyperdiffusion,\n        precipitation,\n        radiation,\n        tracers,\n        lsforcing,\n    )\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct AtmosPhysics{FT, PS, RS, E, M, C, T, TC, HD, VS, P, R, TR, LF}\n    \"Parameter Set (type to dispatch on, e.g., planet parameters. See CLIMAParameters.jl package)\"\n    param_set::PS\n    \"Reference State (For initial conditions, or for linearisation when using implicit solvers)\"\n    ref_state::RS\n    \"Energy sub-model, can be energy-based or θ_liq_ice-based\"\n    energy::E\n    \"Moisture Model (Equations for dynamics of moist variables)\"\n    moisture::M\n    \"Compressibility switch\"\n    compressibility::C\n    \"Turbulence Closure (Equations for dynamics of under-resolved turbulent flows)\"\n    turbulence::T\n    \"Turbulence Convection Closure (e.g., EDMF)\"\n    turbconv::TC\n    \"Hyperdiffusion Model (Equations for dynamics of high-order spatial wave attenuation)\"\n    hyperdiffusion::HD\n    \"Viscous sponge layers\"\n    viscoussponge::VS\n    \"Precipitation Model (Equations for dynamics of precipitating species)\"\n    precipitation::P\n    \"Radiation Model (Equations for radiative fluxes)\"\n    radiation::R\n    \"Tracer Terms (Equations for dynamics of active and passive tracers)\"\n    tracers::TR\n    \"Large-scale forcing (Forcing information from GCMs, reanalyses, or observations)\"\n    lsforcing::LF\nend\n\n\"\"\"\n    AtmosPhysics{FT}()\n\nConstructor for `AtmosPhysics`.\n\"\"\"\nfunction AtmosPhysics{FT}(\n    param_set::AbstractParameterSet;\n    energy = TotalEnergyModel(),\n    ref_state = HydrostaticState(DecayingTemperatureProfile{FT}(param_set),),\n    turbulence = SmagorinskyLilly{FT}(C_smag(param_set)),\n    turbconv = NoTurbConv(),\n    hyperdiffusion = NoHyperDiffusion(),\n    viscoussponge = NoViscousSponge(),\n    moisture = EquilMoist(),\n    precipitation = NoPrecipitation(),\n    radiation = NoRadiation(),\n    tracers = NoTracers(),\n    lsforcing = NoLSForcing(),\n    compressibility = Compressible(),\n) where {FT <: AbstractFloat}\n\n    args = (\n        param_set,\n        ref_state,\n        energy,\n        moisture,\n        compressibility,\n        turbulence,\n        turbconv,\n        hyperdiffusion,\n        viscoussponge,\n        precipitation,\n        radiation,\n        tracers,\n        lsforcing,\n    )\n    return AtmosPhysics{FT, typeof.(args)...}(args...)\nend\n\n\n\"\"\"\n    AtmosModel <: BalanceLaw\n\nA `BalanceLaw` for atmosphere modeling. Users may over-ride prescribed\ndefault values for each field.\n\n# Usage\n\n    AtmosModel(\n        physics,\n        problem,\n        orientation,\n        source,\n        data_config,\n    )\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct AtmosModel{FT, PH, PR, O, S, DC} <: BalanceLaw\n    \"Atmospheric physics\"\n    physics::PH\n    \"Problem (initial and boundary conditions)\"\n    problem::PR\n    \"An orientation model\"\n    orientation::O\n    \"Source Terms (Problem specific source terms)\"\n    source::S\n    \"Data Configuration (Helper field for experiment configuration)\"\n    data_config::DC\nend\n\nparameter_set(atmos::AtmosModel) = parameter_set(atmos.physics)\nmoisture_model(atmos::AtmosModel) = moisture_model(atmos.physics)\nenergy_model(atmos::AtmosModel) = energy_model(atmos.physics)\ncompressibility_model(atmos::AtmosModel) = compressibility_model(atmos.physics)\nreference_state(atmos::AtmosModel) = reference_state(atmos.physics)\nturbulence_model(atmos::AtmosModel) = turbulence_model(atmos.physics)\nturbconv_model(atmos::AtmosModel) = turbconv_model(atmos.physics)\nhyperdiffusion_model(atmos::AtmosModel) = hyperdiffusion_model(atmos.physics)\nviscoussponge_model(atmos::AtmosModel) = viscoussponge_model(atmos.physics)\nprecipitation_model(atmos::AtmosModel) = precipitation_model(atmos.physics)\nradiation_model(atmos::AtmosModel) = radiation_model(atmos.physics)\ntracer_model(atmos::AtmosModel) = tracer_model(atmos.physics)\nlsforcing_model(atmos::AtmosModel) = lsforcing_model(atmos.physics)\n\nparameter_set(physics::AtmosPhysics) = physics.param_set\nmoisture_model(physics::AtmosPhysics) = physics.moisture\nenergy_model(physics::AtmosPhysics) = physics.energy\ncompressibility_model(physics::AtmosPhysics) = physics.compressibility\nreference_state(physics::AtmosPhysics) = physics.ref_state\nturbulence_model(physics::AtmosPhysics) = physics.turbulence\nturbconv_model(physics::AtmosPhysics) = physics.turbconv\nhyperdiffusion_model(physics::AtmosPhysics) = physics.hyperdiffusion\nviscoussponge_model(physics::AtmosPhysics) = physics.viscoussponge\nprecipitation_model(physics::AtmosPhysics) = physics.precipitation\nradiation_model(physics::AtmosPhysics) = physics.radiation\ntracer_model(physics::AtmosPhysics) = physics.tracers\nlsforcing_model(physics::AtmosPhysics) = physics.lsforcing\n\nabstract type Compressibilty end\n\n\"\"\"\n    Compressible <: Compressibilty\n\nDispatch on compressible model (default)\n\n - Density is prognostic\n\"\"\"\nstruct Compressible <: Compressibilty end\n\n\"\"\"\n    Anelastic1D <: Compressibilty\n\nDispatch on Anelastic1D model\n\n - The state density is taken constant in time and equal to the reference density. This\n    constant density profile is used in all equations and conversions from conservative to specific\n    variables per unit mass. The density can be accessed using the dispatch function\n    `density(atmos, state, aux)`.\n - The thermodynamic state is constructed from the reference pressure (constant in time),\n    and the internal energy (which evolves in time).\n - The state density is not consistent with the thermodynamic state, since we neglect \n    buoyancy perturbations on all equations except in the vertical buoyancy flux.\n - The density obtained from the thermodynamic state, `air_density(ts)`, recovers the full density, which\n    should only be used to compute buoyancy and buoyancy fluxes, and in the FV reconstruction.\n - Removes momentum z-component tendencies, assuming balance between the pressure gradient and buoyancy\n    forces.\n\"\"\"\nstruct Anelastic1D <: Compressibilty end\n\n\"\"\"\n    AtmosModel{FT}()\n\nConstructor for `AtmosModel` (where `AtmosModel <: BalanceLaw`).\n\"\"\"\nfunction AtmosModel{FT}(\n    orientation::Orientation,\n    physics::AtmosPhysics;\n    init_state_prognostic = nothing,\n    problem = AtmosProblem(;\n        physics = physics,\n        init_state_prognostic = init_state_prognostic,\n    ),\n    source = (\n        Gravity(),\n        Coriolis(),\n        GeostrophicForcing{FT}(7.62e-5, 0, 0),\n        turbconv_sources(turbconv_model(physics))...,\n    ),\n    data_config = nothing,\n) where {FT <: AbstractFloat}\n\n    atmos = (\n        physics,\n        problem,\n        orientation,\n        prognostic_var_source_map(source),\n        data_config,\n    )\n\n    return AtmosModel{FT, typeof.(atmos)...}(atmos...)\nend\n\n\"\"\"\n    AtmosModel{FT}()\n\nConstructor for `AtmosModel` (where `AtmosModel <: BalanceLaw`) for LES\nand single stack configurations.\n\"\"\"\nfunction AtmosModel{FT}(\n    ::Union{Type{AtmosLESConfigType}, Type{SingleStackConfigType}},\n    physics::AtmosPhysics;\n    orientation = FlatOrientation(),\n    kwargs...,\n) where {FT <: AbstractFloat}\n    return AtmosModel{FT}(orientation, physics; kwargs...)\nend\n\n\"\"\"\n    AtmosModel{FT}()\n\nConstructor for `AtmosModel` (where `AtmosModel <: BalanceLaw`) for GCM\nconfigurations.\n\"\"\"\nfunction AtmosModel{FT}(\n    ::Type{AtmosGCMConfigType},\n    physics::AtmosPhysics;\n    orientation = SphericalOrientation(),\n    kwargs...,\n) where {FT <: AbstractFloat}\n    return AtmosModel{FT}(orientation, physics; kwargs...)\nend\n\n\"\"\"\n    vars_state(m::AtmosModel, ::Prognostic, FT)\n\nConserved state variables (prognostic variables).\n\n!!! warning\n\n    The order of the fields for `AtmosModel` needs to match the one for\n    `AtmosLinearModel` since a shared state is used\n\"\"\"\nfunction vars_state(m::AtmosModel, st::Prognostic, FT)\n    @vars begin\n        # start of inclusion in `AtmosLinearModel`\n        ρ::FT\n        ρu::SVector{3, FT}\n        energy::vars_state(energy_model(m), st, FT) # TODO: adjust linearmodel\n        turbulence::vars_state(turbulence_model(m), st, FT)\n        hyperdiffusion::vars_state(hyperdiffusion_model(m), st, FT)\n        moisture::vars_state(moisture_model(m), st, FT)\n        # end of inclusion in `AtmosLinearModel`\n        precipitation::vars_state(precipitation_model(m), st, FT)\n        turbconv::vars_state(turbconv_model(m), st, FT)\n        radiation::vars_state(radiation_model(m), st, FT)\n        tracers::vars_state(tracer_model(m), st, FT)\n        lsforcing::vars_state(lsforcing_model(m), st, FT)\n    end\nend\n\nfunction vars_state(m::AtmosModel, st::Primitive, FT)\n    @vars begin\n        ρ::FT\n        u::SVector{3, FT}\n        p::FT\n        moisture::vars_state(moisture_model(m), st, FT)\n        turbconv::vars_state(turbconv_model(m), st, FT)\n    end\nend\n\n\"\"\"\n    vars_state(m::AtmosModel, ::Gradient, FT)\n\nPre-transform gradient variables.\n\"\"\"\nfunction vars_state(m::AtmosModel, st::Gradient, FT)\n    @vars begin\n        u::SVector{3, FT}\n        energy::vars_state(energy_model(m), st, FT)\n        turbulence::vars_state(turbulence_model(m), st, FT)\n        turbconv::vars_state(turbconv_model(m), st, FT)\n        hyperdiffusion::vars_state(hyperdiffusion_model(m), st, FT)\n        moisture::vars_state(moisture_model(m), st, FT)\n        lsforcing::vars_state(lsforcing_model(m), st, FT)\n        precipitation::vars_state(precipitation_model(m), st, FT)\n        tracers::vars_state(tracer_model(m), st, FT)\n    end\nend\n\n\"\"\"\n    vars_state(m::AtmosModel, ::GradientFlux, FT)\n\nPost-transform gradient variables.\n\"\"\"\nfunction vars_state(m::AtmosModel, st::GradientFlux, FT)\n    @vars begin\n        energy::vars_state(energy_model(m), st, FT)\n        turbulence::vars_state(turbulence_model(m), st, FT)\n        turbconv::vars_state(turbconv_model(m), st, FT)\n        hyperdiffusion::vars_state(hyperdiffusion_model(m), st, FT)\n        moisture::vars_state(moisture_model(m), st, FT)\n        lsforcing::vars_state(lsforcing_model(m), st, FT)\n        precipitation::vars_state(precipitation_model(m), st, FT)\n        tracers::vars_state(tracer_model(m), st, FT)\n    end\nend\n\n\"\"\"\n    vars_state(m::AtmosModel, ::GradientLaplacian, FT)\n\nPre-transform hyperdiffusive variables.\n\"\"\"\nfunction vars_state(m::AtmosModel, st::GradientLaplacian, FT)\n    @vars begin\n        hyperdiffusion::vars_state(hyperdiffusion_model(m), st, FT)\n    end\nend\n\n\"\"\"\n    vars_state(m::AtmosModel, ::Hyperdiffusive, FT)\n\nPost-transform hyperdiffusive variables.\n\"\"\"\nfunction vars_state(m::AtmosModel, st::Hyperdiffusive, FT)\n    @vars begin\n        hyperdiffusion::vars_state(hyperdiffusion_model(m), st, FT)\n    end\nend\n\n\"\"\"\n    vars_state(m::AtmosModel, ::Auxiliary, FT)\n\nAuxiliary variables, such as vertical (stack) integrals, coordinates,\norientation information, reference states, subcomponent auxiliary vars,\ndebug variables.\n\"\"\"\nfunction vars_state(m::AtmosModel, st::Auxiliary, FT)\n    @vars begin\n        ∫dz::vars_state(m, UpwardIntegrals(), FT)\n        ∫dnz::vars_state(m, DownwardIntegrals(), FT)\n        coord::SVector{3, FT}\n        orientation::vars_state(m.orientation, st, FT)\n        ref_state::vars_state(reference_state(m), st, FT)\n        turbulence::vars_state(turbulence_model(m), st, FT)\n        turbconv::vars_state(turbconv_model(m), st, FT)\n        hyperdiffusion::vars_state(hyperdiffusion_model(m), st, FT)\n        moisture::vars_state(moisture_model(m), st, FT)\n        precipitation::vars_state(precipitation_model(m), st, FT)\n        tracers::vars_state(tracer_model(m), st, FT)\n        radiation::vars_state(radiation_model(m), st, FT)\n        lsforcing::vars_state(lsforcing_model(m), st, FT)\n    end\nend\n\n\"\"\"\n    vars_state(m::AtmosModel, ::UpwardIntegrals, FT)\n\"\"\"\nfunction vars_state(m::AtmosModel, st::UpwardIntegrals, FT)\n    @vars begin\n        radiation::vars_state(radiation_model(m), st, FT)\n        turbconv::vars_state(turbconv_model(m), st, FT)\n    end\nend\n\n\"\"\"\n    vars_state(m::AtmosModel, ::DownwardIntegrals, FT)\n\"\"\"\nfunction vars_state(m::AtmosModel, st::DownwardIntegrals, FT)\n    @vars begin\n        radiation::vars_state(radiation_model(m), st, FT)\n    end\nend\n\nfunction vars_state_filtered(m::AtmosModel, FT)\n    @vars begin\n        ρ::FT\n        u::SVector{3, FT}\n        energy::vars_state_filtered(energy_model(m), FT)\n        moisture::vars_state_filtered(moisture_model(m), FT)\n        turbconv::vars_state_filtered(turbconv_model(m), FT)\n    end\nend\n\n\n####\n#### Forward orientation methods\n####\nprojection_normal(bl, aux, u⃗) =\n    projection_normal(bl.orientation, parameter_set(bl), aux, u⃗)\nprojection_tangential(bl, aux, u⃗) =\n    projection_tangential(bl.orientation, parameter_set(bl), aux, u⃗)\nlatitude(bl, aux) = latitude(bl.orientation, aux)\nlongitude(bl, aux) = longitude(bl.orientation, aux)\naltitude(bl, aux) = altitude(bl.orientation, parameter_set(bl), aux)\nvertical_unit_vector(bl, aux) =\n    vertical_unit_vector(bl.orientation, parameter_set(bl), aux)\ngravitational_potential(bl, aux) = gravitational_potential(bl.orientation, aux)\n∇gravitational_potential(bl, aux) =\n    ∇gravitational_potential(bl.orientation, aux)\n\nturbulence_tensors(atmos::AtmosModel, args...) = turbulence_tensors(\n    turbulence_model(atmos),\n    viscoussponge_model(atmos),\n    atmos,\n    args...,\n)\n\n\"\"\"\n    density(atmos::AtmosModel, state::Vars, aux::Vars)\n\nDensity used in the conservative form of the prognostic equations.\nIn the Compressible case, it is equal to the prognostic density,\nwhereas in the Anelastic1D case it is the reference density,\nwhich is constant in time.\n\"\"\"\ndensity(atmos::AtmosModel, state::Vars, aux::Vars) =\n    density(compressibility_model(atmos), state, aux)\ndensity(::Compressible, state, aux) = state.ρ\ndensity(::Anelastic1D, state, aux) = aux.ref_state.ρ\n\n\"\"\"\n    pressure(atmos::AtmosModel, ts, aux::Vars)\n\nDiagnostic pressure consistent with the given thermodynamic state ts.\nIn the Anelastic1D case it is the reference pressure,\nwhich is constant in time.\n\"\"\"\npressure(atmos::AtmosModel, ts, aux::Vars) =\n    pressure(compressibility_model(atmos), ts, aux)\npressure(::Compressible, ts, aux) = air_pressure(ts)\npressure(::Anelastic1D, ts, aux) = aux.ref_state.p\n\ninclude(\"declare_prognostic_vars.jl\") # declare prognostic variables\ninclude(\"multiphysics_types.jl\")      # types for multi-physics tendencies\ninclude(\"tendencies_mass.jl\")         # specify mass tendencies\ninclude(\"tendencies_momentum.jl\")     # specify momentum tendencies\ninclude(\"tendencies_energy.jl\")       # specify energy tendencies\ninclude(\"tendencies_moisture.jl\")     # specify moisture tendencies\ninclude(\"tendencies_precipitation.jl\")# specify precipitation tendencies\ninclude(\"tendencies_tracers.jl\")      # specify tracer tendencies\n\ninclude(\"problem.jl\")\ninclude(\"ref_state.jl\")\ninclude(\"moisture.jl\")\ninclude(\"energy.jl\")\ninclude(\"precipitation.jl\")\ninclude(\"thermo_states.jl\")\ninclude(\"thermo_states_anelastic.jl\")\ninclude(\"radiation.jl\")\ninclude(\"tracers.jl\")\ninclude(\"lsforcing.jl\")\ninclude(\"linear.jl\")\ninclude(\"courant.jl\")\ninclude(\"filters.jl\")\ninclude(\"prog_prim_conversion.jl\")   # prognostic<->primitive conversion\ninclude(\"reconstructions.jl\")   # finite-volume method reconstructions\ninclude(\"projections.jl\")            # include term-by-term projectinos\n\ninclude(\"linear_tendencies.jl\")\ninclude(\"linear_atmos_tendencies.jl\")\n\ninclude(\"atmos_tendencies.jl\")        # specify atmos tendencies\ninclude(\"get_prognostic_vars.jl\")     # get tuple of prognostic variables\n\n\nsub_model(atmos::AtmosModel, ::Type{<:AbstractEnergyModel}) =\n    energy_model(atmos)\nsub_model(atmos::AtmosModel, ::Type{<:AbstractMoistureModel}) =\n    moisture_model(atmos)\n\n\nfunction precompute(atmos::AtmosModel, args, tt::Flux{FirstOrder})\n    ts = recover_thermo_state(atmos, args.state, args.aux)\n    turbconv = precompute(turbconv_model(atmos), atmos, args, ts, tt)\n    return (; ts, turbconv)\nend\n\nfunction compute_gradient_argument!(\n    atmos::AtmosModel,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ρinv = 1 / state.ρ\n    transform.u = ρinv * state.ρu\n\n    compute_gradient_argument!(\n        energy_model(atmos),\n        atmos,\n        transform,\n        state,\n        aux,\n        t,\n    )\n    compute_gradient_argument!(moisture_model(atmos), transform, state, aux, t)\n    compute_gradient_argument!(\n        precipitation_model(atmos),\n        transform,\n        state,\n        aux,\n        t,\n    )\n    compute_gradient_argument!(\n        turbulence_model(atmos),\n        transform,\n        state,\n        aux,\n        t,\n    )\n    compute_gradient_argument!(\n        hyperdiffusion_model(atmos),\n        atmos,\n        transform,\n        state,\n        aux,\n        t,\n    )\n    compute_gradient_argument!(tracer_model(atmos), transform, state, aux, t)\n    compute_gradient_argument!(lsforcing_model(atmos), transform, state, aux, t)\n    compute_gradient_argument!(\n        turbconv_model(atmos),\n        atmos,\n        transform,\n        state,\n        aux,\n        t,\n    )\nend\n\nfunction compute_gradient_flux!(\n    atmos::AtmosModel,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    compute_gradient_flux!(\n        energy_model(atmos),\n        diffusive,\n        ∇transform,\n        state,\n        aux,\n        t,\n    )\n\n    # diffusion terms required for SGS turbulence computations\n    compute_gradient_flux!(\n        turbulence_model(atmos),\n        atmos.orientation,\n        diffusive,\n        ∇transform,\n        state,\n        aux,\n        t,\n    )\n    # diffusivity of moisture components\n    compute_gradient_flux!(\n        moisture_model(atmos),\n        diffusive,\n        ∇transform,\n        state,\n        aux,\n        t,\n    )\n    compute_gradient_flux!(\n        lsforcing_model(atmos),\n        diffusive,\n        ∇transform,\n        state,\n        aux,\n        t,\n    )\n    compute_gradient_flux!(\n        precipitation_model(atmos),\n        diffusive,\n        ∇transform,\n        state,\n        aux,\n        t,\n    )\n    compute_gradient_flux!(\n        tracer_model(atmos),\n        diffusive,\n        ∇transform,\n        state,\n        aux,\n        t,\n    )\n    compute_gradient_flux!(\n        turbconv_model(atmos),\n        atmos,\n        diffusive,\n        ∇transform,\n        state,\n        aux,\n        t,\n    )\nend\n\nfunction transform_post_gradient_laplacian!(\n    atmos::AtmosModel,\n    hyperdiffusive::Vars,\n    hypertransform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    transform_post_gradient_laplacian!(\n        hyperdiffusion_model(atmos),\n        atmos,\n        hyperdiffusive,\n        hypertransform,\n        state,\n        aux,\n        t,\n    )\nend\n\nfunction precompute(atmos::AtmosModel, args, tt::Flux{SecondOrder})\n    @unpack state, diffusive, aux, t = args\n    ts = recover_thermo_state(atmos, state, aux)\n    ν, D_t, τ = turbulence_tensors(atmos, state, diffusive, aux, t)\n    turbulence = (ν = ν, D_t = D_t, τ = τ)\n    turbconv = precompute(turbconv_model(atmos), atmos, args, ts, tt)\n    return (; ts, turbconv, turbulence)\nend\n\nsoundspeed_air(ts::ThermodynamicState, ::Anelastic1D) = 0\nsoundspeed_air(ts::ThermodynamicState, ::Compressible) = soundspeed_air(ts)\n@inline function wavespeed(\n    m::AtmosModel,\n    nM,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n)\n    ρinv = 1 / state.ρ\n    u = ρinv * state.ρu\n    uN = abs(dot(nM, u))\n    ts = recover_thermo_state(m, state, aux)\n    ss = soundspeed_air(ts, compressibility_model(m))\n    FT = typeof(state.ρ)\n    ws = fill(uN + ss, MVector{number_states(m, Prognostic()), FT})\n    vars_ws = Vars{vars_state(m, Prognostic(), FT)}(ws)\n\n    wavespeed_tracers!(tracer_model(m), vars_ws, nM, state, aux, t)\n\n    return ws\nend\n\n\nfunction update_auxiliary_state!(\n    dg::DGModel,\n    m::AtmosModel,\n    Q::MPIStateArray,\n    t::Real,\n    elems::UnitRange,\n)\n    FT = eltype(Q)\n    state_auxiliary = dg.state_auxiliary\n\n    if number_states(m, UpwardIntegrals()) > 0\n        indefinite_stack_integral!(dg, m, Q, state_auxiliary, t, elems)\n        reverse_indefinite_stack_integral!(dg, m, Q, state_auxiliary, t, elems)\n    end\n\n    update_auxiliary_state!(nodal_update_auxiliary_state!, dg, m, Q, t, elems)\n\n    # TODO: Remove this hook. This hook was added for implementing\n    # the first draft of EDMF, and should be removed so that we can\n    # rely on a single vertical element traversal. This hook allows\n    # us to compute globally vertical quantities specific to EDMF\n    # until we're able to remove them or somehow incorporate them\n    # into a higher level hierarchy.\n    update_auxiliary_state!(dg, turbconv_model(m), m, Q, t, elems)\n\n    return true\nend\n\nfunction nodal_update_auxiliary_state!(\n    m::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    atmos_nodal_update_auxiliary_state!(moisture_model(m), m, state, aux, t)\n    atmos_nodal_update_auxiliary_state!(\n        precipitation_model(m),\n        m,\n        state,\n        aux,\n        t,\n    )\n    atmos_nodal_update_auxiliary_state!(radiation_model(m), m, state, aux, t)\n    atmos_nodal_update_auxiliary_state!(tracer_model(m), m, state, aux, t)\n    turbconv_nodal_update_auxiliary_state!(turbconv_model(m), m, state, aux, t)\nend\n\nfunction integral_load_auxiliary_state!(\n    m::AtmosModel,\n    integ::Vars,\n    state::Vars,\n    aux::Vars,\n)\n    integral_load_auxiliary_state!(radiation_model(m), integ, state, aux)\n    integral_load_auxiliary_state!(turbconv_model(m), m, integ, state, aux)\nend\n\nfunction integral_set_auxiliary_state!(m::AtmosModel, aux::Vars, integ::Vars)\n    integral_set_auxiliary_state!(radiation_model(m), aux, integ)\n    integral_set_auxiliary_state!(turbconv_model(m), m, aux, integ)\nend\n\nfunction reverse_integral_load_auxiliary_state!(\n    m::AtmosModel,\n    integ::Vars,\n    state::Vars,\n    aux::Vars,\n)\n    reverse_integral_load_auxiliary_state!(\n        radiation_model(m),\n        integ,\n        state,\n        aux,\n    )\nend\n\nfunction reverse_integral_set_auxiliary_state!(\n    m::AtmosModel,\n    aux::Vars,\n    integ::Vars,\n)\n    reverse_integral_set_auxiliary_state!(radiation_model(m), aux, integ)\nend\n\nfunction atmos_nodal_init_state_auxiliary!(\n    m::AtmosModel,\n    aux::Vars,\n    tmp::Vars,\n    geom::LocalGeometry,\n)\n    aux.coord = geom.coord\n    init_aux_turbulence!(turbulence_model(m), m, aux, geom)\n    init_aux_hyperdiffusion!(hyperdiffusion_model(m), m, aux, geom)\n    atmos_init_aux!(tracer_model(m), m, aux, geom)\n    init_aux_turbconv!(turbconv_model(m), m, aux, geom)\n    m.problem.init_state_auxiliary(m.problem, m, aux, geom)\nend\n\n\"\"\"\n    init_state_auxiliary!(\n        m::AtmosModel,\n        aux::Vars,\n        grid,\n        direction\n    )\n\nInitialise auxiliary variables for each AtmosModel subcomponent.\nStore Cartesian coordinate information in `aux.coord`.\n\"\"\"\nfunction init_state_auxiliary!(\n    m::AtmosModel,\n    state_auxiliary::MPIStateArray,\n    grid,\n    direction,\n)\n    # update the geopotential Φ in state_auxiliary.orientation.Φ\n    init_aux!(m, m.orientation, state_auxiliary, grid, direction)\n    atmos_init_aux!(m, reference_state(m), state_auxiliary, grid, direction)\n\n    init_state_auxiliary!(\n        m,\n        atmos_nodal_init_state_auxiliary!,\n        state_auxiliary,\n        grid,\n        direction,\n    )\nend\n\nfunction precompute(atmos::AtmosModel, args, tt::Source)\n    ts = recover_thermo_state(atmos, args.state, args.aux)\n    precipitation = precompute(precipitation_model(atmos), atmos, args, ts, tt)\n    turbconv = precompute(turbconv_model(atmos), atmos, args, ts, tt)\n    return (; ts, turbconv, precipitation)\nend\n\n\"\"\"\n    init_state_prognostic!(\n        m::AtmosModel,\n        state::Vars,\n        aux::Vars,\n        localgeo,\n        t,\n        args...,\n    )\n\nInitialise state variables. `args...` provides an option to include\nconfiguration data (current use cases include problem constants,\nspline-interpolants).\n\"\"\"\nfunction init_state_prognostic!(\n    m::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t,\n    args...,\n)\n    m.problem.init_state_prognostic(\n        m.problem,\n        m,\n        state,\n        aux,\n        localgeo,\n        t,\n        args...,\n    )\nend\n\nroe_average(ρ⁻, ρ⁺, var⁻, var⁺) =\n    (sqrt(ρ⁻) * var⁻ + sqrt(ρ⁺) * var⁺) / (sqrt(ρ⁻) + sqrt(ρ⁺))\n\nfunction numerical_flux_first_order!(\n    numerical_flux::RoeNumericalFlux,\n    balance_law::AtmosModel,\n    fluxᵀn::Vars{S},\n    normal_vector::SVector,\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n    direction,\n) where {S, A}\n    @assert moisture_model(balance_law) isa DryModel\n\n    numerical_flux_first_order!(\n        CentralNumericalFluxFirstOrder(),\n        balance_law,\n        fluxᵀn,\n        normal_vector,\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n        direction,\n    )\n\n    FT = eltype(fluxᵀn)\n    param_set = parameter_set(balance_law)\n    _cv_d::FT = cv_d(param_set)\n    _T_0::FT = T_0(param_set)\n\n    Φ = gravitational_potential(balance_law, state_auxiliary⁻)\n\n    ρ⁻ = state_prognostic⁻.ρ\n    ρu⁻ = state_prognostic⁻.ρu\n    ρe⁻ = state_prognostic⁻.energy.ρe\n    ts⁻ = recover_thermo_state(balance_law, state_prognostic⁻, state_auxiliary⁻)\n\n    u⁻ = ρu⁻ / ρ⁻\n    uᵀn⁻ = u⁻' * normal_vector\n    e⁻ = ρe⁻ / ρ⁻\n    h⁻ = total_specific_enthalpy(ts⁻, e⁻)\n    p⁻ = air_pressure(ts⁻)\n    c⁻ = soundspeed_air(ts⁻)\n\n    ρ⁺ = state_prognostic⁺.ρ\n    ρu⁺ = state_prognostic⁺.ρu\n    ρe⁺ = state_prognostic⁺.energy.ρe\n\n    # TODO: state_auxiliary⁺ is not up-to-date\n    # with state_prognostic⁺ on the boundaries\n    ts⁺ = recover_thermo_state(balance_law, state_prognostic⁺, state_auxiliary⁺)\n\n    u⁺ = ρu⁺ / ρ⁺\n    uᵀn⁺ = u⁺' * normal_vector\n    e⁺ = ρe⁺ / ρ⁺\n    h⁺ = total_specific_enthalpy(ts⁺, e⁺)\n    p⁺ = air_pressure(ts⁺)\n    c⁺ = soundspeed_air(ts⁺)\n\n    ρ̃ = sqrt(ρ⁻ * ρ⁺)\n    ũ = roe_average(ρ⁻, ρ⁺, u⁻, u⁺)\n    h̃ = roe_average(ρ⁻, ρ⁺, h⁻, h⁺)\n    c̃ = sqrt(roe_average(ρ⁻, ρ⁺, c⁻^2, c⁺^2))\n\n    ũᵀn = ũ' * normal_vector\n\n    Δρ = ρ⁺ - ρ⁻\n    Δp = p⁺ - p⁻\n    Δu = u⁺ - u⁻\n    Δuᵀn = Δu' * normal_vector\n\n    w1 = abs(ũᵀn - c̃) * (Δp - ρ̃ * c̃ * Δuᵀn) / (2 * c̃^2)\n    w2 = abs(ũᵀn + c̃) * (Δp + ρ̃ * c̃ * Δuᵀn) / (2 * c̃^2)\n    w3 = abs(ũᵀn) * (Δρ - Δp / c̃^2)\n    w4 = abs(ũᵀn) * ρ̃\n\n    fluxᵀn.ρ -= (w1 + w2 + w3) / 2\n    fluxᵀn.ρu -=\n        (\n            w1 * (ũ - c̃ * normal_vector) +\n            w2 * (ũ + c̃ * normal_vector) +\n            w3 * ũ +\n            w4 * (Δu - Δuᵀn * normal_vector)\n        ) / 2\n    fluxᵀn.energy.ρe -=\n        (\n            w1 * (h̃ - c̃ * ũᵀn) +\n            w2 * (h̃ + c̃ * ũᵀn) +\n            w3 * (ũ' * ũ / 2 + Φ - _T_0 * _cv_d) +\n            w4 * (ũ' * Δu - ũᵀn * Δuᵀn)\n        ) / 2\n\n    if !(tracer_model(balance_law) isa NoTracers)\n        ρχ⁻ = state_prognostic⁻.tracers.ρχ\n        χ⁻ = ρχ⁻ / ρ⁻\n\n        ρχ⁺ = state_prognostic⁺.tracers.ρχ\n        χ⁺ = ρχ⁺ / ρ⁺\n\n        χ̃ = roe_average(ρ⁻, ρ⁺, χ⁻, χ⁺)\n        Δρχ = ρχ⁺ - ρχ⁻\n\n        wt = abs(ũᵀn) * (Δρχ - χ̃ * Δp / c̃^2)\n\n        fluxᵀn.tracers.ρχ -= ((w1 + w2) * χ̃ + wt) / 2\n    end\nend\n\n\"\"\"\n    NumericalFluxFirstOrder()\n        ::HLLCNumericalFlux,\n        balance_law::AtmosModel,\n        fluxᵀn,\n        normal_vector,\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n        direction,\n    )\n\nAn implementation of the numerical flux based on the HLLC method for\nthe AtmosModel. For more information on this particular implementation,\nsee Chapter 10.4 in the provided reference below.\n\n## References\n\n - [Toro2013](@cite)\n\n\"\"\"\nfunction numerical_flux_first_order!(\n    ::HLLCNumericalFlux,\n    balance_law::AtmosModel,\n    fluxᵀn::Vars{S},\n    normal_vector::SVector,\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n    direction,\n) where {S, A}\n    FT = eltype(fluxᵀn)\n    num_state_prognostic = number_states(balance_law, Prognostic())\n    param_set = parameter_set(balance_law)\n\n    # Extract the first-order fluxes from the AtmosModel (underlying BalanceLaw)\n    # and compute normals on the positive + and negative - sides of the\n    # interior facets\n    flux⁻ = similar(parent(fluxᵀn), Size(3, num_state_prognostic))\n    fill!(flux⁻, -zero(FT))\n    flux_first_order!(\n        balance_law,\n        Grad{S}(flux⁻),\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        t,\n        direction,\n    )\n    fluxᵀn⁻ = flux⁻' * normal_vector\n\n    flux⁺ = similar(flux⁻)\n    fill!(flux⁺, -zero(FT))\n    flux_first_order!(\n        balance_law,\n        Grad{S}(flux⁺),\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n        direction,\n    )\n    fluxᵀn⁺ = flux⁺' * normal_vector\n\n    # Extract relevant fields and thermodynamic variables defined on\n    # the positive + and negative - sides of the interior facets\n    ρ⁻ = state_prognostic⁻.ρ\n    ρu⁻ = state_prognostic⁻.ρu\n    ρe⁻ = state_prognostic⁻.energy.ρe\n    ts⁻ = recover_thermo_state(balance_law, state_prognostic⁻, state_auxiliary⁻)\n\n    u⁻ = ρu⁻ / ρ⁻\n    c⁻ = soundspeed_air(ts⁻)\n\n    uᵀn⁻ = u⁻' * normal_vector\n    e⁻ = ρe⁻ / ρ⁻\n    p⁻ = air_pressure(ts⁻)\n\n    ρ⁺ = state_prognostic⁺.ρ\n    ρu⁺ = state_prognostic⁺.ρu\n    ρe⁺ = state_prognostic⁺.energy.ρe\n    ts⁺ = recover_thermo_state(balance_law, state_prognostic⁺, state_auxiliary⁺)\n\n    u⁺ = ρu⁺ / ρ⁺\n    uᵀn⁺ = u⁺' * normal_vector\n    e⁺ = ρe⁺ / ρ⁺\n    p⁺ = air_pressure(ts⁺)\n    c⁺ = soundspeed_air(ts⁺)\n\n    # Wave speeds estimates S⁻ and S⁺\n    S⁻ = min(uᵀn⁻ - c⁻, uᵀn⁺ - c⁺)\n    S⁺ = max(uᵀn⁻ + c⁻, uᵀn⁺ + c⁺)\n\n    # Compute the middle wave speed S⁰ in the contact/star region\n    S⁰ =\n        (p⁺ - p⁻ + ρ⁻ * uᵀn⁻ * (S⁻ - uᵀn⁻) - ρ⁺ * uᵀn⁺ * (S⁺ - uᵀn⁺)) /\n        (ρ⁻ * (S⁻ - uᵀn⁻) - ρ⁺ * (S⁺ - uᵀn⁺))\n\n    p⁰ =\n        (\n            p⁺ +\n            p⁻ +\n            ρ⁻ * (S⁻ - uᵀn⁻) * (S⁰ - uᵀn⁻) +\n            ρ⁺ * (S⁺ - uᵀn⁺) * (S⁰ - uᵀn⁺)\n        ) / 2\n\n    # Compute p * D = p * (0, n₁, n₂, n₃, S⁰)\n    pD = @MVector zeros(FT, num_state_prognostic)\n    ref_state = reference_state(balance_law)\n    if ref_state isa HydrostaticState && ref_state.subtract_off\n        # pressure should be continuous but it doesn't hurt to average\n        ref_p⁻ = state_auxiliary⁻.ref_state.p\n        ref_p⁺ = state_auxiliary⁺.ref_state.p\n        ref_p⁰ = (ref_p⁻ + ref_p⁺) / 2\n\n        momentum_p = p⁰ - ref_p⁰\n    else\n        momentum_p = p⁰\n    end\n\n    pD[2] = momentum_p * normal_vector[1]\n    pD[3] = momentum_p * normal_vector[2]\n    pD[4] = momentum_p * normal_vector[3]\n    pD[5] = p⁰ * S⁰\n\n    # Computes both +/- sides of intermediate flux term flux⁰\n    flux⁰⁻ =\n        (S⁰ * (S⁻ * parent(state_prognostic⁻) - fluxᵀn⁻) + S⁻ * pD) / (S⁻ - S⁰)\n    flux⁰⁺ =\n        (S⁰ * (S⁺ * parent(state_prognostic⁺) - fluxᵀn⁺) + S⁺ * pD) / (S⁺ - S⁰)\n\n    if 0 <= S⁻\n        parent(fluxᵀn) .= fluxᵀn⁻\n    elseif S⁻ < 0 <= S⁰\n        parent(fluxᵀn) .= flux⁰⁻\n    elseif S⁰ < 0 <= S⁺\n        parent(fluxᵀn) .= flux⁰⁺\n    else # 0 > S⁺\n        parent(fluxᵀn) .= fluxᵀn⁺\n    end\nend\n\nfunction numerical_flux_first_order!(\n    numerical_flux::RoeNumericalFluxMoist,\n    balance_law::AtmosModel,\n    fluxᵀn::Vars{S},\n    normal_vector::SVector,\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n    direction,\n) where {S, A}\n    moisture_model(balance_law) isa EquilMoist ||\n        error(\"Must use a EquilMoist model for RoeNumericalFluxMoist\")\n    numerical_flux_first_order!(\n        CentralNumericalFluxFirstOrder(),\n        balance_law,\n        fluxᵀn,\n        normal_vector,\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n        direction,\n    )\n\n    FT = eltype(fluxᵀn)\n    param_set = parameter_set(balance_law)\n    _cv_d::FT = cv_d(param_set)\n    _T_0::FT = T_0(param_set)\n    γ::FT = cp_d(param_set) / cv_d(param_set)\n    _e_int_v0::FT = e_int_v0(param_set)\n    Φ = gravitational_potential(balance_law, state_auxiliary⁻)\n\n    ρ⁻ = state_prognostic⁻.ρ\n    ρu⁻ = state_prognostic⁻.ρu\n    ρe⁻ = state_prognostic⁻.energy.ρe\n    ρq_tot⁻ = state_prognostic⁻.moisture.ρq_tot\n\n    u⁻ = ρu⁻ / ρ⁻\n    e⁻ = ρe⁻ / ρ⁻\n    ts⁻ = recover_thermo_state(balance_law, state_prognostic⁻, state_auxiliary⁻)\n    h⁻ = total_specific_enthalpy(ts⁻, e⁻)\n    qt⁻ = ρq_tot⁻ / ρ⁻\n    c⁻ = soundspeed_air(ts⁻)\n\n    ρ⁺ = state_prognostic⁺.ρ\n    ρu⁺ = state_prognostic⁺.ρu\n    ρe⁺ = state_prognostic⁺.energy.ρe\n    ρq_tot⁺ = state_prognostic⁺.moisture.ρq_tot\n\n    u⁺ = ρu⁺ / ρ⁺\n    e⁺ = ρe⁺ / ρ⁺\n    ts⁺ = recover_thermo_state(balance_law, state_prognostic⁺, state_auxiliary⁺)\n    h⁺ = total_specific_enthalpy(ts⁺, e⁺)\n    qt⁺ = ρq_tot⁺ / ρ⁺\n    c⁺ = soundspeed_air(ts⁺)\n    ũ = roe_average(ρ⁻, ρ⁺, u⁻, u⁺)\n    e_tot = roe_average(ρ⁻, ρ⁺, e⁻, e⁺)\n    h̃ = roe_average(ρ⁻, ρ⁺, h⁻, h⁺)\n    qt = roe_average(ρ⁻, ρ⁺, qt⁻, qt⁺)\n    ρ = sqrt(ρ⁻ * ρ⁺)\n    e_int⁻ = internal_energy(ts⁻)\n    e_int⁺ = internal_energy(ts⁺)\n    e_int = roe_average(ρ⁻, ρ⁺, e_int⁻, e_int⁺)\n    ts = TD.PhaseEquil_ρeq(\n        param_set,\n        ρ,\n        e_int,\n        qt,\n        moisture_model(balance_law).maxiter,\n        moisture_model(balance_law).tolerance,\n    )\n    c̃ = sqrt((γ - 1) * (h̃ - (ũ[1]^2 + ũ[2]^2 + ũ[3]^2) / 2))\n    (R_m, _cp_m, _cv_m, gamma) = gas_constants(ts)\n    # chosen by fair dice roll\n    # guaranteed to be random\n    ω = FT(π) / 3\n    δ = FT(π) / 5\n    random_unit_vector = SVector(sin(ω) * cos(δ), cos(ω) * cos(δ), sin(δ))\n    # tangent space basis\n    τ1 = random_unit_vector × normal_vector\n    τ2 = τ1 × normal_vector\n    ũᵀn⁻ = u⁻' * normal_vector\n    ũᵀn⁺ = u⁺' * normal_vector\n    ũᵀn = ũ' * normal_vector\n    ũc̃⁻ = ũ + c̃ * normal_vector\n    ũc̃⁺ = ũ - c̃ * normal_vector\n    e_kin_pot = h̃ - _e_int_v0 * qt - _cp_m * c̃^2 / R_m\n    if (numerical_flux.LM == true)\n        Mach⁺ = sqrt(u⁺' * u⁺) / c⁺\n        Mach⁻ = sqrt(u⁻' * u⁻) / c⁻\n        Mach = (Mach⁺ + Mach⁻) / 2\n        c̃_LM = c̃ * min(Mach * sqrt(4 + (1 - Mach^2)^2) / (1 + Mach^2), 1)\n    else\n        c̃_LM = c̃\n    end\n    #Standard Roe\n    Λ = SDiagonal(\n        abs(ũᵀn - c̃_LM),\n        abs(ũᵀn),\n        abs(ũᵀn),\n        abs(ũᵀn),\n        abs(ũᵀn + c̃_LM),\n        abs(ũᵀn),\n    )\n    #Harten Hyman\n    if (numerical_flux.HH == true)\n        Λ = SDiagonal(\n            max(\n                abs(ũᵀn - c̃_LM),\n                max(\n                    0,\n                    ũᵀn - c̃_LM - (u⁻' * normal_vector - c⁻),\n                    u⁺' * normal_vector - c⁺ - (ũᵀn - c̃_LM),\n                ),\n            ),\n            max(\n                abs(ũᵀn),\n                max(\n                    0,\n                    ũᵀn - (u⁻' * normal_vector),\n                    u⁺' * normal_vector - (ũᵀn),\n                ),\n            ),\n            max(\n                abs(ũᵀn),\n                max(\n                    0,\n                    ũᵀn - (u⁻' * normal_vector),\n                    u⁺' * normal_vector - (ũᵀn),\n                ),\n            ),\n            max(\n                abs(ũᵀn),\n                max(\n                    0,\n                    ũᵀn - (u⁻' * normal_vector),\n                    u⁺' * normal_vector - (ũᵀn),\n                ),\n            ),\n            max(\n                abs(ũᵀn + c̃_LM),\n                max(\n                    0,\n                    ũᵀn + c̃_LM - (u⁻' * normal_vector + c⁻),\n                    u⁺' * normal_vector + c⁺ - (ũᵀn + c̃_LM),\n                ),\n            ),\n            max(\n                abs(ũᵀn),\n                max(\n                    0,\n                    ũᵀn - (u⁻' * normal_vector),\n                    u⁺' * normal_vector - (ũᵀn),\n                ),\n            ),\n        )\n    end\n    if (numerical_flux.LV == true)\n        #Pseudo LeVeque Fix\n        δ_L_1 = max(0, ũᵀn - ũᵀn⁻)\n        δ_L_2 = max(0, ũᵀn - c̃_LM - (ũᵀn⁻ - c⁻))\n        δ_L_3 = max(0, ũᵀn + c̃_LM - (ũᵀn⁻ + c⁻))\n        δ_R_1 = max(0, ũᵀn⁺ - ũᵀn)\n        δ_R_2 = max(0, ũᵀn⁺ - c⁺ - (ũᵀn - c̃_LM))\n        δ_R_3 = max(0, ũᵀn⁺ + c⁺ - (ũᵀn + c̃_LM))\n        if (ũᵀn < δ_L_1 && ũᵀn > -δ_R_1)\n            qa1 = ((δ_L_1 - δ_R_1) * ũᵀn + 2 * δ_L_1 * δ_R_1) / (δ_L_1 + δ_R_1)\n        else\n            qa1 = abs(ũᵀn)\n        end\n        if (ũᵀn - c̃ < δ_L_2 && ũᵀn - c̃_LM > -δ_R_2)\n            qa2 =\n                ((δ_L_2 - δ_R_2) * (ũᵀn - c̃_LM) + 2 * δ_L_2 * δ_R_2) /\n                (δ_L_2 + δ_R_2)\n        else\n            qa2 = abs(ũᵀn - c̃_LM)\n        end\n        if (ũᵀn + c̃_LM < δ_L_3 && ũᵀn + c̃ > -δ_R_3)\n            qa3 =\n                ((δ_L_3 - δ_R_3) * (ũᵀn + c̃_LM) + 2 * δ_R_3 * δ_R_3) /\n                (δ_L_3 + δ_R_3)\n        else\n            qa3 = abs(ũᵀn + c̃_LM)\n        end\n        Λ = SDiagonal(qa2, qa1, qa1, qa1, qa3, qa1)\n    end\n    if (numerical_flux.LVPP == true)\n        #PosPreserving with LeVeque\n        b_L = min(ũᵀn - c̃_LM, ũᵀn⁻ - c⁻)\n        b_R = max(ũᵀn + c̃_LM, ũᵀn⁺ + c⁺)\n        b⁻ = min(0, b_L)\n        b⁺ = max(0, b_R)\n        δ_L_1 = max(0, ũᵀn - b⁻)\n        δ_L_2 = max(0, ũᵀn - c̃_LM - b⁻)\n        δ_L_3 = max(0, ũᵀn + c̃_LM - b⁻)\n        δ_R_1 = max(0, b⁺ - ũᵀn)\n        δ_R_2 = max(0, b⁺ - (ũᵀn - c̃_LM))\n        δ_R_3 = max(0, b⁺ - (ũᵀn + c̃_LM))\n        if (ũᵀn < δ_L_1 && ũᵀn > -δ_R_1)\n            qa1 = ((δ_L_1 - δ_R_1) * ũᵀn + 2 * δ_L_1 * δ_R_1) / (δ_L_1 + δ_R_1)\n        else\n            qa1 = abs(ũᵀn)\n        end\n        if (ũᵀn - c̃_LM < δ_L_2 && ũᵀn - c̃_LM > -δ_R_2)\n            qa2 =\n                ((δ_L_2 - δ_R_2) * (ũᵀn - c̃) + 2 * δ_L_2 * δ_R_2) /\n                (δ_L_2 + δ_R_2)\n        else\n            qa2 = abs(ũᵀn - c̃_LM)\n        end\n        if (ũᵀn + c̃_LM < δ_L_3 && ũᵀn + c̃_LM > -δ_R_3)\n            qa3 =\n                ((δ_L_3 - δ_R_3) * (ũᵀn + c̃_LM) + 2 * δ_R_3 * δ_R_3) /\n                (δ_L_3 + δ_R_3)\n        else\n            qa3 = abs(ũᵀn + c̃_LM)\n        end\n        Λ = SDiagonal(qa2, qa1, qa1, qa1, qa3, qa1)\n    end\n\n    M = hcat(\n        SVector(1, ũc̃⁺[1], ũc̃⁺[2], ũc̃⁺[3], h̃ - c̃ * ũᵀn, qt),\n        SVector(0, τ1[1], τ1[2], τ1[3], τ1' * ũ, 0),\n        SVector(0, τ2[1], τ2[2], τ2[3], τ2' * ũ, 0),\n        SVector(1, ũ[1], ũ[2], ũ[3], ũ' * ũ / 2 + Φ - _T_0 * _cv_m, 0),\n        SVector(1, ũc̃⁻[1], ũc̃⁻[2], ũc̃⁻[3], h̃ + c̃ * ũᵀn, qt),\n        SVector(0, 0, 0, 0, _e_int_v0, 1),\n    )\n    Δρ = ρ⁺ - ρ⁻\n    Δρu = ρu⁺ - ρu⁻\n    Δρe = ρe⁺ - ρe⁻\n    Δρq_tot = ρq_tot⁺ - ρq_tot⁻\n    Δstate = SVector(Δρ, Δρu[1], Δρu[2], Δρu[3], Δρe, Δρq_tot)\n    parent(fluxᵀn) .-= M * Λ * (M \\ Δstate) / 2\nend\n\nfunction numerical_flux_first_order!(\n    numerical_flux::LMARSNumericalFlux,\n    balance_law::AtmosModel,\n    fluxᵀn::Vars{S},\n    normal_vector::SVector,\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n    direction,\n) where {S, A}\n\n\n    @assert moisture_model(balance_law) isa DryModel ||\n            moisture_model(balance_law) isa EquilMoist\n\n    FT = eltype(fluxᵀn)\n    param_set = parameter_set(balance_law)\n\n    ρ⁻ = state_prognostic⁻.ρ\n    ρu⁻ = state_prognostic⁻.ρu\n    ρe⁻ = state_prognostic⁻.energy.ρe\n    ts⁻ = recover_thermo_state(balance_law, state_prognostic⁻, state_auxiliary⁻)\n\n    u⁻ = ρu⁻ / ρ⁻\n    e⁻ = ρe⁻ / ρ⁻\n    uᵀn⁻ = u⁻' * normal_vector\n    p⁻ = air_pressure(ts⁻)\n    ref_state = reference_state(balance_law)\n    if ref_state isa HydrostaticState && ref_state.subtract_off\n        p⁻ -= state_auxiliary⁻.ref_state.p\n    end\n    c⁻ = soundspeed_air(ts⁻)\n    h⁻ = total_specific_enthalpy(ts⁻, e⁻)\n\n    ρ⁺ = state_prognostic⁺.ρ\n    ρu⁺ = state_prognostic⁺.ρu\n    ρe⁺ = state_prognostic⁺.energy.ρe\n    ts⁺ = recover_thermo_state(balance_law, state_prognostic⁺, state_auxiliary⁺)\n    u⁺ = ρu⁺ / ρ⁺\n    e⁺ = ρe⁺ / ρ⁺\n    uᵀn⁺ = u⁺' * normal_vector\n    p⁺ = air_pressure(ts⁺)\n    if ref_state isa HydrostaticState && ref_state.subtract_off\n        p⁺ -= state_auxiliary⁺.ref_state.p\n    end\n    c⁺ = soundspeed_air(ts⁺)\n    h⁺ = total_specific_enthalpy(ts⁺, e⁺)\n\n    # Eqn (49), (50), β the tuning parameter\n    β = FT(1)\n    u_half = 1 / 2 * (uᵀn⁺ + uᵀn⁻) - β * 1 / (ρ⁻ + ρ⁺) / c⁻ * (p⁺ - p⁻)\n    p_half = 1 / 2 * (p⁺ + p⁻) - β * ((ρ⁻ + ρ⁺) * c⁻) / 4 * (uᵀn⁺ - uᵀn⁻)\n\n    # Eqn (46), (47)\n    ρ_b = u_half > FT(0) ? ρ⁻ : ρ⁺\n    ρu_b = u_half > FT(0) ? ρu⁻ : ρu⁺\n    ρh_b = u_half > FT(0) ? ρ⁻ * h⁻ : ρ⁺ * h⁺\n\n    # Update fluxes Eqn (18)\n    fluxᵀn.ρ = ρ_b * u_half\n    fluxᵀn.ρu = ρu_b * u_half .+ p_half * normal_vector\n    fluxᵀn.energy.ρe = ρh_b * u_half\n\n    if moisture_model(balance_law) isa EquilMoist\n        ρq⁻ = state_prognostic⁻.moisture.ρq_tot\n        q⁻ = ρq⁻ / ρ⁻\n        ρq⁺ = state_prognostic⁺.moisture.ρq_tot\n        q⁺ = ρq⁺ / ρ⁺\n        ρq_b = u_half > FT(0) ? ρq⁻ : ρq⁺\n        fluxᵀn.moisture.ρq_tot = ρq_b * u_half\n    end\n    if !(tracer_model(balance_law) isa NoTracers)\n        ρχ⁻ = state_prognostic⁻.tracers.ρχ\n        χ⁻ = ρχ⁻ / ρ⁻\n        ρχ⁺ = state_prognostic⁺.tracers.ρχ\n        χ⁺ = ρχ⁺ / ρ⁺\n        ρχ_b = u_half > FT(0) ? ρχ⁻ : ρχ⁺\n        fluxᵀn.tracers.ρχ = ρχ_b * u_half\n    end\nend\n\nend # module\n"
  },
  {
    "path": "src/Atmos/Model/atmos_tendencies.jl",
    "content": "#####\n##### Tendency specification\n#####\n\n#####\n##### Sources\n#####\n\neq_tends(pv::AbstractPrognosticVariable, m::AtmosModel, tt::Source) =\n    (m.source[pv]..., eq_tends(pv, turbconv_model(m), tt)...)\n\n#####\n##### First order fluxes\n#####\n\neq_tends(::Mass, ::Anelastic1D, ::Flux{FirstOrder}) = ()\n\neq_tends(::Mass, ::Compressible, ::Flux{FirstOrder}) = (Advect(),)\n\n# Mass\neq_tends(pv::Mass, atmos::AtmosModel, tt::Flux{FirstOrder}) =\n    (eq_tends(pv, compressibility_model(atmos), tt))\n\n# Momentum\neq_tends(pv::Momentum, ::Compressible, ::Flux{FirstOrder}) =\n    (PressureGradient(),)\n\neq_tends(pv::Momentum, ::Anelastic1D, ::Flux{FirstOrder}) = ()\n\neq_tends(pv::Momentum, m::AtmosModel, tt::Flux{FirstOrder}) =\n    (Advect(), eq_tends(pv, compressibility_model(m), tt)...)\n\n# Energy\neq_tends(::Energy, m::TotalEnergyModel, tt::Flux{FirstOrder}) =\n    (Advect(), Pressure())\n\neq_tends(::ρθ_liq_ice, m::θModel, tt::Flux{FirstOrder}) = (Advect(),)\n\n# TODO: make radiation aware of which energy formulation is used:\n# eq_tends(pv::PV, m::AtmosModel, tt::Flux{FirstOrder}) where {PV <: AbstractEnergyVariable} =\n#     (eq_tends(pv, energy_model(m), tt)..., eq_tends(pv, energy_model(m), radiation_model(m), tt)...)\neq_tends(pv::AbstractEnergyVariable, m::AtmosModel, tt::Flux{FirstOrder}) = (\n    eq_tends(pv, energy_model(m), tt)...,\n    eq_tends(pv, radiation_model(m), tt)...,\n)\n\n# AbstractMoistureVariable\neq_tends(::AbstractMoistureVariable, ::AtmosModel, ::Flux{FirstOrder}) =\n    (Advect(),)\n\n# AbstractPrecipitationVariable\neq_tends(\n    pv::AbstractPrecipitationVariable,\n    m::AtmosModel,\n    tt::Flux{FirstOrder},\n) = (eq_tends(pv, precipitation_model(m), tt)...,)\n\n# Tracers\neq_tends(pv::Tracers{N}, ::AtmosModel, ::Flux{FirstOrder}) where {N} =\n    (Advect(),)\n\n#####\n##### Second order fluxes\n#####\n\neq_tends(\n    ::Union{Mass, Momentum, AbstractMoistureVariable},\n    ::DryModel,\n    ::Flux{SecondOrder},\n) = ()\neq_tends(\n    ::Union{Mass, Momentum, AbstractMoistureVariable},\n    ::AbstractMoistureModel,\n    ::Flux{SecondOrder},\n) = (MoistureDiffusion(),)\n\n# Mass\neq_tends(pv::Mass, m::AtmosModel, tt::Flux{SecondOrder}) =\n    (eq_tends(pv, moisture_model(m), tt)...,)\n\n# Momentum\neq_tends(pv::Momentum, m::AtmosModel, tt::Flux{SecondOrder}) = (\n    ViscousStress(),\n    eq_tends(pv, moisture_model(m), tt)...,\n    eq_tends(pv, turbconv_model(m), tt)...,\n    eq_tends(pv, hyperdiffusion_model(m), tt)...,\n)\n\n# Energy\neq_tends(::Energy, m::TotalEnergyModel, tt::Flux{SecondOrder}) =\n    (ViscousFlux(), DiffEnthalpyFlux())\n\neq_tends(::ρθ_liq_ice, m::θModel, tt::Flux{SecondOrder}) = (ViscousFlux(),)\n\neq_tends(pv::AbstractEnergyVariable, m::AtmosModel, tt::Flux{SecondOrder}) = (\n    eq_tends(pv, energy_model(m), tt)...,\n    eq_tends(pv, turbconv_model(m), tt)...,\n    eq_tends(pv, hyperdiffusion_model(m), tt)...,\n)\n\n# AbstractMoistureVariable\neq_tends(pv::AbstractMoistureVariable, m::AtmosModel, tt::Flux{SecondOrder}) = (\n    eq_tends(pv, moisture_model(m), tt)...,\n    eq_tends(pv, turbconv_model(m), tt)...,\n    eq_tends(pv, hyperdiffusion_model(m), tt)...,\n)\n\n# AbstractPrecipitationVariable\neq_tends(\n    pv::AbstractPrecipitationVariable,\n    m::AtmosModel,\n    tt::Flux{SecondOrder},\n) = (eq_tends(pv, precipitation_model(m), tt)...,)\n\n# Tracers\neq_tends(pv::Tracers{N}, m::AtmosModel, tt::Flux{SecondOrder}) where {N} =\n    (eq_tends(pv, tracer_model(m), tt)...,)\n"
  },
  {
    "path": "src/Atmos/Model/bc_energy.jl",
    "content": "abstract type EnergyBC end\n\nusing ..TurbulenceClosures\nusing SurfaceFluxes: get_energy_flux, surface_conditions, DGScheme\n\n\"\"\"\n    Insulating() :: EnergyBC\nNo energy flux across the boundary.\n\"\"\"\nstruct Insulating <: EnergyBC end\nfunction atmos_energy_boundary_state!(nf, bc_energy::Insulating, atmos, _...) end\nfunction atmos_energy_normal_boundary_flux_second_order!(\n    nf,\n    bc_energy::Insulating,\n    atmos,\n    _...,\n) end\n\n\n\"\"\"\n    PrescribedTemperature(fn) :: EnergyBC\nPrescribe the temperature at the boundary by `fn`, a function with signature\n`fn(state, aux, t)` returning the temperature (in K).\n\"\"\"\nstruct PrescribedTemperature{FN} <: EnergyBC\n    fn::FN\nend\nfunction atmos_energy_boundary_state!(\n    nf,\n    bc_energy::PrescribedTemperature,\n    atmos,\n    state⁺,\n    args,\n)\n    @unpack aux⁻, state⁻, t = args\n    FT = eltype(aux⁻)\n    param_set = parameter_set(atmos)\n    _T_0::FT = T_0(param_set)\n    _cv_d::FT = cv_d(param_set)\n\n    T = bc_energy.fn(state⁻, aux⁻, t)\n    E_int⁺ = state⁺.ρ * _cv_d * (T - _T_0)\n    state⁺.energy.ρe =\n        E_int⁺ + state⁺.ρ * gravitational_potential(atmos.orientation, aux⁻)\nend\n\nfunction atmos_energy_normal_boundary_flux_second_order!(\n    nf,\n    bc_energy::PrescribedTemperature,\n    atmos,\n    fluxᵀn,\n    args,\n)\n    @unpack state⁻, aux⁻, diffusive⁻, hyperdiff⁻, t, n⁻ = args\n    tend_type = Flux{SecondOrder}()\n    _args⁻ = (;\n        state = state⁻,\n        aux = aux⁻,\n        t,\n        diffusive = diffusive⁻,\n        hyperdiffusive = hyperdiff⁻,\n    )\n    pargs = merge(_args⁻, (precomputed = precompute(atmos, _args⁻, tend_type),))\n    total_flux =\n        Σfluxes(Energy(), eq_tends(Energy(), atmos, tend_type), atmos, pargs)\n    nd_ρh_tot = dot(n⁻, total_flux)\n    # both sides involve projections of normals, so signs are consistent\n    fluxᵀn.energy.ρe += nd_ρh_tot\nend\n\n\n\"\"\"\n    PrescribedEnergyFlux(fn) :: EnergyBC\nPrescribe the net inward energy flux across the boundary by `fn`, a function\nwith signature `fn(state, aux, t)`, returning the flux (in W/m^2).\n\"\"\"\nstruct PrescribedEnergyFlux{FN} <: EnergyBC\n    fn::FN\nend\nfunction atmos_energy_boundary_state!(\n    nf,\n    bc_energy::PrescribedEnergyFlux,\n    atmos,\n    _...,\n) end\nfunction atmos_energy_normal_boundary_flux_second_order!(\n    nf,\n    bc_energy::PrescribedEnergyFlux,\n    atmos,\n    fluxᵀn,\n    args,\n)\n    @unpack state⁻, aux⁻, t = args\n\n    # DG normal is defined in the outward direction\n    # we want to prescribe the inward flux\n    fluxᵀn.energy.ρe -= bc_energy.fn(state⁻, aux⁻, t)\nend\n\n\"\"\"\n    Adiabaticθ(fn) :: EnergyBC\nPrescribe the net inward potential temperature flux\nacross the boundary by `fn`, a function with signature\n`fn(state, aux, t)`, returning the flux (in kgK/m^2).\n\"\"\"\nstruct Adiabaticθ{FN} <: EnergyBC\n    fn::FN\nend\nfunction atmos_energy_boundary_state!(nf, bc_energy::Adiabaticθ, atmos, _...) end\nfunction atmos_energy_normal_boundary_flux_second_order!(\n    nf,\n    bc_energy::Adiabaticθ,\n    atmos,\n    fluxᵀn,\n    args,\n)\n    @unpack state⁻, aux⁻, t = args\n\n    # DG normal is defined in the outward direction\n    # we want to prescribe the inward flux\n    fluxᵀn.energy.ρθ_liq_ice -= bc_energy.fn(state⁻, aux⁻, t)\nend\n\n\"\"\"\n    BulkFormulaEnergy(fn) :: EnergyBC\nCalculate the net inward energy flux across the boundary. The drag\ncoefficient is `C_h = fn_C_h(atmos, state, aux, t, normu_int_tan)`. The surface\ntemperature and q_tot are `T, q_tot = fn_T_and_q_tot(atmos, state, aux, t)`.\nReturn the flux (in W m^-2).\n\"\"\"\nstruct BulkFormulaEnergy{FNX, FNTM} <: EnergyBC\n    fn_C_h::FNX\n    fn_T_and_q_tot::FNTM\nend\nfunction atmos_energy_boundary_state!(\n    nf,\n    bc_energy::BulkFormulaEnergy,\n    atmos,\n    _...,\n) end\nfunction atmos_energy_normal_boundary_flux_second_order!(\n    nf,\n    bc_energy::BulkFormulaEnergy,\n    atmos,\n    fluxᵀn,\n    args,\n)\n    @unpack state⁻, aux⁻, t, state_int⁻, aux_int⁻ = args\n\n    u_int⁻ = state_int⁻.ρu / state_int⁻.ρ\n    u_int⁻_tan = projection_tangential(atmos, aux_int⁻, u_int⁻)\n    normu_int⁻_tan = norm(u_int⁻_tan)\n    C_h = bc_energy.fn_C_h(atmos, state⁻, aux⁻, t, normu_int⁻_tan)\n    T, q_tot = bc_energy.fn_T_and_q_tot(atmos, state⁻, aux⁻, t)\n    param_set = parameter_set(atmos)\n\n    # calculate MSE from the states at the boundary and at the interior point\n    ts = PhaseEquil_ρTq(param_set, state⁻.ρ, T, q_tot)\n    ts_int = recover_thermo_state(atmos, state_int⁻, aux_int⁻)\n    e_pot = gravitational_potential(atmos.orientation, aux⁻)\n    e_pot_int = gravitational_potential(atmos.orientation, aux_int⁻)\n    MSE = moist_static_energy(ts, e_pot)\n    MSE_int = moist_static_energy(ts_int, e_pot_int)\n\n    # TODO: use the correct density at the surface\n    ρ_avg = average_density(state⁻.ρ, state_int⁻.ρ)\n    # NOTE: difference from design docs since normal points outwards\n    fluxᵀn.energy.ρe -= C_h * ρ_avg * normu_int⁻_tan * (MSE - MSE_int)\nend\n\n\"\"\"\n    NishizawaEnergyFlux(fn) :: EnergyBC\nCalculate the net inward energy flux across the boundary following Nishizawa and Kitamura (2018).\nReturn the flux (in W m^-2).\n\"\"\"\nstruct NishizawaEnergyFlux{FNTM, FNX} <: EnergyBC\n    fn_z0::FNX\n    fn_T_and_q_tot::FNTM\nend\nfunction atmos_energy_boundary_state!(\n    nf,\n    bc_energy::NishizawaEnergyFlux,\n    atmos,\n    _...,\n) end\nfunction atmos_energy_normal_boundary_flux_second_order!(\n    nf,\n    bc_energy::NishizawaEnergyFlux,\n    atmos,\n    fluxᵀn,\n    args,\n)\n    @unpack state⁻, aux⁻, t, aux_int⁻, state_int⁻ = args\n    param_set = parameter_set(atmos)\n    FT = eltype(state⁻)\n    # Interior state\n    u_int⁻ = state_int⁻.ρu / state_int⁻.ρ\n    u_int⁻_tan = projection_tangential(atmos, aux_int⁻, u_int⁻)\n    normu_int⁻_tan = norm(u_int⁻_tan)\n    q_tot_int =\n        moisture_model(atmos) isa DryModel ? FT(0) :\n        state_int⁻.moisture.ρq_tot / state_int⁻.ρ\n    # recover thermo state\n    ts_int = recover_thermo_state(atmos, state_int⁻, aux_int⁻)\n    x_in =\n        MArray{Tuple{3}, FT}(FT[normu_int⁻_tan, dry_pottemp(ts_int), q_tot_int])\n\n    # Boundary state\n    T, q_tot = bc_energy.fn_T_and_q_tot(atmos, state⁻, aux⁻, t)\n    x_s = MArray{Tuple{3}, FT}(FT[FT(0), T, q_tot])\n\n    ## Initial guesses for MO parameters, these should be a function of state.\n    LMO_init = FT(100) # Initial value so that ξ_init<<1\n    u_star_init = FT(0.1) * normu_int⁻_tan\n    th_star_init = T\n    qt_star_init = q_tot\n    MO_param_guess = MArray{Tuple{4}, FT}(FT[\n        LMO_init,\n        u_star_init,\n        th_star_init,\n        qt_star_init,\n    ])\n\n    # Roughness and interior heights\n    z_0 = bc_energy.fn_z0(atmos, state⁻, aux⁻, t, normu_int⁻_tan)\n    z_in = altitude(atmos, aux_int⁻)\n\n    θ_flux, q_tot_flux = get_energy_flux(surface_conditions(\n        param_set,\n        MO_param_guess,\n        x_in,\n        x_s,\n        z_0,\n        T,\n        z_in,\n        DGScheme(),\n    ))\n\n    # recover thermo state\n    ts_surf = PhaseEquil_ρTq(param_set, state⁻.ρ, T, q_tot)\n    # Add sensible heat flux\n    fluxᵀn.energy.ρe -= state⁻.ρ * θ_flux * cp_m(ts_surf)\n    # Add latent heat flux\n    fluxᵀn.energy.ρe -= state⁻.ρ * q_tot_flux * latent_heat_vapor(param_set, T)\nend\n"
  },
  {
    "path": "src/Atmos/Model/bc_initstate.jl",
    "content": "using ..Mesh.Grids: _x1, _x2, _x3\n\"\"\"\n    InitStateBC\n\nSet the value at the boundary to match the `init_state_prognostic!` function. This is\nmainly useful for cases where the problem has an explicit solution.\n\n# TODO: This should be fixed later once BCs are figured out (likely want\n# different things here?)\n\"\"\"\nstruct InitStateBC end\nfunction boundary_state!(\n    ::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    bc::InitStateBC,\n    m::AtmosModel,\n    state⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    state⁻::Vars,\n    aux⁻::Vars,\n    t,\n    _...,\n)\n    # Put cood in a NamedTuple to mimmic LocalGeometry\n    init_state_prognostic!(m, state⁺, aux⁺, (coord = aux⁺.coord,), t)\nend\n\nfunction boundary_state!(\n    ::NumericalFluxSecondOrder,\n    bc::InitStateBC,\n    m::AtmosModel,\n    state⁺::Vars,\n    diff⁺::Vars,\n    hyperdiff⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    state⁻::Vars,\n    diff⁻::Vars,\n    hyperdiff⁻::Vars,\n    aux⁻::Vars,\n    t,\n    args...,\n)\n    # Put coord in a NamedTuple to mimmic LocalGeometry\n    init_state_prognostic!(m, state⁺, aux⁺, (coord = aux⁺.coord,), t)\nend\n"
  },
  {
    "path": "src/Atmos/Model/bc_moisture.jl",
    "content": "abstract type MoistureBC end\n\n\"\"\"\n    Impermeable() :: MoistureBC\n\nNo moisture flux.\n\"\"\"\nstruct Impermeable <: MoistureBC end\nfunction atmos_moisture_boundary_state!(\n    nf,\n    bc_moisture::Impermeable,\n    atmos,\n    _...,\n) end\nfunction atmos_moisture_normal_boundary_flux_second_order!(\n    nf,\n    bc_moisture::Impermeable,\n    atmos,\n    _...,\n) end\n\n\n\"\"\"\n    PrescribedMoistureFlux(fn) :: MoistureBC\n\nPrescribe the net inward moisture flux across the boundary by `fn`, a function\nwith signature `fn(state, aux, t)`, returning the flux (in kg/m^2).\n\"\"\"\nstruct PrescribedMoistureFlux{FN} <: MoistureBC\n    fn::FN\nend\nfunction atmos_moisture_boundary_state!(\n    nf,\n    bc_moisture::PrescribedMoistureFlux,\n    atmos,\n    _...,\n) end\nfunction atmos_moisture_normal_boundary_flux_second_order!(\n    nf,\n    bc_moisture::PrescribedMoistureFlux,\n    atmos,\n    fluxᵀn,\n    args,\n)\n    @unpack state⁻, aux⁻, t = args\n\n    nρd_q_tot = -bc_moisture.fn(state⁻, aux⁻, t)\n    fluxᵀn.ρ += nρd_q_tot\n    fluxᵀn.ρu += nρd_q_tot / state⁻.ρ .* state⁻.ρu\n    # assumes EquilMoist\n    fluxᵀn.moisture.ρq_tot += nρd_q_tot\nend\n\n\"\"\"\n    BulkFormulaMoisture(fn) :: MoistureBC\n\nCalculate the net inward moisture flux across the boundary using\nthe bulk formula. The drag coefficient is `C_q = fn_C_q(state, aux,\nt, normu_int_tan)`. The surface q_tot at the boundary is `q_tot =\nfn_q_tot(state, aux, t)`.\n\nReturn the flux (in kg m^-2 s^-1).\n\"\"\"\nstruct BulkFormulaMoisture{FNX, FNM} <: MoistureBC\n    fn_C_q::FNX\n    fn_q_tot::FNM\nend\nfunction atmos_moisture_boundary_state!(\n    nf,\n    bc_moisture::BulkFormulaMoisture,\n    atmos,\n    _...,\n) end\nfunction atmos_moisture_normal_boundary_flux_second_order!(\n    nf,\n    bc_moisture::BulkFormulaMoisture,\n    atmos,\n    fluxᵀn,\n    args,\n)\n    @unpack state⁻, aux⁻, t, aux_int⁻, state_int⁻ = args\n\n    u_int⁻ = state_int⁻.ρu / state_int⁻.ρ\n    u_int⁻_tan = projection_tangential(atmos, aux_int⁻, u_int⁻)\n    normu_int⁻_tan = norm(u_int⁻_tan)\n    C_q = bc_moisture.fn_C_q(state⁻, aux⁻, t, normu_int⁻_tan)\n    q_tot = bc_moisture.fn_q_tot(state⁻, aux⁻, t)\n    q_tot_int = state_int⁻.moisture.ρq_tot / state_int⁻.ρ\n\n    # TODO: use the correct density at the surface\n    ρ_avg = average_density(state⁻.ρ, state_int⁻.ρ)\n    # NOTE: difference from design docs since normal points outwards\n    fluxᵀn.moisture.ρq_tot -= C_q * ρ_avg * normu_int⁻_tan * (q_tot - q_tot_int)\n    fluxᵀn.ρ -= C_q * ρ_avg * normu_int⁻_tan * (q_tot - q_tot_int)\n    fluxᵀn.ρu -= C_q * normu_int⁻_tan * (q_tot - q_tot_int) .* state_int⁻.ρu\nend\n"
  },
  {
    "path": "src/Atmos/Model/bc_momentum.jl",
    "content": "abstract type MomentumBC end\nabstract type MomentumDragBC end\n\n\"\"\"\n    Impenetrable(drag::MomentumDragBC) :: MomentumBC\n\nDefines an impenetrable wall model for momentum. This implies:\n  - no flow in the direction normal to the boundary, and\n  - flow parallel to the boundary is subject to the `drag` condition.\n\"\"\"\nstruct Impenetrable{D <: MomentumDragBC} <: MomentumBC\n    drag::D\nend\n\n\"\"\"\n    FreeSlip() :: MomentumDragBC\n\nNo surface drag on momentum parallel to the boundary.\n\"\"\"\nstruct FreeSlip <: MomentumDragBC end\n\n\n\nfunction atmos_momentum_boundary_state!(\n    nf::NumericalFluxFirstOrder,\n    bc_momentum::Impenetrable{FreeSlip},\n    atmos,\n    state⁺,\n    args,\n)\n    @unpack state⁻, n = args\n    state⁺.ρu -= 2 * dot(state⁻.ρu, n) .* SVector(n)\nend\nfunction atmos_momentum_boundary_state!(\n    nf::NumericalFluxGradient,\n    bc_momentum::Impenetrable{FreeSlip},\n    atmos,\n    state⁺,\n    args,\n)\n    @unpack state⁻, n = args\n    state⁺.ρu -= dot(state⁻.ρu, n) .* SVector(n)\nend\nfunction atmos_momentum_normal_boundary_flux_second_order!(\n    nf,\n    bc_momentum::Impenetrable{FreeSlip},\n    atmos,\n    _...,\n) end\n\n\n\n\"\"\"\n    NoSlip() :: MomentumDragBC\n\nZero momentum at the boundary.\n\"\"\"\nstruct NoSlip <: MomentumDragBC end\n\nfunction atmos_momentum_boundary_state!(\n    nf::NumericalFluxFirstOrder,\n    bc_momentum::Impenetrable{NoSlip},\n    atmos,\n    state⁺,\n    args,\n)\n    @unpack state⁻ = args\n    state⁺.ρu = -state⁻.ρu\nend\nfunction atmos_momentum_boundary_state!(\n    nf::NumericalFluxGradient,\n    bc_momentum::Impenetrable{NoSlip},\n    atmos,\n    state⁺,\n    args,\n)\n    state⁺.ρu = zero(state⁺.ρu)\nend\nfunction atmos_momentum_normal_boundary_flux_second_order!(\n    nf,\n    bc_momentum::Impenetrable{NoSlip},\n    atmos,\n    _...,\n) end\n\n\n\"\"\"\n    DragLaw(fn) :: MomentumDragBC\n\nDrag law for momentum parallel to the boundary. The drag coefficient is\n`C = fn(state, aux, t, normu_int_tan)`, where `normu_int_tan` is the internal speed\nparallel to the boundary.\n`_int` refers to the first interior node.\n\"\"\"\nstruct DragLaw{FN} <: MomentumDragBC\n    fn::FN\nend\nfunction atmos_momentum_boundary_state!(\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    bc_momentum::Impenetrable{DL},\n    atmos,\n    state⁺,\n    args,\n) where {DL <: DragLaw}\n    atmos_momentum_boundary_state!(\n        nf,\n        Impenetrable(FreeSlip()),\n        atmos,\n        state⁺,\n        args,\n    )\nend\nfunction atmos_momentum_normal_boundary_flux_second_order!(\n    nf,\n    bc_momentum::Impenetrable{DL},\n    atmos,\n    fluxᵀn,\n    args,\n) where {DL <: DragLaw}\n    @unpack state⁻, aux⁻, n⁻, t, aux_int⁻, state_int⁻ = args\n\n    u1⁻ = state_int⁻.ρu / state_int⁻.ρ\n    u_int⁻_tan = u1⁻ - dot(u1⁻, n⁻) .* SVector(n⁻)\n    normu_int⁻_tan = norm(u_int⁻_tan)\n    # NOTE: difference from design docs since normal points outwards\n    C = bc_momentum.drag.fn(state⁻, aux⁻, t, normu_int⁻_tan)\n    τn = C * normu_int⁻_tan * u_int⁻_tan\n    # both sides involve projections of normals, so signs are consistent\n    fluxᵀn.ρu += state⁻.ρ * τn\nend\n"
  },
  {
    "path": "src/Atmos/Model/bc_precipitation.jl",
    "content": "abstract type PrecipitationBC end\n\n\"\"\"\n    OutflowPrecipitation() :: PrecipitationBC\n\nFree flux out of the domain.\n\"\"\"\nstruct OutflowPrecipitation <: PrecipitationBC end\nfunction atmos_precipitation_boundary_state!(\n    nf,\n    bc_precipitation::OutflowPrecipitation,\n    atmos,\n    _...,\n) end\nfunction atmos_precipitation_normal_boundary_flux_second_order!(\n    nf,\n    bc_precipitation::OutflowPrecipitation,\n    atmos,\n    _...,\n) end\n"
  },
  {
    "path": "src/Atmos/Model/bc_tracer.jl",
    "content": "abstract type TracerBC end\n\n\"\"\"\n    ImpermeableTracer() :: TracerBC\n\nNo tracer diffusion across boundary\n\"\"\"\nstruct ImpermeableTracer <: TracerBC end\nfunction atmos_tracer_boundary_state!(\n    nf,\n    bc_tracer::ImpermeableTracer,\n    atmos,\n    _...,\n)\n    nothing\nend\nfunction atmos_tracer_normal_boundary_flux_second_order!(\n    nf,\n    bc_tracer::ImpermeableTracer,\n    atmos,\n    _...,\n)\n    nothing\nend\n"
  },
  {
    "path": "src/Atmos/Model/boundaryconditions.jl",
    "content": "using CLIMAParameters.Planet: cv_d, T_0\n\nexport InitStateBC\n\nexport AtmosBC,\n    Impenetrable,\n    FreeSlip,\n    NoSlip,\n    DragLaw,\n    Insulating,\n    PrescribedTemperature,\n    PrescribedEnergyFlux,\n    Adiabaticθ,\n    BulkFormulaEnergy,\n    Impermeable,\n    OutflowPrecipitation,\n    ImpermeableTracer,\n    PrescribedMoistureFlux,\n    BulkFormulaMoisture,\n    PrescribedTracerFlux,\n    NishizawaEnergyFlux\n\nexport average_density_sfc_int\n\n\"\"\"\n    AtmosBC(momentum = Impenetrable(FreeSlip())\n            energy   = Insulating()\n            moisture = Impermeable()\n            precipitation = OutflowPrecipitation()\n            tracer  = ImpermeableTracer())\n\nThe standard boundary condition for [`AtmosModel`](@ref). The default options imply a \"no flux\" boundary condition.\n\"\"\"\nstruct AtmosBC{M, E, Q, P, TR, TC}\n    momentum::M\n    energy::E\n    moisture::Q\n    precipitation::P\n    tracer::TR\n    turbconv::TC\nend\n\nfunction AtmosBC(\n    physics::AtmosPhysics;\n    momentum = Impenetrable(FreeSlip()),\n    energy = Insulating(),\n    moisture = Impermeable(),\n    precipitation = OutflowPrecipitation(),\n    tracer = ImpermeableTracer(),\n    turbconv = NoTurbConvBC(),\n)\n    args = (momentum, energy, moisture, precipitation, tracer, turbconv)\n    return AtmosBC{typeof.(args)...}(args...)\nend\n\n\nboundary_conditions(atmos::AtmosModel) = atmos.problem.boundaryconditions\n\n\nfunction boundary_state!(\n    nf,\n    bc::AtmosBC,\n    atmos::AtmosModel,\n    state⁺,\n    aux⁺,\n    n,\n    state⁻,\n    aux⁻,\n    t,\n    state_int⁻,\n    aux_int⁻,\n)\n\n    args = (; aux⁺, state⁻, aux⁻, t, n, state_int⁻, aux_int⁻)\n\n    atmos_boundary_state!(nf, bc, atmos, state⁺, args)\n    # update moisture auxiliary variables (perform saturation adjustment, if necessary)\n    # to make thermodynamic quantities consistent with the boundary state\n    atmos_nodal_update_auxiliary_state!(\n        moisture_model(atmos),\n        atmos,\n        state⁺,\n        aux⁺,\n        t,\n    )\nend\n\nfunction boundary_state!(\n    nf::Union{CentralNumericalFluxHigherOrder, CentralNumericalFluxDivergence},\n    bc::AtmosBC,\n    m::AtmosModel,\n    x...,\n)\n    nothing\nend\n\nfunction atmos_boundary_state!(nf, bc::AtmosBC, atmos, state⁺, args)\n    atmos_momentum_boundary_state!(nf, bc.momentum, atmos, state⁺, args)\n    atmos_energy_boundary_state!(nf, bc.energy, atmos, state⁺, args)\n    atmos_moisture_boundary_state!(nf, bc.moisture, atmos, state⁺, args)\n    atmos_precipitation_boundary_state!(\n        nf,\n        bc.precipitation,\n        atmos,\n        state⁺,\n        args,\n    )\n    atmos_tracer_boundary_state!(nf, bc.tracer, atmos, state⁺, args)\n    turbconv_boundary_state!(nf, bc.turbconv, atmos, state⁺, args)\nend\n\n\nfunction normal_boundary_flux_second_order!(\n    nf,\n    bc::AtmosBC,\n    atmos::AtmosModel,\n    fluxᵀn::Vars{S},\n    n⁻,\n    state⁻,\n    diffusive⁻,\n    hyperdiff⁻,\n    aux⁻,\n    state⁺,\n    diffusive⁺,\n    hyperdiff⁺,\n    aux⁺,\n    t,\n    state_int⁻,\n    diffusive_int⁻,\n    aux_int⁻,\n) where {S}\n\n    args = (;\n        n⁻,\n        state⁻,\n        diffusive⁻,\n        hyperdiff⁻,\n        aux⁻,\n        t,\n        state_int⁻,\n        diffusive_int⁻,\n        aux_int⁻,\n    )\n\n    atmos_normal_boundary_flux_second_order!(nf, bc, atmos, fluxᵀn, args)\nend\n\nfunction atmos_normal_boundary_flux_second_order!(\n    nf,\n    bc::AtmosBC,\n    atmos::AtmosModel,\n    args...,\n)\n    atmos_momentum_normal_boundary_flux_second_order!(\n        nf,\n        bc.momentum,\n        atmos,\n        args...,\n    )\n    atmos_energy_normal_boundary_flux_second_order!(\n        nf,\n        bc.energy,\n        atmos,\n        args...,\n    )\n    atmos_moisture_normal_boundary_flux_second_order!(\n        nf,\n        bc.moisture,\n        atmos,\n        args...,\n    )\n    atmos_precipitation_normal_boundary_flux_second_order!(\n        nf,\n        bc.precipitation,\n        atmos,\n        args...,\n    )\n    atmos_tracer_normal_boundary_flux_second_order!(\n        nf,\n        bc.tracer,\n        atmos,\n        args...,\n    )\n    turbconv_normal_boundary_flux_second_order!(nf, bc.turbconv, atmos, args...)\nend\n\n\"\"\"\n    average_density(ρ_sfc, ρ_int)\n\nAverage density between the surface and the interior point, given\n - `ρ_sfc` density at the surface\n - `ρ_int` density at the interior point\n\"\"\"\nfunction average_density(ρ_sfc::FT, ρ_int::FT) where {FT <: Real}\n    return FT(0.5) * (ρ_sfc + ρ_int)\nend\n\ninclude(\"bc_momentum.jl\")\ninclude(\"bc_energy.jl\")\ninclude(\"bc_moisture.jl\")\ninclude(\"bc_precipitation.jl\")\ninclude(\"bc_initstate.jl\")\ninclude(\"bc_tracer.jl\")\n"
  },
  {
    "path": "src/Atmos/Model/courant.jl",
    "content": "using ..Mesh.Grids: Direction, HorizontalDirection, VerticalDirection\nusing ..TurbulenceClosures\n\nadvective_courant(m::AtmosLinearModel, a...) = advective_courant(m.atmos, a...)\n\nnondiffusive_courant(m::AtmosLinearModel, a...) =\n    nondiffusive_courant(m.atmos, a...)\n\ndiffusive_courant(m::AtmosLinearModel, a...) = diffusive_courant(m.atmos, a...)\n\nnorm_u(state::Vars, k̂::AbstractVector, ::VerticalDirection) =\n    abs(dot(state.ρu, k̂)) / state.ρ\nnorm_u(state::Vars, k̂::AbstractVector, ::HorizontalDirection) =\n    norm((state.ρu .- dot(state.ρu, k̂) * k̂) / state.ρ)\nnorm_u(state::Vars, k̂::AbstractVector, ::Direction) = norm(state.ρu / state.ρ)\n\nnorm_ν(ν::Real, k̂::AbstractVector, ::Direction) = ν\nnorm_ν(ν::AbstractVector, k̂::AbstractVector, ::VerticalDirection) = dot(ν, k̂)\nnorm_ν(ν::AbstractVector, k̂::AbstractVector, ::HorizontalDirection) =\n    norm(ν - dot(ν, k̂) * k̂)\nnorm_ν(ν::AbstractVector, k̂::AbstractVector, ::Direction) = norm(ν)\n\nfunction advective_courant(\n    m::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    diffusive::Vars,\n    Δx,\n    Δt,\n    t,\n    direction,\n)\n    k̂ = vertical_unit_vector(m, aux)\n    normu = norm_u(state, k̂, direction)\n    return Δt * normu / Δx\nend\n\nfunction nondiffusive_courant(\n    m::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    diffusive::Vars,\n    Δx,\n    Δt,\n    t,\n    direction,\n)\n    k̂ = vertical_unit_vector(m, aux)\n    normu = norm_u(state, k̂, direction)\n    # TODO: Change this to new_thermo_state\n    # so that Courant computations do not depend\n    # on the aux state.\n    ts = recover_thermo_state(m, state, aux)\n    ss = soundspeed_air(ts)\n    return Δt * (normu + ss) / Δx\nend\n\nfunction diffusive_courant(\n    m::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    diffusive::Vars,\n    Δx,\n    Δt,\n    t,\n    direction,\n)\n    ν, _, _ = turbulence_tensors(m, state, diffusive, aux, t)\n    ν = ν isa Real ? ν : diag(ν)\n    k̂ = vertical_unit_vector(m, aux)\n    normν = norm_ν(ν, k̂, direction)\n    return Δt * normν / (Δx * Δx)\nend\n"
  },
  {
    "path": "src/Atmos/Model/declare_prognostic_vars.jl",
    "content": "##### Prognostic variable\n\nexport Mass, Momentum, Energy, ρθ_liq_ice\nexport AbstractMoistureVariable, TotalMoisture, LiquidMoisture, IceMoisture\nexport AbstractPrecipitationVariable, Rain, Snow\nexport Tracers\n\nstruct Mass <: AbstractPrognosticVariable end\nstruct Momentum <: AbstractMomentumVariable end\n\nstruct Energy <: AbstractEnergyVariable end\nstruct ρθ_liq_ice <: AbstractEnergyVariable end\n\nstruct TotalMoisture <: AbstractMoistureVariable end\nstruct LiquidMoisture <: AbstractMoistureVariable end\nstruct IceMoisture <: AbstractMoistureVariable end\n\nstruct Rain <: AbstractPrecipitationVariable end\nstruct Snow <: AbstractPrecipitationVariable end\n\nstruct Tracers{N} <: AbstractTracersVariable{N} end\n"
  },
  {
    "path": "src/Atmos/Model/energy.jl",
    "content": "export AbstractEnergyModel, TotalEnergyModel, θModel\n#### Energy component in atmosphere model\nabstract type AbstractEnergyModel end\nstruct TotalEnergyModel <: AbstractEnergyModel end\nstruct θModel <: AbstractEnergyModel end\n\nvars_state(::TotalEnergyModel, ::AbstractStateType, FT) = @vars()\nvars_state(::TotalEnergyModel, ::Prognostic, FT) = @vars(ρe::FT)\nvars_state(::TotalEnergyModel, ::Gradient, FT) = @vars(h_tot::FT)\nvars_state(::TotalEnergyModel, ::GradientFlux, FT) =\n    @vars(∇h_tot::SVector{3, FT})\nvars_state(::θModel, ::AbstractStateType, FT) = @vars()\nvars_state(::θModel, ::Prognostic, FT) = @vars(ρθ_liq_ice::FT)\nvars_state(::θModel, ::Gradient, FT) = @vars(θ_liq_ice::FT)\nvars_state(::θModel, ::GradientFlux, FT) = @vars(∇θ_liq_ice::SVector{3, FT})\n\nvars_state_filtered(::θModel, FT) = @vars(θ_liq_ice::FT)\nvars_state_filtered(::TotalEnergyModel, FT) = @vars(e::FT)\n\nfunction compute_gradient_argument!(\n    energy::TotalEnergyModel,\n    atmos::AtmosModel,\n    transform,\n    state,\n    aux,\n    t,\n)\n    ts = recover_thermo_state(atmos, state, aux)\n    e_tot = state.energy.ρe * (1 / state.ρ)\n    transform.energy.h_tot = total_specific_enthalpy(ts, e_tot)\nend\n\nfunction compute_gradient_argument!(\n    energy::θModel,\n    ::AtmosModel,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    transform.energy.θ_liq_ice = state.energy.ρθ_liq_ice / state.ρ\nend\n\ncompute_gradient_flux!(::TotalEnergyModel, _...) = nothing\n\nfunction compute_gradient_flux!(::θModel, diffusive, ∇transform, state, aux, t)\n    diffusive.energy.∇θ_liq_ice = ∇transform.energy.θ_liq_ice\nend\n\nfunction compute_gradient_flux!(\n    ::TotalEnergyModel,\n    diffusive,\n    ∇transform,\n    state,\n    aux,\n    t,\n)\n    diffusive.energy.∇h_tot = ∇transform.energy.h_tot\nend\n"
  },
  {
    "path": "src/Atmos/Model/filters.jl",
    "content": "export AtmosFilterPerturbations\nexport AtmosSpecificFilterPerturbations\n\nstruct AtmosFilterPerturbations{M} <: AbstractFilterTarget\n    atmos::M\nend\n\nvars_state_filtered(target::AtmosFilterPerturbations, FT) =\n    vars_state(target.atmos, Prognostic(), FT)\n\nfunction compute_filter_argument!(\n    target::AtmosFilterPerturbations,\n    filter_state::Vars,\n    state::Vars,\n    aux::Vars,\n)\n    # copy the whole state\n    parent(filter_state) .= parent(state)\n    # remove reference state\n    filter_state.ρ -= aux.ref_state.ρ\n    filter_state.energy.ρe -= aux.ref_state.ρe\n    if !(moisture_model(target.atmos) isa DryModel)\n        filter_state.moisture.ρq_tot -= aux.ref_state.ρq_tot\n    end\n    if (moisture_model(target.atmos) isa NonEquilMoist)\n        filter_state.moisture.ρq_liq -= aux.ref_state.ρq_liq\n        filter_state.moisture.ρq_ice -= aux.ref_state.ρq_ice\n    end\nend\nfunction compute_filter_result!(\n    target::AtmosFilterPerturbations,\n    state::Vars,\n    filter_state::Vars,\n    aux::Vars,\n)\n    # copy the whole filter state\n    parent(state) .= parent(filter_state)\n    # add reference state\n    state.ρ += aux.ref_state.ρ\n    state.energy.ρe += aux.ref_state.ρe\n    if !(moisture_model(target.atmos) isa DryModel)\n        state.moisture.ρq_tot += aux.ref_state.ρq_tot\n    end\n    if (moisture_model(target.atmos) isa NonEquilMoist)\n        filter_state.moisture.ρq_liq += aux.ref_state.ρq_liq\n        filter_state.moisture.ρq_ice += aux.ref_state.ρq_ice\n    end\nend\n\nstruct AtmosSpecificFilterPerturbations{M} <: AbstractFilterTarget\n    atmos::M\nend\n\nvars_state_filtered(target::AtmosSpecificFilterPerturbations, FT) =\n    vars_state_filtered(target.atmos, FT)\n\nfunction compute_filter_argument!(\n    target::AtmosSpecificFilterPerturbations,\n    filter_state::Vars,\n    state::Vars,\n    aux::Vars,\n)\n\n    ρ_inv = 1 / state.ρ\n    ρ_ref_inv = 1 / aux.ref_state.ρ\n\n    # copy the whole state\n    parent(filter_state) .= parent(state) * ρ_inv\n\n    # remove reference state\n    filter_state.energy.e -= aux.ref_state.ρe * ρ_ref_inv\n    if !(moisture_model(target.atmos) isa DryModel)\n        filter_state.moisture.q_tot -= aux.ref_state.ρq_tot * ρ_ref_inv\n    end\n    if (moisture_model(target.atmos) isa NonEquilMoist)\n        filter_state.moisture.q_liq -= aux.ref_state.ρq_liq * ρ_ref_inv\n        filter_state.moisture.q_ice -= aux.ref_state.ρq_ice * ρ_ref_inv\n    end\n\nend\n\nfunction compute_filter_result!(\n    target::AtmosSpecificFilterPerturbations,\n    state::Vars,\n    filter_state::Vars,\n    aux::Vars,\n)\n\n    ρ = state.ρ\n    ρ_ρ_ref_ratio = ρ / aux.ref_state.ρ\n\n    # copy the whole filter state\n    parent(state) .= parent(filter_state) * ρ\n\n    # add reference state\n    state.energy.ρe += aux.ref_state.ρe * ρ_ρ_ref_ratio\n    if !(moisture_model(target.atmos) isa DryModel)\n        state.moisture.ρq_tot += aux.ref_state.ρq_tot * ρ_ρ_ref_ratio\n    end\n    if (moisture_model(target.atmos) isa NonEquilMoist)\n        state.moisture.ρq_liq += aux.ref_state.ρq_liq * ρ_ρ_ref_ratio\n        state.moisture.ρq_ice += aux.ref_state.ρq_ice * ρ_ρ_ref_ratio\n    end\nend\n"
  },
  {
    "path": "src/Atmos/Model/get_prognostic_vars.jl",
    "content": "##### Get prognostic variable list\n\nprognostic_vars(::TotalEnergyModel) = (Energy(),)\nprognostic_vars(::θModel) = (ρθ_liq_ice(),)\n\nprognostic_vars(::DryModel) = ()\nprognostic_vars(::EquilMoist) = (TotalMoisture(),)\nprognostic_vars(::NonEquilMoist) =\n    (TotalMoisture(), LiquidMoisture(), IceMoisture())\n\nprognostic_vars(::NoPrecipitation) = ()\nprognostic_vars(::RainModel) = (Rain(),)\nprognostic_vars(::RainSnowModel) = (Rain(), Snow())\n\nprognostic_vars(::NoTracers) = ()\nprognostic_vars(::NTracers{N}) where {N} = (Tracers{N}(),)\n\nprognostic_vars(atmos::AtmosModel) = prognostic_vars(atmos.physics)\n\nprognostic_vars(m::AtmosPhysics) = (\n    Mass(),\n    Momentum(),\n    prognostic_vars(energy_model(m))...,\n    prognostic_vars(moisture_model(m))...,\n    prognostic_vars(precipitation_model(m))...,\n    prognostic_vars(tracer_model(m))...,\n    prognostic_vars(turbconv_model(m))...,\n)\n\nget_prog_state(state, ::Mass) = (state, :ρ)\nget_prog_state(state, ::Momentum) = (state, :ρu)\nget_prog_state(state, ::Energy) = (state.energy, :ρe)\nget_prog_state(state, ::ρθ_liq_ice) = (state.energy, :ρθ_liq_ice)\nget_prog_state(state, ::TotalMoisture) = (state.moisture, :ρq_tot)\nget_prog_state(state, ::LiquidMoisture) = (state.moisture, :ρq_liq)\nget_prog_state(state, ::IceMoisture) = (state.moisture, :ρq_ice)\nget_prog_state(state, ::Rain) = (state.precipitation, :ρq_rai)\nget_prog_state(state, ::Snow) = (state.precipitation, :ρq_sno)\nget_prog_state(state, ::Tracers{N}) where {N} = (state.tracers, :ρχ)\n\nget_specific_state(state, ::Mass) = (state, :ρ)\nget_specific_state(state, ::Momentum) = (state, :u)\nget_specific_state(state, ::Energy) = (state.energy, :e)\nget_specific_state(state, ::ρθ_liq_ice) = (state.energy, :θ_liq_ice)\nget_specific_state(state, ::TotalMoisture) = (state.moisture, :q_tot)\nget_specific_state(state, ::LiquidMoisture) = (state.moisture, :q_liq)\nget_specific_state(state, ::IceMoisture) = (state.moisture, :q_ice)\nget_specific_state(state, ::Rain) = (state.precipitation, :q_rai)\nget_specific_state(state, ::Snow) = (state.precipitation, :q_sno)\nget_specific_state(state, ::Tracers{N}) where {N} = (state.tracers, :χ)\n\nprognostic_vars(m::AtmosLinearModel) = (Mass(), Momentum(), Energy())\n"
  },
  {
    "path": "src/Atmos/Model/linear.jl",
    "content": "using CLIMAParameters.Planet: R_d, cv_d, T_0, e_int_v0, e_int_i0\n\n\"\"\"\n    linearized_air_pressure(ρ, ρe_tot, ρe_pot, ρq_tot=0, ρq_liq=0, ρq_ice=0)\n\nThe air pressure, linearized around a dry rest state, from the equation of state\n(ideal gas law) where:\n\n - `ρ` (moist-)air density\n - `ρe_tot` total energy density\n - `ρe_pot` potential energy density\nand, optionally,\n - `ρq_tot` total water density\n - `ρq_liq` liquid water density\n - `ρq_ice` ice density\n\"\"\"\nfunction linearized_air_pressure(\n    param_set::AbstractParameterSet,\n    ρ::FT,\n    ρe_tot::FT,\n    ρe_pot::FT,\n    ρq_tot::FT = FT(0),\n    ρq_liq::FT = FT(0),\n    ρq_ice::FT = FT(0),\n) where {FT <: Real, PS}\n    _R_d::FT = R_d(param_set)\n    _cv_d::FT = cv_d(param_set)\n    _T_0::FT = T_0(param_set)\n    _e_int_v0::FT = e_int_v0(param_set)\n    _e_int_i0::FT = e_int_i0(param_set)\n    return ρ * _R_d * _T_0 +\n           _R_d / _cv_d * (\n        ρe_tot - ρe_pot - (ρq_tot - ρq_liq) * _e_int_v0 +\n        ρq_ice * (_e_int_i0 + _e_int_v0)\n    )\nend\n\n@inline linearized_pressure(atmos, state::Vars, aux::Vars) =\n    linearized_pressure(\n        moisture_model(atmos),\n        parameter_set(atmos),\n        atmos.orientation,\n        state,\n        aux,\n    )\n\n@inline function linearized_pressure(\n    ::DryModel,\n    param_set::AbstractParameterSet,\n    orientation::Orientation,\n    state::Vars,\n    aux::Vars,\n)\n    ρe_pot = state.ρ * gravitational_potential(orientation, aux)\n    return linearized_air_pressure(param_set, state.ρ, state.energy.ρe, ρe_pot)\nend\n@inline function linearized_pressure(\n    ::EquilMoist,\n    param_set::AbstractParameterSet,\n    orientation::Orientation,\n    state::Vars,\n    aux::Vars,\n)\n    ρe_pot = state.ρ * gravitational_potential(orientation, aux)\n    linearized_air_pressure(\n        param_set,\n        state.ρ,\n        state.energy.ρe,\n        ρe_pot,\n        state.moisture.ρq_tot,\n    )\nend\n@inline function linearized_pressure(\n    ::NonEquilMoist,\n    param_set::AbstractParameterSet,\n    orientation::Orientation,\n    state::Vars,\n    aux::Vars,\n)\n    ρe_pot = state.ρ * gravitational_potential(orientation, aux)\n    linearized_air_pressure(\n        param_set,\n        state.ρ,\n        state.energy.ρe,\n        ρe_pot,\n        state.moisture.ρq_tot,\n        state.moisture.ρq_liq,\n        state.moisture.ρq_ice,\n    )\nend\n\nabstract type AtmosLinearModel <: BalanceLaw end\n\n\"\"\"\n    vars_state(m::AtmosLinearModel, ::Prognostic, FT)\n\nConserved state variables (prognostic variables).\n\n!!! warning\n\n    `AtmosLinearModel` state ordering must be a contiguous subset of the initial\n    state of `AtmosModel` since a shared state is used.\n\"\"\"\nfunction vars_state(lm::AtmosLinearModel, st::Prognostic, FT)\n    @vars begin\n        ρ::FT\n        ρu::SVector{3, FT}\n        energy::vars_state(energy_model(lm.atmos), st, FT)\n        turbulence::vars_state(turbulence_model(lm.atmos), st, FT)\n        hyperdiffusion::vars_state(hyperdiffusion_model(lm.atmos), st, FT)\n        moisture::vars_state(moisture_model(lm.atmos), st, FT)\n    end\nend\nvars_state(lm::AtmosLinearModel, st::Auxiliary, FT) =\n    vars_state(lm.atmos, st, FT)\nvars_state(lm::AtmosLinearModel, ::Primitive, FT) =\n    vars_state(lm, Prognostic(), FT)\n\nfunction update_auxiliary_state!(\n    dg::DGModel,\n    lm::AtmosLinearModel,\n    Q::MPIStateArray,\n    t::Real,\n    elems::UnitRange,\n)\n    return false\nend\nfunction flux_second_order!(\n    lm::AtmosLinearModel,\n    flux::Grad,\n    state::Vars,\n    diffusive::Vars,\n    hyperdiffusive::Vars,\n    aux::Vars,\n    t::Real,\n)\n    nothing\nend\nintegral_load_auxiliary_state!(\n    lm::AtmosLinearModel,\n    integ::Vars,\n    state::Vars,\n    aux::Vars,\n) = nothing\nintegral_set_auxiliary_state!(lm::AtmosLinearModel, aux::Vars, integ::Vars) =\n    nothing\nreverse_integral_load_auxiliary_state!(\n    lm::AtmosLinearModel,\n    integ::Vars,\n    state::Vars,\n    aux::Vars,\n) = nothing\nreverse_integral_set_auxiliary_state!(\n    lm::AtmosLinearModel,\n    aux::Vars,\n    integ::Vars,\n) = nothing\nfunction wavespeed(\n    lm::AtmosLinearModel,\n    nM,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n)\n    ref = aux.ref_state\n    return soundspeed_air(parameter_set(lm.atmos), ref.T)\nend\n\nboundary_conditions(atmoslm::AtmosLinearModel) =\n    (AtmosBC(atmoslm.atmos.physics), AtmosBC(atmoslm.atmos.physics))\n\nfunction boundary_state!(\n    nf::NumericalFluxFirstOrder,\n    bc,\n    atmoslm::AtmosLinearModel,\n    state⁺,\n    aux⁺,\n    n,\n    state⁻,\n    aux⁻,\n    t,\n    state_int⁻,\n    aux_int⁻,\n)\n\n    args = (; aux⁺, state⁻, aux⁻, t, n, state_int⁻, aux_int⁻)\n\n    atmos_boundary_state!(nf, bc, atmoslm, state⁺, args)\nend\nfunction boundary_state!(\n    nf::NumericalFluxSecondOrder,\n    bc,\n    atmoslm::AtmosLinearModel,\n    _...,\n)\n    nothing\nend\ninit_state_auxiliary!(\n    lm::AtmosLinearModel,\n    aux::MPIStateArray,\n    grid,\n    direction,\n) = nothing\ninit_state_prognostic!(\n    lm::AtmosLinearModel,\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t,\n) = nothing\n\n\nstruct AtmosAcousticLinearModel{M} <: AtmosLinearModel\n    atmos::M\n    function AtmosAcousticLinearModel(atmos::M) where {M}\n        if reference_state(atmos) === NoReferenceState()\n            error(\"AtmosAcousticLinearModel needs a model with a reference state\")\n        end\n        new{M}(atmos)\n    end\nend\n\nfunction flux_first_order!(\n    lm::AtmosLinearModel,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n)\n    tend = Flux{FirstOrder}()\n    _args = (; state, aux, t, direction)\n\n    # For some reason, we cannot call precompute, because\n    # sometimes `state.ρ` is somehow `0`, which results in\n    # `e_int = Inf` -> failed saturation adjustment.\n    # TODO: look into this\n    args = _args\n    # args = merge(_args, (precomputed = precompute(lm.atmos, _args, tend),))\n    flux.ρ = Σfluxes(Mass(), eq_tends(Mass(), lm, tend), lm, args)\n    flux.ρu = Σfluxes(Momentum(), eq_tends(Momentum(), lm, tend), lm, args)\n    flux.energy.ρe = Σfluxes(Energy(), eq_tends(Energy(), lm, tend), lm, args)\n    nothing\nend\n\nstruct AtmosAcousticGravityLinearModel{M} <: AtmosLinearModel\n    atmos::M\n    function AtmosAcousticGravityLinearModel(atmos::M) where {M}\n        if reference_state(atmos) === NoReferenceState()\n            error(\"AtmosAcousticGravityLinearModel needs a model with a reference state\")\n        end\n        new{M}(atmos)\n    end\nend\n\nfunction source!(\n    lm::AtmosLinearModel,\n    source::Vars,\n    state::Vars,\n    diffusive::Vars,\n    aux::Vars,\n    t::Real,\n    ::NTuple{1, Dir},\n) where {Dir <: Direction}\n\n    tend = Source()\n    _args = (; state, aux, t, direction = Dir, diffusive)\n\n    # For some reason, we cannot call precompute, because\n    # sometimes `state.ρ` is somehow `0`, which results in\n    # `e_int = Inf` -> failed saturation adjustment.\n    # TODO: look into this\n    args = _args\n    # args = merge(_args, (precomputed = precompute(lm.atmos, _args, tend),))\n\n    # Sources for the linear atmos model only appear in the momentum equation\n    source.ρu = Σsources(Momentum(), eq_tends(Momentum(), lm, tend), lm, args)\n    nothing\nend\n\nfunction numerical_flux_first_order!(\n    numerical_flux::RoeNumericalFlux,\n    balance_law::AtmosLinearModel,\n    fluxᵀn::Vars{S},\n    normal_vector::SVector,\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n    direction,\n) where {S, A}\n    @assert moisture_model(balance_law.atmos) isa DryModel\n\n    numerical_flux_first_order!(\n        CentralNumericalFluxFirstOrder(),\n        balance_law,\n        fluxᵀn,\n        normal_vector,\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n        direction,\n    )\n\n    atmos = balance_law.atmos\n    param_set = parameter_set(atmos)\n\n    ρu⁻ = state_prognostic⁻.ρu\n\n    ref_ρ⁻ = state_auxiliary⁻.ref_state.ρ\n    ref_ρe⁻ = state_auxiliary⁻.ref_state.ρe\n    ref_T⁻ = state_auxiliary⁻.ref_state.T\n    ref_p⁻ = state_auxiliary⁻.ref_state.p\n    ref_h⁻ = (ref_ρe⁻ + ref_p⁻) / ref_ρ⁻\n    ref_c⁻ = soundspeed_air(param_set, ref_T⁻)\n\n    pL⁻ = linearized_pressure(atmos, state_prognostic⁻, state_auxiliary⁻)\n\n    ρu⁺ = state_prognostic⁺.ρu\n\n    ref_ρ⁺ = state_auxiliary⁺.ref_state.ρ\n    ref_ρe⁺ = state_auxiliary⁺.ref_state.ρe\n    ref_T⁺ = state_auxiliary⁺.ref_state.T\n    ref_p⁺ = state_auxiliary⁺.ref_state.p\n    ref_h⁺ = (ref_ρe⁺ + ref_p⁺) / ref_ρ⁺\n    ref_c⁺ = soundspeed_air(param_set, ref_T⁺)\n\n    pL⁺ = linearized_pressure(atmos, state_prognostic⁺, state_auxiliary⁺)\n\n    # not sure if arithmetic averages are a good idea here\n    h̃ = (ref_h⁻ + ref_h⁺) / 2\n    c̃ = (ref_c⁻ + ref_c⁺) / 2\n\n    ΔpL = pL⁺ - pL⁻\n    Δρuᵀn = (ρu⁺ - ρu⁻)' * normal_vector\n\n    fluxᵀn.ρ -= ΔpL / 2c̃\n    fluxᵀn.ρu -= c̃ * Δρuᵀn * normal_vector / 2\n    fluxᵀn.energy.ρe -= h̃ * ΔpL / 2c̃\nend\n\nfunction numerical_flux_first_order!(\n    ::HLLCNumericalFlux,\n    balance_law::AtmosLinearModel,\n    fluxᵀn::Vars{S},\n    normal_vector::SVector,\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n    direction,\n) where {S, A}\n\n    # There is no intermediate speed for the AtmosLinearModel.\n    # As a result, HLLC simplifies to Rusanov.\n    numerical_flux_first_order!(\n        RusanovNumericalFlux(),\n        balance_law,\n        fluxᵀn,\n        normal_vector,\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n        direction,\n    )\nend\nfunction numerical_flux_first_order!(\n    numerical_flux::RoeNumericalFluxMoist,\n    balance_law::AtmosLinearModel,\n    fluxᵀn::Vars{S},\n    normal_vector::SVector,\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n    direction,\n) where {S, A}\n\n    numerical_flux_first_order!(\n        CentralNumericalFluxFirstOrder(),\n        balance_law,\n        fluxᵀn,\n        normal_vector,\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n        direction,\n    )\n\n    atmos = balance_law.atmos\n    param_set = parameter_set(atmos)\n    FT = eltype(aux)\n    ρu⁻ = state_prognostic⁻.ρu\n\n    ref_ρ⁻ = state_auxiliary⁻.ref_state.ρ\n    ref_ρe⁻ = state_auxiliary⁻.ref_state.ρe\n    ref_T⁻ = state_auxiliary⁻.ref_state.T\n    ref_q⁻ = state_auxiliary⁻.ref_state.ρq_tot / ref_ρ⁻\n    ref_qliq⁻ = state_auxiliary⁻.ref_state.ρq_liq / ref_ρ⁻\n    ref_qice⁻ = state_auxiliary⁻.ref_state.ρq_ice / ref_ρ⁻\n    ref_p⁻ = state_auxiliary⁻.ref_state.p\n    ref_q_pt⁻ = PhasePartition(ref_q⁻, ref_qliq⁻, ref_qice⁻)\n    _R_m⁻ = gas_constant_air(param_set, ref_q_pt⁻)\n    ref_h⁻ = total_specific_enthalpy(ref_ρe⁻, _R_m⁻, ref_T⁻)\n\n    ref_c⁻ = soundspeed_air(param_set, ref_T⁻, ref_q_pt⁻)\n\n    pL⁻ = linearized_pressure(atmos, state_prognostic⁻, state_auxiliary⁻)\n\n    ρu⁺ = state_prognostic⁺.ρu\n\n    ref_ρ⁺ = state_auxiliary⁺.ref_state.ρ\n    ref_ρe⁺ = state_auxiliary⁺.ref_state.ρe\n    ref_T⁺ = state_auxiliary⁺.ref_state.T\n    ref_q⁺ = state_auxiliary⁺.ref_state.ρq_tot / ref_ρ⁺\n    ref_qliq⁺ = state_auxiliary⁺.ref_state.ρq_liq / ref_ρ⁺\n    ref_qice⁺ = state_auxiliary⁺.ref_state.ρq_ice / ref_ρ⁺\n    ref_p⁺ = state_auxiliary⁺.ref_state.p\n    ref_q_pt⁺ = PhasePartition(ref_q⁺, ref_qliq⁺, ref_qice⁺)\n    _R_m⁺ = gas_constant_air(param_set, ref_q_pt⁺)\n    ref_h⁺ = total_specific_enthalpy(ref_ρe⁺, _R_m⁺, ref_T⁺)\n    ref_c⁺ = soundspeed_air(param_set, ref_T⁺, ref_q_pt⁺)\n    pL⁺ = linearized_pressure(atmos, state_prognostic⁺, state_auxiliary⁺)\n\n    # not sure if arithmetic averages are a good idea here\n    ref_h̃ = (ref_h⁻ + ref_h⁺) / 2\n    ref_c̃ = (ref_c⁻ + ref_c⁺) / 2\n    ref_qt = (ref_q⁺ + ref_q⁻) / 2\n\n    ΔpL = pL⁺ - pL⁻\n    Δρuᵀn = (ρu⁺ - ρu⁻)' * normal_vector\n\n    _T_0::FT = T_0(param_set)\n    _e_int_v0::FT = e_int_v0(param_set)\n    _cv_d::FT = cv_d(param_set)\n    Φ = gravitational_potential(balance_law.atmos, state_auxiliary⁻)\n    # guaranteed to be random\n    ω = FT(π) / 3\n    δ = FT(π) / 5\n    random_unit_vector = SVector(sin(ω) * cos(δ), cos(ω) * cos(δ), sin(δ))\n    # tangent space basis\n    τ1 = random_unit_vector × normal_vector\n    τ2 = τ1 × normal_vector\n\n\n    ũc̃⁻ = ref_c̃ * normal_vector\n    ũc̃⁺ = -ref_c̃ * normal_vector\n    Λ = SDiagonal(\n        abs(0 - ref_c̃),\n        abs(0),\n        abs(0),\n        abs(0),\n        abs(0 + ref_c̃),\n        abs(0),\n    )\n    M = hcat(\n        SVector(1, ũc̃⁺[1], ũc̃⁺[2], ũc̃⁺[3], ref_h̃, ref_qt),\n        SVector(0, τ1[1], τ1[2], τ1[3], 0, 0),\n        SVector(0, τ2[1], τ2[2], τ2[3], 0, 0),\n        SVector(1, 0, 0, 0, Φ - _T_0 * _cv_d, 0),\n        SVector(1, ũc̃⁻[1], ũc̃⁻[2], ũc̃⁻[3], ref_h̃, ref_qt),\n        SVector(0, 0, 0, 0, _e_int_v0, 1),\n    )\n    Δρ = state_prognostic⁺.ρ - state_prognostic⁻.ρ\n    Δρu = ρu⁺ - ρu⁻\n    Δρe = state_prognostic⁺.energy.ρe - state_prognostic⁻.energy.ρe\n    Δρq_tot =\n        state_prognostic⁺.moisture.ρq_tot - state_prognostic⁻.moisture.ρq_tot\n    Δstate = SVector(Δρ, Δρu[1], Δρu[2], Δρu[3], Δρe, Δρq_tot)\n\n    parent(fluxᵀn) .-= M * Λ * (M \\ Δstate) / 2\nend\n\nfunction numerical_flux_first_order!(\n    ::LMARSNumericalFlux,\n    balance_law::AtmosLinearModel,\n    fluxᵀn::Vars{S},\n    normal_vector::SVector,\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n    direction,\n) where {S, A}\n\n    numerical_flux_first_order!(\n        RusanovNumericalFlux(),\n        balance_law,\n        fluxᵀn,\n        normal_vector,\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n        direction,\n    )\nend\n"
  },
  {
    "path": "src/Atmos/Model/linear_atmos_tendencies.jl",
    "content": "\n#####\n##### First order fluxes\n#####\n\n# Mass\neq_tends(::Mass, ::AtmosLinearModel, ::Flux{FirstOrder}) = (Advect(),)\n\n# Momentum\neq_tends(::Momentum, ::AtmosLinearModel, ::Flux{FirstOrder}) =\n    (LinearPressureGradient(),)\n\n# Energy\neq_tends(::Energy, m::AtmosLinearModel, tt::Flux{FirstOrder}) =\n    (LinearEnergyFlux(),)\n\n# AbstractMoistureVariable\n# TODO: Is this right?\neq_tends(::AbstractMoistureVariable, ::AtmosLinearModel, ::Flux{FirstOrder}) =\n    ()\n\n# Tracers\neq_tends(::Tracers{N}, ::AtmosLinearModel, ::Flux{FirstOrder}) where {N} = ()\n\n#####\n##### Second order fluxes\n#####\n\neq_tends(pv::PV, ::AtmosLinearModel, ::Flux{SecondOrder}) where {PV} = ()\n\n#####\n##### Sources\n#####\neq_tends(pv::PV, ::AtmosLinearModel, ::Source) where {PV} = ()\n\neq_tends(pv::Momentum, ::AtmosAcousticGravityLinearModel, ::Source) =\n    (Gravity(),)\n"
  },
  {
    "path": "src/Atmos/Model/linear_tendencies.jl",
    "content": "##### Mass tendencies\n\n#####\n##### First order fluxes\n#####\n\nfunction flux(::Mass, ::Advect, lm::AtmosLinearModel, args)\n    @unpack state = args\n    return state.ρu\nend\n\n\n##### Momentum tendencies\n\n#####\n##### First order fluxes\n#####\n\nstruct LinearPressureGradient <: TendencyDef{Flux{FirstOrder}} end\n\nfunction flux(::Momentum, ::LinearPressureGradient, lm::AtmosLinearModel, args)\n    @unpack state, aux = args\n    s = state.ρu * state.ρu'\n    pad = SArray{Tuple{size(s)...}}(ntuple(i -> 0, length(s)))\n    pL = linearized_pressure(lm.atmos, state, aux)\n    return pad + pL * I\nend\n\n#####\n##### Sources (Momentum)\n#####\nfunction source(\n    ::Momentum,\n    s::Gravity,\n    lm::AtmosAcousticGravityLinearModel,\n    args,\n)\n    @unpack direction, state, aux = args\n    if direction === VerticalDirection || direction === EveryDirection\n        ∇Φ = ∇gravitational_potential(lm.atmos.orientation, aux)\n        return -state.ρ * ∇Φ\n    end\n    FT = eltype(state)\n    return SVector{3, FT}(0, 0, 0)\nend\n\n##### Energy tendencies\n\n#####\n##### First order fluxes\n#####\n\nstruct LinearEnergyFlux <: TendencyDef{Flux{FirstOrder}} end\n\nfunction flux(::Energy, ::LinearEnergyFlux, lm::AtmosAcousticLinearModel, args)\n    @unpack state, aux = args\n    ref = aux.ref_state\n    e_pot = gravitational_potential(lm.atmos.orientation, aux)\n    return ((ref.ρe + ref.p) / ref.ρ - e_pot) * state.ρu\nend\n\nfunction flux(\n    ::Energy,\n    ::LinearEnergyFlux,\n    lm::AtmosAcousticGravityLinearModel,\n    args,\n)\n    @unpack state, aux = args\n    ref = aux.ref_state\n    return ((ref.ρe + ref.p) / ref.ρ) * state.ρu\nend\n"
  },
  {
    "path": "src/Atmos/Model/lsforcing.jl",
    "content": "export LSForcingModel, NoLSForcing, HadGEMVertical\n\nabstract type LSForcingModel end\n\nvars_state(::LSForcingModel, ::AbstractStateType, FT) = @vars()\n\nfunction compute_gradient_argument!(\n    lsforcing::LSForcingModel,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    nothing\nend\n\nfunction compute_gradient_flux!(\n    lsforcing::LSForcingModel,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    nothing\nend\n\n\"\"\"\n    NoLSForcing <: LSForcingModel\nNo large-scale forcing\n\"\"\"\nstruct NoLSForcing <: LSForcingModel end\n\n\"\"\"\n    Container for GCM variables from HadGEM2-A forcing,\nused in the AMIP experiments\n\"\"\"\nstruct HadGEMVertical <: LSForcingModel end\n\nvars_state(m::HadGEMVertical, ::Auxiliary, FT) = @vars(\n    ta::FT,\n    hus::FT,\n    ua::FT,\n    va::FT,\n    tntΣhava::FT,\n    Σtemp_tendency::FT,\n    Σqt_tendency::FT,\n    w_s::FT,\n)\n\nvars_state(::HadGEMVertical, ::Gradient, FT) = @vars(ta::FT, hus::FT)\nvars_state(::HadGEMVertical, ::GradientFlux, FT) = @vars(∇ᵥta::FT, ∇ᵥhus::FT)\n\nfunction compute_gradient_argument!(\n    lsforcing::HadGEMVertical,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    transform.lsforcing.ta = aux.lsforcing.ta\n    transform.lsforcing.hus = aux.lsforcing.hus\nend\n\nfunction compute_gradient_flux!(\n    lsforcing::HadGEMVertical,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    diffusive.lsforcing.∇ᵥta = ∇transform.lsforcing.ta[3]\n    diffusive.lsforcing.∇ᵥhus = ∇transform.lsforcing.hus[3]\nend\n"
  },
  {
    "path": "src/Atmos/Model/moisture.jl",
    "content": "export AbstractMoistureModel, DryModel, EquilMoist, NonEquilMoist\n\n#### Moisture component in atmosphere model\nabstract type AbstractMoistureModel end\n\nvars_state(::AbstractMoistureModel, ::AbstractStateType, FT) = @vars()\n\nfunction atmos_nodal_update_auxiliary_state!(\n    ::AbstractMoistureModel,\n    m::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) end\nfunction compute_gradient_flux!(\n    ::AbstractMoistureModel,\n    diffusive,\n    ∇transform,\n    state,\n    aux,\n    t,\n) end\n\nfunction compute_gradient_argument!(\n    ::AbstractMoistureModel,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) end\n\ninternal_energy(atmos::AtmosModel, state::Vars, aux::Vars) =\n    internal_energy(atmos, atmos.orientation, state, aux)\n\n@inline function internal_energy(\n    atmos::AtmosModel,\n    orientation::Orientation,\n    state::Vars,\n    aux::Vars,\n)\n    Thermodynamics.internal_energy(\n        density(atmos, state, aux),\n        state.energy.ρe,\n        state.ρu,\n        gravitational_potential(orientation, aux),\n    )\nend\n\n\"\"\"\n    DryModel\n\nAssumes the moisture components is in the dry limit.\n\"\"\"\nstruct DryModel <: AbstractMoistureModel end\n\nvars_state_filtered(::DryModel, FT) = @vars()\nvars_state(::DryModel, ::Auxiliary, FT) = @vars(θ_v::FT, air_T::FT)\n@inline function atmos_nodal_update_auxiliary_state!(\n    moist::DryModel,\n    atmos::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ts = new_thermo_state(atmos, state, aux)\n    aux.moisture.θ_v = virtual_pottemp(ts)\n    aux.moisture.air_T = air_temperature(ts)\n    nothing\nend\n\n\"\"\"\n    EquilMoist\n\nAssumes the moisture components are computed via thermodynamic equilibrium.\n\"\"\"\nBase.@kwdef struct EquilMoist{FT, IT} <: AbstractMoistureModel\n    maxiter::IT = nothing\n    tolerance::FT = nothing\nend\n\nvars_state_filtered(::EquilMoist, FT) = @vars(q_tot::FT)\nvars_state(::EquilMoist, ::Prognostic, FT) = @vars(ρq_tot::FT)\nvars_state(::EquilMoist, ::Primitive, FT) = @vars(q_tot::FT)\nvars_state(::EquilMoist, ::Gradient, FT) = @vars(q_tot::FT)\nvars_state(::EquilMoist, ::GradientFlux, FT) = @vars(∇q_tot::SVector{3, FT})\nvars_state(::EquilMoist, ::Auxiliary, FT) =\n    @vars(temperature::FT, θ_v::FT, q_liq::FT, q_ice::FT)\n\n@inline function atmos_nodal_update_auxiliary_state!(\n    moist::EquilMoist,\n    atmos::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ts = new_thermo_state(atmos, state, aux)\n    aux.moisture.temperature = air_temperature(ts)\n    aux.moisture.θ_v = virtual_pottemp(ts)\n    aux.moisture.q_liq = PhasePartition(ts).liq\n    aux.moisture.q_ice = PhasePartition(ts).ice\n    nothing\nend\n\nfunction compute_gradient_argument!(\n    moist::EquilMoist,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ρinv = 1 / state.ρ\n    transform.moisture.q_tot = state.moisture.ρq_tot * ρinv\nend\n\nfunction compute_gradient_flux!(\n    moist::EquilMoist,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    # diffusive flux of q_tot\n    diffusive.moisture.∇q_tot = ∇transform.moisture.q_tot\nend\n\n\"\"\"\n    NonEquilMoist\n\nDoes not assume that the moisture components are in equilibrium.\n\"\"\"\nstruct NonEquilMoist <: AbstractMoistureModel end\n\nvars_state(::NonEquilMoist, ::Prognostic, FT) =\n    @vars(ρq_tot::FT, ρq_liq::FT, ρq_ice::FT)\nvars_state(::NonEquilMoist, ::Primitive, FT) =\n    @vars(q_tot::FT, q_liq::FT, q_ice::FT)\nvars_state(::NonEquilMoist, ::Gradient, FT) =\n    @vars(q_tot::FT, q_liq::FT, q_ice::FT)\nvars_state(::NonEquilMoist, ::GradientFlux, FT) = @vars(\n    ∇q_tot::SVector{3, FT},\n    ∇q_liq::SVector{3, FT},\n    ∇q_ice::SVector{3, FT}\n)\nvars_state(::NonEquilMoist, ::Auxiliary, FT) = @vars(temperature::FT, θ_v::FT)\n\nvars_state_filtered(::NonEquilMoist, FT) =\n    @vars(q_tot::FT, q_liq::FT, q_ice::FT)\n\n@inline function atmos_nodal_update_auxiliary_state!(\n    moist::NonEquilMoist,\n    atmos::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ts = new_thermo_state(atmos, state, aux)\n    aux.moisture.temperature = air_temperature(ts)\n    aux.moisture.θ_v = virtual_pottemp(ts)\n    nothing\nend\n\nfunction compute_gradient_argument!(\n    moist::NonEquilMoist,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ρinv = 1 / state.ρ\n    transform.moisture.q_tot = state.moisture.ρq_tot * ρinv\n    transform.moisture.q_liq = state.moisture.ρq_liq * ρinv\n    transform.moisture.q_ice = state.moisture.ρq_ice * ρinv\nend\n\nfunction compute_gradient_flux!(\n    moist::NonEquilMoist,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    # diffusive fluxes of moisture variables\n    diffusive.moisture.∇q_tot = ∇transform.moisture.q_tot\n    diffusive.moisture.∇q_liq = ∇transform.moisture.q_liq\n    diffusive.moisture.∇q_ice = ∇transform.moisture.q_ice\nend\n"
  },
  {
    "path": "src/Atmos/Model/multiphysics_types.jl",
    "content": "##### Multi-physics types\n\nusing CLIMAParameters.Planet: T_freeze, cv_l\n\nusing CloudMicrophysics.Microphysics_0M\nusing CloudMicrophysics.Microphysics_1M\nimport CloudMicrophysics\n\nconst CM1M = CloudMicrophysics.Microphysics_1M\n\nexport RainSnow_1M\nexport WarmRain_1M\nexport Diffusion\nexport Subsidence\nexport RemovePrecipitation\n\nstruct Subsidence{FT} <: TendencyDef{Source}\n    D::FT\nend\n\nprognostic_vars(::Subsidence) = (Mass(), Energy(), TotalMoisture())\n\n# Subsidence includes tendencies in Mass, Energy and TotalMoisture equations:\n\nsubsidence_velocity(subsidence::Subsidence{FT}, z::FT) where {FT} =\n    -subsidence.D * z\n\nstruct PressureGradient <: TendencyDef{Flux{FirstOrder}} end\nstruct Pressure <: TendencyDef{Flux{FirstOrder}} end\nstruct Advect <: TendencyDef{Flux{FirstOrder}} end\nstruct Diffusion <: TendencyDef{Flux{SecondOrder}} end\nstruct MoistureDiffusion <: TendencyDef{Flux{SecondOrder}} end\n\n\"\"\"\n    RemovePrecipitation <: TendencyDef{Source}\n\nA sink to `q_tot` when cloud condensate is exceeding a threshold.\nThe threshold is defined either in terms of condensate or supersaturation.\nThe removal rate is implemented as a relaxation term\nin the CloudMicrophysics.jl Microphysics_0M module.\nThe default thresholds and timescale are defined in CLIMAParameters.jl.\n\"\"\"\nstruct RemovePrecipitation <: TendencyDef{Source}\n    \" Set to true if using qc based threshold\"\n    use_qc_thr::Bool\nend\n\nprognostic_vars(::RemovePrecipitation) = (Mass(), Energy(), TotalMoisture())\n\n\"\"\"\n    PrecipitationFlux <: TendencyDef{Flux{FirstOrder}}\n\nComputes the precipitation flux as a sum of air velocity and terminal velocity\nmultiplied by the advected variable.\n\"\"\"\nstruct PrecipitationFlux <: TendencyDef{Flux{FirstOrder}} end\n\nfunction remove_precipitation_sources(s::RemovePrecipitation, atmos, args)\n    @unpack state, aux = args\n    @unpack ts = args.precomputed\n\n    FT = eltype(state)\n\n    q = PhasePartition(ts)\n    λ::FT = liquid_fraction(ts)\n    I_l::FT = internal_energy_liquid(ts)\n    I_i::FT = internal_energy_ice(ts)\n    Φ::FT = gravitational_potential(atmos.orientation, aux)\n\n    S_qt::FT = 0\n    param_set = parameter_set(atmos)\n    if s.use_qc_thr\n        S_qt = remove_precipitation(param_set, q)\n    else\n        q_vap_sat = q_vap_saturation(ts)\n        S_qt = remove_precipitation(param_set, q, q_vap_sat)\n    end\n\n    S_e::FT = (λ * I_l + (1 - λ) * I_i + Φ) * S_qt\n\n    return (; S_ρ_qt = state.ρ * S_qt, S_ρ_e = state.ρ * S_e)\nend\n\n\"\"\"\n    WarmRain_1M <: TendencyDef{Source}\n\nA collection of source/sink terms related to 1-moment warm rain microphysics.\nThe microphysics process rates are implemented\nin the CloudMicrophysics.jl Microphysics_1M module.\n\"\"\"\nstruct WarmRain_1M <: TendencyDef{Source} end\n\nprognostic_vars(::WarmRain_1M) =\n    (Mass(), Energy(), TotalMoisture(), LiquidMoisture(), Rain())\n\nfunction warm_rain_sources(atmos, args, ts)\n    @unpack state, aux = args\n\n    FT = eltype(state)\n\n    q = PhasePartition(ts)\n    T::FT = air_temperature(ts)\n    I_l::FT = internal_energy_liquid(ts)\n    q_rai::FT = state.precipitation.ρq_rai / state.ρ\n    Φ::FT = gravitational_potential(atmos.orientation, aux)\n    param_set = parameter_set(atmos)\n\n    # autoconversion\n    src_q_rai_acnv = conv_q_liq_to_q_rai(param_set, q.liq)\n    # accretion\n    src_q_rai_accr = accretion(\n        param_set,\n        CM1M.LiquidType(),\n        CM1M.RainType(),\n        q.liq,\n        q_rai,\n        state.ρ,\n    )\n    # rain evaporation\n    src_q_rai_evap = evaporation_sublimation(\n        param_set,\n        CM1M.RainType(),\n        q,\n        q_rai,\n        state.ρ,\n        T,\n    )\n\n    S_qr::FT = src_q_rai_acnv + src_q_rai_accr + src_q_rai_evap\n    S_ql::FT = -src_q_rai_acnv - src_q_rai_accr\n    S_qt::FT = -S_qr\n    S_e::FT = S_qt * (I_l + Φ)\n\n    return (;\n        S_ρ_qt = state.ρ * S_qt,\n        S_ρ_ql = state.ρ * S_ql,\n        S_ρ_qr = state.ρ * S_qr,\n        S_ρ_e = state.ρ * S_e,\n    )\nend\n\n\"\"\"\n    RainSnow_1M <: TendencyDef{Source}\n\nA collection of source/sink terms related to 1-moment rain and snow microphysics\nThe microphysics process rates are implemented\nin the CloudMicrophysics.jl Microphysics_1M module.\n\"\"\"\nstruct RainSnow_1M <: TendencyDef{Source} end\n\nprognostic_vars(::RainSnow_1M) = (\n    Mass(),\n    Energy(),\n    TotalMoisture(),\n    LiquidMoisture(),\n    IceMoisture(),\n    Rain(),\n    Snow(),\n)\n\nfunction rain_snow_sources(atmos, args, ts)\n    @unpack state, aux = args\n\n    FT = eltype(state)\n    param_set = parameter_set(atmos)\n\n    q_rai::FT = state.precipitation.ρq_rai / state.ρ\n    q_sno::FT = state.precipitation.ρq_sno / state.ρ\n    q = PhasePartition(ts)\n    T::FT = air_temperature(ts)\n    I_d::FT = internal_energy_dry(ts)\n    I_v::FT = internal_energy_vapor(ts)\n    I_l::FT = internal_energy_liquid(ts)\n    I_i::FT = internal_energy_ice(ts)\n    _T_freeze::FT = T_freeze(param_set)\n    _L_f::FT = latent_heat_fusion(ts)\n    _cv_l::FT = cv_l(param_set)\n    Φ::FT = gravitational_potential(atmos.orientation, aux)\n\n    # temporary vars for summming different source terms\n    S_qr::FT = FT(0)\n    S_qs::FT = FT(0)\n    S_ql::FT = FT(0)\n    S_qi::FT = FT(0)\n    S_qt::FT = FT(0)\n    S_e::FT = FT(0)\n\n    # source of rain via autoconversion\n    tmp = conv_q_liq_to_q_rai(param_set, q.liq)\n    S_qr += tmp\n    S_ql -= tmp\n    S_e -= tmp * (I_l + Φ)\n\n    # source of snow via autoconversion\n    tmp = conv_q_ice_to_q_sno(param_set, q, state.ρ, T)\n    S_qs += tmp\n    S_qi -= tmp\n    S_e -= tmp * (I_i + Φ)\n\n    # source of rain water via accretion cloud water - rain\n    tmp = accretion(\n        param_set,\n        CM1M.LiquidType(),\n        CM1M.RainType(),\n        q.liq,\n        q_rai,\n        state.ρ,\n    )\n    S_qr += tmp\n    S_ql -= tmp\n    S_e -= tmp * (I_l + Φ)\n\n    # source of snow via accretion cloud ice - snow\n    tmp = accretion(\n        param_set,\n        CM1M.IceType(),\n        CM1M.SnowType(),\n        q.ice,\n        q_sno,\n        state.ρ,\n    )\n    S_qs += tmp\n    S_qi -= tmp\n    S_e -= tmp * (I_i + Φ)\n\n    # sink of cloud water via accretion cloud water - snow\n    tmp = accretion(\n        param_set,\n        CM1M.LiquidType(),\n        CM1M.SnowType(),\n        q.liq,\n        q_sno,\n        state.ρ,\n    )\n    if T < _T_freeze # cloud droplets freeze to become snow)\n        S_qs += tmp\n        S_ql -= tmp\n        S_e -= tmp * (I_i + Φ)\n    else # snow melts, both cloud water and snow become rain\n        α::FT = _cv_l / _L_f * (T - _T_freeze)\n        S_ql -= tmp\n        S_qs -= tmp * α\n        S_qr += tmp * (1 + α)\n        S_e -= tmp * ((1 + α) * I_l - α * I_i + Φ)\n    end\n\n    # sink of cloud ice via accretion cloud ice - rain\n    tmp1 = accretion(\n        param_set,\n        CM1M.IceType(),\n        CM1M.RainType(),\n        q.ice,\n        q_rai,\n        state.ρ,\n    )\n    # sink of rain via accretion cloud ice - rain\n    tmp2 = accretion_rain_sink(param_set, q.ice, q_rai, state.ρ)\n    S_qi -= tmp1\n    S_e -= tmp1 * (I_i + Φ)\n    S_qr -= tmp2\n    S_e += tmp2 * _L_f\n    S_qs += tmp1 + tmp2\n\n    # accretion rain - snow\n    if T < _T_freeze\n        tmp = accretion_snow_rain(\n            param_set,\n            CM1M.SnowType(),\n            CM1M.RainType(),\n            q_sno,\n            q_rai,\n            state.ρ,\n        )\n        S_qs += tmp\n        S_qr -= tmp\n        S_e += tmp * _L_f\n    else\n        tmp = accretion_snow_rain(\n            param_set,\n            CM1M.RainType(),\n            CM1M.SnowType(),\n            q_rai,\n            q_sno,\n            state.ρ,\n        )\n        S_qs -= tmp\n        S_qr += tmp\n        S_e -= tmp * _L_f\n    end\n\n    # rain evaporation sink (it already has negative sign for evaporation)\n    tmp = evaporation_sublimation(\n        param_set,\n        CM1M.RainType(),\n        q,\n        q_rai,\n        state.ρ,\n        T,\n    )\n    S_qr += tmp\n    S_e -= tmp * (I_l + Φ)\n\n    # snow sublimation/deposition source/sink\n    tmp = evaporation_sublimation(\n        param_set,\n        CM1M.SnowType(),\n        q,\n        q_sno,\n        state.ρ,\n        T,\n    )\n    S_qs += tmp\n    S_e -= tmp * (I_i + Φ)\n\n    # snow melt\n    tmp = snow_melt(param_set, q_sno, state.ρ, T)\n    S_qs -= tmp\n    S_qr += tmp\n    S_e -= tmp * _L_f\n\n    # total qt sink is the sum of precip sources\n    S_qt = -S_qr - S_qs\n\n    return (;\n        S_ρ_qt = state.ρ * S_qt,\n        S_ρ_ql = state.ρ * S_ql,\n        S_ρ_qi = state.ρ * S_qi,\n        S_ρ_qr = state.ρ * S_qr,\n        S_ρ_qs = state.ρ * S_qs,\n        S_ρ_e = state.ρ * S_e,\n    )\nend\n"
  },
  {
    "path": "src/Atmos/Model/precipitation.jl",
    "content": "#### Precipitation component in atmosphere model\nabstract type PrecipitationModel end\n\nexport PrecipitationModel, NoPrecipitation, RainModel, RainSnowModel\n\neq_tends(pv::PV, m::PrecipitationModel, ::Flux{O}) where {PV, O} = ()\n\nvars_state(::PrecipitationModel, ::AbstractStateType, FT) = @vars()\n\nfunction atmos_nodal_update_auxiliary_state!(\n    ::PrecipitationModel,\n    m::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) end\nfunction compute_gradient_flux!(\n    ::PrecipitationModel,\n    diffusive,\n    ∇transform,\n    state,\n    aux,\n    t,\n) end\nfunction compute_gradient_argument!(\n    ::PrecipitationModel,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) end\n\n\"\"\"\n    NoPrecipitation <: PrecipitationModel\n\nNo precipitation.\n\"\"\"\nstruct NoPrecipitation <: PrecipitationModel end\n\nprecompute(::NoPrecipitation, atmos::AtmosModel, args, ts, ::Source) =\n    NamedTuple()\n\n\"\"\"\n    RainModel <: PrecipitationModel\n\nPrecipitation model with rain.\n\"\"\"\nstruct RainModel <: PrecipitationModel end\n\nvars_state(::RainModel, ::Prognostic, FT) = @vars(ρq_rai::FT)\nvars_state(::RainModel, ::Gradient, FT) = @vars(q_rai::FT)\nvars_state(::RainModel, ::GradientFlux, FT) = @vars(∇q_rai::SVector{3, FT})\n\nprecompute(::RainModel, atmos::AtmosModel, args, ts, ::Source) =\n    (cache = warm_rain_sources(atmos, args, ts),)\n\nfunction atmos_nodal_update_auxiliary_state!(\n    precip::RainModel,\n    atmos::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) end\n\nfunction compute_gradient_argument!(\n    precip::RainModel,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ρinv = 1 / state.ρ\n    transform.precipitation.q_rai = state.precipitation.ρq_rai * ρinv\nend\n\nfunction compute_gradient_flux!(\n    precip::RainModel,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    diffusive.precipitation.∇q_rai = ∇transform.precipitation.q_rai\nend\n\n\"\"\"\n    RainSnowModel <: PrecipitationModel\n\nPrecipitation model with rain and snow.\n\"\"\"\nstruct RainSnowModel <: PrecipitationModel end\n\nvars_state(::RainSnowModel, ::Prognostic, FT) = @vars(ρq_rai::FT, ρq_sno::FT)\nvars_state(::RainSnowModel, ::Gradient, FT) = @vars(q_rai::FT, q_sno::FT)\nvars_state(::RainSnowModel, ::GradientFlux, FT) =\n    @vars(∇q_rai::SVector{3, FT}, ∇q_sno::SVector{3, FT})\n\nprecompute(::RainSnowModel, atmos::AtmosModel, args, ts, ::Source) =\n    (cache = rain_snow_sources(atmos, args, ts),)\n\nfunction atmos_nodal_update_auxiliary_state!(\n    precip::RainSnowModel,\n    atmos::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) end\n\nfunction compute_gradient_argument!(\n    precip::RainSnowModel,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ρinv = 1 / state.ρ\n    transform.precipitation.q_rai = state.precipitation.ρq_rai * ρinv\n    transform.precipitation.q_sno = state.precipitation.ρq_sno * ρinv\nend\n\nfunction compute_gradient_flux!(\n    precip::RainSnowModel,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    diffusive.precipitation.∇q_rai = ∇transform.precipitation.q_rai\n    diffusive.precipitation.∇q_sno = ∇transform.precipitation.q_sno\nend\n\n#####\n##### Tendency specifications\n#####\n\neq_tends(::Rain, ::RainModel, ::Flux{FirstOrder}) = (PrecipitationFlux(),)\neq_tends(::Rain, ::RainModel, ::Flux{SecondOrder}) = (Diffusion(),)\n\neq_tends(::AbstractPrecipitationVariable, ::RainSnowModel, ::Flux{FirstOrder}) =\n    (PrecipitationFlux(),)\neq_tends(\n    ::AbstractPrecipitationVariable,\n    ::RainSnowModel,\n    ::Flux{SecondOrder},\n) = (Diffusion(),)\n"
  },
  {
    "path": "src/Atmos/Model/problem.jl",
    "content": "export AbstractAtmosProblem, AtmosProblem\n\ninclude(\"boundaryconditions.jl\")\n\nabstract type AbstractAtmosProblem <: AbstractProblem end\n\n\"\"\"\n    AtmosProblem\n\nThe default problem definition (initial and boundary conditions)\nfor `AtmosModel`.\n\"\"\"\nstruct AtmosProblem{BCS, ISP, ISA} <: AbstractAtmosProblem\n    \"Boundary condition specification\"\n    boundaryconditions::BCS\n    \"Initial condition (function to assign initial values of prognostic state variables)\"\n    init_state_prognostic::ISP\n    \"Initial condition (function to assign initial values of auxiliary state variables)\"\n    init_state_auxiliary::ISA\nend\n\nfunction AtmosProblem(;\n    physics = nothing,\n    boundaryconditions = nothing,\n    init_state_prognostic::ISP = nothing,\n    init_state_auxiliary::ISA = atmos_problem_init_state_auxiliary,\n) where {ISP, ISA}\n    @assert init_state_prognostic ≠ nothing\n    if boundaryconditions == nothing\n        @assert physics ≠ nothing\n        boundaryconditions = (AtmosBC(physics), AtmosBC(physics))\n    end\n\n    problem = (boundaryconditions, init_state_prognostic, init_state_auxiliary)\n\n    return AtmosProblem{typeof.(problem)...}(problem...)\nend\n\natmos_problem_init_state_auxiliary(\n    problem::AtmosProblem,\n    model::AtmosModel,\n    aux::Vars,\n    geom::LocalGeometry,\n) = nothing\n"
  },
  {
    "path": "src/Atmos/Model/prog_prim_conversion.jl",
    "content": "####\n#### prognostic_to_primitive! and primitive_to_prognostic!\n####\n\n####\n#### Wrappers (entry point)\n####\n\n\"\"\"\n    prognostic_to_primitive!(atmos::AtmosModel, prim::Vars, prog::Vars, aux::Vars)\n\nConvert prognostic variables `prog` to primitive\nvariables `prim` for the atmos model `atmos`.\n\n!!! note\n    The only field in `aux` required for this\n    method is the geo-potential.\n\"\"\"\nfunction prognostic_to_primitive!(\n    atmos::AtmosModel,\n    prim::Vars,\n    prog::Vars,\n    aux,\n)\n    energy_model(atmos) isa TotalEnergyModel ||\n        error(\"TotalEnergyModel only supported\")\n    prognostic_to_primitive!(atmos, moisture_model(atmos), prim, prog, aux)\n    prognostic_to_primitive!(\n        turbconv_model(atmos),\n        atmos,\n        moisture_model(atmos),\n        prim,\n        prog,\n    )\nend\n\n\"\"\"\n    primitive_to_prognostic!(atmos::AtmosModel, prog::Vars, prim::Vars, aux::Vars)\n\nConvert primitive variables `prim` to prognostic\nvariables `prog` for the atmos model `atmos`.\n\n!!! note\n    The only field in `aux` required for this\n    method is the geo-potential.\n\"\"\"\nfunction primitive_to_prognostic!(\n    atmos::AtmosModel,\n    prog::Vars,\n    prim::Vars,\n    aux,\n)\n    primitive_to_prognostic!(atmos, moisture_model(atmos), prog, prim, aux)\n    primitive_to_prognostic!(\n        turbconv_model(atmos),\n        atmos,\n        moisture_model(atmos),\n        prog,\n        prim,\n    )\nend\n\n####\n#### prognostic to primitive\n####\n\nfunction prognostic_to_primitive!(\n    atmos,\n    moist::DryModel,\n    prim::Vars,\n    prog::Vars,\n    aux::Vars,\n)\n    ts = new_thermo_state(atmos, prog, aux)\n    prim.ρ = air_density(ts) # Needed for recovery of energy, not prog.ρ in anelastic1d\n    prim.u = prog.ρu ./ density(atmos, prog, aux)\n    prim.p = pressure(atmos, ts, aux)\nend\n\nfunction prognostic_to_primitive!(\n    atmos,\n    moist::EquilMoist,\n    prim::Vars,\n    prog::Vars,\n    aux::Vars,\n)\n    ts = new_thermo_state(atmos, prog, aux)\n    prim.ρ = air_density(ts) # Needed for recovery of energy, not prog.ρ in anelastic1d\n    prim.u = prog.ρu ./ density(atmos, prog, aux)\n    prim.p = pressure(atmos, ts, aux)\n    prim.moisture.q_tot = PhasePartition(ts).tot\nend\n\nfunction prognostic_to_primitive!(\n    atmos,\n    moist::NonEquilMoist,\n    prim::Vars,\n    prog::Vars,\n    aux::Vars,\n)\n    ts = new_thermo_state(atmos, prog, aux)\n    prim.ρ = air_density(ts) # Needed for recovery of energy, not prog.ρ in anelastic1d\n    prim.u = prog.ρu ./ density(atmos, prog, aux)\n    prim.p = pressure(atmos, ts, aux)\n    prim.moisture.q_tot = PhasePartition(ts).tot\n    prim.moisture.q_liq = PhasePartition(ts).liq\n    prim.moisture.q_ice = PhasePartition(ts).ice\nend\n\n####\n#### primitive to prognostic\n####\n\nfunction primitive_to_prognostic!(\n    atmos,\n    moist::DryModel,\n    prog::Vars,\n    prim::Vars,\n    aux::Vars,\n)\n    energy_model(atmos) isa TotalEnergyModel ||\n        error(\"TotalEnergyModel only supported\")\n    ts = PhaseDry_ρp(parameter_set(atmos), prim.ρ, prim.p)\n    e_kin = prim.u' * prim.u / 2\n    ρ = density(atmos, prim, aux)\n    e_pot = gravitational_potential(atmos.orientation, aux)\n\n    prog.ρ = ρ\n    prog.ρu = ρ .* prim.u\n    prog.energy.ρe = ρ * total_energy(e_kin, e_pot, ts)\nend\n\nfunction primitive_to_prognostic!(\n    atmos,\n    moist::EquilMoist,\n    prog::Vars,\n    prim::Vars,\n    aux::Vars,\n)\n    energy_model(atmos) isa TotalEnergyModel ||\n        error(\"TotalEnergyModel only supported\")\n    ts = PhaseEquil_ρpq(\n        parameter_set(atmos),\n        prim.ρ,\n        prim.p,\n        prim.moisture.q_tot,\n        true,\n    )\n    e_kin = prim.u' * prim.u / 2\n    ρ = density(atmos, prim, aux)\n    e_pot = gravitational_potential(atmos.orientation, aux)\n\n    prog.ρ = ρ\n    prog.ρu = ρ .* prim.u\n    prog.energy.ρe = ρ * total_energy(e_kin, e_pot, ts)\n    prog.moisture.ρq_tot = ρ * PhasePartition(ts).tot\nend\n\nfunction primitive_to_prognostic!(\n    atmos,\n    moist::NonEquilMoist,\n    prog::Vars,\n    prim::Vars,\n    aux::Vars,\n)\n    energy_model(atmos) isa TotalEnergyModel ||\n        error(\"TotalEnergyModel only supported\")\n    q_pt = PhasePartition(\n        prim.moisture.q_tot,\n        prim.moisture.q_liq,\n        prim.moisture.q_ice,\n    )\n    ts = PhaseNonEquil_ρpq(parameter_set(atmos), prim.ρ, prim.p, q_pt)\n    e_kin = prim.u' * prim.u / 2\n    ρ = density(atmos, prim, aux)\n    e_pot = gravitational_potential(atmos.orientation, aux)\n\n    prog.ρ = ρ\n    prog.ρu = ρ .* prim.u\n    prog.energy.ρe = ρ * total_energy(e_kin, e_pot, ts)\n    prog.moisture.ρq_tot = ρ * PhasePartition(ts).tot\n    prog.moisture.ρq_liq = ρ * PhasePartition(ts).liq\n    prog.moisture.ρq_ice = ρ * PhasePartition(ts).ice\nend\n\n\nfunction construct_face_auxiliary_state!(\n    bl::AtmosModel,\n    aux_face::AbstractArray,\n    aux_cell::AbstractArray,\n    Δz::FT,\n) where {FT <: Real}\n    param_set = parameter_set(bl)\n    _grav = FT(grav(param_set))\n    var_aux = Vars{vars_state(bl, Auxiliary(), FT)}\n    aux_face .= aux_cell\n\n    if !(bl.orientation isa NoOrientation)\n        var_aux(aux_face).orientation.Φ =\n            var_aux(aux_cell).orientation.Φ + _grav * Δz / 2\n    end\nend\n"
  },
  {
    "path": "src/Atmos/Model/projections.jl",
    "content": "import ..BalanceLaws: projection\n\n# Zero-out vertical momentum tendencies based on compressibility\nfunction projection(pv::Momentum, atmos::AtmosModel, td::TendencyDef, args, x)\n    return projection(pv, compressibility_model(atmos), td, args, x)\nend\n\n# Zero-out vertical momentum fluxes for Anelastic1D:\nfunction projection(\n    pv::Momentum,\n    ::Anelastic1D,\n    ::TendencyDef{Flux{O}},\n    args,\n    x,\n) where {O}\n    return x .* SArray{Tuple{3, 3}}(1, 1, 1, 1, 1, 1, 0, 0, 0)\nend\n\n# Zero-out vertical momentum sources for Anelastic1D:\nfunction projection(::Momentum, ::Anelastic1D, ::TendencyDef{Source}, args, x)\n    return x .* SVector(1, 1, 0)\nend\n"
  },
  {
    "path": "src/Atmos/Model/radiation.jl",
    "content": "export RadiationModel, NoRadiation\n\nabstract type RadiationModel end\n\nvars_state(::RadiationModel, ::AbstractStateType, FT) = @vars()\n\neq_tends(pv::PV, ::RadiationModel, ::Flux{FirstOrder}) where {PV} = ()\n\nfunction atmos_nodal_update_auxiliary_state!(\n    ::RadiationModel,\n    ::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) end\nfunction integral_set_auxiliary_state!(::RadiationModel, integ::Vars, aux::Vars) end\nfunction integral_load_auxiliary_state!(\n    ::RadiationModel,\n    integ::Vars,\n    state::Vars,\n    aux::Vars,\n) end\nfunction reverse_integral_set_auxiliary_state!(\n    ::RadiationModel,\n    integ::Vars,\n    aux::Vars,\n) end\nfunction reverse_integral_load_auxiliary_state!(\n    ::RadiationModel,\n    integ::Vars,\n    state::Vars,\n    aux::Vars,\n) end\n\nstruct NoRadiation <: RadiationModel end\n"
  },
  {
    "path": "src/Atmos/Model/reconstructions.jl",
    "content": "using KernelAbstractions.Extras: @unroll\n\nexport HBFVReconstruction\n\nusing ..DGMethods.FVReconstructions: AbstractReconstruction\nimport ..DGMethods.FVReconstructions: width\n\nstruct HBFVReconstruction{M, R} <: AbstractReconstruction\n    _atmo::M\n    _recon::R\nend\n\nwidth(hb_recon::HBFVReconstruction) = width(hb_recon._recon)\n\nfunction (hb_recon::HBFVReconstruction)(\n    state_bot,\n    state_top,\n    cell_states::SVector{D},\n    cell_weights,\n) where {D}\n    FT = eltype(state_bot)\n    vars_prim = Vars{vars_state(hb_recon._atmo, Primitive(), FT)}\n    param_set = parameter_set(hb_recon._atmo)\n    _grav = FT(grav(param_set))\n\n    # stencil info\n    stencil_diameter = D\n    stencil_width = div(D - 1, 2)\n    stencil_center = stencil_width + 1\n    # save the pressure Δpressure_ref states\n    ps = similar(state_bot, Size(stencil_diameter))\n    ρgΔz_half = similar(state_bot, Size(stencil_diameter))\n\n    @inbounds begin\n        @unroll for i in 1:stencil_diameter\n            ps[i] = vars_prim(cell_states[i]).p\n            ρgΔz_half[i] =\n                vars_prim(cell_states[i]).ρ * _grav * cell_weights[i] / 2\n        end\n\n        # construct reference pressure states and update pressure states\n        p_ref = ps[stencil_center]\n        p_bot_ref = p_ref + ρgΔz_half[stencil_center]\n        p_top_ref = p_ref - ρgΔz_half[stencil_center]\n\n        vars_prim(cell_states[stencil_center]).p -= p_ref\n\n        p⁺_ref, p⁻_ref = p_ref, p_ref\n        @unroll for i in 1:stencil_width\n            # stencil_center - i , stencil_center - i + 1\n            p⁻_ref +=\n                ρgΔz_half[stencil_center - i + 1] +\n                ρgΔz_half[stencil_center - i]\n            vars_prim(cell_states[stencil_center - i]).p -= p⁻_ref\n\n            # stencil_center + i - 1 , stencil_center + i\n            p⁺_ref -=\n                ρgΔz_half[stencil_center + i - 1] +\n                ρgΔz_half[stencil_center + i]\n            vars_prim(cell_states[stencil_center + i]).p -= p⁺_ref\n        end\n\n        hb_recon._recon(state_bot, state_top, cell_states, cell_weights)\n\n        vars_prim(state_bot).p += p_bot_ref\n        vars_prim(state_top).p += p_top_ref\n\n        # reverse the pressure states back\n        @unroll for i in 1:stencil_diameter\n            vars_prim(cell_states[i]).p = ps[i]\n        end\n    end\nend\n"
  },
  {
    "path": "src/Atmos/Model/ref_state.jl",
    "content": "### Reference state\nusing DocStringExtensions\nusing Thermodynamics.TemperatureProfiles\nexport ReferenceState, NoReferenceState, HydrostaticState\nconst TD = Thermodynamics\nusing CLIMAParameters.Planet: R_d, MSLP, cp_d, grav, T_surf_ref, T_min_ref\nusing ..DGMethods: fvm_balance!\nusing ..Mesh.Grids: polynomialorders\n\n\"\"\"\n    ReferenceState\n\nHydrostatic reference state, for example, used as initial\ncondition or for linearization.\n\"\"\"\nabstract type ReferenceState end\n\nvars_state(m::ReferenceState, ::AbstractStateType, FT) = @vars()\n\n\"\"\"\n    NoReferenceState <: ReferenceState\n\nNo reference state used\n\"\"\"\nstruct NoReferenceState <: ReferenceState end\n\n\"\"\"\n    HydrostaticState{P,T} <: ReferenceState\n\nA hydrostatic state specified by a virtual\ntemperature profile and relative humidity.\n\nBy default, this is a dry hydrostatic reference\nstate.\n\"\"\"\nstruct HydrostaticState{P, FT} <: ReferenceState\n    virtual_temperature_profile::P\n    relative_humidity::FT\n    subtract_off::Bool\nend\n\"\"\"\n    HydrostaticState(\n        virtual_temperature_profile,\n        relative_humidity = 0;\n        subtract_off = true,\n    )\n\nConstruct a `HydrostaticState` given virtual temperature profile and\nrelative humidity. The keyword argument `subtract_off` controls\nwhether the constructed state is subtracted off in the prognostic\nmomentum equation to remove hydrostatic contribution.\n\"\"\"\nfunction HydrostaticState(\n    virtual_temperature_profile::TemperatureProfile{FT},\n    relative_humidity = FT(0);\n    subtract_off = true,\n) where {FT}\n    return HydrostaticState{typeof(virtual_temperature_profile), FT}(\n        virtual_temperature_profile,\n        relative_humidity,\n        subtract_off,\n    )\nend\n\n# TODO: change `ρe` to `energy` to support `θModel`\n# TODO: Make `moisture` sub-component, so that moisture components are optional\nvars_state(m::HydrostaticState, ::Auxiliary, FT) =\n    @vars(ρ::FT, p::FT, T::FT, ρe::FT, ρq_tot::FT, ρq_liq::FT, ρq_ice::FT)\n\nfunction ref_state_init_pρ!(\n    atmos::AtmosModel,\n    aux::Vars,\n    tmp::Vars,\n    geom::LocalGeometry,\n)\n    param_set = parameter_set(atmos)\n    z = altitude(atmos, aux)\n    ref_state = reference_state(atmos)\n    T_virt, p = ref_state.virtual_temperature_profile(param_set, z)\n    aux.ref_state.p = p\n    aux.ref_state.ρ = p / (T_virt * R_d(param_set))\nend\n\nfunction ref_state_init_density_from_pressure!(\n    atmos::AtmosModel,\n    aux::Vars,\n    tmp::Vars,\n    geom::LocalGeometry,\n) where {P, F}\n    k = vertical_unit_vector(atmos, aux)\n    ∇Φ = ∇gravitational_potential(atmos, aux)\n    # density computation from pressure ρ = -1/g*dpdz\n    ρ = -k' * tmp.∇p / (k' * ∇Φ)\n    aux.ref_state.ρ = ρ\nend\n\nfunction ref_state_finalize_init!(\n    atmos::AtmosModel,\n    aux::Vars,\n    tmp::Vars,\n    geom::LocalGeometry,\n)\n    FT = eltype(aux)\n    param_set = parameter_set(atmos)\n    ρ = aux.ref_state.ρ\n    p = aux.ref_state.p\n    T_virt = p / (ρ * FT(R_d(param_set)))\n\n    ref_state = reference_state(atmos)\n    RH = ref_state.relative_humidity\n    phase_type = PhaseEquil\n    (T, q_pt) = TD.temperature_and_humidity_given_TᵥρRH(\n        param_set,\n        T_virt,\n        ρ,\n        RH,\n        phase_type,\n    )\n\n    # Update temperature to be exactly consistent with\n    # p, ρ, and q_pt\n    if moisture_model(atmos) isa DryModel\n        ts = PhaseDry_ρp(param_set, ρ, p)\n    else\n        ts = PhaseEquil_ρpq(param_set, ρ, p, q_pt.tot)\n    end\n    T = air_temperature(ts)\n    q_pt = PhasePartition(ts)\n    q_tot = q_pt.tot\n    q_liq = q_pt.liq\n    q_ice = q_pt.ice\n\n    aux.ref_state.ρq_tot = ρ * q_tot\n    aux.ref_state.ρq_liq = ρ * q_liq\n    aux.ref_state.ρq_ice = ρ * q_ice\n    aux.ref_state.T = T\n    e_kin = FT(0)\n    e_pot = gravitational_potential(atmos.orientation, aux)\n    aux.ref_state.ρe = ρ * total_energy(e_kin, e_pot, ts)\nend\n\natmos_init_aux!(::AtmosModel, ::ReferenceState, _...) = nothing\nfunction atmos_init_aux!(\n    atmos::AtmosModel,\n    ::HydrostaticState,\n    state_auxiliary::MPIStateArray,\n    grid,\n    direction,\n)\n    # Step 1: initialize both ρ and p\n    init_state_auxiliary!(\n        atmos,\n        ref_state_init_pρ!,\n        state_auxiliary,\n        grid,\n        direction,\n    )\n\n    # Step 2: correct ρ from p to satisfy discrete hydrostic balance\n    vertical_fvm = polynomialorders(grid)[end] == 0\n\n    if vertical_fvm\n        # Vertical finite volume scheme\n        # pᵢ - pᵢ₊₁ =  g ρᵢ₊₁ Δzᵢ₊₁/2 + g ρᵢ Δzᵢ/2\n        fvm_balance!(fvm_balance_init!, atmos, state_auxiliary, grid)\n    else\n        ref_state = reference_state(atmos)\n        ∇p = ∇reference_pressure(ref_state, state_auxiliary, grid)\n        init_state_auxiliary!(\n            atmos,\n            ref_state_init_density_from_pressure!,\n            state_auxiliary,\n            grid,\n            direction;\n            state_temporary = ∇p,\n        )\n    end\n\n    init_state_auxiliary!(\n        atmos,\n        ref_state_finalize_init!,\n        state_auxiliary,\n        grid,\n        direction,\n    )\nend\n\nusing ..MPIStateArrays: vars\nusing ..DGMethods: init_ode_state\nusing ..DGMethods.NumericalFluxes:\n    CentralNumericalFluxFirstOrder,\n    CentralNumericalFluxSecondOrder,\n    CentralNumericalFluxGradient\n\n\n\"\"\"\n    PressureGradientModel\n\nA mini balance law that is used to take the gradient of reference\npressure. The gradient is computed as ∇ ⋅(pI) and the calculation\nuses the balance law interface to be numerically consistent with\nthe way this gradient is computed in the dynamics.\n\"\"\"\nstruct PressureGradientModel <: BalanceLaw end\nvars_state(::PressureGradientModel, ::Auxiliary, T) = @vars(p::T)\nvars_state(::PressureGradientModel, ::Prognostic, T) = @vars(∇p::SVector{3, T})\nvars_state(::PressureGradientModel, ::Gradient, T) = @vars()\nvars_state(::PressureGradientModel, ::GradientFlux, T) = @vars()\nfunction init_state_auxiliary!(\n    m::PressureGradientModel,\n    state_auxiliary::MPIStateArray,\n    grid,\n    direction,\n) end\nfunction init_state_prognostic!(\n    ::PressureGradientModel,\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t,\n) end\nfunction flux_first_order!(\n    ::PressureGradientModel,\n    flux::Grad,\n    state::Vars,\n    auxstate::Vars,\n    t::Real,\n    direction,\n)\n    flux.∇p -= auxstate.p * I\nend\nflux_second_order!(::PressureGradientModel, _...) = nothing\nsource!(::PressureGradientModel, _...) = nothing\n\nboundary_conditions(::PressureGradientModel) = ntuple(i -> nothing, 6)\nboundary_state!(nf, ::Nothing, ::PressureGradientModel, _...) = nothing\n\n∇reference_pressure(::NoReferenceState, state_auxiliary, grid) = nothing\nfunction ∇reference_pressure(::ReferenceState, state_auxiliary, grid)\n    FT = eltype(state_auxiliary)\n    ∇p = similar(state_auxiliary; vars = @vars(∇p::SVector{3, FT}), nstate = 3)\n\n    grad_model = PressureGradientModel()\n    # Note that the choice of numerical fluxes doesn't matter\n    # for taking the gradient of a continuous field\n    grad_dg = DGModel(\n        grad_model,\n        grid,\n        CentralNumericalFluxFirstOrder(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    # initialize p\n    ix_p = varsindex(vars(state_auxiliary), :ref_state, :p)\n    grad_dg.state_auxiliary.data .= state_auxiliary.data[:, ix_p, :]\n\n    # FIXME: this isn't used but needs to be passed in\n    gradQ = init_ode_state(grad_dg, FT(0))\n\n    grad_dg(∇p, gradQ, nothing, FT(0))\n    return ∇p\nend\n\nfunction fvm_balance_init!(\n    atmos::AtmosModel,\n    aux_top::Vars,\n    aux::Vars,\n    Δz::MArray{Tuple{2}, FT},\n) where {FT}\n    param_set = parameter_set(atmos)\n    _grav::FT = grav(param_set)\n    _R_d::FT = R_d(param_set)\n\n    ref = aux.ref_state\n    topref = aux_top.ref_state\n\n    T_virt = ref.p / (_R_d * ref.ρ)\n    top_T_virt = topref.p / (_R_d * topref.ρ)\n\n    topref.ρ =\n        ref.ρ * (_R_d * T_virt - _grav * Δz[1] / 2) /\n        (_R_d * top_T_virt + _grav * Δz[2] / 2)\n    topref.p = _R_d * topref.ρ * top_T_virt\nend\n"
  },
  {
    "path": "src/Atmos/Model/tendencies_energy.jl",
    "content": "##### Energy tendencies\n\n#####\n##### First order fluxes\n#####\n\nfunction flux(::Energy, ::Advect, atmos, args)\n    @unpack state = args\n    return (state.ρu / state.ρ) * state.energy.ρe\nend\n\nfunction flux(::ρθ_liq_ice, ::Advect, atmos, args)\n    @unpack state = args\n    return (state.ρu / state.ρ) * state.energy.ρθ_liq_ice\nend\n\nfunction flux(::Energy, ::Pressure, atmos, args)\n    @unpack state = args\n    @unpack ts = args.precomputed\n    return state.ρu / state.ρ * air_pressure(ts)\nend\n\n#####\n##### Second order fluxes\n#####\n\nstruct ViscousFlux <: TendencyDef{Flux{SecondOrder}} end\nfunction flux(::Energy, ::ViscousFlux, atmos, args)\n    @unpack state = args\n    @unpack τ = args.precomputed.turbulence\n    return τ * state.ρu\nend\n\nfunction flux(::ρθ_liq_ice, ::ViscousFlux, atmos, args)\n    @unpack state, diffusive = args\n    @unpack D_t = args.precomputed.turbulence\n    return state.ρ * (-D_t) .* diffusive.energy.∇θ_liq_ice\nend\n\nfunction flux(::Energy, ::HyperdiffViscousFlux, atmos, args)\n    @unpack state, hyperdiffusive = args\n    return hyperdiffusive.hyperdiffusion.ν∇³u_h * state.ρu\nend\n\nfunction flux(::Energy, ::HyperdiffEnthalpyFlux, atmos, args)\n    @unpack state, hyperdiffusive = args\n    return hyperdiffusive.hyperdiffusion.ν∇³h_tot * state.ρ\nend\n\nstruct DiffEnthalpyFlux <: TendencyDef{Flux{SecondOrder}} end\nfunction flux(::Energy, ::DiffEnthalpyFlux, atmos, args)\n    @unpack state, diffusive = args\n    @unpack D_t = args.precomputed.turbulence\n    d_h_tot = -D_t .* diffusive.energy.∇h_tot\n    return d_h_tot * state.ρ\nend\n\n#####\n##### Sources\n#####\n\nfunction source(::Energy, s::Subsidence, m, args)\n    @unpack state, aux, diffusive = args\n    z = altitude(m, aux)\n    w_sub = subsidence_velocity(s, z)\n    k̂ = vertical_unit_vector(m, aux)\n    return -state.ρ * w_sub * dot(k̂, diffusive.energy.∇h_tot)\nend\n\nfunction source(::Energy, s::RemovePrecipitation, m, args)\n    @unpack state = args\n    @unpack ts = args.precomputed\n    if has_condensate(ts)\n        nt = remove_precipitation_sources(s, m, args)\n        return nt.S_ρ_e\n    else\n        FT = eltype(state)\n        return FT(0)\n    end\nend\n\nfunction source(::Energy, s::WarmRain_1M, m, args)\n    @unpack cache = args.precomputed.precipitation\n    return cache.S_ρ_e\nend\n\nfunction source(::Energy, s::RainSnow_1M, m, args)\n    @unpack cache = args.precomputed.precipitation\n    return cache.S_ρ_e\nend\n"
  },
  {
    "path": "src/Atmos/Model/tendencies_mass.jl",
    "content": "##### Mass tendencies\n\n#####\n##### First order fluxes\n#####\n\nfunction flux(::Mass, ::Advect, atmos, args)\n    return args.state.ρu\nend\n\n#####\n##### Second order fluxes\n#####\n\nfunction flux(::Mass, ::MoistureDiffusion, atmos, args)\n    @unpack state, diffusive = args\n    @unpack D_t = args.precomputed.turbulence\n    d_q_tot = (-D_t) .* diffusive.moisture.∇q_tot\n    return d_q_tot * state.ρ\nend\n\n#####\n##### Sources\n#####\n\nfunction source(::Mass, s::Subsidence, m, args)\n    @unpack state, aux, diffusive = args\n    z = altitude(m, aux)\n    w_sub = subsidence_velocity(s, z)\n    k̂ = vertical_unit_vector(m, aux)\n    return -state.ρ * w_sub * dot(k̂, diffusive.moisture.∇q_tot)\nend\n\nfunction source(::Mass, s::RemovePrecipitation, m, args)\n    @unpack state = args\n    @unpack ts = args.precomputed\n    if has_condensate(ts)\n        nt = remove_precipitation_sources(s, m, args)\n        return nt.S_ρ_qt\n    else\n        FT = eltype(state)\n        return FT(0)\n    end\nend\n\nfunction source(::Mass, s::WarmRain_1M, m, args)\n    @unpack cache = args.precomputed.precipitation\n    return cache.S_ρ_qt\nend\n\nfunction source(::Mass, s::RainSnow_1M, m, args)\n    @unpack cache = args.precomputed.precipitation\n    return cache.S_ρ_qt\nend\n"
  },
  {
    "path": "src/Atmos/Model/tendencies_moisture.jl",
    "content": "##### Moisture tendencies\n\nexport CreateClouds\n\n#####\n##### First order fluxes\n#####\n\nfunction flux(::TotalMoisture, ::Advect, atmos, args)\n    @unpack state = args\n    u = state.ρu / state.ρ\n    return u * state.moisture.ρq_tot\nend\n\nfunction flux(::LiquidMoisture, ::Advect, atmos, args)\n    @unpack state = args\n    u = state.ρu / state.ρ\n    return u * state.moisture.ρq_liq\nend\n\nfunction flux(::IceMoisture, ::Advect, atmos, args)\n    @unpack state = args\n    u = state.ρu / state.ρ\n    return u * state.moisture.ρq_ice\nend\n\n#####\n##### Second order fluxes\n#####\n\nfunction flux(::TotalMoisture, ::MoistureDiffusion, atmos, args)\n    @unpack state, diffusive = args\n    @unpack D_t = args.precomputed.turbulence\n    d_q_tot = (-D_t) .* diffusive.moisture.∇q_tot\n    return d_q_tot * state.ρ\nend\n\nfunction flux(::LiquidMoisture, ::MoistureDiffusion, atmos, args)\n    @unpack state, diffusive = args\n    @unpack D_t = args.precomputed.turbulence\n    d_q_liq = (-D_t) .* diffusive.moisture.∇q_liq\n    return d_q_liq * state.ρ\nend\n\nfunction flux(::IceMoisture, ::MoistureDiffusion, atmos, args)\n    @unpack state, diffusive = args\n    @unpack D_t = args.precomputed.turbulence\n    d_q_ice = (-D_t) .* diffusive.moisture.∇q_ice\n    return d_q_ice * state.ρ\nend\n\nfunction flux(::TotalMoisture, ::HyperdiffViscousFlux, atmos, args)\n    @unpack state, hyperdiffusive = args\n    return hyperdiffusive.hyperdiffusion.ν∇³q_tot * state.ρ\nend\n\n#####\n##### Sources\n#####\n\nfunction source(::TotalMoisture, s::Subsidence, m, args)\n    @unpack state, aux, diffusive = args\n    z = altitude(m, aux)\n    w_sub = subsidence_velocity(s, z)\n    k̂ = vertical_unit_vector(m, aux)\n    return -state.ρ * w_sub * dot(k̂, diffusive.moisture.∇q_tot)\nend\n\n\"\"\"\n    CreateClouds <: TendencyDef{Source}\n\nA source/sink to `q_liq` and `q_ice` implemented as a relaxation towards\nequilibrium in the Microphysics module.\nThe default relaxation timescales are defined in CLIMAParameters.jl.\n\"\"\"\nstruct CreateClouds <: TendencyDef{Source} end\n\nprognostic_vars(::CreateClouds) = (LiquidMoisture(), IceMoisture())\n\nfunction source(::LiquidMoisture, s::CreateClouds, m, args)\n    @unpack state = args\n    @unpack ts = args.precomputed\n    # get current temperature and phase partition\n    FT = eltype(state)\n    q = PhasePartition(ts)\n    T = air_temperature(ts)\n    param_set = parameter_set(m)\n\n    # phase partition corresponding to the current T and q.tot\n    # (this is not the same as phase partition from saturation adjustment)\n    ts_eq = PhaseEquil_ρTq(param_set, state.ρ, T, q.tot)\n    q_eq = PhasePartition(ts_eq)\n\n    # cloud condensate as relaxation source terms\n    S_q_liq = conv_q_vap_to_q_liq_ice(param_set, CM1M.LiquidType(), q_eq, q)\n\n    return state.ρ * S_q_liq\nend\n\nfunction source(::IceMoisture, s::CreateClouds, m, args)\n    @unpack state = args\n    @unpack ts = args.precomputed\n    # get current temperature and phase partition\n    FT = eltype(state)\n    q = PhasePartition(ts)\n    T = air_temperature(ts)\n    param_set = parameter_set(m)\n\n    # phase partition corresponding to the current T and q.tot\n    # (this is not the same as phase partition from saturation adjustment)\n    ts_eq = PhaseEquil_ρTq(param_set, state.ρ, T, q.tot)\n    q_eq = PhasePartition(ts_eq)\n\n    # cloud condensate as relaxation source terms\n    S_q_ice = conv_q_vap_to_q_liq_ice(param_set, CM1M.IceType(), q_eq, q)\n\n    return state.ρ * S_q_ice\nend\n\nfunction source(::TotalMoisture, s::RemovePrecipitation, m, args)\n    @unpack state = args\n    @unpack ts = args.precomputed\n    if has_condensate(ts)\n        nt = remove_precipitation_sources(s, m, args)\n        return nt.S_ρ_qt\n    else\n        FT = eltype(state)\n        return FT(0)\n    end\nend\n\nfunction source(::TotalMoisture, s::WarmRain_1M, m, args)\n    @unpack cache = args.precomputed.precipitation\n    return cache.S_ρ_qt\nend\n\nfunction source(::LiquidMoisture, s::WarmRain_1M, m, args)\n    @unpack cache = args.precomputed.precipitation\n    return cache.S_ρ_ql\nend\n\nfunction source(::TotalMoisture, s::RainSnow_1M, m, args)\n    @unpack cache = args.precomputed.precipitation\n    return cache.S_ρ_qt\nend\n\nfunction source(::LiquidMoisture, s::RainSnow_1M, m, args)\n    @unpack cache = args.precomputed.precipitation\n    return cache.S_ρ_ql\nend\n\nfunction source(::IceMoisture, s::RainSnow_1M, m, args)\n    @unpack cache = args.precomputed.precipitation\n    return cache.S_ρ_qi\nend\n"
  },
  {
    "path": "src/Atmos/Model/tendencies_momentum.jl",
    "content": "##### Momentum tendencies\n\nexport GeostrophicForcing\nexport Coriolis\nexport Gravity\nexport RayleighSponge\nusing CLIMAParameters.Planet: Omega\n\n#####\n##### First order fluxes\n#####\n\nfunction flux(::Momentum, ::Advect, atmos, args)\n    @unpack state = args\n    return state.ρu .* (state.ρu / state.ρ)'\nend\n\nfunction flux(::Momentum, ::PressureGradient, atmos, args)\n    @unpack state, aux = args\n    @unpack ts = args.precomputed\n    s = state.ρu * state.ρu'\n    pad = SArray{Tuple{size(s)...}}(ntuple(i -> 0, length(s)))\n    ref_state = reference_state(atmos)\n    if ref_state isa HydrostaticState && ref_state.subtract_off\n        return pad + (air_pressure(ts) - aux.ref_state.p) * I\n    else\n        return pad + air_pressure(ts) * I\n    end\nend\n\n#####\n##### Second order fluxes\n#####\n\nstruct ViscousStress <: TendencyDef{Flux{SecondOrder}} end\n\nfunction flux(::Momentum, ::ViscousStress, atmos, args)\n    @unpack state = args\n    @unpack τ = args.precomputed.turbulence\n    pad = SArray{Tuple{size(τ)...}}(ntuple(i -> 0, length(τ)))\n    return pad + τ * state.ρ\nend\n\nfunction flux(::Momentum, ::MoistureDiffusion, atmos, args)\n    @unpack state, diffusive = args\n    @unpack D_t = args.precomputed.turbulence\n    d_q_tot = (-D_t) .* diffusive.moisture.∇q_tot\n    return d_q_tot .* state.ρu'\nend\n\nfunction flux(::Momentum, ::HyperdiffViscousFlux, atmos, args)\n    @unpack state, hyperdiffusive = args\n    return state.ρ * hyperdiffusive.hyperdiffusion.ν∇³u_h\nend\n\n#####\n##### Sources\n#####\n\nstruct Gravity <: TendencyDef{Source} end\n\nprognostic_vars(::Gravity) = (Momentum(),)\n\nfunction source(::Momentum, ::Gravity, m, args)\n    @unpack state, aux = args\n    ref_state = reference_state(m)\n    if ref_state isa HydrostaticState && ref_state.subtract_off\n        return -(state.ρ - aux.ref_state.ρ) * aux.orientation.∇Φ\n    else\n        return -state.ρ * aux.orientation.∇Φ\n    end\nend\n\nstruct Coriolis <: TendencyDef{Source} end\n\nprognostic_vars(::Coriolis) = (Momentum(),)\n\nfunction source(::Momentum, ::Coriolis, m, args)\n    @unpack state = args\n    FT = eltype(state)\n    param_set = parameter_set(m)\n    _Omega::FT = Omega(param_set)\n    # note: this assumes a SphericalOrientation\n    return -SVector(0, 0, 2 * _Omega) × state.ρu\nend\n\nstruct GeostrophicForcing{FT} <: TendencyDef{Source}\n    f_coriolis::FT\n    u_geostrophic::FT\n    v_geostrophic::FT\nend\n\nprognostic_vars(::GeostrophicForcing) = (Momentum(),)\n\nfunction source(::Momentum, s::GeostrophicForcing, m, args)\n    @unpack state, aux = args\n    u_geo = SVector(s.u_geostrophic, s.v_geostrophic, 0)\n    ẑ = vertical_unit_vector(m, aux)\n    fkvector = s.f_coriolis * ẑ\n    return -fkvector × (state.ρu .- state.ρ * u_geo)\nend\n\n\"\"\"\n    RayleighSponge{FT} <: TendencyDef{Source}\n\nRayleigh Damping (Linear Relaxation) for top wall momentum components\nAssumes laterally periodic boundary conditions for LES flows. Momentum components\nare relaxed to reference values (zero velocities) at the top boundary.\n\"\"\"\nstruct RayleighSponge{FT} <: TendencyDef{Source}\n    \"Maximum domain altitude (m)\"\n    z_max::FT\n    \"Altitude at with sponge starts (m)\"\n    z_sponge::FT\n    \"Sponge Strength 0 ⩽ α_max ⩽ 1\"\n    α_max::FT\n    \"Relaxation velocity components\"\n    u_relaxation::SVector{3, FT}\n    \"Sponge exponent\"\n    γ::FT\nend\n\nprognostic_vars(::RayleighSponge) = (Momentum(),)\n\nfunction source(::Momentum, s::RayleighSponge, m, args)\n    @unpack state, aux = args\n    z = altitude(m, aux)\n    if z >= s.z_sponge\n        r = (z - s.z_sponge) / (s.z_max - s.z_sponge)\n        β_sponge = s.α_max * sinpi(r / 2)^s.γ\n        return -β_sponge * (state.ρu .- state.ρ * s.u_relaxation)\n    else\n        FT = eltype(state)\n        return SVector{3, FT}(0, 0, 0)\n    end\nend\n"
  },
  {
    "path": "src/Atmos/Model/tendencies_precipitation.jl",
    "content": "##### Precipitation tendencies\n\n#####\n##### First order fluxes\n#####\n\nfunction flux(::Rain, ::PrecipitationFlux, atmos, args)\n    @unpack state, aux = args\n    FT = eltype(state)\n    u = state.ρu / state.ρ\n    q_rai = state.precipitation.ρq_rai / state.ρ\n\n    v_term_rai::FT = FT(0)\n    param_set = parameter_set(atmos)\n    if q_rai > FT(0)\n        v_term_rai =\n            terminal_velocity(param_set, CM1M.RainType(), state.ρ, q_rai)\n    end\n\n    k̂ = vertical_unit_vector(atmos, aux)\n    return state.precipitation.ρq_rai * (u - k̂ * v_term_rai)\nend\n\nfunction flux(::Snow, ::PrecipitationFlux, atmos, args)\n    @unpack state, aux = args\n    FT = eltype(state)\n    u = state.ρu / state.ρ\n    q_sno = state.precipitation.ρq_sno / state.ρ\n    param_set = parameter_set(atmos)\n    v_term_sno::FT = FT(0)\n    if q_sno > FT(0)\n        v_term_sno =\n            terminal_velocity(param_set, CM1M.SnowType(), state.ρ, q_sno)\n    end\n\n    k̂ = vertical_unit_vector(atmos, aux)\n    return state.precipitation.ρq_sno * (u - k̂ * v_term_sno)\nend\n\n\n#####\n##### Second order fluxes\n#####\n\nfunction flux(::Rain, ::Diffusion, atmos, args)\n    @unpack state, diffusive = args\n    @unpack D_t = args.precomputed.turbulence\n    d_q_rai = (-D_t) .* diffusive.precipitation.∇q_rai\n    return d_q_rai * state.ρ\nend\n\nfunction flux(::Snow, ::Diffusion, atmos, args)\n    @unpack state, diffusive = args\n    @unpack D_t = args.precomputed.turbulence\n    d_q_sno = (-D_t) .* diffusive.precipitation.∇q_sno\n    return d_q_sno * state.ρ\nend\n\n\n#####\n##### Sources\n#####\n\nfunction source(::Rain, s::WarmRain_1M, m, args)\n    @unpack cache = args.precomputed.precipitation\n    return cache.S_ρ_qr\nend\n\nfunction source(::Rain, s::RainSnow_1M, m, args)\n    @unpack cache = args.precomputed.precipitation\n    return cache.S_ρ_qr\nend\n\nfunction source(::Snow, s::RainSnow_1M, m, args)\n    @unpack cache = args.precomputed.precipitation\n    return cache.S_ρ_qs\nend\n"
  },
  {
    "path": "src/Atmos/Model/tendencies_tracers.jl",
    "content": "##### Moisture tendencies\n\n#####\n##### First order fluxes\n#####\n\nfunction flux(::Tracers{N}, ::Advect, atmos, args) where {N}\n    @unpack state = args\n    u = state.ρu / state.ρ\n    return (state.tracers.ρχ .* u')'\nend\n\n#####\n##### Second order fluxes\n#####\n\nfunction flux(::Tracers{N}, ::Diffusion, atmos, args) where {N}\n    @unpack state, aux, diffusive = args\n    @unpack D_t = args.precomputed.turbulence\n    d_χ = (-D_t) * aux.tracers.δ_χ' .* diffusive.tracers.∇χ\n    return d_χ * state.ρ\nend\n"
  },
  {
    "path": "src/Atmos/Model/thermo_states.jl",
    "content": "#### thermodynamics\n\nexport new_thermo_state, recover_thermo_state\n\n\"\"\"\n    new_thermo_state(atmos::AtmosModel, state::Vars, aux::Vars)\n\nCreate a new thermodynamic state, based on the `state`, and _not_\nthe `aux` state.\n\n!!! note\n    This method calls the iterative saturation adjustment\n    procedure for EquilMoist models.\n\"\"\"\nfunction new_thermo_state end\n\n# First dispatch on compressibility\nnew_thermo_state(atmos::AtmosModel, state::Vars, aux::Vars) =\n    new_thermo_state(compressibility_model(atmos), atmos, state, aux)\n\nnew_thermo_state(::Compressible, atmos::AtmosModel, state::Vars, aux::Vars) =\n    new_thermo_state(\n        atmos,\n        energy_model(atmos),\n        moisture_model(atmos),\n        state,\n        aux,\n    )\nnew_thermo_state(::Anelastic1D, atmos::AtmosModel, state::Vars, aux::Vars) =\n    new_thermo_state_anelastic(atmos, state, aux)\n\n\"\"\"\n    recover_thermo_state(atmos::AtmosModel, state::Vars, aux::Vars)\n\nAn atmospheric thermodynamic state.\n\n!!! warn\n    For now, we are directly calling new_thermo_state to avoid\n    inconsistent aux states in kernels where the aux states are\n    out of sync with the boundary state.\n\n# TODO: Define/call `recover_thermo_state` when it's safely implemented\n  (see https://github.com/CliMA/ClimateMachine.jl/issues/1648)\n\"\"\"\nfunction recover_thermo_state end\n\n# First dispatch on compressibility\nrecover_thermo_state(atmos::AtmosModel, state::Vars, aux::Vars) =\n    new_thermo_state(compressibility_model(atmos), atmos, state, aux)\n\nrecover_thermo_state(\n    ::Compressible,\n    atmos::AtmosModel,\n    state::Vars,\n    aux::Vars,\n) = new_thermo_state(\n    atmos,\n    energy_model(atmos),\n    moisture_model(atmos),\n    state,\n    aux,\n)\n\nrecover_thermo_state(::Anelastic1D, atmos::AtmosModel, state::Vars, aux::Vars) =\n    new_thermo_state_anelastic(atmos, state, aux)\n\nfunction new_thermo_state(\n    atmos::AtmosModel,\n    energy::TotalEnergyModel,\n    moist::DryModel,\n    state::Vars,\n    aux::Vars,\n)\n    param_set = parameter_set(atmos)\n    e_int = internal_energy(atmos, state, aux)\n    return TD.PhaseDry(param_set, e_int, state.ρ)\nend\n\nfunction new_thermo_state(\n    atmos::AtmosModel,\n    energy::TotalEnergyModel,\n    moist::EquilMoist,\n    state::Vars,\n    aux::Vars,\n)\n    e_int = internal_energy(atmos, state, aux)\n    param_set = parameter_set(atmos)\n    return TD.PhaseEquil_ρeq(\n        param_set,\n        state.ρ,\n        e_int,\n        state.moisture.ρq_tot / state.ρ,\n        moist.maxiter,\n        moist.tolerance,\n    )\nend\n\nfunction new_thermo_state(\n    atmos::AtmosModel,\n    energy::TotalEnergyModel,\n    moist::NonEquilMoist,\n    state::Vars,\n    aux::Vars,\n)\n    param_set = parameter_set(atmos)\n    e_int = internal_energy(atmos, state, aux)\n    q = TD.PhasePartition(\n        state.moisture.ρq_tot / state.ρ,\n        state.moisture.ρq_liq / state.ρ,\n        state.moisture.ρq_ice / state.ρ,\n    )\n\n    return TD.PhaseNonEquil{eltype(state), typeof(param_set)}(\n        param_set,\n        e_int,\n        state.ρ,\n        q,\n    )\nend\n\nfunction new_thermo_state(\n    atmos::AtmosModel,\n    energy::θModel,\n    moist::DryModel,\n    state::Vars,\n    aux::Vars,\n)\n    param_set = parameter_set(atmos)\n    θ_liq_ice = state.energy.ρθ_liq_ice / state.ρ\n    return TD.PhaseDry_ρθ(param_set, state.ρ, θ_liq_ice)\nend\n\nfunction new_thermo_state(\n    atmos::AtmosModel,\n    energy::θModel,\n    moist::EquilMoist,\n    state::Vars,\n    aux::Vars,\n)\n    param_set = parameter_set(atmos)\n    θ_liq_ice = state.energy.ρθ_liq_ice / state.ρ\n    return TD.PhaseEquil_ρθq(\n        param_set,\n        state.ρ,\n        θ_liq_ice,\n        state.moisture.ρq_tot / state.ρ,\n        moist.maxiter,\n        moist.tolerance,\n    )\nend\n\nfunction new_thermo_state(\n    atmos::AtmosModel,\n    energy::θModel,\n    moist::NonEquilMoist,\n    state::Vars,\n    aux::Vars,\n)\n    param_set = parameter_set(atmos)\n    θ_liq_ice = state.energy.ρθ_liq_ice / state.ρ\n    q = TD.PhasePartition(\n        state.moisture.ρq_tot / state.ρ,\n        state.moisture.ρq_liq / state.ρ,\n        state.moisture.ρq_ice / state.ρ,\n    )\n\n    return TD.PhaseNonEquil{eltype(state), typeof(param_set)}(\n        param_set,\n        state.ρ,\n        θ_liq_ice,\n        q,\n    )\nend\n"
  },
  {
    "path": "src/Atmos/Model/thermo_states_anelastic.jl",
    "content": "#### thermodynamics\n\nexport new_thermo_state_anelastic, recover_thermo_state_anelastic\n\n\"\"\"\n    new_thermo_state_anelastic(atmos::AtmosModel, state::Vars, aux::Vars)\n\nCreate a new thermodynamic state, based on the `state`, and _not_\nthe `aux` state.\n\n!!! note\n    This method calls the iterative saturation adjustment\n    procedure for EquilMoist models.\n\"\"\"\nnew_thermo_state_anelastic(atmos::AtmosModel, state::Vars, aux::Vars) =\n    new_thermo_state_anelastic(\n        atmos,\n        energy_model(atmos),\n        moisture_model(atmos),\n        state,\n        aux,\n    )\n\n\"\"\"\n    recover_thermo_state_anelastic(atmos::AtmosModel, state::Vars, aux::Vars)\n\nAn atmospheric thermodynamic state.\n\n!!! warn\n    For now, we are directly calling new_thermo_state_anelastic to avoid\n    inconsistent aux states in kernels where the aux states are\n    out of sync with the boundary state.\n\n# TODO: Define/call `recover_thermo_state_anelastic` when it's safely implemented\n  (see https://github.com/CliMA/ClimateMachine.jl/issues/1648)\n\"\"\"\nrecover_thermo_state_anelastic(atmos::AtmosModel, state::Vars, aux::Vars) =\n    new_thermo_state_anelastic(\n        atmos,\n        energy_model(atmos),\n        moisture_model(atmos),\n        state,\n        aux,\n    )\n\nfunction new_thermo_state_anelastic(\n    atmos::AtmosModel,\n    energy::TotalEnergyModel,\n    moist::DryModel,\n    state::Vars,\n    aux::Vars,\n)\n    param_set = parameter_set(atmos)\n    e_int = internal_energy(atmos, state, aux)\n    p = aux.ref_state.p\n    return PhaseDry_pe(param_set, p, e_int)\nend\n\nfunction new_thermo_state_anelastic(\n    atmos::AtmosModel,\n    energy::TotalEnergyModel,\n    moist::EquilMoist,\n    state::Vars,\n    aux::Vars,\n)\n    param_set = parameter_set(atmos)\n    e_int = internal_energy(atmos, state, aux)\n    p = aux.ref_state.p\n    ρ = density(atmos, state, aux)\n    return PhaseEquil_peq(\n        param_set,\n        p,\n        e_int,\n        state.moisture.ρq_tot / ρ,\n        moist.maxiter,\n        moist.tolerance,\n    )\nend\n\nfunction new_thermo_state_anelastic(\n    atmos::AtmosModel,\n    energy::TotalEnergyModel,\n    moist::NonEquilMoist,\n    state::Vars,\n    aux::Vars,\n)\n    param_set = parameter_set(atmos)\n    e_int = internal_energy(atmos, state, aux)\n    p = aux.ref_state.p\n    ρ = density(atmos, state, aux)\n    q = PhasePartition(\n        state.moisture.ρq_tot / ρ,\n        state.moisture.ρq_liq / ρ,\n        state.moisture.ρq_ice / ρ,\n    )\n\n    return PhaseNonEquil_peq(param_set, p, e_int, q)\nend\n"
  },
  {
    "path": "src/Atmos/Model/tracers.jl",
    "content": "# ## [Tracers](@id tracer-model)\n#\n#md # !!! note\n#md #\n#md #     Usage: Enable tracers using a keyword argument in the AtmosModel specification\\\n#md #     `tracers = NoTracer()`\\\n#md #     `tracers = NTracers{N, FT}(δ_χ)` where N is the number of tracers required.\\\n#md #     FT is the float-type and $\\delta_{\\chi}$ is an SVector of diffusivity scaling coefficients\n#\n# In `tracers.jl`, we define the equation sets governing\n# tracer dynamics. Specifically, we address the the equations\n# of tracer motion in conservation form,\n#\n#\nusing DocStringExtensions\n#\n# ### [Equations](@id tracer-eqns)\n# ```math\n# \\frac{\\partial \\rho\\chi}{\\partial t} +  \\nabla \\cdot ( \\rho\\chi u) = \\nabla \\cdot (-\\rho\\delta_{D\\chi}\\mathrm{D_{T}}\\nabla\\chi) + \\rho \\mathrm{S}\n# ```\n# where  $$\\chi$$ represents the tracer species, $$\\mathrm{S}$$ represents the tracer source terms and $$\\delta_{D\\chi} \\mathrm{D_{T}}$$ represents the scaled turbulent eddy diffusivity for each tracer.\n# Currently a default scaling of `1` is supported.\n# The equation as written above corresponds to a single scalar tracer, but can be extended to include\n# multiple independent tracer species.\n#\n# We first define an abstract tracer type, and define the\n# default function signatures. Two options are currently\n# supported. [`NoTracers`](@ref no-tracers),\n# and [`NTracers`](@ref multiple-tracers).\n#\n# ### [Abstract Tracer Type](@id abstract-tracer-type)\n#\n# Default methods for a generic tracer type are defined here.\n#\n\nabstract type TracerModel end\n\nexport TracerModel, NoTracers, NTracers\n\nvars_state(::TracerModel, ::AbstractStateType, FT) = @vars()\n\nfunction atmos_init_aux!(\n    ::TracerModel,\n    ::AtmosModel,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    nothing\nend\nfunction atmos_nodal_update_auxiliary_state!(\n    ::TracerModel,\n    m::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    nothing\nend\nfunction compute_gradient_flux!(\n    ::TracerModel,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    nothing\nend\nfunction compute_gradient_argument!(\n    ::TracerModel,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    nothing\nend\nfunction wavespeed_tracers!(\n    ::TracerModel,\n    wavespeed::Vars,\n    nM,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    nothing\nend\n\n# ### [NoTracers](@id no-tracers)\n# The default tracer type in both the LES and GCM configurations is the\n# no tracer model. (This means no state variables for tracers are being\n# carried around). For the purposes of this model, moist variables are\n# considered separately in `moisture.jl`.\n#\n\"\"\"\n    NoTracers <: TracerModel\nNo tracers. Default model.\n\"\"\"\nstruct NoTracers <: TracerModel end\n\n# ### [NTracers](@id multiple-tracers)\n# Allows users to specify an integer corresponding to the number of\n# tracers required. This can be extended to provide a vector\n# corresponding to a scaling factor for each individual tracer\n# component. Note that tracer naming is not currently supported,\n# i.e. the user must track each tracer variable based on its\n# numerical index. Sources can be added to each tracer based on the\n# same numerical index. Initial profiles must be specified using the\n# `init_state_prognostic!` hook at the experiment level.\n\n\"\"\"\n    NTracers{N, FT} <: TracerModel\nCurrently the simplest way to get n-tracers in an AtmosModel run\nusing the existing machinery. Model input: SVector of\ndiffusivity scaling coefficients. Length of SVector allows number\nof tracers to be inferred. Tracers are currently identified by indices.\n\n# Fields\n#\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct NTracers{N, FT} <: TracerModel\n    \"N-component `SVector` with scaling ratios for tracer diffusivities\"\n    δ_χ::SVector{N, FT}\nend\n\nvars_state(tr::NTracers, ::Prognostic, FT) = @vars(ρχ::typeof(tr.δ_χ))\nvars_state(tr::NTracers, ::Gradient, FT) = @vars(χ::typeof(tr.δ_χ))\nvars_state(tr::NTracers, ::GradientFlux, FT) =\n    @vars(∇χ::SMatrix{3, length(tr.δ_χ), FT, 3 * length(tr.δ_χ)})\nvars_state(tr::NTracers, ::Auxiliary, FT) = @vars(δ_χ::typeof(tr.δ_χ))\n\nfunction atmos_init_aux!(\n    tr::NTracers,\n    am::AtmosModel,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    aux.tracers.δ_χ = tr.δ_χ\nend\nfunction compute_gradient_argument!(\n    tr::NTracers,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ρinv = 1 / state.ρ\n    transform.tracers.χ = state.tracers.ρχ * ρinv\nend\nfunction compute_gradient_flux!(\n    tr::NTracers,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    diffusive.tracers.∇χ = ∇transform.tracers.χ\nend\n\nfunction wavespeed_tracers!(\n    tr::NTracers{N, FT},\n    wavespeed::Vars,\n    nM,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) where {N, FT}\n\n    ρinv = 1 / state.ρ\n    u = ρinv * state.ρu\n    uN = abs(dot(nM, u))\n\n    wavespeed.tracers.ρχ = fill(uN, SVector{N, FT})\n\n    return nothing\nend\n\n#####\n##### Tendency specification\n#####\n\neq_tends(pv::Tracers{N}, m::NTracers{N}, tt::Flux{SecondOrder}) where {N} =\n    (Diffusion(),)\n"
  },
  {
    "path": "src/Atmos/Parameterizations/GravityWaves/README.md",
    "content": "# ClimateMachine-atmos Gravity-waves\nParameterizations of gravity waves\n"
  },
  {
    "path": "src/Atmos/Parameterizations/README.md",
    "content": "# ClimateMachine-atmos Parameterizations\nParameterizations for atmosphere model\n"
  },
  {
    "path": "src/Atmos/Parameterizations/Radiation/README.md",
    "content": "# ClimateMachine-atmos Radiation\nParameterizations of atmospheric radiative transfer\n"
  },
  {
    "path": "src/Atmos/TemperatureProfiles/TemperatureProfiles.jl",
    "content": "module TemperatureProfiles\n\nusing DocStringExtensions\n\nexport TemperatureProfile,\n    IsothermalProfile, DecayingTemperatureProfile, DryAdiabaticProfile\n\nusing CLIMAParameters: AbstractParameterSet\nusing CLIMAParameters.Planet: R_d, MSLP, cp_d, grav, T_surf_ref, T_min_ref\n\n\"\"\"\n    TemperatureProfile\n\nSpecifies the temperature or virtual temperature profile for a reference state.\n\nInstances of this type are required to be callable objects with the following signature\n\n    T,p = (::TemperatureProfile)(param_set::AbstractParameterSet, z::FT) where {FT}\n\nwhere `T` is the temperature or virtual temperature (in K), and `p` is the pressure (in Pa).\n\"\"\"\nabstract type TemperatureProfile{FT} end\n\n\"\"\"\n    IsothermalProfile(param_set, T_virt)\n    IsothermalProfile(param_set, ::Type{FT<:AbstractFloat})\n\nA uniform virtual temperature profile, which is implemented\nas a special case of [`DecayingTemperatureProfile`](@ref).\n\"\"\"\nIsothermalProfile(param_set::AbstractParameterSet, T_virt::FT) where {FT} =\n    DecayingTemperatureProfile{FT}(param_set, T_virt, T_virt)\n\nfunction IsothermalProfile(\n    param_set::AbstractParameterSet,\n    ::Type{FT},\n) where {FT}\n    T_virt = FT(T_surf_ref(param_set))\n    return DecayingTemperatureProfile{FT}(param_set, T_virt, T_virt)\nend\n\n\"\"\"\n    DryAdiabaticProfile{FT} <: TemperatureProfile{FT}\n\n\nA temperature profile that has uniform dry potential temperature `θ`\n\n# Fields\n\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct DryAdiabaticProfile{FT} <: TemperatureProfile{FT}\n    \"Surface temperature (K)\"\n    T_surface::FT\n    \"Minimum temperature (K)\"\n    T_min_ref::FT\n    function DryAdiabaticProfile{FT}(\n        param_set::AbstractParameterSet,\n        T_surface::FT = FT(T_surf_ref(param_set)),\n        _T_min_ref::FT = FT(T_min_ref(param_set)),\n    ) where {FT}\n        return new{FT}(T_surface, _T_min_ref)\n    end\nend\n\n\"\"\"\n    (profile::DryAdiabaticProfile)(\n        param_set::AbstractParameterSet,\n        z::FT,\n    ) where {FT}\n\nReturns dry adiabatic temperature and pressure profiles\nwith zero relative humidity. The temperature is truncated\nto be greater than or equal to `profile.T_min_ref`.\n\"\"\"\nfunction (profile::DryAdiabaticProfile)(\n    param_set::AbstractParameterSet,\n    z::FT,\n) where {FT}\n\n    _R_d::FT = R_d(param_set)\n    _cp_d::FT = cp_d(param_set)\n    _grav::FT = grav(param_set)\n    _MSLP::FT = MSLP(param_set)\n\n    # Temperature\n    Γ = _grav / _cp_d\n    T = max(profile.T_surface - Γ * z, profile.T_min_ref)\n\n    # Pressure\n    p = _MSLP * (T / profile.T_surface)^(_grav / (_R_d * Γ))\n    if T == profile.T_min_ref\n        z_top = (profile.T_surface - profile.T_min_ref) / Γ\n        H_min = _R_d * profile.T_min_ref / _grav\n        p *= exp(-(z - z_top) / H_min)\n    end\n    return (T, p)\nend\n\n\"\"\"\n    DecayingTemperatureProfile{F} <: TemperatureProfile{FT}\n\nA virtual temperature profile that decays smoothly with height `z`, from\n`T_virt_surf` to `T_min_ref` over a height scale `H_t`. The default height\nscale `H_t` is the density scale height evaluated with `T_virt_surf`.\n\n```math\nT_{\\\\text{v}}(z) = \\\\max(T_{\\\\text{v, sfc}} − (T_{\\\\text{v, sfc}} - T_{\\\\text{v, min}}) \\\\tanh(z/H_{\\\\text{t}})\n```\n\n# Fields\n\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct DecayingTemperatureProfile{FT} <: TemperatureProfile{FT}\n    \"Virtual temperature at surface (K)\"\n    T_virt_surf::FT\n    \"Minimum virtual temperature at the top of the atmosphere (K)\"\n    T_min_ref::FT\n    \"Height scale over which virtual temperature drops (m)\"\n    H_t::FT\n    function DecayingTemperatureProfile{FT}(\n        param_set::AbstractParameterSet,\n        _T_virt_surf::FT = FT(T_surf_ref(param_set)),\n        _T_min_ref::FT = FT(T_min_ref(param_set)),\n        H_t::FT = FT(R_d(param_set)) * _T_virt_surf / FT(grav(param_set)),\n    ) where {FT}\n        return new{FT}(_T_virt_surf, _T_min_ref, H_t)\n    end\nend\n\n\nfunction (profile::DecayingTemperatureProfile)(\n    param_set::AbstractParameterSet,\n    z::FT,\n) where {FT}\n    _R_d::FT = R_d(param_set)\n    _grav::FT = grav(param_set)\n    _MSLP::FT = MSLP(param_set)\n\n    # Scale height for surface temperature\n    H_sfc = _R_d * profile.T_virt_surf / _grav\n    H_t = profile.H_t\n    z′ = z / H_t\n    tanh_z′ = tanh(z′)\n\n    ΔTv = profile.T_virt_surf - profile.T_min_ref\n    Tv = profile.T_virt_surf - ΔTv * tanh_z′\n\n    ΔTv′ = ΔTv / profile.T_virt_surf\n    p = -H_t * (z′ + ΔTv′ * (log(1 - ΔTv′ * tanh_z′) - log(1 + tanh_z′) + z′))\n    p /= H_sfc * (1 - ΔTv′^2)\n    p = _MSLP * exp(p)\n    return (Tv, p)\nend\n\nend\n"
  },
  {
    "path": "src/BalanceLaws/BalanceLaws.jl",
    "content": "module BalanceLaws\n\nusing ..VariableTemplates\nusing StaticArrays\n\nexport BalanceLaw,\n    vars_state,\n    number_states,\n    precompute,\n    init_state_prognostic!,\n    init_state_auxiliary!,\n    compute_gradient_flux!,\n    compute_gradient_argument!,\n    transform_post_gradient_laplacian!,\n    flux_first_order!,\n    flux_second_order!,\n    source!,\n    wavespeed,\n    boundary_state!,\n    update_auxiliary_state!,\n    update_auxiliary_state_gradient!,\n    nodal_init_state_auxiliary!,\n    nodal_update_auxiliary_state!,\n    integral_load_auxiliary_state!,\n    integral_set_auxiliary_state!,\n    indefinite_stack_integral!,\n    reverse_indefinite_stack_integral!,\n    reverse_integral_load_auxiliary_state!,\n    reverse_integral_set_auxiliary_state!,\n    parameter_set\n\ninclude(\"state_types.jl\")\ninclude(\"interface.jl\")\ninclude(\"boundaryconditions.jl\")\ninclude(\"tendency_types.jl\")\ninclude(\"show_tendencies.jl\")\ninclude(\"sum_tendencies.jl\")\ninclude(\"prog_prim_conversion.jl\")\ninclude(\"vars_wrappers.jl\")\ninclude(\"kernels.jl\")\n\nend\n"
  },
  {
    "path": "src/BalanceLaws/Problems.jl",
    "content": "module Problems\n\nexport AbstractProblem,\n    init_state_prognostic!, init_state_auxiliary!, boundary_state!\n\n\"\"\"\n    AbstractProblem\n\nAn abstract type representing the initial conditions and\nthe boundary conditions for a `BalanceLaw`.\n\nSubtypes `P` should define the methods below.\n\"\"\"\nabstract type AbstractProblem end\n\n\"\"\"\n    init_state_prognostic!(\n        ::P,\n        ::BalanceLaw,\n        state_prognostic::Vars,\n        state_auxiliary::Vars,\n        localgeo,\n        t,\n        args...,\n    )\n\nInitialize the prognostic state variables at ``t = 0``.\n\"\"\"\nfunction init_state_prognostic! end\n\n\"\"\"\n    init_state_auxiliary!(\n        ::P,\n        ::BalanceLaw,\n        state_auxiliary::Vars,\n        geom::LocalGeometry,\n    )\n\nInitialize the auxiliary state, at ``t = 0``.\n\"\"\"\nfunction init_state_auxiliary! end\n\nend # module\n"
  },
  {
    "path": "src/BalanceLaws/boundaryconditions.jl",
    "content": "# eventually boundary conditions should be a subtype of this\n# we don't enforce it currently to make the transition easier\nabstract type BoundaryCondition end\n\n\n\"\"\"\n    boundary_conditions(::BL)\n\nDefine the set of boundary conditions for the balance law `BL`. This should\nreturn a tuple, where a boundary tagged with the integer `i` will use the `i`th\nelement of the tuple.\n\"\"\"\nfunction boundary_conditions end\n\n\n\"\"\"\n    boundary_state!(\n        ::NumericalFluxGradient,\n        ::BC\n        ::BL,\n        state_prognostic⁺::Vars,\n        state_auxiliary⁺::Vars,\n        normal⁻,\n        state_prognostic⁻::Vars,\n        state_auxiliary⁻::Vars,\n        t\n    )\n    boundary_state!(\n        ::NumericalFluxFirstOrder,\n        ::BC\n        ::BL,\n        state_prognostic⁺::Vars,\n        state_auxiliary⁺::Vars,\n        normal⁻,\n        state_prognostic⁻::Vars,\n        state_auxiliary⁻::Vars,\n        t\n    )\n    boundary_state!(\n        ::NumericalFluxSecondOrder,\n        ::BC\n        ::BL,\n        state_prognostic⁺::Vars,\n        state_gradient_flux⁺::Vars,\n        state_auxiliary⁺:\n        Vars, normal⁻,\n        state_prognostic⁻::Vars,\n        state_gradient_flux⁻::Vars,\n        state_auxiliary⁻::Vars,\n        t\n    )\n\nSpecify the opposite (+ side) face for the boundary condition type `BC` with balance law `BL`.\n\n - `NumericalFluxGradient` numerical flux (internal method)\n - `NumericalFluxFirstOrder` first-order unknowns\n - `NumericalFluxSecondOrder` second-order unknowns\n\n\"\"\"\nfunction boundary_state! end\n"
  },
  {
    "path": "src/BalanceLaws/interface.jl",
    "content": "#### Balance Law Interface\n\"\"\"\n    abstract type BalanceLaw end\n\nAn abstract type representing a PDE balance law of the form:\n\n```math\n\\\\frac{dq}{dt} = \\\\nabla \\\\cdot F_1(q, a, t) + \\\\nabla \\\\cdot F_2(q, \\\\nabla g, h, a, t) + S(q, \\\\nabla g, a, t)\n```\nwhere:\n- ``q`` is the prognostic state,\n- ``a`` is the auxiliary state,\n- ``g = G(q, a, t)`` is the gradient state (variables of which we compute the\n  gradient),\n- ``h`` is the hyperdiffusive state.\n\nSubtypes of `BalanceLaw` should define the following interfaces:\n- [`vars_state`](@ref) to define the prognostic, auxiliary and intermediate\n  variables.\n- [`flux_first_order!`](@ref) to compute ``F_1``\n- [`flux_second_order!`](@ref) to compute ``F_2``\n- [`source!`](@ref) to compute ``S``\n\nIf `vars(bl, ::GradientFlux, FT)` is non-empty, then the following should be\ndefined:\n- [`compute_gradient_argument!`](@ref) to compute ``G``\n- [`compute_gradient_flux!`](@ref) is a linear transformation of ``\\\\nabla g``\n\nIf `vars(bl, ::Hyperdiffusive, FT)` is non-empty, then the following should be\ndefined:\n- [`transform_post_gradient_laplacian!`](@ref)\n\nAdditional functions:\n- [`wavespeed`](@ref) if using the Rusanov numerical flux.\n- [`boundary_state!`](@ref) if using non-periodic boundary conditions.\n\"\"\"\nabstract type BalanceLaw end\n\n\"\"\"\n    BalanceLaws.vars_state(::BL, ::AbstractStateType, FT)\n\nDefines the state variables of a [`BalanceLaw`](@ref) subtype `BL` with floating\npoint type `FT`.\n\nFor each [`AbstractStateType`](@ref), this should return a `NamedTuple` type,\nwith element type either `FT`, an `SArray` with element type `FT` or another\n`NamedTuple` satisfying the same property.\n\nFor convenience, we recommend using the [`VariableTemplates.@vars`](@ref) macro.\n\n# Example\n```julia\nstruct MyBalanceLaw <: BalanceLaw end\n\nBalanceLaws.vars_state(::MyBalanceLaw, ::Prognostic, FT) =\n    @vars(x::FT, y::SVector{3, FT})\nBalanceLaws.vars_state(::MyBalanceLaw, ::Auxiliary, FT) =\n    @vars(components::@vars(a::FT, b::FT))\n```\n\"\"\"\nfunction vars_state end\n\n# Fallback: no variables\nvars_state(::BalanceLaw, ::AbstractStateType, FT) = @vars()\n\n\"\"\"\n    init_state_prognostic!(\n        ::BL,\n        state_prognostic::Vars,\n        state_auxiliary::Vars,\n        localgeo,\n        args...,\n    )\n\nSets the initial state of the prognostic variables `state_prognostic` at each\nnode for a [`BalanceLaw`](@ref) subtype `BL`.\n\"\"\"\nfunction init_state_prognostic! end\n\n# TODO: make these functions consistent with init_state_prognostic!\n\"\"\"\n    nodal_init_state_auxiliary!(::BL, state_auxiliary, state_temporary, geom)\n\nSets the initial state of the auxiliary variables `state_auxiliary` at each\nnode for a [`BalanceLaw`](@ref) subtype `BL`.\n\nSee also [`init_state_auxiliary!`](@ref).\n\"\"\"\nfunction nodal_init_state_auxiliary!(m::BalanceLaw, aux, tmp, geom) end\n\n\n\"\"\"\n    init_state_auxiliary!(\n        ::BL,\n        statearray_auxiliary,\n        geom::LocalGeometry,\n    )\n\nSets the initial state of the auxiliary variables `state_auxiliary` at each node\nfor a [`BalanceLaw`](@ref) subtype `BL`. By default this calls\n[`nodal_init_state_auxiliary!`](@ref).\n\"\"\"\nfunction init_state_auxiliary!(\n    balance_law::BalanceLaw,\n    statearray_auxiliary,\n    grid,\n    direction,\n)\n    init_state_auxiliary!(\n        balance_law,\n        nodal_init_state_auxiliary!,\n        statearray_auxiliary,\n        grid,\n        direction,\n    )\nend\n\n\"\"\"\n    flux_first_order!(\n        ::BL,\n        flux::Grad,\n        state_prognostic::Vars,\n        state_auxiliary::Vars,\n        t::Real,\n        direction\n    )\n\nSets the first-order (hyperbolic) `flux` terms for a [`BalanceLaw`](@ref) subtype `BL`.\n\"\"\"\nfunction flux_first_order! end\n\n\"\"\"\n    flux_second_order!(\n        ::BL,\n        flux::Grad,\n        state_prognostic::Vars,\n        state_gradient_flux::Vars,\n        hyperdiffusive::Vars,\n        state_auxiliary::Vars,\n        t::Real\n    )\n\nSets second-order (parabolic) `flux` terms for a [`BalanceLaw`](@ref) subtype `BL`.\n\"\"\"\nfunction flux_second_order! end\n\n\"\"\"\n    source!(\n        ::BL,\n        source::Vars,\n        state_prognostic::Vars,\n        diffusive::Vars,\n        state_auxiliary::Vars,\n        t::Real\n    )\n\nCompute non-conservative source terms for a [`BalanceLaw`](@ref) subtype `BL`.\n\"\"\"\nfunction source! end\n\n\"\"\"\n    compute_gradient_argument!(\n        ::BL,\n        transformstate::Vars,\n        state_prognostic::Vars,\n        state_auxiliary::Vars,\n        t::Real\n    )\n\nTransformation of state variables `state_prognostic` to variables\n`transformstate` of which gradients are computed for a [`BalanceLaw`](@ref)\nsubtype `BL`.\n\"\"\"\nfunction compute_gradient_argument! end\n\nfunction compute_gradient_argument!(\n    balance_law::BalanceLaw,\n    transformstate::AbstractArray,\n    state_prognostic::AbstractArray,\n    state_auxiliary::AbstractArray,\n    t,\n)\n    FT = eltype(transformstate)\n    compute_gradient_argument!(\n        balance_law,\n        Vars{vars_state(balance_law, Gradient(), FT)}(transformstate),\n        Vars{vars_state(balance_law, Prognostic(), FT)}(state_prognostic),\n        Vars{vars_state(balance_law, Auxiliary(), FT)}(state_auxiliary),\n        t,\n    )\nend\n\n\n\"\"\"\n    compute_gradient_flux!(\n        ::BL,\n        state_gradient_flux::Vars,\n        ∇transformstate::Grad,\n        state_prognostic::Vars,\n        state_auxiliary::Vars,\n        t::Real\n    )\n\nTransformation of gradients to the diffusive variables for a\n[`BalanceLaw`](@ref) subtype `BL`. This should be a linear function of\n`∇transformstate`\n\"\"\"\nfunction compute_gradient_flux! end\n\nfunction compute_gradient_flux!(\n    balance_law::BalanceLaw,\n    state_gradient_flux::AbstractArray,\n    ∇transformstate::AbstractArray,\n    state_prognostic::AbstractArray,\n    state_auxiliary::AbstractArray,\n    t,\n)\n    FT = eltype(state_gradient_flux)\n    compute_gradient_flux!(\n        balance_law,\n        Vars{vars_state(balance_law, GradientFlux(), FT)}(state_gradient_flux),\n        Grad{vars_state(balance_law, Gradient(), FT)}(∇transformstate),\n        Vars{vars_state(balance_law, Prognostic(), FT)}(state_prognostic),\n        Vars{vars_state(balance_law, Auxiliary(), FT)}(state_auxiliary),\n        t,\n    )\nend\n\n\"\"\"\n    transform_post_gradient_laplacian!(\n        ::BL,\n        Qhypervisc_div::Vars,\n        ∇Δtransformstate::Grad,\n        state_auxiliary::Vars,\n        t::Real\n    )\n\nTransformation of Laplacian gradients to the hyperdiffusive variables for a\n[`BalanceLaw`](@ref) subtype `BL`.\n\"\"\"\nfunction transform_post_gradient_laplacian! end\n\n\"\"\"\n    wavespeed(\n        ::BL,\n        n⁻,\n        state_prognostic::Vars,\n        state_auxiliary::Vars,\n        t::Real,\n        direction\n    )\n\nWavespeed in the direction `n⁻` for a [`BalanceLaw`](@ref) subtype `BL`. This is\nrequired to be defined if using a `RusanovNumericalFlux` numerical flux.\n\"\"\"\nfunction wavespeed end\n\n\n\"\"\"\n    update_auxiliary_state!(\n        dg::DGModel,\n        m::BalanceLaw,\n        statearray_aux,\n        t::Real,\n        elems::UnitRange,\n        [diffusive=false]\n    )\n\nHook to update the auxiliary state variables before calling any other functions.\n\nBy default, this calls [`nodal_update_auxiliary_state!`](@ref) at each node.\n\nIf `diffusive=true`, then `state_gradflux` is also passed to\n`nodal_update_auxiliary_state!`.\n\"\"\"\nfunction update_auxiliary_state!(\n    dg,\n    balance_law::BalanceLaw,\n    state_prognostic,\n    t,\n    elems,\n    diffusive = false,\n)\n    update_auxiliary_state!(\n        nodal_update_auxiliary_state!,\n        dg,\n        balance_law,\n        state_prognostic,\n        t,\n        elems;\n        diffusive = diffusive,\n    )\nend\n\n\"\"\"\n    nodal_update_auxiliary_state!(::BL, state_prognostic, state_auxiliary, [state_gradflux,] t)\n\nUpdate the auxiliary state variables at each node for a [`BalanceLaw`](@ref)\nsubtype `BL`. By default it does nothing.\n\nCalled by [`update_auxiliary_state!`](@ref).\n\"\"\"\nfunction nodal_update_auxiliary_state!(args...)\n    nothing\nend\n\n\"\"\"\n    update_auxiliary_state_gradient!(\n        dg::DGModel,\n        m::BalanceLaw,\n        statearray_aux,\n        t::Real,\n        elems::UnitRange,\n        [diffusive=false]\n    )\n\nHook to update the auxiliary state variables after the gradient computation.\n\nBy default, this calls nothing.\n\nIf `diffusive=true`, then `state_gradflux` is also passed to\n`nodal_update_auxiliary_state!`.\n\"\"\"\nfunction update_auxiliary_state_gradient! end\n\n# upward integrals\n\"\"\"\n    integral_load_auxiliary_state!(::BL, integrand, state_prognostic, state_aux)\n\nSpecify variables `integrand` which will have their upward integrals computed.\n\nSee also [`UpwardIntegrals`](@ref)\n\"\"\"\nfunction integral_load_auxiliary_state! end\n\n\"\"\"\n    integral_set_auxiliary_state!(::BL, state_aux, integral)\n\nUpdate auxiliary variables based on the upward integral `integral` defined in\n[`integral_load_auxiliary_state!`](@ref).\n\"\"\"\nfunction integral_set_auxiliary_state! end\n\n\"\"\"\n    indefinite_stack_integral!\n\nCompute indefinite integral along stack.\n\"\"\"\nfunction indefinite_stack_integral! end\n\n# downward integrals\n\"\"\"\n    reverse_integral_load_auxiliary_state!(::BL, integrand, state_prognostic, state_aux)\n\nSpecify variables `integrand` which will have their downward integrals computed.\n\nSee also [`DownwardIntegrals`](@ref)\n\"\"\"\nfunction reverse_integral_load_auxiliary_state! end\n\n\"\"\"\n    reverse_integral_set_auxiliary_state!(::BL, state_aux, integral)\n\nUpdate auxiliary variables based on the downward integral `integral` defined in\n[`reverse_integral_load_auxiliary_state!`](@ref).\n\"\"\"\nfunction reverse_integral_set_auxiliary_state! end\n\n\"\"\"\n    reverse_indefinite_stack_integral!\n\nCompute reverse indefinite integral along stack.\n\"\"\"\nfunction reverse_indefinite_stack_integral! end\n\n\"\"\"\n    function state_to_entropy_variables!(\n        balancelaw::BalanceLaw,\n        state_entropy::Vars,\n        state_prognostic::Vars,\n        state_auxiliary::Vars,\n    )\nCompute entropy variables `state_entropy` from a given `state_prognostic` and\n`state_auxiliary`\n\"\"\"\nfunction state_to_entropy_variables!(\n    balancelaw::BalanceLaw,\n    entropy::AbstractArray{FT},\n    state::AbstractArray{FT},\n    aux::AbstractArray{FT},\n) where {FT}\n    state_to_entropy_variables!(\n        balancelaw,\n        Vars{vars_state(balancelaw, Entropy(), FT)}(entropy),\n        Vars{vars_state(balancelaw, Prognostic(), FT)}(state),\n        Vars{vars_state(balancelaw, Auxiliary(), FT)}(aux),\n    )\nend\n\n\"\"\"\n    function entropy_variables_to_state!(\n        balancelaw::BalanceLaw,\n        state_prognostic::Vars,\n        state_auxiliary::Vars,\n        state_entropy::Vars,\n    )\nCompute `state_prognostic` and relevant componennts of `state_auxiliary` from\nthe entropy variables `state_entropy`\n\"\"\"\nfunction entropy_variables_to_state!(\n    balancelaw::BalanceLaw,\n    state::AbstractArray{FT},\n    aux::AbstractArray{FT},\n    entropy::AbstractArray{FT},\n) where {FT}\n    entropy_variables_to_state!(\n        balancelaw,\n        Vars{vars_state(balancelaw, Prognostic(), FT)}(state),\n        Vars{vars_state(balancelaw, Auxiliary(), FT)}(aux),\n        Vars{vars_state(balancelaw, Entropy(), FT)}(entropy),\n    )\nend\n\n\"\"\"\n    function state_to_entropy(\n        balancelaw::BalanceLaw,\n        state_prognostic::Vars,\n        state_auxiliary::Vars,\n    )\nCompute entropy from a given `state_prognostic` and `state_auxiliary`\n\"\"\"\nfunction state_to_entropy(\n    balancelaw::BalanceLaw,\n    state::AbstractArray{FT},\n    aux::AbstractArray{FT},\n) where {FT}\n    state_to_entropy(\n        balancelaw,\n        Vars{vars_state(balancelaw, Prognostic(), FT)}(state),\n        Vars{vars_state(balancelaw, Auxiliary(), FT)}(aux),\n    )\nend\n\n\n\n# Internal methods\nnumber_states(m::BalanceLaw, st::AbstractStateType, FT = Int) =\n    varsize(vars_state(m, st, FT))\n\n### split explicit functions\nfunction initialize_states! end\nfunction tendency_from_slow_to_fast! end\nfunction cummulate_fast_solution! end\nfunction reconcile_from_fast_to_slow! end\n\nparameter_set(balance_law) = balance_law.param_set\n\n\"\"\"\n    sub_model(::BalanceLaw, ::Type{AbstractSubModel})\n\nReturns a tuple of balance law properties\nwhose supertypes are `AbstractSubModel`.\n\"\"\"\nfunction sub_model end\n"
  },
  {
    "path": "src/BalanceLaws/kernels.jl",
    "content": "\n\"\"\"\n    flux_first_order!(\n        bl::BalanceLaw,\n        flux::Grad,\n        state::Vars,\n        aux::Vars,\n        t::Real\n    )\n\nComputes (and assembles) flux terms `F¹(Y)` in:\n\n```\n∂Y\n-- + ∇ • F¹(Y) + ∇ • F²(Y,G) = S(Y, G),     G = ∇Y\n∂t\n```\n\nComputes and assembles non-diffusive\nfluxes in the model equations.\n\nFor this fallback to work, several methods must be defined:\n - [`prognostic_vars`](@ref)\n - [`eq_tends`](@ref)\n - [`get_prog_state`](@ref)\noptionally,\n - [`precompute`](@ref)\nand individual [`flux`](@ref) kernels that\nare defined for each type that `eq_tends` returns.\n\"\"\"\n@inline function flux_first_order!(\n    bl::BalanceLaw,\n    flux,\n    state,\n    aux,\n    t,\n    direction,\n)\n\n    tend = Flux{FirstOrder}()\n    _args = (; state, aux, t, direction)\n    args = merge(_args, (precomputed = precompute(bl, _args, tend),))\n\n    map(prognostic_vars(bl)) do prog\n        var, name = get_prog_state(flux, prog)\n        val = Σfluxes(prog, eq_tends(prog, bl, tend), bl, args)\n        setproperty!(var, name, val)\n    end\n    nothing\nend\n\n\"\"\"\n    flux_second_order!(\n        bl::BalanceLaw,\n        flux::Grad,\n        state::Vars,\n        diffusive::Vars,\n        hyperdiffusive::Vars,\n        aux::Vars,\n        t::Real\n    )\n\nComputes (and assembles) flux terms `F²(Y, G)` in:\n\n```\n∂Y\n-- + ∇ • F¹(Y) + ∇ • F²(Y,G) = S(Y, G),     G = ∇Y\n∂t\n```\n\nDiffusive fluxes in BalanceLaw. Viscosity, diffusivity are calculated\nin the turbulence subcomponent and accessed within the diffusive flux\nfunction. Contributions from subcomponents are then assembled (pointwise).\n\nFor this fallback to work, several methods must be defined:\n - [`prognostic_vars`](@ref)\n - [`eq_tends`](@ref)\n - [`get_prog_state`](@ref)\noptionally,\n - [`precompute`](@ref)\nand individual [`flux`](@ref) kernels that\nare defined for each type that `eq_tends` returns.\n\"\"\"\n@inline function flux_second_order!(\n    bl::BalanceLaw,\n    flux,\n    state,\n    diffusive,\n    hyperdiffusive,\n    aux,\n    t,\n)\n    tend = Flux{SecondOrder}()\n    _args = (; state, aux, t, diffusive, hyperdiffusive)\n    args = merge(_args, (precomputed = precompute(bl, _args, tend),))\n\n    map(prognostic_vars(bl)) do prog\n        var, name = get_prog_state(flux, prog)\n        val = Σfluxes(prog, eq_tends(prog, bl, tend), bl, args)\n        setproperty!(var, name, val)\n    end\n    nothing\nend\n\n\"\"\"\n    source!(\n        bl::BalanceLaw,\n        source::Vars,\n        state::Vars,\n        diffusive::Vars,\n        aux::Vars,\n        t::Real,\n        direction::Direction,\n    )\n\nComputes (and assembles) source terms `S(Y)` in:\n\n```\n∂Y\n-- + ∇ • F¹(Y) + ∇ • F²(Y,G) = S(Y, G),     G = ∇Y\n∂t\n```\n\nFor this fallback to work, several methods must be defined:\n - [`prognostic_vars`](@ref)\n - [`eq_tends`](@ref)\n - [`get_prog_state`](@ref)\noptionally,\n - [`precompute`](@ref)\nand individual [`source`](@ref) kernels that\nare defined for each type that `eq_tends` returns.\n\"\"\"\nfunction source!(bl::BalanceLaw, source, state, diffusive, aux, t, direction)\n    tend = Source()\n    _args = (; state, aux, t, direction, diffusive)\n    args = merge(_args, (precomputed = precompute(bl, _args, tend),))\n\n    map(prognostic_vars(bl)) do prog\n        var, name = get_prog_state(source, prog)\n        val = Σsources(prog, eq_tends(prog, bl, tend), bl, args)\n        setproperty!(var, name, val)\n    end\n    nothing\nend\n"
  },
  {
    "path": "src/BalanceLaws/prog_prim_conversion.jl",
    "content": "# Add `Primitive` type to BalanceLaws, AtmosModel\n\n# Vars wrapper\nfunction prognostic_to_primitive!(\n    bl::BalanceLaw,\n    prim::AbstractArray,\n    prog::AbstractArray,\n    aux::AbstractArray,\n)\n    FT = eltype(prim)\n    prognostic_to_primitive!(\n        bl,\n        Vars{vars_state(bl, Primitive(), FT)}(prim),\n        Vars{vars_state(bl, Prognostic(), FT)}(prog),\n        Vars{vars_state(bl, Auxiliary(), FT)}(aux),\n    )\nend\nfunction primitive_to_prognostic!(\n    bl::BalanceLaw,\n    prog::AbstractArray,\n    prim::AbstractArray,\n    aux::AbstractArray,\n)\n    FT = eltype(prog)\n    primitive_to_prognostic!(\n        bl,\n        Vars{vars_state(bl, Prognostic(), FT)}(prog),\n        Vars{vars_state(bl, Primitive(), FT)}(prim),\n        Vars{vars_state(bl, Auxiliary(), FT)}(aux),\n    )\nend\n\n# By default the primitive is the prognostic\nvars_state(bl::BalanceLaw, ::Primitive, FT) = vars_state(bl, Prognostic(), FT)\n\nfunction prognostic_to_primitive!(bl, prim::Vars, prog::Vars, aux)\n    prim_arr = parent(prim)\n    prog_arr = parent(prog)\n    prim_arr .= prog_arr\nend\nfunction primitive_to_prognostic!(bl, prog::Vars, prim::Vars, aux)\n    prim_arr = parent(prim)\n    prog_arr = parent(prog)\n    prog_arr .= prim_arr\nend\n\n\n\"\"\"\n    construct_face_auxiliary_state!(\n        bl::BalanceLaw,\n        aux_face::AbstractArray,\n        aux_cell::AbstractArray,\n        Δz::FT\n    )\n\nDefault constructor\n - `bl` balance law\n - `aux_face` face auxiliary variables to be constructed\n - `aux_cell` cell center auxiliary variable\n - `Δz` cell vertical size\n\"\"\"\nfunction construct_face_auxiliary_state!(\n    bl,\n    aux_face::AbstractArray,\n    aux_cell::AbstractArray,\n    Δz::FT,\n) where {FT <: Real}\n    # TODO: is this safe/correct?\n    aux_face .= aux_cell\nend\n"
  },
  {
    "path": "src/BalanceLaws/show_tendencies.jl",
    "content": "##### Show tendencies\n\nexport show_tendencies\n\nusing PrettyTables\n\nformat_tend(tend_type) = \"$(nameof(typeof(tend_type)))\"\n\nformat_tends(tend_types) = \"(\" * join(format_tend.(tend_types), \", \") * \")\"\n\n\"\"\"\n    show_tendencies(\n        bl;\n        include_module = false,\n        table_complete = false,\n    )\n\nShow a table of the tendencies for each\nprognostic variable for the given balance law.\n\n## Arguments\n - `include_module[ = false]` will print not remove the module where each\n   prognostic variable is defined (e.g., `Atmos.Mass`).\n - `table_complete[ = false]` will print a warning (if false) that the\n   tendency table is incomplete.\n\nRequires definitions for\n - [`prognostic_vars`](@ref)\n - [`eq_tends`](@ref)\nfor the balance law.\n\"\"\"\nfunction show_tendencies(bl; include_module = false, table_complete = false)\n    prog_vars = prognostic_vars(bl)\n    if !isempty(prog_vars)\n        header = [\n            \"Equation\" \"Flux{FirstOrder}\" \"Flux{SecondOrder}\" \"Source\"\n            \"(Y_i)\" \"(F_1)\" \"(F_2)\" \"(S)\"\n        ]\n        if include_module\n            eqs = collect(string.(typeof.(prog_vars)))\n        else\n            eqs = collect(last.(split.(string.(typeof.(prog_vars)), \".\")))\n        end\n        fmt_tends(tt) = map(prog_vars) do pv\n            format_tends(eq_tends(pv, bl, tt))\n        end |> collect\n        F1 = fmt_tends(Flux{FirstOrder}())\n        F2 = fmt_tends(Flux{SecondOrder}())\n        S = fmt_tends(Source())\n        data = hcat(eqs, F1, F2, S)\n        table_complete || @warn \"This table is temporarily incomplete\"\n        println(\"\\nPDE: ∂_t Y_i + (∇•F_1(Y))_i + (∇•F_2(Y,G)))_i = (S(Y,G))_i\")\n        pretty_table(\n            data,\n            header,\n            header_crayon = crayon\"yellow bold\",\n            subheader_crayon = crayon\"green bold\",\n            crop = :none,\n        )\n        println(\"\")\n    else\n        msg = \"Defining `prognostic_vars` and\\n\"\n        msg *= \"`eq_tends` for $(nameof(typeof(bl))) will\\n\"\n        msg *= \"enable printing a table of tendencies.\"\n        @info msg\n    end\n    return nothing\nend\n"
  },
  {
    "path": "src/BalanceLaws/state_types.jl",
    "content": "#### State types\n\nexport AbstractStateType,\n    Prognostic,\n    Primitive,\n    Auxiliary,\n    Gradient,\n    GradientFlux,\n    GradientLaplacian,\n    Hyperdiffusive,\n    UpwardIntegrals,\n    DownwardIntegrals,\n    Entropy\n\n\"\"\"\n    AbstractStateType\n\nSubtypes of this describe the variables used by different parts of a [`BalanceLaw`](@ref):\n- [`Prognostic`](@ref)\n- [`Primitive`](@ref)\n- [`Auxiliary`](@ref)\n- [`Gradient`](@ref)\n- [`GradientFlux`](@ref)\n- [`GradientLaplacian`](@ref)\n- [`Hyperdiffusive`](@ref)\n- [`UpwardIntegrals`](@ref)\n- [`DownwardIntegrals`](@ref)\n\nSee also [`vars_state`](@ref).\n\"\"\"\nabstract type AbstractStateType end\n\n\"\"\"\n    Prognostic <: AbstractStateType\n\nPrognostic variables in the PDE system,\nwhich are specified by the [`BalanceLaw`](@ref), and\nsolved for by the ODE solver.\n\"\"\"\nstruct Prognostic <: AbstractStateType end\n\n\"\"\"\n    Primitive <: AbstractStateType\n\nPrimitive variables, which are specified\nby the [`BalanceLaw`](@ref).\n\"\"\"\nstruct Primitive <: AbstractStateType end\n\n\"\"\"\n    Auxiliary <: AbstractStateType\n\nAuxiliary variables help serve several purposes:\n\n - Pre-compute and store \"expensive\" variables,\n   for example, quantities computed in vertical\n   integrals.\n - Diagnostic exports\n\"\"\"\nstruct Auxiliary <: AbstractStateType end\n\n\"\"\"\n    Gradient <: AbstractStateType\n\nVariables whose gradients must be computed.\n\"\"\"\nstruct Gradient <: AbstractStateType end\n\n\"\"\"\n    GradientFlux <: AbstractStateType\n\nFlux variables, which are functions of gradients.\n\"\"\"\nstruct GradientFlux <: AbstractStateType end\n\n\"\"\"\n    GradientLaplacian <: AbstractStateType\n\nGradient-Laplacian variables.\n\"\"\"\nstruct GradientLaplacian <: AbstractStateType end\n\n\"\"\"\n    Hyperdiffusive <: AbstractStateType\n\nHyper-diffusive variables\n\"\"\"\nstruct Hyperdiffusive <: AbstractStateType end\n\n\"\"\"\n    UpwardIntegrals <: AbstractStateType\n\nVariables computed in upward integrals\n\"\"\"\nstruct UpwardIntegrals <: AbstractStateType end\n\n\"\"\"\n    DownwardIntegrals <: AbstractStateType\n\nVariables computed in downward integrals\n\"\"\"\nstruct DownwardIntegrals <: AbstractStateType end\n\n\"\"\"\n    Entropy <: AbstractStateType\nEntropy variables\n\"\"\"\nstruct Entropy <: AbstractStateType end\n"
  },
  {
    "path": "src/BalanceLaws/sum_tendencies.jl",
    "content": "##### Sum wrapper\n\nexport Σfluxes, Σsources\n\n\"\"\"\n    flux\n\nAn individual flux.\nSee [`BalanceLaw`](@ref) for more info.\n\"\"\"\nfunction flux end\n\n\"\"\"\n    source\n\nAn individual source.\nSee [`BalanceLaw`](@ref) for more info.\n\"\"\"\nfunction source end\n\n\"\"\"\n    ntuple_sum(nt::NTuple{N,T}) where {N, T}\n\nsum of `NTuple`, which requires more strict\ntype input than `sum`. This is added to better\nsynchronize the success/failure between CPU/GPU\nruns to help improve debugging.\n\"\"\"\nntuple_sum(nt::NTuple{N, T}) where {N, T} = sum(nt)\n\n\"\"\"\n    Σfluxes(fluxes::NTuple, bl, args)\n\nSum of the fluxes where\n - `fluxes` is an `NTuple{N, TendencyDef{Flux{O}}} where {N, O}`\n - `bl` is the balance law\n - `args` are the arguments passed to the individual `flux` functions\n\"\"\"\nfunction Σfluxes(\n    pv::PV,\n    fluxes::NTuple{N, TendencyDef{Flux{O}}},\n    bl,\n    args,\n) where {N, O, PV}\n    return ntuple_sum(\n        ntuple(Val(N)) do i\n            projection(pv, bl, fluxes[i], args, flux(pv, fluxes[i], bl, args))\n        end,\n    )\nend\n\n# Emptry scalar case:\nfunction Σfluxes(\n    pv::PV,\n    fluxes::NTuple{0, TendencyDef{Flux{O}}},\n    args...,\n) where {O, PV}\n    return SVector(0, 0, 0)\nend\n\n# Emptry vector case:\nfunction Σfluxes(\n    pv::PV,\n    fluxes::NTuple{0, TendencyDef{Flux{O}}},\n    args...,\n) where {O, PV <: AbstractMomentumVariable}\n    return SArray{Tuple{3, 3}}(ntuple(i -> 0, 9))\nend\n\n# Emptry tracer case:\nfunction Σfluxes(\n    pv::PV,\n    fluxes::NTuple{0, TendencyDef{Flux{O}}},\n    args...,\n) where {O, N, PV <: AbstractTracersVariable{N}}\n    return SArray{Tuple{3, N}}(ntuple(i -> 0, 3 * N))\nend\n\n\"\"\"\n    Σsources(sources::NTuple, bl, args)\n\nSum of the sources where\n - `sources` is an `NTuple{N, TendencyDef{Source}} where {N}`\n - `bl` is the balance law\n - `args` are the arguments passed to the individual `source` functions\n\"\"\"\nfunction Σsources(\n    pv::PV,\n    sources::NTuple{N, TendencyDef{Source}},\n    bl,\n    args,\n) where {N, PV}\n    return ntuple_sum(\n        ntuple(Val(N)) do i\n            projection(\n                pv,\n                bl,\n                sources[i],\n                args,\n                source(pv, sources[i], bl, args),\n            )\n        end,\n    )\nend\n\n# Emptry scalar case:\nfunction Σsources(\n    pv::PV,\n    sources::NTuple{0, TendencyDef{Source}},\n    args...,\n) where {PV}\n    return 0\nend\n\n# Emptry vector case:\nfunction Σsources(\n    pv::AbstractMomentumVariable,\n    sources::NTuple{0, TendencyDef{Source}},\n    args...,\n)\n    return SVector(0, 0, 0)\nend\n\n# Emptry tracer case:\nfunction Σsources(\n    pv::AbstractTracersVariable{N},\n    sources::NTuple{0, TendencyDef{Source}},\n    args...,\n) where {N}\n    return SArray{Tuple{N}}(ntuple(i -> 0, N))\nend\n"
  },
  {
    "path": "src/BalanceLaws/tendency_types.jl",
    "content": "#### Tendency types\n\n# Terminology:\n#\n# `∂_t Yᵢ + (∇•F₁(Y))ᵢ + (∇•F₂(Y,G)))ᵢ = (S(Y,G))ᵢ`\n# `__Tᵤ__   ____T₁____   ______T₂______   ___S___`\n#\n#  - `Yᵢ` - the i-th prognostic variable\n#  - `Y` - the prognostic state (column vector)\n#  - `G = ∇Y` - the gradient of the prognostic state (rank 2 tensor)\n#  - `F₁` - the first order tendency flux (rank 2 tensor)\n#  - `F₂` - the second order tendency flux (rank 2 tensor)\n#  - `Tᵤ` - the explicit time derivative (column vector)\n#  - `T₁` - the first order flux divergence (column vector)\n#  - `T₂` - the second order flux divergence (column vector)\n#  - `S` - the non-conservative source (column vector)\n\nusing DispatchedTuples\n\nexport AbstractPrognosticVariable,\n    AbstractMomentumVariable,\n    AbstractEnergyVariable,\n    AbstractMoistureVariable,\n    AbstractPrecipitationVariable,\n    AbstractTracersVariable\n\nexport FirstOrder, SecondOrder\nexport AbstractTendencyType, Flux, Source\nexport TendencyDef\nexport prognostic_var_source_map\nexport eq_tends, prognostic_vars\n\n\"\"\"\n    AbstractPrognosticVariable\n\nSubtypes are used for specifying\neach prognostic variable.\n\"\"\"\nabstract type AbstractPrognosticVariable end\n\nabstract type AbstractMomentumVariable <: AbstractPrognosticVariable end\nabstract type AbstractEnergyVariable <: AbstractPrognosticVariable end\nabstract type AbstractMoistureVariable <: AbstractPrognosticVariable end\nabstract type AbstractPrecipitationVariable <: AbstractPrognosticVariable end\nabstract type AbstractTracersVariable{N} <: AbstractPrognosticVariable end\n\n\n\"\"\"\n    AbstractOrder\n\nSubtypes are used for dispatching\non the flux order.\n\"\"\"\nabstract type AbstractOrder end\n\n\"\"\"\n    FirstOrder\n\nA type for dispatching on first order fluxes\n\"\"\"\nstruct FirstOrder <: AbstractOrder end\n\n\"\"\"\n    SecondOrder\n\nA type for dispatching on second order fluxes\n\"\"\"\nstruct SecondOrder <: AbstractOrder end\n\n\"\"\"\n    AbstractTendencyType\n\nSubtypes are used for specifying a\ntuple of tendencies to be accumulated.\n\"\"\"\nabstract type AbstractTendencyType end\n\n\"\"\"\n    Flux{O <: AbstractOrder}\n\nA type for dispatching on flux tendency types\nwhere `O` is an abstract order ([`FirstOrder`](@ref)\nor [`SecondOrder`](@ref)).\n\"\"\"\nstruct Flux{O <: AbstractOrder} <: AbstractTendencyType end\n\n\"\"\"\n    Source\n\nA type for dispatching on source tendency types\n\"\"\"\nstruct Source <: AbstractTendencyType end\n\n\"\"\"\n    TendencyDef\n\nSubtypes are used for specifying\neach tendency definition.\n\"\"\"\nabstract type TendencyDef{TT <: AbstractTendencyType} end\n\n\"\"\"\n    eq_tends(::AbstractPrognosticVariable, ::BalanceLaw, ::AbstractTendencyType)\n\nA tuple of `TendencyDef`s given\n - `AbstractPrognosticVariable` prognostic variable\n - `AbstractTendencyType` tendency type\n - `BalanceLaw` balance law\n\ni.e., a tuple of `TendencyDef`s corresponding\nto `F₁`, `F₂`, **or** `S` for a single\nprognostic variable in:\n\n    `∂_t Yᵢ + (∇•F₁(Y))ᵢ + (∇•F₂(Y,G)))ᵢ = (S(Y,G))ᵢ`\n\"\"\"\nfunction eq_tends end\n\n\"\"\"\n    prognostic_vars(::BalanceLaw)\n\nA tuple of `AbstractPrognosticVariable`s given\nthe `BalanceLaw`.\n\ni.e., a tuple of `AbstractPrognosticVariable`s\ncorresponding to the column-vector `Yᵢ` in:\n\n    `∂_t Yᵢ + (∇•F₁(Y))ᵢ + (∇•F₂(Y,G)))ᵢ = (S(Y,G))ᵢ`\n\"\"\"\nprognostic_vars(::BalanceLaw) = ()\n\n\"\"\"\n    projection(bl, ::TendencyDef, args, x)\n\nProvide a hook to project individual tendencies.\nReturn identity by defualt\n\"\"\"\nprojection(pv::PV, bl, ::TendencyDef{TT}, args, x) where {TT, PV} = x\n\n\"\"\"\n    var, name = get_prog_state(state::Union{Vars, Grad}, pv::AbstractPrognosticVariable)\n\nReturns a tuple of two elements. `var` is a `Vars` or `Grad`\nobject, and `name` is a Symbol. They should be linked such that\n`getproperty(var, name)` returns the corresponding prognostic\nvariable type `pv`.\n\n# Example\n\n```julia\nget_prog_state(state, ::TotalMoisture) = (state.moisture, :ρq_tot)\nvar, name = get_prog_state(state, TotalMoisture())\n@test getproperty(var, name) == state.moisture.ρq_tot\n```\n\"\"\"\nfunction get_prog_state end\nfunction get_specific_state end\n\n\"\"\"\n    precompute(bl, args, ::AbstractTendencyType)\n\nA nested NamedTuple of precomputed (cached) values\nand or objects. This is useful for \"expensive\"\npoint-wise quantities that are used in multiple\ntendency terms. For example, computing a quantity\nthat requires iteration.\n\"\"\"\nprecompute(bl, args, ::AbstractTendencyType) = NamedTuple()\n\n\"\"\"\n    prognostic_var_source_map(driver_sources::Tuple)\n\nA DispatchedTuple, given a Tuple\nof the driver/experiment sources.\n\n!!! note\n    `prognostic_vars`, which returns a Tuple\n    of prognostic variable types, must be\n    defined for boundary condition types.\n\"\"\"\nfunction prognostic_var_source_map(driver_sources::Tuple)\n    tup = map(driver_sources) do t\n        map(prognostic_vars(t)) do pv\n            (pv, t)\n        end\n    end\n    tup = tuple_of_tuples(tup)\n    return DispatchedTuple(tup)\nend\n\n# Flatten \"tuple of tuple of tuples\" to \"tuple of tuples\"\ntuple_of_tuples(a::Tuple{AbstractPrognosticVariable, T}) where {T} = (a,)\ntuple_of_tuples(a, b...) =\n    tuple(tuple_of_tuples(a)..., tuple_of_tuples(b...)...)\ntuple_of_tuples(a::Tuple) = tuple_of_tuples(a...)\ntuple_of_tuples(a::Tuple{}) = a\n"
  },
  {
    "path": "src/BalanceLaws/vars_wrappers.jl",
    "content": "\nfunction init_state_prognostic_arr!(\n    balance_law,\n    state::AbstractArray,\n    aux::AbstractArray,\n    local_geom,\n    args...,\n)\n    FT = eltype(state)\n    init_state_prognostic!(\n        balance_law,\n        Vars{vars_state(balance_law, Prognostic(), FT)}(state),\n        Vars{vars_state(balance_law, Auxiliary(), FT)}(aux),\n        local_geom,\n        args...,\n    )\n\nend\n\nfunction source_arr!(\n    balance_law,\n    source::AbstractArray,\n    state::AbstractArray,\n    diffusive::AbstractArray,\n    aux::AbstractArray,\n    t::Real,\n    direction,\n)\n    FT = eltype(state)\n    source!(\n        balance_law,\n        Vars{vars_state(balance_law, Prognostic(), FT)}(source),\n        Vars{vars_state(balance_law, Prognostic(), FT)}(state),\n        Vars{vars_state(balance_law, GradientFlux(), FT)}(diffusive),\n        Vars{vars_state(balance_law, Auxiliary(), FT)}(aux),\n        t,\n        direction,\n    )\nend\n\nfunction flux_first_order_arr!(\n    balance_law,\n    flux::AbstractArray,\n    state::AbstractArray,\n    aux::AbstractArray,\n    t::Real,\n    direction,\n)\n    FT = eltype(state)\n    flux_first_order!(\n        balance_law,\n        Grad{vars_state(balance_law, Prognostic(), FT)}(flux),\n        Vars{vars_state(balance_law, Prognostic(), FT)}(state),\n        Vars{vars_state(balance_law, Auxiliary(), FT)}(aux),\n        t,\n        direction,\n    )\nend\n\nfunction flux_second_order_arr!(\n    balance_law,\n    flux::AbstractArray,\n    state::AbstractArray,\n    diffusive::AbstractArray,\n    hyperdiffusive::AbstractArray,\n    aux::AbstractArray,\n    t::Real,\n)\n    FT = eltype(state)\n    flux_second_order!(\n        balance_law,\n        Grad{vars_state(balance_law, Prognostic(), FT)}(flux),\n        Vars{vars_state(balance_law, Prognostic(), FT)}(state),\n        Vars{vars_state(balance_law, GradientFlux(), FT)}(diffusive),\n        Vars{vars_state(balance_law, Hyperdiffusive(), FT)}(hyperdiffusive),\n        Vars{vars_state(balance_law, Auxiliary(), FT)}(aux),\n        t,\n    )\nend\n\nfunction compute_gradient_argument_arr!(\n    balance_law,\n    transform::AbstractArray,\n    state::AbstractArray,\n    aux::AbstractArray,\n    t::Real,\n)\n    FT = eltype(state)\n    compute_gradient_argument!(\n        balance_law,\n        Vars{vars_state(balance_law, Gradient(), FT)}(transform),\n        Vars{vars_state(balance_law, Prognostic(), FT)}(state),\n        Vars{vars_state(balance_law, Auxiliary(), FT)}(aux),\n        t,\n    )\nend\n\nfunction compute_gradient_flux_arr!(\n    balance_law,\n    diffusive::AbstractArray,\n    ∇transform::AbstractArray,\n    state::AbstractArray,\n    aux::AbstractArray,\n    t::Real,\n)\n\n    FT = eltype(state)\n    compute_gradient_flux!(\n        balance_law,\n        Vars{vars_state(balance_law, GradientFlux(), FT)}(diffusive),\n        Grad{vars_state(balance_law, Gradient(), FT)}(∇transform),\n        Vars{vars_state(balance_law, Prognostic(), FT)}(state),\n        Vars{vars_state(balance_law, Auxiliary(), FT)}(aux),\n        t,\n    )\nend\n\nfunction integral_load_auxiliary_state_arr!(\n    balance_law,\n    local_kernel::AbstractArray,\n    state_prognostic::AbstractArray,\n    state_auxiliary::AbstractArray,\n)\n    FT = eltype(state_auxiliary)\n    integral_load_auxiliary_state!(\n        balance_law,\n        Vars{vars_state(balance_law, UpwardIntegrals(), FT)}(local_kernel),\n        Vars{vars_state(balance_law, Prognostic(), FT)}(state_prognostic),\n        Vars{vars_state(balance_law, Auxiliary(), FT)}(state_auxiliary),\n    )\nend\n\nfunction integral_set_auxiliary_state_arr!(\n    balance_law,\n    state_auxiliary::AbstractArray,\n    local_kernel::AbstractArray,\n)\n\n    FT = eltype(state_auxiliary)\n    integral_set_auxiliary_state!(\n        balance_law,\n        Vars{vars_state(balance_law, Auxiliary(), FT)}(state_auxiliary),\n        Vars{vars_state(balance_law, UpwardIntegrals(), FT)}(local_kernel),\n    )\nend\n\nfunction reverse_integral_load_auxiliary_state_arr!(\n    balance_law,\n    l_T::AbstractArray,\n    state::AbstractArray,\n    state_auxiliary::AbstractArray,\n)\n\n    FT = eltype(state_auxiliary)\n    reverse_integral_load_auxiliary_state!(\n        balance_law,\n        Vars{vars_state(balance_law, DownwardIntegrals(), FT)}(l_T),\n        Vars{vars_state(balance_law, Prognostic(), FT)}(state),\n        Vars{vars_state(balance_law, Auxiliary(), FT)}(state_auxiliary),\n    )\n\nend\n\nfunction reverse_integral_set_auxiliary_state_arr!(\n    balance_law,\n    state_auxiliary::AbstractArray,\n    l_V::AbstractArray,\n)\n\n    FT = eltype(state_auxiliary)\n    reverse_integral_set_auxiliary_state!(\n        balance_law,\n        Vars{vars_state(balance_law, Auxiliary(), FT)}(state_auxiliary),\n        Vars{vars_state(balance_law, DownwardIntegrals(), FT)}(l_V),\n    )\nend\n\nfunction transform_post_gradient_laplacian_arr!(\n    balance_law,\n    hyperdiffusion::AbstractArray,\n    l_grad_lap::AbstractArray,\n    prognostic::AbstractArray,\n    auxiliary::AbstractArray,\n    t,\n)\n\n    FT = eltype(prognostic)\n    transform_post_gradient_laplacian!(\n        balance_law,\n        Vars{vars_state(balance_law, Hyperdiffusive(), FT)}(hyperdiffusion),\n        Grad{vars_state(balance_law, GradientLaplacian(), FT)}(l_grad_lap),\n        Vars{vars_state(balance_law, Prognostic(), FT)}(prognostic),\n        Vars{vars_state(balance_law, Auxiliary(), FT)}(auxiliary),\n        t,\n    )\nend\n"
  },
  {
    "path": "src/ClimateMachine.jl",
    "content": "module ClimateMachine\n\nusing Pkg.TOML\n\nconst CLIMATEMACHINE_VERSION =\n    VersionNumber(TOML.parsefile(joinpath(dirname(@__DIR__), \"Project.toml\"))[\"version\"])\n\ninclude(joinpath(\"Utilities\", \"TicToc\", \"TicToc.jl\"))\ninclude(joinpath(\"InputOutput\", \"Writers\", \"Writers.jl\"))\ninclude(joinpath(\"Driver\", \"ConfigTypes\", \"ConfigTypes.jl\"))\ninclude(joinpath(\"Utilities\", \"VariableTemplates\", \"VariableTemplates.jl\"))\ninclude(joinpath(\"Arrays\", \"MPIStateArrays.jl\"))\ninclude(joinpath(\"Numerics\", \"Mesh\", \"Mesh.jl\"))\ninclude(joinpath(\"Common\", \"CartesianDomains\", \"CartesianDomains.jl\"))\ninclude(joinpath(\"Common\", \"CartesianFields\", \"CartesianFields.jl\"))\ninclude(joinpath(\"Numerics\", \"DGMethods\", \"Courant.jl\"))\ninclude(joinpath(\"BalanceLaws\", \"Problems.jl\"))\ninclude(joinpath(\"BalanceLaws\", \"BalanceLaws.jl\"))\ninclude(joinpath(\"Numerics\", \"DGMethods\", \"DGMethods.jl\"))\ninclude(joinpath(\"Common\", \"Orientations\", \"Orientations.jl\"))\ninclude(joinpath(\"Utilities\", \"SingleStackUtils\", \"SingleStackUtils.jl\"))\ninclude(joinpath(\"Numerics\", \"SystemSolvers\", \"SystemSolvers.jl\"))\ninclude(joinpath(\"Numerics\", \"ODESolvers\", \"GenericCallbacks.jl\"))\ninclude(joinpath(\"Numerics\", \"ODESolvers\", \"ODESolvers.jl\"))\ninclude(joinpath(\"Land\", \"Model\", \"LandModel.jl\"))\ninclude(joinpath(\"InputOutput\", \"VTK\", \"VTK.jl\"))\ninclude(joinpath(\"Common\", \"TurbulenceClosures\", \"TurbulenceClosures.jl\"))\ninclude(joinpath(\"Common\", \"TurbulenceConvection\", \"TurbulenceConvection.jl\"))\ninclude(joinpath(\"Atmos\", \"Model\", \"AtmosModel.jl\"))\ninclude(joinpath(\"Common\", \"Spectra\", \"Spectra.jl\"))\ninclude(joinpath(\"Diagnostics\", \"Diagnostics.jl\"))\ninclude(joinpath(\"Diagnostics\", \"DiagnosticsMachine\", \"DiagnosticsMachine.jl\"))\ninclude(joinpath(\"Diagnostics\", \"StdDiagnostics\", \"StdDiagnostics.jl\"))\ninclude(joinpath(\"Diagnostics\", \"Debug\", \"StateCheck.jl\"))\ninclude(joinpath(\"Driver\", \"Checkpoint\", \"Checkpoint.jl\"))\ninclude(joinpath(\"Driver\", \"Callbacks\", \"Callbacks.jl\"))\ninclude(joinpath(\"Driver\", \"Driver.jl\"))\n\ninclude(joinpath(\"Ocean\", \"Ocean.jl\"))\n\nend\n"
  },
  {
    "path": "src/Common/CartesianDomains/CartesianDomains.jl",
    "content": "module CartesianDomains\n\nexport RectangularDomain\n\nusing Printf\n\nabstract type AbstractDomain end\n\ninclude(\"rectangular_domain.jl\")\n\nend\n"
  },
  {
    "path": "src/Common/CartesianDomains/rectangular_domain.jl",
    "content": "using MPI\n\nusing ..Mesh.Topologies: StackedBrickTopology\n\nimport ..Mesh.Grids: DiscontinuousSpectralElementGrid\n\n#####\n##### RectangularDomain\n#####\n\nstruct RectangularDomain{FT} <: AbstractDomain\n    Np::Int\n    Ne::NamedTuple{(:x, :y, :z), NTuple{3, Int}}\n    L::NamedTuple{(:x, :y, :z), NTuple{3, FT}}\n    x::NTuple{2, FT}\n    y::NTuple{2, FT}\n    z::NTuple{2, FT}\n    periodicity::NamedTuple{(:x, :y, :z), NTuple{3, Bool}}\nend\n\nBase.eltype(::RectangularDomain{FT}) where {FT} = FT\n\nfunction Base.show(io::IO, domain::RectangularDomain{FT}) where {FT}\n    Np = domain.Np\n    Ne = domain.Ne\n    L = domain.L\n\n    first = \"RectangularDomain{$FT}\\n\"\n    second = \"    Np = $Np, Ne = $Ne\\n\"\n    third = \"    L = $L\\n\"\n\n    return print(io, first, second, third)\nend\n\nname_it(Ne::NamedTuple{(:x, :y, :z)}) = Ne\nname_it(Ne) = (x = Ne[1], y = Ne[2], z = Ne[3])\n\n\"\"\"\n    RectangularDomain(FT=Float64;\n                      Ne,\n                      Np,\n                      x = (-1, 1),\n                      y = (-1, 1),\n                      z = (-1, 1),\n                      periodicity = (true, true, false))\n\nReturns a `RectangularDomain` representing the product of `x, y, z` intervals,\nspecified by 2-tuples.\n\nThe `RectangularDomain` is meshed with a simple `DiscontinuousSpectralElementGrid`\nwith an isotropic polynomial order `Np` and a 3-tuple of `Ne`lements\ngiving the number of elements in `x, y, z`.\n\nAdditional arguments are:\n\n- `periodicity`: a 3-tuple that indicates periodic dimensions with `true`, \n\n- `boundary`: specifies the boundary condition on each boundary with a\n              boundary condition `tag`\n\n- `array_type`: either `Array` for CPU computations or `CuArray` for\n                GPU computations\n\n- `mpicomm`: communicator for sending data across nodes in a distributed memory\n             configuration using the Message Passing Interface (MPI).\n             See https://pages.tacc.utexas.edu/~eijkhout/pcse/html/mpi-comm.html\n\nExample\n=======\n\n```julia\njulia> using ClimateMachine; ClimateMachine.init()\n\njulia> using ClimateMachine.Ocean.RectangularDomains: RectangularDomain\n\njulia> domain = RectangularDomain(Ne=(7, 8, 9), Np=4, x=(0, 1), y=(0, 1), z=(0, 1))\nRectangularDomain{Float64}:\n    Np = 4, Ne = (x = 7, y = 8, z = 9)\n    L = (x = 1.00e+00, y = 1.00e+00, z = 1.00e+00)\n    x = (0.00e+00, 1.00e+00), y = (0.00e+00, 1.00e+00), z = (1.00e+00, 0.00e+00)\n```\n\"\"\"\nfunction RectangularDomain(\n    FT = Float64;\n    Ne,\n    Np,\n    x::Tuple{<:Number, <:Number},\n    y::Tuple{<:Number, <:Number},\n    z::Tuple{<:Number, <:Number},\n    periodicity = (true, true, false),\n)\n\n    Ne = name_it(Ne)\n    periodicity = name_it(periodicity)\n\n    west, east = FT.(x)\n    south, north = FT.(y)\n    bottom, top = FT.(z)\n\n    east > west || error(\"Domain x-limits must be increasing!\")\n    north > south || error(\"Domain y-limits must be increasing!\")\n    top > bottom || error(\"Domain z-limits must be increasing!\")\n\n    L = (x = east - west, y = north - south, z = top - bottom)\n\n    return RectangularDomain(\n        Np,\n        Ne,\n        L,\n        (west, east),\n        (south, north),\n        (bottom, top),\n        periodicity,\n    )\nend\n\neltype(::RectangularDomain{FT}) where {FT} = FT\n\nfunction DiscontinuousSpectralElementGrid(\n    domain::RectangularDomain{FT};\n    boundary_tags = ((0, 0), (0, 0), (1, 2)),\n    array_type,\n    mpicomm = MPI.COMM_WORLD,\n) where {FT}\n\n    west, east = domain.x\n    south, north = domain.y\n    bottom, top = domain.z\n\n    element_coordinates = (\n        range(west, east, length = domain.Ne.x + 1),\n        range(south, north, length = domain.Ne.y + 1),\n        range(bottom, top, length = domain.Ne.z + 1),\n    )\n\n    topology = StackedBrickTopology(\n        mpicomm,\n        element_coordinates;\n        periodicity = tuple(domain.periodicity...),\n        boundary = boundary_tags,\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = array_type,\n        polynomialorder = domain.Np,\n    )\n\n    return grid\nend\n"
  },
  {
    "path": "src/Common/CartesianFields/CartesianFields.jl",
    "content": "module CartesianFields\n\nexport SpectralElementField, assemble, data\n\nusing CUDA\nusing Printf\n\nusing ..Mesh.Grids: DiscontinuousSpectralElementGrid\nusing ..MPIStateArrays: MPIStateArray\nusing ..CartesianDomains: AbstractDomain\n\n#####\n##### SpectralElementField\n#####\n\nstruct SpectralElementField{E, D, G, A, V, R}\n    elements::E\n    domain::D\n    grid::G\n    data::A\n    realdata::V\n    realelems::R # Required to build realdata from data\nend\n\n\"\"\"\n    SpectralElementField(domain::RectangularDomain, state::MPIStateArray, variable_index::Int)\n\nReturns a Cartesian `view` into `state.realdata[:, variable_index, :]`,\nassuming that `state.realdata` lives on `RectangularDomain`.\n\n`SpectralElementField.elements` is a three-dimensional array of `RectangularElements`.\n\"\"\"\nfunction SpectralElementField(\n    domain::AbstractDomain,\n    grid::DiscontinuousSpectralElementGrid,\n    state::MPIStateArray,\n    variable_index::Int,\n)\n\n    data = view(state.data, :, variable_index, :)\n    realdata = view(state.realdata, :, variable_index, :)\n    realelems = state.realelems\n\n    return SpectralElementField(domain, grid, data, realdata, realelems)\nend\n\nconst SEF = SpectralElementField\n\nBase.@propagate_inbounds Base.getindex(field::SEF, i, j, k) =\n    field.elements[i, j, k]\nBase.size(field::SEF) = size(field.elements)\nBase.eltype(field::SEF) = eltype(field.realdata)\n\nBase.maximum(f, field::SEF) = maximum([maximum(f, el) for el in field.elements])\nBase.minimum(f, field::SEF) = minimum([minimum(f, el) for el in field.elements])\n\nBase.maximum(field::SEF) = maximum([maximum(el) for el in field.elements])\nBase.minimum(field::SEF) = minimum([minimum(el) for el in field.elements])\n\nBase.show(io::IO, field::SEF{E}) where {E} =\n    print(io, \"SpectralElementField{$(E.name.wrapper)}\")\n\n#####\n##### Domain-specific stuff\n#####\n\ninclude(\"rectangular_element.jl\")\ninclude(\"rectangular_spectral_element_fields.jl\")\n\n\n#####\n##### CPU and GPU-friendly element assembly\n#####\n\n\"\"\"\n    assemble(u::SpectralElementField)\n\nAssemble `u.elements` into a single `element::eltype(u)`, averaging shared nodes.\n\"\"\"\nassemble(u::SpectralElementField) = assemble(u.elements)\n\n\"\"\"\n    assemble_data(u::SpectralElementField{<:CuArray})\n\nAssemble the data in `u.elements` into a single `Array`, averaging shared nodes.\n\"\"\"\nfunction assemble(u::SpectralElementField{<:CuArray})\n\n    domain = u.domain\n    index = u.variable_index\n\n    cpudata = Array(u.realdata)\n\n    u_cpu = SpectralElementField(cpudata, domain)\n\n    return assemble(u_cpu)\nend\n\nend # module\n"
  },
  {
    "path": "src/Common/CartesianFields/rectangular_element.jl",
    "content": "using ..CartesianDomains: RectangularDomain\n\n#####\n##### RectangularElement\n#####\n\nstruct RectangularElement{D, X, Y, Z, I}\n    data::D\n    x::X\n    y::Y\n    z::Z\n    index::I # gives state.realdata[:, variable, index]\n    # M::Mⁱʲ\nend\n\n\"\"\"\n    RectangularElement(domain, grid, realdata, element_index)\n\nReturns a Cartesian view into the `realdata` and nodes (located in `grid`)\nassociated with `element_index`.\n\"\"\"\nfunction RectangularElement(\n    domain::RectangularDomain,\n    grid,\n    realdata,\n    element_index,\n)\n    volume_geometry = grid.vgeo\n\n    Np = domain.Np\n    Te = prod(domain.Ne) # total number of elements\n\n    # Extract views of Cartesian coordinates\n    x = view(volume_geometry, :, grid.x1id, :)\n    y = view(volume_geometry, :, grid.x2id, :)\n    z = view(volume_geometry, :, grid.x3id, :)\n\n    # Reshape x, y, z to (xnode, ynode, znode, element)\n    x = reshape(x, Np + 1, Np + 1, Np + 1, Te)\n    y = reshape(y, Np + 1, Np + 1, Np + 1, Te)\n    z = reshape(z, Np + 1, Np + 1, Np + 1, Te)\n\n    # Reshape realdata as coordinate arrays\n    reshaped_realdata = reshape(realdata, Np + 1, Np + 1, Np + 1, Te)\n\n    # Build views to realdata and x, y, z\n    data = view(reshaped_realdata, :, :, :, element_index)\n    x = view(x, :, :, :, element_index)\n    y = view(y, :, :, :, element_index)\n    z = view(z, :, :, :, element_index)\n\n    return RectangularElement(data, x, y, z, element_index)\nend\n\n# Constructor for index-less elements\nRectangularElement(data, x, y, z) = RectangularElement(data, x, y, z, nothing)\n\nBase.eltype(::RectangularElement) = eltype(elem.data)\n\nBase.size(element::RectangularElement) = size(element.data)\n\nBase.@propagate_inbounds Base.getindex(elem::RectangularElement, i, j, k) =\n    elem.data[i, j, k]\n\nBase.maximum(f, element::RectangularElement) = maximum(f, element.data)\nBase.minimum(f, element::RectangularElement) = minimum(f, element.data)\n\nBase.maximum(element::RectangularElement) = maximum(element.data)\nBase.minimum(element::RectangularElement) = minimum(element.data)\n\nfunction Base.show(io::IO, elem::RectangularElement{D}) where {D}\n    intro = \"RectangularElement{$(D.name.wrapper)} with \"\n    data = @sprintf(\n        \"data ∈ [%.2e, %.2e]\\n\",\n        minimum(elem.data),\n        maximum(elem.data)\n    )\n    x = @sprintf(\"    x ∈ [%.2e, %.2e]\\n\", minimum(elem.x), maximum(elem.x))\n    y = @sprintf(\"    y ∈ [%.2e, %.2e]\", minimum(elem.y), maximum(elem.y))\n    z = @sprintf(\"    z ∈ [%.2e, %.2e]\", minimum(elem.z), maximum(elem.z))\n\n    return print(io, intro, data, x, y, z)\nend\n\n\n#####\n##### ⟨⟨ Assemble! ⟩⟩\n#####\n\n\"\"\" Assemble an array along the first dimension. \"\"\"\nfunction assemble(::Val{1}, west::AbstractArray, east::AbstractArray)\n    contact = @. (west[end:end, :, :] + east[1:1, :, :]) / 2\n\n    east = east[2:end, :, :]\n    west = west[1:(end - 1), :, :]\n\n    assembled = cat(west, contact, east, dims = 1)\n\n    return assembled\nend\n\n\"\"\" Assemble an array along the second dimension. \"\"\"\nfunction assemble(::Val{2}, south::AbstractArray, north::AbstractArray)\n    contact = @. (south[:, end:end, :] + north[:, 1:1, :]) / 2\n\n    north = north[:, 2:end, :]\n    south = south[:, 1:(end - 1), :]\n\n    assembled = cat(south, contact, north, dims = 2)\n\n    return assembled\nend\n\n\"\"\" Assemble an array along the third dimension. \"\"\"\nfunction assemble(::Val{3}, bottom::AbstractArray, top::AbstractArray)\n    contact = @. (bottom[:, :, end:end] + top[:, :, 1:1]) / 2\n\n    top = top[:, :, 2:end]\n    bottom = bottom[:, :, 1:(end - 1)]\n\n    assembled = cat(bottom, contact, top, dims = 3)\n\n    return assembled\nend\n\n\"\"\" Assemble elements along `dim`ension. \"\"\"\nfunction assemble(dim::Val, left::RectangularElement, right::RectangularElement)\n    data = assemble(dim, left.data, right.data)\n    x = assemble(dim, left.x, right.x)\n    y = assemble(dim, left.y, right.y)\n    z = assemble(dim, left.z, right.z)\n\n    return RectangularElement(data, x, y, z, nothing)\nend\n\nassemble(dim::Val, e1, e2, e3...) = assemble(dim, e1, assemble(dim, e2, e3...))\nassemble(dim::Val, elem) = elem\n\ndata(elem::RectangularElement) = elem.data\n\n\"\"\"\n    assemble(elements::Array{<:RectangularElement, 3})\n\nAssemble the three-dimensional data in `elements` into a single `Array`,\naveraging data on shared nodes.\n\"\"\"\nfunction assemble(\n    elements::Array{T, 3},\n) where {T <: Union{RectangularElement, AbstractArray}}\n\n    Nx, Ny, Nz = size(elements)\n\n    pencils = [assemble(Val(1), elements[:, j, k]...) for j in 1:Ny, k in 1:Nz]\n\n    slabs = [assemble(Val(2), pencils[:, k]...) for k in 1:Nz]\n\n    volume = assemble(Val(3), slabs...)\n\n    return volume\nend\n"
  },
  {
    "path": "src/Common/CartesianFields/rectangular_spectral_element_fields.jl",
    "content": "using ..CartesianDomains: RectangularDomain\n\n\"\"\" Like a linear index... \"\"\"\nfunction linear_coordinate(elem, domain, cpu_x, cpu_y, cpu_z)\n\n    corner_x = cpu_x[1, elem.index]\n    corner_y = cpu_y[1, elem.index]\n    corner_z = cpu_z[1, elem.index]\n\n    Δx = corner_x - domain.x[1]\n    Δy = corner_y - domain.y[1]\n    Δz = corner_z - domain.z[1]\n\n    coordinate = (\n        Δz / domain.L.z * domain.Ne.z * domain.Ne.y * domain.L.x +\n        Δy / domain.L.y * domain.Ne.y * domain.L.x +\n        Δx\n    )\n\n    return coordinate\nend\n\n\"\"\"\n    SpectralElementField(domain::RectangularDomain, grid, realdata::AbstractArray)\n\nReturns a `SpectralElementField` whose `elements` provide a Cartesian `view`\ninto `realdata`, assuming that `realdata` lives on `domain::RectangularDomain`.\n\"\"\"\nfunction SpectralElementField(\n    domain::RectangularDomain{FT},\n    grid::DiscontinuousSpectralElementGrid,\n    realdata::AbstractArray,\n    data::Union{AbstractArray, Nothing} = nothing,\n    realelems::Union{UnitRange, Nothing} = nothing,\n) where {FT}\n\n    # Build element list\n    Te = prod(domain.Ne) # total number of elements\n\n    element_list = [RectangularElement(domain, grid, realdata, i) for i in 1:Te]\n\n    # Transfer coordinate data to CPU for element sorting\n    volume_geometry = grid.vgeo\n\n    x = convert(Array, view(volume_geometry, :, grid.x1id, :))\n    y = convert(Array, view(volume_geometry, :, grid.x2id, :))\n    z = convert(Array, view(volume_geometry, :, grid.x3id, :))\n\n    # Sort elements by linear coordinate for x, y, z structuring\n    sort!(element_list, by = elem -> linear_coordinate(elem, domain, x, y, z))\n\n    # Reshape and permute dims to obtain array where i, j, k correspond to x, y, z\n    Ne = domain.Ne\n    element_array = reshape(element_list, Ne.x, Ne.y, Ne.z)\n\n    return SpectralElementField(\n        element_array,\n        domain,\n        grid,\n        realdata,\n        data,\n        realelems,\n    )\nend\n"
  },
  {
    "path": "src/Common/Orientations/Orientations.jl",
    "content": "\"\"\"\n    Orientations\n\nOrientation functions:\n\n - `vertical_unit_vector`\n - `altitude`\n - `latitude`\n - `longitude`\n - `gravitational_potential`\n - `projection_normal`\n - `projection_tangential`\n\nfor orientations:\n\n - [`NoOrientation`](@ref)\n - [`FlatOrientation`](@ref)\n - [`SphericalOrientation`](@ref)\n\"\"\"\nmodule Orientations\n\nusing CLIMAParameters: AbstractParameterSet\nconst APS = AbstractParameterSet\nusing CLIMAParameters.Planet: grav, planet_radius\nusing StaticArrays\nusing LinearAlgebra\n\nusing ..VariableTemplates\nusing ..BalanceLaws\nusing ..MPIStateArrays: MPIStateArray\nimport ..BalanceLaws: vars_state\nusing ..DGMethods:\n    init_state_auxiliary!, auxiliary_field_gradient!, LocalGeometry\n\nexport Orientation, NoOrientation, FlatOrientation, SphericalOrientation\n\nexport init_aux!,\n    orientation_nodal_init_aux!,\n    vertical_unit_vector,\n    altitude,\n    latitude,\n    longitude,\n    projection_normal,\n    gravitational_potential,\n    ∇gravitational_potential,\n    projection_tangential,\n    sphr_to_cart_vec,\n    cart_to_sphr_vec\n\n\nabstract type Orientation <: BalanceLaw end\n\n#####\n##### Fallbacks\n#####\n\nfunction vars_state(m::Orientation, ::Auxiliary, FT)\n    @vars begin\n        Φ::FT # gravitational potential\n        ∇Φ::SVector{3, FT}\n    end\nend\n\ngravitational_potential(::Orientation, aux::Vars) = aux.orientation.Φ\n\n∇gravitational_potential(::Orientation, aux::Vars) = aux.orientation.∇Φ\n\nfunction altitude(orientation::Orientation, param_set::APS, aux::Vars)\n    FT = eltype(aux)\n    return gravitational_potential(orientation, aux) / FT(grav(param_set))\nend\n\nfunction vertical_unit_vector(\n    orientation::Orientation,\n    param_set::APS,\n    aux::Vars,\n)\n    FT = eltype(aux)\n    return ∇gravitational_potential(orientation, aux) / FT(grav(param_set))\nend\n\nfunction projection_normal(\n    orientation::Orientation,\n    param_set::APS,\n    aux::Vars,\n    u⃗::AbstractVector,\n)\n    n̂ = vertical_unit_vector(orientation, param_set, aux)\n    return n̂ * (n̂' * u⃗)\nend\n\nfunction projection_tangential(\n    orientation::Orientation,\n    param_set::APS,\n    aux::Vars,\n    u⃗::AbstractVector,\n)\n    return u⃗ .- projection_normal(orientation, param_set, aux, u⃗)\nend\n\nfunction init_aux!(\n    m,\n    ::Orientation,\n    state_auxiliary::MPIStateArray,\n    grid,\n    direction,\n)\n    param_set = parameter_set(m)\n    init_state_auxiliary!(\n        m,\n        (m, aux, tmp, geom) ->\n            orientation_nodal_init_aux!(m.orientation, param_set, aux, geom),\n        state_auxiliary,\n        grid,\n        direction,\n    )\n\n    auxiliary_field_gradient!(\n        m,\n        state_auxiliary,\n        (\"orientation.∇Φ\",),\n        state_auxiliary,\n        (\"orientation.Φ\",),\n        grid,\n        direction,\n    )\nend\n\n#####\n##### NoOrientation\n#####\n\n\"\"\"\n    NoOrientation\n\nNo gravitational force or potential.\n\"\"\"\nstruct NoOrientation <: Orientation end\n\ninit_aux!(m, ::NoOrientation, state_auxiliary::MPIStateArray, grid, direction) =\n    nothing\n\nvars_state(m::NoOrientation, ::Auxiliary, FT) = @vars()\n\ngravitational_potential(::NoOrientation, aux::Vars) = -zero(eltype(aux))\n∇gravitational_potential(::NoOrientation, aux::Vars) =\n    SVector{3, eltype(aux)}(0, 0, 0)\naltitude(orientation::NoOrientation, param_set::APS, aux::Vars) =\n    -zero(eltype(aux))\n\n#####\n##### SphericalOrientation\n#####\n\n\"\"\"\n    SphericalOrientation <: Orientation\n\nGravity acts towards the origin `(0,0,0)`, and the gravitational potential is relative\nto the surface of the planet.\n\"\"\"\nstruct SphericalOrientation <: Orientation end\n\nfunction orientation_nodal_init_aux!(\n    ::SphericalOrientation,\n    param_set::APS,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    FT = eltype(aux)\n    _grav::FT = grav(param_set)\n    _planet_radius::FT = planet_radius(param_set)\n    normcoord = norm(geom.coord)\n    aux.orientation.Φ = _grav * (normcoord - _planet_radius)\nend\n\n\n# TODO: should we define these for non-spherical orientations?\nlatitude(orientation::SphericalOrientation, aux::Vars) =\n    @inbounds asin(aux.coord[3] / norm(aux.coord, 2))\n\nlongitude(orientation::SphericalOrientation, aux::Vars) =\n    @inbounds atan(aux.coord[2], aux.coord[1])\n\n\"\"\"\n    sphr_to_cart_vec(orientation::SphericalOrientation, state::Vars, aux::Vars)\n\nProjects a vector defined based on unit vectors in spherical coordinates to cartesian unit vectors.\n\"\"\"\nfunction sphr_to_cart_vec(\n    orientation::SphericalOrientation,\n    vec::AbstractVector,\n    aux::Vars,\n)\n    FT = eltype(aux)\n    lat = latitude(orientation, aux)\n    lon = longitude(orientation, aux)\n\n    slat, clat = sin(lat), cos(lat)\n    slon, clon = sin(lon), cos(lon)\n\n    u = MVector{3, FT}(\n        -slon * vec[1] - slat * clon * vec[2] + clat * clon * vec[3],\n        clon * vec[1] - slat * slon * vec[2] + clat * slon * vec[3],\n        clat * vec[2] + slat * vec[3],\n    )\n\n    return u\nend\n\n\"\"\"\n    cart_to_sphr_vec(orientation::SphericalOrientation, state::Vars, aux::Vars)\n\nProjects a vector defined based on unit vectors in cartesian coordinates to a spherical unit vectors.\n\"\"\"\nfunction cart_to_sphr_vec(\n    orientation::SphericalOrientation,\n    vec::AbstractVector,\n    aux::Vars,\n)\n    FT = eltype(aux)\n    lat = latitude(orientation, aux)\n    lon = longitude(orientation, aux)\n\n    slat, clat = sin(lat), cos(lat)\n    slon, clon = sin(lon), cos(lon)\n\n    u = MVector{3, FT}(\n        -slon * vec[1] + clon * vec[2],\n        -slat * clon * vec[1] - slat * slon * vec[2] + clat * vec[3],\n        clat * clon * vec[1] + clat * slon * vec[2] + slat * vec[3],\n    )\n\n    return u\nend\n\n#####\n##### FlatOrientation\n#####\n\n\"\"\"\n    FlatOrientation <: Orientation\n\nGravity acts in the third coordinate, and the gravitational potential is relative to\n`coord[3] == 0`.\n\"\"\"\nstruct FlatOrientation <: Orientation\n    # for Coriolis we could add latitude?\nend\nfunction orientation_nodal_init_aux!(\n    ::FlatOrientation,\n    param_set::APS,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    FT = eltype(aux)\n    _grav::FT = grav(param_set)\n    @inbounds aux.orientation.Φ = _grav * geom.coord[3]\nend\n\nend\n"
  },
  {
    "path": "src/Common/Spectra/Spectra.jl",
    "content": "module Spectra\n\nexport power_spectrum_1d, power_spectrum_2d, power_spectrum_3d\n\nusing FFTW\nusing MPI\n\nusing ..ConfigTypes\nusing ..Mesh.Grids\nusing ..MPIStateArrays\n\ninclude(\"power_spectrum_les.jl\")\ninclude(\"power_spectrum_gcm.jl\")\n\nend\n"
  },
  {
    "path": "src/Common/Spectra/power_spectrum_gcm.jl",
    "content": "include(\"spherical_helper.jl\")\n\n\"\"\"\n    power_spectrum_1d(::AtmosGCMConfigType, var_grid, z, lat, lon, weight)\n\nCalculates the 1D (zonal) power spectra using the fourier transform at each latitude and level from a 3D velocity field.\nThe snapshots of these spectra should be averaged to obtain a time-average.\nThe input velocities must be interpolated to a Gaussian grid.\n\n# References\n- [Wiin1967](@cite)\n- [Koshyk2001](@cite)\n\n# Arguments\n- var_grid: variable (typically u or v) on a Gausian (lon, lat, z) grid to be transformed\n- z: vertical coordinate (height or pressure)\n- lat: latitude\n- lon: longitude\n- mass_weight: for mass-weighted calculations\n\"\"\"\nfunction power_spectrum_1d(::AtmosGCMConfigType, var_grid, z, lat, lon, weight)\n    num_lev = length(z)\n    num_lat = length(lat)\n    num_lon = length(lon)\n    num_fourier = Int64(num_lon)\n\n    # get number of positive Fourier coefficients incl. 0\n    if mod(num_lon, 2) == 0 # even\n        num_pfourier = div(num_lon, 2)\n    else # odd\n        num_pfourier = div(num_lon, 2) + 1\n    end\n\n    zon_spectrum = zeros(Float64, num_pfourier, num_lat, num_lev)\n    freqs = zeros(Float64, num_pfourier, num_lat, num_lev)\n\n    for k in 1:num_lev\n        for j in 1:num_lat\n            # compute fft frequencies for each latitude\n            x = lon ./ 180.0 .* π\n            dx = (lon[2] - lon[1]) ./ 180.0 .* π\n\n            freqs_ = fftfreq(num_fourier, 1.0 / dx) # 0,+ve freq,-ve freqs (lowest to highest)\n            freqs[:, j, k] = freqs_[1:num_pfourier] .* 2.0 .* π\n\n            # compute the fourier coefficients for all latitudes\n            fourier = fft(var_grid[:, j, k]) # e.g. vcos_grid, ucos_grid\n            fourier = (fourier / num_fourier)\n\n            # convert to energy spectra\n            zon_spectrum[1, j, k] =\n                zon_spectrum[1, j, k] +\n                weight[k] * fourier[1] .* conj(fourier[1])\n\n            for m in 2:num_pfourier\n                zon_spectrum[m, j, k] =\n                    zon_spectrum[m, j, k] +\n                    2.0 * weight[k] * fourier[m] * conj(fourier[m]) # factor 2 for neg freq contribution\n            end\n        end\n    end\n    return zon_spectrum, freqs\nend\n\n\"\"\"\n    power_spectrum_2d(::AtmosGCMConfigType, var_grid, mass_weight)\n\n- transform variable on grid to the 2d spectral space using fft on latitude circles\n(as for the 1D spectrum) and Legendre polynomials for meridians, and calculate spectra\n\n# Arguments\n- var_grid: variable (typically u or v) on a Gausian (lon, lat, z) grid to be transformed\n- mass_weight: weight for mass-weighted calculations\n\n# References\n- [Baer1972](@cite)\n\"\"\"\nfunction power_spectrum_2d(::AtmosGCMConfigType, var_grid, mass_weight)\n    #  initialize spherical mesh variables\n    nθ, nd = (size(var_grid, 2), size(var_grid, 3))\n    mesh = SpectralSphericalMesh(nθ, nd)\n    var_spectrum = mesh.var_spectrum\n    var_spherical = mesh.var_spherical\n\n    sinθ, wts = compute_gaussian!(mesh.nθ) # latitude weights using Gaussian quadrature, to orthogonalize Legendre polynomials upon summation\n    mesh.qnm =\n        compute_legendre!(mesh.num_fourier, mesh.num_spherical, sinθ, mesh.nθ) #  normalized associated Legendre polynomials\n\n    for k in 1:(mesh.nd)\n        # apply Gaussian quadrature weights\n        for i in 1:(mesh.nθ)\n            mesh.qwg[:, :, i] .= mesh.qnm[:, :, i] * wts[i] * mass_weight[k]\n        end\n\n        # Transform variable using spherical harmonics\n        var_spherical[:, :, k, :] =\n            trans_grid_to_spherical!(mesh, var_grid[:, :, k]) # var_spherical[m,n,k,sinθ]\n\n        # Calculate energy spectra\n        var_spectrum[:, :, k] =\n            2.0 .* sum(var_spherical[:, :, k, :], dims = 3) .*\n            conj(sum(var_spherical[:, :, k, :], dims = 3))  # var_spectrum[m,n,k] # factor 2 to account for negative Fourier frequencies\n        var_spectrum[1, :, k] = var_spectrum[1, :, k] ./ 2.0 # m=0\n    end\n    return var_spectrum, mesh.wave_numbers, var_spherical, mesh\nend\n\n# TODO: enable mass weighted and vertically integrated calculations\n# TODO: generalize for odd number of latitudes\n"
  },
  {
    "path": "src/Common/Spectra/power_spectrum_les.jl",
    "content": "\"\"\"\n    power_spectrum_3d(::AtmosLESConfigType, u, v, w, L, dim, nor)\n\nCalculates the Powerspectrum from the 3D velocity fields `u`, `v`, `w`. Inputs\nneed to be equi-spaced and the domain is assumed to be the same size and\nhave the same number of points in all directions.\n\n# Arguments\n- L: size domain\n- dim: number of points\n- nor: normalization factor\n\"\"\"\nfunction power_spectrum_3d(::AtmosLESConfigType, u, v, w, L, dim, nor)\n    u_fft = fft(u)\n    v_fft = fft(v)\n    w_fft = fft(w)\n\n    mu = Array((abs.(u_fft) / size(u, 1)^3))\n    mv = Array((abs.(v_fft) / size(v, 1)^3))\n    mw = Array((abs.(w_fft) / size(w, 1)^3))\n    if mod(dim, 2) == 0\n        rx = range(0, stop = dim - 1, step = 1) .- dim / 2 .+ 1\n        ry = range(0, stop = dim - 1, step = 1) .- dim / 2 .+ 1\n        rz = range(0, stop = dim - 1, step = 1) .- dim / 2 .+ 1\n        R_x = circshift(rx', (1, dim / 2 + 1))\n        R_y = circshift(ry', (1, dim / 2 + 1))\n        R_z = circshift(rz', (1, dim / 2 + 1))\n        k_nyq = dim / 2\n    else\n        rx = range(0, stop = dim - 1, step = 1) .- (dim - 1) / 2\n        ry = range(0, stop = dim - 1, step = 1) .- (dim - 1) / 2\n        rz = range(0, stop = dim - 1, step = 1) .- (dim - 1) / 2\n        R_x = circshift(rx', (1, (dim + 1) / 2))\n        R_y = circshift(ry', (1, (dim + 1) / 2))\n        R_z = circshift(rz', (1, (dim + 1) / 2))\n        k_nyq = (dim - 1) / 2\n    end\n    r = zeros(size(rx, 1), size(ry, 1), size(rz, 1))\n    for i in 1:size(rx, 1), j in 1:size(ry, 1), l in 1:size(rz, 1)\n        r[i, j, l] = sqrt(R_x[i]^2 + R_y[j]^2 + R_z[l]^2)\n    end\n    dx = 2 * pi / L\n    k = range(1, stop = k_nyq, step = 1) .* dx\n    endk = size(k, 1)\n    contribution = zeros(size(k, 1))\n    spectrum = zeros(size(k, 1))\n    for N in 2:Int64(k_nyq - 1)\n        for i in 1:size(rx, 1), j in 1:size(ry, 1), l in 1:size(rz, 1)\n            if (r[i, j, l] * dx <= (k'[N + 1] + k'[N]) / 2) &&\n               (r[i, j, l] * dx > (k'[N] + k'[N - 1]) / 2)\n                spectrum[N] =\n                    spectrum[N] + mu[i, j, l]^2 + mv[i, j, l]^2 + mw[i, j, l]^2\n                contribution[N] = contribution[N] + 1\n            end\n        end\n    end\n    for i in 1:size(rx, 1), j in 1:size(ry, 1), l in 1:size(rz, 1)\n        if (r[i, j, l] * dx <= (k'[2] + k'[1]) / 2)\n            spectrum[1] =\n                spectrum[1] + mu[i, j, l]^2 + mv[i, j, l]^2 + mw[i, j, l]^2\n            contribution[1] = contribution[1] + 1\n        end\n    end\n    for i in 1:size(rx, 1), j in 1:size(ry, 1), l in 1:size(rz, 1)\n        if (r[i, j, l] * dx <= k'[endk]) &&\n           (r[i, j, l] * dx > (k'[endk] + k'[endk - 1]) / 2)\n            spectrum[endk] =\n                spectrum[endk] + mu[i, j, l]^2 + mv[i, j, l]^2 + mw[i, j, l]^2\n            contribution[endk] = contribution[endk] + 1\n        end\n    end\n    spectrum = spectrum .* 2 .* pi .* k .^ 2 ./ (contribution .* dx .^ 3) ./ nor\n\n    return spectrum, k\nend\n"
  },
  {
    "path": "src/Common/Spectra/spherical_helper.jl",
    "content": "# helper functions for spherical harmonics spectra\n\n\"\"\"\n    SpectralSphericalMesh\n\nStruct with mesh information.\n\"\"\"\nmutable struct SpectralSphericalMesh\n    # grid info\n    num_fourier::Int64\n    num_spherical::Int64\n    nλ::Int64\n    nθ::Int64\n    nd::Int64\n    Δλ::Float64\n    qwg::Array{Float64, 3}\n    qnm::Array{Float64, 3}   # n,m coordinates\n    wave_numbers::Array{Int64, 2}\n\n    # variables\n    var_grid::Array{Float64, 3}\n    var_fourier::Array{ComplexF64, 3}\n    var_spherical::Array{ComplexF64, 4}\n    var_spectrum::Array{Float64, 3}\n\nend\nfunction SpectralSphericalMesh(nθ::Int64, nd::Int64)\n    nλ = 2nθ\n    Δλ = 2π / nλ\n\n    num_fourier = Int64(floor((2 * nθ - 1) / 3)) # number of truncated zonal wavenumbers (m): minimum truncation given nθ - e.g.: nlat = 32 -> T21 (can change manually for more a severe truncation)\n    num_spherical = Int64(num_fourier + 1) # number of total wavenumbers (n)\n\n    radius = Float64(6371000)\n    wave_numbers = compute_wave_numbers(num_fourier, num_spherical)\n\n    qwg = zeros(Float64, num_fourier + 1, num_spherical + 1, nθ)\n    qnm = zeros(Float64, num_fourier + 1, num_spherical + 2, nθ)\n\n    var_fourier = zeros(Complex{Float64}, nλ, nθ, nd)\n    var_grid = zeros(Float64, nλ, nθ, nd)\n    nθ_half = div(nθ, 2)\n    var_spherical =\n        zeros(Complex{Float64}, num_fourier + 1, num_spherical + 1, nd, nθ_half)\n    var_spectrum = zeros(Float64, num_fourier + 1, num_spherical + 1, nd)\n\n    SpectralSphericalMesh(\n        num_fourier,\n        num_spherical,\n        nλ,\n        nθ,\n        nd,\n        Δλ,\n        qwg,\n        qnm,\n        wave_numbers,\n        var_grid,\n        var_fourier,\n        var_spherical,\n        var_spectrum,\n    )\nend\n\n\"\"\"\n    compute_legendre!(num_fourier, num_spherical, sinθ, nθ)\n\nNormalized associated Legendre polynomials, P_{m,l} = qnm\n\n# Arguments:\n- num_fourier\n- num_spherical\n- sinθ\n- nθ\n\n# References:\n- Ehrendorfer, M. (2011) Spectral Numerical Weather Prediction Models, Appendix B, Society for Industrial and Applied Mathematics\n- Winch, D. (2007) Spherical harmonics, in Encyclopedia of Geomagnetism and Paleomagnetism, Eds Gubbins D. and Herrero-Bervera, E., Springer\n\n# Details (using notation and Eq. references from Ehrendorfer, 2011):\n\n    l=0,1...∞    and m = -l, -l+1, ... l-1, l\n\n    P_{0,0} = 1, such that 1/4π ∫∫YYdS = δ (where Y = spherical harmonics, S = domain surface area)\n    P_{m,m} = sqrt((2m+1)/2m) cosθ P_{m-1m-1}\n    P_{m+1,m} = sqrt(2m+3) sinθ P_{m m}\n    sqrt((l^2-m^2)/(4l^2-1))P_{l,m} = P_{l-1, m} -  sqrt(((l-1)^2-m^2)/(4(l-1)^2 - 1))P_{l-2,m}\n\n    THe normalization assures that 1/2 ∫_{-1}^1 P_{l,m}(sinθ) P_{n,m}(sinθ) dsinθ = δ_{n,l}\n\n    Julia index starts with 1, so qnm[m+1,l+1] = P_l^m\n\"\"\"\nfunction compute_legendre!(num_fourier, num_spherical, sinθ, nθ)\n    qnm = zeros(Float64, num_fourier + 1, num_spherical + 2, nθ)\n\n    cosθ = sqrt.(1 .- sinθ .^ 2)\n    ε = zeros(Float64, num_fourier + 1, num_spherical + 2)\n\n    qnm[1, 1, :] .= 1\n    for m in 1:num_fourier\n        qnm[m + 1, m + 1, :] = -sqrt((2m + 1) / (2m)) .* cosθ .* qnm[m, m, :] # Eq. B.20\n        qnm[m, m + 1, :] = sqrt(2m + 1) * sinθ .* qnm[m, m, :] # Eq. B.22\n    end\n    qnm[num_fourier + 1, num_fourier + 2, :] =\n        sqrt(2 * (num_fourier + 2)) * sinθ .*\n        qnm[num_fourier + 1, num_fourier + 1, :]\n\n    for m in 0:num_fourier\n        for l in (m + 2):(num_spherical + 1)\n            ε1 = sqrt(((l - 1)^2 - m^2) ./ (4 * (l - 1)^2 - 1))\n            ε2 = sqrt((l^2 - m^2) ./ (4 * l^2 - 1))\n            qnm[m + 1, l + 1, :] =\n                (sinθ .* qnm[m + 1, l, :] - ε1 * qnm[m + 1, l - 1, :]) / ε2 # Eq. B.18\n        end\n    end\n\n    return qnm[:, 1:(num_spherical + 1), :]\nend\n\n\"\"\"\n    compute_gaussian!(n)\n\nCompute sin(latitude) and the weight factors for Gaussian integration\n\n# Arguments\n- n: number of latitudes\n\n# References\n- Ehrendorfer, M., Spectral Numerical Weather Prediction Models, Appendix B, Society for Industrial and Applied Mathematics, 2011\n\n# Details (following notation from Ehrendorfer, 2011):\n    Pn(x) is an odd function\n    solve half of the n roots and weightes of Pn(x) # n = 2n_half\n    P_{-1}(x) = 0\n    P_0(x) = 1\n    P_1(x) = x\n    nP_n(x) = (2n-1)xP_{n-1}(x) - (n-1)P_{n-2}(x)\n    P'_n(x) = n/(x^2-1)(xP_{n}(x) - P_{n-1}(x))\n    x -= P_n(x)/P'_{n}()\n    Initial guess xi^{0} = cos(π(i-0.25)/(n+0.5))\n    wi = 2/(1-xi^2)/P_n'(xi)^2\n\"\"\"\nfunction compute_gaussian!(n)\n    itermax = 10000\n    tol = 1.0e-15\n\n    sinθ = zeros(Float64, n)\n    wts = zeros(Float64, n)\n\n    n_half = Int64(n / 2)\n    for i in 1:n_half\n        dp = 0.0\n        z = cos(pi * (i - 0.25) / (n + 0.5))\n        for iter in 1:itermax\n            p2 = 0.0\n            p1 = 1.0\n\n            for j in 1:n\n                p3 = p2 # Pj-2\n                p2 = p1 # Pj-1\n                p1 = ((2.0 * j - 1.0) * z * p2 - (j - 1.0) * p3) / j  #Pj\n            end\n            # P'_n\n            dp = n * (z * p1 - p2) / (z * z - 1.0)\n            z1 = z\n            z = z1 - p1 / dp\n            if (abs(z - z1) <= tol)\n                break\n            end\n            if iter == itermax\n                @error(\"Compute_Gaussian! does not converge!\")\n            end\n        end\n\n        sinθ[i], sinθ[n - i + 1], = -z, z\n        wts[i] = wts[n - i + 1] = 2.0 / ((1.0 - z * z) * dp * dp)\n    end\n\n    return sinθ, wts\nend\n\n\"\"\"\n    trans_grid_to_spherical!(mesh::SpectralSphericalMesh, pfield::Array{Float64,2})\n\nTransforms a variable on a Gaussian grid (pfield[nλ, nθ]) into the spherical harmonics domain (var_spherical2d[num_fourier+1, num_spherical+1])\n\nHere λ = longitude, θ = latitude, η = sinθ, m = zonal wavenumber, n = total wavenumber:\nvar_spherical2d = F_{m,n}    # Output variable in spectral space (Complex{Float64}[num_fourier+1, num_spherical+1])\nqwg = P_{m,n}(η)w(η)         # Weighted Legendre polynomials (Float64[num_fourier+1, num_spherical+1, nθ])\nvar_fourier2d = g_{m, θ}     # Untruncated Fourier transformation (Complex{Float64} [nλ, nθ])\npfield = F(λ, η)             # Input variable on Gaussian grid Float64[nλ, nθ]\n\n# Arguments\n- mesh: struct with mesh information\n- pfield: variable on Gaussian grid to be transformed\n\n# References\n- Ehrendorfer, M., Spectral Numerical Weather Prediction Models, Appendix B, Society for Industrial and Applied Mathematics, 2011\n- [Wiin1967](@cite)\n\"\"\"\nfunction trans_grid_to_spherical!(\n    mesh::SpectralSphericalMesh,\n    pfield::Array{Float64, 2},\n)\n\n    num_fourier, num_spherical = mesh.num_fourier, mesh.num_spherical\n    var_fourier2d, var_spherical2d =\n        mesh.var_fourier[:, :, 1] * 0.0, mesh.var_spherical[:, :, 1, :] * 0.0\n    nλ, nθ, nd = mesh.nλ, mesh.nθ, mesh.nd\n\n    # Retrieve weighted Legendre polynomials\n    qwg = mesh.qwg # qwg[m,n,nθ]\n\n    # Fourier transformation\n    for j in 1:nθ\n        var_fourier2d[:, j] = fft(pfield[:, j], 1) / nλ\n    end\n\n    # Complete spherical harmonic transformation\n    @assert(nθ % 2 == 0)\n    nθ_half = div(nθ, 2)\n    for m in 1:(num_fourier + 1)\n        for n in m:num_spherical\n            var_fourier2d_t = transpose(var_fourier2d[m, :])  # truncates var_fourier(nlon, nhlat) to (nfourier,nlat)\n            if (n - m) % 2 == 0\n                var_spherical2d[m, n, :] .=\n                    (\n                        var_fourier2d_t[1:nθ_half] .+\n                        var_fourier2d_t[nθ:-1:(nθ_half + 1)]\n                    ) .* qwg[m, n, 1:nθ_half] ./ 2.0\n            else\n                var_spherical2d[m, n, :] .=\n                    (\n                        var_fourier2d_t[1:nθ_half] .-\n                        var_fourier2d_t[nθ:-1:(nθ_half + 1)]\n                    ) .* qwg[m, n, 1:nθ_half] ./ 2.0\n            end\n        end\n    end\n\n    return var_spherical2d\nend\n\nfunction compute_wave_numbers(num_fourier::Int64, num_spherical::Int64)\n    \"\"\"\n    See wave_numers[i,j] saves the wave number of this basis\n    \"\"\"\n    wave_numbers = zeros(Int64, num_fourier + 1, num_spherical + 1)\n\n    for m in 0:num_fourier\n        for n in m:num_spherical\n            wave_numbers[m + 1, n + 1] = n\n        end\n    end\n\n    return wave_numbers\n\nend\n"
  },
  {
    "path": "src/Common/SurfaceFluxes/README.md",
    "content": "# ClimateMachine-atmos Surface fluxes\nParameterizations of surface fluxes\n"
  },
  {
    "path": "src/Common/TurbulenceClosures/TurbulenceClosures.jl",
    "content": "\"\"\"\n    TurbulenceClosures\n\nFunctions for turbulence, sub-grid scale modeling. These include\nviscosity terms, diffusivity and stress tensors.\n\n- [`ConstantViscosity`](@ref)\n- [`ViscousSponge`](@ref)\n- [`SmagorinskyLilly`](@ref)\n- [`Vreman`](@ref)\n- [`AnisoMinDiss`](@ref)\n\n\"\"\"\nmodule TurbulenceClosures\n\nusing DocStringExtensions\nusing LinearAlgebra\nusing StaticArrays\nusing UnPack\nimport CLIMAParameters: AbstractParameterSet\nusing CLIMAParameters.Atmos.SubgridScale: inv_Pr_turb\n\nusing ClimateMachine\n\nimport ..Mesh.Geometry: LocalGeometry, lengthscale, lengthscale_horizontal\n\nusing ..Orientations\nusing ..VariableTemplates\nusing ..BalanceLaws\n\nimport ..BalanceLaws:\n    vars_state,\n    eq_tends,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    transform_post_gradient_laplacian!\n\nexport TurbulenceClosureModel,\n    ConstantViscosity,\n    ConstantDynamicViscosity,\n    ConstantKinematicViscosity,\n    SmagorinskyLilly,\n    Vreman,\n    AnisoMinDiss,\n    HyperDiffusion,\n    NoHyperDiffusion,\n    DryBiharmonic,\n    EquilMoistBiharmonic,\n    NoViscousSponge,\n    UpperAtmosSponge,\n    turbulence_tensors,\n    init_aux_turbulence!,\n    init_aux_hyperdiffusion!,\n    sponge_viscosity_modifier,\n    HyperdiffEnthalpyFlux,\n    HyperdiffViscousFlux,\n    WithoutDivergence,\n    WithDivergence\n\n\"\"\"\n    TurbulenceClosureModel\n\nAbstract type with default do-nothing behaviour for\narbitrary turbulence closure models.\n\"\"\"\nabstract type TurbulenceClosureModel end\n\nvars_state(::TurbulenceClosureModel, ::AbstractStateType, FT) = @vars()\n\n\"\"\"\n    ConstantViscosity <: TurbulenceClosureModel\n\nAbstract type for constant viscosity models\n\"\"\"\nabstract type ConstantViscosity <: TurbulenceClosureModel end\n\n\"\"\"\n    HyperDiffusion\n\nAbstract type for HyperDiffusion models\n\"\"\"\nabstract type HyperDiffusion end\n\n\"\"\"\n    ViscousSponge\n\nAbstract type for viscous sponge layers.\nModifier for viscosity computed from existing turbulence closures.\n\"\"\"\nabstract type ViscousSponge end\n\n\n\"\"\"\n    init_aux_turbulence!\n\nInitialise auxiliary variables for turbulence models.\nOverload for specific turbulence closure type.\n\"\"\"\nfunction init_aux_turbulence!(\n    ::TurbulenceClosureModel,\n    ::BalanceLaw,\n    aux::Vars,\n    geom::LocalGeometry,\n) end\n\n\"\"\"\n    compute_gradient_argument!\n\nAssign pre-gradient-transform variables specific to turbulence models.\n\"\"\"\nfunction compute_gradient_argument!(\n    ::TurbulenceClosureModel,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) end\n\"\"\"\n    compute_gradient_flux!(::TurbulenceClosureModel, _...)\nPost-gradient-transformed variables specific to turbulence models.\n\"\"\"\nfunction compute_gradient_flux!(\n    ::TurbulenceClosureModel,\n    ::Orientation,\n    diffusive,\n    ∇transform,\n    state,\n    aux,\n    t,\n) end\n\n# Fallback functions for hyperdiffusion model\nvars_state(::HyperDiffusion, ::AbstractStateType, FT) = @vars()\n\nfunction init_aux_hyperdiffusion!(\n    ::HyperDiffusion,\n    ::BalanceLaw,\n    aux::Vars,\n    geom::LocalGeometry,\n) end\nfunction compute_gradient_argument!(\n    ::HyperDiffusion,\n    ::BalanceLaw,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) end\nfunction transform_post_gradient_laplacian!(\n    h::HyperDiffusion,\n    bl::BalanceLaw,\n    hyperdiffusive::Vars,\n    gradvars::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) end\nfunction compute_gradient_flux!(\n    h::HyperDiffusion,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) end\n\nfunction turbulence_tensors end\n\n\"\"\"\n    ν, D_t, τ = turbulence_tensors(\n        ::TurbulenceClosureModel,\n        orientation::Orientation,\n        param_set::AbstractParameterSet,\n        state::Vars,\n        diffusive::Vars,\n        aux::Vars,\n        t::Real\n    )\n\nCompute the kinematic viscosity (`ν`), the\ndiffusivity (`D_t`) and SGS momentum flux\ntensor (`τ`) for a given turbulence closure.\nEach closure overloads this method with the\nappropriate calculations for the returned\nquantities.\n\n# Arguments\n\n- `::TurbulenceClosureModel` = Struct identifier\n   for turbulence closure model\n- `orientation` = `BalanceLaw.orientation`\n- `param_set` parameter set\n- `state` = Array of prognostic (state) variables.\n   See `vars_state` in `BalanceLaw`\n- `diffusive` = Array of diffusive variables\n- `aux` = Array of auxiliary variables\n- `t` = time\n\"\"\"\nfunction turbulence_tensors(\n    m::TurbulenceClosureModel,\n    viscoussponge,\n    bl::BalanceLaw,\n    state,\n    diffusive,\n    aux,\n    t,\n)\n    param_set = parameter_set(bl)\n    ν, D_t, τ = turbulence_tensors(\n        m,\n        bl.orientation,\n        param_set,\n        state,\n        diffusive,\n        aux,\n        t,\n    )\n    ν, D_t, τ = sponge_viscosity_modifier(bl, viscoussponge, ν, D_t, τ, aux)\n    return (ν, D_t, τ)\nend\n\n\"\"\"\n    principal_invariants(X)\n\nCalculates principal invariants of a tensor `X`. Returns 3 element tuple containing the invariants.\n\"\"\"\nfunction principal_invariants(X)\n    first = tr(X)\n    second = (first^2 - tr(X .^ 2)) / 2\n    third = det(X)\n    return (first, second, third)\nend\n\n\"\"\"\n    symmetrize(X)\n\nGiven a (3,3) second rank tensor X, compute `(X + X')/2`, returning a\nsymmetric `SHermitianCompact` object.\n\"\"\"\nfunction symmetrize(X::StaticArray{Tuple{3, 3}})\n    SHermitianCompact(SVector(\n        X[1, 1],\n        (X[2, 1] + X[1, 2]) / 2,\n        (X[3, 1] + X[1, 3]) / 2,\n        X[2, 2],\n        (X[3, 2] + X[2, 3]) / 2,\n        X[3, 3],\n    ))\nend\n\n\"\"\"\n    norm2(X)\n\nGiven a tensor `X`, computes X:X.\n\"\"\"\nfunction norm2(X::SMatrix{3, 3, FT}) where {FT}\n    abs2(X[1, 1]) +\n    abs2(X[2, 1]) +\n    abs2(X[3, 1]) +\n    abs2(X[1, 2]) +\n    abs2(X[2, 2]) +\n    abs2(X[3, 2]) +\n    abs2(X[1, 3]) +\n    abs2(X[2, 3]) +\n    abs2(X[3, 3])\nend\nfunction norm2(X::SHermitianCompact{3, FT, 6}) where {FT}\n    abs2(X[1, 1]) +\n    2 * abs2(X[2, 1]) +\n    2 * abs2(X[3, 1]) +\n    abs2(X[2, 2]) +\n    2 * abs2(X[3, 2]) +\n    abs2(X[3, 3])\nend\n\n\"\"\"\n    strain_rate_magnitude(S)\n\nGiven the rate-of-strain tensor `S`, computes its magnitude.\n\"\"\"\nfunction strain_rate_magnitude(S::SHermitianCompact{3, FT, 6}) where {FT}\n    return sqrt(2 * norm2(S))\nend\n\n\"\"\"\n    WithDivergence\n\nA divergence type which includes the\ndivergence term in the momentum flux tensor\n\"\"\"\nstruct WithDivergence end\n\n\"\"\"\n    WithoutDivergence\n\nA divergence type which does not include the\ndivergence term in the momentum flux tensor\n\"\"\"\nstruct WithoutDivergence end\n\n\"\"\"\n    ConstantDynamicViscosity <: ConstantViscosity\n\nTurbulence with constant dynamic viscosity (`ρν`).\nDivergence terms are included in the momentum flux\ntensor if divergence_type is WithDivergence.\n\n# Fields\n\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct ConstantDynamicViscosity{FT, DT} <: ConstantViscosity\n    \"Dynamic Viscosity [kg/m/s]\"\n    ρν::FT\n    divergence_type::DT\n    function ConstantDynamicViscosity(\n        ρν::FT,\n        divergence_type::Union{WithDivergence, WithoutDivergence} = WithoutDivergence(),\n    ) where {FT}\n        return new{FT, typeof(divergence_type)}(ρν, divergence_type)\n    end\nend\n\n\"\"\"\n    ConstantKinematicViscosity <: ConstantViscosity\n\nTurbulence with constant kinematic viscosity (`ν`).\nDivergence terms are included in the momentum flux\ntensor if divergence_type is WithDivergence.\n\n# Fields\n\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct ConstantKinematicViscosity{FT, DT} <: ConstantViscosity\n    \"Kinematic Viscosity [m2/s]\"\n    ν::FT\n    divergence_type::DT\n    function ConstantKinematicViscosity(\n        ν::FT,\n        divergence_type::Union{WithDivergence, WithoutDivergence} = WithoutDivergence(),\n    ) where {FT}\n        return new{FT, typeof(divergence_type)}(ν, divergence_type)\n    end\nend\n\nvars_state(::ConstantViscosity, ::Gradient, FT) = @vars()\nvars_state(::ConstantViscosity, ::GradientFlux, FT) =\n    @vars(S::SHermitianCompact{3, FT, 6})\n\nfunction compute_gradient_flux!(\n    ::ConstantViscosity,\n    ::Orientation,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n\n    diffusive.turbulence.S = symmetrize(∇transform.u)\nend\n\ncompute_stress(div_type::WithoutDivergence, ν, S) = (-2 * ν) * S\ncompute_stress(div_type::WithDivergence, ν, S) =\n    (-2 * ν) * S + (2 * ν / 3) * tr(S) * I\n\nfunction turbulence_tensors(\n    m::ConstantDynamicViscosity,\n    orientation::Orientation,\n    param_set::AbstractParameterSet,\n    state::Vars,\n    diffusive::Vars,\n    aux::Vars,\n    t::Real,\n)\n\n    FT = eltype(state)\n    _inv_Pr_turb::FT = inv_Pr_turb(param_set)\n    S = diffusive.turbulence.S\n    ν = m.ρν / state.ρ\n    D_t = ν * _inv_Pr_turb\n    τ = compute_stress(m.divergence_type, ν, S)\n    return ν, D_t, τ\nend\n\nfunction turbulence_tensors(\n    m::ConstantKinematicViscosity,\n    orientation::Orientation,\n    param_set::AbstractParameterSet,\n    state::Vars,\n    diffusive::Vars,\n    aux::Vars,\n    t::Real,\n)\n\n    FT = eltype(state)\n    _inv_Pr_turb::FT = inv_Pr_turb(param_set)\n    S = diffusive.turbulence.S\n    ν = m.ν\n    D_t = ν * _inv_Pr_turb\n    τ = compute_stress(m.divergence_type, ν, S)\n    return ν, D_t, τ\nend\n\n\"\"\"\n    SmagorinskyLilly <: TurbulenceClosureModel\n\n# Fields\n\n$(DocStringExtensions.FIELDS)\n\n# Smagorinsky Model Reference\n\nSee [Smagorinsky1963](@cite)\n\n# Lilly Model Reference\n\nSee [Lilly1962](@cite)\n\n# Brunt-Väisälä Frequency Reference\n\nSee [Durran1982](@cite)\n\n\"\"\"\nstruct SmagorinskyLilly{FT} <: TurbulenceClosureModel\n    \"Smagorinsky Coefficient [dimensionless]\"\n    C_smag::FT\nend\n\nvars_state(::SmagorinskyLilly, ::Auxiliary, FT) = @vars(Δ::FT)\nvars_state(::SmagorinskyLilly, ::Gradient, FT) = @vars(θ_v::FT)\nvars_state(::SmagorinskyLilly, ::GradientFlux, FT) =\n    @vars(S::SHermitianCompact{3, FT, 6}, N²::FT)\n\n\nfunction init_aux_turbulence!(\n    ::SmagorinskyLilly,\n    ::BalanceLaw,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    aux.turbulence.Δ = lengthscale(geom)\nend\n\nfunction compute_gradient_argument!(\n    m::SmagorinskyLilly,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    transform.turbulence.θ_v = aux.moisture.θ_v\nend\n\nfunction compute_gradient_flux!(\n    ::SmagorinskyLilly,\n    orientation::Orientation,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n\n    diffusive.turbulence.S = symmetrize(∇transform.u)\n    ∇Φ = ∇gravitational_potential(orientation, aux)\n    diffusive.turbulence.N² =\n        dot(∇transform.turbulence.θ_v, ∇Φ) / aux.moisture.θ_v\nend\n\nfunction turbulence_tensors(\n    m::SmagorinskyLilly,\n    orientation::Orientation,\n    param_set::AbstractParameterSet,\n    state::Vars,\n    diffusive::Vars,\n    aux::Vars,\n    t::Real,\n)\n\n    FT = eltype(state)\n    _inv_Pr_turb::FT = inv_Pr_turb(param_set)\n    S = diffusive.turbulence.S\n    normS = strain_rate_magnitude(S)\n    k̂ = vertical_unit_vector(orientation, param_set, aux)\n\n    # squared buoyancy correction\n    Richardson = diffusive.turbulence.N² / (normS^2 + eps(normS))\n    f_b² = sqrt(clamp(FT(1) - Richardson * _inv_Pr_turb, FT(0), FT(1)))\n    ν₀ = normS * (m.C_smag * aux.turbulence.Δ)^2 + FT(1e-5)\n    ν = SVector{3, FT}(ν₀, ν₀, ν₀)\n    ν_v = k̂ .* dot(ν, k̂)\n    ν_h = ν₀ .- ν_v\n    ν = SDiagonal(ν_h + ν_v .* f_b²)\n    D_t = diag(ν) * _inv_Pr_turb\n    τ = -2 * ν * S\n    return ν, D_t, τ\nend\n\n\"\"\"\n    Vreman{FT} <: TurbulenceClosureModel\n\nFilter width Δ is the local grid resolution calculated from the mesh metric tensor. A Smagorinsky coefficient\nis specified and used to compute the equivalent Vreman coefficient.\n\n1) ν_e = √(Bᵦ/(αᵢⱼαᵢⱼ)) where αᵢⱼ = ∂uⱼ∂uᵢ with uᵢ the resolved scale velocity component.\n2) βij = Δ²αₘᵢαₘⱼ\n3) Bᵦ = β₁₁β₂₂ + β₂₂β₃₃ + β₁₁β₃₃ - β₁₂² - β₁₃² - β₂₃²\nβᵢⱼ is symmetric, positive-definite.\nIf Δᵢ = Δ, then β = Δ²αᵀα\n\n\n# Fields\n\n$(DocStringExtensions.FIELDS)\n\n# Reference\n\n - [Vreman2004](@cite)\n\"\"\"\nstruct Vreman{FT} <: TurbulenceClosureModel\n    \"Smagorinsky Coefficient [dimensionless]\"\n    C_smag::FT\nend\nvars_state(::Vreman, ::Auxiliary, FT) = @vars(Δ::FT)\nvars_state(::Vreman, ::Gradient, FT) = @vars(θ_v::FT)\nvars_state(::Vreman, ::GradientFlux, FT) =\n    @vars(∇u::SMatrix{3, 3, FT, 9}, N²::FT)\n\nfunction init_aux_turbulence!(\n    ::Vreman,\n    ::BalanceLaw,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    aux.turbulence.Δ = lengthscale(geom)\nend\nfunction compute_gradient_argument!(\n    m::Vreman,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    transform.turbulence.θ_v = aux.moisture.θ_v\nend\nfunction compute_gradient_flux!(\n    ::Vreman,\n    orientation::Orientation,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    diffusive.turbulence.∇u = ∇transform.u\n    ∇Φ = ∇gravitational_potential(orientation, aux)\n    diffusive.turbulence.N² =\n        dot(∇transform.turbulence.θ_v, ∇Φ) / aux.moisture.θ_v\nend\n\nfunction turbulence_tensors(\n    m::Vreman,\n    orientation::Orientation,\n    param_set::AbstractParameterSet,\n    state::Vars,\n    diffusive::Vars,\n    aux::Vars,\n    t::Real,\n)\n    FT = eltype(state)\n    _inv_Pr_turb::FT = inv_Pr_turb(param_set)\n    α = diffusive.turbulence.∇u\n    S = symmetrize(α)\n    k̂ = vertical_unit_vector(orientation, param_set, aux)\n\n    normS = strain_rate_magnitude(S)\n    Richardson = diffusive.turbulence.N² / (normS^2 + eps(normS))\n    f_b² = sqrt(clamp(1 - Richardson * _inv_Pr_turb, 0, 1))\n\n    β = (aux.turbulence.Δ)^2 * (α' * α)\n    Bβ = principal_invariants(β)[2]\n\n    ν₀ = m.C_smag^2 * FT(2.5) * sqrt(abs(Bβ / (norm2(α) + eps(FT))))\n\n    ν = SVector{3, FT}(ν₀, ν₀, ν₀)\n    ν_v = k̂ .* dot(ν, k̂)\n    ν_h = ν₀ .- ν_v\n    ν = SDiagonal(ν_h + ν_v .* f_b²)\n    D_t = diag(ν) * _inv_Pr_turb\n    τ = -2 * ν * S\n    return ν, D_t, τ\nend\n\n\"\"\"\n    AnisoMinDiss{FT} <: TurbulenceClosureModel\n\nFilter width Δ is the local grid resolution\ncalculated from the mesh metric tensor. A\nPoincare coefficient is specified and used\nto compute the equivalent AnisoMinDiss\ncoefficient (computed as the solution to the\neigenvalue problem for the Laplacian operator).\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\n# Reference\n\nSee [Vreugdenhil2018](@cite)\n\n\"\"\"\nstruct AnisoMinDiss{FT} <: TurbulenceClosureModel\n    C_poincare::FT\nend\nvars_state(::AnisoMinDiss, ::Auxiliary, FT) = @vars(Δ::FT)\nvars_state(::AnisoMinDiss, ::Gradient, FT) = @vars(θ_v::FT)\nvars_state(::AnisoMinDiss, ::GradientFlux, FT) =\n    @vars(∇u::SMatrix{3, 3, FT, 9}, N²::FT)\nfunction init_aux_turbulence!(\n    ::AnisoMinDiss,\n    ::BalanceLaw,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    aux.turbulence.Δ = lengthscale(geom)\nend\nfunction compute_gradient_argument!(\n    m::AnisoMinDiss,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    transform.turbulence.θ_v = aux.moisture.θ_v\nend\nfunction compute_gradient_flux!(\n    ::AnisoMinDiss,\n    orientation::Orientation,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ∇Φ = ∇gravitational_potential(orientation, aux)\n    diffusive.turbulence.∇u = ∇transform.u\n    diffusive.turbulence.N² =\n        dot(∇transform.turbulence.θ_v, ∇Φ) / aux.moisture.θ_v\nend\nfunction turbulence_tensors(\n    m::AnisoMinDiss,\n    orientation::Orientation,\n    param_set::AbstractParameterSet,\n    state::Vars,\n    diffusive::Vars,\n    aux::Vars,\n    t::Real,\n)\n    FT = eltype(state)\n    k̂ = vertical_unit_vector(orientation, param_set, aux)\n    _inv_Pr_turb::FT = inv_Pr_turb(param_set)\n\n    ∇u = diffusive.turbulence.∇u\n    S = symmetrize(∇u)\n    normS = strain_rate_magnitude(S)\n\n    δ = aux.turbulence.Δ\n    Richardson = diffusive.turbulence.N² / (normS^2 + eps(normS))\n    f_b² = sqrt(clamp(1 - Richardson * _inv_Pr_turb, 0, 1))\n\n    δ_vec = SVector(δ, δ, δ)\n    δ_m = δ_vec ./ transpose(δ_vec)\n    ∇û = ∇u .* δ_m\n    Ŝ = symmetrize(∇û)\n    ν₀ =\n        (m.C_poincare .* δ_vec) .^ 2 * max(\n            FT(1e-5),\n            -dot(transpose(∇û) * (∇û), Ŝ) / (dot(∇û, ∇û) .+ eps(normS)),\n        )\n\n    ν_v = k̂ .* dot(ν₀, k̂)\n    ν_h = ν₀ .- ν_v\n    ν = SDiagonal(ν_h + ν_v .* f_b²)\n    D_t = diag(ν) * _inv_Pr_turb\n    τ = -2 * ν * S\n    return ν, D_t, τ\nend\n\n\"\"\"\n    NoHyperDiffusion <: HyperDiffusion\n\nDefines a default hyperdiffusion model with\nzero hyperdiffusive fluxes.\n\"\"\"\nstruct NoHyperDiffusion <: HyperDiffusion end\n\n\"\"\"\n    EquilMoistBiharmonic{FT} <: HyperDiffusion\n\nAssumes equilibrium thermodynamics in compressible\nflow. Horizontal hyperdiffusion methods for\napplication in GCM and LES settings Timescales are\nprescribed by the user while the diffusion coefficient\nis computed as a function of the grid lengthscale.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct EquilMoistBiharmonic{FT} <: HyperDiffusion\n    τ_timescale::FT\n    τ_timescale_q_tot::FT\nend\n\nEquilMoistBiharmonic(τ_timescale::FT) where {FT} =\n    EquilMoistBiharmonic(τ_timescale, τ_timescale)\n\nvars_state(::EquilMoistBiharmonic, ::Auxiliary, FT) = @vars(Δ::FT)\nvars_state(::EquilMoistBiharmonic, ::Gradient, FT) =\n    @vars(u_h::SVector{3, FT}, h_tot::FT, q_tot::FT)\nvars_state(::EquilMoistBiharmonic, ::GradientLaplacian, FT) =\n    @vars(u_h::SVector{3, FT}, h_tot::FT, q_tot::FT)\nvars_state(::EquilMoistBiharmonic, ::Hyperdiffusive, FT) = @vars(\n    ν∇³u_h::SMatrix{3, 3, FT, 9},\n    ν∇³h_tot::SVector{3, FT},\n    ν∇³q_tot::SVector{3, FT}\n)\n\nfunction init_aux_hyperdiffusion!(\n    ::EquilMoistBiharmonic,\n    ::BalanceLaw,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    aux.hyperdiffusion.Δ = lengthscale_horizontal(geom)\nend\n\nfunction compute_gradient_argument!(\n    h::EquilMoistBiharmonic,\n    bl::BalanceLaw,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ρinv = 1 / state.ρ\n    u = state.ρu * ρinv\n    k̂ = vertical_unit_vector(bl, aux)\n    u_h = (SDiagonal(1, 1, 1) - k̂ * k̂') * u\n    transform.hyperdiffusion.u_h = u_h\n    transform.hyperdiffusion.h_tot = transform.energy.h_tot\n    transform.hyperdiffusion.q_tot = state.moisture.ρq_tot * ρinv\nend\n\nfunction transform_post_gradient_laplacian!(\n    h::EquilMoistBiharmonic,\n    bl::BalanceLaw,\n    hyperdiffusive::Vars,\n    hypertransform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    param_set = parameter_set(bl)\n    _inv_Pr_turb = eltype(state)(inv_Pr_turb(param_set))\n    ∇Δu_h = hypertransform.hyperdiffusion.u_h\n    ∇Δh_tot = hypertransform.hyperdiffusion.h_tot\n    ∇Δq_tot = hypertransform.hyperdiffusion.q_tot\n    # Unpack\n    τ_timescale = h.τ_timescale\n    τ_timescale_q_tot = h.τ_timescale_q_tot\n    # Compute hyperviscosity coefficient\n    ν₄ = (aux.hyperdiffusion.Δ / 2)^4 / 2 / τ_timescale\n    ν₄_q_tot = (aux.hyperdiffusion.Δ / 2)^4 / 2 / τ_timescale_q_tot\n    hyperdiffusive.hyperdiffusion.ν∇³u_h = ν₄ * ∇Δu_h\n    hyperdiffusive.hyperdiffusion.ν∇³h_tot = ν₄ * ∇Δh_tot\n    hyperdiffusive.hyperdiffusion.ν∇³q_tot = ν₄_q_tot * ∇Δq_tot\nend\n\n\"\"\"\n    DryBiharmonic{FT} <: HyperDiffusion\n\nAssumes dry compressible flow.\nHorizontal hyperdiffusion methods for application\nin GCM and LES settings Timescales are prescribed\nby the user while the diffusion coefficient is\ncomputed as a function of the grid lengthscale.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct DryBiharmonic{FT} <: HyperDiffusion\n    τ_timescale::FT\nend\nvars_state(::DryBiharmonic, ::Auxiliary, FT) = @vars(Δ::FT)\nvars_state(::DryBiharmonic, ::Gradient, FT) =\n    @vars(u_h::SVector{3, FT}, h_tot::FT)\nvars_state(::DryBiharmonic, ::GradientLaplacian, FT) =\n    @vars(u_h::SVector{3, FT}, h_tot::FT)\nvars_state(::DryBiharmonic, ::Hyperdiffusive, FT) =\n    @vars(ν∇³u_h::SMatrix{3, 3, FT, 9}, ν∇³h_tot::SVector{3, FT})\n\nfunction init_aux_hyperdiffusion!(\n    ::DryBiharmonic,\n    ::BalanceLaw,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    aux.hyperdiffusion.Δ = lengthscale_horizontal(geom)\nend\n\nfunction compute_gradient_argument!(\n    h::DryBiharmonic,\n    bl::BalanceLaw,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ρinv = 1 / state.ρ\n    u = state.ρu * ρinv\n    k̂ = vertical_unit_vector(bl, aux)\n    u_h = (SDiagonal(1, 1, 1) - k̂ * k̂') * u\n    transform.hyperdiffusion.u_h = u_h\n    transform.hyperdiffusion.h_tot = transform.energy.h_tot\nend\n\nfunction transform_post_gradient_laplacian!(\n    h::DryBiharmonic,\n    bl::BalanceLaw,\n    hyperdiffusive::Vars,\n    hypertransform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    param_set = parameter_set(bl)\n    _inv_Pr_turb = eltype(state)(inv_Pr_turb(param_set))\n    ∇Δu_h = hypertransform.hyperdiffusion.u_h\n    ∇Δh_tot = hypertransform.hyperdiffusion.h_tot\n    # Unpack\n    τ_timescale = h.τ_timescale\n    # Compute hyperviscosity coefficient\n    ν₄ = (aux.hyperdiffusion.Δ / 2)^4 / 2 / τ_timescale\n    hyperdiffusive.hyperdiffusion.ν∇³u_h = ν₄ * ∇Δu_h\n    hyperdiffusive.hyperdiffusion.ν∇³h_tot = ν₄ * ∇Δh_tot\nend\n\n\"\"\"\n    NoViscousSponge\n\nNo modifiers applied to viscosity/diffusivity in sponge layer\n\n# Fields\n\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct NoViscousSponge <: ViscousSponge end\nfunction sponge_viscosity_modifier(\n    bl::BalanceLaw,\n    m::NoViscousSponge,\n    ν,\n    D_t,\n    τ,\n    aux,\n)\n    return (ν, D_t, τ)\nend\n\n\"\"\"\n    UpperAtmosSponge{FT} <: ViscousSponge\n\nUpper domain viscous relaxation.\n\nApplies modifier to viscosity and diffusivity terms\nin a user-specified upper domain sponge region\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct UpperAtmosSponge{FT} <: ViscousSponge\n    \"Maximum domain altitude (m)\"\n    z_max::FT\n    \"Altitude at with sponge starts (m)\"\n    z_sponge::FT\n    \"Sponge Strength 0 ⩽ α_max ⩽ 1\"\n    α_max::FT\n    \"Sponge exponent\"\n    γ::FT\nend\n\nfunction sponge_viscosity_modifier(\n    bl::BalanceLaw,\n    m::UpperAtmosSponge,\n    ν,\n    D_t,\n    τ,\n    aux::Vars,\n)\n    param_set = parameter_set(bl)\n    z = altitude(bl.orientation, param_set, aux)\n    if z >= m.sponge\n        r = (z - m.z_sponge) / (m.z_max - m.z_sponge)\n        β_sponge = m.α_max * sinpi(r / 2)^m.γ\n        ν += β_sponge * ν\n        D_t += β_sponge * D_t\n        τ += β_sponge * τ\n    end\n    return (ν, D_t, τ)\nend\n\nconst Biharmonic = Union{EquilMoistBiharmonic, DryBiharmonic}\n\nstruct HyperdiffEnthalpyFlux <: TendencyDef{Flux{SecondOrder}} end\nstruct HyperdiffViscousFlux <: TendencyDef{Flux{SecondOrder}} end\n\n# empty by default\neq_tends(pv::PV, ::HyperDiffusion, ::AbstractTendencyType) where {PV} = ()\n\n# Enthalpy and viscous for Biharmonic model\neq_tends(::AbstractEnergyVariable, ::Biharmonic, ::Flux{SecondOrder}) =\n    (HyperdiffEnthalpyFlux(), HyperdiffViscousFlux())\n\n# Viscous for Biharmonic model\neq_tends(::AbstractMomentumVariable, ::Biharmonic, ::Flux{SecondOrder}) =\n    (HyperdiffViscousFlux(),)\n\nend #module TurbulenceClosures.jl\n"
  },
  {
    "path": "src/Common/TurbulenceConvection/TurbulenceConvection.jl",
    "content": "\"\"\"\n    TurbulenceConvection\n\nTurbulence convection models, for example\nthe Eddy-Diffusivity Mass-Flux model\n\"\"\"\nmodule TurbulenceConvection\n\nusing ..BalanceLaws: BalanceLaw, AbstractStateType\nusing ..VariableTemplates: @vars, Vars, Grad\n\nimport ..Mesh.Filters: vars_state_filtered\n\nexport TurbulenceConvectionModel, NoTurbConv\n\nexport init_state_prognostic!,\n    init_aux_turbconv!, turbconv_nodal_update_auxiliary_state!\n\nimport ..BalanceLaws:\n    vars_state,\n    eq_tends,\n    prognostic_to_primitive!,\n    primitive_to_prognostic!,\n    precompute,\n    prognostic_vars,\n    init_state_prognostic!,\n    init_state_auxiliary!,\n    update_auxiliary_state!,\n    boundary_state!,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    integral_load_auxiliary_state!,\n    integral_set_auxiliary_state!\n\nusing ..MPIStateArrays: MPIStateArray\nusing ..DGMethods: DGModel, LocalGeometry\n\nabstract type TurbulenceConvectionModel end\n\n\"\"\"\n    NoTurbConv <: TurbulenceConvectionModel\n\nA \"no model\" type, which results in kernels that\npass through and do nothing.\n\"\"\"\nstruct NoTurbConv <: TurbulenceConvectionModel end\n\nprognostic_vars(::NoTurbConv) = ()\n\neq_tends(pv, ::NoTurbConv, tt) = ()\nprecompute(::NoTurbConv, bl, args, ts, tend_type) = NamedTuple()\n\nvars_state(m::TurbulenceConvectionModel, ::AbstractStateType, FT) = @vars()\nvars_state_filtered(m::TurbulenceConvectionModel, FT) = @vars()\n\nfunction init_aux_turbconv!(\n    m::TurbulenceConvectionModel,\n    bl::BalanceLaw,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    return nothing\nend\n\nfunction update_auxiliary_state!(\n    dg::DGModel,\n    m::TurbulenceConvectionModel,\n    bl::BalanceLaw,\n    Q::MPIStateArray,\n    t::Real,\n    elems::UnitRange,\n)\n    return nothing\nend\n\nfunction turbconv_nodal_update_auxiliary_state!(\n    m::TurbulenceConvectionModel,\n    bl::BalanceLaw,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    return nothing\nend\n\nfunction compute_gradient_argument!(\n    m::TurbulenceConvectionModel,\n    bl::BalanceLaw,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    return nothing\nend\n\nfunction compute_gradient_flux!(\n    m::TurbulenceConvectionModel,\n    bl::BalanceLaw,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    return nothing\nend\n\nfunction integral_load_auxiliary_state!(\n    m::TurbulenceConvectionModel,\n    bl::BalanceLaw,\n    integ::Vars,\n    state::Vars,\n    aux::Vars,\n)\n    return nothing\nend\n\nfunction integral_set_auxiliary_state!(\n    m::TurbulenceConvectionModel,\n    bl::BalanceLaw,\n    aux::Vars,\n    integ::Vars,\n)\n    return nothing\nend\n\nfunction init_state_prognostic!(\n    m::NoTurbConv,\n    bl::BalanceLaw,\n    state,\n    aux,\n    localgeo,\n    t,\n) end\n\nprognostic_to_primitive!(::NoTurbConv, _...) = nothing\nprimitive_to_prognostic!(::NoTurbConv, _...) = nothing\n\ninclude(\"boundary_conditions.jl\")\ninclude(\"source.jl\")\n\nend\n"
  },
  {
    "path": "src/Common/TurbulenceConvection/boundary_conditions.jl",
    "content": "#### Boundary conditions\n\nexport TurbConvBC,\n    NoTurbConvBC,\n    turbconv_bcs,\n    turbconv_boundary_state!,\n    turbconv_normal_boundary_flux_second_order!\n\nabstract type TurbConvBC end\n\n\"\"\"\n    NoTurbConvBC <: TurbConvBC\n\nBoundary conditions are not applied\n\"\"\"\nstruct NoTurbConvBC <: TurbConvBC end\n\nturbconv_bcs(::NoTurbConv) = (NoTurbConvBC(), NoTurbConvBC())\n\nfunction turbconv_boundary_state!(nf, bc_turbulence::NoTurbConvBC, bl, _...)\n    nothing\nend\n\nfunction turbconv_normal_boundary_flux_second_order!(\n    nf,\n    bc_turbulence::NoTurbConvBC,\n    bl,\n    _...,\n)\n    nothing\nend\n"
  },
  {
    "path": "src/Common/TurbulenceConvection/source.jl",
    "content": "\nexport turbconv_sources\n\nturbconv_sources(m::TurbulenceConvectionModel) = ()\n"
  },
  {
    "path": "src/Diagnostics/Debug/StateCheck.jl",
    "content": "\"\"\"\n    StateCheck\n\nModule with a minimal set of functions for getting statistics\nand basic I/O from ClimateMachine DG state arrays (`MPIStateArray` type).\nCreated for regression testing and code change tracking and debugging.\nStateCheck functions iterate over named variables in an `MPIStateArray`,\ncalculate and report their statistics and/or write values for all or\nsome subset of points at a fixed frequency.\n\n# Functions\n\n - [`sccreate`](@ref) Create a StateCheck callback variable.\n - [`scdocheck`](@ref) Check StateCheck variable values against reference values.\n - [`scprintref`](@ref) Print StateCheck variable in format for creating reference values.\n\"\"\"\nmodule StateCheck\n\n# Imports from standard Julia packages\nusing Formatting\nusing MPI\nusing ..MPIStateArrays: vars\nusing PrettyTables\nusing Printf\nusing StaticArrays\nusing Statistics\nusing ..VariableTemplates:\n    flattenednames, flattened_tup_chain, RetainArr, varsindex, varsize\n\n# Imports from ClimateMachine core\nimport ..GenericCallbacks: EveryXSimulationSteps\nimport ..MPIStateArrays: MPIStateArray\n\n\"\"\"\n    VStat\n\nType for returning variable statistics.\n\"\"\"\nstruct VStat\n    max::Any\n    min::Any\n    mean::Any\n    std::Any\nend\n\n# Global functions to expose\nexport sccreate # Create a state checker callback\nexport scdocheck\nexport scprintref\n\nconst nt_freq_def = 10 # default frequency (in time steps) for output.\nconst prec_def = 15    # default precision used for formatted output table\n\n# TODO: this should use the new callback interface\n\n\"\"\"\n    sccreate(\n        io::IO,\n        fields::Array{<:Tuple{<:MPIStateArray, String}},\n        nt_freq::Int = $nt_freq_def;\n        prec = $prec_def\n    )\n\nCreate a \"state check\" call-back for one or more `MPIStateArray` variables\nthat will report basic statistics for the fields in the array.\n\n  -  `io` an IO stream to use for printed output\n  -  `fields` a required first argument that is an array of one or more\n                        `MPIStateArray` variable and label string pair tuples.\n                        State array statistics will be reported for the named symbols\n                        in each `MPIStateArray` labeled with the label string.\n  -  `nt_freq` an optional second argument with default value of\n                        $nt_freq_def that sets how frequently (in time-step counts) the\n                        statistics are reported.\n  -  `prec` a named argument that sets number of decimal places to print for\n                        statistics, defaults to $prec_def.\n\n# Examples\n```julia\nusing ClimateMachine.VariableTemplates\nusing StaticArrays\nusing ClimateMachine.MPIStateArrays\nusing MPI\nMPI.Init()\nFT=Float64\nF1=@vars begin; ν∇u::SMatrix{3, 2, FT, 6}; κ∇θ::SVector{3, FT}; end\nF2=@vars begin; u::SVector{2, FT}; θ::SVector{1, FT}; end\nQ1=MPIStateArray{Float32,F1}(MPI.COMM_WORLD,ClimateMachine.array_type(),4,9,8);\nQ2=MPIStateArray{Float64,F2}(MPI.COMM_WORLD,ClimateMachine.array_type(),4,6,8);\ncb=ClimateMachine.StateCheck.sccreate([(Q1,\"My gradients\"),(Q2,\"My fields\")],1; prec=$prec_def);\n```\n\"\"\"\nfunction sccreate(\n    io::IO,\n    fields::Array{<:Tuple{<:MPIStateArray, String}},\n    nt_freq::Int = nt_freq_def;\n    prec = prec_def,\n    print_head = true,\n)\n    mpicomm = first(first(fields)).mpicomm\n    mpirank = MPI.Comm_rank(mpicomm)\n    mpirank == 0 && println(io, \"# SC Start: creating state check callback\")\n\n    ####\n    # Print fields that the callback created by this call will query\n    ####\n    for (Q, lab) in fields\n        V = vars(Q)\n        ftc = flattened_tup_chain(V, RetainArr())\n        nss = length(flattenednames(V))\n        if length(flattenednames(V)) == 0 && mpirank == 0\n            println(\n                io,\n                \"# SC  MPIStateArray labeled \\\"$lab\\\" has no named symbols.\",\n            )\n        elseif mpirank == 0\n            println(\n                io,\n                \"# SC Creating state check callback labeled \\\"$lab\\\" for symbols\",\n            )\n            for s in join.(ftc, \"\")\n                println(io, \"# SC \", s)\n            end\n        end\n    end\n\n    ###\n    # Initialize total calls counter for the callback\n    ###\n    n_cb_calls = 0\n\n    ###\n    # Create holder for most recent stats\n    ###\n    nvars = sum([length(flattenednames(vars(Q))) for (Q, lab) in fields])\n    cur_stats_flat = Array{Any}(undef, nvars)\n\n    # Save io pointer\n    iosave = io\n\n    ######\n    # Create the callback\n    ######\n    cb = EveryXSimulationSteps(nt_freq) do\n        # Track which timestep this is\n        n_cb_calls = n_cb_calls + 1\n        n_step = (n_cb_calls - 1) * nt_freq + 1\n        ns_str = @sprintf(\"%7.7d\", n_step - 1)\n        io = iosave\n        # Obscure trick to do with running in cells in notebook that close Base.stdout\n        # between different stages. This affects parsing Literate/Documenter examples.\n        if !isopen(io)\n            io = Base.stdout\n        end\n\n        ## Print header\n        nprec = min(max(1, prec), 20)\n        if mpirank == 0\n            println(\n                io,\n                \"# SC +++++++++++ClimateMachine StateCheck call-back start+++++++++++++++++\",\n            )\n            println(io, \"# SC Step = $ns_str\")\n        end\n\n        labs = vcat([\n            map(x -> lab, flattenednames(vars(Q))) for (Q, lab) in fields\n        ]...)\n\n        varnames = vcat([\n            map(x -> x, flattenednames(vars(Q))) for (Q, lab) in fields\n        ]...)\n\n        header = [\"Label\" \"Field\" \"min()\" \"max()\" \"mean()\" \"std()\"]\n\n        stats_all = vcat([\n            [scstats(Q, i, nprec)[5] for i in 1:varsize(vars(Q))] for (Q, lab) in fields\n        ]...)\n\n        cur_stats_flat .= vcat([\n            [\n                begin\n                    scstats_i = scstats(Q, i, nprec)[5]\n                    [\n                        lab,\n                        fn,\n                        scstats_i.max,\n                        scstats_i.min,\n                        scstats_i.mean,\n                        scstats_i.std,\n                    ]\n                end for (i, fn) in enumerate(flattenednames(vars(Q)))\n            ] for (Q, lab) in fields\n        ]...)\n\n        # TODO: Verify, not sure why we are swapping these columns:\n        # data_min = map(x->x.min, stats_all)\n        # data_max = map(x->x.max, stats_all)\n\n        data_max = map(x -> x.min, stats_all)\n        data_min = map(x -> x.max, stats_all)\n\n        data_mean = map(x -> x.mean, stats_all)\n        data_std = map(x -> x.std, stats_all)\n        data = hcat(labs, varnames, data_min, data_max, data_mean, data_std)\n        pretty_table(\n            io,\n            data,\n            header;\n            formatters = ft_printf(\"%.16e\", 3:6),\n            header_crayon = crayon\"yellow bold\",\n            subheader_crayon = crayon\"green bold\",\n            crop = :none,\n        )\n\n        if mpirank == 0\n            println(\n                io,\n                \"# SC +++++++++++ClimateMachine StateCheck call-back end+++++++++++++++++++\",\n            )\n        end\n    end\n\n    if mpirank == 0\n        println(io, \"# SC Finish: creating state check callback\")\n    end\n\n    return cb\nend\n\nfunction scstats(Q, ivar, nprec)\n\n    # Get number of MPI procs\n    mpicomm = Q.mpicomm\n    nproc = MPI.Comm_size(mpicomm)\n\n    npr = nprec\n    fmt = @sprintf(\"%%%d.%de\", npr + 8, npr)\n\n    getByField(Q, ivar) = (Q.realdata[:, ivar, :])\n\n    # Get local and global field sizes (degrees of freedom).\n    n_size_loc = length(getByField(Q, ivar))\n    n_size_tot = MPI.Allreduce(n_size_loc, +, mpicomm)\n\n    # Min\n    phi_loc = minimum(getByField(Q, ivar))\n    phi_min = MPI.Allreduce(phi_loc, min, mpicomm)\n    phi = phi_min\n    # minVstr=@sprintf(\"%23.15e\",phi)\n    min_v_str = sprintf1(fmt, phi)\n\n    # Max\n    phi_loc = maximum(getByField(Q, ivar))\n    phi_max = MPI.Allreduce(phi_loc, max, mpicomm)\n    phi = phi_max\n    # maxVstr=@sprintf(\"%23.15e\",phi)\n    max_v_str = sprintf1(fmt, phi)\n\n    # Ave\n    phi_loc = mean(getByField(Q, ivar))\n    phi_loc = phi_loc * n_size_loc / n_size_tot\n    phi_sum = MPI.Allreduce(phi_loc, +, mpicomm)\n    phi_mean = phi_sum\n    phi = phi_mean\n    # aveVstr=@sprintf(\"%23.15e\",phi)\n    ave_v_str = sprintf1(fmt, phi)\n\n    # Std\n    phi_loc = (getByField(Q, ivar) .- phi_mean) .^ 2\n    phi_loc = sum(phi_loc)  # Sum local data explicitly since GPU Allreduce\n    # does not take arrays yet.\n    phi_sum = MPI.Allreduce(phi_loc, +, mpicomm)\n    n_val_sum = n_size_tot\n    phi_std = sqrt(sum(phi_sum) / (n_val_sum - 1))\n    phi = phi_std\n    # stdVstr=@sprintf(\"%23.15e\",phi)\n    std_v_str = sprintf1(fmt, phi)\n\n    vals = VStat(phi_min, phi_max, phi_mean, phi_std)\n\n    return min_v_str, max_v_str, ave_v_str, std_v_str, vals\nend\n\nfunction sccreate(\n    fields::Array{<:Tuple{<:MPIStateArray, String}},\n    nt_freq::Int = nt_freq_def;\n    prec = prec_def,\n)\n    return sccreate(Base.stdout, fields, nt_freq; prec = prec)\nend\n\n\n\"\"\"\n    scprintref(cb)\n\nPrint out a \"state check\" call-back table of values in a format\nsuitable for use as a set of reference numbers for CI comparison.\n\n - `cb` callback variable of type ClimateMachine.GenericCallbacks.Every*\n\"\"\"\nfunction scprintref(cb)\n    sc = cb.callback\n    io = sc.iosave\n    # Obscure trick to do with running in cells in notebook\n    if !isopen(io)\n        io = Base.stdout\n    end\n    mpirank = MPI.Comm_rank(MPI.COMM_WORLD)\n    mpirank == 0 || return\n    # Get print format lengths for cols 1 and 2 so they are aligned\n    # for readability.\n    phi = sc.cur_stats_flat\n    a1l = maximum(length.(map(x -> x[1], phi)))\n    a2l = maximum(length.(String.(map(x -> x[2], phi))))\n    fmt1 = @sprintf(\"%%%d.%ds\", a1l, a1l) # Column 1\n    fmt2 = @sprintf(\"%%%d.%ds\", a2l, a2l) # Column 2\n    fmt3 = @sprintf(\"%%28.20e\")         # All numbers at full precision\n    # Create an string of spaces to be used for formatting\n    sp = repeat(\" \", 75)\n\n    # Write header\n    println(io, \"# BEGIN SCPRINT\")\n    println(io, \"# varr - reference values (from reference run)    \")\n    println(io, \"# parr - digits match precision (hand edit as needed) \")\n    println(io, \"#\")\n    println(io, \"# [\")\n    println(\n        io,\n        \"#  [ MPIStateArray Name, Field Name, Maximum, Minimum, Mean, Standard Deviation ],\",\n    )\n    println(\n        io,\n        \"#  [         :                :          :        :      :          :           ],\",\n    )\n    println(io, \"# ]\")\n    #\n    # Write tables\n    #  Reference value and precision match tables are separate since it is more\n    #  common to update reference values occasionally while precision values are\n    #  typically changed rarely and the precision values are hand edited from experience.\n    #\n    # Write table of reference values\n    println(io, \"varr = [\")\n    for (s1, s2, s3′, s4′, s5′, s6′) in sc.cur_stats_flat\n        s1 = sp[1:(a1l - length(s1))] * \"\\\"\" * s1 * \"\\\"\"\n        s2 = sp[1:(a2l - length(s2))] * \"\\\"\" * s2 * \"\\\"\"\n        s3 = sprintf1(fmt3, s3′)\n        s4 = sprintf1(fmt3, s4′)\n        s5 = sprintf1(fmt3, s5′)\n        s6 = sprintf1(fmt3, s6′)\n        srow = (s3, s4, s5, s6)\n        println(io, \" [ \", s1, \", \", s2, \", \", join(srow, \",\"), \" ],\")\n    end\n    println(io, \"]\")\n\n    # Write table of reference match precisions using default precision that\n    # can be hand updated.\n    println(io, \"parr = [\")\n    for (s1, s2, s3′, s4′, s5′, s6′) in sc.cur_stats_flat\n        s1 = sp[1:(a1l - length(s1))] * \"\\\"\" * s1 * \"\\\"\"\n        s2 = sp[1:(a2l - length(s2))] * \"\\\"\" * s2 * \"\\\"\"\n        println(io, \" [ \", s1, \", \", s2, \",\", \"    16,    16,    16,    16 ],\")\n    end\n    println(io, \"]\")\n    println(io, \"# END SCPRINT\")\nend\n\n# TODO: write unit test for process_stat\nfunction process_stat(row)\n    ## Debugging\n    # println(row)\n    # println(ref_dat_val)\n    # println(ref_dat_prec)\n    row_col_pass = 0\n    row_col_na = 0\n    row_col_fail = 0\n\n    ## Make array copy for reporting\n\n    ## Check MPIStateArrayName\n    cval = row.lab.cur\n    rval = row.lab.ref_val\n    if cval != rval\n        row_col_fail += 1\n        lab = \"N\" * \"(\" * rval * \")\"\n    else\n        lab = cval\n        row_col_pass += 1\n        lab = rval\n    end\n\n    ## Check term name\n    cval = row.varname.cur\n    rval = row.varname.ref_val\n    if cval != rval\n        varname = \"N\" * \"(\" * rval * \")\"\n        row_col_fail += 1\n    else\n        varname = cval\n        row_col_pass += 1\n    end\n\n    res_dat = Dict()\n    # Check numeric values\n    for nv in (:min, :max, :mean, :std)\n        fmt = @sprintf(\"%%28.20e\")\n        lfld = 28\n        cval = getproperty(row, nv).cur\n        cvalc = sprintf1(fmt, cval)\n        rval = getproperty(row, nv).ref_val\n        rvalc = sprintf1(fmt, rval)\n        pcmp = getproperty(row, nv).ref_prec\n\n        # Skip if compare digits set to 0\n        if pcmp > 0\n\n            # Check exponent part\n            ep1 = cvalc[(lfld - 3):lfld]\n            ep2 = rvalc[(lfld - 3):lfld]\n            if ep1 != ep2\n                nmatch = 0\n            else\n                # Now check individual digits left to right\n                dp1 = cvalc[2:(3 + pcmp + 1)]\n                dp2 = rvalc[2:(3 + pcmp + 1)]\n                nmatch = 0\n                imatch = 1\n                for c in dp1\n                    if c == dp2[imatch]\n                        nmatch = imatch\n                    else\n                        break\n                    end\n                    imatch += 1\n                end\n            end\n            # Check for trailing exact 0s number change numerically\n            if nmatch < pcmp\n                if rval != 0\n                    e_diffl10 = round(log10(abs((rval - cval) / rval)))\n                else\n                    e_diffl10 = round(log10(abs(rval - cval)))\n                end\n                if e_diffl10 < -pcmp\n                    nmatch = Int(-e_diffl10)\n                end\n            end\n            if nmatch < pcmp\n                res_dat[nv] = \"N(\" * string(nmatch) * \")\"\n                row_col_fail += 1\n            else\n                res_dat[nv] = string(nmatch)\n                row_col_pass += 1\n            end\n        else\n            res_dat[nv] = \"0\"\n            row_col_na += 1\n        end\n    end\n    na_count = row_col_na\n    fail_count = row_col_fail\n    pass_count = row_col_pass\n    return (; lab, varname, res_dat..., pass_count, fail_count, na_count)\nend\n\n\"\"\"\n    scdocheck(cb, ref_dat)\n\nCompare a current State check call-back set of values with a\nreference set and match precision table pair.\n\n - `cb` `StateCheck` call-back variables\n - `ref_dat` an array of reference values and precision to match tables.\n\"\"\"\nfunction scdocheck(cb, ref_dat)\n    sc = cb.callback\n    io = sc.iosave\n    # Obscure trick to do with running in cells in notebook\n    if !isopen(io)\n        io = Base.stdout\n    end\n    mpirank = MPI.Comm_rank(MPI.COMM_WORLD)\n    mpirank == 0 || return true\n    println(\n        io,\n        \"# SC +++++++++++ClimateMachine StateCheck ref val check start+++++++++++++++++\",\n    )\n    println(\n        io,\n        \"# SC \\\"N( )\\\" bracketing indicates field failed to match      \",\n    )\n\n    get_row(cur, ref_prec, ref_val, i) =\n        (; cur = cur[i], ref_prec = ref_prec[i], ref_val = ref_val[i])\n\n    Z = (sc.cur_stats_flat, ref_dat...)\n    stats_all = [\n        begin\n            (;\n                lab = get_row(row, row_ref_prec, row_ref_val, 1),\n                varname = get_row(row, row_ref_prec, row_ref_val, 2),\n                min = get_row(row, row_ref_prec, row_ref_val, 3),\n                max = get_row(row, row_ref_prec, row_ref_val, 4),\n                mean = get_row(row, row_ref_prec, row_ref_val, 5),\n                std = get_row(row, row_ref_prec, row_ref_val, 6),\n            )\n        end for (row, row_ref_val, row_ref_prec) in zip(Z...)\n    ]\n\n    data_all = map(row -> process_stat(row), stats_all)\n\n    labs = map(stats -> stats.lab, data_all)\n    varnames = map(stats -> stats.varname, data_all)\n    data_min = map(stats -> stats.min, data_all)\n    data_max = map(stats -> stats.max, data_all)\n    data_mean = map(stats -> stats.mean, data_all)\n    data_std = map(stats -> stats.std, data_all)\n    pass_count = map(stats -> stats.pass_count, data_all)\n    fail_count = map(stats -> stats.fail_count, data_all)\n    na_count = map(stats -> stats.na_count, data_all)\n    all_pass = sum(fail_count) == 0\n\n    data = hcat(\n        labs,\n        varnames,\n        data_min,\n        data_max,\n        data_mean,\n        data_std,\n        pass_count,\n        fail_count,\n        na_count,\n    )\n    header = [\n        \"Label\" \"Field\" \"min()\" \"max()\" \"mean()\" \"std()\" \"Pass\" \"Fail\" \"Not checked\"\n        \"\" \"\" \"\" \"\" \"\" \"\" \"count\" \"count\" \"count\"\n    ]\n\n    pretty_table(\n        io,\n        data,\n        header;\n        header_crayon = crayon\"yellow bold\",\n        subheader_crayon = crayon\"green bold\",\n        crop = :none,\n    )\n\n    println(\n        io,\n        \"# SC +++++++++++ClimateMachine StateCheck ref val check end+++++++++++++++++\",\n    )\n    return all_pass\n\nend\n\nend # module\n"
  },
  {
    "path": "src/Diagnostics/Diagnostics.jl",
    "content": "\"\"\"\n    Diagnostics\n\nAccumulate mean fields and covariance statistics on the computational grid.\n\"\"\"\nmodule Diagnostics\n\nexport DiagnosticsGroup,\n    DiagnosticsGroupParams,\n    setup_atmos_default_diagnostics,\n    setup_atmos_core_diagnostics,\n    setup_atmos_default_perturbations,\n    setup_atmos_refstate_perturbations,\n    setup_atmos_turbulence_stats,\n    setup_atmos_mass_energy_loss,\n    setup_atmos_spectra_diagnostics,\n    setup_dump_state_diagnostics,\n    setup_dump_aux_diagnostics,\n    setup_dump_tendencies_diagnostics\n\nusing CUDA\nusing Dates\nusing FileIO\nusing JLD2\nusing KernelAbstractions\nusing MPI\nusing OrderedCollections\nusing Printf\nusing StaticArrays\nimport KernelAbstractions: CPU\n\nusing ..BalanceLaws\nusing ..ConfigTypes\nusing ..DGMethods\nusing ..Mesh.Interpolation\nusing ..MPIStateArrays\nusing ..Spectra\nusing ..TicToc\nusing ..VariableTemplates\nusing ..Writers\nimport ..GenericCallbacks\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: planet_radius\n\nBase.@kwdef mutable struct Diagnostic_Settings\n    mpicomm::MPI.Comm = MPI.COMM_WORLD\n    param_set::Union{Nothing, AbstractParameterSet} = nothing\n    dg::Union{Nothing, SpaceDiscretization} = nothing\n    Q::Union{Nothing, MPIStateArray} = nothing\n    starttime::Union{Nothing, String} = nothing\n    output_dir::Union{Nothing, String} = nothing\n    no_overwrite::Bool = false\nend\nconst Settings = Diagnostic_Settings()\n\n\"\"\"\n    init(mpicomm, param_set, dg, Q, starttime, output_dir)\n\nInitialize the diagnostics collection module -- save the parameters into\n`Settings`.\n\"\"\"\nfunction init(\n    mpicomm::MPI.Comm,\n    param_set::AbstractParameterSet,\n    dg::SpaceDiscretization,\n    Q::MPIStateArray,\n    starttime::String,\n    output_dir::String,\n    no_overwrite::Bool,\n)\n    Settings.mpicomm = mpicomm\n    Settings.param_set = param_set\n    Settings.dg = dg\n    Settings.Q = Q\n    Settings.starttime = starttime\n    Settings.output_dir = output_dir\n    Settings.no_overwrite = no_overwrite\nend\n\ninclude(\"variables.jl\")\ninclude(\"helpers.jl\")\ninclude(\"atmos_common.jl\")\ninclude(\"thermo.jl\")\ninclude(\"groups.jl\")\n\n\"\"\"\n    __init()__\n\nModule initialization function. Currently, only fills in all currently\ndefined diagnostic variables.\n\"\"\"\nfunction __init__()\n    setup_variables()\nend\n\nend # module Diagnostics\n"
  },
  {
    "path": "src/Diagnostics/DiagnosticsMachine/DiagnosticsMachine.jl",
    "content": "\"\"\"\n    DiagnosticsMachine\n\nThis module provides the infrastructure to extract diagnostics from a\nClimateMachine simulation. Two key abstractions are defined: diagnostic\nvariables and diagnostic groups. The `StdDiagnostics` module makes use of\nthese to define many standard variables and groups which may be used\ndirectly by experiments. `DiagnosticsMachine` may be used by experiments\nto define specialized variables and groups.\n\"\"\"\nmodule DiagnosticsMachine\n\nexport DiagnosticVar,\n    PointwiseDiagnostic,\n    @pointwise_diagnostic,\n    dv_PointwiseDiagnostic,\n    HorizontalAverage,\n    @horizontal_average,\n    dv_HorizontalAverage,\n    States,\n    #DiagnosticsGroup,\n    @diagnostics_group\n#DiagnosticsGroupParams\n\nusing CUDA\nusing Dates\nusing InteractiveUtils\nusing KernelAbstractions\nusing MacroTools\nusing MacroTools: prewalk\nusing MPI\nusing OrderedCollections\nusing Printf\nusing StaticArrays\n\nusing ..Diagnostics # until old diagnostics groups are removed\nusing ..Atmos\nusing ..BalanceLaws\nusing ..ConfigTypes\nusing ..DGMethods\nusing ..GenericCallbacks\nusing ..Mesh.Grids\nusing ..Mesh.Interpolation\nusing ..Mesh.Topologies\nusing ..MPIStateArrays\nusing ..Spectra\nusing ..TicToc\nusing ..VariableTemplates\nusing ..Writers\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: planet_radius\n\n# Container to store simulation information necessary for all\n# diagnostics groups.\nBase.@kwdef mutable struct DiagnosticSettings\n    mpicomm::MPI.Comm = MPI.COMM_WORLD\n    param_set::Union{Nothing, AbstractParameterSet} = nothing\n    dg::Union{Nothing, SpaceDiscretization} = nothing\n    Q::Union{Nothing, MPIStateArray} = nothing\n    starttime::Union{Nothing, String} = nothing\n    output_dir::Union{Nothing, String} = nothing\n    no_overwrite::Bool = false\nend\nconst Settings = DiagnosticSettings()\n\ninclude(\"onetime.jl\")\ninclude(\"variables.jl\")\ninclude(\"groups.jl\")\n\nconst AllDiagnosticVars = OrderedDict{\n    Type{<:ClimateMachineConfigType},\n    OrderedDict{String, DiagnosticVar},\n}()\nfunction add_all_dvar_dicts(T::DataType)\n    AllDiagnosticVars[T] = OrderedDict{String, DiagnosticVar}()\n    for t in subtypes(T)\n        add_all_dvar_dicts(t)\n    end\nend\nadd_all_dvar_dicts(ClimateMachineConfigType)\n\n\"\"\"\n    init(mpicomm, param_set, dg, Q, starttime, output_dir)\n\nSave the parameters into `Settings`, a container for simulation\ninformation necessary for all diagnostics groups.\n\"\"\"\nfunction init(\n    mpicomm::MPI.Comm,\n    param_set::AbstractParameterSet,\n    dg::SpaceDiscretization,\n    Q::MPIStateArray,\n    starttime::String,\n    output_dir::String,\n    no_overwrite::Bool,\n)\n    Settings.mpicomm = mpicomm\n    Settings.param_set = param_set\n    Settings.dg = dg\n    Settings.Q = Q\n    Settings.starttime = starttime\n    Settings.output_dir = output_dir\n    Settings.no_overwrite = no_overwrite\nend\n\ninclude(\"atmos_diagnostic_funs.jl\")\n\nend # module DiagnosticsMachine\n"
  },
  {
    "path": "src/Diagnostics/DiagnosticsMachine/atmos_diagnostic_funs.jl",
    "content": "using ..Atmos\nusing ..TurbulenceClosures\nusing ..TurbulenceConvection\n\n# Method definitions for diagnostics collection for all the components\n# of `AtmosModel`.\n\nconst AtmosComponentTypes = Union{\n    AbstractMoistureModel,\n    PrecipitationModel,\n    RadiationModel,\n    TracerModel,\n    TurbulenceClosureModel,\n    TurbulenceConvectionModel,\n}\n\ndv_PointwiseDiagnostic(\n    ::AtmosConfigType,\n    ::PointwiseDiagnostic,\n    ::AtmosComponentTypes,\n    ::AtmosModel,\n    ::States,\n    ::AbstractFloat,\n    ::Dict{Symbol, Any},\n) = 0\ndv_HorizontalAverage(\n    ::AtmosConfigType,\n    ::HorizontalAverage,\n    ::AtmosComponentTypes,\n    ::AtmosModel,\n    ::States,\n    ::AbstractFloat,\n    ::Dict{Symbol, Any},\n) = 0\n"
  },
  {
    "path": "src/Diagnostics/DiagnosticsMachine/group_gen.jl",
    "content": "# Copy the specified kind of state's variables into an `MArray`.\n# This is GPU-safe.\nfunction extract_state(bl, state, ijk, e, st::AbstractStateType)\n    FT = eltype(state)\n    num_state = number_states(bl, st)\n    local_state = MArray{Tuple{num_state}, FT}(undef)\n    for s in 1:num_state\n        local_state[s] = state[ijk, s, e]\n    end\n    return Vars{vars_state(bl, st, FT)}(local_state)\nend\n\n# Return `true` if the specified symbol is a type name that is a subtype\n# of `BalanceLaw` and `false` otherwise.\nfunction isa_bl(sym::Symbol)\n    symstr = String(sym)\n    bls = map(bl -> String(Symbol(bl)), subtypes(BalanceLaw))\n    any(bl -> (isequal(bl, symstr) || endswith(bl, \".\" * symstr)), bls)\nend\nisa_bl(ex) = false\n\nuppers_in(s) = foldl((f, c) -> isuppercase(c) ? f * c : f, s, init = \"\")\n\n# Return a name for the array generated for storing the values of the\n# diagnostic variables of `dvtype`.\nfunction dvar_array_name(::InterpolationType, dvtype)\n    dvt_short = uppers_in(split(String(Symbol(dvtype)), \".\")[end])\n    Symbol(\"vars_\", dvt_short, \"_array\")\nend\nfunction dvar_array_name(::InterpolateBeforeCollection, dvtype)\n    dvt_short = uppers_in(split(String(Symbol(dvtype)), \".\")[end])\n    Symbol(\"acc_vars_\", dvt_short, \"_array\")\nend\n\n# Generate the common definitions used in many places.\nfunction generate_common_defs()\n    quote\n        mpicomm = DiagnosticsMachine.Settings.mpicomm\n        dg = DiagnosticsMachine.Settings.dg\n        Q = DiagnosticsMachine.Settings.Q\n        mpirank = MPI.Comm_rank(mpicomm)\n        bl = dg.balance_law\n        grid = dg.grid\n        grid_info = basic_grid_info(dg)\n        topl_info = basic_topology_info(grid.topology)\n        Nq1 = grid_info.Nq[1]\n        Nq2 = grid_info.Nq[2]\n        Nqh = grid_info.Nqh\n        Nqk = grid_info.Nqk\n        npoints = prod(grid_info.Nq)\n        nrealelem = topl_info.nrealelem\n        nvertelem = topl_info.nvertelem\n        nhorzelem = topl_info.nhorzrealelem\n        FT = eltype(Q)\n        interpol = dgngrp.interpol\n        params = dgngrp.params\n    end\nend\n\n# Generate the `dims` dictionary for `Writers.init_data`.\nfunction generate_init_dims(::NoInterpolation, cfg_type, dvtype_dvars_map)\n    dimslst = Any[]\n    for dvtype in keys(dvtype_dvars_map)\n        dimnames = DiagnosticsMachine.dv_dg_dimnames(cfg_type, dvtype)\n        dimranges = DiagnosticsMachine.dv_dg_dimranges(cfg_type, dvtype)\n        for (dimname, dimrange) in zip(dimnames, dimranges)\n            lhs = :($dimname)\n            rhs = :(collect($dimrange), Dict())\n            push!(dimslst, :($lhs => $rhs))\n        end\n    end\n\n    quote\n        OrderedDict($(Expr(:tuple, dimslst...))...)\n    end\nend\nfunction generate_init_dims(::InterpolationType, cfg_type, dvtype_dvars_map)\n    quote\n        dims = dimensions(interpol)\n        if interpol isa InterpolationCubedSphere\n            # Adjust `level` on the sphere.\n            level_val = dims[\"level\"]\n            dims[\"level\"] = (\n                level_val[1] .-\n                FT(planet_radius(DiagnosticsMachine.Settings.param_set)),\n                level_val[2],\n            )\n        end\n        dims\n    end\nend\n\nget_dimnames(::NoInterpolation, cfg_type, dvtype) =\n    DiagnosticsMachine.dv_dg_dimnames(cfg_type, dvtype)\nget_dimnames(::InterpolationType, cfg_type, dvtype) =\n    DiagnosticsMachine.dv_i_dimnames(cfg_type, dvtype)\n\n# Generate the `vars` dictionary for `Writers.init_data`.\nfunction generate_init_vars(intrp, cfg_type, dvtype_dvars_map)\n    varslst = Any[]\n    for (dvtype, dvlst) in dvtype_dvars_map\n        for dvar in dvlst\n            lhs = :($(DiagnosticsMachine.dv_name(cfg_type, dvar)))\n            dimnames = get_dimnames(intrp, cfg_type, dvtype)\n            rhs = :(\n                $dimnames,\n                FT,\n                $(DiagnosticsMachine.dv_attrib(cfg_type, dvar)),\n            )\n            push!(varslst, :($lhs => $rhs))\n        end\n    end\n\n    quote\n        # TODO: add code to filter this based on what's actually in `bl`.\n        OrderedDict($(Expr(:tuple, varslst...))...)\n    end\nend\n\n# Generate `Diagnostics.$(name)_init(...)` which will initialize the\n# `DiagnosticsGroup` when called.\nfunction generate_init_fun(\n    name,\n    config_type,\n    params_type,\n    init_fun,\n    interpolate,\n    dvtype_dvars_map,\n)\n    init_name = Symbol(name, \"_init\")\n    cfg_type_name = getfield(ConfigTypes, config_type)\n    intrp = getfield(@__MODULE__, interpolate)\n    quote\n        function $init_name(dgngrp, curr_time)\n            $(generate_common_defs())\n\n            $(init_fun)(dgngrp, curr_time)\n\n            # TODO: uncomment when old diagnostics groups are removed\n            #if dgngrp.onetime\n            DiagnosticsMachine.collect_onetime(mpicomm, dg, Q)\n            #end\n\n            if mpirank == 0\n                dims =\n                    $(generate_init_dims(\n                        intrp(),\n                        cfg_type_name(),\n                        dvtype_dvars_map,\n                    ))\n                vars =\n                    $(generate_init_vars(\n                        intrp(),\n                        cfg_type_name(),\n                        dvtype_dvars_map,\n                    ))\n\n                # create the output file\n                dprefix = @sprintf(\"%s_%s\", dgngrp.out_prefix, dgngrp.name)\n                dfilename =\n                    joinpath(DiagnosticsMachine.Settings.output_dir, dprefix)\n                noov = DiagnosticsMachine.Settings.no_overwrite\n                init_data(dgngrp.writer, dfilename, noov, dims, vars)\n            end\n\n            return nothing\n        end\n    end\nend\n\n# Generate code snippet for copying arrays to the CPU if needed. Ideally,\n# this will be removed when diagnostics are made to run on GPU.\nfunction generate_array_copies()\n    quote\n        # get needed arrays onto the CPU\n        if array_device(Q) isa CPU\n            prognostic_data = Q.realdata\n            gradient_flux_data = dg.state_gradient_flux.realdata\n            auxiliary_data = dg.state_auxiliary.realdata\n            vgeo = grid.vgeo\n        else\n            prognostic_data = Array(Q.realdata)\n            gradient_flux_data = Array(dg.state_gradient_flux.realdata)\n            auxiliary_data = Array(dg.state_auxiliary.realdata)\n            vgeo = Array(grid.vgeo)\n        end\n    end\nend\n\n# Generate code to create the necessary arrays for the diagnostics\n# variables.\nfunction generate_create_vars_arrays(\n    ::InterpolateBeforeCollection,\n    cfg_type,\n    dvtype_dvars_map,\n)\n    quote end\nend\nfunction generate_create_vars_arrays(\n    intrp::InterpolationType,\n    cfg_type,\n    dvtype_dvars_map,\n)\n    cva_exs = []\n    for (dvtype, dvlst) in dvtype_dvars_map\n        arr_name = dvar_array_name(intrp, dvtype)\n        npoints = DiagnosticsMachine.dv_dg_points_length(cfg_type, dvtype)\n        nvars = length(dvlst)\n        nelems = DiagnosticsMachine.dv_dg_elems_length(cfg_type, dvtype)\n        cva_ex = quote\n            $arr_name = Array{FT}(undef, $npoints, $nvars, $nelems)\n            fill!($arr_name, 0)\n        end\n        push!(cva_exs, cva_ex)\n    end\n    return Expr(:block, (cva_exs...))\nend\n\n# Generate the LHS of the assignment expression into the diagnostic\n# variables array.\nfunction dvar_array_assign_expr(\n    ::InterpolateBeforeCollection,\n    cfg_type,\n    dvtype,\n    dvidx,\n    arr_name,\n)\n    return :($(arr_name)[x, y, z, $dvidx])\nend\nfunction dvar_array_assign_expr(\n    ::InterpolationType,\n    cfg_type,\n    dvtype,\n    dvidx,\n    arr_name,\n)\n    pt = DiagnosticsMachine.dv_dg_points_index(cfg_type, dvtype)\n    elem = DiagnosticsMachine.dv_dg_elems_index(cfg_type, dvtype)\n    :($(arr_name)[$pt, $dvidx, $elem])\nend\n\n# Generate calls to the implementations for the `DiagnosticVar`s in this\n# group and store the results.\nfunction generate_collect_calls(\n    intrp::InterpolationType,\n    cfg_type,\n    dvtype_dvars_map,\n)\n    cc_exs = []\n    for (dvtype, dvlst) in dvtype_dvars_map\n        arr_name = dvar_array_name(intrp, dvtype)\n        dvt = split(String(Symbol(dvtype)), \".\")[end]\n        var_impl = Symbol(\"dv_\", dvt)\n\n        for (v, dvar) in enumerate(dvlst)\n            lhs_ex =\n                dvar_array_assign_expr(intrp, cfg_type, dvtype, v, arr_name)\n            impl_args = DiagnosticsMachine.dv_args(cfg_type, dvar)\n            AT1 = impl_args[1][2] # the type of the first argument\n            if isa_bl(AT1)\n                impl_extra_params = ()\n            else\n                AT2 = impl_args[2][2] # the type of the second argument\n                @assert isa_bl(AT2)\n                AT1 = impl_args[1][2] # the type of the first argument\n                impl_extra_params = (:(BalanceLaws.sub_model(bl, $AT1)),)\n            end\n            cc_ex = DiagnosticsMachine.dv_op(\n                cfg_type,\n                dvtype,\n                lhs_ex,\n                :(DiagnosticsMachine.$(var_impl)(\n                    $cfg_type,\n                    $dvar,\n                    $(impl_extra_params...),\n                    bl,\n                    states,\n                    curr_time,\n                    cache,\n                )),\n            )\n            push!(cc_exs, cc_ex)\n        end\n    end\n\n    return Expr(:block, (cc_exs...))\nend\n\n# Generate the nested loops to traverse the DG grid within which we extract\n# the various states and then generate the individual collection calls.\nfunction generate_dg_collections(\n    ::InterpolateBeforeCollection,\n    cfg_type,\n    dvtype_dvars_map,\n)\n    quote end\nend\nfunction generate_dg_collections(\n    intrp::InterpolationType,\n    cfg_type,\n    dvtype_dvars_map,\n)\n    quote\n        cache = Dict{Symbol, Any}()\n        for eh in 1:nhorzelem, ev in 1:nvertelem\n            e = ev + (eh - 1) * nvertelem\n            for k in 1:Nqk, j in 1:Nq2, i in 1:Nq1\n                ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n                MH = vgeo[ijk, grid.MHid, e]\n                prognostic = DiagnosticsMachine.extract_state(\n                    bl,\n                    prognostic_data,\n                    ijk,\n                    e,\n                    Prognostic(),\n                )\n                gradient_flux = DiagnosticsMachine.extract_state(\n                    bl,\n                    gradient_flux_data,\n                    ijk,\n                    e,\n                    GradientFlux(),\n                )\n                auxiliary = DiagnosticsMachine.extract_state(\n                    bl,\n                    auxiliary_data,\n                    ijk,\n                    e,\n                    Auxiliary(),\n                )\n                states = States(prognostic, gradient_flux, auxiliary)\n                $(generate_collect_calls(intrp, cfg_type, dvtype_dvars_map))\n                empty!(cache)\n            end\n        end\n    end\nend\n\n# Generate any reductions needed for the data collected thus far.\nfunction generate_dg_reductions(\n    intrp::NoInterpolation,\n    cfg_type,\n    dvtype_dvars_map,\n)\n    red_exs = []\n    for (dvtype, dvlst) in dvtype_dvars_map\n        arr_name = dvar_array_name(intrp, dvtype)\n        red_ex = DiagnosticsMachine.dv_reduce(cfg_type, dvtype, arr_name)\n        push!(red_exs, red_ex)\n    end\n\n    return Expr(:block, (red_exs...))\nend\nfunction generate_dg_reductions(::InterpolationType, cfg_type, dvtype_dvars_map)\n    quote end\nend\n\n# Generate code to perform density averaging, as needed.\nfunction generate_density_averaging(\n    intrp::InterpolationType,\n    cfg_type,\n    dvtype_dvars_map,\n)\n    da_exs = []\n    for (dvtype, dvlst) in dvtype_dvars_map\n        arr_name = dvar_array_name(intrp, dvtype)\n\n        for (v, dvar) in enumerate(dvlst)\n            scale_dvar = DiagnosticsMachine.dv_scale(cfg_type, dvar)\n            if isnothing(scale_dvar)\n                continue\n            end\n            sdv_idx = findfirst(dvar -> scale_dvar == dvar, dvlst)\n            @assert !isnothing(sdv_idx)\n            da_ex = quote\n                $(arr_name)[:, $v, :] ./= $(arr_name)[:, $sdv_idx, :]\n            end\n            push!(da_exs, da_ex)\n        end\n    end\n\n    return Expr(:block, (da_exs...))\nend\n\n# Generate interpolation calls as needed. None for `NoInterpolation`.\nfunction generate_interpolations(::NoInterpolation, cfg_type, dvtype_dvars_map)\n    quote end\nend\n# Interpolate only the diagnostic variables arrays.\nfunction generate_interpolations(\n    intrp::InterpolateAfterCollection,\n    cfg_type,\n    dvtype_dvars_map,\n)\n    ic_exs = []\n    for (dvtype, dvlst) in dvtype_dvars_map\n        nvars = length(dvlst)\n        arr_name = dvar_array_name(intrp, dvtype)\n        iarr_name = Symbol(\"i\", arr_name)\n        acc_arr_name = Symbol(\"acc_\", arr_name)\n        i_pr = findall(\n            dvar -> DiagnosticsMachine.dv_project(cfg_type, dvar),\n            dvlst,\n        )\n        ic_ex = quote\n            # TODO: `interpolate_local!` expects arrays of the same type\n            # as `grid`. This should be solved when we make this GPU-capable.\n            if !(array_device(Q) isa CPU)\n                ArrayType = arraytype(grid)\n                $arr_name = ArrayType($arr_name)\n            end\n            $iarr_name = similar($arr_name, interpol.Npl, $nvars)\n            interpolate_local!(interpol, $arr_name, $iarr_name)\n\n            if interpol isa InterpolationCubedSphere\n                project_cubed_sphere!(\n                    interpol,\n                    $iarr_name,\n                    tuple(collect($i_pr)...),\n                )\n            end\n\n            $acc_arr_name =\n                accumulate_interpolated_data(mpicomm, interpol, $iarr_name)\n            if !(array_device(Q) isa CPU)\n                $acc_arr_name = Array($acc_arr_name)\n            end\n        end\n        push!(ic_exs, ic_ex)\n    end\n\n    return Expr(:block, (ic_exs...))\nend\n# Interpolate all the arrays needed for `States`.\nfunction generate_interpolations(\n    ::InterpolateBeforeCollection,\n    cfg_type,\n    dvtype_dvars_map,\n)\n    quote\n        iprognostic_array =\n            similar(Q.realdata, interpol.Npl, number_states(bl, Prognostic()))\n        interpolate_local!(interpol, Q.realdata, iprognostic_array)\n        igradient_flux_array =\n            similar(Q.realdata, interpol.Npl, number_states(bl, GradientFlux()))\n        interpolate_local!(\n            interpol,\n            dg.state_gradient_flux.realdata,\n            igradient_flux_array,\n        )\n        iauxiliary_array =\n            similar(Q.realdata, interpol.Npl, number_states(bl, Auxiliary()))\n        interpolate_local!(\n            interpol,\n            dg.state_auxiliary.realdata,\n            iauxiliary_array,\n        )\n\n        if interpol isa InterpolationCubedSphere\n            i_ρu = varsindex(vars_state(bl, Prognostic(), FT), :ρu)\n            project_cubed_sphere!(\n                interpol,\n                iprognostic_array,\n                tuple(collect(i_ρu)...),\n            )\n        end\n\n        # FIXME: accumulating to rank 0 is not scalable\n        all_prognostic_data =\n            accumulate_interpolated_data(mpicomm, interpol, iprognostic_array)\n        all_gradient_flux_data = accumulate_interpolated_data(\n            mpicomm,\n            interpol,\n            igradient_flux_array,\n        )\n        all_auxiliary_data =\n            accumulate_interpolated_data(mpicomm, interpol, iauxiliary_array)\n    end\nend\n\n# Generate code to create the necessary arrays to collect the diagnostics\n# variables on the interpolated grid.\nfunction generate_create_i_vars_arrays(\n    intrp::InterpolateBeforeCollection,\n    cfg_type,\n    dvtype_dvars_map,\n)\n    cva_exs = []\n    for (dvtype, dvlst) in dvtype_dvars_map\n        arr_name = dvar_array_name(intrp, dvtype)\n        nvars = length(dvlst)\n        cva_ex = quote\n            $arr_name = Array{FT}(\n                undef,\n                tuple(map(d -> length(d[1]), values(dims))..., $nvars),\n            )\n            fill!($arr_name, 0)\n        end\n        push!(cva_exs, cva_ex)\n    end\n\n    return Expr(:block, (cva_exs...))\nend\nfunction generate_create_i_vars_arrays(\n    ::InterpolationType,\n    cfg_type,\n    dvtype_dvars_map,\n)\n    quote end\nend\n\n# Generate the nested loops to traverse the interpolated grid within\n# which we extract the various (interpolated) states and then generate\n# the individual collection calls.\nfunction generate_i_collections(\n    intrp::InterpolateBeforeCollection,\n    cfg_type,\n    dvtype_dvars_map,\n)\n    quote\n        cache = Dict{Symbol, Any}()\n        (x1, x2, x3) = map(d -> length(d[1]), values(dims))\n        for x in 1:x1, y in 1:x2, z in 1:x3\n            iprognostic = Vars{vars_state(bl, Prognostic(), FT)}(view(\n                all_prognostic_data,\n                x,\n                y,\n                z,\n                :,\n            ))\n            igradient_flux = Vars{vars_state(bl, GradientFlux(), FT)}(view(\n                all_gradient_flux_data,\n                x,\n                y,\n                z,\n                :,\n            ))\n            iauxiliary = Vars{vars_state(bl, Auxiliary(), FT)}(view(\n                all_auxiliary_data,\n                x,\n                y,\n                z,\n                :,\n            ))\n            states = States(iprognostic, igradient_flux, iauxiliary)\n            $(generate_collect_calls(intrp, cfg_type, dvtype_dvars_map))\n            empty!(cache)\n        end\n    end\nend\nfunction generate_i_collections(::InterpolationType, cfg_type, dvtype_dvars_map)\n    quote end\nend\n\n# Generate assignments into `varvals` for writing.\nfunction generate_varvals(intrp::NoInterpolation, cfg_type, dvtype_dvars_map)\n    vv_exs = []\n    for (dvtype, dvlst) in dvtype_dvars_map\n        arr_name = dvar_array_name(intrp, dvtype)\n        for (v, dvar) in enumerate(dvlst)\n            rhs = :(view($(arr_name), :, $v, :))\n            if length(DiagnosticsMachine.dv_dg_dimnames(cfg_type, dvtype)) == 1\n                rhs = :(reshape($(rhs), :))\n            end\n            vv_ex = quote\n                varvals[$(DiagnosticsMachine.dv_name(cfg_type, dvar))] = $rhs\n            end\n            push!(vv_exs, vv_ex)\n        end\n    end\n\n    return Expr(:block, (vv_exs...))\nend\nfunction generate_varvals(::InterpolationType, cfg_type, dvtype_dvars_map)\n    vv_exs = []\n    for (dvtype, dvlst) in dvtype_dvars_map\n        dvt_short = uppers_in(split(String(Symbol(dvtype)), \".\")[end])\n        acc_arr_name = Symbol(\"acc_vars_\", dvt_short, \"_array\")\n        for (v, dvar) in enumerate(dvlst)\n            vv_ex = quote\n                varvals[$(DiagnosticsMachine.dv_name(cfg_type, dvar))] =\n                    $(acc_arr_name)[:, :, :, $v]\n            end\n            push!(vv_exs, vv_ex)\n        end\n    end\n\n    return Expr(:block, (vv_exs...))\nend\n\n# Generate `Diagnostics.$(name)_collect(...)` which when called,\n# performs a collection of all the diagnostic variables in the group\n# and writes them out.\nfunction generate_collect_fun(\n    name,\n    config_type,\n    params_type,\n    init_fun,\n    interpolate,\n    dvtype_dvars_map,\n)\n    collect_name = Symbol(name, \"_collect\")\n    cfg_type_name = getfield(ConfigTypes, config_type)\n    intrp = getfield(@__MODULE__, interpolate)\n    quote\n        function $collect_name(dgngrp, curr_time)\n            $(generate_common_defs())\n            $(generate_array_copies())\n            $(generate_create_vars_arrays(\n                intrp(),\n                cfg_type_name(),\n                dvtype_dvars_map,\n            ))\n\n            # Traverse the DG grid and collect diagnostics as needed.\n            $(generate_dg_collections(\n                intrp(),\n                cfg_type_name(),\n                dvtype_dvars_map,\n            ))\n\n            # Perform any reductions necessary.\n            $(generate_dg_reductions(\n                intrp(),\n                cfg_type_name(),\n                dvtype_dvars_map,\n            ))\n\n            # Perform density averaging if needed.\n            $(generate_density_averaging(\n                intrp(),\n                cfg_type_name(),\n                dvtype_dvars_map,\n            ))\n\n            # Interpolate and accumulate if needed.\n            $(generate_interpolations(\n                intrp(),\n                cfg_type_name(),\n                dvtype_dvars_map,\n            ))\n\n            if mpirank == 0\n                dims = dimensions(interpol)\n\n                $(generate_create_i_vars_arrays(\n                    intrp(),\n                    cfg_type_name(),\n                    dvtype_dvars_map,\n                ))\n\n                # Traverse the interpolated grid and collect diagnostics if needed.\n                $(generate_i_collections(\n                    intrp(),\n                    cfg_type_name(),\n                    dvtype_dvars_map,\n                ))\n\n                # Assemble the diagnostic variables and write them.\n                varvals = OrderedDict()\n                $(generate_varvals(intrp(), cfg_type_name(), dvtype_dvars_map))\n                append_data(dgngrp.writer, varvals, curr_time)\n            end\n\n            MPI.Barrier(mpicomm)\n            return nothing\n        end\n    end\nend\n\n# Generate `Diagnostics.$(name)_fini(...)`, which does nothing right now.\nfunction generate_fini_fun(name, args...)\n    fini_name = Symbol(name, \"_fini\")\n    quote\n        function $fini_name(dgngrp, curr_time) end\n    end\nend\n\n# Generate `$(name)(...)` which will create the `DiagnosticsGroup` for \n# $name when called.\nfunction generate_setup_fun(\n    name,\n    config_type,\n    params_type,\n    init_fun,\n    interpolate,\n    dvtype_dvars_map,\n)\n    init_name = Symbol(name, \"_init\")\n    collect_name = Symbol(name, \"_collect\")\n    fini_name = Symbol(name, \"_fini\")\n\n    setup_name = Symbol(name)\n    intrp = getfield(@__MODULE__, interpolate)\n\n    no_intrp_err = quote end\n    some_intrp_err = quote end\n    if intrp() isa NoInterpolation\n        no_intrp_err = quote\n            @warn \"$($name) does not specify interpolation, but an \" *\n                  \"`InterpolationTopology` has been provided; ignoring.\"\n            interpol = nothing\n        end\n    else\n        some_intrp_err = quote\n            throw(ArgumentError(\n                \"$($name) specifies interpolation, but no \" *\n                \"`InterpolationTopology` has been provided.\",\n            ))\n        end\n    end\n    quote\n        function $setup_name(\n            interval::String,\n            out_prefix::String,\n            params::$params_type = nothing;\n            writer = NetCDFWriter(),\n            interpol = nothing,\n        ) where {\n            $config_type <: ClimateMachineConfigType,\n            $params_type <: Union{Nothing, DiagnosticsGroupParams},\n        }\n            if isnothing(interpol)\n                $(some_intrp_err)\n            else\n                $(no_intrp_err)\n            end\n\n            return DiagnosticsMachine.DiagnosticsGroup(\n                $name,\n                $init_name,\n                $fini_name,\n                $collect_name,\n                interval,\n                out_prefix,\n                writer,\n                interpol,\n                # TODO: uncomment when old diagnostics groups are removed\n                #$(intrp() isa NoInterpolation),\n                params,\n            )\n        end\n    end\nend\n"
  },
  {
    "path": "src/Diagnostics/DiagnosticsMachine/groups.jl",
    "content": "#=\n\"\"\"\n    DiagnosticsGroupParams\n\nBase type for any extra parameters needed by a diagnostics group.\n\"\"\"\nabstract type DiagnosticsGroupParams end\n\n\"\"\"\n    DiagnosticsGroup\n\nA diagnostics group contains a set of diagnostic variables that are\ncollected at the same interval and written to the same file. One or more\ndiagnostics groups are placed in a [`DiagnosticsConfiguration`](@ref)\nwhich is used by [`ClimateMachine.invoke!`](@ref).\n\nA `DiagnosticsGroup` can be easily created with\n[`@diagnostics_group`](@ref).\n\"\"\"\nmutable struct DiagnosticsGroup{\n    DGP <: Union{Nothing, DiagnosticsGroupParams},\n    DGI <: Union{Nothing, InterpolationTopology},\n}\n    name::String\n    init::Function\n    collect::Function\n    fini::Function\n    interval::String\n    out_prefix::String\n    writer::AbstractWriter\n    interpol::DGI\n    onetime::Bool\n    params::DGP\n\n    DiagnosticsGroup(\n        name,\n        init,\n        fini,\n        collect,\n        interval,\n        out_prefix,\n        writer,\n        interpol,\n        onetime,\n        params,\n    ) = new{typeof(params), typeof(interpol)}(\n        name,\n        init,\n        collect,\n        fini,\n        interval,\n        out_prefix,\n        writer,\n        interpol,\n        onetime,\n        params,\n    )\nend\n\n# `GenericCallbacks` implementations for `DiagnosticsGroup`s\nfunction GenericCallbacks.init!(dgngrp::DiagnosticsGroup, solver, Q, param, t)\n    @info @sprintf(\n        \"\"\"\n    Diagnostics: %s\n        initializing at %8.2f\"\"\",\n        dgngrp.name,\n        t,\n    )\n    dgngrp.init(dgngrp, t)\n    dgngrp.collect(dgngrp, t)\n    return nothing\nend\nfunction GenericCallbacks.call!(dgngrp::DiagnosticsGroup, solver, Q, param, t)\n    @tic diagnostics\n    @info @sprintf(\n        \"\"\"\n    Diagnostics: %s\n        collecting at %8.2f\"\"\",\n        dgngrp.name,\n        t,\n    )\n    dgngrp.collect(dgngrp, t)\n    @toc diagnostics\n    return nothing\nend\nfunction GenericCallbacks.fini!(dgngrp::DiagnosticsGroup, solver, Q, param, t)\n    @info @sprintf(\n        \"\"\"\n    Diagnostics: %s\n        finishing at %8.2f\"\"\",\n        dgngrp.name,\n        t,\n    )\n    dgngrp.collect(dgngrp, t)\n    dgngrp.fini(dgngrp, t)\n    return nothing\nend\n=#\nabstract type InterpolationType end\nstruct NoInterpolation <: InterpolationType end\nstruct InterpolateAfterCollection <: InterpolationType end\nstruct InterpolateBeforeCollection <: InterpolationType end\n\n\"\"\"\n    @diagnostics_group\n\nGenerate the functions needed to establish and\nuse a [`DiagnosticsGroup`](@ref) containing the named\n[`DiagnosticVar`](@ref)s. In particular, this creates `$(name)()`,\nwhich creates the diagnostics group.\n\n# Arguments\n- `name`: a string that uniquely identifies the group.\n- `config_type`: a `ClimateMachineConfigType`.\n- `params_type`: a subtype of `DiagnosticsGroupParams` or `Nothing`.\n  An instance of this type is created in `dgngrp.params` when the\n  group is set up.\n- `init_fun`: a function that is called when the group is initialized\n  (called with `(dgngrp, curr_time); `may be `(_...) -> nothing`). Use\n  this to initialize any required state, such as in `dgngrp.params`.\n- `interpolate`: one of `InterpolateBeforeCollection`,\n  `InterpolateAfterCollection` or `NoInterpolation`.\n- `dvarnames`: one or more diagnostic variables. Together with the\n  `config_type`, identify the [`DiagnosticVar`](@ref)s to be included\n  in the group.\n\"\"\"\nmacro diagnostics_group(\n    name,\n    config_type,\n    params_type,\n    init_fun,\n    interpolate,\n    dvarnames..., # TODO: need to separately specify which ones to output\n)\n    CT = getfield(ConfigTypes, config_type)\n    dvars = DiagnosticVar[AllDiagnosticVars[CT][String(dv)] for dv in dvarnames]\n\n    # Partition the diagnostic variables by type.\n    dvtype_dvars_map = Dict{DataType, Array{DiagnosticVar, 1}}()\n    for dvar in dvars\n        push!(\n            get!(dvtype_dvars_map, supertype(typeof(dvar)), DiagnosticVar[]),\n            dvar,\n        )\n    end\n\n    gen_params = (\n        name,\n        config_type,\n        params_type,\n        init_fun,\n        interpolate,\n        dvtype_dvars_map,\n    )\n\n    using_exprs = quote\n        using KernelAbstractions\n        using OrderedCollections\n        using ClimateMachine.BalanceLaws\n        using ClimateMachine.DGMethods\n        using ClimateMachine.Mesh.Interpolation\n        using ClimateMachine.Mesh.Topologies\n        using ClimateMachine.MPIStateArrays\n        using ClimateMachine.VariableTemplates\n        using ClimateMachine.Writers\n    end\n    init_fun = esc(prewalk(unblock, generate_init_fun(gen_params...)))\n    fini_fun = esc(prewalk(unblock, generate_fini_fun(gen_params...)))\n    collect_fun = esc(prewalk(unblock, generate_collect_fun(gen_params...)))\n    setup_fun = esc(prewalk(unblock, generate_setup_fun(gen_params...)))\n\n    return Expr(:block, using_exprs, init_fun, fini_fun, collect_fun, setup_fun)\nend\n\ninclude(\"group_gen.jl\")\n"
  },
  {
    "path": "src/Diagnostics/DiagnosticsMachine/horizontal_average.jl",
    "content": "\"\"\"\n    HorizontalAverage\n\nTargeted at AtmosLES configurations, produces a vector of length `z`\nof averages for the horizontal planes in the domain, where `z` is the\ninterpolated grid height or the DG grid`s `x3id` over the nodes and\nelements (duplicates are not removed).\n\"\"\"\nabstract type HorizontalAverage <: DiagnosticVar end\nfunction dv_HorizontalAverage end\n\n# TODO: replace these with a `dv_array_dims` that takes `nvars`\n# and returns the dims for the array? Or create the array? Use\n# `Array`? `similar`?\n\nfunction dv_dg_points_length(\n    ::ClimateMachineConfigType,\n    ::Type{HorizontalAverage},\n)\n    :(Nqk)\nend\nfunction dv_dg_points_index(\n    ::ClimateMachineConfigType,\n    ::Type{HorizontalAverage},\n)\n    :(k)\nend\n\nfunction dv_dg_elems_length(\n    ::ClimateMachineConfigType,\n    ::Type{HorizontalAverage},\n)\n    :(nvertelem)\nend\nfunction dv_dg_elems_index(\n    ::ClimateMachineConfigType,\n    ::Type{HorizontalAverage},\n)\n    :(ev)\nend\n\nfunction dv_dg_dimnames(::ClimateMachineConfigType, ::Type{HorizontalAverage})\n    (\"z\",)\nend\nfunction dv_dg_dimranges(::ClimateMachineConfigType, ::Type{HorizontalAverage})\n    z = quote\n        ijk_range = 1:Nqh:npoints\n        e_range = 1:nvertelem\n        reshape(grid.vgeo[ijk_range, grid.x3id, e_range], :)\n    end\n    (z,)\nend\n\nfunction dv_i_dimnames(::AtmosLESConfigType, ::Type{HorizontalAverage})\n    (\"z\",)\nend\nfunction dv_i_dimnames(::AtmosGCMConfigType, ::Type{HorizontalAverage})\n    (\"level\",)\nend\n\nfunction dv_op(::ClimateMachineConfigType, ::Type{HorizontalAverage}, lhs, rhs)\n    :($lhs += MH * $rhs)\nend\nfunction dv_reduce(\n    ::ClimateMachineConfigType,\n    ::Type{HorizontalAverage},\n    array_name,\n)\n    quote\n        MPI.Reduce!($array_name, +, 0, mpicomm)\n        if mpirank == 0\n            for v in 1:size($array_name, 2)\n                $(array_name)[:, v, :] ./= DiagnosticsMachine.Collected.ΣMH_z\n            end\n        end\n    end\nend\n\nmacro horizontal_average(impl, config_type, name, scale = nothing)\n    iex = quote\n        $(generate_dv_interface(:HorizontalAverage, config_type, name))\n        $(generate_dv_function(:HorizontalAverage, config_type, name, impl))\n        $(generate_dv_scale(:HorizontalAverage, config_type, name, scale))\n    end\n    esc(MacroTools.prewalk(unblock, iex))\nend\n\n\"\"\"\n    @horizontal_average(\n        impl,\n        config_type,\n        name,\n        units,\n        long_name,\n        standard_name,\n        scale = nothing,\n    )\n\nDefine `name`, a horizontal average diagnostic variable for `config_type`\nwith the specified attributes and the given implementation. In order to\nproduce a density-weighted average, `scale` must be specified as the name\nof the horizontal average diagnostic variable for density.\n\n# Example\n\n```julia\n@horizontal_average(\n    AtmosLESConfigType,\n    w_ht_sgs,\n    \"kg kg^-1 m s^-1\",\n    \"vertical sgs flux of total specific enthalpy\",\n    \"\",\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    D_t = get!(cache, :D_t) do\n        _, D_t, _ = turbulence_tensors(\n            atmos,\n            states.prognostic,\n            states.gradient_flux,\n            states.auxiliary,\n            curr_time,\n        )\n        D_t\n    end\n    d_h_tot = -D_t .* states.gradient_flux.∇h_tot\n    d_h_tot[end] * states.prognostic.ρ\nend\n```\n\"\"\"\nmacro horizontal_average(\n    impl,\n    config_type,\n    name,\n    units,\n    long_name,\n    standard_name,\n    scale = nothing,\n)\n    iex = quote\n        $(generate_dv_interface(\n            :HorizontalAverage,\n            config_type,\n            name,\n            units,\n            long_name,\n            standard_name,\n        ))\n        $(generate_dv_function(:HorizontalAverage, config_type, name, impl))\n        $(generate_dv_scale(:HorizontalAverage, config_type, name, scale))\n    end\n    esc(MacroTools.prewalk(unblock, iex))\nend\n"
  },
  {
    "path": "src/Diagnostics/DiagnosticsMachine/onetime.jl",
    "content": "Base.@kwdef mutable struct CollectedDiagnostics\n    onetime_done::Bool = false\n    ΣMH_z::Union{Nothing, Array} = nothing\nend\nconst Collected = CollectedDiagnostics()\n\nfunction collect_onetime(mpicomm, dg, Q)\n    if !Collected.onetime_done\n        FT = eltype(Q)\n        grid = dg.grid\n        grid_info = basic_grid_info(dg)\n        topl_info = basic_topology_info(grid.topology)\n        topology = grid.topology\n        Nq1 = grid_info.Nq[1]\n        Nq2 = grid_info.Nq[2]\n        Nq3 = grid_info.Nqk\n        npoints = prod(grid_info.Nq)\n        nrealelem = topl_info.nrealelem\n        nvertelem = topl_info.nvertelem\n        nhorzelem = topl_info.nhorzrealelem\n\n        vgeo = array_device(Q) isa CPU ? grid.vgeo : Array(grid.vgeo)\n\n        Collected.ΣMH_z = zeros(FT, Nq3, nvertelem)\n\n        for eh in 1:nhorzelem, ev in 1:nvertelem\n            e = ev + (eh - 1) * nvertelem\n            for k in 1:Nq3, j in 1:Nq2, i in 1:Nq1\n                ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n                MH = vgeo[ijk, grid.MHid, e]\n                Collected.ΣMH_z[k, ev] += MH\n            end\n        end\n\n        # compute the full number of points on a slab on rank 0\n        MPI.Reduce!(Collected.ΣMH_z, +, 0, mpicomm)\n\n        Collected.onetime_done = true\n    end\n\n    return nothing\nend\n"
  },
  {
    "path": "src/Diagnostics/DiagnosticsMachine/pointwise.jl",
    "content": "\"\"\"\n    PointwiseDiagnostic\n\nA diagnostic with the same dimensions as the original grid (DG or\ninterpolated). Mostly used to directly copy out prognostic or\nauxiliary state variables.\n\"\"\"\nabstract type PointwiseDiagnostic <: DiagnosticVar end\nfunction dv_PointwiseDiagnostic end\n\nfunction dv_dg_points_length(\n    ::ClimateMachineConfigType,\n    ::Type{PointwiseDiagnostic},\n)\n    :(npoints)\nend\nfunction dv_dg_points_index(\n    ::ClimateMachineConfigType,\n    ::Type{PointwiseDiagnostic},\n)\n    :(ijk)\nend\n\nfunction dv_dg_elems_length(\n    ::ClimateMachineConfigType,\n    ::Type{PointwiseDiagnostic},\n)\n    :(nrealelem)\nend\nfunction dv_dg_elems_index(\n    ::ClimateMachineConfigType,\n    ::Type{PointwiseDiagnostic},\n)\n    :(e)\nend\n\nfunction dv_dg_dimnames(::ClimateMachineConfigType, ::Type{PointwiseDiagnostic})\n    (\"nodes\", \"elements\")\nend\nfunction dv_dg_dimranges(\n    ::ClimateMachineConfigType,\n    ::Type{PointwiseDiagnostic},\n)\n    (:(1:npoints), :(1:nrealelem))\nend\n\nfunction dv_i_dimnames(::ClimateMachineConfigType, ::Type{PointwiseDiagnostic})\n    :(tuple(collect(keys(dims))...))\nend\n\nfunction dv_op(\n    ::ClimateMachineConfigType,\n    ::Type{PointwiseDiagnostic},\n    lhs,\n    rhs,\n)\n    :($lhs = $rhs)\nend\n\n# Reduction for point-wise diagnostics would be a gather, but that will probably\n# blow up memory. TODO.\nfunction dv_reduce(\n    ::ClimateMachineConfigType,\n    ::Type{PointwiseDiagnostic},\n    array_name,\n)\n    quote end\nend\n\nmacro pointwise_diagnostic(impl, config_type, name, project = false)\n    iex = quote\n        $(generate_dv_interface(:PointwiseDiagnostic, config_type, name))\n        $(generate_dv_function(:PointwiseDiagnostic, config_type, name, impl))\n        $(generate_dv_project(:PointwiseDiagnostic, config_type, name, project))\n    end\n    esc(MacroTools.prewalk(unblock, iex))\nend\n\n\"\"\"\n    @pointwise_diagnostic(\n        impl,\n        config_type,\n        name,\n        units,\n        long_name,\n        standard_name,\n        project = false,\n    )\n\nDefine `name` a point-wise diagnostic variable for `config_type`,\nwith the specified attributes and the given implementation. If\n`project` is `true`, the variable will be projected along unit\nvectors (for cubed shell topologies) after interpolation.\n\n# Example\n\n```julia\n@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    thv,\n    \"K\",\n    \"virtual potential temperature\",\n    \"virtual_potential_temperature\",\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time,\n    cache,\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    virtual_pottemp(ts)\nend\n```\n\"\"\"\nmacro pointwise_diagnostic(\n    impl,\n    config_type,\n    name,\n    units,\n    long_name,\n    standard_name,\n    project = false,\n)\n    iex = quote\n        $(generate_dv_interface(\n            :PointwiseDiagnostic,\n            config_type,\n            name,\n            units,\n            long_name,\n            standard_name,\n        ))\n        $(generate_dv_function(:PointwiseDiagnostic, config_type, name, impl))\n        $(generate_dv_project(:PointwiseDiagnostic, config_type, name, project))\n    end\n    esc(MacroTools.prewalk(unblock, iex))\nend\n"
  },
  {
    "path": "src/Diagnostics/DiagnosticsMachine/variables.jl",
    "content": "\"\"\"\n    DiagnosticVar\n\nThe base type for all diagnostic variables.\n\nVarious kinds of diagnostic variables (such as `HorizontalAverage`)\nare defined as abstract sub-types of this type and implement the group\nof functions listed at the end for use by the diagnostics group code\ngenerator.\n\nA particular diagnostic variable is defined as a concrete sub-type of\none of the kinds of diagnostic variables. The type itself is generated as\nare the group of functions below.\n\nA diagnostic variable is always associated with a `ClimateMachine`\nconfiguration type, so as to allow the same name to be used by different\nconfigurations.\n\"\"\"\nabstract type DiagnosticVar end\n\n\"\"\"\n    dv_dg_points_length(::CT, ::Type{DVT}) where {\n        CT <: ClimateMachineConfigType,\n        DVT <: DiagnosticVar,\n    }\n\nReturns an expression that evaluates to the length of the points\ndimension of a DG array of the diagnostic variable type.\n\"\"\"\nfunction dv_dg_points_length end\n\n\"\"\"\n    dv_dg_points_index(::CT, ::Type{DVT}) where {\n        CT <: ClimateMachineConfigType,\n        DVT <: DiagnosticVar,\n    }\n\nReturns an expression that, within a `@traverse_dg_grid`, evaluates\nto an index into the points dimension of a DG array of the diagnostic\nvariable type.\n\"\"\"\nfunction dv_dg_points_index end\n\n\"\"\"\n    dv_dg_elems_length(::CT, ::Type{DVT}) where {\n        CT <: ClimateMachineConfigType,\n        DVT <: DiagnosticVar,\n    }\n\nReturns an expression that evaluates to the length of the elements\ndimension of a DG array of the diagnostic variable type.\n\"\"\"\nfunction dv_dg_elems_length end\n\n\"\"\"\n    dv_dg_elems_index(::CT, ::Type{DVT}) where {\n        CT <: ClimateMachineConfigType,\n        DVT <: DiagnosticVar,\n    }\n\nReturns an expression that, within a `@traverse_dg_grid`, evaluates to\nan index into the elements dimension of a DG array of the diagnostic\nvariable type.\n\"\"\"\nfunction dv_dg_elems_index end\n\n\"\"\"\n    dv_dg_dimnames(::CT, ::Type{DVT}) where {\n        CT <: ClimateMachineConfigType,\n        DVT <: DiagnosticVar,\n    }\n\nReturns a tuple of the names of the dimensions of the diagnostic\nvariable type for when interpolation is _not_ used.\n\"\"\"\nfunction dv_dg_dimnames end\n\n\"\"\"\n    dv_dg_dimranges(::CT, ::Type{DVT}) where {\n        CT <: ClimateMachineConfigType,\n        DVT <: DiagnosticVar,\n    }\n\nReturns a tuple of expressions that evaluate to the range of the\ndimensions for the diagnostic variable type for when interpolation is\n_not_ used.\n\"\"\"\nfunction dv_dg_dimranges end\n\n\"\"\"\n    dv_i_dimnames(::CT, ::Type{DVT}) where {\n        CT <: ClimateMachineConfigType,\n        DVT <: DiagnosticVar,\n    }\n\nReturns a tuple of the names of the dimensions of the diagnostic\nvariable type or of an expression, for when interpolation is used.\n\"\"\"\nfunction dv_i_dimnames end\n\n\"\"\"\n    dv_op(::CT, ::Type{DVT}, lhs, rhs) where {\n        CT <: ClimateMachineConfigType,\n        DVT <: DiagnosticVar,\n    }\n\nReturns an expression that, within a `@traverse_dg_grid`, evaluates to\nan assignment of `rhs` to `lhs`, where `rhs` is the implementation of\nthe diagnostic variable and `lhs` is the appropriate location in the\narray containing the computed diagnostic variable values.\n\"\"\"\nfunction dv_op end\n\n\"\"\"\n    dv_reduce(::CT, ::Type{DVT}, array_name) where {\n        CT <: ClimateMachineConfigType,\n        DVT <: DiagnosticVar,\n    }\n\n\"\"\"\nfunction dv_reduce end\n\n\"\"\"\n    dv_name(::CT, ::DVT) where {\n        CT <: ClimateMachineConfigType,\n        DVT <: DiagnosticVar,\n    }\n\nReturns the name of the diagnostic variable as a `String`. Generated.\n\"\"\"\nfunction dv_name end\n\n\"\"\"\n    dv_attrib(::CT, ::DVT) where {\n        CT <: ClimateMachineConfigType,\n        DVT <: DiagnosticVar,\n    }\n\nReturns a `Dict` of diagnostic variable attributes, primarily for NetCDF.\nGenerated.\n\"\"\"\nfunction dv_attrib end\n\n# Default method for variable attributes.\ndv_attrib(::ClimateMachineConfigType, ::DiagnosticVar) = Dict()\n\n\"\"\"\n    dv_args(::CT, ::DVT) where {\n        CT <: ClimateMachineConfigType,\n        DVT <: DiagnosticVar,\n    }\n\nReturns a tuple of `(arg_name, arg_type, slurp, default)` (as returned\nby `MacroTools.splitarg` for the arguments specified by the\nimplementation of the diagnostic variable.\n\"\"\"\nfunction dv_args end\n\n\"\"\"\n    dv_scale(::CT, ::DVT) where {\n        CT <: ClimateMachineConfigType,\n        DVT <: DiagnosticVar,\n    }\n\nIf scaling was specified for the diagnostic variable, return the\ndiagnostic variable with which the scaling should be done, otherwise\nreturn `nothing`.\n\"\"\"\nfunction dv_scale end\n\n# Default method for diagnostic variable scaling.\ndv_scale(::ClimateMachineConfigType, ::DiagnosticVar) = nothing\n\n\"\"\"\n    dv_project(::CT, ::DVT) where {\n        CT <: ClimateMachineConfigType,\n        DVT <: DiagnosticVar,\n    }\n\nReturn `true` if the specified diagnostic variable should be\nprojected after interpolation.\n\"\"\"\nfunction dv_project end\n\n# Default method for diagnostic variable scaling.\ndv_project(::ClimateMachineConfigType, ::DiagnosticVar) = false\n\n####\n\n# Generate a standardized type name from:\n# - the configuration type,\n# - the diagnostic variable kind, and\n# - the diagnostic variable name.\nfunction dv_type_name(dvtype, config_type, name)\n    let uppers_in(s) =\n            foldl((f, c) -> isuppercase(c) ? f * c : f, String(s), init = \"\")\n        return uppers_in(config_type) *\n               \"_\" *\n               uppers_in(dvtype) *\n               \"_\" *\n               String(name)\n    end\nend\n\n\"\"\"\n    generate_dv_interface(\n        dvtype,\n        config_type,\n        name,\n        units,\n        long_name,\n        standard_name,\n    )\n\nGenerate the type for a diagnostic variable, add an instance of the\ntype into `AllDiagnosticVars`, and generate `dv_name` and `dv_attrib`.\n\"\"\"\nfunction generate_dv_interface(\n    dvtype,\n    config_type,\n    name,\n    units = \"\",\n    long_name = \"\",\n    standard_name = \"\",\n)\n    dvtypname = Symbol(dv_type_name(dvtype, config_type, name))\n    attrib_ex = quote end\n    if any(a -> a != \"\", [units, long_name, standard_name])\n        attrib_ex = quote\n            dv_attrib(::$config_type, ::$dvtypname) = OrderedDict(\n                \"units\" => $units,\n                \"long_name\" => $long_name,\n                \"standard_name\" => $standard_name,\n            )\n        end\n    end\n    quote\n        struct $dvtypname <: $dvtype end\n        DiagnosticsMachine.AllDiagnosticVars[$config_type][$(String(name))] =\n            $dvtypname()\n        dv_name(::$config_type, ::$dvtypname) = $(String(name))\n        $(attrib_ex)\n    end\nend\n\n\"\"\"\n    generate_dv_function(dvtype, config_type, name, impl)\n\nGenerate `dv_args` for a diagnostic variable as well as the\nimplementation function: `dv_<dvtype>`, adding the configuration\ntype and the diagnostic variable type as the first two parameters\nfor dispatch.\n\nThe implementation _must_ be defined as:\n```\nf(\n    [<component-name>::<component-type>,]\n    bl::<balance-law-type>,\n    states::States,\n    curr_time::Float64,\n    cache::Dict{Symbol, Any},\n)\n```\nWhere `<component-name>` is the name of the property within the balance\nlaw type, `<component-type>` and `<balance-law-type>` are used for\ndispatch, and `cache` may be used to store intermediate computations.\n\"\"\"\nfunction generate_dv_function(dvtype, config_type, name, impl)\n    dvfun = Symbol(\"dv_\", dvtype)\n    dvtypname = Symbol(dv_type_name(dvtype, config_type, name))\n    @capture(impl, ((args__,),) -> (body_)) ||\n        @capture(impl, (args_) -> (body_)) ||\n        error(\"Bad implementation for $(esc(names[1]))\")\n    split_fun_args = map(splitarg, args)\n    fun_args = map(a -> :($(a[1])::$(a[2])), split_fun_args)\n    quote\n        function dv_args(::$config_type, ::$dvtypname)\n            $split_fun_args\n        end\n        function $dvfun(::$config_type, ::$dvtypname, $(fun_args...))\n            $body\n        end\n    end\nend\n\n\"\"\"\n    generate_dv_scale(dvtype, config_type, name, scale)\n\nGenerate `dv_scale` for a diagnostic variable, returning the diagnostic\nvariable with which it is to be scaled.\n\"\"\"\nfunction generate_dv_scale(dvtype, config_type, name, scale)\n    dvtypname = Symbol(dv_type_name(dvtype, config_type, name))\n    sdv = nothing\n    if !isnothing(scale)\n        cfg_type_name = getfield(ConfigTypes, config_type)\n        sdv = DiagnosticsMachine.AllDiagnosticVars[cfg_type_name][String(scale)]\n    end\n    quote\n        dv_scale(::$config_type, ::$dvtypname) = $sdv\n    end\nend\n\n\"\"\"\n    generate_dv_project(dvtype, config_type, name, scale)\n\nGenerate `dv_project` for a diagnostic variable, returning `true` if\nthe diagnostic variable is to be projected.\n\"\"\"\nfunction generate_dv_project(dvtype, config_type, name, project)\n    dvtypname = Symbol(dv_type_name(dvtype, config_type, name))\n    quote\n        dv_project(::$config_type, ::$dvtypname) = $project\n    end\nend\n\n\"\"\"\n    States\n\nComposite of the various states, used as a parameter to diagnostic\nvariable implementations.\n\"\"\"\nstruct States{PS, GFS, AS}\n    prognostic::PS\n    gradient_flux::GFS\n    auxiliary::AS\nend\n\n# The various kinds of diagnostic variables as well as interfaces to\n# create variables of these kinds.\ninclude(\"pointwise.jl\")\ninclude(\"horizontal_average.jl\")\n"
  },
  {
    "path": "src/Diagnostics/StdDiagnostics/StdDiagnostics.jl",
    "content": "\"\"\"\n    StdDiagnostics\n\nThis module defines many standard diagnostic variables and groups that may\nbe used directly by experiments.\n\"\"\"\nmodule StdDiagnostics\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet\nusing CLIMAParameters.Atmos\nusing CLIMAParameters.SubgridScale\n\nusing KernelAbstractions\nusing MPI\nusing OrderedCollections\nusing Printf\nusing StaticArrays\n\nusing ..Diagnostics # until old diagnostics groups are removed\nusing ..Atmos\nusing ..BalanceLaws\nusing ..ConfigTypes\nusing ..DGMethods\nusing ..DiagnosticsMachine\nimport ..DiagnosticsMachine:\n    Settings,\n    dv_name,\n    dv_attrib,\n    dv_args,\n    dv_project,\n    dv_scale,\n    dv_PointwiseDiagnostic,\n    dv_HorizontalAverage\nusing ..Mesh.Grids\nusing ..Mesh.Interpolation\nusing ..Mesh.Topologies\nusing ..MPIStateArrays\nusing Thermodynamics\nusing ..TurbulenceClosures\nusing ..VariableTemplates\nusing ..Writers\n\n\n# Pre-defined diagnostic variables\n\n# Atmos\ninclude(\"atmos_les_diagnostic_vars.jl\")\ninclude(\"atmos_gcm_diagnostic_vars.jl\")\n\n\n# Pre-defined diagnostics groups\n\n# Atmos\ninclude(\"atmos_les_default.jl\")\ninclude(\"atmos_gcm_default.jl\")\n\nend # module StdDiagnostics\n"
  },
  {
    "path": "src/Diagnostics/StdDiagnostics/atmos_gcm_default.jl",
    "content": "using ..Atmos\nusing ..ConfigTypes\nusing ..DiagnosticsMachine\n\n@diagnostics_group(\n    \"AtmosGCMDefault\",          # name\n    AtmosGCMConfigType,         # configuration type\n    Nothing,                    # params type\n    (_...) -> nothing,          # initialization function\n    InterpolateAfterCollection, # if/when to interpolate\n    # various pointwise variables\n    u,\n    v,\n    w,\n    rho,\n    temp,\n    pres,\n    thd,\n    et,\n    ei,\n    ht,\n    hi,\n    #vort, TODO\n    # moisture related\n    qt,\n    ql,\n    qv,\n    qi,\n    thv,\n    thl,\n)\n"
  },
  {
    "path": "src/Diagnostics/StdDiagnostics/atmos_gcm_diagnostic_vars.jl",
    "content": "@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    u,\n    \"m s^-1\",\n    \"zonal wind\",\n    \"eastward_wind\",\n    true,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    states.prognostic.ρu[1] / states.prognostic.ρ\nend\n\n@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    v,\n    \"m s^-1\",\n    \"meridional wind\",\n    \"northward_wind\",\n    true,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    states.prognostic.ρu[2] / states.prognostic.ρ\nend\n\n@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    w,\n    \"m s^-1\",\n    \"vertical wind\",\n    \"upward_air_velocity\",\n    true,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    states.prognostic.ρu[3] / states.prognostic.ρ\nend\n\n@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    rho,\n    \"kg m^-3\",\n    \"air density\",\n    \"air_density\",\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    states.prognostic.ρ\nend\n\n@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    et,\n    \"J kg^-1\",\n    \"total specific energy\",\n    \"specific_dry_energy_of_air\",\n) do (\n    energy::TotalEnergyModel,\n    atmos::AtmosModel,\n    states::States,\n    curr_time,\n    cache,\n)\n    states.prognostic.energy.ρe / states.prognostic.ρ\nend\n\n@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    temp,\n    \"K\",\n    \"air temperature\",\n    \"air_temperature\",\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    air_temperature(ts)\nend\n\n@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    pres,\n    \"Pa\",\n    \"air pressure\",\n    \"air_pressure\",\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    air_pressure(ts)\nend\n\n@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    thd,\n    \"K\",\n    \"dry potential temperature\",\n    \"air_potential_temperature\",\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    dry_pottemp(ts)\nend\n\n@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    ei,\n    \"J kg^-1\",\n    \"specific internal energy\",\n    \"internal_energy\",\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    internal_energy(ts)\nend\n\n@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    ht,\n    \"J kg^-1\",\n    \"specific enthalpy based on total energy\",\n    \"\",\n) do (\n    energy::TotalEnergyModel,\n    atmos::AtmosModel,\n    states::States,\n    curr_time,\n    cache,\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    e_tot = states.prognostic.energy.ρe / states.prognostic.ρ\n    total_specific_enthalpy(ts, e_tot)\nend\n\n@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    hi,\n    \"J kg^-1\",\n    \"specific enthalpy based on internal energy\",\n    \"atmosphere_enthalpy_content\",\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    specific_enthalpy(ts)\nend\n\n#= TODO\n@XXX_diagnostic(\n    \"vort\",\n    AtmosGCMConfigType,\n    GridInterpolated,\n    \"s^-1\",\n    \"vertical component of relative velocity\",\n    \"atmosphere_relative_velocity\",\n) do (atmos::AtmosModel, states::States, curr_time, cache)\nend\n=#\n\n@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    qt,\n    \"kg kg^-1\",\n    \"mass fraction of total water in air (qv+ql+qi)\",\n    \"mass_fraction_of_water_in_air\",\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time,\n    cache,\n)\n    states.prognostic.moisture.ρq_tot / states.prognostic.ρ\nend\n\n@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    ql,\n    \"kg kg^-1\",\n    \"mass fraction of liquid water in air\",\n    \"mass_fraction_of_cloud_liquid_water_in_air\",\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time,\n    cache,\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    liquid_specific_humidity(ts)\nend\n\n@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    qv,\n    \"kg kg^-1\",\n    \"mass fraction of water vapor in air\",\n    \"specific_humidity\",\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time,\n    cache,\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    vapor_specific_humidity(ts)\nend\n\n@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    qi,\n    \"kg kg^-1\",\n    \"mass fraction of ice in air\",\n    \"mass_fraction_of_cloud_ice_in_air\",\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time,\n    cache,\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    ice_specific_humidity(ts)\nend\n\n@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    thv,\n    \"K\",\n    \"virtual potential temperature\",\n    \"virtual_potential_temperature\",\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time,\n    cache,\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    virtual_pottemp(ts)\nend\n\n@pointwise_diagnostic(\n    AtmosGCMConfigType,\n    thl,\n    \"K\",\n    \"liquid-ice potential temperature\",\n    \"\",\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time,\n    cache,\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    liquid_ice_pottemp(ts)\nend\n"
  },
  {
    "path": "src/Diagnostics/StdDiagnostics/atmos_les_default.jl",
    "content": "using ..Atmos\nusing ..ConfigTypes\nusing ..DiagnosticsMachine\n\n@diagnostics_group(\n    \"AtmosLESDefault\",      # name\n    AtmosLESConfigType,     # configuration type\n    Nothing,                # params type\n    (_...) -> nothing,      # initialization function\n    NoInterpolation,        # if/when to interpolate\n    # various horizontal averages\n    u,\n    v,\n    w,\n    rho,\n    temp,\n    pres,\n    thd,\n    et,\n    ei,\n    ht,\n    hi,\n    w_ht_sgs,\n    # moisture related\n    qt,\n    ql,\n    qi,\n    qv,\n    thv,\n    thl,\n    w_qt_sgs,\n    # for variances and co-variances\n    uu,\n    vv,\n    ww,\n    www,\n    eiei,\n    wu,\n    wv,\n    wrho,\n    wthd,\n    wei,\n    qtqt,\n    thlthl,\n    wqt,\n    wql,\n    wqi,\n    wqv,\n    wthv,\n    wthl,\n    qtthl,\n    qtei,\n    #cld_top, TODO\n    #cld_base, TODO\n    #cld_cover, TODO\n    #lwp, TODO\n    #rwp, TODO\n)\n"
  },
  {
    "path": "src/Diagnostics/StdDiagnostics/atmos_les_diagnostic_vars.jl",
    "content": "@horizontal_average(\n    AtmosLESConfigType,\n    rho,\n    \"kg m^-3\",\n    \"air density\",\n    \"air_density\",\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    u,\n    \"m s^-1\",\n    \"x-velocity\",\n    \"\",\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    states.prognostic.ρu[1]\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    v,\n    \"m s^-1\",\n    \"y-velocity\",\n    \"\",\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    states.prognostic.ρu[2]\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    w,\n    \"m s^-1\",\n    \"z-velocity\",\n    \"\",\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    states.prognostic.ρu[3]\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    uu,\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    states.prognostic.ρu[1]^2 / states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    vv,\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    states.prognostic.ρu[2]^2 / states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    ww,\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    states.prognostic.ρu[3]^2 / states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    www,\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    states.prognostic.ρu[3]^3 / states.prognostic.ρ^2\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    wu,\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    states.prognostic.ρu[3] * states.prognostic.ρu[1] / states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    wv,\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    states.prognostic.ρu[3] * states.prognostic.ρu[2] / states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    wrho,\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    states.prognostic.ρu[3] * states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    temp,\n    \"K\",\n    \"air temperature\",\n    \"air_temperature\",\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    air_temperature(ts) * states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    pres,\n    \"Pa\",\n    \"air pressure\",\n    \"air_pressure\",\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    air_pressure(ts) * states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    thd,\n    \"K\",\n    \"dry potential temperature\",\n    \"air_potential_temperature\",\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    dry_pottemp(ts) * states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    et,\n    \"J kg^-1\",\n    \"total specific energy\",\n    \"specific_dry_energy_of_air\",\n    rho,\n) do (\n    energy::TotalEnergyModel,\n    atmos::AtmosModel,\n    states::States,\n    curr_time,\n    cache,\n)\n    states.prognostic.energy.ρe\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    ei,\n    \"J kg^-1\",\n    \"specific internal energy\",\n    \"internal_energy\",\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    internal_energy(ts) * states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    ht,\n    \"J kg^-1\",\n    \"specific enthalpy based on total energy\",\n    \"\",\n    rho,\n) do (\n    energy::TotalEnergyModel,\n    atmos::AtmosModel,\n    states::States,\n    curr_time,\n    cache,\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    e_tot = states.prognostic.energy.ρe / states.prognostic.ρ\n    total_specific_enthalpy(ts, e_tot) * states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    hi,\n    \"J kg^-1\",\n    \"specific enthalpy based on internal energy\",\n    \"atmosphere_enthalpy_content\",\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    specific_enthalpy(ts) * states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    w_ht_sgs,\n    \"kg kg^-1 m s^-1\",\n    \"vertical sgs flux of total specific enthalpy\",\n    \"\",\n    rho,\n) do (\n    energy::TotalEnergyModel,\n    atmos::AtmosModel,\n    states::States,\n    curr_time,\n    cache,\n)\n    D_t = get!(cache, :D_t) do\n        _, D_t, _ = turbulence_tensors(\n            atmos,\n            states.prognostic,\n            states.gradient_flux,\n            states.auxiliary,\n            curr_time,\n        )\n        D_t\n    end\n    d_h_tot = -D_t .* states.gradient_flux.energy.∇h_tot\n    d_h_tot[end] * states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    eiei,\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    internal_energy(ts)^2 * states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    wthd,\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    states.prognostic.ρu[3] * dry_pottemp(ts)\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    wei,\n    rho,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    states.prognostic.ρu[3] * internal_energy(ts)\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    qt,\n    \"kg kg^-1\",\n    \"mass fraction of total water in air (qv+ql+qi)\",\n    \"mass_fraction_of_water_in_air\",\n    rho,\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time::AbstractFloat,\n    cache::Dict{Symbol, Any},\n)\n    states.prognostic.moisture.ρq_tot\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    ql,\n    \"kg kg^-1\",\n    \"mass fraction of liquid water in air\",\n    \"mass_fraction_of_cloud_liquid_water_in_air\",\n    rho,\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time::AbstractFloat,\n    cache::Dict{Symbol, Any},\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    liquid_specific_humidity(ts) * states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    qi,\n    \"kg kg^-1\",\n    \"mass fraction of ice in air\",\n    \"mass_fraction_of_cloud_ice_in_air\",\n    rho,\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time::AbstractFloat,\n    cache::Dict{Symbol, Any},\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    ice_specific_humidity(ts) * states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    qv,\n    \"kg kg^-1\",\n    \"mass fraction of water vapor in air\",\n    \"specific_humidity\",\n    rho,\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time::AbstractFloat,\n    cache::Dict{Symbol, Any},\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    vapor_specific_humidity(ts) * states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    thv,\n    \"K\",\n    \"virtual potential temperature\",\n    \"virtual_potential_temperature\",\n    rho,\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time::AbstractFloat,\n    cache::Dict{Symbol, Any},\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    virtual_pottemp(ts) * states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    thl,\n    \"K\",\n    \"liquid-ice potential temperature\",\n    \"\",\n    rho,\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time::AbstractFloat,\n    cache::Dict{Symbol, Any},\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    liquid_ice_pottemp(ts) * states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    w_qt_sgs,\n    \"kg kg^-1 m s^-1\",\n    \"vertical sgs flux of total specific humidity\",\n    \"\",\n    rho,\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time::AbstractFloat,\n    cache::Dict{Symbol, Any},\n)\n    D_t = get!(cache, :D_t) do\n        _, D_t, _ = turbulence_tensors(\n            atmos,\n            states.prognostic,\n            states.gradient_flux,\n            states.auxiliary,\n            curr_time,\n        )\n        D_t\n    end\n    d_q_tot = -D_t .* states.gradient_flux.moisture.∇q_tot\n    d_q_tot[end] * states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    qtqt,\n    rho,\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time::AbstractFloat,\n    cache::Dict{Symbol, Any},\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    states.prognostic.moisture.ρq_tot^2 / states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    thlthl,\n    rho,\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time::AbstractFloat,\n    cache::Dict{Symbol, Any},\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    liquid_ice_pottemp(ts)^2 * states.prognostic.ρ\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    wqt,\n    rho,\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time::AbstractFloat,\n    cache::Dict{Symbol, Any},\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    q_tot = states.prognostic.moisture.ρq_tot / states.prognostic.ρ\n    states.prognostic.ρu[3] * q_tot\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    wql,\n    rho,\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time::AbstractFloat,\n    cache::Dict{Symbol, Any},\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    states.prognostic.ρu[3] * liquid_specific_humidity(ts)\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    wqi,\n    rho,\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time::AbstractFloat,\n    cache::Dict{Symbol, Any},\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    states.prognostic.ρu[3] * ice_specific_humidity(ts)\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    wqv,\n    rho,\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time::AbstractFloat,\n    cache::Dict{Symbol, Any},\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    states.prognostic.ρu[3] * vapor_specific_humidity(ts)\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    wthv,\n    rho,\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time::AbstractFloat,\n    cache::Dict{Symbol, Any},\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    states.prognostic.ρu[3] * virtual_pottemp(ts)\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    wthl,\n    rho,\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time::AbstractFloat,\n    cache::Dict{Symbol, Any},\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    states.prognostic.ρu[3] * liquid_ice_pottemp(ts)\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    qtthl,\n    rho,\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time::AbstractFloat,\n    cache::Dict{Symbol, Any},\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    states.prognostic.moisture.ρq_tot * liquid_ice_pottemp(ts)\nend\n\n@horizontal_average(\n    AtmosLESConfigType,\n    qtei,\n) do (\n    moisture::Union{EquilMoist, NonEquilMoist},\n    atmos::AtmosModel,\n    states::States,\n    curr_time::AbstractFloat,\n    cache::Dict{Symbol, Any},\n)\n    ts = get!(cache, :ts) do\n        recover_thermo_state(atmos, states.prognostic, states.auxiliary)\n    end\n    states.prognostic.moisture.ρq_tot * internal_energy(ts)\nend\n\n#= TODO\n    Variables[\"cld_frac\"] = DiagnosticVariable(\n        \"cld_frac\",\n        diagnostic_var_attrib(\n            \"\",\n            \"cloud fraction\",\n            \"cloud_area_fraction_in_atmosphere_layer\",\n        ),\n    )\n    Variables[\"cld_cover\"] = DiagnosticVariable(\n        \"cld_cover\",\n        diagnostic_var_attrib(\"\", \"cloud cover\", \"cloud_area_fraction\"),\n    )\n    Variables[\"cld_top\"] = DiagnosticVariable(\n        \"cld_top\",\n        diagnostic_var_attrib(\"m\", \"cloud top\", \"cloud_top_altitude\"),\n    )\n    Variables[\"cld_base\"] = DiagnosticVariable(\n        \"cld_base\",\n        diagnostic_var_attrib(\"m\", \"cloud base\", \"cloud_base_altitude\"),\n    )\n    Variables[\"lwp\"] = DiagnosticVariable(\n        \"lwp\",\n        diagnostic_var_attrib(\n            \"kg m^-2\",\n            \"liquid water path\",\n            \"atmosphere_mass_content_of_cloud_condensed_water\",\n        ),\n    )\n=#\n"
  },
  {
    "path": "src/Diagnostics/atmos_common.jl",
    "content": "# Helpers to gather and store some information useful to multiple diagnostics\n# groups.\n#\n\nBase.@kwdef mutable struct AtmosCollectedDiagnostics\n    onetime_done::Bool = false\n    zvals::Union{Nothing, Array} = nothing\n    MH_z::Union{Nothing, Array} = nothing\nend\nconst AtmosCollected = AtmosCollectedDiagnostics()\n\nfunction atmos_collect_onetime(mpicomm, dg, Q)\n    if !AtmosCollected.onetime_done\n\n        FT = eltype(Q)\n        grid = dg.grid\n        grid_info = basic_grid_info(dg)\n        topl_info = basic_topology_info(grid.topology)\n        Nqk = grid_info.Nqk\n        nvertelem = topl_info.nvertelem\n        localvgeo = array_device(Q) isa CPU ? grid.vgeo : Array(grid.vgeo)\n        AtmosCollected.zvals = zeros(FT, Nqk * nvertelem)\n        AtmosCollected.MH_z = zeros(FT, Nqk * nvertelem)\n        @traverse_dg_grid grid_info topl_info begin\n            z = localvgeo[ijk, grid.x3id, e]\n            MH = localvgeo[ijk, grid.MHid, e]\n            AtmosCollected.zvals[evk] = z\n            AtmosCollected.MH_z[evk] += MH\n        end\n\n        # compute the full number of points on a slab\n        MPI.Allreduce!(AtmosCollected.MH_z, +, mpicomm)\n\n        AtmosCollected.onetime_done = true\n    end\n\n    return nothing\nend\n"
  },
  {
    "path": "src/Diagnostics/atmos_gcm_default.jl",
    "content": "# # Dry Atmosphere GCM diagnostics\n# \n# This file computes selected diagnostics for the GCM and outputs them\n# on the spherical interpolated diagnostic grid.\n#\n# Use it by calling `Diagnostics.setup_atmos_default_diagnostics()`.\n#\n# TODO:\n# - enable zonal means and calculation of covariances using those means\n#   - ds.T_zm = mean(.*1., ds.T; dims = 3)\n#   - ds.u_zm = mean((ds.u); dims = 3 )\n#   - v_zm = mean(ds.v; dims = 3)\n#   - w_zm = mean(ds.w; dims = 3)\n#   - ds.uvcovariance = (ds.u .- ds.u_zm) * (ds.v .- v_zm)\n#   - ds.vTcovariance = (ds.v .- v_zm) * (ds.T .- ds.T_zm)\n# - add more variables, including horiz streamfunction from laplacial of vorticity (LN)\n# - density weighting\n# - maybe change thermo/dyn separation to local/nonlocal vars?\n\nimport CUDA\nusing LinearAlgebra\nusing Printf\nusing Statistics\n\nusing ..Atmos\nusing ..Atmos: recover_thermo_state\nusing ..DGMethods.NumericalFluxes\nusing ..TurbulenceClosures: turbulence_tensors\n\ninclude(\"diagnostic_fields.jl\")\ninclude(\"vorticity_balancelaw.jl\")\n\nmutable struct VorticityBLState <: DiagnosticsGroupParams\n    bl::Union{Nothing, VorticityModel}\n    dg::Union{Nothing, DGModel}\n    state::Union{Nothing, MPIStateArray}\n    dQ::Union{Nothing, MPIStateArray}\n\n    VorticityBLState() = new(nothing, nothing, nothing, nothing)\nend\n\n\"\"\"\n    setup_atmos_default_diagnostics(\n        ::AtmosGCMConfigType,\n        interval::String,\n        out_prefix::String;\n        writer::AbstractWriter,\n        interpol = nothing,\n    )\n\nCreate the \"AtmosGCMDefault\" `DiagnosticsGroup` which contains the following\ndiagnostic variables:\n- u: zonal wind\n- v: meridional wind\n- w: vertical wind\n- rho: air density\n- temp: air temperature\n- pres: air pressure\n- thd: dry potential temperature\n- et: total specific energy\n- ei: specific internal energy\n- ht: specific enthalpy based on total energy\n- hi: specific enthalpy based on internal energy\n- vort: vertical component of relative vorticity\n- vort2: vertical component of relative vorticity from DGModel kernels via a mini balance law\n\nWhen an `EquilMoist` moisture model is used, the following diagnostic\nvariables are also output:\n\n- qt: mass fraction of total water in air\n- ql: mass fraction of liquid water in air\n- qv: mass fraction of water vapor in air\n- qi: mass fraction of ice in air\n- thv: virtual potential temperature\n- thl: liquid-ice potential temperature\n\nAll these variables are output with `lat`, `long`, and `level` dimensions\nof an interpolated grid (`interpol` _must_ be specified) as well as a\n(unlimited) `time` dimension at the specified `interval`.\n\"\"\"\nfunction setup_atmos_default_diagnostics(\n    ::AtmosGCMConfigType,\n    interval::String,\n    out_prefix::String;\n    writer = NetCDFWriter(),\n    interpol = nothing,\n)\n    # TODO: remove this\n    @assert !isnothing(interpol)\n\n    return DiagnosticsGroup(\n        \"AtmosGCMDefault\",\n        Diagnostics.atmos_gcm_default_init,\n        Diagnostics.atmos_gcm_default_fini,\n        Diagnostics.atmos_gcm_default_collect,\n        interval,\n        out_prefix,\n        writer,\n        interpol,\n        VorticityBLState(),\n    )\nend\n\n# Declare all (3D) variables for this diagnostics group\nfunction vars_atmos_gcm_default_simple_3d(atmos::AtmosModel, FT)\n    @vars begin\n        u::FT\n        v::FT\n        w::FT\n        rho::FT\n        temp::FT\n        pres::FT\n        thd::FT                 # θ_dry\n        et::FT                  # e_tot\n        ei::FT                  # e_int\n        ht::FT\n        hi::FT\n        vort::FT                # Ω₃\n        vort2::FT               # Ω_bl₃\n\n        moisture::vars_atmos_gcm_default_simple_3d(moisture_model(atmos), FT)\n    end\nend\nvars_atmos_gcm_default_simple_3d(::AbstractMoistureModel, FT) = @vars()\nfunction vars_atmos_gcm_default_simple_3d(m::EquilMoist, FT)\n    @vars begin\n        qt::FT                  # q_tot\n        ql::FT                  # q_liq\n        qv::FT                  # q_vap\n        qi::FT                  # q_ice\n        thv::FT                 # θ_vir\n        thl::FT                 # θ_liq\n\n    end\nend\nnum_atmos_gcm_default_simple_3d_vars(m, FT) =\n    varsize(vars_atmos_gcm_default_simple_3d(m, FT))\natmos_gcm_default_simple_3d_vars(m, array) =\n    Vars{vars_atmos_gcm_default_simple_3d(m, eltype(array))}(array)\n\n# Collect all (3D) variables for this diagnostics group\nfunction atmos_gcm_default_simple_3d_vars!(\n    atmos::AtmosModel,\n    state_prognostic,\n    thermo,\n    dyni,\n    dyn_bli,\n    vars,\n)\n    vars.u = state_prognostic.ρu[1] / state_prognostic.ρ\n    vars.v = state_prognostic.ρu[2] / state_prognostic.ρ\n    vars.w = state_prognostic.ρu[3] / state_prognostic.ρ\n    vars.rho = state_prognostic.ρ\n    vars.et = state_prognostic.energy.ρe / state_prognostic.ρ\n\n    vars.temp = thermo.temp\n    vars.pres = thermo.pres\n    vars.thd = thermo.θ_dry\n    vars.ei = thermo.e_int\n    vars.ht = thermo.h_tot\n    vars.hi = thermo.h_int\n\n    vars.vort = dyni.Ω₃\n\n    vars.vort2 = dyn_bli.Ω_bl₃\n\n    atmos_gcm_default_simple_3d_vars!(\n        moisture_model(atmos),\n        state_prognostic,\n        thermo,\n        vars,\n    )\n\n    return nothing\nend\nfunction atmos_gcm_default_simple_3d_vars!(\n    ::AbstractMoistureModel,\n    state_prognostic,\n    thermo,\n    vars,\n)\n    return nothing\nend\nfunction atmos_gcm_default_simple_3d_vars!(\n    moist::EquilMoist,\n    state_prognostic,\n    thermo,\n    vars,\n)\n    vars.moisture.qt = state_prognostic.moisture.ρq_tot / state_prognostic.ρ\n    vars.moisture.ql = thermo.moisture.q_liq\n    vars.moisture.qv = thermo.moisture.q_vap\n    vars.moisture.qi = thermo.moisture.q_ice\n    vars.moisture.thv = thermo.moisture.θ_vir\n    vars.moisture.thl = thermo.moisture.θ_liq_ice\n\n    return nothing\nend\n\n# Dynamic variables\nfunction vars_dyn(FT)\n    @vars begin\n        Ω₁::FT\n        Ω₂::FT\n        Ω₃::FT\n    end\nend\ndyn_vars(array) = Vars{vars_dyn(eltype(array))}(array)\n\nfunction vars_dyn_bl(FT)\n    @vars begin\n        Ω_bl₁::FT\n        Ω_bl₂::FT\n        Ω_bl₃::FT\n    end\nend\ndyn_bl_vars(array) = Vars{vars_dyn_bl(eltype(array))}(array)\n\n\n\"\"\"\n    atmos_gcm_default_init(dgngrp, currtime)\n\nInitialize the GCM default diagnostics group, establishing the output file's\ndimensions and variables.\n\"\"\"\nfunction atmos_gcm_default_init(dgngrp::DiagnosticsGroup, currtime)\n    dg = Settings.dg\n    grid = dg.grid\n    atmos = dg.balance_law\n    FT = eltype(Settings.Q)\n    mpicomm = Settings.mpicomm\n    mpirank = MPI.Comm_rank(mpicomm)\n\n    if !(dgngrp.interpol isa InterpolationCubedSphere)\n        @warn \"\"\"\n            Diagnostics ($dgngrp.name): currently requires `InterpolationCubedSphere`!\n            \"\"\"\n        return nothing\n    end\n\n    # set up the vorticity mini balance law\n    vort_state = dgngrp.params\n    vort_state.bl = VorticityModel()\n    vort_state.dg = DGModel(\n        vort_state.bl,\n        grid,\n        CentralNumericalFluxFirstOrder(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n    vort_state.state = init_ode_state(vort_state.dg, FT(0))\n    vort_state.dQ = similar(\n        vort_state.state;\n        vars = @vars(Ω_bl::SVector{3, FT}),\n        nstate = 3,\n    )\n\n    if mpirank == 0\n        # get dimensions for the interpolated grid\n        dims = dimensions(dgngrp.interpol)\n\n        # adjust the level dimension for `planet_radius`\n        level_val = dims[\"level\"]\n        dims[\"level\"] = (\n            level_val[1] .- FT(planet_radius(Settings.param_set)),\n            level_val[2],\n        )\n\n        # set up the variables we're going to be writing\n        vars = OrderedDict()\n        varnames = map(\n            s -> startswith(s, \"moisture.\") ? s[10:end] : s,\n            flattenednames(vars_atmos_gcm_default_simple_3d(atmos, FT)),\n        )\n        for varname in varnames\n            var = Variables[varname]\n            vars[varname] = (tuple(collect(keys(dims))...), FT, var.attrib)\n        end\n\n        # create the output file\n        dprefix = @sprintf(\"%s_%s\", dgngrp.out_prefix, dgngrp.name)\n        dfilename = joinpath(Settings.output_dir, dprefix)\n        noov = Settings.no_overwrite\n        init_data(dgngrp.writer, dfilename, noov, dims, vars)\n    end\n\n    return nothing\nend\n\n\"\"\"\n    atmos_gcm_default_collect(dgngrp, currtime)\n\n    Master function that performs a global grid traversal to compute various\n    diagnostics using the above functions.\n\"\"\"\nfunction atmos_gcm_default_collect(dgngrp::DiagnosticsGroup, currtime)\n    interpol = dgngrp.interpol\n    if !(interpol isa InterpolationCubedSphere)\n        @warn \"\"\"\n            Diagnostics ($dgngrp.name): currently requires `InterpolationCubedSphere`!\n            \"\"\"\n        return nothing\n    end\n    vort_state = dgngrp.params\n\n    mpicomm = Settings.mpicomm\n    dg = Settings.dg\n    Q = Settings.Q\n    mpirank = MPI.Comm_rank(mpicomm)\n    atmos = dg.balance_law\n    grid = dg.grid\n    grid_info = basic_grid_info(dg)\n    topl_info = basic_topology_info(grid.topology)\n    Nqk = grid_info.Nqk\n    Nqh = grid_info.Nqh\n    npoints = prod(grid_info.Nq)\n    nrealelem = topl_info.nrealelem\n    nvertelem = topl_info.nvertelem\n    nhorzelem = topl_info.nhorzrealelem\n\n    # get needed arrays onto the CPU\n    device = array_device(Q)\n    if device isa CPU\n        ArrayType = Array\n        state_data = Q.realdata\n        aux_data = dg.state_auxiliary.realdata\n    else\n        ArrayType = CUDA.CuArray\n        state_data = Array(Q.realdata)\n        aux_data = Array(dg.state_auxiliary.realdata)\n    end\n    FT = eltype(state_data)\n\n    # TODO: can this be done in one pass?\n    #\n    # Non-local vars, e.g. relative vorticity\n    vgrad = VectorGradients(dg, Q)\n    vort = Vorticity(dg, vgrad)\n\n    # run the vorticity mini balance law\n    ix_ρu = varsindex(vars(Q), :ρu)\n    ix_ρ = varsindex(vars(Q), :ρ)\n    ρ = Q.data[:, ix_ρ, :]\n    u = Q.data[:, ix_ρu, :] ./ ρ\n\n    vort_state.dg.state_auxiliary.data .= u\n    vort_state.dg(vort_state.dQ, vort_state.state, nothing, FT(0))\n\n    # Compute thermo variables element-wise\n    thermo_array = Array{FT}(undef, npoints, num_thermo(atmos, FT), nrealelem)\n    @traverse_dg_grid grid_info topl_info begin\n        state = extract_state(dg, state_data, ijk, e, Prognostic())\n        aux = extract_state(dg, aux_data, ijk, e, Auxiliary())\n\n        thermo = thermo_vars(atmos, view(thermo_array, ijk, :, e))\n        compute_thermo!(atmos, state, aux, thermo)\n    end\n\n    # Interpolate the state, thermo, dgdiags and dyn vars to sphere (u and vorticity\n    # need projection to zonal, merid). All this may happen on the GPU.\n    istate =\n        ArrayType{FT}(undef, interpol.Npl, number_states(atmos, Prognostic()))\n    interpolate_local!(interpol, Q.realdata, istate)\n\n    ithermo = ArrayType{FT}(undef, interpol.Npl, num_thermo(atmos, FT))\n    interpolate_local!(interpol, ArrayType(thermo_array), ithermo)\n\n    idyn = ArrayType{FT}(undef, interpol.Npl, size(vort.data, 2))\n    interpolate_local!(interpol, vort.data, idyn)\n\n    idyn_bl = ArrayType{FT}(undef, interpol.Npl, size(vort_state.dQ.data, 2))\n    interpolate_local!(interpol, vort_state.dQ.data, idyn_bl)\n\n    # TODO: get indices here without hard-coding them\n    _ρu, _ρv, _ρw = 2, 3, 4\n    project_cubed_sphere!(interpol, istate, (_ρu, _ρv, _ρw))\n    _Ω₁, _Ω₂, _Ω₃ = 1, 2, 3\n    project_cubed_sphere!(interpol, idyn, (_Ω₁, _Ω₂, _Ω₃))\n    project_cubed_sphere!(interpol, idyn_bl, (_Ω₁, _Ω₂, _Ω₃))\n\n\n    # FIXME: accumulating to rank 0 is not scalable\n    all_state_data = accumulate_interpolated_data(mpicomm, interpol, istate)\n    all_thermo_data = accumulate_interpolated_data(mpicomm, interpol, ithermo)\n    all_dyn_data = accumulate_interpolated_data(mpicomm, interpol, idyn)\n    all_dyn_bl_data = accumulate_interpolated_data(mpicomm, interpol, idyn_bl)\n\n    if mpirank == 0\n        # get dimensions for the interpolated grid\n        dims = dimensions(dgngrp.interpol)\n\n        # set up the array for the diagnostic variables based on the interpolated grid\n        nlong = length(dims[\"long\"][1])\n        nlat = length(dims[\"lat\"][1])\n        nlevel = length(dims[\"level\"][1])\n\n        simple_3d_vars_array = Array{FT}(\n            undef,\n            nlong,\n            nlat,\n            nlevel,\n            num_atmos_gcm_default_simple_3d_vars(atmos, FT),\n        )\n\n        @traverse_interpolated_grid nlong nlat nlevel begin\n            statei = Vars{vars_state(atmos, Prognostic(), FT)}(view(\n                all_state_data,\n                lo,\n                la,\n                le,\n                :,\n            ))\n            thermoi = thermo_vars(atmos, view(all_thermo_data, lo, la, le, :))\n            dyni = dyn_vars(view(all_dyn_data, lo, la, le, :))\n            dyn_bli = dyn_bl_vars(view(all_dyn_bl_data, lo, la, le, :))\n            simple_3d_vars = atmos_gcm_default_simple_3d_vars(\n                atmos,\n                view(simple_3d_vars_array, lo, la, le, :),\n            )\n\n            atmos_gcm_default_simple_3d_vars!(\n                atmos,\n                statei,\n                thermoi,\n                dyni,\n                dyn_bli,\n                simple_3d_vars,\n            )\n        end\n\n        # assemble the diagnostics for writing\n        varvals = OrderedDict()\n        varnames = map(\n            s -> startswith(s, \"moisture.\") ? s[10:end] : s,\n            flattenednames(vars_atmos_gcm_default_simple_3d(atmos, FT)),\n        )\n        for (vari, varname) in enumerate(varnames)\n            varvals[varname] = simple_3d_vars_array[:, :, :, vari]\n        end\n\n        # write output\n        append_data(dgngrp.writer, varvals, currtime)\n    end\n\n    MPI.Barrier(mpicomm)\n    return nothing\nend # function collect\n\nfunction atmos_gcm_default_fini(dgngrp::DiagnosticsGroup, currtime) end\n"
  },
  {
    "path": "src/Diagnostics/atmos_gcm_spectra.jl",
    "content": "# Spectrum calculator for AtmosGCM\n\nstruct AtmosGCMSpectraDiagnosticsParams <: DiagnosticsGroupParams\n    nor::Float64\nend\n\n\"\"\"\n    setup_atmos_spectra_diagnostics(\n        ::AtmosGCMConfigType,\n        interval::String,\n        out_prefix::String;\n        writer = NetCDFWriter(),\n        interpol = nothing,\n        nor = 1.0,\n    )\n\nCreate the \"AtmosGCMSpectra\" `DiagnosticsGroup` which contains the\nfollowing diagnostic variables:\n\n- spectrum_1d: 1D power spectrum of kinetic energy ( 1/2*u^2 + 1/2*v^2) \n- spectrum_2d: 2D power spectrum of kinetic energy ( 1/2*u^2 + 1/2*v^2)\n\nThese are output with `m`, `lat`, `level` and `m_t`, `n`, `level`\ndimensions, i.e. zonal wavenumber, latitude, level and (truncated) zonal\nwavenumber, total wavenumber, level, as well as a (unlimited) `time`\ndimension at the specified `interval`. The spectrum is computed after the\nvelocity fields have been interpolated (`interpol` _must_ be specified).\n\"\"\"\nfunction setup_atmos_spectra_diagnostics(\n    ::AtmosGCMConfigType,\n    interval::String,\n    out_prefix::String;\n    writer = NetCDFWriter(),\n    interpol = nothing,\n    nor = 1.0,\n)\n    @assert !isnothing(interpol)\n\n    return DiagnosticsGroup(\n        \"AtmosGCMSpectra\",\n        Diagnostics.atmos_gcm_spectra_init,\n        Diagnostics.atmos_gcm_spectra_fini,\n        Diagnostics.atmos_gcm_spectra_collect,\n        interval,\n        out_prefix,\n        writer,\n        interpol,\n        AtmosGCMSpectraDiagnosticsParams(nor),\n    )\nend\n\nfunction get_spectra(mpicomm, mpirank, Q, bl, interpol, nor)\n    FT = eltype(Q)\n    if array_device(Q) isa CPU\n        ArrayType = Array\n    else\n        ArrayType = CUDA.CuArray\n    end\n\n    istate = ArrayType{FT}(undef, interpol.Npl, number_states(bl, Prognostic()))\n    interpolate_local!(interpol, Q.realdata, istate)\n\n    # TODO: get indices here without hard-coding them\n    _ρu, _ρv, _ρw = 2, 3, 4\n    project_cubed_sphere!(interpol, istate, (_ρu, _ρv, _ρw))\n\n    all_state_data = accumulate_interpolated_data(mpicomm, interpol, istate)\n\n    if mpirank == 0\n\n        dims = dimensions(interpol)\n        lat = dims[\"lat\"][1]\n        lon = dims[\"long\"][1]\n        level = dims[\"level\"][1] .- FT(planet_radius(Settings.param_set))\n\n        mass_weight = ones(FT, length(level))\n\n        var_grid_u = all_state_data[:, :, :, 2] ./ all_state_data[:, :, :, 1]\n        spectrum_1d_u, m = power_spectrum_1d(\n            AtmosGCMConfigType(),\n            var_grid_u,\n            level,\n            lat,\n            lon,\n            mass_weight,\n        )\n        spectrum_2d_u, m_and_n, _, __ =\n            power_spectrum_2d(AtmosGCMConfigType(), var_grid_u, mass_weight)\n\n        var_grid_v = all_state_data[:, :, :, 3] ./ all_state_data[:, :, :, 1]\n        mass_weight = ones(FT, length(level))\n        spectrum_1d_v, m = power_spectrum_1d(\n            AtmosGCMConfigType(),\n            var_grid_v,\n            level,\n            lat,\n            lon,\n            mass_weight,\n        )\n        spectrum_2d_v, m_and_n, _, __ =\n            power_spectrum_2d(AtmosGCMConfigType(), var_grid_v, mass_weight)\n\n        spectrum_1d = 0.5 .* (spectrum_1d_u + spectrum_1d_v)\n        spectrum_2d = 0.5 .* (spectrum_2d_u + spectrum_2d_v)\n\n        return spectrum_1d, m, spectrum_2d, m_and_n\n    end\n    return nothing, nothing, nothing, nothing\nend\n\nfunction atmos_gcm_spectra_init(dgngrp, currtime)\n    Q = Settings.Q\n    bl = Settings.dg.balance_law\n    mpicomm = Settings.mpicomm\n    mpirank = MPI.Comm_rank(mpicomm)\n    FT = eltype(Q)\n    interpol = dgngrp.interpol\n    nor = dgngrp.params.nor\n\n    # get the 1d and 2d spectra and their associated wavenumbers\n    spectrum_1d, m, spectrum_2d, m_and_n =\n        get_spectra(mpicomm, mpirank, Q, bl, interpol, nor)\n\n    if mpirank == 0\n        # Setup dimensions\n        interpol_dims = dimensions(interpol)\n        lat = interpol_dims[\"lat\"]\n        level = interpol_dims[\"level\"]\n        level = (level[1] .- FT(planet_radius(Settings.param_set)), level[2])\n        num_fourier = ((2 * length(lat[1]) - 1) / 3) # number of truncated zonal wavenumbers\n        num_spherical = (num_fourier + 1) # number of total wavenumbers (n)\n        dims = OrderedDict(\n            \"m\" => (collect(FT, 1:1:length(lat[1])) .- 1, Dict()),\n            \"m_t\" => (collect(FT, 0:1:num_fourier), Dict()),\n            \"n\" => (collect(FT, 0:1:num_spherical), Dict()),\n            \"level\" => level,\n            \"lat\" => lat,\n        )\n        # Setup variables (NB: 2d spectrum is on the truncated wavenumber grid)\n        vars = OrderedDict(\n            \"spectrum_1d\" => ((\"m\", \"lat\", \"level\"), FT, Dict()),\n            \"spectrum_2d\" => ((\"m_t\", \"n\", \"level\"), FT, Dict()),\n        )\n\n        dprefix = @sprintf(\"%s_%s\", dgngrp.out_prefix, dgngrp.name)\n        dfilename = joinpath(Settings.output_dir, dprefix)\n        noov = Settings.no_overwrite\n        init_data(dgngrp.writer, dfilename, noov, dims, vars)\n    end\n\n    return nothing\nend\n\nfunction atmos_gcm_spectra_collect(dgngrp, currtime)\n    Q = Settings.Q\n    bl = Settings.dg.balance_law\n    mpicomm = Settings.mpicomm\n    mpirank = MPI.Comm_rank(mpicomm)\n    FT = eltype(Q)\n    interpol = dgngrp.interpol\n    nor = dgngrp.params.nor\n\n    spectrum_1d, _, spectrum_2d, _ =\n        get_spectra(mpicomm, mpirank, Q, bl, interpol, nor)\n\n    if mpirank == 0\n        varvals = OrderedDict(\n            \"spectrum_1d\" => spectrum_1d,\n            \"spectrum_2d\" => spectrum_2d,\n        )\n        append_data(dgngrp.writer, varvals, currtime)\n    end\n\n    MPI.Barrier(mpicomm)\n    return nothing\nend\n\nfunction atmos_gcm_spectra_fini(dgngrp, currtime) end\n\n# TODO: interpolate on pressure levs and do mass weighted calculations, using mass_weight\n"
  },
  {
    "path": "src/Diagnostics/atmos_les_core.jl",
    "content": "using ..Atmos\nusing ..Atmos: recover_thermo_state\nusing ..Mesh.Topologies\nusing ..Mesh.Grids\nusing Thermodynamics\nusing LinearAlgebra\n\n\"\"\"\n    setup_atmos_core_diagnostics(\n        ::AtmosLESConfigType,\n        interval::String,\n        out_prefix::String;\n        writer::AbstractWriter,\n        interpol = nothing,\n    )\n\nCreate the \"AtmosLESCore\" `DiagnosticsGroup` which contains the following\ndiagnostic variables, all of which are density-averaged horizontal averages\nconditional upon `q_liq > 0 && w > 0` except for `core_frac`:\n\n- core_frac: cloud core fraction\n- u_core: cloud core x-velocity\n- v_core: cloud core y-velocity\n- w_core: cloud core z-velocity\n- avg_rho_core: cloud core air density (_not_ density averaged)\n- rho_core: cloud core air density\n- qt_core: cloud core total specific humidity\n- ql_core: cloud core liquid water specific humidity\n- thv_core: cloud core virtual potential temperature\n- thl_core: cloud core liquid-ice potential temperature\n- ei_core: cloud core specific internal energy\n- var_u_core: cloud core variance of x-velocity\n- var_v_core: cloud core variance of y-velocity\n- var_w_core: cloud core variance of z-velocity\n- var_qt_core: cloud core variance of total specific humidity\n- var_thl_core: cloud core variance of liquid-ice potential temperature\n- var_ei_core: cloud core variance of specific internal energy\n- cov_w_rho_core: cloud core vertical eddy flux of density\n- cov_w_qt_core: cloud core vertical eddy flux of specific humidity\n- cov_w_thl_core: cloud core vertical eddy flux of liquid-ice potential temperature\n- cov_w_ei_core: cloud core vertical eddy flux of specific internal energy\n- cov_qt_thl_core: cloud core covariance of total specific humidity and liquid-ice potential temperature\n- cov_qt_ei_core: cloud core covariance of total specific humidity and specific internal energy\n\nAll these variables are output with the `z` dimension (`x3id`) on the DG grid\n(`interpol` may _not_ be specified) as well as a (unlimited) `time` dimension\nat the specified `interval`.\n\"\"\"\nfunction setup_atmos_core_diagnostics(\n    ::AtmosLESConfigType,\n    interval::String,\n    out_prefix::String;\n    writer = NetCDFWriter(),\n    interpol = nothing,\n)\n    # TODO: remove this\n    @assert isnothing(interpol)\n\n    return DiagnosticsGroup(\n        \"AtmosLESCore\",\n        Diagnostics.atmos_les_core_init,\n        Diagnostics.atmos_les_core_fini,\n        Diagnostics.atmos_les_core_collect,\n        interval,\n        out_prefix,\n        writer,\n        interpol,\n    )\nend\n\n# Simple horizontal averages\nfunction vars_atmos_les_core_simple(m::AtmosModel, FT)\n    @vars begin\n        u_core::FT\n        v_core::FT\n        w_core::FT\n        avg_rho_core::FT        # ρ\n        rho_core::FT            # ρρ\n        qt_core::FT             # q_tot\n        ql_core::FT             # q_liq\n        thv_core::FT            # θ_vir\n        thl_core::FT            # θ_liq\n        ei_core::FT             # e_int\n    end\nend\nnum_atmos_les_core_simple_vars(m, FT) =\n    varsize(vars_atmos_les_core_simple(m, FT))\natmos_les_core_simple_vars(m, array) =\n    Vars{vars_atmos_les_core_simple(m, eltype(array))}(array)\n\nfunction atmos_les_core_simple_sums!(atmos::AtmosModel, state, thermo, MH, sums)\n    sums.u_core += MH * state.ρu[1]\n    sums.v_core += MH * state.ρu[2]\n    sums.w_core += MH * state.ρu[3]\n    sums.avg_rho_core += MH * state.ρ\n    sums.rho_core += MH * state.ρ * state.ρ\n    sums.qt_core += MH * state.moisture.ρq_tot\n    sums.ql_core += MH * thermo.moisture.q_liq * state.ρ\n    sums.thv_core += MH * thermo.moisture.θ_vir * state.ρ\n    sums.thl_core += MH * thermo.moisture.θ_liq_ice * state.ρ\n    sums.ei_core += MH * thermo.e_int * state.ρ\n\n    return nothing\nend\n\n# Variances and covariances\nfunction vars_atmos_les_core_ho(m::AtmosModel, FT)\n    @vars begin\n        var_u_core::FT          # u′u′\n        var_v_core::FT          # v′v′\n        var_w_core::FT          # w′w′\n        var_qt_core::FT         # q_tot′q_tot′\n        var_thl_core::FT        # θ_liq_ice′θ_liq_ice′\n        var_ei_core::FT         # e_int′e_int′\n\n        cov_w_rho_core::FT      # w′ρ′\n        cov_w_qt_core::FT       # w′q_tot′\n        cov_w_thl_core::FT      # w′θ_liq_ice′\n        cov_w_ei_core::FT       # w′e_int′\n        cov_qt_thl_core::FT     # q_tot′θ_liq_ice′\n        cov_qt_ei_core::FT      # q_tot′e_int′\n    end\nend\nnum_atmos_les_core_ho_vars(m, FT) = varsize(vars_atmos_les_core_ho(m, FT))\natmos_les_core_ho_vars(m, array) =\n    Vars{vars_atmos_les_core_ho(m, eltype(array))}(array)\n\nfunction atmos_les_core_ho_sums!(atmos::AtmosModel, state, thermo, MH, ha, sums)\n    u = state.ρu[1] / state.ρ\n    u′ = u - ha.u_core\n    v = state.ρu[2] / state.ρ\n    v′ = v - ha.v_core\n    w = state.ρu[3] / state.ρ\n    w′ = w - ha.w_core\n    q_tot = state.moisture.ρq_tot / state.ρ\n    q_tot′ = q_tot - ha.qt_core\n    θ_liq_ice′ = thermo.moisture.θ_liq_ice - ha.thl_core\n    e_int′ = thermo.e_int - ha.ei_core\n\n    sums.var_u_core += MH * u′^2 * state.ρ\n    sums.var_v_core += MH * v′^2 * state.ρ\n    sums.var_w_core += MH * w′^2 * state.ρ\n    sums.var_qt_core += MH * q_tot′^2 * state.ρ\n    sums.var_thl_core += MH * θ_liq_ice′^2 * state.ρ\n    sums.var_ei_core += MH * e_int′^2 * state.ρ\n\n    sums.cov_w_rho_core += MH * w′ * (state.ρ - ha.avg_rho_core) * state.ρ\n    sums.cov_w_qt_core += MH * w′ * q_tot′ * state.ρ\n    sums.cov_w_thl_core += MH * w′ * θ_liq_ice′ * state.ρ\n    sums.cov_qt_thl_core += MH * q_tot′ * θ_liq_ice′ * state.ρ\n    sums.cov_qt_ei_core += MH * q_tot′ * e_int′ * state.ρ\n    sums.cov_w_ei_core += MH * w′ * e_int′ * state.ρ\n\n    return nothing\nend\n\n\"\"\"\n    atmos_les_core_init(dgngrp, currtime)\n\nInitialize the 'AtmosLESCore' diagnostics group.\n\"\"\"\nfunction atmos_les_core_init(dgngrp::DiagnosticsGroup, currtime)\n    atmos = Settings.dg.balance_law\n    FT = eltype(Settings.Q)\n    mpicomm = Settings.mpicomm\n    mpirank = MPI.Comm_rank(mpicomm)\n\n    # FIXME properly\n    if !isa(moisture_model(atmos), EquilMoist)\n        @warn \"\"\"\n            Diagnostics $(dgngrp.name): can only be used with the `EquilMoist` moisture model\n            \"\"\"\n        return nothing\n    end\n\n    atmos_collect_onetime(Settings.mpicomm, Settings.dg, Settings.Q)\n\n    if mpirank == 0\n        dims = OrderedDict(\"z\" => (AtmosCollected.zvals, Dict()))\n\n        # set up the variables we're going to be writing\n        vars = OrderedDict()\n        vars[\"core_frac\"] = ((\"z\",), FT, Dict())\n\n        varnames = map(\n            s -> startswith(s, \"moisture.\") ? s[10:end] : s,\n            flattenednames(vars_atmos_les_core_simple(atmos, FT)),\n        )\n        ho_varnames = map(\n            s -> startswith(s, \"moisture.\") ? s[10:end] : s,\n            flattenednames(vars_atmos_les_core_ho(atmos, FT)),\n        )\n        append!(varnames, ho_varnames)\n        for varname in varnames\n            var = Variables[varname]\n            vars[varname] = ((\"z\",), FT, var.attrib)\n        end\n\n        # create the output file\n        dprefix = @sprintf(\"%s_%s\", dgngrp.out_prefix, dgngrp.name)\n        dfilename = joinpath(Settings.output_dir, dprefix)\n        noov = Settings.no_overwrite\n        init_data(dgngrp.writer, dfilename, noov, dims, vars)\n    end\n\n    return nothing\nend\n\n\"\"\"\n    atmos_les_core_collect(dgngrp, currtime)\n\nPerform a global grid traversal to compute various diagnostics.\n\"\"\"\nfunction atmos_les_core_collect(dgngrp::DiagnosticsGroup, currtime)\n    mpicomm = Settings.mpicomm\n    dg = Settings.dg\n    Q = Settings.Q\n    mpirank = MPI.Comm_rank(mpicomm)\n    atmos = dg.balance_law\n    if !isa(moisture_model(atmos), EquilMoist)\n        @warn \"\"\"\n            Diagnostics $(dgngrp.name): can only be used with the `EquilMoist` moisture model\n            \"\"\"\n        return nothing\n    end\n    grid = dg.grid\n    grid_info = basic_grid_info(dg)\n    topl_info = basic_topology_info(grid.topology)\n    Nqk = grid_info.Nqk\n    Nqh = grid_info.Nqh\n    npoints = prod(grid_info.Nq)\n    nrealelem = topl_info.nrealelem\n    nvertelem = topl_info.nvertelem\n    nhorzelem = topl_info.nhorzrealelem\n\n    # get needed arrays onto the CPU\n    if array_device(Q) isa CPU\n        state_data = Q.realdata\n        aux_data = dg.state_auxiliary.realdata\n        vgeo = grid.vgeo\n    else\n        state_data = Array(Q.realdata)\n        aux_data = Array(dg.state_auxiliary.realdata)\n        vgeo = Array(grid.vgeo)\n    end\n    FT = eltype(state_data)\n\n    zvals = AtmosCollected.zvals\n\n    # Visit each node of the state variables array and:\n    # - generate and store the thermo variables,\n    # - if core condition holds (q_liq > 0 && w > 0)\n    #   - count that point in the core fraction for that z\n    #   - count the point's weighting towards averaging for that z, and\n    #   - accumulate the simple horizontal sums\n    #\n    core_MH_z = zeros(FT, Nqk * nvertelem)\n    thermo_array =\n        [zeros(FT, num_thermo(atmos, FT)) for _ in 1:npoints, _ in 1:nrealelem]\n    simple_sums = [\n        zeros(FT, num_atmos_les_core_simple_vars(atmos, FT))\n        for _ in 1:(Nqk * nvertelem)\n    ]\n    ql_w_gt_0 = [zeros(FT, (Nqh * nhorzelem)) for _ in 1:(Nqk * nvertelem)]\n    @traverse_dg_grid grid_info topl_info begin\n        state = extract_state(dg, state_data, ijk, e, Prognostic())\n        aux = extract_state(dg, aux_data, ijk, e, Auxiliary())\n        MH = vgeo[ijk, grid.MHid, e]\n\n        thermo = thermo_vars(atmos, thermo_array[ijk, e])\n        compute_thermo!(atmos, state, aux, thermo)\n\n        if thermo.moisture.q_liq > 0 && state.ρu[3] > 0\n            idx = (Nqh * (eh - 1)) + (grid_info.Nq[2] * (j - 1)) + i\n            ql_w_gt_0[evk][idx] = one(FT)\n            core_MH_z[evk] += MH\n\n            simple = atmos_les_core_simple_vars(atmos, simple_sums[evk])\n            atmos_les_core_simple_sums!(atmos, state, thermo, MH, simple)\n        end\n    end\n\n    # reduce horizontal sums and core fraction across ranks and compute averages\n    simple_avgs = [\n        zeros(FT, num_atmos_les_core_simple_vars(atmos, FT))\n        for _ in 1:(Nqk * nvertelem)\n    ]\n    core_frac = zeros(FT, Nqk * nvertelem)\n    MPI.Allreduce!(core_MH_z, +, mpicomm)\n    for evk in 1:(Nqk * nvertelem)\n        tot_ql_w_gt_0 = MPI.Reduce(sum(ql_w_gt_0[evk]), +, 0, mpicomm)\n        tot_horz = MPI.Reduce(length(ql_w_gt_0[evk]), +, 0, mpicomm)\n\n        MPI.Allreduce!(simple_sums[evk], simple_avgs[evk], +, mpicomm)\n        simple_avgs[evk] .= simple_avgs[evk] ./ core_MH_z[evk]\n\n        if mpirank == 0\n            core_frac[evk] = tot_ql_w_gt_0 / tot_horz\n        end\n    end\n\n    # complete density averaging\n    simple_varnames = flattenednames(vars_atmos_les_core_simple(atmos, FT))\n    for vari in 1:length(simple_varnames)\n        for evk in 1:(Nqk * nvertelem)\n            simple_ha = atmos_les_core_simple_vars(atmos, simple_avgs[evk])\n            avg_rho = simple_ha.avg_rho_core\n            if simple_varnames[vari] != \"avg_rho_core\"\n                simple_avgs[evk][vari] /= avg_rho\n            end\n        end\n    end\n\n    # compute the variances and covariances\n    ho_sums = [\n        zeros(FT, num_atmos_les_core_ho_vars(atmos, FT))\n        for _ in 1:(Nqk * nvertelem)\n    ]\n    @traverse_dg_grid grid_info topl_info begin\n        state = extract_state(dg, state_data, ijk, e, Prognostic())\n        thermo = thermo_vars(atmos, thermo_array[ijk, e])\n        MH = vgeo[ijk, grid.MHid, e]\n\n        if thermo.moisture.q_liq > 0 && state.ρu[3] > 0\n            simple_ha = atmos_les_core_simple_vars(atmos, simple_avgs[evk])\n            ho = atmos_les_core_ho_vars(atmos, ho_sums[evk])\n            atmos_les_core_ho_sums!(atmos, state, thermo, MH, simple_ha, ho)\n        end\n    end\n\n    # reduce across ranks and compute averages\n    ho_avgs = [\n        zeros(FT, num_atmos_les_core_ho_vars(atmos, FT))\n        for _ in 1:(Nqk * nvertelem)\n    ]\n    for evk in 1:(Nqk * nvertelem)\n        MPI.Reduce!(ho_sums[evk], ho_avgs[evk], +, 0, mpicomm)\n        if mpirank == 0\n            ho_avgs[evk] .= ho_avgs[evk] ./ core_MH_z[evk]\n        end\n    end\n\n    # complete density averaging and prepare output\n    if mpirank == 0\n        varvals = OrderedDict()\n        varvals[\"core_frac\"] = core_frac\n\n        for (vari, varname) in enumerate(simple_varnames)\n            davg = zeros(FT, Nqk * nvertelem)\n            for evk in 1:(Nqk * nvertelem)\n                davg[evk] = simple_avgs[evk][vari]\n            end\n            varvals[varname] = davg\n        end\n\n        ho_varnames = flattenednames(vars_atmos_les_core_ho(atmos, FT))\n        for (vari, varname) in enumerate(ho_varnames)\n            davg = zeros(FT, Nqk * nvertelem)\n            for evk in 1:(Nqk * nvertelem)\n                simple_ha = atmos_les_core_simple_vars(atmos, simple_avgs[evk])\n                avg_rho = simple_ha.avg_rho_core\n                davg[evk] = ho_avgs[evk][vari] / avg_rho\n            end\n            varvals[varname] = davg\n        end\n\n        # write output\n        append_data(dgngrp.writer, varvals, currtime)\n    end\n\n    MPI.Barrier(mpicomm)\n    return nothing\nend # function collect\n\nfunction atmos_les_core_fini(dgngrp::DiagnosticsGroup, currtime) end\n"
  },
  {
    "path": "src/Diagnostics/atmos_les_default.jl",
    "content": "using ..Atmos\nusing ..Atmos: AbstractMoistureModel, PrecipitationModel, recover_thermo_state\nusing ..Mesh.Topologies\nusing ..Mesh.Grids\nusing Thermodynamics\nusing ..TurbulenceClosures\nusing LinearAlgebra\n\n\"\"\"\n    setup_atmos_default_diagnostics(\n        ::AtmosLESConfigType,\n        interval::String,\n        out_prefix::String;\n        writer = NetCDFWriter(),\n        interpol = nothing,\n    )\n\nCreate the \"AtmosLESDefault\" `DiagnosticsGroup` which contains the following\ndiagnostic variables, all of which are density-averaged horizontal averages,\nvariances and co-variances:\n\n- u: x-velocity\n- v: y-velocity\n- w: z-velocity\n- avg_rho: air density (_not_ density-averaged)\n- rho: air density\n- temp: air temperature\n- pres: air pressure\n- thd: dry potential temperature\n- et: total specific energy\n- ei: specific internal energy\n- ht: specific enthalpy based on total energy\n- hi: specific enthalpy based on internal energy\n- w_ht_sgs: vertical sgs flux of total specific enthalpy\n- var_u: variance of x-velocity\n- var_v: variance of y-velocity\n- var_w: variance of z-velocity\n- w3: third moment of z-velocity\n- tke: turbulent kinetic energy\n- var_ei: variance of specific internal energy\n- cov_w_u: vertical eddy flux of x-velocity\n- cov_w_v: vertical eddy flux of y-velocity\n- cov_w_rho: vertical eddy flux of density\n- cov_w_thd: vertical eddy flux of dry potential temperature\n- cov_w_ei: vertical eddy flux of specific internal energy\n\nWhen an `EquilMoist` or a `NonEquilMoist` moisture model is used, the following\ndiagnostic variables are also output, also density-averaged horizontal averages,\nvariances and co-variances:\n\n- qt: mass fraction of total water in air\n- ql: mass fraction of liquid water in air\n- qv: mass fraction of water vapor in air\n- qi: mass fraction of ice in air\n- thv: virtual potential temperature\n- thl: liquid-ice potential temperature\n- w_qt_sgs: vertical sgs flux of total specific humidity\n- var_qt: variance of total specific humidity\n- var_thl: variance of liquid-ice potential temperature\n- cov_w_qt: vertical eddy flux of total specific humidity\n- cov_w_ql: vertical eddy flux of liquid water specific humidity\n- cov_w_qi: vertical eddy flux of cloud ice specific humidity\n- cov_w_qv: vertical eddy flux of water vapor specific humidity\n- cov_w_thv: vertical eddy flux of virtual potential temperature\n- cov_w_thl: vertical eddy flux of liquid-ice potential temperature\n- cov_qt_thl: covariance of total specific humidity and liquid-ice potential temperature\n- cov_qt_ei: covariance of total specific humidity and specific internal energy\n\nAll these variables are output with the `z` dimension (`x3id`) on the DG grid\n(`interpol` may _not_ be specified) as well as a (unlimited) `time` dimension\nat the specified `interval`.\n\"\"\"\nfunction setup_atmos_default_diagnostics(\n    ::AtmosLESConfigType,\n    interval::String,\n    out_prefix::String;\n    writer = NetCDFWriter(),\n    interpol = nothing,\n)\n    # TODO: remove this\n    @assert isnothing(interpol)\n\n    return DiagnosticsGroup(\n        \"AtmosLESDefault\",\n        Diagnostics.atmos_les_default_init,\n        Diagnostics.atmos_les_default_fini,\n        Diagnostics.atmos_les_default_collect,\n        interval,\n        out_prefix,\n        writer,\n        interpol,\n    )\nend\n\n# Simple horizontal averages\nfunction vars_atmos_les_default_simple(m::AtmosModel, FT)\n    @vars begin\n        u::FT\n        v::FT\n        w::FT\n        avg_rho::FT             # ρ\n        rho::FT                 # ρρ\n        temp::FT\n        pres::FT\n        thd::FT                 # θ_dry\n        et::FT                  # e_tot\n        ei::FT                  # e_int\n        ht::FT\n        hi::FT\n        w_ht_sgs::FT\n\n        moisture::vars_atmos_les_default_simple(moisture_model(m), FT)\n        precipitation::vars_atmos_les_default_simple(precipitation_model(m), FT)\n    end\nend\nvars_atmos_les_default_simple(::AbstractMoistureModel, FT) = @vars()\nfunction vars_atmos_les_default_simple(m::Union{EquilMoist, NonEquilMoist}, FT)\n    @vars begin\n        qt::FT                  # q_tot\n        ql::FT                  # q_liq\n        qi::FT                  # q_ice\n        qv::FT                  # q_vap\n        thv::FT                 # θ_vir\n        thl::FT                 # θ_liq\n        w_qt_sgs::FT\n    end\nend\nvars_atmos_les_default_simple(::PrecipitationModel, FT) = @vars()\nfunction vars_atmos_les_default_simple(::RainModel, FT)\n    @vars begin\n        qr::FT                  # q_rai\n    end\nend\nfunction vars_atmos_les_default_simple(::RainSnowModel, FT)\n    @vars begin\n        qr::FT                  # q_rai\n        qs::FT                  # q_sno\n    end\nend\nnum_atmos_les_default_simple_vars(m, FT) =\n    varsize(vars_atmos_les_default_simple(m, FT))\natmos_les_default_simple_vars(m, array) =\n    Vars{vars_atmos_les_default_simple(m, eltype(array))}(array)\n\nfunction atmos_les_default_simple_sums!(\n    atmos::AtmosModel,\n    state,\n    gradflux,\n    aux,\n    thermo,\n    currtime,\n    MH,\n    sums,\n)\n    sums.u += MH * state.ρu[1]\n    sums.v += MH * state.ρu[2]\n    sums.w += MH * state.ρu[3]\n    sums.avg_rho += MH * state.ρ\n    sums.rho += MH * state.ρ * state.ρ\n    sums.temp += MH * thermo.temp * state.ρ\n    sums.pres += MH * thermo.pres * state.ρ\n    sums.thd += MH * thermo.θ_dry * state.ρ\n    sums.et += MH * state.energy.ρe\n    sums.ei += MH * thermo.e_int * state.ρ\n    sums.ht += MH * thermo.h_tot * state.ρ\n    sums.hi += MH * thermo.h_int * state.ρ\n\n    ν, D_t, _ = turbulence_tensors(atmos, state, gradflux, aux, currtime)\n    d_h_tot = -D_t .* gradflux.energy.∇h_tot\n    sums.w_ht_sgs += MH * d_h_tot[end] * state.ρ\n\n    atmos_les_default_simple_sums!(\n        moisture_model(atmos),\n        state,\n        gradflux,\n        thermo,\n        MH,\n        D_t,\n        sums,\n    )\n    atmos_les_default_simple_sums!(\n        precipitation_model(atmos),\n        state,\n        gradflux,\n        thermo,\n        MH,\n        D_t,\n        sums,\n    )\n    return nothing\nend\nfunction atmos_les_default_simple_sums!(\n    ::AbstractMoistureModel,\n    state,\n    gradflux,\n    thermo,\n    MH,\n    D_t,\n    sums,\n)\n    return nothing\nend\nfunction atmos_les_default_simple_sums!(\n    moist::Union{EquilMoist, NonEquilMoist},\n    state,\n    gradflux,\n    thermo,\n    MH,\n    D_t,\n    sums,\n)\n    sums.moisture.qt += MH * state.moisture.ρq_tot\n    sums.moisture.ql += MH * thermo.moisture.q_liq * state.ρ\n    sums.moisture.qi += MH * thermo.moisture.q_ice * state.ρ\n    sums.moisture.qv += MH * thermo.moisture.q_vap * state.ρ\n    sums.moisture.thv += MH * thermo.moisture.θ_vir * state.ρ\n    sums.moisture.thl += MH * thermo.moisture.θ_liq_ice * state.ρ\n    d_q_tot = (-D_t) .* gradflux.moisture.∇q_tot\n    sums.moisture.w_qt_sgs += MH * d_q_tot[end] * state.ρ\n\n    return nothing\nend\nfunction atmos_les_default_simple_sums!(\n    ::PrecipitationModel,\n    state,\n    gradflux,\n    thermo,\n    MH,\n    D_t,\n    sums,\n)\n    return nothing\nend\nfunction atmos_les_default_simple_sums!(\n    precipitation::RainModel,\n    state,\n    gradflux,\n    thermo,\n    MH,\n    D_t,\n    sums,\n)\n    sums.precipitation.qr += MH * state.precipitation.ρq_rai\n\n    return nothing\nend\nfunction atmos_les_default_simple_sums!(\n    precipitation::RainSnowModel,\n    state,\n    gradflux,\n    thermo,\n    MH,\n    D_t,\n    sums,\n)\n    sums.precipitation.qr += MH * state.precipitation.ρq_rai\n    sums.precipitation.qs += MH * state.precipitation.ρq_sno\n\n    return nothing\nend\n\nfunction atmos_les_default_clouds(\n    ::AbstractMoistureModel,\n    thermo,\n    idx,\n    qc_gt_0_z,\n    qc_gt_0_full,\n    z,\n    cld_top,\n    cld_base,\n)\n    return cld_top, cld_base\nend\nfunction atmos_les_default_clouds(\n    moist::Union{EquilMoist, NonEquilMoist},\n    thermo,\n    idx,\n    qc_gt_0_z,\n    qc_gt_0_full,\n    z,\n    cld_top,\n    cld_base,\n)\n    if thermo.moisture.has_condensate\n        FT = eltype(qc_gt_0_z)\n        qc_gt_0_z[idx] = one(FT)\n        qc_gt_0_full[idx] = one(FT)\n\n        cld_top = max(cld_top, z)\n        cld_base = min(cld_base, z)\n    end\n    return cld_top, cld_base\nend\n\n# Variances and covariances\nfunction vars_atmos_les_default_ho(m::AtmosModel, FT)\n    @vars begin\n        var_u::FT               # u′u′\n        var_v::FT               # v′v′\n        var_w::FT               # w′w′\n        w3::FT                  # w′w′w′\n        tke::FT\n        var_ei::FT              # e_int′e_int′\n\n        cov_w_u::FT             # w′u′\n        cov_w_v::FT             # w′v′\n        cov_w_rho::FT           # w′ρ′\n        cov_w_thd::FT           # w′θ_dry′\n        cov_w_ei::FT            # w′e_int′\n\n        moisture::vars_atmos_les_default_ho(moisture_model(m), FT)\n        precipitation::vars_atmos_les_default_ho(precipitation_model(m), FT)\n    end\nend\nvars_atmos_les_default_ho(::AbstractMoistureModel, FT) = @vars()\nfunction vars_atmos_les_default_ho(m::Union{EquilMoist, NonEquilMoist}, FT)\n    @vars begin\n        var_qt::FT              # q_tot′q_tot′\n        var_thl::FT             # θ_liq_ice′θ_liq_ice′\n\n        cov_w_qt::FT            # w′q_tot′\n        cov_w_ql::FT            # w′q_liq′\n        cov_w_qi::FT            # w′q_ice′\n        cov_w_qv::FT            # w′q_vap′\n        cov_w_thv::FT           # w′θ_v′\n        cov_w_thl::FT           # w′θ_liq_ice′\n        cov_qt_thl::FT          # q_tot′θ_liq_ice′\n        cov_qt_ei::FT           # q_tot′e_int′\n    end\nend\nvars_atmos_les_default_ho(::PrecipitationModel, FT) = @vars()\nfunction vars_atmos_les_default_ho(m::RainModel, FT)\n    @vars begin\n        var_qr::FT              # q_rai′q_rai′\n        cov_w_qr::FT            # w′q_rai′\n    end\nend\nfunction vars_atmos_les_default_ho(m::RainSnowModel, FT)\n    @vars begin\n        var_qr::FT              # q_rai′q_rai′\n        var_qs::FT              # q_sno′q_sno′\n        cov_w_qr::FT            # w′q_rai′\n        cov_w_qs::FT            # w′q_sno′\n    end\nend\nnum_atmos_les_default_ho_vars(m, FT) = varsize(vars_atmos_les_default_ho(m, FT))\natmos_les_default_ho_vars(m, array) =\n    Vars{vars_atmos_les_default_ho(m, eltype(array))}(array)\n\nfunction atmos_les_default_ho_sums!(\n    atmos::AtmosModel,\n    state,\n    thermo,\n    MH,\n    ha,\n    sums,\n)\n    u = state.ρu[1] / state.ρ\n    u′ = u - ha.u\n    v = state.ρu[2] / state.ρ\n    v′ = v - ha.v\n    w = state.ρu[3] / state.ρ\n    w′ = w - ha.w\n    e_int′ = thermo.e_int - ha.ei\n    θ_dry′ = thermo.θ_dry - ha.thd\n\n    sums.var_u += MH * u′^2 * state.ρ\n    sums.var_v += MH * v′^2 * state.ρ\n    sums.var_w += MH * w′^2 * state.ρ\n    sums.w3 += MH * w′^3 * state.ρ\n    sums.tke +=\n        0.5 * (MH * u′^2 * state.ρ + MH * v′^2 * state.ρ + MH * w′^2 * state.ρ)\n    sums.var_ei += MH * e_int′^2 * state.ρ\n\n    sums.cov_w_u += MH * w′ * u′ * state.ρ\n    sums.cov_w_v += MH * w′ * v′ * state.ρ\n    sums.cov_w_rho += MH * w′ * (state.ρ - ha.avg_rho) * state.ρ\n    sums.cov_w_thd += MH * w′ * θ_dry′ * state.ρ\n    sums.cov_w_ei += MH * w′ * e_int′ * state.ρ\n\n    atmos_les_default_ho_sums!(\n        moisture_model(atmos),\n        state,\n        thermo,\n        MH,\n        ha,\n        w′,\n        e_int′,\n        sums,\n    )\n    atmos_les_default_ho_sums!(\n        precipitation_model(atmos),\n        state,\n        thermo,\n        MH,\n        ha,\n        w′,\n        e_int′,\n        sums,\n    )\n    return nothing\nend\nfunction atmos_les_default_ho_sums!(\n    ::AbstractMoistureModel,\n    state,\n    thermo,\n    MH,\n    ha,\n    w′,\n    e_int′,\n    sums,\n)\n    return nothing\nend\nfunction atmos_les_default_ho_sums!(\n    moist::Union{EquilMoist, NonEquilMoist},\n    state,\n    thermo,\n    MH,\n    ha,\n    w′,\n    e_int′,\n    sums,\n)\n    q_tot = state.moisture.ρq_tot / state.ρ\n    q_tot′ = q_tot - ha.moisture.qt\n    q_liq′ = thermo.moisture.q_liq - ha.moisture.ql\n    q_ice′ = thermo.moisture.q_ice - ha.moisture.qi\n    q_vap′ = thermo.moisture.q_vap - ha.moisture.qv\n    θ_vir′ = thermo.moisture.θ_vir - ha.moisture.thv\n    θ_liq_ice′ = thermo.moisture.θ_liq_ice - ha.moisture.thl\n\n    sums.moisture.var_qt += MH * q_tot′^2 * state.ρ\n    sums.moisture.var_thl += MH * θ_liq_ice′^2 * state.ρ\n\n    sums.moisture.cov_w_qt += MH * w′ * q_tot′ * state.ρ\n    sums.moisture.cov_w_ql += MH * w′ * q_liq′ * state.ρ\n    sums.moisture.cov_w_qi += MH * w′ * q_ice′ * state.ρ\n    sums.moisture.cov_w_qv += MH * w′ * q_vap′ * state.ρ\n    sums.moisture.cov_w_thv += MH * w′ * θ_vir′ * state.ρ\n    sums.moisture.cov_w_thl += MH * w′ * θ_liq_ice′ * state.ρ\n    sums.moisture.cov_qt_thl += MH * q_tot′ * θ_liq_ice′ * state.ρ\n    sums.moisture.cov_qt_ei += MH * q_tot′ * e_int′ * state.ρ\n\n    return nothing\nend\nfunction atmos_les_default_ho_sums!(\n    ::PrecipitationModel,\n    state,\n    thermo,\n    MH,\n    ha,\n    w′,\n    e_int′,\n    sums,\n)\n    return nothing\nend\nfunction atmos_les_default_ho_sums!(\n    moist::RainModel,\n    state,\n    thermo,\n    MH,\n    ha,\n    w′,\n    e_int′,\n    sums,\n)\n    q_rai = state.precipitation.ρq_rai / state.ρ\n    q_rai′ = q_rai - ha.precipitation.qr\n\n    sums.precipitation.var_qr += MH * q_rai′^2 * state.ρ\n\n    sums.precipitation.cov_w_qr += MH * w′ * q_rai′ * state.ρ\n\n    return nothing\nend\nfunction atmos_les_default_ho_sums!(\n    moist::RainSnowModel,\n    state,\n    thermo,\n    MH,\n    ha,\n    w′,\n    e_int′,\n    sums,\n)\n    q_rai = state.precipitation.ρq_rai / state.ρ\n    q_rai′ = q_rai - ha.precipitation.qr\n    q_sno = state.precipitation.ρq_sno / state.ρ\n    q_sno′ = q_sno - ha.precipitation.qs\n\n    sums.precipitation.var_qr += MH * q_rai′^2 * state.ρ\n    sums.precipitation.var_qs += MH * q_sno′^2 * state.ρ\n\n    sums.precipitation.cov_w_qr += MH * w′ * q_rai′ * state.ρ\n    sums.precipitation.cov_w_qs += MH * w′ * q_sno′ * state.ρ\n\n    return nothing\nend\n\nfunction prefix_filter(s)\n    if startswith(s, \"moisture.\")\n        s[10:end]\n    elseif startswith(s, \"precipitation.\")\n        s[15:end]\n    else\n        s\n    end\nend\n\n\"\"\"\n    atmos_les_default_init(dgngrp, currtime)\n\nInitialize the 'AtmosLESDefault' diagnostics group.\n\"\"\"\nfunction atmos_les_default_init(dgngrp::DiagnosticsGroup, currtime)\n    atmos = Settings.dg.balance_law\n    FT = eltype(Settings.Q)\n    mpicomm = Settings.mpicomm\n    mpirank = MPI.Comm_rank(mpicomm)\n\n    atmos_collect_onetime(mpicomm, Settings.dg, Settings.Q)\n\n    if mpirank == 0\n        dims = OrderedDict(\"z\" => (AtmosCollected.zvals, Dict()))\n\n        # set up the variables we're going to be writing\n        vars = OrderedDict()\n        varnames = map(\n            prefix_filter,\n            flattenednames(vars_atmos_les_default_simple(atmos, FT)),\n        )\n        ho_varnames = map(\n            prefix_filter,\n            flattenednames(vars_atmos_les_default_ho(atmos, FT)),\n        )\n        append!(varnames, ho_varnames)\n        for varname in varnames\n            var = Variables[varname]\n            vars[varname] = ((\"z\",), FT, var.attrib)\n        end\n        vars[\"cld_frac\"] = ((\"z\",), FT, Variables[\"cld_frac\"].attrib)\n        vars[\"cld_top\"] = ((), FT, Variables[\"cld_top\"].attrib)\n        vars[\"cld_base\"] = ((), FT, Variables[\"cld_base\"].attrib)\n        vars[\"cld_cover\"] = ((), FT, Variables[\"cld_cover\"].attrib)\n        vars[\"lwp\"] = ((), FT, Variables[\"lwp\"].attrib)\n        vars[\"iwp\"] = ((), FT, Variables[\"iwp\"].attrib)\n        vars[\"rwp\"] = ((), FT, Variables[\"rwp\"].attrib)\n        vars[\"swp\"] = ((), FT, Variables[\"swp\"].attrib)\n\n        # create the output file\n        dprefix = @sprintf(\"%s_%s\", dgngrp.out_prefix, dgngrp.name)\n        dfilename = joinpath(Settings.output_dir, dprefix)\n        init_data(dgngrp.writer, dfilename, Settings.no_overwrite, dims, vars)\n    end\n\n    return nothing\nend\n\n\"\"\"\n    atmos_les_default_collect(dgngrp, currtime)\n\nCollect the various 'AtmosLESDefault' diagnostic variables for the\ncurrent timestep and write them into a file.\n\"\"\"\nfunction atmos_les_default_collect(dgngrp::DiagnosticsGroup, currtime)\n    mpicomm = Settings.mpicomm\n    dg = Settings.dg\n    Q = Settings.Q\n    mpirank = MPI.Comm_rank(mpicomm)\n    atmos = dg.balance_law\n    grid = dg.grid\n    grid_info = basic_grid_info(dg)\n    topl_info = basic_topology_info(grid.topology)\n    Nqk = grid_info.Nqk\n    Nqh = grid_info.Nqh\n    npoints = prod(grid_info.Nq)\n    nrealelem = topl_info.nrealelem\n    nvertelem = topl_info.nvertelem\n    nhorzelem = topl_info.nhorzrealelem\n\n    energy_model(atmos) isa TotalEnergyModel ||\n        error(\"TotalEnergyModel only supported\")\n\n    # get needed arrays onto the CPU\n    if array_device(Q) isa CPU\n        state_data = Q.realdata\n        aux_data = dg.state_auxiliary.realdata\n        vgeo = grid.vgeo\n        # ω is the weight vector for the vertical direction\n        ω = grid.ω[end]\n        gradflux_data = dg.state_gradient_flux.realdata\n    else\n        state_data = Array(Q.realdata)\n        aux_data = Array(dg.state_auxiliary.realdata)\n        vgeo = Array(grid.vgeo)\n        # ω is the weight vector for the vertical direction\n        ω = Array(grid.ω[end])\n        gradflux_data = Array(dg.state_gradient_flux.realdata)\n    end\n    FT = eltype(state_data)\n\n    zvals = AtmosCollected.zvals\n    MH_z = AtmosCollected.MH_z\n\n    # Visit each node of the state variables array and:\n    # - generate and store the thermo variables,\n    # - accumulate the simple horizontal sums, and\n    # - determine the cloud fraction, top and base\n    #\n    thermo_array =\n        [zeros(FT, num_thermo(atmos, FT)) for _ in 1:npoints, _ in 1:nrealelem]\n    simple_sums = [\n        zeros(FT, num_atmos_les_default_simple_vars(atmos, FT))\n        for _ in 1:(Nqk * nvertelem)\n    ]\n    # for liquid, ice, rain and snow water paths\n    ρq_liq_z = [zero(FT) for _ in 1:(Nqk * nvertelem)]\n    ρq_ice_z = [zero(FT) for _ in 1:(Nqk * nvertelem)]\n    ρq_rai_z = [zero(FT) for _ in 1:(Nqk * nvertelem)]\n    ρq_sno_z = [zero(FT) for _ in 1:(Nqk * nvertelem)]\n    # for cld*\n    qc_gt_0_z = [zeros(FT, (Nqh * nhorzelem)) for _ in 1:(Nqk * nvertelem)]\n    qc_gt_0_full = zeros(FT, (Nqh * nhorzelem))\n    # In honor of PyCLES!\n    cld_top = FT(-100000)\n    cld_base = FT(100000)\n    @traverse_dg_grid grid_info topl_info begin\n        state = extract_state(dg, state_data, ijk, e, Prognostic())\n        gradflux = extract_state(dg, gradflux_data, ijk, e, GradientFlux())\n        aux = extract_state(dg, aux_data, ijk, e, Auxiliary())\n        MH = vgeo[ijk, grid.MHid, e]\n\n        thermo = thermo_vars(atmos, thermo_array[ijk, e])\n        compute_thermo!(atmos, state, aux, thermo)\n\n        simple = atmos_les_default_simple_vars(atmos, simple_sums[evk])\n        atmos_les_default_simple_sums!(\n            atmos,\n            state,\n            gradflux,\n            aux,\n            thermo,\n            currtime,\n            MH,\n            simple,\n        )\n\n        idx = (Nqh * (eh - 1)) + (grid_info.Nq[2] * (j - 1)) + i\n        cld_top, cld_base = atmos_les_default_clouds(\n            moisture_model(atmos),\n            thermo,\n            idx,\n            qc_gt_0_z[evk],\n            qc_gt_0_full,\n            zvals[evk],\n            cld_top,\n            cld_base,\n        )\n\n        # FIXME properly\n        if isa(moisture_model(atmos), EquilMoist) ||\n           isa(moisture_model(atmos), NonEquilMoist)\n            # for LWP\n            ρq_liq_z[evk] += MH * thermo.moisture.q_liq * state.ρ * state.ρ\n            ρq_ice_z[evk] += MH * thermo.moisture.q_ice * state.ρ * state.ρ\n        end\n        if isa(precipitation_model(atmos), RainModel)\n            # for RWP\n            ρq_rai_z[evk] += MH * state.precipitation.ρq_rai * state.ρ\n        end\n        if isa(precipitation_model(atmos), RainSnowModel)\n            # for RWP\n            ρq_rai_z[evk] += MH * state.precipitation.ρq_rai * state.ρ\n            # for SWP\n            ρq_sno_z[evk] += MH * state.precipitation.ρq_sno * state.ρ\n        end\n    end\n\n    # reduce horizontal sums and cloud data across ranks and compute averages\n    simple_avgs = [\n        zeros(FT, num_atmos_les_default_simple_vars(atmos, FT))\n        for _ in 1:(Nqk * nvertelem)\n    ]\n    cld_frac = zeros(FT, Nqk * nvertelem)\n    for evk in 1:(Nqk * nvertelem)\n        MPI.Allreduce!(simple_sums[evk], simple_avgs[evk], +, mpicomm)\n        simple_avgs[evk] .= simple_avgs[evk] ./ MH_z[evk]\n\n        # FIXME properly\n        if isa(moisture_model(atmos), EquilMoist) ||\n           isa(moisture_model(atmos), NonEquilMoist)\n            tot_qc_gt_0_z = MPI.Reduce(sum(qc_gt_0_z[evk]), +, 0, mpicomm)\n            tot_horz_z = MPI.Reduce(length(qc_gt_0_z[evk]), +, 0, mpicomm)\n            if mpirank == 0\n                cld_frac[evk] = tot_qc_gt_0_z / tot_horz_z\n            end\n\n            # for LWP and IWP\n            tot_ρq_liq_z = MPI.Reduce(ρq_liq_z[evk], +, 0, mpicomm)\n            tot_ρq_ice_z = MPI.Reduce(ρq_ice_z[evk], +, 0, mpicomm)\n            if mpirank == 0\n                ρq_liq_z[evk] = tot_ρq_liq_z / MH_z[evk]\n                ρq_ice_z[evk] = tot_ρq_ice_z / MH_z[evk]\n            end\n        end\n        if isa(precipitation_model(atmos), RainModel)\n            # for RWP\n            tot_ρq_rai_z = MPI.Reduce(ρq_rai_z[evk], +, 0, mpicomm)\n            if mpirank == 0\n                ρq_rai_z[evk] = tot_ρq_rai_z / MH_z[evk]\n            end\n        end\n        if isa(precipitation_model(atmos), RainSnowModel)\n            # for RWP and SWP\n            tot_ρq_rai_z = MPI.Reduce(ρq_rai_z[evk], +, 0, mpicomm)\n            tot_ρq_sno_z = MPI.Reduce(ρq_sno_z[evk], +, 0, mpicomm)\n            if mpirank == 0\n                ρq_rai_z[evk] = tot_ρq_rai_z / MH_z[evk]\n                ρq_sno_z[evk] = tot_ρq_sno_z / MH_z[evk]\n            end\n        end\n    end\n    # FIXME properly\n    if isa(moisture_model(atmos), EquilMoist) ||\n       isa(moisture_model(atmos), NonEquilMoist)\n        cld_top = MPI.Reduce(cld_top, max, 0, mpicomm)\n        if cld_top == FT(-100000)\n            cld_top = NaN\n        end\n        cld_base = MPI.Reduce(cld_base, min, 0, mpicomm)\n        if cld_base == FT(100000)\n            cld_base = NaN\n        end\n        tot_qc_gt_0_full = MPI.Reduce(sum(qc_gt_0_full), +, 0, mpicomm)\n        tot_horz_full = MPI.Reduce(length(qc_gt_0_full), +, 0, mpicomm)\n        cld_cover = zero(FT)\n        if mpirank == 0\n            cld_cover = tot_qc_gt_0_full / tot_horz_full\n        end\n    end\n\n    simple_varnames = map(\n        prefix_filter,\n        flattenednames(vars_atmos_les_default_simple(atmos, FT)),\n    )\n\n    # complete density averaging\n    for evk in 1:(Nqk * nvertelem)\n        simple_ha = atmos_les_default_simple_vars(atmos, simple_avgs[evk])\n        avg_rho = simple_ha.avg_rho\n        for vari in 1:length(simple_varnames)\n            if simple_varnames[vari] != \"avg_rho\"\n                simple_avgs[evk][vari] /= avg_rho\n            end\n        end\n\n        # for all the water paths\n        # FIXME properly\n        if isa(moisture_model(atmos), EquilMoist) ||\n           isa(moisture_model(atmos), NonEquilMoist)\n            ρq_liq_z[evk] /= avg_rho\n            ρq_ice_z[evk] /= avg_rho\n        end\n        if isa(precipitation_model(atmos), RainModel)\n            ρq_rai_z[evk] /= avg_rho\n        end\n        if isa(precipitation_model(atmos), RainSnowModel)\n            ρq_rai_z[evk] /= avg_rho\n            ρq_sno_z[evk] /= avg_rho\n        end\n    end\n\n    # compute all the water paths\n    lwp = NaN\n    iwp = NaN\n    rwp = NaN\n    swp = NaN\n    if mpirank == 0\n        JcV = reshape(\n            view(vgeo, :, grid.JcVid, grid.topology.realelems),\n            Nqh,\n            Nqk,\n            nvertelem,\n            nhorzelem,\n        )\n        Mvert = (ω .* JcV[1, :, :, 1])[:]\n        lwp = FT(sum(ρq_liq_z .* Mvert))\n        iwp = FT(sum(ρq_ice_z .* Mvert))\n        rwp = FT(sum(ρq_rai_z .* Mvert))\n        swp = FT(sum(ρq_sno_z .* Mvert))\n    end\n\n    # compute the variances and covariances\n    ho_sums = [\n        zeros(FT, num_atmos_les_default_ho_vars(atmos, FT))\n        for _ in 1:(Nqk * nvertelem)\n    ]\n    @traverse_dg_grid grid_info topl_info begin\n        state = extract_state(dg, state_data, ijk, e, Prognostic())\n        thermo = thermo_vars(atmos, thermo_array[ijk, e])\n        MH = vgeo[ijk, grid.MHid, e]\n\n        simple_ha = atmos_les_default_simple_vars(atmos, simple_avgs[evk])\n        ho = atmos_les_default_ho_vars(atmos, ho_sums[evk])\n        atmos_les_default_ho_sums!(atmos, state, thermo, MH, simple_ha, ho)\n    end\n\n    # reduce across ranks and compute averages\n    ho_avgs = [\n        zeros(FT, num_atmos_les_default_ho_vars(atmos, FT))\n        for _ in 1:(Nqk * nvertelem)\n    ]\n    for evk in 1:(Nqk * nvertelem)\n        MPI.Reduce!(ho_sums[evk], ho_avgs[evk], +, 0, mpicomm)\n        if mpirank == 0\n            ho_avgs[evk] .= ho_avgs[evk] ./ MH_z[evk]\n        end\n    end\n\n    # complete density averaging and prepare output\n    if mpirank == 0\n        varvals = OrderedDict()\n        for (vari, varname) in enumerate(simple_varnames)\n            davg = zeros(FT, Nqk * nvertelem)\n            for evk in 1:(Nqk * nvertelem)\n                davg[evk] = simple_avgs[evk][vari]\n            end\n            varvals[varname] = davg\n        end\n\n        ho_varnames = map(\n            prefix_filter,\n            flattenednames(vars_atmos_les_default_ho(atmos, FT)),\n        )\n        for (vari, varname) in enumerate(ho_varnames)\n            davg = zeros(FT, Nqk * nvertelem)\n            for evk in 1:(Nqk * nvertelem)\n                simple_ha =\n                    atmos_les_default_simple_vars(atmos, simple_avgs[evk])\n                avg_rho = simple_ha.avg_rho\n                davg[evk] = ho_avgs[evk][vari] / avg_rho\n            end\n            varvals[varname] = davg\n        end\n\n        if isa(moisture_model(atmos), EquilMoist) ||\n           isa(moisture_model(atmos), NonEquilMoist)\n            varvals[\"cld_frac\"] = cld_frac\n            varvals[\"cld_top\"] = cld_top\n            varvals[\"cld_base\"] = cld_base\n            varvals[\"cld_cover\"] = cld_cover\n            varvals[\"lwp\"] = lwp\n            varvals[\"iwp\"] = iwp\n        end\n        if isa(precipitation_model(atmos), RainModel)\n            varvals[\"rwp\"] = rwp\n        end\n        if isa(precipitation_model(atmos), RainSnowModel)\n            varvals[\"rwp\"] = rwp\n            varvals[\"swp\"] = swp\n        end\n\n        # write output\n        append_data(dgngrp.writer, varvals, currtime)\n    end\n\n    MPI.Barrier(mpicomm)\n    return nothing\nend # function collect\n\nfunction atmos_les_default_fini(dgngrp::DiagnosticsGroup, currtime) end\n"
  },
  {
    "path": "src/Diagnostics/atmos_les_default_perturbations.jl",
    "content": "# AtmosLESDefaultPerturbations\n#\n# Computes perturbations from the horizontal averages for various\n# fields.\n\nusing ..Atmos\nusing ..Mesh.Topologies\nusing ..Mesh.Grids\nusing Thermodynamics\n\n\"\"\"\n    setup_atmos_default_perturbations(\n        ::AtmosLESConfigType,\n        interval::String,\n        out_prefix::String;\n        writer::AbstractWriter,\n        interpol = nothing,\n    )\n\nCreate the \"AtmosLESDefaultPerturbations\" `DiagnosticsGroup` which\ncontains the following diagnostic variables, all of which are\nperturbations from the horizontal average of that variable.\n\n- u_prime\n- v_prime\n- w_prime\n- avg_rho_prime\n- temp_prime\n- pres_prime\n- thd_prime\n- et_prime\n- ei_prime\n- ht_prime\n- hi_prime\n\nWhen an `EquilMoist` moisture model is used, the following additional\ndiagnostic variables are also output:\n\n- qt_prime\n- ql_prime\n- qv_prime\n- thv_prime\n- thl_prime\n\nThe perturbations are output on `x`, `y`, `z` dimensions of an\ninterpolated grid (`interpol` _must_ be specified) and a (unlimited)\n`time` dimension at the specified `interval`.\n\"\"\"\nfunction setup_atmos_default_perturbations(\n    ::AtmosLESConfigType,\n    interval::String,\n    out_prefix::String;\n    writer = NetCDFWriter(),\n    interpol = nothing,\n)\n    # TODO: remove this\n    @assert !isnothing(interpol)\n\n    return DiagnosticsGroup(\n        \"AtmosLESDefaultPerturbations\",\n        Diagnostics.atmos_les_default_perturbations_init,\n        Diagnostics.atmos_les_default_perturbations_fini,\n        Diagnostics.atmos_les_default_perturbations_collect,\n        interval,\n        out_prefix,\n        writer,\n        interpol,\n    )\nend\n\n# Compute sums for density-averaged horizontal averages\n#\n# These are trimmed down versions of `atmos_les_default_simple_sums!()`:\n# - operate on interpolated grid (no `MH` scaling)\n# - skip `w_ht_sgs` and `w_qt_sgs`\nfunction atmos_les_default_perturbations_sums!(\n    atmos::AtmosModel,\n    state,\n    thermo,\n    sums,\n)\n    sums.u += state.ρu[1]\n    sums.v += state.ρu[2]\n    sums.w += state.ρu[3]\n    sums.avg_rho += state.ρ\n    sums.rho += state.ρ * state.ρ\n    sums.temp += thermo.temp * state.ρ\n    sums.pres += thermo.pres * state.ρ\n    sums.thd += thermo.θ_dry * state.ρ\n    sums.et += state.energy.ρe\n    sums.ei += thermo.e_int * state.ρ\n    sums.ht += thermo.h_tot * state.ρ\n    sums.hi += thermo.h_int * state.ρ\n\n    atmos_les_default_perturbations_sums!(\n        moisture_model(atmos),\n        state,\n        thermo,\n        sums,\n    )\n\n    return nothing\nend\nfunction atmos_les_default_perturbations_sums!(\n    ::AbstractMoistureModel,\n    state,\n    thermo,\n    sums,\n)\n    return nothing\nend\nfunction atmos_les_default_perturbations_sums!(\n    moist::EquilMoist,\n    state,\n    thermo,\n    sums,\n)\n    sums.moisture.qt += state.moisture.ρq_tot\n    sums.moisture.ql += thermo.moisture.q_liq * state.ρ\n    sums.moisture.qv += thermo.moisture.q_vap * state.ρ\n    sums.moisture.thv += thermo.moisture.θ_vir * state.ρ\n    sums.moisture.thl += thermo.moisture.θ_liq_ice * state.ρ\n\n    return nothing\nend\n\n# Perturbations from horizontal averages\nfunction vars_atmos_les_default_perturbations(m::AtmosModel, FT)\n    @vars begin\n        u_prime::FT\n        v_prime::FT\n        w_prime::FT\n        avg_rho_prime::FT             # ρ\n        temp_prime::FT\n        pres_prime::FT\n        thd_prime::FT                 # θ_dry\n        et_prime::FT                  # e_tot\n        ei_prime::FT                  # e_int\n        ht_prime::FT\n        hi_prime::FT\n\n        moisture::vars_atmos_les_default_perturbations(moisture_model(m), FT)\n    end\nend\nvars_atmos_les_default_perturbations(::AbstractMoistureModel, FT) = @vars()\nfunction vars_atmos_les_default_perturbations(m::EquilMoist, FT)\n    @vars begin\n        qt_prime::FT                  # q_tot\n        ql_prime::FT                  # q_liq\n        qv_prime::FT                  # q_vap\n        thv_prime::FT                 # θ_vir\n        thl_prime::FT                 # θ_liq\n    end\nend\nnum_atmos_les_default_perturbation_vars(m, FT) =\n    varsize(vars_atmos_les_default_perturbations(m, FT))\natmos_les_default_perturbation_vars(m, array) =\n    Vars{vars_atmos_les_default_perturbations(m, eltype(array))}(array)\n\n# Compute the perturbations from horizontal averages\nfunction atmos_les_default_perturbations!(\n    atmos::AtmosModel,\n    state,\n    thermo,\n    ha,\n    vars,\n)\n    u = state.ρu[1] / state.ρ\n    vars.u_prime = u - ha.u\n    v = state.ρu[2] / state.ρ\n    vars.v_prime = v - ha.v\n    w = state.ρu[3] / state.ρ\n    vars.w_prime = w - ha.w\n    vars.avg_rho_prime = state.ρ - ha.avg_rho\n    vars.temp_prime = thermo.temp - ha.temp\n    vars.pres_prime = thermo.pres - ha.pres\n    vars.thd_prime = thermo.θ_dry - ha.thd\n    et = state.energy.ρe / state.ρ\n    vars.et_prime = et - ha.et\n    vars.ei_prime = thermo.e_int - ha.ei\n    vars.ht_prime = thermo.h_tot - ha.ht\n    vars.hi_prime = thermo.h_int - ha.hi\n\n    atmos_les_default_perturbations!(\n        moisture_model(atmos),\n        atmos,\n        state,\n        thermo,\n        ha,\n        vars,\n    )\n\n    return nothing\nend\nfunction atmos_les_default_perturbations!(\n    ::AbstractMoistureModel,\n    ::AtmosModel,\n    state,\n    thermo,\n    ha,\n    vars,\n)\n    return nothing\nend\nfunction atmos_les_default_perturbations!(\n    m::EquilMoist,\n    atmos::AtmosModel,\n    state,\n    thermo,\n    ha,\n    vars,\n)\n    qt = state.moisture.ρq_tot / state.ρ\n    vars.moisture.qt_prime = qt - ha.moisture.qt\n    vars.moisture.ql_prime = thermo.moisture.q_liq - ha.moisture.ql\n    vars.moisture.qv_prime = thermo.moisture.q_vap - ha.moisture.qv\n    vars.moisture.thv_prime = thermo.moisture.θ_vir - ha.moisture.thv\n    vars.moisture.thl_prime = thermo.moisture.θ_liq_ice - ha.moisture.thl\n\n    return nothing\nend\n\nfunction atmos_les_default_perturbations_init(\n    dgngrp::DiagnosticsGroup,\n    currtime,\n)\n    FT = eltype(Settings.Q)\n    atmos = Settings.dg.balance_law\n    mpicomm = Settings.mpicomm\n    mpirank = MPI.Comm_rank(mpicomm)\n\n    if !(dgngrp.interpol isa InterpolationBrick)\n        @warn \"\"\"\n            Diagnostics $(dgngrp.name): requires `InterpolationBrick`!\n            \"\"\"\n        return nothing\n    end\n\n    if mpirank == 0\n        # get dimensions for the interpolated grid\n        dims = dimensions(dgngrp.interpol)\n\n        # set up the variables we're going to be writing\n        vars = OrderedDict()\n        varnames = map(\n            s -> startswith(s, \"moisture.\") ? s[10:end] : s,\n            flattenednames(vars_atmos_les_default_perturbations(atmos, FT)),\n        )\n        for varname in varnames\n            vars[varname] = (tuple(collect(keys(dims))...), FT, OrderedDict())\n        end\n\n        # create the output file\n        dprefix = @sprintf(\"%s_%s\", dgngrp.out_prefix, dgngrp.name)\n        dfilename = joinpath(Settings.output_dir, dprefix)\n        noov = Settings.no_overwrite\n        init_data(dgngrp.writer, dfilename, noov, dims, vars)\n    end\n\n    return nothing\nend\n\n\"\"\"\n    atmos_les_default_perturbations_collect(dgngrp, currtime)\n\nPerform a global grid traversal to compute various diagnostics.\n\"\"\"\nfunction atmos_les_default_perturbations_collect(\n    dgngrp::DiagnosticsGroup,\n    currtime,\n)\n    interpol = dgngrp.interpol\n    if !(interpol isa InterpolationBrick)\n        @warn \"\"\"\n            Diagnostics $(dgngrp.name): requires `InterpolationBrick`!\n            \"\"\"\n        return nothing\n    end\n\n    mpicomm = Settings.mpicomm\n    dg = Settings.dg\n    Q = Settings.Q\n    mpirank = MPI.Comm_rank(mpicomm)\n    atmos = dg.balance_law\n    grid = dg.grid\n    grid_info = basic_grid_info(dg)\n    topl_info = basic_topology_info(grid.topology)\n    Nqk = grid_info.Nqk\n    Nqh = grid_info.Nqh\n    npoints = prod(grid_info.Nq)\n    nrealelem = topl_info.nrealelem\n    nvertelem = topl_info.nvertelem\n    nhorzelem = topl_info.nhorzrealelem\n\n    # get needed arrays onto the CPU\n    if array_device(Q) isa CPU\n        ArrayType = Array\n        state_data = Q.realdata\n        aux_data = dg.state_auxiliary.realdata\n    else\n        ArrayType = CuArray\n        state_data = Array(Q.realdata)\n        aux_data = Array(dg.state_auxiliary.realdata)\n    end\n    FT = eltype(state_data)\n\n    # Compute thermo variables\n    thermo_array = Array{FT}(undef, npoints, num_thermo(atmos, FT), nrealelem)\n    @traverse_dg_grid grid_info topl_info begin\n        state = extract_state(dg, state_data, ijk, e, Prognostic())\n        aux = extract_state(dg, aux_data, ijk, e, Auxiliary())\n\n        thermo = thermo_vars(atmos, view(thermo_array, ijk, :, e))\n        compute_thermo!(atmos, state, aux, thermo)\n    end\n\n    # Interpolate the state and thermo variables.\n    interpol = dgngrp.interpol\n    istate =\n        ArrayType{FT}(undef, interpol.Npl, number_states(atmos, Prognostic()))\n    interpolate_local!(interpol, Q.realdata, istate)\n\n    ithermo = ArrayType{FT}(undef, interpol.Npl, num_thermo(atmos, FT))\n    interpolate_local!(interpol, ArrayType(thermo_array), ithermo)\n\n    # FIXME: accumulating to rank 0 is not scalable\n    all_state_data = accumulate_interpolated_data(mpicomm, interpol, istate)\n    all_thermo_data = accumulate_interpolated_data(mpicomm, interpol, ithermo)\n\n    if mpirank == 0\n        # get dimensions for the interpolated grid\n        dims = dimensions(interpol)\n\n        nx = length(dims[\"x\"][1])\n        ny = length(dims[\"y\"][1])\n        nz = length(dims[\"z\"][1])\n\n        # collect horizontal sums\n        simple_sums = [\n            zeros(FT, num_atmos_les_default_simple_vars(atmos, FT))\n            for _ in 1:nz\n        ]\n        @traverse_interpolated_grid nx ny nz begin\n            statei = Vars{vars_state(atmos, Prognostic(), FT)}(view(\n                all_state_data,\n                lo,\n                la,\n                le,\n                :,\n            ))\n            thermoi = thermo_vars(atmos, view(all_thermo_data, lo, la, le, :))\n            simple = atmos_les_default_simple_vars(atmos, simple_sums[le])\n            atmos_les_default_perturbations_sums!(\n                atmos,\n                statei,\n                thermoi,\n                simple,\n            )\n        end\n\n        # compute horizontal averages\n        simple_avgs = [\n            zeros(FT, num_atmos_les_default_simple_vars(atmos, FT))\n            for _ in 1:nz\n        ]\n        for le in 1:nz\n            simple_avgs[le] .= simple_sums[le] ./ (nx * ny)\n        end\n\n        # complete density averaging\n        simple_varnames = map(\n            s -> startswith(s, \"moisture.\") ? s[10:end] : s,\n            flattenednames(vars_atmos_les_default_simple(atmos, FT)),\n        )\n        for vari in 1:length(simple_varnames)\n            for le in 1:nz\n                ha = atmos_les_default_simple_vars(atmos, simple_avgs[le])\n                avg_rho = ha.avg_rho\n                if simple_varnames[vari] != \"avg_rho\"\n                    simple_avgs[le][vari] /= avg_rho\n                end\n            end\n        end\n\n        # now compute the perturbations from the horizontal averages\n        perturbations_array = Array{FT}(\n            undef,\n            nx,\n            ny,\n            nz,\n            num_atmos_les_default_perturbation_vars(atmos, FT),\n        )\n        @traverse_interpolated_grid nx ny nz begin\n            statei = Vars{vars_state(atmos, Prognostic(), FT)}(view(\n                all_state_data,\n                lo,\n                la,\n                le,\n                :,\n            ))\n            thermoi = thermo_vars(atmos, view(all_thermo_data, lo, la, le, :))\n            ha = atmos_les_default_simple_vars(atmos, simple_avgs[le])\n            perturbations = atmos_les_default_perturbation_vars(\n                atmos,\n                view(perturbations_array, lo, la, le, :),\n            )\n            atmos_les_default_perturbations!(\n                atmos,\n                statei,\n                thermoi,\n                ha,\n                perturbations,\n            )\n        end\n\n        # prepare and write out the perturbations\n        varvals = OrderedDict()\n        varnames = map(\n            s -> startswith(s, \"moisture.\") ? s[10:end] : s,\n            flattenednames(vars_atmos_les_default_perturbations(atmos, FT)),\n        )\n        for (vari, varname) in enumerate(varnames)\n            varvals[varname] = perturbations_array[:, :, :, vari]\n        end\n\n        # write output\n        append_data(dgngrp.writer, varvals, currtime)\n    end\n\n    MPI.Barrier(mpicomm)\n    return nothing\nend # function collect\n\nfunction atmos_les_default_perturbations_fini(\n    dgngrp::DiagnosticsGroup,\n    currtime,\n) end\n"
  },
  {
    "path": "src/Diagnostics/atmos_les_spectra.jl",
    "content": "# Spectrum calculator for AtmosLES\n\nstruct AtmosLESSpectraDiagnosticsParams <: DiagnosticsGroupParams\n    nor::Float64\nend\n\n\"\"\"\n    setup_atmos_spectra_diagnostics(\n        ::AtmosLESConfigType,\n        interval::String,\n        out_prefix::String;\n        writer = NetCDFWriter(),\n        interpol = nothing,\n        nor = Inf,\n    )\n\nCreate the \"AtmosLESSpectra\" `DiagnosticsGroup` which contains the following\ndiagnostic variable:\n\n- spectrum: power spectrum from 3D velocity fields\n\nThis variable is output with the `k` dimension (wave number) on an interpolated\ngrid (`interpol` _must_ be specified) as well as a (unlimited) `time` dimension\nat the specified `interval`.\n\"\"\"\nfunction setup_atmos_spectra_diagnostics(\n    ::AtmosLESConfigType,\n    interval::String,\n    out_prefix::String,\n    nor::Float64;\n    writer = NetCDFWriter(),\n    interpol = nothing,\n)\n    @assert !isnothing(interpol)\n\n    return DiagnosticsGroup(\n        \"AtmosLESSpectra\",\n        Diagnostics.atmos_les_spectra_init,\n        Diagnostics.atmos_les_spectra_fini,\n        Diagnostics.atmos_les_spectra_collect,\n        interval,\n        out_prefix,\n        writer,\n        interpol,\n        AtmosLESSpectraDiagnosticsParams(nor),\n    )\nend\n\nfunction get_spectrum(mpicomm, mpirank, Q, bl, interpol, nor)\n    FT = eltype(Q)\n    istate = similar(Q.data, interpol.Npl, number_states(bl, Prognostic(), FT))\n    interpolate_local!(interpol, Q.data, istate)\n    all_state_data = accumulate_interpolated_data(mpicomm, interpol, istate)\n    if mpirank == 0\n        u = all_state_data[:, :, :, 2] ./ all_state_data[:, :, :, 1]\n        v = all_state_data[:, :, :, 3] ./ all_state_data[:, :, :, 1]\n        w = all_state_data[:, :, :, 4] ./ all_state_data[:, :, :, 1]\n        x1 = Array(interpol.x1g)\n        d = length(x1)\n        s, k = power_spectrum_3d(\n            AtmosLESConfigType(),\n            u,\n            v,\n            w,\n            x1[d] - x1[1],\n            d,\n            nor,\n        )\n        return s, k\n    end\n    return nothing, nothing\nend\n\nfunction atmos_les_spectra_init(dgngrp, currtime)\n    Q = Settings.Q\n    bl = Settings.dg.balance_law\n    mpicomm = Settings.mpicomm\n    mpirank = MPI.Comm_rank(mpicomm)\n    FT = eltype(Q)\n    interpol = dgngrp.interpol\n    nor = dgngrp.params.nor\n\n    spectrum, wavenumber = get_spectrum(mpicomm, mpirank, Q, bl, interpol, nor)\n    if mpirank == 0\n        dims = OrderedDict(\"k\" => (wavenumber, Dict()))\n        vars = OrderedDict(\"spectrum\" => ((\"k\",), FT, Dict()))\n\n        dprefix = @sprintf(\"%s_%s\", dgngrp.out_prefix, dgngrp.name)\n        dfilename = joinpath(Settings.output_dir, dprefix)\n        noov = Settings.no_overwrite\n        init_data(dgngrp.writer, dfilename, noov, dims, vars)\n    end\n\n    return nothing\nend\n\nfunction atmos_les_spectra_collect(dgngrp, currtime)\n    Q = Settings.Q\n    bl = Settings.dg.balance_law\n    mpicomm = Settings.mpicomm\n    mpirank = MPI.Comm_rank(mpicomm)\n    FT = eltype(Q)\n    interpol = dgngrp.interpol\n    nor = dgngrp.params.nor\n\n    spectrum, _ = get_spectrum(mpicomm, mpirank, Q, bl, interpol, nor)\n\n    if mpirank == 0\n        varvals = OrderedDict(\"spectrum\" => spectrum)\n        append_data(dgngrp.writer, varvals, currtime)\n    end\n\n    MPI.Barrier(mpicomm)\n    return nothing\nend\n\nfunction atmos_les_spectra_fini(dgngrp, currtime) end\n"
  },
  {
    "path": "src/Diagnostics/atmos_mass_energy_loss.jl",
    "content": "# AtmosMassEnergyLoss\n#\n# Dump mass and energy loss\n\nusing ..Atmos\nusing ..BalanceLaws\nusing ..Mesh.Topologies\nusing ..Mesh.Grids\nusing ..MPIStateArrays\n\n\"\"\"\n    setup_atmos_mass_energy_loss(\n        ::ClimateMachineConfigType,\n        interval::String,\n        out_prefix::String,\n        writer = NetCDFWriter(),\n        interpol = nothing,\n    )\n\nCreate and return a `DiagnosticsGroup` containing the\n\"AtmosMassEnergyLoss\" diagnostics for Atmos LES and GCM\nconfigurations. All the diagnostics in the group will run at the\nspecified `interval`, be interpolated to the specified boundaries\nand resolution, and written to files prefixed by `out_prefix`\nusing `writer`.\n\"\"\"\nfunction setup_atmos_mass_energy_loss(\n    ::ClimateMachineConfigType,\n    interval::String,\n    out_prefix::String,\n    writer = NetCDFWriter(),\n    interpol = nothing,\n)\n    @assert isnothing(interpol)\n\n    return DiagnosticsGroup(\n        \"AtmosMassEnergyLoss\",\n        Diagnostics.atmos_mass_energy_loss_init,\n        Diagnostics.atmos_mass_energy_loss_fini,\n        Diagnostics.atmos_mass_energy_loss_collect,\n        interval,\n        out_prefix,\n        writer,\n        interpol,\n    )\nend\n\n# store initial mass and energy\nmutable struct AtmosMassEnergyLoss\n    Σρ₀::Union{Nothing, AbstractFloat}\n    Σρe₀::Union{Nothing, AbstractFloat}\n\n    AtmosMassEnergyLoss() = new(nothing, nothing)\nend\nconst AtmosMassEnergyLoss₀ = AtmosMassEnergyLoss()\n\nfunction atmos_mass_energy_loss_init(dgngrp, currtime)\n    mpicomm = Settings.mpicomm\n    mpirank = MPI.Comm_rank(mpicomm)\n    dg = Settings.dg\n    bl = dg.balance_law\n    Q = Settings.Q\n    FT = eltype(Q)\n\n    energy_model(bl) isa TotalEnergyModel ||\n        error(\"Only TotalEnergyModel supported\")\n    ρ_idx = varsindices(vars_state(bl, Prognostic(), FT), \"ρ\")\n    ρe_idx = varsindices(vars_state(bl, Prognostic(), FT), :(energy.ρe))\n    Σρ₀ = weightedsum(Q, ρ_idx)\n    Σρe₀ = weightedsum(Q, ρe_idx)\n\n    if mpirank == 0\n        AtmosMassEnergyLoss₀.Σρ₀ = Σρ₀\n        AtmosMassEnergyLoss₀.Σρe₀ = Σρe₀\n\n        # outputs are scalars\n        dims = OrderedDict()\n\n        # set up the variables we're going to be writing\n        vars = OrderedDict(\n            \"mass_loss\" => ((), FT, Variables[\"mass_loss\"].attrib),\n            \"energy_loss\" => ((), FT, Variables[\"energy_loss\"].attrib),\n        )\n\n        # create the output file\n        dprefix = @sprintf(\"%s_%s\", dgngrp.out_prefix, dgngrp.name)\n        dfilename = joinpath(Settings.output_dir, dprefix)\n        noov = Settings.no_overwrite\n        init_data(dgngrp.writer, dfilename, noov, dims, vars)\n    end\n\n    return nothing\nend\n\nfunction atmos_mass_energy_loss_collect(dgngrp, currtime)\n    mpicomm = Settings.mpicomm\n    mpirank = MPI.Comm_rank(mpicomm)\n    dg = Settings.dg\n    bl = dg.balance_law\n    Q = Settings.Q\n    FT = eltype(Q)\n\n    energy_model(bl) isa TotalEnergyModel ||\n        error(\"Only TotalEnergyModel supported\")\n    ρ_idx = varsindices(vars_state(bl, Prognostic(), FT), \"ρ\")\n    ρe_idx = varsindices(vars_state(bl, Prognostic(), FT), :(energy.ρe))\n    Σρ = weightedsum(Q, ρ_idx)\n    Σρe = weightedsum(Q, ρe_idx)\n\n    if mpirank == 0\n        δρ = (Σρ - AtmosMassEnergyLoss₀.Σρ₀) / AtmosMassEnergyLoss₀.Σρ₀\n        δρe = (Σρe - AtmosMassEnergyLoss₀.Σρe₀) / AtmosMassEnergyLoss₀.Σρe₀\n\n        varvals = OrderedDict(\"mass_loss\" => δρ, \"energy_loss\" => δρe)\n\n        # write output\n        append_data(dgngrp.writer, varvals, currtime)\n    end\n\nend\n\nfunction atmos_mass_energy_loss_fini(dgngrp, currtime) end\n"
  },
  {
    "path": "src/Diagnostics/atmos_refstate_perturbations.jl",
    "content": "# AtmosRefStatePerturbations\n#\n# Computes perturbations from the reference state and outputs them\n# on the specified interpolated grid.\n\nimport CUDA\nusing ..Atmos\nusing ..Mesh.Topologies\nusing ..Mesh.Grids\nusing Thermodynamics\n\n\"\"\"\n    setup_atmos_refstate_perturbations(\n        ::ClimateMachineConfigType,\n        interval::String,\n        out_prefix::String;\n        writer = NetCDFWriter(),\n        interpol = nothing,\n    )\n\nCreate the \"AtmosRefStatePerturbations\" `DiagnosticsGroup` which contains\nperturbations from the (hydrostatic) reference state for:\n\n- rho: air density\n- pres: air pressure\n- temp: air temperature\n- et: total specific energy\n- qt: mass fraction of total water in air (NaN when not an `EquilMoist` moisture model)\n\nThe perturbations are computed from variables on an interpolated grid\n(`interpol` _must_ be specified) and output on `x`, `y`, `z` or `lat`,\n`long`, `level` dimensions as well as a (unlimited) `time` dimension\nat the specified `interval`.\n\"\"\"\nfunction setup_atmos_refstate_perturbations(\n    ::ClimateMachineConfigType,\n    interval::String,\n    out_prefix::String;\n    writer = NetCDFWriter(),\n    interpol = nothing,\n)\n    # TODO: remove this\n    @assert !isnothing(interpol)\n\n    return DiagnosticsGroup(\n        \"AtmosRefStatePerturbations\",\n        Diagnostics.atmos_refstate_perturbations_init,\n        Diagnostics.atmos_refstate_perturbations_fini,\n        Diagnostics.atmos_refstate_perturbations_collect,\n        interval,\n        out_prefix,\n        writer,\n        interpol,\n    )\nend\n\nfunction vars_atmos_refstate_perturbations(m::AtmosModel, FT)\n    @vars begin\n        ref_state::vars_atmos_refstate_perturbations(reference_state(m), FT)\n    end\nend\nvars_atmos_refstate_perturbations(::ReferenceState, FT) = @vars()\nfunction vars_atmos_refstate_perturbations(rs::HydrostaticState, FT)\n    @vars begin\n        rho::FT\n        pres::FT\n        temp::FT\n        et::FT\n        qt::FT\n    end\nend\nnum_atmos_refstate_perturbation_vars(m, FT) =\n    varsize(vars_atmos_refstate_perturbations(m, FT))\natmos_refstate_perturbation_vars(m, array) =\n    Vars{vars_atmos_refstate_perturbations(m, eltype(array))}(array)\n\nfunction atmos_refstate_perturbations!(\n    atmos::AtmosModel,\n    state,\n    aux,\n    thermo,\n    vars,\n)\n    atmos_refstate_perturbations!(\n        reference_state(atmos),\n        atmos,\n        state,\n        aux,\n        thermo,\n        vars,\n    )\n    return nothing\nend\nfunction atmos_refstate_perturbations!(\n    ::ReferenceState,\n    ::AtmosModel,\n    state,\n    aux,\n    thermo,\n    vars,\n)\n    return nothing\nend\nfunction atmos_refstate_perturbations!(\n    rs::HydrostaticState,\n    atmos::AtmosModel,\n    state,\n    aux,\n    thermo,\n    vars,\n)\n    vars.ref_state.rho = state.ρ - aux.ref_state.ρ\n    vars.ref_state.pres = thermo.pres - aux.ref_state.p\n    vars.ref_state.temp = thermo.temp - aux.ref_state.T\n    vars.ref_state.et =\n        (state.energy.ρe / state.ρ) - (aux.ref_state.ρe / aux.ref_state.ρ)\n    # FIXME properly\n    if moisture_model(atmos) isa EquilMoist\n        vars.ref_state.qt =\n            (thermo.moisture.ρq_tot / state.ρ) -\n            (aux.ref_state.ρq_tot / aux.ref_state.ρ)\n    else\n        vars.ref_state.qt = NaN\n    end\n\n    return nothing\nend\n\nfunction atmos_refstate_perturbations_init(dgngrp::DiagnosticsGroup, currtime)\n    FT = eltype(Settings.Q)\n    atmos = Settings.dg.balance_law\n    mpicomm = Settings.mpicomm\n    mpirank = MPI.Comm_rank(mpicomm)\n\n    if mpirank == 0\n        # get dimensions for the interpolated grid\n        dims = dimensions(dgngrp.interpol)\n\n        # adjust the level dimension for `planet_radius` on 'CubedSphere's\n        if dgngrp.interpol isa InterpolationCubedSphere\n            level_val = dims[\"level\"]\n            dims[\"level\"] = (\n                level_val[1] .- FT(planet_radius(Settings.param_set)),\n                level_val[2],\n            )\n        end\n\n        # set up the variables we're going to be writing\n        vars = OrderedDict()\n        varnames = map(\n            s -> startswith(s, \"ref_state.\") ? s[11:end] : s,\n            flattenednames(vars_atmos_refstate_perturbations(atmos, FT)),\n        )\n        for varname in varnames\n            var = Variables[varname]\n            vars[varname] = (tuple(collect(keys(dims))...), FT, var.attrib)\n        end\n\n        # create the output file\n        dprefix = @sprintf(\"%s_%s\", dgngrp.out_prefix, dgngrp.name)\n        dfilename = joinpath(Settings.output_dir, dprefix)\n        noov = Settings.no_overwrite\n        init_data(dgngrp.writer, dfilename, noov, dims, vars)\n    end\n\n    return nothing\nend\n\n\"\"\"\n    atmos_refstate_perturbations_collect(dgngrp, currtime)\n\nPerform a global grid traversal to compute various diagnostics.\n\"\"\"\nfunction atmos_refstate_perturbations_collect(\n    dgngrp::DiagnosticsGroup,\n    currtime,\n)\n    mpicomm = Settings.mpicomm\n    dg = Settings.dg\n    Q = Settings.Q\n    mpirank = MPI.Comm_rank(mpicomm)\n    atmos = dg.balance_law\n    if !isa(reference_state(atmos), HydrostaticState)\n        @warn \"\"\"\n            Diagnostics $(dgngrp.name): has useful output only for `HydrostaticState`\n            \"\"\"\n    end\n    grid = dg.grid\n    grid_info = basic_grid_info(dg)\n    topl_info = basic_topology_info(grid.topology)\n    Nqk = grid_info.Nqk\n    Nqh = grid_info.Nqh\n    npoints = prod(grid_info.Nq)\n    nrealelem = topl_info.nrealelem\n    nvertelem = topl_info.nvertelem\n    nhorzelem = topl_info.nhorzrealelem\n\n    # get needed arrays onto the CPU\n    if array_device(Q) isa CPU\n        ArrayType = Array\n        state_data = Q.realdata\n        aux_data = dg.state_auxiliary.realdata\n    else\n        ArrayType = CUDA.CuArray\n        state_data = Array(Q.realdata)\n        aux_data = Array(dg.state_auxiliary.realdata)\n    end\n    FT = eltype(state_data)\n\n    # Compute thermo variables\n    thermo_array = Array{FT}(undef, npoints, num_thermo(atmos, FT), nrealelem)\n    @traverse_dg_grid grid_info topl_info begin\n        state = extract_state(dg, state_data, ijk, e, Prognostic())\n        aux = extract_state(dg, aux_data, ijk, e, Auxiliary())\n\n        thermo = thermo_vars(atmos, view(thermo_array, ijk, :, e))\n        compute_thermo!(atmos, state, aux, thermo)\n    end\n\n    # Interpolate the state and thermo variables.\n    interpol = dgngrp.interpol\n    istate =\n        ArrayType{FT}(undef, interpol.Npl, number_states(atmos, Prognostic()))\n    interpolate_local!(interpol, Q.realdata, istate)\n\n    if interpol isa InterpolationCubedSphere\n        # TODO: get indices here without hard-coding them\n        _ρu, _ρv, _ρw = 2, 3, 4\n        project_cubed_sphere!(interpol, istate, (_ρu, _ρv, _ρw))\n    end\n\n    iaux = ArrayType{FT}(undef, interpol.Npl, number_states(atmos, Auxiliary()))\n    interpolate_local!(interpol, dg.state_auxiliary.realdata, iaux)\n\n    ithermo = ArrayType{FT}(undef, interpol.Npl, num_thermo(atmos, FT))\n    interpolate_local!(interpol, ArrayType(thermo_array), ithermo)\n\n    # FIXME: accumulating to rank 0 is not scalable\n    all_state_data = accumulate_interpolated_data(mpicomm, interpol, istate)\n    all_aux_data = accumulate_interpolated_data(mpicomm, interpol, iaux)\n    all_thermo_data = accumulate_interpolated_data(mpicomm, interpol, ithermo)\n\n    if mpirank == 0\n        # get dimensions for the interpolated grid\n        dims = dimensions(dgngrp.interpol)\n\n        # set up the array for the diagnostic variables based on the interpolated grid\n        nlong = length(dims[\"long\"][1])\n        nlat = length(dims[\"lat\"][1])\n        nlevel = length(dims[\"level\"][1])\n\n        perturbations_array = Array{FT}(\n            undef,\n            nlong,\n            nlat,\n            nlevel,\n            num_atmos_refstate_perturbation_vars(atmos, FT),\n        )\n\n        @traverse_interpolated_grid nlong nlat nlevel begin\n            statei = Vars{vars_state(atmos, Prognostic(), FT)}(view(\n                all_state_data,\n                lo,\n                la,\n                le,\n                :,\n            ))\n            auxi = Vars{vars_state(atmos, Auxiliary(), FT)}(view(\n                all_aux_data,\n                lo,\n                la,\n                le,\n                :,\n            ))\n            thermoi = thermo_vars(atmos, view(all_thermo_data, lo, la, le, :))\n\n            perturbations = atmos_refstate_perturbation_vars(\n                atmos,\n                view(perturbations_array, lo, la, le, :),\n            )\n            atmos_refstate_perturbations!(\n                atmos,\n                statei,\n                auxi,\n                thermoi,\n                perturbations,\n            )\n        end\n\n        varvals = OrderedDict()\n        varnames = map(\n            s -> startswith(s, \"ref_state.\") ? s[11:end] : s,\n            flattenednames(vars_atmos_refstate_perturbations(atmos, FT)),\n        )\n        for (vari, varname) in enumerate(varnames)\n            varvals[varname] = perturbations_array[:, :, :, vari]\n        end\n\n        # write output\n        append_data(dgngrp.writer, varvals, currtime)\n    end\n\n    MPI.Barrier(mpicomm)\n    return nothing\nend # function collect\n\nfunction atmos_refstate_perturbations_fini(dgngrp::DiagnosticsGroup, currtime) end\n"
  },
  {
    "path": "src/Diagnostics/atmos_turbulence_stats.jl",
    "content": "# AtmosTurbulenceStats\n#\n# Computes average kinetic energy and dissipation.\n\nusing ..Atmos\nusing ..Mesh.Topologies\nusing ..Mesh.Grids\n\nstruct TurbulenceStatsParams <: DiagnosticsGroupParams\n    nor::Float64\n    iter::Float64\nend\n\n\"\"\"\n    setup_atmos_turbulence_stats(\n        ::ClimateMachineConfigType,\n        interval::String,\n        out_prefix::String,\n        nor::Float64,\n        iter::Float64;\n        writer = NetCDFWriter(),\n        interpol = nothing,\n    )\n\nCreate the \"AtmosTurbulenceStats\" `DiagnosticsGroup` which contains the\nfollowing diagnostic variables:\n\n- E_k: volumetrically-averaged dimensionless kinetic energy\n- dE: volumetrically-averaged kinetic energy dissipation\n\nThese are scalar variables computed on the DG grid (`interpol` may _not_\nbe specified), output on the (unlimited) `time` dimension at the specified\n`interval`.\n\"\"\"\nfunction setup_atmos_turbulence_stats(\n    ::ClimateMachineConfigType,\n    interval::String,\n    out_prefix::String,\n    nor::Float64,\n    iter::Float64;\n    writer = NetCDFWriter(),\n    interpol = nothing,\n)\n    @assert isnothing(interpol)\n\n    return DiagnosticsGroup(\n        \"AtmosTurbulenceStats\",\n        Diagnostics.atmos_turbulence_stats_init,\n        Diagnostics.atmos_turbulence_stats_fini,\n        Diagnostics.atmos_turbulence_stats_collect,\n        interval,\n        out_prefix,\n        writer,\n        interpol,\n        TurbulenceStatsParams(nor, iter),\n    )\nend\n\n# store average kinetic energy and dissipation\nBase.@kwdef mutable struct AtmosTurbulenceStats{FT}\n    E_k::FT = 0.0\nend\nconst AtmosTurbulenceStatsCollected = AtmosTurbulenceStats()\n\nfunction atmos_turbulence_stats_init(dgngrp, currtime)\n    FT = eltype(Settings.Q)\n    mpicomm = Settings.mpicomm\n    mpirank = MPI.Comm_rank(mpicomm)\n\n    if mpirank == 0\n        # outputs are scalars\n        dims = OrderedDict()\n\n        # set up the variables we're going to be writing\n        vars = OrderedDict(\n            \"E_k\" => ((), FT, Variables[\"E_k\"].attrib),\n            \"dE\" => ((), FT, Variables[\"dE\"].attrib),\n        )\n\n        # create the output file\n        dprefix = @sprintf(\"%s_%s\", dgngrp.out_prefix, dgngrp.name)\n        dfilename = joinpath(Settings.output_dir, dprefix)\n        noov = Settings.no_overwrite\n        init_data(dgngrp.writer, dfilename, noov, dims, vars)\n    end\n\n    return nothing\nend\n\nfunction average_kinetic_energy_and_dissipation(\n    Q,\n    vgeo,\n    E_0,\n    nor,\n    iter,\n    mpicomm,\n)\n    u₀ = Q.ρu ./ Q.ρ\n    u_0 = u₀[:, 1, :] ./ nor\n    v_0 = u₀[:, 2, :] ./ nor\n    w_0 = u₀[:, 3, :] ./ nor\n\n    M = vgeo[:, Grids._M, 1:size(u_0, 2)]\n    SM = sum(M)\n\n    E_k = 0.5 * sum((u_0 .^ 2 .+ v_0 .^ 2 .+ w_0 .^ 2) .* M) / SM\n    E_k = MPI.Reduce(E_k, +, 0, mpicomm)\n\n    mpirank = MPI.Comm_rank(mpicomm)\n    nranks = MPI.Comm_size(mpicomm)\n    if mpirank == 0\n        E_k /= nranks\n        dE = -(E_k - E_0) / iter\n        return (E_k, dE)\n    end\n\n    return (0.0, 0.0)\nend\n\nfunction atmos_turbulence_stats_collect(dgngrp, currtime)\n    mpicomm = Settings.mpicomm\n    dg = Settings.dg\n    Q = Settings.Q\n    mpirank = MPI.Comm_rank(mpicomm)\n    nranks = MPI.Comm_size(mpicomm)\n\n    E_k, dE = average_kinetic_energy_and_dissipation(\n        Q,\n        dg.grid.vgeo,\n        AtmosTurbulenceStatsCollected.E_k,\n        dgngrp.params.nor,\n        dgngrp.params.iter,\n        mpicomm,\n    )\n\n    if mpirank == 0\n        AtmosTurbulenceStatsCollected.E_k = E_k\n\n        varvals = OrderedDict(\"E_k\" => E_k, \"dE\" => dE)\n\n        # write output\n        append_data(dgngrp.writer, varvals, currtime)\n    end\n\nend\n\nfunction atmos_turbulence_stats_fini(dgngrp, currtime) end\n"
  },
  {
    "path": "src/Diagnostics/diagnostic_fields.jl",
    "content": "using DocStringExtensions\nusing KernelAbstractions\n\nusing ..Mesh.Geometry\nusing ..VariableTemplates\n\nimport ..Mesh.Grids:\n    _ξ1x1, _ξ2x1, _ξ3x1, _ξ1x2, _ξ2x2, _ξ3x2, _ξ1x3, _ξ2x3, _ξ3x3\nimport ..MPIStateArrays: array_device\n\n\"\"\"\n    VectorGradients{\n        FT <: AbstractFloat,\n        FTA2D <: AbstractArray{FT, 2},\n        FTA3D <: AbstractArray{FT, 3},\n    }\n\nThis data structure stores the spatial gradients of a velocity field.\n\n# Fields\n\n$(DocStringExtensions.FIELDS)\n\n# Usage\n\n    VectorGradients(data)\n\n# Arguments for the inner constructor\n - `data`: 3-dimensional device array containing the spatial gradients\n   (the second dimension must be 9)\n\"\"\"\nstruct VectorGradients{\n    FT <: AbstractFloat,\n    FTA2D <: AbstractArray{FT, 2},\n    FTA3D <: AbstractArray{FT, 3},\n}\n    \"Device array storing the spatial gradient data\"\n    data::FTA3D\n    \"View of ∂u₁/∂x₁\"\n    ∂₁u₁::FTA2D\n    \"View of ∂u₁/∂x₂\"\n    ∂₂u₁::FTA2D\n    \"View of ∂u₁/∂x₃\"\n    ∂₃u₁::FTA2D\n    \"View of ∂u₂/∂x₁\"\n    ∂₁u₂::FTA2D\n    \"View of ∂u₂/∂x₂\"\n    ∂₂u₂::FTA2D\n    \"View of ∂u₂/∂x₃\"\n    ∂₃u₂::FTA2D\n    \"View of ∂u₃/∂x₁\"\n    ∂₁u₃::FTA2D\n    \"View of ∂u₃/∂x₂\"\n    ∂₂u₃::FTA2D\n    \"View of ∂u₃/∂x₃\"\n    ∂₃u₃::FTA2D\n\n    function VectorGradients(\n        data::AbstractArray{FT, 3},\n    ) where {FT <: AbstractFloat}\n        ∂₁u₁, ∂₂u₁, ∂₃u₁ =\n            view(data, :, 1, :), view(data, :, 2, :), view(data, :, 3, :)\n        ∂₁u₂, ∂₂u₂, ∂₃u₂ =\n            view(data, :, 4, :), view(data, :, 5, :), view(data, :, 6, :)\n        ∂₁u₃, ∂₂u₃, ∂₃u₃ =\n            view(data, :, 7, :), view(data, :, 8, :), view(data, :, 9, :)\n\n        return new{FT, typeof(∂₁u₁), typeof(data)}(\n            data,\n            ∂₁u₁,\n            ∂₂u₁,\n            ∂₃u₁,\n            ∂₁u₂,\n            ∂₂u₂,\n            ∂₃u₂,\n            ∂₁u₃,\n            ∂₂u₃,\n            ∂₃u₃,\n        )\n    end\nend\n\n\"\"\"\n    VectorGradients(dg::SpaceDiscretization, Q::MPIStateArray)\n\nThis constructor computes the spatial gradients of the velocity field.\n\n# Arguments\n - `dg`: SpaceDiscretization\n - `Q`: MPIStateArray containing the prognostic state variables\n\"\"\"\nfunction VectorGradients(dg::SpaceDiscretization, Q::MPIStateArray)\n    bl = dg.balance_law\n    FT = eltype(dg.grid)\n    Nq = polynomialorders(dg.grid) .+ 1 #N + 1\n    Nqmax = maximum(Nq)\n    npoints = prod(Nq)\n    nrealelem = length(dg.grid.topology.realelems)\n\n    g = similar(Q.realdata, npoints, nrealelem, 3, 3)\n    data = similar(Q.realdata, npoints, 9, nrealelem)\n\n    ind = varsindices(vars_state(bl, Prognostic(), FT), (\"ρ\", \"ρu\"))\n    _ρ, _ρu, _ρv, _ρw = ind[1], ind[2], ind[3], ind[4]\n    device = array_device(Q)\n    comp_stream = Event(device)\n    workgroup = (Nqmax, Nqmax)\n    ndrange = (nrealelem * Nqmax, Nqmax)\n    comp_stream = vector_gradients_kernel!(device, workgroup)(\n        Q.realdata,\n        dg.grid.D,\n        dg.grid.vgeo,\n        g,\n        data,\n        _ρ,\n        _ρu,\n        _ρv,\n        _ρw,\n        Val(Nq),\n        Val(Nqmax),\n        ndrange = ndrange,\n        dependencies = (comp_stream,),\n    )\n    wait(comp_stream)\n\n    return VectorGradients(data)\nend\n\n@kernel function vector_gradients_kernel!(\n    sv::AbstractArray{FT},\n    D::Tuple{AbstractArray{FT, 2}, AbstractArray{FT, 2}, AbstractArray{FT, 2}},\n    vgeo::AbstractArray{FT},\n    g::AbstractArray{FT, 4},\n    vgrad_data::AbstractArray{FT, 3},\n    _ρ::Int,\n    _ρu::Int,\n    _ρv::Int,\n    _ρw::Int,\n    ::Val{qm},\n    ::Val{qmax},\n) where {qm, qmax, FT <: AbstractFloat}\n\n    e = @index(Group, Linear)\n    i, j = @index(Local, NTuple)\n\n    s_D = @localmem FT (qmax, qmax)\n    s_U = @localmem FT (qmax, qmax)\n    s_V = @localmem FT (qmax, qmax)\n    s_W = @localmem FT (qmax, qmax)\n\n    # computing derivatives with respect to ξ1\n    if i ≤ qm[1] && j ≤ qm[1]\n        s_D[i, j] = D[1][i, j]\n    end\n    @synchronize\n\n    for t in 1:qm[3], s in 1:qm[2]\n        if i ≤ qm[1] && j ≤ qm[1]\n            ijk = j + ((s - 1) + (t - 1) * qm[2]) * qm[1]\n            s_U[i, j] = s_D[i, j] * (sv[ijk, _ρu, e] / sv[ijk, _ρ, e])\n            s_V[i, j] = s_D[i, j] * (sv[ijk, _ρv, e] / sv[ijk, _ρ, e])\n            s_W[i, j] = s_D[i, j] * (sv[ijk, _ρw, e] / sv[ijk, _ρ, e])\n        end\n        @synchronize\n        if j == 1\n            for r in 2:qm[1]\n                s_U[i, 1] += s_U[i, r]\n                s_V[i, 1] += s_V[i, r]\n                s_W[i, 1] += s_W[i, r]\n            end\n        end\n        @synchronize\n        if j == 1 && i ≤ qm[1]\n            g[i + ((s - 1) + (t - 1) * qm[2]) * qm[1], e, 1, 1] = s_U[i, 1] # ∂u₁∂ξ₁\n            g[i + ((s - 1) + (t - 1) * qm[2]) * qm[1], e, 2, 1] = s_V[i, 1] # ∂u₂∂ξ₁\n            g[i + ((s - 1) + (t - 1) * qm[2]) * qm[1], e, 3, 1] = s_W[i, 1] # ∂u₃∂ξ₁\n        end\n    end\n    @synchronize\n    # computing derivatives with respect to ξ2\n    if i ≤ qm[2] && j ≤ qm[2]\n        s_D[i, j] = D[2][i, j]\n    end\n    @synchronize\n\n    for t in 1:qm[3], r in 1:qm[1]\n        if i ≤ qm[2] && j ≤ qm[2]\n            ijk = r + ((j - 1) + (t - 1) * qm[2]) * qm[1]\n            s_U[i, j] = s_D[i, j] * (sv[ijk, _ρu, e] / sv[ijk, _ρ, e])\n            s_V[i, j] = s_D[i, j] * (sv[ijk, _ρv, e] / sv[ijk, _ρ, e])\n            s_W[i, j] = s_D[i, j] * (sv[ijk, _ρw, e] / sv[ijk, _ρ, e])\n        end\n        @synchronize\n        if j == 1\n            for s in 2:qm[2]\n                s_U[i, 1] += s_U[i, s]\n                s_V[i, 1] += s_V[i, s]\n                s_W[i, 1] += s_W[i, s]\n            end\n        end\n        @synchronize\n        if j == 1 && i ≤ qm[2]\n            g[r + ((i - 1) + (t - 1) * qm[2]) * qm[1], e, 1, 2] = s_U[i, 1] # ∂u₁∂ξ₂\n            g[r + ((i - 1) + (t - 1) * qm[2]) * qm[1], e, 2, 2] = s_V[i, 1] # ∂u₂∂ξ₂\n            g[r + ((i - 1) + (t - 1) * qm[2]) * qm[1], e, 3, 2] = s_W[i, 1] # ∂u₃∂ξ₂\n        end\n    end\n    @synchronize\n    # computing derivatives with respect to ξ3\n    if i ≤ qm[3] && j ≤ qm[3]\n        s_D[i, j] = D[3][i, j]\n    end\n    @synchronize\n\n\n    for s in 1:qm[2], r in 1:qm[1]\n        if i ≤ qm[3] && j ≤ qm[3]\n            ijk = r + ((s - 1) + (j - 1) * qm[2]) * qm[1]\n            s_U[i, j] = s_D[i, j] * (sv[ijk, _ρu, e] / sv[ijk, _ρ, e])\n            s_V[i, j] = s_D[i, j] * (sv[ijk, _ρv, e] / sv[ijk, _ρ, e])\n            s_W[i, j] = s_D[i, j] * (sv[ijk, _ρw, e] / sv[ijk, _ρ, e])\n        end\n        @synchronize\n        if j == 1\n            for t in 2:qm[3]\n                s_U[i, 1] += s_U[i, t]\n                s_V[i, 1] += s_V[i, t]\n                s_W[i, 1] += s_W[i, t]\n            end\n        end\n        @synchronize\n        if j == 1 && i ≤ qm[3]\n            g[r + ((s - 1) + (i - 1) * qm[2]) * qm[1], e, 1, 3] = s_U[i, 1] # ∂u₁∂ξ₃\n            g[r + ((s - 1) + (i - 1) * qm[2]) * qm[1], e, 2, 3] = s_V[i, 1] # ∂u₂∂ξ₃\n            g[r + ((s - 1) + (i - 1) * qm[2]) * qm[1], e, 3, 3] = s_W[i, 1] # ∂u₃∂ξ₃\n        end\n    end\n    @synchronize\n\n    ∂₁u₁, ∂₂u₁, ∂₃u₁ = 1, 2, 3\n    ∂₁u₂, ∂₂u₂, ∂₃u₂ = 4, 5, 6\n    ∂₁u₃, ∂₂u₃, ∂₃u₃ = 7, 8, 9\n\n    if i ≤ qm[1] && j ≤ qm[2]\n        for k in 1:qm[3]\n            ijk = i + ((j - 1) + (k - 1) * qm[2]) * qm[1]\n\n            ξ1x1 = vgeo[ijk, _ξ1x1, e]\n            ξ1x2 = vgeo[ijk, _ξ1x2, e]\n            ξ1x3 = vgeo[ijk, _ξ1x3, e]\n            ξ2x1 = vgeo[ijk, _ξ2x1, e]\n            ξ2x2 = vgeo[ijk, _ξ2x2, e]\n            ξ2x3 = vgeo[ijk, _ξ2x3, e]\n            ξ3x1 = vgeo[ijk, _ξ3x1, e]\n            ξ3x2 = vgeo[ijk, _ξ3x2, e]\n            ξ3x3 = vgeo[ijk, _ξ3x3, e]\n\n            vgrad_data[ijk, ∂₁u₁, e] =\n                g[ijk, e, 1, 1] * ξ1x1 +\n                g[ijk, e, 1, 2] * ξ2x1 +\n                g[ijk, e, 1, 3] * ξ3x1\n            vgrad_data[ijk, ∂₂u₁, e] =\n                g[ijk, e, 1, 1] * ξ1x2 +\n                g[ijk, e, 1, 2] * ξ2x2 +\n                g[ijk, e, 1, 3] * ξ3x2\n            vgrad_data[ijk, ∂₃u₁, e] =\n                g[ijk, e, 1, 1] * ξ1x3 +\n                g[ijk, e, 1, 2] * ξ2x3 +\n                g[ijk, e, 1, 3] * ξ3x3\n\n            vgrad_data[ijk, ∂₁u₂, e] =\n                g[ijk, e, 2, 1] * ξ1x1 +\n                g[ijk, e, 2, 2] * ξ2x1 +\n                g[ijk, e, 2, 3] * ξ3x1\n            vgrad_data[ijk, ∂₂u₂, e] =\n                g[ijk, e, 2, 1] * ξ1x2 +\n                g[ijk, e, 2, 2] * ξ2x2 +\n                g[ijk, e, 2, 3] * ξ3x2\n            vgrad_data[ijk, ∂₃u₂, e] =\n                g[ijk, e, 2, 1] * ξ1x3 +\n                g[ijk, e, 2, 2] * ξ2x3 +\n                g[ijk, e, 2, 3] * ξ3x3\n\n            vgrad_data[ijk, ∂₁u₃, e] =\n                g[ijk, e, 3, 1] * ξ1x1 +\n                g[ijk, e, 3, 2] * ξ2x1 +\n                g[ijk, e, 3, 3] * ξ3x1\n            vgrad_data[ijk, ∂₂u₃, e] =\n                g[ijk, e, 3, 1] * ξ1x2 +\n                g[ijk, e, 3, 2] * ξ2x2 +\n                g[ijk, e, 3, 3] * ξ3x2\n            vgrad_data[ijk, ∂₃u₃, e] =\n                g[ijk, e, 3, 1] * ξ1x3 +\n                g[ijk, e, 3, 2] * ξ2x3 +\n                g[ijk, e, 3, 3] * ξ3x3\n        end\n    end\nend\n\n#--------------------------------------------------------------------------------------------------\n\n\"\"\"\n    Vorticity{\n        FT <: AbstractFloat,\n        FTA2D <: AbstractArray{FT, 2},\n        FTA3D <: AbstractArray{FT, 3},\n    }\n\nThis data structure stores the vorticity of a velocity field.\n\n# Fields\n\n$(DocStringExtensions.FIELDS)\n\n# Usage\n\n    Vorticity(data)\n\n# Arguments for the inner constructor\n - `data`: 3-dimensional device array containing the vorticity data\n   (the second dimension must be 3)\n\"\"\"\nstruct Vorticity{\n    FT <: AbstractFloat,\n    FTA2D <: AbstractArray{FT, 2},\n    FTA3D <: AbstractArray{FT, 3},\n}\n    \"Device array storing the vorticity data\"\n    data::FTA3D\n    \"View of x1 component of vorticity\"\n    Ω₁::FTA2D\n    \"View of x2 component of vorticity\"\n    Ω₂::FTA2D\n    \"View of x3 component of vorticity\"\n    Ω₃::FTA2D\n    function Vorticity(data::AbstractArray{FT, 3}) where {FT <: AbstractFloat}\n        Ω₁ = view(data, :, 1, :)\n        Ω₂ = view(data, :, 2, :)\n        Ω₃ = view(data, :, 3, :)\n        return new{FT, typeof(Ω₁), typeof(data)}(data, Ω₁, Ω₂, Ω₃)\n    end\nend\n\n\"\"\"\n    Vorticity(\n        dg::SpaceDiscretization,\n        vgrad::VectorGradients,\n    )\n\nThis function computes the vorticity of the velocity field.\n\n# Arguments\n - `dg`: SpaceDiscretization\n - `vgrad`: vector gradients\n\"\"\"\nfunction Vorticity(dg::SpaceDiscretization, vgrad::VectorGradients)\n    bl = dg.balance_law\n    FT = eltype(dg.grid)\n    npoints = prod(polynomialorders(dg.grid) .+ 1)\n    nrealelem = length(dg.grid.topology.realelems)\n\n    data = similar(vgrad.data, npoints, 3, nrealelem)\n\n    device = array_device(data)\n    comp_stream = Event(device)\n    thr_max = 256\n    thr_x = min(thr_max, npoints)\n    workgroup = (thr_x, 1)\n    ndrange = (npoints, nrealelem)\n\n    comp_stream = vorticity_kernel!(device, workgroup)(\n        vgrad.data,\n        data,\n        ndrange = ndrange,\n        dependencies = (comp_stream,),\n    )\n    wait(comp_stream)\n\n    return Vorticity(data)\nend\n\n@kernel function vorticity_kernel!(\n    vgrad_data::AbstractArray{FT, 3},\n    vort_data::AbstractArray{FT, 3},\n) where {FT <: AbstractFloat}\n    ijk, e = @index(Global, NTuple)\n\n    ∂₁u₁, ∂₂u₁, ∂₃u₁ = 1, 2, 3\n    ∂₁u₂, ∂₂u₂, ∂₃u₂ = 4, 5, 6\n    ∂₁u₃, ∂₂u₃, ∂₃u₃ = 7, 8, 9\n    Ω₁, Ω₂, Ω₃ = 1, 2, 3\n\n    vort_data[ijk, Ω₁, e] = vgrad_data[ijk, ∂₂u₃, e] - vgrad_data[ijk, ∂₃u₂, e]\n    vort_data[ijk, Ω₂, e] = vgrad_data[ijk, ∂₃u₁, e] - vgrad_data[ijk, ∂₁u₃, e]\n    vort_data[ijk, Ω₃, e] = vgrad_data[ijk, ∂₁u₂, e] - vgrad_data[ijk, ∂₂u₁, e]\nend\n"
  },
  {
    "path": "src/Diagnostics/dump_aux.jl",
    "content": "\"\"\"\n    setup_dump_aux_diagnostics(\n        ::ClimateMachineConfigType,\n        interval::String,\n        out_prefix::String;\n        writer = NetCDFWriter(),\n        interpol = nothing,\n    )\n\nCreate the \"DumpAux\" `DiagnosticsGroup` which contains all the\nauxiliary state variables. These are output on `x`, `y`, `z` or `lat`,\n`long`, `level` dimensions of an interpolated grid (`interpol` _must_\nbe specified) as well as a `time` dimension at the specified `interval`.\n\"\"\"\nfunction setup_dump_aux_diagnostics(\n    ::ClimateMachineConfigType,\n    interval::String,\n    out_prefix::String;\n    writer = NetCDFWriter(),\n    interpol = nothing,\n)\n    # TODO: remove this\n    @assert !isnothing(interpol)\n\n    return DiagnosticsGroup(\n        \"DumpAux\",\n        Diagnostics.dump_aux_init,\n        Diagnostics.dump_aux_fini,\n        Diagnostics.dump_aux_collect,\n        interval,\n        out_prefix,\n        writer,\n        interpol,\n    )\nend\n\ndump_aux_init(dgngrp, currtime) = dump_init(dgngrp, currtime, Auxiliary())\n\nfunction dump_aux_collect(dgngrp, currtime)\n    interpol = dgngrp.interpol\n    mpicomm = Settings.mpicomm\n    dg = Settings.dg\n    Q = Settings.Q\n    FT = eltype(Q.data)\n    bl = dg.balance_law\n    mpirank = MPI.Comm_rank(mpicomm)\n\n    iaux = similar(\n        dg.state_auxiliary.data,\n        interpol.Npl,\n        number_states(bl, Auxiliary()),\n    )\n\n    interpolate_local!(interpol, dg.state_auxiliary.data, iaux)\n\n    all_aux_data = accumulate_interpolated_data(mpicomm, interpol, iaux)\n\n    if mpirank == 0\n        auxnames = flattenednames(vars_state(bl, Auxiliary(), FT))\n        varvals = OrderedDict()\n        for (vari, varname) in enumerate(auxnames)\n            varvals[varname] = all_aux_data[:, :, :, vari]\n        end\n        append_data(dgngrp.writer, varvals, currtime)\n    end\n\n    MPI.Barrier(mpicomm)\n    return nothing\nend\n\nfunction dump_aux_fini(dgngrp, currtime) end\n"
  },
  {
    "path": "src/Diagnostics/dump_init.jl",
    "content": "function dump_init(dgngrp, currtime, st::AbstractStateType)\n    FT = eltype(Settings.Q)\n    bl = Settings.dg.balance_law\n    mpicomm = Settings.mpicomm\n    mpirank = MPI.Comm_rank(mpicomm)\n\n    if mpirank == 0\n        # get dimensions for the interpolated grid\n        dims = dimensions(dgngrp.interpol)\n\n        # set up the variables we're going to be writing\n        vars = OrderedDict()\n        statenames = flattenednames(vars_state(bl, st, FT))\n        for varname in statenames\n            vars[varname] = (tuple(collect(keys(dims))...), FT, Dict())\n        end\n\n        dprefix = @sprintf(\"%s_%s\", dgngrp.out_prefix, dgngrp.name)\n        dfilename = joinpath(Settings.output_dir, dprefix)\n        noov = Settings.no_overwrite\n        init_data(dgngrp.writer, dfilename, noov, dims, vars)\n    end\n\n    return nothing\nend\n"
  },
  {
    "path": "src/Diagnostics/dump_state.jl",
    "content": "\"\"\"\n    setup_dump_state_diagnostics(\n        ::ClimateMachineConfigType,\n        interval::String,\n        out_prefix::String;\n        writer = NetCDFWriter(),\n        interpol = nothing,\n    )\n\nCreate the \"DumpState\" `DiagnosticsGroup` which contains all the\nprognostic state variables. These are output on `x`, `y`, `z` or `lat`,\n`long`, `level` dimensions of an interpolated grid (`interpol` _must_\nbe specified) as well as a `time` dimension at the specified `interval`.\n\"\"\"\nfunction setup_dump_state_diagnostics(\n    ::ClimateMachineConfigType,\n    interval::String,\n    out_prefix::String;\n    writer = NetCDFWriter(),\n    interpol = nothing,\n)\n    # TODO: remove this\n    @assert !isnothing(interpol)\n\n    return DiagnosticsGroup(\n        \"DumpState\",\n        Diagnostics.dump_state_init,\n        Diagnostics.dump_state_fini,\n        Diagnostics.dump_state_collect,\n        interval,\n        out_prefix,\n        writer,\n        interpol,\n    )\nend\n\ndump_state_init(dgngrp, currtime) = dump_init(dgngrp, currtime, Prognostic())\n\nfunction dump_state_collect(dgngrp, currtime)\n    interpol = dgngrp.interpol\n    mpicomm = Settings.mpicomm\n    dg = Settings.dg\n    Q = Settings.Q\n    FT = eltype(Q.data)\n    bl = dg.balance_law\n    mpirank = MPI.Comm_rank(mpicomm)\n\n    istate = similar(Q.data, interpol.Npl, number_states(bl, Prognostic()))\n    interpolate_local!(interpol, Q.data, istate)\n\n    if interpol isa InterpolationCubedSphere\n        # TODO: get indices here without hard-coding them\n        _ρu, _ρv, _ρw = 2, 3, 4\n        project_cubed_sphere!(interpol, istate, (_ρu, _ρv, _ρw))\n    end\n\n    all_state_data = accumulate_interpolated_data(mpicomm, interpol, istate)\n\n    if mpirank == 0\n        statenames = flattenednames(vars_state(bl, Prognostic(), FT))\n        varvals = OrderedDict()\n        for (vari, varname) in enumerate(statenames)\n            varvals[varname] = all_state_data[:, :, :, vari]\n        end\n        append_data(dgngrp.writer, varvals, currtime)\n    end\n\n    MPI.Barrier(mpicomm)\n    return nothing\nend\n\nfunction dump_state_fini(dgngrp, currtime) end\n"
  },
  {
    "path": "src/Diagnostics/dump_tendencies.jl",
    "content": "\"\"\"\n    setup_dump_tendencies_diagnostics(\n        ::ClimateMachineConfigType,\n        interval::String,\n        out_prefix::String;\n        writer = NetCDFWriter(),\n        interpol = nothing,\n    )\n\nCreate the \"DumpTendencies\" `DiagnosticsGroup` which contains all the\ntendencies for the simulation's `BalanceLaw`. These are collected on\nan interpolated grid (`interpol` _must_ be specified).\n\n!!! warn\n    This diagnostics group can produce a lot of output and is\n    very expensive.\n\n!!! warn\n    These diagnostics are intended for physics-debugging, and not numerics debugging, because\n    `flux` and `source` are called on interpolated data, which may not exactly match the\n    non-interpolated evaluation of fluxes and sources that the solver uses.\n\"\"\"\nfunction setup_dump_tendencies_diagnostics(\n    ::ClimateMachineConfigType,\n    interval::String,\n    out_prefix::String;\n    writer = NetCDFWriter(),\n    interpol = nothing,\n)\n    @assert !isnothing(interpol)\n\n    return DiagnosticsGroup(\n        \"DumpTendencies\",\n        Diagnostics.dump_tendencies_init,\n        Diagnostics.dump_tendencies_fini,\n        Diagnostics.dump_tendencies_collect,\n        interval,\n        out_prefix,\n        writer,\n        interpol,\n    )\nend\n\n# helpers for PV and tendency names\nparams(::T) where {T} = T.parameters\nunval(::Val{i}) where {i} = i\nparam_suffix(p, ::Val{n}) where {n} = string(\"_\", unval(p[1]))\nparam_suffix(p, ::Val{0}) = \"\"\nparam_suffix(p) = param_suffix(p, Val(length(p)))\nprog_name(pv::PV) where {PV} = string(nameof(PV), param_suffix(params(pv)))\ntend_name(t) = String(nameof(typeof(t)))\n\nfunction precompute_args(\n    bl,\n    state,\n    aux,\n    diffusive,\n    hyperdiffusive,\n    t,\n    direction,\n)\n    _args_fx1 = (; state, aux, t, direction)\n    _args_fx2 = (; state, aux, t, diffusive, hyperdiffusive)\n    _args_src = (; state, aux, t, direction, diffusive)\n\n    cache_fx1 = precompute(bl, _args_fx1, Flux{FirstOrder}())\n    cache_fx2 = precompute(bl, _args_fx2, Flux{SecondOrder}())\n    cache_src = precompute(bl, _args_src, Source())\n\n    args_fx1 = merge(_args_fx1, (; precomputed = cache_fx1))\n    args_fx2 = merge(_args_fx2, (; precomputed = cache_fx2))\n    args_src = merge(_args_src, (; precomputed = cache_src))\n\n    return (args_fx1, args_fx2, args_src)\nend\n\nfunction dump_tendencies_init(dgngrp, t)\n    dg = Settings.dg\n    Q = Settings.Q\n    FT = eltype(Q)\n    bl = Settings.dg.balance_law\n    mpicomm = Settings.mpicomm\n    mpirank = MPI.Comm_rank(mpicomm)\n\n    if mpirank == 0\n        if array_device(Q) isa CPU\n            prognostic_data = Q.realdata\n            auxiliary_data = dg.state_auxiliary.realdata\n            gradient_flux_data = dg.state_gradient_flux.realdata\n            hyperdiffusive_data = dg.states_higher_order[2].realdata\n        else\n            prognostic_data = Array(Q.realdata)\n            auxiliary_data = Array(dg.state_auxiliary.realdata)\n            gradient_flux_data = Array(dg.state_gradient_flux.realdata)\n            hyperdiffusive_data = Array(dg.states_higher_order[2].realdata)\n        end\n\n        # get dimensions for the interpolated grid\n        dims = dimensions(dgngrp.interpol)\n        dim_names = tuple(collect(keys(dims))...)\n\n        state, aux, diffusive, hyperdiffusive = (\n            extract_state(dg, prognostic_data, 1, 1, Prognostic()),\n            extract_state(dg, auxiliary_data, 1, 1, Auxiliary()),\n            extract_state(dg, gradient_flux_data, 1, 1, GradientFlux()),\n            extract_state(dg, hyperdiffusive_data, 1, 1, Hyperdiffusive()),\n        )\n        direction = dg.direction\n        args_fx1, args_fx2, args_src = precompute_args(\n            bl,\n            state,\n            aux,\n            diffusive,\n            hyperdiffusive,\n            t,\n            direction,\n        )\n\n        # set up the variables we're going to be writing\n        vars = OrderedDict()\n        for (tend_fun, tend_type, tend_args) in (\n            (flux, Flux{FirstOrder}(), args_fx1),\n            (flux, Flux{SecondOrder}(), args_fx2),\n            (source, Source(), args_src),\n        )\n            for pv in prognostic_vars(bl)\n                progname = prog_name(pv)\n                for tend in eq_tends(pv, bl, tend_type)\n                    flat_vals = flattened_tuple(\n                        FlattenArr(),\n                        tend_fun(pv, tend, bl, tend_args),\n                    )\n                    for i in 1:length(flat_vals)\n                        varname = \"$(progname)_$(tend_name(tend))_$(i)\"\n                        vars[varname] = (dim_names, FT, Dict())\n                    end\n                end\n            end\n        end\n\n        dprefix = @sprintf(\"%s_%s\", dgngrp.out_prefix, dgngrp.name)\n        dfilename = joinpath(Settings.output_dir, dprefix)\n        noov = Settings.no_overwrite\n        init_data(dgngrp.writer, dfilename, noov, dims, vars)\n    end\n\n    return nothing\nend\n\nfunction dump_tendencies_collect(dgngrp, t)\n    interpol = dgngrp.interpol\n    mpicomm = Settings.mpicomm\n    dg = Settings.dg\n    Q = Settings.Q\n    FT = eltype(Q.data)\n    bl = dg.balance_law\n    mpirank = MPI.Comm_rank(mpicomm)\n\n    istate = similar(Q.data, interpol.Npl, number_states(bl, Prognostic()))\n    interpolate_local!(interpol, Q.data, istate)\n    iaux = similar(Q.data, interpol.Npl, number_states(bl, Auxiliary()))\n    interpolate_local!(interpol, dg.state_auxiliary.data, iaux)\n    igf = similar(Q.data, interpol.Npl, number_states(bl, GradientFlux()))\n    interpolate_local!(interpol, dg.state_gradient_flux.data, igf)\n    ihd = similar(Q.data, interpol.Npl, number_states(bl, Hyperdiffusive()))\n    interpolate_local!(interpol, dg.states_higher_order[2].data, ihd)\n\n    if interpol isa InterpolationCubedSphere\n        i_ρu = varsindex(vars_state(bl, Prognostic(), FT), :ρu)\n        project_cubed_sphere!(interpol, istate, tuple(collect(i_ρu)...))\n    end\n\n    all_state_data = accumulate_interpolated_data(mpicomm, interpol, istate)\n    all_aux_data = accumulate_interpolated_data(mpicomm, interpol, iaux)\n    all_gf_data = accumulate_interpolated_data(mpicomm, interpol, igf)\n    all_hd_data = accumulate_interpolated_data(mpicomm, interpol, ihd)\n\n    if mpirank == 0\n        varvals = OrderedDict()\n        dims = dimensions(dgngrp.interpol)\n        direction = dg.direction\n        nx, ny, nz = [length(dim[1]) for dim in values(dims)]\n        @traverse_interpolated_grid nx ny nz begin\n            vars_view(st, data) =\n                Vars{vars_state(bl, st, FT)}(view(data, lo, la, le, :))\n            statei = vars_view(Prognostic(), all_state_data)\n            auxi = vars_view(Auxiliary(), all_aux_data)\n            diffusivei = vars_view(GradientFlux(), all_gf_data)\n            hyperdiffusivei = vars_view(Hyperdiffusive(), all_hd_data)\n\n            args_fx1, args_fx2, args_src = precompute_args(\n                bl,\n                statei,\n                auxi,\n                diffusivei,\n                hyperdiffusivei,\n                t,\n                direction,\n            )\n\n            for (tend_fun, tend_type, tend_args) in (\n                (flux, Flux{FirstOrder}(), args_fx1),\n                (flux, Flux{SecondOrder}(), args_fx2),\n                (source, Source(), args_src),\n            )\n                for pv in prognostic_vars(bl)\n                    progname = prog_name(pv)\n                    for tend in eq_tends(pv, bl, tend_type)\n                        flat_vals = flattened_tuple(\n                            FlattenArr(),\n                            tend_fun(pv, tend, bl, tend_args),\n                        )\n                        for i in 1:length(flat_vals)\n                            varname = \"$(progname)_$(tend_name(tend))_$(i)\"\n                            vals = get!(\n                                varvals,\n                                varname,\n                                Array{FT}(undef, nx, ny, nz),\n                            )\n                            vals[lo, la, le] = flat_vals[i]\n                        end\n                    end\n                end\n            end\n\n        end\n\n        append_data(dgngrp.writer, varvals, t)\n    end\n\n    MPI.Barrier(mpicomm)\n    return nothing\nend\n\nfunction dump_tendencies_fini(dgngrp, t) end\n"
  },
  {
    "path": "src/Diagnostics/groups.jl",
    "content": "abstract type DiagnosticsGroupParams end\n\n\"\"\"\n    DiagnosticsGroup\n\nHolds a set of diagnostics that share a collection interval, a filename\nprefix, an output writer, an interpolation, and any extra parameters.\n\"\"\"\nmutable struct DiagnosticsGroup{DGP <: Union{Nothing, DiagnosticsGroupParams}}\n    name::String\n    init::Function\n    fini::Function\n    collect::Function\n    interval::String\n    out_prefix::String\n    writer::AbstractWriter\n    interpol::Union{Nothing, InterpolationTopology}\n    params::DGP\n\n    DiagnosticsGroup(\n        name,\n        init,\n        fini,\n        collect,\n        interval,\n        out_prefix,\n        writer,\n        interpol,\n        params = nothing,\n    ) = new{typeof(params)}(\n        name,\n        init,\n        fini,\n        collect,\n        interval,\n        out_prefix,\n        writer,\n        interpol,\n        params,\n    )\nend\n\nfunction GenericCallbacks.init!(dgngrp::DiagnosticsGroup, solver, Q, param, t)\n    @info @sprintf(\n        \"\"\"\n    Diagnostics: %s\n        initializing at %8.2f\"\"\",\n        dgngrp.name,\n        t,\n    )\n    dgngrp.init(dgngrp, t)\n    dgngrp.collect(dgngrp, t)\n    return nothing\nend\nfunction GenericCallbacks.call!(dgngrp::DiagnosticsGroup, solver, Q, param, t)\n    @tic diagnostics\n    @info @sprintf(\n        \"\"\"\n    Diagnostics: %s\n        collecting at %8.2f\"\"\",\n        dgngrp.name,\n        t,\n    )\n    dgngrp.collect(dgngrp, t)\n    @toc diagnostics\n    return nothing\nend\nfunction GenericCallbacks.fini!(dgngrp::DiagnosticsGroup, solver, Q, param, t)\n    @info @sprintf(\n        \"\"\"\n    Diagnostics: %s\n        finishing at %8.2f\"\"\",\n        dgngrp.name,\n        t,\n    )\n    dgngrp.collect(dgngrp, t)\n    dgngrp.fini(dgngrp, t)\n    return nothing\nend\n\ninclude(\"atmos_les_default.jl\")\ninclude(\"atmos_gcm_default.jl\")\ninclude(\"atmos_les_core.jl\")\ninclude(\"atmos_les_default_perturbations.jl\")\ninclude(\"atmos_refstate_perturbations.jl\")\ninclude(\"atmos_turbulence_stats.jl\")\ninclude(\"atmos_mass_energy_loss.jl\")\ninclude(\"atmos_les_spectra.jl\")\ninclude(\"atmos_gcm_spectra.jl\")\ninclude(\"dump_init.jl\")\ninclude(\"dump_state.jl\")\ninclude(\"dump_aux.jl\")\ninclude(\"dump_tendencies.jl\")\n"
  },
  {
    "path": "src/Diagnostics/helpers.jl",
    "content": "# Miscellaneous helper macros and functions\n#\n\n# Helper macro to iterate over the DG grid. Generates the needed loops\n# and indices: `eh`, `ev`, `e`, `k,`, `j`, `i`, `ijk`.\nmacro traverse_dg_grid(grid_info, topl_info, expr)\n    return esc(\n        quote\n            for eh in 1:($(topl_info).nhorzrealelem)\n                for ev in 1:($(topl_info).nvertelem)\n                    e = ev + (eh - 1) * $(topl_info).nvertelem\n                    for k in 1:($(grid_info).Nqk)\n                        evk = $(grid_info).Nqk * (ev - 1) + k\n                        for j in 1:$(grid_info).Nq[2]\n                            for i in 1:$(grid_info).Nq[1]\n                                ijk =\n                                    i +\n                                    $(grid_info).Nq[1] *\n                                    ((j - 1) + $(grid_info).Nq[2] * (k - 1))\n                                $expr\n                            end\n                        end\n                    end\n                end\n            end\n        end,\n    )\nend\n\n# Helper macro to iterate over a 3D array. Used for walking the\n# interpolated (GCM) grid.\nmacro traverse_interpolated_grid(nlong, nlat, nlevel, expr)\n    return esc(quote\n        for lo in 1:($nlong)\n            for la in 1:($nlat)\n                for le in 1:($nlevel)\n                    $expr\n                end\n            end\n        end\n    end)\nend\n\n# Helpers to extract data from the various state arrays\nfunction extract_state(dg, state, ijk, e, st::AbstractStateType)\n    bl = dg.balance_law\n    FT = eltype(state)\n    num_state = number_states(bl, st)\n    local_state = MArray{Tuple{num_state}, FT}(undef)\n    for s in 1:num_state\n        local_state[s] = state[ijk, s, e]\n    end\n    return Vars{vars_state(bl, st, FT)}(local_state)\nend\n"
  },
  {
    "path": "src/Diagnostics/thermo.jl",
    "content": "using ..Atmos\nusing ..Atmos: AbstractMoistureModel\n\n# Helpers to gather the thermodynamic variables across the DG grid\n\nfunction vars_thermo(atmos::AtmosModel, FT)\n    @vars begin\n        temp::FT\n        pres::FT\n        θ_dry::FT\n        e_int::FT\n        h_tot::FT\n        h_int::FT\n\n        moisture::vars_thermo(moisture_model(atmos), FT)\n    end\nend\nvars_thermo(::AbstractMoistureModel, FT) = @vars()\nfunction vars_thermo(m::Union{EquilMoist, NonEquilMoist}, FT)\n    @vars begin\n        q_liq::FT\n        q_ice::FT\n        q_vap::FT\n        θ_vir::FT\n        θ_liq_ice::FT\n        has_condensate::Bool\n    end\nend\nnum_thermo(bl, FT) = varsize(vars_thermo(bl, FT))\nthermo_vars(bl, array) = Vars{vars_thermo(bl, eltype(array))}(array)\n\n# compute thermodynamic variables visitor function\nfunction compute_thermo!(atmos::AtmosModel, state, aux, thermo)\n    e_tot = state.energy.ρe / state.ρ\n    ts = recover_thermo_state(atmos, state, aux)\n    e_int = internal_energy(ts)\n\n    thermo.temp = air_temperature(ts)\n    thermo.pres = air_pressure(ts)\n    thermo.θ_dry = dry_pottemp(ts)\n    thermo.e_int = e_int\n\n    thermo.h_tot = total_specific_enthalpy(ts, e_tot)\n    thermo.h_int = specific_enthalpy(ts)\n\n    compute_thermo!(moisture_model(atmos), state, aux, ts, thermo)\n\n    return nothing\nend\nfunction compute_thermo!(::AbstractMoistureModel, state, aux, ts, thermo)\n    return nothing\nend\nfunction compute_thermo!(\n    moist::Union{EquilMoist, NonEquilMoist},\n    state,\n    aux,\n    ts,\n    thermo,\n)\n    thermo.moisture.has_condensate = has_condensate(ts)\n    thermo.moisture.q_liq = liquid_specific_humidity(ts)\n    thermo.moisture.q_ice = ice_specific_humidity(ts)\n    thermo.moisture.q_vap = vapor_specific_humidity(ts)\n    thermo.moisture.θ_vir = virtual_pottemp(ts)\n    thermo.moisture.θ_liq_ice = liquid_ice_pottemp(ts)\n\n    return nothing\nend\n"
  },
  {
    "path": "src/Diagnostics/variables.jl",
    "content": "\"\"\"\n    DiagnosticVariable\n\nCurrently only holds information about a diagnostics variable.\n\nStandard names are from:\nhttp://cfconventions.org/Data/cf-standard-names/71/build/cf-standard-name-table.html\n\nTODO: will expand to include definition (code) as well.\n\"\"\"\nstruct DiagnosticVariable\n    name::String\n    attrib::OrderedDict\n\n    DiagnosticVariable(name::String, attrib::OrderedDict = OrderedDict()) =\n        new(name, attrib)\nend\nconst Variables = OrderedDict{String, DiagnosticVariable}()\n\nfunction var_attrib(\n    units::String,\n    long_name::String,\n    standard_name::String,\n    fill_value::Union{Nothing, Any} = nothing,\n)\n    attrib = OrderedDict(\n        \"units\" => units,\n        \"long_name\" => long_name,\n        \"standard_name\" => standard_name,\n    )\n    if !isnothing(fill_value)\n        attrib[\"_FillValue\"] = fill_value\n    end\n    return attrib\nend\n\n\"\"\"\n    setup_variables()\n\nCalled at module initialization to define all currently defined diagnostic\nvariables.\n\"\"\"\nfunction setup_variables()\n    # les long_name: \"x-velocity\"\n    Variables[\"u\"] = DiagnosticVariable(\n        \"u\",\n        var_attrib(\"m s^-1\", \"zonal wind\", \"eastward_wind\"),\n    )\n    # les long_name: \"y-velocity\"\n    Variables[\"v\"] = DiagnosticVariable(\n        \"v\",\n        var_attrib(\"m s^-1\", \"meridional wind\", \"northward_wind\"),\n    )\n    # les long_name = \"z-velocity\"\n    Variables[\"w\"] = DiagnosticVariable(\n        \"w\",\n        var_attrib(\"m s^-1\", \"vertical wind\", \"upward_air_velocity\"),\n    )\n    Variables[\"rho\"] = DiagnosticVariable(\n        \"rho\",\n        var_attrib(\"kg m^-3\", \"air density\", \"air_density\"),\n    )\n    Variables[\"temp\"] = DiagnosticVariable(\n        \"temp\",\n        var_attrib(\"K\", \"air temperature\", \"air_temperature\"),\n    )\n    Variables[\"pres\"] = DiagnosticVariable(\n        \"pres\",\n        var_attrib(\"Pa\", \"air pressure\", \"air_pressure\"),\n    )\n    Variables[\"thd\"] = DiagnosticVariable(\n        \"thd\",\n        var_attrib(\n            \"K\",\n            \"dry potential temperature\",\n            \"air_potential_temperature\",\n        ),\n    )\n    Variables[\"thv\"] = DiagnosticVariable(\n        \"thv\",\n        var_attrib(\n            \"K\",\n            \"virtual potential temperature\",\n            \"virtual_potential_temperature\",\n        ),\n    )\n    Variables[\"et\"] = DiagnosticVariable(\n        \"et\",\n        var_attrib(\n            \"J kg^-1\",\n            \"total specific energy\",\n            \"specific_dry_energy_of_air\",\n        ),\n    )\n    Variables[\"ei\"] = DiagnosticVariable(\n        \"ei\",\n        var_attrib(\"J kg^-1\", \"specific internal energy\", \"internal_energy\"),\n    )\n    Variables[\"ht\"] = DiagnosticVariable(\n        \"ht\",\n        var_attrib(\"J kg^-1\", \"specific enthalpy based on total energy\", \"\"),\n    )\n    Variables[\"hi\"] = DiagnosticVariable(\n        \"hi\",\n        var_attrib(\n            \"J kg^-1\",\n            \"specific enthalpy based on internal energy\",\n            \"atmosphere_enthalpy_content\",\n        ),\n    )\n    Variables[\"vort\"] = DiagnosticVariable(\n        \"vort\",\n        var_attrib(\n            \"s^-1\",\n            \"vertical component of relative velocity\",\n            \"atmosphere_relative_velocity\",\n        ),\n    )\n    Variables[\"avg_rho\"] = DiagnosticVariable(\n        \"avg_rho\",\n        var_attrib(\"kg m^-3\", \"air density\", \"air_density\"),\n    )\n    Variables[\"qt\"] = DiagnosticVariable(\n        \"qt\",\n        var_attrib(\n            \"kg kg^-1\",\n            \"mass fraction of total water in air (qv+ql+qi)\",\n            \"mass_fraction_of_water_in_air\",\n        ),\n    )\n    Variables[\"ql\"] = DiagnosticVariable(\n        \"ql\",\n        var_attrib(\n            \"kg kg^-1\",\n            \"mass fraction of liquid water in air\",\n            \"mass_fraction_of_cloud_liquid_water_in_air\",\n        ),\n    )\n    Variables[\"qv\"] = DiagnosticVariable(\n        \"qv\",\n        var_attrib(\n            \"kg kg^-1\",\n            \"mass fraction of water vapor in air\",\n            \"specific_humidity\",\n        ),\n    )\n    Variables[\"qi\"] = DiagnosticVariable(\n        \"qi\",\n        var_attrib(\n            \"kg kg^-1\",\n            \"mass fraction of ice in air\",\n            \"mass_fraction_of_cloud_ice_in_air\",\n        ),\n    )\n    Variables[\"thl\"] = DiagnosticVariable(\n        \"thl\",\n        var_attrib(\"K\", \"liquid-ice potential temperature\", \"\"),\n    )\n    Variables[\"qr\"] = DiagnosticVariable(\n        \"qr\",\n        var_attrib(\n            \"kg kg^-1\",\n            \"mass fraction of rain in the air\",\n            \"mass_fraction_of_rain_in_the_air\",\n        ),\n    )\n    Variables[\"qs\"] = DiagnosticVariable(\n        \"qs\",\n        var_attrib(\n            \"kg kg^-1\",\n            \"mass fraction of snow in the air\",\n            \"mass_fraction_of_snow_in_the_air\",\n        ),\n    )\n    Variables[\"cld_frac\"] = DiagnosticVariable(\n        \"cld_frac\",\n        var_attrib(\n            \"\",\n            \"cloud fraction\",\n            \"cloud_area_fraction_in_atmosphere_layer\",\n        ),\n    )\n    Variables[\"var_u\"] = DiagnosticVariable(\n        \"var_u\",\n        var_attrib(\"m^2 s^-2\", \"variance of x-velocity\", \"\"),\n    )\n    Variables[\"var_v\"] = DiagnosticVariable(\n        \"var_v\",\n        var_attrib(\"m^2 s^-2\", \"variance of y-velocity\", \"\"),\n    )\n    Variables[\"var_w\"] = DiagnosticVariable(\n        \"var_w\",\n        var_attrib(\"m^2 s^-2\", \"variance of z-velocity\", \"\"),\n    )\n    Variables[\"w3\"] = DiagnosticVariable(\n        \"w3\",\n        var_attrib(\"m^3 s^-3\", \"third moment of z-velocity\", \"\"),\n    )\n    Variables[\"tke\"] = DiagnosticVariable(\n        \"tke\",\n        var_attrib(\"m^2 s^-2\", \"turbulent kinetic energy\", \"\"),\n    )\n    Variables[\"var_qt\"] = DiagnosticVariable(\n        \"var_qt\",\n        var_attrib(\"kg^2 kg^-2\", \"variance of total specific humidity\", \"\"),\n    )\n    Variables[\"var_thl\"] = DiagnosticVariable(\n        \"var_thl\",\n        var_attrib(\"K^2\", \"variance of liquid-ice potential temperature\", \"\"),\n    )\n    Variables[\"var_ei\"] = DiagnosticVariable(\n        \"var_ei\",\n        var_attrib(\"J^2 kg^-2\", \"variance of specific internal energy\", \"\"),\n    )\n    Variables[\"var_qr\"] = DiagnosticVariable(\n        \"var_qr\",\n        var_attrib(\"kg^2 kg^-2\", \"variance of rain specific humidity\", \"\"),\n    )\n    Variables[\"var_qs\"] = DiagnosticVariable(\n        \"var_qs\",\n        var_attrib(\"kg^2 kg^-2\", \"variance of snow specific humidity\", \"\"),\n    )\n    Variables[\"cov_w_u\"] = DiagnosticVariable(\n        \"cov_w_u\",\n        var_attrib(\"m^2 s^-2\", \"vertical eddy flux of x-velocity\", \"\"),\n    )\n    Variables[\"cov_w_v\"] = DiagnosticVariable(\n        \"cov_w_v\",\n        var_attrib(\"m^2 s^-2\", \"vertical eddy flux of y-velocity\", \"\"),\n    )\n    Variables[\"cov_w_rho\"] = DiagnosticVariable(\n        \"cov_w_rho\",\n        var_attrib(\"kg m^-2 s^-1\", \"vertical eddy flux of density\", \"\"),\n    )\n    Variables[\"cov_w_qt\"] = DiagnosticVariable(\n        \"cov_w_qt\",\n        var_attrib(\n            \"kg kg^-1 m s^-1\",\n            \"vertical eddy flux of total specific humidity\",\n            \"\",\n        ),\n    )\n    Variables[\"cov_w_ql\"] = DiagnosticVariable(\n        \"cov_w_ql\",\n        var_attrib(\n            \"kg kg^-1 m s^-1\",\n            \"vertical eddy flux of liquid water specific humidity\",\n            \"\",\n        ),\n    )\n    Variables[\"cov_w_qi\"] = DiagnosticVariable(\n        \"cov_w_qi\",\n        var_attrib(\n            \"kg kg^-1 m s^-1\",\n            \"vertical eddy flux of cloud ice specific humidity\",\n            \"\",\n        ),\n    )\n    Variables[\"cov_w_qv\"] = DiagnosticVariable(\n        \"cov_w_qv\",\n        var_attrib(\n            \"kg kg^-1 m s^-1\",\n            \"vertical eddy flux of water vapor specific humidity\",\n            \"\",\n        ),\n    )\n    Variables[\"cov_w_thd\"] = DiagnosticVariable(\n        \"cov_w_thd\",\n        var_attrib(\n            \"K m s^-1\",\n            \"vertical eddy flux of dry potential temperature\",\n            \"\",\n        ),\n    )\n    Variables[\"cov_w_thv\"] = DiagnosticVariable(\n        \"cov_w_thv\",\n        var_attrib(\n            \"K m s^-1\",\n            \"vertical eddy flux of virtual potential temperature\",\n            \"\",\n        ),\n    )\n    Variables[\"cov_w_thl\"] = DiagnosticVariable(\n        \"cov_w_thl\",\n        var_attrib(\n            \"K m s^-1\",\n            \"vertical eddy flux of liquid-ice potential temperature\",\n            \"\",\n        ),\n    )\n    Variables[\"cov_w_ei\"] = DiagnosticVariable(\n        \"cov_w_ei\",\n        var_attrib(\n            \"J kg^-1 m s^-1\",\n            \"vertical eddy flux of specific internal energy\",\n            \"\",\n        ),\n    )\n    Variables[\"cov_qt_thl\"] = DiagnosticVariable(\n        \"cov_qt_thl\",\n        var_attrib(\n            \"kg kg^-1 K\",\n            \"covariance of total specific humidity and liquid-ice potential temperature\",\n            \"\",\n        ),\n    )\n    Variables[\"cov_qt_ei\"] = DiagnosticVariable(\n        \"cov_qt_ei\",\n        var_attrib(\n            \"kg kg^-1 J kg^-1\",\n            \"covariance of total specific humidity and specific internal energy\",\n            \"\",\n        ),\n    )\n    Variables[\"cov_w_qr\"] = DiagnosticVariable(\n        \"cov_w_qr\",\n        var_attrib(\n            \"kg kg^-1 m s^-1\",\n            \"vertical eddy flux of liquid water specific humidity\",\n            \"\",\n        ),\n    )\n    Variables[\"cov_w_qs\"] = DiagnosticVariable(\n        \"cov_w_qs\",\n        var_attrib(\n            \"kg kg^-1 m s^-1\",\n            \"vertical eddy flux of snow specific humidity\",\n            \"\",\n        ),\n    )\n    Variables[\"w_qt_sgs\"] = DiagnosticVariable(\n        \"w_qt_sgs\",\n        var_attrib(\n            \"kg kg^-1 m s^-1\",\n            \"vertical sgs flux of total specific humidity\",\n            \"\",\n        ),\n    )\n    Variables[\"w_ht_sgs\"] = DiagnosticVariable(\n        \"w_ht_sgs\",\n        var_attrib(\n            \"kg kg^-1 m s^-1\",\n            \"vertical sgs flux of total specific enthalpy\",\n            \"\",\n        ),\n    )\n    Variables[\"cld_cover\"] = DiagnosticVariable(\n        \"cld_cover\",\n        var_attrib(\"\", \"cloud cover\", \"cloud_area_fraction\"),\n    )\n    Variables[\"cld_top\"] = DiagnosticVariable(\n        \"cld_top\",\n        var_attrib(\"m\", \"cloud top\", \"cloud_top_altitude\"),\n    )\n    Variables[\"cld_base\"] = DiagnosticVariable(\n        \"cld_base\",\n        var_attrib(\"m\", \"cloud base\", \"cloud_base_altitude\"),\n    )\n    Variables[\"lwp\"] = DiagnosticVariable(\n        \"lwp\",\n        var_attrib(\n            \"kg m^-2\",\n            \"liquid water path\",\n            \"atmosphere_mass_content_of_cloud_condensed_water\",\n        ),\n    )\n    Variables[\"iwp\"] = DiagnosticVariable(\n        \"iwp\",\n        var_attrib(\n            \"kg m^-2\",\n            \"ice water path\",\n            \"atmosphere_mass_content_of_cloud_ice\",\n        ),\n    )\n    Variables[\"rwp\"] = DiagnosticVariable(\n        \"rwp\",\n        var_attrib(\n            \"kg m^-2\",\n            \"rain water path\",\n            \"atmosphere_mass_content_of_rain_water\",\n        ),\n    )\n    Variables[\"swp\"] = DiagnosticVariable(\n        \"swp\",\n        var_attrib(\n            \"kg m^-2\",\n            \"snow water path\",\n            \"atmosphere_mass_content_of_snow_water\",\n        ),\n    )\n    Variables[\"core_frac\"] = DiagnosticVariable(\n        \"core_frac\",\n        var_attrib(\"\", \"cloud core fraction\", \"\"),\n    )\n    Variables[\"u_core\"] = DiagnosticVariable(\n        \"u_core\",\n        var_attrib(\"m s^-1\", \"cloud core x-velocity\", \"\"),\n    )\n    Variables[\"v_core\"] = DiagnosticVariable(\n        \"v_core\",\n        var_attrib(\"m s^-1\", \"cloud core y-velocity\", \"\"),\n    )\n    Variables[\"w_core\"] = DiagnosticVariable(\n        \"w_core\",\n        var_attrib(\"m s^-1\", \"cloud core z-velocity\", \"\"),\n    )\n    Variables[\"avg_rho_core\"] = DiagnosticVariable(\n        \"avg_rho_core\",\n        var_attrib(\"kg m^-3\", \"cloud core air density\", \"\"),\n    )\n    Variables[\"rho_core\"] = DiagnosticVariable(\n        \"rho_core\",\n        var_attrib(\"kg m^-3\", \"cloud core (density-averaged) air density\", \"\"),\n    )\n    Variables[\"qt_core\"] = DiagnosticVariable(\n        \"qt_core\",\n        var_attrib(\"kg m^-3\", \"cloud core total specific humidity\", \"\"),\n    )\n    Variables[\"ql_core\"] = DiagnosticVariable(\n        \"ql_core\",\n        var_attrib(\"kg m^-3\", \"cloud core liquid water specific humidity\", \"\"),\n    )\n    Variables[\"thv_core\"] = DiagnosticVariable(\n        \"thv_core\",\n        var_attrib(\"K\", \"cloud core virtual potential temperature\", \"\"),\n    )\n    Variables[\"thl_core\"] = DiagnosticVariable(\n        \"thl_core\",\n        var_attrib(\"K\", \"cloud core liquid-ice potential temperature\", \"\"),\n    )\n    Variables[\"ei_core\"] = DiagnosticVariable(\n        \"ei_core\",\n        var_attrib(\"J kg-1\", \"cloud core specific internal energy\", \"\"),\n    )\n    Variables[\"var_u_core\"] = DiagnosticVariable(\n        \"var_u_core\",\n        var_attrib(\"m^2 s^-2\", \"cloud core variance of x-velocity\", \"\"),\n    )\n    Variables[\"var_v_core\"] = DiagnosticVariable(\n        \"var_v_core\",\n        var_attrib(\"m^2 s^-2\", \"cloud core variance of y-velocity\", \"\"),\n    )\n    Variables[\"var_w_core\"] = DiagnosticVariable(\n        \"var_w_core\",\n        var_attrib(\"m^2 s^-2\", \"cloud core variance of z-velocity\", \"\"),\n    )\n    Variables[\"var_qt_core\"] = DiagnosticVariable(\n        \"var_qt_core\",\n        var_attrib(\n            \"kg^2 kg^-2\",\n            \"cloud core variance of total specific humidity\",\n            \"\",\n        ),\n    )\n    Variables[\"var_thl_core\"] = DiagnosticVariable(\n        \"var_thl_core\",\n        var_attrib(\n            \"K^2\",\n            \"cloud core variance of liquid-ice potential temperature\",\n            \"\",\n        ),\n    )\n    Variables[\"var_ei_core\"] = DiagnosticVariable(\n        \"var_ei_core\",\n        var_attrib(\n            \"J^2 kg^-2\",\n            \"cloud core variance of specific internal energy\",\n            \"\",\n        ),\n    )\n    Variables[\"cov_w_rho_core\"] = DiagnosticVariable(\n        \"cov_w_rho_core\",\n        var_attrib(\n            \"kg m^-2 s^-1\",\n            \"cloud core vertical eddy flux of density\",\n            \"\",\n        ),\n    )\n    Variables[\"cov_w_qt_core\"] = DiagnosticVariable(\n        \"cov_w_qt_core\",\n        var_attrib(\n            \"kg kg^-1 m s^-1\",\n            \"cloud core vertical eddy flux of specific humidity\",\n            \"\",\n        ),\n    )\n    Variables[\"cov_w_thl_core\"] = DiagnosticVariable(\n        \"cov_w_thl_core\",\n        var_attrib(\n            \"K m s^-1\",\n            \"cloud core vertical eddy flux of liquid-ice potential temperature\",\n            \"\",\n        ),\n    )\n    Variables[\"cov_w_ei_core\"] = DiagnosticVariable(\n        \"cov_w_ei_core\",\n        var_attrib(\n            \"J kg^-1 m^-1 s^-1\",\n            \"cloud core vertical eddy flux of specific internal energy\",\n            \"\",\n        ),\n    )\n    Variables[\"cov_qt_thl_core\"] = DiagnosticVariable(\n        \"cov_qt_thl_core\",\n        var_attrib(\n            \"kg kg^-1 K\",\n            \"cloud core covariance of total specific humidity and liquid-ice potential temperature\",\n            \"\",\n        ),\n    )\n    Variables[\"cov_qt_ei_core\"] = DiagnosticVariable(\n        \"cov_qt_ei_core\",\n        var_attrib(\n            \"kg kg^-1 J kg^-1\",\n            \"cloud core covariance of total specific humidity and specific internal energy\",\n            \"\",\n        ),\n    )\n    Variables[\"E_k\"] = DiagnosticVariable(\n        \"E_k\",\n        var_attrib(\n            \"\",\n            \"volumetrically-averaged dimensionless kinetic energy\",\n            \"\",\n        ),\n    )\n    Variables[\"dE\"] = DiagnosticVariable(\n        \"dE\",\n        var_attrib(\n            \"\",\n            \"volumetrically-averaged kinetic energy dissipation\",\n            \"\",\n        ),\n    )\n    Variables[\"mass_loss\"] =\n        DiagnosticVariable(\"mass_loss\", var_attrib(\"\", \"\", \"\"))\n    Variables[\"energy_loss\"] =\n        DiagnosticVariable(\"energy_loss\", var_attrib(\"\", \"\", \"\"))\n\n    Variables[\"vort2\"] = DiagnosticVariable(\n        \"vort2\",\n        var_attrib(\"s^-1\", \"vorticity from DG kernels\", \"\"),\n    )\nend\n"
  },
  {
    "path": "src/Diagnostics/vorticity_balancelaw.jl",
    "content": "# A mini balance law for computing vorticity using the DG kernels.\n#\n\nusing StaticArrays\n\nusing ..BalanceLaws\nusing ..VariableTemplates\nusing ..MPIStateArrays\n\nimport ..BalanceLaws:\n    vars_state,\n    flux_first_order!,\n    flux_second_order!,\n    source!,\n    eq_tends,\n    flux,\n    source,\n    wavespeed,\n    boundary_conditions,\n    boundary_state!,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    transform_post_gradient_laplacian!,\n    init_state_auxiliary!,\n    init_state_prognostic!,\n    update_auxiliary_state!,\n    indefinite_stack_integral!,\n    reverse_indefinite_stack_integral!,\n    integral_load_auxiliary_state!,\n    integral_set_auxiliary_state!,\n    reverse_integral_load_auxiliary_state!,\n    reverse_integral_set_auxiliary_state!\n\n\n# A mini balance law that is used to take the gradient of u and v\n# to obtain vorticity.\nstruct VorticityModel <: BalanceLaw end\n\n# output vorticity vector\nvars_state(::VorticityModel, ::Prognostic, FT) = @vars(Ω_bl::SVector{3, FT})\n# input velocity vector\nvars_state(::VorticityModel, ::Auxiliary, FT) = @vars(u::SVector{3, FT})\nvars_state(::VorticityModel, ::Gradient, FT) = @vars()\nvars_state(::VorticityModel, ::GradientFlux, FT) = @vars()\n\n# required for each balance law \nfunction init_state_auxiliary!(\n    m::VorticityModel,\n    state_auxiliary::MPIStateArray,\n    grid,\n    direction,\n) end\nfunction init_state_prognostic!(\n    ::VorticityModel,\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t,\n) end\nfunction nodal_update_auxiliary_state!(\n    m::VorticityModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) end;\nfunction flux_first_order!(\n    ::VorticityModel,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n)\n    u = aux.u\n    @inbounds begin\n        flux.Ω_bl = @SMatrix [\n            0 u[3] -u[2]\n            -u[3] 0 u[1]\n            u[2] -u[1] 0\n        ]\n    end\nend\nfunction compute_gradient_argument!(\n    ::VorticityModel,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) end\nflux_second_order!(::VorticityModel, _...) = nothing\nsource!(::VorticityModel, _...) = nothing\nboundary_conditions(::VorticityModel) = ntuple(i -> nothing, 6)\nboundary_state!(nf, ::Nothing, ::VorticityModel, _...) = nothing\n"
  },
  {
    "path": "src/Driver/Callbacks/Callbacks.jl",
    "content": "module Callbacks\n\nusing CUDA\nusing Dates\nusing KernelAbstractions\nusing LinearAlgebra\nusing MPI\nusing Printf\nusing Statistics\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: day\n\nusing ..ClimateMachine\nusing ..BalanceLaws\nusing ..Courant\nusing ..Checkpoint\nusing ..DGMethods\nusing ..BalanceLaws: vars_state, Prognostic, Auxiliary\nusing ..Diagnostics\nusing ..GenericCallbacks\nusing ..Mesh.Grids: EveryDirection, HorizontalDirection, VerticalDirection\nusing ..MPIStateArrays\nusing ..ODESolvers\nusing ..TicToc\nusing ..VariableTemplates\nusing ..VTK\n\n_sync_device(::Type{CuArray}) = synchronize()\n_sync_device(::Type{Array}) = nothing\n\n\"\"\"\n    SummaryLogCallback([stimeend])\n\nLog a summary of the current run.\n\nThe optional `stimeend` argument is used to print the total simulation time.\n\"\"\"\nmutable struct SummaryLogCallback\n    stimeend::Union{Nothing, Real}\n    wtimestart::DateTime # wall time at start of simulation\n    function SummaryLogCallback(stimeend = nothing)\n        new(stimeend, now())\n    end\nend\n\nfunction GenericCallbacks.init!(cb::SummaryLogCallback, solver, Q, param, t)\n    cb.wtimestart = now()\n    return nothing\nend\nfunction GenericCallbacks.call!(cb::SummaryLogCallback, solver, Q, param, t)\n    wallclock_time_ms = Dates.now() - cb.wtimestart\n    wallclock = Dates.format(\n        convert(Dates.DateTime, wallclock_time_ms),\n        Dates.dateformat\"HH:MM:SS\",\n    )\n    normQ = norm(Q)\n    if cb.stimeend === nothing\n        simtime = @sprintf \"%8.2f\" t\n    else\n        simtime = @sprintf \"%8.2f / %8.2f\" t cb.stimeend\n    end\n    wallclock_s = wallclock_time_ms.value / 1000\n    efficiency = @sprintf \"%8.4f\" t / wallclock_s\n\n    estimated_wallclock_end = cb.stimeend * wallclock_s / t\n\n    estimated_wallclock_end_ms =\n        Dates.Millisecond(ceil(Int, estimated_wallclock_end * 1000))\n\n    estimated_remaining = Dates.format(\n        convert(Dates.DateTime, estimated_wallclock_end_ms),\n        Dates.dateformat\"HH:MM:SS\",\n    )\n    @info @sprintf(\n        \"\"\"\n        Update\n            simtime = %s\n            wallclock = %s\n            efficiency (simtime / wallclock) = %s\n            wallclock end (estimated) = %s\n            norm(Q) = %.16e\"\"\",\n        simtime,\n        wallclock,\n        efficiency,\n        estimated_remaining,\n        normQ,\n    )\n    if !isfinite(normQ)\n        show_not_finite_fields(Q)\n        error(\"norm(Q) is not finite\")\n    end\n    return nothing\nend\nfunction GenericCallbacks.fini!(cb::SummaryLogCallback, solver, Q, param, t)\n    return nothing\nend\n\n\"\"\"\n    show_updates(show_updates_opt, solver_config, user_info_callback)\n\nReturn a callback function that shows simulation updates at the specified\ninterval and also invokes the user-specified info callback.\n\"\"\"\nfunction show_updates(show_updates_opt, solver_config, user_info_callback)\n    timeend = solver_config.timeend\n\n    cb_constr = CB_constructor(show_updates_opt, solver_config)\n    if cb_constr !== nothing\n        return cb_constr((\n            SummaryLogCallback(solver_config.timeend),\n            GenericCallbacks.AtInit(user_info_callback),\n        ))\n    else\n        return nothing\n    end\nend\n\n\"\"\"\n    diagnostics(diagnostics_opt, solver_config, dgn_starttime, diagnostics_config)\n\nReturn callback functions that shows simulation updates at the specified\ninterval and also invokes the user-specified info callback.\n\"\"\"\nfunction diagnostics(\n    diagnostics_opt,\n    solver_config,\n    dgn_starttime,\n    diagnostics_config,\n)\n    if diagnostics_opt != \"never\" && diagnostics_config !== nothing\n        # set up a callback for each diagnostics group\n        dgncbs = ()\n        for dgngrp in diagnostics_config.groups\n            cb_constr =\n                CB_constructor(diagnostics_opt, solver_config, dgngrp.interval)\n            cb_constr === nothing && continue\n            dgncbs = (dgncbs..., cb_constr(dgngrp))\n        end\n        return dgncbs\n    else\n        return nothing\n    end\nend\n\n\"\"\"\n    vtk(vtk_opt, solver_config)\n\nReturn a callback that saves state and auxiliary variables to a VTK\nfile.\n\"\"\"\nfunction vtk(vtk_opt, solver_config, output_dir, number_sample_points)\n    cb_constr = CB_constructor(vtk_opt, solver_config)\n    cb_constr === nothing && return nothing\n\n    vtknum = Ref(1)\n\n    mpicomm = solver_config.mpicomm\n    dg = solver_config.dg\n    bl = dg.balance_law\n    Q = solver_config.Q\n    FT = eltype(Q)\n\n    cb_vtk = GenericCallbacks.AtInitAndFini() do\n        # TODO: make an object\n        vprefix = @sprintf(\n            \"%s_mpirank%04d_num%04d\",\n            solver_config.name,\n            MPI.Comm_rank(mpicomm),\n            vtknum[],\n        )\n        outprefix = joinpath(output_dir, vprefix)\n\n        statenames = flattenednames(vars_state(bl, Prognostic(), FT))\n        auxnames = flattenednames(vars_state(bl, Auxiliary(), FT))\n\n        writevtk(\n            outprefix,\n            Q,\n            dg,\n            statenames,\n            dg.state_auxiliary,\n            auxnames;\n            number_sample_points = number_sample_points,\n        )\n\n        # Generate the pvtu file for these vtk files\n        if MPI.Comm_rank(mpicomm) == 0\n            # name of the pvtu file\n            pprefix = @sprintf(\"%s_num%04d\", solver_config.name, vtknum[])\n            pvtuprefix = joinpath(output_dir, pprefix)\n\n            # name of each of the ranks vtk files\n            prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n                @sprintf(\n                    \"%s_mpirank%04d_num%04d\",\n                    solver_config.name,\n                    i - 1,\n                    vtknum[],\n                )\n            end\n            writepvtu(\n                pvtuprefix,\n                prefixes,\n                (statenames..., auxnames...),\n                eltype(Q),\n            )\n        end\n\n        vtknum[] += 1\n        nothing\n    end\n    return cb_constr(cb_vtk)\nend\n\n\"\"\"\n    monitor_timestep_duration(mtd_opt, array_type, comm)\n\nReturns a callback function that displays wall-clock time per time-step\nstatistics across MPI ranks in the communicator `comm`.  The times are\naveraged over the `mtd_opt` time steps requested for output.  The\n`array_type` is used to synchronize the compute device.\n\"\"\"\nfunction monitor_timestep_duration(mtd_opt, array_type, comm)\n    if mtd_opt == \"never\"\n        return nothing\n    elseif !endswith(mtd_opt, \"steps\")\n        @warn @sprintf(\n            \"\"\"\n            monitor-timestep-duration must be in 'steps'; %s unrecognized; disabling\"\"\",\n            mtd_opt,\n        )\n        return nothing\n    end\n    steps = parse(Int, mtd_opt[1:(end - 5)])\n\n    _sync_device(array_type)\n    before = time_ns()\n\n    cb_mtd = GenericCallbacks.EveryXSimulationSteps(steps) do\n        _sync_device(array_type)\n        after = time_ns()\n\n        time_per_timesteps = after - before\n\n        times = MPI.Gather(time_per_timesteps, 0, comm)\n        if MPI.Comm_rank(comm) == 0\n            ns_per_s = 1e9\n            times = times ./ ns_per_s ./ steps\n\n            @info @sprintf(\n                \"\"\"\n                Wall-clock time per time-step (statistics across MPI ranks)\n                   maximum (s) = %25.16e\n                   minimum (s) = %25.16e\n                   median  (s) = %25.16e\n                   std     (s) = %25.16e\"\"\",\n                maximum(times),\n                minimum(times),\n                median(times),\n                std(times),\n            )\n        end\n\n        _sync_device(array_type)\n        before = time_ns()\n\n        nothing\n    end\n\n    return cb_mtd\nend\n\n\"\"\"\n    monitor_courant_numbers(mcn_opt, solver_config)\n\nReturn a callback function that displays Courant numbers for the simulation\nat `mcn_opt` intervals.\n\"\"\"\nfunction monitor_courant_numbers(mcn_opt, solver_config)\n    cb_constr = CB_constructor(mcn_opt, solver_config)\n    cb_constr === nothing && return nothing\n\n    cb_cfl = cb_constr() do\n        Δt = solver_config.dt\n        c_v = courant(\n            nondiffusive_courant,\n            solver_config,\n            direction = VerticalDirection(),\n        )\n        c_h = courant(\n            nondiffusive_courant,\n            solver_config,\n            direction = HorizontalDirection(),\n        )\n        ca_v = courant(\n            advective_courant,\n            solver_config,\n            direction = VerticalDirection(),\n        )\n        ca_h = courant(\n            advective_courant,\n            solver_config,\n            direction = HorizontalDirection(),\n        )\n        cd_v = courant(\n            diffusive_courant,\n            solver_config,\n            direction = VerticalDirection(),\n        )\n        cd_h = courant(\n            diffusive_courant,\n            solver_config,\n            direction = HorizontalDirection(),\n        )\n        simtime = ODESolvers.gettime(solver_config.solver)\n        @info @sprintf(\n            \"\"\"\n            Courant numbers at simtime: %8.2f, Δt = %8.2f s\n                Acoustic (vertical) Courant number    = %.2g\n                Acoustic (horizontal) Courant number  = %.2g\n                Advection (vertical) Courant number   = %.2g\n                Advection (horizontal) Courant number = %.2g\n                Diffusion (vertical) Courant number   = %.2g\n                Diffusion (horizontal) Courant number = %.2g\"\"\",\n            simtime,\n            Δt,\n            c_v,\n            c_h,\n            ca_v,\n            ca_h,\n            cd_v,\n            cd_h,\n        )\n        nothing\n    end\n    return cb_cfl\nend\n\nfunction adapt_timestep(adp_opt, solver_config)\n    cb_constr = CB_constructor(adp_opt, solver_config)\n    cb_constr === nothing && return nothing\n    cb_adp = cb_constr() do\n        dt = solver_config.solver.dt\n        dg = solver_config.dg\n        bl = dg.balance_law\n        Q = solver_config.Q\n        t0 = solver_config.t0\n        ode_solver_type = solver_config.ode_solver_type\n        dtmodel = getdtmodel(ode_solver_type, bl)\n        ndt = ClimateMachine.DGMethods.calculate_dt(\n            dg,\n            dtmodel,\n            Q,\n            solver_config.CFL,\n            t0,\n            solver_config.diffdir,\n        )\n        @info @sprintf(\"\"\"Updating time step: %8.16f => %8.16f\"\"\", dt, ndt)\n        updatedt!(solver_config.solver, ndt)\n        nothing\n    end\n    return cb_adp\nend\n\n\"\"\"\n    checkpoint(\n        checkpoint_opt,\n        checkpoint_keep_one,\n        solver_config,\n        checkpoint_dir,\n    )\n\nReturn a callback function that runs at `checkpoint_opt` intervals\nand stores a simulation checkpoint into `checkpoint_dir` identified\nby a running number.\n\"\"\"\nfunction checkpoint(\n    checkpoint_opt,\n    checkpoint_keep_one,\n    solver_config,\n    checkpoint_dir,\n)\n    cb_constr = CB_constructor(checkpoint_opt, solver_config)\n    cb_constr === nothing && return nothing\n\n    cpnum = Ref(1)\n    cb_checkpoint = cb_constr() do\n        write_checkpoint(\n            solver_config,\n            checkpoint_dir,\n            solver_config.name,\n            solver_config.mpicomm,\n            cpnum[],\n        )\n        if checkpoint_keep_one\n            rm_checkpoint(\n                checkpoint_dir,\n                solver_config.name,\n                solver_config.mpicomm,\n                cpnum[] - 1,\n            )\n        end\n        cpnum[] += 1\n        nothing\n    end\n    return cb_checkpoint\nend\n\n\"\"\"\n    ConsCallback(dg, mass, energy, show_cons)\n\nCheck mass and energy conservation against specified tolerances.\n\"\"\"\nmutable struct ConsCallback{FT}\n    bl::BalanceLaw\n    varname::String\n    error_threshold::FT\n    show::Bool\n    Σvar₀::FT\nend\n\nfunction GenericCallbacks.init!(cb::ConsCallback, solver, Q, param, t)\n    FT = eltype(Q)\n    idx = varsindices(vars_state(cb.bl, Prognostic(), FT), cb.varname)\n    cb.Σvar₀ = weightedsum(Q, idx)\n    return nothing\nend\nfunction GenericCallbacks.call!(cb::ConsCallback, solver, Q, param, t)\n    FT = eltype(Q)\n    idx = varsindices(vars_state(cb.bl, Prognostic(), FT), cb.varname)\n    Σvar = weightedsum(Q, idx)\n    δvar = (Σvar - cb.Σvar₀) / cb.Σvar₀\n\n    if abs(δvar) > cb.error_threshold\n        error(\"abs(δ$(cb.varname)) > $(cb.error_threshold)\")\n    end\n\n    if cb.show\n        simtime = @sprintf \"%8.2f\" t\n        @info @sprintf(\n            \"\"\"\n            Conservation\n                simtime = %s\n                abs(δ%s) = %.5e\"\"\",\n            simtime,\n            cb.varname,\n            abs(δvar),\n        )\n    end\n    return nothing\nend\nfunction GenericCallbacks.fini!(cb::ConsCallback, solver, Q, param, t)\n    return nothing\nend\n\n\"\"\"\n    check_cons(\n        check_cons,\n        solver_config,\n    )\n\nReturn a callback function for each element of `check_cons`, which must be\na tuple of [`ConservationCheck`s](@ref ClimateMachine.ConservationCheck).\n\"\"\"\nfunction check_cons(check_cons, solver_config)\n    cbs = ()\n    for cc in check_cons\n        cb_constr = CB_constructor(cc.interval, solver_config)\n        cb_constr === nothing && continue\n\n        FT = eltype(solver_config.Q)\n        cb = cb_constr((ConsCallback(\n            solver_config.dg.balance_law,\n            cc.varname,\n            FT(cc.error_threshold),\n            cc.show,\n            FT(0),\n        ),))\n\n        cbs = (cbs..., cb)\n    end\n\n    return cbs\nend\n\n\"\"\"\n    CB_constructor(interval, solver_config, default)\n\nParse the specified `interval` and return the appropriate `GenericCallbacks`\nconstructor. If `interval` is \"default\", then use `default` as the interval.\n\"\"\"\nfunction CB_constructor(interval::String, solver_config, default = nothing)\n    mpicomm = solver_config.mpicomm\n    solver = solver_config.solver\n    dg = solver_config.dg\n    bl = dg.balance_law\n    param_set = parameter_set(bl)\n    secs_per_day = day(param_set)\n\n    if (\n        m = match(r\"^([0-9\\.]+)(smonths|sdays|shours|smins|ssecs)$\", interval)\n    ) !== nothing\n        n = parse(Float64, m[1])\n        if m[2] == \"smonths\"\n            ssecs = 30 * secs_per_day * n\n        elseif m[2] == \"sdays\"\n            ssecs = secs_per_day * n\n        elseif m[2] == \"shours\"\n            ssecs = 60 * 60 * n\n        elseif m[2] == \"smins\"\n            ssecs = 60 * n\n        elseif m[2] == \"ssecs\"\n            ssecs = n\n        end\n        return cb -> GenericCallbacks.EveryXSimulationTime(cb, ssecs)\n    elseif (m = match(r\"^([0-9]+)(steps)$\", interval)) !== nothing\n        steps = parse(Int, m[1])\n        return cb -> GenericCallbacks.EveryXSimulationSteps(cb, steps)\n    elseif (m = match(r\"^([0-9\\.]+)(hours|mins|secs)$\", interval)) !== nothing\n        n = parse(Float64, m[1])\n        if m[2] == \"hours\"\n            secs = 60 * 60 * n\n        elseif m[2] == \"mins\"\n            secs = 60 * n\n        elseif m[2] == \"secs\"\n            secs = n\n        end\n        return cb -> GenericCallbacks.EveryXWallTimeSeconds(cb, secs, mpicomm)\n    elseif interval == \"default\"\n        if default === nothing\n            @warn \"no default available; ignoring\"\n            return nothing\n        elseif default === \"\"\n            return nothing\n        else\n            return CB_constructor(default, solver_config, \"\")\n        end\n    elseif interval == \"never\"\n        return nothing\n    else\n        @warn @sprintf(\n            \"\"\"\n%s: unrecognized interval; ignoring\"\"\",\n            interval,\n        )\n        return nothing\n    end\nend\n\nfunction __init__()\n    tictoc()\nend\n\nend # module\n"
  },
  {
    "path": "src/Driver/Checkpoint/Checkpoint.jl",
    "content": "module Checkpoint\n\nexport write_checkpoint, rm_checkpoint, read_checkpoint\n\nusing JLD2\nusing MPI\nusing Printf\nimport KernelAbstractions: CPU\n\nusing ..ODESolvers\nusing ..ODESolvers: AbstractODESolver\nusing ..MPIStateArrays\nimport ..MPIStateArrays: array_device\n\n\"\"\"\n    write_checkpoint(solver_config, checkpoint_dir, name, mpicomm, num)\n\nRead in the state and auxiliary arrays as well as the simulation time\nstored in the checkpoint file for `name` and `num`.\n\"\"\"\nfunction write_checkpoint(\n    solver_config,\n    checkpoint_dir::String,\n    name::String,\n    mpicomm::MPI.Comm,\n    num::Int,\n)\n    Q = solver_config.Q\n    A = solver_config.dg.state_auxiliary\n    odesolver = solver_config.solver\n\n    write_checkpoint(Q, A, odesolver, checkpoint_dir, name, mpicomm, num)\n\n    return nothing\nend\n\nfunction write_checkpoint(\n    Q::MPIStateArray,\n    A::MPIStateArray,\n    odesolver::AbstractODESolver,\n    checkpoint_dir::String,\n    name::String,\n    mpicomm::MPI.Comm,\n    num::Int,\n)\n    nm = replace(name, \" \" => \"_\")\n    cname = @sprintf(\n        \"%s_checkpoint_mpirank%04d_num%04d.jld2\",\n        nm,\n        MPI.Comm_rank(mpicomm),\n        num,\n    )\n    cfull = joinpath(checkpoint_dir, cname)\n    @info @sprintf(\n        \"\"\"\nCheckpoint\n    saving to %s\"\"\",\n        cfull\n    )\n\n    if array_device(Q) isa CPU\n        h_Q = Q.realdata\n        h_aux = A.realdata\n    else\n        h_Q = Array(Q.realdata)\n        h_aux = Array(A.realdata)\n    end\n    t = ODESolvers.gettime(odesolver)\n    @save cfull h_Q h_aux t\n\n    return nothing\nend\n\n\"\"\"\n    rm_checkpoint(checkpoint_dir, name, mpicomm, num)\n\nRemove the checkpoint file identified by `solver_config.name` and `num`.\n\"\"\"\nfunction rm_checkpoint(\n    checkpoint_dir::String,\n    name::String,\n    mpicomm::MPI.Comm,\n    num::Int,\n)\n    nm = replace(name, \" \" => \"_\")\n    cname = @sprintf(\n        \"%s_checkpoint_mpirank%04d_num%04d.jld2\",\n        nm,\n        MPI.Comm_rank(mpicomm),\n        num,\n    )\n    rm(joinpath(checkpoint_dir, cname), force = true)\n\n    return nothing\nend\n\n\"\"\"\n    read_checkpoint(checkpoint_dir, name, mpicomm, num)\n\nRead in the state and auxiliary arrays as well as the simulation time\nstored in the checkpoint file for `name` and `num`.\n\"\"\"\nfunction read_checkpoint(\n    checkpoint_dir::String,\n    name::String,\n    array_type,\n    mpicomm::MPI.Comm,\n    num::Int,\n)\n    nm = replace(name, \" \" => \"_\")\n    cname = @sprintf(\n        \"%s_checkpoint_mpirank%04d_num%04d.jld2\",\n        nm,\n        MPI.Comm_rank(mpicomm),\n        num,\n    )\n    cfull = joinpath(checkpoint_dir, cname)\n    if !isfile(cfull)\n        error(\"Cannot restore from checkpoint in $(cfull), file not found\")\n    end\n\n    @load cfull h_Q h_aux t\n\n    return (array_type(h_Q), array_type(h_aux), t)\nend\n\nend # module\n"
  },
  {
    "path": "src/Driver/ConfigTypes/ConfigTypes.jl",
    "content": "\"\"\"\n    ConfigTypes\n\nModule containing ClimateMachine configuration types.\n\"\"\"\nmodule ConfigTypes\n\nexport ClimateMachineConfigType,\n    AtmosConfigType,\n    AtmosLESConfigType,\n    AtmosGCMConfigType,\n    OceanConfigType,\n    OceanBoxGCMConfigType,\n    OceanSplitExplicitConfigType,\n    SingleStackConfigType,\n    MultiColumnLandConfigType\n\nabstract type ClimateMachineConfigType end\nabstract type AtmosConfigType <: ClimateMachineConfigType end\nstruct AtmosLESConfigType <: AtmosConfigType end\nstruct AtmosGCMConfigType <: AtmosConfigType end\nabstract type OceanConfigType <: ClimateMachineConfigType end\nstruct OceanBoxGCMConfigType <: OceanConfigType end\nstruct OceanSplitExplicitConfigType <: OceanConfigType end\nstruct SingleStackConfigType <: ClimateMachineConfigType end\nstruct MultiColumnLandConfigType <: ClimateMachineConfigType end\nend\n"
  },
  {
    "path": "src/Driver/Driver.jl",
    "content": "using Base.Threads\n\nusing ArgParse\nusing CUDA\nusing Dates\nusing LinearAlgebra\nusing Logging\nusing MPI\nusing Printf\nusing Random\n\nusing CLIMAParameters\n\nusing ..Atmos\nusing ..Callbacks\nusing ..Checkpoint\nusing ..SystemSolvers\nusing ..ConfigTypes\nusing ..Diagnostics\nusing ..DiagnosticsMachine\nusing ..DGMethods\nusing ..BalanceLaws\nusing ..DGMethods: remainder_DGModel, SpaceDiscretization\nusing ..DGMethods.NumericalFluxes\nusing ..DGMethods.FVReconstructions\n\nusing ..Mesh.Grids\nusing ..Mesh.Topologies\nusing ..Mesh.Filters\nusing Thermodynamics\nusing ..MPIStateArrays\nusing ..ODESolvers\nusing ..TicToc\nusing ..VariableTemplates\nusing ..VTK\n\nfunction _init_array(::Type{CuArray})\n    comm = MPI.COMM_WORLD\n    # allocate GPUs among MPI ranks\n    local_comm =\n        MPI.Comm_split_type(comm, MPI.MPI_COMM_TYPE_SHARED, MPI.Comm_rank(comm))\n    # we intentionally oversubscribe GPUs for testing: may want to disable this for production\n    CUDA.device!(MPI.Comm_rank(local_comm) % length(devices()))\n    CUDA.allowscalar(false)\n    return nothing\nend\n\n_init_array(::Type{Array}) = nothing\n\nfunction gpu_allowscalar(val::Bool)\n    CUDA.allowscalar(val)\n    return\nend\n\n# Note that the initial values specified here are overwritten by the\n# command line argument defaults in `parse_commandline()`.\nBase.@kwdef mutable struct ClimateMachine_Settings\n    disable_gpu::Bool = false\n    show_updates::String = \"60secs\"\n    start_datetime::DateTime = DateTime(2000, 1, 1, 12)\n    diagnostics::String = \"never\"\n    no_overwrite::Bool = false\n    vtk::String = \"never\"\n    vtk_number_sample_points::Int = 0\n    monitor_timestep_duration::String = \"never\"\n    monitor_courant_numbers::String = \"never\"\n    adapt_timestep::String = \"never\"\n    checkpoint::String = \"never\"\n    checkpoint_keep_one::Bool = true\n    checkpoint_at_end::Bool = false\n    checkpoint_on_crash::Bool = false\n    checkpoint_dir::String = \"checkpoint\"\n    restart_from_num::Int = -1\n    fix_rng_seed::Bool = false\n    log_level::String = \"INFO\"\n    disable_custom_logger::Bool = false\n    output_dir::String = \"output\"\n    debug_init::Bool = false\n    integration_testing::Bool = false\n    array_type::Type = Array\n    sim_time::Float64 = NaN\n    fixed_number_of_steps::Int = -1\n    degree::NTuple{2, Int} = (-1, -1)\n    cutoff_degree::NTuple{2, Int} = (-1, -1)\n    nelems::NTuple{3, Int} = (-1, -1, -1)\n    domain_height::Float64 = -1\n    resolution::NTuple{3, Float64} = (-1, -1, -1)\n    domain_min::NTuple{3, Float64} = (-1, -1, -1)\n    domain_max::NTuple{3, Float64} = (-1, -1, -1)\nend\n\nconst Settings = ClimateMachine_Settings()\n\n\"\"\"\n    ClimateMachine.array_type()\n\nReturn the array type used by ClimateMachine. This defaults to (CPU-based)\n`Array` and is only correctly set (based on choice from the command\nline, from an environment variable, or from experiment code) after\n`ClimateMachine.init()` is called.\n\"\"\"\narray_type() = Settings.array_type\n\n\"\"\"\n    ClimateMachine.datetime(solver::AbstractODESolver)\n\nReturn the current simulation date and time.\n\"\"\"\nfunction datetime(solver::AbstractODESolver)\n    simtime = gettime(solver)\n    return Settings.start_datetime + Second(round(Int, simtime))\nend\n\n\"\"\"\n    get_setting(setting_name::Symbol, settings, defaults)\n\nDefine fallback behavior for driver settings, first accessing overloaded\n`settings` if defined, followed by constructed global ENV variable\n`CLIMATEMACHINE_SETTINGS_<SETTING_NAME>`, then (global) defaults.\n\nReturns setting value.\n\"\"\"\nfunction get_setting(setting_name::Symbol, settings, defaults)\n    if !haskey(defaults, setting_name)\n        error(\"setting $setting_name is not defined in `defaults`\")\n    end\n    setting_type = typeof(defaults[setting_name])\n    setting_env = \"CLIMATEMACHINE_SETTINGS_\" * uppercase(String(setting_name))\n    if haskey(settings, setting_name)\n        return convert(setting_type, settings[setting_name])\n    elseif haskey(ENV, setting_env)\n        env_val = ENV[setting_env]\n        if setting_type == String\n            v = env_val\n        elseif setting_type == NTuple{2, Int} ||\n               setting_type == NTuple{3, Float64} ||\n               setting_type == DateTime\n            v = ArgParse.parse_item(setting_type, env_val)\n        else\n            v = tryparse(setting_type, env_val)\n        end\n        if isnothing(v)\n            error(\"cannot parse ENV $setting_env value $env_val, to setting type $setting_type\")\n        end\n        return v\n    elseif haskey(defaults, setting_name)\n        return defaults[setting_name]\n    else\n        error(\"setting $setting_name is not contained in either settings or defaults\")\n    end\nend\n\nfunction get_gpu_setting(setting_name::Symbol, settings, defaults)\n    # do not override disable_gpu keyword argument setting if it exists\n    if !haskey(settings, setting_name)\n        # if old GPU ENV keyword exists, overwrite the settings variable if not defined\n        if haskey(ENV, \"CLIMATEMACHINE_GPU\")\n            settings[setting_name] = ENV[\"CLIMATEMACHINE_GPU\"] == \"false\"\n        end\n    end\n    # fallback behavior\n    return get_setting(setting_name, settings, defaults)\nend\n\n\"\"\"\n    parse_commandline(\n        defaults::Dict{Symbol, Any},\n        global_defaults::Dict{Symbol, Any},\n        custom_clargs::Union{Nothing, ArgParseSettings} = nothing,\n    )\n\nParse process command line ARGS values. If `defaults` is specified, it\noverrides default values for command line argument defaults. If\n`custom_clargs` is specified, it is added to the parsing step.\n\nReturns a `Dict` containing parsed process ARGS values.\n\"\"\"\nfunction parse_commandline(\n    defaults::Dict{Symbol, Any},\n    global_defaults::Dict{Symbol, Any},\n    custom_clargs::Union{Nothing, ArgParseSettings} = nothing,\n)\n    exc_handler = ArgParse.default_handler\n    if Base.isinteractive()\n        exc_handler = ArgParse.debug_handler\n    end\n    s = ArgParseSettings(\n        prog = PROGRAM_FILE,\n        description = \"Climate Machine: an Earth System Model that automatically learns from data\\n\",\n        preformatted_description = true,\n        epilog = \"\"\"\n            Any <interval> unless otherwise stated may be specified as:\n                - 2hours or 10mins or 30secs => wall-clock time\n                - 9.5smonths or 3.3sdays or 1.5shours => simulation time\n                - 1000steps => simulation steps\n                - never => disable\n                - default => use experiment specified interval (only for diagnostics at present)\n            \"\"\",\n        preformatted_epilog = true,\n        version = string(CLIMATEMACHINE_VERSION),\n        exc_handler = exc_handler,\n        autofix_names = true,     # switches --flag-name to 'flag_name'\n        error_on_conflict = true, # don't allow custom_clargs' settings to override these\n    )\n    add_arg_group!(s, \"ClimateMachine\")\n\n    @add_arg_table! s begin\n        \"--disable-gpu\"\n        help = \"do not use the GPU\"\n        action = :store_const\n        constant = true\n        default = get_gpu_setting(:disable_gpu, defaults, global_defaults)\n        \"--show-updates\"\n        help = \"interval at which to show simulation updates\"\n        metavar = \"<interval>\"\n        arg_type = String\n        default = get_setting(:show_updates, defaults, global_defaults)\n        \"--start-datetime\"\n        help = \"specify simulation start date/time as 'yyyy-mm-ddTHH:MM:SS'\"\n        metavar = \"<date/time>\"\n        arg_type = DateTime\n        default = get_setting(:start_datetime, defaults, global_defaults)\n        \"--diagnostics\"\n        help = \"interval at which to collect diagnostics\"\n        metavar = \"<interval>\"\n        arg_type = String\n        default = get_setting(:diagnostics, defaults, global_defaults)\n        \"--no-overwrite\"\n        help = \"throw an error if an output file would be overwritten\"\n        action = :store_const\n        constant = true\n        default = get_setting(:no_overwrite, defaults, global_defaults)\n        \"--vtk\"\n        help = \"interval at which to output VTK\"\n        metavar = \"<interval>\"\n        arg_type = String\n        default = get_setting(:vtk, defaults, global_defaults)\n        \"--vtk-number-sample-points\"\n        help = \"number of sampling points in each element for VTK output\"\n        metavar = \"<number>\"\n        arg_type = Int\n        default =\n            get_setting(:vtk_number_sample_points, defaults, global_defaults)\n        \"--monitor-timestep-duration\"\n        help = \"interval in time-steps at which to output wall-clock time per time-step\"\n        metavar = \"<interval>\"\n        arg_type = String\n        default =\n            get_setting(:monitor_timestep_duration, defaults, global_defaults)\n        \"--monitor-courant-numbers\"\n        help = \"interval at which to output acoustic, advective, and diffusive Courant numbers\"\n        metavar = \"<interval>\"\n        arg_type = String\n        default =\n            get_setting(:monitor_courant_numbers, defaults, global_defaults)\n        \"--adapt-timestep\"\n        help = \"interval at which to update the timestep\"\n        metavar = \"<interval>\"\n        arg_type = String\n        default = get_setting(:adapt_timestep, defaults, global_defaults)\n        \"--checkpoint\"\n        help = \"interval at which to create a checkpoint\"\n        metavar = \"<interval>\"\n        arg_type = String\n        default = get_setting(:checkpoint, defaults, global_defaults)\n        \"--checkpoint-keep-all\"\n        help = \"keep all checkpoints (instead of just the most recent)\"\n        action = :store_const\n        constant = true\n        default = !get_setting(:checkpoint_keep_one, defaults, global_defaults)\n        \"--checkpoint-at-end\"\n        help = \"create a checkpoint at the end of the simulation\"\n        action = :store_const\n        constant = true\n        default = get_setting(:checkpoint_at_end, defaults, global_defaults)\n        \"--checkpoint-on-crash\"\n        help = \"create a checkpoint on a kernel crash (hurts performance!)\"\n        action = :store_const\n        constant = true\n        default = get_setting(:checkpoint_on_crash, defaults, global_defaults)\n        \"--checkpoint-dir\"\n        help = \"the directory in which to store checkpoints\"\n        metavar = \"<path>\"\n        arg_type = String\n        default = get_setting(:checkpoint_dir, defaults, global_defaults)\n        \"--restart-from-num\"\n        help = \"checkpoint number from which to restart (in <checkpoint-dir>)\"\n        metavar = \"<number>\"\n        arg_type = Int\n        default = get_setting(:restart_from_num, defaults, global_defaults)\n        \"--fix-rng-seed\"\n        help = \"set RNG seed to a fixed value for reproducibility\"\n        action = :store_const\n        constant = true\n        default = get_setting(:fix_rng_seed, defaults, global_defaults)\n        \"--disable-custom-logger\"\n        help = \"do not use a custom logger\"\n        action = :store_const\n        constant = true\n        default = get_setting(:disable_custom_logger, defaults, global_defaults)\n        \"--log-level\"\n        help = \"set the log level to one of debug/info/warn/error\"\n        metavar = \"<level>\"\n        arg_type = String\n        default = uppercase(get_setting(:log_level, defaults, global_defaults))\n        \"--output-dir\"\n        help = \"directory for output data\"\n        metavar = \"<path>\"\n        arg_type = String\n        default = get(defaults, :output_dir) do\n            get(ENV, \"CLIMATEMACHINE_OUTPUT_DIR\") do\n                get(ENV, \"CLIMATEMACHINE_SETTINGS_OUTPUT_DIR\", Settings.output_dir)\n            end\n        end\n        \"--debug-init\"\n        help = \"fill state arrays with NaNs and dump them post-initialization\"\n        action = :store_const\n        constant = true\n        default = get_setting(:debug_init, defaults, global_defaults)\n        \"--integration-testing\"\n        help = \"enable integration testing\"\n        action = :store_const\n        constant = true\n        default = get_setting(:integration_testing, defaults, global_defaults)\n        \"--sim-time\"\n        help = \"run for the specified time (in simulation seconds)\"\n        metavar = \"<number>\"\n        arg_type = Float64\n        default = get_setting(:sim_time, defaults, global_defaults)\n        \"--fixed-number-of-steps\"\n        help = \"if `≥0` perform specified number of steps\"\n        metavar = \"<number>\"\n        arg_type = Int\n        default = get_setting(:fixed_number_of_steps, defaults, global_defaults)\n        \"--degree\"\n        help = \"tuple of horizontal and vertical polynomial degrees for spatial discretization order (no space before/after comma)\"\n        metavar = \"<horizontal>,<vertical>\"\n        arg_type = NTuple{2, Int}\n        default = get_setting(:degree, defaults, global_defaults)\n        \"--cutoff-degree\"\n        help = \"tuple of horizontal and vertical polynomial degrees to keep when applying a cutoff filter (no space before/after comma)\"\n        metavar = \"<horizontal>,<vertical>\"\n        arg_type = NTuple{2, Int}\n        default = get_setting(:cutoff_degree, defaults, global_defaults)\n        \"--nelems\"\n        help = \"number of elements in each direction: 3 for Ocean GCM, 2 for Atmos GCM or 1 for Atmos single-stack (no space before/after comma)\"\n        metavar = \"<nelem_1>[,<nelem_2>[,<nelem_3>]]\"\n        arg_type = NTuple{3, Int}\n        default = get_setting(:nelems, defaults, global_defaults)\n        \"--domain-height\"\n        help = \"domain height (in meters) for GCM or single-stack configurations\"\n        metavar = \"<number>\"\n        arg_type = Float64\n        default = get_setting(:domain_height, defaults, global_defaults)\n        \"--resolution\"\n        help = \"tuple of three element resolutions (in meters) for LES and MultiColumnLandModel configurations\"\n        metavar = \"<Δx>,<Δy>,<Δz>\"\n        arg_type = NTuple{3, Float64}\n        default = get_setting(:resolution, defaults, global_defaults)\n        \"--domain-min\"\n        help = \"tuple of three minima for the domain size (in meters) for LES and MultiColumnLandModel configurations\"\n        metavar = \"<xmin>,<ymin>,<zmin>\"\n        arg_type = NTuple{3, Float64}\n        default = get_setting(:domain_min, defaults, global_defaults)\n        \"--domain-max\"\n        help = \"tuple of three maxima for the domain size (in meters) for LES and MultiColumnLandModel configurations\"\n        metavar = \"<xmax>,<ymax>,<zmax>\"\n        arg_type = NTuple{3, Float64}\n        default = get_setting(:domain_max, defaults, global_defaults)\n    end\n    # add custom cli argparse settings if provided\n    if !isnothing(custom_clargs)\n        import_settings!(s, custom_clargs)\n    end\n    return parse_args(s)\nend\n\n\"\"\"\n    ClimateMachine.init(;\n        parse_clargs::Bool = false,\n        custom_clargs::Union{Nothing, ArgParseSettings} = nothing,\n        init_driver::Bool = true,\n        keyword_args...,\n    )\n\nInitialize the ClimateMachine. If `parse_clargs` is set, parse command line\narguments (additional driver-specific arguments can be added by specifying\n`custom_clargs`).\n\nSetting `init_driver = false` will set up the `ClimateMachine.Settings`\nsingleton values without initializing the ClimateMachine runtime. Otherwise,\nthe runtime will be initialized (see `init_runtime()`).\n\nFinally, key-value pairs can be supplied to `ClimateMachine.init()` to set\nsystem default settings -- the final settings are decided as follows (in\norder of precedence):\n1. Command line arguments (if `parse_clargs = true`).\n2. Environment variables.\n3. Keyword arguments to `init()`.\n4. Defaults (in `ClimateMachine_Settings`).\n\nRecognized keyword arguments are:\n- `disable_gpu::Bool = false`:\n        do not use the GPU\n- `show_updates::String = \"60secs\"`:\n        interval at which to show simulation updates\n- `start_datetime::DateTime = DateTime(2000, 1, 1, 12)`:\n        date/time at which the simulation starts\n- `diagnostics::String = \"never\"`:\n        interval at which to collect diagnostics\"\n- `no_overwrite::Bool = false`:\n        throw an error if an output file would be overwritten\n- `vtk::String = \"never\"`:\n        interval at which to write simulation vtk output\n- `vtk-number-sample-points::Int` = 0:\n        the number of sampling points in each element for VTK output\n- `monitor_timestep_duration::String = \"never\"`:\n        interval in time-steps at which to output wall-clock time per time-step\n- `monitor_courant_numbers::String = \"never\"`:\n        interval at which to output acoustic, advective, and diffusive Courant numbers\n- `adapt-timestep::String = \"never\"`:\n        interval at which to update the timestep\n- `checkpoint::String = \"never\"`:\n        interval at which to write a checkpoint\n- `checkpoint_keep_one::Bool = true`: (interval)\n        keep all checkpoints (instead of just the most recent)\n- `checkpoint_at_end::Bool = false`:\n        create a checkpoint at the end of the simulation\n- `checkpoint_on_crash::Bool = false`:\n    create a checkpoint on a kernel crash (hurts performance!)\n- `checkpoint_dir::String = \"checkpoint\"`:\n        absolute or relative path to checkpoint directory\n- `restart_from_num::Int = -1`:\n        checkpoint number from which to restart (in `checkpoint_dir`)\n- `fix_rng_seed::Bool = false`:\n        set RNG seed to a fixed value for reproducibility\n- `log_level::String = \"INFO\"`:\n        log level for ClimateMachine global default runtime logger\n- `disable_custom_logger::String = false`:\n        disable using a global custom logger for ClimateMachine\n- `output_dir::String = \"output\"`: (path)\n        absolute or relative path to output data directory\n- `debug_init::Bool = false`:\n        fill state arrays with NaNs and dump them post-initialization\n- `integration_testing::Bool = false`:\n        enable integration_testing\n- `sim_time::Float64 = NaN`:\n        run for the specified time (in simulation seconds)\n- `fixed_number_of_steps::Int = -1`:\n        if `≥0` perform specified number of steps\n- `degree::NTuple{2, Int} = (-1, -1)`:\n        tuple of horizontal and vertical polynomial degrees for spatial discretization order\n- `cutoff_degree::NTuple{2, Int} = (-1, -1)`:\n        tuple of horizontal and vertical polynomial degrees for cutoff filter\n- `nelems::NTuple{3, Int} = (-1, -1, -1)`:\n        tuple of number of elements in each direction: 3 for Ocean, 2 for GCM or 1 for single-stack\n- `domain_height::Float64 = -1`:\n        domain height (in meters) for GCM or single-stack configurations\n- `resolution::NTuple{3, Float64} = (-1, -1, -1)`:\n        tuple of three element resolutions (in meters) for LES and MultiColumnLandModel configurations\n- `domain_min::NTuple{3, Float64} = (-1, -1, -1)`:\n        tuple of three minima for the domain size (in meters) for LES and MultiColumnLandModel configurations\n- `domain_max::NTuple{3, Float64} = (-1, -1, -1)`:\n        tuple of three maxima for the domain size (in meters) for LES and MultiColumnLandModel configurations\n\nReturns `nothing`, or if `parse_clargs = true`, returns parsed command line\narguments.\n\"\"\"\nfunction init(;\n    parse_clargs::Bool = false,\n    custom_clargs::Union{Nothing, ArgParseSettings} = nothing,\n    init_driver::Bool = true,\n    keyword_args...,\n)\n    # `Settings` contains global defaults\n    global_defaults = Dict{Symbol, Any}(\n        (n, getproperty(Settings, n)) for n in propertynames(Settings)\n    )\n\n    # keyword arguments must be applicable to `Settings`\n    all_args = Dict{Symbol, Any}(keyword_args)\n    for kwarg in keys(all_args)\n        if get(global_defaults, kwarg, nothing) === nothing\n            throw(ArgumentError(string(kwarg)))\n        end\n    end\n\n    # if command line arguments should be processed, do so and override\n    # keyword arguments\n    cl_args = nothing\n    if parse_clargs\n        cl_args = parse_commandline(all_args, global_defaults, custom_clargs)\n\n        # We need to munge the parsed arg dict a bit as parsed arg keys\n        # and initialization keywords are not 1:1\n        cl_args[\"checkpoint_keep_one\"] = !cl_args[\"checkpoint_keep_all\"]\n\n        # parsed command line arguments override keyword arguments\n        all_args = merge(\n            all_args,\n            Dict{Symbol, Any}((Symbol(k), v) for (k, v) in cl_args),\n        )\n    end\n\n    # TODO: also add validation for initialization values\n\n    # Here, `all_args` contains command line arguments and keyword arguments.\n    # They must be applied to `Settings`.\n    #\n    # special cases for backward compatibility\n    if haskey(all_args, :disable_gpu)\n        Settings.disable_gpu = all_args[:disable_gpu]\n    elseif haskey(ENV, \"CLIMATEMACHINE_GPU\")\n        @warn(\n            \"CLIMATEMACHINE_GPU will be deprecated; \" *\n            \"use CLIMATEMACHINE_SETTINGS_DISABLE_GPU\"\n        )\n        Settings.disable_gpu = ENV[\"CLIMATEMACHINE_GPU\"] == \"false\"\n    elseif haskey(ENV, \"CLIMATEMACHINE_SETTINGS_DISABLE_GPU\")\n        Settings.disable_gpu =\n            parse(Bool, ENV[\"CLIMATEMACHINE_SETTINGS_DISABLE_GPU\"])\n    end\n\n    if haskey(all_args, :output_dir)\n        Settings.output_dir = all_args[:output_dir]\n    elseif haskey(ENV, \"CLIMATEMACHINE_OUTPUT_DIR\")\n        @warn(\n            \"CLIMATEMACHINE_OUTPUT_DIR will be deprecated; \" *\n            \"use CLIMATEMACHINE_SETTINGS_OUTPUT_DIR\"\n        )\n        Settings.output_dir = ENV[\"CLIMATEMACHINE_OUTPUT_DIR\"]\n    elseif haskey(ENV, \"CLIMATEMACHINE_SETTINGS_OUTPUT_DIR\")\n        Settings.output_dir = ENV[\"CLIMATEMACHINE_SETTINGS_OUTPUT_DIR\"]\n    end\n\n    # all other settings\n    for n in propertynames(Settings)\n        # skip over the special backwards compat cases defined above\n        if n == :disable_gpu || n == :output_dir\n            continue\n        end\n        setproperty!(Settings, n, get_setting(n, all_args, global_defaults))\n    end\n\n    # set up the array type appropriately depending on whether we're using GPUs\n    if !Settings.disable_gpu && CUDA.has_cuda_gpu()\n        Settings.array_type = CUDA.CuArray\n    else\n        Settings.array_type = Array\n    end\n\n    # initialize the runtime\n    if init_driver\n        init_runtime(Settings)\n    end\n    return cl_args\nend\n\n\"\"\"\n    init_runtime(settings::ClimateMachine_Settings)\n\nInitialize the ClimateMachine runtime: initialize MPI, set the default\narray type, set RNG seeds for all threads, create output directory and\nset up logging.\n\"\"\"\nfunction init_runtime(settings::ClimateMachine_Settings)\n    # set up timing mechanism\n    tictoc()\n\n    # initialize MPI\n    if !MPI.Initialized()\n        MPI.Init()\n    end\n\n    # initialize the array GPU backend if appropriate\n    _init_array(settings.array_type)\n\n    # fix the RNG seeds if requested\n    if settings.fix_rng_seed\n        rank = MPI.Comm_rank(MPI.COMM_WORLD)\n        for tid in 1:nthreads()\n            s = 1000 * rank + tid\n            Random.seed!(Random.default_rng(tid), s)\n        end\n    end\n\n    # TODO: write a better MPI logging back-end and also integrate Dlog\n    # for large scale\n\n    # set up logging\n    log_level_str = uppercase(settings.log_level)\n    loglevel =\n        log_level_str == \"DEBUG\" ? Logging.Debug :\n        log_level_str == \"WARN\" ? Logging.Warn :\n        log_level_str == \"ERROR\" ? Logging.Error : Logging.Info\n    if !settings.disable_custom_logger\n        # cannot use `NullLogger` here because MPI collectives may be\n        # used in logging calls!\n        logger_stream = MPI.Comm_rank(MPI.COMM_WORLD) == 0 ? stderr : devnull\n        prev_logger = global_logger(ConsoleLogger(logger_stream, loglevel))\n        atexit() do\n            global_logger(prev_logger)\n        end\n    end\n    return\nend\n\ninclude(\"driver_configs.jl\")\ninclude(\"solver_configs.jl\")\ninclude(\"diagnostics_configs.jl\")\n\n\"\"\"\n    ClimateMachine.ConservationCheck\n\nPass a tuple of these to `ClimateMachine.invoke!` to perform a\nconservation check of each `varname` at the specified `interval`. This\ncomputes `Σv = weightedsum(Q.varname)` and `δv = (Σv - Σv₀) / Σv`.\n`invoke!` throws an error if `abs(δv)` exceeds `error_threshold. If\n`show`, `δv` is displayed.\n\"\"\"\nstruct ConservationCheck{FT}\n    varname::String\n    interval::String\n    error_threshold::FT\n    show::Bool\nend\nConservationCheck(varname::String, interval::String) =\n    ConservationCheck(varname, interval, Inf, true)\nConservationCheck(\n    varname::String,\n    interval::String,\n    error_threshold::FT,\n) where {FT} = ConservationCheck(varname, interval, error_threshold, true)\n\n\"\"\"\n    ClimateMachine.invoke!(\n        solver_config::SolverConfiguration;\n        adjustfinalstep = false,\n        diagnostics_config = nothing,\n        user_callbacks = (),\n        user_info_callback = () -> nothing,\n        check_cons = (),\n        check_euclidean_distance = false,\n    )\n\nRun the simulation defined by `solver_config`.\n\nKeyword Arguments:\n\nThe value of 'adjustfinalstep` is passed to the ODE solver; see\n[`solve!`](@ref ODESolvers.solve!).\n\nThe `user_callbacks` are passed to the ODE solver as callback functions;\nsee [`solve!`](@ref ODESolvers.solve!).\n\nThe function `user_info_callback` is called after the default info\ncallback (which is called every `Settings.show_updates` interval). The\nsingle input argument `init` is `true` when the callback is called for\ninitialization (before time stepping begins) and `false` when called\nduring the actual ODE solve; see [`GenericCallbacks`](@ref) and\n[`solve!`](@ref ODESolvers.solve!).\n\nIf conservation checks are to be performed, `check_cons` must be a\ntuple of [`ConservationCheck`](@ref).\n\nIf `check_euclidean_distance` is `true, then the Euclidean distance\nbetween the final solution and initial condition function evaluated with\n`solver_config.timeend` is reported.\n\"\"\"\nfunction invoke!(\n    solver_config::SolverConfiguration;\n    adjustfinalstep = false,\n    diagnostics_config = nothing,\n    user_callbacks = (),\n    user_info_callback = () -> nothing,\n    check_cons = (),\n    check_euclidean_distance = false,\n)\n    mpicomm = solver_config.mpicomm\n    dg = solver_config.dg\n    bl = dg.balance_law\n    Q = solver_config.Q\n    FT = eltype(Q)\n    timeend = solver_config.timeend\n    init_on_cpu = solver_config.init_on_cpu\n    init_args = solver_config.init_args\n    solver = solver_config.solver\n\n    # create the output directories if needed on delegated rank\n    if MPI.Comm_rank(MPI.COMM_WORLD) == 0\n        if Settings.diagnostics != \"never\" || Settings.vtk != \"never\"\n            mkpath(Settings.output_dir)\n        end\n        if Settings.checkpoint != \"never\" ||\n           Settings.checkpoint_at_end ||\n           Settings.checkpoint_on_crash\n            mkpath(Settings.checkpoint_dir)\n        end\n    end\n    MPI.Barrier(MPI.COMM_WORLD)\n\n    # set up callbacks\n    callbacks = ()\n\n    # info callback\n    cb_updates = Callbacks.show_updates(\n        Settings.show_updates,\n        solver_config,\n        user_info_callback,\n    )\n    if !isnothing(cb_updates)\n        callbacks = (callbacks..., cb_updates)\n    end\n\n    # diagnostics callback(s)\n    if Settings.diagnostics != \"never\" && !isnothing(diagnostics_config)\n        dgn_starttime = replace(string(now()), \":\" => \".\")\n        Diagnostics.init(\n            mpicomm,\n            solver_config.param_set,\n            dg,\n            Q,\n            dgn_starttime,\n            Settings.output_dir,\n            Settings.no_overwrite,\n        )\n        DiagnosticsMachine.init(\n            mpicomm,\n            solver_config.param_set,\n            dg,\n            Q,\n            dgn_starttime,\n            Settings.output_dir,\n            Settings.no_overwrite,\n        )\n\n        dgncbs = Callbacks.diagnostics(\n            Settings.diagnostics,\n            solver_config,\n            dgn_starttime,\n            diagnostics_config,\n        )\n        callbacks = (callbacks..., dgncbs...)\n    end\n\n    # vtk callback\n    cb_vtk = Callbacks.vtk(\n        Settings.vtk,\n        solver_config,\n        Settings.output_dir,\n        Settings.vtk_number_sample_points,\n    )\n    if !isnothing(cb_vtk)\n        callbacks = (callbacks..., cb_vtk)\n    end\n\n    # timestep duration monitor\n    cb_mtd = Callbacks.monitor_timestep_duration(\n        Settings.monitor_timestep_duration,\n        Settings.array_type,\n        mpicomm,\n    )\n    if !isnothing(cb_mtd)\n        callbacks = (callbacks..., cb_mtd)\n    end\n\n    # Courant number monitor\n    cb_mcn = Callbacks.monitor_courant_numbers(\n        Settings.monitor_courant_numbers,\n        solver_config,\n    )\n    if !isnothing(cb_mcn)\n        callbacks = (callbacks..., cb_mcn)\n    end\n\n    # Timestep adapter\n    cb_adp = Callbacks.adapt_timestep(Settings.adapt_timestep, solver_config)\n    if !isnothing(cb_adp)\n        callbacks = (callbacks..., cb_adp)\n    end\n\n    # checkpointing callback\n    cb_checkpoint = Callbacks.checkpoint(\n        Settings.checkpoint,\n        Settings.checkpoint_keep_one,\n        solver_config,\n        Settings.checkpoint_dir,\n    )\n    if !isnothing(cb_checkpoint)\n        callbacks = (callbacks..., cb_checkpoint)\n    end\n\n    # conservation callbacks\n    cccbs = Callbacks.check_cons(check_cons, solver_config)\n    callbacks = (callbacks..., cccbs...)\n\n    # user callbacks\n    callbacks = (user_callbacks..., callbacks...)\n\n    # initial condition norm\n    eng0 = norm(Q)\n    @info @sprintf(\n        \"\"\"\n        %s %s\n            dt              = %.5e\n            timeend         = %8.2f\n            number of steps = %d\n            norm(Q)         = %.16e\"\"\",\n        Settings.restart_from_num > 0 ? \"Restarting\" : \"Starting\",\n        solver_config.name,\n        solver_config.dt,\n        solver_config.timeend,\n        solver_config.numberofsteps,\n        eng0\n    )\n\n    # run the simulation\n    try\n        @tic solve!\n        solve!(\n            Q,\n            solver;\n            timeend = timeend,\n            callbacks = callbacks,\n            adjustfinalstep = adjustfinalstep,\n        )\n        @toc solve!\n    catch exc\n        if Settings.checkpoint_on_crash\n            Callbacks.write_checkpoint(\n                solver_config,\n                Settings.checkpoint_dir,\n                solver_config.name,\n                mpicomm,\n                solver_config.numberofsteps,\n            )\n        end\n        rethrow()\n    end\n\n    # write end checkpoint if requested\n    if Settings.checkpoint_at_end\n        Callbacks.write_checkpoint(\n            solver_config,\n            Settings.checkpoint_dir,\n            solver_config.name,\n            mpicomm,\n            solver_config.numberofsteps,\n        )\n    end\n\n    engf = norm(Q)\n    @info @sprintf(\n        \"\"\"\n        Finished\n            norm(Q)            = %.16e\n            norm(Q) / norm(Q₀) = %.16e\n            norm(Q) - norm(Q₀) = %.16e\"\"\",\n        engf,\n        engf / eng0,\n        engf - eng0\n    )\n\n    if check_euclidean_distance\n        Qe =\n            init_ode_state(dg, timeend, init_args...; init_on_cpu = init_on_cpu)\n        engfe = norm(Qe)\n        errf = euclidean_distance(Q, Qe)\n        @info @sprintf(\n            \"\"\"\n            Euclidean distance\n                norm(Q - Qe)            = %.16e\n                norm(Q - Qe) / norm(Qe) = %.16e\"\"\",\n            errf,\n            errf / engfe\n        )\n    end\n\n    return engf / eng0\nend\n"
  },
  {
    "path": "src/Driver/SolverTypes/ExplicitSolverType.jl",
    "content": "export ExplicitSolverType\n\n\"\"\"\n# Description\n    ExplicitSolverType(;\n        solver_method = LSRK54CarpenterKennedy,\n    )\n\nThis solver type constructs an ODE solver using an explicit\nRunge-Kutta method.\n\n# Arguments\n- `solver_method` (Function): Function defining the explicit\n    Runge-Kutta solver.\n    Default: `LSRK54CarpenterKennedy`\n\"\"\"\nstruct ExplicitSolverType <: AbstractSolverType\n    # Function for an explicit Runge-Kutta method\n    solver_method::Function\n\n    function ExplicitSolverType(; solver_method = LSRK54CarpenterKennedy)\n\n        return new(solver_method)\n    end\nend\n\n\"\"\"\n    getdtmodel(ode_solver::AbstractSolverType, bl)\n\nA function which returns a model representing the dynamics\nwith the most restrictive time-stepping requirements.\n\"\"\"\nfunction getdtmodel(::ExplicitSolverType, bl)\n    # For explicit methods, the entire model itself\n    # contributes to the total stability of the time-integrator\n    return bl\nend\n\n\"\"\"\n# Description\n    function solversetup(\n        ode_solver::ExplicitSolverType,\n        dg,\n        Q,\n        dt,\n        t0,\n        diffusion_direction,\n    )\n\nCreates an explicit ODE solver.\n\"\"\"\nfunction solversetup(\n    ode_solver::ExplicitSolverType,\n    dg,\n    Q,\n    dt,\n    t0,\n    diffusion_direction,\n)\n    solver = ode_solver.solver_method(dg, Q; dt = dt, t0 = t0)\n\n    return solver\nend\n"
  },
  {
    "path": "src/Driver/SolverTypes/HEVISolverType.jl",
    "content": "\nexport HEVISolverType\n\n\"\"\"\n# Description\n    HEVISolverType(FT;\n        solver_method = ARK2ImplicitExplicitMidpoint,\n\n        linear_max_subspace_size = Int(30)\n        linear_atol = FT(-1.0)\n        linear_rtol = FT(5e-5)\n\n        nonlinear_max_iterations = Int(10)\n        nonlinear_rtol = FT(1e-4)\n        nonlinear_ϵ = FT(1.e-10)\n        preconditioner_update_freq = Int(50)\n    )\n\nThis solver type constructs a solver for ODEs with the\nadditively horizontal explicit vertical explicit~(HEVI) partitioned form. \nthe equation is assumed to be decomposed as\n\n```math\n  \\\\dot{Q} = [l(Q, t)] + [f(Q, t) - l(Q, t)]\n```\n\nwhere `Q` is the state, `f` is the full tendency and `l` is the vertical implicit\noperator. The splitting is done automatically.\n\n# Arguments\n- `solver_method` (Function): Function defining the particular additive\n    Runge-Kutta method to be used for the HEVI method.\n    Default: `ARK2ImplicitExplicitMidpoint`\n- `linear_max_subspace_size` (Int): maximal dimension of each (batched)\n    Krylov subspace. GEMRES, a iterative linear solver is applied \n    Default: `30`\n- `linear_atol` (FT): absolute tolerance for linear solver convergence.\n    Default: `-1.0`\n- `linear_rtol` (FT): relative tolerance for linear solver convergence.\n    Default: `5.0e-5`\n- `nonlinear_max_iterations` (Int): max number of Newton iterations\n    Default: `10`\n- `nonlinear_rtol` (FT): relative tolerance for nonlinear solver convergence.\n    Default: `1e-4`\n- `nonlinear_ϵ` (FT): parameter denoting finite different step size for the \n   Jacobian approximation.\n   Default: `1e-10`\n- `preconditioner_update_freq` (Int): Int denoting how frequent you need \n    to update the preconditioner \n    -1: no preconditioner;\n    positive number, update every freq times.\n    Default: `50`\n\"\"\"\nstruct HEVISolverType{FT} <: AbstractSolverType\n    # Function for the HEVI method\n    solver_method::Function\n\n    linear_max_subspace_size::Int\n    linear_atol::FT\n    linear_rtol::FT\n\n    nonlinear_max_iterations::Int\n    nonlinear_rtol::FT\n    nonlinear_ϵ::FT\n\n    preconditioner_update_freq::Int\n\n    function HEVISolverType(\n        FT;\n        solver_method = ARK2ImplicitExplicitMidpoint,\n        linear_max_subspace_size = Int(30),\n        linear_atol = FT(-1.0),\n        linear_rtol = FT(5e-5),\n        nonlinear_max_iterations = Int(10),\n        nonlinear_rtol = FT(1e-4),\n        nonlinear_ϵ = FT(1.e-10),\n        preconditioner_update_freq = Int(50),\n    )\n\n        return new{FT}(\n            solver_method,\n            linear_max_subspace_size,\n            linear_atol,\n            linear_rtol,\n            nonlinear_max_iterations,\n            nonlinear_rtol,\n            nonlinear_ϵ,\n            preconditioner_update_freq,\n        )\n    end\nend\n\n\"\"\"\n    getdtmodel(ode_solver::HEVISolverType, bl)\n\nA function which returns a model representing the dynamics\nwith the most restrictive time-stepping requirements.\n\"\"\"\nfunction getdtmodel(ode_solver::HEVISolverType, bl)\n    # Most restrictive dynamics are treated implicitly\n    return bl\nend\n\n\"\"\"\n# Description\n    solversetup(\n        ode_solver::HEVISolverType{FT},\n        dg,\n        Q,\n        dt,\n        t0,\n        diffusion_direction,\n    )\n\nCreates an ODE solver using a HEVI-type time-integration\nscheme. All horizontal acoustic waves are treated explicitly,\nwhile the 1-D vertical problem is treated implicitly. All\nother dynamics (advection, diffusion) is treated explicitly\nin the additive Runge-Kutta method.\n\n# Comments:\nCurrently, the only HEVI-type splitting ClimateMachine can currently\ndo only involves splitting the acoustic processes; it is not currently\npossible to perform more fine-grained separation of tendencies\n(for example, including vertical advection or diffusion in the 1-D implicit problem)\n\"\"\"\nfunction solversetup(\n    ode_solver::HEVISolverType,\n    dg,\n    Q,\n    dt,\n    t0,\n    diffusion_direction,\n)\n    # All we need to do is create a DGModel for the\n    # vertical acoustic waves (determined from the `implicit_model`)\n    vdg = DGModel(\n        dg.balance_law,\n        dg.grid,\n        dg.numerical_flux_first_order,\n        dg.numerical_flux_second_order,\n        dg.numerical_flux_gradient;\n        state_auxiliary = dg.state_auxiliary,\n        direction = VerticalDirection(),\n        diffusion_direction = VerticalDirection(),\n        check_for_crashes = dg.check_for_crashes,\n    )\n\n    # linear solver relative tolerance rtol which should be slightly smaller than the nonlinear solver tol\n    linearsolver = BatchedGeneralizedMinimalResidual(\n        dg,\n        Q;\n        max_subspace_size = ode_solver.linear_max_subspace_size,\n        atol = ode_solver.linear_atol,\n        rtol = ode_solver.linear_rtol,\n    )\n\n    # N(q)(Q) = Qhat  => F(Q) = N(q)(Q) - Qhat\n    # \n    # F(Q) == 0\n    # ||F(Q^i) || / ||F(Q^0) || < tol\n    # ϵ is a sensity parameter for this problem, it determines the finite difference Jacobian dF = (F(Q + ϵdQ) - F(Q))/ϵ\n    # I have also try larger tol, but tol = 1e-3 does not work\n    nonlinearsolver = JacobianFreeNewtonKrylovSolver(\n        Q,\n        linearsolver;\n        tol = ode_solver.nonlinear_rtol,\n        ϵ = ode_solver.nonlinear_ϵ,\n        M = ode_solver.nonlinear_max_iterations,\n    )\n\n    # this is a second order time integrator, to change it to a first order time integrator\n    # change it ARK1ForwardBackwardEuler, which can reduce the cost by half at the cost of accuracy \n    # and stability\n    # preconditioner_update_freq = 50 means updating the preconditioner every 50 Newton solves, \n    # update it more freqent will accelerate the convergence of linear solves, but updating it \n    # is very expensive\n    solver = ode_solver.solver_method(\n        dg,\n        vdg,\n        NonLinearBackwardEulerSolver(\n            nonlinearsolver;\n            isadjustable = true,\n            preconditioner_update_freq = ode_solver.preconditioner_update_freq,\n        ),\n        Q;\n        dt = dt,\n        t0 = t0,\n        split_explicit_implicit = false,\n        variant = NaiveVariant(),\n    )\n\n    return solver\nend\n"
  },
  {
    "path": "src/Driver/SolverTypes/IMEXSolverType.jl",
    "content": "\nexport IMEXSolverType\n\n\"\"\"\n# Description\n    IMEXSolverType(;\n        splitting_type = HEVISplitting(),\n        implicit_model = AtmosAcousticGravityLinearModel,\n        implicit_solver = ManyColumnLU,\n        implicit_solver_adjustable = false,\n        solver_method = ARK2GiraldoKellyConstantinescu,\n        solver_storage_variant = LowStorageVariant(),\n        split_explicit_implicit = false,\n        discrete_splitting = true,\n    )\n\nThis solver type constructs a solver for ODEs with the\nadditively-partitioned form.  When `split_explicit_implicit == false`\nthe equation is assumed to be decomposed as\n\n```math\n  \\\\dot{Q} = [l(Q, t)] + [f(Q, t) - l(Q, t)]\n```\n\nwhere `Q` is the state, `f` is the full tendency and `l` is the chosen implicit\noperator.\n\nWhen `split_explicit_implicit == true` the assumed decomposition is\n\n```math\n  \\\\dot{Q} = [l(Q, t)] + [n(Q, t)]\n```\n\nwhere `n` is now only the nonlinear tendency.\n\n# Arguments\n- `splitting_type` (DiscreteSplittingType): The type of discrete\n    splitting to apply to the right-hand side.\n    Default: `HEVISplitting()`\n- `implicit_model` (Type): The model describing dynamics to be\n    treated implicitly.\n    Default: `AtmosAcousticGravityLinearModel`\n- `implicit_solver` (Type): A solver for inverting the\n    implicit system of equations.\n    Default: `ManyColumnLU`\n- `implicit_solver_adjustable` (Bool): A flag identifying whether\n    or not the `implicit_solver` can be updated as the time-step\n    size changes.\n    Default: `false`\n- `solver_method` (Function): Function defining the particular additive\n    Runge-Kutta method to be used for the IMEX method.\n    Default: `ARK2GiraldoKellyConstantinescu`\n- `solver_storage_variant` (Type): Storage type for the additive\n    Runge-Kutta method.\n    Default: `LowStorageVariant()`\n- `split_explicit_implicit` (Boolean): Whether the tendency is split in explicit\n    and implicit parts or not.\n- `discrete_splitting` (Boolean): Boolean denoting whether a PDE level or\n    discretized level splitting should be used. If `true` then the PDE is\n    discretized in such a way that `f_fast + f_slow` is equivalent to\n    discretizing the original PDE directly.\n\n### References\n - [Giraldo2013](@cite)\n\"\"\"\nstruct IMEXSolverType{DS, ST} <: AbstractSolverType\n    # The type of discrete splitting to apply to the right-hand side\n    splitting_type::DS\n    # The implicit model\n    implicit_model::Type\n    # Choice of implicit solver\n    implicit_solver::Type\n    # Can the implicit solver be updated with changing dt?\n    implicit_solver_adjustable::Bool\n    # Function for the IMEX method\n    solver_method::Function\n    # Storage type for the ARK scheme\n    solver_storage_variant::ST\n    # Split tendency or not\n    split_explicit_implicit::Bool\n    # Whether to use a PDE level or discrete splitting\n    discrete_splitting::Bool\n\n    function IMEXSolverType(;\n        splitting_type = HEVISplitting(),\n        implicit_model = AtmosAcousticGravityLinearModel,\n        implicit_solver = ManyColumnLU,\n        implicit_solver_adjustable = false,\n        solver_method = ARK2GiraldoKellyConstantinescu,\n        solver_storage_variant = LowStorageVariant(),\n        split_explicit_implicit = false,\n        discrete_splitting = true,\n    )\n        @assert discrete_splitting || split_explicit_implicit\n\n        DS = typeof(splitting_type)\n        ST = typeof(solver_storage_variant)\n\n        return new{DS, ST}(\n            splitting_type,\n            implicit_model,\n            implicit_solver,\n            implicit_solver_adjustable,\n            solver_method,\n            solver_storage_variant,\n            split_explicit_implicit,\n            discrete_splitting,\n        )\n    end\nend\n\n\"\"\"\n    getdtmodel(ode_solver::IMEXSolverType, bl)\n\nA function which returns a model representing the dynamics\nwith the most restrictive time-stepping requirements.\n\"\"\"\nfunction getdtmodel(ode_solver::IMEXSolverType, bl)\n    # Most restrictive dynamics are treated implicitly\n    return ode_solver.implicit_model(bl)\nend\n\n\"\"\"\n# Description\n    solversetup(\n        ode_solver::IMEXSolverType{HEVISplitting},\n        dg,\n        Q,\n        dt,\n        t0,\n        diffusion_direction,\n    )\n\nCreates an ODE solver using a HEVI-type time-integration\nscheme. All horizontal acoustic waves are treated explicitly,\nwhile the 1-D vertical problem is treated implicitly. All\nother dynamics (advection, diffusion) is treated explicitly\nin the additive Runge-Kutta method.\n\n# Comments:\nCurrently, the only HEVI-type splitting ClimateMachine can currently\ndo only involves splitting the acoustic processes; it is not currently\npossible to perform more fine-grained separation of tendencies\n(for example, including vertical advection or diffusion in the 1-D implicit problem)\n\"\"\"\nfunction solversetup(\n    ode_solver::IMEXSolverType{HEVISplitting},\n    dg,\n    Q,\n    dt,\n    t0,\n    diffusion_direction,\n)\n\n    # All we need to do is create a DGModel for the\n    # vertical acoustic waves (determined from the `implicit_model`)\n    vdg = DGModel(\n        ode_solver.implicit_model(dg.balance_law),\n        dg.grid,\n        dg.numerical_flux_first_order,\n        dg.numerical_flux_second_order,\n        dg.numerical_flux_gradient,\n        state_auxiliary = dg.state_auxiliary,\n        state_gradient_flux = dg.state_gradient_flux,\n        states_higher_order = dg.states_higher_order,\n        direction = VerticalDirection(),\n        check_for_crashes = dg.check_for_crashes,\n    )\n\n    if ode_solver.split_explicit_implicit\n        if ode_solver.discrete_splitting\n            numerical_flux_first_order = (\n                dg.numerical_flux_first_order,\n                (dg.numerical_flux_first_order,),\n            )\n        else\n            numerical_flux_first_order = dg.numerical_flux_first_order\n        end\n        rem_dg = remainder_DGModel(\n            dg,\n            (vdg,);\n            numerical_flux_first_order = numerical_flux_first_order,\n        )\n        solver = ode_solver.solver_method(\n            rem_dg,\n            vdg,\n            LinearBackwardEulerSolver(\n                ode_solver.implicit_solver();\n                isadjustable = false,\n            ),\n            Q;\n            split_explicit_implicit = true,\n            dt = dt,\n            t0 = t0,\n        )\n    else\n        solver = ode_solver.solver_method(\n            dg,\n            vdg,\n            LinearBackwardEulerSolver(\n                ode_solver.implicit_solver();\n                isadjustable = ode_solver.implicit_solver_adjustable,\n            ),\n            Q;\n            dt = dt,\n            t0 = t0,\n            # NOTE: This needs to be `false` since the ARK method will\n            # evaluate the explicit part using the RemainderModel\n            # (Difference between full DG model (dg) and the\n            # DG model associated with the 1-D implicit problem (vdg))\n            split_explicit_implicit = false,\n            variant = ode_solver.solver_storage_variant,\n        )\n    end\n\n    return solver\nend\n"
  },
  {
    "path": "src/Driver/SolverTypes/ImplicitSolverType.jl",
    "content": "export ImplicitSolverType\n\n\"\"\"\n# Description\n    ImplicitSolverType(;\n        solver_method = KenCarp4,\n    )\n\nThis solver type constructs an ODE solver using a _fully implicit_\nmethod.\n\n# Arguments\n- `solver_method` (Function): Function defining the implicit\n    solver.\n    Default: `KenCarp4`\n\"\"\"\nstruct ImplicitSolverType <: AbstractSolverType\n    # Function for an implicit method\n    solver_method::Function\n\n    function ImplicitSolverType(\n        alg;\n        solver_method = ODESolvers.DiffEqJLConstructor(alg),\n    )\n\n        return new(solver_method)\n    end\nend\n\n\"\"\"\n    getdtmodel(ode_solver::AbstractSolverType, bl)\n\nA function which returns a model representing the dynamics\nwith the most restrictive time-stepping requirements.\n\"\"\"\nfunction getdtmodel(ode_solver::ImplicitSolverType, bl)\n    # For implicit methods, the entire model itself\n    # contributes to the total stability of the time-integrator\n    return bl\nend\n\n\n\"\"\"\n# Description\n    function solversetup(\n        ode_solver::ImplicitSolverType,\n        dg,\n        Q,\n        dt,\n        t0,\n        diffusion_direction,\n    )\n\nCreates an implicit ODE solver.\n\"\"\"\nfunction solversetup(\n    ode_solver::ImplicitSolverType,\n    dg,\n    Q,\n    dt,\n    t0,\n    diffusion_direction,\n)\n    solver = ode_solver.solver_method(dg, Q; dt = dt, t0 = t0)\n\n    return solver\nend\n"
  },
  {
    "path": "src/Driver/SolverTypes/MISSolverType.jl",
    "content": "export MISSolverType\n\n\"\"\"\n# Description\n    MISSolverType(;\n        splitting_type = SlowFastSplitting(),\n        fast_model = AtmosAcousticGravityLinearModel,\n        mis_method = MIS2,\n        fast_method = LSRK54CarpenterKennedy,\n        nsubsteps = 50,\n    )\n\nThis solver type constructs an ODE solver using a generalization\nof the split-explicit Runge-Kutta method. Known as the Multirate\nInfinitesimal Step (MIS) method, this solver solves ODEs with\nthe partitioned form:\n\n```math\n    \\\\dot{Q} = f_{fast}(Q, t) + f_{slow}(Q, t)\n```\n\nwhere the right-hand-side functions `f_fast` and `f_slow` denote\nfast and slow dynamics respectively, depending on the state `Q`.\n\n# Arguments\n- `splitting_type` (DiscreteSplittingType): The type of discrete\n    splitting to apply to the right-hand side.\n    Default: `SlowFastSplitting()`\n- `fast_model` (Type): The model describing fast dynamics.\n    Default: `AtmosAcousticGravityLinearModel`\n- `mis_method` (Function): Function defining the particular MIS\n    method to be used.\n    Default: `MIS2`\n- `fast_method` (Function): Function defining the fast solver.\n    Default: `LSRK54CarpenterKennedy`\n- `nsubsteps` (Tuple): Tuple denoting the total number of times\n    to substep the fast process.\n    Default: `(50,)`\n- `discrete_splitting` (Boolean): Boolean denoting whether a PDE level or\n    discretized level splitting should be used. If `true` then the PDE is\n    discretized in such a way that `f_fast + f_slow` is equivalent to\n    discretizing the original PDE directly.\n    Default: `false`\n\n### References\n - [KnothWensch2014](@cite)\n\"\"\"\nstruct MISSolverType{DS} <: AbstractSolverType\n    # The type of discrete splitting to apply to the right-hand side\n    splitting_type::DS\n    # The model describing fast dynamics\n    fast_model::Type\n    # Main MIS function\n    mis_method::Function\n    # Fast RK solver\n    fast_method::Function\n    # Substepping parameter for the fast processes\n    nsubsteps::Tuple\n    # Whether to use a PDE level or discrete splitting\n    discrete_splitting::Bool\n\n    function MISSolverType(;\n        splitting_type = SlowFastSplitting(),\n        fast_model = AtmosAcousticGravityLinearModel,\n        mis_method = MIS2,\n        fast_method = LSRK54CarpenterKennedy,\n        nsubsteps = (50,),\n        discrete_splitting = false,\n    )\n\n        DS = typeof(splitting_type)\n\n        return new{DS}(\n            splitting_type,\n            fast_model,\n            mis_method,\n            fast_method,\n            nsubsteps,\n            discrete_splitting,\n        )\n    end\nend\n\n\"\"\"\n    getdtmodel(ode_solver::MISSolverType, bl)\n\nA function which returns a model representing the dynamics\nwith the most restrictive time-stepping requirements.\n\"\"\"\nfunction getdtmodel(ode_solver::MISSolverType, bl)\n    # Most restrictive dynamics are part of the fast model\n    return ode_solver.fast_model(bl)\nend\n\n\"\"\"\n# Description\n    function solversetup(\n        ode_solver::MISSolverType{SlowFastSplitting},\n        dg,\n        Q,\n        dt,\n        t0,\n        diffusion_direction,\n    )\n\nCreates an ODE solver for the partition slow-fast ODE\nusing an MIS method with explicit time-integration.\nThe splitting of the fast (acoustic and gravity waves)\ndynamics is done in _all_ spatial directions.\n\"\"\"\nfunction solversetup(\n    ode_solver::MISSolverType{SlowFastSplitting},\n    dg,\n    Q,\n    dt,\n    t0,\n    diffusion_direction,\n)\n    # Extract fast model and define a DG model\n    # for the fast processes (acoustic/gravity waves\n    # in all spatial directions)\n    fast_model = ode_solver.fast_model(dg.balance_law)\n    fast_dg = DGModel(\n        fast_model,\n        dg.grid,\n        dg.numerical_flux_first_order,\n        dg.numerical_flux_second_order,\n        dg.numerical_flux_gradient,\n        state_auxiliary = dg.state_auxiliary,\n        state_gradient_flux = dg.state_gradient_flux,\n        states_higher_order = dg.states_higher_order,\n        direction = EveryDirection(),\n        check_for_crashes = dg.check_for_crashes,\n    )\n\n    # Using the RemainderModel, we subtract away the\n    # fast processes and define a DG model for the\n    # slower processes (advection and diffusion)\n    if ode_solver.discrete_splitting\n        numerical_flux_first_order =\n            (dg.numerical_flux_first_order, (dg.numerical_flux_first_order,))\n    else\n        numerical_flux_first_order = dg.numerical_flux_first_order\n    end\n    slow_dg = remainder_DGModel(\n        dg,\n        (fast_dg,);\n        numerical_flux_first_order = numerical_flux_first_order,\n    )\n\n    solver = ode_solver.mis_method(\n        slow_dg,\n        fast_dg,\n        ode_solver.fast_method,\n        ode_solver.nsubsteps[1],\n        Q;\n        dt = dt,\n        t0 = t0,\n    )\n\n    return solver\nend\n\nfunction solversetup(\n    ode_solver::MISSolverType{HEVISplitting},\n    dg,\n    Q,\n    dt,\n    t0,\n    diffusion_direction,\n)\n    # Extract fast model and define a DG model\n    # for the fast processes (acoustic/gravity waves\n    # in all spatial directions)\n    fast_model = ode_solver.fast_model(dg.balance_law)\n    fast_method = ode_solver.fast_method\n    fast_dg = DGModel(\n        fast_model,\n        dg.grid,\n        dg.numerical_flux_first_order,\n        dg.numerical_flux_second_order,\n        dg.numerical_flux_gradient,\n        state_auxiliary = dg.state_auxiliary,\n        state_gradient_flux = dg.state_gradient_flux,\n        states_higher_order = dg.states_higher_order,\n        direction = EveryDirection(),\n        check_for_crashes = dg.check_for_crashes,\n    )\n\n    # Using the RemainderModel, we subtract away the\n    # fast processes and define a DG model for the\n    # slower processes (advection and diffusion)\n    if ode_solver.discrete_splitting\n        numerical_flux_first_order =\n            (dg.numerical_flux_first_order, (dg.numerical_flux_first_order,))\n    else\n        numerical_flux_first_order = dg.numerical_flux_first_order\n    end\n    slow_dg = remainder_DGModel(\n        dg,\n        (fast_dg,);\n        numerical_flux_first_order = numerical_flux_first_order,\n    )\n\n    # Defining horizontal and vertical fast tendencies\n    fast_dg_h = DGModel(\n        fast_model,\n        dg.grid,\n        dg.numerical_flux_first_order,\n        dg.numerical_flux_second_order,\n        dg.numerical_flux_gradient,\n        state_auxiliary = dg.state_auxiliary,\n        state_gradient_flux = dg.state_gradient_flux,\n        states_higher_order = dg.states_higher_order,\n        direction = HorizontalDirection(),\n        check_for_crashes = dg.check_for_crashes,\n    )\n    fast_dg_v = DGModel(\n        fast_model,\n        dg.grid,\n        dg.numerical_flux_first_order,\n        dg.numerical_flux_second_order,\n        dg.numerical_flux_gradient,\n        state_auxiliary = dg.state_auxiliary,\n        state_gradient_flux = dg.state_gradient_flux,\n        states_higher_order = dg.states_higher_order,\n        direction = VerticalDirection(),\n        check_for_crashes = dg.check_for_crashes,\n    )\n\n    fast_dg_tendencies = (fast_dg_h, fast_dg_v)\n\n    if length(ode_solver.nsubsteps) == 1\n        nsubsteps = getnsubsteps(\n            ode_solver.mis_method,\n            ode_solver.nsubsteps[1],\n            real(eltype(Q)),\n        )\n        fast_method =\n            (dg, Q) -> ode_solver.fast_method(\n                dg,\n                Q,\n                dt / ode_solver.nsubsteps[1],\n                nsubsteps,\n            )\n    elseif length(ode_solver.nsubsteps) == 2\n        fast_method =\n            (dg, Q) -> ode_solver.fast_method(dg, Q, ode_solver.nsubsteps[2])\n    end\n\n    solver = ode_solver.mis_method(\n        slow_dg,\n        fast_dg_tendencies,\n        fast_method,\n        ode_solver.nsubsteps[1],\n        Q;\n        dt = dt,\n        t0 = t0,\n    )\n    return solver\nend\n"
  },
  {
    "path": "src/Driver/SolverTypes/MultirateSolverType.jl",
    "content": "export MultirateSolverType\n\n\"\"\"\n# Description\n    MultirateSolverType(;\n        splitting_type = SlowFastSplitting(),\n        fast_model = AtmosAcousticGravityLinearModel,\n        implicit_solver = ManyColumnLU,\n        implicit_solver_adjustable = false,\n        slow_method = LSRK54CarpenterKennedy,\n        fast_method = LSRK54CarpenterKennedy,\n        timestep_ratio = 100,\n    )\n\nThis solver type constructs an ODE solver using a standard multirate\nRunge-Kutta implementation. This solver computes solutions to ODEs with\nthe partitioned form:\n\n```math\n    \\\\dot{Q} = f_{fast}(Q, t) + f_{slow}(Q, t)\n```\n\nwhere the right-hand-side functions `f_fast` and `f_slow` denote\nfast and slow dynamics respectively, depending on the state `Q`.\n\n# Arguments\n- `splitting_type` (DiscreteSplittingType): The type of discrete\n    splitting to apply to the right-hand side.\n    Default: `SlowFastSplitting()`\n- `fast_model` (Type): The model describing fast dynamics.\n    Default: `AtmosAcousticGravityLinearModel`\n- `implicit_solver` (Type): An implicit solver for inverting the\n    implicit system of equations (if using `HEVISplitting()`).\n    Default: `ManyColumnLU`\n- `implicit_solver_adjustable` (Bool): A flag identifying whether\n    or not the `implicit_solver` can be updated as the time-step\n    size changes. This is particularly important when using\n    an implicit solver within a multirate scheme.\n    Default: `false`\n- `slow_method` (Function): Function defining the particular explicit\n    Runge-Kutta method to be used for the slow processes.\n    Default: `LSRK54CarpenterKennedy`\n- `fast_method` (Function): Function defining the fast solver.\n    Depending on the choice of `splitting_type`, this can be\n    an explicit Runge Kutta method or a 1-D IMEX (additive Runge-Kutta)\n    method.\n    Default: `LSRK54CarpenterKennedy`\n- `timestep_ratio` (Int): Integer denoting the ratio between the slow\n    and fast time-step sizes.\n    Default: `100`\n- `discrete_splitting` (Boolean): Boolean denoting whether a PDE level or\n    discretized level splitting should be used. If `true` then the PDE is\n    discretized in such a way that `f_fast + f_slow` is equivalent to\n    discretizing the original PDE directly.\n\n### References\n - [Schlegel2012](@cite)\n\"\"\"\nstruct MultirateSolverType{DS} <: AbstractSolverType\n    # The type of discrete splitting to apply to the right-hand side\n    splitting_type::DS\n    # The model describing fast dynamics\n    fast_model::Type\n    # Choice of implicit solver\n    implicit_solver::Type\n    # Can the implicit solver be updated with changing dt?\n    implicit_solver_adjustable::Bool\n    # RK method for evaluating the slow processes\n    slow_method::Function\n    # RK method for evaluating the fast processes\n    fast_method::Function\n    # The ratio between slow and fast time-step sizes\n    timestep_ratio::Int\n    # Whether to use a PDE level or discrete splitting\n    discrete_splitting::Bool\n\n    function MultirateSolverType(;\n        splitting_type = SlowFastSplitting(),\n        fast_model = AtmosAcousticGravityLinearModel,\n        implicit_solver = ManyColumnLU,\n        implicit_solver_adjustable = false,\n        slow_method = LSRK54CarpenterKennedy,\n        fast_method = LSRK54CarpenterKennedy,\n        timestep_ratio = 100,\n        discrete_splitting = false,\n    )\n\n        DS = typeof(splitting_type)\n\n        return new{DS}(\n            splitting_type,\n            fast_model,\n            implicit_solver,\n            implicit_solver_adjustable,\n            slow_method,\n            fast_method,\n            timestep_ratio,\n            discrete_splitting,\n        )\n    end\nend\n\n\"\"\"\n    getdtmodel(ode_solver::MultirateSolverType, bl)\n\nA function which returns a model representing the dynamics\nwith the most restrictive time-stepping requirements.\n\"\"\"\nfunction getdtmodel(ode_solver::MultirateSolverType, bl)\n    # Most restrictive dynamics are part of the fast model\n    return ode_solver.fast_model(bl)\nend\n\n\"\"\"\n# Description\n    function solversetup(\n        ode_solver::MultirateSolverType{SlowFastSplitting},\n        dg,\n        Q,\n        dt,\n        t0,\n        diffusion_direction,\n    )\n\nCreates an ODE solver for the partition slow-fast ODE\nusing a multirate method with explicit time-integration.\nThe splitting of the fast (acoustic and gravity waves)\ndynamics is done in _all_ spatial directions. Examples\nof a similar implementations include the following\nreferences.\n\n### References\n - [Schlegel2012](@cite)\n - [Schlegel2009](@cite)\n\"\"\"\nfunction solversetup(\n    ode_solver::MultirateSolverType{SlowFastSplitting},\n    dg,\n    Q,\n    dt,\n    t0,\n    diffusion_direction,\n)\n    # Extract fast model and define a DG model\n    # for the fast processes (acoustic/gravity waves\n    # in all spatial directions)\n    fast_model = ode_solver.fast_model(dg.balance_law)\n    fast_dg = DGModel(\n        fast_model,\n        dg.grid,\n        dg.numerical_flux_first_order,\n        dg.numerical_flux_second_order,\n        dg.numerical_flux_gradient,\n        state_auxiliary = dg.state_auxiliary,\n        state_gradient_flux = dg.state_gradient_flux,\n        states_higher_order = dg.states_higher_order,\n        direction = EveryDirection(),\n        check_for_crashes = dg.check_for_crashes,\n    )\n\n    # Using the RemainderModel, we subtract away the\n    # fast processes and define a DG model for the\n    # slower processes (advection and diffusion)\n    if ode_solver.discrete_splitting\n        numerical_flux_first_order =\n            (dg.numerical_flux_first_order, (dg.numerical_flux_first_order,))\n    else\n        numerical_flux_first_order = dg.numerical_flux_first_order\n    end\n    slow_dg = remainder_DGModel(\n        dg,\n        (fast_dg,);\n        numerical_flux_first_order = numerical_flux_first_order,\n    )\n\n    slow_solver = ode_solver.slow_method(slow_dg, Q; dt = dt)\n    fast_dt = dt / ode_solver.timestep_ratio\n    fast_solver = ode_solver.fast_method(fast_dg, Q; dt = fast_dt)\n\n    solver = MultirateRungeKutta((slow_solver, fast_solver), t0 = t0)\n\n    return solver\nend\n\n\"\"\"\n# Description\n    solversetup(\n        ode_solver::MultirateSolverType{HEVISplitting},\n        dg,\n        Q,\n        dt,\n        t0,\n        diffusion_direction,\n    )\n\nCreates an ODE solver for the partition slow-fast ODE\nusing a multirate method with HEVI time-integration.\nThe splitting of the fast (acoustic and gravity waves)\ndynamics is performed by splitting the fast model\ninto horizontal and vertical directions. All horizontal\nacoustic waves are treated explicitly, while the 1-D\nvertical problem is treated implicitly. The HEVI-splitting\nof the acoustic waves is handled using an IMEX additive\nRunge-Kutta method in the fast (inner-most) solver.\n\nExamples of similar multirate-HEVI approaches include\nthe following references.\n\n### References\n - [Doms2011](@cite)\n - [Tomita2008](@cite)\n\n# Comments:\nCurrently, the only HEVI-type splitting ClimateMachine can currently\ndo only involves splitting the acoustic processes; it is not currently\npossible to perform more fine-grained separation of tendencies\n(for example, including vertical advection or diffusion in the 1-D implicit problem)\n\"\"\"\nfunction solversetup(\n    ode_solver::MultirateSolverType{HEVISplitting},\n    dg,\n    Q,\n    dt,\n    t0,\n    diffusion_direction,\n)\n    # Extract fast model and define a DG model\n    # for the fast processes\n    fast_model = ode_solver.fast_model(dg.balance_law)\n\n    # Full DG model for the acoustic waves in all directions\n    acoustic_dg_full = DGModel(\n        fast_model,\n        dg.grid,\n        dg.numerical_flux_first_order,\n        dg.numerical_flux_second_order,\n        dg.numerical_flux_gradient,\n        state_auxiliary = dg.state_auxiliary,\n        state_gradient_flux = dg.state_gradient_flux,\n        states_higher_order = dg.states_higher_order,\n        direction = EveryDirection(),\n        check_for_crashes = dg.check_for_crashes,\n    )\n\n    # DG model for the vertical acoustic waves only\n    acoustic_dg_vert = DGModel(\n        fast_model,\n        dg.grid,\n        dg.numerical_flux_first_order,\n        dg.numerical_flux_second_order,\n        dg.numerical_flux_gradient,\n        state_auxiliary = dg.state_auxiliary,\n        state_gradient_flux = dg.state_gradient_flux,\n        states_higher_order = dg.states_higher_order,\n        direction = VerticalDirection(),\n        check_for_crashes = dg.check_for_crashes,\n    )\n\n    # Compute fast time-step size from target ratio\n    fast_dt = dt / ode_solver.timestep_ratio\n\n    # Fast solver for the acoustic/gravity waves using\n    # a HEVI-type splitting and a 1-D IMEX method\n    fast_solver = ode_solver.fast_method(\n        acoustic_dg_full,\n        acoustic_dg_vert,\n        LinearBackwardEulerSolver(\n            ode_solver.implicit_solver();\n            isadjustable = ode_solver.implicit_solver_adjustable,\n        ),\n        Q;\n        dt = fast_dt,\n        t0 = t0,\n        # NOTE: This needs to be `false` since the ARK method will\n        # evaluate the explicit part using the RemainderModel\n        # (Difference between acoustic_dg_full and acoustic_dg_vert)\n        split_explicit_implicit = false,\n        # NOTE: Do we want to support more variants?\n        variant = LowStorageVariant(),\n    )\n\n    # Finally, we subtract away the\n    # fast processes and define a DG model for the\n    # slower processes (advection and diffusion)\n    if ode_solver.discrete_splitting\n        numerical_flux_first_order =\n            (dg.numerical_flux_first_order, (dg.numerical_flux_first_order,))\n    else\n        numerical_flux_first_order = dg.numerical_flux_first_order\n    end\n    slow_dg = remainder_DGModel(\n        dg,\n        (acoustic_dg_full,);\n        numerical_flux_first_order = numerical_flux_first_order,\n    )\n\n    slow_solver = ode_solver.slow_method(slow_dg, Q; dt = dt, t0 = t0)\n\n    solver = MultirateRungeKutta((slow_solver, fast_solver), t0 = t0)\n\n    return solver\nend\n"
  },
  {
    "path": "src/Driver/SolverTypes/SolverTypes.jl",
    "content": "export AbstractSolverType\nexport DiscreteSplittingType, HEVISplitting\nexport getdtmodel\n\n\"\"\"\n    AbstractSolverType\n\nThis is an abstract type representing a generic solver. By\na \"solver,\" we mean an ODE solver together with any potential\nimplicit solver (linear solvers).\n\"\"\"\nabstract type AbstractSolverType end\n\n\"\"\"\n    DiscreteSplittingType\n\nThis is an abstract type representing a temporal splitting\nin the discrete equations. For example, HEVI\n(horizontally explicit, vertically implicit) type methods.\n\"\"\"\nabstract type DiscreteSplittingType end\n\n\"\"\"\n    SlowFastSplitting\n\nThe tendency is treated using a standard slow-fast splitting,\nwhere the fast processes are purely related to acoustic/gravity\nwaves (ideal for a typical multirate or MIS method).\nThis splitting does not take into account the geometric\ndirection (vertical vs horizontal).\n\"\"\"\nstruct SlowFastSplitting <: DiscreteSplittingType end\n\n\"\"\"\n    HEVISplitting\n\nHEVI (horizontally explicit, vertically implicit) type method,\nwhere vertical acoustic waves are treated implicitly. All other\ndynamics are treated explicitly.\n\nNote: Can potentially imagine several different types of\nHEVI splittings (for example, include vertical momentum and/or\ndiffusion)\n\"\"\"\nstruct HEVISplitting <: DiscreteSplittingType end\n\n\"\"\"\n    getdtmodel(ode_solver::AbstractSolverType, bl)\n\nA function which returns a model representing the dynamics\nwith the most restrictive time-stepping requirements.\n\"\"\"\ngetdtmodel(ode_solver::AbstractSolverType, bl) =\n    throw(MethodError(solversetup, (ode_solver, bl)),)\n\n\"\"\"\n    solversetup(\n        ode_solver::AbstractSolverType,\n        dg,\n        Q,\n        dt,\n        t0,\n        diffusion_direction,\n    )\n\nA function which returns a configured ODE solver.\n\"\"\"\nsolversetup(\n    ode_solver::AbstractSolverType,\n    dg,\n    Q,\n    dt,\n    t0,\n    diffusion_direction,\n) = throw(MethodError(\n    solversetup,\n    (ode_solver, dg, Q, dt, t0, diffusion_direction),\n))\n\ninclude(\"ExplicitSolverType.jl\")\ninclude(\"ImplicitSolverType.jl\")\ninclude(\"IMEXSolverType.jl\")\ninclude(\"HEVISolverType.jl\")\ninclude(\"MultirateSolverType.jl\")\ninclude(\"MISSolverType.jl\")\ninclude(\"SplitExplicitSolverType.jl\")\n\nDefaultSolverType = IMEXSolverType\nexport DefaultSolverType\n"
  },
  {
    "path": "src/Driver/SolverTypes/SplitExplicitSolverType.jl",
    "content": "export SplitExplicitSolverType\n\n\"\"\"\n# Description\n    SplitExplicitSolverType\n\nThis solver type constructs an ODE solver using the SplitExplicitLSRK2nSolver.\n# Arguments\n- `dt_slow` (AbstractFloat): Time step for the slow solver\n- `dt_fast` (AbstractFloat): Time step for the fast solver\n- `slow_method` (Function): Function defining the explicit\n    Runge-Kutta solver for the slow model.\n    Default: `LSRK54CarpenterKennedy`\n- `fast_method` (Function): Function defining the explicit\n    Runge-Kutta solver for the fast model.\n    Default: `LSRK54CarpenterKennedy`\n\"\"\"\nstruct SplitExplicitSolverType{SM, FM, FT} <: AbstractSolverType\n    # Function for an explicit Runge-Kutta method\n    slow_method::SM\n    # Function for an explicit Runge-Kutta method\n    fast_method::FM\n    # time step for slow method\n    dt_slow::FT\n    # time step for fast method\n    dt_fast::FT\n\n    function SplitExplicitSolverType{FT}(\n        dt_slow,\n        dt_fast;\n        slow_method = LSRK54CarpenterKennedy,\n        fast_method = LSRK54CarpenterKennedy,\n    ) where {FT <: AbstractFloat}\n        SM = typeof(slow_method)\n        FM = typeof(fast_method)\n\n        return new{SM, FM, FT}(slow_method, fast_method, dt_slow, dt_fast)\n    end\nend\n\n\"\"\"\n    getdtmodel(ode_solver::AbstractSolverType, bl)\n\nA function which returns a model representing the dynamics\nwith the most restrictive time-stepping requirements.\n\"\"\"\nfunction getdtmodel(::SplitExplicitSolverType, bl)\n    # For explicit methods, the entire model itself\n    # contributes to the total stability of the time-integrator\n    return bl\nend\n\n\"\"\"\n# Description\n    function solversetup(\n        ode_solver::ExplicitSolverType,\n        dg,\n        Q,\n        dt,\n        t0,\n        diffusion_direction,\n    )\n\nCreates an explicit ODE solver.\n\"\"\"\nfunction solversetup(ode_solver::SplitExplicitSolverType, dg_3D, Q_3D, _, t0, _)\n    dg_2D = dg_3D.modeldata.dg_2D\n    Q_2D = dg_3D.modeldata.Q_2D\n\n    fast_solver =\n        ode_solver.fast_method(dg_2D, Q_2D, dt = ode_solver.dt_fast, t0 = t0)\n    slow_solver =\n        ode_solver.slow_method(dg_3D, Q_3D, dt = ode_solver.dt_slow, t0 = t0)\n\n    solver = ClimateMachine.Ocean.SplitExplicit01.SplitExplicitLSRK2nSolver(\n        slow_solver,\n        fast_solver,\n    )\n\n    return solver\nend\n"
  },
  {
    "path": "src/Driver/diagnostics_configs.jl",
    "content": "using CLIMAParameters\nusing CLIMAParameters.Planet: planet_radius\nusing ..Diagnostics\nusing ..Mesh.Interpolation\n\n\"\"\"\n    DiagnosticsConfiguration\n\nContainer for all the `DiagnosticsGroup`s to be used for a simulation.\n\"\"\"\nmutable struct DiagnosticsConfiguration\n    groups::Array{DiagnosticsGroup, 1}\n\n    DiagnosticsConfiguration(\n        groups::Array{DG, 1},\n    ) where {DG <: DiagnosticsGroup} = new(groups)\nend\n\nfunction InterpolationConfiguration(\n    ::StackedBrickTopology,\n    driver_config,\n    boundaries,\n    axes,\n)\n    grid = driver_config.grid\n    return InterpolationBrick(grid, boundaries, axes[1], axes[2], axes[3])\nend\n\nfunction InterpolationConfiguration(\n    ::StackedCubedSphereTopology,\n    driver_config,\n    boundaries,\n    axes;\n    nr_toler = nothing,\n)\n    grid = driver_config.grid\n    info = driver_config.config_info\n    return InterpolationCubedSphere(\n        grid,\n        info.vert_range,\n        info.nelem_horz,\n        axes[1],\n        axes[2],\n        axes[3];\n        nr_toler = nr_toler,\n    )\nend\n\n\"\"\"\n    InterpolationConfiguration(\n        driver_config::DriverConfiguration,\n        boundaries::Array,\n        resolution = nothing;\n        axes = nothing;\n    )\n\nCreates an `InterpolationTopology` (either an `InterpolationBrick` or an\n`InterpolationCubedSphere`) to be used with a `DiagnosticsGroup`. Either\n`resolution` is specified, in which case the axes are set up with\nequi-distant points, or the `axes` may be specified directly (in\nlat/lon/lvl or x/y/z order).\n\"\"\"\nfunction InterpolationConfiguration(\n    driver_config::DriverConfiguration,\n    boundaries::Array,\n    resolution = nothing;\n    axes = nothing,\n)\n    @assert isnothing(resolution) || isnothing(axes)\n    if isnothing(axes)\n        axes = (\n            collect(range(\n                boundaries[1, 1],\n                boundaries[2, 1],\n                step = resolution[1],\n            )),\n            collect(range(\n                boundaries[1, 2],\n                boundaries[2, 2],\n                step = resolution[2],\n            )),\n            collect(range(\n                boundaries[1, 3],\n                boundaries[2, 3],\n                step = resolution[3],\n            )),\n        )\n    end\n\n    return InterpolationConfiguration(\n        driver_config.grid.topology,\n        driver_config,\n        boundaries,\n        axes,\n    )\nend\n"
  },
  {
    "path": "src/Driver/driver_configs.jl",
    "content": "# ClimateMachine driver configurations\n#\n# Contains helper functions to establish simulation configurations to be\n# used with the ClimateMachine driver. Currently:\n# - AtmosLESConfiguration\n# - AtmosGCMConfiguration\n# - OceanBoxGCMConfiguration\n# - SingleStackConfiguration\n# - MultiColumnLandModel\n#\n# User-customized configurations can use these as templates.\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: planet_radius\n\nabstract type ConfigSpecificInfo end\nstruct AtmosLESSpecificInfo <: ConfigSpecificInfo end\nstruct AtmosGCMSpecificInfo{FT} <: ConfigSpecificInfo\n    domain_height::FT\n    nelem_vert::Int\n    nelem_horz::Int\n    vert_range::Array{FT, 1}\nend\nstruct OceanBoxGCMSpecificInfo <: ConfigSpecificInfo end\nstruct SingleStackSpecificInfo{FT} <: ConfigSpecificInfo\n    zmax::FT\n    hmax::FT\nend\nstruct MultiColumnLandSpecificInfo <: ConfigSpecificInfo end\n\ninclude(\"SolverTypes/SolverTypes.jl\")\n\n\"\"\"\n    ArgParse.parse_item\n\nParses custom command line option for tuples of three or fewer integers.\n\"\"\"\nfunction ArgParse.parse_item(::Type{NTuple{2, Int}}, s::AbstractString)\n\n    str_array = split(s, \",\")\n    int_1 = length(str_array) > 0 ? parse(Int, str_array[1]) : 0\n    int_2 = length(str_array) > 1 ? parse(Int, str_array[2]) : 0\n\n    return (int_1, int_2)\nend\n\n\"\"\"\n    ArgParse.parse_item\n\nParses custom command line option for tuples of three or fewer integers.\n\"\"\"\nfunction ArgParse.parse_item(::Type{NTuple{3, Int}}, s::AbstractString)\n\n    str_array = split(s, \",\")\n    int_1 = length(str_array) > 0 ? parse(Int, str_array[1]) : 0\n    int_2 = length(str_array) > 1 ? parse(Int, str_array[2]) : 0\n    int_3 = length(str_array) > 2 ? parse(Int, str_array[3]) : 0\n\n    return (int_1, int_2, int_3)\nend\n\n\"\"\"\n    ArgParse.parse_item\n\nParses custom command line option for tuples of three integers.\n\"\"\"\nfunction ArgParse.parse_item(::Type{NTuple{3, Float64}}, s::AbstractString)\n\n    str_array = split(s, \",\")\n    ft_1 = length(str_array) > 0 ? parse(Float64, str_array[1]) : 0\n    ft_2 = length(str_array) > 1 ? parse(Float64, str_array[2]) : 0\n    ft_3 = length(str_array) > 2 ? parse(Float64, str_array[3]) : 0\n\n    return (ft_1, ft_2, ft_3)\nend\n\n\"\"\"\n    ArgParse.parse_item\n\nParses custom command line option for `DateTime`s.\n\"\"\"\nfunction ArgParse.parse_item(::Type{DateTime}, s::AbstractString)\n    DateTime(s, \"yyyy-mm-ddTHH:MM:SS\")\nend\n\n\"\"\"\n    get_polyorders\n\nUtility function that gets the polynomial orders for the given configuration\neither passed from command line or as default values\n\"\"\"\nfunction get_polyorders(N, setting = ClimateMachine.Settings.degree)\n\n    # Check if polynomial degree was passed as a CL option\n    if setting != (-1, -1)\n        setting\n    elseif N isa Int\n        (N, N)\n    else\n        N\n    end\nend\n\n\"\"\"\n    ClimateMachine.DriverConfiguration\n\nCollects all parameters necessary to set up a ClimateMachine simulation.\n\"\"\"\nstruct DriverConfiguration{FT}\n    config_type::ClimateMachineConfigType\n\n    name::String\n    # Polynomial order tuple (polyorder_horz, polyorder_vert)\n    polyorders::NTuple{2, Int}\n    array_type::Any\n    #\n    # Model details\n    param_set::AbstractParameterSet\n    bl::BalanceLaw\n    #\n    # Execution details\n    mpicomm::MPI.Comm\n    #\n    # Mesh details\n    grid::DiscontinuousSpectralElementGrid\n    #\n    # DGModel details\n    numerical_flux_first_order::NumericalFluxFirstOrder\n    numerical_flux_second_order::NumericalFluxSecondOrder\n    numerical_flux_gradient::NumericalFluxGradient\n    # DGFVModel details, used when polyorder_vert = 0\n    fv_reconstruction::Union{Nothing, AbstractReconstruction}\n    # Cutoff filter to emulate overintegration\n    filter::Any\n    #\n    # Configuration-specific info\n    config_info::ConfigSpecificInfo\n\n    function DriverConfiguration(\n        config_type,\n        name::String,\n        polyorders::NTuple{2, Int},\n        FT,\n        array_type,\n        param_set::AbstractParameterSet,\n        bl::BalanceLaw,\n        mpicomm::MPI.Comm,\n        grid::DiscontinuousSpectralElementGrid,\n        numerical_flux_first_order::NumericalFluxFirstOrder,\n        numerical_flux_second_order::NumericalFluxSecondOrder,\n        numerical_flux_gradient::NumericalFluxGradient,\n        fv_reconstruction::Union{Nothing, AbstractReconstruction},\n        filter,\n        config_info::ConfigSpecificInfo,\n    )\n        return new{FT}(\n            config_type,\n            name,\n            polyorders,\n            array_type,\n            param_set,\n            bl,\n            mpicomm,\n            grid,\n            numerical_flux_first_order,\n            numerical_flux_second_order,\n            numerical_flux_gradient,\n            fv_reconstruction,\n            filter,\n            config_info,\n        )\n    end\nend\n\nfunction print_model_info(model, mpicomm)\n    mpirank = MPI.Comm_rank(mpicomm)\n    if mpirank == 0\n        @show ClimateMachine.array_type()\n        msg = \"Model composition\\n\"\n        for key in fieldnames(typeof(model))\n            msg =\n                msg * @sprintf(\n                    \"    %s = %s\\n\",\n                    string(key),\n                    string((getproperty(model, key)))\n                )\n        end\n        @info msg\n        show_tendencies(model; table_complete = model isa AtmosModel)\n    end\nend\n\nfunction AtmosLESConfiguration(\n    name::String,\n    N::Union{Int, NTuple{2, Int}},\n    (Δx, Δy, Δz)::NTuple{3, FT},\n    xmax::FT,\n    ymax::FT,\n    zmax::FT,\n    param_set::AbstractParameterSet,\n    init_LES!;\n    xmin = zero(FT),\n    ymin = zero(FT),\n    zmin = zero(FT),\n    array_type = ClimateMachine.array_type(),\n    physics = AtmosPhysics{FT}(param_set;),\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        init_state_prognostic = init_LES!,\n    ),\n    mpicomm = MPI.COMM_WORLD,\n    boundary = ((0, 0), (0, 0), (1, 2)),\n    periodicity = (true, true, false),\n    meshwarp = (x...) -> identity(x),\n    grid_stretching = (nothing, nothing, nothing),\n    numerical_flux_first_order = RusanovNumericalFlux(),\n    numerical_flux_second_order = CentralNumericalFluxSecondOrder(),\n    numerical_flux_gradient = CentralNumericalFluxGradient(),\n    fv_reconstruction = nothing,\n    Ncutoff = get_polyorders(N),\n) where {FT <: AbstractFloat}\n\n    (polyorder_horz, polyorder_vert) = get_polyorders(N)\n    (cutofforder_horz, cutofforder_vert) =\n        get_polyorders(Ncutoff, ClimateMachine.Settings.cutoff_degree)\n\n    # Check if the element resolution was passed as a CL option\n    if ClimateMachine.Settings.resolution != (-1, -1, -1)\n        (Δx, Δy, Δz) = ClimateMachine.Settings.resolution\n    end\n\n    # Check if the min of the domain was passed as a CL option\n    if ClimateMachine.Settings.domain_min != (-1, -1, -1)\n        (xmin, ymin, zmin) = ClimateMachine.Settings.domain_min\n    end\n\n    # Check if the max of the domain was passed as a CL option\n    if ClimateMachine.Settings.domain_max != (-1, -1, -1)\n        (xmax, ymax, zmax) = ClimateMachine.Settings.domain_max\n    end\n\n    print_model_info(model, mpicomm)\n\n    brickrange = (\n        grid1d(\n            xmin,\n            xmax,\n            grid_stretching[1],\n            elemsize = Δx * max(polyorder_horz, 1),\n        ),\n        grid1d(\n            ymin,\n            ymax,\n            grid_stretching[2],\n            elemsize = Δy * max(polyorder_horz, 1),\n        ),\n        grid1d(\n            zmin,\n            zmax,\n            grid_stretching[3],\n            elemsize = Δz * max(polyorder_vert, 1),\n        ),\n    )\n    topology = StackedBrickTopology(\n        mpicomm,\n        brickrange,\n        periodicity = periodicity,\n        boundary = boundary,\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = array_type,\n        polynomialorder = (polyorder_horz, polyorder_vert),\n        meshwarp = meshwarp,\n    )\n\n    if cutofforder_horz < polyorder_horz && cutofforder_vert < polyorder_vert\n        filter = CutoffFilter(\n            grid,\n            (cutofforder_horz + 1, cutofforder_horz + 1, cutofforder_vert + 1),\n        )\n    else\n        filter = nothing\n        cutofforder_horz = cutofforder_vert = nothing\n    end\n\n\n    @info @sprintf(\n        \"\"\"\nEstablishing Atmos LES configuration for %s\n    precision               = %s\n    horiz polynomial order  = %d\n    vert polynomial order   = %d\n    horiz cutoff order      = %s\n    vert cutoff order       = %s\n    domain_min              = %.2f m, %.2f m, %.2f m\n    domain_max              = %.2f m, %.2f m, %.2f m\n    resolution              = %dx%dx%d\n    MPI ranks               = %d\n    min(Δ_horz)             = %.2f m\n    min(Δ_vert)             = %.2f m\"\"\",\n        name,\n        FT,\n        polyorder_horz,\n        polyorder_vert,\n        cutofforder_horz,\n        cutofforder_vert,\n        xmin,\n        ymin,\n        zmin,\n        xmax,\n        ymax,\n        zmax,\n        Δx,\n        Δy,\n        Δz,\n        MPI.Comm_size(mpicomm),\n        min_node_distance(grid, HorizontalDirection()),\n        min_node_distance(grid, VerticalDirection())\n    )\n\n    return DriverConfiguration(\n        AtmosLESConfigType(),\n        name,\n        (polyorder_horz, polyorder_vert),\n        FT,\n        array_type,\n        param_set,\n        model,\n        mpicomm,\n        grid,\n        numerical_flux_first_order,\n        numerical_flux_second_order,\n        numerical_flux_gradient,\n        fv_reconstruction,\n        filter,\n        AtmosLESSpecificInfo(),\n    )\nend\n\nfunction AtmosGCMConfiguration(\n    name::String,\n    N::Union{Int, NTuple{2, Int}},\n    (nelem_horz, nelem_vert)::NTuple{2, Int},\n    domain_height::FT,\n    param_set::AbstractParameterSet,\n    init_GCM!;\n    array_type = ClimateMachine.array_type(),\n    physics = AtmosPhysics{FT}(param_set),\n    model = AtmosModel{FT}(\n        AtmosGCMConfigType,\n        physics;\n        init_state_prognostic = init_GCM!,\n    ),\n    mpicomm = MPI.COMM_WORLD,\n    grid_stretching = (nothing,),\n    meshwarp::Function = equiangular_cubed_sphere_warp,\n    numerical_flux_first_order = RusanovNumericalFlux(),\n    numerical_flux_second_order = CentralNumericalFluxSecondOrder(),\n    numerical_flux_gradient = CentralNumericalFluxGradient(),\n    fv_reconstruction = nothing,\n    Ncutoff = get_polyorders(N),\n) where {FT <: AbstractFloat}\n\n    (polyorder_horz, polyorder_vert) = get_polyorders(N)\n    (cutofforder_horz, cutofforder_vert) =\n        get_polyorders(Ncutoff, ClimateMachine.Settings.cutoff_degree)\n\n    # Check if the number of elements was passed as a CL option\n    if ClimateMachine.Settings.nelems != (-1, -1, -1)\n        (nelem_horz, nelem_vert) = ClimateMachine.Settings.nelems\n    end\n\n    # Check if the domain height was passed as a CL option\n    if ClimateMachine.Settings.domain_height != -1\n        domain_height = ClimateMachine.Settings.domain_height\n    end\n\n    print_model_info(model, mpicomm)\n\n    _planet_radius::FT = planet_radius(param_set)\n    vert_range = grid1d(\n        _planet_radius,\n        FT(_planet_radius + domain_height),\n        grid_stretching[1],\n        nelem = nelem_vert,\n    )\n\n    topology = StackedCubedSphereTopology(\n        mpicomm,\n        nelem_horz,\n        vert_range;\n        boundary = (1, 2),\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = array_type,\n        polynomialorder = (polyorder_horz, polyorder_vert),\n        meshwarp = meshwarp,\n    )\n\n\n    if cutofforder_horz < polyorder_horz && cutofforder_vert < polyorder_vert\n        filter = CutoffFilter(\n            grid,\n            (cutofforder_horz + 1, cutofforder_horz + 1, cutofforder_vert + 1),\n        )\n    else\n        filter = nothing\n        cutofforder_horz = cutofforder_vert = nothing\n    end\n\n    @info @sprintf(\n        \"\"\"\nEstablishing Atmos GCM configuration for %s\n    precision               = %s\n    horiz polynomial order  = %d\n    vert polynomial order   = %d\n    horiz cutoff order      = %s\n    vert cutoff order       = %s\n    # horiz elem            = %d\n    # vert elems            = %d\n    domain height           = %.2e m\n    MPI ranks               = %d\n    min(Δ_horz)             = %.2f m\n    min(Δ_vert)             = %.2f m\"\"\",\n        name,\n        FT,\n        polyorder_horz,\n        polyorder_vert,\n        cutofforder_horz,\n        cutofforder_vert,\n        nelem_horz,\n        nelem_vert,\n        domain_height,\n        MPI.Comm_size(mpicomm),\n        min_node_distance(grid, HorizontalDirection()),\n        min_node_distance(grid, VerticalDirection())\n    )\n\n    return DriverConfiguration(\n        AtmosGCMConfigType(),\n        name,\n        (polyorder_horz, polyorder_vert),\n        FT,\n        array_type,\n        param_set,\n        model,\n        mpicomm,\n        grid,\n        numerical_flux_first_order,\n        numerical_flux_second_order,\n        numerical_flux_gradient,\n        fv_reconstruction,\n        filter,\n        AtmosGCMSpecificInfo(\n            domain_height,\n            nelem_vert,\n            nelem_horz,\n            collect(vert_range),\n        ),\n    )\nend\n\nfunction OceanBoxGCMConfiguration(\n    name::String,\n    N::Union{Int, NTuple{2, Int}},\n    (Nˣ, Nʸ, Nᶻ)::NTuple{3, Int},\n    param_set::AbstractParameterSet,\n    model;\n    FT = Float64,\n    array_type = ClimateMachine.array_type(),\n    mpicomm = MPI.COMM_WORLD,\n    numerical_flux_first_order = RusanovNumericalFlux(),\n    numerical_flux_second_order = CentralNumericalFluxSecondOrder(),\n    numerical_flux_gradient = CentralNumericalFluxGradient(),\n    fv_reconstruction = nothing,\n    periodicity = (false, false, false),\n    boundary = ((1, 1), (1, 1), (2, 3)),\n)\n\n    (polyorder_horz, polyorder_vert) = get_polyorders(N)\n\n    # Check if the number of elements was passed as a CL option\n    if ClimateMachine.Settings.nelems != (-1, -1, -1)\n        (Nˣ, Nʸ, Nᶻ) = ClimateMachine.Settings.nelems\n    end\n\n    brickrange = (\n        range(FT(0); length = Nˣ + 1, stop = model.problem.Lˣ),\n        range(FT(0); length = Nʸ + 1, stop = model.problem.Lʸ),\n        range(FT(-model.problem.H); length = Nᶻ + 1, stop = 0),\n    )\n\n    topology = StackedBrickTopology(\n        mpicomm,\n        brickrange;\n        periodicity = periodicity,\n        boundary = boundary,\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = array_type,\n        polynomialorder = (polyorder_horz, polyorder_vert),\n    )\n\n    @info @sprintf(\n        \"\"\"\nEstablishing Ocean Box GCM configuration for %s\n    precision               = %s\n    horiz polynomial order  = %d\n    vert polynomial order   = %d\n    Nˣ # elems              = %d\n    Nʸ # elems              = %d\n    Nᶻ # elems              = %d\n    domain depth            = %.2e m\n    MPI ranks               = %d\"\"\",\n        name,\n        FT,\n        polyorder_horz,\n        polyorder_vert,\n        Nˣ,\n        Nʸ,\n        Nᶻ,\n        -model.problem.H,\n        MPI.Comm_size(mpicomm)\n    )\n\n    return DriverConfiguration(\n        OceanBoxGCMConfigType(),\n        name,\n        (polyorder_horz, polyorder_vert),\n        FT,\n        array_type,\n        param_set,\n        model,\n        mpicomm,\n        grid,\n        numerical_flux_first_order,\n        numerical_flux_second_order,\n        numerical_flux_gradient,\n        fv_reconstruction,\n        nothing, # filter\n        OceanBoxGCMSpecificInfo(),\n    )\nend\n\nfunction SingleStackConfiguration(\n    name::String,\n    N::Union{Int, NTuple{2, Int}},\n    nelem_vert::Int,\n    zmax::FT,\n    param_set::AbstractParameterSet,\n    model::BalanceLaw;\n    zmin = zero(FT),\n    hmax = one(FT),\n    array_type = ClimateMachine.array_type(),\n    mpicomm = MPI.COMM_WORLD,\n    boundary = ((0, 0), (0, 0), (1, 2)),\n    periodicity = (true, true, false),\n    meshwarp = (x...) -> identity(x),\n    numerical_flux_first_order = RusanovNumericalFlux(),\n    numerical_flux_second_order = CentralNumericalFluxSecondOrder(),\n    numerical_flux_gradient = CentralNumericalFluxGradient(),\n    fv_reconstruction = nothing,\n) where {FT <: AbstractFloat}\n\n    (polyorder_horz, polyorder_vert) = get_polyorders(N)\n\n    # Check if the number of elements was passed as a CL option\n    if ClimateMachine.Settings.nelems != (-1, -1, -1)\n        nE = ClimateMachine.Settings.nelems\n        nelem_vert = nE[1]\n    end\n\n    # Check if the domain height was passed as a CL option\n    if ClimateMachine.Settings.domain_height != -1\n        zmax = ClimateMachine.Settings.domain_height\n    end\n\n    print_model_info(model, mpicomm)\n\n    xmin, xmax = zero(FT), hmax\n    ymin, ymax = zero(FT), hmax\n    brickrange = (\n        grid1d(xmin, xmax, nelem = 1),\n        grid1d(ymin, ymax, nelem = 1),\n        grid1d(zmin, zmax, nelem = nelem_vert),\n    )\n    topology = StackedBrickTopology(\n        mpicomm,\n        brickrange,\n        periodicity = periodicity,\n        boundary = boundary,\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = array_type,\n        polynomialorder = (polyorder_horz, polyorder_vert),\n        meshwarp = meshwarp,\n    )\n\n    @info @sprintf(\n        \"\"\"\nEstablishing single stack configuration for %s\n    precision               = %s\n    horiz polynomial order  = %d\n    vert polynomial order   = %d\n    domain_min              = %.2f m, %.2f m, %.2f m\n    domain_max              = %.2f m, %.2f m, %.2f m\n    # vert elems            = %d\n    MPI ranks               = %d\n    min(Δ_horz)             = %.2f m\n    min(Δ_vert)             = %.2f m\"\"\",\n        name,\n        FT,\n        polyorder_horz,\n        polyorder_vert,\n        xmin,\n        ymin,\n        zmin,\n        xmax,\n        ymax,\n        zmax,\n        nelem_vert,\n        MPI.Comm_size(mpicomm),\n        min_node_distance(grid, HorizontalDirection()),\n        min_node_distance(grid, VerticalDirection())\n    )\n\n    return DriverConfiguration(\n        SingleStackConfigType(),\n        name,\n        (polyorder_horz, polyorder_vert),\n        FT,\n        array_type,\n        param_set,\n        model,\n        mpicomm,\n        grid,\n        numerical_flux_first_order,\n        numerical_flux_second_order,\n        numerical_flux_gradient,\n        fv_reconstruction,\n        nothing, # filter\n        SingleStackSpecificInfo(zmax, hmax),\n    )\nend\n\nfunction MultiColumnLandModel(\n    name::String,\n    N::Union{Int, NTuple{2, Int}},\n    (Δx, Δy, Δz)::NTuple{3, FT},\n    xmax::FT,\n    ymax::FT,\n    zmax::FT,\n    param_set::AbstractParameterSet,\n    model::BalanceLaw;\n    xmin = zero(FT),\n    ymin = zero(FT),\n    zmin = zero(FT),\n    array_type = ClimateMachine.array_type(),\n    mpicomm = MPI.COMM_WORLD,\n    boundary = ((3, 4), (5, 6), (1, 2)),\n    solver_type = ExplicitSolverType(),\n    periodicity = (false, false, false),\n    meshwarp = (x...) -> identity(x),\n    numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n    numerical_flux_second_order = CentralNumericalFluxSecondOrder(),\n    numerical_flux_gradient = CentralNumericalFluxGradient(),\n    fv_reconstruction = nothing,\n) where {FT <: AbstractFloat}\n\n    (polyorder_horz, polyorder_vert) = get_polyorders(N)\n\n    # Check if the element resolution was passed as a CL option\n    if ClimateMachine.Settings.resolution != (-1, -1, -1)\n        (Δx, Δy, Δz) = ClimateMachine.Settings.resolution\n    end\n\n    # Check if the min of the domain was passed as a CL option\n    if ClimateMachine.Settings.domain_min != (-1, -1, -1)\n        (xmin, ymin, zmin) = ClimateMachine.Settings.domain_min\n    end\n\n    # Check if the max of the domain was passed as a CL option\n    if ClimateMachine.Settings.domain_max != (-1, -1, -1)\n        (xmax, ymax, zmax) = ClimateMachine.Settings.domain_max\n    end\n\n    print_model_info(model, mpicomm)\n\n    brickrange = (\n        grid1d(xmin, xmax, elemsize = Δx * max(polyorder_horz, 1)),\n        grid1d(ymin, ymax, elemsize = Δy * max(polyorder_horz, 1)),\n        grid1d(zmin, zmax, elemsize = Δz * max(polyorder_vert, 1)),\n    )\n\n    topology = StackedBrickTopology(\n        mpicomm,\n        brickrange,\n        periodicity = periodicity,\n        boundary = boundary,\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = array_type,\n        polynomialorder = (polyorder_horz, polyorder_vert),\n        meshwarp = meshwarp,\n    )\n\n    @info @sprintf(\n        \"\"\"\nEstablishing MultiColumnLandModel configuration for %s\n    precision        = %s\n    vert polyn order = %d\n    horz polyn order = %d\n    domain_min       = %.2f m, %.2f m, %.2f m\n    domain_max       = %.2f m, %.2f m, %.2f m\n    resolution       = %dx%dx%d\n    MPI ranks        = %d\n    min(Δ_horz)      = %.2f m\n    min(Δ_vert)      = %.2f m\"\"\",\n        name,\n        FT,\n        polyorder_vert,\n        polyorder_horz,\n        xmin,\n        ymin,\n        zmin,\n        xmax,\n        ymax,\n        zmax,\n        Δx,\n        Δy,\n        Δz,\n        MPI.Comm_size(mpicomm),\n        min_node_distance(grid, HorizontalDirection()),\n        min_node_distance(grid, VerticalDirection())\n    )\n\n    return DriverConfiguration(\n        MultiColumnLandConfigType(),\n        name,\n        (polyorder_horz, polyorder_vert),\n        FT,\n        array_type,\n        param_set,\n        model,\n        mpicomm,\n        grid,\n        numerical_flux_first_order,\n        numerical_flux_second_order,\n        numerical_flux_gradient,\n        fv_reconstruction,\n        nothing, # filter\n        MultiColumnLandSpecificInfo(),\n    )\nend\n\nimport ..DGMethods: DGModel\n\n\"\"\"\n    DGModel(driver_config; kwargs...)\n\nInitialize a [`DGModel`](@ref) given a\n[`DriverConfiguration`](@ref) and keyword\narguments supported by [`DGModel`](@ref).\n\"\"\"\nDGModel(driver_config; kwargs...) = DGModel(\n    driver_config.bl,\n    driver_config.grid,\n    driver_config.numerical_flux_first_order,\n    driver_config.numerical_flux_second_order,\n    driver_config.numerical_flux_gradient;\n    gradient_filter = driver_config.filter,\n    tendency_filter = driver_config.filter,\n    kwargs...,\n)\n\n\"\"\"\n    SpaceDiscretization(driver_config; kwargs...)\n\nInitialize a [`SpaceDiscretization`](@ref) given a\n[`DriverConfiguration`](@ref) and keyword\narguments supported by [`SpaceDiscretization`](@ref).\n\"\"\"\nSpaceDiscretization(driver_config; kwargs...) =\n    (driver_config.polyorders[2] == 0) ?\n    DGFVModel(\n        driver_config.bl,\n        driver_config.grid,\n        driver_config.fv_reconstruction,\n        driver_config.numerical_flux_first_order,\n        driver_config.numerical_flux_second_order,\n        driver_config.numerical_flux_gradient;\n        gradient_filter = driver_config.filter,\n        tendency_filter = driver_config.filter,\n        kwargs...,\n    ) :\n    DGModel(\n        driver_config.bl,\n        driver_config.grid,\n        driver_config.numerical_flux_first_order,\n        driver_config.numerical_flux_second_order,\n        driver_config.numerical_flux_gradient;\n        gradient_filter = driver_config.filter,\n        tendency_filter = driver_config.filter,\n        kwargs...,\n    )\n"
  },
  {
    "path": "src/Driver/solver_config_wrappers.jl",
    "content": "import ..SingleStackUtils: single_stack_diagnostics, NodalStack\nusing ..ODESolvers\n\n# Convenience wrapper\nsingle_stack_diagnostics(solver_config; kwargs...) = single_stack_diagnostics(\n    solver_config.dg.grid,\n    solver_config.dg.balance_law,\n    gettime(solver_config.solver),\n    solver_config.dg.direction;\n    prognostic = solver_config.Q,\n    auxiliary = solver_config.dg.state_auxiliary,\n    diffusive = solver_config.dg.state_gradient_flux,\n    hyperdiffusive = solver_config.dg.states_higher_order[2],\n    kwargs...,\n)\n\n# Convenience wrapper\nNodalStack(solver_config; kwargs...) = NodalStack(\n    solver_config.dg.balance_law,\n    solver_config.dg.grid;\n    prognostic = solver_config.Q,\n    auxiliary = solver_config.dg.state_auxiliary,\n    diffusive = solver_config.dg.state_gradient_flux,\n    hyperdiffusive = solver_config.dg.states_higher_order[2],\n    kwargs...,\n)\n"
  },
  {
    "path": "src/Driver/solver_configs.jl",
    "content": "# ClimateMachine solver configurations\n#\n# Contains helper functions to establish solver configurations to be\n# used with the ClimateMachine driver.\n\n\"\"\"\n    ClimateMachine.SolverConfiguration\n\nParameters needed by `ClimateMachine.solve!()` to run a simulation.\n\"\"\"\nmutable struct SolverConfiguration{FT}\n    name::String\n    mpicomm::MPI.Comm\n    param_set::AbstractParameterSet\n    dg::SpaceDiscretization\n    Q::MPIStateArray\n    t0::FT\n    timeend::FT\n    dt::FT\n    init_on_cpu::Bool\n    numberofsteps::Int\n    init_args::Any\n    solver::Any\n    ode_solver_type::Any\n    diffdir::Any\n    CFL::FT\nend\n\n\"\"\"\n    DGMethods.courant(local_cfl, solver_config::SolverConfiguration;\n                      Q=solver_config.Q, dt=solver_config.dt)\n\nReturns the maximum of the evaluation of the function `local_courant`\npointwise throughout the domain with the model defined by `solver_config`. The\nkeyword arguments `Q` and `dt` can be used to call the courant method with a\ndifferent state `Q` or time step `dt` than are defined in `solver_config`.\n\"\"\"\nDGMethods.courant(\n    f,\n    sc::SolverConfiguration;\n    Q = sc.Q,\n    dt = sc.dt,\n    simtime = gettime(sc.solver),\n    direction = EveryDirection(),\n) = DGMethods.courant(f, sc.dg, sc.dg.balance_law, Q, dt, simtime, direction)\n\nget_direction(::ClimateMachineConfigType) = EveryDirection()\nget_direction(::SingleStackConfigType) = VerticalDirection()\n\nget_solver_type(::Type{FT}, ::AtmosGCMConfigType) where {FT} =\n    DefaultSolverType()\nget_solver_type(::Type{FT}, ::AtmosLESConfigType) where {FT} =\n    solver_type = IMEXSolverType(\n        implicit_solver = SingleColumnLU,\n        implicit_solver_adjustable = false,\n    )\nget_solver_type(::Type{FT}, ::OceanSplitExplicitConfigType) where {FT} =\n    SplitExplicitSolverType{FT}(90.0 * 60.0, 240.0)\nget_solver_type(::Type{FT}, ::OceanBoxGCMConfigType) where {FT} =\n    ExplicitSolverType(solver_method = LSRK144NiegemannDiehlBusch)\nget_solver_type(::Type{FT}, ::SingleStackConfigType) where {FT} =\n    ExplicitSolverType()\nget_solver_type(::Type{FT}, ::MultiColumnLandConfigType) where {FT} =\n    ExplicitSolverType()\n\n\"\"\"\n    ClimateMachine.SolverConfiguration(\n        t0::FT,\n        timeend::FT,\n        driver_config::DriverConfiguration,\n        init_args...;\n        init_on_cpu=false,\n        ode_solver_type,\n        ode_dt=nothing,\n        modeldata=nothing,\n        Courant_number=0.4,\n        diffdir=EveryDirection(),\n    )\n\nSet up the DG model per the specified driver configuration, set up\nthe ODE solver, and return a `SolverConfiguration` to be used with\n`ClimateMachine.invoke!()`.\n\n# Arguments:\n# - `t0::FT`: simulation start time.\n# - `timeend::FT`: simulation end time.\n# - `driver_config::DriverConfiguration`: from `AtmosLESConfiguration()`, etc.\n# - `init_args...`: passed through to `init_state_prognostic!()`.\n# - `init_on_cpu=false`: run `init_state_prognostic!()` on CPU?\n# - `ode_solver_type`: override solver choice.\n# - `ode_dt=nothing`: override timestep computation.\n# - `modeldata=nothing`: passed through to `DGModel`.\n# - `Courant_number=0.4`: for the simulation.\n# - `diffdir=EveryDirection()`: restrict diffusivity direction.\n# - `direction=EveryDirection()`: restrict diffusivity direction.\n# - `timeend_dt_adjust=true`: should `dt` be adjusted to hit `timeend` exactly\n# - `CFL_direction=EveryDirection()`: direction for `calculate_dt`\n# - `sim_time`: run for the specified time (in simulation seconds).\n# - `fixed_number_of_steps`: if `≥0` perform specified number of steps.\n\nNote that `diffdir`, `direction`, and `CFL_direction` are `VerticalDirection()`\nwhen `driver_config.config_type isa SingleStackConfigType`.\n\"\"\"\nfunction SolverConfiguration(\n    t0::FT,\n    timeend::FT,\n    driver_config::DriverConfiguration,\n    init_args...;\n    init_on_cpu = false,\n    ode_solver_type = get_solver_type(FT, driver_config.config_type),\n    ode_dt = nothing,\n    modeldata = nothing,\n    Courant_number = nothing,\n    diffdir = get_direction(driver_config.config_type),\n    direction = get_direction(driver_config.config_type),\n    timeend_dt_adjust = true,\n    CFL_direction = get_direction(driver_config.config_type),\n    sim_time = Settings.sim_time,\n    fixed_number_of_steps = Settings.fixed_number_of_steps,\n    skip_update_aux = false,\n) where {FT <: AbstractFloat}\n    @tic SolverConfiguration\n\n    bl = driver_config.bl\n    grid = driver_config.grid\n\n    # Create the DG model and initialize the ODE state. If we're restarting,\n    # use state data from the checkpoint.\n    if Settings.restart_from_num > 0\n        s_Q, s_aux, t0 = read_checkpoint(\n            Settings.checkpoint_dir,\n            driver_config.name,\n            driver_config.array_type,\n            driver_config.mpicomm,\n            Settings.restart_from_num,\n        )\n\n        state_auxiliary = restart_auxiliary_state(bl, grid, s_aux, direction)\n\n        if hasproperty(driver_config.config_info, :dg)\n            dg = driver_config.config_info.dg\n\n            dg.state_auxiliary .= state_auxiliary\n        else\n            dg = SpaceDiscretization(\n                driver_config;\n                state_auxiliary = state_auxiliary,\n                direction = direction,\n                diffusion_direction = diffdir,\n                modeldata = modeldata,\n                check_for_crashes = Settings.checkpoint_on_crash,\n            )\n        end\n\n        @info @sprintf(\n            \"Initializing %s from time %8.2f\",\n            driver_config.name,\n            t0\n        )\n        Q = restart_ode_state(dg, s_Q; init_on_cpu = init_on_cpu)\n    else\n        if hasproperty(driver_config.config_info, :dg)\n            dg = driver_config.config_info.dg\n        else\n            dg = SpaceDiscretization(\n                driver_config;\n                fill_nan = Settings.debug_init,\n                direction = direction,\n                diffusion_direction = diffdir,\n                modeldata = modeldata,\n                check_for_crashes = Settings.checkpoint_on_crash,\n            )\n        end\n\n        if Settings.debug_init\n            write_debug_init_vtk_and_pvtu(\n                \"init_auxiliary\",\n                driver_config,\n                dg,\n                dg.state_auxiliary,\n                flattenednames(vars_state(bl, Auxiliary(), FT)),\n            )\n        end\n\n        @info @sprintf(\"Initializing %s\", driver_config.name,)\n        Q = init_ode_state(dg, FT(0), init_args...; init_on_cpu = init_on_cpu)\n        if driver_config.filter !== nothing\n            Filters.apply!(Q, :, dg.grid, driver_config.filter)\n        end\n\n        if Settings.debug_init\n            write_debug_init_vtk_and_pvtu(\n                \"init_prognostic\",\n                driver_config,\n                dg,\n                Q,\n                flattenednames(vars_state(bl, Prognostic(), FT)),\n            )\n        end\n    end\n    if !skip_update_aux\n        update_auxiliary_state!(dg, bl, Q, FT(0), dg.grid.topology.realelems)\n    end\n\n    if Settings.debug_init\n        write_debug_init_vtk_and_pvtu(\n            \"first_update_auxiliary\",\n            driver_config,\n            dg,\n            dg.state_auxiliary,\n            flattenednames(vars_state(bl, Auxiliary(), FT)),\n        )\n    end\n\n    # default Courant number\n    # TODO: Think about revising this or drop it entirely.\n    # This is difficult to determine/approximate\n    # for MIS and general multirate methods.\n    if Courant_number === nothing\n        if isa(ode_solver_type, ExplicitSolverType)\n            if ode_solver_type.solver_method == LSRK144NiegemannDiehlBusch\n                Courant_number = FT(1.7)\n            else\n                @assert ode_solver_type.solver_method == LSRK54CarpenterKennedy\n                Courant_number = FT(0.3)\n            end\n        else\n            Courant_number = FT(0.5)\n        end\n    end\n\n    # initial Δt specified or computed\n    if ode_dt === nothing\n        dtmodel = getdtmodel(ode_solver_type, bl)\n        ode_dt = ClimateMachine.DGMethods.calculate_dt(\n            dg,\n            dtmodel,\n            Q,\n            Courant_number,\n            t0,\n            CFL_direction,\n        )\n    end\n    if !isnan(sim_time)\n        timeend = sim_time\n    end\n    if fixed_number_of_steps < 0\n        numberofsteps = convert(Int, cld(timeend - t0, ode_dt))\n        timeend_dt_adjust && (ode_dt = (timeend - t0) / numberofsteps)\n    else\n        numberofsteps = fixed_number_of_steps\n        timeend = fixed_number_of_steps * ode_dt\n    end\n\n    # create the solver\n    solver = solversetup(ode_solver_type, dg, Q, ode_dt, t0, diffdir)\n\n    @toc SolverConfiguration\n\n    return SolverConfiguration(\n        driver_config.name,\n        driver_config.mpicomm,\n        driver_config.param_set,\n        dg,\n        Q,\n        t0,\n        timeend,\n        ode_dt,\n        init_on_cpu,\n        numberofsteps,\n        init_args,\n        solver,\n        ode_solver_type,\n        diffdir,\n        Courant_number,\n    )\nend\n\nfunction write_debug_init_vtk_and_pvtu(\n    suffix_name,\n    driver_config,\n    dg,\n    state,\n    state_names,\n)\n    mpicomm = driver_config.mpicomm\n    bl = driver_config.bl\n\n    vprefix = @sprintf(\n        \"%s_mpirank%04d_%s\",\n        driver_config.name,\n        MPI.Comm_rank(mpicomm),\n        suffix_name,\n    )\n    out_prefix = joinpath(Settings.output_dir, vprefix)\n\n    writevtk(\n        out_prefix,\n        state,\n        dg,\n        state_names;\n        number_sample_points = Settings.vtk_number_sample_points,\n    )\n\n    # Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        pprefix = @sprintf(\"%s_%s\", driver_config.name, suffix_name)\n        pvtuprefix = joinpath(Settings.output_dir, pprefix)\n\n        # name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_%s\", driver_config.name, i - 1, suffix_name,)\n        end\n        writepvtu(pvtuprefix, prefixes, state_names, eltype(state))\n    end\nend\n\ninclude(\"solver_config_wrappers.jl\")\n"
  },
  {
    "path": "src/InputOutput/VTK/VTK.jl",
    "content": "module VTK\n\nexport writevtk, writepvtu, VTKFieldWriter\n\ninclude(\"writemesh.jl\")\ninclude(\"writevtk.jl\")\ninclude(\"writepvtu.jl\")\ninclude(\"fieldwriter.jl\")\n\nend\n"
  },
  {
    "path": "src/InputOutput/VTK/fieldwriter.jl",
    "content": "import KernelAbstractions: CPU\n\nusing MPI\nusing Printf\n\nusing ..BalanceLaws\nusing ..MPIStateArrays\nusing ..DGMethods: SpaceDiscretization\nusing ..VariableTemplates\n\n\"\"\"\n    VTKFieldWriter(\n        name::String,\n        FT::DataType,\n        fields::Vector{<:Tuple{String, <:Function}};\n        path_prefix = \".\",\n        number_sample_points = 0,\n    )\n\nConstruct a callable type that computes the specified `fields` and\nwrites them to a VTK file in `path_prefix`/. `number_sample_points`\nare passed to [`writevtk`](@ref).\n\nIntended for use in a callback passed to a running simulation; files\nare suffixed with a running number beginning with 0.\n\n# Example\n```julia\n    function pres_fun(atmos::AtmosModel, prognostic::Vars, auxiliary::Vars)\n        ts = recover_thermo_state(atmos, prognostic, auxiliary)\n        air_pressure(ts)\n    end\n    fwriter = VTKFieldWriter(solver_config.name, [(\"pressure\", pres_fun)])\n    cbfw = GenericCallbacks.EveryXSimulationTime(60) do\n        fwriter(solver_config.dg, solver_config.Q)\n    end\n```\n\"\"\"\nmutable struct VTKFieldWriter\n    path_prefix::String\n    name::String\n    number_sample_points::Int\n    nfields::Int\n    field_names::Vector{String}\n    field_funs::Vector{<:Function}\n    vars_type::DataType\n    num::Int\n\n    function VTKFieldWriter(\n        name::String,\n        FT::DataType,\n        fields::Vector{<:Tuple{String, <:Function}};\n        path_prefix = \".\",\n        number_sample_points = 0,\n    )\n        nfields = length(fields)\n        field_names = [name for (name, _) in fields]\n        field_funs = [fun for (_, fun) in fields]\n        vars_type = NamedTuple{\n            tuple(Symbol.(field_names)...),\n            Tuple{[FT for _ in 1:length(fields)]...},\n        }\n        new(\n            path_prefix,\n            name * \"_fields\",\n            number_sample_points,\n            nfields,\n            field_names,\n            field_funs,\n            vars_type,\n            0,\n        )\n    end\nend\nfunction (vfw::VTKFieldWriter)(dg::SpaceDiscretization, Q::MPIStateArray)\n    bl = dg.balance_law\n    fQ = similar(Q, Array; vars = vfw.vars_type, nstate = vfw.nfields)\n\n    if array_device(Q) isa CPU\n        prognostic_array = Q.realdata\n        auxiliary_array = dg.state_auxiliary.realdata\n    else\n        prognostic_array = Array(Q.realdata)\n        auxiliary_array = Array(dg.state_auxiliary.realdata)\n    end\n    FT = eltype(prognostic_array)\n\n    for e in 1:size(prognostic_array, 3)\n        for n in 1:size(prognostic_array, 1)\n            prognostic = Vars{vars_state(bl, Prognostic(), FT)}(view(\n                prognostic_array,\n                n,\n                :,\n                e,\n            ),)\n            auxiliary = Vars{vars_state(bl, Auxiliary(), FT)}(view(\n                auxiliary_array,\n                n,\n                :,\n                e,\n            ),)\n            for i in 1:(vfw.nfields)\n                fQ[n, i, e] = vfw.field_funs[i](bl, prognostic, auxiliary)\n            end\n        end\n    end\n\n    mpirank = MPI.Comm_rank(fQ.mpicomm)\n    vprefix = @sprintf(\"%s_mpirank%04d_num%04d\", vfw.name, mpirank, vfw.num,)\n    outprefix = joinpath(vfw.path_prefix, vprefix)\n    writevtk(outprefix, fQ, dg, number_sample_points = vfw.number_sample_points)\n\n    # Generate the pvtu file for these vtk files\n    if mpirank == 0\n        pprefix = @sprintf(\"%s_num%04d\", vfw.name, vfw.num)\n        pvtuprefix = joinpath(vfw.path_prefix, pprefix)\n\n        prefixes = ntuple(MPI.Comm_size(fQ.mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_num%04d\", vfw.name, i - 1, vfw.num,)\n        end\n        writepvtu(pvtuprefix, prefixes, tuple(vfw.field_names...), eltype(fQ))\n    end\n\n    vfw.num += 1\n    nothing\nend\n"
  },
  {
    "path": "src/InputOutput/VTK/writemesh.jl",
    "content": "using WriteVTK\n\nfunction vtk_connectivity_map_highorder(Nq1, Nq2 = 1, Nq3 = 1)\n    connectivity = Array{Int, 1}(undef, Nq1 * Nq2 * Nq3)\n    L = LinearIndices((1:Nq1, 1:Nq2, 1:Nq3))\n\n    corners = (\n        (1, 1, 1),\n        (Nq1, 1, 1),\n        (Nq1, Nq2, 1),\n        (1, Nq2, 1),\n        (1, 1, Nq3),\n        (Nq1, 1, Nq3),\n        (Nq1, Nq2, Nq3),\n        (1, Nq2, Nq3),\n    )\n    edges = (\n        (2:(Nq1 - 1), 1:1, 1:1),\n        (Nq1:Nq1, 2:(Nq2 - 1), 1:1),\n        (2:(Nq1 - 1), Nq2:Nq2, 1:1),\n        (1:1, 2:(Nq2 - 1), 1:1, 1:1),\n        (2:(Nq1 - 1), 1:1, Nq3:Nq3),\n        (Nq1:Nq1, 2:(Nq2 - 1), Nq3:Nq3),\n        (2:(Nq1 - 1), Nq2:Nq2, Nq3:Nq3),\n        (1:1, 2:(Nq2 - 1), Nq3:Nq3),\n        (1:1, 1:1, 2:(Nq3 - 1)),\n        (Nq1:Nq1, 1:1, 2:(Nq3 - 1)),\n        (1:1, Nq2:Nq2, 2:(Nq3 - 1)),\n        (Nq1:Nq1, Nq2:Nq2, 2:(Nq3 - 1)),\n    )\n    faces = (\n        (1:1, 2:(Nq2 - 1), 2:(Nq3 - 1)),\n        (Nq1:Nq1, 2:(Nq2 - 1), 2:(Nq3 - 1)),\n        (2:(Nq1 - 1), 1:1, 2:(Nq3 - 1)),\n        (2:(Nq1 - 1), Nq2:Nq2, 2:(Nq3 - 1)),\n        (2:(Nq1 - 1), 2:(Nq2 - 1), 1:1),\n        (2:(Nq1 - 1), 2:(Nq2 - 1), Nq3:Nq3),\n    )\n    if Nq2 == Nq3 == 1\n        @assert Nq1 > 1\n        corners = (corners[1:2]...,)\n        edges = (edges[1],)\n        faces = ()\n    elseif Nq3 == 1\n        @assert Nq1 > 1\n        @assert Nq2 > 1\n        corners = (corners[1:4]...,)\n        edges = (edges[1:4]...,)\n        faces = (faces[5],)\n    end\n\n    # corners\n    iter = 1\n    for (i, j, k) in corners\n        connectivity[iter] = L[i, j, k]\n        iter += 1\n    end\n    # edges\n    for (is, js, ks) in edges\n        for k in ks, j in js, i in is\n            connectivity[iter] = L[i, j, k]\n            iter += 1\n        end\n    end\n    # faces\n    for (is, js, ks) in faces\n        for k in ks, j in js, i in is\n            connectivity[iter] = L[i, j, k]\n            iter += 1\n        end\n    end\n    # interior\n    for k in 2:(Nq3 - 1), j in 2:(Nq2 - 1), i in 2:(Nq1 - 1)\n        connectivity[iter] = L[i, j, k]\n        iter += 1\n    end\n    return connectivity\nend\n\n#=\nThis is the 1D WriteMesh routine\n=#\nfunction writemesh_highorder(\n    base_name,\n    x1;\n    x2 = nothing,\n    x3 = nothing,\n    fields = (),\n    realelems = 1:size(x1)[end],\n)\n    (Nq1, _) = size(x1)\n\n    con = vtk_connectivity_map_highorder(Nq1)\n\n    M = MeshCell{VTKCellTypes.VTKCellType, Array{Int, 1}}\n    cells = Array{M, 1}(undef, length(realelems))\n\n    for (i, e) in enumerate(realelems)\n        offset = (e - 1) * Nq1\n        cells[i] = MeshCell(VTKCellTypes.VTK_LAGRANGE_CURVE, offset .+ con)\n    end\n\n    if x2 == nothing\n        @assert isnothing(x3)\n        vtkfile =\n            vtk_grid(\"$(base_name)\", @view(x1[:]), cells; compress = false)\n    elseif x3 == nothing\n        vtkfile = vtk_grid(\n            \"$(base_name)\",\n            @view(x1[:]),\n            @view(x2[:]),\n            cells;\n            compress = false,\n        )\n    else\n        vtkfile = vtk_grid(\n            \"$(base_name)\",\n            @view(x1[:]),\n            @view(x2[:]),\n            @view(x3[:]),\n            cells;\n            compress = false,\n        )\n    end\n    for (name, v) in fields\n        vtk_point_data(vtkfile, v, name)\n    end\n    outfiles = vtk_save(vtkfile)\nend\n\n#=\nThis is the 2D WriteMesh routine\n=#\nfunction writemesh_highorder(\n    base_name,\n    x1,\n    x2;\n    x3 = nothing,\n    fields = (),\n    realelems = 1:size(x1)[end],\n)\n    @assert size(x1) == size(x2)\n    (Nq1, Nq2, _) = size(x1)\n    @assert Nq1 == Nq2\n    con = vtk_connectivity_map_highorder(Nq1, Nq2)\n\n    M = MeshCell{VTKCellTypes.VTKCellType, Array{Int, 1}}\n    cells = Array{M, 1}(undef, length(realelems))\n    for (i, e) in enumerate(realelems)\n        offset = (e - 1) * Nq1 * Nq2\n        cells[i] =\n            MeshCell(VTKCellTypes.VTK_LAGRANGE_QUADRILATERAL, offset .+ con[:])\n    end\n\n    if x3 == nothing\n        vtkfile = vtk_grid(\n            \"$(base_name)\",\n            @view(x1[:]),\n            @view(x2[:]),\n            cells;\n            compress = false,\n        )\n    else\n        vtkfile = vtk_grid(\n            \"$(base_name)\",\n            @view(x1[:]),\n            @view(x2[:]),\n            @view(x3[:]),\n            cells;\n            compress = false,\n        )\n    end\n    for (name, v) in fields\n        vtk_point_data(vtkfile, v, name)\n    end\n    outfiles = vtk_save(vtkfile)\nend\n\n#=\nThis is the 3D WriteMesh routine\n=#\nfunction writemesh_highorder(\n    base_name,\n    x1,\n    x2,\n    x3;\n    fields = (),\n    realelems = 1:size(x1)[end],\n)\n    (Nq1, Nq2, Nq3, _) = size(x1)\n    @assert Nq1 == Nq2 == Nq3\n    con = vtk_connectivity_map_highorder(Nq1, Nq2, Nq3)\n    M = MeshCell{VTKCellTypes.VTKCellType, Array{Int, 1}}\n    cells = Array{M, 1}(undef, length(realelems))\n    for (i, e) in enumerate(realelems)\n        offset = (e - 1) * Nq1 * Nq2 * Nq3\n        cells[i] = MeshCell(VTKCellTypes.VTK_LAGRANGE_HEXAHEDRON, offset .+ con)\n    end\n\n    vtkfile = vtk_grid(\n        \"$(base_name)\",\n        @view(x1[:]),\n        @view(x2[:]),\n        @view(x3[:]),\n        cells;\n        compress = false,\n    )\n    for (name, v) in fields\n        vtk_point_data(vtkfile, v, name)\n    end\n    outfiles = vtk_save(vtkfile)\nend\n\n#=\nThis is the 1D WriteMesh routine\n=#\nfunction writemesh_raw(\n    base_name,\n    x1;\n    x2 = nothing,\n    x3 = nothing,\n    fields = (),\n    realelems = 1:size(x1)[end],\n)\n    (Nq1, _) = size(x1)\n    Nsubcells = (Nq1 - 1)\n\n    M = MeshCell{VTKCellTypes.VTKCellType, Array{Int, 1}}\n    cells = Array{M, 1}(undef, Nsubcells * length(realelems))\n    for e in realelems\n        offset = (e - 1) * Nq1\n        for i in 1:(Nq1 - 1)\n            cells[i + (e - 1) * Nsubcells] =\n                MeshCell(VTKCellTypes.VTK_LINE, offset .+ [i, i + 1])\n        end\n    end\n\n    vtkfile = vtk_grid(\"$(base_name)\", @view(x1[:]), cells; compress = false)\n    for (name, v) in fields\n        vtk_point_data(vtkfile, v, name)\n    end\n    outfiles = vtk_save(vtkfile)\nend\n\n#=\nThis is the 2D WriteMesh routine\n=#\nfunction writemesh_raw(\n    base_name,\n    x1,\n    x2;\n    x3 = nothing,\n    fields = (),\n    realelems = 1:size(x1)[end],\n)\n    @assert size(x1) == size(x2)\n    (Nq1, Nq2, _) = size(x1)\n    Nsubcells = (Nq1 - 1) * (Nq2 - 1)\n\n    M = MeshCell{VTKCellTypes.VTKCellType, Array{Int, 1}}\n    cells = Array{M, 1}(undef, Nsubcells * length(realelems))\n    ind = LinearIndices((1:Nq1, 1:Nq2))\n    for e in realelems\n        offset = (e - 1) * Nq1 * Nq2\n        for j in 1:(Nq2 - 1)\n            for i in 1:(Nq1 - 1)\n                cells[i + (j - 1) * (Nq1 - 1) + (e - 1) * Nsubcells] = MeshCell(\n                    VTKCellTypes.VTK_PIXEL,\n                    offset .+ ind[i:(i + 1), j:(j + 1)][:],\n                )\n            end\n        end\n    end\n\n    if x3 == nothing\n        vtkfile = vtk_grid(\n            \"$(base_name)\",\n            @view(x1[:]),\n            @view(x2[:]),\n            cells;\n            compress = false,\n        )\n    else\n        vtkfile = vtk_grid(\n            \"$(base_name)\",\n            @view(x1[:]),\n            @view(x2[:]),\n            @view(x3[:]),\n            cells;\n            compress = false,\n        )\n    end\n    for (name, v) in fields\n        vtk_point_data(vtkfile, v, name)\n    end\n    outfiles = vtk_save(vtkfile)\nend\n\n#=\nThis is the 3D WriteMesh routine\n=#\nfunction writemesh_raw(\n    base_name,\n    x1,\n    x2,\n    x3;\n    fields = (),\n    realelems = 1:size(x1)[end],\n)\n    (Nq1, Nq2, Nq3, _) = size(x1)\n    (N1, N2, N3) = (Nq1 - 1, Nq2 - 1, Nq3 - 1)\n    Nsubcells = N1 * N2 * N3\n\n    M = MeshCell{VTKCellTypes.VTKCellType, Array{Int, 1}}\n    cells = Array{M, 1}(undef, Nsubcells * length(realelems))\n    ind = LinearIndices((1:Nq1, 1:Nq2, 1:Nq3))\n    for e in realelems\n        offset = (e - 1) * Nq1 * Nq2 * Nq3\n        for k in 1:N3\n            for j in 1:N2\n                for i in 1:N1\n                    cells[i + (j - 1) * N1 + (k - 1) * N1 * N2 + (e - 1) * Nsubcells] =\n                        MeshCell(\n                            VTKCellTypes.VTK_VOXEL,\n                            offset .+ ind[i:(i + 1), j:(j + 1), k:(k + 1)][:],\n                        )\n                end\n            end\n        end\n    end\n\n    vtkfile = vtk_grid(\n        \"$(base_name)\",\n        @view(x1[:]),\n        @view(x2[:]),\n        @view(x3[:]),\n        cells;\n        compress = false,\n    )\n    for (name, v) in fields\n        vtk_point_data(vtkfile, v, name)\n    end\n    outfiles = vtk_save(vtkfile)\nend\n"
  },
  {
    "path": "src/InputOutput/VTK/writepvtu.jl",
    "content": "using ..TicToc\n\n\"\"\"\n    writepvtu(pvtuprefix, vtkprefixes, fieldnames, FT)\n\nWrite a pvtu file with the prefix 'pvtuprefix' for the collection of vtk files\ngiven by 'vtkprefixes' using names  of fields 'fieldnames'. The data in the\n`vtu` files is of type `FT`.\n\"\"\"\nfunction writepvtu(pvtuprefix, vtkprefixes, fieldnames, FT)\n    open(pvtuprefix * \".pvtu\", \"w\") do pvtufile\n        write(\n            pvtufile,\n            \"\"\"\n            <?xml version=\"1.0\"?>\n            <VTKFile type=\"PUnstructuredGrid\" version=\"0.1\" compressor=\"vtkZLibDataCompressor\" byte_order=\"LittleEndian\">\n              <PUnstructuredGrid GhostLevel=\"0\">\n                <PPoints>\n                  <PDataArray type=\"$FT\" Name=\"Position\" NumberOfComponents=\"3\" format=\"binary\"/>\n                </PPoints>\n                <PPointData>\n            \"\"\",\n        )\n\n        for name in fieldnames\n            write(\n                pvtufile,\n                \"\"\"\n                      <PDataArray type=\"$FT\" Name=\"$name\" format=\"binary\"/>\n                \"\"\",\n            )\n        end\n\n        write(\n            pvtufile,\n            \"\"\"\n                </PPointData>\n            \"\"\",\n        )\n\n        for name in vtkprefixes\n            write(\n                pvtufile,\n                \"\"\"\n                    <Piece Source=\"$name.vtu\"/>\n                \"\"\",\n            )\n        end\n\n        write(\n            pvtufile,\n            \"\"\"\n              </PUnstructuredGrid>\n            </VTKFile>\n            \"\"\",\n        )\n    end\n    return nothing\nend\n"
  },
  {
    "path": "src/InputOutput/VTK/writevtk.jl",
    "content": "import KernelAbstractions: CPU\n\nusing ..Mesh.Grids\nusing ..Mesh.Elements: interpolationmatrix\nusing ..MPIStateArrays\nusing ..DGMethods: SpaceDiscretization\nusing ..TicToc\n\n\"\"\"\n    writevtk(prefix, Q::MPIStateArray, dg::SpaceDiscretization [, fieldnames];\n             number_sample_points = 0)\n\nWrite a vtk file for all the fields in the state array `Q` using geometry and\nconnectivity information from `dg.grid`. The filename will start with `prefix`\nwhich may also contain a directory path. The names used for each of the fields\nin the vtk file can be specified through the collection of strings `fieldnames`;\nif not specified the fields names will be `\"Q1\"` through `\"Qk\"` where `k` is the\nnumber of states in `Q`, i.e., `k = size(Q,2)`.\n\nIf `number_sample_points > 0` then the fields are sampled on an equally spaced,\ntensor-product grid of points with 'number_sample_points' in each direction and\nthe output VTK element type is set to by a VTK lagrange type.\n\nWhen `number_sample_points == 0` the raw nodal values are saved, and linear VTK\nelements are used connecting the degree of freedom boxes.\n\"\"\"\nfunction writevtk(\n    prefix,\n    Q::MPIStateArray,\n    dg::SpaceDiscretization,\n    fieldnames = nothing;\n    number_sample_points = 0,\n)\n    vgeo = dg.grid.vgeo\n    h_vgeo = array_device(vgeo) isa CPU ? vgeo : Array(vgeo)\n    h_Q = array_device(Q) isa CPU ? Q.data : Array(Q)\n    writevtk_helper(\n        prefix,\n        h_vgeo,\n        h_Q,\n        dg.grid,\n        fieldnames;\n        number_sample_points = number_sample_points,\n    )\n    return nothing\nend\n\n\"\"\"\n    writevtk(prefix, Q::MPIStateArray, dg::SpaceDiscretization, fieldnames,\n             state_auxiliary::MPIStateArray, auxfieldnames;\n             number_sample_points = 0)\n\nWrite a vtk file for all the fields in the state array `Q` and auxiliary state\n`state_auxiliary` using geometry and connectivity information from `dg.grid`. The\nfilename will start with `prefix` which may also contain a directory path. The\nnames used for each of the fields in the vtk file can be specified through the\ncollection of strings `fieldnames` and `auxfieldnames`.\n\nIf `fieldnames === nothing` then the fields names will be `\"Q1\"` through `\"Qk\"`\nwhere `k` is the number of states in `Q`, i.e., `k = size(Q,2)`.\n\nIf `auxfieldnames === nothing` then the fields names will be `\"aux1\"` through\n`\"auxk\"` where `k` is the number of states in `state_auxiliary`, i.e., `k =\nsize(state_auxiliary,2)`.\n\nIf `number_sample_points > 0` then the fields are sampled on an equally spaced,\ntensor-product grid of points with 'number_sample_points' in each direction and\nthe output VTK element type is set to by a VTK lagrange type.\n\nWhen `number_sample_points == 0` the raw nodal values are saved, and linear VTK\nelements are used connecting the degree of freedom boxes.\n\"\"\"\nfunction writevtk(\n    prefix,\n    Q::MPIStateArray,\n    dg::SpaceDiscretization,\n    fieldnames,\n    state_auxiliary,\n    auxfieldnames;\n    number_sample_points = 0,\n)\n    vgeo = dg.grid.vgeo\n    device = array_device(Q)\n    (h_vgeo, h_Q, h_aux) =\n        device isa CPU ? (vgeo, Q.data, state_auxiliary.data) :\n        (Array(vgeo), Array(Q), Array(state_auxiliary))\n    writevtk_helper(\n        prefix,\n        h_vgeo,\n        h_Q,\n        dg.grid,\n        fieldnames,\n        h_aux,\n        auxfieldnames;\n        number_sample_points = number_sample_points,\n    )\n    return nothing\nend\n\nreshaped_view(fields, ind, Np_N1, Nq, nelem) =\n    ntuple(length(fields)) do i\n        fld = reshape(fields[i], Nq..., nelem)\n        fld2 = @view fld[ind..., :]\n        reshape(fld2, (Np_N1, nelem))\n    end\n\n\"\"\"\n    writevtk_helper(prefix, vgeo::Array, Q::Array, grid, fieldnames)\n\nInternal helper function for `writevtk`\n\"\"\"\nfunction writevtk_helper(\n    prefix,\n    vgeo::Array,\n    Q::Array,\n    grid,\n    fieldnames,\n    state_auxiliary = nothing,\n    auxfieldnames = nothing;\n    number_sample_points,\n)\n    @assert number_sample_points >= 0\n\n    dim = dimensionality(grid)\n    N = polynomialorders(grid)\n    Nq = N .+ 1\n\n    nelem = size(Q)[end]\n\n    X = grid.x_vtk[1:dim]\n    fields = ntuple(j -> (@view Q[:, j, :]), size(Q, 2))\n\n    auxfields =\n        isnothing(state_auxiliary) ? () :\n        (\n            auxfields = ntuple(\n                j -> (@view state_auxiliary[:, j, :]),\n                size(state_auxiliary, 2),\n            )\n        )\n\n    # If any dimension are N = 0 we mirror these out to the boundaries for viz\n    # purposed\n    if any(N .== 0)\n        Nq_N1 = max.(Nq, 2)\n        Np_N1 = prod(Nq_N1)\n        ind = ntuple(i -> N[i] == 0 ? [1, 1] : Colon(), dim)\n        fields = reshaped_view(fields, ind, Np_N1, Nq, nelem)\n        auxfields = reshaped_view(auxfields, ind, Np_N1, Nq, nelem)\n        Nq = Nq_N1\n    end\n\n    # Interpolate to an equally spaced grid if necessary\n    if number_sample_points > 0\n        FT = eltype(Q)\n        # If any dimension are N = 0 we manual set (-1, 1) grids\n        ξ = ntuple(\n            i -> N[i] == 0 ? FT.([-1, 1]) : referencepoints(grid)[i],\n            dim,\n        )\n        ξdst = range(FT(-1); length = number_sample_points, stop = 1)\n        I1d = ntuple(i -> interpolationmatrix(ξ[dim - i + 1], ξdst), dim)\n        I = kron(I1d...)\n        fields = ntuple(i -> I * fields[i], length(fields))\n        auxfields = ntuple(i -> I * auxfields[i], length(auxfields))\n        X = ntuple(i -> I * X[i], length(X))\n        Nq = ntuple(j -> number_sample_points, dim)\n    end\n\n    X = ntuple(i -> reshape(X[i], Nq..., nelem), length(X))\n\n    function get_fields(x, fieldnames, name)\n        x = ntuple(i -> reshape(x[i], Nq..., nelem), length(x))\n        if fieldnames === nothing\n            return ntuple(i -> (\"$name$i\", x[i]), length(x))\n        else\n            return ntuple(i -> (fieldnames[i], x[i]), length(x))\n        end\n    end\n\n    fields = get_fields(fields, fieldnames, \"Q\")\n    auxfields = get_fields(auxfields, auxfieldnames, \"aux\")\n\n    fields = (fields..., auxfields...)\n    if number_sample_points > 0\n        return writemesh_highorder(\n            prefix,\n            X...;\n            fields = fields,\n            realelems = grid.topology.realelems,\n        )\n    else\n        return writemesh_raw(\n            prefix,\n            X...;\n            fields = fields,\n            realelems = grid.topology.realelems,\n        )\n    end\nend\n"
  },
  {
    "path": "src/InputOutput/Writers/Writers.jl",
    "content": "\"\"\"\n    Writers\n\nAbstracts writing dimensioned data so that output can be to a NetCDF\nfile or another file format.\n\"\"\"\n\nmodule Writers\n\nexport AbstractWriter, NetCDFWriter, full_name, init_data, append_data\n\nabstract type AbstractWriter end\n\n\"\"\"\n    full_name(\n        writer,\n        filename,\n    )\n\nAppends the appropriate (based on `writer`) extension to the specified\nfilename.\n\"\"\"\nfunction full_name end\n\n\"\"\"\n    init_data(\n        writer,\n        filename,\n        no_overwrite,\n        dims,\n        vars,\n    )\n\nCreates the specified file, initializing it with the specified dimension\ninformation. An unlimited `time` dimension is implicitly created. The\nspecified variables are also defined. This function must be called before\n`append_data()`. Specialized by every `Writer` subtype.\n\n# Arguments:\n# - `writer`: instance of a subtype of `AbstractWriter`.\n# - `filename`: into which to write data (without extension).\n# - `no_overwrite`: if `true`, throw an error if `filename` exists and\n#   would be overwritten.\n# - `dims`: Dict of dimension name to 2-tuple of dimension values and Dict\n#   of attributes.\n# - `vars`: Dict of variable name to 3-tuple of a k-tuple of dimension\n#   names, variable type, and Dict of attributes.\n\"\"\"\nfunction init_data end\n\n\"\"\"\n    append_data(\n        writer,\n        filename,\n        varvals,\n        simtime,\n    )\n\nAppends the specified variables to the specified file. The file must have\nbeen previously created with `init_data()`. `simtime` is appended to the\n`time` dimension variable. Specialized by every `Writer` subtype.\n\n# Arguments:\n# - `writer`: instance of a subtype of `AbstractWriter`.\n# - `filename`: into which to write data (without extension).\n# - `varvals`: Dict of variable name to k-dimensional array of values.\n# - `simtime`: Current simulation time.\n\"\"\"\nfunction append_data end\n\ninclude(\"netcdf_writer.jl\")\n\nend\n"
  },
  {
    "path": "src/InputOutput/Writers/netcdf_writer.jl",
    "content": "# we order the time dimension last because Julia is column-major (see:\n# https://github.com/Alexander-Barth/NCDatasets.jl/issues/87#issuecomment-636098859)\n\nusing NCDatasets\nusing OrderedCollections\n\nmutable struct NetCDFWriter <: AbstractWriter\n    filename::Union{Nothing, String}\n\n    NetCDFWriter() = new(nothing)\nend\n\nfunction full_name(writer::NetCDFWriter, filename = nothing)\n    if !isnothing(filename)\n        return filename * \".nc\"\n    else\n        return writer.filename * \".nc\"\n    end\nend\n\nfunction init_data(nc::NetCDFWriter, filename, no_overwrite, dims, vars)\n    nm = full_name(nc, filename)\n    if ispath(nm)\n        if no_overwrite\n            error(\"$(nm) exists and overwriting is forbidden.\")\n        else\n            @warn \"$(nm) exists and will be overwritten.\"\n        end\n    end\n    Dataset(nm, \"c\") do ds\n        # define spatial and time dimensions\n        for (dn, (dv, da)) in dims\n            defDim(ds, dn, length(dv))\n        end\n        defDim(ds, \"time\", Inf) # Inf sets UNLIMITED dimension\n\n        # include dimensions as variables\n        for (dn, (dv, da)) in dims\n            defVar(ds, dn, dv, (dn,), attrib = da)\n        end\n        defVar(\n            ds,\n            \"time\",\n            Float64,\n            (\"time\",),\n            attrib = OrderedDict(\n                \"units\" => \"seconds since 1900-01-01 00:00:00\",\n                \"long_name\" => \"time\",\n            ),\n        )\n\n        # define variables\n        for (vn, (vd, vt, va)) in vars\n            defVar(ds, vn, vt, (vd..., \"time\"), attrib = va)\n        end\n    end\n    nc.filename = filename\n    return nothing\nend\n\nfunction append_data(nc::NetCDFWriter, varvals, simtime)\n    Dataset(full_name(nc), \"a\") do ds\n        timevar = ds[\"time\"]\n        t = length(timevar) + 1\n        timevar[t] = simtime\n        for (vn, vv) in varvals\n            dsvar = ds[vn]\n            dsvar[ntuple(_ -> Colon(), ndims(vv))..., t] = vv\n        end\n    end\n    return nothing\nend\n"
  },
  {
    "path": "src/LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "src/Land/Model/LandModel.jl",
    "content": "module Land\n\nusing DocStringExtensions\nusing UnPack\nusing DispatchedTuples\nusing LinearAlgebra, StaticArrays\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet:\n    ρ_cloud_liq, ρ_cloud_ice, cp_l, cp_i, T_0, LH_f0, T_freeze, grav\n\nusing ..VariableTemplates\nusing ..MPIStateArrays\nusing ..BalanceLaws\nimport ..BalanceLaws:\n    BalanceLaw,\n    prognostic_vars,\n    get_prog_state,\n    flux,\n    source,\n    eq_tends,\n    precompute,\n    vars_state,\n    boundary_conditions,\n    parameter_set,\n    boundary_state!,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    nodal_init_state_auxiliary!,\n    init_state_prognostic!,\n    nodal_update_auxiliary_state!,\n    wavespeed\nusing ..DGMethods: LocalGeometry, DGModel\nexport LandModel\n\n\n\"\"\"\n    LandModel{PS, S, SF, LBC, SRC, SRCDT, IS} <: BalanceLaw\n\nA BalanceLaw for land modeling.\nUsers may over-ride prescribed default values for each field.\n\n# Usage\n\n    LandModel(\n        param_set,\n        soil;\n        surface,\n        boundary_conditions,\n        source,\n        source_dt,\n        init_state_prognostic\n    )\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct LandModel{PS, S, SF, LBC, SRC, SRCDT, IS} <: BalanceLaw\n    \"Parameter set\"\n    param_set::PS\n    \"Soil model\"\n    soil::S\n    \"Surface Flow model\"\n    surface::SF\n    \"struct of boundary conditions\"\n    boundary_conditions::LBC\n    \"Source Terms (Problem specific source terms)\"\n    source::SRC\n    \"DispatchedTuple of sources\"\n    source_dt::SRCDT\n    \"Initial Condition (Function to assign initial values of state variables)\"\n    init_state_prognostic::IS\nend\n\nparameter_set(m::LandModel) = m.param_set\n\n\"\"\"\n    LandModel(\n        param_set::AbstractParameterSet,\n        soil::BalanceLaw;\n        surface::BalanceLaw = NoSurfaceFlowModel(),\n        boundary_conditions::LBC = (),\n        source::SRC = (),\n        init_state_prognostic::IS = nothing\n    ) where {SRC, IS, LBC}\n\nConstructor for the LandModel structure.\n\"\"\"\nfunction LandModel(\n    param_set::AbstractParameterSet,\n    soil::BalanceLaw;\n    surface::BalanceLaw = NoSurfaceFlowModel(),\n    boundary_conditions::LBC = LandDomainBC(),\n    source::SRC = (),\n    init_state_prognostic::IS = nothing,\n) where {SRC, IS, LBC}\n    @assert init_state_prognostic ≠ nothing\n    source_dt = prognostic_var_source_map(source)\n    land = (\n        param_set,\n        soil,\n        surface,\n        boundary_conditions,\n        source,\n        source_dt,\n        init_state_prognostic,\n    )\n    return LandModel{typeof.(land)...}(land...)\nend\n\n\nfunction vars_state(land::LandModel, st::Prognostic, FT)\n    @vars begin\n        soil::vars_state(land.soil, st, FT)\n        surface::vars_state(land.surface, st, FT)\n    end\nend\n\n\nfunction vars_state(land::LandModel, st::Auxiliary, FT)\n    @vars begin\n        x::FT\n        y::FT\n        z::FT\n        soil::vars_state(land.soil, st, FT)\n        surface::vars_state(land.surface, st, FT)\n    end\nend\n\nfunction vars_state(land::LandModel, st::Gradient, FT)\n    @vars begin\n        soil::vars_state(land.soil, st, FT)\n        surface::vars_state(land.surface, st, FT)\n    end\nend\n\nfunction vars_state(land::LandModel, st::GradientFlux, FT)\n    @vars begin\n        soil::vars_state(land.soil, st, FT)\n        surface::vars_state(land.surface, st, FT)\n    end\nend\n\nfunction nodal_init_state_auxiliary!(\n    land::LandModel,\n    aux::Vars,\n    tmp::Vars,\n    geom::LocalGeometry,\n)\n    aux.x = geom.coord[1]\n    aux.y = geom.coord[2]\n    aux.z = geom.coord[3]\n    land_init_aux!(land, land.soil, aux, geom)\n    land_init_aux!(land, land.surface, aux, geom)\nend\n\nfunction compute_gradient_argument!(\n    land::LandModel,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n\n    compute_gradient_argument!(land, land.soil, transform, state, aux, t)\nend\n\nfunction compute_gradient_flux!(\n    land::LandModel,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n\n    compute_gradient_flux!(\n        land,\n        land.soil,\n        diffusive,\n        ∇transform,\n        state,\n        aux,\n        t,\n    )\n\nend\n\nfunction nodal_update_auxiliary_state!(\n    land::LandModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    land_nodal_update_auxiliary_state!(land, land.soil, state, aux, t)\n    land_nodal_update_auxiliary_state!(land, land.surface, state, aux, t)\nend\n\nfunction init_state_prognostic!(\n    land::LandModel,\n    state::Vars,\n    aux::Vars,\n    coords,\n    t,\n    args...,\n)\n    land.init_state_prognostic(land, state, aux, coords, t, args...)\nend\n\ninclude(\"prog_types.jl\")\ninclude(\"RadiativeEnergyFlux.jl\")\nusing .RadiativeEnergyFlux\ninclude(\"SoilWaterParameterizations.jl\")\nusing .SoilWaterParameterizations\ninclude(\"SoilHeatParameterizations.jl\")\nusing .SoilHeatParameterizations\ninclude(\"soil_model.jl\")\ninclude(\"soil_water.jl\")\ninclude(\"soil_heat.jl\")\ninclude(\"Runoff.jl\")\nusing .Runoff\ninclude(\"land_bc.jl\")\ninclude(\"SurfaceFlow.jl\")\nusing .SurfaceFlow\ninclude(\"soil_bc.jl\")\ninclude(\"prognostic_vars.jl\")\ninclude(\"land_tendencies.jl\")\ninclude(\"source.jl\")\n\nfunction wavespeed(m::LandModel, n⁻, state::Vars, aux::Vars, t::Real, direction)\n    FT = eltype(state)\n    h = max(state.surface.height, FT(0.0))\n    v = calculate_velocity(m.surface, aux.x, aux.y, h)\n    speed = norm(v)\n    return speed\nend\n\nend # Module\n"
  },
  {
    "path": "src/Land/Model/RadiativeEnergyFlux.jl",
    "content": "module RadiativeEnergyFlux\n\nusing ...VariableTemplates\nusing DocStringExtensions\n\nexport AbstractNetSwFluxModel,\n    PrescribedSwFluxAndAlbedo,\n    PrescribedNetSwFlux,\n    compute_net_sw_flux,\n    compute_net_radiative_energy_flux\n\n\"\"\"\n   AbstractNetSwFluxModel{FT <: AbstractFloat}\n\"\"\"\nabstract type AbstractNetSwFluxModel{FT <: AbstractFloat} end\n\n\"\"\"\n    PrescribedSwFluxAndAlbedo{FT, FN1, FN2} <: AbstractNetSwFluxModel{FT}\n\nStructure which contains functions for shortwave albedo and\nshortwave fluxes. They are user-defined, constant across the domain\nand can be a function of time.\n\nAll fluxes are assumed to be normal to the surface.\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct PrescribedSwFluxAndAlbedo{FT, FN1, FN2} <: AbstractNetSwFluxModel{FT}\n    \"Shortwave albedo in grid. No units.\"\n    α::FN1\n    \"Shortwave flux in grid. Units of J m-2 s-1\"\n    swf::FN2\n    function PrescribedSwFluxAndAlbedo(\n        ::Type{FT};\n        α::FN1 = (t) -> FT(NaN),\n        swf::FN2 = (t) -> FT(NaN),\n    ) where {FT, FN1, FN2}\n        args = (α, swf)\n        return new{FT, FN1, FN2}(args...)\n    end\nend\n\n\"\"\"\n    PrescribedNetSwFlux{FT,FN3} <: AbstractNetSwFluxModel{FT}\n\nStructure which contains net shortwave flux values. The function is\nuser-defined, constant across the domain and can be a function of time.\n\nAll fluxes are assumed to be normal to the surface.\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct PrescribedNetSwFlux{FT, FN3} <: AbstractNetSwFluxModel{FT}\n    \"Net shortwave flux. Units of J m-2 s-1\"\n    nswf::FN3\n    function PrescribedNetSwFlux(\n        ::Type{FT};\n        nswf::FN3 = (t) -> FT(NaN),\n    ) where {FT, FN3}\n        return new{FT, FN3}(nswf)\n    end\nend\n\n\"\"\"\n    compute_net_sw_flux(\n        nswf_model::PrescribedSwFluxAndAlbedo{FT},\n        t::Real,\n    ) where {FT}\n\nComputes the net shortwave flux magnitude as a function of time.\n\"\"\"\nfunction compute_net_sw_flux(\n    nswf_model::PrescribedSwFluxAndAlbedo{FT},\n    t::Real,\n) where {FT}\n    net_sw_flux = (FT(1) - nswf_model.α(t)) * nswf_model.swf(t)\n    return net_sw_flux\nend\n\n\"\"\"\n    compute_net_sw_flux(\n        nswf_model:: PrescribedNetSwFlux{FT},\n        t::Real\n    ) where {FT}\n\nComputes the net shortwave flux magnitude as a function of time.\n\"\"\"\nfunction compute_net_sw_flux(\n    nswf_model::PrescribedNetSwFlux{FT},\n    t::Real,\n) where {FT}\n    net_sw_flux = nswf_model.nswf(t)\n    return net_sw_flux\nend\n\n\"\"\"\n    compute_net_radiative_energy_flux(\n        nswf_model::AbstractNetSwFluxModel{FT},\n        t::Real\n    )\n\nReturns the net radiative energy flux magnitude as a function of time. Will\ninclude long wave fluxes in future version.\n\nThe flux vector is this magnitude * -n̂, where n̂ is the normal vector pointing out\nof the domain at the surface.\n\"\"\"\nfunction compute_net_radiative_energy_flux(\n    nswf_model::AbstractNetSwFluxModel{FT},\n    t::Real,\n) where {FT}\n    net_radiative_flux = compute_net_sw_flux(nswf_model, t)\n    return net_radiative_flux\nend\n\nend\n"
  },
  {
    "path": "src/Land/Model/Runoff.jl",
    "content": "module Runoff\n\nusing LinearAlgebra\nusing DocStringExtensions\n\n\nusing ...VariableTemplates\nusing ...Land:\n    SoilModel,\n    pressure_head,\n    hydraulic_conductivity,\n    get_temperature,\n    effective_saturation,\n    impedance_factor,\n    viscosity_factor,\n    moisture_factor\n\nexport AbstractPrecipModel,\n    DrivenConstantPrecip,\n    AbstractSurfaceRunoffModel,\n    NoRunoff,\n    compute_surface_grad_bc,\n    CoarseGridRunoff\n\n\"\"\"\n    AbstractPrecipModel{FT <: AbstractFloat}\n\"\"\"\nabstract type AbstractPrecipModel{FT <: AbstractFloat} end\n\n\"\"\"\n    DrivenConstantPrecip{FT, F} <: AbstractPrecipModel{FT}\n\nInstance of a precipitation distribution where the precipication value\nis constant across the domain. However, this value can change in time.\n\nPrecipitation is assumed to be aligned with the vertical, i.e. P⃗ = Pẑ,\nwith P<0.  \n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct DrivenConstantPrecip{FT, F} <: AbstractPrecipModel{FT}\n    \"Mean precipitation in grid\"\n    mp::F\n    function DrivenConstantPrecip{FT}(mp::F) where {FT, F}\n        new{FT, F}(mp)\n    end\nend\n\nfunction (dcp::DrivenConstantPrecip{FT})(t::Real) where {FT}\n    return FT(dcp.mp(t))\nend\n\n\"\"\"\n    AbstractSurfaceRunoffModel\n\nAbstract type for different surface runoff models. Currently, only\n`NoRunoff` is supported.\n\"\"\"\nabstract type AbstractSurfaceRunoffModel end\n\n\"\"\"\n    NoRunoff <: AbstractSurfaceRunoffModel\n\nChosen when no runoff is to be modeled.\n\"\"\"\nstruct NoRunoff <: AbstractSurfaceRunoffModel end\n\n\n\"\"\"\n    CoarseGridRunoff{FT} <: AbstractSurfaceRunoffModel\n\nChosen when no subgrid effects are to be modeled.\n\"\"\"\nstruct CoarseGridRunoff{FT} <: AbstractSurfaceRunoffModel\n    \"Mean vertical resolution at the surface\"\n    Δz::FT\nend\n\n\"\"\"\n    function compute_surface_grad_bc(soil::SoilModel,\n                                     runoff_model::CoarseGridRunoff,\n                                     precip_model::AbstractPrecipModel,\n                                     n̂,\n                                     state⁻::Vars,\n                                     diff⁻::Vars,\n                                     aux⁻::Vars,\n                                     t::Real\n                                     )\n\nGiven a runoff model and a precipitation distribution function, compute \nthe surface water flux normal to the surface. The sign of the flux\n(inwards or outwards) is determined by the magnitude of precipitation,\nevaporation, and infiltration.\n\"\"\"\nfunction compute_surface_grad_bc(\n    soil::SoilModel,\n    runoff_model::CoarseGridRunoff,\n    precip_model::AbstractPrecipModel,\n    n̂,\n    state⁻::Vars,\n    diff⁻::Vars,\n    aux⁻::Vars,\n    t::Real,\n)\n    FT = eltype(state⁻)\n    precip_vector = (FT(0), FT(0), precip_model(t))\n    incident_water_flux = dot(precip_vector, n̂)\n    Δz = runoff_model.Δz\n    water = soil.water\n    param_functions = soil.param_functions\n    hydraulics = water.hydraulics(aux⁻)\n    ν = param_functions.porosity\n    θ_r = param_functions.water.θ_r(aux⁻)\n    S_s = param_functions.water.S_s(aux⁻)\n\n\n\n    T = get_temperature(soil.heat, aux⁻, t)\n    θ_i = state⁻.soil.water.θ_i\n    # Ponding Dirichlet BC\n    ϑ_bc = FT(ν - θ_i)\n    # Value below surface\n    ϑ_below = state⁻.soil.water.ϑ_l\n\n    # Approximate derivative of hydraulic head with respect to z\n    ∂h∂z =\n        FT(1) +\n        (\n            pressure_head(hydraulics, ν, S_s, θ_r, ϑ_bc, θ_i) -\n            pressure_head(hydraulics, ν, S_s, θ_r, ϑ_below, θ_i)\n        ) / Δz\n\n\n    S_l = effective_saturation(ν, ϑ_bc, θ_r)\n    f_i = θ_i / (ϑ_bc + θ_i)\n    impedance_f = impedance_factor(water.impedance_factor, f_i)\n    viscosity_f = viscosity_factor(water.viscosity_factor, T)\n    moisture_f = moisture_factor(water.moisture_factor, hydraulics, S_l)\n    K = hydraulic_conductivity(\n        param_functions.water.Ksat(aux⁻),\n        impedance_f,\n        viscosity_f,\n        moisture_f,\n    )\n\n    i_c = n̂ * (K * ∂h∂z)\n    if incident_water_flux < -norm(i_c) # More negative if both are negative,\n        #ponding BC\n        K∇h⁺ = i_c\n    else\n\n        K∇h⁺ = n̂ * (-FT(2) * incident_water_flux) - diff⁻.soil.water.K∇h\n    end\n    return K∇h⁺\nend\n\n\n\n\"\"\"\n    function compute_surface_grad_bc(soil::SoilModel,\n                                     runoff_model::NoRunoff,\n                                     precip_model::AbstractPrecipModel,\n                                     n̂,\n                                     state⁻::Vars,\n                                     aux⁻::Vars,\n                                     diff⁻::Vars,\n                                     t::Real\n                                     )\n\nGiven a runoff model and a precipitation distribution function, compute \nthe surface water flux normal to the surface. In this case, no runoff is\nassumed. The direction of the flux (inwards or outwards) depends on the\nmagnitude of evaporation, precipitation, and infiltration.\n\"\"\"\nfunction compute_surface_grad_bc(\n    soil::SoilModel,\n    runoff_model::NoRunoff,\n    precip_model::AbstractPrecipModel,\n    n̂,\n    state⁻::Vars,\n    diff⁻::Vars,\n    aux⁻::Vars,\n    t::Real,\n)\n    FT = eltype(state⁻)\n    precip_vector = (FT(0), FT(0), precip_model(t))\n    incident_water_flux = dot(precip_vector, n̂)\n    K∇h⁺ = n̂ * (-FT(2) * incident_water_flux) - diff⁻.soil.water.K∇h\n    return K∇h⁺\nend\n\nend\n"
  },
  {
    "path": "src/Land/Model/SoilHeatParameterizations.jl",
    "content": "\"\"\"\n    SoilHeatParameterizations\n\nFunctions for volumetric heat capacity, temperature as a function\nof volumetric internal energy, saturated thermal conductivity, thermal\nconductivity, relative saturation and the Kersten number are included.\nHeat capacities denoted by `ρc_` are volumetric, while `cp_` denotes an isobaric\nspecific heat capacity.\n\"\"\"\nmodule SoilHeatParameterizations\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: ρ_cloud_liq, ρ_cloud_ice, cp_l, cp_i, T_0, LH_f0\nusing CLIMAParameters.Atmos.Microphysics: K_therm\nusing DocStringExtensions\n\nexport volumetric_heat_capacity,\n    volumetric_internal_energy,\n    saturated_thermal_conductivity,\n    thermal_conductivity,\n    relative_saturation,\n    kersten_number,\n    volumetric_internal_energy_liq,\n    temperature_from_ρe_int,\n    k_solid,\n    k_dry,\n    ksat_unfrozen,\n    ksat_frozen\n\n\"\"\"\n    function temperature_from_ρe_int(\n        ρe_int::FT,\n        θ_i::FT,\n        ρc_s::FT,\n        param_set::AbstractParameterSet\n    ) where {FT}\n\nComputes the temperature of soil given `θ_i` and volumetric\ninternal energy `ρe_int`.\n\"\"\"\nfunction temperature_from_ρe_int(\n    ρe_int::FT,\n    θ_i::FT,\n    ρc_s::FT,\n    param_set::AbstractParameterSet,\n) where {FT}\n    _ρ_i = FT(ρ_cloud_ice(param_set))\n    _T_ref = FT(T_0(param_set))\n    _LH_f0 = FT(LH_f0(param_set))\n    T = _T_ref + (ρe_int + θ_i * _ρ_i * _LH_f0) / ρc_s\n    return T\nend\n\n\"\"\"\n    volumetric_heat_capacity(\n        θ_l::FT,\n        θ_i::FT,\n        ρc_ds::FT,\n        param_set::AbstractParameterSet\n    ) where {FT}\n\nCompute the expression for volumetric heat capacity.\n\"\"\"\nfunction volumetric_heat_capacity(\n    θ_l::FT,\n    θ_i::FT,\n    ρc_ds::FT,\n    param_set::AbstractParameterSet,\n) where {FT}\n    _ρ_i = FT(ρ_cloud_ice(param_set))\n    ρcp_i = FT(cp_i(param_set) * _ρ_i)\n\n    _ρ_l = FT(ρ_cloud_liq(param_set))\n    ρcp_l = FT(cp_l(param_set) * _ρ_l)\n\n    ρc_s = ρc_ds + θ_l * ρcp_l + θ_i * ρcp_i\n    return ρc_s\nend\n\n\"\"\"\n    volumetric_internal_energy(\n        θ_i::FT,\n        ρc_s::FT,\n        T::FT,\n        param_set::AbstractParameterSet\n    ) where {FT}\n\nCompute the expression for volumetric internal energy.\n\"\"\"\nfunction volumetric_internal_energy(\n    θ_i::FT,\n    ρc_s::FT,\n    T::FT,\n    param_set::AbstractParameterSet,\n) where {FT}\n    _ρ_i = FT(ρ_cloud_ice(param_set))\n    _LH_f0 = FT(LH_f0(param_set))\n    _T_ref = FT(T_0(param_set))\n    ρe_int = ρc_s * (T - _T_ref) - θ_i * _ρ_i * _LH_f0\n    return ρe_int\nend\n\n\"\"\"\n    saturated_thermal_conductivity(\n        θ_l::FT,\n        θ_i::FT,\n        κ_sat_unfrozen::FT,\n        κ_sat_frozen::FT\n    ) where {FT}\n\nCompute the expression for saturated thermal conductivity of soil matrix.\n\"\"\"\nfunction saturated_thermal_conductivity(\n    θ_l::FT,\n    θ_i::FT,\n    κ_sat_unfrozen::FT,\n    κ_sat_frozen::FT,\n) where {FT}\n    θ_w = θ_l + θ_i\n    if θ_w < eps(FT)\n        κ_sat = FT(0.0)\n    else\n        κ_sat = FT(κ_sat_unfrozen^(θ_l / θ_w) * κ_sat_frozen^(θ_i / θ_w))\n    end\n\n    return κ_sat\nend\n\n\"\"\"\n    relative_saturation(\n            θ_l::FT,\n            θ_i::FT,\n            porosity::FT\n    ) where {FT}\n\nCompute the expression for relative saturation.\n\"\"\"\nfunction relative_saturation(θ_l::FT, θ_i::FT, porosity::FT) where {FT}\n    S_r = (θ_l + θ_i) / porosity\n    return S_r\nend\n\"\"\"\n    kersten_number(\n        θ_i::FT,\n        S_r::FT,\n        soil_param_functions::PS\n    ) where {FT, PS}\n\nCompute the expression for the Kersten number.\n\"\"\"\nfunction kersten_number(\n    θ_i::FT,\n    S_r::FT,\n    soil_param_functions::PS,\n) where {FT, PS}\n    a = soil_param_functions.a\n    b = soil_param_functions.b\n    ν_ss_om = soil_param_functions.ν_ss_om\n    ν_ss_quartz = soil_param_functions.ν_ss_quartz\n    ν_ss_gravel = soil_param_functions.ν_ss_gravel\n\n    if θ_i < eps(FT)\n        K_e =\n            S_r^((FT(1) + ν_ss_om - a * ν_ss_quartz - ν_ss_gravel) / FT(2)) *\n            (\n                (FT(1) + exp(-b * S_r))^(-FT(3)) -\n                ((FT(1) - S_r) / FT(2))^FT(3)\n            )^(FT(1) - ν_ss_om)\n    else\n        K_e = S_r^(FT(1) + ν_ss_om)\n    end\n    return K_e\nend\n\n\"\"\"\n    thermal_conductivity(\n        κ_dry::FT,\n        K_e::FT,\n        κ_sat::FT\n    ) where {FT}\n\nCompute the expression for thermal conductivity of soil matrix.\n\"\"\"\nfunction thermal_conductivity(κ_dry::FT, K_e::FT, κ_sat::FT) where {FT}\n    κ = K_e * κ_sat + (FT(1) - K_e) * κ_dry\n    return κ\nend\n\n\"\"\"\n    volumetric_internal_energy_liq(\n        T::FT,\n        T_ref::FT,\n    ) where {FT}\n\nCompute the expression for the volumetric internal energy of liquid water.\n\"\"\"\nfunction volumetric_internal_energy_liq(\n    T::FT,\n    param_set::AbstractParameterSet,\n) where {FT}\n    _T_ref = FT(T_0(param_set))\n    _ρ_l = FT(ρ_cloud_liq(param_set))\n    ρcp_l = FT(cp_l(param_set) * _ρ_l)\n    ρe_int_l = ρcp_l * (T - _T_ref)\n    return ρe_int_l\nend\n\n\"\"\"\n    function k_solid(\n        ν_ss_om::FT,\n        ν_ss_quartz::FT,\n        κ_quartz::FT,\n        κ_minerals::FT,\n        κ_om::FT,\n    ) where {FT}\n\nComputes the thermal conductivity of the solid material in soil.\nThe `_ss_` subscript denotes that the volumetric fractions of the soil\ncomponents are referred to the soil solid components, not including the pore\nspace.\n\"\"\"\nfunction k_solid(\n    ν_ss_om::FT,\n    ν_ss_quartz::FT,\n    κ_quartz::FT,\n    κ_minerals::FT,\n    κ_om::FT,\n) where {FT}\n    return κ_om^ν_ss_om *\n           κ_quartz^ν_ss_quartz *\n           κ_minerals^(FT(1) - ν_ss_om - ν_ss_quartz)\nend\n\n\n\"\"\"\n    function ksat_frozen(\n        κ_solid::FT,\n        porosity::FT,\n        κ_ice::FT\n    ) where {FT}\n\nComputes the thermal conductivity for saturated frozen soil.\n\"\"\"\nfunction ksat_frozen(κ_solid::FT, porosity::FT, κ_ice::FT) where {FT}\n    return κ_solid^(FT(1.0) - porosity) * κ_ice^(porosity)\nend\n\n\"\"\"\n    function ksat_unfrozen(\n        κ_solid::FT,\n        porosity::FT,\n        κ_l::FT\n    ) where {FT}\n\nComputes the thermal conductivity for saturated unfrozen soil.\n\"\"\"\nfunction ksat_unfrozen(κ_solid::FT, porosity::FT, κ_l::FT) where {FT}\n    return κ_solid^(FT(1.0) - porosity) * κ_l^porosity\nend\n\n\"\"\"\n    function ρb_ss(porosity::FT, ρp::FT) where {FT}\nComputes the dry soil bulk density from the dry soil particle\ndensity.\n\"\"\"\nfunction ρb_ss(porosity::FT, ρp::FT) where {FT}\n    return (FT(1.0) - porosity) * ρp\nend\n\n\"\"\"\n    function k_dry(\n        param_set::AbstractParameterSet\n        soil_param_functions::PS,\n    ) where {PS}\n\nComputes the thermal conductivity of dry soil.\n\"\"\"\nfunction k_dry(\n    param_set::AbstractParameterSet,\n    soil_param_functions::PS,\n) where {PS}\n    κ_dry_parameter = soil_param_functions.κ_dry_parameter\n    FT = typeof(κ_dry_parameter)\n    porosity = soil_param_functions.porosity\n    ρp = soil_param_functions.ρp\n    κ_solid = soil_param_functions.κ_solid\n    κ_air = FT(K_therm(param_set))\n    ρb_val = ρb_ss(porosity, ρp)\n    numerator = (κ_dry_parameter * κ_solid - κ_air) * ρb_val + κ_air * ρp\n    denom = ρp - (FT(1.0) - κ_dry_parameter) * ρb_val\n    return numerator / denom\nend\n\nend # Module\n"
  },
  {
    "path": "src/Land/Model/SoilWaterParameterizations.jl",
    "content": "\"\"\"\n    SoilWaterParameterizations\n\nvan Genuchten, Brooks and Corey, and Haverkamp parameters for and formulation of\n  - hydraulic conductivity\n  - matric potential\n\nHydraulic conductivity can be chosen to be dependent or independent of \nimpedance, viscosity and moisture.\n\nFunctions for hydraulic head, effective saturation, pressure head, matric \npotential, and the relationship between augmented liquid fraction and liquid\nfraction are also included.\n\"\"\"\nmodule SoilWaterParameterizations\n\nusing DocStringExtensions\nusing UnPack\n\nexport AbstractImpedanceFactor,\n    NoImpedance,\n    IceImpedance,\n    impedance_factor,\n    AbstractViscosityFactor,\n    ConstantViscosity,\n    TemperatureDependentViscosity,\n    viscosity_factor,\n    AbstractMoistureFactor,\n    MoistureDependent,\n    MoistureIndependent,\n    moisture_factor,\n    AbstractHydraulicsModel,\n    vanGenuchten,\n    BrooksCorey,\n    Haverkamp,\n    hydraulic_conductivity,\n    effective_saturation,\n    pressure_head,\n    hydraulic_head,\n    matric_potential,\n    volumetric_liquid_fraction,\n    inverse_matric_potential\n\n\"\"\"\n    AbstractImpedanceFactor{FT <: AbstractFloat}\n\n\"\"\"\nabstract type AbstractImpedanceFactor{FT <: AbstractFloat} end\n\n\"\"\"\n    AbstractViscosityFactor{FT <: AbstractFloat}\n\"\"\"\nabstract type AbstractViscosityFactor{FT <: AbstractFloat} end\n\n\"\"\"\n    AbstractMoistureFactor{FT <:AbstractFloat}\n\"\"\"\nabstract type AbstractMoistureFactor{FT <: AbstractFloat} end\n\n\n\"\"\"\n    AbstractsHydraulicsModel{FT <: AbstractFloat}\n\nHydraulics model is used in the moisture factor in hydraulic \nconductivity and in the matric potential. The single hydraulics model \nchoice sets both of these.\n\"\"\"\nabstract type AbstractHydraulicsModel{FT <: AbstractFloat} end\n\n\n\"\"\"\n    vanGenuchten{FT} <: AbstractHydraulicsModel{FT}\n\nThe necessary parameters for the van Genuchten hydraulic model; \ndefaults are for Yolo light clay.\n\n The user can supply either\nfloats or  functions of `aux` (`aux.x`, `aux.y`, `aux.z`), which return a scalar float.\nInternally,\nthe parameters will be converted to type FT and the functions\naltered to return type FT, so the parameters must be abstract floats\nor functions that return abstract floats.\n\n# Fields\n\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct vanGenuchten{FT, T1, T2, T3} <: AbstractHydraulicsModel{FT}\n    \"Exponent parameter - used in matric potential\"\n    n::T1\n    \"used in matric potential. The inverse of this carries units in \n     the expression for matric potential (specify in inverse meters).\"\n    α::T2\n    \"Exponent parameter - determined by n, used in hydraulic conductivity\"\n    m::T3\nend\n\n\nfunction vanGenuchten(\n    ::Type{FT};\n    n::Union{AbstractFloat, Function} = FT(1.43),\n    α::Union{AbstractFloat, Function} = FT(2.6),\n) where {FT}\n    nt = n isa AbstractFloat ? FT(n) : (aux) -> FT(n(aux))\n    mt =\n        n isa AbstractFloat ? FT(1) - FT(1) / nt :\n        (aux) -> FT(1) - FT(1) / nt(aux)\n    αt = α isa AbstractFloat ? FT(α) : (aux) -> FT(α(aux))\n    args = (nt, αt, mt)\n    return vanGenuchten{FT, typeof.(args)...}(args...)\nend\n\n\n\"\"\"\n    (model::vanGenuchten)(aux)\n\nEvaluate the hydraulic model parameters at aux, and return\na struct of type `vanGenuchten` with those *constant* parameters,\nwhich will be of float type FT.\n\"\"\"\nfunction (model::vanGenuchten{FT, T1, T2, T3})(aux) where {FT, T1, T2, T3}\n    @unpack n, α = model\n    fn = typeof(n) == FT ? n : n(aux)\n    fα = typeof(α) == FT ? α : α(aux)\n    return vanGenuchten(FT; n = fn, α = fα)\nend\n\n\"\"\"\n    BrooksCorey{FT,T1,T2} <: AbstractHydraulicsModel{FT}\n\nThe necessary parameters for the Brooks and Corey hydraulic model.\n\nDefaults are chosen to somewhat mirror the Havercamp/vG Yolo light \nclay hydraulic conductivity/matric potential.  The user can supply either\nfloats or functions of `aux` (`aux.x`, `aux.y`, `aux.z`), which return a scalar float. \nInternally,\nthe parameters will be converted to type FT and the functions\naltered to return type FT, so the parameters must be abstract floats\nor functions that return abstract floats.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct BrooksCorey{FT, T1, T2} <: AbstractHydraulicsModel{FT}\n    \"ψ_b - used in matric potential. Units of meters.\"\n    ψb::T1\n    \"Exponent used in matric potential and hydraulic conductivity.\"\n    m::T2\nend\n\nfunction BrooksCorey(\n    ::Type{FT};\n    ψb::Union{AbstractFloat, Function} = FT(0.1656),\n    m::Union{AbstractFloat, Function} = FT(0.5),\n) where {FT}\n    mt = m isa AbstractFloat ? FT(m) : (aux) -> FT(m(aux))\n    ψt = ψb isa AbstractFloat ? FT(ψb) : (aux) -> FT(ψb(aux))\n    args = (ψt, mt)\n    return BrooksCorey{FT, typeof.(args)...}(args...)\nend\n\n\"\"\"\n    (model::BrooksCorey)(aux)\n\nEvaluate the hydraulic model parameters at aux, and return\na struct of type `BrooksCorey` with those parameters.\n\"\"\"\nfunction (model::BrooksCorey{FT, T1, T2})(aux) where {FT, T1, T2}\n    @unpack ψb, m = model\n    fψ = typeof(ψb) == FT ? ψb : ψb(aux)\n    fm = typeof(m) == FT ? m : m(aux)\n    return BrooksCorey(FT; ψb = fψ, m = fm)\nend\n\n\n\"\"\"\n    Haverkamp{FT,T1,T2,T3,T4,T5} <: AbstractHydraulicsModel{FT}\n\nThe necessary parameters for the Haverkamp hydraulic model for Yolo light\n clay.\n\nNote that this only is used in creating a hydraulic conductivity function,\n and another formulation for matric potential must be used.\nThe user can supply either\nfloats or functions of `aux` (`aux.x`, `aux.y`, `aux.z`), which return a scalar float. Internally,\nthe parameters will be converted to type FT and the functions\naltered to return type FT, so the parameters must be abstract floats\nor functions that return abstract floats.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct Haverkamp{FT, T1, T2, T3, T4, T5} <: AbstractHydraulicsModel{FT}\n    \"exponent in conductivity\"\n    k::T1\n    \"constant A (units of cm^k) using in conductivity. Our sim is in meters\"\n    A::T2\n    \"Exponent parameter - using in matric potential\"\n    n::T3\n    \"used in matric potential. The inverse of this carries units in the \n     expression for matric potential (specify in inverse meters).\"\n    α::T4\n    \"Exponent parameter - determined by n, used in hydraulic conductivity\"\n    m::T5\nend\n\nfunction Haverkamp(\n    ::Type{FT};\n    k::Union{AbstractFloat, Function} = FT(1.77),\n    A::Union{AbstractFloat, Function} = FT(124.6 / 100.0^1.77),\n    n::Union{AbstractFloat, Function} = FT(1.43),\n    α::Union{AbstractFloat, Function} = FT(2.6),\n) where {FT}\n    nt = n isa AbstractFloat ? FT(n) : (aux) -> FT(n(aux))\n    mt =\n        n isa AbstractFloat ? FT(1) - FT(1) / nt :\n        (aux) -> FT(1) - FT(1) / nt(aux)\n    αt = α isa AbstractFloat ? FT(α) : (aux) -> FT(α(aux))\n    kt = k isa AbstractFloat ? FT(k) : (aux) -> FT(k(aux))\n    At = A isa AbstractFloat ? FT(A) : (aux) -> FT(A(aux))\n\n    args = (kt, At, nt, αt, mt)\n    return Haverkamp{FT, typeof.(args)...}(args...)\nend\n\n\"\"\"\n    (model::Haverkcamp)(aux)\n\nEvaluate the hydraulic model parameters at aux, and return\na struct of type `Haverkamp` with those parameters.\n\"\"\"\nfunction (model::Haverkamp{FT, T1, T2, T3, T4, T5})(\n    aux,\n) where {FT, T1, T2, T3, T4, T5}\n    @unpack k, A, n, α = model\n    fn = typeof(n) == FT ? n : n(aux)\n    fα = typeof(α) == FT ? α : α(aux)\n    fA = typeof(A) == FT ? A : A(aux)\n    fk = typeof(k) == FT ? k : k(aux)\n    return Haverkamp(FT; k = fk, A = fA, n = fn, α = fα)\nend\n\n\n\"\"\"\n    MoistureIndependent{FT} <: AbstractMoistureFactor{FT} end\n\nMoisture independent moisture factor.\n\"\"\"\nstruct MoistureIndependent{FT} <: AbstractMoistureFactor{FT} end\n\n\n\"\"\"\n    MoistureDependent{FT} <: AbstractMoistureFactor{FT} end\n\nMoisture dependent moisture factor.\n\"\"\"\nstruct MoistureDependent{FT} <: AbstractMoistureFactor{FT} end\n\n\n\"\"\"\n    moisture_factor(\n        mm::MoistureDependent,\n        hm::vanGenuchten{FT},\n        S_l::FT,\n    ) where {FT}\n\nReturns the moisture factor of the hydraulic conductivy assuming a \nMoistureDependent and van Genuchten hydraulic model.\n\nThis is intended to be used with an instance of `vanGenuchten`\nthat has float parameters. \n\"\"\"\nfunction moisture_factor(\n    mm::MoistureDependent,\n    hm::vanGenuchten{FT},\n    S_l::FT,\n) where {FT}\n    m = hm.m\n    if S_l < FT(1)\n        K = sqrt(S_l) * (FT(1) - (FT(1) - S_l^(FT(1) / m))^m)^FT(2)\n    else\n        K = FT(1)\n    end\n    return K\nend\n\n\"\"\"\n    moisture_factor(\n        mm::MoistureDependent,\n        hm::BrooksCorey{FT},\n        S_l::FT,\n    ) where {FT}\n\nReturns the moisture factor of the hydraulic conductivy assuming a \nMoistureDependent and Brooks/Corey hydraulic model.\n\nThis is intended to be used with an instance of `BrooksCorey`\nthat has float parameters.\n\"\"\"\nfunction moisture_factor(\n    mm::MoistureDependent,\n    hm::BrooksCorey{FT},\n    S_l::FT,\n) where {FT}\n    m = hm.m\n    if S_l < FT(1)\n        K = S_l^(FT(2) * m + FT(3))\n    else\n        K = FT(1)\n    end\n    return K\nend\n\n\"\"\"\n    moisture_factor(\n        mm::MoistureDependent,\n        hm::Haverkamp{FT},\n        S_l::FT,\n    ) where {FT}\n\nReturns the moisture factor of the hydraulic conductivy assuming a \nMoistureDependent and Haverkamp hydraulic model.\n\nThis is intended to be used with an instance of `Haverkamp`\nthat has float parameters.\n\"\"\"\nfunction moisture_factor(\n    mm::MoistureDependent,\n    hm::Haverkamp{FT},\n    S_l::FT,\n) where {FT}\n    @unpack k, A, n, m, α = hm\n    if S_l < FT(1)\n        ψ = -((S_l^(-FT(1) / m) - FT(1)) * α^(-n))^(FT(1) / n)\n        K = A / (A + abs(ψ)^k)\n    else\n        K = FT(1)\n    end\n    return K\nend\n\n\n\"\"\"\n    moisture_factor(mm::MoistureIndependent,\n                    hm::AbstractHydraulicsModel{FT},\n                    S_l::FT,\n    ) where {FT}\n\nReturns the moisture factor in hydraulic conductivity when a \nMoistureIndependent model is chosen. Returns 1.\n\nNote that the hydraulics model and S_l are not used, but are included \nas arguments to unify the function call.\n\"\"\"\nfunction moisture_factor(\n    mm::MoistureIndependent,\n    hm::AbstractHydraulicsModel{FT},\n    S_l::FT,\n) where {FT}\n    Factor = FT(1.0)\n    return Factor\nend\n\n\"\"\"\n    ConstantViscosity{FT} <: AbstractViscosityFactor{FT}\n\nA model to indicate a constant viscosity - independent of temperature - \nfactor in hydraulic conductivity.\n\"\"\"\nstruct ConstantViscosity{FT} <: AbstractViscosityFactor{FT} end\n\n\n\"\"\"\n    TemperatureDependentViscosity{FT} <: AbstractViscosityFactor{FT}\n\nThe necessary parameters for the temperature dependent portion of hydraulic \nconductivity.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nBase.@kwdef struct TemperatureDependentViscosity{FT} <:\n                   AbstractViscosityFactor{FT}\n    \"Empirical coefficient\"\n    γ::FT = FT(2.64e-2)\n    \"Reference temperature\"\n    T_ref::FT = FT(288.0)\nend\n\n\n\"\"\"\n    viscosity_factor(\n        vm::ConstantViscosity{FT},\n        T::FT,\n    ) where {FT}\n\nReturns the viscosity factor when we choose no temperature dependence, i.e. \na constant viscosity. Returns 1.\n\nT is included as an argument to unify the function call.\n\"\"\"\nfunction viscosity_factor(vm::ConstantViscosity{FT}, T::FT) where {FT}\n    Theta = FT(1.0)\n    return Theta\nend\n\n\"\"\"\n    viscosity_factor(\n        vm::TemperatureDependentViscosity{FT},\n        T::FT,\n    ) where {FT}\n\nReturns the viscosity factor when we choose a TemperatureDependentViscosity.\n\"\"\"\nfunction viscosity_factor(\n    vm::TemperatureDependentViscosity{FT},\n    T::FT,\n) where {FT}\n    γ = vm.γ\n    T_ref = vm.T_ref\n    factor = FT(γ * (T - T_ref))\n    Theta = FT(exp(factor))\n    return Theta\nend\n\n\n\"\"\"\n    NoImpedance{FT} <: AbstractImpedanceFactor{FT}\n\nA model to indicate to dependence on ice for the hydraulic conductivity.\n\"\"\"\nstruct NoImpedance{FT} <: AbstractImpedanceFactor{FT} end\n\n\n\n\"\"\"\n    IceImpedance{FT} <: AbstractImpedanceFactor{FT}\n\nThe necessary parameters for the empirical impedance factor due to ice.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nBase.@kwdef struct IceImpedance{FT} <: AbstractImpedanceFactor{FT}\n    \"Empirical coefficient from Hansson 2014. \"\n    Ω::FT = FT(7)\nend\n\n\"\"\"\n    impedance_factor(\n        imp::NoImpedance{FT},\n        f_i::FT,\n    ) where {FT}\n\nReturns the impedance factor when no effect due to ice is desired. \nReturns 1.\n\nThe other arguments are included to unify the function call.\n\"\"\"\nfunction impedance_factor(imp::NoImpedance{FT}, f_i::FT) where {FT}\n    gamma = FT(1.0)\n    return gamma\nend\n\n\"\"\"\n    impedance_factor(\n        imp::IceImpedance{FT},\n        f_i::FT,\n    ) where {FT}\n\nReturns the impedance factor when an effect due to the fraction of \nice is desired. \n\"\"\"\nfunction impedance_factor(imp::IceImpedance{FT}, f_i::FT) where {FT}\n    Ω = imp.Ω\n    gamma = FT(10.0^(-Ω * f_i))\n    return gamma\nend\n\n\"\"\"\n    hydraulic_conductivity(\n        Ksat::FT,\n        impedance::FT,\n        viscosity::FT,\n        moisture::FT,\n    ) where {FT}\n\nReturns the hydraulic conductivity.\n\"\"\"\nfunction hydraulic_conductivity(\n    Ksat::FT,\n    impedance::FT,\n    viscosity::FT,\n    moisture::FT,\n) where {FT}\n    K = Ksat * impedance * viscosity * moisture\n    return K\nend\n\n\"\"\"\n    hydraulic_head(z,ψ)\n\nReturn the hydraulic head.\n\nThe hydraulic head is defined as the sum of vertical height z and \npressure head ψ; meters.\n\"\"\"\nhydraulic_head(z, ψ) = z + ψ\n\n\"\"\"\n    volumetric_liquid_fraction(\n        ϑ_l::FT,\n        eff_porosity::FT,\n    ) where {FT}\n\nCompute the volumetric liquid fraction from the effective porosity and the augmented liquid\nfraction.\n\"\"\"\nfunction volumetric_liquid_fraction(ϑ_l::FT, eff_porosity::FT) where {FT}\n    if ϑ_l < eff_porosity\n        θ_l = ϑ_l\n    else\n        θ_l = eff_porosity\n    end\n    return θ_l\nend\n\n\n\"\"\"\n    effective_saturation(\n        porosity::FT,\n        ϑ_l::FT,\n        θ_r::FT,\n    ) where {FT}\n\nCompute the effective saturation of soil.\n\n`ϑ_l` is defined to be larger than `θ_r`. If `ϑ_l-θ_r` is negative, \nhydraulic functions that take it as an argument will return \nimaginary numbers, resulting in domain errors. Exit in this \ncase with an error.\n\"\"\"\nfunction effective_saturation(porosity::FT, ϑ_l::FT, θ_r::FT) where {FT}\n    ϑ_l < θ_r && error(\"Effective saturation is negative\")\n    S_l = (ϑ_l - θ_r) / (porosity - θ_r)\n    return S_l\nend\n\n\"\"\"\n    pressure_head(\n        model::AbstractHydraulicsModel{FT},\n        ν::FT,\n        S_s::FT,\n        θ_r::FT,\n        ϑ_l::FT,\n        θ_i::FT,\n    ) where {FT,PS}\n\nDetermine the pressure head in both saturated and unsaturated soil. \n\nIf ice is present, it reduces the volume available for liquid water. \nThe augmented liquid fraction changes behavior depending on if this \nvolume is full of liquid water vs not. Therefore, the region of saturated\nvs unsaturated soil depends on porosity - θ_i, not just on porosity.  \nIf the liquid water is unsaturated, the usual matric potential expression\nis treated as unaffected by the presence of ice.\n\"\"\"\nfunction pressure_head(\n    model::AbstractHydraulicsModel{FT},\n    ν::FT,\n    S_s::FT,\n    θ_r::FT,\n    ϑ_l::FT,\n    θ_i::FT,\n) where {FT}\n    eff_porosity = ν - θ_i\n    if ϑ_l < eff_porosity\n        S_l = effective_saturation(ν, ϑ_l, θ_r)\n        ψ = matric_potential(model, S_l)\n    else\n        ψ = (ϑ_l - eff_porosity) / S_s\n    end\n    return ψ\nend\n\n\n\"\"\"\n    matric_potential(\n            model::vanGenuchten{FT},\n            S_l::FT\n    ) where {FT}\n\nWrapper function which computes the van Genuchten function for matric potential.\n\"\"\"\nfunction matric_potential(model::vanGenuchten{FT}, S_l::FT) where {FT}\n    @unpack n, m, α = model\n    ψ_m = -((S_l^(-FT(1) / m) - FT(1)) * α^(-n))^(FT(1) / n)\n    return ψ_m\nend\n\n\"\"\"\n    matric_potential(\n            model::Haverkamp{FT},\n            S_l::FT\n    ) where {FT}\n\nCompute the van Genuchten function as a proxy for the Haverkamp model \nmatric potential (for testing purposes).\n\"\"\"\nfunction matric_potential(model::Haverkamp{FT}, S_l::FT) where {FT}\n    @unpack n, m, α = model\n    ψ_m = -((S_l^(-FT(1) / m) - FT(1)) * α^(-n))^(FT(1) / n)\n    return ψ_m\nend\n\n\"\"\"\n    matric_potential(\n            model::BrooksCorey{FT},\n            S_l::FT\n    ) where {FT}\n\nCompute the Brooks and Corey function for matric potential.\n\"\"\"\nfunction matric_potential(model::BrooksCorey{FT}, S_l::FT) where {FT}\n    @unpack ψb, m = model\n    ψ_m = ψ_m = -ψb * S_l^(-m)\n    return ψ_m\nend\n\n\n\n\"\"\"\n    inverse_matric_potential(\n        model::vanGenuchten{FT},\n        ψ::FT\n    ) where {FT}\n\nCompute the effective saturation given the matric potential, using\nthe van Genuchten formulation.\n\"\"\"\nfunction inverse_matric_potential(model::vanGenuchten{FT}, ψ::FT) where {FT}\n    ψ > 0 && error(\"Matric potential is positive\")\n    @unpack n, m, α = model\n    S = (FT(1) + (α * abs(ψ))^n)^(-m)\n    return S\nend\n\n\n\"\"\"\n    inverse_matric_potential(\n        model::Haverkamp{FT}\n        ψ::FT\n    ) where {FT}\n\nCompute the effective saturation given the matric potential using the \nHaverkamp hydraulics model. This model uses the van Genuchten \nformulation for matric potential.\n\"\"\"\nfunction inverse_matric_potential(model::Haverkamp{FT}, ψ::FT) where {FT}\n    ψ > 0 && error(\"Matric potential is positive\")\n    @unpack n, m, α = model\n    S = (FT(1) + (α * abs(ψ))^n)^(-m)\n    return S\nend\n\n\n\"\"\"\n    inverse_matric_potential(\n        model::BrooksCorey{FT}\n        ψ::FT\n    ) where {FT}\n\nCompute the effective saturation given the matric potential using the \nBrooks and Corey formulation.\n\"\"\"\nfunction inverse_matric_potential(model::BrooksCorey{FT}, ψ::FT) where {FT}\n    ψ > 0 && error(\"Matric potential is positive\")\n    @unpack ψb, m = model\n    S = (-ψ / ψb)^(-FT(1) / m)\n    return S\nend\n\nend #Module\n"
  },
  {
    "path": "src/Land/Model/SurfaceFlow.jl",
    "content": "module SurfaceFlow\n\nusing DocStringExtensions\nusing UnPack\nusing ..Land\nusing ..VariableTemplates\nusing ..BalanceLaws\nimport ..BalanceLaws:\n    BalanceLaw,\n    prognostic_vars,\n    flux,\n    source,\n    precompute,\n    eq_tends,\n    vars_state,\n    Prognostic,\n    Auxiliary,\n    Gradient,\n    GradientFlux\n\nusing ...DGMethods: LocalGeometry, DGModel\nusing StaticArrays: SVector\n\nexport OverlandFlowModel,\n    NoSurfaceFlowModel,\n    surface_boundary_flux!,\n    surface_boundary_state!,\n    calculate_velocity,\n    Precip,\n    VolumeAdvection,\n    SurfaceWaterHeight\n\n\"\"\"\n    SurfaceWaterHeight <: AbstractPrognosticVariable\n\nThe prognostic variable type for the 2d overland flow model. Used only for\ndispatching on.\n\"\"\"\nstruct SurfaceWaterHeight <: AbstractPrognosticVariable end\n\n\n\"\"\"\n    NoSurfaceFlowModel <: BalanceLaw\n\nThe default surface flow model, which does not add any prognostic variables\nto the land model and therefore does not model surface flow.\n\"\"\"\nstruct NoSurfaceFlowModel <: BalanceLaw end\n\n\"\"\"\n    OverlandFlowModel{Sx,Sy,M} <: BalanceLaw\n    \nThe 2D overland flow model, with a prognostic variable\nequal to the height of the surface water.\n\nThis model simulates the depth-averaged shallow water equation under the\nkinematic approximation, and employs Manning's relationship to relate\nvelocity to the height of the water.\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct OverlandFlowModel{Sx, Sy, M} <: BalanceLaw\n    \"Slope in x direction; field(x,y), unitless\"\n    slope_x::Sx\n    \"Slope in y direction; field(x,y), unitless\"\n    slope_y::Sy\n    \"Mannings coefficient; field(x,y), units of s/m^(1/3)\"\n    mannings::M\nend\n\nfunction OverlandFlowModel(\n    slope_x::Function,\n    slope_y::Function;\n    mannings::Function = (x, y) -> convert(eltype(x), 0.03),\n)\n    args = (slope_x, slope_y, mannings)\n    return OverlandFlowModel{typeof.(args)...}(args...)\nend\n\n\n\"\"\"\n    calculate_velocity(surface, x::Real, y::Real, h::Real)\n\nGiven the surface flow model, calculate the velocity of the flow\nbased on height, slope, and Manning's coefficient.\n\"\"\"\nfunction calculate_velocity(surface, x::Real, y::Real, h::Real)\n    FT = eltype(h)\n    sx = FT(surface.slope_x(x, y))\n    sy = FT(surface.slope_y(x, y))\n    mannings_coeff = FT(surface.mannings(x, y))\n    coeff = h^FT(2 / 3) / mannings_coeff\n    #The velocity direction is opposite the slope vector (∂_x z, ∂_y z)\n    return SVector(\n        -sign(sx) * coeff * sqrt(abs(sx)),\n        -sign(sy) * coeff * sqrt(abs(sy)),\n        zero(FT),\n    )\nend\n\nvars_state(surface::OverlandFlowModel, st::Prognostic, FT) = @vars(height::FT)\n\nfunction Land.land_init_aux!(\n    land::LandModel,\n    surface::Union{NoSurfaceFlowModel, OverlandFlowModel},\n    aux,\n    geom::LocalGeometry,\n) end\n\nfunction Land.land_nodal_update_auxiliary_state!(\n    land::LandModel,\n    surface::Union{NoSurfaceFlowModel, OverlandFlowModel},\n    state,\n    aux,\n    t,\n) end\n\n\"\"\"\n    VolumeAdvection <: TendencyDef{Flux{FirstOrder}}\n\nA first order flux type for the overland flow model.\n\"\"\"\nstruct VolumeAdvection <: TendencyDef{Flux{FirstOrder}} end\n\n\n\"\"\"\n    flux(::SurfaceWaterHeight, ::VolumeAdvection, land::LandModel, args,)\n\nA first order flux method for the OverlandFlow model, adding in advection of water volume.\n\"\"\"\nfunction flux(::SurfaceWaterHeight, ::VolumeAdvection, land::LandModel, args)\n    @unpack state, aux = args\n    x = aux.x\n    y = aux.y\n    height = max(state.surface.height, eltype(aux)(0.0))\n    v = calculate_velocity(land.surface, x, y, height)\n    return height * v\nend\n\n# Boundary Conditions\n\n# General case - to be used with bc::NoBC\nfunction surface_boundary_flux!(\n    nf,\n    bc::Land.AbstractBoundaryConditions,\n    m::Union{NoSurfaceFlowModel, OverlandFlowModel},\n    land::LandModel,\n    _...,\n) end\n\nfunction surface_boundary_state!(\n    nf,\n    bc::Land.AbstractBoundaryConditions,\n    m::Union{NoSurfaceFlowModel, OverlandFlowModel},\n    land::LandModel,\n    _...,\n) end\n\n\"\"\"\n    surface_boundary_flux!(\n        nf,\n        bc::Land.Dirichlet,\n        model::OverlandFlowModel,\n        land::LandModel,\n        _...,\n    )\n\nThe surface boundary flux function for the OverlandFlow model, which\ndoes nothing if Dirichlet conditions are chosen in order to not\noverconstrain the solution.\n\"\"\"\nfunction surface_boundary_flux!(\n    nf,\n    bc::Land.Dirichlet,\n    model::OverlandFlowModel,\n    land::LandModel,\n    _...,\n) end\n\n\"\"\"\n    surface_boundary_state!(\n        nf,\n        bc::Land.Dirichlet,\n        model::OverlandFlowModel,\n        land::LandModel,\n        state⁺::Vars,\n        aux⁺::Vars,\n        nM,\n        state⁻::Vars,\n        aux⁻::Vars,\n        t,\n        _...,\n    )\n\nThe surface boundary state function for the OverlandFlow model when\nDirichlet conditions are chosen. This should be equivalent to \noutflow boundary conditions, also referred to as gradient outlet\nconditions.\n\"\"\"\nfunction surface_boundary_state!(\n    nf,\n    bc::Land.Dirichlet,\n    model::OverlandFlowModel,\n    land::LandModel,\n    state⁺::Vars,\n    aux⁺::Vars,\n    nM,\n    state⁻::Vars,\n    aux⁻::Vars,\n    t,\n    _...,\n)\n    bc_function = bc.state_bc\n    state⁺.surface.height = bc_function(aux⁻, t)\nend\n\n\"\"\"\n    Precip <: TendencyDef{Source}\n\nA source term for overland flow where a prescribed net precipitation\ndrives the overland flow.\n\"\"\"\nstruct Precip{FT, F} <: TendencyDef{Source}\n    precip::F\n\n    function Precip{FT}(precip::F) where {FT, F}\n        new{FT, F}(precip)\n    end\nend\n\nfunction (p::Precip{FT})(x, y, t) where {FT}\n    FT(p.precip(x, y, t))\nend\n\n\nprognostic_vars(::Precip) = (SurfaceWaterHeight(),)\n\n\nprecompute(source_type::Precip, land::LandModel, args, tt::Source) =\n    NamedTuple()\n\nfunction source(::SurfaceWaterHeight, s::Precip, land::LandModel, args)\n    @unpack aux, t = args\n    return s(aux.x, aux.y, t)\nend\n\nend\n"
  },
  {
    "path": "src/Land/Model/land_bc.jl",
    "content": "export LandDomainBC,\n    LandComponentBC,\n    Dirichlet,\n    Neumann,\n    NoBC,\n    SurfaceDrivenWaterBoundaryConditions,\n    SurfaceDrivenHeatBoundaryConditions\n\n\"\"\"\n   AbstractBoundaryConditions \n\"\"\"\nabstract type AbstractBoundaryConditions end\n\n\n\"\"\"\n    NoBC <: AbstractBoundaryConditions\n\nThis type is used for dispatch when no boundary condition needs\nto be enforced - for example, if no prognostic variables are included\nfor a subcomponent, or for lateral faces when a 1D vertical setup\nis used.\n\"\"\"\nstruct NoBC <: AbstractBoundaryConditions end\n\n\"\"\"\n    Dirichlet{Fs} <: AbstractBoundaryConditions\n\nA concrete type to hold the state variable \nfunction, if Dirichlet boundary conditions are desired.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct Dirichlet{Fs} <: AbstractBoundaryConditions\n    \"state boundary condition\"\n    state_bc::Fs\nend\n\n\"\"\"\n    Neumann{Ff} <: AbstractBoundaryConditions\n\nA concrete type for specifying the magnitude of an inward diffusive flux,\nnormal to the domain boundary, from which a Neumann boundary condition \nis determined and applied.\n\nFor the soil water model, the Darcy flux is defined as F⃗(Darcy) = -K∇h. \nWith the `Neumann` boundary\ncondition type, the user supplies a scalar `f` such that the applied flux\nboundary conditon is F⃗(Darcy, boundary) = -f n̂, \nwhere n̂ is the normal vector pointing out of the domain. For the soil heat model,\nthe same rule applies, except now we have a conductive heat flux \nF⃗(Conductive, boundary) = -κ∇T = -f n̂.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct Neumann{Ff} <: AbstractBoundaryConditions\n    \"Scalar flux boundary condition\"\n    scalar_flux_bc::Ff\nend\n\n\"\"\"\n    SurfaceDrivenWaterBoundaryConditions{FT, PD, RD} <: AbstractBoundaryConditions\n\nBoundary condition type to be used when the user wishes to \napply physical fluxes of water at the top of the domain, normal to the surface.\nThe flux can be inwards or outwards, depending on the magnitude of infiltration,\nevaporation, and precipitation.\n\nPrecipitation is assumed to be \nin the ẑ direction, P⃗ = Pẑ, while evaporation is along the normal \ndirection, E⃗ = En̂. The applied boundary flux is F⃗ = f n̂, and the magnitude f is\ndetermined internally from P⃗, E⃗, and soil conditions.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct SurfaceDrivenWaterBoundaryConditions{FT, PD, RD} <:\n       AbstractBoundaryConditions where {FT, PD, RD}\n    \"Precipitation model\"\n    precip_model::PD\n    \"Runoff model\"\n    runoff_model::RD\nend\n\n\"\"\"\n    SurfaceDrivenWaterBoundaryConditions(\n        ::Type{FT};\n        precip_model::AbstractPrecipModel{FT} = DrivenConstantPrecip{FT}(),\n        runoff_model::AbstractSurfaceRunoffModel{FT} = NoRunoff(),\n    ) where {FT}\n\nConstructor for the SurfaceDrivenWaterBoundaryConditions object. The default\nis a constant precipitation rate on the subgrid scale, and no runoff.\n\"\"\"\nfunction SurfaceDrivenWaterBoundaryConditions(\n    ::Type{FT};\n    precip_model::AbstractPrecipModel{FT} = DrivenConstantPrecip{FT}(\n        (t) -> (0.0),\n    ),\n    runoff_model::AbstractSurfaceRunoffModel = NoRunoff(),\n) where {FT}\n    args = (precip_model, runoff_model)\n    return SurfaceDrivenWaterBoundaryConditions{FT, typeof.(args)...}(args...)\nend\n\n\"\"\"\n    SurfaceDrivenHeatBoundaryConditions{FT, SWD} <: AbstractBoundaryConditions\n\nBoundary condition type to be used when the user wishes to \napply normal fluxes of heat at the top of the domain \n(according to radiative energy fluxes).\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct SurfaceDrivenHeatBoundaryConditions{FT, SWD} <:\n       AbstractBoundaryConditions where {FT, SWD}\n    \"Net short wave flux model\"\n    nswf_model::SWD\nend\n\n\"\"\"\n    SurfaceDrivenHeatBoundaryConditions(\n        ::Type{FT};\n        nswf_model::AbstractNetSwFluxModel{FT} = PrescribedNetSwFlux{FT}()\n    ) where {FT}\n\nConstructor for the SurfaceDrivenHeatBoundaryConditions object. The default\nis no shortwave flux.\n\"\"\"\nfunction SurfaceDrivenHeatBoundaryConditions(\n    ::Type{FT};\n    nswf_model::AbstractNetSwFluxModel{FT} = PrescribedNetSwFlux(\n        FT;\n        nswf = t -> eltype(t)(0),\n    ),\n) where {FT}\n    return SurfaceDrivenHeatBoundaryConditions{FT, typeof(nswf_model)}(\n        nswf_model,\n    )\nend\n\n\"\"\"\n    LandDomainBC{TBC, BBC, LBC}\n\nA container for the land boundary conditions, with options for surface\nboundary conditions, bottom boundary conditions, or lateral face\nboundary conditions.\n\nThe user should supply an instance of `LandComponentBC` for each piece, as needed.\nIf none is supplied, the default for is `NoBC` for each subcomponent. At a minimum, both top and bottom boundary conditions should be supplied. Whether or not to include the lateral faces depends on the configuration of the domain.\n\"\"\"\nBase.@kwdef struct LandDomainBC{TBC, BBC, MinXBC, MaxXBC, MinYBC, MaxYBC}\n    \"surface boundary conditions\"\n    surface_bc::TBC = LandComponentBC()\n    \"bottom boundary conditions\"\n    bottom_bc::BBC = LandComponentBC()\n    \"lateral boundary conditions\"\n    minx_bc::MinXBC = LandComponentBC()\n    maxx_bc::MaxXBC = LandComponentBC()\n    miny_bc::MinYBC = LandComponentBC()\n    maxy_bc::MaxYBC = LandComponentBC()\nend\n\n\"\"\"\n    LandComponentBC{SW, SH, SF}\n\nAn object that holds the boundary conditions for each of the subcomponents\nof the land model. \n\nThe boundary conditions supplied should be of type `AbstractBoundaryConditions`. \nThe default is `NoBC` for each component, so that the user only\nneeds to define the BC for the components they wish to model.\n\"\"\"\nBase.@kwdef struct LandComponentBC{\n    SW <: AbstractBoundaryConditions,\n    SH <: AbstractBoundaryConditions,\n    SF <: AbstractBoundaryConditions,\n}\n    soil_water::SW = NoBC()\n    soil_heat::SH = NoBC()\n    surface::SF = NoBC()\nend\n\n\"\"\"\n    function boundary_conditions(land::LandModel)\n\nUnpacks the `boundary_conditions` field of the land model, and\nputs into the correct order based on the integers used to identify\nfaces, as defined in the Driver configuration.\n\"\"\"\nfunction boundary_conditions(land::LandModel)\n    bc = land.boundary_conditions\n    mytuple = (\n        bc.bottom_bc,\n        bc.surface_bc,\n        bc.minx_bc,\n        bc.maxx_bc,\n        bc.miny_bc,\n        bc.maxy_bc,\n    )\n    # faces labeled integer 1,2 are bottom, top, lateral sides are 3, 4, 5, 6\n    return mytuple\nend\n\nfunction boundary_state!(\n    nf,\n    bc::LandComponentBC,\n    land::LandModel,\n    state⁺::Vars,\n    aux⁺::Vars,\n    n,\n    state⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    land_boundary_state!(\n        nf,\n        bc,\n        land,\n        state⁺,\n        aux⁺,\n        n,\n        state⁻,\n        aux⁻,\n        t,\n        args...,\n    )\nend\n\nfunction land_boundary_state!(nf, bc::LandComponentBC, land, args...)\n    soil_boundary_state!(nf, bc.soil_water, land.soil.water, land, args...)\n    soil_boundary_state!(nf, bc.soil_heat, land.soil.heat, land, args...)\n    surface_boundary_state!(nf, bc.surface, land.surface, land, args...)\nend\n\nfunction boundary_state!(\n    nf,\n    bc::LandComponentBC,\n    land::LandModel,\n    state⁺::Vars,\n    diff⁺::Vars,\n    hyperdiff⁺::Vars,\n    aux⁺::Vars,\n    n,\n    state⁻,\n    diff⁻,\n    hyperdiff⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    land_boundary_flux!(\n        nf,\n        bc,\n        land,\n        state⁺,\n        diff⁺,\n        aux⁺,\n        n,\n        state⁻,\n        diff⁻,\n        aux⁻,\n        t,\n        args...,\n    )\nend\n\nfunction land_boundary_flux!(nf, bc::LandComponentBC, land, args...)\n    soil_boundary_flux!(nf, bc.soil_water, land.soil.water, land, args...)\n    soil_boundary_flux!(nf, bc.soil_heat, land.soil.heat, land, args...)\n    surface_boundary_flux!(nf, bc.surface, land.surface, land, args...)\nend\n"
  },
  {
    "path": "src/Land/Model/land_tendencies.jl",
    "content": "#####\n##### Sources\n#####\n\neq_tends(pv::AbstractPrognosticVariable, m::LandModel, tt::Source) =\n    (m.source_dt[pv]...,)\n\n#####\n##### First order fluxes\n#####\n\neq_tends(pv::PV, land::LandModel, tt::Flux{FirstOrder}) where {PV} = (\n    eq_tends(pv, land.soil.heat, tt)...,\n    eq_tends(pv, land.soil.water, tt)...,\n    eq_tends(pv, land.surface, tt)...,\n)\n\neq_tends(::PV, ::AbstractSoilComponentModel, ::Flux{FirstOrder}) where {PV} = ()\neq_tends(::PV, ::NoSurfaceFlowModel, ::Flux{FirstOrder}) where {PV} = ()\n\neq_tends(::SurfaceWaterHeight, ::OverlandFlowModel, ::Flux{FirstOrder}) =\n    (VolumeAdvection(),)\n\n#####\n##### Second order fluxes\n#####\n\n# Empty by default\n\neq_tends(pv::PV, ::AbstractSoilComponentModel, ::Flux{SecondOrder}) where {PV} =\n    ()\neq_tends(pv::PV, ::OverlandFlowModel, ::Flux{SecondOrder}) where {PV} = ()\neq_tends(pv::PV, ::NoSurfaceFlowModel, ::Flux{SecondOrder}) where {PV} = ()\n\neq_tends(pv::PV, land::LandModel, tt::Flux{SecondOrder}) where {PV} = (\n    eq_tends(pv, land.soil.heat, tt)...,\n    eq_tends(pv, land.soil.water, tt)...,\n    eq_tends(pv, land.surface, tt)...,\n)\n\neq_tends(\n    pv::PV,\n    land::LandModel,\n    tt::Flux{SecondOrder},\n) where {PV <: VolumetricInternalEnergy} =\n    (eq_tends(pv, land.soil.heat, land.soil.water, tt)...,)\n\neq_tends(\n    ::VolumetricInternalEnergy,\n    ::SoilHeatModel,\n    ::SoilWaterModel,\n    ::Flux{SecondOrder},\n) = (DiffHeatFlux(), DarcyDrivenHeatFlux())\n\neq_tends(\n    ::VolumetricInternalEnergy,\n    ::SoilHeatModel,\n    ::PrescribedWaterModel,\n    ::Flux{SecondOrder},\n) = (DiffHeatFlux(),)\n\neq_tends(::VolumetricLiquidFraction, ::SoilWaterModel, ::Flux{SecondOrder}) =\n    (DarcyFlux(),)\n"
  },
  {
    "path": "src/Land/Model/prog_types.jl",
    "content": "#####\n##### Prognostic Variable types\n#####\n\nstruct VolumetricLiquidFraction <: AbstractPrognosticVariable end\nstruct VolumetricInternalEnergy <: AbstractPrognosticVariable end\nstruct VolumetricIceFraction <: AbstractPrognosticVariable end\n"
  },
  {
    "path": "src/Land/Model/prognostic_vars.jl",
    "content": "#####\n##### Prognostic Variables\n#####\n\nprognostic_vars(water::PrescribedWaterModel) = ()\nprognostic_vars(water::SoilWaterModel) =\n    (VolumetricLiquidFraction(), VolumetricIceFraction())\nprognostic_vars(heat::PrescribedTemperatureModel) = ()\nprognostic_vars(heat::SoilHeatModel) = (VolumetricInternalEnergy(),)\nprognostic_vars(surface::NoSurfaceFlowModel) = ()\nprognostic_vars(surface::OverlandFlowModel) = (SurfaceWaterHeight(),)\n\nprognostic_vars(land::LandModel) = (\n    prognostic_vars(land.soil.water)...,\n    prognostic_vars(land.soil.heat)...,\n    prognostic_vars(land.surface)...,\n)\n\n\n\nget_prog_state(state, ::VolumetricLiquidFraction) = (state.soil.water, :ϑ_l)\nget_prog_state(state, ::VolumetricIceFraction) = (state.soil.water, :θ_i)\nget_prog_state(state, ::VolumetricInternalEnergy) = (state.soil.heat, :ρe_int)\nget_prog_state(state, ::SurfaceWaterHeight) = (state.surface, :height)\n"
  },
  {
    "path": "src/Land/Model/soil_bc.jl",
    "content": "# General case - to be used with bc::NoBC or m::PrescribedXModels\nfunction soil_boundary_flux!(\n    nf,\n    bc::AbstractBoundaryConditions,\n    m::AbstractSoilComponentModel,\n    land::LandModel,\n    state⁺::Vars,\n    diff⁺::Vars,\n    aux⁺::Vars,\n    nM,\n    state⁻::Vars,\n    diff⁻::Vars,\n    aux⁻::Vars,\n    t,\n    _...,\n) end\n\n\n\n\nfunction soil_boundary_state!(\n    nf,\n    bc::AbstractBoundaryConditions,\n    m::AbstractSoilComponentModel,\n    land::LandModel,\n    state⁺::Vars,\n    aux⁺::Vars,\n    nM,\n    state⁻::Vars,\n    aux⁻::Vars,\n    t,\n    _...,\n)\n\nend\n\n# Dirichlet Methods for SoilHeat and SoilWater\n\n\"\"\"\n    function soil_boundary_state!(\n        nf,\n        bc::Dirichlet,\n        water::SoilWaterModel,\n        land::LandModel,\n        state⁺::Vars,\n        aux⁺::Vars,\n        nM,\n        state⁻::Vars,\n        aux⁻::Vars,\n        t,\n    )\n\nThe Dirichlet-type method for `soil_boundary_state!` for the\n`SoilWaterModel`.\n\"\"\"\nfunction soil_boundary_state!(\n    nf,\n    bc::Dirichlet,\n    water::SoilWaterModel,\n    land::LandModel,\n    state⁺::Vars,\n    aux⁺::Vars,\n    nM,\n    state⁻::Vars,\n    aux⁻::Vars,\n    t,\n    _...,\n)\n    bc_function = bc.state_bc\n    state⁺.soil.water.ϑ_l = bc_function(aux⁻, t)\n\nend\n\n\n\"\"\"\n    function soil_boundary_state!(\n        nf,\n        bc::Dirichlet,\n        heat::SoilHeatModel,\n        land::LandModel,\n        state⁺::Vars,\n        aux⁺::Vars,\n        nM,\n        state⁻::Vars,\n        aux⁻::Vars,\n        t,\n    )\n\nThe Dirichlet-type method for `soil_boundary_state!` for the\n`SoilHeatModel`.\n\"\"\"\nfunction soil_boundary_state!(\n    nf,\n    bc::Dirichlet,\n    heat::SoilHeatModel,\n    land::LandModel,\n    state⁺::Vars,\n    aux⁺::Vars,\n    nM,\n    state⁻::Vars,\n    aux⁻::Vars,\n    t,\n    _...,\n)\n    bc_function = bc.state_bc\n    param_set = parameter_set(land)\n    ϑ_l, θ_i = get_water_content(land.soil.water, aux⁻, state⁻, t)\n    θ_l = volumetric_liquid_fraction(ϑ_l, land.soil.param_functions.porosity)\n    ρc_s = volumetric_heat_capacity(\n        θ_l,\n        θ_i,\n        land.soil.param_functions.ρc_ds,\n        param_set,\n    )\n\n    ρe_int_bc =\n        volumetric_internal_energy(θ_i, ρc_s, bc_function(aux⁻, t), param_set)\n\n    state⁺.soil.heat.ρe_int = ρe_int_bc\nend\n\n# Neumann conditions for SoilHeat and SoilWater\n\n\"\"\"\n    function soil_boundary_flux!(\n        nf,\n        bc::Neumann,\n        water::SoilWaterModel,\n        land::LandModel,\n        state⁺::Vars,\n        diff⁺::Vars,\n        aux⁺::Vars,\n        n̂,\n        state⁻::Vars,\n        diff⁻::Vars,\n        aux⁻::Vars,\n        t,\n    )\n\nThe Neumann method for `soil_boundary_flux!` for the\n`SoilWaterModel`.\n\"\"\"\nfunction soil_boundary_flux!(\n    nf,\n    bc::Neumann,\n    water::SoilWaterModel,\n    land::LandModel,\n    state⁺::Vars,\n    diff⁺::Vars,\n    aux⁺::Vars,\n    n̂,\n    state⁻::Vars,\n    diff⁻::Vars,\n    aux⁻::Vars,\n    t,\n    _...,\n)\n    bc_function = bc.scalar_flux_bc\n    # See documentation for `Neumann`. The user supplies f = scalar_flux_bc\n    # such that F⃗ = -K∇h = -f n̂. \n    # Since the BC is on K∇h, no minus sign is needed.\n    diff⁺.soil.water.K∇h = n̂ * bc_function(aux⁻, t)\nend\n\n\n\"\"\"\n    function soil_boundary_flux!(\n        nf,\n        bc::Neumann,\n        heat::SoilHeatModel,\n        land::LandModel,\n        state⁺::Vars,\n        diff⁺::Vars,\n        aux⁺::Vars,\n        n̂,\n        state⁻::Vars,\n        diff⁻::Vars,\n        aux⁻::Vars,\n        t,\n    )\n\nThe Neumann method for `soil_boundary_flux!` for the\n`SoilHeatModel`.\n\"\"\"\nfunction soil_boundary_flux!(\n    nf,\n    bc::Neumann,\n    heat::SoilHeatModel,\n    land::LandModel,\n    state⁺::Vars,\n    diff⁺::Vars,\n    aux⁺::Vars,\n    n̂,\n    state⁻::Vars,\n    diff⁻::Vars,\n    aux⁻::Vars,\n    t,\n    _...,\n)\n    bc_function = bc.scalar_flux_bc\n    # See documentation for `Neumann`. The user supplies f = scalar_flux_bc\n    # such that F⃗ = -κ∇T = -f n̂. \n    # Since the BC is on κ∇T, no minus sign is needed.\n    diff⁺.soil.heat.κ∇T = n̂ * bc_function(aux⁻, t)\nend\n\n\n# SurfaceDriven conditions for for SoilHeat and SoilWater\n\n\"\"\"\n    function soil_boundary_flux!(\n        nf,\n        bc::SurfaceDrivenWaterBoundaryConditions,\n        water::SoilWaterModel,\n        land::LandModel,\n        state⁺::Vars,\n        diff⁺::Vars,\n        aux⁺::Vars,\n        n̂,\n        state⁻::Vars,\n        diff⁻::Vars,\n        aux⁻::Vars,\n        t,\n    )\n\nThe Surface Driven BC method for `soil_boundary_flux!` for the\n`SoilWaterModel`.\n\"\"\"\nfunction soil_boundary_flux!(\n    nf,\n    bc::SurfaceDrivenWaterBoundaryConditions,\n    water::SoilWaterModel,\n    land::LandModel,\n    state⁺::Vars,\n    diff⁺::Vars,\n    aux⁺::Vars,\n    n̂,\n    state⁻::Vars,\n    diff⁻::Vars,\n    aux⁻::Vars,\n    t,\n    _...,\n)\n\n    diff⁺.soil.water.K∇h = compute_surface_grad_bc(\n        land.soil,\n        bc.runoff_model,\n        bc.precip_model,\n        n̂,\n        state⁻,\n        diff⁻,\n        aux⁻,\n        t,\n    )\n\nend\n\n\"\"\"\n    soil_boundary_flux!(\n        nf,\n        bc::SurfaceDrivenHeatBoundaryConditions,\n        heat::SoilHeatModel,\n        land::LandModel,\n        state⁺::Vars,\n        diff⁺::Vars,\n        aux⁺::Vars,\n        n̂,\n        state⁻::Vars,\n        diff⁻::Vars,\n        aux⁻::Vars,\n        t,\n    )\n\nThe Surface Driven BC method for `soil_boundary_flux!` for the\n`SoilHeatModel`.\n\"\"\"\nfunction soil_boundary_flux!(\n    nf,\n    bc::SurfaceDrivenHeatBoundaryConditions,\n    heat::SoilHeatModel,\n    land::LandModel,\n    state⁺::Vars,\n    diff⁺::Vars,\n    aux⁺::Vars,\n    n̂,\n    state⁻::Vars,\n    diff⁻::Vars,\n    aux⁻::Vars,\n    t,\n    _...,\n)\n\n    net_surface_flux = compute_net_radiative_energy_flux(bc.nswf_model, t)\n    diff⁺.soil.heat.κ∇T = n̂ * (-net_surface_flux)\nend\n"
  },
  {
    "path": "src/Land/Model/soil_heat.jl",
    "content": "### Soil heat model\n\nexport SoilHeatModel, PrescribedTemperatureModel, get_temperature\n\nabstract type AbstractHeatModel <: AbstractSoilComponentModel end\n\n\"\"\"\n    PrescribedTemperatureModel{F1} <: AbstractHeatModel\n\nModel structure for a prescribed temperature model.\n\"\"\"\nstruct PrescribedTemperatureModel{F1} <: AbstractHeatModel\n    \"Temperature\"\n    T::F1\nend\n\n\"\"\"\n    PrescribedTemperatureModel(\n        T::Function = (aux,t) -> eltype(aux)(0.0)\n    )\nOuter constructor for the PrescribedTemperatureModel defining default values.\nThe functions supplied by the user are point-wise evaluated and are\nevaluated in the Balance Law functions compute_gradient_argument,\n nodal_update, etc. whenever the prescribed temperature content variables are\nneeded by the water model.\n\"\"\"\nfunction PrescribedTemperatureModel(T::Function = (aux, t) -> eltype(aux)(0.0))\n    return PrescribedTemperatureModel{typeof(T)}(T)\nend\n\n\"\"\"\n    SoilHeatModel{FT, FiT} <: AbstractHeatModel\nThe necessary components for the Heat Equation in a soil water matrix.\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct SoilHeatModel{FT, FiT} <: AbstractHeatModel\n    \"Initial conditions for temperature\"\n    initialT::FiT\nend\n\n\"\"\"\n    SoilHeatModel(\n        ::Type{FT};\n        initialT::FT = FT(NaN),\n    ) where {FT}\n\nConstructor for the SoilHeatModel.\n\"\"\"\nfunction SoilHeatModel(::Type{FT}; initialT = (aux) -> FT(NaN)) where {FT}\n    args = initialT\n    return SoilHeatModel{FT, typeof(args)}(args)\nend\n\n\"\"\"\n    get_temperature(\n        heat::SoilHeatModel\n        aux::Vars,\n        t::Real\n    )\nReturns the temperature when the heat model chosen is the dynamical SoilHeatModel.\nThis is necessary for fully coupling heat and water.\n\"\"\"\nfunction get_temperature(heat::SoilHeatModel, aux::Vars, t::Real)\n    T = aux.soil.heat.T\n    return T\nend\n\n\"\"\"\n    get_temperature(\n        heat::PrescribedTemperatureModel,\n        aux::Vars,\n        t::Real\n    )\nReturns the temperature when the heat model chosen is a user prescribed one.\nThis is useful for driving Richard's equation without a back reaction on temperature.\n\"\"\"\nfunction get_temperature(heat::PrescribedTemperatureModel, aux::Vars, t::Real)\n    T = heat.T(aux, t)\n    return T\nend\n\n\"\"\"\n    get_initial_temperature(\n        m::SoilHeatModel\n        aux::Vars,\n        t::Real\n    )\nReturns the temperature from the SoilHeatModel.\nNeeded for soil_init_aux! of SoilWaterModel.\n\"\"\"\nfunction get_initial_temperature(m::SoilHeatModel, aux::Vars, t::Real)\n    return m.initialT(aux)\nend\n\n\n\"\"\"\n    get_initial_temperature(\n        m::PrescribedTemperatureModel,\n        aux::Vars,\n        t::Real\n    )\nReturns the temperature from the prescribed model.\nNeeded for soil_init_aux! of SoilWaterModel.\n\"\"\"\nfunction get_initial_temperature(\n    m::PrescribedTemperatureModel,\n    aux::Vars,\n    t::Real,\n)\n    return m.T(aux, t)\nend\n\n\nvars_state(heat::SoilHeatModel, st::Prognostic, FT) = @vars(ρe_int::FT)\nvars_state(heat::SoilHeatModel, st::Auxiliary, FT) = @vars(T::FT)\nvars_state(heat::SoilHeatModel, st::Gradient, FT) = @vars(T::FT)\nvars_state(heat::SoilHeatModel, st::GradientFlux, FT) =\n    @vars(κ∇T::SVector{3, FT})\n\nfunction soil_init_aux!(\n    land::LandModel,\n    soil::SoilModel,\n    heat::SoilHeatModel,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    aux.soil.heat.T = heat.initialT(aux)\nend\n\nfunction land_nodal_update_auxiliary_state!(\n    land::LandModel,\n    soil::SoilModel,\n    heat::SoilHeatModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n\n    param_set = parameter_set(land)\n    ϑ_l, θ_i = get_water_content(land.soil.water, aux, state, t)\n    eff_porosity = soil.param_functions.porosity - θ_i\n    θ_l = volumetric_liquid_fraction(ϑ_l, eff_porosity)\n    ρc_ds = soil.param_functions.ρc_ds\n    ρc_s = volumetric_heat_capacity(θ_l, θ_i, ρc_ds, param_set)\n    aux.soil.heat.T =\n        temperature_from_ρe_int(state.soil.heat.ρe_int, θ_i, ρc_s, param_set)\nend\n\nfunction compute_gradient_argument!(\n    land::LandModel,\n    soil::SoilModel,\n    heat::SoilHeatModel,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n\n    param_set = parameter_set(land)\n    ϑ_l, θ_i = get_water_content(land.soil.water, aux, state, t)\n    eff_porosity = soil.param_functions.porosity - θ_i\n    θ_l = volumetric_liquid_fraction(ϑ_l, eff_porosity)\n    ρc_ds = soil.param_functions.ρc_ds\n    ρc_s = volumetric_heat_capacity(θ_l, θ_i, ρc_ds, param_set)\n    transform.soil.heat.T =\n        temperature_from_ρe_int(state.soil.heat.ρe_int, θ_i, ρc_s, param_set)\nend\n\nfunction compute_gradient_flux!(\n    land::LandModel,\n    soil::SoilModel,\n    heat::SoilHeatModel,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    param_set = parameter_set(land)\n    ϑ_l, θ_i = get_water_content(land.soil.water, aux, state, t)\n    eff_porosity = soil.param_functions.porosity - θ_i\n    θ_l = volumetric_liquid_fraction(ϑ_l, eff_porosity)\n    κ_dry = k_dry(param_set, soil.param_functions)\n    S_r = relative_saturation(θ_l, θ_i, soil.param_functions.porosity)\n    kersten = kersten_number(θ_i, S_r, soil.param_functions)\n    κ_sat = saturated_thermal_conductivity(\n        θ_l,\n        θ_i,\n        soil.param_functions.κ_sat_unfrozen,\n        soil.param_functions.κ_sat_frozen,\n    )\n    diffusive.soil.heat.κ∇T =\n        thermal_conductivity(κ_dry, kersten, κ_sat) * ∇transform.soil.heat.T\nend\n\nstruct DiffHeatFlux <: TendencyDef{Flux{SecondOrder}} end\nstruct DarcyDrivenHeatFlux <: TendencyDef{Flux{SecondOrder}} end\n\nfunction flux(::VolumetricInternalEnergy, ::DiffHeatFlux, land::LandModel, args)\n    @unpack diffusive = args\n    return -diffusive.soil.heat.κ∇T\nend\n\nfunction flux(\n    ::VolumetricInternalEnergy,\n    ::DarcyDrivenHeatFlux,\n    land::LandModel,\n    args,\n)\n    @unpack aux, diffusive = args\n    param_set = parameter_set(land)\n    ρe_int_l = volumetric_internal_energy_liq(aux.soil.heat.T, param_set)\n    return -ρe_int_l * diffusive.soil.water.K∇h\nend\n"
  },
  {
    "path": "src/Land/Model/soil_model.jl",
    "content": "#### Soil model\n\nexport SoilModel, SoilParamFunctions, WaterParamFunctions\n\n\"\"\"\n    AbstractSoilParameterFunctions{FT <: AbstractFloat}\n\"\"\"\nabstract type AbstractSoilParameterFunctions{FT <: AbstractFloat} end\n\n\n\"\"\"\n    WaterParamFunctions{FT, TK, TS, TR} <: AbstractSoilParameterFunctions{FT}\n\nNecessary parameters for the soil water model. These can be floating point\n parameters or functions of space (via the auxiliary variable `aux`). Internally,\nthey will be converted to type FT or altered so that the functions return type FT.\n\nThis is not a complete list - the hydraulic parameters necessary for specifying\nthe van Genuchten or Brooks and Corey functions are stored in the hydraulics model.\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct WaterParamFunctions{FT, TK, TS, TR} <: AbstractSoilParameterFunctions{FT}\n    \"Saturated conductivity. Units of m s-1.\"\n    Ksat::TK\n    \"Specific storage. Units of m s-1.\"\n    S_s::TS\n    \"Residual Water Fraction - default is zero; unitless.\"\n    θ_r::TR\nend\n\n\n\"\"\"\n    HeatParamFunctions{FT, TK, TS, TR} <: AbstractSoilParameterFunctions{FT}\n\nNecessary parameters for the soil water model. These can be floating point\n parameters or functions of space (via the auxiliary variable `aux`). Internally,\nthey will be converted to type FT or altered so that the functions return type FT.\n\nThis is not a complete list - the hydraulic parameters necessary for specifying\nthe van Genuchten or Brooks and Corey functions are stored in the hydraulics model.\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\n\nfunction WaterParamFunctions(\n    ::Type{FT};\n    Ksat::Union{Function, AbstractFloat} = (aux) -> eltype(aux)(0.0),\n    S_s::Union{Function, AbstractFloat} = (aux) -> eltype(aux)(1e-3),\n    θ_r::Union{Function, AbstractFloat} = (aux) -> eltype(aux)(0.0),\n) where {FT}\n    fKsat = Ksat isa AbstractFloat ? (aux) -> FT(Ksat) : (aux) -> FT(Ksat(aux))\n    fS_s = S_s isa AbstractFloat ? (aux) -> FT(S_s) : (aux) -> FT(S_s(aux))\n    fθ = θ_r isa AbstractFloat ? (aux) -> FT(θ_r) : (aux) -> FT(θ_r(aux))\n    args = (fKsat, fS_s, fθ)\n    return WaterParamFunctions{FT, typeof.(args)...}(args...)\nend\n\n\n\"\"\"\n    SoilParamFunctions{FT, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, WP}\n               <: AbstractSoilParameterFunctions{FT}\n\nNecessary parameters for the soil model. Heat parameters to be moved to their \nown structure in next iteration.\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct SoilParamFunctions{\n    FT,\n    F1,\n    F2,\n    F3,\n    F4,\n    F5,\n    F6,\n    F7,\n    F8,\n    F9,\n    F10,\n    F11,\n    F12,\n    WP,\n} <: AbstractSoilParameterFunctions{FT}\n    \"Aggregate porosity of the soil\"\n    porosity::F1\n    \"Volume fraction of gravels, relative to soil solids only; unitless.\"\n    ν_ss_gravel::F2\n    \"Volume fraction of SOM, relative to soil solids only; unitless.\"\n    ν_ss_om::F3\n    \"Volume fraction of quartz, relative to soil solids only; unitless.\"\n    ν_ss_quartz::F4\n    \"Bulk volumetric heat capacity of dry soil. Units of J m-3 K-1.\"\n    ρc_ds::F5\n    \"Particle density for soil solids. Units of kg m-3\"\n    ρp::F6\n    \"Thermal conductivity of the soil solids. Units of W m-1 K-1.\"\n    κ_solid::F7\n    \"Saturated thermal conductivity for unfrozen soil. Units of W m-1 K-1.\"\n    κ_sat_unfrozen::F8\n    \"Saturated thermal conductivity for frozen soil. Units of W m-1 K-1.\"\n    κ_sat_frozen::F9\n    \"Adjustable scale parameter for determining Kersten number in the Balland and Arp formulation; unitless.\"\n    a::F10\n    \"Adjustable scale parameter for determining Kersten number in the Balland and Arp formulation; unitless.\"\n    b::F11\n    \"Parameter used in the Balland and Arp formulation for κ_dry; unitless\"\n    κ_dry_parameter::F12\n    \"Hydrology parameter functions\"\n    water::WP\nend\n\nfunction SoilParamFunctions(\n    ::Type{FT};\n    porosity::FT = FT(NaN),\n    ν_ss_gravel::FT = FT(NaN),\n    ν_ss_om::FT = FT(NaN),\n    ν_ss_quartz::FT = FT(NaN),\n    ρc_ds::FT = FT(NaN),\n    ρp::FT = FT(NaN),\n    κ_solid::FT = FT(NaN),\n    κ_sat_unfrozen::FT = FT(NaN),\n    κ_sat_frozen::FT = FT(NaN),\n    a::FT = FT(0.24),\n    b::FT = FT(18.1),\n    κ_dry_parameter::FT = FT(0.053),\n    water::AbstractSoilParameterFunctions{FT} = WaterParamFunctions(FT;),\n) where {FT}\n    args = (\n        porosity,\n        ν_ss_gravel,\n        ν_ss_om,\n        ν_ss_quartz,\n        ρc_ds,\n        ρp,\n        κ_solid,\n        κ_sat_unfrozen,\n        κ_sat_frozen,\n        a,\n        b,\n        κ_dry_parameter,\n        water,\n    )\n\n    return SoilParamFunctions{FT, typeof.(args)...}(args...)\nend\n\n\n\n\"\"\"\n    SoilModel{PF, W, H} <: BalanceLaw\n\nA BalanceLaw for soil modeling.\nUsers may over-ride prescribed default values for each field.\n\n# Usage\n\n    SoilModel(\n        param_functions,\n        water,\n        heat,\n    )\n\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct SoilModel{PF, W, H} <: BalanceLaw\n    \"Soil Parameter Functions\"\n    param_functions::PF\n    \"Water model\"\n    water::W\n    \"Heat model\"\n    heat::H\nend\n\n\nfunction vars_state(soil::SoilModel, st::Prognostic, FT)\n    @vars begin\n        water::vars_state(soil.water, st, FT)\n        heat::vars_state(soil.heat, st, FT)\n    end\nend\n\nfunction vars_state(soil::SoilModel, st::Auxiliary, FT)\n    @vars begin\n        water::vars_state(soil.water, st, FT)\n        heat::vars_state(soil.heat, st, FT)\n    end\nend\n\n\nfunction vars_state(soil::SoilModel, st::Gradient, FT)\n    @vars begin\n        water::vars_state(soil.water, st, FT)\n        heat::vars_state(soil.heat, st, FT)\n    end\nend\n\n\nfunction vars_state(soil::SoilModel, st::GradientFlux, FT)\n    @vars begin\n        water::vars_state(soil.water, st, FT)\n        heat::vars_state(soil.heat, st, FT)\n    end\nend\n\nfunction compute_gradient_argument!(\n    land::LandModel,\n    soil::SoilModel,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    compute_gradient_argument!(land, soil, soil.heat, transform, state, aux, t)\n    compute_gradient_argument!(land, soil, soil.water, transform, state, aux, t)\nend\n\n\nfunction compute_gradient_flux!(\n    land::LandModel,\n    soil::SoilModel,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n\n    compute_gradient_flux!(\n        land,\n        soil,\n        soil.water,\n        diffusive,\n        ∇transform,\n        state,\n        aux,\n        t,\n    )\n    compute_gradient_flux!(\n        land,\n        soil,\n        soil.heat,\n        diffusive,\n        ∇transform,\n        state,\n        aux,\n        t,\n    )\n\nend\n\nfunction land_nodal_update_auxiliary_state!(\n    land::LandModel,\n    soil::SoilModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    land_nodal_update_auxiliary_state!(land, soil, soil.water, state, aux, t)\n    land_nodal_update_auxiliary_state!(land, soil, soil.heat, state, aux, t)\nend\n\n\nfunction land_init_aux!(\n    land::LandModel,\n    soil::SoilModel,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    soil_init_aux!(land, soil, soil.water, aux, geom)\n    soil_init_aux!(land, soil, soil.heat, aux, geom)\nend\n\n\"\"\"\n    abstract type AbstractSoilComponentModel <: BalanceLaw\n\"\"\"\nabstract type AbstractSoilComponentModel <: BalanceLaw end\n\n## When PrescribedModels are chosen, all balance law functions and boundary\n## condition functions should do nothing. Since these models are of super\n## type AbstractSoilComponentModel, use AbstractSoilComponentModel in\n## argument type. The more specific Water and Heat models will have\n## different methods (see soil_water.jl and soil_heat.jl).\n\nvars_state(m::AbstractSoilComponentModel, st::AbstractStateType, FT) = @vars()\n\n\nfunction soil_init_aux!(\n    land::LandModel,\n    soil::SoilModel,\n    m::AbstractSoilComponentModel,\n    aux::Vars,\n    geom::LocalGeometry,\n) end\n\n\nfunction land_nodal_update_auxiliary_state!(\n    land::LandModel,\n    soil::SoilModel,\n    m::AbstractSoilComponentModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n\nend\n\n\nfunction compute_gradient_argument!(\n    land::LandModel,\n    soil::SoilModel,\n    m::AbstractSoilComponentModel,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n\nend\n\n\nfunction compute_gradient_flux!(\n    land::LandModel,\n    soil::SoilModel,\n    m::AbstractSoilComponentModel,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n\nend\n"
  },
  {
    "path": "src/Land/Model/soil_water.jl",
    "content": "export SoilWaterModel, PrescribedWaterModel, get_water_content\n\nabstract type AbstractWaterModel <: AbstractSoilComponentModel end\n\n\"\"\"\n    PrescribedWaterModel{F1, F2} <: AbstractWaterModel\n\nModel structure for a prescribed water content model.\n\nThe user supplies functions of space and time for both `ϑ_l` and\n`θ_i`. No auxiliary or state variables are added, no PDE is solved.\nThe defaults are no moisture anywhere, for all time.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct PrescribedWaterModel{FN1, FN2} <: AbstractWaterModel\n    \"Augmented liquid fraction\"\n    ϑ_l::FN1\n    \"Volumetric fraction of ice\"\n    θ_i::FN2\nend\n\n\"\"\"\n    PrescribedWaterModel(\n        ϑ_l::Function = (aux, t) -> eltype(aux)(0.0),\n        θ_i::Function = (aux, t) -> eltype(aux)(0.0),\n    )\n\nOuter constructor for the PrescribedWaterModel defining default values.\n\nThe functions supplied by the user are point-wise evaluated and are\nevaluated in the Balance Law functions compute_gradient_argument,\nnodal_update, etc. whenever the prescribed water content variables are\nneeded by the heat model.\n\"\"\"\nfunction PrescribedWaterModel(\n    ϑ_l::Function = (aux, t) -> eltype(aux)(0.0),\n    θ_i::Function = (aux, t) -> eltype(aux)(0.0),\n)\n    args = (ϑ_l, θ_i)\n    return PrescribedWaterModel{typeof.(args)...}(args...)\nend\n\n\n\n\"\"\"\n    SoilWaterModel{FT, IF, VF, MF, HM, Fiϑl, Fiθi} <: AbstractWaterModel\n\nThe necessary components for solving the equations for water (liquid or ice) in soil.\n\nWithout freeze/thaw source terms added (separately), this model reduces to\nRichard's equation for liquid water. Note that the default for `θ_i` is zero.\nWithout freeze/thaw source terms added to both the liquid and ice equations,\nthe default should never be changed, because we do not enforce that the total\nvolumetric water fraction is less than or equal to porosity otherwise.\n\nWhen freeze/thaw source terms are included, this model encompasses water in both\nliquid and ice form, and water content is conserved upon phase change.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct SoilWaterModel{FT, IF, VF, MF, HM, Fiϑl, Fiθi} <: AbstractWaterModel\n    \"Impedance Factor - will be 1 or ice dependent\"\n    impedance_factor::IF\n    \"Viscosity Factor - will be 1 or temperature dependent\"\n    viscosity_factor::VF\n    \"Moisture Factor - will be 1 or moisture dependent\"\n    moisture_factor::MF\n    \"Hydraulics Model - used in matric potential and moisture factor of hydraulic conductivity\"\n    hydraulics::HM\n    \"Initial condition: augmented liquid fraction\"\n    initialϑ_l::Fiϑl\n    \"Initial condition: volumetric ice fraction\"\n    initialθ_i::Fiθi\nend\n\n\"\"\"\n    SoilWaterModel(\n        ::Type{FT};\n        impedance_factor::AbstractImpedanceFactor{FT} = NoImpedance{FT}(),\n        viscosity_factor::AbstractViscosityFactor{FT} = ConstantViscosity{FT}(),\n        moisture_factor::AbstractMoistureFactor{FT} = MoistureIndependent{FT}(),\n        hydraulics::AbstractHydraulicsModel{FT} = vanGenuchten(FT;),\n        initialϑ_l = (aux) -> FT(NaN),\n        initialθ_i = (aux) -> FT(0.0),\n    ) where {FT}\n\nConstructor for the SoilWaterModel. Defaults imply a constant K = K_sat model.\n\"\"\"\nfunction SoilWaterModel(\n    ::Type{FT};\n    impedance_factor::AbstractImpedanceFactor{FT} = NoImpedance{FT}(),\n    viscosity_factor::AbstractViscosityFactor{FT} = ConstantViscosity{FT}(),\n    moisture_factor::AbstractMoistureFactor{FT} = MoistureIndependent{FT}(),\n    hydraulics::AbstractHydraulicsModel{FT} = vanGenuchten(FT;),\n    initialϑ_l::Function = (aux) -> eltype(aux)(NaN),\n    initialθ_i::Function = (aux) -> eltype(aux)(0.0),\n) where {FT}\n\n    args = (\n        impedance_factor,\n        viscosity_factor,\n        moisture_factor,\n        hydraulics,\n        initialϑ_l,\n        initialθ_i,\n    )\n    return SoilWaterModel{FT, typeof.(args)...}(args...)\nend\n\n\n\"\"\"\n    get_water_content(\n        water::SoilWaterModel,\n        aux::Vars,\n        state::Vars,\n        t::Real\n    )\n\nReturn the moisture variables for the balance law soil water model.\n\"\"\"\nfunction get_water_content(\n    water::SoilWaterModel,\n    aux::Vars,\n    state::Vars,\n    t::Real,\n)\n    FT = eltype(state)\n    return FT(state.soil.water.ϑ_l), FT(state.soil.water.θ_i)\nend\n\n\n\n\"\"\"\n    get_water_content(\n        water::PrescribedWaterModel,\n        aux::Vars,\n        state::Vars,\n        t::Real\n    )\n\nReturn the moisture variables for the prescribed soil water model.\n\"\"\"\nfunction get_water_content(\n    water::PrescribedWaterModel,\n    aux::Vars,\n    state::Vars,\n    t::Real,\n)\n    FT = eltype(aux)\n    ϑ_l = water.ϑ_l(aux, t)\n    θ_i = water.θ_i(aux, t)\n    return FT(ϑ_l), FT(θ_i)\nend\n\nvars_state(water::SoilWaterModel, st::Prognostic, FT) = @vars(ϑ_l::FT, θ_i::FT)\n\nvars_state(water::SoilWaterModel, st::Auxiliary, FT) = @vars(h::FT, K::FT)\n\n\nvars_state(water::SoilWaterModel, st::Gradient, FT) = @vars(h::FT)\n\n\nvars_state(::SoilWaterModel, st::GradientFlux, FT) = @vars(K∇h::SVector{3, FT})#really, the flux is - K∇h\n\nfunction soil_init_aux!(\n    land::LandModel,\n    soil::SoilModel,\n    water::SoilWaterModel,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    FT = eltype(aux)\n    T = get_initial_temperature(land.soil.heat, aux, FT(0.0))\n    θ_r = soil.param_functions.water.θ_r(aux)\n    ν = soil.param_functions.porosity #(aux)\n    S_s = soil.param_functions.water.S_s(aux)\n    Ksat = soil.param_functions.water.Ksat(aux)\n\n    ϑ_l = water.initialϑ_l(aux)\n    θ_i = water.initialθ_i(aux)\n    θ_l = volumetric_liquid_fraction(ϑ_l, ν - θ_i)\n\n    S_l = effective_saturation(ν, ϑ_l, θ_r)\n    hydraulics = water.hydraulics(aux)\n    ψ = pressure_head(hydraulics, ν, S_s, θ_r, ϑ_l, θ_i)\n    aux.soil.water.h = hydraulic_head(aux.z, ψ)\n\n    # compute conductivity\n    f_i = θ_i / (θ_l + θ_i)\n    impedance_f = impedance_factor(water.impedance_factor, f_i)\n    viscosity_f = viscosity_factor(water.viscosity_factor, T)\n    moisture_f = moisture_factor(water.moisture_factor, hydraulics, S_l)\n    aux.soil.water.K =\n        hydraulic_conductivity(Ksat, impedance_f, viscosity_f, moisture_f)\nend\n\n\nfunction land_nodal_update_auxiliary_state!(\n    land::LandModel,\n    soil::SoilModel,\n    water::SoilWaterModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ν = soil.param_functions.porosity #(aux)\n    S_s = soil.param_functions.water.S_s(aux)\n    Ksat = soil.param_functions.water.Ksat(aux)\n    θ_r = soil.param_functions.water.θ_r(aux)\n\n    ϑ_l = state.soil.water.ϑ_l\n    θ_i = state.soil.water.θ_i\n    θ_l = volumetric_liquid_fraction(ϑ_l, ν - θ_i)\n\n    T = get_temperature(land.soil.heat, aux, t)\n    S_l = effective_saturation(ν, ϑ_l, θ_r)\n    hydraulics = water.hydraulics(aux)\n    ψ = pressure_head(hydraulics, ν, S_s, θ_r, ϑ_l, θ_i)\n    aux.soil.water.h = hydraulic_head(aux.z, ψ)\n\n    f_i = θ_i / (θ_l + θ_i)\n    impedance_f = impedance_factor(water.impedance_factor, f_i)\n    viscosity_f = viscosity_factor(water.viscosity_factor, T)\n    moisture_f = moisture_factor(water.moisture_factor, hydraulics, S_l)\n    aux.soil.water.K =\n        hydraulic_conductivity(Ksat, impedance_f, viscosity_f, moisture_f)\nend\n\n\nfunction compute_gradient_argument!(\n    land::LandModel,\n    soil::SoilModel,\n    water::SoilWaterModel,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ν = soil.param_functions.porosity #(aux)\n    S_s = soil.param_functions.water.S_s(aux)\n    θ_r = soil.param_functions.water.θ_r(aux)\n    ϑ_l = state.soil.water.ϑ_l\n    θ_i = state.soil.water.θ_i\n\n    S_l = effective_saturation(ν, ϑ_l, θ_r)\n    hydraulics = water.hydraulics(aux)\n    ψ = pressure_head(hydraulics, ν, S_s, θ_r, ϑ_l, θ_i)\n    transform.soil.water.h = hydraulic_head(aux.z, ψ)\n\nend\n\n\nfunction compute_gradient_flux!(\n    land::LandModel,\n    soil::SoilModel,\n    water::SoilWaterModel,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    diffusive.soil.water.K∇h = aux.soil.water.K * ∇transform.soil.water.h\nend\n\nstruct DarcyFlux <: TendencyDef{Flux{SecondOrder}} end\n\nfunction flux(::VolumetricLiquidFraction, ::DarcyFlux, ::LandModel, args)\n    @unpack diffusive = args\n    return -diffusive.soil.water.K∇h\nend\n"
  },
  {
    "path": "src/Land/Model/source.jl",
    "content": "#### Land sources\nexport PhaseChange\n\nfunction heaviside(x::FT) where {FT}\n    if x >= FT(0)\n        output = FT(1)\n    else\n        output = FT(0)\n    end\n    return output\nend\n\n\n\"\"\"\n    PhaseChange <: TendencyDef{Source}\nThe function which computes the freeze/thaw source term for Richard's equation.\n\"\"\"\nBase.@kwdef struct PhaseChange{FT} <: TendencyDef{Source}\n    \"Typical resolution in the vertical\"\n    Δz::FT = FT(NaN)\nend\n\nprognostic_vars(::PhaseChange) =\n    (VolumetricLiquidFraction(), VolumetricIceFraction())\n\nfunction precompute(land::LandModel, args, tt::Source)\n    dtup = DispatchedSet(map(land.source) do s\n        (s, precompute(s, land, args, tt))\n    end)\n    return (; dtup)\nend\n\nfunction precompute(source_type::PhaseChange, land::LandModel, args, tt::Source)\n    @unpack state, diffusive, aux, t, direction = args\n\n    FT = eltype(state)\n\n    param_set = parameter_set(land)\n    _ρliq = FT(ρ_cloud_liq(param_set))\n    _ρice = FT(ρ_cloud_ice(param_set))\n    _Tfreeze = FT(T_freeze(param_set))\n    _LH_f0 = FT(LH_f0(param_set))\n    _g = FT(grav(param_set))\n\n\n\n    ϑ_l, θ_i = get_water_content(land.soil.water, aux, state, t)\n    ν = land.soil.param_functions.porosity\n    eff_porosity = ν - θ_i\n    θ_l = volumetric_liquid_fraction(ϑ_l, eff_porosity)\n    T = get_temperature(land.soil.heat, aux, t)\n    κ_dry = k_dry(land.param_set, land.soil.param_functions)\n    S_r = relative_saturation(θ_l, θ_i, ν)\n    kersten = kersten_number(θ_i, S_r, land.soil.param_functions)\n    κ_sat = saturated_thermal_conductivity(\n        θ_l,\n        θ_i,\n        land.soil.param_functions.κ_sat_unfrozen,\n        land.soil.param_functions.κ_sat_frozen,\n    )\n    κ = thermal_conductivity(κ_dry, kersten, κ_sat)\n    ρc_ds = land.soil.param_functions.ρc_ds\n    ρc_s = volumetric_heat_capacity(θ_l, θ_i, ρc_ds, land.param_set)\n    θ_r = land.soil.param_functions.water.θ_r(aux)\n\n    hydraulics = land.soil.water.hydraulics(aux)\n    θ_m = min(_ρice * θ_i / _ρliq + θ_l, ν)\n    ψ0 = matric_potential(hydraulics, θ_m)\n    ψT = _LH_f0 / _g / _Tfreeze * (T - _Tfreeze)\n    if T < _Tfreeze\n        θstar = θ_r + (ν - θ_r) * inverse_matric_potential(hydraulics, ψ0 + ψT)\n    else\n        θstar = θ_l\n    end\n    Δz = source_type.Δz\n    τLTE = FT(ρc_s * Δz^2 / κ)\n    ΔT = norm(diffusive.soil.heat.κ∇T) / κ * Δz\n    ρ_w = FT(0.5) * (_ρliq + _ρice)\n\n    τpt = τLTE * (ρ_w * _LH_f0 * (ν - θ_r)) / (ρc_s * ΔT)\n\n    τft = max(τLTE, τpt)\n    freeze_thaw =\n        FT(1) / τft * (\n            _ρliq *\n            (θ_l - θstar) *\n            heaviside(_Tfreeze - T) *\n            heaviside(θ_l - θstar) - _ρice * θ_i * heaviside(T - _Tfreeze)\n        )\n    return (; freeze_thaw)\nend\n\nfunction source(\n    ::VolumetricLiquidFraction,\n    s::PhaseChange,\n    land::LandModel,\n    args,\n)\n    @unpack state = args\n    @unpack freeze_thaw = args.precomputed.dtup[s]\n    FT = eltype(state)\n    _ρliq = FT(ρ_cloud_liq(land.param_set))\n    return -freeze_thaw / _ρliq\nend\n\nfunction source(::VolumetricIceFraction, s::PhaseChange, land::LandModel, args)\n    @unpack state = args\n    @unpack freeze_thaw = args.precomputed.dtup[s]\n    FT = eltype(state)\n    _ρice = FT(ρ_cloud_ice(land.param_set))\n    return freeze_thaw / _ρice\nend\n"
  },
  {
    "path": "src/Numerics/DGMethods/Courant.jl",
    "content": "\"\"\"\n    Courant\n\nContains stubs for advective, diffusive, and nondiffusive courant number\ncalculations to be used in `ClimateMachine.DGMethods.courant`. Models\nshould provide concrete implementations if they wish to use these functions.\n\"\"\"\nmodule Courant\n\nexport advective_courant, diffusive_courant, nondiffusive_courant\n\nfunction advective_courant end\n\nfunction nondiffusive_courant end\n\nfunction diffusive_courant end\n\nfunction viscous_courant end\n\nend\n"
  },
  {
    "path": "src/Numerics/DGMethods/DGFVModel.jl",
    "content": "include(\"DGFVModel_kernels.jl\")\nstruct DGFVModel{BL, G, FVR, NFND, NFD, GNF, AS, DS, HDS, D, DD, MD, GF, TF} <:\n       SpaceDiscretization\n    balance_law::BL\n    grid::G\n    fv_reconstruction::FVR\n    numerical_flux_first_order::NFND\n    numerical_flux_second_order::NFD\n    numerical_flux_gradient::GNF\n    state_auxiliary::AS\n    state_gradient_flux::DS\n    states_higher_order::HDS\n    direction::D\n    diffusion_direction::DD\n    modeldata::MD\n    gradient_filter::GF\n    tendency_filter::TF\n    check_for_crashes::Bool\nend\n\nfunction DGFVModel(\n    balance_law,\n    grid,\n    fv_reconstruction,\n    numerical_flux_first_order,\n    numerical_flux_second_order,\n    numerical_flux_gradient;\n    fill_nan = false,\n    check_for_crashes = false,\n    state_auxiliary = create_state(\n        balance_law,\n        grid,\n        Auxiliary(),\n        fill_nan = fill_nan,\n    ),\n    state_gradient_flux = create_state(balance_law, grid, GradientFlux()),\n    states_higher_order = (\n        create_state(balance_law, grid, GradientLaplacian()),\n        create_state(balance_law, grid, Hyperdiffusive()),\n    ),\n    direction = EveryDirection(),\n    diffusion_direction = direction,\n    modeldata = nothing,\n    gradient_filter = nothing,\n    tendency_filter = nothing,\n)\n    # Make sure we are FVM in the vertical\n    @assert polynomialorders(grid)[end] == 0\n    @assert isstacked(grid.topology)\n    state_auxiliary =\n        init_state(state_auxiliary, balance_law, grid, direction, Auxiliary())\n    DGFVModel(\n        balance_law,\n        grid,\n        fv_reconstruction,\n        numerical_flux_first_order,\n        numerical_flux_second_order,\n        numerical_flux_gradient,\n        state_auxiliary,\n        state_gradient_flux,\n        states_higher_order,\n        direction,\n        diffusion_direction,\n        modeldata,\n        gradient_filter,\n        tendency_filter,\n        check_for_crashes,\n    )\nend\n\n\"\"\"\n    (dgfvm::DGFVModel)(tendency, state_prognostic, _, t, α, β)\n\nUses spectral element discontinuous Galerkin in the horizontal and finite volume\nin the vertical to compute the tendency.\n\nComputes the tendency terms compatible with `IncrementODEProblem`\n\n    tendency .= α .* dQdt(state_prognostic, p, t) .+ β .* tendency\n\nThe 4-argument form will just compute\n\n    tendency .= dQdt(state_prognostic, p, t)\n\"\"\"\nfunction (dgfvm::DGFVModel)(tendency, state_prognostic, _, t, α, β)\n    device = array_device(state_prognostic)\n\n    FT = eltype(state_prognostic)\n    num_state_prognostic = number_states(dgfvm.balance_law, Prognostic())\n    num_state_gradient_flux = number_states(dgfvm.balance_law, GradientFlux())\n    @assert 0 == number_states(dgfvm.balance_law, Hyperdiffusive())\n    num_state_tendency = size(tendency, 2)\n\n    if num_state_prognostic < num_state_tendency && β != 1\n        # If we don't operate on the full state, then we need to scale here instead of volume_tendency!\n        tendency .*= β\n        β = β != 0 # if β==0 then we can avoid the memory load in volume_tendency!\n    end\n\n    communicate =\n        !(\n            isstacked(dgfvm.grid.topology) &&\n            typeof(dgfvm.direction) <: VerticalDirection\n        )\n\n    update_auxiliary_state!(\n        dgfvm,\n        dgfvm.balance_law,\n        state_prognostic,\n        t,\n        dgfvm.grid.topology.realelems,\n    )\n\n    exchange_state_gradient_flux = NoneEvent()\n    exchange_state_prognostic = NoneEvent()\n\n    comp_stream = Event(device)\n\n    if communicate\n        exchange_state_prognostic = MPIStateArrays.begin_ghost_exchange!(\n            state_prognostic;\n            dependencies = comp_stream,\n        )\n    end\n\n    if num_state_gradient_flux > 0\n        ########################\n        # Gradient Computation #\n        ########################\n\n        comp_stream = launch_volume_gradients!(\n            dgfvm,\n            state_prognostic,\n            t;\n            dependencies = comp_stream,\n        )\n\n        comp_stream = launch_interface_gradients!(\n            dgfvm,\n            state_prognostic,\n            t;\n            surface = :interior,\n            dependencies = comp_stream,\n        )\n\n        if communicate\n            exchange_state_prognostic = MPIStateArrays.end_ghost_exchange!(\n                state_prognostic;\n                dependencies = exchange_state_prognostic,\n                check_for_crashes = dgfvm.check_for_crashes,\n            )\n\n            # Update_aux may start asynchronous work on the compute device and\n            # we synchronize those here through a device event.\n            checked_wait(\n                device,\n                exchange_state_prognostic,\n                nothing,\n                dgfvm.check_for_crashes,\n            )\n            update_auxiliary_state!(\n                dgfvm,\n                dgfvm.balance_law,\n                state_prognostic,\n                t,\n                dgfvm.grid.topology.ghostelems,\n            )\n            exchange_state_prognostic = Event(device)\n        end\n\n        comp_stream = launch_interface_gradients!(\n            dgfvm,\n            state_prognostic,\n            t;\n            surface = :exterior,\n            dependencies = (comp_stream, exchange_state_prognostic),\n        )\n\n        if dgfvm.gradient_filter !== nothing\n            comp_stream = Filters.apply_async!(\n                dgfvm.state_gradient_flux,\n                1:num_state_gradient_flux,\n                dgfvm.grid,\n                dgfvm.gradient_filter;\n                dependencies = comp_stream,\n            )\n        end\n\n\n        if communicate\n            if num_state_gradient_flux > 0\n                exchange_state_gradient_flux =\n                    MPIStateArrays.begin_ghost_exchange!(\n                        dgfvm.state_gradient_flux,\n                        dependencies = comp_stream,\n                    )\n            end\n        end\n\n        if num_state_gradient_flux > 0\n            # Update_aux_diffusive may start asynchronous work on the compute device\n            # and we synchronize those here through a device event.\n            checked_wait(device, comp_stream, nothing, dgfvm.check_for_crashes)\n            update_auxiliary_state_gradient!(\n                dgfvm,\n                dgfvm.balance_law,\n                state_prognostic,\n                t,\n                dgfvm.grid.topology.realelems,\n            )\n            comp_stream = Event(device)\n        end\n    end\n\n    ###################\n    # RHS Computation #\n    ###################\n    comp_stream = launch_volume_tendency!(\n        dgfvm,\n        tendency,\n        state_prognostic,\n        t,\n        α,\n        β;\n        dependencies = comp_stream,\n    )\n\n    comp_stream = launch_interface_tendency!(\n        dgfvm,\n        tendency,\n        state_prognostic,\n        t,\n        α,\n        β;\n        surface = :interior,\n        dependencies = comp_stream,\n    )\n\n    if communicate\n        if num_state_gradient_flux > 0\n            exchange_state_gradient_flux = MPIStateArrays.end_ghost_exchange!(\n                dgfvm.state_gradient_flux;\n                dependencies = exchange_state_gradient_flux,\n                check_for_crashes = dgfvm.check_for_crashes,\n            )\n\n            # Update_aux_diffusive may start asynchronous work on the\n            # compute device and we synchronize those here through a device\n            # event.\n            checked_wait(\n                device,\n                exchange_state_gradient_flux,\n                nothing,\n                dgfvm.check_for_crashes,\n            )\n            update_auxiliary_state_gradient!(\n                dgfvm,\n                dgfvm.balance_law,\n                state_prognostic,\n                t,\n                dgfvm.grid.topology.ghostelems,\n            )\n            exchange_state_gradient_flux = Event(device)\n        else\n            exchange_state_prognostic = MPIStateArrays.end_ghost_exchange!(\n                state_prognostic;\n                dependencies = exchange_state_prognostic,\n                check_for_crashes = dgfvm.check_for_crashes,\n            )\n\n            # Update_aux may start asynchronous work on the compute device and\n            # we synchronize those here through a device event.\n            checked_wait(\n                device,\n                exchange_state_prognostic,\n                nothing,\n                dgfvm.check_for_crashes,\n            )\n            update_auxiliary_state!(\n                dgfvm,\n                dgfvm.balance_law,\n                state_prognostic,\n                t,\n                dgfvm.grid.topology.ghostelems,\n            )\n            exchange_state_prognostic = Event(device)\n        end\n    end\n\n    comp_stream = launch_interface_tendency!(\n        dgfvm,\n        tendency,\n        state_prognostic,\n        t,\n        α,\n        β;\n        surface = :exterior,\n        dependencies = (\n            comp_stream,\n            exchange_state_prognostic,\n            exchange_state_gradient_flux,\n            # XXX: This is disabled until FVM with hyperdiffusion for DG is implemented: exchange_Qhypervisc_grad,\n        ),\n    )\n\n    if dgfvm.tendency_filter !== nothing\n        comp_stream = Filters.apply_async!(\n            tendency,\n            1:num_state_tendency,\n            dgfvm.grid,\n            dgfvm.tendency_filter;\n            dependencies = comp_stream,\n        )\n    end\n\n    # The synchronization here through a device event prevents CuArray based and\n    # other default stream kernels from launching before the work scheduled in\n    # this function is finished.\n    checked_wait(device, comp_stream, nothing, dgfvm.check_for_crashes)\nend\n\nfunction fvm_balance!(\n    balance_func,\n    m::BalanceLaw,\n    state_auxiliary::MPIStateArray,\n    grid,\n)\n    device = array_device(state_auxiliary)\n\n\n    dim = dimensionality(grid)\n    N = polynomialorders(grid)\n    Nq = N .+ 1\n    Nqj = dim == 2 ? 1 : Nq[2]\n\n    topology = grid.topology\n    elems = topology.elems\n    nelem = length(elems)\n    nvertelem = topology.stacksize\n    horzelems = fld1(first(elems), nvertelem):fld1(last(elems), nvertelem)\n\n    event = Event(device)\n    event = kernel_fvm_balance!(device, (Nq[1], Nqj))(\n        balance_func,\n        m,\n        Val(nvertelem),\n        state_auxiliary.data,\n        grid.vgeo,\n        horzelems;\n        ndrange = (length(horzelems) * Nq[1], Nqj),\n        dependencies = (event,),\n    )\n    wait(device, event)\nend\n"
  },
  {
    "path": "src/Numerics/DGMethods/DGFVModel_kernels.jl",
    "content": "import .FVReconstructions: FVConstant, FVLinear\nusing ..BalanceLaws: Primitive\nimport ..BalanceLaws:\n    prognostic_to_primitive!,\n    primitive_to_prognostic!,\n    construct_face_auxiliary_state!\nimport .FVReconstructions: width\nimport StaticArrays: SUnitRange\n\n@doc \"\"\"\n    function vert_fvm_interface_tendency!(\n        balance_law::BalanceLaw,\n        ::Val{info},\n        ::Val{nvertelem},\n        ::Val{periodicstack},\n        ::VerticalDirection,\n        numerical_flux_first_order,\n        tendency,\n        state_prognostic,\n        state_auxiliary,\n        vgeo,\n        sgeo,\n        t,\n        vmap⁻,\n        vmap⁺,\n        elemtobndy,\n        elems,\n        α,\n    )\n\nCompute kernel for evaluating the interface tendencies using vertical FVM\nreconstructions with a DG method of the form:\n\n∫ₑ ψ⋅ ∂q/∂t dx - ∫ₑ ∇ψ⋅(Fⁱⁿᵛ + Fᵛⁱˢᶜ) dx + ∮ₑ n̂ ψ⋅(Fⁱⁿᵛ⋆ + Fᵛⁱˢᶜ⋆) dS,\n\nor equivalently in matrix form:\n\ndQ/dt = M⁻¹(MS + DᵀM(Fⁱⁿᵛ + Fᵛⁱˢᶜ) + ∑ᶠ LᵀMf(Fⁱⁿᵛ⋆ + Fᵛⁱˢᶜ⋆)).\n\nThis kernel computes the surface terms: M⁻¹ ∑ᶠ LᵀMf(Fⁱⁿᵛ⋆ + Fᵛⁱˢᶜ⋆)), where M\nis the mass matrix, Mf is the face mass matrix, L is an interpolator from\nvolume to face, and Fⁱⁿᵛ⋆, Fᵛⁱˢᶜ⋆ are the numerical fluxes for the inviscid\nand viscous fluxes, respectively.\n\nA finite volume reconstruction is used to construction `Fⁱⁿᵛ⋆`\n\"\"\" vert_fvm_interface_tendency!\n@kernel function vert_fvm_interface_tendency!(\n    balance_law::BalanceLaw,\n    ::Val{info},\n    ::Val{nvertelem},\n    ::Val{periodicstack},\n    ::VerticalDirection,\n    reconstruction!,\n    numerical_flux_first_order,\n    numerical_flux_second_order,\n    tendency,\n    state_prognostic,\n    state_gradient_flux,\n    state_auxiliary,\n    vgeo,\n    sgeo,\n    t,\n    elemtobndy,\n    elems,\n    α,\n    β,\n    increment,\n    add_source,\n) where {info, nvertelem, periodicstack}\n    @uniform begin\n        dim = info.dim\n        FT = eltype(state_prognostic)\n        num_state_prognostic = number_states(balance_law, Prognostic())\n        num_state_primitive = number_states(balance_law, Primitive())\n        num_state_auxiliary = number_states(balance_law, Auxiliary())\n        num_state_gradient_flux = number_states(balance_law, GradientFlux())\n        num_state_hyperdiffusive = number_states(balance_law, Hyperdiffusive())\n        @assert num_state_hyperdiffusive == 0\n\n        nface = info.nface\n        Np = info.Np\n        Nqk = info.Nqk # Can only be 1 for the FVM method!\n        @assert Nqk == 1\n\n        # We only have the vertical faces\n        faces = (nface - 1):nface\n\n        stencil_width = width(reconstruction!)\n\n        # If this fails the `@nif` below needs to change\n        @assert stencil_width < 4\n\n        # In the case of stencil_width = 0 we still need two values to evaluate\n        # the fluxes, so the minimum stencil diameter is 2\n        stencil_diameter = max(2, 2stencil_width + 1)\n\n        # Value in the stencil that corresponds to the top face with respect to\n        # face being updated\n        stencil_center = max(stencil_width, 1) + 1\n\n        # 1 → element i, face i - 1/2 (bottom face)\n        # 2 → element i, face i + 1/2 (top face)\n        local_state_face_prognostic = ntuple(Val(2)) do _\n            MArray{Tuple{num_state_prognostic}, FT}(undef)\n        end\n\n        local_cell_weights = MArray{Tuple{stencil_diameter}, FT}(undef)\n\n        # Two mass matrix inverse corresponding to +/- cells\n        vMI = MArray{Tuple{2}, FT}(undef)\n\n        # Storing the value below element when walking up the stack\n        # cell i-1, face i - 1/2\n        local_state_face_prognostic_neighbor =\n            MArray{Tuple{num_state_prognostic}, FT}(undef)\n\n        local_state_face_auxiliary_neighbor =\n            MArray{Tuple{num_state_auxiliary}, FT}(undef)\n\n        local_state_face_primitive = ntuple(Val(2)) do _\n            MArray{Tuple{num_state_primitive}, FT}(undef)\n        end\n\n        local_state_face_auxiliary = ntuple(Val(2)) do _\n            MArray{Tuple{num_state_auxiliary}, FT}(undef)\n        end\n\n        # Storage for all the values in the stencil\n        local_state_prognostic = ntuple(Val(stencil_diameter)) do _\n            MArray{Tuple{num_state_prognostic}, FT}(undef)\n        end\n\n\n        # Need to wrap in SVector so we can use views later\n        local_state_primitive = SVector(ntuple(Val(stencil_diameter)) do _\n            MArray{Tuple{num_state_primitive}, FT}(undef)\n        end...)\n\n        local_state_auxiliary = ntuple(Val(stencil_diameter)) do _\n            MArray{Tuple{num_state_auxiliary}, FT}(undef)\n        end\n\n        # FIXME: These two arrays could be smaller\n        # (only 2 elements not stencil_diameter)\n        local_state_gradient_flux = ntuple(Val(stencil_diameter)) do _\n            MArray{Tuple{num_state_gradient_flux}, FT}(undef)\n        end\n\n        local_state_hyperdiffusive = ntuple(Val(stencil_diameter)) do _\n            MArray{Tuple{num_state_hyperdiffusive}, FT}(undef)\n        end\n\n        # Storage for the numerical flux\n        local_flux = MArray{Tuple{num_state_prognostic}, FT}(undef)\n\n        # Storage for the extra boundary points for some BCs\n        local_state_prognostic_bottom1 =\n            MArray{Tuple{num_state_prognostic}, FT}(undef)\n        local_state_gradient_flux_bottom1 =\n            MArray{Tuple{num_state_gradient_flux}, FT}(undef)\n        local_state_auxiliary_bottom1 =\n            MArray{Tuple{num_state_auxiliary}, FT}(undef)\n\n        # Storage for the tendency\n        local_tendency = MArray{Tuple{num_state_prognostic}, FT}(undef)\n        local_source = MArray{Tuple{num_state_prognostic}, FT}(undef)\n\n        # The remainder model needs to know which direction of face the model is\n        # being evaluated for. In this case we only have `VerticalDirection()`\n        # faces\n        face_direction = (VerticalDirection(),)\n    end\n\n    # Optimization ideas:\n    #  - More than 1 thread per stack\n    #  - shift pointers not data\n    #  - Don't keep wide stencil for all variables / data\n    #  - Don't load periodic data when domain is not periodic\n\n    # Get the horizontal group IDs\n    grp_H = @index(Group, Linear)\n\n    # Determine the index for the element at the bottom of the stack\n    eHI = (grp_H - 1) * nvertelem + 1\n\n    # Compute bottom stack element index minus one (so we can add vert element\n    # number directly)\n    eH = elems[eHI] - 1\n\n    # Which degree of freedom do we handle in the element\n    n = @index(Local, Linear)\n\n\n    # Loads the data for a given element\n    function load_data!(\n        local_state_prognostic,\n        local_state_auxiliary,\n        local_state_gradient_flux,\n        e,\n    )\n        @unroll for s in 1:num_state_prognostic\n            @inbounds local_state_prognostic[s] = state_prognostic[n, s, e]\n        end\n\n        @unroll for s in 1:num_state_auxiliary\n            @inbounds local_state_auxiliary[s] = state_auxiliary[n, s, e]\n        end\n\n        @unroll for s in 1:num_state_gradient_flux\n            @inbounds local_state_gradient_flux[s] =\n                state_gradient_flux[n, s, e]\n        end\n    end\n\n    # To update the first element we either need to apply a BCS on the bottom\n    # face in the nonperiodic case, or we need to reconstruct the periodic value\n    # for the bottom face\n    @inbounds begin\n        # If periodic, we are doing the reconstruction in element periodically\n        # below the first element, otherwise we are reconstructing the first\n        # element\n        eV = periodicstack ? nvertelem : 1\n\n        # Figure out the data we need\n        els = ntuple(Val(stencil_diameter)) do k\n            eH + mod1(eV - 1 + k - (stencil_center - 1), nvertelem)\n        end\n\n        # Load all the stencil data\n        @unroll for k in 1:stencil_diameter\n            load_data!(\n                local_state_prognostic[k],\n                local_state_auxiliary[k],\n                local_state_gradient_flux[k],\n                els[k],\n            )\n            # If local cell weights are NOT _M we need to load _vMI out of sgeo\n            local_cell_weights[k] = 2 * vgeo[n, _JcV, els[k]]\n        end\n\n        # Transform all the data into primitive variables\n        @unroll for k in 1:stencil_diameter\n            prognostic_to_primitive!(\n                balance_law,\n                local_state_primitive[k],\n                local_state_prognostic[k],\n                local_state_auxiliary[k],\n            )\n        end\n        vMI[2] = sgeo[_vMI, n, faces[2], els[stencil_center]]\n\n        # If we are periodic we reconstruct the top and bottom values for eV\n        # then start with eV update in loop below\n        if periodicstack\n            # Reconstruct the top and bottom values\n            rng1, rng2 = stencil_center .+ (-stencil_width, stencil_width)\n            rng = SUnitRange(rng1, rng2)\n            reconstruction!(\n                local_state_face_primitive[1],\n                local_state_face_primitive[2],\n                local_state_primitive[rng],\n                local_cell_weights[rng],\n            )\n\n            construct_face_auxiliary_state!(\n                balance_law,\n                local_state_face_auxiliary[1],\n                local_state_auxiliary[stencil_center],\n                -local_cell_weights[stencil_center],\n            )\n            construct_face_auxiliary_state!(\n                balance_law,\n                local_state_face_auxiliary[2],\n                local_state_auxiliary[stencil_center],\n                local_cell_weights[stencil_center],\n            )\n\n            # Transform the values back to prognostic state\n            @unroll for f in 1:2\n                primitive_to_prognostic!(\n                    balance_law,\n                    local_state_face_prognostic[f],\n                    local_state_face_primitive[f],\n                    # Use the cell auxiliary data\n                    local_state_face_auxiliary[f],\n                )\n            end\n\n            # Initialize local tendency\n            @unroll for s in 1:num_state_prognostic\n                local_tendency[s] = -zero(FT)\n            end\n        else\n\n            bctag = elemtobndy[faces[1], eH + eV]\n\n            sM = sgeo[_sM, n, faces[1], eH + eV]\n            normal = SVector(\n                sgeo[_n1, n, faces[1], eH + eV],\n                sgeo[_n2, n, faces[1], eH + eV],\n                sgeo[_n3, n, faces[1], eH + eV],\n            )\n\n            # Reconstruction using only eVs cell value\n            rng = SUnitRange(stencil_center, stencil_center)\n            reconstruction!(\n                local_state_face_primitive[1],\n                local_state_face_primitive[2],\n                local_state_primitive[rng],\n                local_cell_weights[rng],\n            )\n\n\n            construct_face_auxiliary_state!(\n                balance_law,\n                local_state_face_auxiliary[1],\n                local_state_auxiliary[stencil_center],\n                -local_cell_weights[stencil_center],\n            )\n            construct_face_auxiliary_state!(\n                balance_law,\n                local_state_face_auxiliary[2],\n                local_state_auxiliary[stencil_center],\n                local_cell_weights[stencil_center],\n            )\n\n            # Transform the values back to prognostic state\n            @unroll for k in 1:2\n                primitive_to_prognostic!(\n                    balance_law,\n                    local_state_face_prognostic[k],\n                    local_state_face_primitive[k],\n                    local_state_face_auxiliary[k],\n                )\n            end\n\n            # Fill ghost cell data\n            fill!(local_flux, -zero(FT))\n            local_state_face_prognostic_neighbor .=\n                local_state_face_prognostic[1]\n            local_state_face_auxiliary_neighbor .= local_state_face_auxiliary[1]\n\n\n            numerical_boundary_flux_first_order!(\n                numerical_flux_first_order,\n                bctag,\n                balance_law,\n                local_flux,\n                normal,\n                # current cell\n                local_state_face_prognostic[1],\n                local_state_face_auxiliary[1],\n                # ghost cell\n                local_state_face_prognostic_neighbor,\n                local_state_face_auxiliary_neighbor,\n                t,\n                face_direction,\n                # use current cell states as bottom states\n                local_state_face_prognostic[1],\n                local_state_face_auxiliary[1],\n            )\n\n            # Fill / reset ghost cell data\n            local_state_prognostic[stencil_center - 1] .=\n                local_state_prognostic[stencil_center]\n            local_state_gradient_flux[stencil_center - 1] .=\n                local_state_gradient_flux[stencil_center]\n            local_state_hyperdiffusive[stencil_center - 1] .=\n                local_state_hyperdiffusive[stencil_center]\n            local_state_auxiliary[stencil_center - 1] .=\n                local_state_auxiliary[stencil_center]\n\n            numerical_boundary_flux_second_order!(\n                numerical_flux_second_order,\n                bctag,\n                balance_law,\n                local_flux,\n                normal,\n                # current cell\n                local_state_prognostic[stencil_center],\n                local_state_gradient_flux[stencil_center],\n                local_state_hyperdiffusive[stencil_center],\n                local_state_auxiliary[stencil_center],\n                # ghost cell\n                local_state_prognostic[stencil_center - 1],\n                local_state_gradient_flux[stencil_center - 1],\n                local_state_hyperdiffusive[stencil_center - 1],\n                local_state_auxiliary[stencil_center - 1],\n                t,\n                # use current cell states as bottom states\n                local_state_prognostic[stencil_center],\n                local_state_gradient_flux[stencil_center],\n                local_state_auxiliary[stencil_center],\n            )\n\n\n            # Compute boundary flux and add it in bottom element of the mesh\n            @unroll for s in 1:num_state_prognostic\n                local_tendency[s] = -α * sM * vMI[2] * local_flux[s]\n            end\n        end\n\n        # The rest of the elements in the stack\n        # Compute flux and update for face between elements eV and eV - 1\n        #    top face of eV - 1\n        #    bottom face of eV\n        # For the reconstruction arrays `stencil_center - 1` corresponds to `eV\n        # - 1` and `stencil_center` corresponds to `eV`\n        #\n        # Loop for periodic case has to go beyond the top element so we compute\n        # the flux through the top face of vertical element `nvertelem` and\n        # bottom face of vertical element 1\n        for eV_up in (periodicstack ? (1:nvertelem) : (2:nvertelem))\n            # mod1 handles periodicity\n            eV_dn = mod1(eV_up - 1, nvertelem)\n\n            # Shift data in storage in order to load new upper element for\n            # reconstruction\n            # FIXME: shift pointers not data?\n            @unroll for k in 1:(stencil_diameter - 1)\n                local_state_prognostic[k] .= local_state_prognostic[k + 1]\n                local_state_primitive[k] .= local_state_primitive[k + 1]\n                local_state_auxiliary[k] .= local_state_auxiliary[k + 1]\n                local_state_gradient_flux[k] .= local_state_gradient_flux[k + 1]\n                local_cell_weights[k] = local_cell_weights[k + 1]\n            end\n\n            # Update volume mass inverse as we move up the stack of elements\n            vMI[1] = vMI[2]\n\n            # Load surface metrics for the face we will update (bottom face of\n            # `eV_up`)\n            sM = sgeo[_sM, n, faces[1], eH + eV_up]\n            normal = SVector(\n                sgeo[_n1, n, faces[1], eH + eV_up],\n                sgeo[_n2, n, faces[1], eH + eV_up],\n                sgeo[_n3, n, faces[1], eH + eV_up],\n            )\n\n            # Reconstruction for eV_dn was computed in last time through the\n            # loop, so we need to store the upper reconstructed values to\n            # compute flux for this face\n            local_state_face_prognostic_neighbor .=\n                local_state_face_prognostic[2]\n            local_state_face_auxiliary_neighbor .= local_state_face_auxiliary[2]\n            # Next data we need to load (assume periodic, mod1, for now  will\n            # mask out below as needed for boundary conditions)\n            eV_load = mod1(eV_up + stencil_width, nvertelem)\n\n            # Get element number\n            e_load = eH + eV_load\n\n            # Load the next cell into the end of the element arrays\n            load_data!(\n                local_state_prognostic[stencil_diameter],\n                local_state_auxiliary[stencil_diameter],\n                local_state_gradient_flux[stencil_diameter],\n                e_load,\n            )\n\n            # Get local volume mass matrix inverse\n            local_cell_weights[stencil_diameter] = 2vgeo[n, _JcV, e_load]\n            vMI[2] = sgeo[_vMI, n, faces[2], eH + eV_up]\n\n            # Tranform the prognostic data to primitive data\n            prognostic_to_primitive!(\n                balance_law,\n                local_state_primitive[stencil_diameter],\n                local_state_prognostic[stencil_diameter],\n                local_state_auxiliary[stencil_diameter],\n            )\n\n            # Do the reconstruction! for this cell and compute the values at the\n            # bottom (1) and top (2) faces of element `eV_up`\n            if periodicstack ||\n               stencil_width < eV_up < nvertelem - stencil_width + 1\n                # If we are in the interior or periodic just use the\n                # reconstruction\n                rng1, rng2 = stencil_center .+ (-stencil_width, stencil_width)\n                rng = SUnitRange(rng1, rng2)\n                reconstruction!(\n                    local_state_face_primitive[1],\n                    local_state_face_primitive[2],\n                    local_state_primitive[rng],\n                    local_cell_weights[rng],\n                )\n            elseif eV_up <= stencil_width\n                # Bottom of the element stack requires reconstruct using a\n                # subset of the elements\n                # Values around stencil center that we need for this\n                # reconstruction\n                Base.Cartesian.@nif 4 w -> (eV_up == w) w -> begin\n                    rng1, rng2 = stencil_center .+ (1 - w, w - 1)\n                    rng = SUnitRange(rng1, rng2)\n                    reconstruction!(\n                        local_state_face_primitive[1],\n                        local_state_face_primitive[2],\n                        local_state_primitive[rng],\n                        local_cell_weights[rng],\n                    )\n                end w -> throw(BoundsError(local_state_primitive, w))\n            elseif eV_up >= nvertelem - stencil_width + 1\n                # Top of the element stack requires reconstruct using a\n                # subset of the elements\n                Base.Cartesian.@nif 4 w -> (w == (nvertelem - eV_up + 1)) w ->\n                    begin\n                        rng1, rng2 = stencil_center .+ (1 - w, w - 1)\n                        rng = SUnitRange(rng1, rng2)\n                        reconstruction!(\n                            local_state_face_primitive[1],\n                            local_state_face_primitive[2],\n                            local_state_primitive[rng],\n                            local_cell_weights[rng],\n                        )\n                    end w -> throw(BoundsError(local_state_primitive, w))\n            end\n\n            construct_face_auxiliary_state!(\n                balance_law,\n                local_state_face_auxiliary[1],\n                local_state_auxiliary[stencil_center],\n                -local_cell_weights[stencil_center],\n            )\n            construct_face_auxiliary_state!(\n                balance_law,\n                local_state_face_auxiliary[2],\n                local_state_auxiliary[stencil_center],\n                local_cell_weights[stencil_center],\n            )\n\n            # Transform reconstructed primitive values to prognostic\n            @unroll for k in 1:2\n                primitive_to_prognostic!(\n                    balance_law,\n                    local_state_face_prognostic[k],\n                    local_state_face_primitive[k],\n                    local_state_face_auxiliary[k],\n                )\n            end\n\n            # Compute the flux for the bottom face of the element we are\n            # considering\n            fill!(local_flux, -zero(FT))\n            numerical_flux_first_order!(\n                numerical_flux_first_order,\n                balance_law,\n                local_flux,\n                normal,\n                local_state_face_prognostic[1],\n                local_state_face_auxiliary[1],\n                local_state_face_prognostic_neighbor,\n                local_state_face_auxiliary_neighbor,\n                t,\n                face_direction,\n            )\n\n\n            numerical_flux_second_order!(\n                numerical_flux_second_order,\n                balance_law,\n                local_flux,\n                normal,\n                local_state_prognostic[stencil_center],\n                local_state_gradient_flux[stencil_center],\n                local_state_hyperdiffusive[stencil_center],\n                local_state_auxiliary[stencil_center],\n                local_state_prognostic[stencil_center - 1],\n                local_state_gradient_flux[stencil_center - 1],\n                local_state_hyperdiffusive[stencil_center - 1],\n                local_state_auxiliary[stencil_center - 1],\n                t,\n            )\n\n\n            if add_source\n                fill!(local_source, -zero(eltype(local_source)))\n                source_arr!(\n                    balance_law,\n                    local_source,\n                    local_state_prognostic[stencil_center - 1],\n                    local_state_gradient_flux[stencil_center - 1],\n                    local_state_auxiliary[stencil_center - 1],\n                    t,\n                    (VerticalDirection(),),\n                )\n\n\n                @unroll for s in 1:num_state_prognostic\n                    local_tendency[s] += local_source[s]\n                end\n            end\n\n\n            # Update the bottom element:\n            # numerical flux is computed with respect to the top element, so\n            # `+=` is used to reverse the flux\n            @unroll for s in 1:num_state_prognostic\n                local_tendency[s] += α * sM * vMI[1] * local_flux[s]\n\n                if increment\n                    tendency[n, s, eH + eV_dn] += local_tendency[s]\n                else\n                    if β != 0\n                        T = local_tendency[s] + β * tendency[n, s, eH + eV_dn]\n                    else\n                        T = local_tendency[s]\n                    end\n                    tendency[n, s, eH + eV_dn] = T\n                end\n\n                # Store contribution to the top element tendency\n                local_tendency[s] = -α * sM * vMI[2] * local_flux[s]\n            end\n\n            # Update top element of the stack\n            if eV_up == nvertelem\n                if add_source\n                    fill!(local_source, -zero(eltype(local_source)))\n                    source_arr!(\n                        balance_law,\n                        local_source,\n                        local_state_prognostic[stencil_center],\n                        local_state_gradient_flux[stencil_center],\n                        local_state_auxiliary[stencil_center],\n                        t,\n                        (VerticalDirection(),),\n                    )\n\n\n                    @unroll for s in 1:num_state_prognostic\n                        local_tendency[s] += local_source[s]\n                    end\n                end\n                # If periodic just add in the tendency that we just computed\n                if periodicstack\n                    @unroll for s in 1:num_state_prognostic\n                        if increment\n                            tendency[n, s, eH + eV_up] += local_tendency[s]\n                        else\n                            # This tendency has already been updated in the first element (eV_up=1)\n                            tendency[n, s, eH + eV_up] += local_tendency[s]\n                        end\n                    end\n                else\n                    # Load surface metrics for the face we will update\n                    # (top face of `eV_up`)\n                    bctag = elemtobndy[faces[2], eH + eV_up]\n                    sM = sgeo[_sM, n, faces[2], eH + eV_up]\n                    normal = SVector(\n                        sgeo[_n1, n, faces[2], eH + eV_up],\n                        sgeo[_n2, n, faces[2], eH + eV_up],\n                        sgeo[_n3, n, faces[2], eH + eV_up],\n                    )\n\n                    # Since we are at the last element to update, we can safely use\n                    # `stencil_center - 1` to store the ghost data\n\n                    fill!(local_flux, -zero(FT))\n\n                    # Fill ghost cell data\n                    # Use the top reconstruction (since handling top face)\n                    local_state_face_prognostic_neighbor .=\n                        local_state_face_prognostic[2]\n                    local_state_face_auxiliary_neighbor .=\n                        local_state_face_auxiliary[2]\n                    numerical_boundary_flux_first_order!(\n                        numerical_flux_first_order,\n                        bctag,\n                        balance_law,\n                        local_flux,\n                        normal,\n                        # Use the top reconstruction (since handling top face)\n                        # current state\n                        local_state_face_prognostic[2],\n                        local_state_face_auxiliary[2],\n                        # ghost state\n                        local_state_face_prognostic_neighbor,\n                        local_state_face_auxiliary_neighbor,\n                        t,\n                        face_direction,\n                        # use current cell states as bottom states\n                        local_state_face_prognostic[2],\n                        local_state_face_auxiliary[2],\n                    )\n\n\n                    # Fill / reset ghost cell data\n                    local_state_prognostic[stencil_center - 1] .=\n                        local_state_prognostic[stencil_center]\n                    local_state_gradient_flux[stencil_center - 1] .=\n                        local_state_gradient_flux[stencil_center]\n                    local_state_hyperdiffusive[stencil_center - 1] .=\n                        local_state_hyperdiffusive[stencil_center]\n                    local_state_auxiliary[stencil_center - 1] .=\n                        local_state_auxiliary[stencil_center]\n                    numerical_boundary_flux_second_order!(\n                        numerical_flux_second_order,\n                        bctag,\n                        balance_law,\n                        local_flux,\n                        normal,\n                        # current state\n                        local_state_prognostic[stencil_center],\n                        local_state_gradient_flux[stencil_center],\n                        local_state_hyperdiffusive[stencil_center],\n                        local_state_auxiliary[stencil_center],\n                        # ghost state\n                        local_state_prognostic[stencil_center - 1],\n                        local_state_gradient_flux[stencil_center - 1],\n                        local_state_hyperdiffusive[stencil_center - 1],\n                        local_state_auxiliary[stencil_center - 1],\n                        t,\n                        # use current cell states as bottom states\n                        local_state_prognostic[stencil_center],\n                        local_state_gradient_flux[stencil_center],\n                        local_state_auxiliary[stencil_center],\n                    )\n\n\n                    @unroll for s in 1:num_state_prognostic\n                        local_tendency[s] -= α * sM * vMI[2] * local_flux[s]\n                        if increment\n                            tendency[n, s, eH + eV_up] += local_tendency[s]\n                        else\n                            if β != 0\n                                T =\n                                    local_tendency[s] +\n                                    β * tendency[n, s, eH + eV_up]\n                            else\n                                T = local_tendency[s]\n                            end\n                            tendency[n, s, eH + eV_up] = T\n                        end\n                    end\n                end\n            end\n        end\n    end\nend\n\n@kernel function vert_fvm_interface_gradients!(\n    balance_law::BalanceLaw,\n    ::Val{info},\n    ::Val{nvertelem},\n    ::Val{periodicstack},\n    ::VerticalDirection,\n    state_prognostic,\n    state_gradient_flux,\n    state_auxiliary,\n    vgeo,\n    sgeo,\n    t,\n    elemtobndy,\n    elems,\n    increment,\n) where {info, nvertelem, periodicstack}\n    @uniform begin\n\n        dim = info.dim\n        FT = eltype(state_prognostic)\n        num_state_prognostic = number_states(balance_law, Prognostic())\n        ngradstate = number_states(balance_law, Gradient())\n        num_state_gradient_flux = number_states(balance_law, GradientFlux())\n        num_state_auxiliary = number_states(balance_law, Auxiliary())\n        nface = info.nface\n        Np = info.Np\n        faces = (nface - 1, nface)\n\n        # Storage for the prognostic state for e-1, e, e+1\n        local_state_prognostic =\n            ntuple(_ -> MArray{Tuple{num_state_prognostic}, FT}(undef), Val(3))\n\n        # Storage for the auxiliary state for e-1, e, e+1\n        local_state_auxiliary =\n            ntuple(_ -> MArray{Tuple{num_state_auxiliary}, FT}(undef), Val(3))\n\n        # Storage for the transform state for e-1, e, e+1\n        # e.g., the state we take the gradient of\n        l_grad_arg = ntuple(_ -> MArray{Tuple{ngradstate}, FT}(undef), Val(3))\n\n        # Storage for the contribution to the gradient from these faces\n        l_nG = MArray{Tuple{3, ngradstate}, FT}(undef)\n\n        l_nG_bc = MArray{Tuple{3, ngradstate}, FT}(undef)\n\n        # Storage for the state gradient flux locally\n        local_state_gradient_flux =\n            MArray{Tuple{num_state_gradient_flux}, FT}(undef)\n\n        local_state_prognostic_bottom1 =\n            MArray{Tuple{num_state_prognostic}, FT}(undef)\n        local_state_auxiliary_bottom1 =\n            MArray{Tuple{num_state_auxiliary}, FT}(undef)\n\n        # XXX: will revisit this later for FVM\n        fill!(local_state_prognostic_bottom1, NaN)\n        fill!(local_state_auxiliary_bottom1, NaN)\n    end\n\n    # Element index\n    eI = @index(Group, Linear)\n    # Index of a quadrature point on a face\n    n = @index(Local, Linear)\n\n    @inbounds begin\n        e = elems[eI]\n        eV = mod1(e, nvertelem)\n\n        # Figure out the element above and below e\n        e_dn, bc_dn = if eV > 1\n            e - 1, 0\n        elseif periodicstack\n            e + nvertelem - 1, 0\n        else\n            e, elemtobndy[faces[1], e]\n        end\n\n        e_up, bc_up = if eV < nvertelem\n            e + 1, 0\n        elseif periodicstack\n            e - nvertelem + 1, 0\n        else\n            e, elemtobndy[faces[2], e]\n        end\n\n        bctag = (bc_dn, bc_up)\n\n        els = (e_dn, e, e_up)\n\n        # Load the normal vectors and surface mass matrix on the faces\n        normal_vector = ntuple(\n            k -> SVector(\n                sgeo[_n1, n, faces[k], e],\n                sgeo[_n2, n, faces[k], e],\n                sgeo[_n3, n, faces[k], e],\n            ),\n            Val(2),\n        )\n        sM = ntuple(k -> sgeo[_sM, n, faces[k], e], Val(2))\n\n        # Volume mass same on both faces\n        vMI = sgeo[_vMI, n, faces[1], e]\n\n        # Get the mass matrix for each of the elements\n        M = ntuple(k -> vgeo[n, _M, els[k]], Val(3))\n\n        # Load prognostic data\n        @unroll for k in 1:3\n            @unroll for s in 1:num_state_prognostic\n                local_state_prognostic[k][s] = state_prognostic[n, s, els[k]]\n            end\n        end\n\n        # Load auxiliary data\n        @unroll for k in 1:3\n            @unroll for s in 1:num_state_auxiliary\n                local_state_auxiliary[k][s] = state_auxiliary[n, s, els[k]]\n            end\n        end\n\n        # Transform to the gradient argument (i.e., the values we take the\n        # gradient of)\n        @unroll for k in 1:3\n            fill!(l_grad_arg[k], -zero(eltype(l_grad_arg[k])))\n            compute_gradient_argument_arr!(\n                balance_law,\n                l_grad_arg[k],\n                local_state_prognostic[k],\n                local_state_auxiliary[k],\n                t,\n            )\n        end\n\n        # Compute the surface integral contribution from these two faces:\n        #   M⁻¹ Lᵀ Mf n̂ G*\n        # Since this is FVM we do not subtract the interior state\n        fill!(l_nG, -zero(eltype(l_nG)))\n        @unroll for f in 1:2\n            if bctag[f] == 0\n                @unroll for s in 1:ngradstate\n                    # Interpolate to the face (using the mass matrix for the\n                    # interpolation weights) -- \"the gradient numerical flux\"\n                    G =\n                        (\n                            M[f] * l_grad_arg[f + 1][s] +\n                            M[f + 1] * l_grad_arg[f][s]\n                        ) / (M[f] + M[f + 1])\n\n                    # Compute the surface integral for this component and face\n                    # multiplied by the normal to get the rotation to the\n                    # physical space\n                    @unroll for i in 1:3\n                        l_nG[i, s] += vMI * sM[f] * normal_vector[f][i] * G\n                    end\n                end\n            else\n                # Computes G* incorporating boundary conditions\n                numerical_boundary_flux_gradient!(\n                    CentralNumericalFluxGradient(),\n                    bctag[f],\n                    balance_law,\n                    l_nG_bc,\n                    normal_vector[f],\n                    l_grad_arg[2],\n                    local_state_prognostic[2],\n                    local_state_auxiliary[2],\n                    l_grad_arg[2f - 1],\n                    local_state_prognostic[2f - 1],\n                    local_state_auxiliary[2f - 1],\n                    t,\n                    local_state_prognostic_bottom1,\n                    local_state_auxiliary_bottom1,\n                )\n\n                @unroll for s in 1:ngradstate\n                    @unroll for i in 1:3\n                        l_nG[i, s] += vMI * sM[f] * l_nG_bc[i, s]\n                    end\n                end\n            end\n        end\n\n        # Applies linear transformation of gradients to the diffusive variables\n        # for storage\n        compute_gradient_flux_arr!(\n            balance_law,\n            local_state_gradient_flux,\n            l_nG,\n            local_state_prognostic[2],\n            local_state_auxiliary[2],\n            t,\n        )\n\n        # This is the surface integral evaluated discretely\n        # M^(-1) Mf G*\n        @unroll for s in 1:num_state_gradient_flux\n            if increment\n                state_gradient_flux[n, s, e] += local_state_gradient_flux[s]\n            else\n                state_gradient_flux[n, s, e] = local_state_gradient_flux[s]\n            end\n        end\n    end\nend\n\n@kernel function vert_fvm_auxiliary_field_gradient!(\n    balance_law::BalanceLaw,\n    ::Val{info},\n    ∇state,\n    state,\n    vgeo,\n    sgeo,\n    vmap⁻,\n    vmap⁺,\n    elemtobndy,\n    ::Val{I},\n    ::Val{O},\n    increment,\n) where {info, I, O}\n    @uniform begin\n        FT = eltype(state)\n        ngradstate = length(I)\n\n        dim = info.dim\n        nface = info.nface\n        Np = info.Np\n\n        # We only have the vertical faces\n        faces = (nface - 1):nface\n\n        local_state_bottom = fill!(MArray{Tuple{ngradstate}, FT}(undef), NaN)\n        local_state = fill!(MArray{Tuple{ngradstate}, FT}(undef), NaN)\n        local_state_top = fill!(MArray{Tuple{ngradstate}, FT}(undef), NaN)\n    end\n\n    e = @index(Group, Linear)\n    n = @index(Local, Linear)\n\n    @inbounds begin\n        face_bottom = faces[1]\n        face_top = faces[2]\n\n        bctag_bottom = elemtobndy[face_bottom, e]\n        bctag_top = elemtobndy[face_top, e]\n\n        # TODO: exploit structured grid\n        id = vmap⁻[n, face_bottom, e]\n        id_bottom = vmap⁺[n, face_bottom, e]\n        id_top = vmap⁺[n, face_top, e]\n\n        e_bottom = ((id_bottom - 1) ÷ Np) + 1\n        e_top = ((id_top - 1) ÷ Np) + 1\n\n        vid = ((id - 1) % Np) + 1\n        vid_bottom = ((id_bottom - 1) % Np) + 1\n        vid_top = ((id_top - 1) % Np) + 1\n\n        if bctag_bottom != 0\n            e_bottom = e\n            vid_bottom = vid\n        end\n        if bctag_top != 0\n            e_top = e\n            vid_top = vid\n        end\n\n        dzh_bottom = vgeo[vid_bottom, _JcV, e_bottom]\n        dzh = vgeo[vid, _JcV, e]\n        dzh_top = vgeo[vid_top, _JcV, e_top]\n\n        if dim == 2\n            ξvx1 = vgeo[vid, _ξ2x1, e]\n            ξvx2 = vgeo[vid, _ξ2x2, e]\n            ξvx3 = vgeo[vid, _ξ2x3, e]\n        elseif dim == 3\n            ξvx1 = vgeo[vid, _ξ3x1, e]\n            ξvx2 = vgeo[vid, _ξ3x2, e]\n            ξvx3 = vgeo[vid, _ξ3x3, e]\n        end\n\n        @unroll for s in 1:ngradstate\n            local_state_bottom[s] = state[vid_bottom, I[s], e_bottom]\n            local_state_top[s] = state[vid_top, I[s], e_top]\n        end\n\n        # only need the middle state near the boundaries\n        if bctag_bottom != 0 || bctag_top != 0\n            @unroll for s in 1:ngradstate\n                local_state[s] = state[vid, I[s], e]\n            end\n        end\n\n        # extrapolation at the boundaries equivalent to one-sided differencing\n        if bctag_bottom != 0\n            @unroll for s in 1:ngradstate\n                local_state_bottom[s] = 2 * local_state[s] - local_state_top[s]\n            end\n            dzh_bottom = dzh_top\n        end\n\n        if bctag_top != 0\n            @unroll for s in 1:ngradstate\n                local_state_top[s] = 2 * local_state[s] - local_state_bottom[s]\n            end\n            dzh_top = dzh_bottom\n        end\n\n        @unroll for s in 1:ngradstate\n            dz = dzh_top + 2dzh + dzh_bottom\n            ∇s_v = (local_state_top[s] - local_state_bottom[s]) / dz\n            # rotate back to Cartesian\n            if increment\n                ∇state[vid, O[3 * (s - 1) + 1], e] += ξvx1 * dzh * ∇s_v\n                ∇state[vid, O[3 * (s - 1) + 2], e] += ξvx2 * dzh * ∇s_v\n                ∇state[vid, O[3 * (s - 1) + 3], e] += ξvx3 * dzh * ∇s_v\n            else\n                ∇state[vid, O[3 * (s - 1) + 1], e] = ξvx1 * dzh * ∇s_v\n                ∇state[vid, O[3 * (s - 1) + 2], e] = ξvx2 * dzh * ∇s_v\n                ∇state[vid, O[3 * (s - 1) + 3], e] = ξvx3 * dzh * ∇s_v\n            end\n        end\n    end\nend\n\n\"\"\"\n    kernel_fvm_balance!(f!, balance_law::BalanceLaw, ::Val{nvertelem}, state_auxiliary, vgeo, elems)\n\n    pᵢ(ρᵢ, Tᵢ) =  pᵢ₋₁ - g ρᵢ Δzᵢ/2 - g ρᵢ₋₁ Δzᵢ₋₁/2\n    for i = 2:nvertelem\n        pᵢ(ρᵢ, Tᵢ) =  pᵢ₋₁ - g ρᵢ Δzᵢ/2 - g ρᵢ₋₁ Δzᵢ₋₁/2\n    end\n\n - `f!`: update function\n - `balance_law`: atmosphere model\n - `state_auxiliary`: auxiliary variables, ρ is updated \n - `vgeo`: 2*vgeo[ijk ,_JcV , e] is Δz\n - `elems`: horizontal element list\n\"\"\"\n@kernel function kernel_fvm_balance!(\n    f!,\n    balance_law::BalanceLaw,\n    ::Val{nvertelem},\n    state_auxiliary,\n    vgeo,\n    elems,\n) where {nvertelem}\n    @uniform begin\n        FT = eltype(state_auxiliary)\n        num_state_auxiliary = number_states(balance_law, Auxiliary())\n        # ρᵢ pᵢ\n        local_state_auxiliary_top =\n            MArray{Tuple{num_state_auxiliary}, FT}(undef)\n        # ρᵢ₋₁ pᵢ₋₁\n        local_state_auxiliary = MArray{Tuple{num_state_auxiliary}, FT}(undef)\n\n        Δz = MArray{Tuple{2}, FT}(undef)\n    end\n\n    _eH = @index(Group, Linear)\n    n = @index(Local, Linear)\n\n    @inbounds begin\n        eH = elems[_eH]\n\n        # handle bottom element\n        eV = 1\n        e = eV + (eH - 1) * nvertelem\n        @unroll for s in 1:num_state_auxiliary\n            local_state_auxiliary[s] = state_auxiliary[n, s, e]\n        end\n        # Δzᵢ₋₁\n        Δz[1] = 2 * vgeo[n, _JcV, e]\n\n        # Loop up the stack of elements\n        for eV in 2:nvertelem\n            e = eV + (eH - 1) * nvertelem\n\n            @unroll for s in 1:num_state_auxiliary\n                local_state_auxiliary_top[s] = state_auxiliary[n, s, e]\n            end\n            # Δzᵢ\n            Δz[2] = 2 * vgeo[n, _JcV, e]\n\n            f!(\n                balance_law,\n                # ρᵢ pᵢ\n                Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                    local_state_auxiliary_top,\n                ),\n                # ρᵢ₋₁ pᵢ₋₁\n                Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                    local_state_auxiliary,\n                ),\n                Δz,\n            )\n\n            # update to the global array\n            @unroll for s in 1:num_state_auxiliary\n                state_auxiliary[n, s, e] = local_state_auxiliary_top[s]\n            end\n\n            # (ρᵢ pᵢ Δzᵢ) -> (ρᵢ₋₁ pᵢ₋₁ Δzᵢ₋₁)\n            @unroll for s in 1:num_state_auxiliary\n                local_state_auxiliary[s] = local_state_auxiliary_top[s]\n            end\n            Δz[1] = Δz[2]\n        end\n    end\nend\n"
  },
  {
    "path": "src/Numerics/DGMethods/DGMethods.jl",
    "content": "module DGMethods\n\nusing MPI\nusing StaticArrays\nusing DocStringExtensions\nusing KernelAbstractions\nusing KernelAbstractions.Extras: @unroll\nusing ..MPIStateArrays\nusing ..Mesh.Grids\nusing ..Mesh.Topologies\nusing ..Mesh.Filters\nusing ..VariableTemplates\nusing ..Courant\nusing ..BalanceLaws:\n    BalanceLaw,\n    AbstractStateType,\n    Prognostic,\n    Auxiliary,\n    Gradient,\n    GradientFlux,\n    GradientLaplacian,\n    Hyperdiffusive,\n    UpwardIntegrals,\n    DownwardIntegrals,\n    vars_state,\n    number_states\n\nimport ..BalanceLaws:\n    BalanceLaw,\n    init_state_prognostic_arr!,\n    init_state_auxiliary!,\n    flux_first_order_arr!,\n    flux_second_order_arr!,\n    compute_gradient_flux_arr!,\n    compute_gradient_argument_arr!,\n    source_arr!,\n    transform_post_gradient_laplacian_arr!,\n    wavespeed,\n    boundary_conditions,\n    boundary_state!,\n    update_auxiliary_state!,\n    update_auxiliary_state_gradient!,\n    nodal_update_auxiliary_state!,\n    nodal_init_state_auxiliary!,\n    integral_load_auxiliary_state_arr!,\n    integral_set_auxiliary_state_arr!,\n    indefinite_stack_integral!,\n    reverse_indefinite_stack_integral!,\n    reverse_integral_load_auxiliary_state_arr!,\n    reverse_integral_set_auxiliary_state_arr!\n\nexport DGModel,\n    DGFVModel,\n    ESDGModel,\n    SpaceDiscretization,\n    init_ode_state,\n    restart_ode_state,\n    restart_auxiliary_state,\n    basic_grid_info,\n    init_state_auxiliary!,\n    auxiliary_field_gradient!,\n    courant\n\ninclude(\"custom_filter.jl\")\ninclude(\"FVReconstructions.jl\")\ninclude(\"NumericalFluxes.jl\")\ninclude(\"SpaceDiscretization.jl\")\ninclude(\"DGModel.jl\")\ninclude(\"DGFVModel.jl\")\ninclude(\"ESDGModel.jl\")\ninclude(\"create_states.jl\")\n\n\"\"\"\n    calculate_dt(dg, model, Q, Courant_number, direction, t)\n\nFor a given model, compute a time step satisying the nondiffusive Courant number\n`Courant_number`\n\"\"\"\nfunction calculate_dt(dg, model, Q, Courant_number, t, direction)\n    Δt = one(eltype(Q))\n    CFL = courant(nondiffusive_courant, dg, model, Q, Δt, t, direction)\n    return Courant_number / CFL\nend\n\nend\n"
  },
  {
    "path": "src/Numerics/DGMethods/DGModel.jl",
    "content": "include(\"DGModel_kernels.jl\")\n\nstruct DGModel{BL, G, NFND, NFD, GNF, AS, DS, HDS, D, DD, MD, GF, TF} <:\n       SpaceDiscretization\n    balance_law::BL\n    grid::G\n    numerical_flux_first_order::NFND\n    numerical_flux_second_order::NFD\n    numerical_flux_gradient::GNF\n    state_auxiliary::AS\n    state_gradient_flux::DS\n    states_higher_order::HDS\n    direction::D\n    diffusion_direction::DD\n    modeldata::MD\n    gradient_filter::GF\n    tendency_filter::TF\n    check_for_crashes::Bool\nend\n\n\nfunction DGModel(\n    balance_law,\n    grid,\n    numerical_flux_first_order,\n    numerical_flux_second_order,\n    numerical_flux_gradient;\n    fill_nan = false,\n    check_for_crashes = false,\n    state_auxiliary = create_state(\n        balance_law,\n        grid,\n        Auxiliary(),\n        fill_nan = fill_nan,\n    ),\n    state_gradient_flux = create_state(balance_law, grid, GradientFlux()),\n    states_higher_order = (\n        create_state(balance_law, grid, GradientLaplacian()),\n        create_state(balance_law, grid, Hyperdiffusive()),\n    ),\n    direction = EveryDirection(),\n    diffusion_direction = direction,\n    modeldata = nothing,\n    gradient_filter = nothing,\n    tendency_filter = nothing,\n)\n    state_auxiliary =\n        init_state(state_auxiliary, balance_law, grid, direction, Auxiliary())\n    DGModel(\n        balance_law,\n        grid,\n        numerical_flux_first_order,\n        numerical_flux_second_order,\n        numerical_flux_gradient,\n        state_auxiliary,\n        state_gradient_flux,\n        states_higher_order,\n        direction,\n        diffusion_direction,\n        modeldata,\n        gradient_filter,\n        tendency_filter,\n        check_for_crashes,\n    )\nend\n\n# Include the remainder model for composing DG models and balance laws\ninclude(\"remainder.jl\")\n\n\n\"\"\"\n    (dg::DGModel)(tendency, state_prognostic, _, t, α, β)\n\nUses spectral element discontinuous Galerkin in all direction to compute the\ntendency.\n\nComputes the tendency terms compatible with `IncrementODEProblem`\n\n    tendency .= α .* dQdt(state_prognostic, p, t) .+ β .* tendency\n\nThe 4-argument form will just compute\n\n    tendency .= dQdt(state_prognostic, p, t)\n\"\"\"\nfunction (dg::DGModel)(tendency, state_prognostic, _, t, α, β)\n\n    device = array_device(state_prognostic)\n    Qhypervisc_grad, Qhypervisc_div = dg.states_higher_order\n\n    FT = eltype(state_prognostic)\n    num_state_prognostic = number_states(dg.balance_law, Prognostic())\n    num_state_gradient_flux = number_states(dg.balance_law, GradientFlux())\n    nhyperviscstate = number_states(dg.balance_law, Hyperdiffusive())\n    num_state_tendency = size(tendency, 2)\n\n    @assert num_state_prognostic ≤ num_state_tendency\n\n    if num_state_prognostic < num_state_tendency && β != 1\n        # If we don't operate on the full state, then we need to scale here instead of volume_tendency!\n        tendency .*= β\n        β = β != 0 # if β==0 then we can avoid the memory load in volume_tendency!\n    end\n\n    communicate =\n        !(\n            isstacked(dg.grid.topology) &&\n            typeof(dg.direction) <: VerticalDirection\n        )\n\n    update_auxiliary_state!(\n        dg,\n        dg.balance_law,\n        state_prognostic,\n        t,\n        dg.grid.topology.realelems,\n    )\n\n    exchange_state_prognostic = NoneEvent()\n    exchange_state_gradient_flux = NoneEvent()\n    exchange_Qhypervisc_grad = NoneEvent()\n    exchange_Qhypervisc_div = NoneEvent()\n\n    comp_stream = Event(device)\n\n    if communicate\n        exchange_state_prognostic = MPIStateArrays.begin_ghost_exchange!(\n            state_prognostic;\n            dependencies = comp_stream,\n        )\n    end\n\n    if num_state_gradient_flux > 0 || nhyperviscstate > 0\n        ########################\n        # Gradient Computation #\n        ########################\n\n        comp_stream = launch_volume_gradients!(\n            dg,\n            state_prognostic,\n            t;\n            dependencies = comp_stream,\n        )\n\n        comp_stream = launch_interface_gradients!(\n            dg,\n            state_prognostic,\n            t;\n            surface = :interior,\n            dependencies = comp_stream,\n        )\n\n        if communicate\n            exchange_state_prognostic = MPIStateArrays.end_ghost_exchange!(\n                state_prognostic;\n                dependencies = exchange_state_prognostic,\n                check_for_crashes = dg.check_for_crashes,\n            )\n\n            # Update_aux may start asynchronous work on the compute device and\n            # we synchronize those here through a device event.\n            checked_wait(\n                device,\n                exchange_state_prognostic,\n                nothing,\n                dg.check_for_crashes,\n            )\n            update_auxiliary_state!(\n                dg,\n                dg.balance_law,\n                state_prognostic,\n                t,\n                dg.grid.topology.ghostelems,\n            )\n            exchange_state_prognostic = Event(device)\n        end\n\n        comp_stream = launch_interface_gradients!(\n            dg,\n            state_prognostic,\n            t;\n            surface = :exterior,\n            dependencies = (comp_stream, exchange_state_prognostic),\n        )\n\n        if dg.gradient_filter !== nothing\n            comp_stream = Filters.apply_async!(\n                dg.state_gradient_flux,\n                1:num_state_gradient_flux,\n                dg.grid,\n                dg.gradient_filter;\n                dependencies = comp_stream,\n            )\n        end\n\n        if communicate\n            if num_state_gradient_flux > 0\n                exchange_state_gradient_flux =\n                    MPIStateArrays.begin_ghost_exchange!(\n                        dg.state_gradient_flux,\n                        dependencies = comp_stream,\n                    )\n            end\n            if nhyperviscstate > 0\n                exchange_Qhypervisc_grad = MPIStateArrays.begin_ghost_exchange!(\n                    Qhypervisc_grad,\n                    dependencies = comp_stream,\n                )\n            end\n        end\n\n        if num_state_gradient_flux > 0\n            # Update_aux_diffusive may start asynchronous work on the compute device\n            # and we synchronize those here through a device event.\n            checked_wait(device, comp_stream, nothing, dg.check_for_crashes)\n            update_auxiliary_state_gradient!(\n                dg,\n                dg.balance_law,\n                state_prognostic,\n                t,\n                dg.grid.topology.realelems,\n            )\n            comp_stream = Event(device)\n        end\n    end\n\n    if nhyperviscstate > 0\n        #########################\n        # Laplacian Computation #\n        #########################\n\n        comp_stream = launch_volume_divergence_of_gradients!(\n            dg,\n            state_prognostic,\n            t;\n            dependencies = comp_stream,\n        )\n\n        comp_stream = launch_interface_divergence_of_gradients!(\n            dg,\n            state_prognostic,\n            t;\n            surface = :interior,\n            dependencies = comp_stream,\n        )\n\n        if communicate\n            exchange_Qhypervisc_grad = MPIStateArrays.end_ghost_exchange!(\n                Qhypervisc_grad,\n                dependencies = exchange_Qhypervisc_grad,\n                check_for_crashes = dg.check_for_crashes,\n            )\n        end\n\n        comp_stream = launch_interface_divergence_of_gradients!(\n            dg,\n            state_prognostic,\n            t;\n            surface = :exterior,\n            dependencies = (comp_stream, exchange_Qhypervisc_grad),\n        )\n\n        if communicate\n            exchange_Qhypervisc_div = MPIStateArrays.begin_ghost_exchange!(\n                Qhypervisc_div,\n                dependencies = comp_stream,\n            )\n        end\n\n        ####################################\n        # Hyperdiffusive terms computation #\n        ####################################\n\n        comp_stream = launch_volume_gradients_of_laplacians!(\n            dg,\n            state_prognostic,\n            t,\n            dependencies = (comp_stream,),\n        )\n\n        comp_stream = launch_interface_gradients_of_laplacians!(\n            dg,\n            state_prognostic,\n            t;\n            surface = :interior,\n            dependencies = (comp_stream,),\n        )\n\n        if communicate\n            exchange_Qhypervisc_div = MPIStateArrays.end_ghost_exchange!(\n                Qhypervisc_div,\n                dependencies = exchange_Qhypervisc_div,\n                check_for_crashes = dg.check_for_crashes,\n            )\n        end\n\n        comp_stream = launch_interface_gradients_of_laplacians!(\n            dg,\n            state_prognostic,\n            t;\n            surface = :exterior,\n            dependencies = (comp_stream, exchange_Qhypervisc_div),\n        )\n\n        if communicate\n            exchange_Qhypervisc_grad = MPIStateArrays.begin_ghost_exchange!(\n                Qhypervisc_grad,\n                dependencies = comp_stream,\n            )\n        end\n    end\n\n\n    ###################\n    # RHS Computation #\n    ###################\n    comp_stream = launch_volume_tendency!(\n        dg,\n        tendency,\n        state_prognostic,\n        t,\n        α,\n        β;\n        dependencies = (comp_stream,),\n    )\n\n    comp_stream = launch_interface_tendency!(\n        dg,\n        tendency,\n        state_prognostic,\n        t,\n        α,\n        β;\n        surface = :interior,\n        dependencies = (comp_stream,),\n    )\n\n    if communicate\n        if num_state_gradient_flux > 0 || nhyperviscstate > 0\n            if num_state_gradient_flux > 0\n                exchange_state_gradient_flux =\n                    MPIStateArrays.end_ghost_exchange!(\n                        dg.state_gradient_flux;\n                        dependencies = exchange_state_gradient_flux,\n                        check_for_crashes = dg.check_for_crashes,\n                    )\n\n                # Update_aux_diffusive may start asynchronous work on the\n                # compute device and we synchronize those here through a device\n                # event.\n                checked_wait(\n                    device,\n                    exchange_state_gradient_flux,\n                    nothing,\n                    dg.check_for_crashes,\n                )\n                update_auxiliary_state_gradient!(\n                    dg,\n                    dg.balance_law,\n                    state_prognostic,\n                    t,\n                    dg.grid.topology.ghostelems,\n                )\n                exchange_state_gradient_flux = Event(device)\n            end\n            if nhyperviscstate > 0\n                exchange_Qhypervisc_grad = MPIStateArrays.end_ghost_exchange!(\n                    Qhypervisc_grad;\n                    dependencies = exchange_Qhypervisc_grad,\n                    check_for_crashes = dg.check_for_crashes,\n                )\n            end\n        else\n            exchange_state_prognostic = MPIStateArrays.end_ghost_exchange!(\n                state_prognostic;\n                dependencies = exchange_state_prognostic,\n                check_for_crashes = dg.check_for_crashes,\n            )\n\n            # Update_aux may start asynchronous work on the compute device and\n            # we synchronize those here through a device event.\n            checked_wait(\n                device,\n                exchange_state_prognostic,\n                nothing,\n                dg.check_for_crashes,\n            )\n            update_auxiliary_state!(\n                dg,\n                dg.balance_law,\n                state_prognostic,\n                t,\n                dg.grid.topology.ghostelems,\n            )\n            exchange_state_prognostic = Event(device)\n        end\n    end\n\n    comp_stream = launch_interface_tendency!(\n        dg,\n        tendency,\n        state_prognostic,\n        t,\n        α,\n        β;\n        surface = :exterior,\n        dependencies = (\n            comp_stream,\n            exchange_state_prognostic,\n            exchange_state_gradient_flux,\n            exchange_Qhypervisc_grad,\n        ),\n    )\n\n    # The synchronization here through a device event prevents CuArray based and\n    # other default stream kernels from launching before the work scheduled in\n    # this function is finished.\n    if dg.tendency_filter !== nothing\n        comp_stream = Filters.apply_async!(\n            tendency,\n            1:num_state_tendency,\n            dg.grid,\n            dg.tendency_filter;\n            dependencies = comp_stream,\n        )\n    end\n    checked_wait(device, comp_stream, nothing, dg.check_for_crashes)\nend\n\nfunction restart_ode_state(dg::DGModel, state_data; init_on_cpu = false)\n    bl = dg.balance_law\n    grid = dg.grid\n\n    state = create_state(bl, grid, Prognostic())\n    state .= state_data\n\n    device = arraytype(dg.grid) <: Array ? CPU() : CUDADevice()\n    event = Event(device)\n    event = MPIStateArrays.begin_ghost_exchange!(state; dependencies = event)\n    event = MPIStateArrays.end_ghost_exchange!(state; dependencies = event)\n    wait(device, event)\n\n    return state\nend\n\nfunction indefinite_stack_integral!(\n    dg::DGModel,\n    m::BalanceLaw,\n    state_prognostic::MPIStateArray,\n    state_auxiliary::MPIStateArray,\n    t::Real,\n    elems::UnitRange = dg.grid.topology.elems,\n)\n\n    device = array_device(state_prognostic)\n\n    grid = dg.grid\n    topology = grid.topology\n\n    dim = dimensionality(grid)\n    N = polynomialorders(grid)\n    Nq = N .+ 1\n    Nqj = dim == 2 ? 1 : Nq[2]\n\n    FT = eltype(state_prognostic)\n\n    # Compute integrals\n    nelem = length(elems)\n    nvertelem = topology.stacksize\n    horzelems = fld1(first(elems), nvertelem):fld1(last(elems), nvertelem)\n\n    event = Event(device)\n    event = kernel_indefinite_stack_integral!(device, (Nq[1], Nqj))(\n        m,\n        Val(dim),\n        Val(N),\n        Val(nvertelem),\n        state_prognostic.data,\n        state_auxiliary.data,\n        grid.vgeo,\n        # Only need the vertical Imat since this kernel is vertically oriented\n        grid.Imat[dim],\n        horzelems;\n        ndrange = (length(horzelems) * Nq[1], Nqj),\n        dependencies = (event,),\n    )\n    checked_wait(device, event, nothing, dg.check_for_crashes)\nend\n\nfunction reverse_indefinite_stack_integral!(\n    dg::DGModel,\n    m::BalanceLaw,\n    state_prognostic::MPIStateArray,\n    state_auxiliary::MPIStateArray,\n    t::Real,\n    elems::UnitRange = dg.grid.topology.elems,\n)\n\n    device = array_device(state_auxiliary)\n\n    grid = dg.grid\n    topology = grid.topology\n\n    dim = dimensionality(grid)\n    N = polynomialorders(grid)\n    Nq = N .+ 1\n    Nqj = dim == 2 ? 1 : Nq[2]\n\n    FT = eltype(state_auxiliary)\n\n    # Compute integrals\n    nelem = length(elems)\n    nvertelem = topology.stacksize\n    horzelems = fld1(first(elems), nvertelem):fld1(last(elems), nvertelem)\n\n    event = Event(device)\n    event = kernel_reverse_indefinite_stack_integral!(device, (Nq[1], Nqj))(\n        m,\n        Val(dim),\n        Val(N),\n        Val(nvertelem),\n        state_prognostic.data,\n        state_auxiliary.data,\n        horzelems;\n        ndrange = (length(horzelems) * Nq[1], Nqj),\n        dependencies = (event,),\n    )\n    checked_wait(device, event, nothing, dg.check_for_crashes)\nend\n"
  },
  {
    "path": "src/Numerics/DGMethods/DGModel_kernels.jl",
    "content": "using .NumericalFluxes:\n    numerical_flux_gradient!,\n    numerical_flux_first_order!,\n    numerical_flux_second_order!,\n    numerical_flux_divergence!,\n    numerical_flux_higher_order!,\n    numerical_boundary_flux_gradient!,\n    numerical_boundary_flux_first_order!,\n    numerical_boundary_flux_second_order!,\n    numerical_boundary_flux_divergence!,\n    numerical_boundary_flux_higher_order!,\n    CentralNumericalFluxGradient\n\nusing ..Mesh.Geometry\n\n# {{{ FIXME: remove this after we've figure out how to pass through to kernel\nconst _ξ1x1, _ξ2x1, _ξ3x1 = Grids._ξ1x1, Grids._ξ2x1, Grids._ξ3x1\nconst _ξ1x2, _ξ2x2, _ξ3x2 = Grids._ξ1x2, Grids._ξ2x2, Grids._ξ3x2\nconst _ξ1x3, _ξ2x3, _ξ3x3 = Grids._ξ1x3, Grids._ξ2x3, Grids._ξ3x3\nconst _M, _MI = Grids._M, Grids._MI\nconst _x1, _x2, _x3 = Grids._x1, Grids._x2, Grids._x3\nconst _JcV = Grids._JcV\n\nconst _n1, _n2, _n3 = Grids._n1, Grids._n2, Grids._n3\nconst _sM, _vMI = Grids._sM, Grids._vMI\n# }}}\n\n\"\"\"\n    function volume_tendency!(\n        balance_law::BalanceLaw,\n        ::Val{dim},\n        ::Val{polyorder},\n        model_direction,\n        direction,\n        tendency,\n        state_prognostic,\n        state_gradient_flux,\n        Qhypervisc_grad,\n        state_auxiliary,\n        vgeo,\n        t,\n        ω,\n        D,\n        elems,\n        α,\n        β,\n        add_source = false,\n    )\n\nCompute kernel for evaluating the volume tendencies for the\nDG form:\n\n∫ₑ ψ⋅ ∂q/∂t dx - ∫ₑ ∇ψ⋅(Fⁱⁿᵛ + Fᵛⁱˢᶜ) dx + ∮ₑ n̂ ψ⋅(Fⁱⁿᵛ* + Fᵛⁱˢᶜ*) dS,\n\nor equivalently in matrix form:\n\ndQ/dt = M⁻¹(MS + DᵀM(Fⁱⁿᵛ + Fᵛⁱˢᶜ) + ∑ᶠ LᵀMf(Fⁱⁿᵛ* + Fᵛⁱˢᶜ*)).\n\nThis kernel computes the volume terms: M⁻¹(MS + DᵀM(Fⁱⁿᵛ + Fᵛⁱˢᶜ)),\nwhere M is the mass matrix and D is the differentiation matrix,\nS is the source, and Fⁱⁿᵛ, Fᵛⁱˢᶜ are the inviscid and viscous\nfluxes, respectively.\n\"\"\"\n@kernel function volume_tendency!(\n    balance_law::BalanceLaw,\n    ::Val{info},\n    model_direction,\n    direction,\n    tendency,\n    state_prognostic,\n    state_gradient_flux,\n    Qhypervisc_grad,\n    state_auxiliary,\n    vgeo,\n    t,\n    ω,\n    D,\n    elems,\n    α,\n    β,\n    add_source = false,\n) where {info}\n    @uniform begin\n        dim = info.dim\n        FT = eltype(state_prognostic)\n        num_state_prognostic = number_states(balance_law, Prognostic())\n        num_state_gradient_flux = number_states(balance_law, GradientFlux())\n        num_state_auxiliary = number_states(balance_law, Auxiliary())\n\n        ngradlapstate = number_states(balance_law, GradientLaplacian())\n        nhyperviscstate = number_states(balance_law, Hyperdiffusive())\n\n        @inbounds Nq1 = info.Nq[1]\n        @inbounds Nq2 = info.Nq[2]\n        Nq3 = info.Nqk\n\n        local_source = MArray{Tuple{num_state_prognostic}, FT}(undef)\n        local_state_prognostic = MArray{Tuple{num_state_prognostic}, FT}(undef)\n        local_state_gradient_flux =\n            MArray{Tuple{num_state_gradient_flux}, FT}(undef)\n        local_state_hyperdiffusion = MArray{Tuple{nhyperviscstate}, FT}(undef)\n        local_state_auxiliary = MArray{Tuple{num_state_auxiliary}, FT}(undef)\n        local_flux = MArray{Tuple{3, num_state_prognostic}, FT}(undef)\n        local_flux_3 = MArray{Tuple{num_state_prognostic}, FT}(undef)\n    end\n\n    # Arrays for F, and the differentiation matrix D\n    shared_flux = @localmem FT (2, Nq1, Nq2, num_state_prognostic)\n\n    # Storage for tendency and mass inverse M⁻¹\n    local_tendency = @private FT (Nq3, num_state_prognostic)\n    local_MI = @private FT (Nq3,)\n\n    # Grab the index associated with the current element `e` and the\n    # horizontal quadrature indices `i` (in the ξ1-direction),\n    # `j` (in the ξ2-direction) [directions on the reference element].\n    # Parallelize over elements, then over columns\n    e = @index(Group, Linear)\n    i, j = @index(Local, NTuple)\n\n    @inbounds begin\n        @unroll for k in 1:Nq3\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            # initialize local tendency\n            @unroll for s in 1:num_state_prognostic\n                local_tendency[k, s] = zero(FT)\n            end\n            # read in mass matrix inverse for element `e`\n            local_MI[k] = vgeo[ijk, _MI, e]\n        end\n\n        @unroll for k in 1:Nq3\n            @synchronize\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n            M = vgeo[ijk, _M, e]\n\n            # Extract Jacobian terms ∂ξᵢ/∂xⱼ\n            ξ1x1 = vgeo[ijk, _ξ1x1, e]\n            ξ1x2 = vgeo[ijk, _ξ1x2, e]\n            ξ1x3 = vgeo[ijk, _ξ1x3, e]\n            if dim == 3 || (dim == 2 && direction isa EveryDirection)\n                ξ2x1 = vgeo[ijk, _ξ2x1, e]\n                ξ2x2 = vgeo[ijk, _ξ2x2, e]\n                ξ2x3 = vgeo[ijk, _ξ2x3, e]\n            end\n            if dim == 3 && direction isa EveryDirection\n                ξ3x1 = vgeo[ijk, _ξ3x1, e]\n                ξ3x2 = vgeo[ijk, _ξ3x2, e]\n                ξ3x3 = vgeo[ijk, _ξ3x3, e]\n            end\n\n            # Read fields into registers (hopefully)\n            @unroll for s in 1:num_state_prognostic\n                local_state_prognostic[s] = state_prognostic[ijk, s, e]\n            end\n\n            @unroll for s in 1:num_state_auxiliary\n                local_state_auxiliary[s] = state_auxiliary[ijk, s, e]\n            end\n\n            @unroll for s in 1:num_state_gradient_flux\n                local_state_gradient_flux[s] = state_gradient_flux[ijk, s, e]\n            end\n\n            @unroll for s in 1:nhyperviscstate\n                local_state_hyperdiffusion[s] = Qhypervisc_grad[ijk, s, e]\n            end\n\n            # Computes the local inviscid fluxes Fⁱⁿᵛ\n            fill!(local_flux, -zero(eltype(local_flux)))\n            flux_first_order_arr!(\n                balance_law,\n                local_flux,\n                local_state_prognostic,\n                local_state_auxiliary,\n                t,\n                (model_direction,),\n            )\n\n            @unroll for s in 1:num_state_prognostic\n                shared_flux[1, i, j, s] = local_flux[1, s]\n                shared_flux[2, i, j, s] = local_flux[2, s]\n                local_flux_3[s] = local_flux[3, s]\n            end\n\n            # Computes the local viscous fluxes Fᵛⁱˢᶜ\n            fill!(local_flux, -zero(eltype(local_flux)))\n            flux_second_order_arr!(\n                balance_law,\n                local_flux,\n                local_state_prognostic,\n                local_state_gradient_flux,\n                local_state_hyperdiffusion,\n                local_state_auxiliary,\n                t,\n            )\n\n            @unroll for s in 1:num_state_prognostic\n                shared_flux[1, i, j, s] += local_flux[1, s]\n                shared_flux[2, i, j, s] += local_flux[2, s]\n                local_flux_3[s] += local_flux[3, s]\n            end\n\n            # Build \"inside metrics\" flux\n            @unroll for s in 1:num_state_prognostic\n                F1, F2, F3 = shared_flux[1, i, j, s],\n                shared_flux[2, i, j, s],\n                local_flux_3[s]\n\n                shared_flux[1, i, j, s] =\n                    M * (ξ1x1 * F1 + ξ1x2 * F2 + ξ1x3 * F3)\n                if dim == 3 || (dim == 2 && direction isa EveryDirection)\n                    shared_flux[2, i, j, s] =\n                        M * (ξ2x1 * F1 + ξ2x2 * F2 + ξ2x3 * F3)\n                end\n                if dim == 3 && direction isa EveryDirection\n                    local_flux_3[s] = M * (ξ3x1 * F1 + ξ3x2 * F2 + ξ3x3 * F3)\n                end\n            end\n\n            # In the case of the remainder model we may need to loop through the\n            # models to add in restricted direction components\n            if model_direction isa EveryDirection && balance_law isa RemBL\n                if rembl_has_subs_direction(HorizontalDirection(), balance_law)\n                    fill!(local_flux, -zero(eltype(local_flux)))\n                    flux_first_order_arr!(\n                        balance_law,\n                        local_flux,\n                        local_state_prognostic,\n                        local_state_auxiliary,\n                        t,\n                        (HorizontalDirection(),),\n                    )\n\n                    # Precomputing J ∇ξⁱ⋅ F\n                    @unroll for s in 1:num_state_prognostic\n                        F1, F2, F3 =\n                            local_flux[1, s], local_flux[2, s], local_flux[3, s]\n                        shared_flux[1, i, j, s] +=\n                            M * (ξ1x1 * F1 + ξ1x2 * F2 + ξ1x3 * F3)\n                        if dim == 3\n                            shared_flux[2, i, j, s] +=\n                                M * (ξ2x1 * F1 + ξ2x2 * F2 + ξ2x3 * F3)\n                        end\n                    end\n                end\n            end\n\n            if dim == 3 && direction isa EveryDirection\n                @unroll for n in 1:Nq3\n                    MI = local_MI[n]\n                    @unroll for s in 1:num_state_prognostic\n                        local_tendency[n, s] += MI * D[k, n] * local_flux_3[s]\n                    end\n                end\n            end\n\n            # Computes the contribution due to the source term S\n            if add_source\n                fill!(local_source, -zero(eltype(local_source)))\n                source_arr!(\n                    balance_law,\n                    local_source,\n                    local_state_prognostic,\n                    local_state_gradient_flux,\n                    local_state_auxiliary,\n                    t,\n                    (model_direction,),\n                )\n\n                @unroll for s in 1:num_state_prognostic\n                    local_tendency[k, s] += local_source[s]\n                end\n\n            end\n            @synchronize\n\n            # Weak \"inside metrics\" derivative.\n            # Computes the rest of the volume term: M⁻¹DᵀF\n            MI = local_MI[k]\n            @unroll for s in 1:num_state_prognostic\n                @unroll for n in 1:Nq1\n                    # ξ1-grid lines\n                    local_tendency[k, s] +=\n                        MI * D[n, i] * shared_flux[1, n, j, s]\n\n                    # ξ2-grid lines\n                    if dim == 3 || (dim == 2 && direction isa EveryDirection)\n                        local_tendency[k, s] +=\n                            MI * D[n, j] * shared_flux[2, i, n, s]\n                    end\n                end\n            end\n        end\n\n        @unroll for k in 1:Nq3\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            @unroll for s in 1:num_state_prognostic\n                if β != 0\n                    T = α * local_tendency[k, s] + β * tendency[ijk, s, e]\n                else\n                    T = α * local_tendency[k, s]\n                end\n                tendency[ijk, s, e] = T\n            end\n        end\n    end\nend\n\n\n@kernel function volume_tendency!(\n    balance_law::BalanceLaw,\n    ::Val{info},\n    model_direction,\n    ::VerticalDirection,\n    tendency,\n    state_prognostic,\n    state_gradient_flux,\n    Qhypervisc_grad,\n    state_auxiliary,\n    vgeo,\n    t,\n    ω,\n    D,\n    elems,\n    α,\n    β,\n    add_source = false,\n) where {info}\n    @uniform begin\n        dim = info.dim\n        FT = eltype(state_prognostic)\n        num_state_prognostic = number_states(balance_law, Prognostic())\n        num_state_gradient_flux = number_states(balance_law, GradientFlux())\n        num_state_auxiliary = number_states(balance_law, Auxiliary())\n\n        ngradlapstate = number_states(balance_law, GradientLaplacian())\n        nhyperviscstate = number_states(balance_law, Hyperdiffusive())\n\n        @inbounds Nq1 = info.Nq[1]\n        @inbounds Nq2 = info.Nq[2]\n        Nq3 = info.Nqk\n\n        local_source = MArray{Tuple{num_state_prognostic}, FT}(undef)\n        local_state_prognostic = MArray{Tuple{num_state_prognostic}, FT}(undef)\n        local_state_gradient_flux =\n            MArray{Tuple{num_state_gradient_flux}, FT}(undef)\n        local_state_hyperdiffusion = MArray{Tuple{nhyperviscstate}, FT}(undef)\n        local_state_auxiliary = MArray{Tuple{num_state_auxiliary}, FT}(undef)\n        local_flux = MArray{Tuple{3, num_state_prognostic}, FT}(undef)\n        local_flux_total = MArray{Tuple{3, num_state_prognostic}, FT}(undef)\n\n        _ζx1 = dim == 2 ? _ξ2x1 : _ξ3x1\n        _ζx2 = dim == 2 ? _ξ2x2 : _ξ3x2\n        _ζx3 = dim == 2 ? _ξ2x3 : _ξ3x3\n\n        @inbounds Nqv = dim == 2 ? Nq2 : info.Nq[dim]\n        shared_flux_size =\n            dim == 2 ? (Nq1, Nqv, num_state_prognostic) : (0, 0, 0)\n    end\n\n    # Arrays for F, and the differentiation matrix D\n    shared_flux = @localmem FT shared_flux_size\n\n    # Storage for tendency and mass inverse M⁻¹\n    local_tendency = @private FT (Nq3, num_state_prognostic)\n    local_MI = @private FT (Nq3,)\n\n    # Grab the index associated with the current element `e` and the\n    # horizontal quadrature indices `i` (in the ξ1-direction),\n    # `j` (in the ξ2-direction) [directions on the reference element].\n    # Parallelize over elements, then over columns\n    e = @index(Group, Linear)\n    i, j = @index(Local, NTuple)\n\n    @inbounds begin\n        @unroll for k in 1:Nq3\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            # initialize local tendency\n            @unroll for s in 1:num_state_prognostic\n                local_tendency[k, s] = zero(FT)\n            end\n            # read in mass matrix inverse for element `e`\n            local_MI[k] = vgeo[ijk, _MI, e]\n        end\n\n        # ensure D is loaded\n        @synchronize(dim == 3)\n\n        @unroll for k in 1:Nq3\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n            M = vgeo[ijk, _M, e]\n\n            # Extract vertical Jacobian terms ∂ζ/∂xⱼ\n            ζx1 = vgeo[ijk, _ζx1, e]\n            ζx2 = vgeo[ijk, _ζx2, e]\n            ζx3 = vgeo[ijk, _ζx3, e]\n\n            # Read fields into registers (hopefully)\n            @unroll for s in 1:num_state_prognostic\n                local_state_prognostic[s] = state_prognostic[ijk, s, e]\n            end\n\n            @unroll for s in 1:num_state_auxiliary\n                local_state_auxiliary[s] = state_auxiliary[ijk, s, e]\n            end\n\n            @unroll for s in 1:num_state_gradient_flux\n                local_state_gradient_flux[s] = state_gradient_flux[ijk, s, e]\n            end\n\n            @unroll for s in 1:nhyperviscstate\n                local_state_hyperdiffusion[s] = Qhypervisc_grad[ijk, s, e]\n            end\n\n            # Computes the local inviscid fluxes Fⁱⁿᵛ\n            fill!(local_flux, -zero(eltype(local_flux)))\n            flux_first_order_arr!(\n                balance_law,\n                local_flux,\n                local_state_prognostic,\n                local_state_auxiliary,\n                t,\n                (model_direction,),\n            )\n\n            @unroll for s in 1:num_state_prognostic\n                local_flux_total[1, s] = local_flux[1, s]\n                local_flux_total[2, s] = local_flux[2, s]\n                local_flux_total[3, s] = local_flux[3, s]\n            end\n\n            # Computes the local viscous fluxes Fᵛⁱˢᶜ\n            fill!(local_flux, -zero(eltype(local_flux)))\n            flux_second_order_arr!(\n                balance_law,\n                local_flux,\n                local_state_prognostic,\n                local_state_gradient_flux,\n                local_state_hyperdiffusion,\n                local_state_auxiliary,\n                t,\n            )\n\n            @unroll for s in 1:num_state_prognostic\n                local_flux_total[1, s] += local_flux[1, s]\n                local_flux_total[2, s] += local_flux[2, s]\n                local_flux_total[3, s] += local_flux[3, s]\n            end\n\n            # Build \"inside metrics\" flux\n            @unroll for s in 1:num_state_prognostic\n                F1, F2, F3 = local_flux_total[1, s],\n                local_flux_total[2, s],\n                local_flux_total[3, s]\n                Fv = M * (ζx1 * F1 + ζx2 * F2 + ζx3 * F3)\n                if dim == 2\n                    shared_flux[i, j, s] = Fv\n                else\n                    local_flux_total[1, s] = Fv\n                end\n            end\n\n            # In the case of the remainder model we may need to loop through the\n            # models to add in restricted direction components\n            if model_direction isa EveryDirection && balance_law isa RemBL\n                if rembl_has_subs_direction(VerticalDirection(), balance_law)\n                    fill!(local_flux, -zero(eltype(local_flux)))\n                    flux_first_order_arr!(\n                        balance_law,\n                        local_flux,\n                        local_state_prognostic,\n                        local_state_auxiliary,\n                        t,\n                        (VerticalDirection(),),\n                    )\n\n                    # Precomputing J ∇ζ⋅ F\n                    @unroll for s in 1:num_state_prognostic\n                        F1, F2, F3 =\n                            local_flux[1, s], local_flux[2, s], local_flux[3, s]\n                        Fv = M * (ζx1 * F1 + ζx2 * F2 + ζx3 * F3)\n                        if dim == 2\n                            shared_flux[i, j, s] += Fv\n                        else\n                            local_flux_total[1, s] += Fv\n                        end\n                    end\n                end\n            end\n\n            if dim == 3\n                @unroll for n in 1:Nq3\n                    MI = local_MI[n]\n                    @unroll for s in 1:num_state_prognostic\n                        local_tendency[n, s] +=\n                            MI * D[k, n] * local_flux_total[1, s]\n                    end\n                end\n            end\n\n            # Computes the contribution due to the source term S\n            if add_source\n                fill!(local_source, -zero(eltype(local_source)))\n                source_arr!(\n                    balance_law,\n                    local_source,\n                    local_state_prognostic,\n                    local_state_gradient_flux,\n                    local_state_auxiliary,\n                    t,\n                    (model_direction,),\n                )\n\n                @unroll for s in 1:num_state_prognostic\n                    local_tendency[k, s] += local_source[s]\n                end\n            end\n            @synchronize(dim == 2)\n\n            # Weak \"inside metrics\" derivative.\n            # Computes the rest of the volume term: M⁻¹DᵀMF\n            if dim == 2\n                MI = local_MI[k]\n                @unroll for n in 1:Nqv\n                    @unroll for s in 1:num_state_prognostic\n                        local_tendency[k, s] +=\n                            MI * D[n, j] * shared_flux[i, n, s]\n                    end\n                end\n            end\n        end\n\n        @unroll for k in 1:Nq3\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            @unroll for s in 1:num_state_prognostic\n                if β != 0\n                    T = α * local_tendency[k, s] + β * tendency[ijk, s, e]\n                else\n                    T = α * local_tendency[k, s]\n                end\n                tendency[ijk, s, e] = T\n            end\n        end\n    end\nend\n\n@doc \"\"\"\n    function dgsem_interface_tendency!(\n        balance_law::BalanceLaw,\n        ::Val{dim},\n        ::Val{polyorder},\n        direction,\n        numerical_flux_first_order,\n        numerical_flux_second_order,\n        tendency,\n        state_prognostic,\n        state_gradient_flux,\n        Qhypervisc_grad,\n        state_auxiliary,\n        vgeo,\n        sgeo,\n        t,\n        vmap⁻,\n        vmap⁺,\n        elemtobndy,\n        elems,\n        α,\n    )\n\nCompute kernel for evaluating the interface tendencies for the\nDG form:\n\n∫ₑ ψ⋅ ∂q/∂t dx - ∫ₑ ∇ψ⋅(Fⁱⁿᵛ + Fᵛⁱˢᶜ) dx + ∮ₑ n̂ ψ⋅(Fⁱⁿᵛ⋆ + Fᵛⁱˢᶜ⋆) dS,\n\nor equivalently in matrix form:\n\ndQ/dt = M⁻¹(MS + DᵀM(Fⁱⁿᵛ + Fᵛⁱˢᶜ) + ∑ᶠ LᵀMf(Fⁱⁿᵛ⋆ + Fᵛⁱˢᶜ⋆)).\n\nThis kernel computes the surface terms: M⁻¹ ∑ᶠ LᵀMf(Fⁱⁿᵛ⋆ + Fᵛⁱˢᶜ⋆)),\nwhere M is the mass matrix, Mf is the face mass matrix, L is an interpolator\nfrom volume to face, and Fⁱⁿᵛ⋆, Fᵛⁱˢᶜ⋆\nare the numerical fluxes for the inviscid and viscous\nfluxes, respectively.\n\"\"\" dgsem_interface_tendency!\n@kernel function dgsem_interface_tendency!(\n    balance_law::BalanceLaw,\n    ::Val{info},\n    direction,\n    numerical_flux_first_order,\n    numerical_flux_second_order,\n    tendency,\n    state_prognostic,\n    state_gradient_flux,\n    Qhypervisc_grad,\n    state_auxiliary,\n    vgeo,\n    sgeo,\n    t,\n    vmap⁻,\n    vmap⁺,\n    elemtobndy,\n    elems,\n    α,\n) where {info}\n    @uniform begin\n        dim = info.dim\n        FT = eltype(state_prognostic)\n        num_state_prognostic = number_states(balance_law, Prognostic())\n        num_state_gradient_flux = number_states(balance_law, GradientFlux())\n        nhyperviscstate = number_states(balance_law, Hyperdiffusive())\n        num_state_auxiliary = number_states(balance_law, Auxiliary())\n        ngradlapstate = number_states(balance_law, GradientLaplacian())\n        nface = info.nface\n        Np = info.Np\n        Nqk = info.Nqk\n\n        faces = 1:nface\n        if direction isa VerticalDirection\n            faces = (nface - 1):nface\n        elseif direction isa HorizontalDirection\n            faces = 1:(nface - 2)\n        end\n\n        local_state_prognostic⁻ = MArray{Tuple{num_state_prognostic}, FT}(undef)\n        local_state_gradient_flux⁻ =\n            MArray{Tuple{num_state_gradient_flux}, FT}(undef)\n        local_state_hyperdiffusion⁻ = MArray{Tuple{nhyperviscstate}, FT}(undef)\n        local_state_auxiliary⁻ = MArray{Tuple{num_state_auxiliary}, FT}(undef)\n\n        # Need two copies since numerical_flux_first_order! can modify state_prognostic⁺\n        local_state_prognostic⁺nondiff =\n            MArray{Tuple{num_state_prognostic}, FT}(undef)\n        local_state_prognostic⁺diff =\n            MArray{Tuple{num_state_prognostic}, FT}(undef)\n\n        # Need two copies since numerical_flux_first_order! can modify state_auxiliary⁺\n        local_state_auxiliary⁺nondiff =\n            MArray{Tuple{num_state_auxiliary}, FT}(undef)\n        local_state_auxiliary⁺diff =\n            MArray{Tuple{num_state_auxiliary}, FT}(undef)\n\n        local_state_gradient_flux⁺ =\n            MArray{Tuple{num_state_gradient_flux}, FT}(undef)\n        local_state_hyperdiffusion⁺ = MArray{Tuple{nhyperviscstate}, FT}(undef)\n\n        local_state_prognostic_bottom1 =\n            MArray{Tuple{num_state_prognostic}, FT}(undef)\n        local_state_gradient_flux_bottom1 =\n            MArray{Tuple{num_state_gradient_flux}, FT}(undef)\n        local_state_auxiliary_bottom1 =\n            MArray{Tuple{num_state_auxiliary}, FT}(undef)\n\n        local_flux = MArray{Tuple{num_state_prognostic}, FT}(undef)\n    end\n\n    eI = @index(Group, Linear)\n    n = @index(Local, Linear)\n\n    e = @private Int (1,)\n    @inbounds e[1] = elems[eI]\n\n    @inbounds for f in faces\n        # The remainder model needs to know which direction of face the model is\n        # being evaluated for. So faces 1:(nface - 2) are flagged as\n        # `HorizontalDirection()` faces and the remaining two faces are\n        # `VerticalDirection()` faces\n        face_direction =\n            f in 1:(nface - 2) ? (EveryDirection(), HorizontalDirection()) :\n            (EveryDirection(), VerticalDirection())\n        e⁻ = e[1]\n        normal_vector = SVector(\n            sgeo[_n1, n, f, e⁻],\n            sgeo[_n2, n, f, e⁻],\n            sgeo[_n3, n, f, e⁻],\n        )\n        bctag = elemtobndy[f, e⁻]\n        # Get surface mass, volume mass inverse\n        sM, vMI = sgeo[_sM, n, f, e⁻], sgeo[_vMI, n, f, e⁻]\n        id⁻, id⁺ = vmap⁻[n, f, e⁻], vmap⁺[n, f, e⁻]\n        e⁺ = ((id⁺ - 1) ÷ Np) + 1\n\n        vid⁻, vid⁺ = ((id⁻ - 1) % Np) + 1, ((id⁺ - 1) % Np) + 1\n        if bctag != 0\n            # TODO: we will use vmap⁺ to store the boundary element info\n            #be = e⁺\n            #bcid = vid⁺\n            e⁺ = e⁻\n            vid⁺ = vid⁻\n        end\n\n        # Load minus side data\n        @unroll for s in 1:num_state_prognostic\n            local_state_prognostic⁻[s] = state_prognostic[vid⁻, s, e⁻]\n        end\n\n        @unroll for s in 1:num_state_gradient_flux\n            local_state_gradient_flux⁻[s] = state_gradient_flux[vid⁻, s, e⁻]\n        end\n\n        @unroll for s in 1:nhyperviscstate\n            local_state_hyperdiffusion⁻[s] = Qhypervisc_grad[vid⁻, s, e⁻]\n        end\n\n        @unroll for s in 1:num_state_auxiliary\n            local_state_auxiliary⁻[s] = state_auxiliary[vid⁻, s, e⁻]\n        end\n\n        # Load plus side data\n        @unroll for s in 1:num_state_prognostic\n            local_state_prognostic⁺diff[s] =\n                local_state_prognostic⁺nondiff[s] =\n                    state_prognostic[vid⁺, s, e⁺]\n        end\n\n        @unroll for s in 1:num_state_gradient_flux\n            local_state_gradient_flux⁺[s] = state_gradient_flux[vid⁺, s, e⁺]\n        end\n\n        @unroll for s in 1:nhyperviscstate\n            local_state_hyperdiffusion⁺[s] = Qhypervisc_grad[vid⁺, s, e⁺]\n        end\n\n        @unroll for s in 1:num_state_auxiliary\n            local_state_auxiliary⁺diff[s] =\n                local_state_auxiliary⁺nondiff[s] = state_auxiliary[vid⁺, s, e⁺]\n        end\n\n        # Oh dang, it's boundary conditions\n        fill!(local_flux, -zero(eltype(local_flux)))\n        if bctag == 0\n            numerical_flux_first_order!(\n                numerical_flux_first_order,\n                balance_law,\n                Vars{vars_state(balance_law, Prognostic(), FT)}(local_flux),\n                SVector(normal_vector),\n                Vars{vars_state(balance_law, Prognostic(), FT)}(\n                    local_state_prognostic⁻,\n                ),\n                Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                    local_state_auxiliary⁻,\n                ),\n                Vars{vars_state(balance_law, Prognostic(), FT)}(\n                    local_state_prognostic⁺nondiff,\n                ),\n                Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                    local_state_auxiliary⁺nondiff,\n                ),\n                t,\n                face_direction,\n            )\n            numerical_flux_second_order!(\n                numerical_flux_second_order,\n                balance_law,\n                Vars{vars_state(balance_law, Prognostic(), FT)}(local_flux),\n                normal_vector,\n                Vars{vars_state(balance_law, Prognostic(), FT)}(\n                    local_state_prognostic⁻,\n                ),\n                Vars{vars_state(balance_law, GradientFlux(), FT)}(\n                    local_state_gradient_flux⁻,\n                ),\n                Vars{vars_state(balance_law, Hyperdiffusive(), FT)}(\n                    local_state_hyperdiffusion⁻,\n                ),\n                Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                    local_state_auxiliary⁻,\n                ),\n                Vars{vars_state(balance_law, Prognostic(), FT)}(\n                    local_state_prognostic⁺diff,\n                ),\n                Vars{vars_state(balance_law, GradientFlux(), FT)}(\n                    local_state_gradient_flux⁺,\n                ),\n                Vars{vars_state(balance_law, Hyperdiffusive(), FT)}(\n                    local_state_hyperdiffusion⁺,\n                ),\n                Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                    local_state_auxiliary⁺diff,\n                ),\n                t,\n            )\n        else\n            if (dim == 2 && f == 3) || (dim == 3 && f == 5)\n                if info.N[end] == 0\n                    # Loop up to next element for all horizontal elements\n                    @unroll for s in 1:num_state_prognostic\n                        local_state_prognostic_bottom1[s] =\n                            state_prognostic[n, s, e⁻ + 1]\n                    end\n                    @unroll for s in 1:num_state_gradient_flux\n                        local_state_gradient_flux_bottom1[s] =\n                            state_gradient_flux[n, s, e⁻ + 1]\n                    end\n                    @unroll for s in 1:num_state_auxiliary\n                        local_state_auxiliary_bottom1[s] =\n                            state_auxiliary[n, s, e⁻ + 1]\n                    end\n                else\n                    # Loop up the first element along all horizontal elements\n                    @unroll for s in 1:num_state_prognostic\n                        local_state_prognostic_bottom1[s] =\n                            state_prognostic[n + Nqk^2, s, e⁻]\n                    end\n                    @unroll for s in 1:num_state_gradient_flux\n                        local_state_gradient_flux_bottom1[s] =\n                            state_gradient_flux[n + Nqk^2, s, e⁻]\n                    end\n                    @unroll for s in 1:num_state_auxiliary\n                        local_state_auxiliary_bottom1[s] =\n                            state_auxiliary[n + Nqk^2, s, e⁻]\n                    end\n                end\n            end\n\n            bcs = boundary_conditions(balance_law)\n            # TODO: there is probably a better way to unroll this loop\n            Base.Cartesian.@nif 7 d -> bctag == d <= length(bcs) d -> begin\n                bc = bcs[d]\n                numerical_boundary_flux_first_order!(\n                    numerical_flux_first_order,\n                    bc,\n                    balance_law,\n                    Vars{vars_state(balance_law, Prognostic(), FT)}(local_flux),\n                    SVector(normal_vector),\n                    Vars{vars_state(balance_law, Prognostic(), FT)}(\n                        local_state_prognostic⁻,\n                    ),\n                    Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                        local_state_auxiliary⁻,\n                    ),\n                    Vars{vars_state(balance_law, Prognostic(), FT)}(\n                        local_state_prognostic⁺nondiff,\n                    ),\n                    Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                        local_state_auxiliary⁺nondiff,\n                    ),\n                    t,\n                    face_direction,\n                    Vars{vars_state(balance_law, Prognostic(), FT)}(\n                        local_state_prognostic_bottom1,\n                    ),\n                    Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                        local_state_auxiliary_bottom1,\n                    ),\n                )\n                numerical_boundary_flux_second_order!(\n                    numerical_flux_second_order,\n                    bc,\n                    balance_law,\n                    Vars{vars_state(balance_law, Prognostic(), FT)}(local_flux),\n                    normal_vector,\n                    Vars{vars_state(balance_law, Prognostic(), FT)}(\n                        local_state_prognostic⁻,\n                    ),\n                    Vars{vars_state(balance_law, GradientFlux(), FT)}(\n                        local_state_gradient_flux⁻,\n                    ),\n                    Vars{vars_state(balance_law, Hyperdiffusive(), FT)}(\n                        local_state_hyperdiffusion⁻,\n                    ),\n                    Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                        local_state_auxiliary⁻,\n                    ),\n                    Vars{vars_state(balance_law, Prognostic(), FT)}(\n                        local_state_prognostic⁺diff,\n                    ),\n                    Vars{vars_state(balance_law, GradientFlux(), FT)}(\n                        local_state_gradient_flux⁺,\n                    ),\n                    Vars{vars_state(balance_law, Hyperdiffusive(), FT)}(\n                        local_state_hyperdiffusion⁺,\n                    ),\n                    Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                        local_state_auxiliary⁺diff,\n                    ),\n                    t,\n                    Vars{vars_state(balance_law, Prognostic(), FT)}(\n                        local_state_prognostic_bottom1,\n                    ),\n                    Vars{vars_state(balance_law, GradientFlux(), FT)}(\n                        local_state_gradient_flux_bottom1,\n                    ),\n                    Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                        local_state_auxiliary_bottom1,\n                    ),\n                )\n            end d -> throw(BoundsError(bcs, bctag))\n        end\n\n        # Update RHS (in outer face loop): M⁻¹ Mfᵀ(Fⁱⁿᵛ⋆ + Fᵛⁱˢᶜ⋆))\n        @unroll for s in 1:num_state_prognostic\n            # FIXME: Should we pretch these?\n            tendency[vid⁻, s, e⁻] -= α * vMI * sM * local_flux[s]\n        end\n        # Need to wait after even faces to avoid race conditions\n        @synchronize(f % 2 == 0)\n    end\nend\n\n\"\"\"\n    function volume_gradients!(\n        balance_law::BalanceLaw,\n        ::Val{dim},\n        ::Val{polyorder},\n        direction,\n        state_prognostic,\n        state_gradient_flux,\n        Qhypervisc_grad,\n        state_auxiliary,\n        vgeo,\n        t,\n        D,\n        ::Val{hypervisc_indexmap},\n        elems,\n        increment = false,\n    )\n\nComputes the volume integral for the auxiliary equation\n(in DG strong form):\n\n∫ₑ ψI⋅Σ dx = ∫ₑ ψI⋅∇G dx + ∮ₑ nψI⋅(G* - G) dS,\n\nor equivalently in matrix notation:\n\nΣ = M⁻¹ LᵀMf(G* - G) + D G\n\nThis kernel computes the volume gradient: D * G, where\nD is the differentiation matrix and G is the auxiliary\ngradient flux.\n\"\"\"\n@kernel function volume_gradients!(\n    balance_law::BalanceLaw,\n    ::Val{info},\n    direction,\n    state_prognostic,\n    state_gradient_flux,\n    Qhypervisc_grad,\n    state_auxiliary,\n    vgeo,\n    t,\n    D,\n    ::Val{hypervisc_indexmap},\n    elems,\n    increment = false,\n) where {info, hypervisc_indexmap}\n    @uniform begin\n        dim = info.dim\n        FT = eltype(state_prognostic)\n        num_state_prognostic = number_states(balance_law, Prognostic())\n        ngradstate = number_states(balance_law, Gradient())\n        ngradlapstate = number_states(balance_law, GradientLaplacian())\n        num_state_gradient_flux = number_states(balance_law, GradientFlux())\n        num_state_auxiliary = number_states(balance_law, Auxiliary())\n\n        # Kernel assumes same polynomial order in both\n        # horizontal directions (x, y)\n        @inbounds Nq1 = info.Nq[1]\n        @inbounds Nq2 = info.Nq[2]\n        Nq3 = info.Nqk\n\n        ngradtransformstate = num_state_prognostic\n\n        local_transform = MArray{Tuple{ngradstate}, FT}(undef)\n        local_state_gradient_flux =\n            MArray{Tuple{num_state_gradient_flux}, FT}(undef)\n    end\n\n    # Transformation from conservative variables to\n    # primitive variables (i.e. ρu → u)\n    shared_transform = @localmem FT (Nq1, Nq2, ngradstate)\n\n    local_state_prognostic = @private FT (ngradtransformstate, Nq3)\n    local_state_auxiliary = @private FT (num_state_auxiliary, Nq3)\n    local_transform_gradient = @private FT (3, ngradstate, Nq3)\n    Gξ3 = @private FT (ngradstate, Nq3)\n\n    # Grab the index associated with the current element `e` and the\n    # horizontal quadrature indices `i` (in the ξ1-direction),\n    # `j` (in the ξ2-direction) [directions on the reference element].\n    # Parallelize over elements, then over columns\n    e = @index(Group, Linear)\n    i, j = @index(Local, NTuple)\n\n    @inbounds @views begin\n        @unroll for k in 1:Nq3\n            # Initialize local gradient variables\n            @unroll for s in 1:ngradstate\n                local_transform_gradient[1, s, k] = -zero(FT)\n                local_transform_gradient[2, s, k] = -zero(FT)\n                local_transform_gradient[3, s, k] = -zero(FT)\n                Gξ3[s, k] = -zero(FT)\n            end\n\n            # Load prognostic and auxiliary variables\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            @unroll for s in 1:ngradtransformstate\n                local_state_prognostic[s, k] = state_prognostic[ijk, s, e]\n            end\n            @unroll for s in 1:num_state_auxiliary\n                local_state_auxiliary[s, k] = state_auxiliary[ijk, s, e]\n            end\n        end\n\n        # Compute G(q) and write the result into shared memory\n        @unroll for k in 1:Nq3\n            fill!(local_transform, -zero(eltype(local_transform)))\n            compute_gradient_argument_arr!(\n                balance_law,\n                local_transform,\n                local_state_prognostic[:, k],\n                local_state_auxiliary[:, k],\n                t,\n            )\n\n            @unroll for s in 1:ngradstate\n                shared_transform[i, j, s] = local_transform[s]\n            end\n\n            # Synchronize threads on the device\n            @synchronize\n\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            ξ1x1, ξ1x2, ξ1x3 =\n                vgeo[ijk, _ξ1x1, e], vgeo[ijk, _ξ1x2, e], vgeo[ijk, _ξ1x3, e]\n\n            # Compute gradient of each state\n            @unroll for s in 1:ngradstate\n                Gξ1 = Gξ2 = zero(FT)\n\n                @unroll for n in 1:Nq1\n                    # Smack G with the differentiation matrix\n                    Gξ1 += D[i, n] * shared_transform[n, j, s]\n                    if dim == 3 || (dim == 2 && direction isa EveryDirection)\n                        Gξ2 += D[j, n] * shared_transform[i, n, s]\n                    end\n                    # Compute the gradient of G over the entire column\n                    if dim == 3 && direction isa EveryDirection\n                        Gξ3[s, n] += D[n, k] * shared_transform[i, j, s]\n                    end\n                end\n\n                # Application of chain-rule in ξ1 and ξ2 directions,\n                # ∂G/∂xi = ∂ξ1/∂xi * ∂G/∂ξ1, ∂G/∂xi = ∂ξ2/∂xi * ∂G/∂ξ2\n                # to get a physical gradient\n                local_transform_gradient[1, s, k] += ξ1x1 * Gξ1\n                local_transform_gradient[2, s, k] += ξ1x2 * Gξ1\n                local_transform_gradient[3, s, k] += ξ1x3 * Gξ1\n\n                if dim == 3 || (dim == 2 && direction isa EveryDirection)\n                    ξ2x1, ξ2x2, ξ2x3 = vgeo[ijk, _ξ2x1, e],\n                    vgeo[ijk, _ξ2x2, e],\n                    vgeo[ijk, _ξ2x3, e]\n                    local_transform_gradient[1, s, k] += ξ2x1 * Gξ2\n                    local_transform_gradient[2, s, k] += ξ2x2 * Gξ2\n                    local_transform_gradient[3, s, k] += ξ2x3 * Gξ2\n                end\n            end\n\n            # Synchronize threads on the device\n            @synchronize\n        end\n\n        @unroll for k in 1:Nq3\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n            # Application of chain-rule in ξ3-direction: ∂G/∂xi = ∂ξ3/∂xi * ∂G/∂ξ3\n            if dim == 3 && direction isa EveryDirection\n                ξ3x1, ξ3x2, ξ3x3 = vgeo[ijk, _ξ3x1, e],\n                vgeo[ijk, _ξ3x2, e],\n                vgeo[ijk, _ξ3x3, e]\n                @unroll for s in 1:ngradstate\n                    local_transform_gradient[1, s, k] += ξ3x1 * Gξ3[s, k]\n                    local_transform_gradient[2, s, k] += ξ3x2 * Gξ3[s, k]\n                    local_transform_gradient[3, s, k] += ξ3x3 * Gξ3[s, k]\n                end\n            end\n\n            # Hyperdiffusion (avoid recomputing gradients of the state since\n            # these are needed for the hyperdiffusion kernels)\n            @unroll for s in 1:ngradlapstate\n                if increment\n                    Qhypervisc_grad[ijk, 3 * (s - 1) + 1, e] +=\n                        local_transform_gradient[1, hypervisc_indexmap[s], k]\n                    Qhypervisc_grad[ijk, 3 * (s - 1) + 2, e] +=\n                        local_transform_gradient[2, hypervisc_indexmap[s], k]\n                    Qhypervisc_grad[ijk, 3 * (s - 1) + 3, e] +=\n                        local_transform_gradient[3, hypervisc_indexmap[s], k]\n                else\n                    Qhypervisc_grad[ijk, 3 * (s - 1) + 1, e] =\n                        local_transform_gradient[1, hypervisc_indexmap[s], k]\n                    Qhypervisc_grad[ijk, 3 * (s - 1) + 2, e] =\n                        local_transform_gradient[2, hypervisc_indexmap[s], k]\n                    Qhypervisc_grad[ijk, 3 * (s - 1) + 3, e] =\n                        local_transform_gradient[3, hypervisc_indexmap[s], k]\n                end\n            end\n\n            if num_state_gradient_flux > 0\n                fill!(\n                    local_state_gradient_flux,\n                    -zero(eltype(local_state_gradient_flux)),\n                )\n\n                # Applies a linear transformation of gradients to the diffusive variables\n                compute_gradient_flux_arr!(\n                    balance_law,\n                    local_state_gradient_flux,\n                    local_transform_gradient[:, :, k],\n                    local_state_prognostic[:, k],\n                    local_state_auxiliary[:, k],\n                    t,\n                )\n\n                # Write out the result of the kernel to global memory\n                @unroll for s in 1:num_state_gradient_flux\n                    if increment\n                        state_gradient_flux[ijk, s, e] +=\n                            local_state_gradient_flux[s]\n                    else\n                        state_gradient_flux[ijk, s, e] =\n                            local_state_gradient_flux[s]\n                    end\n                end\n            end\n        end\n    end\nend\n\n@kernel function volume_gradients!(\n    balance_law::BalanceLaw,\n    ::Val{info},\n    ::VerticalDirection,\n    state_prognostic,\n    state_gradient_flux,\n    Qhypervisc_grad,\n    state_auxiliary,\n    vgeo,\n    t,\n    D,\n    ::Val{hypervisc_indexmap},\n    elems,\n    increment = false,\n) where {info, hypervisc_indexmap}\n    @uniform begin\n        dim = info.dim\n\n        FT = eltype(state_prognostic)\n        num_state_prognostic = number_states(balance_law, Prognostic())\n        ngradstate = number_states(balance_law, Gradient())\n        ngradlapstate = number_states(balance_law, GradientLaplacian())\n        num_state_gradient_flux = number_states(balance_law, GradientFlux())\n        num_state_auxiliary = number_states(balance_law, Auxiliary())\n\n        # Assumes same polynomial order in both\n        # horizontal directions (x,y)\n        @inbounds Nq1 = info.Nq[1]\n        @inbounds Nq2 = info.Nq[2]\n        Nq3 = info.Nqk\n\n        ngradtransformstate = num_state_prognostic\n\n        local_transform = MArray{Tuple{ngradstate}, FT}(undef)\n        local_state_gradient_flux =\n            MArray{Tuple{num_state_gradient_flux}, FT}(undef)\n\n        _ζx1 = dim == 2 ? _ξ2x1 : _ξ3x1\n        _ζx2 = dim == 2 ? _ξ2x2 : _ξ3x2\n        _ζx3 = dim == 2 ? _ξ2x3 : _ξ3x3\n\n        Gζ_size = dim == 3 ? (ngradstate, Nq3) : (0, 0)\n        @inbounds Nqv = dim == 2 ? Nq2 : info.Nq[dim]\n        shared_transform_dim2 = dim == 2 ? Nqv : Nq1\n    end\n\n    # Transformation from conservative variables to\n    # primitive variables (i.e. ρu → u)\n    shared_transform = @localmem FT (Nq1, shared_transform_dim2, ngradstate)\n\n    local_state_prognostic = @private FT (ngradtransformstate, Nq3)\n    local_state_auxiliary = @private FT (num_state_auxiliary, Nq3)\n    local_transform_gradient = @private FT (3, ngradstate, Nq3)\n\n    local_ζ = @private FT (3, Nq3)\n\n    Gζ = @private FT Gζ_size\n\n    # Grab the index associated with the current element `e` and the\n    # horizontal quadrature indices `i` (in the ξ1-direction),\n    # `j` (in the ξ2-direction) [directions on the reference element].\n    # Parallelize over elements, then over columns\n    e = @index(Group, Linear)\n    i, j = @index(Local, NTuple)\n\n    @inbounds @views begin\n\n        @unroll for k in 1:Nq3\n            # Initialize local gradient variables\n            @unroll for s in 1:ngradstate\n                local_transform_gradient[1, s, k] = -zero(FT)\n                local_transform_gradient[2, s, k] = -zero(FT)\n                local_transform_gradient[3, s, k] = -zero(FT)\n                if dim == 3\n                    Gζ[s, k] = -zero(FT)\n                end\n            end\n\n            # Load prognostic and auxiliary variables\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            @unroll for s in 1:ngradtransformstate\n                local_state_prognostic[s, k] = state_prognostic[ijk, s, e]\n            end\n            @unroll for s in 1:num_state_auxiliary\n                local_state_auxiliary[s, k] = state_auxiliary[ijk, s, e]\n            end\n\n            # Load geometry terms for the Jacobian: ∂ζ/∂xⱼ\n            local_ζ[1, k] = vgeo[ijk, _ζx1, e]\n            local_ζ[2, k] = vgeo[ijk, _ζx2, e]\n            local_ζ[3, k] = vgeo[ijk, _ζx3, e]\n        end\n\n        # Compute G(q) and write the result into shared memory\n        @unroll for k in 1:Nq3\n            fill!(local_transform, -zero(eltype(local_transform)))\n            compute_gradient_argument_arr!(\n                balance_law,\n                local_transform,\n                local_state_prognostic[:, k],\n                local_state_auxiliary[:, k],\n                t,\n            )\n\n            @unroll for s in 1:ngradstate\n                shared_transform[i, j, s] = local_transform[s]\n            end\n\n            # Synchronize threads on the device\n            @synchronize\n\n            # Compute gradient of each state\n            @unroll for s in 1:ngradstate\n                # Compute the gradient of G using the chain-rule:\n                # ∂G/∂xi = ∂ζ/∂xi * ∂G/∂ζ to get a physical gradient\n                if dim == 2\n                    Gζ1 = zero(FT)\n                    @unroll for n in 1:Nqv\n                        Gζ1 += D[j, n] * shared_transform[i, n, s]\n                    end\n                    local_transform_gradient[1, s, k] += local_ζ[1, k] * Gζ1\n                    local_transform_gradient[2, s, k] += local_ζ[2, k] * Gζ1\n                    local_transform_gradient[3, s, k] += local_ζ[3, k] * Gζ1\n                else\n                    @unroll for n in 1:Nq3\n                        Gζ[s, n] += D[n, k] * shared_transform[i, j, s]\n                    end\n                end\n            end\n            @synchronize\n        end\n\n        @unroll for k in 1:Nq3\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n            # Application of chain-rule: ∂G/∂xi = ∂ζ/∂xi * ∂G/∂ζ\n            if dim == 3\n                @unroll for s in 1:ngradstate\n                    local_transform_gradient[1, s, k] +=\n                        local_ζ[1, k] * Gζ[s, k]\n                    local_transform_gradient[2, s, k] +=\n                        local_ζ[2, k] * Gζ[s, k]\n                    local_transform_gradient[3, s, k] +=\n                        local_ζ[3, k] * Gζ[s, k]\n                end\n            end\n\n            # Hyperdiffusion (avoid recomputing gradients of the state since\n            # these are needed for the hyperdiffusion kernels)\n            @unroll for s in 1:ngradlapstate\n                if increment\n                    Qhypervisc_grad[ijk, 3 * (s - 1) + 1, e] +=\n                        local_transform_gradient[1, hypervisc_indexmap[s], k]\n                    Qhypervisc_grad[ijk, 3 * (s - 1) + 2, e] +=\n                        local_transform_gradient[2, hypervisc_indexmap[s], k]\n                    Qhypervisc_grad[ijk, 3 * (s - 1) + 3, e] +=\n                        local_transform_gradient[3, hypervisc_indexmap[s], k]\n                else\n                    Qhypervisc_grad[ijk, 3 * (s - 1) + 1, e] =\n                        local_transform_gradient[1, hypervisc_indexmap[s], k]\n                    Qhypervisc_grad[ijk, 3 * (s - 1) + 2, e] =\n                        local_transform_gradient[2, hypervisc_indexmap[s], k]\n                    Qhypervisc_grad[ijk, 3 * (s - 1) + 3, e] =\n                        local_transform_gradient[3, hypervisc_indexmap[s], k]\n                end\n            end\n\n            if num_state_gradient_flux > 0\n                fill!(\n                    local_state_gradient_flux,\n                    -zero(eltype(local_state_gradient_flux)),\n                )\n\n                # Applies a linear transformation of gradients to the diffusive variables\n                compute_gradient_flux_arr!(\n                    balance_law,\n                    local_state_gradient_flux,\n                    local_transform_gradient[:, :, k],\n                    local_state_prognostic[:, k],\n                    local_state_auxiliary[:, k],\n                    t,\n                )\n\n                # Write out the result of the kernel to global memory\n                @unroll for s in 1:num_state_gradient_flux\n                    if increment\n                        state_gradient_flux[ijk, s, e] +=\n                            local_state_gradient_flux[s]\n                    else\n                        state_gradient_flux[ijk, s, e] =\n                            local_state_gradient_flux[s]\n                    end\n                end\n            end\n        end\n    end\nend\n\n@doc \"\"\"\n    function dgsem_interface_gradients!(\n        balance_law::BalanceLaw,\n        ::Val{dim},\n        ::Val{polyorder},\n        direction,\n        numerical_flux_gradient,\n        state_prognostic,\n        state_gradient_flux,\n        Qhypervisc_grad,\n        state_auxiliary,\n        vgeo,\n        sgeo,\n        t,\n        vmap⁻,\n        vmap⁺,\n        elemtobndy,\n        ::Val{hypervisc_indexmap},\n        elems,\n    )\n\nComputes the surface integral for the auxiliary equation\n(in DG strong form):\n\n∫ₑ ψI⋅Σ dx = ∫ₑ ψI⋅∇G dx + ∮ₑ nψI⋅(G* - G) dS,\n\nor equivalently in matrix notation:\n\nΣ = M⁻¹ LᵀMf(G* - G) + D G\n\nThis kernel computes the interface gradient term: M⁻¹ LᵀMf(G* - G),\nwhere M is the mass matrix, Mf is the face mass matrix, L is an interpolator\nfrom volume to face, G is the\nauxiliary gradient flux, and G* is the associated numerical flux.\n\"\"\" dgsem_interface_gradients!\n@kernel function dgsem_interface_gradients!(\n    balance_law::BalanceLaw,\n    ::Val{info},\n    direction,\n    numerical_flux_gradient,\n    state_prognostic,\n    state_gradient_flux,\n    Qhypervisc_grad,\n    state_auxiliary,\n    vgeo,\n    sgeo,\n    t,\n    vmap⁻,\n    vmap⁺,\n    elemtobndy,\n    ::Val{hypervisc_indexmap},\n    elems,\n) where {info, hypervisc_indexmap}\n    @uniform begin\n        dim = info.dim\n        FT = eltype(state_prognostic)\n        num_state_prognostic = number_states(balance_law, Prognostic())\n        ngradstate = number_states(balance_law, Gradient())\n        ngradlapstate = number_states(balance_law, GradientLaplacian())\n        num_state_gradient_flux = number_states(balance_law, GradientFlux())\n        num_state_auxiliary = number_states(balance_law, Auxiliary())\n        nface = info.nface\n        Np = info.Np\n        Nqk = info.Nqk\n\n        # Determines the number of faces depending on\n        # the direction argument\n        faces = 1:nface\n        if direction isa VerticalDirection\n            faces = (nface - 1):nface\n        elseif direction isa HorizontalDirection\n            faces = 1:(nface - 2)\n        end\n\n        ngradtransformstate = num_state_prognostic\n\n        # Create local arrays for states inside the element, wrt to a particular face (-)\n        local_state_prognostic⁻ = MArray{Tuple{ngradtransformstate}, FT}(undef)\n        local_state_auxiliary⁻ = MArray{Tuple{num_state_auxiliary}, FT}(undef)\n        local_transform⁻ = MArray{Tuple{ngradstate}, FT}(undef)\n        l_nG⁻ = MArray{Tuple{3, ngradstate}, FT}(undef)\n\n        # Create local arrays for states outside the element, wrt to a particular face (+)\n        local_state_prognostic⁺ = MArray{Tuple{ngradtransformstate}, FT}(undef)\n        local_state_auxiliary⁺ = MArray{Tuple{num_state_auxiliary}, FT}(undef)\n        local_transform⁺ = MArray{Tuple{ngradstate}, FT}(undef)\n\n        # FIXME state_gradient_flux is sort of a terrible name...\n        local_state_gradient_flux =\n            MArray{Tuple{num_state_gradient_flux}, FT}(undef)\n        local_transform_gradient = MArray{Tuple{3, ngradstate}, FT}(undef)\n        local_state_prognostic⁻visc =\n            MArray{Tuple{num_state_gradient_flux}, FT}(undef)\n\n        local_state_prognostic_bottom1 =\n            MArray{Tuple{num_state_prognostic}, FT}(undef)\n        local_state_auxiliary_bottom1 =\n            MArray{Tuple{num_state_auxiliary}, FT}(undef)\n    end\n\n    # Element index\n    eI = @index(Group, Linear)\n    # Index of a quadrature point on a face\n    n = @index(Local, Linear)\n\n    e = @private Int (1,)\n    @inbounds e[1] = elems[eI]\n\n    # Spin over the faces of the element `e`\n    @inbounds for f in faces\n        e⁻ = e[1]\n        normal_vector = SVector(\n            sgeo[_n1, n, f, e⁻],\n            sgeo[_n2, n, f, e⁻],\n            sgeo[_n3, n, f, e⁻],\n        )\n\n        # Extract surface mass operator `sM` and volumne mass inverse `vMI`\n        bctag = elemtobndy[f, e⁻]\n        sM, vMI = sgeo[_sM, n, f, e⁻], sgeo[_vMI, n, f, e⁻]\n        id⁻, id⁺ = vmap⁻[n, f, e⁻], vmap⁺[n, f, e⁻]\n        e⁺ = ((id⁺ - 1) ÷ Np) + 1\n\n        vid⁻, vid⁺ = ((id⁻ - 1) % Np) + 1, ((id⁺ - 1) % Np) + 1\n        if bctag != 0\n            # TODO: we will use vmap⁺ to store the boundary element info\n            #be = e⁺\n            #bcid = vid⁺\n            e⁺ = e⁻\n            vid⁺ = vid⁻\n        end\n\n        # Load minus side data\n        @unroll for s in 1:ngradtransformstate\n            local_state_prognostic⁻[s] = state_prognostic[vid⁻, s, e⁻]\n        end\n\n        @unroll for s in 1:num_state_auxiliary\n            local_state_auxiliary⁻[s] = state_auxiliary[vid⁻, s, e⁻]\n        end\n\n        # Compute G(q) on the minus side write the result into registers\n        fill!(local_transform⁻, -zero(eltype(local_transform⁻)))\n        compute_gradient_argument_arr!(\n            balance_law,\n            local_transform⁻,\n            local_state_prognostic⁻,\n            local_state_auxiliary⁻,\n            t,\n        )\n\n        # Load plus side data\n        @unroll for s in 1:ngradtransformstate\n            local_state_prognostic⁺[s] = state_prognostic[vid⁺, s, e⁺]\n        end\n\n        @unroll for s in 1:num_state_auxiliary\n            local_state_auxiliary⁺[s] = state_auxiliary[vid⁺, s, e⁺]\n        end\n\n        # Compute G(q) on the plus side and write the result into registers\n        fill!(local_transform⁺, -zero(eltype(local_transform⁺)))\n        compute_gradient_argument_arr!(\n            balance_law,\n            local_transform⁺,\n            local_state_prognostic⁺,\n            local_state_auxiliary⁺,\n            t,\n        )\n\n        # Oh drat, it's boundary conditions\n\n        fill!(\n            local_state_gradient_flux,\n            -zero(eltype(local_state_gradient_flux)),\n        )\n        if bctag == 0  # Periodic boundary condition (boundary-less)\n            # Computes G* on the minus side\n            numerical_flux_gradient!(\n                numerical_flux_gradient,\n                balance_law,\n                local_transform_gradient,\n                normal_vector,\n                local_transform⁻,\n                local_state_prognostic⁻,\n                local_state_auxiliary⁻,\n                local_transform⁺,\n                local_state_prognostic⁺,\n                local_state_auxiliary⁺,\n                t,\n            )\n            if num_state_gradient_flux > 0\n                # Applies linear transformation of gradients to the diffusive variables\n                # on the minus side\n                compute_gradient_flux_arr!(\n                    balance_law,\n                    local_state_gradient_flux,\n                    local_transform_gradient,\n                    local_state_prognostic⁻,\n                    local_state_auxiliary⁻,\n                    t,\n                )\n            end\n        else\n            # NOTE: Used for boundary conditions related to the energy\n            # variables (see `BulkFormulaEnergy`)\n            if (dim == 2 && f == 3) || (dim == 3 && f == 5)\n                if info.N[end] == 0\n                    # Loop up to next element for all horizontal elements\n                    @unroll for s in 1:num_state_prognostic\n                        local_state_prognostic_bottom1[s] =\n                            state_prognostic[n, s, e⁻ + 1]\n                    end\n                    @unroll for s in 1:num_state_auxiliary\n                        local_state_auxiliary_bottom1[s] =\n                            state_auxiliary[n, s, e⁻ + 1]\n                    end\n                else\n                    # Loop up the first element along all horizontal elements\n                    @unroll for s in 1:num_state_prognostic\n                        local_state_prognostic_bottom1[s] =\n                            state_prognostic[n + Nqk^2, s, e⁻]\n                    end\n                    @unroll for s in 1:num_state_auxiliary\n                        local_state_auxiliary_bottom1[s] =\n                            state_auxiliary[n + Nqk^2, s, e⁻]\n                    end\n                end\n            end\n            bcs = boundary_conditions(balance_law)\n            # TODO: there is probably a better way to unroll this loop\n            Base.Cartesian.@nif 7 d -> bctag == d <= length(bcs) d -> begin\n                bc = bcs[d]\n                # Computes G* incorporating boundary conditions\n                numerical_boundary_flux_gradient!(\n                    numerical_flux_gradient,\n                    bc,\n                    balance_law,\n                    local_transform_gradient,\n                    SVector(normal_vector),\n                    Vars{vars_state(balance_law, Gradient(), FT)}(\n                        local_transform⁻,\n                    ),\n                    Vars{vars_state(balance_law, Prognostic(), FT)}(\n                        local_state_prognostic⁻,\n                    ),\n                    Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                        local_state_auxiliary⁻,\n                    ),\n                    Vars{vars_state(balance_law, Gradient(), FT)}(\n                        local_transform⁺,\n                    ),\n                    Vars{vars_state(balance_law, Prognostic(), FT)}(\n                        local_state_prognostic⁺,\n                    ),\n                    Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                        local_state_auxiliary⁺,\n                    ),\n                    t,\n                    Vars{vars_state(balance_law, Prognostic(), FT)}(\n                        local_state_prognostic_bottom1,\n                    ),\n                    Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                        local_state_auxiliary_bottom1,\n                    ),\n                )\n                if num_state_gradient_flux > 0\n                    # Applies linear transformation of gradients to the diffusive variables\n                    # on the minus side\n                    compute_gradient_flux_arr!(\n                        balance_law,\n                        local_state_gradient_flux,\n                        local_transform_gradient,\n                        local_state_prognostic⁻,\n                        local_state_auxiliary⁻,\n                        t,\n                    )\n                end\n            end d -> throw(BoundsError(bcs, bctag))\n        end\n\n        # Compute n*G, where n is the face normal\n        @unroll for j in 1:ngradstate\n            @unroll for i in 1:3\n                l_nG⁻[i, j] = normal_vector[i] * local_transform⁻[j]\n            end\n        end\n\n        # Storing gradients for applying hyperdiffusion\n        @unroll for s in 1:ngradlapstate\n            j = hypervisc_indexmap[s]\n            Qhypervisc_grad[vid⁻, 3 * (s - 1) + 1, e⁻] +=\n                vMI * sM * (local_transform_gradient[1, j] - l_nG⁻[1, j])\n            Qhypervisc_grad[vid⁻, 3 * (s - 1) + 2, e⁻] +=\n                vMI * sM * (local_transform_gradient[2, j] - l_nG⁻[2, j])\n            Qhypervisc_grad[vid⁻, 3 * (s - 1) + 3, e⁻] +=\n                vMI * sM * (local_transform_gradient[3, j] - l_nG⁻[3, j])\n        end\n\n        # Applies linear transformation of gradients to the diffusive variables\n        # on the minus side\n        compute_gradient_flux_arr!(\n            balance_law,\n            local_state_prognostic⁻visc,\n            l_nG⁻,\n            local_state_prognostic⁻,\n            local_state_auxiliary⁻,\n            t,\n        )\n\n        # This is the surface integral evaluated discretely\n        # M^(-1) Mf(G* - G)\n        @unroll for s in 1:num_state_gradient_flux\n            state_gradient_flux[vid⁻, s, e⁻] +=\n                vMI *\n                sM *\n                (local_state_gradient_flux[s] - local_state_prognostic⁻visc[s])\n        end\n        # Need to wait after even faces to avoid race conditions\n        @synchronize(f % 2 == 0)\n    end\nend\n\n@kernel function kernel_init_state_prognostic!(\n    balance_law::BalanceLaw,\n    ::Val{dim},\n    ::Val{polyorder},\n    state,\n    state_auxiliary,\n    vgeo,\n    elems,\n    args...,\n) where {dim, polyorder}\n    N = polyorder\n    FT = eltype(state_auxiliary)\n    num_state_auxiliary = number_states(balance_law, Auxiliary())\n    num_state_prognostic = number_states(balance_law, Prognostic())\n\n    Nq = N .+ 1\n    @inbounds Nqk = dim == 2 ? 1 : Nq[dim]\n    @inbounds Np = Nq[1] * Nq[2] * Nqk\n\n    l_state = MArray{Tuple{num_state_prognostic}, FT}(undef)\n    local_state_auxiliary = MArray{Tuple{num_state_auxiliary}, FT}(undef)\n\n    I = @index(Global, Linear)\n    e = (I - 1) ÷ Np + 1\n    n = (I - 1) % Np + 1\n\n    @inbounds begin\n        @unroll for s in 1:num_state_auxiliary\n            local_state_auxiliary[s] = state_auxiliary[n, s, e]\n        end\n        @unroll for s in 1:num_state_prognostic\n            l_state[s] = state[n, s, e]\n        end\n        init_state_prognostic_arr!(\n            balance_law,\n            l_state,\n            local_state_auxiliary,\n            LocalGeometry{Np, N}(vgeo, n, e),\n            args...,\n        )\n        @unroll for s in 1:num_state_prognostic\n            state[n, s, e] = l_state[s]\n        end\n        @unroll for s in 1:num_state_auxiliary\n            state_auxiliary[n, s, e] = local_state_auxiliary[s]\n        end\n    end\nend\n\n\n@doc \"\"\"\n    kernel_nodal_init_state_auxiliary!(balance_law::BalanceLaw, Val(polyorder),\n                                       init_f!, state_auxiliary, state_init,\n                                       Val(vars_state_init), vgeo, elems)\n\nComputational kernel: Initialize the auxiliary state\n\nSee [`BalanceLaw`](@ref) for usage.\n\"\"\" kernel_nodal_init_state_auxiliary!\n@kernel function kernel_nodal_init_state_auxiliary!(\n    balance_law::BalanceLaw,\n    ::Val{dim},\n    ::Val{polyorder},\n    init_f!,\n    state_auxiliary,\n    state_temporary,\n    ::Val{vars_state_temporary},\n    vgeo,\n    elems,\n) where {dim, polyorder, vars_state_temporary}\n    N = polyorder\n    FT = eltype(state_auxiliary)\n    num_state_auxiliary = number_states(balance_law, Auxiliary())\n    num_state_temporary = varsize(vars_state_temporary)\n\n    Nq = N .+ 1\n    @inbounds Nqk = dim == 2 ? 1 : Nq[dim]\n    @inbounds Np = Nq[1] * Nq[2] * Nqk\n\n    local_state_auxiliary = MArray{Tuple{num_state_auxiliary}, FT}(undef)\n    local_state_temporary = MArray{Tuple{num_state_temporary}, FT}(undef)\n\n    I = @index(Global, Linear)\n    e = (I - 1) ÷ Np + 1\n    n = (I - 1) % Np + 1\n\n    @inbounds begin\n        @unroll for s in 1:num_state_auxiliary\n            local_state_auxiliary[s] = state_auxiliary[n, s, e]\n        end\n\n        @unroll for s in 1:num_state_temporary\n            local_state_temporary[s] = state_temporary[n, s, e]\n        end\n\n        init_f!(\n            balance_law,\n            Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                local_state_auxiliary,\n            ),\n            Vars{vars_state_temporary}(local_state_temporary),\n            LocalGeometry{Np, N}(vgeo, n, e),\n        )\n\n        @unroll for s in 1:num_state_auxiliary\n            state_auxiliary[n, s, e] = local_state_auxiliary[s]\n        end\n    end\nend\n\n@doc \"\"\"\n    kernel_nodal_update_auxiliary_state!(balance_law::BalanceLaw, ::Val{dim}, ::Val{N}, f!, state_prognostic, state_auxiliary, [state_gradient_flux,]\n                          t, elems, activedofs) where {dim, N}\n\nUpdate the auxiliary state array\n\"\"\" kernel_nodal_update_auxiliary_state!\n@kernel function kernel_nodal_update_auxiliary_state!(\n    balance_law::BalanceLaw,\n    ::Val{dim},\n    ::Val{N},\n    f!,\n    state_prognostic,\n    state_auxiliary,\n    t,\n    elems,\n    activedofs,\n) where {dim, N}\n    FT = eltype(state_prognostic)\n    num_state_prognostic = number_states(balance_law, Prognostic())\n    num_state_auxiliary = number_states(balance_law, Auxiliary())\n\n    Nq = N .+ 1\n    @inbounds Nqk = dim == 2 ? 1 : Nq[dim]\n    @inbounds Np = Nq[1] * Nq[2] * Nqk\n\n    local_state_prognostic = MArray{Tuple{num_state_prognostic}, FT}(undef)\n    local_state_auxiliary = MArray{Tuple{num_state_auxiliary}, FT}(undef)\n\n    I = @index(Global, Linear)\n    eI = (I - 1) ÷ Np + 1\n    n = (I - 1) % Np + 1\n\n    @inbounds begin\n        e = elems[eI]\n\n        active = activedofs[n + (e - 1) * Np]\n\n        if active\n            @unroll for s in 1:num_state_prognostic\n                local_state_prognostic[s] = state_prognostic[n, s, e]\n            end\n\n            @unroll for s in 1:num_state_auxiliary\n                local_state_auxiliary[s] = state_auxiliary[n, s, e]\n            end\n\n            f!(\n                balance_law,\n                Vars{vars_state(balance_law, Prognostic(), FT)}(\n                    local_state_prognostic,\n                ),\n                Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                    local_state_auxiliary,\n                ),\n                t,\n            )\n\n            @unroll for s in 1:num_state_auxiliary\n                state_auxiliary[n, s, e] = local_state_auxiliary[s]\n            end\n        end\n    end\nend\n\n@kernel function kernel_nodal_update_auxiliary_state!(\n    balance_law::BalanceLaw,\n    ::Val{dim},\n    ::Val{N},\n    f!,\n    state_prognostic,\n    state_auxiliary,\n    state_gradient_flux,\n    t,\n    elems,\n    activedofs,\n) where {dim, N}\n    FT = eltype(state_prognostic)\n    num_state_prognostic = number_states(balance_law, Prognostic())\n    num_state_gradient_flux = number_states(balance_law, GradientFlux())\n    num_state_auxiliary = number_states(balance_law, Auxiliary())\n\n    Nq = N .+ 1\n    @inbounds Nqk = dim == 2 ? 1 : Nq[dim]\n    @inbounds Np = Nq[1] * Nq[2] * Nqk\n\n    local_state_prognostic = MArray{Tuple{num_state_prognostic}, FT}(undef)\n    local_state_auxiliary = MArray{Tuple{num_state_auxiliary}, FT}(undef)\n    local_state_gradient_flux =\n        MArray{Tuple{num_state_gradient_flux}, FT}(undef)\n\n    I = @index(Global, Linear)\n    eI = (I - 1) ÷ Np + 1\n    n = (I - 1) % Np + 1\n\n    @inbounds begin\n        e = elems[eI]\n\n        active = activedofs[n + (e - 1) * Np]\n\n        if active\n            @unroll for s in 1:num_state_prognostic\n                local_state_prognostic[s] = state_prognostic[n, s, e]\n            end\n\n            @unroll for s in 1:num_state_auxiliary\n                local_state_auxiliary[s] = state_auxiliary[n, s, e]\n            end\n\n            @unroll for s in 1:num_state_gradient_flux\n                local_state_gradient_flux[s] = state_gradient_flux[n, s, e]\n            end\n\n            f!(\n                balance_law,\n                Vars{vars_state(balance_law, Prognostic(), FT)}(\n                    local_state_prognostic,\n                ),\n                Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                    local_state_auxiliary,\n                ),\n                Vars{vars_state(balance_law, GradientFlux(), FT)}(\n                    local_state_gradient_flux,\n                ),\n                t,\n            )\n\n            @unroll for s in 1:num_state_auxiliary\n                state_auxiliary[n, s, e] = local_state_auxiliary[s]\n            end\n        end\n    end\nend\n\n@doc \"\"\"\n    kernel_indefinite_stack_integral!(balance_law::BalanceLaw, ::Val{dim}, ::Val{N},\n                                  ::Val{nvertelem}, state_prognostic, state_auxiliary, vgeo,\n                                  Imat, elems) where {dim, N, nvertelem}\nComputational kernel: compute indefinite integral along the vertical stack\nSee [`BalanceLaw`](@ref) for usage.\n\"\"\" kernel_indefinite_stack_integral!\n@kernel function kernel_indefinite_stack_integral!(\n    balance_law::BalanceLaw,\n    ::Val{dim},\n    ::Val{N},\n    ::Val{nvertelem},\n    state_prognostic,\n    state_auxiliary,\n    vgeo,\n    Imat,\n    elems,\n) where {dim, N, nvertelem}\n    @uniform begin\n        FT = eltype(state_prognostic)\n        num_state_prognostic = number_states(balance_law, Prognostic())\n        num_state_auxiliary = number_states(balance_law, Auxiliary())\n        nout = number_states(balance_law, UpwardIntegrals())\n\n        # Number of Gauss-Lobatto quadrature points in each direction\n        Nq = N .+ 1\n        Nq1 = Nq[1]\n        Nq2 = dim == 2 ? 1 : Nq[2]\n        Nq3 = Nq[end]\n\n        local_state_prognostic = MArray{Tuple{num_state_prognostic}, FT}(undef)\n        local_state_auxiliary = MArray{Tuple{num_state_auxiliary}, FT}(undef)\n        local_kernel = MArray{Tuple{nout, Nq3}, FT}(undef)\n    end\n\n    local_integral = @private FT (nout, Nq1)\n    s_I = @localmem FT (Nq3, Nq3)\n\n    _eh = @index(Group, Linear)\n    i, j = @index(Local, NTuple)\n\n    @inbounds begin\n        @unroll for iv in i:Nq1:Nq3\n            @unroll for jv in j:Nq2:Nq3\n                s_I[iv, jv] = Imat[iv, jv]\n            end\n        end\n        @synchronize\n\n        # Initialize the constant state at zero\n        @unroll for k in 1:Nq3\n            @unroll for s in 1:nout\n                local_integral[s, k] = 0\n            end\n        end\n\n        eh = elems[_eh]\n\n        # Loop up the stack of elements\n        for ev in 1:nvertelem\n            e = ev + (eh - 1) * nvertelem\n\n            # Evaluate the integral kernel at each DOF in the slabk\n            # loop up the pencil\n            @unroll for k in 1:Nq3\n                ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n                Jc = vgeo[ijk, _JcV, e]\n                @unroll for s in 1:num_state_prognostic\n                    local_state_prognostic[s] = state_prognostic[ijk, s, e]\n                end\n\n                @unroll for s in 1:num_state_auxiliary\n                    local_state_auxiliary[s] = state_auxiliary[ijk, s, e]\n                end\n\n                integral_load_auxiliary_state_arr!(\n                    balance_law,\n                    view(local_kernel, :, k),\n                    local_state_prognostic,\n                    local_state_auxiliary,\n                )\n\n                # Multiply in the curve jacobian\n                @unroll for s in 1:nout\n                    local_kernel[s, k] *= Jc\n                end\n            end\n\n            # Evaluate the integral up the element\n            @unroll for s in 1:nout\n                @unroll for k in 1:Nq3\n                    @unroll for n in 1:Nq3\n                        local_integral[s, k] += s_I[k, n] * local_kernel[s, n]\n                    end\n                end\n            end\n\n            # Store out to memory and reset the background value for next element\n            @unroll for k in 1:Nq3\n                @unroll for s in 1:nout\n                    local_kernel[s, k] = local_integral[s, k]\n                end\n                ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n                integral_set_auxiliary_state_arr!(\n                    balance_law,\n                    view(state_auxiliary, ijk, :, e),\n                    view(local_kernel, :, k),\n                )\n                @unroll for ind_out in 1:nout\n                    local_integral[ind_out, k] = local_integral[ind_out, Nq3]\n                end\n            end\n        end\n    end\nend\n\n@kernel function kernel_reverse_indefinite_stack_integral!(\n    balance_law::BalanceLaw,\n    ::Val{dim},\n    ::Val{N},\n    ::Val{nvertelem},\n    state,\n    state_auxiliary,\n    elems,\n) where {dim, N, nvertelem}\n    @uniform begin\n        FT = eltype(state_auxiliary)\n\n        # Number of Gauss-Lobatto quadrature points in each direction\n        Nq = N .+ 1\n        Nq1 = Nq[1]\n        Nq2 = dim == 2 ? 1 : Nq[2]\n        Nq3 = Nq[end]\n        nout = number_states(balance_law, DownwardIntegrals())\n\n        # Note that k is the second not 4th index (since this is scratch memory and k\n        # needs to be persistent across threads)\n        l_T = MArray{Tuple{nout}, FT}(undef)\n        l_V = MArray{Tuple{nout}, FT}(undef)\n    end\n\n    _eh = @index(Group, Linear)\n    i, j = @index(Local, NTuple)\n\n    @inbounds begin\n        eh = elems[_eh]\n\n        # Extract the top value from the top element which is degree of freedom\n        # (i, j, Nq3)\n        ijk = i + Nq1 * ((j - 1) + Nq2 * (Nq3 - 1))\n        et = nvertelem + (eh - 1) * nvertelem\n        reverse_integral_load_auxiliary_state_arr!(\n            balance_law,\n            l_T,\n            view(state, ijk, :, et),\n            view(state_auxiliary, ijk, :, et),\n        )\n\n        # Loop up the stack of elements\n        #\n        # In the case of N = 0 the forward integral computed the top face\n        # integral value, when reversing we want to store the bottom face value\n        # (there is no need to store the top face value of the top element since\n        # the reverse integral will be zero there, whereas in the forward case\n        # the bottom face of the first element was zero).\n        #\n        # This loop gets complicated in this case since we have a shifting of\n        # the element values.\n\n        # Loop limits for N = 0 versus N > 0\n        for ev in (Nq3 == 1 ? (1:(nvertelem - 1)) : (1:nvertelem))\n            e = ev + (eh - 1) * nvertelem\n            @unroll for k in 1:Nq3\n                ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n                reverse_integral_load_auxiliary_state_arr!(\n                    balance_law,\n                    l_V,\n                    view(state, ijk, :, e),\n                    view(state_auxiliary, ijk, :, e),\n                )\n                l_V .= l_T .- l_V\n                reverse_integral_set_auxiliary_state_arr!(\n                    balance_law,\n                    view(\n                        state_auxiliary,\n                        ijk,\n                        :,\n                        # In the N = 0 case we shift the data up\n                        Nq3 == 1 ? e + 1 : e,\n                    ),\n                    l_V,\n                )\n            end\n        end\n        # We need to update the first vertical element value still with the very\n        # top value\n        if Nq3 == 1\n            ev = 1\n            k = 1\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            e = ev + (eh - 1) * nvertelem\n            reverse_integral_set_auxiliary_state_arr!(\n                balance_law,\n                view(state_auxiliary, ijk, :, e),\n                l_T,\n            )\n        end\n    end\nend\n\n\"\"\"\n    function volume_divergence_of_gradients!(\n        balance_law::BalanceLaw,\n        ::Val{info},\n        direction,\n        Qhypervisc_grad,\n        Qhypervisc_div,\n        vgeo,\n        D,\n        elems,\n        increment = false,\n    )\n\nCompute kernel for evaluating the volume divergence of gradients (or,\nequivalently, the scalar laplacian) for the DG form:\n\n∫ₑ ψ⋅ΔG dx - ∫ₑ ∇ψ⋅∇G dx + ∮ₑ n̂ ψ⋅(∇G)⋆ dS,\n\nor equivalently in matrix form:\n\nΔG = M⁻¹(DᵀM ∇G + ∑ᶠ LᵀMf (∇G)⋆).\n\nThis kernel computes the volume terms: M⁻¹(DᵀM ∇G),\nwhere M is the mass matrix and D is the differentiation matrix,\nand ∇G are the gradients.\n\"\"\"\n@kernel function volume_divergence_of_gradients!(\n    balance_law::BalanceLaw,\n    ::Val{info},\n    direction,\n    Qhypervisc_grad,\n    Qhypervisc_div,\n    vgeo,\n    D,\n    elems,\n    increment = false,\n) where {info}\n    @uniform begin\n        dim = info.dim\n        FT = eltype(Qhypervisc_grad)\n        ngradlapstate = number_states(balance_law, GradientLaplacian())\n\n        @inbounds Nq1 = info.Nq[1]\n        @inbounds Nq2 = info.Nq[2]\n        Nq3 = info.Nqk\n    end\n\n    s_grad = @localmem FT (2, Nq1, Nq2, ngradlapstate)\n\n    local_div = @private FT (Nq3, ngradlapstate)\n    local_MI = @private FT (Nq3,)\n\n    e = @index(Group, Linear)\n    i, j = @index(Local, NTuple)\n\n    @inbounds begin\n        @unroll for k in 1:Nq3\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            # initialize local tendency\n            @unroll for s in 1:ngradlapstate\n                local_div[k, s] = zero(FT)\n            end\n            # read in mass matrix inverse for element `e`\n            local_MI[k] = vgeo[ijk, _MI, e]\n        end\n\n        @unroll for k in 1:Nq3\n            @synchronize\n\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n            M = vgeo[ijk, _M, e]\n\n            # Extract Jacobian terms ∂ξᵢ/∂xⱼ\n            ξ1x1, ξ1x2, ξ1x3 =\n                vgeo[ijk, _ξ1x1, e], vgeo[ijk, _ξ1x2, e], vgeo[ijk, _ξ1x3, e]\n            if dim == 3 || (dim == 2 && direction isa EveryDirection)\n                ξ2x1, ξ2x2, ξ2x3 = vgeo[ijk, _ξ2x1, e],\n                vgeo[ijk, _ξ2x2, e],\n                vgeo[ijk, _ξ2x3, e]\n            end\n\n            @unroll for s in 1:ngradlapstate\n                G1 = Qhypervisc_grad[ijk, 3 * (s - 1) + 1, e]\n                G2 = Qhypervisc_grad[ijk, 3 * (s - 1) + 2, e]\n                G3 = Qhypervisc_grad[ijk, 3 * (s - 1) + 3, e]\n\n                s_grad[1, i, j, s] = M * (ξ1x1 * G1 + ξ1x2 * G2 + ξ1x3 * G3)\n                if dim == 3\n                    s_grad[2, i, j, s] = M * (ξ2x1 * G1 + ξ2x2 * G2 + ξ2x3 * G3)\n                end\n            end\n            @synchronize\n\n            MI = local_MI[k]\n            @unroll for s in 1:ngradlapstate\n                @unroll for n in 1:Nq1\n                    Dni = D[n, i]\n                    local_div[k, s] -= MI * Dni * s_grad[1, n, j, s]\n                    if dim == 3\n                        Dnj = D[n, j]\n                        local_div[k, s] -= MI * Dnj * s_grad[2, i, n, s]\n                    end\n                end\n            end\n        end\n\n        @unroll for k in 1:Nq3\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            @unroll for s in 1:ngradlapstate\n                if increment\n                    Qhypervisc_div[ijk, s, e] += local_div[k, s]\n                else\n                    Qhypervisc_div[ijk, s, e] = local_div[k, s]\n                end\n            end\n        end\n    end\nend\n\n@kernel function volume_divergence_of_gradients!(\n    balance_law::BalanceLaw,\n    ::Val{info},\n    ::VerticalDirection,\n    Qhypervisc_grad,\n    Qhypervisc_div,\n    vgeo,\n    D,\n    elems,\n    increment = false,\n) where {info}\n    @uniform begin\n        dim = info.dim\n        FT = eltype(Qhypervisc_grad)\n        ngradlapstate = number_states(balance_law, GradientLaplacian())\n\n        @inbounds Nq1 = info.Nq[1]\n        @inbounds Nq2 = info.Nq[2]\n        Nq3 = info.Nqk\n\n        l_grad = MArray{Tuple{ngradlapstate}, FT}(undef)\n\n        @inbounds Nqv = dim == 2 ? Nq2 : info.Nq[dim]\n        s_grad_size = dim == 2 ? (Nq1, Nqv, ngradlapstate) : (0, 0, 0)\n    end\n\n    local_div = @private FT (Nq3, ngradlapstate)\n    local_MI = @private FT (Nq3,)\n    s_grad = @localmem FT s_grad_size\n\n    e = @index(Group, Linear)\n    i, j = @index(Local, NTuple)\n\n    @inbounds begin\n        @unroll for k in 1:Nq3\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            # initialize local tendency\n            @unroll for s in 1:ngradlapstate\n                local_div[k, s] = zero(FT)\n            end\n            # read in mass matrix inverse for element `e`\n            local_MI[k] = vgeo[ijk, _MI, e]\n        end\n\n        @unroll for k in 1:Nq3\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n            M = vgeo[ijk, _M, e]\n\n            if dim == 2\n                ξ2x1, ξ2x2, ξ2x3 = vgeo[ijk, _ξ2x1, e],\n                vgeo[ijk, _ξ2x2, e],\n                vgeo[ijk, _ξ2x3, e]\n            else\n                ξ3x1, ξ3x2, ξ3x3 = vgeo[ijk, _ξ3x1, e],\n                vgeo[ijk, _ξ3x2, e],\n                vgeo[ijk, _ξ3x3, e]\n            end\n\n            @unroll for s in 1:ngradlapstate\n                G1 = Qhypervisc_grad[ijk, 3 * (s - 1) + 1, e]\n                G2 = Qhypervisc_grad[ijk, 3 * (s - 1) + 2, e]\n                G3 = Qhypervisc_grad[ijk, 3 * (s - 1) + 3, e]\n\n                if dim == 2\n                    s_grad[i, j, s] = M * (ξ2x1 * G1 + ξ2x2 * G2 + ξ2x3 * G3)\n                else\n                    l_grad[s] = M * (ξ3x1 * G1 + ξ3x2 * G2 + ξ3x3 * G3)\n                end\n            end\n\n            if dim == 3\n                @unroll for n in 1:Nq3\n                    MI = local_MI[n]\n                    @unroll for s in 1:ngradlapstate\n                        local_div[n, s] -= MI * D[k, n] * l_grad[s]\n                    end\n                end\n            end\n\n            @synchronize(dim == 2)\n\n            if dim == 2\n                MI = local_MI[k]\n                @unroll for n in 1:Nqv\n                    @unroll for s in 1:ngradlapstate\n                        local_div[k, s] -= MI * D[n, j] * s_grad[i, n, s]\n                    end\n                end\n            end\n        end\n\n        @unroll for k in 1:Nq3\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            @unroll for s in 1:ngradlapstate\n                if increment\n                    Qhypervisc_div[ijk, s, e] += local_div[k, s]\n                else\n                    Qhypervisc_div[ijk, s, e] = local_div[k, s]\n                end\n            end\n        end\n    end\nend\n\n\"\"\"\n    function interface_divergence_of_gradients!(\n        balance_law::BalanceLaw,\n        ::Val{info},\n        direction,\n        divgradnumpenalty,\n        Qhypervisc_grad,\n        Qhypervisc_div,\n        vgeo,\n        sgeo,\n        vmap⁻,\n        vmap⁺,\n        elemtobndy,\n        elems,\n    )\n\nCompute kernel for evaluating the interface divergence of gradients (or,\nequivalently, the scalar laplacian) for the DG form:\n\n∫ₑ ψ⋅ΔG dx - ∫ₑ ∇ψ⋅∇G dx + ∮ₑ n̂ ψ⋅(∇G)⋆ dS,\n\nor equivalently in matrix form:\n\nΔG = M⁻¹(DᵀM ∇G + ∑ᶠ LᵀMf (∇G)⋆).\n\nThis kernel computes the interface terms: M⁻¹∑ᶠ LᵀMf (∇G)⋆\nwhere M is the mass matrix, Mf is the face mass matrix, L is an interpolator\nfrom volume to face, and (∇G)⋆ is the numerical fluxes for the gradients.\n\"\"\"\n@kernel function interface_divergence_of_gradients!(\n    balance_law::BalanceLaw,\n    ::Val{info},\n    direction,\n    divgradnumpenalty,\n    Qhypervisc_grad,\n    Qhypervisc_div,\n    state_auxiliary,\n    vgeo,\n    sgeo,\n    vmap⁻,\n    vmap⁺,\n    elemtobndy,\n    t,\n    elems,\n) where {info}\n    @uniform begin\n        dim = info.dim\n        FT = eltype(Qhypervisc_grad)\n        ngradlapstate = number_states(balance_law, GradientLaplacian())\n        nauxstate = number_states(balance_law, Auxiliary())\n        nface = info.nface\n        Np = info.Np\n        Nqk = info.Nqk\n\n        faces = 1:nface\n        if direction isa VerticalDirection\n            faces = (nface - 1):nface\n        elseif direction isa HorizontalDirection\n            faces = 1:(nface - 2)\n        end\n\n        l_state_auxiliary⁻ = MArray{Tuple{nauxstate}, FT}(undef)\n        l_state_auxiliary⁺ = MArray{Tuple{nauxstate}, FT}(undef)\n        l_grad⁻ = MArray{Tuple{3, ngradlapstate}, FT}(undef)\n        l_grad⁺ = MArray{Tuple{3, ngradlapstate}, FT}(undef)\n        l_div = MArray{Tuple{ngradlapstate}, FT}(undef)\n    end\n\n    eI = @index(Group, Linear)\n    n = @index(Local, Linear)\n\n    e = @private Int (1,)\n    @inbounds e[1] = elems[eI]\n\n    @inbounds for f in faces\n        e⁻ = e[1]\n        normal_vector = SVector(\n            sgeo[_n1, n, f, e⁻],\n            sgeo[_n2, n, f, e⁻],\n            sgeo[_n3, n, f, e⁻],\n        )\n        bctag = elemtobndy[f, e⁻]\n        sM, vMI = sgeo[_sM, n, f, e⁻], sgeo[_vMI, n, f, e⁻]\n        id⁻, id⁺ = vmap⁻[n, f, e⁻], vmap⁺[n, f, e⁻]\n        e⁺ = ((id⁺ - 1) ÷ Np) + 1\n\n        vid⁻, vid⁺ = ((id⁻ - 1) % Np) + 1, ((id⁺ - 1) % Np) + 1\n        if bctag != 0\n            # TODO: we will use vmap⁺ to store the boundary element info\n            #be = e⁺\n            #bcid = vid⁺\n            e⁺ = e⁻\n            vid⁺ = vid⁻\n        end\n\n        # Load minus side data\n        @unroll for s in 1:ngradlapstate\n            l_grad⁻[1, s] = Qhypervisc_grad[vid⁻, 3 * (s - 1) + 1, e⁻]\n            l_grad⁻[2, s] = Qhypervisc_grad[vid⁻, 3 * (s - 1) + 2, e⁻]\n            l_grad⁻[3, s] = Qhypervisc_grad[vid⁻, 3 * (s - 1) + 3, e⁻]\n        end\n\n        # Load plus side data\n        @unroll for s in 1:ngradlapstate\n            l_grad⁺[1, s] = Qhypervisc_grad[vid⁺, 3 * (s - 1) + 1, e⁺]\n            l_grad⁺[2, s] = Qhypervisc_grad[vid⁺, 3 * (s - 1) + 2, e⁺]\n            l_grad⁺[3, s] = Qhypervisc_grad[vid⁺, 3 * (s - 1) + 3, e⁺]\n        end\n\n        if bctag == 0\n            numerical_flux_divergence!(\n                divgradnumpenalty,\n                balance_law,\n                Vars{vars_state(balance_law, GradientLaplacian(), FT)}(l_div),\n                normal_vector,\n                Grad{vars_state(balance_law, GradientLaplacian(), FT)}(l_grad⁻),\n                Grad{vars_state(balance_law, GradientLaplacian(), FT)}(l_grad⁺),\n            )\n        else\n            # load state auxiliary (only needed for bcs !)\n            @unroll for s in 1:nauxstate\n                l_state_auxiliary⁻[s] = state_auxiliary[vid⁻, s, e⁻]\n            end\n\n            @unroll for s in 1:nauxstate\n                l_state_auxiliary⁺[s] = state_auxiliary[vid⁺, s, e⁺]\n            end\n\n            bcs = boundary_conditions(balance_law)\n            # TODO: there is probably a better way to unroll this loop\n            Base.Cartesian.@nif 7 d -> bctag == d <= length(bcs) d -> begin\n                bc = bcs[d]\n                numerical_boundary_flux_divergence!(\n                    divgradnumpenalty,\n                    bc,\n                    balance_law,\n                    Vars{vars_state(balance_law, GradientLaplacian(), FT)}(\n                        l_div,\n                    ),\n                    normal_vector,\n                    Grad{vars_state(balance_law, GradientLaplacian(), FT)}(\n                        l_grad⁻,\n                    ),\n                    Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                        l_state_auxiliary⁻,\n                    ),\n                    Grad{vars_state(balance_law, GradientLaplacian(), FT)}(\n                        l_grad⁺,\n                    ),\n                    Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                        l_state_auxiliary⁺,\n                    ),\n                    t,\n                )\n            end d -> throw(BoundsError(bcs, bctag))\n        end\n\n        @unroll for s in 1:ngradlapstate\n            Qhypervisc_div[vid⁻, s, e⁻] += vMI * sM * l_div[s]\n        end\n        # Need to wait after even faces to avoid race conditions\n        @synchronize(f % 2 == 0)\n    end\nend\n\n\"\"\"\n    function volume_gradients_of_laplacians!(\n        balance_law::BalanceLaw,\n        ::Val{info},\n        direction,\n        Qhypervisc_grad,\n        Qhypervisc_div,\n        state_prognostic,\n        state_auxiliary,\n        vgeo,\n        ω,\n        D,\n        elems,\n        t,\n        increment = false,\n    ) where {info}\n\nComputes the volume integral for the auxiliary equation\n(in DG strong form):\n\n∫ₑ ψI⋅η dx = ∫ₑ ψI⋅∇ΔG dx + ∮ₑ nψI⋅((ΔG)⋆ - ΔG) dS,\n\nor equivalently in matrix notation:\n\nη = M⁻¹ LᵀMf((ΔG)⋆ - ΔG) + D ΔG\n\nThis kernel computes the volume gradient: D * ΔG, where\nD is the differentiation matrix and ΔG is the laplacian\n\"\"\"\n@kernel function volume_gradients_of_laplacians!(\n    balance_law::BalanceLaw,\n    ::Val{info},\n    direction,\n    Qhypervisc_grad,\n    Qhypervisc_div,\n    state_prognostic,\n    state_auxiliary,\n    vgeo,\n    ω,\n    D,\n    elems,\n    t,\n    increment = false,\n) where {info}\n    @uniform begin\n        dim = info.dim\n\n        FT = eltype(Qhypervisc_grad)\n        num_state_prognostic = number_states(balance_law, Prognostic())\n        ngradlapstate = number_states(balance_law, GradientLaplacian())\n        nhyperviscstate = number_states(balance_law, Hyperdiffusive())\n        num_state_auxiliary = number_states(balance_law, Auxiliary())\n        ngradtransformstate = num_state_prognostic\n\n        @inbounds Nq1 = info.Nq[1]\n        @inbounds Nq2 = info.Nq[2]\n        Nq3 = info.Nqk\n\n        local_state_hyperdiffusion = MArray{Tuple{nhyperviscstate}, FT}(undef)\n    end\n\n    s_lap = @localmem FT (Nq1, Nq2, ngradlapstate)\n    local_state_prognostic = @private FT (ngradtransformstate, Nq3)\n    local_state_auxiliary = @private FT (num_state_auxiliary, Nq3)\n    l_grad_lap = @private FT (3, ngradlapstate, Nq3)\n    lap_ξ3 = @private FT (ngradlapstate, Nq3)\n\n    e = @index(Group, Linear)\n    i, j = @index(Local, NTuple)\n\n    @inbounds @views begin\n        @unroll for k in 1:Nq3\n            @unroll for s in 1:ngradlapstate\n                l_grad_lap[1, s, k] = -zero(FT)\n                l_grad_lap[2, s, k] = -zero(FT)\n                l_grad_lap[3, s, k] = -zero(FT)\n                lap_ξ3[s, k] = -zero(FT)\n            end\n\n            # Load prognostic and auxiliary variables\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            @unroll for s in 1:ngradtransformstate\n                local_state_prognostic[s, k] = state_prognostic[ijk, s, e]\n            end\n            @unroll for s in 1:num_state_auxiliary\n                local_state_auxiliary[s, k] = state_auxiliary[ijk, s, e]\n            end\n        end\n\n        @unroll for k in 1:Nq3\n            # store laplacian into shared memory\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            @unroll for s in 1:ngradlapstate\n                s_lap[i, j, s] = Qhypervisc_div[ijk, s, e]\n            end\n            @synchronize\n\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n            ξ1x1, ξ1x2, ξ1x3 =\n                vgeo[ijk, _ξ1x1, e], vgeo[ijk, _ξ1x2, e], vgeo[ijk, _ξ1x3, e]\n\n            # Compute gradient of each state\n            @unroll for s in 1:ngradlapstate\n                lap_ξ1 = lap_ξ2 = zero(FT)\n\n                @unroll for n in 1:Nq1\n                    lap_ξ1 += D[i, n] * s_lap[n, j, s]\n                    if dim == 3 || (dim == 2 && direction isa EveryDirection)\n                        lap_ξ2 += D[j, n] * s_lap[i, n, s]\n                    end\n                    if dim == 3 && direction isa EveryDirection\n                        lap_ξ3[s, n] += D[n, k] * s_lap[i, j, s]\n                    end\n                end\n\n                # Application of chain-rule in ξ1 and ξ2 directions,\n                # ∂G/∂xi = ∂ξ1/∂xi * ∂G/∂ξ1, ∂G/∂xi = ∂ξ2/∂xi * ∂G/∂ξ2\n                # to get a physical gradient\n                l_grad_lap[1, s, k] = ξ1x1 * lap_ξ1\n                l_grad_lap[2, s, k] = ξ1x2 * lap_ξ1\n                l_grad_lap[3, s, k] = ξ1x3 * lap_ξ1\n\n                if dim == 3 || (dim == 2 && direction isa EveryDirection)\n                    ξ2x1, ξ2x2, ξ2x3 = vgeo[ijk, _ξ2x1, e],\n                    vgeo[ijk, _ξ2x2, e],\n                    vgeo[ijk, _ξ2x3, e]\n                    l_grad_lap[1, s, k] += ξ2x1 * lap_ξ2\n                    l_grad_lap[2, s, k] += ξ2x2 * lap_ξ2\n                    l_grad_lap[3, s, k] += ξ2x3 * lap_ξ2\n                end\n\n            end\n\n            # Synchronize threads on the device\n            @synchronize\n        end\n\n        @unroll for k in 1:Nq3\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n            # Application of chain-rule in ξ3-direction: ∂G/∂xi = ∂ξ3/∂xi * ∂G/∂ξ3\n            if dim == 3 && direction isa EveryDirection\n                ξ3x1, ξ3x2, ξ3x3 = vgeo[ijk, _ξ3x1, e],\n                vgeo[ijk, _ξ3x2, e],\n                vgeo[ijk, _ξ3x3, e]\n                l_grad_lap[1, s, k] += ξ3x1 * lap_ξ3[s, k]\n                l_grad_lap[2, s, k] += ξ3x2 * lap_ξ3[s, k]\n                l_grad_lap[3, s, k] += ξ3x3 * lap_ξ3[s, k]\n            end\n\n            fill!(\n                local_state_hyperdiffusion,\n                -zero(eltype(local_state_hyperdiffusion)),\n            )\n\n            # Applies a linear transformation of gradients to the hyperdiffusive variables\n            transform_post_gradient_laplacian_arr!(\n                balance_law,\n                local_state_hyperdiffusion,\n                l_grad_lap[:, :, k],\n                local_state_prognostic[:, k],\n                local_state_auxiliary[:, k],\n                t,\n            )\n\n            # Write out the result of the kernel to global memory\n            @unroll for s in 1:nhyperviscstate\n                if increment\n                    Qhypervisc_grad[ijk, s, e] += local_state_hyperdiffusion[s]\n                else\n                    Qhypervisc_grad[ijk, s, e] = local_state_hyperdiffusion[s]\n                end\n            end\n        end\n    end\nend\n\n@kernel function volume_gradients_of_laplacians!(\n    balance_law::BalanceLaw,\n    ::Val{info},\n    ::VerticalDirection,\n    Qhypervisc_grad,\n    Qhypervisc_div,\n    state_prognostic,\n    state_auxiliary,\n    vgeo,\n    ω,\n    D,\n    elems,\n    t,\n    increment = false,\n) where {info}\n    @uniform begin\n        dim = info.dim\n\n        FT = eltype(Qhypervisc_grad)\n        num_state_prognostic = number_states(balance_law, Prognostic())\n        ngradlapstate = number_states(balance_law, GradientLaplacian())\n        nhyperviscstate = number_states(balance_law, Hyperdiffusive())\n        num_state_auxiliary = number_states(balance_law, Auxiliary())\n        ngradtransformstate = num_state_prognostic\n\n        @inbounds Nq1 = info.Nq[1]\n        @inbounds Nq2 = info.Nq[2]\n        Nq3 = info.Nqk\n\n        _ζx1 = dim == 2 ? _ξ2x1 : _ξ3x1\n        _ζx2 = dim == 2 ? _ξ2x2 : _ξ3x2\n        _ζx3 = dim == 2 ? _ξ2x3 : _ξ3x3\n\n        @inbounds Nqv = dim == 2 ? Nq2 : info.Nq[dim]\n        lap_ζ_size = dim == 3 ? (ngradlapstate, Nq3) : (0, 0)\n        shared_lap_dim2 = dim == 2 ? Nqv : Nq1\n\n        local_state_hyperdiffusion = MArray{Tuple{nhyperviscstate}, FT}(undef)\n    end\n\n    s_lap = @localmem FT (Nq1, shared_lap_dim2, ngradlapstate)\n    local_state_prognostic = @private FT (ngradtransformstate, Nq3)\n    local_state_auxiliary = @private FT (num_state_auxiliary, Nq3)\n    l_grad_lap = @private FT (3, ngradlapstate, Nq3)\n\n    local_ζ = @private FT (3, Nq3)\n\n    lap_ζ = @private FT lap_ζ_size\n\n    e = @index(Group, Linear)\n    i, j = @index(Local, NTuple)\n\n    @inbounds @views begin\n        @unroll for k in 1:Nq3\n            @unroll for s in 1:ngradlapstate\n                l_grad_lap[1, s, k] = -zero(FT)\n                l_grad_lap[2, s, k] = -zero(FT)\n                l_grad_lap[3, s, k] = -zero(FT)\n                if dim == 3\n                    lap_ζ[s, k] = -zero(FT)\n                end\n            end\n\n            # Load prognostic and auxiliary variables\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            @unroll for s in 1:ngradtransformstate\n                local_state_prognostic[s, k] = state_prognostic[ijk, s, e]\n            end\n            @unroll for s in 1:num_state_auxiliary\n                local_state_auxiliary[s, k] = state_auxiliary[ijk, s, e]\n            end\n\n            # Load geometry terms for the Jacobian: ∂ζ/∂xⱼ\n            local_ζ[1, k] = vgeo[ijk, _ζx1, e]\n            local_ζ[2, k] = vgeo[ijk, _ζx2, e]\n            local_ζ[3, k] = vgeo[ijk, _ζx3, e]\n        end\n\n        @unroll for k in 1:Nq3\n            # store laplacian into shared memory\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            @unroll for s in 1:ngradlapstate\n                s_lap[i, j, s] = Qhypervisc_div[ijk, s, e]\n            end\n\n            @synchronize\n\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n            # Compute gradient of each state\n            @unroll for s in 1:ngradlapstate\n                if dim == 2\n                    lap_ζ1 = zero(FT)\n                    @unroll for n in 1:Nqv\n                        lap_ζ1 += D[j, n] * s_lap[i, n, s]\n                    end\n                    # Application of chain-rule in ζ direction\n                    # ∂G/∂xi = ∂ζ/∂xi * ∂G/∂ζ\n                    # to get a physical gradient\n                    l_grad_lap[1, s, k] = local_ζ[1, k] * lap_ζ1\n                    l_grad_lap[2, s, k] = local_ζ[2, k] * lap_ζ1\n                    l_grad_lap[3, s, k] = local_ζ[3, k] * lap_ζ1\n                else\n                    @unroll for n in 1:Nq3\n                        lap_ζ[s, n] += D[n, k] * s_lap[i, j, s]\n                    end\n                end\n            end\n            # Synchronize threads on the device\n            @synchronize\n        end\n\n        @unroll for k in 1:Nq3\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n            # Application of chain-rule in ξ3-direction: ∂G/∂xi = ∂ξ3/∂xi * ∂G/∂ξ3\n            if dim == 3\n                ζx1, ζx2, ζx3 = local_ζ[1, k], local_ζ[2, k], local_ζ[3, k]\n                @unroll for s in 1:ngradlapstate\n                    l_grad_lap[1, s, k] += ζx1 * lap_ζ[s, k]\n                    l_grad_lap[2, s, k] += ζx2 * lap_ζ[s, k]\n                    l_grad_lap[3, s, k] += ζx3 * lap_ζ[s, k]\n                end\n            end\n\n            fill!(\n                local_state_hyperdiffusion,\n                -zero(eltype(local_state_hyperdiffusion)),\n            )\n\n            # Applies a linear transformation of gradients to the hyperdiffusive variables\n            transform_post_gradient_laplacian_arr!(\n                balance_law,\n                local_state_hyperdiffusion,\n                l_grad_lap[:, :, k],\n                local_state_prognostic[:, k],\n                local_state_auxiliary[:, k],\n                t,\n            )\n\n            # Write out the result of the kernel to global memory\n            @unroll for s in 1:nhyperviscstate\n                if increment\n                    Qhypervisc_grad[ijk, s, e] += local_state_hyperdiffusion[s]\n                else\n                    Qhypervisc_grad[ijk, s, e] = local_state_hyperdiffusion[s]\n                end\n            end\n        end\n    end\nend\n\n\"\"\"\n    function interface_gradients_of_laplacians!(\n        balance_law::BalanceLaw,\n        ::Val{info},\n        direction,\n        hyperviscnumflux,\n        Qhypervisc_grad,\n        Qhypervisc_div,\n        state_prognostic,\n        state_auxiliary,\n        vgeo,\n        sgeo,\n        vmap⁻,\n        vmap⁺,\n        elemtobndy,\n        elems,\n        t,\n    )\n\nComputes the volume integral for the auxiliary equation\n(in DG strong form):\n\n∫ₑ ψI⋅η dx = ∫ₑ ψI⋅∇ΔG dx + ∮ₑ nψI⋅((ΔG)⋆ - ΔG) dS,\n\nor equivalently in matrix notation:\n\nη = M⁻¹ LᵀMf((ΔG)⋆ - ΔG) + D ΔG\n\nThis kernel computes the interface gradient term: M⁻¹ LᵀMf((ΔG)⋆ - ΔG),\nwhere M is the mass matrix, Mf is the face mass matrix, L is an interpolator\nfrom volume to face, ΔG is the laplacian, and (ΔG)⋆ is\nthe associated numerical flux.\n\"\"\"\n@kernel function interface_gradients_of_laplacians!(\n    balance_law::BalanceLaw,\n    ::Val{info},\n    direction,\n    hyperviscnumflux,\n    Qhypervisc_grad,\n    Qhypervisc_div,\n    state_prognostic,\n    state_auxiliary,\n    vgeo,\n    sgeo,\n    vmap⁻,\n    vmap⁺,\n    elemtobndy,\n    elems,\n    t,\n) where {info}\n    @uniform begin\n        dim = info.dim\n        FT = eltype(Qhypervisc_grad)\n        num_state_prognostic = number_states(balance_law, Prognostic())\n        ngradlapstate = number_states(balance_law, GradientLaplacian())\n        nhyperviscstate = number_states(balance_law, Hyperdiffusive())\n        num_state_auxiliary = number_states(balance_law, Auxiliary())\n        ngradtransformstate = num_state_prognostic\n        nface = info.nface\n        Np = info.Np\n        Nqk = info.Nqk\n\n        faces = 1:nface\n        if direction isa VerticalDirection\n            faces = (nface - 1):nface\n        elseif direction isa HorizontalDirection\n            faces = 1:(nface - 2)\n        end\n\n        l_lap⁻ = MArray{Tuple{ngradlapstate}, FT}(undef)\n        l_lap⁺ = MArray{Tuple{ngradlapstate}, FT}(undef)\n        local_state_hyperdiffusion = MArray{Tuple{nhyperviscstate}, FT}(undef)\n\n        local_state_prognostic⁻ = MArray{Tuple{ngradtransformstate}, FT}(undef)\n        local_state_auxiliary⁻ = MArray{Tuple{num_state_auxiliary}, FT}(undef)\n\n        local_state_prognostic⁺ = MArray{Tuple{ngradtransformstate}, FT}(undef)\n        local_state_auxiliary⁺ = MArray{Tuple{num_state_auxiliary}, FT}(undef)\n    end\n\n    eI = @index(Group, Linear)\n    n = @index(Local, Linear)\n\n    e = @private Int (1,)\n    @inbounds e[1] = elems[eI]\n\n    @inbounds for f in faces\n        e⁻ = e[1]\n        normal_vector = SVector(\n            sgeo[_n1, n, f, e⁻],\n            sgeo[_n2, n, f, e⁻],\n            sgeo[_n3, n, f, e⁻],\n        )\n        bctag = elemtobndy[f, e⁻]\n        sM, vMI = sgeo[_sM, n, f, e⁻], sgeo[_vMI, n, f, e⁻]\n        id⁻, id⁺ = vmap⁻[n, f, e⁻], vmap⁺[n, f, e⁻]\n        e⁺ = ((id⁺ - 1) ÷ Np) + 1\n\n        vid⁻, vid⁺ = ((id⁻ - 1) % Np) + 1, ((id⁺ - 1) % Np) + 1\n        if bctag != 0\n            # TODO: we will use vmap⁺ to store the boundary element info\n            #be = e⁺\n            #bcid = vid⁺\n            e⁺ = e⁻\n            vid⁺ = vid⁻\n        end\n\n        # Load minus side data\n        @unroll for s in 1:ngradtransformstate\n            local_state_prognostic⁻[s] = state_prognostic[vid⁻, s, e⁻]\n        end\n\n        @unroll for s in 1:num_state_auxiliary\n            local_state_auxiliary⁻[s] = state_auxiliary[vid⁻, s, e⁻]\n        end\n\n        @unroll for s in 1:ngradlapstate\n            l_lap⁻[s] = Qhypervisc_div[vid⁻, s, e⁻]\n        end\n\n        # Load plus side data\n        @unroll for s in 1:ngradtransformstate\n            local_state_prognostic⁺[s] = state_prognostic[vid⁺, s, e⁺]\n        end\n\n        @unroll for s in 1:num_state_auxiliary\n            local_state_auxiliary⁺[s] = state_auxiliary[vid⁺, s, e⁺]\n        end\n\n        @unroll for s in 1:ngradlapstate\n            l_lap⁺[s] = Qhypervisc_div[vid⁺, s, e⁺]\n        end\n\n        if bctag == 0\n            numerical_flux_higher_order!(\n                hyperviscnumflux,\n                balance_law,\n                Vars{vars_state(balance_law, Hyperdiffusive(), FT)}(\n                    local_state_hyperdiffusion,\n                ),\n                normal_vector,\n                Vars{vars_state(balance_law, GradientLaplacian(), FT)}(l_lap⁻),\n                Vars{vars_state(balance_law, Prognostic(), FT)}(\n                    local_state_prognostic⁻,\n                ),\n                Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                    local_state_auxiliary⁻,\n                ),\n                Vars{vars_state(balance_law, GradientLaplacian(), FT)}(l_lap⁺),\n                Vars{vars_state(balance_law, Prognostic(), FT)}(\n                    local_state_prognostic⁺,\n                ),\n                Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                    local_state_auxiliary⁺,\n                ),\n                t,\n            )\n        else\n            bcs = boundary_conditions(balance_law)\n            # TODO: there is probably a better way to unroll this loop\n            Base.Cartesian.@nif 7 d -> bctag == d <= length(bcs) d -> begin\n                bc = bcs[d]\n                numerical_boundary_flux_higher_order!(\n                    hyperviscnumflux,\n                    bc,\n                    balance_law,\n                    Vars{vars_state(balance_law, Hyperdiffusive(), FT)}(\n                        local_state_hyperdiffusion,\n                    ),\n                    normal_vector,\n                    Vars{vars_state(balance_law, GradientLaplacian(), FT)}(\n                        l_lap⁻,\n                    ),\n                    Vars{vars_state(balance_law, Prognostic(), FT)}(\n                        local_state_prognostic⁻,\n                    ),\n                    Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                        local_state_auxiliary⁻,\n                    ),\n                    Vars{vars_state(balance_law, GradientLaplacian(), FT)}(\n                        l_lap⁺,\n                    ),\n                    Vars{vars_state(balance_law, Prognostic(), FT)}(\n                        local_state_prognostic⁺,\n                    ),\n                    Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                        local_state_auxiliary⁺,\n                    ),\n                    t,\n                )\n            end d -> throw(BoundsError(bcs, bctag))\n        end\n\n        @unroll for s in 1:nhyperviscstate\n            Qhypervisc_grad[vid⁻, s, e⁻] +=\n                vMI * sM * local_state_hyperdiffusion[s]\n        end\n        # Need to wait after even faces to avoid race conditions\n        @synchronize(f % 2 == 0)\n    end\nend\n\n@kernel function kernel_local_courant!(\n    balance_law::BalanceLaw,\n    ::Val{dim},\n    ::Val{N},\n    pointwise_courant,\n    local_courant,\n    state_prognostic,\n    state_auxiliary,\n    state_gradient_flux,\n    elems,\n    Δt,\n    simtime,\n    direction,\n) where {dim, N}\n    @uniform begin\n        FT = eltype(state_prognostic)\n        num_state_prognostic = number_states(balance_law, Prognostic())\n        num_state_gradient_flux = number_states(balance_law, GradientFlux())\n        num_state_auxiliary = number_states(balance_law, Auxiliary())\n\n        Nq = N .+ 1\n        @inbounds Nqk = dim == 2 ? 1 : Nq[dim]\n        @inbounds Np = Nq[1] * Nq[2] * Nqk\n\n        local_state_prognostic = MArray{Tuple{num_state_prognostic}, FT}(undef)\n        local_state_auxiliary = MArray{Tuple{num_state_auxiliary}, FT}(undef)\n        local_state_gradient_flux =\n            MArray{Tuple{num_state_gradient_flux}, FT}(undef)\n    end\n\n    I = @index(Global, Linear)\n    e = (I - 1) ÷ Np + 1\n    n = (I - 1) % Np + 1\n\n    @inbounds begin\n        @unroll for s in 1:num_state_prognostic\n            local_state_prognostic[s] = state_prognostic[n, s, e]\n        end\n\n        @unroll for s in 1:num_state_auxiliary\n            local_state_auxiliary[s] = state_auxiliary[n, s, e]\n        end\n\n        @unroll for s in 1:num_state_gradient_flux\n            local_state_gradient_flux[s] = state_gradient_flux[n, s, e]\n        end\n\n        Δx = pointwise_courant[n, e]\n        c = local_courant(\n            balance_law,\n            Vars{vars_state(balance_law, Prognostic(), FT)}(\n                local_state_prognostic,\n            ),\n            Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                local_state_auxiliary,\n            ),\n            Vars{vars_state(balance_law, GradientFlux(), FT)}(\n                local_state_gradient_flux,\n            ),\n            Δx,\n            Δt,\n            simtime,\n            direction,\n        )\n\n        pointwise_courant[n, e] = c\n    end\nend\n\n@kernel function dgsem_auxiliary_field_gradient!(\n    balance_law::BalanceLaw,\n    ::Val{dim},\n    ::Val{N},\n    direction,\n    ∇state,\n    state,\n    vgeo,\n    D,\n    ω,\n    ::Val{I},\n    ::Val{O},\n    increment,\n) where {dim, N, I, O}\n    @uniform begin\n        FT = eltype(state)\n        ngradstate = length(I)\n        Nq = N .+ 1\n        @inbounds begin\n            Nq1 = Nq[1]\n            Nq2 = Nq[2]\n            Nq3 = dim == 2 ? 1 : Nq[dim]\n        end\n    end\n\n    shared_state = @localmem FT (Nq1, Nq2, ngradstate)\n\n    local_gradient = @private FT (3, ngradstate, Nq3)\n    Gξ3 = @private FT (ngradstate, Nq3)\n\n    e = @index(Group, Linear)\n    i, j = @index(Local, NTuple)\n\n    @inbounds @views begin\n        @unroll for k in 1:Nq3\n            @unroll for s in 1:ngradstate\n                local_gradient[1, s, k] = -zero(FT)\n                local_gradient[2, s, k] = -zero(FT)\n                local_gradient[3, s, k] = -zero(FT)\n                Gξ3[s, k] = -zero(FT)\n            end\n        end\n\n        @unroll for k in 1:Nq3\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n            @unroll for s in 1:ngradstate\n                shared_state[i, j, s] = state[ijk, I[s], e]\n            end\n            @synchronize\n\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            ξ1x1, ξ1x2, ξ1x3 =\n                vgeo[ijk, _ξ1x1, e], vgeo[ijk, _ξ1x2, e], vgeo[ijk, _ξ1x3, e]\n\n            # Compute gradient of each state\n            @unroll for s in 1:ngradstate\n                Gξ1 = Gξ2 = zero(FT)\n\n                if (dim == 2 && (direction isa VerticalDirection))\n                    @unroll for n in 1:Nq2\n                        Gξ2 += D[j, n] * shared_state[i, n, s]\n                    end\n                end\n                if (dim == 3 && (direction isa VerticalDirection))\n                    @unroll for n in 1:Nq3\n                        Gξ3[s, n] += D[n, k] * shared_state[i, j, s]\n                    end\n                end\n\n                if (dim == 2 && (direction isa HorizontalDirection))\n                    @unroll for n in 1:Nq1\n                        Gξ1 += D[i, n] * shared_state[n, j, s]\n                    end\n                end\n                if (dim == 3 && (direction isa HorizontalDirection))\n                    @unroll for n in 1:Nq1\n                        Gξ1 += D[i, n] * shared_state[n, j, s]\n                        Gξ2 += D[j, n] * shared_state[i, n, s]\n                    end\n                end\n\n                if (direction isa HorizontalDirection)\n                    local_gradient[1, s, k] += ξ1x1 * Gξ1\n                    local_gradient[2, s, k] += ξ1x2 * Gξ1\n                    local_gradient[3, s, k] += ξ1x3 * Gξ1\n                end\n\n                if (\n                    (dim == 2 && (direction isa VerticalDirection)) ||\n                    (dim == 3 && (direction isa HorizontalDirection))\n                )\n                    ξ2x1, ξ2x2, ξ2x3 = vgeo[ijk, _ξ2x1, e],\n                    vgeo[ijk, _ξ2x2, e],\n                    vgeo[ijk, _ξ2x3, e]\n                    local_gradient[1, s, k] += ξ2x1 * Gξ2\n                    local_gradient[2, s, k] += ξ2x2 * Gξ2\n                    local_gradient[3, s, k] += ξ2x3 * Gξ2\n                end\n            end\n            @synchronize\n        end\n\n        @unroll for k in 1:Nq3\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n            if (dim == 3 && (direction isa VerticalDirection))\n                ξ3x1, ξ3x2, ξ3x3 = vgeo[ijk, _ξ3x1, e],\n                vgeo[ijk, _ξ3x2, e],\n                vgeo[ijk, _ξ3x3, e]\n                @unroll for s in 1:ngradstate\n                    local_gradient[1, s, k] += ξ3x1 * Gξ3[s, k]\n                    local_gradient[2, s, k] += ξ3x2 * Gξ3[s, k]\n                    local_gradient[3, s, k] += ξ3x3 * Gξ3[s, k]\n                end\n            end\n\n            if increment\n                @unroll for s in 1:ngradstate\n                    ∇state[ijk, O[3 * (s - 1) + 1], e] +=\n                        local_gradient[1, s, k]\n                    ∇state[ijk, O[3 * (s - 1) + 2], e] +=\n                        local_gradient[2, s, k]\n                    ∇state[ijk, O[3 * (s - 1) + 3], e] +=\n                        local_gradient[3, s, k]\n                end\n            else\n                @unroll for s in 1:ngradstate\n                    ∇state[ijk, O[3 * (s - 1) + 1], e] = local_gradient[1, s, k]\n                    ∇state[ijk, O[3 * (s - 1) + 2], e] = local_gradient[2, s, k]\n                    ∇state[ijk, O[3 * (s - 1) + 3], e] = local_gradient[3, s, k]\n                end\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "src/Numerics/DGMethods/ESDGModel.jl",
    "content": "using .NumericalFluxes: EntropyConservative\n\ninclude(\"ESDGModel_kernels.jl\")\n\n\"\"\"\n    ESDGModel\n\nContain type and functor that is used to evaluated the tendency for a entropy\nstable / conservative DGSEM discretization. Major fundamental difference between\nthis and the more vanilla DGSEM is that the first order flux derivatives in the\nbalance laws are evaluated using \"flux-differencing\". Namely, the following identities are used:\n```math\n    ∂x f(q(x)) = 2∂x F(q(x), q(y))|_{x = y},\n    A(q(x)) ∂x q(x) = 2∂x D(q(x), q(y))|_{x = y},\n```\nwhere the numerical conservative flux `F` and numerical fluctuation flux `D`\nsatisfy the following consistency and symmetry properties\n```math\n    F(q, p) = F(p, q),\n    F(q, q) = f(q),\n    D(q, p) = B(q, p)(q - p),\n    2B(q, q) = A(p).\n```\nFor the scheme to be entropy stable (and not just consistent) other properties\nof the numerical flux are also required. In particular, consider a balance laws\nof the form\n```math\n    ∂t q + ∑_{j=1:d} (∂xj fj(q) + Aj(q) ∂xj q) = g(q, x, t),\n```\nwhere `q` is the state vector, `fj` is the conservative flux, and `Aj`\nnonconservative variable coefficient matrix, and `g` is the production field.\nLet there exists a scalar companion balance law of the form\n```math\n    ∂t η(q) + ∑_{j=1:d} ∂xj ζj(q) = Π(q, x, t),\n    Π(q, x, t) = β(q)^T g(q, x, t),\n    β(q) = ∂q η(q).\n```\nThen for the scheme to be entropy stable it is requires that the numerical flux\n`H(q, p) = F(q, p) + D(q, p)` satisfy the following Tadmor-shuffle:\n```math\n    β(q)^T Hj(q, p) - β(p)^T Hj(p, q) <= ψj(q) - ψ(p),\n    ψj(q) = β(q)^T fj(q) - ζj(q);\n```\nwhen the equality is satisfied the scheme is called entropy conservative. For\nbalance laws without a nonconservative term, `ψj` is the entropy potential.\n\"\"\"\nstruct ESDGModel{BL, SA, VNFFO, SNFFO} <: SpaceDiscretization\n    \"definition of the physics being considered, primary dispatch type\"\n    balance_law::BL\n    \"all the grid related information (connectivity, metric terms, etc.)\"\n    grid::DiscontinuousSpectralElementGrid\n    \"auxiliary state are quantities needed to evaluate the physics that are not\n    explicitly time stepped by the ode solvers\"\n    state_auxiliary::SA\n    \"first order, two-point flux to be used for volume derivatives\"\n    volume_numerical_flux_first_order::VNFFO\n    \"first order, two-point flux to be used for surface integrals\"\n    surface_numerical_flux_first_order::SNFFO\nend\n\n\"\"\"\n    ESDGModel(\n        balance_law,\n        grid;\n        state_auxiliary = create_state(balance_law, grid, Auxiliary()),\n        volume_numerical_flux_first_order = EntropyConservative(),\n        surface_numerical_flux_first_order = EntropyConservative(),\n    )\n\nConstruct a `ESDGModel` type from a given `grid` and `balance_law` using the\n`volume_numerical_flux_first_order` and `surface_numerical_flux_first_order`\ntwo-point fluxes. If the two-point fluxes satisfy the appropriate Tadmor shuffle\nthen semi-discrete scheme will be entropy stable (or conservative).\n\"\"\"\nfunction ESDGModel(\n    balance_law,\n    grid;\n    # FIXME: this probably should be done differently\n    state_auxiliary = (\n        aux = create_state(balance_law, grid, Auxiliary());\n        init_state(aux, balance_law, grid, EveryDirection(), Auxiliary())\n    ),\n    volume_numerical_flux_first_order = EntropyConservative(),\n    surface_numerical_flux_first_order = EntropyConservative(),\n)\n\n    ESDGModel(\n        balance_law,\n        grid,\n        state_auxiliary,\n        volume_numerical_flux_first_order,\n        surface_numerical_flux_first_order,\n    )\nend\n\n\"\"\"\n    (esdg::ESDGModel)(\n        tendency::MPIStateArray,\n        state_prognostic::MPIStateArray,\n        param::Nothing,\n        t,\n        α = true,\n        β = false,\n    )\n\nCompute the entropy stable tendency from the model `esdg`.\n\n    tendency .= α .* dQdt(state_prognostic, param, t) .+ β .* tendency\n\"\"\"\nfunction (esdg::ESDGModel)(\n    tendency::MPIStateArray,\n    state_prognostic::MPIStateArray,\n    ::Nothing,\n    t,\n    α = true,\n    β = false,\n)\n\n    balance_law = esdg.balance_law\n    @assert number_states(balance_law, GradientFlux(), Int) == 0\n\n    grid = esdg.grid\n    topology = grid.topology\n\n    # Currently only support two polynomial orders\n    info = basic_launch_info(esdg)\n\n    device = info.device # array_device(state_prognostic)\n    workgroup = (info.Nq[1], info.Nq[2], info.Nqk)\n    ndrange = (info.Nq[1] * info.nrealelem, info.Nq[2], info.Nqk)\n    nrealelem = length(topology.realelems)\n\n    state_auxiliary = esdg.state_auxiliary\n\n    # XXX: When we do stacked meshes and IMEX this will change\n    communicate = true\n\n    exchange_state_prognostic = NoneEvent()\n\n    comp_stream = Event(device)\n\n    ########################\n    # tendency Computation #\n    ########################\n    if communicate\n        exchange_state_prognostic = MPIStateArrays.begin_ghost_exchange!(\n            state_prognostic;\n            dependencies = comp_stream,\n        )\n    end\n\n    # volume tendency\n    comp_stream = esdg_volume_tendency!(device, workgroup)(\n        balance_law,\n        Val(1),\n        Val(info),\n        esdg.volume_numerical_flux_first_order,\n        tendency.data,\n        state_prognostic.data,\n        state_auxiliary.data,\n        grid.vgeo,\n        grid.D[1],\n        α,\n        β,\n        true, # add_source\n        ndrange = ndrange,\n        dependencies = (comp_stream,),\n    )\n    comp_stream = esdg_volume_tendency!(device, workgroup)(\n        balance_law,\n        Val(2),\n        Val(info),\n        esdg.volume_numerical_flux_first_order,\n        tendency.data,\n        state_prognostic.data,\n        state_auxiliary.data,\n        grid.vgeo,\n        grid.D[2],\n        α,\n        true,\n        ndrange = ndrange,\n        dependencies = (comp_stream,),\n    )\n    if info.dim == 3\n        comp_stream = esdg_volume_tendency!(device, workgroup)(\n            balance_law,\n            Val(3),\n            Val(info),\n            esdg.volume_numerical_flux_first_order,\n            tendency.data,\n            state_prognostic.data,\n            state_auxiliary.data,\n            grid.vgeo,\n            grid.D[3],\n            α,\n            true,\n            ndrange = ndrange,\n            dependencies = (comp_stream,),\n        )\n    end\n\n    # interfaces: Horizontal => Nfp_v and Vertical => Nfp_h\n    # mirror surface tendency: interior\n    Nfp = info.Nfp_v\n    ndrange = Nfp * info.ninteriorelem\n    comp_stream = dgsem_interface_tendency!(device, (Nfp,))(\n        balance_law,\n        Val(info),\n        HorizontalDirection(),\n        esdg.surface_numerical_flux_first_order,\n        nothing,\n        tendency.data,\n        state_prognostic.data,\n        nothing,\n        nothing,\n        state_auxiliary.data,\n        grid.vgeo,\n        grid.sgeo,\n        t,\n        grid.vmap⁻,\n        grid.vmap⁺,\n        grid.elemtobndy,\n        grid.interiorelems,\n        α;\n        ndrange = ndrange,\n        dependencies = (comp_stream,),\n    )\n\n    Nfp = info.Nfp_h\n    ndrange = Nfp * info.ninteriorelem\n    comp_stream = dgsem_interface_tendency!(device, (Nfp,))(\n        balance_law,\n        Val(info),\n        VerticalDirection(),\n        esdg.surface_numerical_flux_first_order,\n        nothing,\n        tendency.data,\n        state_prognostic.data,\n        nothing,\n        nothing,\n        state_auxiliary.data,\n        grid.vgeo,\n        grid.sgeo,\n        t,\n        grid.vmap⁻,\n        grid.vmap⁺,\n        grid.elemtobndy,\n        grid.interiorelems,\n        α;\n        ndrange = ndrange,\n        dependencies = (comp_stream,),\n    )\n\n    if communicate\n        exchange_state_prognostic = MPIStateArrays.end_ghost_exchange!(\n            state_prognostic;\n            dependencies = exchange_state_prognostic,\n        )\n    end\n\n    # mirror surface tendency: exterior\n    Nfp = info.Nfp_v\n    ndrange = Nfp * info.nexteriorelem\n    comp_stream = dgsem_interface_tendency!(device, (Nfp,))(\n        balance_law,\n        Val(info),\n        HorizontalDirection(),\n        esdg.surface_numerical_flux_first_order,\n        nothing,\n        tendency.data,\n        state_prognostic.data,\n        nothing,\n        nothing,\n        state_auxiliary.data,\n        grid.vgeo,\n        grid.sgeo,\n        t,\n        grid.vmap⁻,\n        grid.vmap⁺,\n        grid.elemtobndy,\n        grid.exteriorelems,\n        α;\n        ndrange = ndrange,\n        dependencies = (comp_stream, exchange_state_prognostic),\n    )\n\n    Nfp = info.Nfp_h\n    ndrange = Nfp * info.nexteriorelem\n    comp_stream = dgsem_interface_tendency!(device, (Nfp,))(\n        balance_law,\n        Val(info),\n        VerticalDirection(),\n        esdg.surface_numerical_flux_first_order,\n        nothing,\n        tendency.data,\n        state_prognostic.data,\n        nothing,\n        nothing,\n        state_auxiliary.data,\n        grid.vgeo,\n        grid.sgeo,\n        t,\n        grid.vmap⁻,\n        grid.vmap⁺,\n        grid.elemtobndy,\n        grid.exteriorelems,\n        α;\n        ndrange = ndrange,\n        dependencies = (comp_stream, exchange_state_prognostic),\n    )\n\n    # The synchronization here through a device event prevents CuArray based and\n    # other default stream kernels from launching before the work scheduled in\n    # this function is finished.\n    wait(device, comp_stream)\nend\n"
  },
  {
    "path": "src/Numerics/DGMethods/ESDGModel_kernels.jl",
    "content": "using .NumericalFluxes: numerical_volume_flux_first_order!\n\n# {{{ FIXME: remove this after we've figure out how to pass through to kernel\nconst _ξ1x1, _ξ2x1, _ξ3x1 = Grids._ξ1x1, Grids._ξ2x1, Grids._ξ3x1\nconst _ξ1x2, _ξ2x2, _ξ3x2 = Grids._ξ1x2, Grids._ξ2x2, Grids._ξ3x2\nconst _ξ1x3, _ξ2x3, _ξ3x3 = Grids._ξ1x3, Grids._ξ2x3, Grids._ξ3x3\nconst _M = Grids._M\n\nconst _n1, _n2, _n3 = Grids._n1, Grids._n2, Grids._n3\nconst _sM, _vMI = Grids._sM, Grids._vMI\n# }}}\n\n@doc \"\"\"\n    esdg_volume_tendency!(\n        balance_law::BalanceLaw,\n        ::Val{dim},\n        ::Val{polyorder},\n        tendency,\n        state_prognostic,\n        state_auxiliary,\n        vgeo,\n        D,\n        α,\n        β,\n    ) where {dim, polyorder}\n\nComputes the reference element horizontal tendency using a two-point flux\napproximation of all derivatives.\n\"\"\"\n@kernel function esdg_volume_tendency!(\n    balance_law::BalanceLaw,\n    ::Val{dir},\n    ::Val{info},\n    volume_numerical_flux_first_order,\n    tendency,\n    state_prognostic,\n    state_auxiliary,\n    vgeo,\n    D,\n    α,\n    β,\n    add_source = false,\n) where {dir, info}\n    @uniform begin\n        dim = info.dim\n\n        FT = eltype(state_prognostic)\n        num_state = number_states(balance_law, Prognostic(), FT)\n        num_aux = number_states(balance_law, Auxiliary(), FT)\n\n        local_H = MArray{Tuple{3, num_state}, FT}(undef)\n\n        state_2 = MArray{Tuple{num_state}, FT}(undef)\n        aux_2 = MArray{Tuple{num_aux}, FT}(undef)\n\n        local_source = MArray{Tuple{num_state}, FT}(undef)\n\n        @inbounds Nq1 = info.Nq[1]\n        @inbounds Nq2 = info.Nq[2]\n        Nq3 = info.Nqk\n\n        if dir == 1\n            _ξdx1, _ξdx2, _ξdx3 = _ξ1x1, _ξ1x2, _ξ1x3\n            Nqd = Nq1\n        elseif dir == 2\n            _ξdx1, _ξdx2, _ξdx3 = _ξ2x1, _ξ2x2, _ξ2x3\n            Nqd = Nq2\n        elseif dir == 3\n            _ξdx1, _ξdx2, _ξdx3 = _ξ3x1, _ξ3x2, _ξ3x3\n            Nqd = Nq3\n        end\n    end\n\n    e = @index(Group, Linear)\n    i, j, k = @index(Local, NTuple)\n\n    state_1 = @private FT (num_state,)\n    aux_1 = @private FT (num_aux,)\n    local_tendency = @private FT (num_state,)\n    local_MI = @private FT (1,)\n    shared_G = @localmem FT (Nq1 * Nq2 * Nq3, 3)\n    shared_D = @localmem FT (Nqd, Nqd)\n\n    @inbounds begin\n        ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n        # generalization for different polynomial orders\n        @unroll for l in 1:Nqd\n            if dir == 1\n                id = i\n                ild = l + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            elseif dir == 2\n                id = j\n                ild = i + Nq1 * ((l - 1) + Nq2 * (k - 1))\n            elseif dir == 3\n                id = k\n                ild = i + Nq1 * ((j - 1) + Nq2 * (l - 1))\n            end\n            shared_D[id, l] = D[id, l]\n        end\n\n        M = vgeo[ijk, _M, e]\n        shared_G[ijk, 1] = M * vgeo[ijk, _ξdx1, e]\n        shared_G[ijk, 2] = M * vgeo[ijk, _ξdx2, e]\n        if dim == 3\n            shared_G[ijk, 3] = M * vgeo[ijk, _ξdx3, e]\n        end\n\n        # Build ode scaling into mass matrix (so it doesn't show up later)\n        local_MI[1] = α / M\n\n        # Get the volume tendency (scaling by β done below)\n        @unroll for s in 1:num_state\n            local_tendency[s] = β != 0 ? tendency[ijk, s, e] : -zero(FT)\n        end\n\n        # Scale β into the volume tendency\n        @unroll for s in 1:num_state\n            local_tendency[s] *= β\n        end\n\n        # Compute the volume tendency\n        # ∑_{i,j}^{d} ( G_ij (Q_i ∘ H_j) - (H_j ∘ Q_i^T) G_ij)\n        #  =\n        # ( G_11 (Q_1 ∘ H_1) - (H_1 ∘ Q_1^T) G_11) 1 +\n        # ( G_12 (Q_1 ∘ H_2) - (H_2 ∘ Q_1^T) G_12) 1 +\n        # ( G_13 (Q_1 ∘ H_3) - (H_3 ∘ Q_1^T) G_13) 1 +\n        # ( G_21 (Q_2 ∘ H_1) - (H_1 ∘ Q_2^T) G_21) 1 +\n        # ( G_22 (Q_2 ∘ H_2) - (H_2 ∘ Q_2^T) G_22) 1 +\n        # ( G_23 (Q_2 ∘ H_3) - (H_3 ∘ Q_2^T) G_23) 1 +\n        # ( G_31 (Q_3 ∘ H_1) - (H_1 ∘ Q_3^T) G_31) 1 +\n        # ( G_32 (Q_3 ∘ H_2) - (H_2 ∘ Q_3^T) G_32) 1 +\n        # ( G_33 (Q_3 ∘ H_3) - (H_3 ∘ Q_3^T) G_33) 1\n        @unroll for s in 1:num_state\n            state_1[s] = state_prognostic[ijk, s, e]\n        end\n        @unroll for s in 1:num_aux\n            aux_1[s] = state_auxiliary[ijk, s, e]\n        end\n\n        if add_source\n            fill!(local_source, -zero(eltype(local_source)))\n            source!(\n                balance_law,\n                Vars{vars_state(balance_law, Prognostic(), FT)}(local_source),\n                Vars{vars_state(balance_law, Prognostic(), FT)}(state_1),\n                Vars{vars_state(balance_law, Auxiliary(), FT)}(aux_1),\n            )\n\n            @unroll for s in 1:num_state\n                local_tendency[s] += α * local_source[s]\n            end\n        end\n\n        @synchronize\n\n        ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n        # Note: unrolling this loop makes things slower\n        @views for l in 1:Nqd\n            if dir == 1\n                id = i\n                ild = l + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            elseif dir == 2\n                id = j\n                ild = i + Nq1 * ((l - 1) + Nq2 * (k - 1))\n            elseif dir == 3\n                id = k\n                ild = i + Nq1 * ((j - 1) + Nq2 * (l - 1))\n            end\n\n            # Compute derivatives wrt ξd\n            # ( G_31 (Q_3 ∘ H_1) - (H_1 ∘ Q_3^T) G_31) 1 +\n            # ( G_32 (Q_3 ∘ H_2) - (H_2 ∘ Q_3^T) G_32) 1 +\n            # ( G_33 (Q_3 ∘ H_3) - (H_3 ∘ Q_3^T) G_33) 1 +\n            @unroll for s in 1:num_state\n                state_2[s] = state_prognostic[ild, s, e]\n            end\n            @unroll for s in 1:num_aux\n                aux_2[s] = state_auxiliary[ild, s, e]\n            end\n            fill!(local_H, -zero(FT))\n            numerical_volume_flux_first_order!(\n                volume_numerical_flux_first_order,\n                balance_law,\n                local_H,\n                state_1[:],\n                aux_1[:],\n                state_2,\n                aux_2,\n            )\n            @unroll for s in 1:num_state\n                # G_21 (Q_2 ∘ H_1) 1 +\n                # G_22 (Q_2 ∘ H_2) 1 +\n                # G_23 (Q_2 ∘ H_3) 1\n                local_tendency[s] -=\n                    local_MI[1] *\n                    shared_D[id, l] *\n                    (\n                        shared_G[ijk, 1] * local_H[1, s] +\n                        shared_G[ijk, 2] * local_H[2, s] +\n                        (\n                            dim == 3 ? shared_G[ijk, 3] * local_H[3, s] :\n                            -zero(FT)\n                        )\n                    )\n                #  (H_1 ∘ Q_2^T) G_21 1 +\n                #  (H_2 ∘ Q_2^T) G_22 1 +\n                #  (H_3 ∘ Q_2^T) G_23 1\n                local_tendency[s] +=\n                    local_MI[1] *\n                    (\n                        local_H[1, s] * shared_G[ild, 1] +\n                        local_H[2, s] * shared_G[ild, 2] +\n                        (\n                            dim == 3 ? local_H[3, s] * shared_G[ild, 3] :\n                            -zero(FT)\n                        )\n                    ) *\n                    shared_D[l, id]\n            end\n        end\n\n        @unroll for s in 1:num_state\n            tendency[ijk, s, e] = local_tendency[s]\n        end\n    end\nend\n"
  },
  {
    "path": "src/Numerics/DGMethods/FVReconstructions.jl",
    "content": "module FVReconstructions\nexport AbstractReconstruction\nusing KernelAbstractions.Extras: @unroll\nimport StaticArrays: SUnitRange, SVector\n\n\"\"\"\n    AbstractReconstruction\n\nSupertype for FV reconstructions.\n\nConcrete types must provide implementions of\n    - `width(recon)`\n       returns the width of the reconstruction. Total number of points used in\n       reconstruction of top and bottom states is `2width(recon) + 1`\n       - (::AbstractReconstruction)(state_top, state_bottom, cell_states::SVector,\n                                    cell_weights)\n      compute the reconstruction\n\n```\n(::AbstractReconstruction)(\n        state_top,\n        state_bottom,\n        cell_states,\n        cell_weights,\n    )\n```\n\nPerform the finite volume reconstruction for the top and bottom states using the\ntuple of `cell_states` values using the `cell_weights`.\n\"\"\"\nabstract type AbstractReconstruction end\nfunction (::AbstractReconstruction) end\n\n\"\"\"\n    AbstractSlopeLimiter\n\nSupertype for FV slope limiter\n\nGiven two values `Δ1` and `Δ2`, should return the value of\n```\nΔ2 * ϕ(Δ1 / Δ2)\n```\nwhere `0 ≤ ϕ(r) ≤ 2` is the slope limiter\n\"\"\"\nabstract type AbstractSlopeLimiter end\n\n\"\"\"\n    width(recon::AbstractReconstruction)\n\nReturns the width of the stencil need for the FV reconstruction `recon`. Total\nnumber of values used in the reconstruction are `2width(recon) + 1`\n\"\"\"\nwidth(recon::AbstractReconstruction) = throw(MethodError(width, (recon,)))\n\n\"\"\"\n    FVConstant <: AbstractReconstruction\n\nReconstruction type for cell centered finite volume methods (e.g., constants)\n\"\"\"\nstruct FVConstant <: AbstractReconstruction end\n\nwidth(::FVConstant) = 0\n\nfunction (::FVConstant)(state_bot, state_top, cell_states::SVector{1}, _)\n    @inbounds state_top .= cell_states[1]\n    @inbounds state_bot .= cell_states[1]\nend\n\"\"\"\n    FVLinear{W = 1} <: AbstractReconstruction\n\nReconstruction type for limited linear reconstruction finite volume methods.\n\n!!! note\n   The optional type parameter `W` is mainly for debugging purposes and allows\n   the stencil to be artificially widened to make sure the kernels work with\n   wide stencils.\n\n    FVLinear(limiter = VanLeer())\n    FVLinear{W}(limiter = VanLeer())\n\nConstruct the `FVLinear` reconstruction type with the given slope `limiter`\n\"\"\"\nstruct FVLinear{W, L} <: AbstractReconstruction\n    limiter::L\n\nend\n\n\"\"\"\n    FVLinear(limiter = VanLeer())\n    FVLinear{W}(limiter = VanLeer())\n\nConstruct the `FVLinear` reconstruction type with the given slope `limiter` and\noptional width `W`.\n\"\"\"\nfunction FVLinear{W}(limiter = VanLeer()) where {W}\n    @assert W > 0\n    FVLinear{W, typeof(limiter)}(limiter)\nend\nFVLinear(limiter = VanLeer()) = FVLinear{1}(limiter)\n\nwidth(::FVLinear{W}) where {W} = W\n\nfunction (fvrecon::FVLinear)(\n    state_bot,\n    state_top,\n    cell_states::SVector{3},\n    cell_weights,\n)\n    @inbounds wi_top = 1 / (cell_weights[3] + cell_weights[2])\n    @inbounds wi_bot = 1 / (cell_weights[2] + cell_weights[1])\n    @inbounds @unroll for s in 1:length(state_top)\n        # Compute the edge gradient approximations\n        Δ_top = wi_top * (cell_states[3][s] - cell_states[2][s])\n        Δ_bot = wi_bot * (cell_states[2][s] - cell_states[1][s])\n\n        # Compute the limited slope\n        Δ = fvrecon.limiter(Δ_top, Δ_bot)\n\n        # Compute the reconstructions at the cell edges\n        state_top[s] = cell_states[2][s] + Δ * cell_weights[2]\n        state_bot[s] = cell_states[2][s] - Δ * cell_weights[2]\n    end\nend\n\nfunction (fvrecon::FVLinear)(\n    state_bot,\n    state_top,\n    cell_states::SVector{1},\n    cell_weights,\n)\n    FVConstant()(state_bot, state_top, cell_states, cell_weights)\nend\n\nfunction (fvrecon::FVLinear)(\n    state_bot,\n    state_top,\n    cell_states::SVector{D},\n    cell_weights,\n) where {D}\n    W = div(D - 1, 2)\n    rng = SUnitRange(W, W + 2)\n    @inbounds fvrecon(state_bot, state_top, cell_states[rng], cell_weights[rng])\nend\n\n\"\"\"\n    VanLeer <: AbstractSlopeLimiter\n\nClassic Van-Leer limiter\n```\n   ϕ(r) = (r + |r|) / (1 + r)\n```\n\n### References\n    @article{van1974towards,\n      title = {Towards the ultimate conservative difference scheme. II.\n               Monotonicity and conservation combined in a second-order scheme},\n      author = {Van Leer, Bram},\n      journal = {Journal of computational physics},\n      volume = {14},\n      number = {4},\n      pages = {361--370},\n      year = {1974},\n      doi = {10.1016/0021-9991(74)90019-9}\n    }\n\"\"\"\nstruct VanLeer <: AbstractSlopeLimiter end\n\nfunction (::VanLeer)(Δ_top, Δ_bot)\n    FT = eltype(Δ_top)\n    if Δ_top * Δ_bot > 0\n        return 2Δ_top * Δ_bot / (Δ_top + Δ_bot)\n    else\n        return FT(0)\n    end\nend\n\n\n\"\"\"\n    NoLimiter <: AbstractSlopeLimiter\n\nNo Limiter: \n1) smooth case, no dissipation is added\n2) linear case, which guarantee the discreted equation is linear\n```\n   ϕ(r) = r\n```\n\"\"\"\nstruct NoLimiter <: AbstractSlopeLimiter end\n\nfunction (::NoLimiter)(Δ_top, Δ_bot)\n    return (Δ_top + Δ_bot) / 2\nend\n\nend\n"
  },
  {
    "path": "src/Numerics/DGMethods/NumericalFluxes.jl",
    "content": "module NumericalFluxes\n\nexport NumericalFluxGradient,\n    NumericalFluxFirstOrder,\n    NumericalFluxSecondOrder,\n    RusanovNumericalFlux,\n    RoeNumericalFlux,\n    HLLCNumericalFlux,\n    RoeNumericalFluxMoist,\n    CentralNumericalFluxGradient,\n    CentralNumericalFluxFirstOrder,\n    CentralNumericalFluxSecondOrder,\n    CentralNumericalFluxDivergence,\n    CentralNumericalFluxHigherOrder,\n    LMARSNumericalFlux\n\n\nusing StaticArrays, LinearAlgebra\nusing ClimateMachine.VariableTemplates\nusing KernelAbstractions.Extras: @unroll\nusing ...BalanceLaws\nimport ...BalanceLaws:\n    vars_state,\n    boundary_state!,\n    wavespeed,\n    flux_first_order!,\n    flux_second_order!,\n    compute_gradient_flux!,\n    compute_gradient_argument!,\n    transform_post_gradient_laplacian!,\n    boundary_conditions\n\n\"\"\"\n    NumericalFluxGradient\n\nAny `P <: NumericalFluxGradient` should define methods for:\n\n    numerical_flux_gradient!(\n        gnf::P,\n        balance_law::BalanceLaw,\n        diffF, n⁻,\n        Q⁻, Qstate_gradient_flux⁻, Qaux⁻,\n        Q⁺, Qstate_gradient_flux⁺, Qaux⁺,\n        t\n    )\n\n    numerical_boundary_flux_gradient!(\n        gnf::P,\n        balance_law::BalanceLaw,\n        local_state_gradient_flux,\n        n⁻,\n        local_transform⁻, local_state_prognostic⁻, local_state_auxiliary⁻,\n        local_transform⁺, local_state_prognostic⁺, local_state_auxiliary⁺,\n        bctype,\n        t\n    )\n\n\"\"\"\nabstract type NumericalFluxGradient end\n\n\"\"\"\n    CentralNumericalFluxGradient <: NumericalFluxGradient\n\n\"\"\"\nstruct CentralNumericalFluxGradient <: NumericalFluxGradient end\n\nfunction numerical_flux_gradient!(\n    ::CentralNumericalFluxGradient,\n    balance_law::BalanceLaw,\n    transform_gradient::MMatrix,\n    normal_vector::AbstractArray,\n    state_gradient⁻::AbstractArray,\n    state_prognostic⁻::AbstractArray,\n    state_auxiliary⁻::AbstractArray,\n    state_gradient⁺::AbstractArray,\n    state_prognostic⁺::AbstractArray,\n    state_auxiliary⁺::AbstractArray,\n    t,\n)\n\n    transform_gradient .=\n        SVector(normal_vector) .* (state_gradient⁺ .+ state_gradient⁻)' ./ 2\nend\n\nfunction numerical_boundary_flux_gradient!(\n    numerical_flux::CentralNumericalFluxGradient,\n    bctype,\n    balance_law::BalanceLaw,\n    transform_gradient::MMatrix,\n    normal_vector::SVector,\n    state_gradient⁻::Vars{T},\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    state_gradient⁺::Vars{T},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n    state1⁻::Vars{S},\n    aux1⁻::Vars{A},\n) where {D, T, S, A}\n    boundary_state!(\n        numerical_flux,\n        bctype,\n        balance_law,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        normal_vector,\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        t,\n        state1⁻,\n        aux1⁻,\n    )\n\n    compute_gradient_argument!(\n        balance_law,\n        state_gradient⁺,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n    )\n    transform_gradient .= normal_vector .* parent(state_gradient⁺)'\nend\n\n\"\"\"\n    NumericalFluxFirstOrder\n\nAny `N <: NumericalFluxFirstOrder` should define the a method for\n\n    numerical_flux_first_order!(\n        numerical_flux::N,\n        balance_law::BalanceLaw,\n        flux,\n        normal_vector⁻,\n        Q⁻, Qaux⁻,\n        Q⁺, Qaux⁺,\n        t\n    )\n\nwhere\n- `flux` is the numerical flux array\n- `normal_vector⁻` is the unit normal\n- `Q⁻`/`Q⁺` are the minus/positive state arrays\n- `t` is the time\n\nAn optional method can also be defined for\n\n    numerical_boundary_flux_first_order!(\n        numerical_flux::N,\n        balance_law::BalanceLaw,\n        flux,\n        normal_vector⁻,\n        Q⁻, Qaux⁻,\n        Q⁺, Qaux⁺,\n        bctype, t\n    )\n\n\"\"\"\nabstract type NumericalFluxFirstOrder end\n\nfunction numerical_flux_first_order! end\n\nfunction numerical_boundary_flux_first_order!(\n    numerical_flux::NumericalFluxFirstOrder,\n    bctype,\n    balance_law::BalanceLaw,\n    fluxᵀn::Vars{S},\n    normal_vector::SVector,\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n    direction,\n    state1⁻::Vars{S},\n    aux1⁻::Vars{A},\n) where {S, A}\n\n    boundary_state!(\n        numerical_flux,\n        bctype,\n        balance_law,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        normal_vector,\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        t,\n        state1⁻,\n        aux1⁻,\n    )\n\n    numerical_flux_first_order!(\n        numerical_flux,\n        balance_law,\n        fluxᵀn,\n        normal_vector,\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n        direction,\n    )\nend\n\n\n\"\"\"\n    RusanovNumericalFlux <: NumericalFluxFirstOrder\n\nThe RusanovNumericalFlux (aka local Lax-Friedrichs) numerical flux.\n\n# Usage\n\n    RusanovNumericalFlux()\n\nRequires a `flux_first_order!` and `wavespeed` method for the balance law.\n\"\"\"\nstruct RusanovNumericalFlux <: NumericalFluxFirstOrder end\n\nupdate_penalty!(::RusanovNumericalFlux, ::BalanceLaw, _...) = nothing\n\nfunction numerical_flux_first_order!(\n    numerical_flux::RusanovNumericalFlux,\n    balance_law::BalanceLaw,\n    fluxᵀn::Vars{S},\n    normal_vector::SVector,\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n    direction,\n) where {S, A}\n\n    numerical_flux_first_order!(\n        CentralNumericalFluxFirstOrder(),\n        balance_law,\n        fluxᵀn,\n        normal_vector,\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n        direction,\n    )\n\n    fluxᵀn = parent(fluxᵀn)\n    wavespeed⁻ = wavespeed(\n        balance_law,\n        normal_vector,\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        t,\n        direction,\n    )\n    wavespeed⁺ = wavespeed(\n        balance_law,\n        normal_vector,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n        direction,\n    )\n    max_wavespeed = max.(wavespeed⁻, wavespeed⁺)\n    penalty =\n        max_wavespeed .* (parent(state_prognostic⁻) - parent(state_prognostic⁺))\n\n    # TODO: should this operate on ΔQ or penalty?\n    update_penalty!(\n        numerical_flux,\n        balance_law,\n        normal_vector,\n        max_wavespeed,\n        Vars{S}(penalty),\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n    )\n\n    fluxᵀn .+= penalty / 2\nend\n\n\"\"\"\n    CentralNumericalFluxFirstOrder() <: NumericalFluxFirstOrder\n\nThe central numerical flux for nondiffusive terms\n\n# Usage\n\n    CentralNumericalFluxFirstOrder()\n\nRequires a `flux_first_order!` method for the balance law.\n\"\"\"\nstruct CentralNumericalFluxFirstOrder <: NumericalFluxFirstOrder end\n\nfunction numerical_flux_first_order!(\n    ::CentralNumericalFluxFirstOrder,\n    balance_law::BalanceLaw,\n    fluxᵀn::Vars{S},\n    normal_vector::SVector,\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n    direction,\n) where {S, A}\n\n    FT = eltype(fluxᵀn)\n    num_state_prognostic = number_states(balance_law, Prognostic())\n    fluxᵀn = parent(fluxᵀn)\n\n    flux⁻ = similar(fluxᵀn, Size(3, num_state_prognostic))\n    fill!(flux⁻, -zero(FT))\n    flux_first_order!(\n        balance_law,\n        Grad{S}(flux⁻),\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        t,\n        direction,\n    )\n\n    flux⁺ = similar(fluxᵀn, Size(3, num_state_prognostic))\n    fill!(flux⁺, -zero(FT))\n    flux_first_order!(\n        balance_law,\n        Grad{S}(flux⁺),\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n        direction,\n    )\n\n    fluxᵀn .+= (flux⁻ + flux⁺)' * (normal_vector / 2)\nend\n\n\"\"\"\n    RoeNumericalFlux() <: NumericalFluxFirstOrder\n\nA numerical flux based on the approximate Riemann solver of Roe\n\n# Usage\n\n    RoeNumericalFlux()\n\nRequires a custom implementation for the balance law.\n\"\"\"\nstruct RoeNumericalFlux <: NumericalFluxFirstOrder end\n\n\"\"\"\n    HLLCNumericalFlux() <: NumericalFluxFirstOrder\n\nA numerical flux based on the approximate Riemann solver of the\nHLLC method. The HLLC flux is a modification of the Harten, Lax, van-Leer\n(HLL) flux, where an additional contact property is introduced in order\nto restore missing rarefraction waves. The HLLC flux requires\nmodel-specific information, hence it requires a custom implementation\nbased on the underlying balance law.\n\n# Usage\n\n    HLLCNumericalFlux()\n\nRequires a custom implementation for the balance law.\n\n - [Toro2013](@cite)\n\"\"\"\nstruct HLLCNumericalFlux <: NumericalFluxFirstOrder end\n\n\n\"\"\"\n    LMARSNumericalFlux <: NumericalFluxFirstOrder\nLow Mach Number Approximate Riemann Solver. Upwind biased\nfirst order flux function. \n\n- [Chen2013](@cite)\n\"\"\"\nstruct LMARSNumericalFlux <: NumericalFluxFirstOrder end\n\n\"\"\"\n    RoeNumericalFluxMoist <: NumericalFluxFirstOrder\n\nA moist implementation of the numerical flux based on the approximate Riemann solver of Roe\n\nRequires a custom implementation for the balance law.\n\"\"\"\nstruct RoeNumericalFluxMoist <: NumericalFluxFirstOrder\n    \" set to true for low Mach number correction\"\n    LM::Bool\n    \" set to true for Hartman Hyman correction\"\n    HH::Bool\n    \" set to true for LeVeque correction\"\n    LV::Bool\n    \" set to true for Positivity preserving LeVeque Correction\"\n    LVPP::Bool\nend\n\nRoeNumericalFluxMoist(;\n    LM::Bool = false,\n    HH::Bool = false,\n    LV::Bool = false,\n    LVPP::Bool = false,\n) = RoeNumericalFluxMoist(LM, HH, LV, LVPP)\n\n\"\"\"\ndispatch type for entropy conservative numerical fluxes (these are balance law\nspecific)\n\"\"\"\nstruct EntropyConservative <: NumericalFluxFirstOrder end\n\n\"\"\"\n    numerical_volume_conservative_flux_first_order!(\n        numflux::NumericalFluxFirstOrder,\n        balancelaw::BalanceLaw,\n        flux::Grad,\n        state_1::Vars,\n        aux_1::Vars,\n        state_2::Vars,\n        aux_2::Vars,\n    )\nTwo point flux for use in the volume of entropy stable discretizations. Should\ncompute and store the conservative (symmetric) part of an entropy stable\nflux-fluctuation and store the three vector components back to `flux`.\nThis should be symmetric in the sense that swapping the place of `state_1`,\n`aux_1` with `state_2`, `aux_2` should result in the same flux.\nThis should be implemented with addition assignment.\nEach balance law must implement a concrete implementation of this function.\n\"\"\"\nfunction numerical_volume_conservative_flux_first_order!(\n    numflux::NumericalFluxFirstOrder,\n    balancelaw::BalanceLaw,\n    flux::AbstractArray{FT, 2},\n    state_1::AbstractArray{FT, 1},\n    aux_1::AbstractArray{FT, 1},\n    state_2::AbstractArray{FT, 1},\n    aux_2::AbstractArray{FT, 1},\n) where {FT}\n    numerical_volume_conservative_flux_first_order!(\n        numflux,\n        balancelaw,\n        Grad{vars_state(balancelaw, Prognostic(), FT)}(flux),\n        Vars{vars_state(balancelaw, Prognostic(), FT)}(state_1),\n        Vars{vars_state(balancelaw, Auxiliary(), FT)}(aux_1),\n        Vars{vars_state(balancelaw, Prognostic(), FT)}(state_2),\n        Vars{vars_state(balancelaw, Auxiliary(), FT)}(aux_2),\n    )\nend\nnumerical_volume_conservative_flux_first_order!(::Nothing, _...) = nothing\n\n\"\"\"\n    numerical_volume_fluctuation_flux_first_order!(\n        numflux::NumericalFluxFirstOrder,\n        balancelaw::BalanceLaw,\n        flux::Grad,\n        state_1::Vars,\n        aux_1::Vars,\n        state_2::Vars,\n        aux_2::Vars,\n    )\nTwo point fluctuation flux for use in the volume of entropy stable\ndiscretizations. Should compute and store the non-conservative (possibly\nnon-symmetric) part of an entropy stable flux-fluctuation and store the three\nvector components back to `flux`.\nThis can be non-symmetric in the sense that swapping the place of `state_1`,\n`aux_1` with `state_2`, `aux_2` can result in a different flux.\nThis should be implemented with addition assignment.\nEach balance law must implement a concrete implementation of this function.\n\"\"\"\nfunction numerical_volume_fluctuation_flux_first_order!(\n    numflux::NumericalFluxFirstOrder,\n    balancelaw::BalanceLaw,\n    fluctuation::AbstractArray{FT, 2},\n    state_1::AbstractArray{FT, 1},\n    aux_1::AbstractArray{FT, 1},\n    state_2::AbstractArray{FT, 1},\n    aux_2::AbstractArray{FT, 1},\n) where {FT}\n    numerical_volume_fluctuation_flux_first_order!(\n        numflux,\n        balancelaw,\n        Grad{vars_state(balancelaw, Prognostic(), FT)}(fluctuation),\n        Vars{vars_state(balancelaw, Prognostic(), FT)}(state_1),\n        Vars{vars_state(balancelaw, Auxiliary(), FT)}(aux_1),\n        Vars{vars_state(balancelaw, Prognostic(), FT)}(state_2),\n        Vars{vars_state(balancelaw, Auxiliary(), FT)}(aux_2),\n    )\nend\nnumerical_volume_fluctuation_flux_first_order!(::Nothing, _...) = nothing\n\n\"\"\"\n    numerical_volume_flux_first_order!(\n        numflux::NumericalFluxFirstOrder,\n        balancelaw::BalanceLaw,\n        flux::AbstractArray{FT, 2},\n        state_1::AbstractArray{FT, 1},\n        aux_1::AbstractArray{FT, 1},\n        state_2::AbstractArray{FT, 1},\n        aux_2::AbstractArray{FT, 1},\n    )\nConvenience function which calls\n  `numerical_volume_conservative_flux_first_order!`\nfollowed by\n  `numerical_volume_fluctuation_flux_first_order!`\n\"\"\"\nfunction numerical_volume_flux_first_order!(\n    numflux::NumericalFluxFirstOrder,\n    balancelaw::BalanceLaw,\n    flux::AbstractArray{FT, 2},\n    state_1::AbstractArray{FT, 1},\n    aux_1::AbstractArray{FT, 1},\n    state_2::AbstractArray{FT, 1},\n    aux_2::AbstractArray{FT, 1},\n) where {FT}\n    numerical_volume_conservative_flux_first_order!(\n        numflux,\n        balancelaw,\n        flux,\n        state_1,\n        aux_1,\n        state_2,\n        aux_2,\n    )\n    numerical_volume_fluctuation_flux_first_order!(\n        numflux,\n        balancelaw,\n        flux,\n        state_1,\n        aux_1,\n        state_2,\n        aux_2,\n    )\nend\nnumerical_volume_flux_first_order!(::Nothing, _...) = nothing\n\nfunction numerical_flux_first_order!(\n    numerical_flux::EntropyConservative,\n    balance_law::BalanceLaw,\n    fluxᵀn::Vars{S},\n    normal_vector::SVector,\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n    direction,\n) where {S, A}\n    # Let's just work with straight arrays!\n    fluxᵀn = parent(fluxᵀn)\n    state_1 = parent(state_prognostic⁻)\n    aux_1 = parent(state_auxiliary⁻)\n    state_2 = parent(state_prognostic⁺)\n    aux_2 = parent(state_auxiliary⁺)\n\n    # create the storage for the volume flux\n    num_state = length(fluxᵀn)\n    FT = eltype(fluxᵀn)\n    H = MArray{Tuple{3, num_state}, FT}(undef)\n    fill!(H, -zero(FT))\n\n    # Compute the volume flux\n    numerical_volume_flux_first_order!(\n        numerical_flux,\n        balance_law,\n        H,\n        state_1,\n        aux_1,\n        state_2,\n        aux_2,\n    )\n\n    # Multiply in the normal\n    @inbounds fluxᵀn .+=\n        normal_vector[1] * H[1, :] +\n        normal_vector[2] * H[2, :] +\n        normal_vector[3] * H[3, :]\nend\n\n# Convenience function for entropy stable discretizations\n\"\"\"\n    ave(a, b)\nThis computes the mean\n    ave(a, b) = (a + b) / 2\n\"\"\"\nave(a, b) = (a + b) / 2\n\n# Convenience function for entropy stable discretizations\n\"\"\"\n    logave(a, b)\nThis computes the logarithmic mean\n    logave(a, b) = (a - b) / (log(a) - log(b))\nin a numerically stable way using the method in Appendix B. of Ishail and Roe\n<doi:10.1016/j.jcp.2009.04.021>.\n\"\"\"\nfunction logave(a, b)\n    ζ = a / b\n    f = (ζ - 1) / (ζ + 1)\n    u = f^2\n    ϵ = eps(eltype(u))\n\n    if u < ϵ\n        F = @evalpoly(u, one(u), one(u) / 3, one(u) / 5, one(u) / 7, one(u) / 9)\n    else\n        F = log(ζ) / 2f\n    end\n\n    (a + b) / 2F\nend\n\n\"\"\"\n    NumericalFluxSecondOrder\n\nAny `N <: NumericalFluxSecondOrder` should define the a method for\n\n    numerical_flux_second_order!(\n        numerical_flux::N,\n        balance_law::BalanceLaw,\n        flux,\n        normal_vector⁻,\n        Q⁻, Qstate_gradient_flux⁻, Qaux⁻,\n        Q⁺, Qstate_gradient_flux⁺, Qaux⁺,\n        t\n    )\n\nwhere\n- `flux` is the numerical flux array\n- `normal_vector⁻` is the unit normal\n- `Q⁻`/`Q⁺` are the minus/positive state arrays\n- `Qstate_gradient_flux⁻`/`Qstate_gradient_flux⁺` are the minus/positive diffusive state arrays\n- `Qstate_gradient_flux⁻`/`Qstate_gradient_flux⁺` are the minus/positive auxiliary state arrays\n- `t` is the time\n\nAn optional method can also be defined for\n\n    numerical_boundary_flux_second_order!(\n        numerical_flux::N,\n        balance_law::BalanceLaw,\n        flux,\n        normal_vector⁻,\n        Q⁻, Qstate_gradient_flux⁻, Qaux⁻,\n        Q⁺, Qstate_gradient_flux⁺, Qaux⁺,\n        bctype,\n        t\n    )\n\n\"\"\"\nabstract type NumericalFluxSecondOrder end\n\nfunction numerical_flux_second_order! end\n\nfunction numerical_boundary_flux_second_order! end\n\n\"\"\"\n    CentralNumericalFluxSecondOrder <: NumericalFluxSecondOrder\n\nThe central numerical flux for diffusive terms\n\n# Usage\n\n    CentralNumericalFluxSecondOrder()\n\nRequires a `flux_second_order!` for the balance law.\n\"\"\"\nstruct CentralNumericalFluxSecondOrder <: NumericalFluxSecondOrder end\n\nfunction numerical_flux_second_order!(\n    ::CentralNumericalFluxSecondOrder,\n    balance_law::BalanceLaw,\n    fluxᵀn::Vars{S},\n    normal_vector⁻::SVector,\n    state_prognostic⁻::Vars{S},\n    state_gradient_flux⁻::Vars{D},\n    state_hyperdiffusive⁻::Vars{HD},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_gradient_flux⁺::Vars{D},\n    state_hyperdiffusive⁺::Vars{HD},\n    state_auxiliary⁺::Vars{A},\n    t,\n) where {S, D, HD, A}\n\n    FT = eltype(fluxᵀn)\n    num_state_prognostic = number_states(balance_law, Prognostic())\n    fluxᵀn = parent(fluxᵀn)\n\n    flux⁻ = similar(fluxᵀn, Size(3, num_state_prognostic))\n    fill!(flux⁻, -zero(FT))\n    flux_second_order!(\n        balance_law,\n        Grad{S}(flux⁻),\n        state_prognostic⁻,\n        state_gradient_flux⁻,\n        state_hyperdiffusive⁻,\n        state_auxiliary⁻,\n        t,\n    )\n\n    flux⁺ = similar(fluxᵀn, Size(3, num_state_prognostic))\n    fill!(flux⁺, -zero(FT))\n    flux_second_order!(\n        balance_law,\n        Grad{S}(flux⁺),\n        state_prognostic⁺,\n        state_gradient_flux⁺,\n        state_hyperdiffusive⁺,\n        state_auxiliary⁺,\n        t,\n    )\n\n    fluxᵀn .+= (flux⁻ + flux⁺)' * (normal_vector⁻ / 2)\nend\n\nabstract type DivNumericalPenalty end\nstruct CentralNumericalFluxDivergence <: DivNumericalPenalty end\n\nfunction numerical_flux_divergence!(\n    ::CentralNumericalFluxDivergence,\n    balance_law::BalanceLaw,\n    div_penalty::Vars{GL},\n    normal_vector::SVector,\n    grad⁻::Grad{GL},\n    grad⁺::Grad{GL},\n) where {GL}\n    parent(div_penalty) .=\n        (parent(grad⁺) .+ parent(grad⁻))' * (normal_vector / 2)\nend\n\nfunction numerical_boundary_flux_divergence!(\n    numerical_flux::CentralNumericalFluxDivergence,\n    bctype,\n    balance_law::BalanceLaw,\n    div_penalty::Vars{GL},\n    normal_vector::SVector,\n    grad⁻::Grad{GL},\n    state_auxiliary⁻::Vars{A},\n    grad⁺::Grad{GL},\n    state_auxiliary⁺::Vars{A},\n    t,\n) where {GL, A}\n    boundary_state!(\n        numerical_flux,\n        bctype,\n        balance_law,\n        grad⁺,\n        state_auxiliary⁺,\n        normal_vector,\n        grad⁻,\n        state_auxiliary⁻,\n        t,\n    )\n    numerical_flux_divergence!(\n        numerical_flux,\n        balance_law,\n        div_penalty,\n        normal_vector,\n        grad⁻,\n        grad⁺,\n    )\nend\n\nabstract type GradNumericalFlux end\nstruct CentralNumericalFluxHigherOrder <: GradNumericalFlux end\n\nfunction numerical_flux_higher_order!(\n    ::CentralNumericalFluxHigherOrder,\n    balance_law::BalanceLaw,\n    hyperdiff::Vars{HD},\n    normal_vector::SVector,\n    lap⁻::Vars{GL},\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    lap⁺::Vars{GL},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n) where {HD, GL, S, A}\n    G = normal_vector .* (parent(lap⁺) .- parent(lap⁻))' ./ 2\n    transform_post_gradient_laplacian!(\n        balance_law,\n        hyperdiff,\n        Grad{GL}(G),\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        t,\n    )\nend\n\nfunction numerical_boundary_flux_higher_order!(\n    numerical_flux::CentralNumericalFluxHigherOrder,\n    bctype,\n    balance_law::BalanceLaw,\n    hyperdiff::Vars{HD},\n    normal_vector::SVector,\n    lap⁻::Vars{GL},\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    lap⁺::Vars{GL},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n) where {HD, GL, S, A}\n    boundary_state!(\n        numerical_flux,\n        bctype,\n        balance_law,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        lap⁺,\n        normal_vector,\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        lap⁻,\n        t,\n    )\n    numerical_flux_higher_order!(\n        numerical_flux,\n        balance_law,\n        hyperdiff,\n        normal_vector,\n        lap⁻,\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        lap⁺,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n    )\nend\n\nnumerical_boundary_flux_second_order!(\n    numerical_flux::CentralNumericalFluxSecondOrder,\n    bctype,\n    balance_law::BalanceLaw,\n    fluxᵀn::Vars{S},\n    normal_vector::SVector,\n    state_prognostic⁻::Vars{S},\n    state_gradient_flux⁻::Vars{D},\n    state_hyperdiffusive⁻::Vars{HD},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_gradient_flux⁺::Vars{D},\n    state_hyperdiffusive⁺::Vars{HD},\n    state_auxiliary⁺::Vars{A},\n    t,\n    state1⁻::Vars{S},\n    diff1⁻::Vars{D},\n    aux1⁻::Vars{A},\n) where {S, D, HD, A} = normal_boundary_flux_second_order!(\n    numerical_flux,\n    bctype,\n    balance_law,\n    fluxᵀn,\n    normal_vector,\n    state_prognostic⁻,\n    state_gradient_flux⁻,\n    state_hyperdiffusive⁻,\n    state_auxiliary⁻,\n    state_prognostic⁺,\n    state_gradient_flux⁺,\n    state_hyperdiffusive⁺,\n    state_auxiliary⁺,\n    t,\n    state1⁻,\n    diff1⁻,\n    aux1⁻,\n)\n\nfunction normal_boundary_flux_second_order!(\n    numerical_flux,\n    bctype,\n    balance_law::BalanceLaw,\n    fluxᵀn::Vars{S},\n    normal_vector,\n    state_prognostic⁻,\n    state_gradient_flux⁻,\n    state_hyperdiffusive⁻,\n    state_auxiliary⁻,\n    state_prognostic⁺,\n    state_gradient_flux⁺,\n    state_hyperdiffusive⁺,\n    state_auxiliary⁺,\n    t,\n    state1⁻,\n    diff1⁻,\n    aux1⁻,\n) where {S}\n    FT = eltype(fluxᵀn)\n    num_state_prognostic = number_states(balance_law, Prognostic())\n    fluxᵀn = parent(fluxᵀn)\n\n    flux = similar(fluxᵀn, Size(3, num_state_prognostic))\n    fill!(flux, -zero(FT))\n    boundary_flux_second_order!(\n        numerical_flux,\n        bctype,\n        balance_law,\n        Grad{S}(flux),\n        state_prognostic⁺,\n        state_gradient_flux⁺,\n        state_hyperdiffusive⁺,\n        state_auxiliary⁺,\n        normal_vector,\n        state_prognostic⁻,\n        state_gradient_flux⁻,\n        state_hyperdiffusive⁻,\n        state_auxiliary⁻,\n        t,\n        state1⁻,\n        diff1⁻,\n        aux1⁻,\n    )\n\n    fluxᵀn .+= flux' * normal_vector\nend\n\n# This is the function that my be overloaded for flux-based BCs\nfunction boundary_flux_second_order!(\n    numerical_flux::NumericalFluxSecondOrder,\n    bctype,\n    balance_law,\n    flux,\n    state_prognostic⁺,\n    state_gradient_flux⁺,\n    state_hyperdiffusive⁺,\n    state_auxiliary⁺,\n    normal_vector,\n    state_prognostic⁻,\n    state_gradient_flux⁻,\n    state_hyperdiffusive⁻,\n    state_auxiliary⁻,\n    t,\n    state1⁻,\n    diff1⁻,\n    aux1⁻,\n)\n    boundary_state!(\n        numerical_flux,\n        bctype,\n        balance_law,\n        state_prognostic⁺,\n        state_gradient_flux⁺,\n        state_hyperdiffusive⁺,\n        state_auxiliary⁺,\n        normal_vector,\n        state_prognostic⁻,\n        state_gradient_flux⁻,\n        state_hyperdiffusive⁻,\n        state_auxiliary⁻,\n        t,\n        state1⁻,\n        diff1⁻,\n        aux1⁻,\n    )\n    flux_second_order!(\n        balance_law,\n        flux,\n        state_prognostic⁺,\n        state_gradient_flux⁺,\n        state_hyperdiffusive⁺,\n        state_auxiliary⁺,\n        t,\n    )\nend\n\n# Array wrappers for numerical Fluxes\nfunction numerical_flux_first_order!(\n    numerical_flux_first_order,\n    balance_law,\n    flux::AbstractArray,\n    normal_vector::AbstractArray,\n    state_prognostic⁻::AbstractArray,\n    state_auxiliary⁻::AbstractArray,\n    state_prognostic⁺::AbstractArray,\n    state_auxiliary⁺::AbstractArray,\n    t,\n    face_direction,\n)\n    FT = eltype(flux)\n    numerical_flux_first_order!(\n        numerical_flux_first_order,\n        balance_law,\n        Vars{vars_state(balance_law, Prognostic(), FT)}(flux),\n        SVector(normal_vector),\n        Vars{vars_state(balance_law, Prognostic(), FT)}(state_prognostic⁻),\n        Vars{vars_state(balance_law, Auxiliary(), FT)}(state_auxiliary⁻),\n        Vars{vars_state(balance_law, Prognostic(), FT)}(state_prognostic⁺),\n        Vars{vars_state(balance_law, Auxiliary(), FT)}(state_auxiliary⁺),\n        t,\n        face_direction,\n    )\nend\n\nfunction numerical_boundary_flux_first_order!(\n    numerical_flux_first_order,\n    bctag::Int,\n    balance_law,\n    flux::AbstractArray,\n    normal_vector::AbstractArray,\n    state_prognostic⁻::AbstractArray,\n    state_auxiliary⁻::AbstractArray,\n    state_prognostic⁺::AbstractArray,\n    state_auxiliary⁺::AbstractArray,\n    t,\n    face_direction,\n    state_prognostic_bottom1::AbstractArray,\n    state_auxiliary_bottom1::AbstractArray,\n)\n    FT = eltype(flux)\n    bcs = boundary_conditions(balance_law)\n    # TODO: there is probably a better way to unroll this loop\n    Base.Cartesian.@nif 7 d -> bctag == d <= length(bcs) d -> begin\n        bc = bcs[d]\n        numerical_boundary_flux_first_order!(\n            numerical_flux_first_order,\n            bc,\n            balance_law,\n            Vars{vars_state(balance_law, Prognostic(), FT)}(flux),\n            SVector(normal_vector),\n            Vars{vars_state(balance_law, Prognostic(), FT)}(state_prognostic⁻),\n            Vars{vars_state(balance_law, Auxiliary(), FT)}(state_auxiliary⁻),\n            Vars{vars_state(balance_law, Prognostic(), FT)}(state_prognostic⁺),\n            Vars{vars_state(balance_law, Auxiliary(), FT)}(state_auxiliary⁺),\n            t,\n            face_direction,\n            Vars{vars_state(balance_law, Prognostic(), FT)}(\n                state_prognostic_bottom1,\n            ),\n            Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                state_auxiliary_bottom1,\n            ),\n        )\n    end d -> throw(BoundsError(bcs, bctag))\nend\n\nfunction numerical_flux_second_order!(\n    numerical_flux_second_order,\n    balance_law,\n    flux::AbstractArray,\n    normal_vector::AbstractArray,\n    state_prognostic⁻::AbstractArray,\n    state_gradient_flux⁻::AbstractArray,\n    state_hyperdiffusive⁻::AbstractArray,\n    state_auxiliary⁻::AbstractArray,\n    state_prognostic⁺::AbstractArray,\n    state_gradient_flux⁺::AbstractArray,\n    state_hyperdiffusive⁺::AbstractArray,\n    state_auxiliary⁺::AbstractArray,\n    t,\n)\n    FT = eltype(flux)\n    numerical_flux_second_order!(\n        numerical_flux_second_order,\n        balance_law,\n        Vars{vars_state(balance_law, Prognostic(), FT)}(flux),\n        SVector(normal_vector),\n        Vars{vars_state(balance_law, Prognostic(), FT)}(state_prognostic⁻),\n        Vars{vars_state(balance_law, GradientFlux(), FT)}(state_gradient_flux⁻),\n        Vars{vars_state(balance_law, Hyperdiffusive(), FT)}(\n            state_hyperdiffusive⁻,\n        ),\n        Vars{vars_state(balance_law, Auxiliary(), FT)}(state_auxiliary⁻),\n        Vars{vars_state(balance_law, Prognostic(), FT)}(state_prognostic⁺),\n        Vars{vars_state(balance_law, GradientFlux(), FT)}(state_gradient_flux⁺),\n        Vars{vars_state(balance_law, Hyperdiffusive(), FT)}(\n            state_hyperdiffusive⁺,\n        ),\n        Vars{vars_state(balance_law, Auxiliary(), FT)}(state_auxiliary⁺),\n        t,\n    )\nend\n\nfunction numerical_boundary_flux_second_order!(\n    numerical_flux_second_order,\n    bctag::Int,\n    balance_law,\n    flux::AbstractArray,\n    normal_vector::AbstractArray,\n    state_prognostic⁻::AbstractArray,\n    state_gradient_flux⁻::AbstractArray,\n    state_hyperdiffusive⁻::AbstractArray,\n    state_auxiliary⁻::AbstractArray,\n    state_prognostic⁺::AbstractArray,\n    state_gradient_flux⁺::AbstractArray,\n    state_hyperdiffusive⁺::AbstractArray,\n    state_auxiliary⁺::AbstractArray,\n    t,\n    state_prognostic_bottom1::AbstractArray,\n    state_auxiliary_bottom1::AbstractArray,\n    state_gradient_flux_bottom1::AbstractArray,\n)\n    FT = eltype(flux)\n    bcs = boundary_conditions(balance_law)\n    # TODO: there is probably a better way to unroll this loop\n    Base.Cartesian.@nif 7 d -> bctag == d <= length(bcs) d -> begin\n        bc = bcs[d]\n        numerical_boundary_flux_second_order!(\n            numerical_flux_second_order,\n            bc,\n            balance_law,\n            Vars{vars_state(balance_law, Prognostic(), FT)}(flux),\n            SVector(normal_vector),\n            Vars{vars_state(balance_law, Prognostic(), FT)}(state_prognostic⁻),\n            Vars{vars_state(balance_law, GradientFlux(), FT)}(\n                state_gradient_flux⁻,\n            ),\n            Vars{vars_state(balance_law, Hyperdiffusive(), FT)}(\n                state_hyperdiffusive⁻,\n            ),\n            Vars{vars_state(balance_law, Auxiliary(), FT)}(state_auxiliary⁻),\n            Vars{vars_state(balance_law, Prognostic(), FT)}(state_prognostic⁺),\n            Vars{vars_state(balance_law, GradientFlux(), FT)}(\n                state_gradient_flux⁺,\n            ),\n            Vars{vars_state(balance_law, Hyperdiffusive(), FT)}(\n                state_hyperdiffusive⁺,\n            ),\n            Vars{vars_state(balance_law, Auxiliary(), FT)}(state_auxiliary⁺),\n            t,\n            Vars{vars_state(balance_law, Prognostic(), FT)}(\n                state_prognostic_bottom1,\n            ),\n            Vars{vars_state(balance_law, GradientFlux(), FT)}(\n                state_gradient_flux_bottom1,\n            ),\n            Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                state_auxiliary_bottom1,\n            ),\n        )\n    end d -> throw(BoundsError(bcs, bctag))\nend\n\nfunction numerical_boundary_flux_gradient!(\n    numerical_flux_gradient,\n    bctag::Int,\n    balance_law,\n    flux::AbstractArray,\n    normal_vector::AbstractArray,\n    state_gradient⁻::AbstractArray,\n    state_prognostic⁻::AbstractArray,\n    state_auxiliary⁻::AbstractArray,\n    state_gradient⁺::AbstractArray,\n    state_prognostic⁺::AbstractArray,\n    state_auxiliary⁺::AbstractArray,\n    t,\n    state_prognostic_bottom1::AbstractArray,\n    state_auxiliary_bottom1::AbstractArray,\n)\n    FT = eltype(flux)\n    bcs = boundary_conditions(balance_law)\n    # TODO: there is probably a better way to unroll this loop\n    Base.Cartesian.@nif 7 d -> bctag == d <= length(bcs) d -> begin\n        bc = bcs[d]\n        # Computes G* incorporating boundary conditions\n        numerical_boundary_flux_gradient!(\n            numerical_flux_gradient,\n            bc,\n            balance_law,\n            flux,\n            SVector(normal_vector),\n            Vars{vars_state(balance_law, Gradient(), FT)}(state_gradient⁻),\n            Vars{vars_state(balance_law, Prognostic(), FT)}(state_prognostic⁻),\n            Vars{vars_state(balance_law, Auxiliary(), FT)}(state_auxiliary⁻),\n            Vars{vars_state(balance_law, Gradient(), FT)}(state_gradient⁺),\n            Vars{vars_state(balance_law, Prognostic(), FT)}(state_prognostic⁺),\n            Vars{vars_state(balance_law, Auxiliary(), FT)}(state_auxiliary⁺),\n            t,\n            Vars{vars_state(balance_law, Prognostic(), FT)}(\n                state_prognostic_bottom1,\n            ),\n            Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                state_auxiliary_bottom1,\n            ),\n        )\n    end d -> throw(BoundsError(bcs, bctag))\nend\n\nend # end of module\n"
  },
  {
    "path": "src/Numerics/DGMethods/SpaceDiscretization.jl",
    "content": "using .NumericalFluxes:\n    CentralNumericalFluxHigherOrder, CentralNumericalFluxDivergence\n\n\"\"\"\n    SpaceDiscretization\n\nSupertype for spatial discretizations.\n\nMust have the following properties:\n\n    - `grid`\n    - `balance_law`\n    - `state_auxiliary`\n\"\"\"\nabstract type SpaceDiscretization end\n\n\nbasic_grid_info(spacedisc::SpaceDiscretization) =\n    basic_grid_info(spacedisc.grid)\nfunction basic_grid_info(grid)\n    dim = dimensionality(grid)\n    # Tuple of polynomial degrees (N₁, N₂, N₃)\n    N = polynomialorders(grid)\n    @assert dim == 2 || N[1] == N[2]\n\n    # Number of quadrature point in each direction (Nq₁, Nq₂, Nq₃)\n    Nq = N .+ 1\n\n    # Number of quadrature point in the horizontal direction Nq₁ * Nq₂\n    Nqh = Nq[1] * Nq[2]\n\n    # Number of quadrature points in the vertical Nq₃\n    Nqk = dim == 2 ? 1 : Nq[dim]\n\n    Np = dofs_per_element(grid)\n    Nfp_v, Nfp_h = div.(Np, (Nq[1], Nq[end]))\n\n    topology_info = basic_topology_info(grid.topology)\n\n    ninteriorelem = length(grid.interiorelems)\n    nexteriorelem = length(grid.exteriorelems)\n\n    nface = 2 * dim\n\n    grid_info = (\n        dim = dim,\n        N = N,\n        Nq = Nq,\n        Nqh = Nqh,\n        Nqk = Nqk,\n        Nfp_v = Nfp_v,\n        Nfp_h = Nfp_h,\n        Np = Np,\n        nface = nface,\n        ninteriorelem = ninteriorelem,\n        nexteriorelem = nexteriorelem,\n    )\n\n    return merge(grid_info, topology_info)\nend\n\nfunction basic_launch_info(spacedisc::SpaceDiscretization)\n    device = array_device(spacedisc.state_auxiliary)\n    grid_info = basic_grid_info(spacedisc.grid)\n    return merge(grid_info, (device = device,))\nend\n\nfunction (spacedisc::SpaceDiscretization)(\n    tendency,\n    state_prognostic,\n    param,\n    t;\n    increment = false,\n)\n    # TODO deprecate increment argument\n    spacedisc(tendency, state_prognostic, param, t, true, increment)\nend\n\nfunction init_ode_state(\n    spacedisc::SpaceDiscretization,\n    args...;\n    init_on_cpu = false,\n    fill_nan = false,\n)\n    grid = spacedisc.grid\n    balance_law = spacedisc.balance_law\n    state_auxiliary = spacedisc.state_auxiliary\n\n    device = arraytype(grid) <: Array ? CPU() : CUDADevice()\n\n    state_prognostic =\n        create_state(balance_law, grid, Prognostic(), fill_nan = fill_nan)\n\n    topology = grid.topology\n    Np = dofs_per_element(grid)\n\n    dim = dimensionality(grid)\n    N = polynomialorders(grid)\n    nrealelem = length(topology.realelems)\n\n    if !init_on_cpu\n        event = Event(device)\n        event = kernel_init_state_prognostic!(device, min(Np, 1024))(\n            balance_law,\n            Val(dim),\n            Val(N),\n            state_prognostic.data,\n            state_auxiliary.data,\n            grid.vgeo,\n            topology.realelems,\n            args...;\n            ndrange = Np * nrealelem,\n            dependencies = (event,),\n        )\n        wait(device, event)\n    else\n        h_state_prognostic = similar(state_prognostic, Array)\n        h_state_auxiliary = similar(state_auxiliary, Array)\n        h_state_auxiliary .= state_auxiliary\n        event = kernel_init_state_prognostic!(CPU(), Np)(\n            balance_law,\n            Val(dim),\n            Val(N),\n            h_state_prognostic.data,\n            h_state_auxiliary.data,\n            Array(grid.vgeo),\n            topology.realelems,\n            args...;\n            ndrange = Np * nrealelem,\n        )\n        wait(event)\n        state_prognostic .= h_state_prognostic\n        state_auxiliary .= h_state_auxiliary\n    end\n\n    event = Event(device)\n    event = MPIStateArrays.begin_ghost_exchange!(\n        state_prognostic;\n        dependencies = event,\n    )\n    event = MPIStateArrays.end_ghost_exchange!(\n        state_prognostic;\n        dependencies = event,\n    )\n    wait(device, event)\n\n    return state_prognostic\nend\n\nupdate_auxiliary_state_gradient!(::SpaceDiscretization, _...) = false\n\n# By default, we call update_auxiliary_state!, given\n# nodal_update_auxiliary_state!, defined for the\n# particular balance_law:\n\n# TODO: this should really be a separate function\nfunction update_auxiliary_state!(\n    f!,\n    spacedisc::SpaceDiscretization,\n    m::BalanceLaw,\n    state_prognostic::MPIStateArray,\n    t::Real,\n    elems::UnitRange = spacedisc.grid.topology.realelems;\n    diffusive = false,\n)\n    device = array_device(state_prognostic)\n\n    grid = spacedisc.grid\n    topology = grid.topology\n\n    dim = dimensionality(grid)\n    N = polynomialorders(grid)\n    nelem = length(elems)\n\n    Np = dofs_per_element(grid)\n\n    knl_nodal_update_auxiliary_state! =\n        kernel_nodal_update_auxiliary_state!(device, min(Np, 1024))\n    ### Update state_auxiliary variables\n    event = Event(device)\n    if diffusive\n        event = knl_nodal_update_auxiliary_state!(\n            m,\n            Val(dim),\n            Val(N),\n            f!,\n            state_prognostic.data,\n            spacedisc.state_auxiliary.data,\n            spacedisc.state_gradient_flux.data,\n            t,\n            elems,\n            grid.activedofs;\n            ndrange = Np * nelem,\n            dependencies = (event,),\n        )\n    else\n        event = knl_nodal_update_auxiliary_state!(\n            m,\n            Val(dim),\n            Val(N),\n            f!,\n            state_prognostic.data,\n            spacedisc.state_auxiliary.data,\n            t,\n            elems,\n            grid.activedofs;\n            ndrange = Np * nelem,\n            dependencies = (event,),\n        )\n    end\n    checked_wait(device, event, nothing, spacedisc.check_for_crashes)\nend\n\nfunction MPIStateArrays.MPIStateArray(dg::SpaceDiscretization)\n    balance_law = dg.balance_law\n    grid = dg.grid\n\n    state_prognostic = create_state(balance_law, grid, Prognostic())\n\n    return state_prognostic\nend\n\nfunction restart_auxiliary_state(bl, grid, aux_data, direction)\n    state_auxiliary = create_state(bl, grid, Auxiliary())\n    state_auxiliary =\n        init_state(state_auxiliary, bl, grid, direction, Auxiliary())\n    state_auxiliary .= aux_data\n    return state_auxiliary\nend\n\n# TODO: this should really be a separate function\n\"\"\"\n    init_state_auxiliary!(\n        bl::BalanceLaw,\n        f!,\n        statearray_auxiliary,\n        grid,\n        direction;\n        state_temporary = nothing\n    )\n\nApply `f!(bl, state_auxiliary, tmp, geom)` at each node, storing the result in\n`statearray_auxiliary`, where `tmp` are the values at the corresponding node in\n`state_temporary` and `geom` contains the geometry information.\n\"\"\"\nfunction init_state_auxiliary!(\n    balance_law,\n    init_f!,\n    state_auxiliary,\n    grid,\n    direction;\n    state_temporary = nothing,\n)\n    topology = grid.topology\n    dim = dimensionality(grid)\n    Np = dofs_per_element(grid)\n    N = polynomialorders(grid)\n    vgeo = grid.vgeo\n    device = array_device(state_auxiliary)\n    nrealelem = length(topology.realelems)\n\n    event = Event(device)\n    event = kernel_nodal_init_state_auxiliary!(\n        device,\n        min(Np, 1024),\n        Np * nrealelem,\n    )(\n        balance_law,\n        Val(dim),\n        Val(N),\n        init_f!,\n        state_auxiliary.data,\n        isnothing(state_temporary) ? nothing : state_temporary.data,\n        Val(isnothing(state_temporary) ? @vars() : vars(state_temporary)),\n        vgeo,\n        topology.realelems,\n        dependencies = (event,),\n    )\n\n    event = MPIStateArrays.begin_ghost_exchange!(\n        state_auxiliary;\n        dependencies = event,\n    )\n    event = MPIStateArrays.end_ghost_exchange!(\n        state_auxiliary;\n        dependencies = event,\n    )\n    wait(device, event)\nend\n\n\"\"\"\n    courant(local_courant::Function, dg::DGModel, m::BalanceLaw,\n            state_prognostic::MPIStateArray, direction=EveryDirection())\nReturns the maximum of the evaluation of the function `local_courant`\npointwise throughout the domain.  The function `local_courant` is given an\napproximation of the local node distance `Δx`.  The `direction` controls which\nreference directions are considered when computing the minimum node distance\n`Δx`.\nAn example `local_courant` function is\n    function local_courant(m::AtmosModel, state_prognostic::Vars, state_auxiliary::Vars,\n                           diffusive::Vars, Δx)\n      return Δt * cmax / Δx\n    end\nwhere `Δt` is the time step size and `cmax` is the maximum flow speed in the\nmodel.\n\"\"\"\nfunction courant(\n    local_courant::Function,\n    dg::SpaceDiscretization,\n    m::BalanceLaw,\n    state_prognostic::MPIStateArray,\n    Δt,\n    simtime,\n    direction = EveryDirection(),\n)\n    grid = dg.grid\n    topology = grid.topology\n    nrealelem = length(topology.realelems)\n\n    if nrealelem > 0\n        N = polynomialorders(grid)\n        dim = dimensionality(grid)\n        Nq = N .+ 1\n        Nqk = dim == 2 ? 1 : Nq[dim]\n        device = array_device(grid.vgeo)\n        pointwise_courant = similar(grid.vgeo, prod(Nq), nrealelem)\n        event = Event(device)\n        event = Grids.kernel_min_neighbor_distance!(\n            device,\n            min(Nq[1] * Nq[2] * Nqk, 1024),\n        )(\n            Val(N),\n            Val(dim),\n            direction,\n            pointwise_courant,\n            grid.vgeo,\n            topology.realelems;\n            ndrange = (nrealelem * Nq[1] * Nq[2] * Nqk),\n            dependencies = (event,),\n        )\n        event = kernel_local_courant!(device, min(Nq[1] * Nq[2] * Nqk, 1024))(\n            m,\n            Val(dim),\n            Val(N),\n            pointwise_courant,\n            local_courant,\n            state_prognostic.data,\n            dg.state_auxiliary.data,\n            dg.state_gradient_flux.data,\n            topology.realelems,\n            Δt,\n            simtime,\n            direction;\n            ndrange = (nrealelem * Nq[1] * Nq[2] * Nqk),\n            dependencies = (event,),\n        )\n        checked_wait(device, event, nothing, dg.check_for_crashes)\n\n        rank_courant_max = maximum(pointwise_courant)\n    else\n        rank_courant_max = typemin(eltype(state_prognostic))\n    end\n\n    MPI.Allreduce(rank_courant_max, max, topology.mpicomm)\nend\n\n\"\"\"\n    auxiliary_field_gradient!(::BalanceLaw, ∇state::MPIStateArray,\n                               vars_out, state::MPIStateArray, vars_in, grid;\n                               direction = EveryDirection())\n\nTake the gradient of the variables `vars_in` located in the array `state`\nand stores it in the variables `vars_out` of `∇state`. This function computes\nelement wise gradient without accounting for numerical fluxes and hence\nits primary purpose is to take the gradient of continuous reference fields.\n\n## Examples\n```julia\nFT = eltype(state_auxiliary)\ngrad_Φ = similar(state_auxiliary, vars=@vars(∇Φ::SVector{3, FT}))\nauxiliary_field_gradient!(\n    model,\n    grad_Φ,\n    (\"∇Φ\",),\n    state_auxiliary,\n    (\"orientation.Φ\",),\n    grid,\n)\n```\n\"\"\"\nfunction auxiliary_field_gradient!(\n    m::BalanceLaw,\n    ∇state::MPIStateArray,\n    vars_out,\n    state::MPIStateArray,\n    vars_in,\n    grid,\n    direction = EveryDirection(),\n)\n    topology = grid.topology\n    nrealelem = length(topology.realelems)\n    N = polynomialorders(grid)\n    dim = dimensionality(grid)\n    Nq = N .+ 1\n    Nqk = dim == 2 ? 1 : Nq[dim]\n    device = array_device(state)\n\n    I = varsindices(vars(state), vars_in)\n    O = varsindices(vars(∇state), vars_out)\n\n    event = Event(device)\n\n    if direction isa EveryDirection || direction isa HorizontalDirection\n        # We assume N₁ = N₂, so the same polyorder, quadrature weights,\n        # and differentiation operators are used\n        horizontal_polyorder = N[1]\n        horizontal_D = grid.D[1]\n        horizontal_ω = grid.ω[1]\n        event = dgsem_auxiliary_field_gradient!(device, (Nq[1], Nq[2]))(\n            m,\n            Val(dim),\n            Val(N),\n            HorizontalDirection(),\n            ∇state.data,\n            state.data,\n            grid.vgeo,\n            horizontal_D,\n            horizontal_ω,\n            Val(I),\n            Val(O),\n            false,\n            ndrange = (nrealelem * Nq[1], Nq[2]),\n            dependencies = (event,),\n        )\n    end\n\n    if direction isa EveryDirection || direction isa VerticalDirection\n        vertical_polyorder = N[dim]\n        if vertical_polyorder > 0\n            vertical_D = grid.D[dim]\n            vertical_ω = grid.ω[dim]\n            event = dgsem_auxiliary_field_gradient!(device, (Nq[1], Nq[2]))(\n                m,\n                Val(dim),\n                Val(N),\n                VerticalDirection(),\n                ∇state.data,\n                state.data,\n                grid.vgeo,\n                vertical_D,\n                vertical_ω,\n                Val(I),\n                Val(O),\n                # If we are computing in every direction, we need to\n                # increment after we compute the horizontal values\n                (direction isa EveryDirection);\n                ndrange = (nrealelem * Nq[1], Nq[2]),\n                dependencies = (event,),\n            )\n        else\n            info = basic_grid_info(grid)\n            event = vert_fvm_auxiliary_field_gradient!(device, info.Nfp_h)(\n                m,\n                Val(info),\n                ∇state.data,\n                state.data,\n                grid.vgeo,\n                grid.sgeo,\n                grid.vmap⁻,\n                grid.vmap⁺,\n                grid.elemtobndy,\n                Val(I),\n                Val(O),\n                # If we are computing in every direction, we need to\n                # increment after we compute the horizontal values\n                (direction isa EveryDirection);\n                ndrange = (nrealelem * info.Nfp_h),\n                dependencies = (event,),\n            )\n        end\n    end\n    wait(device, event)\nend\n\nfunction hyperdiff_indexmap(balance_law, ::Type{FT}) where {FT}\n    ns_hyperdiff = number_states(balance_law, Hyperdiffusive())\n    if ns_hyperdiff > 0\n        return varsindices(\n            vars_state(balance_law, Gradient(), FT),\n            fieldnames(vars_state(balance_law, GradientLaplacian(), FT)),\n        )\n    else\n        return nothing\n    end\nend\n\n\"\"\"\n    launch_volume_gradients!(spacedisc, state_prognostic, t; dependencies)\n\nLaunches horizontal and vertical kernels for computing the volume gradients.\n\"\"\"\nfunction launch_volume_gradients!(spacedisc, state_prognostic, t; dependencies)\n    FT = eltype(state_prognostic)\n    # XXX: This is until FVM with hyperdiffusion for DG is implemented\n    if spacedisc isa DGFVModel\n        @assert 0 == number_states(spacedisc.balance_law, Hyperdiffusive())\n        Qhypervisc_grad_data = nothing\n    elseif spacedisc isa DGModel\n        Qhypervisc_grad_data = spacedisc.states_higher_order[1].data\n    end\n\n    # Workgroup is determined by the number of quadrature points\n    # in the horizontal direction. For each horizontal quadrature\n    # point, we operate on a stack of quadrature in the vertical\n    # direction. (Iteration space is in the horizontal)\n    info = basic_launch_info(spacedisc)\n\n    # We assume (in 3-D) that both x and y directions\n    # are discretized using the same polynomial order, Nq[1] == Nq[2].\n    # In 2-D, the workgroup spans the entire set of quadrature points:\n    # Nq[1] * Nq[2]\n    workgroup = (info.Nq[1], info.Nq[2])\n    ndrange = (info.Nq[1] * info.nrealelem, info.Nq[2])\n    comp_stream = dependencies\n\n    # If the model direction is EveryDirection, we need to perform\n    # both horizontal AND vertical kernel calls; otherwise, we only\n    # call the kernel corresponding to the model direction `spacedisc.diffusion_direction`\n    if spacedisc.diffusion_direction isa EveryDirection ||\n       spacedisc.diffusion_direction isa HorizontalDirection\n\n        # We assume N₁ = N₂, so the same polyorder, quadrature weights,\n        # and differentiation operators are used\n        horizontal_polyorder = info.N[1]\n        horizontal_D = spacedisc.grid.D[1]\n        comp_stream = volume_gradients!(info.device, workgroup)(\n            spacedisc.balance_law,\n            Val(info),\n            HorizontalDirection(),\n            state_prognostic.data,\n            spacedisc.state_gradient_flux.data,\n            Qhypervisc_grad_data,\n            spacedisc.state_auxiliary.data,\n            spacedisc.grid.vgeo,\n            t,\n            horizontal_D,\n            Val(hyperdiff_indexmap(spacedisc.balance_law, FT)),\n            spacedisc.grid.topology.realelems,\n            ndrange = ndrange,\n            dependencies = comp_stream,\n        )\n    end\n\n    # Now we call the kernel corresponding to the vertical direction\n    if spacedisc isa DGModel && (\n        spacedisc.diffusion_direction isa EveryDirection ||\n        spacedisc.diffusion_direction isa VerticalDirection\n    )\n\n        # Vertical polynomial degree and differentiation matrix\n        vertical_polyorder = info.N[info.dim]\n        vertical_D = spacedisc.grid.D[info.dim]\n        comp_stream = volume_gradients!(info.device, workgroup)(\n            spacedisc.balance_law,\n            Val(info),\n            VerticalDirection(),\n            state_prognostic.data,\n            spacedisc.state_gradient_flux.data,\n            Qhypervisc_grad_data,\n            spacedisc.state_auxiliary.data,\n            spacedisc.grid.vgeo,\n            t,\n            vertical_D,\n            Val(hyperdiff_indexmap(spacedisc.balance_law, FT)),\n            spacedisc.grid.topology.realelems,\n            # If we are computing the volume gradient in every direction, we\n            # need to increment into the appropriate fields _after_ the\n            # horizontal computation.\n            !(spacedisc.diffusion_direction isa VerticalDirection),\n            ndrange = ndrange,\n            dependencies = comp_stream,\n        )\n    end\n    return comp_stream\nend\n\n\"\"\"\n    launch_interface_gradients!(spacedisc, state_prognostic, t; surface::Symbol, dependencies)\n\nLaunches horizontal and vertical kernels for computing the interface gradients.\nThe argument `surface` is either `:interior` or `:exterior`, which denotes whether\nwe are computing interface gradients on boundaries which are interior (exterior resp.)\nto the _parallel_ boundary.\n\"\"\"\nfunction launch_interface_gradients!(\n    spacedisc,\n    state_prognostic,\n    t;\n    surface::Symbol,\n    dependencies,\n)\n    @assert surface === :interior || surface === :exterior\n    # XXX: This is until FVM with DG hyperdiffusion is implemented\n    if spacedisc isa DGFVModel\n        @assert 0 == number_states(spacedisc.balance_law, Hyperdiffusive())\n        Qhypervisc_grad_data = nothing\n    elseif spacedisc isa DGModel\n        Qhypervisc_grad_data = spacedisc.states_higher_order[1].data\n    end\n\n    FT = eltype(state_prognostic)\n\n    info = basic_launch_info(spacedisc)\n    comp_stream = dependencies\n\n    # If the model direction is EveryDirection, we need to perform\n    # both horizontal AND vertical kernel calls; otherwise, we only\n    # call the kernel corresponding to the model direction `spacedisc.diffusion_direction`\n    if spacedisc.diffusion_direction isa EveryDirection ||\n       spacedisc.diffusion_direction isa HorizontalDirection\n\n        workgroup = info.Nfp_v\n        if surface === :interior\n            elems = spacedisc.grid.interiorelems\n            ndrange = workgroup * info.ninteriorelem\n        else\n            elems = spacedisc.grid.exteriorelems\n            ndrange = workgroup * info.nexteriorelem\n        end\n\n        # Hoirzontal polynomial order (assumes same for both horizontal directions)\n        horizontal_polyorder = info.N[1]\n\n        comp_stream = dgsem_interface_gradients!(info.device, workgroup)(\n            spacedisc.balance_law,\n            Val(info),\n            HorizontalDirection(),\n            spacedisc.numerical_flux_gradient,\n            state_prognostic.data,\n            spacedisc.state_gradient_flux.data,\n            Qhypervisc_grad_data,\n            spacedisc.state_auxiliary.data,\n            spacedisc.grid.vgeo,\n            spacedisc.grid.sgeo,\n            t,\n            spacedisc.grid.vmap⁻,\n            spacedisc.grid.vmap⁺,\n            spacedisc.grid.elemtobndy,\n            Val(hyperdiff_indexmap(spacedisc.balance_law, FT)),\n            elems;\n            ndrange = ndrange,\n            dependencies = comp_stream,\n        )\n    end\n\n    # Vertical interface kernel call\n    if spacedisc.diffusion_direction isa EveryDirection ||\n       spacedisc.diffusion_direction isa VerticalDirection\n\n        workgroup = info.Nfp_h\n        if surface === :interior\n            elems = spacedisc.grid.interiorelems\n            ndrange = workgroup * info.ninteriorelem\n        else\n            elems = spacedisc.grid.exteriorelems\n            ndrange = workgroup * info.nexteriorelem\n        end\n\n        # Vertical polynomial degree\n        vertical_polyorder = info.N[info.dim]\n\n        if spacedisc isa DGModel\n            comp_stream = dgsem_interface_gradients!(info.device, workgroup)(\n                spacedisc.balance_law,\n                Val(info),\n                VerticalDirection(),\n                spacedisc.numerical_flux_gradient,\n                state_prognostic.data,\n                spacedisc.state_gradient_flux.data,\n                Qhypervisc_grad_data,\n                spacedisc.state_auxiliary.data,\n                spacedisc.grid.vgeo,\n                spacedisc.grid.sgeo,\n                t,\n                spacedisc.grid.vmap⁻,\n                spacedisc.grid.vmap⁺,\n                spacedisc.grid.elemtobndy,\n                Val(hyperdiff_indexmap(spacedisc.balance_law, FT)),\n                elems;\n                ndrange = ndrange,\n                dependencies = comp_stream,\n            )\n        elseif spacedisc isa DGFVModel\n            # Make sure FVM in the vertical\n            @assert info.N[info.dim] == 0\n\n            # The FVM will only work on stacked grids!\n            @assert isstacked(spacedisc.grid.topology)\n            nvertelem = spacedisc.grid.topology.stacksize\n            periodicstack = spacedisc.grid.topology.periodicstack\n\n            # 1 thread per degree freedom per element\n            comp_stream = vert_fvm_interface_gradients!(info.device, workgroup)(\n                spacedisc.balance_law,\n                Val(info),\n                Val(nvertelem),\n                Val(periodicstack),\n                VerticalDirection(),\n                state_prognostic.data,\n                spacedisc.state_gradient_flux.data,\n                spacedisc.state_auxiliary.data,\n                spacedisc.grid.vgeo,\n                spacedisc.grid.sgeo,\n                t,\n                spacedisc.grid.elemtobndy,\n                elems,\n                # If we are computing in every direction, we need to\n                # increment after we compute the horizontal values\n                spacedisc.direction isa EveryDirection,\n                ndrange = ndrange,\n                dependencies = comp_stream,\n            )\n        else\n            error(\"unknown spatial discretization: $(typeof(spacedisc))\")\n        end\n    end\n    return comp_stream\nend\n\n\"\"\"\n    launch_volume_divergence_of_gradients!(dg, state_prognostic, t; dependencies)\n\nLaunches horizontal and vertical volume kernels for computing the divergence of gradients.\n\"\"\"\nfunction launch_volume_divergence_of_gradients!(\n    dg,\n    state_prognostic,\n    t;\n    dependencies,\n)\n    Qhypervisc_grad, Qhypervisc_div = dg.states_higher_order\n\n    info = basic_launch_info(dg)\n    workgroup = (info.Nq[1], info.Nq[2])\n    ndrange = (info.nrealelem * info.Nq[1], info.Nq[2])\n    comp_stream = dependencies\n\n    # If the model direction is EveryDirection, we need to perform\n    # both horizontal AND vertical kernel calls; otherwise, we only\n    # call the kernel corresponding to the model direction `dg.diffusion_direction`\n    if dg.diffusion_direction isa EveryDirection ||\n       dg.diffusion_direction isa HorizontalDirection\n\n        # Horizontal polynomial order and differentiation matrix\n        horizontal_polyorder = info.N[1]\n        horizontal_D = dg.grid.D[1]\n\n        comp_stream = volume_divergence_of_gradients!(info.device, workgroup)(\n            dg.balance_law,\n            Val(info),\n            HorizontalDirection(),\n            Qhypervisc_grad.data,\n            Qhypervisc_div.data,\n            dg.grid.vgeo,\n            horizontal_D,\n            dg.grid.topology.realelems;\n            ndrange = ndrange,\n            dependencies = comp_stream,\n        )\n    end\n\n    # And now the vertical kernel call\n    if dg.diffusion_direction isa EveryDirection ||\n       dg.diffusion_direction isa VerticalDirection\n\n        # Vertical polynomial order and differentiation matrix\n        vertical_polyorder = info.N[info.dim]\n        vertical_D = dg.grid.D[info.dim]\n\n        comp_stream = volume_divergence_of_gradients!(info.device, workgroup)(\n            dg.balance_law,\n            Val(info),\n            VerticalDirection(),\n            Qhypervisc_grad.data,\n            Qhypervisc_div.data,\n            dg.grid.vgeo,\n            vertical_D,\n            dg.grid.topology.realelems,\n            # If we are computing the volume gradient in every direction, we\n            # need to increment into the appropriate fields _after_ the\n            # horizontal computation.\n            !(dg.diffusion_direction isa VerticalDirection);\n            ndrange = ndrange,\n            dependencies = comp_stream,\n        )\n    end\n    return comp_stream\nend\n\n\"\"\"\n    launch_interface_divergence_of_gradients!(dg, state_prognostic, t; surface::Symbol, dependencies)\n\nLaunches horizontal and vertical interface kernels for computing the divergence of gradients.\nThe argument `surface` is either `:interior` or `:exterior`, which denotes whether\nwe are computing values on boundaries which are interior (exterior resp.)\nto the _parallel_ boundary.\n\"\"\"\nfunction launch_interface_divergence_of_gradients!(\n    dg,\n    state_prognostic,\n    t;\n    surface::Symbol,\n    dependencies,\n)\n    @assert surface === :interior || surface === :exterior\n    Qhypervisc_grad, Qhypervisc_div = dg.states_higher_order\n\n    info = basic_launch_info(dg)\n    comp_stream = dependencies\n\n    # If the model direction is EveryDirection, we need to perform\n    # both horizontal AND vertical kernel calls; otherwise, we only\n    # call the kernel corresponding to the model direction `dg.diffusion_direction`\n    if dg.diffusion_direction isa EveryDirection ||\n       dg.diffusion_direction isa HorizontalDirection\n\n        workgroup = info.Nfp_v\n        if surface === :interior\n            elems = dg.grid.interiorelems\n            ndrange = info.Nfp_v * info.ninteriorelem\n        else\n            elems = dg.grid.exteriorelems\n            ndrange = info.Nfp_v * info.nexteriorelem\n        end\n\n        # Hoirzontal polynomial order (assumes same for both horizontal directions)\n        horizontal_polyorder = info.N[1]\n\n        comp_stream =\n            interface_divergence_of_gradients!(info.device, workgroup)(\n                dg.balance_law,\n                Val(info),\n                HorizontalDirection(),\n                CentralNumericalFluxDivergence(),\n                Qhypervisc_grad.data,\n                Qhypervisc_div.data,\n                dg.state_auxiliary.data,\n                dg.grid.vgeo,\n                dg.grid.sgeo,\n                dg.grid.vmap⁻,\n                dg.grid.vmap⁺,\n                dg.grid.elemtobndy,\n                t,\n                elems;\n                ndrange = ndrange,\n                dependencies = comp_stream,\n            )\n    end\n\n    # Vertical kernel call\n    if dg.diffusion_direction isa EveryDirection ||\n       dg.diffusion_direction isa VerticalDirection\n\n        workgroup = info.Nfp_h\n        if surface === :interior\n            elems = dg.grid.interiorelems\n            ndrange = info.Nfp_h * info.ninteriorelem\n        else\n            elems = dg.grid.exteriorelems\n            ndrange = info.Nfp_h * info.nexteriorelem\n        end\n\n        # Vertical polynomial degree\n        vertical_polyorder = info.N[info.dim]\n\n        comp_stream =\n            interface_divergence_of_gradients!(info.device, workgroup)(\n                dg.balance_law,\n                Val(info),\n                VerticalDirection(),\n                CentralNumericalFluxDivergence(),\n                Qhypervisc_grad.data,\n                Qhypervisc_div.data,\n                dg.state_auxiliary.data,\n                dg.grid.vgeo,\n                dg.grid.sgeo,\n                dg.grid.vmap⁻,\n                dg.grid.vmap⁺,\n                dg.grid.elemtobndy,\n                t,\n                elems;\n                ndrange = ndrange,\n                dependencies = comp_stream,\n            )\n    end\n\n    return comp_stream\nend\n\n\"\"\"\n    launch_volume_gradients_of_laplacians!(dg, state_prognostic, t; dependencies)\n\nLaunches horizontal and vertical volume kernels for computing the DG gradient of\na second-order DG gradient (Laplacian).\n\"\"\"\nfunction launch_volume_gradients_of_laplacians!(\n    dg,\n    state_prognostic,\n    t;\n    dependencies,\n)\n    Qhypervisc_grad, Qhypervisc_div = dg.states_higher_order\n\n    info = basic_launch_info(dg)\n    workgroup = (info.Nq[1], info.Nq[2])\n    ndrange = (info.nrealelem * info.Nq[1], info.Nq[2])\n    comp_stream = dependencies\n\n    # If the model direction is EveryDirection, we need to perform\n    # both horizontal AND vertical kernel calls; otherwise, we only\n    # call the kernel corresponding to the model direction `dg.diffusion_direction`\n    if dg.diffusion_direction isa EveryDirection ||\n       dg.diffusion_direction isa HorizontalDirection\n\n        # Horizontal polynomial degree\n        horizontal_polyorder = info.N[1]\n        # Horizontal quadrature weights and differentiation matrix\n        horizontal_ω = dg.grid.ω[1]\n        horizontal_D = dg.grid.D[1]\n\n        comp_stream = volume_gradients_of_laplacians!(info.device, workgroup)(\n            dg.balance_law,\n            Val(info),\n            HorizontalDirection(),\n            Qhypervisc_grad.data,\n            Qhypervisc_div.data,\n            state_prognostic.data,\n            dg.state_auxiliary.data,\n            dg.grid.vgeo,\n            horizontal_ω,\n            horizontal_D,\n            dg.grid.topology.realelems,\n            t;\n            ndrange = ndrange,\n            dependencies = comp_stream,\n        )\n    end\n\n    # Vertical kernel call\n    if dg.diffusion_direction isa EveryDirection ||\n       dg.diffusion_direction isa VerticalDirection\n\n        # Vertical polynomial degree\n        vertical_polyorder = info.N[info.dim]\n        # Vertical quadrature weights and differentiation matrix\n        vertical_ω = dg.grid.ω[info.dim]\n        vertical_D = dg.grid.D[info.dim]\n\n        comp_stream = volume_gradients_of_laplacians!(info.device, workgroup)(\n            dg.balance_law,\n            Val(info),\n            VerticalDirection(),\n            Qhypervisc_grad.data,\n            Qhypervisc_div.data,\n            state_prognostic.data,\n            dg.state_auxiliary.data,\n            dg.grid.vgeo,\n            vertical_ω,\n            vertical_D,\n            dg.grid.topology.realelems,\n            t,\n            # If we are computing the volume gradient in every direction, we\n            # need to increment into the appropriate fields _after_ the\n            # horizontal computation.\n            !(dg.diffusion_direction isa VerticalDirection);\n            ndrange = ndrange,\n            dependencies = comp_stream,\n        )\n    end\n\n    return comp_stream\nend\n\n\"\"\"\n    launch_interface_gradients_of_laplacians!(dg, state_prognostic, t; surface::Symbol, dependencies)\n\nLaunches horizontal and vertical interface kernels for computing the gradients of Laplacians\n(second-order gradients). The argument `surface` is either `:interior` or `:exterior`,\nwhich denotes whether we are computing values on boundaries which are interior (exterior resp.)\nto the _parallel_ boundary.\n\"\"\"\nfunction launch_interface_gradients_of_laplacians!(\n    dg,\n    state_prognostic,\n    t;\n    surface::Symbol,\n    dependencies,\n)\n    @assert surface === :interior || surface === :exterior\n    Qhypervisc_grad, Qhypervisc_div = dg.states_higher_order\n    comp_stream = dependencies\n    info = basic_launch_info(dg)\n\n    # If the model direction is EveryDirection, we need to perform\n    # both horizontal AND vertical kernel calls; otherwise, we only\n    # call the kernel corresponding to the model direction `dg.diffusion_direction`\n    if dg.diffusion_direction isa EveryDirection ||\n       dg.diffusion_direction isa HorizontalDirection\n\n        workgroup = info.Nfp_v\n        if surface === :interior\n            elems = dg.grid.interiorelems\n            ndrange = info.Nfp_v * info.ninteriorelem\n        else\n            elems = dg.grid.exteriorelems\n            ndrange = info.Nfp_v * info.nexteriorelem\n        end\n\n        # Hoirzontal polynomial order (assumes same for both horizontal directions)\n        horizontal_polyorder = info.N[1]\n\n        comp_stream =\n            interface_gradients_of_laplacians!(info.device, workgroup)(\n                dg.balance_law,\n                Val(info),\n                HorizontalDirection(),\n                CentralNumericalFluxHigherOrder(),\n                Qhypervisc_grad.data,\n                Qhypervisc_div.data,\n                state_prognostic.data,\n                dg.state_auxiliary.data,\n                dg.grid.vgeo,\n                dg.grid.sgeo,\n                dg.grid.vmap⁻,\n                dg.grid.vmap⁺,\n                dg.grid.elemtobndy,\n                elems,\n                t;\n                ndrange = ndrange,\n                dependencies = comp_stream,\n            )\n    end\n\n    # Vertical kernel call\n    if dg.diffusion_direction isa EveryDirection ||\n       dg.diffusion_direction isa VerticalDirection\n\n        workgroup = info.Nfp_h\n        if surface === :interior\n            elems = dg.grid.interiorelems\n            ndrange = info.Nfp_h * info.ninteriorelem\n        else\n            elems = dg.grid.exteriorelems\n            ndrange = info.Nfp_h * info.nexteriorelem\n        end\n\n        # Vertical polynomial degree\n        vertical_polyorder = info.N[info.dim]\n\n        comp_stream =\n            interface_gradients_of_laplacians!(info.device, workgroup)(\n                dg.balance_law,\n                Val(info),\n                VerticalDirection(),\n                CentralNumericalFluxHigherOrder(),\n                Qhypervisc_grad.data,\n                Qhypervisc_div.data,\n                state_prognostic.data,\n                dg.state_auxiliary.data,\n                dg.grid.vgeo,\n                dg.grid.sgeo,\n                dg.grid.vmap⁻,\n                dg.grid.vmap⁺,\n                dg.grid.elemtobndy,\n                elems,\n                t;\n                ndrange = ndrange,\n                dependencies = comp_stream,\n            )\n    end\n\n    return comp_stream\nend\n\n\"\"\"\n    launch_volume_tendency!(spacedisc, state_prognostic, t; dependencies)\n\nLaunches horizontal and vertical volume kernels for computing tendencies (sources, sinks, etc).\n\"\"\"\nfunction launch_volume_tendency!(\n    spacedisc,\n    tendency,\n    state_prognostic,\n    t,\n    α,\n    β;\n    dependencies,\n)\n    # XXX: This is until FVM with hyperdiffusion is implemented\n    if spacedisc isa DGFVModel\n        @assert 0 == number_states(spacedisc.balance_law, Hyperdiffusive())\n        Qhypervisc_grad_data = nothing\n    elseif spacedisc isa DGModel\n        Qhypervisc_grad_data = spacedisc.states_higher_order[1].data\n    end\n    grad_flux_data = spacedisc.state_gradient_flux.data\n\n    # Workgroup is determined by the number of quadrature points\n    # in the horizontal direction. For each horizontal quadrature\n    # point, we operate on a stack of quadrature in the vertical\n    # direction. (Iteration space is in the horizontal)\n    info = basic_launch_info(spacedisc)\n\n    # We assume (in 3-D) that both x and y directions\n    # are discretized using the same polynomial order, Nq[1] == Nq[2].\n    # In 2-D, the workgroup spans the entire set of quadrature points:\n    # Nq[1] * Nq[2]\n    workgroup = (info.Nq[1], info.Nq[2])\n    ndrange = (info.Nq[1] * info.nrealelem, info.Nq[2])\n    comp_stream = dependencies\n\n    # If the model direction is EveryDirection, we need to perform\n    # both horizontal AND vertical kernel calls; otherwise, we only\n    # call the kernel corresponding to the model direction\n    # `spacedisc.diffusion_direction`\n    if spacedisc.direction isa EveryDirection ||\n       spacedisc.direction isa HorizontalDirection\n\n        # Horizontal polynomial degree\n        horizontal_polyorder = info.N[1]\n        # Horizontal quadrature weights and differentiation matrix\n        horizontal_ω = spacedisc.grid.ω[1]\n        horizontal_D = spacedisc.grid.D[1]\n\n        comp_stream = volume_tendency!(info.device, workgroup)(\n            spacedisc.balance_law,\n            Val(info),\n            spacedisc.direction,\n            HorizontalDirection(),\n            tendency.data,\n            state_prognostic.data,\n            grad_flux_data,\n            Qhypervisc_grad_data,\n            spacedisc.state_auxiliary.data,\n            spacedisc.grid.vgeo,\n            t,\n            horizontal_ω,\n            horizontal_D,\n            spacedisc.grid.topology.realelems,\n            α,\n            β,\n            # If the model direction is horizontal or FV in the vertical,\n            # we want to be sure to add sources\n            spacedisc.direction isa HorizontalDirection ||\n            spacedisc isa DGFVModel,\n            ndrange = ndrange,\n            dependencies = comp_stream,\n        )\n    end\n\n    # Vertical kernel\n    if spacedisc isa DGModel && (\n        spacedisc.direction isa EveryDirection ||\n        spacedisc.direction isa VerticalDirection\n    )\n\n        # Vertical polynomial degree\n        vertical_polyorder = info.N[info.dim]\n        # Vertical quadrature weights and differentiation matrix\n        vertical_ω = spacedisc.grid.ω[info.dim]\n        vertical_D = spacedisc.grid.D[info.dim]\n\n        comp_stream = volume_tendency!(info.device, workgroup)(\n            spacedisc.balance_law,\n            Val(info),\n            spacedisc.direction,\n            VerticalDirection(),\n            tendency.data,\n            state_prognostic.data,\n            grad_flux_data,\n            Qhypervisc_grad_data,\n            spacedisc.state_auxiliary.data,\n            spacedisc.grid.vgeo,\n            t,\n            vertical_ω,\n            vertical_D,\n            spacedisc.grid.topology.realelems,\n            α,\n            # If we are computing the volume gradient in every direction, we\n            # need to increment into the appropriate fields _after_ the\n            # horizontal computation.\n            spacedisc.direction isa EveryDirection ? true : β,\n            # Boolean to add source. In the case of EveryDirection, we always add the sources\n            # in the vertical kernel. Here, we make the assumption that we're either computing\n            # in every direction, or _just_ the vertical direction.\n            true;\n            ndrange = ndrange,\n            dependencies = comp_stream,\n        )\n    end\n\n    return comp_stream\nend\n\n\"\"\"\n    launch_interface_tendency!(spacedisc, state_prognostic, t; surface::Symbol, dependencies)\n\nLaunches horizontal and vertical interface kernels for computing tendencies (sources, sinks, etc).\nThe argument `surface` is either `:interior` or `:exterior`, which denotes whether we are computing\nvalues on boundaries which are interior (exterior resp.) to the _parallel_ boundary.\n\"\"\"\nfunction launch_interface_tendency!(\n    spacedisc,\n    tendency,\n    state_prognostic,\n    t,\n    α,\n    β;\n    surface::Symbol,\n    dependencies,\n)\n    @assert surface === :interior || surface === :exterior\n    # XXX: This is until FVM with diffusion is implemented\n    if spacedisc isa DGFVModel\n        @assert 0 == number_states(spacedisc.balance_law, Hyperdiffusive())\n        Qhypervisc_grad_data = nothing\n    elseif spacedisc isa DGModel\n        Qhypervisc_grad_data = spacedisc.states_higher_order[1].data\n    end\n    grad_flux_data = spacedisc.state_gradient_flux.data\n    numerical_flux_second_order = spacedisc.numerical_flux_second_order\n\n    info = basic_launch_info(spacedisc)\n    comp_stream = dependencies\n\n    # If the model direction is EveryDirection, we need to perform\n    # both horizontal AND vertical kernel calls; otherwise, we only\n    # call the kernel corresponding to the model direction\n    # `spacedisc.diffusion_direction`\n    if spacedisc.direction isa EveryDirection ||\n       spacedisc.direction isa HorizontalDirection\n\n        workgroup = info.Nfp_v\n        if surface === :interior\n            elems = spacedisc.grid.interiorelems\n            ndrange = workgroup * info.ninteriorelem\n        else\n            elems = spacedisc.grid.exteriorelems\n            ndrange = workgroup * info.nexteriorelem\n        end\n\n        # Hoirzontal polynomial order (assumes same for both horizontal\n        # directions)\n        horizontal_polyorder = info.N[1]\n\n        comp_stream = dgsem_interface_tendency!(info.device, workgroup)(\n            spacedisc.balance_law,\n            Val(info),\n            HorizontalDirection(),\n            spacedisc.numerical_flux_first_order,\n            numerical_flux_second_order,\n            tendency.data,\n            state_prognostic.data,\n            grad_flux_data,\n            Qhypervisc_grad_data,\n            spacedisc.state_auxiliary.data,\n            spacedisc.grid.vgeo,\n            spacedisc.grid.sgeo,\n            t,\n            spacedisc.grid.vmap⁻,\n            spacedisc.grid.vmap⁺,\n            spacedisc.grid.elemtobndy,\n            elems,\n            α;\n            ndrange = ndrange,\n            dependencies = comp_stream,\n        )\n    end\n\n    # Vertical kernel call\n    if spacedisc.direction isa EveryDirection ||\n       spacedisc.direction isa VerticalDirection\n        elems =\n            surface === :interior ? elems = spacedisc.grid.interiorelems :\n            spacedisc.grid.exteriorelems\n\n        if spacedisc isa DGModel\n            workgroup = info.Nfp_h\n            ndrange = workgroup * length(elems)\n\n            # Vertical polynomial degree\n            vertical_polyorder = info.N[info.dim]\n\n            comp_stream = dgsem_interface_tendency!(info.device, workgroup)(\n                spacedisc.balance_law,\n                Val(info),\n                VerticalDirection(),\n                spacedisc.numerical_flux_first_order,\n                numerical_flux_second_order,\n                tendency.data,\n                state_prognostic.data,\n                grad_flux_data,\n                Qhypervisc_grad_data,\n                spacedisc.state_auxiliary.data,\n                spacedisc.grid.vgeo,\n                spacedisc.grid.sgeo,\n                t,\n                spacedisc.grid.vmap⁻,\n                spacedisc.grid.vmap⁺,\n                spacedisc.grid.elemtobndy,\n                elems,\n                α;\n                ndrange = ndrange,\n                dependencies = comp_stream,\n            )\n        elseif spacedisc isa DGFVModel\n            # Make sure FVM in the vertical\n            @assert info.N[info.dim] == 0\n\n            # The FVM will only work on stacked grids!\n            @assert isstacked(spacedisc.grid.topology)\n\n            # Figute out the stacking of the mesh\n            nvertelem = spacedisc.grid.topology.stacksize\n            nhorzelem = div(length(elems), nvertelem)\n            periodicstack = spacedisc.grid.topology.periodicstack\n\n            # 2-D workgroup\n            workgroup = info.Nfp_h\n            ndrange = workgroup * nhorzelem\n\n            # XXX: This will need to be updated to diffusion\n            comp_stream = vert_fvm_interface_tendency!(info.device, workgroup)(\n                spacedisc.balance_law,\n                Val(info),\n                Val(nvertelem),\n                Val(periodicstack),\n                VerticalDirection(),\n                spacedisc.fv_reconstruction,\n                spacedisc.numerical_flux_first_order,\n                numerical_flux_second_order,\n                tendency.data,\n                state_prognostic.data,\n                grad_flux_data,\n                spacedisc.state_auxiliary.data,\n                spacedisc.grid.vgeo,\n                spacedisc.grid.sgeo,\n                t,\n                spacedisc.grid.elemtobndy,\n                elems,\n                α,\n                β,\n                # If we are computing in every direction, we need to\n                # increment after we compute the horizontal values\n                spacedisc.direction isa EveryDirection,\n                # If we are computing in vertical direction, we need to\n                # add sources here\n                spacedisc.direction isa VerticalDirection,\n                ndrange = ndrange,\n                dependencies = comp_stream,\n            )\n        else\n            error(\"unknown spatial discretization: $(typeof(spacedisc))\")\n        end\n    end\n\n    return comp_stream\nend\n"
  },
  {
    "path": "src/Numerics/DGMethods/create_states.jl",
    "content": "#### Create states\n\nfunction create_state(\n    balance_law,\n    grid,\n    st::AbstractStateType;\n    fill_nan = false,\n)\n    topology = grid.topology\n    Np = dofs_per_element(grid)\n\n    h_vgeo = Array(grid.vgeo)\n    FT = eltype(h_vgeo)\n    DA = arraytype(grid)\n\n    weights = view(h_vgeo, :, grid.Mid, :)\n    weights = reshape(weights, size(weights, 1), 1, size(weights, 2))\n\n    # TODO: Clean up this MPIStateArray interface...\n    ns = number_states(balance_law, st)\n    V = vars_state(balance_law, st, FT)\n    if st isa GradientLaplacian\n        ns = 3ns\n        # Since the names do not match the storage we do not set them\n        V = @vars()\n    end\n    state = MPIStateArray{FT, V}(\n        topology.mpicomm,\n        DA,\n        Np,\n        ns,\n        length(topology.elems),\n        realelems = topology.realelems,\n        ghostelems = topology.ghostelems,\n        vmaprecv = grid.vmaprecv,\n        vmapsend = grid.vmapsend,\n        nabrtorank = topology.nabrtorank,\n        nabrtovmaprecv = grid.nabrtovmaprecv,\n        nabrtovmapsend = grid.nabrtovmapsend,\n        weights = weights,\n    )\n    fill_nan && fill!(state, NaN)\n    return state\nend\n\nfunction init_state(state, balance_law, grid, direction, ::Auxiliary)\n    init_state_auxiliary!(balance_law, state, grid, direction)\n    return state\nend\n"
  },
  {
    "path": "src/Numerics/DGMethods/custom_filter.jl",
    "content": "#### Custom Filter\nusing ..Mesh.Filters: AbstractFilter\nimport ..Mesh.Filters: apply!\nexport AbstractCustomFilter\n\n\"\"\"\n    AbstractCustomFilter <: AbstractFilter\n\nDispatch type for a pointwise custom filter; see [`custom_filter!`](@ref)\n\n!!! warning\n    Modifying the prognostic state with this filter\n    does not guarantee conservation of the modified\n    state.\n\"\"\"\nabstract type AbstractCustomFilter <: AbstractFilter end\n\n\"\"\"\n    custom_filter!(\n        ::AbstractCustomFilter,\n        balance_law,\n        state,\n        aux,\n    )\n\nApply the custom filter `AbstractCustomFilter`,\nto the prognostic state for a given balance law.\n\"\"\"\nfunction custom_filter!(::AbstractCustomFilter, balance_law, state, aux) end\n\nfunction apply!(\n    custom_filter::AbstractCustomFilter,\n    grid::DiscontinuousSpectralElementGrid,\n    m::BalanceLaw,\n    state_prognostic::MPIStateArray,\n    state_auxiliary::MPIStateArray,\n) where {F!}\n    elems = grid.topology.realelems\n    device = array_device(state_prognostic)\n    Np = dofs_per_element(grid)\n    N = polynomialorders(grid)\n    knl_custom_filter! = kernel_custom_filter!(device, min(Np, 1024))\n    event = Event(device)\n    event = knl_custom_filter!(\n        m,\n        Val(dimensionality(grid)),\n        Val(N),\n        custom_filter,\n        state_prognostic.data,\n        state_auxiliary.data,\n        elems,\n        grid.activedofs;\n        ndrange = Np * length(elems),\n        dependencies = (event,),\n    )\n    wait(device, event)\nend\n\n@kernel function kernel_custom_filter!(\n    bl::BalanceLaw,\n    ::Val{dim},\n    ::Val{N},\n    custom_filter,\n    state_prognostic,\n    state_auxiliary,\n    elems,\n    activedofs,\n) where {dim, N}\n    @uniform begin\n        FT = eltype(state_prognostic)\n        num_state_prognostic = number_states(bl, Prognostic())\n        num_state_auxiliary = number_states(bl, Auxiliary())\n        vs_p = Vars{vars_state(bl, Prognostic(), FT)}\n        vs_a = Vars{vars_state(bl, Auxiliary(), FT)}\n        Nq = N .+ 1\n\n        @inbounds Nqk = dim == 2 ? 1 : Nq[dim]\n        @inbounds Np = Nq[1] * Nq[2] * Nqk\n\n        local_state_prognostic = MArray{Tuple{num_state_prognostic}, FT}(undef)\n        local_state_auxiliary = MArray{Tuple{num_state_auxiliary}, FT}(undef)\n    end\n\n    I = @index(Global, Linear)\n    eI = (I - 1) ÷ Np + 1\n    n = (I - 1) % Np + 1\n\n    @inbounds begin\n        e = elems[eI]\n\n        active = activedofs[n + (e - 1) * Np]\n\n        if active\n            @unroll for s in 1:num_state_prognostic\n                local_state_prognostic[s] = state_prognostic[n, s, e]\n            end\n\n            @unroll for s in 1:num_state_auxiliary\n                local_state_auxiliary[s] = state_auxiliary[n, s, e]\n            end\n\n            custom_filter!(\n                custom_filter,\n                bl,\n                vs_p(local_state_prognostic),\n                vs_a(local_state_auxiliary),\n            )\n\n            @unroll for s in 1:num_state_prognostic\n                state_prognostic[n, s, e] = local_state_prognostic[s]\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "src/Numerics/DGMethods/remainder.jl",
    "content": "using StaticNumbers\nexport remainder_DGModel\n\nimport ..BalanceLaws:\n    vars_state,\n    init_state_prognostic!,\n    init_state_auxiliary!,\n    flux_first_order!,\n    flux_first_order_arr!,\n    flux_second_order!,\n    source!,\n    source_arr!,\n    compute_gradient_flux!,\n    compute_gradient_argument!,\n    transform_post_gradient_laplacian!,\n    wavespeed,\n    boundary_conditions,\n    boundary_state!,\n    update_auxiliary_state!,\n    integral_load_auxiliary_state!,\n    integral_set_auxiliary_state!,\n    reverse_integral_load_auxiliary_state!,\n    reverse_integral_set_auxiliary_state!\n\n\"\"\"\n    RemBL(\n        main::BalanceLaw,\n        subcomponents::Tuple,\n        maindir::Direction,\n        subsdir::Tuple,\n    )\n\nBalance law for holding remainder model information. Direction is put here since\ndirection is so intertwined with the DGModel_kernels, that it is easier to hande\nthis in this container.\n\"\"\"\nstruct RemBL{M, S, MD, SD} <: BalanceLaw\n    main::M\n    subs::S\n    maindir::MD\n    subsdir::SD\nend\n\n\"\"\"\n    rembl_has_subs_direction(\n        direction::Direction,\n        rem_balance_law::RemBL,\n    )\n\nQuery whether the `rem_balance_law` has any subcomponent balance laws operating\nin the direction `direction`\n\"\"\"\n@generated function rembl_has_subs_direction(\n    ::Dir,\n    ::RemBL{MainBL, SubsBL, MainDir, SubsDir},\n) where {Dir <: Direction, MainBL, SubsBL, MainDir, SubsDir <: Tuple}\n    if Dir in SubsDir.types\n        return :(true)\n    else\n        return :(false)\n    end\nend\n\n\n\"\"\"\n    remainder_DGModel(\n        maindg::DGModel,\n        subsdg::NTuple{NumModels, DGModel};\n        numerical_flux_first_order,\n        numerical_flux_second_order,\n        numerical_flux_gradient,\n        state_auxiliary,\n        state_gradient_flux,\n        states_higher_order,\n        diffusion_direction,\n        modeldata,\n    )\n\n\nConstructs a `DGModel` from the `maindg` model and the tuple of `subsdg` models.\nThe concept of a remainder model is that it computes the contribution of the\nmodel after subtracting all of the subcomponents.\n\nBy default the numerical fluxes are set to be a tuple of the main models\nnumerical flux and the splitting is done at the PDE level (e.g., the remainder\nmodel is calculated prior to discretization). If instead a tuple of numerical\nfluxes is passed in the main numerical flux is evaluated first and then the\nsubcomponent numerical fluxes are subtracted off. This is discretely different\n(for the Rusanov / local Lax-Friedrichs flux) than defining a numerical flux for\nthe remainder of the physics model.\n\nThe other parameters are set to the value in the `maindg` component, mainly the\ndata and arrays are aliased to the `maindg` values.\n\"\"\"\nfunction remainder_DGModel(\n    maindg::DGModel,\n    subsdg::NTuple{NumModels, DGModel};\n    numerical_flux_first_order = maindg.numerical_flux_first_order,\n    numerical_flux_second_order = maindg.numerical_flux_second_order,\n    numerical_flux_gradient = maindg.numerical_flux_gradient,\n    state_auxiliary = maindg.state_auxiliary,\n    state_gradient_flux = maindg.state_gradient_flux,\n    states_higher_order = maindg.states_higher_order,\n    diffusion_direction = maindg.diffusion_direction,\n    modeldata = maindg.modeldata,\n    gradient_filter = maindg.gradient_filter,\n    tendency_filter = maindg.tendency_filter,\n    check_for_crashes = maindg.check_for_crashes,\n) where {NumModels}\n    FT = eltype(state_auxiliary)\n\n    # If any of these asserts fail, the remainder model will need to be extended\n    # to allow for it; see `flux_first_order!` and `source!` below.\n    for subdg in subsdg\n        @assert number_states(subdg.balance_law, Prognostic()) <=\n                number_states(maindg.balance_law, Prognostic())\n\n        @assert number_states(subdg.balance_law, Auxiliary()) ==\n                number_states(maindg.balance_law, Auxiliary())\n\n        @assert number_states(subdg.balance_law, Gradient()) == 0\n        @assert number_states(subdg.balance_law, GradientFlux()) == 0\n\n        @assert number_states(subdg.balance_law, GradientLaplacian()) == 0\n        @assert number_states(subdg.balance_law, Hyperdiffusive()) == 0\n\n        # Do not currenlty support nested remainder models\n        # For this to work the way directions and numerical fluxes are handled\n        # would need to be updated.\n        @assert !(subdg.balance_law isa RemBL)\n\n        @assert number_states(subdg.balance_law, UpwardIntegrals()) == 0\n        @assert number_states(subdg.balance_law, DownwardIntegrals()) == 0\n\n        # The remainder model requires that the subcomponent direction be\n        # included in the main model directions\n        @assert (\n            maindg.direction isa EveryDirection ||\n            maindg.direction === subdg.direction\n        )\n    end\n    balance_law = RemBL(\n        maindg.balance_law,\n        ntuple(i -> subsdg[i].balance_law, length(subsdg)),\n        maindg.direction,\n        ntuple(i -> subsdg[i].direction, length(subsdg)),\n    )\n\n\n    DGModel(\n        balance_law,\n        maindg.grid,\n        numerical_flux_first_order,\n        numerical_flux_second_order,\n        numerical_flux_gradient,\n        state_auxiliary,\n        state_gradient_flux,\n        states_higher_order,\n        maindg.direction,\n        diffusion_direction,\n        modeldata,\n        gradient_filter,\n        tendency_filter,\n        check_for_crashes,\n    )\nend\n\n# Inherit most of the functionality from the main model\nvars_state(rem_balance_law::RemBL, st::AbstractStateType, FT) =\n    vars_state(rem_balance_law.main, st, FT)\n\nupdate_auxiliary_state!(dg::DGModel, rem_balance_law::RemBL, args...) =\n    update_auxiliary_state!(dg, rem_balance_law.main, args...)\n\nnodal_update_auxiliary_state!(dg::DGModel, rem_balance_law::RemBL, args...) =\n    nodal_update_auxiliary_state!(dg, rem_balance_law.main, args...)\n\nupdate_auxiliary_state_gradient!(dg::DGModel, rem_balance_law::RemBL, args...) =\n    update_auxiliary_state_gradient!(dg, rem_balance_law.main, args...)\n\nintegral_load_auxiliary_state!(rem_balance_law::RemBL, args...) =\n    integral_load_auxiliary_state!(rem_balance_law.main, args...)\n\nintegral_set_auxiliary_state!(rem_balance_law::RemBL, args...) =\n    integral_set_auxiliary_state!(rem_balance_law.main, args...)\n\nreverse_integral_load_auxiliary_state!(rem_balance_law::RemBL, args...) =\n    reverse_integral_load_auxiliary_state!(rem_balance_law.main, args...)\n\nreverse_integral_set_auxiliary_state!(rem_balance_law::RemBL, args...) =\n    reverse_integral_set_auxiliary_state!(rem_balance_law.main, args...)\n\ntransform_post_gradient_laplacian!(rem_balance_law::RemBL, args...) =\n    transform_post_gradient_laplacian!(rem_balance_law.main, args...)\n\nflux_second_order!(rem_balance_law::RemBL, args...) =\n    flux_second_order!(rem_balance_law.main, args...)\n\ncompute_gradient_argument!(rem_balance_law::RemBL, args...) =\n    compute_gradient_argument!(rem_balance_law.main, args...)\n\ncompute_gradient_flux!(rem_balance_law::RemBL, args...) =\n    compute_gradient_flux!(rem_balance_law.main, args...)\n\nboundary_conditions(rem_balance_law::RemBL) =\n    boundary_conditions(rem_balance_law.main)\n\nboundary_state!(nf, bc, rem_balance_law::RemBL, args...) =\n    boundary_state!(nf, bc, rem_balance_law.main, args...)\n\ninit_state_auxiliary!(rem_balance_law::RemBL, args...) =\n    init_state_auxiliary!(rem_balance_law.main, args...)\n\nnodal_init_state_auxiliary!(rem_balance_law::RemBL, args...) =\n    nodal_init_state_auxiliary!(rem_balance_law.main, args...)\n\ninit_state_prognostic!(rem_balance_law::RemBL, args...) =\n    init_state_prognostic!(rem_balance_law.main, args...)\n\n# Still need Vars wrapper for now:\nfunction flux_first_order!(\n    rem_balance_law::RemBL,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    d::Dirs,\n) where {NumDirs, Dirs <: NTuple{NumDirs, Direction}}\n    flux_first_order_arr!(\n        rem_balance_law,\n        parent(flux),\n        parent(state),\n        parent(aux),\n        t,\n        d,\n    )\nend\n\n\"\"\"\n    flux_first_order_arr!(\n        rem_balance_law::RemBL,\n        flux::AbstractArray,\n        state::AbstractArray,\n        aux::AbstractArray,\n        t::Real,\n        ::Dirs,\n    ) where {NumDirs, Dirs <: NTuple{NumDirs, Direction}}\n\nEvaluate the remainder flux by first evaluating the main flux and subtracting\nthe subcomponent fluxes.\n\nOnly models which have directions that are included in the `directions` tuple\nare evaluated. When these models are evaluated the models underlying `direction`\nis passed (not the original `directions` argument).\n\"\"\"\nfunction flux_first_order_arr!(\n    rem_balance_law::RemBL,\n    flux::AbstractArray,\n    state::AbstractArray,\n    aux::AbstractArray,\n    t::Real,\n    ::Dirs,\n) where {NumDirs, Dirs <: NTuple{NumDirs, Direction}}\n    if rem_balance_law.maindir isa Union{Dirs.types...}\n        flux_first_order_arr!(\n            rem_balance_law.main,\n            flux,\n            state,\n            aux,\n            t,\n            (rem_balance_law.maindir,),\n        )\n    end\n\n    flux_s = similar(flux)\n\n    # Force the loop to unroll to get type stability on the GPU\n    @inbounds ntuple(Val(length(rem_balance_law.subs))) do k\n        Base.@_inline_meta\n        if rem_balance_law.subsdir[k] isa Union{Dirs.types...}\n            sub = rem_balance_law.subs[k]\n            fill!(flux_s, -zero(eltype(flux_s)))\n            flux_first_order_arr!(\n                sub,\n                flux_s,\n                state,\n                aux,\n                t,\n                (rem_balance_law.subsdir[k],),\n            )\n            flux .-= flux_s\n        end\n    end\n    nothing\nend\n\n# Still need Vars wrapper for now:\nfunction source!(\n    rem_balance_law::RemBL,\n    source::Vars,\n    state::Vars,\n    diffusive::Vars,\n    aux::Vars,\n    t::Real,\n    d::Dirs,\n) where {NumDirs, Dirs <: NTuple{NumDirs, Direction}}\n    source_arr!(\n        rem_balance_law,\n        parent(source),\n        parent(state),\n        parent(diffusive),\n        parent(aux),\n        t,\n        d,\n    )\nend\n\n\"\"\"\n    source_arr!(\n        rem_balance_law::RemBL,\n        source::AbstractArray,\n        state::AbstractArray,\n        diffusive::AbstractArray,\n        aux::AbstractArray,\n        t::Real,\n        ::Dirs,\n    ) where {NumDirs, Dirs <: NTuple{NumDirs, Direction}}\n\nEvaluate the remainder source by first evaluating the main source and subtracting\nthe subcomponent sources.\n\nOnly models which have directions that are included in the `directions` tuple\nare evaluated. When these models are evaluated the models underlying `direction`\nis passed (not the original `directions` argument).\n\"\"\"\nfunction source_arr!(\n    rem_balance_law::RemBL,\n    source::AbstractArray,\n    state::AbstractArray,\n    diffusive::AbstractArray,\n    aux::AbstractArray,\n    t::Real,\n    ::Dirs,\n) where {NumDirs, Dirs <: NTuple{NumDirs, Direction}}\n    if EveryDirection() isa Union{Dirs.types...} ||\n       rem_balance_law.maindir isa EveryDirection ||\n       rem_balance_law.maindir isa Union{Dirs.types...}\n        source_arr!(\n            rem_balance_law.main,\n            source,\n            state,\n            diffusive,\n            aux,\n            t,\n            (rem_balance_law.maindir,),\n        )\n    end\n\n    source_s = similar(source)\n\n    # Force the loop to unroll to get type stability on the GPU\n    ntuple(Val(length(rem_balance_law.subs))) do k\n        Base.@_inline_meta\n        @inbounds if EveryDirection() isa Union{Dirs.types...} ||\n                     rem_balance_law.subsdir[k] isa EveryDirection ||\n                     rem_balance_law.subsdir[k] isa Union{Dirs.types...}\n            sub = rem_balance_law.subs[k]\n            fill!(source_s, -zero(eltype(source_s)))\n            source_arr!(\n                sub,\n                source_s,\n                state,\n                diffusive,\n                aux,\n                t,\n                (rem_balance_law.subsdir[k],),\n            )\n            source .-= source_s\n        end\n    end\n    nothing\nend\n\n\"\"\"\n    function wavespeed(\n        rem_balance_law::RemBL,\n        args...,\n    )\n\nThe wavespeed for a remainder model is defined to be the difference of the\nwavespeed of the main model and the sum of the subcomponents.\n\nNote: Defining the wavespeed in this manner can result in a smaller value than\nthe actually wavespeed of the remainder physics model depending on the\ncomposition of the models.\n\"\"\"\nfunction wavespeed(\n    rem_balance_law::RemBL,\n    nM,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    dir::Dirs,\n) where {ND, Dirs <: NTuple{ND, Direction}}\n    FT = eltype(state)\n\n    ws = fill(\n        -zero(FT),\n        MVector{number_states(rem_balance_law.main, Prognostic()), FT},\n    )\n    rs = fill(\n        -zero(FT),\n        MVector{number_states(rem_balance_law.main, Prognostic()), FT},\n    )\n\n    # Compute the main components wavespeed\n    if rem_balance_law.maindir isa Union{Dirs.types...}\n        ws .= wavespeed(\n            rem_balance_law.main,\n            nM,\n            state,\n            aux,\n            t,\n            (rem_balance_law.maindir,),\n        )\n    end\n\n    # Compute the sub components wavespeed\n    for (sub, subdir) in zip(rem_balance_law.subs, rem_balance_law.subsdir)\n        @inbounds if subdir isa Union{Dirs.types...}\n            num_state = static(number_states(sub, Prognostic()))\n            rs[static(1):num_state] .+=\n                wavespeed(sub, nM, state, aux, t, (subdir,))\n        end\n    end\n\n    ws .-= rs\n\n    return ws\nend\n\n# Here the fluxes are pirated to handle the case of tuples of fluxes\nimport ..DGMethods.NumericalFluxes:\n    NumericalFluxFirstOrder,\n    numerical_flux_first_order!,\n    numerical_boundary_flux_first_order!,\n    normal_boundary_flux_second_order!\n\n\"\"\"\n    function numerical_flux_first_order!(\n        numerical_fluxes::Tuple{\n            NumericalFluxFirstOrder,\n            NTuple{NumSubFluxes, NumericalFluxFirstOrder},\n        },\n        rem_balance_law::RemBL,\n        fluxᵀn::Vars{S},\n        normal_vector::SVector,\n        state_prognostic⁻::Vars{S},\n        state_auxiliary⁻::Vars{A},\n        state_prognostic⁺::Vars{S},\n        state_auxiliary⁺::Vars{A},\n        t,\n        directions,\n    )\n\nWhen the `numerical_fluxes` are a tuple and the balance law is a remainder\nbalance law the main components numerical flux is evaluated then all the\nsubcomponent numerical fluxes are evaluated and subtracted.\n\nOnly models which have directions that are included in the `directions` tuple\nare evaluated. When these models are evaluated the models underlying `direction`\nis passed (not the original `directions` argument).\n\"\"\"\nfunction numerical_flux_first_order!(\n    numerical_fluxes::Tuple{\n        NumericalFluxFirstOrder,\n        NTuple{NumSubFluxes, NumericalFluxFirstOrder},\n    },\n    rem_balance_law::RemBL,\n    fluxᵀn::Vars{S},\n    normal_vector::SVector,\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n    ::Dirs,\n) where {NumSubFluxes, S, A, Dirs <: NTuple{2, Direction}}\n    # Call the numerical flux for the main model\n    if rem_balance_law.maindir isa Union{Dirs.types...}\n        @inbounds numerical_flux_first_order!(\n            numerical_fluxes[1],\n            rem_balance_law.main,\n            fluxᵀn,\n            normal_vector,\n            state_prognostic⁻,\n            state_auxiliary⁻,\n            state_prognostic⁺,\n            state_auxiliary⁺,\n            t,\n            (rem_balance_law.maindir,),\n        )\n    end\n\n    # Create put the sub model fluxes\n    a_fluxᵀn = parent(fluxᵀn)\n\n    # Force the loop to unroll to get type stability on the GPU\n    ntuple(Val(length(rem_balance_law.subs))) do k\n        Base.@_inline_meta\n        @inbounds if rem_balance_law.subsdir[k] isa Union{Dirs.types...}\n            sub = rem_balance_law.subs[k]\n            nf = numerical_fluxes[2][k]\n\n            FT = eltype(a_fluxᵀn)\n            num_state_prognostic = number_states(sub, Prognostic())\n\n            a_sub_fluxᵀn = MVector{num_state_prognostic, FT}(undef)\n            a_sub_state_prognostic⁻ = MVector{num_state_prognostic, FT}(undef)\n            a_sub_state_prognostic⁺ = MVector{num_state_prognostic, FT}(undef)\n\n            state_rng = static(1):static(number_states(sub, Prognostic()))\n            a_sub_fluxᵀn .= a_fluxᵀn[state_rng]\n            a_sub_state_prognostic⁻ .= parent(state_prognostic⁻)[state_rng]\n            a_sub_state_prognostic⁺ .= parent(state_prognostic⁺)[state_rng]\n\n            # compute this submodels flux\n            fill!(a_sub_fluxᵀn, -zero(eltype(a_sub_fluxᵀn)))\n            numerical_flux_first_order!(\n                nf,\n                sub,\n                Vars{vars_state(sub, Prognostic(), FT)}(a_sub_fluxᵀn),\n                normal_vector,\n                Vars{vars_state(sub, Prognostic(), FT)}(\n                    a_sub_state_prognostic⁻,\n                ),\n                state_auxiliary⁻,\n                Vars{vars_state(sub, Prognostic(), FT)}(\n                    a_sub_state_prognostic⁺,\n                ),\n                state_auxiliary⁺,\n                t,\n                (rem_balance_law.subsdir[k],),\n            )\n\n            # Subtract off this sub models flux\n            a_fluxᵀn[state_rng] .-= a_sub_fluxᵀn\n        end\n    end\nend\n\n\"\"\"\n    function numerical_boundary_flux_first_order!(\n        numerical_fluxes::Tuple{\n            NumericalFluxFirstOrder,\n            NTuple{NumSubFluxes, NumericalFluxFirstOrder},\n        },\n        bc,\n        rem_balance_law::RemBL,\n        fluxᵀn::Vars{S},\n        normal_vector::SVector,\n        state_prognostic⁻::Vars{S},\n        state_auxiliary⁻::Vars{A},\n        state_prognostic⁺::Vars{S},\n        state_auxiliary⁺::Vars{A},\n        t,\n        directions,\n        args...,\n    )\n\nWhen the `numerical_fluxes` are a tuple and the balance law is a remainder\nbalance law the main components numerical flux is evaluated then all the\nsubcomponent numerical fluxes are evaluated and subtracted.\n\nOnly models which have directions that are included in the `directions` tuple\nare evaluated. When these models are evaluated the models underlying `direction`\nis passed (not the original `directions` argument).\n\"\"\"\nfunction numerical_boundary_flux_first_order!(\n    numerical_fluxes::Tuple{\n        NumericalFluxFirstOrder,\n        NTuple{NumSubFluxes, NumericalFluxFirstOrder},\n    },\n    bc,\n    rem_balance_law::RemBL,\n    fluxᵀn::Vars{S},\n    normal_vector::SVector,\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n    ::Dirs,\n    state_prognostic1⁻::Vars{S},\n    state_auxiliary1⁻::Vars{A},\n) where {NumSubFluxes, S, A, Dirs <: NTuple{2, Direction}}\n    # Since the fluxes are allowed to modified these we need backups so they can\n    # be reset as we go\n    a_state_prognostic⁺ = parent(state_prognostic⁺)\n    a_state_auxiliary⁺ = parent(state_auxiliary⁺)\n\n    a_back_state_prognostic⁺ = copy(a_state_prognostic⁺)\n    a_back_state_auxiliary⁺ = copy(a_state_auxiliary⁺)\n\n\n    # Call the numerical flux for the main model\n    if rem_balance_law.maindir isa Union{Dirs.types...}\n        @inbounds numerical_boundary_flux_first_order!(\n            numerical_fluxes[1],\n            bc,\n            rem_balance_law.main,\n            fluxᵀn,\n            normal_vector,\n            state_prognostic⁻,\n            state_auxiliary⁻,\n            state_prognostic⁺,\n            state_auxiliary⁺,\n            t,\n            (rem_balance_law.maindir,),\n            state_prognostic1⁻,\n            state_auxiliary1⁻,\n        )\n    end\n\n    # Create put the sub model fluxes\n    a_fluxᵀn = parent(fluxᵀn)\n\n    # Force the loop to unroll to get type stability on the GPU\n    ntuple(Val(length(rem_balance_law.subs))) do k\n        Base.@_inline_meta\n        @inbounds if rem_balance_law.subsdir[k] isa Union{Dirs.types...}\n            sub = rem_balance_law.subs[k]\n            nf = numerical_fluxes[2][k]\n\n            # reset the plus-side data\n            a_state_prognostic⁺ .= a_back_state_prognostic⁺\n            a_state_auxiliary⁺ .= a_back_state_auxiliary⁺\n\n            FT = eltype(a_fluxᵀn)\n            num_state_prognostic = number_states(sub, Prognostic())\n\n            a_sub_fluxᵀn = MVector{num_state_prognostic, FT}(undef)\n            a_sub_state_prognostic⁻ = MVector{num_state_prognostic, FT}(undef)\n            a_sub_state_prognostic⁺ = MVector{num_state_prognostic, FT}(undef)\n            a_sub_state_prognostic1⁻ = MVector{num_state_prognostic, FT}(undef)\n\n            state_rng = static(1):static(number_states(sub, Prognostic()))\n            a_sub_fluxᵀn .= a_fluxᵀn[state_rng]\n            a_sub_state_prognostic⁻ .= parent(state_prognostic⁻)[state_rng]\n            a_sub_state_prognostic⁺ .= parent(state_prognostic⁺)[state_rng]\n            a_sub_state_prognostic1⁻ .= parent(state_prognostic1⁻)[state_rng]\n\n\n            # compute this submodels flux\n            fill!(a_sub_fluxᵀn, -zero(eltype(a_sub_fluxᵀn)))\n            numerical_boundary_flux_first_order!(\n                nf,\n                bc,\n                sub,\n                Vars{vars_state(sub, Prognostic(), FT)}(a_sub_fluxᵀn),\n                normal_vector,\n                Vars{vars_state(sub, Prognostic(), FT)}(\n                    a_sub_state_prognostic⁻,\n                ),\n                state_auxiliary⁻,\n                Vars{vars_state(sub, Prognostic(), FT)}(\n                    a_sub_state_prognostic⁺,\n                ),\n                state_auxiliary⁺,\n                t,\n                (rem_balance_law.subsdir[k],),\n                Vars{vars_state(sub, Prognostic(), FT)}(\n                    a_sub_state_prognostic1⁻,\n                ),\n                state_auxiliary1⁻,\n            )\n\n            # Subtract off this sub models flux\n            a_fluxᵀn[state_rng] .-= a_sub_fluxᵀn\n        end\n    end\nend\n\n\"\"\"\n    normal_boundary_flux_second_order!(nf, rem_balance_law::RemBL, args...)\n\nCurrently the main models `normal_boundary_flux_second_order!` is called. If the\nsubcomponents models have second order terms this would need to be updated.\n\"\"\"\nnormal_boundary_flux_second_order!(\n    nf,\n    bc,\n    rem_balance_law::RemBL,\n    fluxᵀn::Vars{S},\n    args...,\n) where {S} = normal_boundary_flux_second_order!(\n    nf,\n    bc,\n    rem_balance_law.main,\n    fluxᵀn,\n    args...,\n)\n"
  },
  {
    "path": "src/Numerics/Mesh/BrickMesh.jl",
    "content": "module BrickMesh\n\nexport brickmesh, centroidtocode, connectmesh, partition, connectmeshfull\n\nusing MPI\n\n\"\"\"\n    linearpartition(n, p, np)\n\nPartition the range `1:n` into `np` pieces and return the `p`th piece as a\nrange.\n\nThis will provide an equal partition when `n` is divisible by `np` and\notherwise the ranges will have lengths of either `floor(Int, n/np)` or\n`ceil(Int, n/np)`.\n\"\"\"\nlinearpartition(n, p, np) =\n    range(div((p - 1) * n, np) + 1, stop = div(p * n, np))\n\n\"\"\"\n    hilbertcode(Y::AbstractArray{T}; bits=8sizeof(T)) where T\n\nGiven an array of axes coordinates `Y` stored as `bits`-bit integers\nthe function returns the Hilbert integer `H`.\n\nThe encoding of the Hilbert integer is best described by example.\nIf 5-bits are used from each of 3 coordinates then the function performs\n\n     X[2]|                       H[0] = A B C D E\n         | /X[1]       ------->  H[1] = F G H I J\n    axes |/                      H[2] = K L M N O\n         0------ X[0]                   high low\n\nwhere the 15-bit Hilbert integer = `A B C D E F G H I J K L M N O` is stored\nin `H`\n\nThis function is based on public domain code from John Skilling which can be\nfound in [Skilling2004](@cite).\n\"\"\"\nfunction hilbertcode(Y::AbstractArray{T}; bits = 8 * sizeof(T)) where {T}\n    # Below is Skilling's AxestoTranspose\n    X = deepcopy(Y)\n    n = length(X)\n    M = one(T) << (bits - 1)\n\n    Q = M\n    for j in 1:(bits - 1)\n        P = Q - one(T)\n        for i in 1:n\n            if X[i] & Q != zero(T)\n                X[1] ⊻= P\n            else\n                t = (X[1] ⊻ X[i]) & P\n                X[1] ⊻= t\n                X[i] ⊻= t\n            end\n        end\n        Q >>>= one(T)\n    end\n\n    for i in 2:n\n        X[i] ⊻= X[i - 1]\n    end\n\n    t = zero(T)\n    Q = M\n    for j in 1:(bits - 1)\n        if X[n] & Q != zero(T)\n            t ⊻= Q - one(T)\n        end\n        Q >>>= one(T)\n    end\n\n    for i in 1:n\n        X[i] ⊻= t\n    end\n\n    # Below we transpose X and store it in H, i.e.:\n    #\n    #   X[0] = A D G J M               H[0] = A B C D E\n    #   X[1] = B E H K N   <------->   H[1] = F G H I J\n    #   X[2] = C F I L O               H[2] = K L M N O\n    #\n    # The 15-bit Hilbert integer is then = A B C D E F G H I J K L M N O\n    H = zero(X)\n    for i in 0:(n - 1), j in 0:(bits - 1)\n        k = i * bits + j\n        bit = (X[n - mod(k, n)] >>> div(k, n)) & one(T)\n        H[n - i] |= (bit << j)\n    end\n\n    return H\nend\n\n\"\"\"\n    centroidtocode(comm::MPI.Comm, elemtocorner; coortocode, CT)\n\nReturns a code for each element based on its centroid.\n\nThese element codes can be used to determine a linear ordering for the\npartition function.\n\nThe communicator `comm` is used to calculate the bounding box for representing\nthe centroids in coordinates of type `CT`, defaulting to `CT=UInt64`.  These\ninteger coordinates are converted to a code using the function `coortocode`,\nwhich defaults to `hilbertcode`.\n\nThe array containing the element corner coordinates, `elemtocorner`, is used\nto compute the centroids.  `elemtocorner` is a dimension by number of corners\nby number of elements array.\n\"\"\"\nfunction centroidtocode(\n    comm::MPI.Comm,\n    elemtocorner;\n    coortocode = hilbertcode,\n    CT = UInt64,\n)\n    (d, nvert, nelem) = size(elemtocorner)\n\n    centroids = sum(elemtocorner, dims = 2) ./ nvert\n    T = eltype(centroids)\n\n    centroidmin =\n        (nelem > 0) ? minimum(centroids, dims = 3) : fill(typemax(T), d)\n    centroidmax =\n        (nelem > 0) ? maximum(centroids, dims = 3) : fill(typemin(T), d)\n\n    centroidmin = MPI.Allreduce(centroidmin, min, comm)\n    centroidmax = MPI.Allreduce(centroidmax, max, comm)\n    centroidsize = centroidmax - centroidmin\n\n    # Fix centroidsize to be nonzero.  It can be zero for a couple of reasons.\n    # For example, it will be zero if we have just one element.\n    if iszero(centroidsize)\n        centroidsize = ones(T, d)\n    else\n        for i in 1:d\n            if iszero(centroidsize[i])\n                centroidsize[i] = maximum(centroidsize)\n            end\n        end\n    end\n\n    code = Array{CT}(undef, d, nelem)\n    for e in 1:nelem\n        c = (centroids[:, 1, e] .- centroidmin) ./ centroidsize\n        X =\n            CT.(floor.(\n                typemax(CT) .* BigFloat.(c; precision = 16 * sizeof(CT)),\n            ))\n        code[:, e] = coortocode(X)\n    end\n\n    code\nend\n\n\"\"\"\n    brickmesh(x, periodic; part=1, numparts=1; boundary)\n\nGenerate a brick mesh with coordinates given by the tuple `x` and the\nperiodic dimensions given by the `periodic` tuple.\n\nThe brick can optionally be partitioned into `numparts` and this returns\npartition `part`.  This is a simple Cartesian partition and further\npartitioning (e.g, based on a space-filling curve) should be done before the\nmesh is used for computation.\n\nBy default boundary faces will be marked with a one and other faces with a\nzero.  Specific boundary numbers can also be passed for each face of the brick\nin `boundary`.  This will mark the nonperiodic brick faces with the given\nboundary number.\n\n# Examples\n\nWe can build a 3 by 2 element two-dimensional mesh that is periodic in the\n\\$x_2\\$-direction with\n```jldoctest brickmesh\njulia> (elemtovert, elemtocoord, elemtobndy, faceconnections) =\n        brickmesh((2:5,4:6), (false,true); boundary=((1,2), (3,4)));\n```\nThis returns the mesh structure for\n\n             x_2\n\n              ^\n              |\n             6-  9----10----11----12\n              |  |     |     |     |\n              |  |  4  |  5  |  6  |\n              |  |     |     |     |\n             5-  5-----6-----7-----8\n              |  |     |     |     |\n              |  |  1  |  2  |  3  |\n              |  |     |     |     |\n             4-  1-----2-----3-----4\n              |\n              +--|-----|-----|-----|--> x_1\n                 2     3     4     5\n\nThe (number of corners by number of elements) array `elemtovert` gives the\nglobal vertex number for the corners of each element.\n```jldoctest brickmesh\njulia> elemtovert\n4×6 Array{Int64,2}:\n 1  2  3   5   6   7\n 2  3  4   6   7   8\n 5  6  7   9  10  11\n 6  7  8  10  11  12\n```\nNote that the vertices are listed in Cartesian order.\n\nThe (dimension by number of corners by number of elements) array `elemtocoord`\ngives the coordinates of the corners of each element.\n```jldoctes brickmesh\njulia> elemtocoord\n2×4×6 Array{Int64,3}:\n[:, :, 1] =\n 2  3  2  3\n 4  4  5  5\n\n[:, :, 2] =\n 3  4  3  4\n 4  4  5  5\n\n[:, :, 3] =\n 4  5  4  5\n 4  4  5  5\n\n[:, :, 4] =\n 2  3  2  3\n 5  5  6  6\n\n[:, :, 5] =\n 3  4  3  4\n 5  5  6  6\n\n[:, :, 6] =\n 4  5  4  5\n 5  5  6  6\n```\n\nThe (number of faces by number of elements) array `elemtobndy` gives the\nboundary number for each face of each element.  A zero will be given for\nconnected faces.\n```jldoctest brickmesh\njulia> elemtobndy\n4×6 Array{Int64,2}:\n 1  0  0  1  0  0\n 0  0  2  0  0  2\n 0  0  0  0  0  0\n 0  0  0  0  0  0\n```\nNote that the faces are listed in Cartesian order.\n\nFinally, the periodic face connections are given in `faceconnections` which is a\nlist of arrays, one for each connection.\nEach array in the list is given in the format `[e, f, vs...]` where\n - `e`  is the element number;\n - `f`  is the face number; and\n - `vs` is the global vertices that face associated with.\nI the example\n```jldoctest brickmesh\njulia> faceconnections\n3-element Array{Array{Int64,1},1}:\n [4, 4, 1, 2]\n [5, 4, 2, 3]\n [6, 4, 3, 4]\n```\nwe see that face `4` of element `5` is associated with vertices `[2 3]` (the\nvertices for face `1` of element `2`).\n\"\"\"\nfunction brickmesh(\n    x,\n    periodic;\n    part = 1,\n    numparts = 1,\n    boundary = ntuple(j -> (1, 1), length(x)),\n)\n    if boundary isa Matrix\n        boundary = tuple(mapslices(x -> tuple(x...), boundary, dims = 1)...)\n    end\n\n    @assert length(x) == length(periodic)\n    @assert length(x) >= 1\n    @assert 1 <= part <= numparts\n\n    T = promote_type(eltype.(x)...)\n    d = length(x)\n    nvert = 2^d\n    nface = 2d\n\n    nelemdim = length.(x) .- 1\n    elemlocal = linearpartition(prod(nelemdim), part, numparts)\n\n    elemtovert = Array{Int}(undef, nvert, length(elemlocal))\n    elemtocoord = Array{T}(undef, d, nvert, length(elemlocal))\n    elemtobndy = zeros(Int, nface, length(elemlocal))\n    faceconnections = Array{Array{Int, 1}}(undef, 0)\n\n    verts = LinearIndices(ntuple(j -> 1:length(x[j]), d))\n    elems = CartesianIndices(ntuple(j -> 1:(length(x[j]) - 1), d))\n\n    p = reshape(1:nvert, ntuple(j -> 2, d))\n    fmask = hcat((\n        p[ntuple(\n            j ->\n                (j == div(f - 1, 2) + 1) ?\n                ((mod(f - 1, 2) + 1):(mod(f - 1, 2) + 1)) : (:),\n            d,\n        )...,][:] for f in 1:nface\n    )...)\n\n\n    for (e, ec) in enumerate(elems[elemlocal])\n        corners = CartesianIndices(ntuple(j -> ec[j]:(ec[j] + 1), d))\n        for (v, vc) in enumerate(corners)\n            elemtovert[v, e] = verts[vc]\n\n            for j in 1:d\n                elemtocoord[j, v, e] = x[j][vc[j]]\n            end\n        end\n\n        for i in 1:d\n            if !periodic[i] && ec[i] == 1\n                elemtobndy[2 * (i - 1) + 1, e] = boundary[i][1]\n            end\n            if !periodic[i] && ec[i] == nelemdim[i]\n                elemtobndy[2 * (i - 1) + 2, e] = boundary[i][2]\n            end\n        end\n\n        for i in 1:d\n            if periodic[i] && ec[i] == nelemdim[i]\n                neighcorners = CartesianIndices(ntuple(\n                    j -> (i == j) ? (1:2) : (ec[j]:(ec[j] + 1)),\n                    d,\n                ))\n                push!(\n                    faceconnections,\n                    vcat(e, 2i, verts[neighcorners[fmask[:, 2i - 1]]]),\n                )\n            end\n        end\n    end\n\n    (elemtovert, elemtocoord, elemtobndy, faceconnections)\nend\n\n\"\"\"\n    parallelsortcolumns(comm::MPI.Comm, A;\n                        alg::Base.Sort.Algorithm=Base.Sort.DEFAULT_UNSTABLE,\n                        lt=isless,\n                        by=identity,\n                        rev::Union{Bool,Nothing}=nothing)\n\nSorts the columns of the distributed matrix `A`.\n\nSee the documentation of `sort!` for a description of the keyword arguments.\n\nThis function assumes `A` has the same number of rows on each MPI rank but can\nhave a different number of columns.\n\"\"\"\nfunction parallelsortcolumns(\n    comm::MPI.Comm,\n    A;\n    alg::Base.Sort.Algorithm = Base.Sort.DEFAULT_UNSTABLE,\n    lt = isless,\n    by = identity,\n    rev::Union{Bool, Nothing} = nothing,\n)\n\n    m, n = size(A)\n    T = eltype(A)\n\n    csize = MPI.Comm_size(comm)\n    crank = MPI.Comm_rank(comm)\n    croot = 0\n\n    A = sortslices(A, dims = 2, alg = alg, lt = lt, by = by, rev = rev)\n\n    npivots = clamp(n, 0, csize)\n    pivots = T[A[i, div(n * p, npivots) + 1] for i in 1:m, p in 0:(npivots - 1)]\n    pivotcounts = MPI.Allgather(Cint(length(pivots)), comm)\n    pivots = MPI.Allgatherv!(\n        pivots,\n        VBuffer(similar(pivots, sum(pivotcounts)), pivotcounts),\n        comm,\n    )\n    pivots = reshape(pivots, m, div(length(pivots), m))\n    pivots =\n        sortslices(pivots, dims = 2, alg = alg, lt = lt, by = by, rev = rev)\n\n    # if we don't have any pivots then we must have zero columns\n    if size(pivots) == (m, 0)\n        return A\n    end\n\n    pivots = [\n        pivots[i, div(div(length(pivots), m) * r, csize) + 1]\n        for i in 1:m, r in 0:(csize - 1)\n    ]\n\n    cols = map(i -> view(A, :, i), 1:n)\n    senddispls = Cint[\n        (\n            searchsortedfirst(cols, pivots[:, i], lt = lt, by = by, rev = rev) - 1\n        ) * m for i in 1:csize\n    ]\n    sendcounts = Cint[\n        (i == csize ? n * m : senddispls[i + 1]) - senddispls[i]\n        for i in 1:csize\n    ]\n\n    recvcounts = similar(sendcounts)\n    MPI.Alltoall!(UBuffer(sendcounts, 1), MPI.UBuffer(recvcounts, 1), comm)\n    B = similar(A, sum(recvcounts))\n    MPI.Alltoallv!(\n        VBuffer(A, sendcounts, senddispls),\n        VBuffer(B, recvcounts),\n        comm,\n    )\n    B = reshape(B, m, div(length(B), m))\n    sortslices(B, dims = 2, alg = alg, lt = lt, by = by, rev = rev)\nend\n\n\"\"\"\n    getpartition(comm::MPI.Comm, elemtocode)\n\nReturns an equally weighted partition of a distributed set of elements by\nsorting their codes given in `elemtocode`.\n\nThe codes for each element, `elemtocode`, are given as an array with a single\nentry per local element or as a matrix with a column for each local element.\n\nThe partition is returned as a tuple three parts:\n\n - `partsendorder`: permutation of elements into sending order\n - `partsendstarts`: start entries in the send array for each rank\n - `partrecvstarts`: start entries in the receive array for each rank\n\nNote that both `partsendstarts` and `partrecvstarts` are of length\n`MPI.Comm_size(comm)+1` where the last entry has the total number of elements\nto send or receive, respectively.\n\"\"\"\ngetpartition(comm::MPI.Comm, elemtocode::AbstractVector) =\n    getpartition(comm, reshape(elemtocode, 1, length(elemtocode)))\n\nfunction getpartition(comm::MPI.Comm, elemtocode::AbstractMatrix)\n    (ncode, nelem) = size(elemtocode)\n\n    csize = MPI.Comm_size(comm)\n    crank = MPI.Comm_rank(comm)\n\n    CT = eltype(elemtocode)\n\n    A = CT[\n        elemtocode                                 # code\n        collect(CT, 1:nelem)'                      # original element number\n        fill(CT(MPI.Comm_rank(comm)), (1, nelem))  # original rank\n        fill(typemax(CT), (1, nelem))\n    ]              # new rank\n    m, n = size(A)\n\n    # sort by just code\n    A = parallelsortcolumns(comm, A)\n\n    # count the distribution of A\n    counts = MPI.Allgather(last(size(A)), comm)\n    starts = ones(Int, csize + 1)\n    for i in 1:csize\n        starts[i + 1] = counts[i] + starts[i]\n    end\n\n    # loop to determine new rank\n    j = range(starts[crank + 1], stop = starts[crank + 2] - 1)\n    for r in 0:(csize - 1)\n        k = linearpartition(starts[end] - 1, r + 1, csize)\n        o = intersect(k, j) .- (starts[crank + 1] - 1)\n        A[ncode + 3, o] .= r\n    end\n\n    # sort by original rank and code\n    A = sortslices(A, dims = 2, by = x -> x[[ncode + 2, (1:ncode)...]])\n\n    # count number of elements that are going to be sent\n    sendcounts = zeros(Cint, csize)\n    for i in 1:last(size(A))\n        sendcounts[A[ncode + 2, i] + 1] += m\n    end\n\n    # communicate columns of A to original rank\n    recvcounts = similar(sendcounts)\n    MPI.Alltoall!(UBuffer(sendcounts, 1), UBuffer(recvcounts, 1), comm)\n    B = similar(A, sum(recvcounts))\n    MPI.Alltoallv!(VBuffer(A, sendcounts), VBuffer(B, recvcounts), comm)\n    B = reshape(B, m, div(length(B), m))\n\n    # check to make sure we didn't drop any elements\n    @assert nelem == n == size(B)[2]\n\n    partsendcounts = zeros(Cint, csize)\n    for i in 1:last(size(B))\n        partsendcounts[B[ncode + 3, i] + 1] += 1\n    end\n    partsendstarts = ones(Int, csize + 1)\n    for i in 1:csize\n        partsendstarts[i + 1] = partsendcounts[i] + partsendstarts[i]\n    end\n\n    partsendorder = Int.(B[ncode + 1, :])\n\n    partrecvcounts = similar(partsendcounts)\n    MPI.Alltoall!(UBuffer(partsendcounts, 1), UBuffer(partrecvcounts, 1), comm)\n\n    partrecvstarts = ones(Int, csize + 1)\n    for i in 1:csize\n        partrecvstarts[i + 1] = partrecvcounts[i] + partrecvstarts[i]\n    end\n\n    partsendorder, partsendstarts, partrecvstarts\nend\n\n\"\"\"\n    partition(comm::MPI.Comm, elemtovert, elemtocoord, elemtobndy,\n              faceconnections)\n\nThis function takes in a mesh (as returned for example by `brickmesh`) and\nreturns a Hilbert curve based partitioned mesh.\n\"\"\"\nfunction partition(\n    comm::MPI.Comm,\n    elemtovert,\n    elemtocoord,\n    elemtobndy,\n    faceconnections,\n    globord = [],\n)\n    (d, nvert, nelem) = size(elemtocoord)\n\n    csize = MPI.Comm_size(comm)\n    crank = MPI.Comm_rank(comm)\n\n    nface = 2d\n    nfacevert = 2^(d - 1)\n\n    # Here we expand the list of face connections into a structure that is easy\n    # to partition.  The cost is extra memory transfer.  If this becomes a\n    # bottleneck something more efficient may be implemented.\n    #\n    elemtofaceconnect =\n        zeros(eltype(eltype(faceconnections)), nfacevert, nface, nelem)\n    for fc in faceconnections\n        elemtofaceconnect[:, fc[2], fc[1]] = fc[3:end]\n    end\n\n    elemtocode = centroidtocode(comm, elemtocoord; CT = UInt64)\n    sendorder, sendstarts, recvstarts = getpartition(comm, elemtocode)\n\n    elemtovert = elemtovert[:, sendorder]\n    elemtocoord = elemtocoord[:, :, sendorder]\n    elemtobndy = elemtobndy[:, sendorder]\n    elemtofaceconnect = elemtofaceconnect[:, :, sendorder]\n\n    if !isempty(globord)\n        globord = globord[sendorder]\n    end\n\n\n    sendcounts = diff(sendstarts)\n    recvcounts = similar(sendcounts)\n    MPI.Alltoall!(UBuffer(sendcounts, 1), UBuffer(recvcounts, 1), comm)\n\n    newelemtovert = similar(elemtovert, nvert * sum(recvcounts))\n    MPI.Alltoallv!(\n        VBuffer(elemtovert, Cint(nvert) .* sendcounts),\n        VBuffer(newelemtovert, Cint(nvert) .* recvcounts),\n        comm,\n    )\n\n    newelemtocoord = similar(elemtocoord, d * nvert * sum(recvcounts))\n    MPI.Alltoallv!(\n        VBuffer(elemtocoord, Cint(d * nvert) .* sendcounts),\n        VBuffer(newelemtocoord, Cint(d * nvert) .* recvcounts),\n        comm,\n    )\n\n    newelemtobndy = similar(elemtobndy, nface * sum(recvcounts))\n    MPI.Alltoallv!(\n        VBuffer(elemtobndy, Cint(nface) .* sendcounts),\n        VBuffer(newelemtobndy, Cint(nface) .* recvcounts),\n        comm,\n    )\n\n    newelemtofaceconnect =\n        similar(elemtofaceconnect, nfacevert * nface * sum(recvcounts))\n    MPI.Alltoallv!(\n        VBuffer(elemtofaceconnect, Cint(nfacevert * nface) .* sendcounts),\n        VBuffer(newelemtofaceconnect, Cint(nfacevert * nface) .* recvcounts),\n        comm,\n    )\n\n    if !isempty(globord)\n        newglobord = similar(globord, sum(recvcounts))\n        MPI.Alltoallv!(\n            VBuffer(globord, sendcounts),\n            VBuffer(newglobord, recvcounts),\n            comm,\n        )\n    else\n        newglobord = similar(globord)\n    end\n\n    newnelem = recvstarts[end] - 1\n    newelemtovert = reshape(newelemtovert, nvert, newnelem)\n    newelemtocoord = reshape(newelemtocoord, d, nvert, newnelem)\n    newelemtobndy = reshape(newelemtobndy, nface, newnelem)\n    newelemtofaceconnect =\n        reshape(newelemtofaceconnect, nfacevert, nface, newnelem)\n\n    # reorder local elements based on code of new elements\n    A = UInt64[\n        centroidtocode(comm, newelemtocoord; CT = UInt64)\n        collect(1:newnelem)'\n    ]\n    A = sortslices(A, dims = 2)\n    newsortorder = view(A, d + 1, :)\n\n    newelemtovert = newelemtovert[:, newsortorder]\n    newelemtocoord = newelemtocoord[:, :, newsortorder]\n    newelemtobndy = newelemtobndy[:, newsortorder]\n    newelemtofaceconnect = newelemtofaceconnect[:, :, newsortorder]\n\n    newfaceconnections = similar(faceconnections, 0)\n    for e in 1:newnelem, f in 1:nface\n        if newelemtofaceconnect[1, f, e] > 0\n            push!(newfaceconnections, vcat(e, f, newelemtofaceconnect[:, f, e]))\n        end\n    end\n\n    if !isempty(globord)\n        newglobord = newglobord[newsortorder]\n    end\n\n    (\n        newelemtovert,\n        newelemtocoord,\n        newelemtobndy,\n        newfaceconnections,\n        newglobord,\n    )#sendorder)\nend\n\n\"\"\"\n    minmaxflip(x, y)\n\nReturns `x, y` sorted lowest to highest and a bool that indicates if a swap\nwas needed.\n\"\"\"\nminmaxflip(x, y) = y < x ? (y, x, true) : (x, y, false)\n\n\"\"\"\n    vertsortandorder(a)\n\nReturns `(a)` and an ordering `o==0`.\n\"\"\"\nvertsortandorder(a) = ((a,), 1)\n\n\"\"\"\n    vertsortandorder(a, b)\n\nReturns sorted vertex numbers `(a,b)` and an ordering `o` depending on the\norder needed to sort the elements.  This ordering is given below including the\nvetex ordering for faces.\n\n    o=    0      1\n\n        (a,b)  (b,a)\n\n          a      b\n          |      |\n          |      |\n          b      a\n\"\"\"\nfunction vertsortandorder(a, b)\n    a, b, s1 = minmaxflip(a, b)\n    o = s1 ? 2 : 1\n    ((a, b), o)\nend\n\n\"\"\"\n    vertsortandorder(a, b, c)\n\nReturns sorted vertex numbers `(a,b,c)` and an ordering `o` depending on the\norder needed to sort the elements.  This ordering is given below including the\nvetex ordering for faces.\n\n    o=     1         2         3         4         5         6\n\n        (a,b,c)   (c,a,b)   (b,c,a)   (b,a,c)   (c,b,a)   (a,c,b)\n\n          /c\\\\      /b\\\\      /a\\\\      /c\\\\      /a\\\\      /b\\\\\n         /   \\\\    /   \\\\    /   \\\\    /   \\\\    /   \\\\    /   \\\\\n        /a___b\\\\  /c___a\\\\  /b___c\\\\  /b___a\\\\  /c___b\\\\  /a___c\\\\\n\"\"\"\nfunction vertsortandorder(a, b, c)\n    # Use a (Bose-Nelson Algorithm based) sorting network from\n    # <http://pages.ripco.net/~jgamble/nw.html> to sort the vertices.\n    b, c, s1 = minmaxflip(b, c)\n    a, c, s2 = minmaxflip(a, c)\n    a, b, s3 = minmaxflip(a, b)\n\n    if !s1 && !s2 && !s3\n        o = 1\n    elseif !s1 && s2 && s3\n        o = 2\n    elseif s1 && !s2 && s3\n        o = 3\n    elseif !s1 && !s2 && s3\n        o = 4\n    elseif s1 && s2 && s3\n        o = 5\n    elseif s1 && !s2 && !s3\n        o = 6\n    else\n        error(\"Problem finding vertex ordering $((a,b,c)) with flips\n              $((s1,s2,s3))\")\n    end\n\n    ((a, b, c), o)\nend\n\n\"\"\"\n    vertsortandorder(a, b, c, d)\n\nReturns sorted vertex numbers `(a,b,c,d)` and an ordering `o` depending on the\norder needed to sort the elements.  This ordering is given below including the\nvetex ordering for faces.\n\n    o=   1      2      3      4      5      6      7      8\n\n       (a,b,  (a,c,  (b,a,  (b,d,  (c,a,  (c,d,  (d,b,  (d,c,\n        c,d)   b,d)   c,d)   a,c)   d,b)   a,b)   c,a)   b,a)\n\n       c---d  b---d  c---d  a---c  d---b  a---b  c---a  b---a\n       |   |  |   |  |   |  |   |  |   |  |   |  |   |  |   |\n       a---b  a---c  b---a  b---d  c---a  c---d  d---b  d---c\n\"\"\"\nfunction vertsortandorder(a, b, c, d)\n    # Use a (Bose-Nelson Algorithm based) sorting network from\n    # <http://pages.ripco.net/~jgamble/nw.html> to sort the vertices.\n    a, b, s1 = minmaxflip(a, b)\n    c, d, s2 = minmaxflip(c, d)\n    a, c, s3 = minmaxflip(a, c)\n    b, d, s4 = minmaxflip(b, d)\n    b, c, s5 = minmaxflip(b, c)\n\n    if !s1 && !s2 && !s3 && !s4 && !s5\n        o = 1\n    elseif !s1 && !s2 && !s3 && !s4 && s5\n        o = 2\n    elseif s1 && !s2 && !s3 && !s4 && !s5\n        o = 3\n    elseif !s1 && !s2 && s3 && s4 && s5\n        o = 4\n    elseif s1 && s2 && !s3 && !s4 && s5\n        o = 5\n    elseif !s1 && !s2 && s3 && s4 && !s5\n        o = 6\n    elseif s1 && s2 && s3 && s4 && s5\n        o = 7\n    elseif s1 && s2 && s3 && s4 && !s5\n        o = 8\n    else\n        # FIXME: some possible orientations are missing since there are a total of\n        # 24. Missing orientations:\n        #=\n           d---c  d---c  b---c  a---d  c---b  d---a  a---b  b---a\n           |   |  |   |  |   |  |   |  |   |  |   |  |   |  |   |\n           a---b  b---a  a---d  b---c  d---a  c---b  d---c  c---d\n\n           c---b  d---b  b---d  b---c  c---a  d---a  a---d  a---c\n           |   |  |   |  |   |  |   |  |   |  |   |  |   |  |   |\n           a---d  a---c  c---a  d---a  b---d  b---c  c---b  d---b\n        =#\n        error(\"Problem finding vertex ordering $((a,b,c,d))\n                with flips $((s1,s2,s3,s4,s5))\")\n    end\n\n    ((a, b, c, d), o)\nend\n\n\"\"\"\n    connectmesh(comm::MPI.Comm, elemtovert, elemtocoord, elemtobndy,\n                faceconnections)\n\nThis function takes in a mesh (as returned for example by `brickmesh`) and\nreturns a connected mesh.  This returns a `NamedTuple` of:\n\n - `elems` the range of element indices\n - `realelems` the range of real (aka nonghost) element indices\n - `ghostelems` the range of ghost element indices\n - `ghostfaces` ghost element to face is received;\n   `ghostfaces[f,ge] == true` if face `f` of ghost element `ge` is received.\n - `sendelems` an array of send element indices\n - `sendfaces` send element to face is sent;\n   `sendfaces[f,se] == true` if face `f` of send element `se` is sent.\n - `elemtocoord` element to vertex coordinates; `elemtocoord[d,i,e]` is the\n    `d`th coordinate of corner `i` of element `e`\n - `elemtoelem` element to neighboring element; `elemtoelem[f,e]` is the\n   number of the element neighboring element `e` across face `f`.  If there is\n   no neighboring element then `elemtoelem[f,e] == e`.\n - `elemtoface` element to neighboring element face; `elemtoface[f,e]` is the\n   face number of the element neighboring element `e` across face `f`.  If\n   there is no neighboring element then `elemtoface[f,e] == f`.\n - `elemtoordr` element to neighboring element order; `elemtoordr[f,e]` is the\n   ordering number of the element neighboring element `e` across face `f`.  If\n   there is no neighboring element then `elemtoordr[f,e] == 1`.\n - `elemtobndy` element to bounday number; `elemtobndy[f,e]` is the\n   boundary number of face `f` of element `e`.  If there is a neighboring\n   element then `elemtobndy[f,e] == 0`.\n - `nabrtorank` a list of the MPI ranks for the neighboring processes\n - `nabrtorecv` a range in ghost elements to receive for each neighbor\n - `nabrtosend` a range in `sendelems` to send for each neighbor\n\n\"\"\"\nfunction connectmesh(\n    comm::MPI.Comm,\n    elemtovert,\n    elemtocoord,\n    elemtobndy,\n    faceconnections;\n    dim = size(elemtocoord, 1),\n)\n    d = dim\n    (coorddim, nvert, nelem) = size(elemtocoord)\n    nface, nfacevert = 2d, 2^(d - 1)\n\n    p = reshape(1:nvert, ntuple(j -> 2, d))\n    fmask = hcat((\n        p[ntuple(\n            j ->\n                (j == div(f - 1, 2) + 1) ?\n                ((mod(f - 1, 2) + 1):(mod(f - 1, 2) + 1)) : (:),\n            d,\n        )...][:] for f in 1:nface\n    )...)\n\n    csize = MPI.Comm_size(comm)\n    crank = MPI.Comm_rank(comm)\n\n    VT = eltype(elemtovert)\n    A = Array{VT}(undef, nfacevert + 8, nface * nelem)\n\n    MR, ME, MF, MO, NR, NE, NF, NO = nfacevert .+ (1:8)\n    for e in 1:nelem\n        v = reshape(elemtovert[:, e], ntuple(j -> 2, d))\n        for f in 1:nface\n            j = (e - 1) * nface + f\n            fv, o = vertsortandorder(v[fmask[:, f]]...)\n            A[1:nfacevert, j] .= fv\n            A[MR, j] = crank\n            A[ME, j] = e\n            A[MF, j] = f\n            A[MO, j] = o\n            A[NR, j] = typemax(VT)\n            A[NE, j] = typemax(VT)\n            A[NF, j] = typemax(VT)\n            A[NO, j] = typemax(VT)\n        end\n    end\n\n    # use neighboring vertices for connected faces\n    for fc in faceconnections\n        e = fc[1]\n        f = fc[2]\n        v = fc[3:end]\n        j = (e - 1) * nface + f\n        fv, o = vertsortandorder(v...)\n        A[1:nfacevert, j] .= fv\n        A[MO, j] = o\n    end\n\n    A = parallelsortcolumns(comm, A, by = x -> x[1:nfacevert])\n    m, n = size(A)\n\n    # match faces\n    j = 1\n    while j <= n\n        if j + 1 <= n && A[1:nfacevert, j] == A[1:nfacevert, j + 1]\n            # found connected face\n            A[NR:NO, j] = A[MR:MO, j + 1]\n            A[NR:NO, j + 1] = A[MR:MO, j]\n            j += 2\n        else\n            # found unconnect face\n            A[NR:NO, j] = A[MR:MO, j]\n            j += 1\n        end\n    end\n\n    A = sortslices(A, dims = 2, by = x -> (x[MR], x[NR], x[ME], x[MF]))\n\n    # count number of elements that are going to be sent\n    sendcounts = zeros(Cint, csize)\n    for i in 1:last(size(A))\n        sendcounts[A[MR, i] + 1] += m\n    end\n    sendstarts = ones(Int, csize + 1)\n    for i in 1:csize\n        sendstarts[i + 1] = sendcounts[i] + sendstarts[i]\n    end\n\n    # communicate columns of A to original rank\n    recvcounts = similar(sendcounts)\n    MPI.Alltoall!(UBuffer(sendcounts, 1), UBuffer(recvcounts, 1), comm)\n\n    B = similar(A, sum(recvcounts))\n    MPI.Alltoallv!(VBuffer(A, sendcounts), VBuffer(B, recvcounts), comm)\n    B = reshape(B, m, nface * nelem)\n\n    # get element sending information\n    B = sortslices(B, dims = 2, by = x -> (x[NR], x[ME]))\n    sendelems = Int[]\n    counts = zeros(Int, csize + 1)\n    counts[1] = (last(size(B)) > 0) ? 1 : 0\n    sr, se = -1, 0\n    for i in 1:last(size(B))\n        r, e = B[NR, i], B[ME, i]\n        # See if we need to send element `e` to rank `r` and make sure that we\n        # didn't already mark it for sending.\n        if r != crank && !(sr == r && se == e)\n            counts[r + 2] += 1\n            append!(sendelems, e)\n            sr, se = r, e\n        end\n    end\n\n    # Mark which faces need to be sent\n    sendfaces = BitArray(undef, nface, length(sendelems))\n    sendfaces .= false\n    sr, se, n = -1, 0, 0\n    for i in 1:last(size(B))\n        r, e, f = B[NR, i], B[ME, i], B[MF, i]\n        if r != crank\n            if !(sr == r && se == e)\n                n += 1\n                sr, se = r, e\n            end\n            sendfaces[f, n] = true\n        end\n    end\n\n    sendstarts = cumsum(counts)\n    nabrtosendrank = Int[\n        r for r = 0:(csize - 1) if sendstarts[r + 2] - sendstarts[r + 1] > 0\n    ]\n    nabrtosend = UnitRange{Int}[\n        (sendstarts[r + 1]:(sendstarts[r + 2] - 1))\n        for r = 0:(csize - 1) if sendstarts[r + 2] - sendstarts[r + 1] > 0\n    ]\n\n    # get element receiving information\n    B = sortslices(B, dims = 2, by = x -> (x[NR], x[NE]))\n    counts = zeros(Int, csize + 1)\n    counts[1] = (last(size(B)) > 0) ? 1 : 0\n    sr, se = -1, 0\n    nghost = 0\n    for i in 1:last(size(B))\n        r, e = B[NR, i], B[NE, i]\n        if r != crank\n            # Check to make sure we have not already marked the element for\n            # receiving since we could be connected to the receiving element across\n            # multiple faces.\n            if !(sr == r && se == e)\n                nghost += 1\n                counts[r + 2] += 1\n                sr, se = r, e\n            end\n            B[NE, i] = nelem + nghost\n        end\n    end\n\n    # Mark which faces will be received\n    ghostfaces = BitArray(undef, nface, nghost)\n    ghostfaces .= false\n    sr, se, ge = -1, 0, 0\n    for i in 1:last(size(B))\n        r, e, f = B[NR, i], B[NE, i], B[NF, i]\n        if r != crank\n            if !(sr == r && se == e)\n                ge += 1\n                sr, se = r, e\n            end\n            B[NR, i] = crank\n            ghostfaces[f, ge] = true\n        end\n    end\n\n    recvstarts = cumsum(counts)\n    nabrtorecvrank = Int[\n        r for r = 0:(csize - 1) if recvstarts[r + 2] - recvstarts[r + 1] > 0\n    ]\n    nabrtorecv = UnitRange{Int}[\n        (recvstarts[r + 1]:(recvstarts[r + 2] - 1))\n        for r = 0:(csize - 1) if recvstarts[r + 2] - recvstarts[r + 1] > 0\n    ]\n\n    @assert nabrtorecvrank == nabrtosendrank\n    nabrtorank = nabrtorecvrank\n\n    elemtoelem = repeat((1:(nelem + nghost))', nface, 1)\n    elemtoface = repeat(1:nface, 1, nelem + nghost)\n    elemtoordr = ones(Int, nface, nelem + nghost)\n\n    if d == 2\n        for i in 1:last(size(B))\n            me, mf, mo = B[ME, i], B[MF, i], B[MO, i]\n            ne, nf, no = B[NE, i], B[NF, i], B[NO, i]\n\n            elemtoelem[mf, me] = ne\n            elemtoface[mf, me] = nf\n            elemtoordr[mf, me] = (no == mo ? 1 : 2)\n        end\n    else\n        for i in 1:last(size(B))\n            me, mf, mo = B[ME, i], B[MF, i], B[MO, i]\n            ne, nf, no = B[NE, i], B[NF, i], B[NO, i]\n\n            elemtoelem[mf, me] = ne\n            elemtoface[mf, me] = nf\n            if no != 1 || mo != 1\n                error(\"TODO add support for other orientations\")\n            end\n            elemtoordr[mf, me] = 1\n        end\n    end\n\n    # fill the ghost values in elemtocoord\n    newelemtocoord = similar(elemtocoord, coorddim, nvert, nelem + nghost)\n    newelemtobndy = similar(elemtobndy, nface, nelem + nghost)\n\n    sendelemtocoord = elemtocoord[:, :, sendelems]\n    sendelemtobndy = elemtobndy[:, sendelems]\n\n    crreq = [\n        MPI.Irecv!(view(newelemtocoord, :, :, nelem .+ er), r, 666, comm)\n        for (r, er) in zip(nabrtorank, nabrtorecv)\n    ]\n\n    brreq = [\n        MPI.Irecv!(view(newelemtobndy, :, nelem .+ er), r, 666, comm)\n        for (r, er) in zip(nabrtorank, nabrtorecv)\n    ]\n\n    csreq = [\n        MPI.Isend(view(sendelemtocoord, :, :, es), r, 666, comm)\n        for (r, es) in zip(nabrtorank, nabrtosend)\n    ]\n\n    bsreq = [\n        MPI.Isend(view(sendelemtobndy, :, es), r, 666, comm)\n        for (r, es) in zip(nabrtorank, nabrtosend)\n    ]\n\n    newelemtocoord[:, :, 1:nelem] .= elemtocoord\n    newelemtobndy[:, 1:nelem] .= elemtobndy\n\n    MPI.Waitall!([csreq; crreq; bsreq; brreq])\n\n    (\n        elems = 1:(nelem + nghost),       # range of          element indices\n        realelems = 1:nelem,            # range of real     element indices\n        ghostelems = nelem .+ (1:nghost), # range of ghost    element indices\n        ghostfaces = ghostfaces,\n        sendelems = sendelems,          # array of send     element indices\n        sendfaces = sendfaces,\n        elemtocoord = newelemtocoord,   # element to vertex coordinates\n        elemtovert = nothing,           # element to locally unique global vertex number (for direct stiffness summation)\n        elemtoelem = elemtoelem,        # element to neighboring element\n        elemtoface = elemtoface,        # element to neighboring element face\n        elemtoordr = elemtoordr,        # element to neighboring element order\n        elemtobndy = newelemtobndy,     # element to boundary number\n        nabrtorank = nabrtorank,        # list of neighboring processes MPI ranks\n        nabrtorecv = nabrtorecv,        # neighbor receive ranges into `ghostelems`\n        nabrtosend = nabrtosend,\n    )        # neighbor send ranges into `sendelems`\nend\n\n\"\"\"\n    (bndytoelem, bndytoface) = enumerateboundaryfaces!(elemtoelem, elemtobndy, periodicity, boundary)\n\nUpdate the `elemtoelem` array based on the boundary faces specified in\n`elemtobndy`. Builds the `bndytoelem` and `bndytoface` tuples.\n\"\"\"\nfunction enumerateboundaryfaces!(elemtoelem, elemtobndy, periodicity, boundary)\n    nb = 0\n    for i in 1:length(periodicity)\n        if !periodicity[i]\n            nb = max(nb, boundary[i]...)\n        end\n    end\n    # Limitation of the boundary condition unrolling in the DG kernels\n    # (should never be violated unless more general unstructured meshes are used\n    # since cube meshes only have 6 faces in 3D, and only 1 bcs is currently allowed\n    # per face)\n\n    @assert nb <= 6\n\n    bndytoelem = ntuple(b -> Vector{Int64}(), nb)\n    bndytoface = ntuple(b -> Vector{Int64}(), nb)\n\n    nface, nelem = size(elemtoelem)\n\n    N = zeros(Int, nb)\n    for e in 1:nelem\n        for f in 1:nface\n            d = elemtobndy[f, e]\n            @assert 0 <= d <= nb\n            if d != 0\n                elemtoelem[f, e] = N[d] += 1\n                push!(bndytoelem[d], e)\n                push!(bndytoface[d], f)\n            end\n        end\n    end\n    return (bndytoelem, bndytoface)\nend\n\n\"\"\"\n    connectmeshfull(comm::MPI.Comm, elemtovert, elemtocoord, elemtobndy,\n                faceconnections)\n\nThis function takes in a mesh (as returned for example by `brickmesh`) and\nreturns a corner connected mesh. It is similar to [`connectmesh`](@ref) but returns \na corner connected mesh, i.e. `ghostelems` will also include remote elements \nthat are only connected by vertices. This returns a `NamedTuple` of:\n\n - `elems` the range of element indices\n - `realelems` the range of real (aka nonghost) element indices\n - `ghostelems` the range of ghost element indices\n - `ghostfaces` ghost element to face is received;\n   `ghostfaces[f,ge] == true` if face `f` of ghost element `ge` is received.\n - `sendelems` an array of send element indices\n - `sendfaces` send element to face is sent;\n   `sendfaces[f,se] == true` if face `f` of send element `se` is sent.\n - `elemtocoord` element to vertex coordinates; `elemtocoord[d,i,e]` is the\n    `d`th coordinate of corner `i` of element `e`\n - `elemtoelem` element to neighboring element; `elemtoelem[f,e]` is the\n   number of the element neighboring element `e` across face `f`.  If there is\n   no neighboring element then `elemtoelem[f,e] == e`.\n - `elemtoface` element to neighboring element face; `elemtoface[f,e]` is the\n   face number of the element neighboring element `e` across face `f`.  If\n   there is no neighboring element then `elemtoface[f,e] == f`.\n - `elemtoordr` element to neighboring element order; `elemtoordr[f,e]` is the\n   ordering number of the element neighboring element `e` across face `f`.  If\n   there is no neighboring element then `elemtoordr[f,e] == 1`.\n - `elemtobndy` element to bounday number; `elemtobndy[f,e]` is the\n   boundary number of face `f` of element `e`.  If there is a neighboring\n   element then `elemtobndy[f,e] == 0`.\n - `nabrtorank` a list of the MPI ranks for the neighboring processes\n - `nabrtorecv` a range in ghost elements to receive for each neighbor\n - `nabrtosend` a range in `sendelems` to send for each neighbor\n\nThe algorithm assumes that the 2-D mesh can be gathered on single process,\nwhich is reasonable for the 2-D mesh.\n\"\"\"\nfunction connectmeshfull(\n    comm::MPI.Comm,\n    elemtovert,\n    elemtocoord,\n    elemtobndy,\n    faceconnections;\n    dim = size(elemtocoord, 1),\n)\n    @assert dim == 2\n    dim_coord = size(elemtocoord, 1)\n    csize = MPI.Comm_size(comm)\n    crank = MPI.Comm_rank(comm)\n    root = 0\n    I = eltype(elemtovert)\n    FT = eltype(elemtocoord)\n    nvert, nelem = size(elemtovert)\n    nfaces = 2 * dim\n    nelemv = zeros(I, csize)\n    fmask = build_fmask(dim)\n    nfvert = size(fmask, 1)\n    # collecting # of realelems on each process\n    MPI.Allgather!(nelem, UBuffer(nelemv, Cint(1)), comm)\n    offset = cumsum(nelemv) .+ 1\n    pushfirst!(offset, 1)\n    nelemg = sum(nelemv)\n    # collecting elemtovert on each process\n    elemtovertg = zeros(I, nvert + 2, nelemg)\n    MPI.Allgatherv!(\n        [elemtovert; ones(I, 1, nelem) .* crank; reshape(Array(1:nelem), 1, :)],\n        VBuffer(elemtovertg, nelemv .* (nvert + 2)),\n        comm,\n    )\n\n    nvertg = maximum(elemtovertg[1:nvert, :])\n    # collecting elemtocoordg on each process\n    elemtocoordg = Array{FT}(undef, dim_coord, nvert, nelemg)\n    MPI.Allgatherv!(\n        elemtocoord,\n        VBuffer(elemtocoordg, nelemv .* (dim_coord * nvert)),\n        comm,\n    )\n    # collecting elemtobndy on each process\n    elemtobndyg = zeros(I, nfaces, nelemg)\n    MPI.Allgatherv!(elemtobndy, VBuffer(elemtobndyg, nelemv .* nfaces), comm)\n    # accounting for vertices on periodic faces\n    nper = length(faceconnections) * nfvert\n    vconn = Array{Int}(undef, 2, nper)          # stores the periodic and corresponding shadow vertex\n    ctr = 1\n    for fc in faceconnections\n        e, f, v = fc[1], fc[2], fc[3:end]\n        fv = elemtovert[fmask[:, f], e]\n        fv, o = vertsortandorder(fv)\n        v, o = vertsortandorder(v)\n        for i in 1:nfvert\n            vconn[1, ctr] = fv[1][i]\n            vconn[2, ctr] = v[1][i]\n            ctr += 1\n        end\n    end\n    vconn = unique(vconn, dims = 2)\n    nper = size(vconn, 2)\n    nperv = zeros(I, csize)\n    MPI.Allgather!(nper, UBuffer(nperv, Cint(1)), comm)\n    nperg = sum(nperv)\n    vconng = Array{Int}(undef, 2, nperg)\n    MPI.Allgatherv!(vconn, VBuffer(vconng, nperv .* 2), comm)\n\n    gldofv = -ones(Int, nvertg)\n    pmarker = -ones(Int, nperg)\n    # conflict-free periodic nodes\n    for i in 1:nperg\n        v1, v2 = vconng[1, i], vconng[2, i]\n        if gldofv[v1] == -1 && gldofv[v2] == -1\n            id = min(v1, v2)\n            gldofv[v1] = id\n            gldofv[v2] = id\n            pmarker[i] = 1\n        end\n    end\n    # dealing with doubly periodic nodes (whenever applicable)\n    for i in 1:nperg\n        if pmarker[i] == -1\n            v1, v2 = vconng[1, i], vconng[2, i]\n            id = min(gldofv[v1], gldofv[v2])\n            gldofv[v1] = id\n            gldofv[v2] = id\n        end\n    end\n    # labeling non-periodic vertices\n    for i in 1:nvertg\n        if gldofv[i] == -1\n            gldofv[i] = i\n        end\n    end\n    #-------------------------------------------------------------\n    elemtovertg_orig = deepcopy(elemtovertg)\n    for i in 1:nelemg, j in 1:nvert\n        elemtovertg[j, i] = gldofv[elemtovertg[j, i]]\n    end\n    #-------------------------------------------------\n    nsend, nghost = 0, 0\n    interiorelems, exteriorelems = Int[], Int[]\n    vertgtoprocs = map(i -> zeros(Int, i), zeros(Int, nvertg)) # procs for each vertex\n    vertgtolelem = map(i -> zeros(Int, i), zeros(Int, nvertg)) # local cell # for each vertex\n    vertgtolvert = map(i -> zeros(Int, i), zeros(Int, nvertg)) # local vertex # in local cell for each vertex\n    sendelems = map(i -> zeros(Int, i), zeros(Int, csize))\n    recvelems = map(i -> zeros(Int, i), zeros(Int, csize))\n\n    for icls in 1:nelemg # gathering connectivity info for each global vertex\n        for ivt in 1:nvert\n            gvt = elemtovertg[ivt, icls]\n            prc = elemtovertg[nvert + 1, icls]\n            lcl = elemtovertg[nvert + 2, icls]\n            push!(vertgtoprocs[gvt], prc)\n            push!(vertgtolelem[gvt], lcl)\n            push!(vertgtolvert[gvt], ivt)\n        end\n    end\n\n    for icls in 1:nelem # building sendelems and recvelems\n        interior = true\n        for ivt in 1:nvert\n            vt = elemtovert[ivt, icls]\n            gvt = gldofv[vt]\n            for ip in 1:length(vertgtoprocs[gvt])\n                proc = vertgtoprocs[gvt][ip]\n                if proc ≠ crank\n                    lcell = vertgtolelem[gvt][ip]\n                    if findfirst(recvelems[proc + 1] .== lcell) == nothing\n                        push!(recvelems[proc + 1], lcell)\n                        nghost += 1\n                    end\n                    if findfirst(sendelems[proc + 1] .== icls) == nothing\n                        push!(sendelems[proc + 1], icls)\n                        nsend += 1\n                    end\n                    interior = false\n                end\n            end\n        end\n        interior ? push!(interiorelems, icls) : push!(exteriorelems, icls)\n    end\n\n    nabrtorank = Int[]\n    newsendelems = Array{Int}(undef, nsend)\n    nabrtosend = Array{UnitRange{Int64}}(undef, 0)\n    nabrtorecv = Array{UnitRange{Int64}}(undef, 0)\n    st_recv, en_recv = 1, 1\n    st_send, en_send = 1, 1\n\n    for ipr in 0:(csize - 1)\n        l_send = length(sendelems[ipr + 1])\n        l_recv = length(recvelems[ipr + 1])\n        if l_send > 0\n            en_send = st_send + l_send - 1\n            sort!(sendelems[ipr + 1])\n            newsendelems[st_send:en_send] .= sendelems[ipr + 1][:]\n            push!(nabrtosend, st_send:en_send)\n            st_send = en_send + 1\n            push!(nabrtorank, ipr)\n\n        end\n        if l_recv > 0\n            en_recv = st_recv + l_recv - 1\n            sort!(recvelems[ipr + 1])\n            push!(nabrtorecv, st_recv:en_recv)\n            st_recv = en_recv + 1\n        end\n    end\n\n    sendfaces = BitArray(zeros(nfaces, nsend))\n    ghostfaces = BitArray(zeros(nfaces, nghost))\n\n    newelemtovert = similar(elemtovert, nvert, (nelem + nghost))\n    newelemtocoord = similar(elemtocoord, dim_coord, nvert, (nelem + nghost))\n    newelemtobndy = similar(elemtobndy, nfaces, (nelem + nghost))\n    newelemtovert[1:nvert, 1:nelem] .= elemtovert\n    newelemtocoord[:, :, 1:nelem] .= elemtocoord\n    newelemtobndy[:, 1:nelem] .= elemtobndy\n    vmarker = BitArray(undef, nvert)\n    ctrg, ctrs = 1, 1\n\n    for ipr in 0:(csize - 1)\n        # building ghost faces\n        for icls in recvelems[ipr + 1]\n            vmarker .= 0\n            off = offset[ipr + 1]\n            newelemtovert[1:nvert, nelem + ctrg] .=\n                elemtovertg_orig[1:nvert, off + icls - 1]\n            newelemtocoord[:, :, nelem + ctrg] .=\n                elemtocoordg[:, :, off + icls - 1]\n            newelemtobndy[:, nelem + ctrg] .= elemtobndyg[:, off + icls - 1]\n            for ivt in 1:nvert\n                gvt = elemtovertg[ivt, icls + off - 1]\n                if findfirst(vertgtoprocs[gvt] .== crank) ≠ nothing\n                    vmarker[ivt] = 1\n                end\n            end\n            for fc in 1:nfaces\n                if findfirst(vmarker[fmask[:, fc]]) ≠ nothing\n                    ghostfaces[fc, ctrg] = 1\n                end\n            end\n            ctrg += 1\n        end\n        # building send faces\n        for icls in sendelems[ipr + 1]\n            vmarker .= 0\n            for ivt in 1:nvert\n                vt = elemtovert[ivt, icls]\n                gvt = gldofv[vt]\n                if findfirst(vertgtoprocs[gvt] .== ipr) ≠ nothing\n                    vmarker[ivt] = 1\n                end\n            end\n            for fc in 1:nfaces\n                if findfirst(vmarker[fmask[:, fc]]) ≠ nothing\n                    sendfaces[fc, ctrs] = 1\n                end\n            end\n            ctrs += 1\n        end\n    end\n\n    A = zeros(Int, (nfvert + 3), nfaces * (nelem + nghost))\n    for e in 1:(nelem + nghost)\n        v = reshape(newelemtovert[:, e], ntuple(j -> 2, dim))\n        for f in 1:nfaces\n            j = (e - 1) * nfaces + f\n            fv, o = vertsortandorder(v[fmask[:, f]]...) # faces vertices, B-N orientation\n            for fvt in 1:nfvert\n                A[fvt, j] = gldofv[fv[fvt]]\n            end\n            A[nfvert + 1, j] = o # orientation\n            A[nfvert + 2, j] = e # local element number\n            A[nfvert + 3, j] = f # local face number for element e\n        end\n    end\n\n    A = sortslices(A, dims = 2, by = x -> x[1:nfvert])\n    elemtoelem = Array{Int}(undef, nfaces, (nelem + nghost))\n    elemtoface = Array{Int}(undef, nfaces, (nelem + nghost))\n    elemtoordr = Array{Int}(undef, nfaces, (nelem + nghost))\n    # match faces\n    n = size(A, 2)\n    j = 1\n    while j ≤ n\n        lel = A[nfvert + 2, j]\n        lfc = A[nfvert + 3, j]\n        if j + 1 ≤ n && A[1:nfvert, j] == A[1:nfvert, j + 1]\n            # found connected face\n            nel = A[nfvert + 2, j + 1] # local neighboring element\n            nfc = A[nfvert + 3, j + 1] # local face # of local neighboring element\n            elemtoelem[lfc, lel] = nel\n            elemtoface[lfc, lel] = nfc\n            elemtoelem[nfc, nel] = lel\n            elemtoface[nfc, nel] = lfc\n            if A[nfvert + 1, j] == A[nfvert + 1, j + 1]\n                elemtoordr[lfc, lel] = 1\n                elemtoordr[nfc, nel] = 1\n            else\n                elemtoordr[lfc, lel] = 2\n                elemtoordr[nfc, nel] = 2\n            end\n            j += 2\n        else\n            # found unconnected face\n            elemtoelem[lfc, lel] = lel\n            elemtoface[lfc, lel] = lfc\n            elemtoordr[lfc, lel] = 1\n            j += 1\n        end\n    end\n    # provide locally unique elemtovert for DSS\n    uvert = -ones(Int, nvertg)\n    for el in 1:last(size(newelemtovert)), i in 1:nvert\n        newelemtovert[i, el] = gldofv[newelemtovert[i, el]]\n        uvert[newelemtovert[i, el]] = 0\n    end\n    ctr = 1\n    for i in 1:length(uvert)\n        if uvert[i] == 0\n            uvert[i] = ctr\n            ctr += 1\n        end\n    end\n    for el in 1:last(size(newelemtovert)), i in 1:nvert\n        newelemtovert[i, el] = uvert[newelemtovert[i, el]]\n    end\n    #---------------------------------------------------------------\n    (\n        elems = 1:(nelem + nghost),       # range of element indices\n        realelems = 1:nelem,              # range of real element indices\n        ghostelems = nelem .+ (1:nghost), # range of ghost element indices\n        ghostfaces = ghostfaces,          # bit array of recv faces for ghost elems\n        sendelems = newsendelems,         # array of send element indices\n        sendfaces = sendfaces,            # bit array of send faces for send elems\n        elemtocoord = newelemtocoord,     # element to vertex coordinates\n        elemtovert = newelemtovert,       # element to locally unique global vertex number (for direct stiffness summation)\n        elemtoelem = elemtoelem,          # element to neighboring element\n        elemtoface = elemtoface,          # element to neighboring element face\n        elemtoordr = elemtoordr,          # element to neighboring element order\n        elemtobndy = newelemtobndy,       # element to boundary number\n        nabrtorank = nabrtorank,          # list of neighboring processes MPI ranks\n        nabrtorecv = nabrtorecv,          # neighbor receive ranges into `ghostelems`\n        nabrtosend = nabrtosend,          # neighbor send ranges into `sendelems`\n    )\nend\n\n\"\"\"\n    build_fmask(dim)\n\nReturns the face mask for mapping element vertices to face vertices.\n\nex: for 2D element with vertices (1, 2, 3, 4)\n\n       3---4  \n       |   |  \n       1---2 \n\nthe function returns the face mask\nf1 | f2 | f3 | f4 \n=================\n 1 |  2 |  1 | 3\n 3 |  4 |  2 | 4\n================= \n\"\"\"\nfunction build_fmask(dim)\n    nvert = 2^dim\n    nfaces = 2 * dim\n    p = reshape(1:nvert, ntuple(j -> 2, dim))\n    fmask = Array{Int64}(undef, 2^(dim - 1), nfaces)\n    f = 0\n    for d in 1:dim\n        for slice in eachslice(p, dims = d)\n            fmask[:, f += 1] = vec(slice)\n        end\n    end\n    return fmask\nend\n\nend # module\n"
  },
  {
    "path": "src/Numerics/Mesh/DSS.jl",
    "content": "module DSS\n\nusing ..Grids\nusing ClimateMachine.MPIStateArrays\nusing CUDA\nusing KernelAbstractions\nusing DocStringExtensions\n\nexport dss!\n\n\"\"\"\n    dss3d(Q::MPIStateArray,\n        grid::DiscontinuousSpectralElementGrid)\n\nThis function computes the 3D direct stiffness summation for all variables in the MPIStateArray.\n\n# Fields\n - `Q`: MPIStateArray\n - `grid`: Discontinuous Spectral Element Grid\n\"\"\"\nfunction dss!(\n    Q::MPIStateArray,\n    grid::DiscontinuousSpectralElementGrid{FT, 3};\n    max_threads = 256,\n) where {FT}\n    DA = arraytype(grid)                                      # device array\n    device = arraytype(grid) <: Array ? CPU() : CUDADevice()  # device\n    #----communication--------------------------\n    event = MPIStateArrays.begin_ghost_exchange!(Q)\n    event = MPIStateArrays.end_ghost_exchange!(Q, dependencies = event)\n    wait(event)\n    #----Direct Stiffness Summation-------------\n    vertmap = grid.vertmap\n    edgemap = grid.edgemap\n    facemap = grid.facemap\n\n    vtconn = grid.topology.vtconn\n    fcconn = grid.topology.fcconn\n    edgconn = grid.topology.edgconn\n\n    vtconnoff = grid.topology.vtconnoff\n    edgconnoff = grid.topology.edgconnoff\n\n    nvt, nfc, nedg =\n        length(vtconnoff) - 1, size(fcconn, 1), length(edgconnoff) - 1\n\n    Nq = polynomialorders(grid) .+ 1\n    Nqmax = maximum(Nq)\n    Nemax = Nqmax - 2\n    Nfmax = size(facemap, 1)\n    args = (\n        Q.data,\n        vertmap,\n        edgemap,\n        facemap,\n        vtconn,\n        vtconnoff,\n        edgconn,\n        edgconnoff,\n        fcconn,\n        nvt,\n        nedg,\n        nfc,\n        Nemax,\n        Nfmax,\n    )\n    if device == CPU()\n        dss3d_CPU!(args...)\n    else\n        n_items = nvt + nfc + nedg\n        tx = max(n_items, max_threads)\n        bx = cld(n_items, tx)\n        @cuda threads = (tx) blocks = (bx) dss3d_CUDA!(args...)\n    end\n    return nothing\nend\n\nfunction dss3d_CUDA!(\n    data,\n    vertmap,\n    edgemap,\n    facemap,\n    vtconn,\n    vtconnoff,\n    edgconn,\n    edgconnoff,\n    fcconn,\n    nvt,\n    nedg,\n    nfc,\n    Nemax,\n    Nfmax,\n)\n    I = eltype(nvt)\n    FT = eltype(data)\n\n    tx = threadIdx().x        # threaid id\n    bx = blockIdx().x         # block id\n    bxdim = blockDim().x      # block dimension\n    glx = tx + (bx - 1) * bxdim # global id\n    nvars = size(data, 2)\n\n    # A mesh node is either\n    # - interior (no dss required)\n    # - on an element corner / vertex,\n    # - edge (excluding end point element corners / vertices)\n    # - face (excluding edges and corners)\n    if glx ≤ nvt #vertex DSS\n        vx = glx\n        for ivar in 1:nvars\n            dss_vertex!(vtconn, vtconnoff, vertmap, data, ivar, vx, FT)\n        end\n    elseif glx > nvt && glx ≤ (nvt + nedg) # edge DSS\n        ex = glx - nvt\n        for ivar in 1:nvars\n            dss_edge!(edgconn, edgconnoff, edgemap, Nemax, data, ivar, ex, FT)\n        end\n    elseif glx > (nvt + nedg) && glx ≤ (nvt + nedg + nfc) # face DSS\n        fx = glx - (nvt + nedg)\n        for ivar in 1:nvars\n            dss_face!(fcconn, facemap, Nfmax, data, ivar, fx)\n        end\n    end\n\n    return nothing\nend\n\nfunction dss3d_CPU!(\n    data,\n    vertmap,\n    edgemap,\n    facemap,\n    vtconn,\n    vtconnoff,\n    edgconn,\n    edgconnoff,\n    fcconn,\n    nvt,\n    nedg,\n    nfc,\n    Nemax,\n    Nfmax,\n)\n    I = eltype(nvt)\n    FT = eltype(data)\n    nvars = size(data, 2)\n\n    # A mesh node is either\n    # - interior (no dss required)\n    # - on an element corner / vertex,\n    # - edge (excluding end point element corners / vertices)\n    # - face (excluding edges and corners)\n    for ivar in 1:nvars\n        for vx in 1:nvt # vertex DSS\n            dss_vertex!(vtconn, vtconnoff, vertmap, data, ivar, vx, FT)\n        end\n        for ex in 1:nedg # edge DSS\n            dss_edge!(edgconn, edgconnoff, edgemap, Nemax, data, ivar, ex, FT)\n        end\n        for fx in 1:nfc # face DSS\n            dss_face!(fcconn, facemap, Nfmax, data, ivar, fx)\n        end\n    end\n    return nothing\nend\n\n\"\"\"\n    dss_vertex!(\n    vtconn,\n    vtconnoff,\n    vertmap,\n    data,\n    ivar,\n    vx,\n    ::Type{FT},\n) where {FT}\n\nThis function computes the direct stiffness summation for the vertex `vx`.\n\n# Fields\n - `vtconn`: vertex connectivity array\n - `vtconnoff`: offsets for vertex connectivity array\n - `vertmap`: map to vertex degrees of freedom: `vertmap[vx]` contains the \n    degree of freedom located at vertex `vx`.\n - `data`: data field of MPIStateArray\n - `ivar`: variable # in the MPIStateArray\n - `vx`: unique edge number\n - `::Type{FT}`: Floating point type\n\"\"\"\nfunction dss_vertex!(\n    vtconn,\n    vtconnoff,\n    vertmap,\n    data,\n    ivar,\n    vx,\n    ::Type{FT},\n) where {FT}\n    @inbounds st = vtconnoff[vx]\n    @inbounds nlvt = Int((vtconnoff[vx + 1] - vtconnoff[vx]) / 2)\n    sumv = -FT(0)\n    @inbounds for j in 1:nlvt\n        lvt = vtconn[st + (j - 1) * 2]\n        lelem = vtconn[st + (j - 1) * 2 + 1]\n        loc = vertmap[lvt]\n        sumv += data[loc, ivar, lelem]\n    end\n    @inbounds for j in 1:nlvt\n        lvt = vtconn[st + (j - 1) * 2]\n        lelem = vtconn[st + (j - 1) * 2 + 1]\n        loc = vertmap[lvt]\n        data[loc, ivar, lelem] = sumv\n    end\nend\n\n\"\"\"\n    dss_edge!(\n    edgconn,\n    edgconnoff,\n    edgemap,\n    Nemax,\n    data,\n    ivar,\n    ex,\n    ::Type{FT},\n) where {FT}\n\nThis function computes the direct stiffness summation for \nall degrees of freedom corresponding to edge `ex`. dss_edge!\napplies only to interior (non-vertex) edge nodes.\n\n# Fields\n - `edgconn`: edge connectivity array\n - `edgconnoff`: offsets for edge connectivity array\n - `edgemap`: map to edge degrees of freedom: \n    `edgemap[i, edgno, orient]` contains the element node index of \n    the `i`th interior node on edge `edgno`, under orientation `orient`. \n - `Nemax`: # of relevant degrees of freedom per edge (other dof are marked as -1)\n - `data`: data field of MPIStateArray\n - `ivar`: variable # in the MPIStateArray\n - `ex`: unique edge number\n - `::Type{FT}`: Floating point type\n\"\"\"\nfunction dss_edge!(\n    edgconn,\n    edgconnoff,\n    edgemap,\n    Nemax,\n    data,\n    ivar,\n    ex,\n    ::Type{FT},\n) where {FT}\n    @inbounds st = edgconnoff[ex]\n    @inbounds nledg = Int((edgconnoff[ex + 1] - edgconnoff[ex]) / 3)\n\n    @inbounds for k in 1:Nemax\n        sume = -FT(0)\n        @inbounds for j in 1:nledg\n            ledg = edgconn[st + (j - 1) * 3]\n            lor = edgconn[st + (j - 1) * 3 + 1]\n            lelem = edgconn[st + (j - 1) * 3 + 2]\n            loc = edgemap[k, ledg, lor]\n            if loc ≠ -1\n                sume += data[loc, ivar, lelem]\n            end\n        end\n        @inbounds for j in 1:nledg\n            ledg = edgconn[st + (j - 1) * 3]\n            lor = edgconn[st + (j - 1) * 3 + 1]\n            lelem = edgconn[st + (j - 1) * 3 + 2]\n            loc = edgemap[k, ledg, lor]\n            if loc ≠ -1\n                data[loc, ivar, lelem] = sume\n            end\n        end\n    end\nend\n\"\"\"\n    dss_face!(fcconn, facemap, Nfmax, data, ivar, fx)\n\nThis function computes the direct stiffness summation for \nall degrees of freedom corresponding to face `fx`. dss_face!\napplies only to interior (non-vertex and non-edge) face nodes.\n\n# Fields\n - `fcconn`: face connectivity array\n - `facemap`: map to face degrees of freedom: `facemap[ij, fcno, orient]` \n    contains the element node index of the `ij`th \n    interior node on face `fcno` under orientation `orient`\n - `Nfmax`: # of relevant degrees of freedom per face (other dof are marked as -1)\n - `data`: data field of MPIStateArray\n - `ivar`: variable # in the MPIStateArray\n - `fx`: unique face number\n\"\"\"\nfunction dss_face!(fcconn, facemap, Nfmax, data, ivar, fx)\n    @inbounds lfc = fcconn[fx, 1]\n    @inbounds lel = fcconn[fx, 2]\n    @inbounds nabrlfc = fcconn[fx, 3]\n    @inbounds nabrlel = fcconn[fx, 4]\n    @inbounds ordr = fcconn[fx, 5]\n    ordr = ordr == 3 ? 2 : 1\n    # mesh orientation 3 is a flip along the horizontal edges.\n    # This is the only orientation currently support by the mesh generator\n    # see vertsortandorder in\n    # src/Numerics/Mesh/BrickMesh.jl                \n    @inbounds for j in 1:Nfmax\n        loc1 = facemap[j, lfc, ordr]\n        loc2 = facemap[j, nabrlfc, 1]\n        if loc1 ≠ -1 && loc2 ≠ -1\n            sumf = data[loc1, ivar, lel] + data[loc2, ivar, nabrlel]\n            data[loc1, ivar, lel] = sumf\n            data[loc2, ivar, nabrlel] = sumf\n        end\n    end\nend\n\nend\n"
  },
  {
    "path": "src/Numerics/Mesh/Elements.jl",
    "content": "module Elements\nimport GaussQuadrature\n\n\"\"\"\n    lglpoints(::Type{T}, N::Integer) where T <: AbstractFloat\n\nreturns the points `r` and weights `w` associated with the `N+1`-point\nGauss-Legendre-Lobatto quadrature rule of type `T`\n\n\"\"\"\nfunction lglpoints(::Type{T}, N::Integer) where {T <: AbstractFloat}\n    @assert N ≥ 1\n    GaussQuadrature.legendre(T, N + 1, GaussQuadrature.both)\nend\n\n\"\"\"\n    glpoints(::Type{T}, N::Integer) where T <: AbstractFloat\n\nreturns the points `r` and weights `w` associated with the `N+1`-point\nGauss-Legendre quadrature rule of type `T`\n\"\"\"\nfunction glpoints(::Type{T}, N::Integer) where {T <: AbstractFloat}\n    GaussQuadrature.legendre(T, N + 1, GaussQuadrature.neither)\nend\n\n\"\"\"\n    baryweights(r)\n\nreturns the barycentric weights associated with the array of points `r`\n\nReference:\n  [Berrut2004](@cite)\n\"\"\"\nfunction baryweights(r::AbstractVector{T}) where {T}\n    Np = length(r)\n    wb = ones(T, Np)\n\n    for j in 1:Np\n        for i in 1:Np\n            if i != j\n                wb[j] = wb[j] * (r[j] - r[i])\n            end\n        end\n        wb[j] = T(1) / wb[j]\n    end\n    wb\nend\n\n\n\"\"\"\n    spectralderivative(r::AbstractVector{T},\n                       wb=baryweights(r)::AbstractVector{T}) where T\n\nreturns the spectral differentiation matrix for a polynomial defined on the\npoints `r` with associated barycentric weights `wb`\n\nReference:\n - [Berrut2004](@cite)\n\"\"\"\nfunction spectralderivative(\n    r::AbstractVector{T},\n    wb = baryweights(r)::AbstractVector{T},\n) where {T}\n    Np = length(r)\n    @assert Np == length(wb)\n    D = zeros(T, Np, Np)\n\n    for k in 1:Np\n        for j in 1:Np\n            if k == j\n                for l in 1:Np\n                    if l != k\n                        D[j, k] = D[j, k] + T(1) / (r[k] - r[l])\n                    end\n                end\n            else\n                D[j, k] = (wb[k] / wb[j]) / (r[j] - r[k])\n            end\n        end\n    end\n    D\nend\n\n\"\"\"\n    interpolationmatrix(rsrc::AbstractVector{T}, rdst::AbstractVector{T},\n                        wbsrc=baryweights(rsrc)::AbstractVector{T}) where T\n\nreturns the polynomial interpolation matrix for interpolating between the points\n`rsrc` (with associated barycentric weights `wbsrc`) and `rdst`\n\nReference:\n - [Berrut2004](@cite)\n\"\"\"\nfunction interpolationmatrix(\n    rsrc::AbstractVector{T},\n    rdst::AbstractVector{T},\n    wbsrc = baryweights(rsrc)::AbstractVector{T},\n) where {T}\n    Npdst = length(rdst)\n    Npsrc = length(rsrc)\n    @assert Npsrc == length(wbsrc)\n    I = zeros(T, Npdst, Npsrc)\n    for k in 1:Npdst\n        for j in 1:Npsrc\n            I[k, j] = wbsrc[j] / (rdst[k] - rsrc[j])\n            if !isfinite(I[k, j])\n                I[k, :] .= T(0)\n                I[k, j] = T(1)\n                break\n            end\n        end\n        d = sum(I[k, :])\n        I[k, :] = I[k, :] / d\n    end\n    I\nend\n\n\"\"\"\n    jacobip(α, β, N, x)\n\nReturns a `(nx, N+1)` array containing the `N+1` Jacobi polynomials of order `N`, \nwith parameter `(α, β)`, evaluated on 1D grid `x`.\n\"\"\"\nfunction jacobip(α, β, N, x)\n    a, b = GaussQuadrature.jacobi_coefs(N, α, β)\n    V = GaussQuadrature.orthonormal_poly(x, a, b)\n    return V\nend\n\nend # module\n"
  },
  {
    "path": "src/Numerics/Mesh/Filters.jl",
    "content": "module Filters\n\nusing SpecialFunctions\nusing LinearAlgebra, GaussQuadrature, KernelAbstractions\nusing KernelAbstractions.Extras: @unroll\nusing StaticArrays\nusing ..Grids\nusing ..Grids: Direction, EveryDirection, HorizontalDirection, VerticalDirection\n\nusing ...MPIStateArrays\nusing ...VariableTemplates: @vars, varsize, Vars, varsindices\n\nexport AbstractSpectralFilter, AbstractFilter\nexport ExponentialFilter,\n    CutoffFilter, MassPreservingCutoffFilter, TMARFilter, BoydVandevenFilter\n\nabstract type AbstractFilter end\nabstract type AbstractSpectralFilter <: AbstractFilter end\n\n\"\"\"\n    AbstractFilterTarget\n\nAn abstract type representing variables that the filter\nwill act on\n\"\"\"\nabstract type AbstractFilterTarget end\n\n\"\"\"\n    vars_state_filtered(::AbstractFilterTarget, FT)\n\nA tuple of symbols containing variables that the filter\nwill act on given a float type `FT`\n\"\"\"\nfunction vars_state_filtered end\n\n\"\"\"\n    compute_filter_argument!(::AbstractFilterTarget,\n                             state_filter::Vars,\n                             state::Vars,\n                             state_auxiliary::Vars)\n\nCompute filter argument `state_filter` based on `state`\nand `state_auxiliary`\n\"\"\"\nfunction compute_filter_argument! end\n\"\"\"\n    compute_filter_result!(::AbstractFilterTarget,\n                           state::Vars,\n                           state_filter::Vars,\n                           state_auxiliary::Vars)\n\nCompute filter result `state` based on the filtered state\n`state_filter` and `state_auxiliary`\n\"\"\"\nfunction compute_filter_result! end\n\nnumber_state_filtered(t::AbstractFilterTarget, FT) =\n    varsize(vars_state_filtered(t, FT))\n\n\"\"\"\n    FilterIndices(I)\n\nFilter variables based on their indices `I` where `I` can\nbe a range or a list of indices\n\n## Examples\n```julia\nFiltersIndices(1:3)\nFiltersIndices(1, 3, 5)\n```\n\"\"\"\nstruct FilterIndices{I} <: AbstractFilterTarget\n    FilterIndices(I::Integer...) = new{I}()\n    FilterIndices(I::AbstractRange) = new{I}()\nend\nvars_state_filtered(::FilterIndices{I}, FT) where {I} =\n    @vars(_::SVector{length(I), FT})\n\nfunction compute_filter_argument!(\n    ::FilterIndices{I},\n    filter_state::Vars,\n    state::Vars,\n    aux::Vars,\n) where {I}\n    @unroll for s in 1:length(I)\n        @inbounds parent(filter_state)[s] = parent(state)[I[s]]\n    end\nend\n\nfunction compute_filter_result!(\n    ::FilterIndices{I},\n    state::Vars,\n    filter_state::Vars,\n    aux::Vars,\n) where {I}\n    @unroll for s in 1:length(I)\n        @inbounds parent(state)[I[s]] = parent(filter_state)[s]\n    end\nend\n\n\n\"\"\"\n    spectral_filter_matrix(r, Nc, σ)\n\nReturns the filter matrix that takes function values at the interpolation\n`N+1` points, `r`, converts them into Legendre polynomial basis coefficients,\nmultiplies\n```math\nσ((n-N_c)/(N-N_c))\n```\nagainst coefficients `n=Nc:N` and evaluates the resulting polynomial at the\npoints `r`.\n\"\"\"\nfunction spectral_filter_matrix(r, Nc, σ)\n    N = length(r) - 1\n    T = eltype(r)\n\n    @assert N >= 0\n    @assert 0 <= Nc <= N\n\n    a, b = GaussQuadrature.legendre_coefs(T, N)\n    V = (N == 0 ? ones(T, 1, 1) : GaussQuadrature.orthonormal_poly(r, a, b))\n\n    Σ = ones(T, N + 1)\n    Σ[(Nc:N) .+ 1] .= σ.(((Nc:N) .- Nc) ./ (N - Nc))\n\n    V * Diagonal(Σ) / V\nend\n\n\"\"\"\n    modified_filter_matrix(r, Nc, σ)\n\nReturns the filter matrix that takes function values at the interpolation\n`N+1` points, `r`, converts them into Legendre polynomial basis coefficients,\nmultiplies\n```math\nσ((n-N_c)/(N-N_c))\n```\nagainst coefficients `n=Nc:N` and evaluates the resulting polynomial at the\npoints `r`. Unlike spectral_filter_matrix, this allows for the identity matrix,\nto be applied.\n\"\"\"\nfunction modified_filter_matrix(r, Nc, σ)\n    N = length(r) - 1\n    T = eltype(r)\n\n    @assert N >= 0\n    @assert 0 <= Nc\n\n    Nc > N && return Array{T}(I, N + 1, N + 1)\n\n    a, b = GaussQuadrature.legendre_coefs(T, N)\n    V = (N == 0 ? ones(T, 1, 1) : GaussQuadrature.orthonormal_poly(r, a, b))\n\n    Σ = ones(T, N + 1)\n    Σ[(Nc:N) .+ 1] .= σ.(((Nc:N) .- Nc) ./ (N - Nc))\n\n    V * Diagonal(Σ) / V\nend\n\n\"\"\"\n    ExponentialFilter(grid, Nc=0, s=32, α=-log(eps(eltype(grid))))\n\nReturns the spectral filter with the filter function\n```math\nσ(η) = \\exp(-α η^s)\n```\nwhere `s` is the filter order (must be even), the filter starts with\npolynomial order `Nc`, and `alpha` is a parameter controlling the smallest\nvalue of the filter function.\n\"\"\"\nstruct ExponentialFilter{FM} <: AbstractSpectralFilter\n    \"filter matrices in all directions (tuple of filter matrices)\"\n    filter_matrices::FM\n\n    function ExponentialFilter(\n        grid,\n        Nc = 0,\n        s = 32,\n        α = -log(eps(eltype(grid))),\n    )\n        dim = dimensionality(grid)\n\n        # Support different filtering thresholds in different\n        # directions (default behavior is to apply the same threshold\n        # uniformly in all directions)\n        if Nc isa Integer\n            Nc = ntuple(i -> Nc, dim)\n        elseif Nc isa NTuple{2} && dim == 3\n            Nc = (Nc[1], Nc[1], Nc[2])\n        end\n        @assert length(Nc) == dim\n\n        # Tuple of polynomial degrees (N₁, N₂, N₃)\n        N = polynomialorders(grid)\n        # In 2D, we assume same polynomial order in the horizontal\n        @assert dim == 2 || N[1] == N[2]\n        @assert iseven(s)\n        @assert all(0 .<= Nc .<= N)\n\n        σ(η) = exp(-α * η^s)\n\n        AT = arraytype(grid)\n        ξ = referencepoints(grid)\n        filter_matrices =\n            ntuple(i -> AT(spectral_filter_matrix(ξ[i], Nc[i], σ)), dim)\n        new{typeof(filter_matrices)}(filter_matrices)\n    end\nend\n\n\"\"\"\n    BoydVandevenFilter(grid, Nc=0, s=32)\n\nReturns the spectral filter using the logarithmic error function of\nthe form:\n```math\nσ(η) = 1/2 erfc(2*sqrt(s)*χ(η)*(abs(η)-0.5))\n```\nwhenever s ≤ i ≤ N, and 1 otherwise. The function `χ(η)` is defined\nas\n```math\nχ(η) = sqrt(-log(1-4*(abs(η)-0.5)^2)/(4*(abs(η)-0.5)^2))\n```\nif `x != 0.5` and `1` otherwise. Here, `s` is the filter order,\nthe filter starts with polynomial order `Nc`, and `alpha` is a parameter\ncontrolling the smallest value of the filter function.\n\n### References\n - [Boyd1996](@cite)\n\"\"\"\nstruct BoydVandevenFilter{FM} <: AbstractSpectralFilter\n    \"filter matrices in all directions (tuple of filter matrices)\"\n    filter_matrices::FM\n\n    function BoydVandevenFilter(grid, Nc = 0, s = 32)\n        dim = dimensionality(grid)\n\n        # Support different filtering thresholds in different\n        # directions (default behavior is to apply the same threshold\n        # uniformly in all directions)\n        if Nc isa Integer\n            Nc = ntuple(i -> Nc, dim)\n        elseif Nc isa NTuple{2} && dim == 3\n            Nc = (Nc[1], Nc[1], Nc[2])\n        end\n        @assert length(Nc) == dim\n\n        # Tuple of polynomial degrees (N₁, N₂, N₃)\n        N = polynomialorders(grid)\n        # In 2D, we assume same polynomial order in the horizontal\n        @assert dim == 2 || N[1] == N[2]\n        @assert iseven(s)\n        @assert all(0 .<= Nc .<= N)\n\n        function σ(η)\n            a = 2 * abs(η) - 1\n            χ = iszero(a) ? one(a) : sqrt(-log1p(-a^2) / a^2)\n            return erfc(sqrt(s) * χ * a) / 2\n        end\n\n        AT = arraytype(grid)\n        ξ = referencepoints(grid)\n        filter_matrices =\n            ntuple(i -> AT(spectral_filter_matrix(ξ[i], Nc[i], σ)), dim)\n        new{typeof(filter_matrices)}(filter_matrices)\n    end\nend\n\n\"\"\"\n    CutoffFilter(grid, Nc=polynomialorders(grid))\n\nReturns the spectral filter that zeros out polynomial modes greater than or\nequal to `Nc`.\n\"\"\"\nstruct CutoffFilter{FM} <: AbstractSpectralFilter\n    \"filter matrices in all directions (tuple of filter matrices)\"\n    filter_matrices::FM\n\n    function CutoffFilter(grid, Nc = polynomialorders(grid))\n        dim = dimensionality(grid)\n\n        # Support different filtering thresholds in different\n        # directions (default behavior is to apply the same threshold\n        # uniformly in all directions)\n        if Nc isa Integer\n            Nc = ntuple(i -> Nc, dim)\n        elseif Nc isa NTuple{2} && dim == 3\n            Nc = (Nc[1], Nc[1], Nc[2])\n        end\n        @assert length(Nc) == dim\n\n        # Tuple of polynomial degrees (N₁, N₂, N₃)\n        N = polynomialorders(grid)\n        # In 2D, we assume same polynomial order in the horizontal\n        @assert dim == 2 || N[1] == N[2]\n        @assert all(0 .<= Nc .<= N)\n\n        σ(η) = 0\n\n        AT = arraytype(grid)\n        ξ = referencepoints(grid)\n        filter_matrices =\n            ntuple(i -> AT(spectral_filter_matrix(ξ[i], Nc[i], σ)), dim)\n        new{typeof(filter_matrices)}(filter_matrices)\n    end\nend\n\n\n\"\"\"\n    MassPreservingCutoffFilter(grid, Nc=polynomialorders(grid))\n    \nReturns the spectral filter that zeros out polynomial modes greater than or\nequal to `Nc` while preserving the cell average value. Use this filter if the\njacobian is nonconstant.\n\"\"\"\nstruct MassPreservingCutoffFilter{FM} <: AbstractSpectralFilter\n    \"filter matrices in all directions (tuple of filter matrices)\"\n    filter_matrices::FM\n\n    function MassPreservingCutoffFilter(grid, Nc = polynomialorders(grid))\n        dim = dimensionality(grid)\n\n        # Support different filtering thresholds in different\n        # directions (default behavior is to apply the same threshold\n        # uniformly in all directions)\n        if Nc isa Integer\n            Nc = ntuple(i -> Nc, dim)\n        elseif Nc isa NTuple{2} && dim == 3\n            Nc = (Nc[1], Nc[1], Nc[2])\n        end\n        @assert length(Nc) == dim\n\n        # Tuple of polynomial degrees (N₁, N₂, N₃)\n        N = polynomialorders(grid)\n        # In 2D, we assume same polynomial order in the horizontal\n        @assert dim == 2 || N[1] == N[2]\n        @assert all(0 .<= Nc)\n\n        σ(η) = 0\n\n        AT = arraytype(grid)\n        ξ = referencepoints(grid)\n        filter_matrices =\n            ntuple(i -> AT(modified_filter_matrix(ξ[i], Nc[i], σ)), dim)\n        new{typeof(filter_matrices)}(filter_matrices)\n    end\nend\n\n\"\"\"\n    TMARFilter()\n\nReturns the truncation and mass aware rescaling nonnegativity preservation\nfilter.  The details of this filter are described in [Light2016](@cite)\n\nNote this needs to be used with a restrictive time step or a flux correction\nto ensure that grid integral is conserved.\n\n## Examples\n\nThis filter can be applied to the 3rd and 4th fields of an `MPIStateArray` `Q`\nwith the code\n\n```julia\nFilters.apply!(Q, (3, 4), grid, TMARFilter())\n```\n\nwhere `grid` is the associated `DiscontinuousSpectralElementGrid`.\n\"\"\"\nstruct TMARFilter <: AbstractFilter end\n\n\"\"\"\n    Filters.apply!(Q::MPIStateArray,\n        target,\n        grid::DiscontinuousSpectralElementGrid,\n        filter::AbstractSpectralFilter;\n        kwargs...)\n\nApplies `filter` to `Q` given a `grid` and a custom `target`.\n\nA `target` can be any of the following:\n - a tuple or range of indices\n - a tuple of symbols or strings of variable names\n - a colon (`:`) to apply to all variables\n - a custom [`AbstractFilterTarget`]\n\nThe following keyword arguments are supported for some filters:\n- `direction`: for `AbstractSpectralFilter` controls if the filter is\n  applied in the horizontal and/or vertical directions. It is assumed that the\n  trailing dimension on the reference element is the vertical dimension and the\n  rest are horizontal.\n- `state_auxiliary`: if `target` requires auxiliary state to compute its argument or results.\n\n# Examples\n\nSpecifying the `target` via indices:\n```julia\nFilters.apply!(Q, :, grid, TMARFilter())\nFilters.apply!(Q, (1, 3), grid, CutoffFilter(grid); direction=VerticalDirection())\n```\n\nSpeciying `target` via symbols or strings:\n```julia\nFilters.apply!(Q, (:ρ, \"energy.ρe\"), grid, TMARFilter())\nFilters.apply!(Q, (\"moisture.ρq_tot\",), grid, CutoffFilter(grid);\n               direction=VerticalDirection())\n```\n\"\"\"\nfunction apply!(\n    Q,\n    target,\n    grid::DiscontinuousSpectralElementGrid,\n    filter::AbstractFilter;\n    kwargs...,\n)\n    device = typeof(Q.data) <: Array ? CPU() : CUDADevice()\n    event = Event(device)\n    event =\n        apply_async!(Q, target, grid, filter; dependencies = event, kwargs...)\n    wait(device, event)\nend\n\n\n\"\"\"\n    Filters.apply_async!(Q, target, grid::DiscontinuousSpectralElementGrid,\n        filter::AbstractFilter;\n        dependencies,\n        kwargs...)\n\nAn asynchronous version of [`Filters.apply!`](@ref), returning an `Event`\nobject. `dependencies` should be an `Event` or tuple of `Event`s which need to\nfinish before applying the filter.\n\n```julia\ncompstream = Filters.apply_async!(Q, :, grid, CutoffFilter(grid); dependencies=compstream)\nwait(compstream)\n```\n\"\"\"\nfunction apply_async! end\n\nfunction apply_async!(\n    Q,\n    target::AbstractFilterTarget,\n    grid::DiscontinuousSpectralElementGrid,\n    filter::AbstractSpectralFilter;\n    dependencies,\n    state_auxiliary = nothing,\n    direction = EveryDirection(),\n)\n    topology = grid.topology\n\n    # Tuple of polynomial degrees (N₁, N₂, N₃)\n    N = polynomialorders(grid)\n    # In 2D, we assume same polynomial order in the horizontal\n    dim = dimensionality(grid)\n    # Currently only support same polynomial in both horizontal directions\n    @assert N[1] == N[2]\n\n    device = typeof(Q.data) <: Array ? CPU() : CUDADevice()\n\n    nelem = length(topology.elems)\n    # Number of Gauss-Lobatto quadrature points in each direction\n    Nq = N .+ 1\n    Nq1 = Nq[1]\n    Nq2 = Nq[2]\n    Nq3 = dim == 2 ? 1 : Nq[dim]\n\n    nrealelem = length(topology.realelems)\n    event = dependencies\n\n    if direction isa EveryDirection || direction isa HorizontalDirection\n        @assert dim == 2 || Nq1 == Nq2\n        filtermatrix = filter.filter_matrices[1]\n        event = kernel_apply_filter!(device, (Nq1, Nq2, Nq3))(\n            Val(dim),\n            Val(N),\n            Val(vars(Q)),\n            Val(isnothing(state_auxiliary) ? nothing : vars(state_auxiliary)),\n            HorizontalDirection(),\n            Q.data,\n            isnothing(state_auxiliary) ? nothing : state_auxiliary.data,\n            target,\n            filtermatrix,\n            ndrange = (nrealelem * Nq1, Nq2, Nq3),\n            dependencies = event,\n        )\n    end\n    if direction isa EveryDirection || direction isa VerticalDirection\n        filtermatrix = filter.filter_matrices[end]\n        event = kernel_apply_filter!(device, (Nq1, Nq2, Nq3))(\n            Val(dim),\n            Val(N),\n            Val(vars(Q)),\n            Val(isnothing(state_auxiliary) ? nothing : vars(state_auxiliary)),\n            VerticalDirection(),\n            Q.data,\n            isnothing(state_auxiliary) ? nothing : state_auxiliary.data,\n            target,\n            filtermatrix,\n            ndrange = (nrealelem * Nq1, Nq2, Nq3),\n            dependencies = event,\n        )\n    end\n    return event\nend\n\n\nfunction apply_async!(\n    Q,\n    target::AbstractFilterTarget,\n    grid::DiscontinuousSpectralElementGrid,\n    ::TMARFilter;\n    dependencies,\n)\n    topology = grid.topology\n\n    device = typeof(Q.data) <: Array ? CPU() : CUDADevice()\n\n    dim = dimensionality(grid)\n    N = polynomialorders(grid)\n    # Currently only support same polynomial in both horizontal directions\n    @assert dim == 2 || N[1] == N[2]\n    Nqs = N .+ 1\n    Nq = Nqs[1]\n    Nqj = dim == 2 ? 1 : Nqs[2]\n\n    nrealelem = length(topology.realelems)\n    nreduce = 2^ceil(Int, log2(Nq * Nqj))\n\n    event = dependencies\n    event = kernel_apply_TMAR_filter!(device, (Nq, Nqj), (nrealelem * Nq, Nqj))(\n        Val(nreduce),\n        Val(dim),\n        Val(N),\n        Q.data,\n        target,\n        grid.vgeo,\n        dependencies = event,\n    )\n    return event\nend\n\nfunction apply_async!(\n    Q,\n    target::AbstractFilterTarget,\n    grid::DiscontinuousSpectralElementGrid,\n    filter::MassPreservingCutoffFilter;\n    dependencies,\n    state_auxiliary = nothing,\n    direction = EveryDirection(),\n)\n    topology = grid.topology\n\n    device = typeof(Q.data) <: Array ? CPU() : CUDADevice()\n\n    dim = dimensionality(grid)\n    N = polynomialorders(grid)\n    # Currently only support same polynomial in both horizontal directions\n    @assert dim == 2 || N[1] == N[2]\n    Nq = N .+ 1\n    Nq1 = Nq[1]\n    Nq2 = Nq[2]\n    Nq3 = dim == 2 ? 1 : Nq[dim]\n\n    nrealelem = length(topology.realelems)\n    # parallel sum info\n    nreduce = 2^ceil(Int, log2(Nq1 * Nq2 * Nq3))\n    event = dependencies\n\n    if direction isa EveryDirection || direction isa HorizontalDirection\n        @assert dim == 2 || Nq1 == Nq2\n        filtermatrix = filter.filter_matrices[1]\n        event = kernel_apply_mp_filter!(device, (Nq1, Nq2, Nq3))(\n            Val(nreduce),\n            Val(dim),\n            Val(N),\n            Val(vars(Q)),\n            Val(isnothing(state_auxiliary) ? nothing : vars(state_auxiliary)),\n            HorizontalDirection(),\n            Q.data,\n            isnothing(state_auxiliary) ? nothing : state_auxiliary.data,\n            target,\n            filtermatrix,\n            grid.vgeo,\n            ndrange = (nrealelem * Nq1, Nq2, Nq3),\n            dependencies = event,\n        )\n    end\n    if direction isa EveryDirection || direction isa VerticalDirection\n        filtermatrix = filter.filter_matrices[end]\n        event = kernel_apply_mp_filter!(device, (Nq1, Nq2, Nq3))(\n            Val(nreduce),\n            Val(dim),\n            Val(N),\n            Val(vars(Q)),\n            Val(isnothing(state_auxiliary) ? nothing : vars(state_auxiliary)),\n            VerticalDirection(),\n            Q.data,\n            isnothing(state_auxiliary) ? nothing : state_auxiliary.data,\n            target,\n            filtermatrix,\n            grid.vgeo,\n            ndrange = (nrealelem * Nq1, Nq2, Nq3),\n            dependencies = event,\n        )\n    end\n    return event\nend\n\nfunction apply_async!(\n    Q,\n    indices::Union{Colon, AbstractRange, Tuple{Vararg{Integer}}},\n    grid::DiscontinuousSpectralElementGrid,\n    filter::AbstractFilter;\n    kwargs...,\n)\n    if indices isa Colon\n        indices = 1:size(Q, 2)\n    end\n    apply_async!(Q, FilterIndices(indices...), grid, filter; kwargs...)\nend\n\nfunction apply_async!(\n    Q,\n    vs::Tuple,\n    grid::DiscontinuousSpectralElementGrid,\n    filter::AbstractFilter;\n    kwargs...,\n)\n    apply_async!(\n        Q,\n        FilterIndices(varsindices(vars(Q), vs)...),\n        grid,\n        filter;\n        kwargs...,\n    )\nend\n\nconst _M = Grids._M\n\n@doc \"\"\"\n    kernel_apply_filter!(::Val{dim}, ::Val{N}, direction,\n                         Q, state_auxiliary, target, filtermatrix\n                        ) where {dim, N}\n\nComputational kernel: Applies the `filtermatrix` to `Q` given a\ncustom target `target` while preserving the cell average.\n\nThe `direction` argument is used to control if the filter is applied in the\nhorizontal and/or vertical reference directions.\n\"\"\" kernel_apply_filter!\n@kernel function kernel_apply_filter!(\n    ::Val{dim},\n    ::Val{N},\n    ::Val{vars_Q},\n    ::Val{vars_state_auxiliary},\n    direction,\n    Q,\n    state_auxiliary,\n    target::AbstractFilterTarget,\n    filtermatrix,\n) where {dim, N, vars_Q, vars_state_auxiliary}\n    @uniform begin\n        FT = eltype(Q)\n\n        Nqs = N .+ 1\n        Nq1 = Nqs[1]\n        Nq2 = Nqs[2]\n        Nq3 = dim == 2 ? 1 : Nqs[dim]\n\n        if direction isa EveryDirection\n            filterinξ1 = filterinξ2 = true\n            filterinξ3 = dim == 2 ? false : true\n        elseif direction isa HorizontalDirection\n            filterinξ1 = true\n            filterinξ2 = dim == 2 ? false : true\n            filterinξ3 = false\n        elseif direction isa VerticalDirection\n            filterinξ1 = false\n            filterinξ2 = dim == 2 ? true : false\n            filterinξ3 = dim == 2 ? false : true\n        end\n\n        nstates = varsize(vars_Q)\n        nfilterstates = number_state_filtered(target, FT)\n        nfilteraux =\n            isnothing(state_auxiliary) ? 0 : varsize(vars_state_auxiliary)\n\n        # ugly workaround around problems with @private\n        # hopefully will be soon fixed in KA\n        l_Q2 = MVector{nstates, FT}(undef)\n        l_Qfiltered2 = MVector{nfilterstates, FT}(undef)\n    end\n\n    s_Q = @localmem FT (Nq1, Nq2, Nq3, nfilterstates)\n    l_Q = @private FT (nstates,)\n    l_Qfiltered = @private FT (nfilterstates,)\n    l_aux = @private FT (nfilteraux,)\n\n    e = @index(Group, Linear)\n    i, j, k = @index(Local, NTuple)\n\n    @inbounds begin\n        ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n        @unroll for s in 1:nstates\n            l_Q[s] = Q[ijk, s, e]\n        end\n\n        @unroll for s in 1:nfilteraux\n            l_aux[s] = state_auxiliary[ijk, s, e]\n        end\n\n        fill!(l_Qfiltered2, -zero(FT))\n\n        compute_filter_argument!(\n            target,\n            Vars{vars_state_filtered(target, FT)}(l_Qfiltered2),\n            Vars{vars_Q}(l_Q[:]),\n            Vars{vars_state_auxiliary}(l_aux[:]),\n        )\n\n        @unroll for fs in 1:nfilterstates\n            l_Qfiltered[fs] = zero(FT)\n        end\n\n        @unroll for fs in 1:nfilterstates\n            s_Q[i, j, k, fs] = l_Qfiltered2[fs]\n        end\n\n        if filterinξ1\n            @synchronize\n            @unroll for n in 1:Nq1\n                @unroll for fs in 1:nfilterstates\n                    l_Qfiltered[fs] += filtermatrix[i, n] * s_Q[n, j, k, fs]\n                end\n            end\n\n            if filterinξ2 || filterinξ3\n                @synchronize\n                @unroll for fs in 1:nfilterstates\n                    s_Q[i, j, k, fs] = l_Qfiltered[fs]\n                    l_Qfiltered[fs] = zero(FT)\n                end\n            end\n        end\n\n        if filterinξ2\n            @synchronize\n            @unroll for n in 1:Nq2\n                @unroll for fs in 1:nfilterstates\n                    l_Qfiltered[fs] += filtermatrix[j, n] * s_Q[i, n, k, fs]\n                end\n            end\n\n            if filterinξ3\n                @synchronize\n                @unroll for fs in 1:nfilterstates\n                    s_Q[i, j, k, fs] = l_Qfiltered[fs]\n                    l_Qfiltered[fs] = zero(FT)\n                end\n            end\n        end\n\n        if filterinξ3\n            @synchronize\n            @unroll for n in 1:Nq3\n                @unroll for fs in 1:nfilterstates\n                    l_Qfiltered[fs] += filtermatrix[k, n] * s_Q[i, j, n, fs]\n                end\n            end\n        end\n\n        @unroll for s in 1:nstates\n            l_Q2[s] = l_Q[s]\n        end\n\n        compute_filter_result!(\n            target,\n            Vars{vars_Q}(l_Q2),\n            Vars{vars_state_filtered(target, FT)}(l_Qfiltered[:]),\n            Vars{vars_state_auxiliary}(l_aux[:]),\n        )\n\n        # Store result\n        ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n        @unroll for s in 1:nstates\n            Q[ijk, s, e] = l_Q2[s]\n        end\n\n        @synchronize\n    end\nend\n\n@kernel function kernel_apply_TMAR_filter!(\n    ::Val{nreduce},\n    ::Val{dim},\n    ::Val{N},\n    Q,\n    target::FilterIndices{I},\n    vgeo,\n) where {nreduce, dim, N, I}\n    @uniform begin\n        FT = eltype(Q)\n\n        Nqs = N .+ 1\n        Nq1 = Nqs[1]\n        Nq2 = dim == 2 ? 1 : Nqs[2]\n        Nq3 = Nqs[end]\n\n        nfilterstates = number_state_filtered(target, FT)\n        nelemperblock = 1\n    end\n\n    l_Q = @private FT (nfilterstates, Nq1)\n    l_MJ = @private FT (Nq1,)\n\n    s_MJQ = @localmem FT (Nq1 * Nq2, nfilterstates)\n    s_MJQclipped = @localmem FT (Nq1 * Nq2, nfilterstates)\n\n    e = @index(Group, Linear)\n    i, j = @index(Local, NTuple)\n\n    @inbounds begin\n        # loop up the pencil and load Q and MJ\n        @unroll for k in 1:Nq3\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n            @unroll for sf in 1:nfilterstates\n                s = I[sf]\n                l_Q[sf, k] = Q[ijk, s, e]\n            end\n\n            l_MJ[k] = vgeo[ijk, _M, e]\n        end\n\n        @unroll for sf in 1:nfilterstates\n            MJQ, MJQclipped = zero(FT), zero(FT)\n\n            @unroll for k in 1:Nq3\n                MJ = l_MJ[k]\n                Qs = l_Q[sf, k]\n                Qsclipped = Qs ≥ 0 ? Qs : zero(Qs)\n\n                MJQ += MJ * Qs\n                MJQclipped += MJ * Qsclipped\n            end\n\n            ij = i + Nq1 * (j - 1)\n\n            s_MJQ[ij, sf] = MJQ\n            s_MJQclipped[ij, sf] = MJQclipped\n        end\n        @synchronize\n\n        @unroll for n in 11:-1:1\n            if nreduce ≥ 2^n\n                ij = i + Nq1 * (j - 1)\n                ijshift = ij + 2^(n - 1)\n                if ij ≤ 2^(n - 1) && ijshift ≤ Nq1 * Nq2\n                    @unroll for sf in 1:nfilterstates\n                        s_MJQ[ij, sf] += s_MJQ[ijshift, sf]\n                        s_MJQclipped[ij, sf] += s_MJQclipped[ijshift, sf]\n                    end\n                end\n                @synchronize\n            end\n        end\n\n        @unroll for sf in 1:nfilterstates\n            qs_average = s_MJQ[1, sf]\n            qs_clipped_average = s_MJQclipped[1, sf]\n\n            r = qs_average > 0 ? qs_average / qs_clipped_average : zero(FT)\n\n            s = I[sf]\n            @unroll for k in 1:Nq3\n                ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n                Qs = l_Q[sf, k]\n                Q[ijk, s, e] = Qs ≥ 0 ? r * Qs : zero(Qs)\n            end\n        end\n    end\nend\n\n\n\"\"\"\n    kernel_apply_mp_filter!(::Val{dim}, ::Val{N}, direction,\n                         Q, state_auxiliary, target, filtermatrix\n                        ) where {dim, N}\nComputational kernel: Applies the `filtermatrix` to `Q` given a\ncustom target `target`.\nThe `direction` argument is used to control if the filter is applied in the\n\"\"\"\n@kernel function kernel_apply_mp_filter!(\n    ::Val{nreduce},\n    ::Val{dim},\n    ::Val{N},\n    ::Val{vars_Q},\n    ::Val{vars_state_auxiliary},\n    direction,\n    Q,\n    state_auxiliary,\n    target::AbstractFilterTarget,\n    filtermatrix,\n    vgeo,\n) where {nreduce, dim, N, vars_Q, vars_state_auxiliary}\n    @uniform begin\n        FT = eltype(Q)\n\n        Nqs = N .+ 1\n        Nq1 = Nqs[1]\n        Nq2 = Nqs[2]\n        Nq3 = dim == 2 ? 1 : Nqs[dim]\n\n        if direction isa EveryDirection\n            filterinξ1 = filterinξ2 = true\n            filterinξ3 = dim == 2 ? false : true\n        elseif direction isa HorizontalDirection\n            filterinξ1 = true\n            filterinξ2 = dim == 2 ? false : true\n            filterinξ3 = false\n        elseif direction isa VerticalDirection\n            filterinξ1 = false\n            filterinξ2 = dim == 2 ? true : false\n            filterinξ3 = dim == 2 ? false : true\n        end\n\n        nstates = varsize(vars_Q)\n        nfilterstates = number_state_filtered(target, FT)\n        nfilteraux =\n            isnothing(state_auxiliary) ? 0 : varsize(vars_state_auxiliary)\n\n        # ugly workaround around problems with @private\n        # hopefully will be soon fixed in KA\n        u_Q = MVector{nstates, FT}(undef)\n        u_Qfiltered = MVector{nfilterstates, FT}(undef)\n    end\n\n    l_Q = @localmem FT (Nq1, Nq2, Nq3, nfilterstates) # element local \n    l_MQᴮ = @localmem FT (Nq1 * Nq2 * Nq3, nstates) # before applying filter\n    l_MQᴬ = @localmem FT (Nq1 * Nq2 * Nq3, nstates) # after applying filter\n    l_M = @localmem FT (Nq1 * Nq2 * Nq3) # local mass matrix\n\n    p_Q = @private FT (nstates,)\n    p_Qfiltered = @private FT (nfilterstates,) # scratch space for storing mat mul\n    p_aux = @private FT (nfilteraux,)\n\n    e = @index(Group, Linear)\n    i, j, k = @index(Local, NTuple)\n    ijk = @index(Local, Linear)\n\n    @inbounds begin\n\n        @unroll for s in 1:nstates\n            p_Q[s] = Q[ijk, s, e]\n        end\n\n        @unroll for s in 1:nfilteraux\n            p_aux[s] = state_auxiliary[ijk, s, e]\n        end\n\n        # Load mass matrix and pre-filtered mass weighted quantities to shared memory\n        l_M[ijk] = vgeo[ijk, _M, e]\n        @unroll for s in 1:nstates\n            l_MQᴮ[ijk, s] = l_M[ijk] * p_Q[s]\n        end\n\n        fill!(u_Qfiltered, -zero(FT))\n\n        compute_filter_argument!(\n            target,\n            Vars{vars_state_filtered(target, FT)}(u_Qfiltered),\n            Vars{vars_Q}(p_Q[:]),\n            Vars{vars_state_auxiliary}(p_aux[:]),\n        )\n\n        @unroll for fs in 1:nfilterstates\n            p_Qfiltered[fs] = zero(FT)\n        end\n\n        @unroll for fs in 1:nfilterstates\n            l_Q[i, j, k, fs] = u_Qfiltered[fs]\n        end\n\n        if filterinξ1\n            @synchronize\n            @unroll for n in 1:Nq1\n                @unroll for fs in 1:nfilterstates\n                    p_Qfiltered[fs] += filtermatrix[i, n] * l_Q[n, j, k, fs]\n                end\n            end\n\n            if filterinξ2 || filterinξ3\n                @synchronize\n                @unroll for fs in 1:nfilterstates\n                    l_Q[i, j, k, fs] = p_Qfiltered[fs]\n                    p_Qfiltered[fs] = zero(FT)\n                end\n            end\n        end\n\n        if filterinξ2\n            @synchronize\n            @unroll for n in 1:Nq2\n                @unroll for fs in 1:nfilterstates\n                    p_Qfiltered[fs] += filtermatrix[j, n] * l_Q[i, n, k, fs]\n                end\n            end\n\n            if filterinξ3\n                @synchronize\n                @unroll for fs in 1:nfilterstates\n                    l_Q[i, j, k, fs] = p_Qfiltered[fs]\n                    p_Qfiltered[fs] = zero(FT)\n                end\n            end\n        end\n\n        if filterinξ3\n            @synchronize\n            @unroll for n in 1:Nq3\n                @unroll for fs in 1:nfilterstates\n                    p_Qfiltered[fs] += filtermatrix[k, n] * l_Q[i, j, n, fs]\n                end\n            end\n        end\n\n        # work around for not being able to `Vars` `@private` arrays\n        @unroll for s in 1:nstates\n            u_Q[s] = p_Q[s]\n        end\n\n        compute_filter_result!(\n            target,\n            Vars{vars_Q}(u_Q),\n            Vars{vars_state_filtered(target, FT)}(p_Qfiltered[:]),\n            Vars{vars_state_auxiliary}(p_aux[:]),\n        )\n        # Store result and post-filtered mass weighted quantities\n        @unroll for s in 1:nstates\n            p_Q[s] = u_Q[s]\n            l_MQᴬ[ijk, s] = l_M[ijk] * p_Q[s]\n        end\n\n        @synchronize\n        @unroll for n in 11:-1:1\n            if nreduce ≥ (1 << n)\n                ijkshift = ijk + (1 << (n - 1))\n                if ijk ≤ (1 << (n - 1)) && ijkshift ≤ Nq1 * Nq2 * Nq3\n                    l_M[ijk] += l_M[ijkshift]\n                    @unroll for s in 1:nstates\n                        l_MQᴮ[ijk, s] += l_MQᴮ[ijkshift, s]\n                        l_MQᴬ[ijk, s] += l_MQᴬ[ijkshift, s]\n                    end\n                end\n                @synchronize\n            end\n        end\n\n        @synchronize\n        M⁻¹ = 1 / l_M[1]\n        # Reset the element average and store result\n        @unroll for s in 1:nstates\n            Q[ijk, s, e] = p_Q[s] + M⁻¹ * (l_MQᴮ[1, s] - l_MQᴬ[1, s])\n        end\n\n        @synchronize\n    end\n\nend\n\nend # end of module\n"
  },
  {
    "path": "src/Numerics/Mesh/GeometricFactors.jl",
    "content": "module GeometricFactors\n\nexport VolumeGeometry, SurfaceGeometry\n\n\"\"\"\n    VolumeGeometry{Nq, AA <: AbstractArray, A <: AbstractArray}\n\nA struct that collects `VolumeGeometry` fields:\n- array: Array contatining the data stored in a VolumeGeometry struct (the following fields are views into this array)\n- ∂ξk/∂xi: Derivative of the Cartesian reference element coordinate `ξ_k` with respect to the Cartesian physical coordinate `x_i`\n- ωJ: Mass matrix. This is the physical mass matrix, and thus contains the Jacobian determinant, J .* (ωᵢ ⊗ ωⱼ ⊗ ωₖ), where ωᵢ are the quadrature weights and J is the Jacobian determinant, det(∂x/∂ξ)\n- ωJI: Inverse mass matrix: 1 ./ ωJ\n- ωJH: Horizontal mass matrix (used in diagnostics),  J .* norm(∂ξ3/∂x) * (ωᵢ ⊗ ωⱼ); for integrating over a plane (in 2-D ξ2 is used, not ξ3)\n- xi: Nodal degrees of freedom locations in Cartesian physical space\n- JcV: Metric terms for vertical line integrals norm(∂x/∂ξ3) (in 2-D ξ2 is used, not ξ3)\n- ∂xk/∂ξi: Inverse of matrix `∂ξk/∂xi` that represents the derivative of Cartesian physical coordinate `x_i` with respect to Cartesian reference element coordinate `ξ_k`\n\"\"\"\nstruct VolumeGeometry{Nq, AA <: AbstractArray, A <: AbstractArray}\n    array::AA\n    ξ1x1::A\n    ξ2x1::A\n    ξ3x1::A\n    ξ1x2::A\n    ξ2x2::A\n    ξ3x2::A\n    ξ1x3::A\n    ξ2x3::A\n    ξ3x3::A\n    ωJ::A\n    ωJI::A\n    ωJH::A\n    x1::A\n    x2::A\n    x3::A\n    JcV::A\n    x1ξ1::A\n    x2ξ1::A\n    x3ξ1::A\n    x1ξ2::A\n    x2ξ2::A\n    x3ξ2::A\n    x1ξ3::A\n    x2ξ3::A\n    x3ξ3::A\n    function VolumeGeometry{Nq}(\n        array::AA,\n        args::A...,\n    ) where {Nq, AA, A <: AbstractArray}\n        new{Nq, AA, A}(array, args...)\n    end\nend\n\n\"\"\"\n    VolumeGeometry(FT, Nq::NTuple{N, Int}, nelems::Int)\n\nConstruct an empty `VolumeGeometry` object, in `FT` precision.\n- `Nq` is a tuple containing the number of quadrature points in each direction.\n- `nelem` is the number of elements.\n\"\"\"\nfunction VolumeGeometry(FT, Nq::NTuple{N, Int}, nelem::Int) where {N}\n    # - 1 after fieldcount is to remove the `array` field from the array allocation\n    array = zeros(FT, prod(Nq), fieldcount(VolumeGeometry) - 1, nelem)\n    VolumeGeometry{Nq}(\n        array,\n        ntuple(j -> @view(array[:, j, :]), fieldcount(VolumeGeometry) - 1)...,\n    )\nend\n\n\"\"\"\n    SurfaceGeometry{Nq, AA, A <: AbstractArray}\n\nA struct that collects `VolumeGeometry` fields:\n- array: Array contatining the data stored in a SurfaceGeometry struct (the following fields are views into this array)\n- ni: Outward pointing unit normal in physical space\n- sωJ: Surface mass matrix. This is the physical mass matrix, and thus contains the surface Jacobian determinant, sJ .* (ωⱼ ⊗ ωₖ), where ωᵢ are the quadrature weights and sJ is the surface Jacobian determinant\n- vωJI: Volume mass matrix at the surface nodes (needed in the lift operation, i.e., the projection of a face field back to the volume). Since DGSEM is used only collocated, volume mass matrices are required.\n\"\"\"\nstruct SurfaceGeometry{Nq, AA, A <: AbstractArray}\n    array::AA\n    n1::A\n    n2::A\n    n3::A\n    sωJ::A\n    vωJI::A\n    function SurfaceGeometry{Nq}(\n        array::AA,\n        args::A...,\n    ) where {Nq, AA, A <: AbstractArray}\n        new{Nq, AA, A}(array, args...)\n    end\nend\n\n\"\"\"\n    SurfaceGeometry(FT, Nq::NTuple{N, Int}, nface::Int, nelem::Int)\n\nConstruct an empty `SurfaceGeometry` object, in `FT` precision.\n- `Nq` is a tuple containing the number of quadrature points in each direction.\n- `nface` is the number of faces.\n- `nelem` is the number of elements.\n\"\"\"\nfunction SurfaceGeometry(\n    FT,\n    Nq::NTuple{N, Int},\n    nface::Int,\n    nelem::Int,\n) where {N}\n    Np = prod(Nq)\n    Nfp = div.(Np, Nq)\n    # - 1 after fieldcount is to remove the `array` field from the array allocation\n    array =\n        zeros(FT, fieldcount(SurfaceGeometry) - 1, maximum(Nfp), nface, nelem)\n    SurfaceGeometry{Nfp}(\n        array,\n        ntuple(\n            j -> @view(array[j, :, :, :]),\n            fieldcount(SurfaceGeometry) - 1,\n        )...,\n    )\nend\n\nend # module\n"
  },
  {
    "path": "src/Numerics/Mesh/Geometry.jl",
    "content": "module Geometry\n\nusing StaticArrays, LinearAlgebra, DocStringExtensions\nusing KernelAbstractions.Extras: @unroll\nusing ..Grids:\n    _ξ1x1,\n    _ξ2x1,\n    _ξ3x1,\n    _ξ1x2,\n    _ξ2x2,\n    _ξ3x2,\n    _ξ1x3,\n    _ξ2x3,\n    _ξ3x3,\n    _M,\n    _MI,\n    _x1,\n    _x2,\n    _x3,\n    _JcV\n\nexport LocalGeometry, lengthscale, resolutionmetric, lengthscale_horizontal\n\n\"\"\"\n    LocalGeometry\n\nThe local geometry at a nodal point.\n\n# Constructors\n\n    LocalGeometry{Np, N}(vgeo::AbstractArray{T}, n::Integer, e::Integer)\n\nExtracts a `LocalGeometry` object from the `vgeo` array at node `n` in element\n`e` with `Np` being the number of points in the element and `N` being the\npolynomial order\n\n# Fields\n\n- `polyorder`\n\n   polynomial order of the element\n\n- `coord`\n\n   local degree of freedom Cartesian coordinate \n\n- `invJ`\n\n   Jacobian from Cartesian to element coordinates: `invJ[i,j]` is ``∂ξ_i / ∂x_j``\n\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct LocalGeometry{Np, N, AT, IT}\n    \"Global volume geometry array\"\n    vgeo::AT\n    \"element local linear node index\"\n    n::IT\n    \"process local element index\"\n    e::IT\n\n    LocalGeometry{Np, N}(vgeo::AT, n::IT, e::IT) where {Np, N, AT, IT} =\n        new{Np, N, AT, IT}(vgeo, n, e)\nend\n\n@inline function Base.getproperty(\n    geo::LocalGeometry{Np, N},\n    sym::Symbol,\n) where {Np, N}\n    @inbounds if sym === :polyorder\n        return N\n    elseif sym === :coord\n        vgeo, n, e = getfield(geo, :vgeo), getfield(geo, :n), getfield(geo, :e)\n        FT = eltype(vgeo)\n        return @SVector FT[vgeo[n, _x1, e], vgeo[n, _x2, e], vgeo[n, _x3, e]]\n    elseif sym === :invJ\n        vgeo, n, e = getfield(geo, :vgeo), getfield(geo, :n), getfield(geo, :e)\n        FT = eltype(vgeo)\n        return @SMatrix FT[\n            vgeo[n, _ξ1x1, e] vgeo[n, _ξ1x2, e] vgeo[n, _ξ1x3, e]\n            vgeo[n, _ξ2x1, e] vgeo[n, _ξ2x2, e] vgeo[n, _ξ2x3, e]\n            vgeo[n, _ξ3x1, e] vgeo[n, _ξ3x2, e] vgeo[n, _ξ3x3, e]\n        ]\n    elseif sym === :center_coord\n        vgeo, n, e = getfield(geo, :vgeo), getfield(geo, :n), getfield(geo, :e)\n        FT = eltype(vgeo)\n        coords = SVector(vgeo[n, _x1, e], vgeo[n, _x2, e], vgeo[n, _x3, e])\n        V = FT(0)\n        xc = FT(0)\n        yc = FT(0)\n        zc = FT(0)\n        @unroll for i in 1:Np\n            M = vgeo[i, _M, e]\n            V += M\n            xc += M * vgeo[i, _x1, e]\n            yc += M * vgeo[i, _x2, e]\n            zc += M * vgeo[i, _x3, e]\n        end\n        return SVector(xc / V, yc / V, zc / V)\n    else\n        return getfield(geo, sym)\n    end\nend\n\n\"\"\"\n    resolutionmetric(g::LocalGeometry)\n\nThe metric tensor of the discretisation resolution. Given a unit vector `u` in\nCartesian coordinates and `M = resolutionmetric(g)`, `sqrt(u'*M*u)` is the\ndegree-of-freedom density in the direction of `u`.\n\"\"\"\nfunction resolutionmetric(g::LocalGeometry)\n    S = g.polyorder * g.invJ / 2\n    S' * S # TODO: return an eigendecomposition / symmetric object?\nend\n\n\"\"\"\n    lengthscale(g::LocalGeometry)\n\nThe effective geometric mean grid resolution at the point.\n\"\"\"\nlengthscale(g::LocalGeometry) =\n    2 / (cbrt(det(g.invJ)) * maximum(max.(1, g.polyorder)))\n\n\"\"\"\n    lengthscale_horizontal(g::LocalGeometry)\n\nThe effective horizontal grid resolution at the point.\n\"\"\"\nfunction lengthscale_horizontal(g::LocalGeometry)\n    # inverse Jacobian matrix:\n    #\n    #    invJ[i,j] =  ∂ξ_i / ∂x_j\n    invJ = g.invJ\n\n    # The local horizontal grid stretchings are:\n    #\n    #    horizontal direction 1: √( ∑_j ∂x_1 / ∂ξ_j) * 2\n    #    horizontal direction 2: √( ∑_j ∂x_2 / ∂ξ_j) * 2\n    #\n    # To get these we need to have the Jacobian matrix:\n    #\n    #    J[i,j] =  ∂x_i / ∂ξ_j\n    #\n    # To get this we can solve the system with the unit basis vectors\n    # (division by `N` gives the local average node distance)\n    Δ1 = norm(invJ \\ SVector(1, 0, 0)) * 2 / g.polyorder[1]\n    Δ2 = norm(invJ \\ SVector(0, 1, 0)) * 2 / g.polyorder[2]\n\n    # Set the horizontal length scale to the average of the two calculated values\n    return (Δ1 + Δ2) / 2\nend\n\nend # module\n"
  },
  {
    "path": "src/Numerics/Mesh/Grids.jl",
    "content": "module Grids\nusing ..Topologies, ..GeometricFactors\nimport ..Metrics, ..Elements\nimport ..BrickMesh\nusing ClimateMachine.MPIStateArrays\n\nusing MPI\nusing LinearAlgebra\nusing KernelAbstractions\nusing DocStringExtensions\n\nexport DiscontinuousSpectralElementGrid, AbstractGrid\nexport dofs_per_element, arraytype, dimensionality, polynomialorders\nexport referencepoints, min_node_distance, get_z, computegeometry\nexport EveryDirection, HorizontalDirection, VerticalDirection, Direction\n\nabstract type Direction end\nstruct EveryDirection <: Direction end\nstruct HorizontalDirection <: Direction end\nstruct VerticalDirection <: Direction end\nBase.in(::T, ::S) where {T <: Direction, S <: Direction} = T == S\n\n\"\"\"\n    MinNodalDistance{FT}\n\nA struct containing the minimum nodal distance\nalong horizontal and vertical directions.\n\"\"\"\nstruct MinNodalDistance{FT}\n    \"horizontal\"\n    h::FT\n    \"vertical\"\n    v::FT\nend\n\nabstract type AbstractGrid{\n    FloatType,\n    dim,\n    polynomialorder,\n    numberofDOFs,\n    DeviceArray,\n} end\n\ndofs_per_element(::AbstractGrid{T, D, N, Np}) where {T, D, N, Np} = Np\n\npolynomialorders(::AbstractGrid{T, dim, N}) where {T, dim, N} = N\n\ndimensionality(::AbstractGrid{T, dim}) where {T, dim} = dim\n\nBase.eltype(::AbstractGrid{T}) where {T} = T\n\narraytype(::AbstractGrid{T, D, N, Np, DA}) where {T, D, N, Np, DA} = DA\n\n\"\"\"\n    referencepoints(::AbstractGrid)\n\nReturns the points on the reference element.\n\"\"\"\nreferencepoints(::AbstractGrid) = error(\"needs to be implemented\")\n\n\"\"\"\n    min_node_distance(::AbstractGrid, direction::Direction=EveryDirection() )\n\nReturns an approximation of the minimum node distance in physical space.\n\"\"\"\nfunction min_node_distance(\n    ::AbstractGrid,\n    direction::Direction = EveryDirection(),\n)\n    error(\"needs to be implemented\")\nend\n\n# {{{\n# `vgeo` stores geometry and metrics terms at the volume quadrature /\n# interpolation points\nconst _nvgeo = 16\nconst _ξ1x1,\n_ξ2x1,\n_ξ3x1,\n_ξ1x2,\n_ξ2x2,\n_ξ3x2,\n_ξ1x3,\n_ξ2x3,\n_ξ3x3,\n_M,\n_MI,\n_MH,\n_x1,\n_x2,\n_x3,\n_JcV = 1:_nvgeo\nconst vgeoid = (\n    # ∂ξk/∂xi: derivative of the Cartesian reference element coordinate `ξ_k`\n    # with respect to the Cartesian physical coordinate `x_i`\n    ξ1x1id = _ξ1x1,\n    ξ2x1id = _ξ2x1,\n    ξ3x1id = _ξ3x1,\n    ξ1x2id = _ξ1x2,\n    ξ2x2id = _ξ2x2,\n    ξ3x2id = _ξ3x2,\n    ξ1x3id = _ξ1x3,\n    ξ2x3id = _ξ2x3,\n    ξ3x3id = _ξ3x3,\n    # M refers to the mass matrix. This is the physical mass matrix, and thus\n    # contains the Jacobian determinant:\n    #    J .* (ωᵢ ⊗ ωⱼ ⊗ ωₖ)\n    # where ωᵢ are the quadrature weights and J is the Jacobian determinant\n    # det(∂x/∂ξ)\n    Mid = _M,\n    # Inverse mass matrix: 1 ./ M\n    MIid = _MI,\n    # Horizontal mass matrix (used in diagnostics)\n    #    J .* norm(∂ξ3/∂x) * (ωᵢ ⊗ ωⱼ); for integrating over a plane\n    # (in 2-D ξ2 not ξ3 is used)\n    MHid = _MH,\n    # Nodal degrees of freedom locations in Cartesian physical space\n    x1id = _x1,\n    x2id = _x2,\n    x3id = _x3,\n    # Metric terms for vertical line integrals\n    #   norm(∂x/∂ξ3)\n    # (in 2-D ξ2 not ξ3 is used)\n    JcVid = _JcV,\n)\n\n# `sgeo` stores geometry and metrics terms at the surface quadrature /\n# interpolation points\nconst _nsgeo = 5\nconst _n1, _n2, _n3, _sM, _vMI = 1:_nsgeo\nconst sgeoid = (\n    # outward pointing unit normal in physical space\n    n1id = _n1,\n    n2id = _n2,\n    n3id = _n3,\n    # sM refers to the surface mass matrix. This is the physical mass matrix,\n    # and thus contains the surface Jacobian determinant:\n    #    sJ .* (ωⱼ ⊗ ωₖ)\n    # where ωᵢ are the quadrature weights and sJ is the surface Jacobian\n    # determinant\n    sMid = _sM,\n    # Volume mass matrix at the surface nodes (needed in the lift operation,\n    # i.e., the projection of a face field back to the volume). Since DGSEM is\n    # used only collocated, volume mass matrices are required.\n    vMIid = _vMI,\n)\n# }}}\n\n\"\"\"\n    DiscontinuousSpectralElementGrid(topology; FloatType, DeviceArray,\n                                     polynomialorder,\n                                     meshwarp = (x...)->identity(x))\n\nGenerate a discontinuous spectral element (tensor product,\nLegendre-Gauss-Lobatto) grid/mesh from a `topology`, where the order of the\nelements is given by `polynomialorder`. `DeviceArray` gives the array type used\nto store the data (`CuArray` or `Array`), and the coordinate points will be of\n`FloatType`.\n\nThe polynomial order can be different in each direction (specified as a\n`NTuple`). If only a single integer is specified, then each dimension will use\nthe same order. If the topology dimension is 3 and the `polynomialorder` has\ndimension 2, then the first value will be used for horizontal and the second for\nthe vertical.\n\nThe optional `meshwarp` function allows the coordinate points to be warped after\nthe mesh is created; the mesh degrees of freedom are orginally assigned using a\ntrilinear blend of the element corner locations.\n\"\"\"\nstruct DiscontinuousSpectralElementGrid{\n    T,\n    dim,\n    N,\n    Np,\n    DA,\n    DAT1,\n    DAT2,\n    DAT3,\n    DAT4,\n    DAI1,\n    DAI2,\n    DAI3,\n    TOP,\n    TVTK,\n    MINΔ,\n} <: AbstractGrid{T, dim, N, Np, DA}\n    \"mesh topology\"\n    topology::TOP\n\n    \"volume metric terms\"\n    vgeo::DAT3\n\n    \"surface metric terms\"\n    sgeo::DAT4\n\n    \"element to boundary condition map\"\n    elemtobndy::DAI2\n\n    \"volume DOF to element minus side map\"\n    vmap⁻::DAI3\n\n    \"volume DOF to element plus side map\"\n    vmap⁺::DAI3\n\n    \"list of DOFs that need to be received (in neighbors order)\"\n    vmaprecv::DAI1\n\n    \"list of DOFs that need to be sent (in neighbors order)\"\n    vmapsend::DAI1\n\n    \"An array of ranges in `vmaprecv` to receive from each neighbor\"\n    nabrtovmaprecv::Any\n\n    \"An array of ranges in `vmapsend` to send to each neighbor\"\n    nabrtovmapsend::Any\n\n    \"Array of real elements that do not have a ghost element as a neighbor\"\n    interiorelems::Any\n\n    \"Array of real elements that have at least one ghost element as a neighbor\"\n    exteriorelems::Any\n\n    \"Array indicating if a degree of freedom (real or ghost) is active\"\n    activedofs::Any\n\n    \"1-D LGL weights on the device (one for each dimension)\"\n    ω::DAT1\n\n    \"1-D derivative operator on the device (one for each dimension)\"\n    D::DAT2\n\n    \"1-D indefinite integral operator on the device (one for each dimension)\"\n    Imat::DAT2\n\n    \"\"\"\n    tuple of (x1, x2, x3) to use for vtk output (Needed for the `N = 0` case) in\n    other cases these match `vgeo` values\n    \"\"\"\n    x_vtk::TVTK\n\n    \"\"\"\n    Minimum nodal distances for horizontal and vertical directions\n    \"\"\"\n    minΔ::MINΔ\n\n    \"\"\"\n    Map to vertex degrees of freedom: `vertmap[v]` contains the degree of freedom located at vertex `v`.\n    \"\"\"\n    vertmap::Union{DAI1, Nothing}\n\n    \"\"\"\n    Map to edge degrees of freedom: `edgemap[i, edgno, orient]` contains the element node index of \n    the `i`th interior node on edge `edgno`, under orientation `orient`.\n    \"\"\"\n    edgemap::Union{DAI3, Nothing}\n\n    \"\"\"\n    Map to face degrees of freedom: `facemap[ij, fcno, orient]` contains the element node index of the `ij`th \n    interior node on face `fcno` under orientation `orient`.\n\n    Note that only the two orientations that are generated for stacked meshes are currently supported, i.e.,\n    mesh orientation `3` as defined by `BrickMesh` gets mapped to orientation `2` for this data structure.    \n    \"\"\"\n    facemap::Union{DAI3, Nothing}\n\n    # Constructor for a tuple of polynomial orders\n    function DiscontinuousSpectralElementGrid(\n        topology::AbstractTopology{dim};\n        polynomialorder,\n        FloatType,\n        DeviceArray,\n        meshwarp::Function = (x...) -> identity(x),\n    ) where {dim}\n\n        if polynomialorder isa Integer\n            polynomialorder = ntuple(j -> polynomialorder, dim)\n        elseif polynomialorder isa NTuple{2} && dim == 3\n            polynomialorder =\n                (polynomialorder[1], polynomialorder[1], polynomialorder[2])\n        end\n\n        @assert dim == length(polynomialorder)\n\n        N = polynomialorder\n\n        (vmap⁻, vmap⁺) = mappings(\n            N,\n            topology.elemtoelem,\n            topology.elemtoface,\n            topology.elemtoordr,\n        )\n\n        (vmaprecv, nabrtovmaprecv) = commmapping(\n            N,\n            topology.ghostelems,\n            topology.ghostfaces,\n            topology.nabrtorecv,\n        )\n        (vmapsend, nabrtovmapsend) = commmapping(\n            N,\n            topology.sendelems,\n            topology.sendfaces,\n            topology.nabrtosend,\n        )\n\n        Np = prod(N .+ 1)\n\n        vertmap, edgemap, facemap = init_vertex_edge_face_mappings(N)\n        # Create element operators for each polynomial order\n        ξω = ntuple(\n            j ->\n                N[j] == 0 ? Elements.glpoints(FloatType, N[j]) :\n                Elements.lglpoints(FloatType, N[j]),\n            dim,\n        )\n        ξ, ω = ntuple(j -> map(x -> x[j], ξω), 2)\n\n        Imat = ntuple(\n            j -> indefinite_integral_interpolation_matrix(ξ[j], ω[j]),\n            dim,\n        )\n        D = ntuple(j -> Elements.spectralderivative(ξ[j]), dim)\n\n        (vgeo, sgeo, x_vtk) =\n            computegeometry(topology.elemtocoord, D, ξ, ω, meshwarp)\n\n        vgeo = vgeo.array\n        sgeo = sgeo.array\n        @assert Np == size(vgeo, 1)\n\n        activedofs = zeros(Bool, Np * length(topology.elems))\n        activedofs[1:(Np * length(topology.realelems))] .= true\n        activedofs[vmaprecv] .= true\n\n        # Create arrays on the device\n        vgeo = DeviceArray(vgeo)\n        sgeo = DeviceArray(sgeo)\n        elemtobndy = DeviceArray(topology.elemtobndy)\n        vmap⁻ = DeviceArray(vmap⁻)\n        vmap⁺ = DeviceArray(vmap⁺)\n        vmapsend = DeviceArray(vmapsend)\n        vmaprecv = DeviceArray(vmaprecv)\n        activedofs = DeviceArray(activedofs)\n        ω = DeviceArray.(ω)\n        D = DeviceArray.(D)\n        Imat = DeviceArray.(Imat)\n\n        # FIXME: There has got to be a better way!\n        DAT1 = typeof(ω)\n        DAT2 = typeof(D)\n        DAT3 = typeof(vgeo)\n        DAT4 = typeof(sgeo)\n        DAI1 = typeof(vmapsend)\n        DAI2 = typeof(elemtobndy)\n        DAI3 = typeof(vmap⁻)\n        TOP = typeof(topology)\n        TVTK = typeof(x_vtk)\n        if vertmap isa Array\n            vertmap = DAI1(vertmap)\n        end\n        if edgemap isa Array\n            edgemap = DAI3(edgemap)\n        end\n        if facemap isa Array\n            facemap = DAI3(facemap)\n        end\n        FT = FloatType\n        minΔ = MinNodalDistance(\n            min_node_distance(vgeo, topology, N, FT, HorizontalDirection()),\n            min_node_distance(vgeo, topology, N, FT, VerticalDirection()),\n        )\n        MINΔ = typeof(minΔ)\n\n        new{\n            FloatType,\n            dim,\n            N,\n            Np,\n            DeviceArray,\n            DAT1,\n            DAT2,\n            DAT3,\n            DAT4,\n            DAI1,\n            DAI2,\n            DAI3,\n            TOP,\n            TVTK,\n            MINΔ,\n        }(\n            topology,\n            vgeo,\n            sgeo,\n            elemtobndy,\n            vmap⁻,\n            vmap⁺,\n            vmaprecv,\n            vmapsend,\n            nabrtovmaprecv,\n            nabrtovmapsend,\n            DeviceArray(topology.interiorelems),\n            DeviceArray(topology.exteriorelems),\n            activedofs,\n            ω,\n            D,\n            Imat,\n            x_vtk,\n            minΔ,\n            vertmap,\n            edgemap,\n            facemap,\n        )\n    end\nend\n\n\"\"\"\n    referencepoints(::DiscontinuousSpectralElementGrid)\n\nReturns the 1D interpolation points used for the reference element.\n\"\"\"\nfunction referencepoints(\n    ::DiscontinuousSpectralElementGrid{FT, dim, N},\n) where {FT, dim, N}\n    ξω = ntuple(\n        j ->\n            N[j] == 0 ? Elements.glpoints(FT, N[j]) :\n            Elements.lglpoints(FT, N[j]),\n        dim,\n    )\n    ξ, _ = ntuple(j -> map(x -> x[j], ξω), 2)\n    return ξ\nend\n\n\"\"\"\n    min_node_distance(\n        ::DiscontinuousSpectralElementGrid,\n        direction::Direction=EveryDirection())\n    )\n\nReturns an approximation of the minimum node distance in physical space along\nthe reference coordinate directions.  The direction controls which reference\ndirections are considered.\n\"\"\"\nmin_node_distance(\n    grid::DiscontinuousSpectralElementGrid,\n    direction::Direction = EveryDirection(),\n) = min_node_distance(grid.minΔ, direction)\n\nmin_node_distance(minΔ::MinNodalDistance, ::VerticalDirection) = minΔ.v\nmin_node_distance(minΔ::MinNodalDistance, ::HorizontalDirection) = minΔ.h\nmin_node_distance(minΔ::MinNodalDistance, ::EveryDirection) =\n    min(minΔ.h, minΔ.v)\n\nfunction min_node_distance(\n    vgeo,\n    topology::AbstractTopology{dim},\n    N,\n    ::Type{T},\n    direction::Direction = EveryDirection(),\n) where {T, dim}\n    topology = topology\n    nrealelem = length(topology.realelems)\n    if nrealelem > 0\n        Nq = N .+ 1\n        Np = prod(Nq)\n        device = vgeo isa Array ? CPU() : CUDADevice()\n        min_neighbor_distance = similar(vgeo, Np, nrealelem)\n        event = Event(device)\n        event = kernel_min_neighbor_distance!(device, min(Np, 1024))(\n            Val(N),\n            Val(dim),\n            direction,\n            min_neighbor_distance,\n            vgeo,\n            topology.realelems;\n            ndrange = (Np * nrealelem),\n            dependencies = (event,),\n        )\n        wait(device, event)\n        locmin = minimum(min_neighbor_distance)\n    else\n        locmin = typemax(T)\n    end\n\n    MPI.Allreduce(locmin, min, topology.mpicomm)\nend\n\n\"\"\"\n    get_z(grid; z_scale = 1, rm_dupes = false)\n\nGet the Gauss-Lobatto points along the Z-coordinate.\n\n - `grid`: DG grid\n - `z_scale`: multiplies `z-coordinate`\n - `rm_dupes`: removes duplicate Gauss-Lobatto points\n\"\"\"\nfunction get_z(\n    grid::DiscontinuousSpectralElementGrid{T, dim, N};\n    z_scale = 1,\n    rm_dupes = false,\n) where {T, dim, N}\n    # Assumes same polynomial orders in all horizontal directions\n    @assert dim < 3 || N[1] == N[2]\n    Nhoriz = N[1]\n    Nvert = N[end]\n    Nph = (Nhoriz + 1)^2\n    Np = Nph * (Nvert + 1)\n    if last(polynomialorders(grid)) == 0\n        rm_dupes = false # no duplicates in FVM\n    end\n    if rm_dupes\n        ijk_range = (1:Nph:(Np - Nph))\n        vgeo = Array(grid.vgeo)\n        z = reshape(vgeo[ijk_range, _x3, :], :)\n        z = [z..., vgeo[Np, _x3, end]]\n        return z * z_scale\n    else\n        ijk_range = (1:Nph:Np)\n        z = Array(reshape(grid.vgeo[ijk_range, _x3, :], :))\n        return z * z_scale\n    end\n    return reshape(grid.vgeo[(1:Nph:Np), _x3, :], :) * z_scale\nend\n\nfunction Base.getproperty(G::DiscontinuousSpectralElementGrid, s::Symbol)\n    if s ∈ keys(vgeoid)\n        vgeoid[s]\n    elseif s ∈ keys(sgeoid)\n        sgeoid[s]\n    else\n        getfield(G, s)\n    end\nend\n\nfunction Base.propertynames(G::DiscontinuousSpectralElementGrid)\n    (\n        fieldnames(DiscontinuousSpectralElementGrid)...,\n        keys(vgeoid)...,\n        keys(sgeoid)...,\n    )\nend\n\n# {{{ mappings\n\"\"\"\n    mappings(N, elemtoelem, elemtoface, elemtoordr)\n\nThis function takes in a tuple of polynomial orders `N` and parts of a topology\n(as returned from `connectmesh`) and returns index mappings for the element\nsurface flux computation. The returned `Tuple` contains:\n\n - `vmap⁻` an array of linear indices into the volume degrees of freedom where\n   `vmap⁻[:,f,e]` are the degrees of freedom indices for face `f` of element\n    `e`.\n\n - `vmap⁺` an array of linear indices into the volume degrees of freedom where\n   `vmap⁺[:,f,e]` are the degrees of freedom indices for the face neighboring\n   face `f` of element `e`.\n\"\"\"\nfunction mappings(N, elemtoelem, elemtoface, elemtoordr)\n    nfaces, nelem = size(elemtoelem)\n\n    d = div(nfaces, 2)\n    Nq = N .+ 1\n    # number of points in the element\n    Np = prod(Nq)\n\n    # Compute the maximum number of points on a face\n    Nfp = div.(Np, Nq)\n\n    # linear index for each direction, e.g., (i, j, k) -> n\n    p = reshape(1:Np, ntuple(j -> Nq[j], d))\n\n    # fmask[f] -> returns an array of all degrees of freedom on face f\n    fmask = if d == 1\n        (\n            p[1:1],    # Face 1\n            p[Nq[1]:Nq[1]], # Face 2\n        )\n    elseif d == 2\n        (\n            p[1, :][:],     # Face 1\n            p[Nq[1], :][:], # Face 2\n            p[:, 1][:],     # Face 3\n            p[:, Nq[2]][:], # Face 4\n        )\n    elseif d == 3\n        (\n            p[1, :, :][:],     # Face 1\n            p[Nq[1], :, :][:], # Face 2\n            p[:, 1, :][:],     # Face 3\n            p[:, Nq[2], :][:], # Face 4\n            p[:, :, 1][:],     # Face 5\n            p[:, :, Nq[3]][:], # Face 6\n        )\n    else\n        error(\"unknown dimensionality\")\n    end\n\n    # Create a map from Cartesian face dof number to linear face dof numbering\n    # inds[face][i, j] -> n\n    inds = ntuple(\n        f -> dropdims(\n            LinearIndices(ntuple(j -> j == cld(f, 2) ? 1 : Nq[j], d));\n            dims = cld(f, 2),\n        ),\n        nfaces,\n    )\n\n    # Use the largest possible storage\n    vmap⁻ = fill!(similar(elemtoelem, maximum(Nfp), nfaces, nelem), 0)\n    vmap⁺ = fill!(similar(elemtoelem, maximum(Nfp), nfaces, nelem), 0)\n\n    for e1 in 1:nelem, f1 in 1:nfaces\n        e2 = elemtoelem[f1, e1]\n        f2 = elemtoface[f1, e1]\n        o2 = elemtoordr[f1, e1]\n        d1, d2 = cld(f1, 2), cld(f2, 2)\n\n        # Check to make sure the dof grid is conforming\n        @assert Nfp[d1] == Nfp[d2]\n\n        # Always pull out minus side without any flips / rotations\n        vmap⁻[1:Nfp[d1], f1, e1] .= Np * (e1 - 1) .+ fmask[f1][1:Nfp[d1]][:]\n\n        # Orientation codes defined in BrickMesh.jl (arbitrary numbers in 3D)\n        if o2 == 1 # Neighbor oriented same as minus\n            vmap⁺[1:Nfp[d1], f1, e1] .= Np * (e2 - 1) .+ fmask[f2][1:Nfp[d1]][:]\n        elseif d == 3 && o2 == 3 # Neighbor fliped in first index\n            vmap⁺[1:Nfp[d1], f1, e1] =\n                Np * (e2 - 1) .+ fmask[f2][inds[f2][end:-1:1, :]][:]\n        else\n            error(\"Orientation '$o2' with dim '$d' not supported yet\")\n        end\n    end\n\n    (vmap⁻, vmap⁺)\nend\n# }}}\n\nfunction init_vertex_edge_face_mappings(N)\n    dim = length(N)\n    Np = N .+ 1\n    if dim == 3 && Np[end] > 2\n        nodes = reshape(1:prod(Np), Np)\n        vertmap =\n            Int64.([\n                nodes[1, 1, 1],\n                nodes[Np[1], 1, 1],\n                nodes[1, Np[2], 1],\n                nodes[Np[1], Np[2], 1],\n                nodes[1, 1, Np[3]],\n                nodes[Np[1], 1, Np[3]],\n                nodes[1, Np[2], Np[3]],\n                nodes[Np[1], Np[2], Np[3]],\n            ])\n        Ne = Np .- 2\n        Ne_max = maximum(Ne)\n        if Ne_max ≥ 1\n            edgemap = -ones(Int64, Ne_max, 12, 2)\n\n            if Np[1] > 2\n                edgemap[1:Ne[1], 1, 1] .= nodes[2:(end - 1), 1, 1]\n                edgemap[1:Ne[1], 2, 1] .= nodes[2:(end - 1), Np[2], 1]\n                edgemap[1:Ne[1], 3, 1] .= nodes[2:(end - 1), 1, Np[3]]\n                edgemap[1:Ne[1], 4, 1] .= nodes[2:(end - 1), Np[2], Np[3]]\n\n                edgemap[1:Ne[1], 1, 2] .= nodes[(end - 1):-1:2, 1, 1]\n                edgemap[1:Ne[1], 2, 2] .= nodes[(end - 1):-1:2, Np[2], 1]\n                edgemap[1:Ne[1], 3, 2] .= nodes[(end - 1):-1:2, 1, Np[3]]\n                edgemap[1:Ne[1], 4, 2] .= nodes[(end - 1):-1:2, Np[2], Np[3]]\n            end\n\n            if Np[2] > 2\n                edgemap[1:Ne[2], 5, 1] .= nodes[1, 2:(end - 1), 1]\n                edgemap[1:Ne[2], 6, 1] .= nodes[Np[1], 2:(end - 1), 1]\n                edgemap[1:Ne[2], 7, 1] .= nodes[1, 2:(end - 1), Np[3]]\n                edgemap[1:Ne[2], 8, 1] .= nodes[Np[1], 2:(end - 1), Np[3]]\n\n                edgemap[1:Ne[2], 5, 2] .= nodes[1, (end - 1):-1:2, 1]\n                edgemap[1:Ne[2], 6, 2] .= nodes[Np[1], (end - 1):-1:2, 1]\n                edgemap[1:Ne[2], 7, 2] .= nodes[1, (end - 1):-1:2, Np[3]]\n                edgemap[1:Ne[2], 8, 2] .= nodes[Np[1], (end - 1):-1:2, Np[3]]\n            end\n\n            if Np[3] > 2\n                edgemap[1:Ne[3], 9, 1] .= nodes[1, 1, 2:(end - 1)]\n                edgemap[1:Ne[3], 10, 1] .= nodes[Np[1], 1, 2:(end - 1)]\n                edgemap[1:Ne[3], 11, 1] .= nodes[1, Np[2], 2:(end - 1)]\n                edgemap[1:Ne[3], 12, 1] .= nodes[Np[1], Np[2], 2:(end - 1)]\n\n                edgemap[1:Ne[3], 9, 2] .= nodes[1, 1, (end - 1):-1:2]\n                edgemap[1:Ne[3], 10, 2] .= nodes[Np[1], 1, (end - 1):-1:2]\n                edgemap[1:Ne[3], 11, 2] .= nodes[1, Np[2], (end - 1):-1:2]\n                edgemap[1:Ne[3], 12, 2] .= nodes[Np[1], Np[2], (end - 1):-1:2]\n            end\n        else\n            edgemap = nothing\n        end\n\n        Nf = Np .- 2\n        Nf_max = maximum([Nf[1] * Nf[2], Nf[2] * Nf[3], Nf[1] * Nf[3]])\n        if Nf_max ≥ 1\n            facemap = -ones(Int64, Nf_max, 6, 2)\n\n            if Nf[2] > 0 && Nf[3] > 0\n                nfc = Nf[2] * Nf[3]\n                facemap[1:nfc, 1, 1] .= nodes[1, 2:(end - 1), 2:(end - 1)][:]\n                facemap[1:nfc, 2, 1] .=\n                    nodes[Np[1], 2:(end - 1), 2:(end - 1)][:]\n\n                facemap[1:nfc, 1, 2] .= nodes[1, (end - 1):-1:2, 2:(end - 1)][:]\n                facemap[1:nfc, 2, 2] .=\n                    nodes[Np[1], (end - 1):-1:2, 2:(end - 1)][:]\n            end\n\n            if Nf[1] > 0 && Nf[3] > 0\n                nfc = Nf[1] * Nf[3]\n                facemap[1:nfc, 3, 1] .= nodes[2:(end - 1), 1, 2:(end - 1)][:]\n                facemap[1:nfc, 4, 1] .=\n                    nodes[2:(end - 1), Np[2], 2:(end - 1)][:]\n\n                facemap[1:nfc, 3, 2] .= nodes[(end - 1):-1:2, 1, 2:(end - 1)][:]\n                facemap[1:nfc, 4, 2] .=\n                    nodes[(end - 1):-1:2, Np[2], 2:(end - 1)][:]\n            end\n\n            if Nf[1] > 0 && Nf[2] > 0\n                nfc = Nf[1] * Nf[2]\n                facemap[1:nfc, 5, 1] .= nodes[2:(end - 1), 2:(end - 1), 1][:]\n                facemap[1:nfc, 6, 1] .=\n                    nodes[2:(end - 1), 2:(end - 1), Np[3]][:]\n\n                facemap[1:nfc, 5, 2] .= nodes[(end - 1):-1:2, 2:(end - 1), 1][:]\n                facemap[1:nfc, 6, 2] .=\n                    nodes[(end - 1):-1:2, 2:(end - 1), Np[3]][:]\n            end\n        else\n            facemap = nothing\n        end\n    else\n        vertmap, edgemap, facemap = nothing, nothing, nothing\n    end\n\n    return vertmap, edgemap, facemap\nend\n\n\n\"\"\"\n    commmapping(N, commelems, commfaces, nabrtocomm)\n\nThis function takes in a tuple of polynomial orders `N` and parts of a mesh (as\nreturned from `connectmesh` such as `sendelems`, `sendfaces`, and `nabrtosend`)\nand returns index mappings for the element surface flux parallel communcation.\nThe returned `Tuple` contains:\n\n - `vmapC` an array of linear indices into the volume degrees of freedom to be\n   communicated.\n\n - `nabrtovmapC` a range in `vmapC` to communicate with each neighbor.\n\"\"\"\nfunction commmapping(N, commelems, commfaces, nabrtocomm)\n    nface, nelem = size(commfaces)\n\n    @assert nelem == length(commelems)\n\n    d = div(nface, 2)\n    Nq = N .+ 1\n    Np = prod(Nq)\n\n    vmapC = similar(commelems, nelem * Np)\n    nabrtovmapC = similar(nabrtocomm)\n\n    i = 1\n    e = 1\n    for neighbor in 1:length(nabrtocomm)\n        rbegin = i\n        for ne in nabrtocomm[neighbor]\n            ce = commelems[ne]\n\n            # Whole element sending\n            # for n = 1:Np\n            #   vmapC[i] = (ce-1)*Np + n\n            #   i += 1\n            # end\n\n            CI = CartesianIndices(ntuple(j -> 1:Nq[j], d))\n            for (ci, li) in zip(CI, LinearIndices(CI))\n                addpoint = false\n                for j in 1:d\n                    addpoint |=\n                        (commfaces[2 * (j - 1) + 1, e] && ci[j] == 1) ||\n                        (commfaces[2 * (j - 1) + 2, e] && ci[j] == Nq[j])\n                end\n\n                if addpoint\n                    vmapC[i] = (ce - 1) * Np + li\n                    i += 1\n                end\n            end\n\n            e += 1\n        end\n        rend = i - 1\n\n        nabrtovmapC[neighbor] = rbegin:rend\n    end\n\n    resize!(vmapC, i - 1)\n\n    (vmapC, nabrtovmapC)\nend\n\n# Compute geometry FVM version\nfunction computegeometry_fvm(elemtocoord, D, ξ, ω, meshwarp)\n    FT = eltype(D[1])\n    dim = length(D)\n    nface = 2dim\n    nelem = size(elemtocoord, 3)\n\n    Nq = ntuple(j -> size(D[j], 1), dim)\n    Np = prod(Nq)\n    Nfp = div.(Np, Nq)\n\n    Nq_N1 = max.(Nq, 2)\n    Np_N1 = prod(Nq_N1)\n    Nfp_N1 = div.(Np_N1, Nq_N1)\n\n    # First we compute the geometry with all the N = 0 dimension set to N = 1\n    # so that we can later compute the geometry for the case N = 0, as the\n    # average of two N = 1 cases\n    ξ1, ω1 = Elements.lglpoints(FT, 1)\n    D1 = Elements.spectralderivative(ξ1)\n    D_N1 = ntuple(j -> Nq[j] == 1 ? D1 : D[j], dim)\n    ξ_N1 = ntuple(j -> Nq[j] == 1 ? ξ1 : ξ[j], dim)\n    ω_N1 = ntuple(j -> Nq[j] == 1 ? ω1 : ω[j], dim)\n    (vgeo_N1, sgeo_N1, x_vtk) =\n        computegeometry(elemtocoord, D_N1, ξ_N1, ω_N1, meshwarp)\n\n    # Allocate the storage for N = 0 volume metrics\n    vgeo = VolumeGeometry(FT, Nq, nelem)\n\n    # Counter to make sure we got all the vgeo terms\n    num_vgeo_handled = 0\n\n    Metrics.creategrid!(vgeo, elemtocoord, ξ)\n\n    x1 = vgeo.x1\n    x2 = vgeo.x2\n    x3 = vgeo.x3\n    @inbounds for j in 1:length(vgeo.x1)\n        (x1[j], x2[j], x3[j]) = meshwarp(vgeo.x1[j], vgeo.x2[j], vgeo.x3[j])\n    end\n\n    # Update data in vgeo\n    vgeo.x1 .= x1\n    vgeo.x2 .= x2\n    vgeo.x3 .= x3\n    num_vgeo_handled += 3\n\n    @views begin\n        # ωJ should be a sum\n        ωJ_N1 = reshape(vgeo_N1.ωJ, (Nq_N1..., nelem))\n        vgeo.ωJ[:] .= sum(ωJ_N1, dims = findall(Nq .== 1))[:]\n        num_vgeo_handled += 1\n\n        # need to recompute ωJI\n        vgeo.ωJI .= 1 ./ vgeo.ωJ\n        num_vgeo_handled += 1\n\n        # coordinates should just be averages\n        avg_den = 2^sum(Nq .== 1)\n        JcV_N1 = reshape(vgeo_N1.JcV, (Nq_N1..., nelem))\n        vgeo.JcV[:] .= sum(JcV_N1, dims = findall(Nq .== 1))[:] ./ avg_den\n        num_vgeo_handled += 1\n\n        # For the metrics it is J * ξixk we approximate so multiply and divide the\n        # mass matrix (which has the Jacobian determinant and the proper averaging\n        # due to the quadrature weights)\n        ωJ_N1 = reshape(vgeo_N1.ωJ, (Nq_N1..., nelem))\n        ωJI = vgeo.ωJI\n\n        ξ1x1_N1 = reshape(vgeo_N1.ξ1x1, (Nq_N1..., nelem))\n        ξ2x1_N1 = reshape(vgeo_N1.ξ2x1, (Nq_N1..., nelem))\n        ξ3x1_N1 = reshape(vgeo_N1.ξ3x1, (Nq_N1..., nelem))\n        ξ1x2_N1 = reshape(vgeo_N1.ξ1x2, (Nq_N1..., nelem))\n        ξ2x2_N1 = reshape(vgeo_N1.ξ2x2, (Nq_N1..., nelem))\n        ξ3x2_N1 = reshape(vgeo_N1.ξ3x2, (Nq_N1..., nelem))\n        ξ1x3_N1 = reshape(vgeo_N1.ξ1x3, (Nq_N1..., nelem))\n        ξ2x3_N1 = reshape(vgeo_N1.ξ2x3, (Nq_N1..., nelem))\n        ξ3x3_N1 = reshape(vgeo_N1.ξ3x3, (Nq_N1..., nelem))\n\n        vgeo.ξ1x1[:] .=\n            sum(ωJ_N1 .* ξ1x1_N1, dims = findall(Nq .== 1))[:] .* ωJI[:]\n        vgeo.ξ2x1[:] .=\n            sum(ωJ_N1 .* ξ2x1_N1, dims = findall(Nq .== 1))[:] .* ωJI[:]\n        vgeo.ξ3x1[:] .=\n            sum(ωJ_N1 .* ξ3x1_N1, dims = findall(Nq .== 1))[:] .* ωJI[:]\n        vgeo.ξ1x2[:] .=\n            sum(ωJ_N1 .* ξ1x2_N1, dims = findall(Nq .== 1))[:] .* ωJI[:]\n        vgeo.ξ2x2[:] .=\n            sum(ωJ_N1 .* ξ2x2_N1, dims = findall(Nq .== 1))[:] .* ωJI[:]\n        vgeo.ξ3x2[:] .=\n            sum(ωJ_N1 .* ξ3x2_N1, dims = findall(Nq .== 1))[:] .* ωJI[:]\n        vgeo.ξ1x3[:] .=\n            sum(ωJ_N1 .* ξ1x3_N1, dims = findall(Nq .== 1))[:] .* ωJI[:]\n        vgeo.ξ2x3[:] .=\n            sum(ωJ_N1 .* ξ2x3_N1, dims = findall(Nq .== 1))[:] .* ωJI[:]\n        vgeo.ξ3x3[:] .=\n            sum(ωJ_N1 .* ξ3x3_N1, dims = findall(Nq .== 1))[:] .* ωJI[:]\n        num_vgeo_handled += 9\n\n        # compute ωJH and JvC\n        horizontal_metrics!(vgeo, Nq, ω)\n        num_vgeo_handled += 1\n\n        # Make sure we handled all the vgeo terms\n        @assert _nvgeo == num_vgeo_handled\n    end\n\n    # Sort out the sgeo terms\n    @views begin\n        sgeo = SurfaceGeometry(FT, Nq, nface, nelem)\n\n        # for the volume inverse mass matrix\n        p = reshape(1:Np, Nq)\n        if dim == 1\n            fmask = (p[1:1], p[Nq[1]:Nq[1]])\n        elseif dim == 2\n            fmask = (p[1, :][:], p[Nq[1], :][:], p[:, 1][:], p[:, Nq[2]][:])\n        elseif dim == 3\n            fmask = (\n                p[1, :, :][:],\n                p[Nq[1], :, :][:],\n                p[:, 1, :][:],\n                p[:, Nq[2], :][:],\n                p[:, :, 1][:],\n                p[:, :, Nq[3]][:],\n            )\n        end\n        for d in 1:dim\n            for f in (2d - 1):(2d)\n                # number of points matches means that we keep all the data\n                # (N = 0 is not on the face)\n                if Nfp[d] == Nfp_N1[d]\n                    sgeo.n1[:, f, :] .= sgeo_N1.n1[:, f, :]\n                    sgeo.n2[:, f, :] .= sgeo_N1.n2[:, f, :]\n                    sgeo.n3[:, f, :] .= sgeo_N1.n3[:, f, :]\n                    sgeo.sωJ[:, f, :] .= sgeo_N1.sωJ[:, f, :]\n\n                    # Volume inverse mass will be wrong so reset it\n                    sgeo.vωJI[:, f, :] .= vgeo.ωJI[fmask[f], :]\n                else\n                    # Counter to make sure we got all the sgeo terms\n                    num_sgeo_handled = 0\n\n                    # sum to get sM\n                    Nq_f = (Nq[1:(d - 1)]..., Nq[(d + 1):dim]...)\n                    Nq_f_N1 = (Nq_N1[1:(d - 1)]..., Nq_N1[(d + 1):dim]...)\n                    sM_N1 = reshape(\n                        sgeo_N1.sωJ[1:Nfp_N1[d], f, :],\n                        Nq_f_N1...,\n                        nelem,\n                    )\n                    sgeo.sωJ[1:Nfp[d], f, :][:] .=\n                        sum(sM_N1, dims = findall(Nq_f .== 1))[:]\n                    num_sgeo_handled += 1\n\n                    # Normals (like metrics in the volume) need to be computed\n                    # scaled by surface Jacobian which we can do with the\n                    # surface mass matrices\n                    sM = sgeo.sωJ[1:Nfp[d], f, :]\n\n                    fld_N1_n1 = reshape(\n                        sgeo_N1.n1[1:Nfp_N1[d], f, :],\n                        Nq_f_N1...,\n                        nelem,\n                    )\n                    fld_N1_n2 = reshape(\n                        sgeo_N1.n2[1:Nfp_N1[d], f, :],\n                        Nq_f_N1...,\n                        nelem,\n                    )\n                    fld_N1_n3 = reshape(\n                        sgeo_N1.n3[1:Nfp_N1[d], f, :],\n                        Nq_f_N1...,\n                        nelem,\n                    )\n\n                    sgeo.n1[1:Nfp[d], f, :][:] .=\n                        sum(sM_N1 .* fld_N1_n1, dims = findall(Nq_f .== 1))[:] ./\n                        sM[:]\n\n                    sgeo.n2[1:Nfp[d], f, :][:] .=\n                        sum(sM_N1 .* fld_N1_n2, dims = findall(Nq_f .== 1))[:] ./\n                        sM[:]\n\n                    sgeo.n3[1:Nfp[d], f, :][:] .=\n                        sum(sM_N1 .* fld_N1_n3, dims = findall(Nq_f .== 1))[:] ./\n                        sM[:]\n\n                    num_sgeo_handled += 3\n\n                    # set the volume inverse mass matrix\n                    sgeo.vωJI[1:Nfp[d], f, :] .= vgeo.ωJI[fmask[f], :]\n                    num_sgeo_handled += 1\n\n                    # Make sure we handled all the vgeo terms\n                    @assert _nsgeo == num_sgeo_handled\n                end\n            end\n        end\n    end\n\n    (vgeo, sgeo, x_vtk)\nend\n\n\"\"\"\n    computegeometry(elemtocoord, D, ξ, ω, meshwarp)\n\nCompute the geometric factors data needed to define metric terms at\neach quadrature point. First, compute the so called \"topology coordinates\"\nfrom reference coordinates ξ. Then map these topology coordinate\nto physical coordinates. Then compute the Jacobian of the mapping from\nreference coordinates to physical coordinates, i.e., ∂x/∂ξ, by calling\n`compute_reference_to_physical_coord_jacobian!`.\nFinally, compute the metric terms by calling the function `computemetric!`.\n\"\"\"\nfunction computegeometry(elemtocoord, D, ξ, ω, meshwarp)\n    FT = eltype(D[1])\n    dim = length(D)\n    nface = 2dim\n    nelem = size(elemtocoord, 3)\n\n    Nq = ntuple(j -> size(D[j], 1), dim)\n\n    # Compute metric terms for FVM\n    if any(Nq .== 1)\n        return computegeometry_fvm(elemtocoord, D, ξ, ω, meshwarp)\n    end\n\n    Np = prod(Nq)\n    Nfp = div.(Np, Nq)\n\n    # Initialize volume and surface geometric term data structures\n    vgeo = VolumeGeometry(FT, Nq, nelem)\n    sgeo = SurfaceGeometry(FT, Nq, nface, nelem)\n\n    # a) Compute \"topology coordinates\" from reference coordinates ξ\n    Metrics.creategrid!(vgeo, elemtocoord, ξ)\n\n    # Create local variables\n    x1 = vgeo.x1\n    x2 = vgeo.x2\n    x3 = vgeo.x3\n\n    # b) Map \"topology coordinates\" -> physical coordinates\n    @inbounds for j in 1:length(vgeo.x1)\n        (x1[j], x2[j], x3[j]) = meshwarp(vgeo.x1[j], vgeo.x2[j], vgeo.x3[j])\n    end\n\n    # Update global data in vgeo\n    vgeo.x1 .= x1\n    vgeo.x2 .= x2\n    vgeo.x3 .= x3\n\n    # c) Compute Jacobian matrix, ∂x/∂ξ\n    Metrics.compute_reference_to_physical_coord_jacobian!(vgeo, nelem, D)\n\n    # d) Compute the metric terms\n    Metrics.computemetric!(vgeo, sgeo, D)\n\n    # Note:\n    # To get analytic derivatives, we need to be able differentiate through (a,b) and combine (a,b,c)\n\n    # Compute the metric terms\n    p = reshape(1:Np, Nq)\n    if dim == 1\n        fmask = (p[1:1], p[Nq[1]:Nq[1]])\n    elseif dim == 2\n        fmask = (p[1, :][:], p[Nq[1], :][:], p[:, 1][:], p[:, Nq[2]][:])\n    elseif dim == 3\n        fmask = (\n            p[1, :, :][:],\n            p[Nq[1], :, :][:],\n            p[:, 1, :][:],\n            p[:, Nq[2], :][:],\n            p[:, :, 1][:],\n            p[:, :, Nq[3]][:],\n        )\n    end\n\n    # since `ξ1` is the fastest dimension and `ξdim` the slowest the tensor\n    # product order is reversed\n    M = kron(1, reverse(ω)...)\n    vgeo.ωJ .*= M\n    vgeo.ωJI .= 1 ./ vgeo.ωJ\n    for d in 1:dim\n        for f in (2d - 1):(2d)\n            sgeo.vωJI[1:Nfp[d], f, :] .= vgeo.ωJI[fmask[f], :]\n        end\n    end\n\n    sM = fill!(similar(sgeo.sωJ, maximum(Nfp), nface), NaN)\n    for d in 1:dim\n        for f in (2d - 1):(2d)\n            ωf = ntuple(j -> ω[mod1(d + j, dim)], dim - 1)\n            # Because of the `mod1` this face is already flipped\n            if !(dim == 3 && d == 2)\n                ωf = reverse(ωf)\n            end\n            sM[1:Nfp[d], f] .= dim > 1 ? kron(1, ωf...) : one(FT)\n        end\n    end\n    sgeo.sωJ .*= sM\n\n    # compute MH and JvC\n    horizontal_metrics!(vgeo, Nq, ω)\n\n    # This is mainly done to support FVM plotting when N=0 (since we need cell\n    # edge values)\n    x_vtk = (vgeo.x1, vgeo.x2, vgeo.x3)\n\n    return (vgeo, sgeo, x_vtk)\nend\n\n\"\"\"\n    horizontal_metrics!(vgeo::VolumeGeometry, Nq, ω)\n\nCompute the horizontal mass matrix `ωJH` field of `vgeo`\n```\nJ .* norm(∂ξ3/∂x) * (ωᵢ ⊗ ωⱼ); for integrating over a plane\n```\n(in 2-D ξ2 not ξ3 is used).\n\"\"\"\nfunction horizontal_metrics!(vgeo::VolumeGeometry, Nq, ω)\n    dim = length(Nq)\n\n    MH = dim == 1 ? 1 : kron(ones(1, Nq[dim]), reverse(ω[1:(dim - 1)])...)[:]\n    M = vec(kron(1, reverse(ω)...))\n\n    J = vgeo.ωJ ./ M\n\n    # Compute |r'(ξ3)| for vertical line integrals\n    if dim == 1\n        vgeo.ωJH .= 1\n    elseif dim == 2\n        vgeo.ωJH .= MH .* hypot.(J .* vgeo.ξ2x1, J .* vgeo.ξ2x2)\n    elseif dim == 3\n        vgeo.ωJH .= MH .* hypot.(J .* vgeo.ξ3x1, J .* vgeo.ξ3x2, J .* vgeo.ξ3x3)\n    else\n        error(\"dim $dim not implemented\")\n    end\n    return vgeo\nend\n\n\"\"\"\n    indefinite_integral_interpolation_matrix(r, ω)\n\nGiven a set of integration points `r` and integration weights `ω` this computes\na matrix that will compute the indefinite integral of the (interpolant) of a\nfunction and evaluate the indefinite integral at the points `r`.\n\nNamely, let\n```math\n    q(ξ) = ∫_{ξ_{0}}^{ξ} f(ξ') dξ'\n```\nthen we have that\n```\nI∫ * f.(r) = q.(r)\n```\nwhere `I∫` is the integration and interpolation matrix defined by this function.\n\n!!! note\n\n    The integration is done using the provided quadrature weight, so if these\n    cannot integrate `f(ξ)` exactly, `f` is first interpolated and then\n    integrated using quadrature. Namely, we have that:\n    ```math\n        q(ξ) = ∫_{ξ_{0}}^{ξ} I(f(ξ')) dξ'\n    ```\n    where `I` is the interpolation operator.\n\n\"\"\"\nfunction indefinite_integral_interpolation_matrix(r, ω)\n    Nq = length(r)\n\n    I∫ = similar(r, Nq, Nq)\n    # first value is zero\n    I∫[1, :] .= Nq == 1 ? ω[1] : 0\n\n    # barycentric weights for interpolation\n    wbary = Elements.baryweights(r)\n\n    # Compute the interpolant of the indefinite integral\n    for n in 2:Nq\n        # grid from first dof to current point\n        rdst = (1 .- r) / 2 * r[1] + (1 .+ r) / 2 * r[n]\n        # interpolation matrix\n        In = Elements.interpolationmatrix(r, rdst, wbary)\n        # scaling from LGL to current of the interval\n        Δ = (r[n] - r[1]) / 2\n        # row of the matrix we have computed\n        I∫[n, :] .= (Δ * ω' * In)[:]\n    end\n    I∫\nend\n# }}}\n\nusing KernelAbstractions.Extras: @unroll\n\nusing StaticArrays\n\nconst _x1 = Grids._x1\nconst _x2 = Grids._x2\nconst _x3 = Grids._x3\nconst _JcV = Grids._JcV\n\n@doc \"\"\"\n    kernel_min_neighbor_distance!(::Val{N}, ::Val{dim}, direction,\n                             min_neighbor_distance, vgeo, topology.realelems)\n\nComputational kernel: Computes the minimum physical distance between node\nneighbors within an element.\n\nThe `direction` in the reference element controls which nodes are considered\nneighbors.\n\"\"\" kernel_min_neighbor_distance!\n@kernel function kernel_min_neighbor_distance!(\n    ::Val{N},\n    ::Val{dim},\n    direction,\n    min_neighbor_distance,\n    vgeo,\n    elems,\n) where {N, dim}\n\n    @uniform begin\n        FT = eltype(min_neighbor_distance)\n        Nq = N .+ 1\n        Np = prod(Nq)\n\n        if direction isa EveryDirection\n            mininξ = (true, true, true)\n        elseif direction isa HorizontalDirection\n            mininξ = (true, dim == 2 ? false : true, false)\n        elseif direction isa VerticalDirection\n            mininξ = (false, dim == 2 ? true : false, dim == 2 ? false : true)\n        end\n\n        @inbounds begin\n            # 2D Nq = (nh, nv)\n            # 3D Nq = (nh, nh, nv)\n            Nq1 = Nq[1]\n            Nq2 = Nq[2]\n            Nqk = dim == 2 ? 1 : Nq[end]\n            mininξ1 = mininξ[1]\n            mininξ2 = mininξ[2]\n            mininξ3 = mininξ[3]\n        end\n    end\n\n\n    I = @index(Global, Linear)\n    # local element id\n    e = (I - 1) ÷ Np + 1\n    # local quadrature id\n    ijk = (I - 1) % Np + 1\n\n    # local i, j, k quadrature id\n    i = (ijk - 1) % Nq1 + 1\n    j = (ijk - 1) ÷ Nq1 % Nq2 + 1\n    k = (ijk - 1) ÷ (Nq1 * Nq2) % Nqk + 1\n\n    md = typemax(FT)\n\n    x = SVector(vgeo[ijk, _x1, e], vgeo[ijk, _x2, e], vgeo[ijk, _x3, e])\n\n    # first horizontal distance\n    if mininξ1\n        @unroll for î in (i - 1, i + 1)\n            if 1 ≤ î ≤ Nq1\n                îjk = î + Nq1 * (j - 1) + Nq1 * Nq2 * (k - 1)\n                x̂ = SVector(\n                    vgeo[îjk, _x1, e],\n                    vgeo[îjk, _x2, e],\n                    vgeo[îjk, _x3, e],\n                )\n                md = min(md, norm(x - x̂))\n            end\n        end\n    end\n\n    # second horizontal distance or vertical distance (dim=2)\n    if mininξ2\n        # FV Vercial direction, use 2vgeo[ijk, _JcV, e]\n        if dim == 2 && Nq2 == 1\n            md = min(md, 2vgeo[ijk, _JcV, e])\n        else\n            @unroll for ĵ in (j - 1, j + 1)\n                if 1 ≤ ĵ ≤ Nq2\n                    iĵk = i + Nq1 * (ĵ - 1) + Nq1 * Nq2 * (k - 1)\n                    x̂ = SVector(\n                        vgeo[iĵk, _x1, e],\n                        vgeo[iĵk, _x2, e],\n                        vgeo[iĵk, _x3, e],\n                    )\n                    md = min(md, norm(x - x̂))\n                end\n            end\n        end\n    end\n\n    # vertical distance (dim=3)\n    if mininξ3\n        # FV Vercial direction, use 2vgeo[ijk, _JcV, e]\n        if dim == 3 && Nqk == 1\n            md = min(md, 2vgeo[ijk, _JcV, e])\n        else\n            @unroll for k̂ in (k - 1, k + 1)\n                if 1 ≤ k̂ ≤ Nqk\n                    ijk̂ = i + Nq1 * (j - 1) + Nq1 * Nq2 * (k̂ - 1)\n                    x̂ = SVector(\n                        vgeo[ijk̂, _x1, e],\n                        vgeo[ijk̂, _x2, e],\n                        vgeo[ijk̂, _x3, e],\n                    )\n                    md = min(md, norm(x - x̂))\n                end\n            end\n        end\n    end\n\n    min_neighbor_distance[ijk, e] = md\nend\n\nend # module\n"
  },
  {
    "path": "src/Numerics/Mesh/Interpolation.jl",
    "content": "module Interpolation\n\nusing CUDA\nusing DocStringExtensions\nusing LinearAlgebra\nusing MPI\nusing OrderedCollections\nusing StaticArrays\nusing KernelAbstractions\n\nusing ClimateMachine\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Geometry\nimport ClimateMachine.Mesh.Elements: baryweights\nimport ClimateMachine.MPIStateArrays: array_device\n\nexport dimensions,\n    accumulate_interpolated_data,\n    accumulate_interpolated_data!,\n    InterpolationBrick,\n    InterpolationCubedSphere,\n    interpolate_local!,\n    project_cubed_sphere!,\n    InterpolationTopology\n\nabstract type InterpolationTopology end\n\ndimensions(nothing) = OrderedDict()\n\n\"\"\"\n    InterpolationBrick{\n        FT <: AbstractFloat,CuArrays\n        UI8AD <: AbstractArray{UInt8, 2},\n        UI16VD <: AbstractVector{UInt16},\n        I32V <: AbstractVector{Int32},\n    } <: InterpolationTopology\n\nThis interpolation data structure and the corresponding functions works for a\nbrick, where stretching/compression happens only along the x1, x2 & x3 axis.\nHere x1 = X1(ξ1), x2 = X2(ξ2) and x3 = X3(ξ3).\n\n# Fields\n\n$(DocStringExtensions.FIELDS)\n\n# Usage\n\n    InterpolationBrick(\n        grid::DiscontinuousSpectralElementGrid{FT},\n        xbnd::Array{FT,2},\n        xres,\n    ) where FT <: AbstractFloat\n\nThis interpolation structure and the corresponding functions works for a brick,\nwhere stretching/compression happens only along the x1, x2 & x3 axis. Here x1\n= X1(ξ1), x2 = X2(ξ2) and x3 = X3(ξ3).\n\n# Arguments for the inner constructor\n - `grid`: DiscontinousSpectralElementGrid\n - `xbnd`: Domain boundaries in x1, x2 and x3 directions\n - `x1g`: Interpolation grid in x1 direction\n - `x2g`: Interpolation grid in x2 direction\n - `x3g`: Interpolation grid in x3 direction\n\"\"\"\nstruct InterpolationBrick{\n    FT <: AbstractFloat,\n    I <: Int,\n    FTV <: AbstractVector{FT},\n    FTVD <: AbstractVector{FT},\n    IVD <: AbstractVector{I},\n    FTA2 <: Array{FT, 2},\n    UI8AD <: AbstractArray{UInt8, 2},\n    UI16VD <: AbstractVector{UInt16},\n    I32V <: AbstractVector{Int32},\n} <: InterpolationTopology\n    \"Number of elements\"\n    Nel::I\n    \"Total number of interpolation points\"\n    Np::I\n    \"Total number of interpolation points on local process\"\n    Npl::I\n    \"Domain bounds in x1, x2 and x3 directions\"\n    xbnd::FTA2\n    \"Interpolation grid in x1 direction\"\n    x1g::FTV\n    \"Interpolation grid in x2 direction\"\n    x2g::FTV\n    \"Interpolation grid in x3 direction\"\n    x3g::FTV\n    \"Unique ξ1 coordinates of interpolation points within each spectral element\"\n    ξ1::FTVD\n    \"Unique ξ2 coordinates of interpolation points within each spectral element\"\n    ξ2::FTVD\n    \"Unique ξ3 coordinates of interpolation points within each spectral element\"\n    ξ3::FTVD\n    \"Flags when ξ1/ξ2/ξ3 interpolation point matches with a GLL point\"\n    flg::UI8AD\n    \"Normalization factor\"\n    fac::FTVD\n    \"x1 interpolation grid index of interpolation points within each element on the local process\"\n    x1i::UI16VD\n    \"x2 interpolation grid index of interpolation points within each element on the local process\"\n    x2i::UI16VD\n    \"x3 interpolation grid index of interpolation points within each element on the local process\"\n    x3i::UI16VD\n    \"Offsets for each element\"\n    offset::IVD    # offsets for each element for v\n    \"GLL points in ξ1 direction\"\n    m_ξ1::FTVD\n    \"GLL points in ξ2 direction\"\n    m_ξ2::FTVD\n    \"GLL points in ξ3 direction\"\n    m_ξ3::FTVD\n    \"Barycentric weights\"\n    wb1::FTVD\n    \"Barycentric weights\"\n    wb2::FTVD\n    \"Barycentric weights\"\n    wb3::FTVD\n    # MPI setup for gathering interpolated variable on proc # 0\n    \"Number of interpolation points on each of the processes\"\n    Np_all::I32V\n\n    \"x1 interpolation grid index of interpolation points within each element on all processes stored only on proc 0\"\n    x1i_all::UI16VD\n    \"x2 interpolation grid index of interpolation points within each element on all processes stored only on proc 0\"\n    x2i_all::UI16VD\n    \"x3 interpolation grid index of interpolation points within each element on all processes stored only on proc 0\"\n    x3i_all::UI16VD\n\n    function InterpolationBrick(\n        grid::DiscontinuousSpectralElementGrid{FT},\n        xbnd::Array{FT, 2},\n        x1g::AbstractArray{FT, 1},\n        x2g::AbstractArray{FT, 1},\n        x3g::AbstractArray{FT, 1},\n    ) where {FT <: AbstractFloat}\n        mpicomm = grid.topology.mpicomm\n        pid = MPI.Comm_rank(mpicomm)\n        npr = MPI.Comm_size(mpicomm)\n\n        DA = arraytype(grid)                    # device array\n        device = arraytype(grid) <: Array ? CPU() : CUDADevice()\n\n        qm = polynomialorders(grid) .+ 1\n        ndim = 3\n        toler = 4 * eps(FT) # tolerance\n\n        n1g = length(x1g)\n        n2g = length(x2g)\n        n3g = length(x3g)\n\n        Np = n1g * n2g * n3g\n        marker = BitArray{3}(undef, n1g, n2g, n3g)\n        fill!(marker, true)\n\n        Nel = length(grid.topology.realelems) # # of elements on local process\n        offset = Vector{Int}(undef, Nel + 1) # offsets for the interpolated variable\n        n123 = zeros(Int, ndim)        # # of unique ξ1, ξ2, ξ3 points in each cell\n        xsten = zeros(Int, 2, ndim)        # x1, x2, x3 start and end for each brick element\n        xbndl = zeros(FT, 2, ndim)        # x1,x2,x3 limits (min,max) for each brick element\n\n        ξ1 = map(i -> zeros(FT, i), zeros(Int, Nel))\n        ξ2 = map(i -> zeros(FT, i), zeros(Int, Nel))\n        ξ3 = map(i -> zeros(FT, i), zeros(Int, Nel))\n\n        x1i = map(i -> zeros(UInt16, i), zeros(UInt16, Nel))\n        x2i = map(i -> zeros(UInt16, i), zeros(UInt16, Nel))\n        x3i = map(i -> zeros(UInt16, i), zeros(UInt16, Nel))\n\n        x = map(i -> zeros(FT, ndim, i), zeros(Int, Nel)) # interpolation grid points embedded in each cell\n\n        offset[1] = 0\n\n        for el in 1:Nel\n            for (xg, dim) in zip((x1g, x2g, x3g), 1:ndim)\n                xbndl[1, dim], xbndl[2, dim] =\n                    extrema(grid.topology.elemtocoord[dim, :, el])\n\n                st = findfirst(xg .≥ xbndl[1, dim] .- toler)\n                if st ≠ nothing\n                    if xg[st] > (xbndl[2, dim] + toler)\n                        st = nothing\n                    end\n                end\n\n                if st ≠ nothing\n                    xsten[1, dim] = st\n                    xsten[2, dim] =\n                        findlast(temp -> temp .≤ xbndl[2, dim] .+ toler, xg)\n                    n123[dim] = xsten[2, dim] - xsten[1, dim] + 1\n                else\n                    n123[dim] = 0\n                end\n            end\n\n            if prod(n123) > 0\n                for k in xsten[1, 3]:xsten[2, 3],\n                    j in xsten[1, 2]:xsten[2, 2],\n                    i in xsten[1, 1]:xsten[2, 1]\n\n                    if marker[i, j, k]\n                        push!(\n                            ξ1[el],\n                            2 * (x1g[i] - xbndl[1, 1]) /\n                            (xbndl[2, 1] - xbndl[1, 1]) - 1,\n                        )\n                        push!(\n                            ξ2[el],\n                            2 * (x2g[j] - xbndl[1, 2]) /\n                            (xbndl[2, 2] - xbndl[1, 2]) - 1,\n                        )\n                        push!(\n                            ξ3[el],\n                            2 * (x3g[k] - xbndl[1, 3]) /\n                            (xbndl[2, 3] - xbndl[1, 3]) - 1,\n                        )\n\n                        push!(x1i[el], UInt16(i))\n                        push!(x2i[el], UInt16(j))\n                        push!(x3i[el], UInt16(k))\n                        marker[i, j, k] = false\n                    end\n                end\n                offset[el + 1] = offset[el] + length(ξ1[el])\n            else\n                offset[el + 1] = offset[el]\n            end\n\n        end # el loop\n\n        m_ξ1, m_ξ2, m_ξ3 = referencepoints(grid)\n        wb1, wb2, wb3 = baryweights(m_ξ1), baryweights(m_ξ2), baryweights(m_ξ3)\n\n        Npl = offset[end]\n\n        ξ1_d = Array{FT}(undef, Npl)\n        ξ2_d = Array{FT}(undef, Npl)\n        ξ3_d = Array{FT}(undef, Npl)\n        x1i_d = Array{UInt16}(undef, Npl)\n        x2i_d = Array{UInt16}(undef, Npl)\n        x3i_d = Array{UInt16}(undef, Npl)\n        fac_d = zeros(FT, Npl)\n        flg_d = zeros(UInt8, 3, Npl)\n\n        for i in 1:Nel\n            ctr = 1\n            for j in (offset[i] + 1):offset[i + 1]\n                ξ1_d[j] = ξ1[i][ctr]\n                ξ2_d[j] = ξ2[i][ctr]\n                ξ3_d[j] = ξ3[i][ctr]\n                x1i_d[j] = x1i[i][ctr]\n                x2i_d[j] = x2i[i][ctr]\n                x3i_d[j] = x3i[i][ctr]\n                # set up interpolation\n                fac1 = FT(0)\n                fac2 = FT(0)\n                fac3 = FT(0)\n                for ib in 1:qm[1]\n                    if abs(m_ξ1[ib] - ξ1_d[j]) < toler\n                        @inbounds flg_d[1, j] = UInt8(ib)\n                    else\n                        @inbounds fac1 += wb1[ib] / (ξ1_d[j] - m_ξ1[ib])\n                    end\n                end\n\n                for ib in 1:qm[2]\n                    if abs(m_ξ2[ib] - ξ2_d[j]) < toler\n                        @inbounds flg_d[2, j] = UInt8(ib)\n                    else\n                        @inbounds fac2 += wb2[ib] / (ξ2_d[j] - m_ξ2[ib])\n                    end\n                end\n\n                for ib in 1:qm[3]\n                    if abs(m_ξ3[ib] - ξ3_d[j]) < toler\n                        @inbounds flg_d[3, j] = UInt8(ib)\n                    else\n                        @inbounds fac3 += wb3[ib] / (ξ3_d[j] - m_ξ3[ib])\n                    end\n                end\n\n                flg_d[1, j] ≠ UInt8(0) && (fac1 = FT(1))\n                flg_d[2, j] ≠ UInt8(0) && (fac2 = FT(1))\n                flg_d[3, j] ≠ UInt8(0) && (fac3 = FT(1))\n\n                fac_d[j] = FT(1) / (fac1 * fac2 * fac3)\n\n                ctr += 1\n            end\n        end\n        # MPI setup for gathering data on proc 0\n        root = 0\n        Np_all = zeros(Int32, npr)\n        Np_all[pid + 1] = Npl\n\n        MPI.Allreduce!(Np_all, +, mpicomm)\n\n        if pid ≠ root\n            x1i_all = zeros(UInt16, 0)\n            x2i_all = zeros(UInt16, 0)\n            x3i_all = zeros(UInt16, 0)\n            MPI.Gatherv!(x1i_d, nothing, root, mpicomm)\n            MPI.Gatherv!(x2i_d, nothing, root, mpicomm)\n            MPI.Gatherv!(x3i_d, nothing, root, mpicomm)\n        else\n            x1i_all = Array{UInt16}(undef, sum(Np_all))\n            x2i_all = Array{UInt16}(undef, sum(Np_all))\n            x3i_all = Array{UInt16}(undef, sum(Np_all))\n            MPI.Gatherv!(x1i_d, VBuffer(x1i_all, Np_all), root, mpicomm)\n            MPI.Gatherv!(x2i_d, VBuffer(x2i_all, Np_all), root, mpicomm)\n            MPI.Gatherv!(x3i_d, VBuffer(x3i_all, Np_all), root, mpicomm)\n        end\n\n\n        if device isa CUDADevice\n            ξ1_d = DA(ξ1_d)\n            ξ2_d = DA(ξ2_d)\n            ξ3_d = DA(ξ3_d)\n            x1i_d = DA(x1i_d)\n            x2i_d = DA(x2i_d)\n            x3i_d = DA(x3i_d)\n            flg_d = DA(flg_d)\n            fac_d = DA(fac_d)\n            offset = DA(offset)\n            m_ξ1 = DA(m_ξ1)\n            m_ξ2 = DA(m_ξ2)\n            m_ξ3 = DA(m_ξ3)\n            wb1 = DA(wb1)\n            wb2 = DA(wb2)\n            wb3 = DA(wb3)\n            x1i_all = DA(x1i_all)\n            x2i_all = DA(x2i_all)\n            x3i_all = DA(x3i_all)\n        end\n        return new{\n            FT,\n            Int,\n            typeof(x1g),\n            typeof(ξ1_d),\n            typeof(offset),\n            typeof(xbnd),\n            typeof(flg_d),\n            typeof(x1i_d),\n            typeof(Np_all),\n        }(\n            Nel,\n            Np,\n            Npl,\n            xbnd,\n            x1g,\n            x2g,\n            x3g,\n            ξ1_d,\n            ξ2_d,\n            ξ3_d,\n            flg_d,\n            fac_d,\n            x1i_d,\n            x2i_d,\n            x3i_d,\n            offset,\n            m_ξ1,\n            m_ξ2,\n            m_ξ3,\n            wb1,\n            wb2,\n            wb3,\n            Np_all,\n            x1i_all,\n            x2i_all,\n            x3i_all,\n        )\n\n    end\n\nend # struct InterpolationBrick\n\n\"\"\"\n    interpolate_local!(\n        intrp_brck::InterpolationBrick{FT},\n        sv::AbstractArray{FT},\n        v::AbstractArray{FT},\n    ) where {FT <: AbstractFloat}\n\nThis interpolation function works for a brick, where stretching/compression\nhappens only along the x1, x2 & x3 axis.  Here x1 = X1(ξ1), x2 = X2(ξ2) and x3\n= X3(ξ3)\n\n# Arguments\n - `intrp_brck`: Initialized InterpolationBrick structure\n - `sv`: State Array consisting of various variables on the discontinuous\n   Galerkin grid\n - `v`:  Interpolated variables\n\"\"\"\nfunction interpolate_local!(\n    intrp_brck::InterpolationBrick{FT},\n    sv::AbstractArray{FT},\n    v::AbstractArray{FT},\n) where {FT <: AbstractFloat}\n\n    offset = intrp_brck.offset\n    m_ξ1 = intrp_brck.m_ξ1\n    m_ξ2 = intrp_brck.m_ξ2\n    m_ξ3 = intrp_brck.m_ξ3\n    wb1 = intrp_brck.wb1\n    wb2 = intrp_brck.wb2\n    wb3 = intrp_brck.wb3\n    ξ1 = intrp_brck.ξ1\n    ξ2 = intrp_brck.ξ2\n    ξ3 = intrp_brck.ξ3\n    flg = intrp_brck.flg\n    fac = intrp_brck.fac\n\n    qm = (length(m_ξ1), length(m_ξ2), length(m_ξ3))\n    Nel = length(offset) - 1\n    nvars = size(sv, 2)\n\n    device = array_device(sv)\n    comp_stream = Event(device)\n\n    workgroup = (qm[2], qm[3])\n    ndrange = (qm[2] * Nel, qm[3] * nvars)\n\n    comp_stream = interpolate_local_kernel!(device, workgroup)(\n        offset,\n        m_ξ1,\n        m_ξ2,\n        m_ξ3,\n        wb1,\n        wb2,\n        wb3,\n        ξ1,\n        ξ2,\n        ξ3,\n        flg,\n        fac,\n        sv,\n        v,\n        Val(qm),\n        ndrange = ndrange,\n        dependencies = (comp_stream,),\n    )\n    wait(comp_stream)\n    return nothing\nend\n#---------------------------------------------------------------\n@kernel function interpolate_local_kernel!(\n    offset::AbstractArray{T, 1},\n    m_ξ1::AbstractArray{FT, 1},\n    m_ξ2::AbstractArray{FT, 1},\n    m_ξ3::AbstractArray{FT, 1},\n    wb1::AbstractArray{FT, 1},\n    wb2::AbstractArray{FT, 1},\n    wb3::AbstractArray{FT, 1},\n    ξ1::AbstractArray{FT, 1},\n    ξ2::AbstractArray{FT, 1},\n    ξ3::AbstractArray{FT, 1},\n    flg::AbstractArray{UInt8, 2},\n    fac::AbstractArray{FT, 1},\n    sv::AbstractArray{FT},\n    v::AbstractArray{FT},\n    ::Val{qm},\n) where {qm, T <: Int, FT <: AbstractFloat}\n\n    el, st_idx = @index(Group, NTuple)\n    tj, tk = @index(Local, NTuple)\n\n\n    vout_jk = @localmem FT (qm[2], qm[3])\n    m_ξ1_sh = @localmem FT (qm[1],)\n    m_ξ2_sh = @localmem FT (qm[2],)\n    m_ξ3_sh = @localmem FT (qm[3],)\n    wb1_sh = @localmem FT (qm[1],)\n    wb2_sh = @localmem FT (qm[2],)\n    wb3_sh = @localmem FT (qm[3],)\n\n    np = @localmem T (1,)\n    off = @localmem T (1,)\n\n    # load shared memory\n    if tk == 1\n        m_ξ2_sh[tj] = m_ξ2[tj]\n        wb2_sh[tj] = wb2[tj]\n    end\n    if tj == 1\n        m_ξ3_sh[tk] = m_ξ3[tk]\n        wb3_sh[tk] = wb3[tk]\n    end\n    if tj == 1 && tk == 1\n        for i in 1:qm[1]\n            m_ξ1_sh[i] = m_ξ1[i]\n            wb1_sh[i] = wb1[i]\n        end\n        np[1] = offset[el + 1] - offset[el]\n        off[1] = offset[el]\n    end\n    @synchronize\n\n\n    for i in 1:np[1] # interpolate point-by-point\n\n        ξ1l = ξ1[off[1] + i]\n        ξ2l = ξ2[off[1] + i]\n\n        f1 = flg[1, off[1] + i]\n\n        if f1 == 0 # apply phir\n            @inbounds vout_jk[tj, tk] =\n                sv[\n                    1 + (tj - 1) * qm[1] + (tk - 1) * qm[1] * qm[2],\n                    st_idx,\n                    el,\n                ] * wb1_sh[1] / (ξ1l - m_ξ1_sh[1])\n            for ii in 2:qm[1]\n                @inbounds vout_jk[tj, tk] +=\n                    sv[\n                        ii + (tj - 1) * qm[1] + (tk - 1) * qm[1] * qm[2],\n                        st_idx,\n                        el,\n                    ] * wb1_sh[ii] / (ξ1l - m_ξ1_sh[ii])\n            end\n        else\n            @inbounds vout_jk[tj, tk] =\n                sv[f1 + (tj - 1) * qm[1] + (tk - 1) * qm[1] * qm[2], st_idx, el]\n        end\n\n        if flg[2, off[1] + i] == 0 # apply phis\n            @inbounds vout_jk[tj, tk] *= (wb2_sh[tj] / (ξ2l - m_ξ2_sh[tj]))\n        end\n        @synchronize\n\n        f2 = flg[2, off[1] + i]\n        ξ3l = ξ3[off[1] + i]\n\n        if tj == 1 # reduction\n            if f2 == 0\n                for ij in 2:qm[2]\n                    @inbounds vout_jk[1, tk] += vout_jk[ij, tk]\n                end\n            else\n                if f2 ≠ 1\n                    @inbounds vout_jk[1, tk] = vout_jk[f2, tk]\n                end\n            end\n\n            if flg[3, off[1] + i] == 0 # apply phit\n                @inbounds vout_jk[1, tk] *= (wb3_sh[tk] / (ξ3l - m_ξ3_sh[tk]))\n            end\n        end\n        @synchronize\n\n        f3 = flg[3, off[1] + i]\n\n        if tj == 1 && tk == 1 # reduction\n            if f3 == 0\n                for ik in 2:qm[3]\n                    @inbounds vout_jk[1, 1] += vout_jk[1, ik]\n                end\n            else\n                if f3 ≠ 1\n                    @inbounds vout_jk[1, 1] = vout_jk[1, f3]\n                end\n            end\n            @inbounds v[off[1] + i, st_idx] = vout_jk[1, 1] * fac[off[1] + i]\n        end\n        @synchronize\n    end\nend\n#---------------------------------------------------------------\nfunction dimensions(interpol::InterpolationBrick)\n    if Array ∈ typeof(interpol.x1g).parameters\n        h_x1g = interpol.x1g\n        h_x2g = interpol.x2g\n        h_x3g = interpol.x3g\n    else\n        h_x1g = Array(interpol.x1g)\n        h_x2g = Array(interpol.x2g)\n        h_x3g = Array(interpol.x3g)\n    end\n    return OrderedDict(\n        \"x\" => (h_x1g, OrderedDict()),\n        \"y\" => (h_x2g, OrderedDict()),\n        \"z\" => (h_x3g, OrderedDict()),\n    )\nend\n\n\"\"\"\n    InterpolationCubedSphere{\n    FT <: AbstractFloat,\n    T <: Int,\n    FTV <: AbstractVector{FT},\n    FTVD <: AbstractVector{FT},\n    TVD <: AbstractVector{T},\n    UI8AD <: AbstractArray{UInt8, 2},\n    UI16VD <: AbstractVector{UInt16},\n    I32V <: AbstractVector{Int32},\n    } <: InterpolationTopology\n\nThis interpolation structure and the corresponding functions works for a cubed sphere topology. The data is interpolated along a lat/long/rad grid.\n\n-90⁰  ≤ lat  ≤ 90⁰\n\n-180⁰ ≤ long ≤ 180⁰\n\nRᵢ ≤ r ≤ Rₒ\n\n# Fields\n\n$(DocStringExtensions.FIELDS)\n\n# Usage\n\n    InterpolationCubedSphere(grid::DiscontinuousSpectralElementGrid, vert_range::AbstractArray{FT}, nhor::Int, lat_res::FT, long_res::FT, rad_res::FT) where {FT <: AbstractFloat}\n\nThis interpolation structure and the corresponding functions works for a cubed sphere topology. The data is interpolated along a lat/long/rad grid.\n\n-90⁰  ≤ lat  ≤ 90⁰\n\n-180⁰ ≤ long ≤ 180⁰\n\nRᵢ ≤ r ≤ Rₒ\n\n# Arguments for the inner constructor\n - `grid`: DiscontinousSpectralElementGrid\n - `vert_range`: Vertex range along the radial coordinate\n - `lat_res`: Resolution of the interpolation grid along the latitude coordinate in radians\n - `long_res`: Resolution of the interpolation grid along the longitude coordinate in radians\n - `rad_res`: Resolution of the interpolation grid along the radial coordinate\n\"\"\"\nstruct InterpolationCubedSphere{\n    FT <: AbstractFloat,\n    T <: Int,\n    FTV <: AbstractVector{FT},\n    FTVD <: AbstractVector{FT},\n    TVD <: AbstractVector{T},\n    UI8AD <: AbstractArray{UInt8, 2},\n    UI16VD <: AbstractVector{UInt16},\n    I32V <: AbstractVector{Int32},\n} <: InterpolationTopology\n    \"Number of elements\"\n    Nel::T\n    \"Number of interpolation points\"\n    Np::T\n    \"Number of interpolation points on local process\"\n    Npl::T            # # of interpolation points on the local process\n    \"Number of interpolation points in radial direction\"\n    n_rad::T\n    \"Number of interpolation points in lat direction\"\n    n_lat::T\n    \"Number of interpolation points in long direction\"\n    n_long::T\n    \"Interpolation grid in radial direction\"\n    rad_grd::FTV\n    \"Interpolation grid in lat direction\"\n    lat_grd::FTV\n    \"Interpolation grid in long direction\"\n    long_grd::FTV # rad, lat & long locations of interpolation grid\n    \"Device array containing ξ1 coordinates of interpolation points within each element\"\n    ξ1::FTVD\n    \"Device array containing ξ2 coordinates of interpolation points within each element\"\n    ξ2::FTVD\n    \"Device array containing ξ3 coordinates of interpolation points within each element\"\n    ξ3::FTVD\n    \"flags when ξ1/ξ2/ξ3 interpolation point matches with a GLL point\"\n    flg::UI8AD\n    \"Normalization factor\"\n    fac::FTVD\n    \"Radial coordinates of interpolation points withing each element\"\n    radi::UI16VD\n    \"Latitude coordinates of interpolation points withing each element\"\n    lati::UI16VD\n    \"Longitude coordinates of interpolation points withing each element\"\n    longi::UI16VD\n    \"Offsets for each element\"\n    offset::TVD\n    \"GLL points in ξ1 direction\"\n    m_ξ1::FTVD\n    \"GLL points in ξ2 direction\"\n    m_ξ2::FTVD\n    \"GLL points in ξ3 direction\"\n    m_ξ3::FTVD\n    \"Barycentric weights in ξ1 direction\"\n    wb1::FTVD\n    \"Barycentric weights in ξ2 direction\"\n    wb2::FTVD\n    \"Barycentric weights in ξ3 direction\"\n    wb3::FTVD\n    # MPI setup for gathering interpolated variable on proc 0\n    \"Number of interpolation points on each of the processes\"\n    Np_all::I32V\n    \"Radial interpolation grid index of interpolation points within each element on all processes stored only on proc 0\"\n    radi_all::UI16VD\n    \"Latitude interpolation grid index of interpolation points within each element on all processes stored only on proc 0\"\n    lati_all::UI16VD\n    \"Longitude interpolation grid index of interpolation points within each element on all processes stored only on proc 0\"\n    longi_all::UI16VD\n\n    function InterpolationCubedSphere(\n        grid::DiscontinuousSpectralElementGrid,\n        vert_range::AbstractArray{FT},\n        nhor::Int,\n        lat_grd::AbstractArray{FT, 1},\n        long_grd::AbstractArray{FT, 1},\n        rad_grd::AbstractArray{FT};\n        nr_toler = nothing,\n    ) where {FT <: AbstractFloat}\n        mpicomm = MPI.COMM_WORLD\n        pid = MPI.Comm_rank(mpicomm)\n        npr = MPI.Comm_size(mpicomm)\n\n        DA = arraytype(grid)                    # device array\n        device = arraytype(grid) <: Array ? CPU() : CUDADevice()\n\n        qm = polynomialorders(grid) .+ 1\n        toler1 = FT(eps(FT) * vert_range[1] * 2.0) # tolerance for unwarp function\n        toler2 = FT(eps(FT) * 4.0)                 # tolerance\n        # tolerance for Newton-Raphson\n        if isnothing(nr_toler)\n            nr_toler = FT(eps(FT) * vert_range[1] * 10.0)\n        end\n\n        Nel = length(grid.topology.realelems) # # of local elements on the local process\n\n        nvert_range = length(vert_range)\n        nvert = nvert_range - 1              # # of elements in vertical direction\n        Nel_glob = nvert * nhor * nhor * 6\n\n        nblck = nhor * nhor * nvert\n        Δh = 2 / nhor                               # horizontal grid spacing in unwarped grid\n\n        n_lat, n_long, n_rad =\n            Int(length(lat_grd)), Int(length(long_grd)), Int(length(rad_grd))\n\n        Np = n_lat * n_long * n_rad\n\n        uw_grd = zeros(FT, 3)\n        diffv = zeros(FT, 3)\n        ξ = zeros(FT, 3)\n\n        glob_ord = grid.topology.origsendorder # to account for reordering of elements after the partitioning process\n\n        glob_elem_no = zeros(Int, nvert * length(glob_ord))\n\n        for i in 1:length(glob_ord), j in 1:nvert\n            glob_elem_no[j + (i - 1) * nvert] = (glob_ord[i] - 1) * nvert + j\n        end\n        glob_to_loc = Dict(glob_elem_no[i] => Int(i) for i in 1:Nel) # using dictionary for speedup\n\n        ξ1, ξ2, ξ3 = map(i -> zeros(FT, i), zeros(Int, Nel)),\n        map(i -> zeros(FT, i), zeros(Int, Nel)),\n        map(i -> zeros(FT, i), zeros(Int, Nel))\n\n        radi, lati, longi = map(i -> zeros(UInt16, i), zeros(UInt16, Nel)),\n        map(i -> zeros(UInt16, i), zeros(UInt16, Nel)),\n        map(i -> zeros(UInt16, i), zeros(UInt16, Nel))\n\n\n        offset_d = zeros(Int, Nel + 1)\n\n        for i in 1:n_rad\n            rad = rad_grd[i]\n            if rad ≤ vert_range[1]       # accounting for minor rounding errors from unwarp function at boundaries\n                vert_range[1] - rad < toler1 ? l_nrm = 1 :\n                error(\n                    \"fatal error, rad lower than inner radius: \",\n                    vert_range[1] - rad,\n                    \" $rad_grd /// $lat_grd //// $long_grd\",\n                )\n            elseif rad ≥ vert_range[end] # accounting for minor rounding errors from unwarp function at boundaries\n                rad - vert_range[end] < toler1 ? l_nrm = nvert :\n                error(\"fatal error, rad greater than outer radius\")\n            else                         # normal scenario\n                for l in 2:nvert_range\n                    if vert_range[l] - rad > FT(0)\n                        l_nrm = l - 1\n                        break\n                    end\n                end\n            end\n\n            for j in 1:n_lat\n                @inbounds x3_grd = rad * sind(lat_grd[j])\n                for k in 1:n_long\n                    @inbounds x1_grd =\n                        rad * cosd(lat_grd[j]) * cosd(long_grd[k]) # inclination -> latitude; azimuthal -> longitude.\n                    @inbounds x2_grd =\n                        rad * cosd(lat_grd[j]) * sind(long_grd[k]) # inclination -> latitude; azimuthal -> longitude.\n\n                    uw_grd[1], uw_grd[2], uw_grd[3] =\n                        Topologies.cubed_sphere_unwarp(\n                            EquiangularCubedSphere(),\n                            x1_grd,\n                            x2_grd,\n                            x3_grd,\n                        ) # unwarping from sphere to cubed shell\n\n                    x1_uw2_grd = uw_grd[1] / rad # unwrapping cubed shell on to a 2D grid (in 3D space, -1 to 1 cube)\n                    x2_uw2_grd = uw_grd[2] / rad\n                    x3_uw2_grd = uw_grd[3] / rad\n\n                    if abs(x1_uw2_grd + 1) < toler2 # face 1 (x1 == -1 plane)\n                        l2 = min(div(x2_uw2_grd + 1, Δh) + 1, nhor)\n                        l3 = min(div(x3_uw2_grd + 1, Δh) + 1, nhor)\n                        el_glob = Int(\n                            l_nrm +\n                            (nhor - l2) * nvert +\n                            (l3 - 1) * nvert * nhor,\n                        )\n                    elseif abs(x2_uw2_grd + 1) < toler2 # face 2 (x2 == -1 plane)\n                        l1 = min(div(x1_uw2_grd + 1, Δh) + 1, nhor)\n                        l3 = min(div(x3_uw2_grd + 1, Δh) + 1, nhor)\n                        el_glob = Int(\n                            l_nrm +\n                            (l1 - 1) * nvert +\n                            (l3 - 1) * nvert * nhor +\n                            nblck * 1,\n                        )\n                    elseif abs(x1_uw2_grd - 1) < toler2 # face 3 (x1 == +1 plane)\n                        l2 = min(div(x2_uw2_grd + 1, Δh) + 1, nhor)\n                        l3 = min(div(x3_uw2_grd + 1, Δh) + 1, nhor)\n                        el_glob = Int(\n                            l_nrm +\n                            (l2 - 1) * nvert +\n                            (l3 - 1) * nvert * nhor +\n                            nblck * 2,\n                        )\n                    elseif abs(x3_uw2_grd - 1) < toler2 # face 4 (x3 == +1 plane)\n                        l1 = min(div(x1_uw2_grd + 1, Δh) + 1, nhor)\n                        l2 = min(div(x2_uw2_grd + 1, Δh) + 1, nhor)\n                        el_glob = Int(\n                            l_nrm +\n                            (l1 - 1) * nvert +\n                            (l2 - 1) * nvert * nhor +\n                            nblck * 3,\n                        )\n                    elseif abs(x2_uw2_grd - 1) < toler2 # face 5 (x2 == +1 plane)\n                        l1 = min(div(x1_uw2_grd + 1, Δh) + 1, nhor)\n                        l3 = min(div(x3_uw2_grd + 1, Δh) + 1, nhor)\n                        el_glob = Int(\n                            l_nrm +\n                            (l1 - 1) * nvert +\n                            (nhor - l3) * nvert * nhor +\n                            nblck * 4,\n                        )\n                    elseif abs(x3_uw2_grd + 1) < toler2 # face 6 (x3 == -1 plane)\n                        l1 = min(div(x1_uw2_grd + 1, Δh) + 1, nhor)\n                        l2 = min(div(x2_uw2_grd + 1, Δh) + 1, nhor)\n                        el_glob = Int(\n                            l_nrm +\n                            (l1 - 1) * nvert +\n                            (nhor - l2) * nvert * nhor +\n                            nblck * 5,\n                        )\n                    else\n                        error(\"error: unwrapped grid does not lie on any of the 6 faces\")\n                    end\n\n                    el_loc = get(glob_to_loc, el_glob, nothing)\n                    if el_loc ≠ nothing # computing inner coordinates for local elements\n                        invert_trilear_mapping_hex!(\n                            view(grid.topology.elemtocoord, 1, :, el_loc),\n                            view(grid.topology.elemtocoord, 2, :, el_loc),\n                            view(grid.topology.elemtocoord, 3, :, el_loc),\n                            uw_grd,\n                            diffv,\n                            nr_toler,\n                            ξ,\n                        )\n                        push!(ξ1[el_loc], ξ[1])\n                        push!(ξ2[el_loc], ξ[2])\n                        push!(ξ3[el_loc], ξ[3])\n                        push!(radi[el_loc], UInt16(i))\n                        push!(lati[el_loc], UInt16(j))\n                        push!(longi[el_loc], UInt16(k))\n                        offset_d[el_loc + 1] += 1\n                    end\n                end\n            end\n        end\n\n        for i in 2:(Nel + 1)\n            @inbounds offset_d[i] += offset_d[i - 1]\n        end\n\n        Npl = offset_d[Nel + 1]\n\n        v = Vector{FT}(undef, offset_d[Nel + 1]) # Allocating storage for interpolation variable\n\n        ξ1_d = Vector{FT}(undef, Npl)\n        ξ2_d = Vector{FT}(undef, Npl)\n        ξ3_d = Vector{FT}(undef, Npl)\n\n        flg_d = zeros(UInt8, 3, Npl)\n        fac_d = ones(FT, Npl)\n\n        rad_d = Vector{UInt16}(undef, Npl)\n        lat_d = Vector{UInt16}(undef, Npl)\n        long_d = Vector{UInt16}(undef, Npl)\n\n        m_ξ1, m_ξ2, m_ξ3 = referencepoints(grid)\n        wb1, wb2, wb3 = baryweights(m_ξ1), baryweights(m_ξ2), baryweights(m_ξ3)\n        for i in 1:Nel\n            ctr = 1\n            for j in (offset_d[i] + 1):offset_d[i + 1]\n                @inbounds ξ1_d[j] = ξ1[i][ctr]\n                @inbounds ξ2_d[j] = ξ2[i][ctr]\n                @inbounds ξ3_d[j] = ξ3[i][ctr]\n                @inbounds rad_d[j] = radi[i][ctr]\n                @inbounds lat_d[j] = lati[i][ctr]\n                @inbounds long_d[j] = longi[i][ctr]\n                # set up interpolation\n                fac1 = FT(0)\n                fac2 = FT(0)\n                fac3 = FT(0)\n                for ib in 1:qm[1]\n                    if abs(m_ξ1[ib] - ξ1_d[j]) < toler2\n                        @inbounds flg_d[1, j] = UInt8(ib)\n                    else\n                        @inbounds fac1 += wb1[ib] / (ξ1_d[j] - m_ξ1[ib])\n                    end\n                end\n\n                for ib in 1:qm[2]\n                    if abs(m_ξ2[ib] - ξ2_d[j]) < toler2\n                        @inbounds flg_d[2, j] = UInt8(ib)\n                    else\n                        @inbounds fac2 += wb2[ib] / (ξ2_d[j] - m_ξ2[ib])\n                    end\n                end\n\n                for ib in 1:qm[3]\n                    if abs(m_ξ3[ib] - ξ3_d[j]) < toler2\n                        @inbounds flg_d[3, j] = UInt8(ib)\n                    else\n                        @inbounds fac3 += wb3[ib] / (ξ3_d[j] - m_ξ3[ib])\n                    end\n                end\n\n                flg_d[1, j] ≠ 0 && (fac1 = FT(1))\n                flg_d[2, j] ≠ 0 && (fac2 = FT(1))\n                flg_d[3, j] ≠ 0 && (fac3 = FT(1))\n\n                fac_d[j] = FT(1) / (fac1 * fac2 * fac3)\n\n                ctr += 1\n            end\n        end\n        # MPI setup for gathering data on proc 0\n        root = 0\n        Np_all = zeros(Int32, npr)\n        Np_all[pid + 1] = Int32(Npl)\n\n        MPI.Allreduce!(Np_all, +, mpicomm)\n\n        if pid ≠ root\n            radi_all = zeros(UInt16, 0)\n            lati_all = zeros(UInt16, 0)\n            longi_all = zeros(UInt16, 0)\n            MPI.Gatherv!(rad_d, nothing, root, mpicomm)\n            MPI.Gatherv!(lat_d, nothing, root, mpicomm)\n            MPI.Gatherv!(long_d, nothing, root, mpicomm)\n        else\n            radi_all = Array{UInt16}(undef, sum(Np_all))\n            lati_all = Array{UInt16}(undef, sum(Np_all))\n            longi_all = Array{UInt16}(undef, sum(Np_all))\n            MPI.Gatherv!(rad_d, VBuffer(radi_all, Np_all), root, mpicomm)\n            MPI.Gatherv!(lat_d, VBuffer(lati_all, Np_all), root, mpicomm)\n            MPI.Gatherv!(long_d, VBuffer(longi_all, Np_all), root, mpicomm)\n        end\n\n\n        if device isa CUDADevice\n            ξ1_d = DA(ξ1_d)\n            ξ2_d = DA(ξ2_d)\n            ξ3_d = DA(ξ3_d)\n\n            flg_d = DA(flg_d)\n            fac_d = DA(fac_d)\n\n            rad_d = DA(rad_d)\n            lat_d = DA(lat_d)\n            long_d = DA(long_d)\n\n            m_ξ1 = DA(m_ξ1)\n            m_ξ2 = DA(m_ξ2)\n            m_ξ3 = DA(m_ξ3)\n            wb1 = DA(wb1)\n            wb2 = DA(wb2)\n            wb3 = DA(wb3)\n\n            offset_d = DA(offset_d)\n\n            rad_grd = DA(rad_grd)\n            lat_grd = DA(lat_grd)\n            long_grd = DA(long_grd)\n\n            radi_all = DA(radi_all)\n            lati_all = DA(lati_all)\n            longi_all = DA(longi_all)\n        end\n\n        return new{\n            FT,\n            Int,\n            typeof(rad_grd),\n            typeof(ξ1_d),\n            typeof(offset_d),\n            typeof(flg_d),\n            typeof(rad_d),\n            typeof(Np_all),\n        }(\n            Nel,\n            Np,\n            Npl,\n            n_rad,\n            n_lat,\n            n_long,\n            rad_grd,\n            lat_grd,\n            long_grd,\n            ξ1_d,\n            ξ2_d,\n            ξ3_d,\n            flg_d,\n            fac_d,\n            rad_d,\n            lat_d,\n            long_d,\n            offset_d,\n            m_ξ1,\n            m_ξ2,\n            m_ξ3,\n            wb1,\n            wb2,\n            wb3,\n            Np_all,\n            radi_all,\n            lati_all,\n            longi_all,\n        )\n\n    end # Inner constructor InterpolationCubedSphere\n\nend # struct InterpolationCubedSphere\n\n\"\"\"\n    invert_trilear_mapping_hex!(X1::AbstractArray{FT,1},\n                                X2::AbstractArray{FT,1},\n                                X3::AbstractArray{FT,1},\n                                 x::AbstractArray{FT,1},\n                                 d::AbstractArray{FT,1},\n                               tol::FT,\n                                 ξ::AbstractArray{FT,1}) where FT <: AbstractFloat\n\nThis function computes ξ = (ξ1,ξ2,ξ3) given x = (x1,x2,x3) and the (8) vertex coordinates of a Hexahedron. Newton-Raphson method is used.\n\n# Arguments\n - `X1`: X1 coordinates of the (8) vertices of the hexahedron\n - `X2`: X2 coordinates of the (8) vertices of the hexahedron\n - `X3`: X3 coordinates of the (8) vertices of the hexahedron\n - `x`: (x1,x2,x3) coordinates of the point\n - `d`: (x1,x2,x3) coordinates, temporary storage\n - `tol`: Tolerance for convergence\n - `ξ`: (ξ1,ξ2,ξ3) coordinates of the point\n\"\"\"\nfunction invert_trilear_mapping_hex!(\n    X1::AbstractArray{FT, 1},\n    X2::AbstractArray{FT, 1},\n    X3::AbstractArray{FT, 1},\n    x::AbstractArray{FT, 1},\n    d::AbstractArray{FT, 1},\n    tol::FT,\n    ξ::AbstractArray{FT, 1},\n) where {FT <: AbstractFloat}\n    max_it = 10     # maximum # of iterations\n    ξ .= FT(0) #zeros(FT,3,1) # initial guess => cell centroid\n    trilinear_map_minus_x!(ξ, X1, X2, X3, x, d)\n    err = sqrt(d[1] * d[1] + d[2] * d[2] + d[3] * d[3])\n    ctr = 0\n    # Newton-Raphson iterations\n    while err > tol\n        trilinear_map_IJac_x_vec!(ξ, X1, X2, X3, d)\n        ξ .-= d\n        trilinear_map_minus_x!(ξ, X1, X2, X3, x, d)\n        err = sqrt(d[1] * d[1] + d[2] * d[2] + d[3] * d[3]) #norm(d)\n        ctr += 1\n        if ctr > max_it\n            error(\n                \"invert_trilinear_mapping_hex: Newton-Raphson not converging to desired tolerance after max_it = \",\n                max_it,\n                \" iterations; err = \",\n                err,\n                \"; toler = \",\n                tol,\n            )\n        end\n    end\n\n    clamp!(ξ, FT(-1), FT(1))\n    return nothing\nend\n\nfunction trilinear_map_minus_x!(\n    ξ::AbstractArray{FT, 1},\n    x1v::AbstractArray{FT, 1},\n    x2v::AbstractArray{FT, 1},\n    x3v::AbstractArray{FT, 1},\n    x::AbstractArray{FT, 1},\n    d::AbstractArray{FT, 1},\n) where {FT <: AbstractFloat}\n    p1 = 1 + ξ[1]\n    p2 = 1 + ξ[2]\n    p3 = 1 + ξ[3]\n    m1 = 1 - ξ[1]\n    m2 = 1 - ξ[2]\n    m3 = 1 - ξ[3]\n\n\n    d[1] =\n        (\n            m1 * (\n                m2 * (m3 * x1v[1] + p3 * x1v[5]) +\n                p2 * (m3 * x1v[3] + p3 * x1v[7])\n            ) +\n            p1 * (\n                m2 * (m3 * x1v[2] + p3 * x1v[6]) +\n                p2 * (m3 * x1v[4] + p3 * x1v[8])\n            )\n        ) / 8.0 - x[1]\n\n    d[2] =\n        (\n            m1 * (\n                m2 * (m3 * x2v[1] + p3 * x2v[5]) +\n                p2 * (m3 * x2v[3] + p3 * x2v[7])\n            ) +\n            p1 * (\n                m2 * (m3 * x2v[2] + p3 * x2v[6]) +\n                p2 * (m3 * x2v[4] + p3 * x2v[8])\n            )\n        ) / 8.0 - x[2]\n\n    d[3] =\n        (\n            m1 * (\n                m2 * (m3 * x3v[1] + p3 * x3v[5]) +\n                p2 * (m3 * x3v[3] + p3 * x3v[7])\n            ) +\n            p1 * (\n                m2 * (m3 * x3v[2] + p3 * x3v[6]) +\n                p2 * (m3 * x3v[4] + p3 * x3v[8])\n            )\n        ) / 8.0 - x[3]\n\n    return nothing\nend\n\nfunction trilinear_map_IJac_x_vec!(\n    ξ::AbstractArray{FT, 1},\n    x1v::AbstractArray{FT, 1},\n    x2v::AbstractArray{FT, 1},\n    x3v::AbstractArray{FT, 1},\n    v::AbstractArray{FT, 1},\n) where {FT <: AbstractFloat}\n    p1 = 1 + ξ[1]\n    p2 = 1 + ξ[2]\n    p3 = 1 + ξ[3]\n    m1 = 1 - ξ[1]\n    m2 = 1 - ξ[2]\n    m3 = 1 - ξ[3]\n\n    Jac11 =\n        (\n            m2 * (m3 * (x1v[2] - x1v[1]) + p3 * (x1v[6] - x1v[5])) +\n            p2 * (m3 * (x1v[4] - x1v[3]) + p3 * (x1v[8] - x1v[7]))\n        ) / 8.0\n\n    Jac12 =\n        (\n            m1 * (m3 * (x1v[3] - x1v[1]) + p3 * (x1v[7] - x1v[5])) +\n            p1 * (m3 * (x1v[4] - x1v[2]) + p3 * (x1v[8] - x1v[6]))\n        ) / 8.0\n\n    Jac13 =\n        (\n            m1 * (m2 * (x1v[5] - x1v[1]) + p2 * (x1v[7] - x1v[3])) +\n            p1 * (m2 * (x1v[6] - x1v[2]) + p2 * (x1v[8] - x1v[4]))\n        ) / 8.0\n\n    Jac21 =\n        (\n            m2 * (m3 * (x2v[2] - x2v[1]) + p3 * (x2v[6] - x2v[5])) +\n            p2 * (m3 * (x2v[4] - x2v[3]) + p3 * (x2v[8] - x2v[7]))\n        ) / 8.0\n\n    Jac22 =\n        (\n            m1 * (m3 * (x2v[3] - x2v[1]) + p3 * (x2v[7] - x2v[5])) +\n            p1 * (m3 * (x2v[4] - x2v[2]) + p3 * (x2v[8] - x2v[6]))\n        ) / 8.0\n\n    Jac23 =\n        (\n            m1 * (m2 * (x2v[5] - x2v[1]) + p2 * (x2v[7] - x2v[3])) +\n            p1 * (m2 * (x2v[6] - x2v[2]) + p2 * (x2v[8] - x2v[4]))\n        ) / 8.0\n\n    Jac31 =\n        (\n            m2 * (m3 * (x3v[2] - x3v[1]) + p3 * (x3v[6] - x3v[5])) +\n            p2 * (m3 * (x3v[4] - x3v[3]) + p3 * (x3v[8] - x3v[7]))\n        ) / 8.0\n\n    Jac32 =\n        (\n            m1 * (m3 * (x3v[3] - x3v[1]) + p3 * (x3v[7] - x3v[5])) +\n            p1 * (m3 * (x3v[4] - x3v[2]) + p3 * (x3v[8] - x3v[6]))\n        ) / 8.0\n\n    Jac33 =\n        (\n            m1 * (m2 * (x3v[5] - x3v[1]) + p2 * (x3v[7] - x3v[3])) +\n            p1 * (m2 * (x3v[6] - x3v[2]) + p2 * (x3v[8] - x3v[4]))\n        ) / 8.0\n\n    # computing cofactor matrix\n    C11 = Jac22 * Jac33 - Jac23 * Jac32\n    C12 = -Jac21 * Jac33 + Jac23 * Jac31\n    C13 = Jac21 * Jac32 - Jac22 * Jac31\n    C21 = -Jac12 * Jac33 + Jac13 * Jac32\n    C22 = Jac11 * Jac33 - Jac13 * Jac31\n    C23 = -Jac11 * Jac32 + Jac12 * Jac31\n    C31 = Jac12 * Jac23 - Jac13 * Jac22\n    C32 = -Jac11 * Jac23 + Jac13 * Jac21\n    C33 = Jac11 * Jac22 - Jac12 * Jac21\n\n    # computing determinant\n    det = Jac11 * C11 + Jac12 * C12 + Jac13 * C13\n\n    Jac11 = (C11 * v[1] + C21 * v[2] + C31 * v[3]) / det\n    Jac21 = (C12 * v[1] + C22 * v[2] + C32 * v[3]) / det\n    Jac31 = (C13 * v[1] + C23 * v[2] + C33 * v[3]) / det\n\n    v[1] = Jac11\n    v[2] = Jac21\n    v[3] = Jac31\n\n    return nothing\nend\n\n\"\"\"\n    interpolate_local!(intrp_cs::InterpolationCubedSphere{FT},\n                             sv::AbstractArray{FT},\n                              v::AbstractArray{FT}) where {FT <: AbstractFloat}\n\nThis interpolation function works for cubed spherical shell geometry.\n\n# Arguments\n - `intrp_cs`: Initialized cubed sphere structure\n - `sv`: Array consisting of various variables on the discontinuous Galerkin grid\n - `v`:  Array consisting of variables on the interpolated grid\n\"\"\"\nfunction interpolate_local!(\n    intrp_cs::InterpolationCubedSphere{FT},\n    sv::AbstractArray{FT},\n    v::AbstractArray{FT},\n) where {FT <: AbstractFloat}\n\n    offset = intrp_cs.offset\n    m_ξ1 = intrp_cs.m_ξ1\n    m_ξ2 = intrp_cs.m_ξ2\n    m_ξ3 = intrp_cs.m_ξ3\n    wb1 = intrp_cs.wb1\n    wb2 = intrp_cs.wb2\n    wb3 = intrp_cs.wb3\n    ξ1 = intrp_cs.ξ1\n    ξ2 = intrp_cs.ξ2\n    ξ3 = intrp_cs.ξ3\n    flg = intrp_cs.flg\n    fac = intrp_cs.fac\n\n    qm = (length(m_ξ1), length(m_ξ2), length(m_ξ3))\n    nvars = size(sv, 2)\n    Nel = length(offset) - 1\n    np_tot = size(v, 1)\n\n    device = array_device(sv)\n    comp_stream = Event(device)\n\n    workgroup = (qm[2], qm[3])\n    ndrange = (qm[2] * Nel, qm[3] * nvars)\n\n    comp_stream = interpolate_local_kernel!(device, workgroup)(\n        offset,\n        m_ξ1,\n        m_ξ2,\n        m_ξ3,\n        wb1,\n        wb2,\n        wb3,\n        ξ1,\n        ξ2,\n        ξ3,\n        flg,\n        fac,\n        sv,\n        v,\n        Val(qm),\n        ndrange = ndrange,\n        dependencies = (comp_stream,),\n    )\n    wait(comp_stream)\n\n    return nothing\nend\n\n\"\"\"\n    project_cubed_sphere!(intrp_cs::InterpolationCubedSphere{FT},\n                                 v::AbstractArray{FT},\n                              uvwi::Tuple{Int,Int,Int}) where {FT <: AbstractFloat}\n\nThis function projects the velocity field along unit vectors in radial, lat and long directions for cubed spherical shell geometry.\n\n# Fields\n - `intrp_cs`: Initialized cubed sphere structure\n - `v`: Array consisting of x1, x2 and x3 components of the vector field\n - `uvwi`:  Tuple providing the column numbers for x1, x2 and x3 components of vector field in the array.\n            These columns will be replaced with projected vector fields along unit vectors in long, lat and rad directions.\n\"\"\"\nfunction project_cubed_sphere!(\n    intrp_cs::InterpolationCubedSphere{FT},\n    v::AbstractArray{FT},\n    uvwi::Tuple{Int, Int, Int},\n) where {FT <: AbstractFloat}\n    # projecting velocity onto unit vectors in long, lat and rad directions\n    # assumes u, v and w are located in columns specified in vector uvwi\n    @assert length(uvwi) == 3 \"length(uvwi) is not 3\"\n    lati = intrp_cs.lati\n    longi = intrp_cs.longi\n    lat_grd = intrp_cs.lat_grd\n    long_grd = intrp_cs.long_grd\n    _ρu = uvwi[1]\n    _ρv = uvwi[2]\n    _ρw = uvwi[3]\n    np_tot = size(v, 1)\n\n    device = array_device(v)\n    comp_stream = Event(device)\n\n    thr_x = min(256, np_tot)\n\n    workgroup = (thr_x,)\n    ndrange = (np_tot,)\n\n    comp_stream = project_cubed_sphere_kernel!(device, workgroup)(\n        lat_grd,\n        long_grd,\n        lati,\n        longi,\n        v,\n        _ρu,\n        _ρv,\n        _ρw,\n        ndrange = ndrange,\n        dependencies = (comp_stream,),\n    )\n    wait(comp_stream)\n    return nothing\nend\n\n@kernel function project_cubed_sphere_kernel!(\n    lat_grd::AbstractArray{FT, 1},\n    long_grd::AbstractArray{FT, 1},\n    lati::AbstractVector{UInt16},\n    longi::AbstractVector{UInt16},\n    v::AbstractArray{FT},\n    _ρu::Int,\n    _ρv::Int,\n    _ρw::Int,\n) where {FT <: AbstractFloat}\n\n    idx = @index(Global, Linear) # global thread ids\n    # projecting velocity onto unit vectors in long, lat and rad directions\n    # assumed u, v and w are located in columns 2, 3 and 4\n    deg2rad = FT(π) / FT(180)\n    vrad =\n        v[idx, _ρu] *\n        cos(lat_grd[lati[idx]] * deg2rad) *\n        cos(long_grd[longi[idx]] * deg2rad) +\n        v[idx, _ρv] *\n        cos(lat_grd[lati[idx]] * deg2rad) *\n        sin(long_grd[longi[idx]] * deg2rad) +\n        v[idx, _ρw] * sin(lat_grd[lati[idx]] * deg2rad)\n\n    vlat =\n        -v[idx, _ρu] *\n        sin(lat_grd[lati[idx]] * deg2rad) *\n        cos(long_grd[longi[idx]] * deg2rad) -\n        v[idx, _ρv] *\n        sin(lat_grd[lati[idx]] * deg2rad) *\n        sin(long_grd[longi[idx]] * deg2rad) +\n        v[idx, _ρw] * cos(lat_grd[lati[idx]] * deg2rad)\n\n    vlon =\n        -v[idx, _ρu] * sin(long_grd[longi[idx]] * deg2rad) +\n        v[idx, _ρv] * cos(long_grd[longi[idx]] * deg2rad)\n\n    v[idx, _ρu] = vlon\n    v[idx, _ρv] = vlat\n    v[idx, _ρw] = vrad\n    # TODO: cosd / sind having issues on GPU. Unable to isolate the issue at this point. Needs to be revisited.\nend\n\nfunction dimensions(interpol::InterpolationCubedSphere)\n    if Array ∈ typeof(interpol.rad_grd).parameters\n        h_long_grd = interpol.long_grd\n        h_lat_grd = interpol.lat_grd\n        h_rad_grd = interpol.rad_grd\n    else\n        h_long_grd = Array(interpol.long_grd)\n        h_lat_grd = Array(interpol.lat_grd)\n        h_rad_grd = Array(interpol.rad_grd)\n    end\n    FT = eltype(h_rad_grd)\n    return OrderedDict(\n        \"long\" => (\n            h_long_grd,\n            OrderedDict(\"units\" => \"degrees_east\", \"long_name\" => \"longitude\"),\n        ),\n        \"lat\" => (\n            h_lat_grd,\n            OrderedDict(\"units\" => \"degrees_north\", \"long_name\" => \"latitude\"),\n        ),\n        \"level\" =>\n            (h_rad_grd, OrderedDict(\"units\" => \"m\", \"long_name\" => \"level\")),\n    )\nend\n\n\"\"\"\n    accumulate_interpolated_data!(intrp::InterpolationTopology,\n                                     iv::AbstractArray{FT,2},\n                                    fiv::AbstractArray{FT,4}) where {FT <: AbstractFloat}\n\nThis interpolation function gathers interpolated data onto process # 0.\n\n# Fields\n - `intrp`: Initialized interpolation topology structure\n - `iv`: Interpolated variables on local process\n - `fiv`: Full interpolated variables accumulated on process # 0\n\"\"\"\nfunction accumulate_interpolated_data!(\n    intrp::InterpolationTopology,\n    iv::AbstractArray{FT, 2},\n    fiv::AbstractArray{FT, 4},\n) where {FT <: AbstractFloat}\n\n    device = array_device(iv)\n    mpicomm = MPI.COMM_WORLD\n    pid = MPI.Comm_rank(mpicomm)\n    npr = MPI.Comm_size(mpicomm)\n    root = 0\n    nvars = size(iv, 2)\n\n    if intrp isa InterpolationCubedSphere\n        nx1 = length(intrp.long_grd)\n        nx2 = length(intrp.lat_grd)\n        nx3 = length(intrp.rad_grd)\n        np_tot = length(intrp.radi_all)\n        i1 = intrp.longi_all\n        i2 = intrp.lati_all\n        i3 = intrp.radi_all\n    elseif intrp isa InterpolationBrick\n        nx1 = length(intrp.x1g)\n        nx2 = length(intrp.x2g)\n        nx3 = length(intrp.x3g)\n        np_tot = length(intrp.x1i_all)\n        i1 = intrp.x1i_all\n        i2 = intrp.x2i_all\n        i3 = intrp.x3i_all\n    else\n        error(\"Unsupported topology; only InterpolationCubedSphere and InterpolationBrick supported\")\n    end\n\n    if pid == 0 && size(fiv) ≠ (nx1, nx2, nx3, nvars)\n        error(\"size of fiv = $(size(fiv)); which does not match with ($nx1,$nx2,$nx3,$nvars) \")\n    end\n\n    if npr > 1\n        Np_all = intrp.Np_all\n        pid == 0 ? v_all = Array{FT}(undef, np_tot, nvars) :\n        v_all = Array{FT}(undef, 0, nvars)\n        if device isa CPU\n\n            for vari in 1:nvars\n                MPI.Gatherv!(\n                    view(iv, :, vari),\n                    pid == root ? VBuffer(view(v_all, :, vari), Np_all) :\n                    nothing,\n                    root,\n                    mpicomm,\n                )\n            end\n\n        elseif device isa CUDADevice\n\n            v = Array(iv)\n            for vari in 1:nvars\n                MPI.Gatherv!(\n                    view(v, :, vari),\n                    pid == root ? VBuffer(view(v_all, :, vari), Np_all) :\n                    nothing,\n                    root,\n                    mpicomm,\n                )\n            end\n            v_all = CuArray(v_all)\n\n        else\n            error(\"accumulate_interpolate_data: unsupported device, only CPU() and CUDADevice() supported\")\n        end\n    else\n        v_all = iv\n    end\n\n    if pid == 0\n        comp_stream = Event(device)\n        thr_x = min(256, np_tot)\n        workgroup = (thr_x,)\n        ndrange = (np_tot,)\n\n        comp_stream = accumulate_helper_kernel!(device, workgroup)(\n            i1,\n            i2,\n            i3,\n            v_all,\n            fiv,\n            ndrange = ndrange,\n            dependencies = (comp_stream,),\n        )\n        wait(comp_stream)\n    end\n    MPI.Barrier(mpicomm)\n    return nothing\nend\n\n@kernel function accumulate_helper_kernel!(\n    i1::AbstractArray{UInt16, 1},\n    i2::AbstractArray{UInt16, 1},\n    i3::AbstractArray{UInt16, 1},\n    v_all::AbstractArray{FT, 2},\n    fiv::AbstractArray{FT, 4},\n) where {FT <: AbstractFloat}\n    idx = @index(Global, Linear)\n    nvars = size(v_all, 2)\n\n    for vari in 1:nvars\n        @inbounds fiv[i1[idx], i2[idx], i3[idx], vari] = v_all[idx, vari]\n    end\nend\n\nfunction accumulate_interpolated_data(\n    mpicomm::MPI.Comm,\n    intrp::InterpolationTopology,\n    iv::AbstractArray{FT, 2},\n) where {FT <: AbstractFloat}\n\n    mpirank = MPI.Comm_rank(mpicomm)\n    numranks = MPI.Comm_size(mpicomm)\n    nvars = size(iv, 2)\n\n    if intrp isa InterpolationCubedSphere\n        nx1 = length(intrp.long_grd)\n        nx2 = length(intrp.lat_grd)\n        nx3 = length(intrp.rad_grd)\n        np_tot = length(intrp.radi_all)\n        i1 = intrp.longi_all\n        i2 = intrp.lati_all\n        i3 = intrp.radi_all\n    elseif intrp isa InterpolationBrick\n        nx1 = length(intrp.x1g)\n        nx2 = length(intrp.x2g)\n        nx3 = length(intrp.x3g)\n        np_tot = length(intrp.x1i_all)\n        i1 = intrp.x1i_all\n        i2 = intrp.x2i_all\n        i3 = intrp.x3i_all\n    else\n        error(\"Unsupported topology; only InterpolationCubedSphere and InterpolationBrick supported\")\n    end\n\n    if array_device(iv) isa CPU\n        h_iv = iv\n        h_i1 = i1\n        h_i2 = i2\n        h_i3 = i3\n    else\n        h_iv = Array(iv)\n        h_i1 = Array(i1)\n        h_i2 = Array(i2)\n        h_i3 = Array(i3)\n    end\n\n    if numranks == 1\n        v_all = h_iv\n    else\n        v_all = Array{FT}(undef, mpirank == 0 ? np_tot : 0, nvars)\n        for vari in 1:nvars\n            MPI.Gatherv!(\n                view(h_iv, :, vari),\n                mpirank == 0 ? VBuffer(view(v_all, :, vari), intrp.Np_all) :\n                nothing,\n                0,\n                mpicomm,\n            )\n        end\n    end\n\n    if mpirank == 0\n        fiv = Array{FT}(undef, nx1, nx2, nx3, nvars)\n        for i in 1:np_tot\n            for vari in 1:nvars\n                @inbounds fiv[h_i1[i], h_i2[i], h_i3[i], vari] = v_all[i, vari]\n            end\n        end\n    else\n        fiv = nothing\n    end\n\n    return fiv\nend\n\nend # module Interpolation\n"
  },
  {
    "path": "src/Numerics/Mesh/Mesh.jl",
    "content": "module Mesh\ninclude(\"BrickMesh.jl\")\ninclude(\"Topologies.jl\")\ninclude(\"GeometricFactors.jl\")\ninclude(\"Metrics.jl\")\ninclude(\"Elements.jl\")\ninclude(\"Grids.jl\")\ninclude(\"DSS.jl\")\ninclude(\"Filters.jl\")\ninclude(\"Geometry.jl\")\ninclude(\"Interpolation.jl\")\nend # module\n"
  },
  {
    "path": "src/Numerics/Mesh/Metrics.jl",
    "content": "module Metrics\n\nusing ..GeometricFactors\n\nexport creategrid, compute_reference_to_physical_coord_jacobian, computemetric\n\n\"\"\"\n    creategrid!(vgeo, elemtocoord, ξ)\n\nCreate a 1-D grid using `elemtocoord` (see `brickmesh`) using the 1-D\n`(-1, 1)` reference coordinates `ξ` (in 1D, `ξ = ξ1`). The element grids\nare filled using linear interpolation of the element coordinates.\n\nIf `Nq = length(ξ)` and `nelem = size(elemtocoord, 3)` then the preallocated\narray `vgeo.x1` should be `Nq * nelem == length(x1)`.\n\"\"\"\nfunction creategrid!(\n    vgeo::VolumeGeometry{Nq, <:AbstractArray, <:AbstractArray},\n    e2c,\n    ξ::NTuple{1, Vector{FT}},\n) where {Nq, FT}\n    (d, nvert, nelem) = size(e2c)\n    @assert d == 1\n    (ξ1,) = ξ\n    x1 = reshape(vgeo.x1, (Nq..., nelem))\n\n    # Linear blend\n    @inbounds for e in 1:nelem\n        for i in 1:Nq[1]\n            vgeo.x1[i, e] =\n                ((1 - ξ1[i]) * e2c[1, 1, e] + (1 + ξ1[i]) * e2c[1, 2, e]) / 2\n        end\n    end\n    nothing\nend\n\n\"\"\"\n    creategrid!(vgeo, elemtocoord, ξ)\n\nCreate a 2-D tensor product grid using `elemtocoord` (see `brickmesh`)\nusing the tuple `ξ = (ξ1, ξ2)`, composed by the 1D reference coordinates `ξ1` and `ξ2` in `(-1, 1)^2`.\nThe element grids are filled using bilinear interpolation of the element coordinates.\n\nIf `Nq = (length(ξ1), length(ξ2))` and `nelem = size(elemtocoord, 3)` then the\npreallocated arrays `vgeo.x1` and `vgeo.x2` should be\n`prod(Nq) * nelem == size(vgeo.x1) == size(vgeo.x2)`.\n\"\"\"\nfunction creategrid!(\n    vgeo::VolumeGeometry{Nq, <:AbstractArray, <:AbstractArray},\n    e2c,\n    ξ::NTuple{2, Vector{FT}},\n) where {Nq, FT}\n    (d, nvert, nelem) = size(e2c)\n    @assert d == 2\n    (ξ1, ξ2) = ξ\n    x1 = reshape(vgeo.x1, (Nq..., nelem))\n    x2 = reshape(vgeo.x2, (Nq..., nelem))\n\n    # Bilinear blend of corners\n    @inbounds for (f, n) in zip((x1, x2), 1:d)\n        for e in 1:nelem, j in 1:Nq[2], i in 1:Nq[1]\n            f[i, j, e] =\n                (\n                    (1 - ξ1[i]) * (1 - ξ2[j]) * e2c[n, 1, e] +\n                    (1 + ξ1[i]) * (1 - ξ2[j]) * e2c[n, 2, e] +\n                    (1 - ξ1[i]) * (1 + ξ2[j]) * e2c[n, 3, e] +\n                    (1 + ξ1[i]) * (1 + ξ2[j]) * e2c[n, 4, e]\n                ) / 4\n        end\n    end\n    nothing\nend\n\n\"\"\"\n    creategrid!(vgeo, elemtocoord, ξ)\n\nCreate a 3-D tensor product grid using `elemtocoord` (see `brickmesh`)\nusing the tuple `ξ = (ξ1, ξ2, ξ3)`, composed by the 1D reference coordinates `ξ1`, `ξ2`, `ξ3` in `(-1, 1)^3`.\nThe element grids are filled using trilinear interpolation of the element coordinates.\n\nIf `Nq = (length(ξ1), length(ξ2), length(ξ3))` and\n`nelem = size(elemtocoord, 3)` then the preallocated arrays `vgeo.x1`, `vgeo.x2`,\nand `vgeo.x3` should be `prod(Nq) * nelem == size(vgeo.x1) == size(vgeo.x2) == size(vgeo.x3)`.\n\"\"\"\nfunction creategrid!(\n    vgeo::VolumeGeometry{Nq, <:AbstractArray, <:AbstractArray},\n    e2c,\n    ξ::NTuple{3, Vector{FT}},\n) where {Nq, FT}\n    (d, nvert, nelem) = size(e2c)\n    @assert d == 3\n    (ξ1, ξ2, ξ3) = ξ\n    x1 = reshape(vgeo.x1, (Nq..., nelem))\n    x2 = reshape(vgeo.x2, (Nq..., nelem))\n    x3 = reshape(vgeo.x3, (Nq..., nelem))\n\n    # Trilinear blend of corners\n    @inbounds for (f, n) in zip((x1, x2, x3), 1:d)\n        for e in 1:nelem, k in 1:Nq[3], j in 1:Nq[2], i in 1:Nq[1]\n            f[i, j, k, e] =\n                (\n                    (1 - ξ1[i]) * (1 - ξ2[j]) * (1 - ξ3[k]) * e2c[n, 1, e] +\n                    (1 + ξ1[i]) * (1 - ξ2[j]) * (1 - ξ3[k]) * e2c[n, 2, e] +\n                    (1 - ξ1[i]) * (1 + ξ2[j]) * (1 - ξ3[k]) * e2c[n, 3, e] +\n                    (1 + ξ1[i]) * (1 + ξ2[j]) * (1 - ξ3[k]) * e2c[n, 4, e] +\n                    (1 - ξ1[i]) * (1 - ξ2[j]) * (1 + ξ3[k]) * e2c[n, 5, e] +\n                    (1 + ξ1[i]) * (1 - ξ2[j]) * (1 + ξ3[k]) * e2c[n, 6, e] +\n                    (1 - ξ1[i]) * (1 + ξ2[j]) * (1 + ξ3[k]) * e2c[n, 7, e] +\n                    (1 + ξ1[i]) * (1 + ξ2[j]) * (1 + ξ3[k]) * e2c[n, 8, e]\n                ) / 8\n        end\n    end\n    nothing\nend\n\n\"\"\"\n    compute_reference_to_physical_coord_jacobian!(vgeo, nelem, D)\n\nInput arguments:\n- vgeo::VolumeGeometry, a struct containing the volumetric geometric factors\n- D::NTuple{2,Int}, a tuple of derivative matrices, i.e., D = (D1,), where:\n    - D1::DAT2, 1-D derivative operator on the device in the first dimension\n\nCompute the Jacobian matrix, ∂x / ∂ξ, of the transformation from reference coordinates,\n`ξ1`, to physical coordinates, `vgeo.x1`, for each quadrature point in element e.\n\"\"\"\nfunction compute_reference_to_physical_coord_jacobian!(\n    vgeo::VolumeGeometry{Nq, <:AbstractArray, <:AbstractArray},\n    nelem,\n    D::NTuple{1, Matrix{FT}},\n) where {Nq, FT}\n\n    @assert Nq == map(d -> size(d, 1), D)\n\n    T = eltype(vgeo.x1)\n    (D1,) = D\n\n    vgeo.x1ξ1 .= zero(T)\n\n    for e in 1:nelem\n        for i in 1:Nq[1]\n            for n in 1:Nq[1]\n                vgeo.x1ξ1[i, e] += D1[i, n] * vgeo.x1[n, e]\n            end\n        end\n    end\n\n    return vgeo\nend\n\n\"\"\"\n    compute_reference_to_physical_coord_jacobian!(vgeo, nelem, D)\n\nInput arguments:\n- vgeo::VolumeGeometry, a struct containing the volumetric geometric factors\n- D::NTuple{2,Int}, a tuple of derivative matrices, i.e., D = (D1, D2), where:\n    - D1::DAT2, 1-D derivative operator on the device in the first dimension\n    - D2::DAT2, 1-D derivative operator on the device in the second dimension\n\nCompute the Jacobian matrix, ∂x / ∂ξ, of the transformation from reference coordinates,\n`ξ1`, `ξ2`, to physical coordinates, `vgeo.x1`, `vgeo.x2`,\nfor each quadrature point in element e.\n\"\"\"\nfunction compute_reference_to_physical_coord_jacobian!(\n    vgeo::VolumeGeometry{Nq, <:AbstractArray, <:AbstractArray},\n    nelem,\n    D::NTuple{2, Matrix{FT}},\n) where {Nq, FT}\n\n    @assert Nq == map(d -> size(d, 1), D)\n\n    T = eltype(vgeo.x1)\n    (D1, D2) = D\n\n    x1 = reshape(vgeo.x1, (Nq..., nelem))\n    x2 = reshape(vgeo.x2, (Nq..., nelem))\n    x1ξ1 = reshape(vgeo.x1ξ1, (Nq..., nelem))\n    x2ξ1 = reshape(vgeo.x2ξ1, (Nq..., nelem))\n    x1ξ2 = reshape(vgeo.x1ξ2, (Nq..., nelem))\n    x2ξ2 = reshape(vgeo.x2ξ2, (Nq..., nelem))\n\n    x1ξ1 .= x1ξ2 .= zero(T)\n    x2ξ1 .= x2ξ2 .= zero(T)\n\n    for e in 1:nelem\n        for j in 1:Nq[2], i in 1:Nq[1]\n            for n in 1:Nq[1]\n                x1ξ1[i, j, e] += D1[i, n] * x1[n, j, e]\n                x2ξ1[i, j, e] += D1[i, n] * x2[n, j, e]\n            end\n            for n in 1:Nq[2]\n                x1ξ2[i, j, e] += D2[j, n] * x1[i, n, e]\n                x2ξ2[i, j, e] += D2[j, n] * x2[i, n, e]\n            end\n        end\n    end\n\n    return vgeo\nend\n\n\"\"\"\n    compute_reference_to_physical_coord_jacobian!(vgeo, nelem, D)\n\nInput arguments:\n- vgeo::VolumeGeometry, a struct containing the volumetric geometric factors\n- D::NTuple{3,Int}, a tuple of derivative matrices, i.e., D = (D1, D2, D3), where:\n    - D1::DAT2, 1-D derivative operator on the device in the first dimension\n    - D2::DAT2, 1-D derivative operator on the device in the second dimension\n    - D3::DAT2, 1-D derivative operator on the device in the third dimension\n\nCompute the Jacobian matrix, ∂x / ∂ξ, of the transformation from reference coordinates,\n`ξ1`, `ξ2`, `ξ3` to physical coordinates, `vgeo.x1`, `vgeo.x2`, `vgeo.x3` for\neach quadrature point in element e.\n\"\"\"\nfunction compute_reference_to_physical_coord_jacobian!(\n    vgeo::VolumeGeometry{Nq, <:AbstractArray, <:AbstractArray},\n    nelem,\n    D::NTuple{3, Matrix{FT}},\n) where {Nq, FT}\n\n    @assert Nq == map(d -> size(d, 1), D)\n\n    T = eltype(vgeo.x1)\n    (D1, D2, D3) = D\n\n    x1 = reshape(vgeo.x1, (Nq..., nelem))\n    x2 = reshape(vgeo.x2, (Nq..., nelem))\n    x3 = reshape(vgeo.x3, (Nq..., nelem))\n    x1ξ1 = reshape(vgeo.x1ξ1, (Nq..., nelem))\n    x2ξ1 = reshape(vgeo.x2ξ1, (Nq..., nelem))\n    x3ξ1 = reshape(vgeo.x3ξ1, (Nq..., nelem))\n    x1ξ2 = reshape(vgeo.x1ξ2, (Nq..., nelem))\n    x2ξ2 = reshape(vgeo.x2ξ2, (Nq..., nelem))\n    x3ξ2 = reshape(vgeo.x3ξ2, (Nq..., nelem))\n    x1ξ3 = reshape(vgeo.x1ξ3, (Nq..., nelem))\n    x2ξ3 = reshape(vgeo.x2ξ3, (Nq..., nelem))\n    x3ξ3 = reshape(vgeo.x3ξ3, (Nq..., nelem))\n\n    x1ξ1 .= x1ξ2 .= x1ξ3 .= zero(T)\n    x2ξ1 .= x2ξ2 .= x2ξ3 .= zero(T)\n    x3ξ1 .= x3ξ2 .= x3ξ3 .= zero(T)\n\n    @inbounds for e in 1:nelem\n        for k in 1:Nq[3], j in 1:Nq[2], i in 1:Nq[1]\n            for n in 1:Nq[1]\n                x1ξ1[i, j, k, e] += D1[i, n] * x1[n, j, k, e]\n                x2ξ1[i, j, k, e] += D1[i, n] * x2[n, j, k, e]\n                x3ξ1[i, j, k, e] += D1[i, n] * x3[n, j, k, e]\n            end\n            for n in 1:Nq[2]\n                x1ξ2[i, j, k, e] += D2[j, n] * x1[i, n, k, e]\n                x2ξ2[i, j, k, e] += D2[j, n] * x2[i, n, k, e]\n                x3ξ2[i, j, k, e] += D2[j, n] * x3[i, n, k, e]\n            end\n            for n in 1:Nq[3]\n                x1ξ3[i, j, k, e] += D3[k, n] * x1[i, j, n, e]\n                x2ξ3[i, j, k, e] += D3[k, n] * x2[i, j, n, e]\n                x3ξ3[i, j, k, e] += D3[k, n] * x3[i, j, n, e]\n            end\n        end\n    end\n\n    return vgeo\nend\n\n\"\"\"\n    computemetric!(vgeo, sgeo, D)\n\nInput arguments:\n- vgeo::VolumeGeometry, a struct containing the volumetric geometric factors\n- sgeo::SurfaceGeometry, a struct containing the surface geometric factors\n- D::NTuple{1,Int}, tuple with 1-D derivative operator on the device\n\nCompute the 1-D metric terms from the element grid arrays `vgeo.x1`. All the arrays\nare preallocated by the user and the (square) derivative matrix `D` should be\nconsistent with the reference grid `ξ1` used in [`creategrid!`](@ref).\n\nIf `Nq = size(D, 1)` and `nelem = div(length(x1), Nq)` then the volume arrays\n`x1`, `J`, and `ξ1x1` should all have length `Nq * nelem`.  Similarly, the face\narrays `sJ` and `n1` should be of length `nface * nelem` with `nface = 2`.\n\"\"\"\nfunction computemetric!(\n    vgeo::VolumeGeometry{Nq, <:AbstractArray, <:AbstractArray},\n    sgeo::SurfaceGeometry{Nfp, <:AbstractArray},\n    D::NTuple{1, Matrix{FT}},\n) where {Nq, Nfp, FT}\n\n    @assert Nq == map(d -> size(d, 1), D)\n\n    nelem = div(length(vgeo.ωJ), Nq[1])\n    ωJ = reshape(vgeo.ωJ, (Nq[1], nelem))\n    nface = 2\n    n1 = reshape(sgeo.n1, (1, nface, nelem))\n    sωJ = reshape(sgeo.sωJ, (1, nface, nelem))\n\n    # Compute vertical Jacobian determinant, JcV, and Jacobian determinant, det(∂x/∂ξ), per quadrature point\n    vgeo.JcV .= vgeo.x1ξ1\n    vgeo.ωJ .= vgeo.x1ξ1\n\n    vgeo.ξ1x1 .= 1 ./ vgeo.ωJ\n\n    sgeo.n1[1, 1, :] .= -sign.(ωJ[1, :])\n    sgeo.n1[1, 2, :] .= sign.(ωJ[Nq[1], :])\n    sgeo.sωJ .= 1\n    nothing\nend\n\n\"\"\"\n    computemetric!(vgeo, sgeo, D)\n\nInput arguments:\n- vgeo::VolumeGeometry, a struct containing the volumetric geometric factors\n- sgeo::SurfaceGeometry, a struct containing the surface geometric factors\n- D::NTuple{2,Int}, a tuple of derivative matrices, i.e., D = (D1, D2), where:\n    - D1::DAT2, 1-D derivative operator on the device in the first dimension\n    - D2::DAT2, 1-D derivative operator on the device in the second dimension\n\nCompute the 2-D metric terms from the element grid arrays `vgeo.x1` and `vgeo.x2`. All the\narrays are preallocated by the user and the (square) derivative matrice `D1` and\n`D2` should be consistent with the reference grid `ξ1` and `ξ2` used in\n[`creategrid!`](@ref).\n\nIf `Nq = (size(D1, 1), size(D2, 1))` and `nelem = div(length(vgeo.x1), prod(Nq))`\nthen the volume arrays `vgeo.x1`, `vgeo.x2`, `vgeo.ωJ`, `vgeo.ξ1x1`, `vgeo.ξ2x1`, `vgeo.ξ1x2`, and `vgeo.ξ2x2`\nshould all be of size `(Nq..., nelem)`.  Similarly, the face arrays `sgeo.sωJ`, `sgeo.n1`,\nand `sgeo.n2` should be of size `(maximum(Nq), nface, nelem)` with `nface = 4`\n\"\"\"\nfunction computemetric!(\n    vgeo::VolumeGeometry{Nq, <:AbstractArray, <:AbstractArray},\n    sgeo::SurfaceGeometry{Nfp, <:AbstractArray},\n    D::NTuple{2, Matrix{FT}},\n) where {Nq, Nfp, FT}\n\n    @assert Nq == map(d -> size(d, 1), D)\n    @assert Nfp == div.(prod(Nq), Nq)\n\n    nelem = div(length(vgeo.ωJ), prod(Nq))\n    x1 = reshape(vgeo.x1, (Nq..., nelem))\n    x2 = reshape(vgeo.x2, (Nq..., nelem))\n    ωJ = reshape(vgeo.ωJ, (Nq..., nelem))\n    JcV = reshape(vgeo.JcV, (Nq..., nelem))\n    ξ1x1 = reshape(vgeo.ξ1x1, (Nq..., nelem))\n    ξ2x1 = reshape(vgeo.ξ2x1, (Nq..., nelem))\n    ξ1x2 = reshape(vgeo.ξ1x2, (Nq..., nelem))\n    ξ2x2 = reshape(vgeo.ξ2x2, (Nq..., nelem))\n    x1ξ1 = reshape(vgeo.x1ξ1, (Nq..., nelem))\n    x1ξ2 = reshape(vgeo.x1ξ2, (Nq..., nelem))\n    x2ξ1 = reshape(vgeo.x2ξ1, (Nq..., nelem))\n    x2ξ2 = reshape(vgeo.x2ξ2, (Nq..., nelem))\n    nface = 4\n    n1 = reshape(sgeo.n1, (maximum(Nfp), nface, nelem))\n    n2 = reshape(sgeo.n2, (maximum(Nfp), nface, nelem))\n    sωJ = reshape(sgeo.sωJ, (maximum(Nfp), nface, nelem))\n\n    for e in 1:nelem\n        for j in 1:Nq[2], i in 1:Nq[1]\n\n            # Compute vertical Jacobian determinant, JcV, per quadrature point\n            JcV[i, j, e] = hypot(x1ξ2[i, j, e], x2ξ2[i, j, e])\n            # Compute Jacobian determinant, det(∂x/∂ξ), per quadrature point\n            ωJ[i, j, e] =\n                x1ξ1[i, j, e] * x2ξ2[i, j, e] - x2ξ1[i, j, e] * x1ξ2[i, j, e]\n\n            ξ1x1[i, j, e] = x2ξ2[i, j, e] / ωJ[i, j, e]\n            ξ2x1[i, j, e] = -x2ξ1[i, j, e] / ωJ[i, j, e]\n            ξ1x2[i, j, e] = -x1ξ2[i, j, e] / ωJ[i, j, e]\n            ξ2x2[i, j, e] = x1ξ1[i, j, e] / ωJ[i, j, e]\n        end\n\n        # Compute surface struct field entries\n        for i in 1:maximum(Nfp)\n            if i <= Nfp[1]\n                sgeo.n1[i, 1, e] = -ωJ[1, i, e] * ξ1x1[1, i, e]\n                sgeo.n2[i, 1, e] = -ωJ[1, i, e] * ξ1x2[1, i, e]\n                sgeo.n1[i, 2, e] = ωJ[Nq[1], i, e] * ξ1x1[Nq[1], i, e]\n                sgeo.n2[i, 2, e] = ωJ[Nq[1], i, e] * ξ1x2[Nq[1], i, e]\n            else\n                sgeo.n1[i, 1:2, e] .= NaN\n                sgeo.n2[i, 1:2, e] .= NaN\n            end\n            if i <= Nfp[2]\n                sgeo.n1[i, 3, e] = -ωJ[i, 1, e] * ξ2x1[i, 1, e]\n                sgeo.n2[i, 3, e] = -ωJ[i, 1, e] * ξ2x2[i, 1, e]\n                sgeo.n1[i, 4, e] = ωJ[i, Nq[2], e] * ξ2x1[i, Nq[2], e]\n                sgeo.n2[i, 4, e] = ωJ[i, Nq[2], e] * ξ2x2[i, Nq[2], e]\n            else\n                sgeo.n1[i, 3:4, e] .= NaN\n                sgeo.n2[i, 3:4, e] .= NaN\n            end\n\n            for n in 1:nface\n                sgeo.sωJ[i, n, e] = hypot(n1[i, n, e], n2[i, n, e])\n                sgeo.n1[i, n, e] /= sωJ[i, n, e]\n                sgeo.n2[i, n, e] /= sωJ[i, n, e]\n            end\n        end\n\n    end\n\n    nothing\nend\n\n\"\"\"\n    computemetric!(vgeo, sgeo, D)\n\nInput arguments:\n- vgeo::VolumeGeometry, a struct containing the volumetric geometric factors\n- sgeo::SurfaceGeometry, a struct containing the surface geometric factors\n- D::NTuple{3,Int}, a tuple of derivative matrices, i.e., D = (D1, D2, D3), where:\n    - D1::DAT2, 1-D derivative operator on the device in the first dimension\n    - D2::DAT2, 1-D derivative operator on the device in the second dimension\n    - D3::DAT2, 1-D derivative operator on the device in the third dimension\n\nCompute the 3-D metric terms from the element grid arrays `vgeo.x1`, `vgeo.x2`, and `vgeo.x3`.\nAll the arrays are preallocated by the user and the (square) derivative matrice `D1`,\n`D2`, and `D3` should be consistent with the reference grid `ξ1`, `ξ2`, and `ξ3` used in\n[`creategrid!`](@ref).\n\nIf `Nq = size(D1, 1)` and `nelem = div(length(vgeo.x1), Nq^3)` then the volume arrays\n`vgeo.x1`, `vgeo.x2`, `vgeo.x3`, `vgeo.ωJ`, `vgeo.ξ1x1`, `vgeo.ξ2x1`, `vgeo.ξ3x1`,\n`vgeo.ξ1x2`, `vgeo.ξ2x2`, `vgeo.ξ3x2`, `vgeo.ξ1x3`,`vgeo.ξ2x3`, and `vgeo.ξ3x3`\nshould all be of length `Nq^3 * nelem`.  Similarly, the face\narrays `sgeo.sωJ`, `sgeo.n1`, `sgeo.n2`, and `sgeo.n3` should be of size `Nq^2 * nface * nelem` with\n`nface = 6`.\n\nThe curl invariant formulation of Kopriva (2006), equation 37, is used.\n\nReference:\n - [Kopriva2006](@cite)\n\"\"\"\nfunction computemetric!(\n    vgeo::VolumeGeometry{Nq, <:AbstractArray, <:AbstractArray},\n    sgeo::SurfaceGeometry{Nfp, <:AbstractArray},\n    D::NTuple{3, Matrix{FT}},\n) where {Nq, Nfp, FT}\n\n    @assert Nq == map(d -> size(d, 1), D)\n    @assert Nfp == div.(prod(Nq), Nq)\n\n    T = eltype(vgeo.x1)\n    nelem = div(length(vgeo.ωJ), prod(Nq))\n    x1 = reshape(vgeo.x1, (Nq..., nelem))\n    x2 = reshape(vgeo.x2, (Nq..., nelem))\n    x3 = reshape(vgeo.x3, (Nq..., nelem))\n    ωJ = reshape(vgeo.ωJ, (Nq..., nelem))\n    JcV = reshape(vgeo.JcV, (Nq..., nelem))\n    ξ1x1 = reshape(vgeo.ξ1x1, (Nq..., nelem))\n    ξ2x1 = reshape(vgeo.ξ2x1, (Nq..., nelem))\n    ξ3x1 = reshape(vgeo.ξ3x1, (Nq..., nelem))\n    ξ1x2 = reshape(vgeo.ξ1x2, (Nq..., nelem))\n    ξ2x2 = reshape(vgeo.ξ2x2, (Nq..., nelem))\n    ξ3x2 = reshape(vgeo.ξ3x2, (Nq..., nelem))\n    ξ1x3 = reshape(vgeo.ξ1x3, (Nq..., nelem))\n    ξ2x3 = reshape(vgeo.ξ2x3, (Nq..., nelem))\n    ξ3x3 = reshape(vgeo.ξ3x3, (Nq..., nelem))\n    x1ξ1 = reshape(vgeo.x1ξ1, (Nq..., nelem))\n    x1ξ2 = reshape(vgeo.x1ξ2, (Nq..., nelem))\n    x1ξ3 = reshape(vgeo.x1ξ3, (Nq..., nelem))\n    x2ξ1 = reshape(vgeo.x2ξ1, (Nq..., nelem))\n    x2ξ2 = reshape(vgeo.x2ξ2, (Nq..., nelem))\n    x2ξ3 = reshape(vgeo.x2ξ3, (Nq..., nelem))\n    x3ξ1 = reshape(vgeo.x3ξ1, (Nq..., nelem))\n    x3ξ2 = reshape(vgeo.x3ξ2, (Nq..., nelem))\n    x3ξ3 = reshape(vgeo.x3ξ3, (Nq..., nelem))\n\n    nface = 6\n    n1 = reshape(sgeo.n1, maximum(Nfp), nface, nelem)\n    n2 = reshape(sgeo.n2, maximum(Nfp), nface, nelem)\n    n3 = reshape(sgeo.n3, maximum(Nfp), nface, nelem)\n    sωJ = reshape(sgeo.sωJ, maximum(Nfp), nface, nelem)\n\n    JI2 = similar(vgeo.ωJ, Nq...)\n    (yzr, yzs, yzt) = (similar(JI2), similar(JI2), similar(JI2))\n    (zxr, zxs, zxt) = (similar(JI2), similar(JI2), similar(JI2))\n    (xyr, xys, xyt) = (similar(JI2), similar(JI2), similar(JI2))\n    # Temporary variables to compute inverse of a 3x3 matrix\n    (a11, a12, a13) = (similar(JI2), similar(JI2), similar(JI2))\n    (a21, a22, a23) = (similar(JI2), similar(JI2), similar(JI2))\n    (a31, a32, a33) = (similar(JI2), similar(JI2), similar(JI2))\n\n    ξ1x1 .= ξ2x1 .= ξ3x1 .= zero(T)\n    ξ1x2 .= ξ2x2 .= ξ3x2 .= zero(T)\n    ξ1x3 .= ξ2x3 .= ξ3x3 .= zero(T)\n\n    fill!(n1, NaN)\n    fill!(n2, NaN)\n    fill!(n3, NaN)\n    fill!(sωJ, NaN)\n\n    @inbounds for e in 1:nelem\n        for k in 1:Nq[3], j in 1:Nq[2], i in 1:Nq[1]\n\n            # Compute vertical Jacobian determinant, JcV, per quadrature point\n            JcV[i, j, k, e] =\n                hypot(x1ξ3[i, j, k, e], x2ξ3[i, j, k, e], x3ξ3[i, j, k, e])\n            # Compute Jacobian determinant, det(∂x/∂ξ), per quadrature point\n            ωJ[i, j, k, e] = (\n                x1ξ1[i, j, k, e] * (\n                    x2ξ2[i, j, k, e] * x3ξ3[i, j, k, e] -\n                    x3ξ2[i, j, k, e] * x2ξ3[i, j, k, e]\n                ) +\n                x2ξ1[i, j, k, e] * (\n                    x3ξ2[i, j, k, e] * x1ξ3[i, j, k, e] -\n                    x1ξ2[i, j, k, e] * x3ξ3[i, j, k, e]\n                ) +\n                x3ξ1[i, j, k, e] * (\n                    x1ξ2[i, j, k, e] * x2ξ3[i, j, k, e] -\n                    x2ξ2[i, j, k, e] * x1ξ3[i, j, k, e]\n                )\n            )\n\n            JI2[i, j, k] = 1 / (2 * ωJ[i, j, k, e])\n\n            yzr[i, j, k] =\n                x2[i, j, k, e] * x3ξ1[i, j, k, e] -\n                x3[i, j, k, e] * x2ξ1[i, j, k, e]\n            yzs[i, j, k] =\n                x2[i, j, k, e] * x3ξ2[i, j, k, e] -\n                x3[i, j, k, e] * x2ξ2[i, j, k, e]\n            yzt[i, j, k] =\n                x2[i, j, k, e] * x3ξ3[i, j, k, e] -\n                x3[i, j, k, e] * x2ξ3[i, j, k, e]\n            zxr[i, j, k] =\n                x3[i, j, k, e] * x1ξ1[i, j, k, e] -\n                x1[i, j, k, e] * x3ξ1[i, j, k, e]\n            zxs[i, j, k] =\n                x3[i, j, k, e] * x1ξ2[i, j, k, e] -\n                x1[i, j, k, e] * x3ξ2[i, j, k, e]\n            zxt[i, j, k] =\n                x3[i, j, k, e] * x1ξ3[i, j, k, e] -\n                x1[i, j, k, e] * x3ξ3[i, j, k, e]\n            xyr[i, j, k] =\n                x1[i, j, k, e] * x2ξ1[i, j, k, e] -\n                x2[i, j, k, e] * x1ξ1[i, j, k, e]\n            xys[i, j, k] =\n                x1[i, j, k, e] * x2ξ2[i, j, k, e] -\n                x2[i, j, k, e] * x1ξ2[i, j, k, e]\n            xyt[i, j, k] =\n                x1[i, j, k, e] * x2ξ3[i, j, k, e] -\n                x2[i, j, k, e] * x1ξ3[i, j, k, e]\n        end\n\n        for k in 1:Nq[3], j in 1:Nq[2], i in 1:Nq[1]\n            for n in 1:Nq[1]\n                ξ2x1[i, j, k, e] -= D[1][i, n] * yzt[n, j, k]\n                ξ3x1[i, j, k, e] += D[1][i, n] * yzs[n, j, k]\n                ξ2x2[i, j, k, e] -= D[1][i, n] * zxt[n, j, k]\n                ξ3x2[i, j, k, e] += D[1][i, n] * zxs[n, j, k]\n                ξ2x3[i, j, k, e] -= D[1][i, n] * xyt[n, j, k]\n                ξ3x3[i, j, k, e] += D[1][i, n] * xys[n, j, k]\n            end\n            for n in 1:Nq[2]\n                ξ1x1[i, j, k, e] += D[2][j, n] * yzt[i, n, k]\n                ξ3x1[i, j, k, e] -= D[2][j, n] * yzr[i, n, k]\n                ξ1x2[i, j, k, e] += D[2][j, n] * zxt[i, n, k]\n                ξ3x2[i, j, k, e] -= D[2][j, n] * zxr[i, n, k]\n                ξ1x3[i, j, k, e] += D[2][j, n] * xyt[i, n, k]\n                ξ3x3[i, j, k, e] -= D[2][j, n] * xyr[i, n, k]\n            end\n            for n in 1:Nq[3]\n                ξ1x1[i, j, k, e] -= D[3][k, n] * yzs[i, j, n]\n                ξ2x1[i, j, k, e] += D[3][k, n] * yzr[i, j, n]\n                ξ1x2[i, j, k, e] -= D[3][k, n] * zxs[i, j, n]\n                ξ2x2[i, j, k, e] += D[3][k, n] * zxr[i, j, n]\n                ξ1x3[i, j, k, e] -= D[3][k, n] * xys[i, j, n]\n                ξ2x3[i, j, k, e] += D[3][k, n] * xyr[i, j, n]\n            end\n            ξ1x1[i, j, k, e] *= JI2[i, j, k]\n            ξ2x1[i, j, k, e] *= JI2[i, j, k]\n            ξ3x1[i, j, k, e] *= JI2[i, j, k]\n            ξ1x2[i, j, k, e] *= JI2[i, j, k]\n            ξ2x2[i, j, k, e] *= JI2[i, j, k]\n            ξ3x2[i, j, k, e] *= JI2[i, j, k]\n            ξ1x3[i, j, k, e] *= JI2[i, j, k]\n            ξ2x3[i, j, k, e] *= JI2[i, j, k]\n            ξ3x3[i, j, k, e] *= JI2[i, j, k]\n\n\n            # Invert ∂ξk/∂xi, since the discrete curl-invariant form that we have\n            # just computed, ∂ξk/∂xi, is not equal to its inverse\n            a11[i, j, k] =\n                ξ2x2[i, j, k, e] * ξ3x3[i, j, k, e] -\n                ξ2x3[i, j, k, e] * ξ3x2[i, j, k, e]\n            a12[i, j, k] =\n                ξ1x3[i, j, k, e] * ξ3x2[i, j, k, e] -\n                ξ1x2[i, j, k, e] * ξ3x3[i, j, k, e]\n            a13[i, j, k] =\n                ξ1x2[i, j, k, e] * ξ2x3[i, j, k, e] -\n                ξ1x3[i, j, k, e] * ξ2x2[i, j, k, e]\n            a21[i, j, k] =\n                ξ2x3[i, j, k, e] * ξ3x1[i, j, k, e] -\n                ξ2x1[i, j, k, e] * ξ3x3[i, j, k, e]\n            a22[i, j, k] =\n                ξ1x1[i, j, k, e] * ξ3x3[i, j, k, e] -\n                ξ1x3[i, j, k, e] * ξ3x1[i, j, k, e]\n            a23[i, j, k] =\n                ξ1x3[i, j, k, e] * ξ2x1[i, j, k, e] -\n                ξ1x1[i, j, k, e] * ξ2x3[i, j, k, e]\n            a31[i, j, k] =\n                ξ2x1[i, j, k, e] * ξ3x2[i, j, k, e] -\n                ξ2x2[i, j, k, e] * ξ3x1[i, j, k, e]\n            a32[i, j, k] =\n                ξ1x2[i, j, k, e] * ξ3x1[i, j, k, e] -\n                ξ1x1[i, j, k, e] * ξ3x2[i, j, k, e]\n            a33[i, j, k] =\n                ξ1x1[i, j, k, e] * ξ2x2[i, j, k, e] -\n                ξ1x2[i, j, k, e] * ξ2x1[i, j, k, e]\n\n            det =\n                ξ1x1[i, j, k, e] * a11[i, j, k] +\n                ξ2x1[i, j, k, e] * a12[i, j, k] +\n                ξ3x1[i, j, k, e] * a13[i, j, k]\n\n            x1ξ1[i, j, k, e] =\n                1.0 / det * (\n                    a11[i, j, k] * a11[i, j, k] +\n                    a12[i, j, k] * a12[i, j, k] +\n                    a13[i, j, k] * a13[i, j, k]\n                )\n            x1ξ2[i, j, k, e] =\n                1.0 / det * (\n                    a11[i, j, k] * a21[i, j, k] +\n                    a12[i, j, k] * a22[i, j, k] +\n                    a13[i, j, k] * a23[i, j, k]\n                )\n            x1ξ3[i, j, k, e] =\n                1.0 / det * (\n                    a11[i, j, k] * a31[i, j, k] +\n                    a12[i, j, k] * a32[i, j, k] +\n                    a13[i, j, k] * a33[i, j, k]\n                )\n            x2ξ1[i, j, k, e] =\n                1.0 / det * (\n                    a21[i, j, k] * a11[i, j, k] +\n                    a22[i, j, k] * a12[i, j, k] +\n                    a23[i, j, k] * a13[i, j, k]\n                )\n            x2ξ2[i, j, k, e] =\n                1.0 / det * (\n                    a21[i, j, k] * a21[i, j, k] +\n                    a22[i, j, k] * a22[i, j, k] +\n                    a23[i, j, k] * a23[i, j, k]\n                )\n            x2ξ3[i, j, k, e] =\n                1.0 / det * (\n                    a21[i, j, k] * a31[i, j, k] +\n                    a22[i, j, k] * a32[i, j, k] +\n                    a23[i, j, k] * a33[i, j, k]\n                )\n            x3ξ1[i, j, k, e] =\n                1.0 / det * (\n                    a31[i, j, k] * a11[i, j, k] +\n                    a32[i, j, k] * a12[i, j, k] +\n                    a33[i, j, k] * a13[i, j, k]\n                )\n            x3ξ2[i, j, k, e] =\n                1.0 / det * (\n                    a31[i, j, k] * a21[i, j, k] +\n                    a32[i, j, k] * a22[i, j, k] +\n                    a33[i, j, k] * a23[i, j, k]\n                )\n            x3ξ3[i, j, k, e] =\n                1.0 / det * (\n                    a31[i, j, k] * a31[i, j, k] +\n                    a32[i, j, k] * a32[i, j, k] +\n                    a33[i, j, k] * a33[i, j, k]\n                )\n        end\n\n        # Compute surface struct field entries\n        # faces 1 & 2\n        for k in 1:Nq[3], j in 1:Nq[2]\n            n = j + (k - 1) * Nq[2]\n            sgeo.n1[n, 1, e] = -ωJ[1, j, k, e] * ξ1x1[1, j, k, e]\n            sgeo.n2[n, 1, e] = -ωJ[1, j, k, e] * ξ1x2[1, j, k, e]\n            sgeo.n3[n, 1, e] = -ωJ[1, j, k, e] * ξ1x3[1, j, k, e]\n            sgeo.n1[n, 2, e] = ωJ[Nq[1], j, k, e] * ξ1x1[Nq[1], j, k, e]\n            sgeo.n2[n, 2, e] = ωJ[Nq[1], j, k, e] * ξ1x2[Nq[1], j, k, e]\n            sgeo.n3[n, 2, e] = ωJ[Nq[1], j, k, e] * ξ1x3[Nq[1], j, k, e]\n            for f in 1:2\n                sgeo.sωJ[n, f, e] = hypot(n1[n, f, e], n2[n, f, e], n3[n, f, e])\n                sgeo.n1[n, f, e] /= sωJ[n, f, e]\n                sgeo.n2[n, f, e] /= sωJ[n, f, e]\n                sgeo.n3[n, f, e] /= sωJ[n, f, e]\n            end\n        end\n        # faces 3 & 4\n        for k in 1:Nq[3], i in 1:Nq[1]\n            n = i + (k - 1) * Nq[1]\n            sgeo.n1[n, 3, e] = -ωJ[i, 1, k, e] * ξ2x1[i, 1, k, e]\n            sgeo.n2[n, 3, e] = -ωJ[i, 1, k, e] * ξ2x2[i, 1, k, e]\n            sgeo.n3[n, 3, e] = -ωJ[i, 1, k, e] * ξ2x3[i, 1, k, e]\n            sgeo.n1[n, 4, e] = ωJ[i, Nq[2], k, e] * ξ2x1[i, Nq[2], k, e]\n            sgeo.n2[n, 4, e] = ωJ[i, Nq[2], k, e] * ξ2x2[i, Nq[2], k, e]\n            sgeo.n3[n, 4, e] = ωJ[i, Nq[2], k, e] * ξ2x3[i, Nq[2], k, e]\n            for f in 3:4\n                sgeo.sωJ[n, f, e] = hypot(n1[n, f, e], n2[n, f, e], n3[n, f, e])\n                sgeo.n1[n, f, e] /= sωJ[n, f, e]\n                sgeo.n2[n, f, e] /= sωJ[n, f, e]\n                sgeo.n3[n, f, e] /= sωJ[n, f, e]\n            end\n        end\n        # faces 5 & 6\n        for j in 1:Nq[2], i in 1:Nq[1]\n            n = i + (j - 1) * Nq[1]\n            sgeo.n1[n, 5, e] = -ωJ[i, j, 1, e] * ξ3x1[i, j, 1, e]\n            sgeo.n2[n, 5, e] = -ωJ[i, j, 1, e] * ξ3x2[i, j, 1, e]\n            sgeo.n3[n, 5, e] = -ωJ[i, j, 1, e] * ξ3x3[i, j, 1, e]\n            sgeo.n1[n, 6, e] = ωJ[i, j, Nq[3], e] * ξ3x1[i, j, Nq[3], e]\n            sgeo.n2[n, 6, e] = ωJ[i, j, Nq[3], e] * ξ3x2[i, j, Nq[3], e]\n            sgeo.n3[n, 6, e] = ωJ[i, j, Nq[3], e] * ξ3x3[i, j, Nq[3], e]\n            for f in 5:6\n                sgeo.sωJ[n, f, e] = hypot(n1[n, f, e], n2[n, f, e], n3[n, f, e])\n                sgeo.n1[n, f, e] /= sωJ[n, f, e]\n                sgeo.n2[n, f, e] /= sωJ[n, f, e]\n                sgeo.n3[n, f, e] /= sωJ[n, f, e]\n            end\n        end\n    end\n\n    nothing\nend\n\nend # module\n"
  },
  {
    "path": "src/Numerics/Mesh/Topologies.jl",
    "content": "module Topologies\nusing ClimateMachine\nusing CubedSphere, Rotations\nimport ..BrickMesh\nimport MPI\nusing CUDA\nusing DocStringExtensions\n\nexport AbstractTopology,\n    BrickTopology,\n    StackedBrickTopology,\n    CubedShellTopology,\n    StackedCubedSphereTopology,\n    AnalyticalTopography,\n    NoTopography,\n    DCMIPMountain,\n    EquidistantCubedSphere,\n    EquiangularCubedSphere,\n    isstacked,\n    cubed_sphere_topo_warp,\n    compute_lat_long,\n    compute_analytical_topography,\n    cubed_sphere_warp,\n    cubed_sphere_unwarp,\n    equiangular_cubed_sphere_warp,\n    equiangular_cubed_sphere_unwarp,\n    equidistant_cubed_sphere_warp,\n    equidistant_cubed_sphere_unwarp,\n    conformal_cubed_sphere_warp,\n    conformal_cubed_sphere_unwarp\n\nexport grid1d, SingleExponentialStretching, InteriorStretching\n\nexport basic_topology_info\n\n\"\"\"\n    AbstractTopology{dim, T, nb}\n\nRepresents the connectivity of individual elements, with local dimension `dim`\nwith `nb` boundary conditions types. The element coordinates are of type `T`.\n\"\"\"\nabstract type AbstractTopology{dim, T, nb} end\n\n\"\"\"\n    BoxElementTopology{dim, T, nb} <: AbstractTopology{dim,T,nb}\n\nThe local topology of a larger MPI-distributed topology, represented by\n`dim`-dimensional box elements, with `nb` boundary conditions.\n\nThis contains the necessary information for the connectivity elements of the\nelements on the local process, along with \"ghost\" elements from neighbouring\nprocesses.\n\n# Fields\n\n$(DocStringExtensions.FIELDS)\n\"\"\"\nstruct BoxElementTopology{dim, T, nb} <: AbstractTopology{dim, T, nb}\n\n    \"\"\"\n    MPI communicator for communicating with neighbouring processes.\n    \"\"\"\n    mpicomm::MPI.Comm\n\n    \"\"\"\n    Range of element indices\n    \"\"\"\n    elems::UnitRange{Int64}\n\n    \"\"\"\n    Range of real (aka nonghost) element indices\n    \"\"\"\n    realelems::UnitRange{Int64}\n\n    \"\"\"\n    Range of ghost element indices\n    \"\"\"\n    ghostelems::UnitRange{Int64}\n\n    \"\"\"\n    Ghost element to face is received; `ghostfaces[f,ge] == true` if face `f` of\n    ghost element `ge` is received.\n    \"\"\"\n    ghostfaces::BitArray{2}\n\n    \"\"\"\n    Array of send element indices\n    \"\"\"\n    sendelems::Array{Int64, 1}\n\n    \"\"\"\n    Send element to face is sent; `sendfaces[f,se] == true` if face `f` of send\n    element `se` is sent.\n    \"\"\"\n    sendfaces::BitArray{2}\n\n    \"\"\"\n    Array of real elements that do not have a ghost element as a neighbor.\n    \"\"\"\n    interiorelems::Array{Int64, 1}\n\n    \"\"\"\n    Array of real elements that have at least on ghost element as a neighbor.\n\n    Note that this is different from `sendelems` because `sendelems` duplicates\n    elements that need to be sent to multiple neighboring processes.\n    \"\"\"\n    exteriorelems::Array{Int64, 1}\n\n    \"\"\"\n    Element to vertex coordinates; `elemtocoord[d,i,e]` is the `d`th coordinate\n    of corner `i` of element `e`\n\n    !!! note\n        currently coordinates always are of size 3 for `(x1, x2, x3)`\n    \"\"\"\n    elemtocoord::Array{T, 3}\n\n    \"\"\"\n    Element to neighboring element; `elemtoelem[f,e]` is the number of the\n    element neighboring element `e` across face `f`. If it is a boundary face,\n    then it is the boundary element index.\n    \"\"\"\n    elemtoelem::Array{Int64, 2}\n\n    \"\"\"\n    Element to neighboring element face; `elemtoface[f,e]` is the face number of\n    the element neighboring element `e` across face `f`.  If there is no\n    neighboring element then `elemtoface[f,e] == f`.\"\n    \"\"\"\n    elemtoface::Array{Int64, 2}\n\n    \"\"\"\n    element to neighboring element order; `elemtoordr[f,e]` is the ordering\n    number of the element neighboring element `e` across face `f`.  If there is\n    no neighboring element then `elemtoordr[f,e] == 1`.\n    \"\"\"\n    elemtoordr::Array{Int64, 2}\n\n    \"\"\"\n    Element to boundary number; `elemtobndy[f,e]` is the boundary number of face\n    `f` of element `e`.  If there is a neighboring element then `elemtobndy[f,e]\n    == 0`.\n    \"\"\"\n    elemtobndy::Array{Int64, 2}\n\n    \"\"\"\n    List of the MPI ranks for the neighboring processes\n    \"\"\"\n    nabrtorank::Array{Int64, 1}\n\n    \"\"\"\n    Range in ghost elements to receive for each neighbor\n    \"\"\"\n    nabrtorecv::Array{UnitRange{Int64}, 1}\n\n    \"\"\"\n    Range in `sendelems` to send for each neighbor\n    \"\"\"\n    nabrtosend::Array{UnitRange{Int64}, 1}\n\n    \"\"\"\n    original order in partitioning\n    \"\"\"\n    origsendorder::Array{Int64, 1}\n\n    \"\"\"\n    Tuple of boundary to element. `bndytoelem[b][i]` is the element which faces\n    the `i`th boundary element of boundary `b`.\n    \"\"\"\n    bndytoelem::NTuple{nb, Array{Int64, 1}}\n\n    \"\"\"\n    Tuple of boundary to element face. `bndytoface[b][i]` is the face number of\n    the element which faces the `i`th boundary element of boundary `b`.\n    \"\"\"\n    bndytoface::NTuple{nb, Array{Int64, 1}}\n\n    \"\"\"\n    Element to locally unique vertex number; `elemtouvert[v,e]` is the `v`th vertex\n    of element `e`\n    \"\"\"\n    elemtouvert::Union{Array{Int64, 2}, Nothing}\n\n    \"\"\"\n    Vertex connectivity information for direct stiffness summation;\n    `vtconn[vtconnoff[i]:vtconnoff[i+1]-1] == vtconn[lvt1, lelem1, lvt2, lelem2, ....]`\n    for each rank-local unique vertex number,\n    where `lvt1` is the element-local vertex number of element `lelem1`, etc.\n    Vertices, not shared by multiple elements, are ignored.\n    \"\"\"\n    vtconn::Union{AbstractArray{Int64, 1}, Nothing}\n\n    \"\"\"\n    Vertex offset for vertex connectivity information\n    \"\"\"\n    vtconnoff::Union{AbstractArray{Int64, 1}, Nothing}\n\n    \"\"\"\n    Face connectivity information for direct stiffness summation;\n    `fcconn[ufc,:] = [lfc1, lelem1, lfc2, lelem2, ordr]`\n    where `ufc` is the rank-local unique face number, and\n    `lfc1` is the element-local face number of element `lelem1`, etc.,\n    and ordr is the relative orientation. Faces, not shared by multiple\n    elements are ignored.\n    \"\"\"\n    fcconn::Union{AbstractArray{Int64, 2}, Nothing}\n\n    \"\"\"\n    Edge connectivity information for direct stiffness summation;\n    `edgconn[edgconnoff[i]:edgconnoff[i+1]-1] == edgconn[ledg1, orient, lelem1, ledg2, orient, lelem2, ...]`\n    for each rank-local unique edge number,\n    where `ledg1` is the element-local edge number, `orient1` is orientation (forward/reverse) of dof along edge.\n    Edges, not shared by multiple elements, are ignored.\n    \"\"\"\n    edgconn::Union{AbstractArray{Int64, 1}, Nothing}\n\n    \"\"\"\n    Edge offset for edge connectivity information\n    \"\"\"\n    edgconnoff::Union{AbstractArray{Int64, 1}, Nothing}\n\n    function BoxElementTopology{dim, T, nb}(\n        mpicomm,\n        elems,\n        realelems,\n        ghostelems,\n        ghostfaces,\n        sendelems,\n        sendfaces,\n        elemtocoord,\n        elemtoelem,\n        elemtoface,\n        elemtoordr,\n        elemtobndy,\n        nabrtorank,\n        nabrtorecv,\n        nabrtosend,\n        origsendorder,\n        bndytoelem,\n        bndytoface;\n        device_array = ClimateMachine.array_type(),\n        elemtouvert = nothing,\n        vtconn = nothing,\n        vtconnoff = nothing,\n        fcconn = nothing,\n        edgconn = nothing,\n        edgconnoff = nothing,\n    ) where {dim, T, nb}\n\n        exteriorelems = sort(unique(sendelems))\n        interiorelems = sort(setdiff(realelems, exteriorelems))\n        if vtconn isa Array && vtconnoff isa Array\n            vtconn = device_array(vtconn)\n            vtconnoff = device_array(vtconnoff)\n        end\n        if fcconn isa Array\n            fcconn = device_array(fcconn)\n        end\n        if edgconn isa Array && edgconnoff isa Array\n            edgconn = device_array(edgconn)\n            edgconnoff = device_array(edgconnoff)\n        end\n        return new{dim, T, nb}(\n            mpicomm,\n            elems,\n            realelems,\n            ghostelems,\n            ghostfaces,\n            sendelems,\n            sendfaces,\n            interiorelems,\n            exteriorelems,\n            elemtocoord,\n            elemtoelem,\n            elemtoface,\n            elemtoordr,\n            elemtobndy,\n            nabrtorank,\n            nabrtorecv,\n            nabrtosend,\n            origsendorder,\n            bndytoelem,\n            bndytoface,\n            elemtouvert,\n            vtconn,\n            vtconnoff,\n            fcconn,\n            edgconn,\n            edgconnoff,\n        )\n    end\nend\n\n\"\"\"\n    hasboundary(topology::AbstractTopology)\n\nquery function to check whether a topology has a boundary (i.e., not fully\nperiodic)\n\"\"\"\nhasboundary(topology::AbstractTopology{dim, T, nb}) where {dim, T, nb} = nb != 0\n\nif VERSION >= v\"1.2-\"\n    isstacked(::T) where {T <: AbstractTopology} = hasfield(T, :stacksize)\nelse\n    isstacked(::T) where {T <: AbstractTopology} =\n        Base.fieldindex(T, :stacksize, false) > 0\nend\n\n\"\"\"\n    BrickTopology{dim, T, nb} <: AbstractTopology{dim, T, nb}\n\nA simple grid-based topology. This is a convenience wrapper around\n[`BoxElementTopology`](@ref).\n\"\"\"\nstruct BrickTopology{dim, T, nb} <: AbstractTopology{dim, T, nb}\n    topology::BoxElementTopology{dim, T, nb}\nend\nBase.getproperty(a::BrickTopology, p::Symbol) =\n    getproperty(getfield(a, :topology), p)\n\n\"\"\"\n    CubedShellTopology{T} <: AbstractTopology{2, T, 0}\n\nA cube-shell topology. This is a convenience wrapper around\n[`BoxElementTopology`](@ref).\n\"\"\"\nstruct CubedShellTopology{T} <: AbstractTopology{2, T, 0}\n    topology::BoxElementTopology{2, T, 0}\nend\nBase.getproperty(a::CubedShellTopology, p::Symbol) =\n    getproperty(getfield(a, :topology), p)\n\nabstract type AbstractStackedTopology{dim, T, nb} <:\n              AbstractTopology{dim, T, nb} end\n\n\n\"\"\"\n    StackedBrickTopology{dim, T, nb} <: AbstractStackedTopology{dim}\n\nA simple grid-based topology, where all elements on the trailing dimension are\nstacked to be contiguous. This is a convenience wrapper around\n[`BoxElementTopology`](@ref).\n\"\"\"\nstruct StackedBrickTopology{dim, T, nb} <: AbstractStackedTopology{dim, T, nb}\n    topology::BoxElementTopology{dim, T, nb}\n    stacksize::Int64\n    periodicstack::Bool\nend\nfunction Base.getproperty(a::StackedBrickTopology, p::Symbol)\n    return p in (:stacksize, :periodicstack) ? getfield(a, p) :\n           getproperty(getfield(a, :topology), p)\nend\n\n\"\"\"\n    StackedCubedSphereTopology{T, nb} <: AbstractStackedTopology{3, T, nb}\n\nA cube-sphere topology. All elements on the same \"vertical\" dimension are\nstacked to be contiguous. This is a convenience wrapper around\n[`BoxElementTopology`](@ref).\n\"\"\"\nstruct StackedCubedSphereTopology{T, nb} <: AbstractStackedTopology{3, T, nb}\n    topology::BoxElementTopology{3, T, nb}\n    stacksize::Int64\nend\nfunction Base.getproperty(a::StackedCubedSphereTopology, p::Symbol)\n    if p == :periodicstack\n        return false\n    else\n        return p == :stacksize ? getfield(a, p) :\n               getproperty(getfield(a, :topology), p)\n    end\nend\n\n\"\"\" A wrapper for the BrickTopology \"\"\"\nBrickTopology(mpicomm, Nelems::NTuple{N, Integer}; kw...) where {N} =\n    BrickTopology(mpicomm, map(Ne -> 0:Ne, Nelems); kw...)\n\n\"\"\"\n    BrickTopology{dim, T}(mpicomm, elemrange; boundary, periodicity)\n\nGenerate a brick mesh topology with coordinates given by the tuple `elemrange`\nand the periodic dimensions given by the `periodicity` tuple.\n\nThe elements of the brick are partitioned equally across the MPI ranks based\non a space-filling curve.\n\nBy default boundary faces will be marked with a one and other faces with a\nzero.  Specific boundary numbers can also be passed for each face of the brick\nin `boundary`.  This will mark the nonperiodic brick faces with the given\nboundary number.\n\n# Examples\n\nWe can build a 3 by 2 element two-dimensional mesh that is periodic in the\n\\$x2\\$-direction with\n```jldoctest brickmesh\n\nusing ClimateMachine.Topologies\nusing MPI\nMPI.Init()\ntopology = BrickTopology(MPI.COMM_SELF, (2:5,4:6);\n                         periodicity=(false,true),\n                         boundary=((1,2),(3,4)))\n```\nThis returns the mesh structure for\n\n             x2\n\n              ^\n              |\n             6-  +-----+-----+-----+\n              |  |     |     |     |\n              |  |  3  |  4  |  5  |\n              |  |     |     |     |\n             5-  +-----+-----+-----+\n              |  |     |     |     |\n              |  |  1  |  2  |  6  |\n              |  |     |     |     |\n             4-  +-----+-----+-----+\n              |\n              +--|-----|-----|-----|--> x1\n                 2     3     4     5\n\nFor example, the (dimension by number of corners by number of elements) array\n`elemtocoord` gives the coordinates of the corners of each element.\n```jldoctest brickmesh\njulia> topology.elemtocoord\n2×4×6 Array{Int64,3}:\n[:, :, 1] =\n 2  3  2  3\n 4  4  5  5\n\n[:, :, 2] =\n 3  4  3  4\n 4  4  5  5\n\n[:, :, 3] =\n 2  3  2  3\n 5  5  6  6\n\n[:, :, 4] =\n 3  4  3  4\n 5  5  6  6\n\n[:, :, 5] =\n 4  5  4  5\n 5  5  6  6\n\n[:, :, 6] =\n 4  5  4  5\n 4  4  5  5\n```\nNote that the corners are listed in Cartesian order.\n\nThe (number of faces by number of elements) array `elemtobndy` gives the\nboundary number for each face of each element.  A zero will be given for\nconnected faces.\n```jldoctest brickmesh\njulia> topology.elemtobndy\n4×6 Array{Int64,2}:\n 1  0  1  0  0  0\n 0  0  0  0  2  2\n 0  0  0  0  0  0\n 0  0  0  0  0  0\n```\nNote that the faces are listed in Cartesian order.\n\n\"\"\"\nfunction BrickTopology(\n    mpicomm,\n    elemrange;\n    boundary = ntuple(j -> (1, 1), length(elemrange)),\n    periodicity = ntuple(j -> false, length(elemrange)),\n    connectivity = :face,\n    ghostsize = 1,\n)\n\n    if boundary isa Matrix\n        boundary = tuple(mapslices(x -> tuple(x...), boundary, dims = 1)...)\n    end\n\n\n    # We cannot handle anything else right now...\n    @assert ghostsize == 1\n\n    mpirank = MPI.Comm_rank(mpicomm)\n    mpisize = MPI.Comm_size(mpicomm)\n    topology = BrickMesh.brickmesh(\n        elemrange,\n        periodicity,\n        part = mpirank + 1,\n        numparts = mpisize,\n        boundary = boundary,\n    )\n    topology = BrickMesh.partition(mpicomm, topology...)\n    origsendorder = topology[5]\n    topology =\n        connectivity == :face ?\n        BrickMesh.connectmesh(mpicomm, topology[1:4]...) :\n        BrickMesh.connectmeshfull(mpicomm, topology[1:4]...)\n    bndytoelem, bndytoface = BrickMesh.enumerateboundaryfaces!(\n        topology.elemtoelem,\n        topology.elemtobndy,\n        periodicity,\n        boundary,\n    )\n\n    nb = length(bndytoelem)\n    dim = length(elemrange)\n    T = eltype(topology.elemtocoord)\n    return BrickTopology{dim, T, nb}(BoxElementTopology{dim, T, nb}(\n        mpicomm,\n        topology.elems,\n        topology.realelems,\n        topology.ghostelems,\n        topology.ghostfaces,\n        topology.sendelems,\n        topology.sendfaces,\n        topology.elemtocoord,\n        topology.elemtoelem,\n        topology.elemtoface,\n        topology.elemtoordr,\n        topology.elemtobndy,\n        topology.nabrtorank,\n        topology.nabrtorecv,\n        topology.nabrtosend,\n        origsendorder,\n        bndytoelem,\n        bndytoface,\n        elemtouvert = topology.elemtovert,\n    ))\nend\n\n\"\"\" A wrapper for the StackedBrickTopology \"\"\"\nStackedBrickTopology(mpicomm, Nelems::NTuple{N, Integer}; kw...) where {N} =\n    StackedBrickTopology(mpicomm, map(Ne -> 0:Ne, Nelems); kw...)\n\n\"\"\"\n    StackedBrickTopology{dim, T}(mpicomm, elemrange; boundary, periodicity)\n\nGenerate a stacked brick mesh topology with coordinates given by the tuple\n`elemrange` and the periodic dimensions given by the `periodicity` tuple.\n\nThe elements are stacked such that the elements associated with range\n`elemrange[dim]` are contiguous in the element ordering.\n\nThe elements of the brick are partitioned equally across the MPI ranks based\non a space-filling curve.  Further, stacks are not split at MPI boundaries.\n\nBy default boundary faces will be marked with a one and other faces with a\nzero.  Specific boundary numbers can also be passed for each face of the brick\nin `boundary`.  This will mark the nonperiodic brick faces with the given\nboundary number.\n\n# Examples\n\nWe can build a 3 by 2 element two-dimensional mesh that is periodic in the\n\\$x2\\$-direction with\n```jldoctest brickmesh\n\nusing ClimateMachine.Topologies\nusing MPI\nMPI.Init()\ntopology = StackedBrickTopology(MPI.COMM_SELF, (2:5,4:6);\n                                periodicity=(false,true),\n                                boundary=((1,2),(3,4)))\n```\nThis returns the mesh structure stacked in the \\$x2\\$-direction for\n\n             x2\n\n              ^\n              |\n             6-  +-----+-----+-----+\n              |  |     |     |     |\n              |  |  2  |  4  |  6  |\n              |  |     |     |     |\n             5-  +-----+-----+-----+\n              |  |     |     |     |\n              |  |  1  |  3  |  5  |\n              |  |     |     |     |\n             4-  +-----+-----+-----+\n              |\n              +--|-----|-----|-----|--> x1\n                 2     3     4     5\n\nFor example, the (dimension by number of corners by number of elements) array\n`elemtocoord` gives the coordinates of the corners of each element.\n```jldoctest brickmesh\njulia> topology.elemtocoord\n2×4×6 Array{Int64,3}:\n[:, :, 1] =\n 2  3  2  3\n 4  4  5  5\n\n[:, :, 2] =\n 2  3  2  3\n 5  5  6  6\n\n[:, :, 3] =\n 3  4  3  4\n 4  4  5  5\n\n[:, :, 4] =\n 3  4  3  4\n 5  5  6  6\n\n[:, :, 5] =\n 4  5  4  5\n 4  4  5  5\n\n[:, :, 6] =\n 4  5  4  5\n 5  5  6  6\n```\nNote that the corners are listed in Cartesian order.\n\nThe (number of faces by number of elements) array `elemtobndy` gives the\nboundary number for each face of each element.  A zero will be given for\nconnected faces.\n```jldoctest brickmesh\njulia> topology.elemtobndy\n4×6 Array{Int64,2}:\n 1  0  1  0  0  0\n 0  0  0  0  2  2\n 0  0  0  0  0  0\n 0  0  0  0  0  0\n```\nNote that the faces are listed in Cartesian order.\n\"\"\"\nfunction StackedBrickTopology(\n    mpicomm,\n    elemrange;\n    boundary = ntuple(j -> (1, 1), length(elemrange)),\n    periodicity = ntuple(j -> false, length(elemrange)),\n    connectivity = :full,\n    ghostsize = 1,\n)\n\n    if boundary isa Matrix\n        boundary = tuple(mapslices(x -> tuple(x...), boundary, dims = 1)...)\n    end\n\n    dim = length(elemrange)\n\n    dim <= 1 && error(\"Stacked brick topology works for 2D and 3D\")\n\n    # Build the base topology\n    basetopo = BrickTopology(\n        mpicomm,\n        elemrange[1:(dim - 1)];\n        boundary = boundary[1:(dim - 1)],\n        periodicity = periodicity[1:(dim - 1)],\n        connectivity = connectivity,\n        ghostsize = ghostsize,\n    )\n\n\n    # Use the base topology to build the stacked topology\n    stack = elemrange[dim]\n    stacksize = length(stack) - 1\n\n    nvert = 2^dim\n    nface = 2dim\n\n    nreal = length(basetopo.realelems) * stacksize\n    nghost = length(basetopo.ghostelems) * stacksize\n\n    elems = 1:(nreal + nghost)\n    realelems = 1:nreal\n    ghostelems = nreal .+ (1:nghost)\n\n    sendelems =\n        similar(basetopo.sendelems, length(basetopo.sendelems) * stacksize)\n    for i in 1:length(basetopo.sendelems), j in 1:stacksize\n        sendelems[stacksize * (i - 1) + j] =\n            stacksize * (basetopo.sendelems[i] - 1) + j\n    end\n\n    ghostfaces = similar(basetopo.ghostfaces, nface, length(ghostelems))\n    ghostfaces .= false\n\n    for i in 1:length(basetopo.ghostelems), j in 1:stacksize\n        e = stacksize * (i - 1) + j\n        for f in 1:(2 * (dim - 1))\n            ghostfaces[f, e] = basetopo.ghostfaces[f, i]\n        end\n    end\n\n    sendfaces = similar(basetopo.sendfaces, nface, length(sendelems))\n    sendfaces .= false\n\n    for i in 1:length(basetopo.sendelems), j in 1:stacksize\n        e = stacksize * (i - 1) + j\n        for f in 1:(2 * (dim - 1))\n            sendfaces[f, e] = basetopo.sendfaces[f, i]\n        end\n    end\n\n    elemtocoord = similar(basetopo.elemtocoord, dim, nvert, length(elems))\n\n    for i in 1:length(basetopo.elems), j in 1:stacksize\n        e = stacksize * (i - 1) + j\n\n        for v in 1:(2^(dim - 1))\n            for d in 1:(dim - 1)\n                elemtocoord[d, v, e] = basetopo.elemtocoord[d, v, i]\n                elemtocoord[d, 2^(dim - 1) + v, e] =\n                    basetopo.elemtocoord[d, v, i]\n            end\n\n            elemtocoord[dim, v, e] = stack[j]\n            elemtocoord[dim, 2^(dim - 1) + v, e] = stack[j + 1]\n        end\n    end\n\n    elemtoelem = similar(basetopo.elemtoelem, nface, length(elems))\n    elemtoface = similar(basetopo.elemtoface, nface, length(elems))\n    elemtoordr = similar(basetopo.elemtoordr, nface, length(elems))\n    elemtobndy = similar(basetopo.elemtobndy, nface, length(elems))\n\n    for e in 1:(length(basetopo.elems) * stacksize), f in 1:nface\n        elemtoelem[f, e] = e\n        elemtoface[f, e] = f\n        elemtoordr[f, e] = 1\n        elemtobndy[f, e] = 0\n    end\n\n    for i in 1:length(basetopo.realelems), j in 1:stacksize\n        e1 = stacksize * (i - 1) + j\n\n        for f in 1:(2 * (dim - 1))\n            e2 = stacksize * (basetopo.elemtoelem[f, i] - 1) + j\n\n            elemtoelem[f, e1] = e2\n            elemtoface[f, e1] = basetopo.elemtoface[f, i]\n\n            # We assume a simple orientation right now\n            @assert basetopo.elemtoordr[f, i] == 1\n            elemtoordr[f, e1] = basetopo.elemtoordr[f, i]\n        end\n\n        et = stacksize * (i - 1) + j + 1\n        eb = stacksize * (i - 1) + j - 1\n        ft = 2 * (dim - 1) + 1\n        fb = 2 * (dim - 1) + 2\n        ot = 1\n        ob = 1\n\n        if j == stacksize\n            et = periodicity[dim] ? stacksize * (i - 1) + 1 : e1\n            ft = periodicity[dim] ? ft : 2 * (dim - 1) + 2\n        end\n        if j == 1\n            eb = periodicity[dim] ? stacksize * (i - 1) + stacksize : e1\n            fb = periodicity[dim] ? fb : 2 * (dim - 1) + 1\n        end\n\n        elemtoelem[2 * (dim - 1) + 1, e1] = eb\n        elemtoelem[2 * (dim - 1) + 2, e1] = et\n        elemtoface[2 * (dim - 1) + 1, e1] = fb\n        elemtoface[2 * (dim - 1) + 2, e1] = ft\n        elemtoordr[2 * (dim - 1) + 1, e1] = ob\n        elemtoordr[2 * (dim - 1) + 2, e1] = ot\n    end\n\n    for i in 1:length(basetopo.elems), j in 1:stacksize\n        e1 = stacksize * (i - 1) + j\n\n        for f in 1:(2 * (dim - 1))\n            elemtobndy[f, e1] = basetopo.elemtobndy[f, i]\n        end\n\n        bt = bb = 0\n\n        if j == stacksize\n            bt = periodicity[dim] ? bt : boundary[dim][2]\n        end\n        if j == 1\n            bb = periodicity[dim] ? bb : boundary[dim][1]\n        end\n\n        elemtobndy[2 * (dim - 1) + 1, e1] = bb\n        elemtobndy[2 * (dim - 1) + 2, e1] = bt\n    end\n\n    nabrtorank = basetopo.nabrtorank\n    nabrtorecv = UnitRange{Int}[\n        UnitRange(\n            stacksize * (first(basetopo.nabrtorecv[n]) - 1) + 1,\n            stacksize * last(basetopo.nabrtorecv[n]),\n        ) for n in 1:length(nabrtorank)\n    ]\n    nabrtosend = UnitRange{Int}[\n        UnitRange(\n            stacksize * (first(basetopo.nabrtosend[n]) - 1) + 1,\n            stacksize * last(basetopo.nabrtosend[n]),\n        ) for n in 1:length(nabrtorank)\n    ]\n\n    bndytoelem, bndytoface = BrickMesh.enumerateboundaryfaces!(\n        elemtoelem,\n        elemtobndy,\n        periodicity,\n        boundary,\n    )\n    nb = length(bndytoelem)\n\n    T = eltype(basetopo.elemtocoord)\n    #----setting up DSS----------\n    if basetopo.elemtouvert isa Array\n        #---setup vertex DSS\n        nvertby2 = div(nvert, 2)\n        elemtouvert = similar(basetopo.elemtouvert, nvert, length(elems))\n        # base level\n        for i in 1:length(basetopo.elems)\n            e = stacksize * (i - 1) + 1\n            for vt in 1:nvertby2\n                elemtouvert[vt, e] =\n                    (basetopo.elemtouvert[vt, i] - 1) * (stacksize + 1) + 1\n            end\n        end\n        # interior levels\n        for i in 1:length(basetopo.elems), j in 1:(stacksize - 1)\n            e = stacksize * (i - 1) + j\n            for vt in 1:nvertby2\n                elemtouvert[nvertby2 + vt, e] = elemtouvert[vt, e] + 1\n                elemtouvert[vt, e + 1] = elemtouvert[nvertby2 + vt, e]\n            end\n        end\n        # top level\n        if periodicity[dim]\n            for i in 1:length(basetopo.elems)\n                e = stacksize * i\n                for vt in 1:nvertby2\n                    elemtouvert[nvertby2 + vt, e] =\n                        (basetopo.elemtouvert[vt, i] - 1) * (stacksize + 1) + 1\n                end\n            end\n        else\n            for i in 1:length(basetopo.elems)\n                e = stacksize * i\n                for vt in 1:nvertby2\n                    elemtouvert[nvertby2 + vt, e] = elemtouvert[vt, e] + 1\n                end\n            end\n        end\n        vtmax = maximum(unique(elemtouvert))\n        vtconn = map(j -> zeros(Int, j), zeros(Int, vtmax))\n        for el in elems, lvt in 1:nvert\n            vt = elemtouvert[lvt, el]\n            push!(vtconn[vt], lvt) # local vertex number\n            push!(vtconn[vt], el)  # local elem number\n        end\n        # building the vertex connectivity device array\n        vtconnoff = zeros(Int, vtmax + 1)\n        vtconnoff[1] = 1\n        temp = Int64[]\n        for vt in 1:vtmax\n            nconn = length(vtconn[vt])\n            if nconn > 2\n                vtconnoff[vt + 1] = vtconnoff[vt] + nconn\n                append!(temp, vtconn[vt])\n            else\n                vtconnoff[vt + 1] = vtconnoff[vt]\n            end\n        end\n        vtconn = temp\n        #---setup face DSS---------------------\n        fcmarker = -ones(Int, nface, length(elems))\n        fcno = 0\n        for el in realelems\n            for fc in 1:nface\n                if elemtobndy[fc, el] == 0\n                    nabrel = elemtoelem[fc, el]\n                    nabrfc = elemtoface[fc, el]\n                    if fcmarker[fc, el] == -1 && fcmarker[nabrfc, nabrel] == -1\n                        fcno += 1\n                        fcmarker[fc, el] = fcno\n                        fcmarker[nabrfc, nabrel] = fcno\n                    end\n                end\n            end\n        end\n        fcconn = -ones(Int, fcno, 5)\n        for el in realelems\n            for fc in 1:nface\n                fcno = fcmarker[fc, el]\n                ordr = elemtoordr[fc, el]\n                if fcno ≠ -1\n                    nabrel = elemtoelem[fc, el]\n                    nabrfc = elemtoface[fc, el]\n                    fcconn[fcno, 1] = fc\n                    fcconn[fcno, 2] = el\n                    fcconn[fcno, 3] = nabrfc\n                    fcconn[fcno, 4] = nabrel\n                    fcconn[fcno, 5] = ordr\n                    fcmarker[fc, el] = -1\n                    fcmarker[nabrfc, nabrel] = -1\n                end\n            end\n        end\n        #---setup edge DSS---------------------\n        if dim == 3\n            nedge = 12\n            edgemask = [\n                1 3 5 7 1 2 5 6 1 2 3 4\n                2 4 6 8 3 4 7 8 5 6 7 8\n            ]\n            edges = Array{Int64}(undef, 6, nedge * length(elems))\n            ledge = zeros(Int64, 2)\n            uedge = Dict{Array{Int64, 1}, Int64}()\n            orient = 1\n            uedgno = Int64(0)\n            ctr = 1\n            for el in elems\n                for edg in 1:nedge\n                    orient = 1\n                    for i in 1:2\n                        ledge[i] = elemtouvert[edgemask[i, edg], el]\n                        edges[i, ctr] = ledge[i]                     # edge vertices [1:2]\n                    end\n                    if ledge[1] > ledge[2]\n                        sort!(ledge)\n                        orient = 2\n                    end\n                    edges[3, ctr] = edg    # local edge number\n                    edges[4, ctr] = orient # edge orientation\n                    edges[5, ctr] = el     # element number\n                    if haskey(uedge, ledge[:])\n                        edges[6, ctr] = uedge[ledge[:]]  # unique edge number\n                    else\n                        uedgno += 1\n                        uedge[ledge[:]] = uedgno\n                        edges[6, ctr] = uedgno\n                    end\n                    ctr += 1\n                end\n            end\n            edgconn = map(j -> zeros(Int, j), zeros(Int, uedgno))\n            ctr = 1\n            for el in elems\n                for edg in 1:nedge\n                    uedg = edges[6, ctr]\n                    push!(edgconn[uedg], edg)           # local edge number\n                    push!(edgconn[uedg], edges[4, ctr]) # orientation\n                    push!(edgconn[uedg], el)            # element number\n                    ctr += 1\n                end\n            end\n            # remove edges belonging to a single element and edges\n            # belonging exclusively to ghost elements\n            # and build edge connectivity device array\n            edgconnoff = Int64[]\n            temp = Int64[]\n            push!(edgconnoff, 1)\n            for i in 1:uedgno\n                elen = length(edgconn[i])\n                encls = Int(elen / 3)\n                edgmrk = true\n                if encls > 1\n                    for j in 1:encls\n                        if edgconn[i][3 * j] ≤ nreal\n                            edgmrk = false\n                        end\n                    end\n                end\n                if edgmrk\n                    edgconn[i] = []\n                else\n                    shift = edgconnoff[end]\n                    push!(edgconnoff, (shift + length(edgconn[i])))\n                    append!(temp, edgconn[i][:])\n                end\n            end\n            edgconn = temp\n        else\n            edgconn = nothing\n            edgconnoff = nothing\n        end\n    else\n        elemtouvert = nothing\n        vtconn = nothing\n        vtconnoff = nothing\n        fcconn = nothing\n        edgconn = nothing\n        edgconnoff = nothing\n    end\n    #---------------\n    StackedBrickTopology{dim, T, nb}(\n        BoxElementTopology{dim, T, nb}(\n            mpicomm,\n            elems,\n            realelems,\n            ghostelems,\n            ghostfaces,\n            sendelems,\n            sendfaces,\n            elemtocoord,\n            elemtoelem,\n            elemtoface,\n            elemtoordr,\n            elemtobndy,\n            nabrtorank,\n            nabrtorecv,\n            nabrtosend,\n            basetopo.origsendorder,\n            bndytoelem,\n            bndytoface,\n            elemtouvert = elemtouvert,\n            vtconn = vtconn,\n            vtconnoff = vtconnoff,\n            fcconn = fcconn,\n            edgconn = edgconn,\n            edgconnoff = edgconnoff,\n        ),\n        stacksize,\n        periodicity[end],\n    )\nend\n\n\"\"\"\n    CubedShellTopology(mpicomm, Nelem, T) <: AbstractTopology{dim,T,nb}\n\nGenerate a cubed shell mesh with the number of elements along each dimension of\nthe cubes being `Nelem`. This topology actually creates a cube mesh, and the\nwarping should be done after the grid is created using the `cubed_sphere_warp`\nfunction. The coordinates of the points will be of type `T`.\n\nThe elements of the shell are partitioned equally across the MPI ranks based\non a space-filling curve.\n\nNote that this topology is logically 2-D but embedded in a 3-D space\n\n# Examples\n\nWe can build a cubed shell mesh with 10*10 elements on each cube face, i.e., the\ntotal number of elements is\n`10 * 10 * 6 = 600`, with\n```jldoctest brickmesh\nusing ClimateMachine.Topologies\nusing MPI\nMPI.Init()\ntopology = CubedShellTopology(MPI.COMM_SELF, 10, Float64)\n\n# Typically the warping would be done after the grid is created, but the cell\n# corners could be warped with...\n\n# Shell radius = 1\nx1, x2, x3 = ntuple(j->topology.elemtocoord[j, :, :], 3)\nfor n = 1:length(x1)\n   x1[n], x2[n], x3[n] = Topologies.cubed_sphere_warp(EquiangularCubedSphere(), x1[n], x2[n], x3[n])\nend\n\n# in case a unitary equiangular cubed sphere is desired, or\n\n# Shell radius = 10\nx1, x2, x3 = ntuple(j->topology.elemtocoord[j, :, :], 3)\nfor n = 1:length(x1)\n  x1[n], x2[n], x3[n] = Topologies.cubed_sphere_warp(EquidistantCubedSphere(), x1[n], x2[n], x3[n], 10)\nend\n\n# in case an equidistant cubed sphere of radius 10 is desired.\n```\n\"\"\"\nfunction CubedShellTopology(\n    mpicomm,\n    Neside,\n    T;\n    connectivity = :full,\n    ghostsize = 1,\n)\n\n    # We cannot handle anything else right now...\n    @assert ghostsize == 1\n\n    mpirank = MPI.Comm_rank(mpicomm)\n    mpisize = MPI.Comm_size(mpicomm)\n\n    topology = cubedshellmesh(Neside, part = mpirank + 1, numparts = mpisize)\n\n    topology = BrickMesh.partition(mpicomm, topology...)\n    origsendorder = topology[5]\n    dim, nvert = 3, 4\n    elemtovert = topology[1]\n    nelem = size(elemtovert, 2)\n    elemtocoord = Array{T}(undef, dim, nvert, nelem)\n    ind2vert = CartesianIndices((Neside + 1, Neside + 1, Neside + 1))\n    for e in 1:nelem\n        for n in 1:nvert\n            v = elemtovert[n, e]\n            i, j, k = Tuple(ind2vert[v])\n            elemtocoord[:, n, e] =\n                (2 * [i - 1, j - 1, k - 1] .- Neside) / Neside\n        end\n    end\n\n    topology =\n        connectivity == :face ?\n        BrickMesh.connectmesh(\n            mpicomm,\n            topology[1],\n            elemtocoord,\n            topology[3],\n            topology[4],\n            dim = 2,\n        ) :\n        BrickMesh.connectmeshfull(\n            mpicomm,\n            topology[1],\n            elemtocoord,\n            topology[3],\n            topology[4],\n            dim = 2,\n        )\n\n    CubedShellTopology{T}(BoxElementTopology{2, T, 0}(\n        mpicomm,\n        topology.elems,\n        topology.realelems,\n        topology.ghostelems,\n        topology.ghostfaces,\n        topology.sendelems,\n        topology.sendfaces,\n        topology.elemtocoord,\n        topology.elemtoelem,\n        topology.elemtoface,\n        topology.elemtoordr,\n        topology.elemtobndy,\n        topology.nabrtorank,\n        topology.nabrtorecv,\n        topology.nabrtosend,\n        origsendorder,\n        (),\n        (),\n        elemtouvert = topology.elemtovert,\n    ))\nend\n\n\"\"\"\n    cubedshellmesh(T, Ne; part=1, numparts=1)\n\nGenerate a cubed mesh where each of the \"cubes\" has an `Ne X Ne` grid of\nelements.\n\nThe mesh can optionally be partitioned into `numparts` and this returns\npartition `part`. This is a simple Cartesian partition and further partitioning\n(e.g, based on a space-filling curve) should be done before the mesh is used for\ncomputation.\n\nThis mesh returns the cubed spehere in a flattened fashion for the vertex values,\nand a remapping is needed to embed the mesh in a 3-D space.\n\nThe mesh structures for the cubes is as follows:\n\n```\nx2\n   ^\n   |\n4Ne-           +-------+\n   |           |       |\n   |           |   6   |\n   |           |       |\n3Ne-           +-------+\n   |           |       |\n   |           |   5   |\n   |           |       |\n2Ne-           +-------+\n   |           |       |\n   |           |   4   |\n   |           |       |\n Ne-   +-------+-------+-------+\n   |   |       |       |       |\n   |   |   1   |   2   |   3   |\n   |   |       |       |       |\n  0-   +-------+-------+-------+\n   |\n   +---|-------|-------|------|-> x1\n       0      Ne      2Ne    3Ne\n```\n\n\"\"\"\nfunction cubedshellmesh(Ne; part = 1, numparts = 1)\n    dim = 2\n    @assert 1 <= part <= numparts\n\n    globalnelems = 6 * Ne^2\n\n    # How many vertices and faces per element\n    nvert = 2^dim # 4\n    nface = 2dim  # 4\n\n    # linearly partition to figure out which elements we own\n    elemlocal = BrickMesh.linearpartition(prod(globalnelems), part, numparts)\n\n    # elemen to vertex maps which we own\n    elemtovert = Array{Int}(undef, nvert, length(elemlocal))\n    elemtocoord = Array{Int}(undef, dim, nvert, length(elemlocal))\n\n    nelemcube = Ne^dim # Ne^2\n\n    etoijb = CartesianIndices((Ne, Ne, 6))\n    bx = [0 Ne 2Ne Ne Ne Ne]\n    by = [0 0 0 Ne 2Ne 3Ne]\n\n    vertmap = LinearIndices((Ne + 1, Ne + 1, Ne + 1))\n    for (le, e) in enumerate(elemlocal)\n        i, j, blck = Tuple(etoijb[e])\n        elemtocoord[1, :, le] = bx[blck] .+ [i - 1 i i - 1 i]\n        elemtocoord[2, :, le] = by[blck] .+ [j - 1 j - 1 j j]\n\n        for n in 1:4\n            ix = i + mod(n - 1, 2)\n            jx = j + div(n - 1, 2)\n            # set the vertices like they are the face vertices of a cube\n            if blck == 1\n                elemtovert[n, le] = vertmap[1, Ne + 2 - ix, jx]\n            elseif blck == 2\n                elemtovert[n, le] = vertmap[ix, 1, jx]\n            elseif blck == 3\n                elemtovert[n, le] = vertmap[Ne + 1, ix, jx]\n            elseif blck == 4\n                elemtovert[n, le] = vertmap[ix, jx, Ne + 1]\n            elseif blck == 5\n                elemtovert[n, le] = vertmap[ix, Ne + 1, Ne + 2 - jx]\n            elseif blck == 6\n                elemtovert[n, le] = vertmap[ix, Ne + 2 - jx, 1]\n            end\n        end\n    end\n\n    # no boundaries for a shell\n    elemtobndy = zeros(Int, nface, length(elemlocal))\n\n    # no faceconnections for a shell\n    faceconnections = Array{Array{Int, 1}}(undef, 0)\n\n    (elemtovert, elemtocoord, elemtobndy, faceconnections, collect(elemlocal))\nend\n\nabstract type AbstractCubedSphere end\nstruct EquiangularCubedSphere <: AbstractCubedSphere end\nstruct EquidistantCubedSphere <: AbstractCubedSphere end\nstruct ConformalCubedSphere <: AbstractCubedSphere end\n\n\"\"\"\n    cubed_sphere_warp(::EquiangularCubedSphere, a, b, c, R = max(abs(a), abs(b), abs(c)))\n\nGiven points `(a, b, c)` on the surface of a cube, warp the points out to a\nspherical shell of radius `R` based on the equiangular gnomonic grid proposed by\n[Ronchi1996](@cite)\n\"\"\"\nfunction cubed_sphere_warp(\n    ::EquiangularCubedSphere,\n    a,\n    b,\n    c,\n    R = max(abs(a), abs(b), abs(c)),\n)\n\n    function f(sR, ξ, η)\n        X, Y = tan(π * ξ / 4), tan(π * η / 4)\n        ζ1 = sR / sqrt(X^2 + Y^2 + 1)\n        ζ2, ζ3 = X * ζ1, Y * ζ1\n        ζ1, ζ2, ζ3\n    end\n\n    fdim = argmax(abs.((a, b, c)))\n    if fdim == 1 && a < 0\n        # (-R, *, *) : formulas for Face I from Ronchi, Iacono, Paolucci (1996)\n        #              but for us face II of the developed net of the cube\n        x1, x2, x3 = f(-R, b / a, c / a)\n    elseif fdim == 2 && b < 0\n        # ( *,-R, *) : formulas for Face II from Ronchi, Iacono, Paolucci (1996)\n        #              but for us face III of the developed net of the cube\n        x2, x1, x3 = f(-R, a / b, c / b)\n    elseif fdim == 1 && a > 0\n        # ( R, *, *) : formulas for Face III from Ronchi, Iacono, Paolucci (1996)\n        #              but for us face IV of the developed net of the cube\n        x1, x2, x3 = f(R, b / a, c / a)\n    elseif fdim == 2 && b > 0\n        # ( *, R, *) : formulas for Face IV from Ronchi, Iacono, Paolucci (1996)\n        #              but for us face I of the developed net of the cube\n        x2, x1, x3 = f(R, a / b, c / b)\n    elseif fdim == 3 && c > 0\n        # ( *, *, R) : formulas for Face V from Ronchi, Iacono, Paolucci (1996)\n        #              and the same for us on the developed net of the cube\n        x3, x2, x1 = f(R, b / c, a / c)\n    elseif fdim == 3 && c < 0\n        # ( *, *,-R) : formulas for Face VI from Ronchi, Iacono, Paolucci (1996)\n        #              and the same for us on the developed net of the cube\n        x3, x2, x1 = f(-R, b / c, a / c)\n    else\n        error(\"invalid case for cubed_sphere_warp(::EquiangularCubedSphere): $a, $b, $c\")\n    end\n\n    return x1, x2, x3\nend\n\n\"\"\"\n    equiangular_cubed_sphere_warp(a, b, c, R = max(abs(a), abs(b), abs(c)))\n\nA wrapper function for the cubed_sphere_warp function, when called with the\nEquiangularCubedSphere type\n\"\"\"\nequiangular_cubed_sphere_warp(a, b, c, R = max(abs(a), abs(b), abs(c))) =\n    cubed_sphere_warp(EquiangularCubedSphere(), a, b, c, R)\n\n\"\"\"\n    cubed_sphere_unwarp(x1, x2, x3)\n\nThe inverse of [`cubed_sphere_warp`](@ref). This function projects\na given point `(x_1, x_2, x_3)` from the surface of a sphere onto a cube\n\"\"\"\nfunction cubed_sphere_unwarp(::EquiangularCubedSphere, x1, x2, x3)\n\n    function g(R, X, Y)\n        ξ = atan(X) * 4 / pi\n        η = atan(Y) * 4 / pi\n        R, R * ξ, R * η\n    end\n\n    R = hypot(x1, x2, x3)\n    fdim = argmax(abs.((x1, x2, x3)))\n\n    if fdim == 1 && x1 < 0\n        # (-R, *, *) : formulas for Face I from Ronchi, Iacono, Paolucci (1996)\n        #              but for us face II of the developed net of the cube\n        a, b, c = g(-R, x2 / x1, x3 / x1)\n    elseif fdim == 2 && x2 < 0\n        # ( *,-R, *) : formulas for Face II from Ronchi, Iacono, Paolucci (1996)\n        #              but for us face III of the developed net of the cube\n        b, a, c = g(-R, x1 / x2, x3 / x2)\n    elseif fdim == 1 && x1 > 0\n        # ( R, *, *) : formulas for Face III from Ronchi, Iacono, Paolucci (1996)\n        #              but for us face IV of the developed net of the cube\n        a, b, c = g(R, x2 / x1, x3 / x1)\n    elseif fdim == 2 && x2 > 0\n        # ( *, R, *) : formulas for Face IV from Ronchi, Iacono, Paolucci (1996)\n        #              but for us face I of the developed net of the cube\n        b, a, c = g(R, x1 / x2, x3 / x2)\n    elseif fdim == 3 && x3 > 0\n        # ( *, *, R) : formulas for Face V from Ronchi, Iacono, Paolucci (1996)\n        #              and the same for us on the developed net of the cube\n        c, b, a = g(R, x2 / x3, x1 / x3)\n    elseif fdim == 3 && x3 < 0\n        # ( *, *,-R) : formulas for Face VI from Ronchi, Iacono, Paolucci (1996)\n        #              and the same for us on the developed net of the cube\n        c, b, a = g(-R, x2 / x3, x1 / x3)\n    else\n        error(\"invalid case for cubed_sphere_unwarp(::EquiangularCubedSphere): $a, $b, $c\")\n    end\n\n    return a, b, c\nend\n\n\"\"\"\n    equiangular_cubed_sphere_unwarp(x1, x2, x3)\n\nA wrapper function for the cubed_sphere_unwarp function, when called with the\nEquiangularCubedSphere type\n\"\"\"\nequiangular_cubed_sphere_unwarp(x1, x2, x3) =\n    cubed_sphere_unwarp(EquiangularCubedSphere(), x1, x2, x3)\n\n\"\"\"\n    cubed_sphere_warp(a, b, c, R = max(abs(a), abs(b), abs(c)))\n\nGiven points `(a, b, c)` on the surface of a cube, warp the points out to a\nspherical shell of radius `R` based on the equidistant gnomonic grid outlined in\n[Rancic1996](@cite) and [Nair2005](@cite)\n\"\"\"\nfunction cubed_sphere_warp(\n    ::EquidistantCubedSphere,\n    a,\n    b,\n    c,\n    R = max(abs(a), abs(b), abs(c)),\n)\n\n    r = hypot(a, b, c)\n\n    x1 = R * a / r\n    x2 = R * b / r\n    x3 = R * c / r\n\n    return x1, x2, x3\nend\n\n\"\"\"\n    equidistant_cubed_sphere_warp(a, b, c, R = max(abs(a), abs(b), abs(c)))\n\nA wrapper function for the cubed_sphere_warp function, when called with the\nEquidistantCubedSphere type\n\"\"\"\nequidistant_cubed_sphere_warp(a, b, c, R = max(abs(a), abs(b), abs(c))) =\n    cubed_sphere_warp(EquidistantCubedSphere(), a, b, c, R)\n\n\"\"\"\n    cubed_sphere_unwarp(x1, x2, x3)\n\nThe inverse of [`cubed_sphere_warp`](@ref). This function projects\na given point `(x_1, x_2, x_3)` from the surface of a sphere onto a cube\n\"\"\"\nfunction cubed_sphere_unwarp(::EquidistantCubedSphere, x1, x2, x3)\n\n    r = hypot(1, x2 / x1, x3 / x1)\n    R = hypot(x1, x2, x3)\n\n    a = r * x1\n    b = r * x2\n    c = r * x3\n\n    m = max(abs(a), abs(b), abs(c))\n\n    return a * R / m, b * R / m, c * R / m\nend\n\n\"\"\"\n    equidistant_cubed_sphere_unwarp(x1, x2, x3)\n\nA wrapper function for the cubed_sphere_unwarp function, when called with the\nEquidistantCubedSphere type\n\"\"\"\nequidistant_cubed_sphere_unwarp(x1, x2, x3) =\n    cubed_sphere_unwarp(EquidistantCubedSphere(), x1, x2, x3)\n\n\"\"\"\n    cubed_sphere_warp(::ConformalCubedSphere, a, b, c, R = max(abs(a), abs(b), abs(c)))\n\nGiven points `(a, b, c)` on the surface of a cube, warp the points out to a\nspherical shell of radius `R` based on the conformal grid proposed by\n[Rancic1996](@cite)\n\"\"\"\nfunction cubed_sphere_warp(\n    ::ConformalCubedSphere,\n    a,\n    b,\n    c,\n    R = max(abs(a), abs(b), abs(c)),\n)\n\n    fdim = argmax(abs.((a, b, c)))\n    M = max(abs.((a, b, c))...)\n    if fdim == 1 && a < 0\n        # left face\n        x1, x2, x3 = conformal_cubed_sphere_mapping(-b / M, c / M)\n        x1, x2, x3 = RotX(π / 2) * RotY(-π / 2) * [x1, x2, x3]\n    elseif fdim == 2 && b < 0\n        # front face\n        x1, x2, x3 = conformal_cubed_sphere_mapping(a / M, c / M)\n        x1, x2, x3 = RotX(π / 2) * [x1, x2, x3]\n    elseif fdim == 1 && a > 0\n        # right face\n        x1, x2, x3 = conformal_cubed_sphere_mapping(b / M, c / M)\n        x1, x2, x3 = RotX(π / 2) * RotY(π / 2) * [x1, x2, x3]\n    elseif fdim == 2 && b > 0\n        # back face\n        x1, x2, x3 = conformal_cubed_sphere_mapping(a / M, -c / M)\n        x1, x2, x3 = RotX(-π / 2) * [x1, x2, x3]\n    elseif fdim == 3 && c > 0\n        # top face\n        x1, x2, x3 = conformal_cubed_sphere_mapping(a / M, b / M)\n    elseif fdim == 3 && c < 0\n        # bottom face\n        x1, x2, x3 = conformal_cubed_sphere_mapping(a / M, -b / M)\n        x1, x2, x3 = RotX(π) * [x1, x2, x3]\n    else\n        error(\"invalid case for cubed_sphere_warp(::ConformalCubedSphere): $a, $b, $c\")\n    end\n\n    return x1 * R, x2 * R, x3 * R\nend\n\n\"\"\"\n    conformal_cubed_sphere_warp(a, b, c, R = max(abs(a), abs(b), abs(c)))\n\nA wrapper function for the cubed_sphere_warp function, when called with the\nConformalCubedSphere type\n\"\"\"\nconformal_cubed_sphere_warp(a, b, c, R = max(abs(a), abs(b), abs(c))) =\n    cubed_sphere_warp(ConformalCubedSphere(), a, b, c, R)\n\n\"\"\"\n    cubed_sphere_unwarp(::ConformalCubedSphere, x1, x2, x3)\n\nThe inverse of [`cubed_sphere_warp`](@ref). This function projects\na given point `(x_1, x_2, x_3)` from the surface of a sphere onto a cube\n[Rancic1996](@cite)\n\"\"\"\nfunction cubed_sphere_unwarp(::ConformalCubedSphere, x1, x2, x3)\n\n    # Auxuliary function that flips coordinates, if needed, to prepare input\n    # arguments in the correct quadrant for the `conformal_cubed_sphere_inverse_mapping`\n    # function. Then, flips the output of `conformal_cubed_sphere_inverse_mapping`\n    # back to original face and scales the coordinates so that result is on the cube\n    function flip_unwarp_scale(x1, x2, x3)\n        R = hypot(x1, x2, x3)\n        flipx1, flipx2 = false, false\n        if x1 < 0 # flip the point around x2 axis\n            x1 = -x1\n            flipx1 = true\n        end\n        if x2 < 0 # flip the point around x1 axis\n            x2 = -x2\n            flipx2 = true\n        end\n        a, b = conformal_cubed_sphere_inverse_mapping(x1 / R, x2 / R, x3 / R)\n        if flipx1 == true\n            a = -a\n        end\n        if flipx2 == true\n            b = -b\n        end\n\n        # Rescale to desired length\n        a *= R\n        b *= R\n        # Since we were trating coordinates on top face of the cube, the c\n        # coordinate must have the top-face z value (z = R)\n        c = R\n\n        return a, b, c\n    end\n\n    fdim = argmax(abs.((x1, x2, x3)))\n    if fdim == 1 && x1 < 0 # left face\n        # rotate to align with top face\n        x1, x2, x3 = RotY(π / 2) * RotX(-π / 2) * [x1, x2, x3]\n        # call the unwarp function, with appropriate flipping and scaling\n        a, b, c = flip_unwarp_scale(x1, x2, x3)\n        # rotate back\n        a, b, c = RotX(π / 2) * RotY(-π / 2) * [a, b, c]\n    elseif fdim == 2 && x2 < 0 # front face\n        # rotate to align with top face\n        x1, x2, x3 = RotX(-π / 2) * [x1, x2, x3]\n        # call the unwarp function, with appropriate flipping and scaling\n        a, b, c = flip_unwarp_scale(x1, x2, x3)\n        # rotate back\n        a, b, c = RotX(π / 2) * [a, b, c]\n    elseif fdim == 1 && x1 > 0 # right face\n        # rotate to align with top face\n        x1, x2, x3 = RotZ(-π / 2) * RotY(-π / 2) * [x1, x2, x3]\n        # call the unwarp function, with appropriate flipping and scaling\n        a, b, c = flip_unwarp_scale(x1, x2, x3)\n        # rotate back\n        a, b, c = RotY(π / 2) * RotZ(π / 2) * [a, b, c]\n    elseif fdim == 2 && x2 > 0 # back face\n        # rotate to align with top face\n        x1, x2, x3 = RotZ(π) * RotX(π / 2) * [x1, x2, x3]\n        # call the unwarp function, with appropriate flipping and scaling\n        a, b, c = flip_unwarp_scale(x1, x2, x3)\n        # rotate back\n        a, b, c = RotX(-π / 2) * RotZ(-π) * [a, b, c]\n    elseif fdim == 3 && x3 > 0 # top face\n        # already on top face, no need to rotate\n        a, b, c = flip_unwarp_scale(x1, x2, x3)\n    elseif fdim == 3 && x3 < 0 # bottom face\n        # rotate to align with top face\n        x1, x2, x3 = RotX(π) * [x1, x2, x3]\n        # call the unwarp function, with appropriate flipping and scaling\n        a, b, c = flip_unwarp_scale(x1, x2, x3)\n        # rotate back\n        a, b, c = RotX(-π) * [a, b, c]\n    else\n        error(\"invalid case for cubed_sphere_unwarp(::ConformalCubedSphere): $a, $b, $c\")\n    end\n\n    return a, b, c\nend\n\n\"\"\"\n    conformal_cubed_sphere_unwarp(x1, x2, x3)\n\nA wrapper function for the cubed_sphere_unwarp function, when called with the\nConformalCubedSphere type\n\"\"\"\nconformal_cubed_sphere_unwarp(x1, x2, x3) =\n    cubed_sphere_unwarp(ConformalCubedSphere(), x1, x2, x3)\n\n\"\"\"\n   StackedCubedSphereTopology(mpicomm, Nhorz, Rrange;\n                              boundary=(1,1)) <: AbstractTopology{3}\n\nGenerate a stacked cubed sphere topology with `Nhorz` by `Nhorz` cells for each\nhorizontal face and `Rrange` is the radius edges of the stacked elements. This\ntopology actual creates a cube mesh, and the warping should be done after the\ngrid is created using the `cubed_sphere_warp` function. The coordinates of the\npoints will be of type `eltype(Rrange)`. The inner boundary condition type is\n`boundary[1]` and the outer boundary condition type is `boundary[2]`.\n\nThe elements are stacked such that the vertical elements are contiguous in the\nelement ordering.\n\nThe elements of the brick are partitioned equally across the MPI ranks based\non a space-filling curve. Further, stacks are not split at MPI boundaries.\n\n# Examples\n\nWe can build a cubed sphere mesh with 10 x 10 x 5 elements on each cube face,\ni.e., the total number of elements is `10 * 10 * 5 * 6 = 3000`, with\n```jldoctest brickmesh\nusing ClimateMachine.Topologies\nusing MPI\nMPI.Init()\nNhorz = 10\nNstack = 5\nRrange = Float64.(accumulate(+,1:Nstack+1))\ntopology = StackedCubedSphereTopology(MPI.COMM_SELF, Nhorz, Rrange)\n\nx1, x2, x3 = ntuple(j->reshape(topology.elemtocoord[j, :, :],\n                            2, 2, 2, length(topology.elems)), 3)\nfor n = 1:length(x1)\n   x1[n], x2[n], x3[n] = Topologies.cubed_sphere_warp(EquiangularCubedSphere(),x1[n], x2[n], x3[n])\nend\n```\nNote that the faces are listed in Cartesian order.\n\"\"\"\nfunction StackedCubedSphereTopology(\n    mpicomm,\n    Nhorz,\n    Rrange;\n    boundary = (1, 1),\n    connectivity = :full,\n    ghostsize = 1,\n)\n    T = eltype(Rrange)\n\n    basetopo = CubedShellTopology(\n        mpicomm,\n        Nhorz,\n        T;\n        connectivity = connectivity,\n        ghostsize = ghostsize,\n    )\n\n    dim = 3\n    nvert = 2^dim\n    nface = 2dim\n    stacksize = length(Rrange) - 1\n\n    nreal = length(basetopo.realelems) * stacksize\n    nghost = length(basetopo.ghostelems) * stacksize\n\n    elems = 1:(nreal + nghost)\n    realelems = 1:nreal\n    ghostelems = nreal .+ (1:nghost)\n\n    sendelems =\n        similar(basetopo.sendelems, length(basetopo.sendelems) * stacksize)\n\n    for i in 1:length(basetopo.sendelems), j in 1:stacksize\n        sendelems[stacksize * (i - 1) + j] =\n            stacksize * (basetopo.sendelems[i] - 1) + j\n    end\n\n    ghostfaces = similar(basetopo.ghostfaces, nface, length(ghostelems))\n    ghostfaces .= false\n\n    for i in 1:length(basetopo.ghostelems), j in 1:stacksize\n        e = stacksize * (i - 1) + j\n        for f in 1:(2 * (dim - 1))\n            ghostfaces[f, e] = basetopo.ghostfaces[f, i]\n        end\n    end\n\n    sendfaces = similar(basetopo.sendfaces, nface, length(sendelems))\n    sendfaces .= false\n\n    for i in 1:length(basetopo.sendelems), j in 1:stacksize\n        e = stacksize * (i - 1) + j\n        for f in 1:(2 * (dim - 1))\n            sendfaces[f, e] = basetopo.sendfaces[f, i]\n        end\n    end\n\n    elemtocoord = similar(basetopo.elemtocoord, dim, nvert, length(elems))\n\n    for i in 1:length(basetopo.elems), j in 1:stacksize\n        # i is base element\n        # e is stacked element\n        e = stacksize * (i - 1) + j\n\n\n        # v is base vertex\n        for v in 1:(2^(dim - 1))\n            for d in 1:dim # dim here since shell is embedded in 3-D\n                # v is lower stacked vertex\n                elemtocoord[d, v, e] = basetopo.elemtocoord[d, v, i] * Rrange[j]\n                # 2^(dim-1) + v is higher stacked vertex\n                elemtocoord[d, 2^(dim - 1) + v, e] =\n                    basetopo.elemtocoord[d, v, i] * Rrange[j + 1]\n            end\n        end\n    end\n\n    elemtoelem = similar(basetopo.elemtoelem, nface, length(elems))\n    elemtoface = similar(basetopo.elemtoface, nface, length(elems))\n    elemtoordr = similar(basetopo.elemtoordr, nface, length(elems))\n    elemtobndy = similar(basetopo.elemtobndy, nface, length(elems))\n\n    for e in 1:(length(basetopo.elems) * stacksize), f in 1:nface\n        elemtoelem[f, e] = e\n        elemtoface[f, e] = f\n        elemtoordr[f, e] = 1\n        elemtobndy[f, e] = 0\n    end\n\n    for i in 1:length(basetopo.realelems), j in 1:stacksize\n        e1 = stacksize * (i - 1) + j\n\n        for f in 1:(2 * (dim - 1))\n            e2 = stacksize * (basetopo.elemtoelem[f, i] - 1) + j\n\n            elemtoelem[f, e1] = e2\n            elemtoface[f, e1] = basetopo.elemtoface[f, i]\n\n            # since the basetopo is 2-D we only need to worry about two\n            # orientations\n            @assert basetopo.elemtoordr[f, i] ∈ (1, 2)\n            #=\n            orientation 1:\n            2---3     2---3\n            |   | --> |   |\n            0---1     0---1\n            same:\n            (a,b) --> (a,b)\n\n            orientation 3:\n            2---3     3---2\n            |   | --> |   |\n            0---1     1---0\n            reverse first index:\n            (a,b) --> (N+1-a,b)\n            =#\n            elemtoordr[f, e1] = basetopo.elemtoordr[f, i] == 1 ? 1 : 3\n        end\n\n        # If top or bottom of stack set neighbor to self on respective face\n        elemtoelem[2 * (dim - 1) + 1, e1] =\n            j == 1 ? e1 : stacksize * (i - 1) + j - 1\n        elemtoelem[2 * (dim - 1) + 2, e1] =\n            j == stacksize ? e1 : stacksize * (i - 1) + j + 1\n\n        elemtoface[2 * (dim - 1) + 1, e1] =\n            j == 1 ? 2 * (dim - 1) + 1 : 2 * (dim - 1) + 2\n        elemtoface[2 * (dim - 1) + 2, e1] =\n            j == stacksize ? 2 * (dim - 1) + 2 : 2 * (dim - 1) + 1\n\n        elemtoordr[2 * (dim - 1) + 1, e1] = 1\n        elemtoordr[2 * (dim - 1) + 2, e1] = 1\n    end\n\n    # Set the top and bottom boundary condition\n    for i in 1:length(basetopo.elems)\n        eb = stacksize * (i - 1) + 1\n        et = stacksize * (i - 1) + stacksize\n\n        elemtobndy[2 * (dim - 1) + 1, eb] = boundary[1]\n        elemtobndy[2 * (dim - 1) + 2, et] = boundary[2]\n    end\n\n    nabrtorank = basetopo.nabrtorank\n    nabrtorecv = UnitRange{Int}[\n        UnitRange(\n            stacksize * (first(basetopo.nabrtorecv[n]) - 1) + 1,\n            stacksize * last(basetopo.nabrtorecv[n]),\n        ) for n in 1:length(nabrtorank)\n    ]\n    nabrtosend = UnitRange{Int}[\n        UnitRange(\n            stacksize * (first(basetopo.nabrtosend[n]) - 1) + 1,\n            stacksize * last(basetopo.nabrtosend[n]),\n        ) for n in 1:length(nabrtorank)\n    ]\n\n    bndytoelem, bndytoface = BrickMesh.enumerateboundaryfaces!(\n        elemtoelem,\n        elemtobndy,\n        (false,),\n        (boundary,),\n    )\n    nb = length(bndytoelem)\n    #----setting up DSS----------\n    if basetopo.elemtouvert isa Array\n        #---setup vertex DSS\n        nvertby2 = div(nvert, 2)\n        elemtouvert = similar(basetopo.elemtouvert, nvert, length(elems))\n        # base level\n        for i in 1:length(basetopo.elems)\n            e = stacksize * (i - 1) + 1\n            for vt in 1:nvertby2\n                elemtouvert[vt, e] =\n                    (basetopo.elemtouvert[vt, i] - 1) * (stacksize + 1) + 1\n            end\n        end\n        # interior levels\n        for i in 1:length(basetopo.elems), j in 1:(stacksize - 1)\n            e = stacksize * (i - 1) + j\n            for vt in 1:nvertby2\n                elemtouvert[nvertby2 + vt, e] = elemtouvert[vt, e] + 1\n                elemtouvert[vt, e + 1] = elemtouvert[nvertby2 + vt, e]\n            end\n        end\n        # top level\n        for i in 1:length(basetopo.elems)\n            e = stacksize * i\n            for vt in 1:nvertby2\n                elemtouvert[nvertby2 + vt, e] = elemtouvert[vt, e] + 1\n            end\n        end\n        vtmax = maximum(unique(elemtouvert))\n        vtconn = map(j -> zeros(Int, j), zeros(Int, vtmax))\n\n        for el in elems, lvt in 1:nvert\n            vt = elemtouvert[lvt, el]\n            push!(vtconn[vt], lvt) # local vertex number\n            push!(vtconn[vt], el)  # local elem number\n        end\n        # building the vertex connectivity device array\n        vtconnoff = zeros(Int, vtmax + 1)\n        vtconnoff[1] = 1\n        temp = Int64[]\n        for vt in 1:vtmax\n            nconn = length(vtconn[vt])\n            if nconn > 2\n                vtconnoff[vt + 1] = vtconnoff[vt] + nconn\n                append!(temp, vtconn[vt])\n            else\n                vtconnoff[vt + 1] = vtconnoff[vt]\n            end\n        end\n        vtconn = temp\n        #---setup face DSS---------------------\n        fcmarker = -ones(Int, nface, length(elems))\n        fcno = 0\n        for el in realelems\n            for fc in 1:nface\n                if elemtobndy[fc, el] == 0\n                    nabrel = elemtoelem[fc, el]\n                    nabrfc = elemtoface[fc, el]\n                    if fcmarker[fc, el] == -1 && fcmarker[nabrfc, nabrel] == -1\n                        fcno += 1\n                        fcmarker[fc, el] = fcno\n                        fcmarker[nabrfc, nabrel] = fcno\n                    end\n                end\n            end\n        end\n        fcconn = -ones(Int, fcno, 5)\n        for el in realelems\n            for fc in 1:nface\n                fcno = fcmarker[fc, el]\n                ordr = elemtoordr[fc, el]\n                if fcno ≠ -1\n                    nabrel = elemtoelem[fc, el]\n                    nabrfc = elemtoface[fc, el]\n                    fcconn[fcno, 1] = fc\n                    fcconn[fcno, 2] = el\n                    fcconn[fcno, 3] = nabrfc\n                    fcconn[fcno, 4] = nabrel\n                    fcconn[fcno, 5] = ordr\n                    fcmarker[fc, el] = -1\n                    fcmarker[nabrfc, nabrel] = -1\n                end\n            end\n        end\n        #---setup edge DSS---------------------\n        if dim == 3\n            nedge = 12\n            edgemask = [\n                1 3 5 7 1 2 5 6 1 2 3 4\n                2 4 6 8 3 4 7 8 5 6 7 8\n            ]\n            edges = Array{Int64}(undef, 6, nedge * length(elems))\n            ledge = zeros(Int64, 2)\n            uedge = Dict{Array{Int64, 1}, Int64}()\n            orient = 1\n            uedgno = Int64(0)\n            ctr = 1\n            for el in elems\n                for edg in 1:nedge\n                    orient = 1\n                    for i in 1:2\n                        ledge[i] = elemtouvert[edgemask[i, edg], el]\n                        edges[i, ctr] = ledge[i]                     # edge vertices [1:2]\n                    end\n                    if ledge[1] > ledge[2]\n                        sort!(ledge)\n                        orient = 2\n                    end\n                    edges[3, ctr] = edg    # local edge number\n                    edges[4, ctr] = orient # edge orientation\n                    edges[5, ctr] = el     # element number\n                    if haskey(uedge, ledge[:])\n                        edges[6, ctr] = uedge[ledge[:]]  # unique edge number\n                    else\n                        uedgno += 1\n                        uedge[ledge[:]] = uedgno\n                        edges[6, ctr] = uedgno\n                    end\n                    ctr += 1\n                end\n            end\n            edgconn = map(j -> zeros(Int, j), zeros(Int, uedgno))\n            ctr = 1\n            for el in elems\n                for edg in 1:nedge\n                    uedg = edges[6, ctr]\n                    push!(edgconn[uedg], edg)           # local edge number\n                    push!(edgconn[uedg], edges[4, ctr]) # orientation\n                    push!(edgconn[uedg], el)            # element number\n                    ctr += 1\n                end\n            end\n            # remove edges belonging to a single element and edges\n            # belonging exclusively to ghost elements\n            # and build edge connectivity device array\n            edgconnoff = Int64[]\n            temp = Int64[]\n            push!(edgconnoff, 1)\n            for i in 1:uedgno\n                elen = length(edgconn[i])\n                encls = Int(elen / 3)\n                edgmrk = true\n                if encls > 1\n                    for j in 1:encls\n                        if edgconn[i][3 * j] ≤ nreal\n                            edgmrk = false\n                        end\n                    end\n                end\n                if edgmrk\n                    edgconn[i] = []\n                else\n                    shift = edgconnoff[end]\n                    push!(edgconnoff, (shift + length(edgconn[i])))\n                    append!(temp, edgconn[i][:])\n                end\n            end\n            edgconn = temp\n        else\n            edgconn = nothing\n            edgconnoff = nothing\n        end\n        #-------------------------------------\n    else\n        elemtouvert = nothing\n        vtconn = nothing\n        vtconnoff = nothing\n        fcconn = nothing\n        edgconn = nothing\n        edgconnoff = nothing\n    end\n    #-----------------------------\n    StackedCubedSphereTopology{T, nb}(\n        BoxElementTopology{3, T, nb}(\n            mpicomm,\n            elems,\n            realelems,\n            ghostelems,\n            ghostfaces,\n            sendelems,\n            sendfaces,\n            elemtocoord,\n            elemtoelem,\n            elemtoface,\n            elemtoordr,\n            elemtobndy,\n            nabrtorank,\n            nabrtorecv,\n            nabrtosend,\n            basetopo.origsendorder,\n            bndytoelem,\n            bndytoface,\n            elemtouvert = elemtouvert,\n            vtconn = vtconn,\n            vtconnoff = vtconnoff,\n            fcconn = fcconn,\n            edgconn = edgconn,\n            edgconnoff = edgconnoff,\n        ),\n        stacksize,\n    )\nend\n\n\"\"\"\n    grid1d(a, b[, stretch::AbstractGridStretching]; elemsize, nelem)\n\nDiscretize the 1D interval [`a`,`b`] into elements.\nExactly one of the following keyword arguments must be provided:\n- `elemsize`: the average element size, or\n- `nelem`: the number of elements.\n\nThe optional `stretch` argument allows stretching, otherwise the element sizes\nwill be uniform.\n\nReturns either a range object or a vector containing the element boundaries.\n\"\"\"\nfunction grid1d(a, b, stretch = nothing; elemsize = nothing, nelem = nothing)\n    xor(nelem === nothing, elemsize === nothing) ||\n        error(\"Either `elemsize` or `nelem` arguments must be provided\")\n    if elemsize !== nothing\n        nelem = round(Int, abs(b - a) / elemsize)\n    end\n    grid1d(a, b, stretch, nelem)\nend\nfunction grid1d(a, b, ::Nothing, nelem)\n    range(a, stop = b, length = nelem + 1)\nend\n\n# TODO: document these\nabstract type AbstractGridStretching end\n\n\"\"\"\n    SingleExponentialStretching(A)\n\nApply single-exponential stretching: `A > 0` will increase the density of points\nat the lower boundary, `A < 0` will increase the density at the upper boundary.\n\n# Reference\n* \"Handbook of Grid Generation\" J. F. Thompson, B. K. Soni, N. P. Weatherill\n  (Editors) RCR Press 1999, §3.6.1 Single-Exponential Function\n\"\"\"\nstruct SingleExponentialStretching{T} <: AbstractGridStretching\n    A::T\nend\nfunction grid1d(\n    a::A,\n    b::B,\n    stretch::SingleExponentialStretching,\n    nelem,\n) where {A, B}\n    F = float(promote_type(A, B))\n    s = range(zero(F), stop = one(F), length = nelem + 1)\n    a .+ (b - a) .* expm1.(stretch.A .* s) ./ expm1(stretch.A)\nend\n\nstruct InteriorStretching{T} <: AbstractGridStretching\n    attractor::T\nend\nfunction grid1d(a::A, b::B, stretch::InteriorStretching, nelem) where {A, B}\n    F = float(promote_type(A, B))\n    coe = F(2.5)\n    s = range(zero(F), stop = one(F), length = nelem + 1)\n    range(a, stop = b, length = nelem + 1) .+\n    coe .* (stretch.attractor .- (b - a) .* s) .* (1 .- s) .* s\nend\n\nfunction basic_topology_info(topology::AbstractStackedTopology)\n    nelem = length(topology.elems)\n    nvertelem = topology.stacksize\n    nhorzelem = div(nelem, nvertelem)\n    nrealelem = length(topology.realelems)\n    nhorzrealelem = div(nrealelem, nvertelem)\n\n    return (\n        nelem = nelem,\n        nvertelem = nvertelem,\n        nhorzelem = nhorzelem,\n        nrealelem = nrealelem,\n        nhorzrealelem = nhorzrealelem,\n    )\nend\n\nfunction basic_topology_info(topology::AbstractTopology)\n    return (\n        nelem = length(topology.elems),\n        nrealelem = length(topology.realelems),\n    )\nend\n\n\n### Helper Functions for Topography Calculations\n\"\"\"\n    compute_lat_long(X,Y,δ,faceid)\nHelper function to allow computation of latitute and longitude coordinates\ngiven the cubed sphere coordinates X, Y, δ, faceid\n\"\"\"\nfunction compute_lat_long(X, Y, δ, faceid)\n    if faceid == 1\n        λ = atan(X)                     # longitude\n        ϕ = atan(cos(λ) * Y)            # latitude\n    elseif faceid == 2\n        λ = atan(X) + π / 2\n        ϕ = atan(Y * cos(atan(X)))\n    elseif faceid == 3\n        λ = atan(X) + π\n        ϕ = atan(Y * cos(atan(X)))\n    elseif faceid == 4\n        λ = atan(X) + (3 / 2) * π\n        ϕ = atan(Y * cos(atan(X)))\n    elseif faceid == 5\n        λ = atan(X, -Y) + π\n        ϕ = atan(1 / sqrt(δ - 1))\n    elseif faceid == 6\n        λ = atan(X, Y)\n        ϕ = -atan(1 / sqrt(δ - 1))\n    end\n    return λ, ϕ\nend\n\n\n\"\"\"\n    AnalyticalTopography\nAbstract type to allow dispatch over different analytical topography prescriptions\nin experiments.\n\"\"\"\nabstract type AnalyticalTopography end\n\nfunction compute_analytical_topography(\n    ::AnalyticalTopography,\n    λ,\n    ϕ,\n    sR,\n    r_inner,\n    r_outer,\n)\n    return sR\nend\n\n\"\"\"\n    NoTopography <: AnalyticalTopography\nAllows definition of fallback methods in case cubed_sphere_topo_warp is used with\nno prescribed topography function.\n\"\"\"\nstruct NoTopography <: AnalyticalTopography end\n\n### DCMIP Mountain\n\"\"\"\n    DCMIPMountain <: AnalyticalTopography\nTopography description based on standard DCMIP experiments.\n\"\"\"\nstruct DCMIPMountain <: AnalyticalTopography end\nfunction compute_analytical_topography(\n    ::DCMIPMountain,\n    λ,\n    ϕ,\n    sR,\n    r_inner,\n    r_outer,\n)\n    #User specified warp parameters\n    R_m = π * 3 / 4\n    h0 = 2000\n    ζ_m = π / 16\n    φ_m = 0\n    λ_m = π * 3 / 2\n    r_m = acos(sin(φ_m) * sin(ϕ) + cos(φ_m) * cos(ϕ) * cos(λ - λ_m))\n    # Define mesh decay profile\n    Δ = (r_outer - abs(sR)) / (r_outer - r_inner)\n    if r_m < R_m\n        zs =\n            0.5 *\n            h0 *\n            (1 + cos(π * r_m / R_m)) *\n            cos(π * r_m / ζ_m) *\n            cos(π * r_m / ζ_m)\n    else\n        zs = 0.0\n    end\n    mR = sign(sR) * (abs(sR) + zs * Δ)\n    return mR\nend\n\n\"\"\"\n    cubed_sphere_topo_warp(a, b, c, R = max(abs(a), abs(b), abs(c));\n                       r_inner = _planet_radius,\n                       r_outer = _planet_radius + domain_height,\n                       topography = NoTopography())\n\nGiven points `(a, b, c)` on the surface of a cube, warp the points out to a\nspherical shell of radius `R` based on the equiangular gnomonic grid proposed by\n[Ronchi1996](@cite). Assumes a user specified modified radius using the\ncompute_analytical_topography function. Defaults to smooth cubed sphere unless otherwise specified\nvia the AnalyticalTopography type.\n\"\"\"\nfunction cubed_sphere_topo_warp(\n    a,\n    b,\n    c,\n    R = max(abs(a), abs(b), abs(c));\n    r_inner = _planet_radius,\n    r_outer = _planet_radius + domain_height,\n    topography::AnalyticalTopography = NoTopography(),\n)\n\n    function f(sR, ξ, η, faceid)\n        X, Y = tan(π * ξ / 4), tan(π * η / 4)\n        δ = 1 + X^2 + Y^2\n        λ, ϕ = compute_lat_long(X, Y, δ, faceid)\n        mR = compute_analytical_topography(\n            topography,\n            λ,\n            ϕ,\n            sR,\n            r_inner,\n            r_outer,\n        )\n        x1 = mR / sqrt(δ)\n        x2, x3 = X * x1, Y * x1\n        x1, x2, x3\n    end\n    fdim = argmax(abs.((a, b, c)))\n\n    if fdim == 1 && a < 0\n        faceid = 1\n        # (-R, *, *) : Face I from Ronchi, Iacono, Paolucci (1996)\n        x1, x2, x3 = f(-R, b / a, c / a, faceid)\n    elseif fdim == 2 && b < 0\n        faceid = 2\n        # ( *,-R, *) : Face II from Ronchi, Iacono, Paolucci (1996)\n        x2, x1, x3 = f(-R, a / b, c / b, faceid)\n    elseif fdim == 1 && a > 0\n        faceid = 3\n        # ( R, *, *) : Face III from Ronchi, Iacono, Paolucci (1996)\n        x1, x2, x3 = f(R, b / a, c / a, faceid)\n    elseif fdim == 2 && b > 0\n        faceid = 4\n        # ( *, R, *) : Face IV from Ronchi, Iacono, Paolucci (1996)\n        x2, x1, x3 = f(R, a / b, c / b, faceid)\n    elseif fdim == 3 && c > 0\n        faceid = 5\n        # ( *, *, R) : Face V from Ronchi, Iacono, Paolucci (1996)\n        x3, x2, x1 = f(R, b / c, a / c, faceid)\n    elseif fdim == 3 && c < 0\n        faceid = 6\n        # ( *, *,-R) : Face VI from Ronchi, Iacono, Paolucci (1996)\n        x3, x2, x1 = f(-R, b / c, a / c, faceid)\n    else\n        error(\"invalid case for cubed_sphere_warp(::EquiangularCubedSphere): $a, $b, $c\")\n    end\n\n    return x1, x2, x3\nend\n\nend\n"
  },
  {
    "path": "src/Numerics/ODESolvers/AdditiveRungeKuttaMethod.jl",
    "content": "export AbstractAdditiveRungeKutta\nexport LowStorageVariant, NaiveVariant\nexport AdditiveRungeKutta\nexport ARK1ForwardBackwardEuler\nexport ARK2ImplicitExplicitMidpoint\nexport ARK2GiraldoKellyConstantinescu\nexport ARK548L2SA2KennedyCarpenter, ARK437L2SA1KennedyCarpenter\nexport Trap2LockWoodWeller\nexport DBM453VoglEtAl\n\n# Naive formulation that uses equation 3.8 from Giraldo, Kelly, and\n# Constantinescu (2013) directly.  Seems to cut the number of solver iterations\n# by half but requires Nstages - 1 additional storage.\nstruct NaiveVariant end\nadditional_storage(::NaiveVariant, Q, Nstages) =\n    (Lstages = ntuple(i -> similar(Q), Nstages),)\n\n# Formulation that does things exactly as in Giraldo, Kelly, and Constantinescu\n# (2013).  Uses only one additional vector of storage regardless of the number\n# of stages.\nstruct LowStorageVariant end\nadditional_storage(::LowStorageVariant, Q, Nstages) = (Qtt = similar(Q),)\n\nabstract type AbstractAdditiveRungeKutta <: AbstractODESolver end\n\n\"\"\"\n    AdditiveRungeKutta(f, l, backward_euler_solver, RKAe, RKAi, RKB, RKC, Q;\n                       split_explicit_implicit, variant, dt, t0 = 0)\n\nThis is a time stepping object for implicit-explicit time stepping of a\ndecomposed differential equation. When `split_explicit_implicit == false`\nthe equation is assumed to be decomposed as\n\n```math\n  \\\\dot{Q} = [l(Q, t)] + [f(Q, t) - l(Q, t)]\n```\n\nwhere `Q` is the state, `f` is the full tendency and\n`l` is the chosen implicit operator. When `split_explicit_implicit == true`\nthe assumed decomposition is\n\n```math\n  \\\\dot{Q} = [l(Q, t)] + [f(Q, t)]\n```\n\nwhere `f` is now only the nonlinear tendency. For both decompositions the implicit\noperator `l` is integrated implicitly whereas the remaining part is integrated\nexplicitly. Other arguments are the required time step size `dt` and the\noptional initial time `t0`. The resulting backward Euler type systems are solved\nusing the provided `backward_euler_solver`. This time stepping object is\nintended to be passed to the `solve!` command.\n\nThe constructor builds an additive Runge--Kutta scheme based on the provided\n`RKAe`, `RKAi`, `RKB` and `RKC` coefficient arrays.  Additionally `variant`\nspecifies which of the analytically equivalent but numerically different\nformulations of the scheme is used.\n\nThe available concrete implementations are:\n\n  - [`ARK1ForwardBackwardEuler`](@ref)\n  - [`ARK2ImplicitExplicitMidpoint`](@ref)\n  - [`ARK2GiraldoKellyConstantinescu`](@ref)\n  - [`ARK548L2SA2KennedyCarpenter`](@ref)\n  - [`ARK437L2SA1KennedyCarpenter`](@ref)\n  - [`Trap2LockWoodWeller`](@ref)\n  - [`DBM453VoglEtAl`](@ref)\n\"\"\"\nmutable struct AdditiveRungeKutta{\n    T,\n    RT,\n    AT,\n    V,\n    VS,\n    IST,\n    Nstages,\n    Nstages_sq,\n    Nstagesm1,\n} <: AbstractAdditiveRungeKutta\n    \"time step\"\n    dt::RT\n    \"time\"\n    t::RT\n    \"elapsed time steps\"\n    steps::Int\n    \"rhs function\"\n    rhs!::Any\n    \"rhs linear operator\"\n    rhs_implicit!::Any\n    \"a dictionary of backward Euler solvers\"\n    implicit_solvers::IST\n    \"An integer which is updated to determine the appropriate cached implicit\n    solver (used in substepping)\"\n    substep_stage::Int\n    \"Storage for solution during the AdditiveRungeKutta update\"\n    Qstages::NTuple{Nstagesm1, AT}\n    \"Storage for RHS during the AdditiveRungeKutta update\"\n    Rstages::NTuple{Nstages, AT}\n    \"Storage for the linear solver rhs vector\"\n    Qhat::AT\n    \"RK coefficient matrix A for the explicit scheme\"\n    RKA_explicit::SArray{NTuple{2, Nstages}, RT, 2, Nstages_sq}\n    \"RK coefficient matrix A for the implicit scheme\"\n    RKA_implicit::SArray{NTuple{2, Nstages}, RT, 2, Nstages_sq}\n    \"RK coefficient vector B for the explicit scheme (rhs add in scaling)\"\n    RKB_explicit::SArray{Tuple{Nstages}, RT, 1, Nstages}\n    \"RK coefficient vector B for the implicit scheme (rhs add in scaling)\"\n    RKB_implicit::SArray{Tuple{Nstages}, RT, 1, Nstages}\n    \"RK_explicit coefficient vector C for the explicit scheme (time scaling)\"\n    RKC_explicit::SArray{Tuple{Nstages}, RT, 1, Nstages}\n    \"RK_implicit coefficient vector C for the implicit scheme (time scaling)\"\n    RKC_implicit::SArray{Tuple{Nstages}, RT, 1, Nstages}\n    split_explicit_implicit::Bool\n    \"Variant of the ARK scheme\"\n    variant::V\n    \"Storage dependent on the variant of the ARK scheme\"\n    variant_storage::VS\n\n    function AdditiveRungeKutta(\n        rhs!,\n        rhs_implicit!,\n        backward_euler_solver,\n        RKA_explicit,\n        RKA_implicit,\n        RKB_explicit,\n        RKB_implicit,\n        RKC_explicit,\n        RKC_implicit,\n        split_explicit_implicit,\n        variant,\n        Q::AT;\n        dt = nothing,\n        t0 = 0,\n        nsubsteps = [],\n    ) where {AT <: AbstractArray}\n\n        @assert dt !== nothing\n\n        T = eltype(Q)\n        RT = real(T)\n\n        Nstages = length(RKB_explicit)\n\n        Qstages = ntuple(i -> similar(Q), Nstages - 1)\n        Rstages = ntuple(i -> similar(Q), Nstages)\n        Qhat = similar(Q)\n\n        V = typeof(variant)\n        variant_storage = additional_storage(variant, Q, Nstages)\n        VS = typeof(variant_storage)\n\n        implicit_solvers = Dict()\n\n        rk_diag = unique(diag(RKA_implicit))\n        # Remove all zero entries from `rk_diag`\n        # so we build all unique implicit solvers (parameterized by the\n        # corresponding RK coefficient)\n        filter!(c -> !iszero(c), rk_diag)\n\n        # LowStorageVariant ARK methods assume that both the explicit and\n        # implicit B and C vectors are the same. Additionally, the diagonal\n        # of the implicit Butcher table A is assumed to have the form:\n        # [0, c, ... c ], where c is some non-zero constant.\n        if variant isa LowStorageVariant\n            @assert RKB_explicit == RKB_implicit\n            @assert RKC_explicit == RKC_implicit\n            # rk_diag here has been filtered of all non-unique and zero values.\n            # So [0, c, ... c ] filters to [c]. We error if its length is not 1\n            @assert length(rk_diag) == 1\n        end\n\n        if isempty(nsubsteps)\n            for rk_coeff in rk_diag\n                α = dt * rk_coeff\n                besolver! = setup_backward_Euler_solver(\n                    backward_euler_solver,\n                    Q,\n                    α,\n                    rhs_implicit!,\n                )\n                @assert besolver! isa AbstractBackwardEulerSolver\n                implicit_solvers[rk_coeff] = (besolver!,)\n            end\n        else\n            nsteps = length(nsubsteps)\n            for rk_coeff in rk_diag\n                solvers = ntuple(\n                    i -> setup_backward_Euler_solver(\n                        backward_euler_solver,\n                        Q,\n                        dt * nsubsteps[i] * rk_coeff,\n                        rhs_implicit!,\n                    ),\n                    nsteps,\n                )\n                @assert(all(isa.(solvers, AbstractBackwardEulerSolver)))\n                implicit_solvers[rk_coeff] = solvers\n            end\n        end\n\n        IST = typeof(implicit_solvers)\n        new{T, RT, AT, V, VS, IST, Nstages, Nstages^2, Nstages - 1}(\n            RT(dt),\n            RT(t0),\n            0,\n            rhs!,\n            rhs_implicit!,\n            implicit_solvers,\n            # By default (no substepping, this parameter is simply set to 1)\n            1,\n            Qstages,\n            Rstages,\n            Qhat,\n            RKA_explicit,\n            RKA_implicit,\n            RKB_explicit,\n            RKB_implicit,\n            RKC_explicit,\n            RKC_implicit,\n            split_explicit_implicit,\n            variant,\n            variant_storage,\n        )\n    end\nend\n\nfunction AdditiveRungeKutta(\n    ark,\n    op::TimeScaledRHS{2, RT} where {RT},\n    backward_euler_solver,\n    Q::AT;\n    dt = 0,\n    t0 = 0,\n    nsubsteps = [],\n    split_explicit_implicit = true,\n    variant = NaiveVariant(),\n) where {AT <: AbstractArray}\n    return ark(\n        op.rhs![1],\n        op.rhs![2],\n        backward_euler_solver,\n        Q;\n        dt = dt,\n        t0 = t0,\n        nsubsteps = nsubsteps,\n        split_explicit_implicit = split_explicit_implicit,\n        variant = variant,\n    )\nend\n\n# this will only work for iterative solves\n# direct solvers use prefactorization\nfunction updatedt!(ark::AdditiveRungeKutta, dt)\n    for (rk_coeff, implicit_solvers) in ark.implicit_solvers\n        implicit_solver! = implicit_solvers[ark.substep_stage]\n        @assert Δt_is_adjustable(implicit_solver!)\n        # New coefficient\n        α = dt * rk_coeff\n        # Update with new dt and implicit coefficient\n        ark.dt = dt\n        update_backward_Euler_solver!(implicit_solver!, ark.Qstages[1], α)\n    end\nend\n\nfunction dostep!(\n    Q,\n    ark::AdditiveRungeKutta,\n    p,\n    time,\n    slow_δ = nothing,\n    slow_rv_dQ = nothing,\n    slow_scaling = nothing,\n)\n    dostep!(Q, ark, ark.variant, p, time, slow_δ, slow_rv_dQ, slow_scaling)\nend\n\nfunction dostep!(\n    Q,\n    ark::AdditiveRungeKutta,\n    p,\n    time::Real,\n    nsubsteps::Int,\n    iStage::Int,\n    slow_δ = nothing,\n    slow_rv_dQ = nothing,\n    slow_scaling = nothing,\n)\n    ark.substep_stage = iStage\n    for i in 1:nsubsteps\n        dostep!(Q, ark, ark.variant, p, time, slow_δ, slow_rv_dQ, slow_scaling)\n        time += ark.dt\n    end\nend\n\nfunction dostep!(\n    Q,\n    ark::AdditiveRungeKutta,\n    variant::NaiveVariant,\n    p,\n    time::Real,\n    slow_δ = nothing,\n    slow_rv_dQ = nothing,\n    slow_scaling = nothing,\n)\n    dt = ark.dt\n\n    RKA_explicit, RKA_implicit = ark.RKA_explicit, ark.RKA_implicit\n    RKB_explicit, RKC_explicit = ark.RKB_explicit, ark.RKC_explicit\n    RKB_implicit, RKC_implicit = ark.RKB_implicit, ark.RKC_implicit\n    rhs!, rhs_implicit! = ark.rhs!, ark.rhs_implicit!\n    Qstages, Rstages = (Q, ark.Qstages...), ark.Rstages\n    Qhat = ark.Qhat\n    split_explicit_implicit = ark.split_explicit_implicit\n    Lstages = ark.variant_storage.Lstages\n\n    rv_Q = realview(Q)\n    rv_Qstages = realview.(Qstages)\n    rv_Lstages = realview.(Lstages)\n    rv_Rstages = realview.(Rstages)\n    rv_Qhat = realview(Qhat)\n\n    Nstages = length(RKB_explicit)\n\n    groupsize = 256\n\n    # calculate the rhs at first stage to initialize the stage loop\n    rhs!(\n        Rstages[1],\n        Qstages[1],\n        p,\n        time + RKC_explicit[1] * dt,\n        increment = false,\n    )\n\n    rhs_implicit!(\n        Lstages[1],\n        Qstages[1],\n        p,\n        time + RKC_implicit[1] * dt,\n        increment = false,\n    )\n\n    # note that it is important that this loop does not modify Q!\n    for istage in 2:Nstages\n        stagetime_implicit = time + RKC_implicit[istage] * dt\n        stagetime_explicit = time + RKC_explicit[istage] * dt\n\n        # this kernel also initializes Qstages[istage] with an initial guess\n        # for the linear solver\n        event = Event(array_device(Q))\n        event = stage_update!(array_device(Q), groupsize)(\n            variant,\n            rv_Q,\n            rv_Qstages,\n            rv_Lstages,\n            rv_Rstages,\n            rv_Qhat,\n            RKA_explicit,\n            RKA_implicit,\n            dt,\n            Val(istage),\n            Val(split_explicit_implicit),\n            slow_δ,\n            slow_rv_dQ;\n            ndrange = length(rv_Q),\n            dependencies = (event,),\n        )\n        wait(array_device(Q), event)\n\n        # solves\n        # Qs = Qhat + dt * RKA_implicit[istage, istage] * rhs_implicit!(Qs)\n        rk_coeff = RKA_implicit[istage, istage]\n        if !iszero(rk_coeff)\n            α = rk_coeff * dt\n            besolver! = ark.implicit_solvers[rk_coeff][ark.substep_stage]\n            besolver!(Qstages[istage], Qhat, α, p, stagetime_implicit)\n        end\n\n        rhs!(\n            Rstages[istage],\n            Qstages[istage],\n            p,\n            stagetime_explicit,\n            increment = false,\n        )\n        rhs_implicit!(\n            Lstages[istage],\n            Qstages[istage],\n            p,\n            stagetime_implicit,\n            increment = false,\n        )\n    end\n\n    # compose the final solution\n    event = Event(array_device(Q))\n    event = solution_update!(array_device(Q), groupsize)(\n        variant,\n        rv_Q,\n        rv_Lstages,\n        rv_Rstages,\n        RKB_explicit,\n        RKB_implicit,\n        dt,\n        Val(Nstages),\n        Val(split_explicit_implicit),\n        slow_δ,\n        slow_rv_dQ,\n        slow_scaling;\n        ndrange = length(rv_Q),\n        dependencies = (event,),\n    )\n    wait(array_device(Q), event)\nend\n\nfunction dostep!(\n    Q,\n    ark::AdditiveRungeKutta,\n    variant::LowStorageVariant,\n    p,\n    time::Real,\n    slow_δ = nothing,\n    slow_rv_dQ = nothing,\n    slow_scaling = nothing,\n)\n    dt = ark.dt\n\n    RKA_explicit, RKA_implicit = ark.RKA_explicit, ark.RKA_implicit\n    # LowStorageVariant ARK methods assumes that the implicit\n    # Butcher table has an SDIRK form; meaning explicit first step (no\n    # implicit solve at the first stage) and all non-zero diaognal\n    # coefficients are the same.\n    rk_coeff = RKA_implicit[2, 2]\n    besolver! = ark.implicit_solvers[rk_coeff][ark.substep_stage]\n    # NOTE: Using low-storage variant assumes that the butcher tables\n    # for both the explicit and implicit parts have the same B and C\n    # vectors\n    RKB, RKC = ark.RKB_explicit, ark.RKC_explicit\n    rhs!, rhs_implicit! = ark.rhs!, ark.rhs_implicit!\n    Qstages, Rstages = (Q, ark.Qstages...), ark.Rstages\n    Qhat = ark.Qhat\n    split_explicit_implicit = ark.split_explicit_implicit\n    Qtt = ark.variant_storage.Qtt\n\n    rv_Q = realview(Q)\n    rv_Qstages = realview.(Qstages)\n    rv_Rstages = realview.(Rstages)\n    rv_Qhat = realview(Qhat)\n    rv_Qtt = realview(Qtt)\n\n    Nstages = length(RKB)\n\n    groupsize = 256\n\n    # calculate the rhs at first stage to initialize the stage loop\n    rhs!(Rstages[1], Qstages[1], p, time + RKC[1] * dt, increment = false)\n\n    # note that it is important that this loop does not modify Q!\n    for istage in 2:Nstages\n        stagetime = time + RKC[istage] * dt\n\n        # this kernel also initializes Qtt for the linear solver\n        event = Event(array_device(Q))\n        event = stage_update!(array_device(Q), groupsize)(\n            variant,\n            rv_Q,\n            rv_Qstages,\n            rv_Rstages,\n            rv_Qhat,\n            rv_Qtt,\n            RKA_explicit,\n            RKA_implicit,\n            dt,\n            Val(istage),\n            Val(split_explicit_implicit),\n            slow_δ,\n            slow_rv_dQ;\n            ndrange = length(rv_Q),\n            dependencies = (event,),\n        )\n        wait(array_device(Q), event)\n\n        # solves\n        # Q_tt = Qhat + dt * RKA_implicit[istage, istage] * rhs_implicit!(Q_tt)\n        α = dt * RKA_implicit[istage, istage]\n        besolver!(Qtt, Qhat, α, p, stagetime)\n\n        # update Qstages\n        Qstages[istage] .+= Qtt\n\n        rhs!(Rstages[istage], Qstages[istage], p, stagetime, increment = false)\n    end\n\n    if split_explicit_implicit\n        for istage in 1:Nstages\n            stagetime = time + RKC[istage] * dt\n            rhs_implicit!(\n                Rstages[istage],\n                Qstages[istage],\n                p,\n                stagetime,\n                increment = true,\n            )\n        end\n    end\n\n    # compose the final solution\n    event = Event(array_device(Q))\n    event = solution_update!(array_device(Q), groupsize)(\n        variant,\n        rv_Q,\n        rv_Rstages,\n        RKB,\n        dt,\n        Val(Nstages),\n        slow_δ,\n        slow_rv_dQ,\n        slow_scaling;\n        ndrange = length(rv_Q),\n        dependencies = (event,),\n    )\n    wait(array_device(Q), event)\nend\n\n@kernel function stage_update!(\n    ::NaiveVariant,\n    Q,\n    Qstages,\n    Lstages,\n    Rstages,\n    Qhat,\n    RKA_explicit,\n    RKA_implicit,\n    dt,\n    ::Val{is},\n    ::Val{split_explicit_implicit},\n    slow_δ,\n    slow_dQ,\n) where {is, split_explicit_implicit}\n    i = @index(Global, Linear)\n    @inbounds begin\n        Qhat_i = Q[i]\n        Qstages_is_i = Q[i]\n\n        if slow_δ !== nothing\n            Rstages[is - 1][i] += slow_δ * slow_dQ[i]\n        end\n\n        @unroll for js in 1:(is - 1)\n            R_explicit = dt * RKA_explicit[is, js] * Rstages[js][i]\n            L_explicit = dt * RKA_explicit[is, js] * Lstages[js][i]\n            L_implicit = dt * RKA_implicit[is, js] * Lstages[js][i]\n            Qhat_i += (R_explicit + L_implicit)\n            Qstages_is_i += R_explicit\n            if split_explicit_implicit\n                Qstages_is_i += L_explicit\n            else\n                Qhat_i -= L_explicit\n            end\n        end\n        Qstages[is][i] = Qstages_is_i\n        Qhat[i] = Qhat_i\n    end\nend\n\n@kernel function stage_update!(\n    ::LowStorageVariant,\n    Q,\n    Qstages,\n    Rstages,\n    Qhat,\n    Qtt,\n    RKA_explicit,\n    RKA_implicit,\n    dt,\n    ::Val{is},\n    ::Val{split_explicit_implicit},\n    slow_δ,\n    slow_dQ,\n) where {is, split_explicit_implicit}\n    i = @index(Global, Linear)\n    @inbounds begin\n        Qhat_i = Q[i]\n        Qstages_is_i = -zero(eltype(Q))\n\n        if slow_δ !== nothing\n            Rstages[is - 1][i] += slow_δ * slow_dQ[i]\n        end\n\n        @unroll for js in 1:(is - 1)\n            if split_explicit_implicit\n                rkcoeff = RKA_implicit[is, js] / RKA_implicit[is, is]\n            else\n                rkcoeff =\n                    (RKA_implicit[is, js] - RKA_explicit[is, js]) /\n                    RKA_implicit[is, is]\n            end\n            commonterm = rkcoeff * Qstages[js][i]\n            Qhat_i += commonterm + dt * RKA_explicit[is, js] * Rstages[js][i]\n            Qstages_is_i -= commonterm\n        end\n        Qstages[is][i] = Qstages_is_i\n        Qhat[i] = Qhat_i\n        Qtt[i] = Qhat_i\n    end\nend\n\n@kernel function solution_update!(\n    ::NaiveVariant,\n    Q,\n    Lstages,\n    Rstages,\n    RKB_explicit,\n    RKB_implicit,\n    dt,\n    ::Val{Nstages},\n    ::Val{split_explicit_implicit},\n    slow_δ,\n    slow_dQ,\n    slow_scaling,\n) where {Nstages, split_explicit_implicit}\n    i = @index(Global, Linear)\n    @inbounds begin\n        if slow_δ !== nothing\n            Rstages[Nstages][i] += slow_δ * slow_dQ[i]\n        end\n        if slow_scaling !== nothing\n            slow_dQ[i] *= slow_scaling\n        end\n\n        @unroll for is in 1:Nstages\n            Q[i] += RKB_explicit[is] * dt * Rstages[is][i]\n            if split_explicit_implicit\n                Q[i] += RKB_implicit[is] * dt * Lstages[is][i]\n            end\n        end\n    end\nend\n\n@kernel function solution_update!(\n    ::LowStorageVariant,\n    Q,\n    Rstages,\n    RKB,\n    dt,\n    ::Val{Nstages},\n    slow_δ,\n    slow_dQ,\n    slow_scaling,\n) where {Nstages}\n    i = @index(Global, Linear)\n    @inbounds begin\n        if slow_δ !== nothing\n            Rstages[Nstages][i] += slow_δ * slow_dQ[i]\n        end\n        if slow_scaling !== nothing\n            slow_dQ[i] *= slow_scaling\n        end\n\n        @unroll for is in 1:Nstages\n            Q[i] += RKB[is] * dt * Rstages[is][i]\n        end\n    end\nend\n\n\"\"\"\n    ARK1ForwardBackwardEuler(f, l, backward_euler_solver, Q; dt, t0,\n                             split_explicit_implicit, variant)\n\nThis function returns an [`AdditiveRungeKutta`](@ref) time stepping object,\nsee the documentation of [`AdditiveRungeKutta`](@ref) for arguments definitions.\nThis time stepping object is intended to be passed to the `solve!` command.\n\nThis uses a first-order-accurate two-stage additive Runge--Kutta scheme\nby combining a forward Euler explicit step with a backward Euler implicit\ncorrection.\n\n### References\n    @article{Ascher1997,\n      title = {Implicit-explicit Runge-Kutta methods for time-dependent\n               partial differential equations},\n      author = {Uri M. Ascher and Steven J. Ruuth and Raymond J. Spiteri},\n      volume = {25},\n      number = {2-3},\n      pages = {151--167},\n      year = {1997},\n      journal = {Applied Numerical Mathematics},\n      publisher = {Elsevier {BV}}\n    }\n\"\"\"\nfunction ARK1ForwardBackwardEuler(\n    F,\n    L,\n    backward_euler_solver,\n    Q::AT;\n    dt = nothing,\n    t0 = 0,\n    nsubsteps = [],\n    split_explicit_implicit = false,\n    variant = LowStorageVariant(),\n) where {AT <: AbstractArray}\n\n    @assert dt !== nothing\n\n    T = eltype(Q)\n    RT = real(T)\n\n    RKA_explicit = [\n        RT(0) RT(0)\n        RT(1) RT(0)\n    ]\n    RKA_implicit = [\n        RT(0) RT(0)\n        RT(0) RT(1)\n    ]\n\n    RKB_explicit = [RT(0), RT(1)]\n    RKC_explicit = [RT(0), RT(1)]\n    # For this ARK method, both RK methods share the same\n    # B and C vectors in the Butcher table\n    RKB_implicit = RKB_explicit\n    RKC_implicit = RKC_explicit\n\n    Nstages = length(RKB_explicit)\n\n    AdditiveRungeKutta(\n        F,\n        L,\n        backward_euler_solver,\n        RKA_explicit,\n        RKA_implicit,\n        RKB_explicit,\n        RKB_implicit,\n        RKC_explicit,\n        RKC_implicit,\n        split_explicit_implicit,\n        variant,\n        Q;\n        dt = dt,\n        t0 = t0,\n        nsubsteps = nsubsteps,\n    )\nend\n\n\"\"\"\n    ARK2ImplicitExplicitMidpoint(f, l, backward_euler_solver, Q; dt, t0,\n                                 split_explicit_implicit, variant)\n\nThis function returns an [`AdditiveRungeKutta`](@ref) time stepping object,\nsee the documentation of [`AdditiveRungeKutta`](@ref) for arguments definitions.\nThis time stepping object is intended to be passed to the `solve!` command.\n\nThis uses a second-order-accurate two-stage additive Runge--Kutta scheme\nby combining the implicit and explicit midpoint methods.\n\n### References\n    @article{Ascher1997,\n      title = {Implicit-explicit Runge-Kutta methods for time-dependent\n               partial differential equations},\n      author = {Uri M. Ascher and Steven J. Ruuth and Raymond J. Spiteri},\n      volume = {25},\n      number = {2-3},\n      pages = {151--167},\n      year = {1997},\n      journal = {Applied Numerical Mathematics},\n      publisher = {Elsevier {BV}}\n    }\n\"\"\"\nfunction ARK2ImplicitExplicitMidpoint(\n    F,\n    L,\n    backward_euler_solver,\n    Q::AT;\n    dt = nothing,\n    t0 = 0,\n    nsubsteps = [],\n    split_explicit_implicit = false,\n    variant = LowStorageVariant(),\n) where {AT <: AbstractArray}\n\n    @assert dt !== nothing\n\n    T = eltype(Q)\n    RT = real(T)\n\n    RKA_explicit = [\n        RT(0) RT(0)\n        RT(1 / 2) RT(0)\n    ]\n    RKA_implicit = [\n        RT(0) RT(0)\n        RT(0) RT(1 / 2)\n    ]\n\n    RKB_explicit = [RT(0), RT(1)]\n    RKC_explicit = [RT(0), RT(1 / 2)]\n    # For this ARK method, both RK methods share the same\n    # B and C vectors in the Butcher table\n    RKB_implicit = RKB_explicit\n    RKC_implicit = RKC_explicit\n\n    Nstages = length(RKB_explicit)\n\n    AdditiveRungeKutta(\n        F,\n        L,\n        backward_euler_solver,\n        RKA_explicit,\n        RKA_implicit,\n        RKB_explicit,\n        RKB_implicit,\n        RKC_explicit,\n        RKC_implicit,\n        split_explicit_implicit,\n        variant,\n        Q;\n        dt = dt,\n        t0 = t0,\n        nsubsteps = nsubsteps,\n    )\nend\n\n\"\"\"\n    ARK2GiraldoKellyConstantinescu(f, l, backward_euler_solver, Q; dt, t0,\n                                   split_explicit_implicit, variant, paperversion)\n\nThis function returns an [`AdditiveRungeKutta`](@ref) time stepping object,\nsee the documentation of [`AdditiveRungeKutta`](@ref) for arguments definitions.\nThis time stepping object is intended to be passed to the `solve!` command.\n\n`paperversion=true` uses the coefficients from the paper, `paperversion=false`\nuses coefficients that make the scheme (much) more stable but less accurate\n\nThis uses the second-order-accurate 3-stage additive Runge--Kutta scheme of\nGiraldo, Kelly and Constantinescu (2013).\n\n### References\n - [Giraldo2013](@cite)\n\"\"\"\nfunction ARK2GiraldoKellyConstantinescu(\n    F,\n    L,\n    backward_euler_solver,\n    Q::AT;\n    dt = nothing,\n    t0 = 0,\n    nsubsteps = [],\n    split_explicit_implicit = false,\n    variant = LowStorageVariant(),\n    paperversion = false,\n) where {AT <: AbstractArray}\n\n    @assert dt !== nothing\n\n    T = eltype(Q)\n    RT = real(T)\n\n    a32 = RT(paperversion ? (3 + 2 * sqrt(2)) / 6 : 1 // 2)\n    RKA_explicit = [\n        RT(0) RT(0) RT(0)\n        RT(2 - sqrt(2)) RT(0) RT(0)\n        RT(1 - a32) RT(a32) RT(0)\n    ]\n\n    RKA_implicit = [\n        RT(0) RT(0) RT(0)\n        RT(1 - 1 / sqrt(2)) RT(1 - 1 / sqrt(2)) RT(0)\n        RT(1 / (2 * sqrt(2))) RT(1 / (2 * sqrt(2))) RT(1 - 1 / sqrt(2))\n    ]\n\n    RKB_explicit =\n        [RT(1 / (2 * sqrt(2))), RT(1 / (2 * sqrt(2))), RT(1 - 1 / sqrt(2))]\n    RKC_explicit = [RT(0), RT(2 - sqrt(2)), RT(1)]\n    # For this ARK method, both RK methods share the same\n    # B and C vectors in the Butcher table\n    RKB_implicit = RKB_explicit\n    RKC_implicit = RKC_explicit\n\n    Nstages = length(RKB_explicit)\n\n    AdditiveRungeKutta(\n        F,\n        L,\n        backward_euler_solver,\n        RKA_explicit,\n        RKA_implicit,\n        RKB_explicit,\n        RKB_implicit,\n        RKC_explicit,\n        RKC_implicit,\n        split_explicit_implicit,\n        variant,\n        Q;\n        dt = dt,\n        t0 = t0,\n        nsubsteps = nsubsteps,\n    )\nend\n\n\"\"\"\n    Trap2LockWoodWeller(F, L, backward_euler_solver, Q; dt, t0, nsubsteps,\n                        split_explicit_implicit, variant)\n\nThis function returns an [`AdditiveRungeKutta`](@ref) time stepping object,\nsee the documentation of [`AdditiveRungeKutta`](@ref) for arguments definitions.\nThis time stepping object is intended to be passed to the `solve!` command.\n\nThe time integrator scheme used is Trap2(2,3,2) with δ_s = 1, δ_f = 0, from\nthe following reference\n\n### References\n    @article{Ascher1997,\n      title = {Numerical analyses of Runge–Kutta implicit–explicit schemes\n               for horizontally explicit, vertically implicit solutions of\n               atmospheric models},\n      author = {S.-J. Lock and N. Wood and H. Weller},\n      volume = {140},\n      number = {682},\n      pages = {1654-1669},\n      year = {2014},\n      journal = {Quarterly Journal of the Royal Meteorological Society},\n      publisher = {{RMetS}}\n    }\n\"\"\"\nfunction Trap2LockWoodWeller(\n    F,\n    L,\n    backward_euler_solver,\n    Q::AT;\n    dt = nothing,\n    t0 = 0,\n    nsubsteps = [],\n    split_explicit_implicit = false,\n    variant = NaiveVariant(),\n    δ_s = 1,\n    δ_f = 0,\n    α = 0,\n) where {AT <: AbstractArray}\n\n    @assert dt !== nothing\n    # In this scheme B and C vectors do not coincide,\n    # hence we can't use the LowStorageVariant optimization\n    @assert variant isa NaiveVariant\n\n    T = eltype(Q)\n    RT = real(T)\n\n    #! format: off\n    RKA_explicit = [\n        RT(0)      RT(0)      RT(0)      RT(0)\n        RT(δ_s)    RT(0)      RT(0)      RT(0)\n        RT(1 / 2)  RT(1 / 2)  RT(0)      RT(0)\n        RT(1 / 2)  RT(0)      RT(1 / 2)  RT(0)\n    ]\n\n    RKA_implicit = [\n        RT(0)                  RT(0)                  RT(0)      RT(0)\n        RT(δ_f * (1 - α) / 2)  RT(δ_f * (1 + α) / 2)  RT(0)      RT(0)\n        RT(1 / 2)              RT(0)                  RT(1 / 2)  RT(0)\n        RT(1 / 2)              RT(0)                  RT(0)      RT(1 / 2)\n    ]\n    #! format: on\n\n    RKB_explicit = [RT(1 / 2), RT(0), RT(1 / 2), RT(0)]\n    RKB_implicit = [RT(1 / 2), RT(0), RT(0), RT(1 / 2)]\n    RKC_explicit = [RT(0), RT(δ_s), RT(1), RT(1)]\n    RKC_implicit = [RT(0), RT(δ_f), RT(1), RT(1)]\n\n    Nstages = length(RKB_explicit)\n\n    AdditiveRungeKutta(\n        F,\n        L,\n        backward_euler_solver,\n        RKA_explicit,\n        RKA_implicit,\n        RKB_explicit,\n        RKB_implicit,\n        RKC_explicit,\n        RKC_implicit,\n        split_explicit_implicit,\n        variant,\n        Q;\n        dt = dt,\n        t0 = t0,\n        nsubsteps = nsubsteps,\n    )\nend\n\n\"\"\"\n    ARK548L2SA2KennedyCarpenter(f, l, backward_euler_solver, Q; dt, t0,\n                                split_explicit_implicit, variant)\n\nThis function returns an [`AdditiveRungeKutta`](@ref) time stepping object,\nsee the documentation of [`AdditiveRungeKutta`](@ref) for arguments definitions.\nThis time stepping object is intended to be passed to the `solve!` command.\n\nThis uses the fifth-order-accurate 8-stage additive Runge--Kutta scheme of\nKennedy and Carpenter (2013).\n\n### References\n - [Kennedy2019](@cite)\n\"\"\"\nfunction ARK548L2SA2KennedyCarpenter(\n    F,\n    L,\n    backward_euler_solver,\n    Q::AT;\n    dt = nothing,\n    t0 = 0,\n    nsubsteps = [],\n    split_explicit_implicit = false,\n    variant = LowStorageVariant(),\n) where {AT <: AbstractArray}\n\n    @assert dt !== nothing\n\n    T = eltype(Q)\n    RT = real(T)\n\n    Nstages = 8\n    gamma = RT(2 // 9)\n\n    # declared as Arrays for mutability, later these will be converted to static\n    # arrays\n    RKA_explicit = zeros(RT, Nstages, Nstages)\n    RKA_implicit = zeros(RT, Nstages, Nstages)\n    RKB_explicit = zeros(RT, Nstages)\n    RKC_explicit = zeros(RT, Nstages)\n\n    # the main diagonal\n    for is in 2:Nstages\n        RKA_implicit[is, is] = gamma\n    end\n\n    RKA_implicit[3, 2] = RT(2366667076620 // 8822750406821)\n    RKA_implicit[4, 2] = RT(-257962897183 // 4451812247028)\n    RKA_implicit[4, 3] = RT(128530224461 // 14379561246022)\n    RKA_implicit[5, 2] = RT(-486229321650 // 11227943450093)\n    RKA_implicit[5, 3] = RT(-225633144460 // 6633558740617)\n    RKA_implicit[5, 4] = RT(1741320951451 // 6824444397158)\n    RKA_implicit[6, 2] = RT(621307788657 // 4714163060173)\n    RKA_implicit[6, 3] = RT(-125196015625 // 3866852212004)\n    RKA_implicit[6, 4] = RT(940440206406 // 7593089888465)\n    RKA_implicit[6, 5] = RT(961109811699 // 6734810228204)\n    RKA_implicit[7, 2] = RT(2036305566805 // 6583108094622)\n    RKA_implicit[7, 3] = RT(-3039402635899 // 4450598839912)\n    RKA_implicit[7, 4] = RT(-1829510709469 // 31102090912115)\n    RKA_implicit[7, 5] = RT(-286320471013 // 6931253422520)\n    RKA_implicit[7, 6] = RT(8651533662697 // 9642993110008)\n\n    RKA_explicit[3, 1] = RT(1 // 9)\n    RKA_explicit[3, 2] = RT(1183333538310 // 1827251437969)\n    RKA_explicit[4, 1] = RT(895379019517 // 9750411845327)\n    RKA_explicit[4, 2] = RT(477606656805 // 13473228687314)\n    RKA_explicit[4, 3] = RT(-112564739183 // 9373365219272)\n    RKA_explicit[5, 1] = RT(-4458043123994 // 13015289567637)\n    RKA_explicit[5, 2] = RT(-2500665203865 // 9342069639922)\n    RKA_explicit[5, 3] = RT(983347055801 // 8893519644487)\n    RKA_explicit[5, 4] = RT(2185051477207 // 2551468980502)\n    RKA_explicit[6, 1] = RT(-167316361917 // 17121522574472)\n    RKA_explicit[6, 2] = RT(1605541814917 // 7619724128744)\n    RKA_explicit[6, 3] = RT(991021770328 // 13052792161721)\n    RKA_explicit[6, 4] = RT(2342280609577 // 11279663441611)\n    RKA_explicit[6, 5] = RT(3012424348531 // 12792462456678)\n    RKA_explicit[7, 1] = RT(6680998715867 // 14310383562358)\n    RKA_explicit[7, 2] = RT(5029118570809 // 3897454228471)\n    RKA_explicit[7, 3] = RT(2415062538259 // 6382199904604)\n    RKA_explicit[7, 4] = RT(-3924368632305 // 6964820224454)\n    RKA_explicit[7, 5] = RT(-4331110370267 // 15021686902756)\n    RKA_explicit[7, 6] = RT(-3944303808049 // 11994238218192)\n    RKA_explicit[8, 1] = RT(2193717860234 // 3570523412979)\n    RKA_explicit[8, 2] = RKA_explicit[8, 1]\n    RKA_explicit[8, 3] = RT(5952760925747 // 18750164281544)\n    RKA_explicit[8, 4] = RT(-4412967128996 // 6196664114337)\n    RKA_explicit[8, 5] = RT(4151782504231 // 36106512998704)\n    RKA_explicit[8, 6] = RT(572599549169 // 6265429158920)\n    RKA_explicit[8, 7] = RT(-457874356192 // 11306498036315)\n\n    RKB_explicit[2] = 0\n    RKB_explicit[3] = RT(3517720773327 // 20256071687669)\n    RKB_explicit[4] = RT(4569610470461 // 17934693873752)\n    RKB_explicit[5] = RT(2819471173109 // 11655438449929)\n    RKB_explicit[6] = RT(3296210113763 // 10722700128969)\n    RKB_explicit[7] = RT(-1142099968913 // 5710983926999)\n\n    RKC_explicit[2] = RT(4 // 9)\n    RKC_explicit[3] = RT(6456083330201 // 8509243623797)\n    RKC_explicit[4] = RT(1632083962415 // 14158861528103)\n    RKC_explicit[5] = RT(6365430648612 // 17842476412687)\n    RKC_explicit[6] = RT(18 // 25)\n    RKC_explicit[7] = RT(191 // 200)\n\n    for is in 2:Nstages\n        RKA_implicit[is, 1] = RKA_implicit[is, 2]\n    end\n\n    for is in 1:(Nstages - 1)\n        RKA_implicit[Nstages, is] = RKB_explicit[is]\n    end\n\n    RKB_explicit[1] = RKB_explicit[2]\n    RKB_explicit[8] = gamma\n\n    RKA_explicit[2, 1] = RKC_explicit[2]\n    RKA_explicit[Nstages, 1] = RKA_explicit[Nstages, 2]\n\n    RKC_explicit[1] = 0\n    RKC_explicit[Nstages] = 1\n\n    # For this ARK method, both RK methods share the same\n    # B and C vectors in the Butcher table\n    RKB_implicit = RKB_explicit\n    RKC_implicit = RKC_explicit\n\n    ark = AdditiveRungeKutta(\n        F,\n        L,\n        backward_euler_solver,\n        RKA_explicit,\n        RKA_implicit,\n        RKB_explicit,\n        RKB_implicit,\n        RKC_explicit,\n        RKC_implicit,\n        split_explicit_implicit,\n        variant,\n        Q;\n        dt = dt,\n        t0 = t0,\n        nsubsteps = nsubsteps,\n    )\nend\n\n\"\"\"\n    ARK437L2SA1KennedyCarpenter(f, l, backward_euler_solver, Q; dt, t0,\n                                split_explicit_implicit, variant)\n\nThis function returns an [`AdditiveRungeKutta`](@ref) time stepping object,\nsee the documentation of [`AdditiveRungeKutta`](@ref) for arguments definitions.\nThis time stepping object is intended to be passed to the `solve!` command.\n\nThis uses the fourth-order-accurate 7-stage additive Runge--Kutta scheme of\nKennedy and Carpenter (2013).\n\n### References\n - [Kennedy2019](@cite)\n\"\"\"\nfunction ARK437L2SA1KennedyCarpenter(\n    F,\n    L,\n    backward_euler_solver,\n    Q::AT;\n    dt = nothing,\n    t0 = 0,\n    nsubsteps = [],\n    split_explicit_implicit = false,\n    variant = LowStorageVariant(),\n) where {AT <: AbstractArray}\n\n    @assert dt !== nothing\n\n    T = eltype(Q)\n    RT = real(T)\n\n    Nstages = 7\n    gamma = RT(1235 // 10000)\n\n    # declared as Arrays for mutability, later these will be converted to static\n    # arrays\n    RKA_explicit = zeros(RT, Nstages, Nstages)\n    RKA_implicit = zeros(RT, Nstages, Nstages)\n    RKB_explicit = zeros(RT, Nstages)\n    RKC_explicit = zeros(RT, Nstages)\n\n    # the main diagonal\n    for is in 2:Nstages\n        RKA_implicit[is, is] = gamma\n    end\n\n    RKA_implicit[3, 2] = RT(624185399699 // 4186980696204)\n    RKA_implicit[4, 2] = RT(1258591069120 // 10082082980243)\n    RKA_implicit[4, 3] = RT(-322722984531 // 8455138723562)\n    RKA_implicit[5, 2] = RT(-436103496990 // 5971407786587)\n    RKA_implicit[5, 3] = RT(-2689175662187 // 11046760208243)\n    RKA_implicit[5, 4] = RT(4431412449334 // 12995360898505)\n    RKA_implicit[6, 2] = RT(-2207373168298 // 14430576638973)\n    RKA_implicit[6, 3] = RT(242511121179 // 3358618340039)\n    RKA_implicit[6, 4] = RT(3145666661981 // 7780404714551)\n    RKA_implicit[6, 5] = RT(5882073923981 // 14490790706663)\n    RKA_implicit[7, 2] = 0\n    RKA_implicit[7, 3] = RT(9164257142617 // 17756377923965)\n    RKA_implicit[7, 4] = RT(-10812980402763 // 74029279521829)\n    RKA_implicit[7, 5] = RT(1335994250573 // 5691609445217)\n    RKA_implicit[7, 6] = RT(2273837961795 // 8368240463276)\n\n    RKA_explicit[3, 1] = RT(247 // 4000)\n    RKA_explicit[3, 2] = RT(2694949928731 // 7487940209513)\n    RKA_explicit[4, 1] = RT(464650059369 // 8764239774964)\n    RKA_explicit[4, 2] = RT(878889893998 // 2444806327765)\n    RKA_explicit[4, 3] = RT(-952945855348 // 12294611323341)\n    RKA_explicit[5, 1] = RT(476636172619 // 8159180917465)\n    RKA_explicit[5, 2] = RT(-1271469283451 // 7793814740893)\n    RKA_explicit[5, 3] = RT(-859560642026 // 4356155882851)\n    RKA_explicit[5, 4] = RT(1723805262919 // 4571918432560)\n    RKA_explicit[6, 1] = RT(6338158500785 // 11769362343261)\n    RKA_explicit[6, 2] = RT(-4970555480458 // 10924838743837)\n    RKA_explicit[6, 3] = RT(3326578051521 // 2647936831840)\n    RKA_explicit[6, 4] = RT(-880713585975 // 1841400956686)\n    RKA_explicit[6, 5] = RT(-1428733748635 // 8843423958496)\n    RKA_explicit[7, 2] = RT(760814592956 // 3276306540349)\n    RKA_explicit[7, 3] = RT(-47223648122716 // 6934462133451)\n    RKA_explicit[7, 4] = RT(71187472546993 // 9669769126921)\n    RKA_explicit[7, 5] = RT(-13330509492149 // 9695768672337)\n    RKA_explicit[7, 6] = RT(11565764226357 // 8513123442827)\n\n    RKB_explicit[2] = 0\n    RKB_explicit[3] = RT(9164257142617 // 17756377923965)\n    RKB_explicit[4] = RT(-10812980402763 // 74029279521829)\n    RKB_explicit[5] = RT(1335994250573 // 5691609445217)\n    RKB_explicit[6] = RT(2273837961795 // 8368240463276)\n    RKB_explicit[7] = RT(247 // 2000)\n\n    RKC_explicit[2] = RT(247 // 1000)\n    RKC_explicit[3] = RT(4276536705230 // 10142255878289)\n    RKC_explicit[4] = RT(67 // 200)\n    RKC_explicit[5] = RT(3 // 40)\n    RKC_explicit[6] = RT(7 // 10)\n\n    for is in 2:Nstages\n        RKA_implicit[is, 1] = RKA_implicit[is, 2]\n    end\n\n    for is in 1:(Nstages - 1)\n        RKA_implicit[Nstages, is] = RKB_explicit[is]\n    end\n\n    RKB_explicit[1] = RKB_explicit[2]\n\n    RKA_explicit[2, 1] = RKC_explicit[2]\n    RKA_explicit[Nstages, 1] = RKA_explicit[Nstages, 2]\n\n    RKC_explicit[1] = 0\n    RKC_explicit[Nstages] = 1\n\n    # For this ARK method, both RK methods share the same\n    # B and C vectors in the Butcher table\n    RKB_implicit = RKB_explicit\n    RKC_implicit = RKC_explicit\n\n    ark = AdditiveRungeKutta(\n        F,\n        L,\n        backward_euler_solver,\n        RKA_explicit,\n        RKA_implicit,\n        RKB_explicit,\n        RKB_implicit,\n        RKC_explicit,\n        RKC_implicit,\n        split_explicit_implicit,\n        variant,\n        Q;\n        dt = dt,\n        t0 = t0,\n        nsubsteps = nsubsteps,\n    )\nend\n\n\"\"\"\n    DBM453VoglEtAl(f, l, backward_euler_solver, Q; dt, t0,\n                   split_explicit_implicit, variant)\n\nThis function returns an [`AdditiveRungeKutta`](@ref) time stepping object,\nsee the documentation of [`AdditiveRungeKutta`](@ref) for arguments definitions.\nThis time stepping object is intended to be passed to the `solve!` command.\n\nThis uses the third-order-accurate 5-stage additive Runge--Kutta scheme of\nVogl et al. (2019).\n\n### References\n - [Vogl2019](@cite)\n\"\"\"\nfunction DBM453VoglEtAl(\n    F,\n    L,\n    backward_euler_solver,\n    Q::AT;\n    dt = nothing,\n    t0 = 0,\n    nsubsteps = [],\n    split_explicit_implicit = false,\n    variant = LowStorageVariant(),\n) where {AT <: AbstractArray}\n\n    @assert dt !== nothing\n\n    T = eltype(Q)\n    RT = real(T)\n\n    Nstages = 5\n    gamma = RT(0.32591194130117247)\n\n    # declared as Arrays for mutability, later these will be converted to static\n    # arrays\n    RKA_explicit = zeros(RT, Nstages, Nstages)\n    RKA_implicit = zeros(RT, Nstages, Nstages)\n    RKB_explicit = zeros(RT, Nstages)\n    RKC_explicit = zeros(RT, Nstages)\n\n    # the main diagonal\n    for is in 2:Nstages\n        RKA_implicit[is, is] = gamma\n    end\n\n    RKA_implicit[2, 1] = -0.22284985318525410\n    RKA_implicit[3, 1] = -0.46801347074080545\n    RKA_implicit[3, 2] = 0.86349284225716961\n    RKA_implicit[4, 1] = -0.46509906651927421\n    RKA_implicit[4, 2] = 0.81063103116959553\n    RKA_implicit[4, 3] = 0.61036726756832357\n    RKA_implicit[5, 1] = 0.87795339639076675\n    RKA_implicit[5, 2] = -0.72692641526151547\n    RKA_implicit[5, 3] = 0.75204137157372720\n    RKA_implicit[5, 4] = -0.22898029400415088\n\n    RKA_explicit[2, 1] = 0.10306208811591838\n    RKA_explicit[3, 1] = -0.94124866143519894\n    RKA_explicit[3, 2] = 1.66263997425273560\n    RKA_explicit[4, 1] = -1.36709752014377650\n    RKA_explicit[4, 2] = 1.38158529110168730\n    RKA_explicit[4, 3] = 1.26732340256190650\n    RKA_explicit[5, 1] = -0.81287582068772448\n    RKA_explicit[5, 2] = 0.81223739060505738\n    RKA_explicit[5, 3] = 0.90644429603699305\n    RKA_explicit[5, 4] = 0.094194134045674111\n\n    RKB_explicit[1] = 0.87795339639076672\n    RKB_explicit[2] = -0.72692641526151549\n    RKB_explicit[3] = 0.7520413715737272\n    RKB_explicit[4] = -0.22898029400415090\n    RKB_explicit[5] = 0.32591194130117247\n\n    RKC_explicit[1] = 0\n    RKC_explicit[2] = 0.1030620881159184\n    RKC_explicit[3] = 0.72139131281753662\n    RKC_explicit[4] = 1.28181117351981733\n    RKC_explicit[5] = 1\n\n    # For this ARK method, both RK methods share the same\n    # B and C vectors in the Butcher table\n    RKB_implicit = RKB_explicit\n    RKC_implicit = RKC_explicit\n\n    ark = AdditiveRungeKutta(\n        F,\n        L,\n        backward_euler_solver,\n        RKA_explicit,\n        RKA_implicit,\n        RKB_explicit,\n        RKB_implicit,\n        RKC_explicit,\n        RKC_implicit,\n        split_explicit_implicit,\n        variant,\n        Q;\n        dt = dt,\n        t0 = t0,\n        nsubsteps = nsubsteps,\n    )\nend\n"
  },
  {
    "path": "src/Numerics/ODESolvers/BackwardEulerSolvers.jl",
    "content": "export LinearBackwardEulerSolver, AbstractBackwardEulerSolver\nexport NonLinearBackwardEulerSolver\n\nabstract type AbstractImplicitOperator end\n\n\"\"\"\n    op! = EulerOperator(f!, ϵ)\n\nConstruct a linear operator which performs an explicit Euler step ``Q + α\nf(Q)``, where `f!` and `op!` both operate inplace, with extra arguments passed\nthrough, i.e.\n```\nop!(LQ, Q, args...)\n```\nis equivalent to\n```\nf!(dQ, Q, args...)\nLQ .= Q .+ ϵ .* dQ\n```\n\"\"\"\nmutable struct EulerOperator{F, FT} <: AbstractImplicitOperator\n    f!::F\n    ϵ::FT\nend\n\nfunction (op::EulerOperator)(LQ, Q, args...)\n    op.f!(LQ, Q, args..., increment = false)\n    @. LQ = Q + op.ϵ * LQ\nend\n\n\"\"\"\n    AbstractBackwardEulerSolver\n\nAn abstract backward Euler method\n\"\"\"\nabstract type AbstractBackwardEulerSolver end\n\n\"\"\"\n    (be::AbstractBackwardEulerSolver)(Q, Qhat, α, param, time)\n\nEach concrete implementations of `AbstractBackwardEulerSolver` should provide a\ncallable version which solves the following system for `Q`\n```\n    Q = Qhat + α f(Q, param, time)\n```\nwhere `f` is the ODE tendency function, `param` are the ODE parameters, and\n`time` is the current ODE time. The arguments `Q` should be modified in place\nand should not be assumed to be initialized to any value.\n\"\"\"\n(be::AbstractBackwardEulerSolver)(Q, Qhat, α, p, t) =\n    throw(MethodError(be, (Q, Qhat, α, p, t)))\n\n\"\"\"\n    Δt_is_adjustable(::AbstractBackwardEulerSolver)\n\nReturn `Bool` for whether this backward Euler solver can be updated. default is\n`false`.\n\"\"\"\nΔt_is_adjustable(::AbstractBackwardEulerSolver) = false\n\n\"\"\"\n    update_backward_Euler_solver!(::AbstractBackwardEulerSolver, α)\n\nUpdate the given backward Euler solver for the parameter `α`; see\n['AbstractBackwardEulerSolver'](@ref). Default behavior is no change to the\nsolver.\n\"\"\"\nupdate_backward_Euler_solver!(::AbstractBackwardEulerSolver, Q, α) = nothing\n\n\"\"\"\n    setup_backward_Euler_solver(solver, Q, α, tendency!)\n\nReturns a concrete implementation of an `AbstractBackwardEulerSolver` that will\nsolve for `Q` in systems of the form of\n```\n    Q = Qhat + α f(Q, param, time)\n```\nwhere `tendency!` is the in-place tendency function. Not the array `Q` is just\npassed in for type information, e.g., `Q` the same `Q` will not be used for all\ncalls to the solver.\n\"\"\"\nsetup_backward_Euler_solver(solver::AbstractBackwardEulerSolver, _...) = solver\n\n\"\"\"\n    LinearBackwardEulerSolver(::AbstractSystemSolver; isadjustable = false)\n\nHelper type for specifying building a backward Euler solver with a linear\nsolver.  If `isadjustable == true` then the solver can be updated with a new\ntime step size.\n\"\"\"\nstruct LinearBackwardEulerSolver{LS}\n    solver::LS\n    isadjustable::Bool\n    preconditioner_update_freq::Int\n    LinearBackwardEulerSolver(\n        solver;\n        isadjustable = false,\n        preconditioner_update_freq = -1,\n    ) = new{typeof(solver)}(solver, isadjustable, preconditioner_update_freq)\nend\n\n\"\"\"\n    LinBESolver\n\nConcrete implementation of an `AbstractBackwardEulerSolver` to use linear\nsolvers of type `AbstractSystemSolver`. See helper type\n[`LinearBackwardEulerSolver`](@ref)\n```\n    Q = Qhat + α f(Q, param, time)\n```\n\"\"\"\nmutable struct LinBESolver{FT, F, LS} <: AbstractBackwardEulerSolver\n    α::FT\n    f_imp!::F\n    solver::LS\n    isadjustable::Bool\n    # used only for iterative solver\n    preconditioner::AbstractPreconditioner\n    # used only for direct solver\n    factors::Any\nend\n\nΔt_is_adjustable(lin::LinBESolver) = lin.isadjustable\n\nfunction setup_backward_Euler_solver(\n    lin::LinearBackwardEulerSolver,\n    Q,\n    α,\n    f_imp!,\n)\n    FT = eltype(α)\n    rhs! = EulerOperator(f_imp!, -α)\n\n    factors = prefactorize(rhs!, lin.solver, Q, nothing, FT(NaN))\n\n    # when direct solver is applied preconditioner_update_freq <= 0\n    @assert(\n        typeof(lin.solver) <: AbstractIterativeSystemSolver ||\n        lin.preconditioner_update_freq <= 0\n    )\n\n    preconditioner_update_freq = lin.preconditioner_update_freq\n    # construct an empty preconditioner\n    preconditioner = (\n        preconditioner_update_freq > 0 ?\n        ColumnwiseLUPreconditioner(f_imp!, Q, preconditioner_update_freq) :\n        NoPreconditioner()\n    )\n\n    LinBESolver(\n        α,\n        f_imp!,\n        lin.solver,\n        lin.isadjustable,\n        preconditioner,\n        factors,\n    )\nend\n\nfunction update_backward_Euler_solver!(lin::LinBESolver, Q, α)\n    lin.α = α\n    FT = eltype(Q)\n    # for direct solver, update factors\n    # for iterative solver, set factors to Nothing (TODO optimize)\n    lin.factors = prefactorize(\n        EulerOperator(lin.f_imp!, -α),\n        lin.solver,\n        Q,\n        nothing,\n        FT(NaN),\n    )\nend\n\nfunction (lin::LinBESolver)(Q, Qhat, α, p, t)\n\n    # If α is not the same as the already assembled operator\n    # with its previous value of α (lin.α), then we need to\n    # first check that the solver CAN be rebuilt (lin.isadjustable)\n    # followed by recalling the set up routine by updating the\n    # coefficient with the new version of α\n    if lin.α != α\n        @assert lin.isadjustable\n        update_backward_Euler_solver!(lin, Q, α)\n    end\n\n    rhs! = EulerOperator(lin.f_imp!, -α)\n\n    if typeof(lin.solver) <: AbstractIterativeSystemSolver\n        FT = eltype(α)\n        preconditioner_update!(rhs!, rhs!.f!, lin.preconditioner, p, t)\n        linearsolve!(rhs!, lin.preconditioner, lin.solver, Q, Qhat, p, t)\n        preconditioner_counter_update!(lin.preconditioner)\n    else\n        linearsolve!(rhs!, lin.factors, lin.solver, Q, Qhat, p, t)\n    end\nend\n\n\"\"\"\n    struct NonLinearBackwardEulerSolver{NLS}\n        nlsolver::NLS\n        isadjustable::Bool\n        preconditioner_update_freq::Int64\n    end\n\nHelper type for specifying building a nonlinear backward Euler solver with a nonlinear\nsolver.\n\n# Arguments\n- `nlsolver`: iterative nonlinear solver, i.e., JacobianFreeNewtonKrylovSolver\n- `isadjustable`: TODO not used, might use for updating preconditioner\n- `preconditioner_update_freq`:  relavent to Jacobian free -1: no preconditioner;\n                             positive number, update every freq times\n\"\"\"\nstruct NonLinearBackwardEulerSolver{NLS}\n    nlsolver::NLS\n    isadjustable::Bool\n    # preconditioner_update_freq, -1: no preconditioner;\n    # positive number, update every freq times\n    preconditioner_update_freq::Int\n    function NonLinearBackwardEulerSolver(\n        nlsolver;\n        isadjustable = false,\n        preconditioner_update_freq = -1,\n    )\n        NLS = typeof(nlsolver)\n        return new{NLS}(nlsolver, isadjustable, preconditioner_update_freq)\n    end\nend\n\n\n\"\"\"\n    LinBESolver\n\nConcrete implementation of an `AbstractBackwardEulerSolver` to use nonlinear\nsolvers of type `NLS`. See helper type\n[`NonLinearBackwardEulerSolver`](@ref)\n```\n    Q = Qhat + α f_imp(Q, param, time)\n```\n\"\"\"\nmutable struct NonLinBESolver{FT, F, NLS} <: AbstractBackwardEulerSolver\n    # Solve Q - α f_imp(Q) = Qrhs\n    α::FT\n    # implcit operator\n    f_imp!::F\n    # jacobian action, which approximates drhs!/dQ⋅ΔQ , here rhs!(Q) = Q - α f_imp(Q)\n    jvp!::JacobianAction\n    # nonlinear solver\n    nlsolver::NLS\n    # whether adjust the time step or not\n    isadjustable::Bool\n    # preconditioner, approximation of drhs!/dQ\n    preconditioner::AbstractPreconditioner\n\nend\n\nΔt_is_adjustable(nlsolver::NonLinBESolver) = nlsolver.isadjustable\n\n\"\"\"\n    setup_backward_Euler_solver(solver::NonLinearBackwardEulerSolver, Q, α, tendency!)\n\nReturns a concrete implementation of an `AbstractBackwardEulerSolver` that will\nsolve for `Q` in nonlinear systems of the form of\n```\n    Q = Qhat + α f(Q, param, time)\n```\nCreate an empty JacobianAction\n\nCreate an empty preconditioner if preconditioner_update_freq > 0\n\"\"\"\nfunction setup_backward_Euler_solver(\n    nlbesolver::NonLinearBackwardEulerSolver,\n    Q,\n    α,\n    f_imp!,\n)\n    # Create an empty JacobianAction (without operator)\n    jvp! = JacobianAction(nothing, Q, nlbesolver.nlsolver.ϵ)\n\n    # Create an empty preconditioner if preconditioner_update_freq > 0\n    preconditioner_update_freq = nlbesolver.preconditioner_update_freq\n    # construct an empty preconditioner\n    preconditioner = (\n        preconditioner_update_freq > 0 ?\n        ColumnwiseLUPreconditioner(f_imp!, Q, preconditioner_update_freq) :\n        NoPreconditioner()\n    )\n    NonLinBESolver(\n        α,\n        f_imp!,\n        jvp!,\n        nlbesolver.nlsolver,\n        nlbesolver.isadjustable,\n        preconditioner,\n    )\nend\n\n\"\"\"\nNonlinear solve\n\nUpdate rhs! with α\n\nUpdate the rhs! in the jacobian action jvp!\n\"\"\"\nfunction (nlbesolver::NonLinBESolver)(Q, Qhat, α, p, t)\n\n    rhs! = EulerOperator(nlbesolver.f_imp!, -α)\n\n    nlbesolver.jvp!.rhs! = rhs!\n\n    nonlinearsolve!(\n        rhs!,\n        nlbesolver.jvp!,\n        nlbesolver.preconditioner,\n        nlbesolver.nlsolver,\n        Q,\n        Qhat,\n        p,\n        t;\n        max_newton_iters = nlbesolver.nlsolver.M,\n    )\n\nend\n"
  },
  {
    "path": "src/Numerics/ODESolvers/DifferentialEquations.jl",
    "content": "import DiffEqBase\nexport DiffEqJLSolver, DiffEqJLIMEXSolver\n\nabstract type AbstractDiffEqJLSolver <: AbstractODESolver end\n\n\"\"\"\n    DiffEqJLSolver(f, RKA, RKB, RKC, Q; dt, t0 = 0)\n\nThis is a time stepping object for explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q} = f(Q, t)\n```\n\nvia a DifferentialEquations.jl DEAlgorithm, which includes support\nfor OrdinaryDiffEq.jl, Sundials.jl, and more.\n\"\"\"\nmutable struct DiffEqJLSolver{I} <: AbstractDiffEqJLSolver\n    integ::I\n    steps::Int\n\n    function DiffEqJLSolver(\n        rhs!,\n        alg,\n        Q,\n        args...;\n        t0 = 0,\n        p = nothing,\n        kwargs...,\n    )\n        prob = DiffEqBase.ODEProblem(\n            (du, u, p, t) -> rhs!(du, u, p, t; increment = false),\n            Q,\n            (float(t0), typemax(typeof(float(t0)))),\n            p,\n        )\n        integ = DiffEqBase.init(\n            prob,\n            alg,\n            args...;\n            adaptive = false,\n            save_everystep = false,\n            save_start = false,\n            save_end = false,\n            kwargs...,\n        )\n        new{typeof(integ)}(integ, 0)\n    end\nend\n\n\"\"\"\n    DiffEqJLSolver(f, RKA, RKB, RKC, Q; dt, t0 = 0)\n\nThis is a time stepping object for explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q} = f_I(Q, t) + f_E(Q, t)\n```\n\nvia a DifferentialEquations.jl DEAlgorithm, which includes support\nfor OrdinaryDiffEq.jl, Sundials.jl, and more.\n\"\"\"\nmutable struct DiffEqJLIMEXSolver{I} <: AbstractDiffEqJLSolver\n    integ::I\n    steps::Int\n\n    function DiffEqJLIMEXSolver(\n        rhs!,\n        rhs_implicit!,\n        alg,\n        Q,\n        args...;\n        t0 = 0,\n        p = nothing,\n        kwargs...,\n    )\n        prob = DiffEqBase.SplitODEProblem(\n            (du, u, p, t) -> rhs_implicit!(du, u, p, t; increment = false),\n            (du, u, p, t) -> rhs!(du, u, p, t; increment = false),\n            Q,\n            (float(t0), typemax(typeof(float(t0)))),\n            p,\n        )\n        integ = DiffEqBase.init(\n            prob,\n            alg,\n            args...;\n            adaptive = false,\n            save_everystep = false,\n            save_start = false,\n            save_end = false,\n            kwargs...,\n        )\n\n        new{typeof(integ)}(integ, 0)\n    end\nend\n\ngettime(solver::AbstractDiffEqJLSolver) = solver.integ.t\ngetdt(solver::AbstractDiffEqJLSolver) = solver.integ.dt\nupdatedt!(solver::AbstractDiffEqJLSolver, dt) =\n    DiffEqBase.set_proposed_dt!(solver.integ, dt)\nupdatetime!(solver::AbstractDiffEqJLSolver, t) =\n    DiffEqBase.set_t!(solver.integ, t)\nisadjustable(solver::AbstractDiffEqJLSolver) = true # Is this isadaptive? Or something different?\n\n\"\"\"\n    ODESolvers.general_dostep!(Q, solver::AbstractODESolver, p,\n                               timeend::Real, adjustfinalstep::Bool)\n\nUse the solver to step `Q` forward in time from the current time, to the time\n`timeend`. If `adjustfinalstep == true` then `dt` is adjusted so that the step\ndoes not take the solution beyond the `timeend`.\n\"\"\"\nfunction general_dostep!(\n    Q,\n    solver::AbstractDiffEqJLSolver,\n    p,\n    timeend::Real;\n    adjustfinalstep::Bool,\n)\n    integ = solver.integ\n\n    if first(integ.opts.tstops) !== timeend\n        DiffEqBase.add_tstop!(integ, timeend)\n    end\n    dostep!(Q, solver, p, time)\n    solver.integ.t\nend\n\nfunction dostep!(\n    Q,\n    solver::AbstractDiffEqJLSolver,\n    p,\n    time,\n    slow_δ = nothing,\n    slow_rv_dQ = nothing,\n    in_slow_scaling = nothing,\n)\n\n    integ = solver.integ\n    integ.p = p # Can this change?\n\n    rv_Q = realview(Q)\n    if integ.u != Q\n        integ.u .= Q\n        DiffEqBase.u_modified!(integ, true)\n        # Will time always be correct?\n    end\n\n    DiffEqBase.step!(integ)\n    rv_Q .= solver.integ.u\nend\n\nfunction DiffEqJLConstructor(alg)\n    constructor =\n        (F, Q; dt = 0, t0 = 0) -> DiffEqJLSolver(F, alg, Q; t0 = t0, dt = dt)\n    return constructor\nend\n"
  },
  {
    "path": "src/Numerics/ODESolvers/GenericCallbacks.jl",
    "content": "\"\"\"\n    GenericCallbacks\n\nThis module defines interfaces and wrappers for callbacks to be used with an\n`AbstractODESolver`.\n\nA callback `cb` defines three methods:\n\n- `GenericCallbacks.init!(cb, solver, Q, param, t)`, to be called at solver\n  initialization.\n\n- `GenericCallbacks.call!(cb, solver, Q, param, t)`, to be called after each\n  time step: the return value dictates what action should be taken:\n\n   * `0` or `nothing`: continue time stepping as usual\n   * `1`: stop time stepping after all callbacks have been executed\n   * `2`: stop time stepping immediately\n\n- `GenericCallbacks.fini!(cb, solver, Q, param, t)`, to be called at solver\n  finish.\n\nAdditionally, _wrapper_ callbacks can be used to execute the callbacks under\ncertain conditions:\n\n - [`AtInit`](@ref)\n - [`AtInitAndFini`](@ref)\n - [`EveryXWallTimeSeconds`](@ref)\n - [`EveryXSimulationTime`](@ref)\n - [`EveryXSimulationSteps`](@ref)\n\nFor convenience, the following objects can also be used as callbacks:\n\n- A `Function` object `f`, `init!` and `fini!` are no-ops, and `call!` will\n  call `f()`, and ignore the return value.\n- A `Tuple` object will call `init!`, `call!` and `fini!` on each element\n  of the tuple.\n\"\"\"\nmodule GenericCallbacks\n\nexport AtInit,\n    AtInitAndFini,\n    EveryXWallTimeSeconds,\n    EveryXSimulationTime,\n    EveryXSimulationSteps\n\nusing MPI\n\ninit!(f::Function, solver, Q, param, t) = nothing\nfunction call!(f::Function, solver, Q, param, t)\n    f()\n    return nothing\nend\nfini!(f::Function, solver, Q, param, t) = nothing\n\nfunction init!(callbacks::Tuple, solver, Q, param, t)\n    for cb in callbacks\n        GenericCallbacks.init!(cb, solver, Q, param, t)\n    end\nend\nfunction call!(callbacks::Tuple, solver, Q, param, t)\n    val = 0\n    for cb in callbacks\n        val_i = GenericCallbacks.call!(cb, solver, Q, param, t)\n        val_i = (val_i === nothing) ? 0 : val_i\n        val = max(val, val_i)\n        if val == 2\n            return val\n        end\n    end\n    return val\nend\nfunction fini!(callbacks::Tuple, solver, Q, param, t)\n    for cb in callbacks\n        GenericCallbacks.fini!(cb, solver, Q, param, t)\n    end\nend\n\nabstract type AbstractCallback end\n\n\"\"\"\n    AtInit(callback) <: AbstractCallback\n\nA wrapper callback to execute `callback` at initialization as well as\nafter each interval.\n\"\"\"\nstruct AtInit <: AbstractCallback\n    callback::Any\nend\nfunction init!(cb::AtInit, solver, Q, param, t)\n    init!(cb.callback, solver, Q, param, t)\n    call!(cb.callback, solver, Q, param, t)\nend\nfunction call!(cb::AtInit, solver, Q, param, t)\n    call!(cb.callback, solver, Q, param, t)\nend\nfunction fini!(cb::AtInit, solver, Q, param, t)\n    fini!(cb.callback, solver, Q, param, t)\nend\n\n\"\"\"\n    AtInitAndFini(callback) <: AbstractCallback\n\nA wrapper callback to execute `callback` at initialization and at\nfinish as well as after each interval.\n\"\"\"\nstruct AtInitAndFini <: AbstractCallback\n    callback::Any\nend\nfunction init!(cb::AtInitAndFini, solver, Q, param, t)\n    init!(cb.callback, solver, Q, param, t)\n    call!(cb.callback, solver, Q, param, t)\nend\nfunction call!(cb::AtInitAndFini, solver, Q, param, t)\n    call!(cb.callback, solver, Q, param, t)\nend\nfunction fini!(cb::AtInitAndFini, solver, Q, param, t)\n    call!(cb.callback, solver, Q, param, t)\n    fini!(cb.callback, solver, Q, param, t)\nend\n\n\"\"\"\n    EveryXWallTimeSeconds(callback, Δtime, mpicomm)\n\nA wrapper callback to execute `callback` every `Δtime` wallclock time seconds.\n`mpicomm` is used to syncronize runtime across MPI ranks.\n\"\"\"\nmutable struct EveryXWallTimeSeconds <: AbstractCallback\n    \"callback to wrap\"\n    callback::Any\n    \"wall time seconds between callbacks\"\n    Δtime::Real\n    \"MPI communicator\"\n    mpicomm::MPI.Comm\n    \"time of the last callback\"\n    lastcbtime_ns::UInt64\n    function EveryXWallTimeSeconds(callback, Δtime, mpicomm)\n        lastcbtime_ns = zero(UInt64)\n        new(callback, Δtime, mpicomm, lastcbtime_ns)\n    end\nend\n\nfunction init!(cb::EveryXWallTimeSeconds, solver, Q, param, t)\n    cb.lastcbtime_ns = time_ns()\n    init!(cb.callback, solver, Q, param, t)\nend\nfunction call!(cb::EveryXWallTimeSeconds, solver, Q, param, t)\n    # Check whether we should do a callback\n    currtime_ns = time_ns()\n    runtime = (currtime_ns - cb.lastcbtime_ns) * 1e-9\n    runtime = MPI.Allreduce(runtime, max, cb.mpicomm)\n    if runtime < cb.Δtime\n        return 0\n    else\n        # Compute the next time to do a callback\n        cb.lastcbtime_ns = currtime_ns\n        return call!(cb.callback, solver, Q, param, t)\n    end\nend\nfunction fini!(cb::EveryXWallTimeSeconds, solver, Q, param, t)\n    fini!(cb.callback, solver, Q, param, t)\nend\n\n\n\"\"\"\n    EveryXSimulationTime(f, Δtime)\n\nA wrapper callback to execute `callback` every `time` simulation time seconds.\n\"\"\"\nmutable struct EveryXSimulationTime <: AbstractCallback\n    \"callback to wrap\"\n    callback::Any\n    \"simulation time seconds between callbacks\"\n    Δtime::Real\n    \"time of the last callback\"\n    lastcbtime::Real\n    function EveryXSimulationTime(callback, Δtime)\n        new(callback, Δtime, 0)\n    end\nend\n\nfunction init!(cb::EveryXSimulationTime, solver, Q, param, t)\n    cb.lastcbtime = t\n    init!(cb.callback, solver, Q, param, t)\nend\nfunction call!(cb::EveryXSimulationTime, solver, Q, param, t)\n    # Check whether we should do a callback\n    if (t - cb.lastcbtime) < cb.Δtime\n        return 0\n    else\n        # Compute the next time to do a callback\n        cb.lastcbtime = t\n        return call!(cb.callback, solver, Q, param, t)\n    end\nend\nfunction fini!(cb::EveryXSimulationTime, solver, Q, param, t)\n    fini!(cb.callback, solver, Q, param, t)\nend\n\n\n\"\"\"\n    EveryXSimulationSteps(callback, Δsteps)\n\nA wrapper callback to execute `callback` every `nsteps` of the time stepper.\n\"\"\"\nmutable struct EveryXSimulationSteps <: AbstractCallback\n    \"callback to wrap\"\n    callback::Any\n    \"number of steps between callbacks\"\n    Δsteps::Int\n    \"number of steps since last callback\"\n    steps::Int\n    function EveryXSimulationSteps(callback, Δsteps)\n        new(callback, Δsteps, 0)\n    end\nend\n\nfunction init!(cb::EveryXSimulationSteps, solver, Q, param, t)\n    cb.steps = 0\n    init!(cb.callback, solver, Q, param, t)\nend\nfunction call!(cb::EveryXSimulationSteps, solver, Q, param, t)\n    cb.steps += 1\n    if cb.steps < cb.Δsteps\n        return 0\n    else\n        cb.steps = 0\n        return call!(cb.callback, solver, Q, param, t)\n    end\nend\nfunction fini!(cb::EveryXSimulationSteps, solver, Q, param, t)\n    fini!(cb.callback, solver, Q, param, t)\nend\n\nend\n"
  },
  {
    "path": "src/Numerics/ODESolvers/LowStorageRungeKutta3NMethod.jl",
    "content": "export LowStorageRungeKutta3N\nexport LS3NRK44Classic, LS3NRK33Heuns\n\n\"\"\"\n    LowStorageRungeKutta3N(f, RKA, RKB, RKC, RKW, Q; dt, t0 = 0)\n\nThis is a time stepping object for explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q} = f(Q, t)\n```\n\nwith the required time step size `dt` and optional initial time `t0`.  This\ntime stepping object is intended to be passed to the `solve!` command.\n\nThe constructor builds a low-storage Runge--Kutta scheme using 3N storage\nbased on the provided `RKA`, `RKB` and `RKC` coefficient arrays.\n `RKC` (vector of length the number of stages `ns`) set nodal points position;\n `RKA` and `RKB` (size: ns x 2) set weight for tendency and stage-state;\n `RKW` (unused) provides RK weight (last row in Butcher's tableau).\n\nThe 3-N storage formulation from Fyfe (1966) is applicable to any 4-stage,\nfourth-order RK scheme. It is implemented here as:\n\n```math\n\\\\hspace{-20mm} for ~~ j ~~ in ~ [1:ns]: \\\\hspace{10mm}\n  t_j = t^n + \\\\Delta t ~ rkC_j\n```\n```math\n  dQ_j = dQ^*_j + f(Q_j,t_j)\n```\n```math\n  Q_{j+1} = Q_{j} + \\\\Delta t \\\\{ rkB_{j,1} ~ dQ_j + rkB_{j,2} ~ dR_j \\\\}\n```\n```math\n  dR_{j+1} = dR_j + rkA_{j+1,2} ~ dQ_j\n```\n```math\n  dQ^*_{j+1} = rkA_{j+1,1} ~ dQ_j\n```\n\nThe available concrete implementations are:\n\n  - [`LS3NRK44Classic`](@ref)\n  - [`LS3NRK33Heuns`](@ref)\n\n### References\n\n    @article{Fyfe1966,\n       title = {Economical Evaluation of Runge-Kutta Formulae},\n       author = {Fyfe, David J.},\n       journal = {Mathematics of Computation},\n       volume = {20},\n       pages = {392--398},\n       year = {1966}\n    }\n\"\"\"\nmutable struct LowStorageRungeKutta3N{T, RT, AT, Nstages} <: AbstractODESolver\n    \"time step\"\n    dt::RT\n    \"time\"\n    t::RT\n    \"elapsed time steps\"\n    steps::Int\n    \"rhs function\"\n    rhs!::Any\n    \"Storage for RHS during the `LowStorageRungeKutta3N` update\"\n    dQ::AT\n    \"Secondary Storage for RHS during the `LowStorageRungeKutta3N` update\"\n    dR::AT\n    \"low storage RK coefficient array A (rhs scaling)\"\n    RKA::Array{RT, 2}\n    \"low storage RK coefficient array B (rhs add in scaling)\"\n    RKB::Array{RT, 2}\n    \"low storage RK coefficient vector C (time scaling)\"\n    RKC::Array{RT, 1}\n    \"RK weight coefficient vector W (last row in Butcher's tableau)\"\n    RKW::Array{RT, 1}\n\n    function LowStorageRungeKutta3N(\n        rhs!,\n        RKA,\n        RKB,\n        RKC,\n        RKW,\n        Q::AT;\n        dt = 0,\n        t0 = 0,\n    ) where {AT <: AbstractArray}\n\n        T = eltype(Q)\n        RT = real(T)\n\n        dQ = similar(Q)\n        dR = similar(Q)\n        fill!(dQ, 0)\n        fill!(dR, 0)\n\n        new{T, RT, AT, length(RKC)}(\n            RT(dt),\n            RT(t0),\n            0,\n            rhs!,\n            dQ,\n            dR,\n            RKA,\n            RKB,\n            RKC,\n            RKW,\n        )\n    end\nend\n\n\"\"\"\n    dostep!(Q, lsrk3n::LowStorageRungeKutta3N, p, time::Real, nsubsteps::Int,\n            iStage::Int, [slow_δ, slow_rv_dQ, slow_scaling])\n\nWrapper function to use the 3N low storage Runge--Kutta method `lsrk3n` as the fast\nsolver for a Multirate Infinitesimal Step method by calling dostep!(Q,\nlsrk3n::LowStorageRungeKutta3N, p, time::Real, [slow_δ, slow_rv_dQ, slow_scaling])\nnsubsteps times.\n\"\"\"\nfunction dostep!(\n    Q,\n    lsrk3n::LowStorageRungeKutta3N,\n    p,\n    time::Real,\n    nsubsteps::Int,\n    iStage::Int,\n    slow_δ = nothing,\n    slow_rv_dQ = nothing,\n    slow_scaling = nothing,\n)\n    for i in 1:nsubsteps\n        dostep!(Q, lsrk3n, p, time, slow_δ, slow_rv_dQ, slow_scaling)\n        time += lsrk3n.dt\n    end\nend\n\n\"\"\"\n    dostep!(Q, lsrk3n::LowStorageRungeKutta3N, p, time::Real,\n            [slow_δ, slow_rv_dQ, slow_scaling])\n\nUse the 3N low storage Runge--Kutta method `lsrk3n` to step `Q` forward in time\nfrom the current time `time` to final time `time + getdt(lsrk3n)`.\n\nIf the optional parameter `slow_δ !== nothing` then `slow_rv_dQ * slow_δ` is\nadded as an additional ODE right-hand side source. If the optional parameter\n`slow_scaling !== nothing` then after the final stage update the scaling\n`slow_rv_dQ *= slow_scaling` is performed.\n\"\"\"\nfunction dostep!(\n    Q,\n    lsrk3n::LowStorageRungeKutta3N,\n    p,\n    time,\n    slow_δ = nothing,\n    slow_rv_dQ = nothing,\n    in_slow_scaling = nothing,\n)\n    dt = lsrk3n.dt\n\n    RKA, RKB, RKC = lsrk3n.RKA, lsrk3n.RKB, lsrk3n.RKC\n    rhs!, dQ, dR = lsrk3n.rhs!, lsrk3n.dQ, lsrk3n.dR\n\n    rv_Q = realview(Q)\n    rv_dQ = realview(dQ)\n    rv_dR = realview(dR)\n    groupsize = 256\n\n    rv_dR .= -0\n    for s in 1:length(RKC)\n        rhs!(dQ, Q, p, time + RKC[s] * dt, increment = true)\n\n        slow_scaling = nothing\n        if s == length(RKC)\n            slow_scaling = in_slow_scaling\n        end\n        # update solution and scale RHS\n        event = Event(array_device(Q))\n        event = update!(array_device(Q), groupsize)(\n            rv_dQ,\n            rv_dR,\n            rv_Q,\n            RKA[s % length(RKC) + 1, 1],\n            RKA[s % length(RKC) + 1, 2],\n            RKB[s, 1],\n            RKB[s, 2],\n            dt,\n            slow_δ,\n            slow_rv_dQ,\n            slow_scaling;\n            ndrange = length(rv_Q),\n            dependencies = (event,),\n        )\n        wait(array_device(Q), event)\n    end\nend\n\n@kernel function update!(\n    dQ,\n    dR,\n    Q,\n    rka1,\n    rka2,\n    rkb1,\n    rkb2,\n    dt,\n    slow_δ,\n    slow_dQ,\n    slow_scaling,\n)\n    i = @index(Global, Linear)\n    @inbounds begin\n        if slow_δ !== nothing\n            dQ[i] += slow_δ * slow_dQ[i]\n        end\n        Q[i] += rkb1 * dt * dQ[i] + rkb2 * dt * dR[i]\n        dR[i] += rka2 * dQ[i]\n        dQ[i] *= rka1\n        if slow_scaling !== nothing\n            slow_dQ[i] *= slow_scaling\n        end\n    end\nend\n\n\"\"\"\n    LS3NRK44Classic(f, Q; dt, t0 = 0)\n\nThis function returns a [`LowStorageRungeKutta3N`](@ref) time stepping object\nfor explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q} = f(Q, t)\n```\n\nwith the required time step size `dt` and optional initial time `t0`.  This\ntime stepping object is intended to be passed to the `solve!` command.\n\nThis uses the classic 4-stage, fourth-order Runge--Kutta scheme\nin the low-storage implementation of Blum (1962)\n\n### References\n    @article {Blum1962,\n       title = {A Modification of the Runge-Kutta Fourth-Order Method}\n       author = {Blum, E. K.},\n       journal = {Mathematics of Computation},\n       volume = {16},\n       pages = {176-187},\n       year = {1962}\n    }\n\"\"\"\nfunction LS3NRK44Classic(F, Q::AT; dt = 0, t0 = 0) where {AT <: AbstractArray}\n    T = eltype(Q)\n    RT = real(T)\n\n    RKA = [\n        RT(0) RT(0)\n        RT(0) RT(1)\n        RT(-1 // 2) RT(0)\n        RT(2) RT(-6)\n    ]\n\n    RKB = [\n        RT(1 // 2) RT(0)\n        RT(1 // 2) RT(-1 // 2)\n        RT(1) RT(0)\n        RT(1 // 6) RT(1 // 6)\n    ]\n\n    RKC = [RT(0), RT(1 // 2), RT(1 // 2), RT(1)]\n\n    RKW = [RT(1 // 6), RT(1 // 3), RT(1 // 3), RT(1 // 6)]\n\n    LowStorageRungeKutta3N(F, RKA, RKB, RKC, RKW, Q; dt = dt, t0 = t0)\nend\n\n\"\"\"\n    LS3NRK33Heuns(f, Q; dt, t0 = 0)\n\nThis function returns a [`LowStorageRungeKutta3N`](@ref) time stepping object\nfor explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q} = f(Q, t)\n```\n\nwith the required time step size `dt` and optional initial time `t0`.  This\ntime stepping object is intended to be passed to the `solve!` command.\n\nThis method uses the 3-stage, third-order Heun's Runge--Kutta scheme.\n\n### References\n    @article {Heun1900,\n       title = {Neue Methoden zur approximativen Integration der\n       Differentialgleichungen einer unabh\\\"{a}ngigen Ver\\\"{a}nderlichen}\n       author = {Heun, Karl},\n       journal = {Z. Math. Phys},\n       volume = {45},\n       pages = {23--38},\n       year = {1900}\n    }\n\"\"\"\nfunction LS3NRK33Heuns(\n    F,\n    Q::AT;\n    dt = nothing,\n    t0 = 0,\n) where {AT <: AbstractArray}\n    T = eltype(Q)\n    RT = real(T)\n\n    RKA = [\n        RT(0) RT(0)\n        RT(0) RT(1)\n        RT(-1) RT(1 // 3)\n    ]\n\n    RKB = [\n        RT(1 // 3) RT(0)\n        RT(2 // 3) RT(-1 // 3)\n        RT(3 // 4) RT(1 // 4)\n    ]\n\n    RKC = [RT(0), RT(1 // 3), RT(2 // 3)]\n\n    RKW = [RT(1 // 4), RT(0), RT(3 // 4)]\n\n    LowStorageRungeKutta3N(F, RKA, RKB, RKC, RKW, Q; dt = dt, t0 = t0)\nend\n"
  },
  {
    "path": "src/Numerics/ODESolvers/LowStorageRungeKuttaMethod.jl",
    "content": "\nexport LowStorageRungeKutta2N\nexport LSRK54CarpenterKennedy, LSRK144NiegemannDiehlBusch, LSRKEulerMethod\n\n\"\"\"\n    LowStorageRungeKutta2N(f, RKA, RKB, RKC, Q; dt, t0 = 0)\n\nThis is a time stepping object for explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q} = f(Q, t)\n```\n\nwith the required time step size `dt` and optional initial time `t0`.  This\ntime stepping object is intended to be passed to the `solve!` command.\n\nThe constructor builds a low-storage Runge-Kutta scheme using 2N\nstorage based on the provided `RKA`, `RKB` and `RKC` coefficient arrays.\n\nThe available concrete implementations are:\n\n  - [`LSRK54CarpenterKennedy`](@ref)\n  - [`LSRK144NiegemannDiehlBusch`](@ref)\n\"\"\"\nmutable struct LowStorageRungeKutta2N{T, RT, AT, Nstages} <: AbstractODESolver\n    \"time step\"\n    dt::RT\n    \"time\"\n    t::RT\n    \"elapsed time steps\"\n    steps::Int\n    \"rhs function\"\n    rhs!::Any\n    \"Storage for RHS during the LowStorageRungeKutta update\"\n    dQ::AT\n    \"low storage RK coefficient vector A (rhs scaling)\"\n    RKA::NTuple{Nstages, RT}\n    \"low storage RK coefficient vector B (rhs add in scaling)\"\n    RKB::NTuple{Nstages, RT}\n    \"low storage RK coefficient vector C (time scaling)\"\n    RKC::NTuple{Nstages, RT}\n\n    function LowStorageRungeKutta2N(\n        rhs!,\n        RKA,\n        RKB,\n        RKC,\n        Q::AT;\n        dt = 0,\n        t0 = 0,\n    ) where {AT <: AbstractArray}\n\n        T = eltype(Q)\n        RT = real(T)\n\n        dQ = similar(Q)\n        fill!(dQ, 0)\n\n        new{T, RT, AT, length(RKA)}(RT(dt), RT(t0), 0, rhs!, dQ, RKA, RKB, RKC)\n    end\nend\n\n\"\"\"\n    dostep!(Q, lsrk::LowStorageRungeKutta2N, p, time::Real, nsubsteps::Int,\n            iStage::Int, [slow_δ, slow_rv_dQ, slow_scaling])\n\nWrapper function to use the 2N low storage Runge--Kutta method `lsrk` as the fast\nsolver for a Multirate Infinitesimal Step method by calling dostep!(Q,\nlsrk::LowStorageRungeKutta2N, p, time::Real, [slow_δ, slow_rv_dQ, slow_scaling])\nnsubsteps times.\n\"\"\"\nfunction dostep!(\n    Q,\n    lsrk::LowStorageRungeKutta2N,\n    p,\n    time::Real,\n    nsubsteps::Int,\n    iStage::Int,\n    slow_δ = nothing,\n    slow_rv_dQ = nothing,\n    slow_scaling = nothing,\n)\n    for i in 1:nsubsteps\n        dostep!(Q, lsrk, p, time, slow_δ, slow_rv_dQ, slow_scaling)\n        time += lsrk.dt\n    end\nend\n\n\"\"\"\n    dostep!(Q, lsrk::LowStorageRungeKutta2N, p, time::Real,\n            [slow_δ, slow_rv_dQ, slow_scaling])\n\nUse the 2N low storage Runge--Kutta method `lsrk` to step `Q` forward in time\nfrom the current time `time` to final time `time + getdt(lsrk)`.\n\nIf the optional parameter `slow_δ !== nothing` then `slow_rv_dQ * slow_δ` is\nadded as an additionall ODE right-hand side source. If the optional parameter\n`slow_scaling !== nothing` then after the final stage update the scaling\n`slow_rv_dQ *= slow_scaling` is performed.\n\"\"\"\nfunction dostep!(\n    Q,\n    lsrk::LowStorageRungeKutta2N,\n    p,\n    time,\n    slow_δ = nothing,\n    slow_rv_dQ = nothing,\n    in_slow_scaling = nothing,\n)\n    dt = lsrk.dt\n\n    RKA, RKB, RKC = lsrk.RKA, lsrk.RKB, lsrk.RKC\n    rhs!, dQ = lsrk.rhs!, lsrk.dQ\n\n    rv_Q = realview(Q)\n    rv_dQ = realview(dQ)\n\n    groupsize = 256\n\n    for s in 1:length(RKA)\n        rhs!(dQ, Q, p, time + RKC[s] * dt, increment = true)\n\n        slow_scaling = nothing\n        if s == length(RKA)\n            slow_scaling = in_slow_scaling\n        end\n        # update solution and scale RHS\n        event = Event(array_device(Q))\n        event = update!(array_device(Q), groupsize)(\n            rv_dQ,\n            rv_Q,\n            RKA[s % length(RKA) + 1],\n            RKB[s],\n            dt,\n            slow_δ,\n            slow_rv_dQ,\n            slow_scaling;\n            ndrange = length(rv_Q),\n            dependencies = (event,),\n        )\n        wait(array_device(Q), event)\n    end\nend\n\n@kernel function update!(dQ, Q, rka, rkb, dt, slow_δ, slow_dQ, slow_scaling)\n    i = @index(Global, Linear)\n    @inbounds begin\n        if slow_δ !== nothing\n            dQ[i] += slow_δ * slow_dQ[i]\n        end\n        Q[i] += rkb * dt * dQ[i]\n        dQ[i] *= rka\n        if slow_scaling !== nothing\n            slow_dQ[i] *= slow_scaling\n        end\n    end\nend\n\n\"\"\"\n    dostep!(Q, lsrk::LowStorageRungeKutta2N, p::MRIParam, time::Real,\n            dt::Real)\n\nUse the 2N low storage Runge--Kutta method `lsrk` to step `Q` forward in time\nfrom the current time `time` to final time `time + dt`.\n\nIf the optional parameter `slow_δ !== nothing` then `slow_rv_dQ * slow_δ` is\nadded as an additionall ODE right-hand side source. If the optional parameter\n`slow_scaling !== nothing` then after the final stage update the scaling\n`slow_rv_dQ *= slow_scaling` is performed.\n\"\"\"\nfunction dostep!(Q, lsrk::LowStorageRungeKutta2N, mrip::MRIParam, time::Real)\n    dt = lsrk.dt\n\n    RKA, RKB, RKC = lsrk.RKA, lsrk.RKB, lsrk.RKC\n    rhs!, dQ = lsrk.rhs!, lsrk.dQ\n\n    rv_Q = realview(Q)\n    rv_dQ = realview(dQ)\n\n    groupsize = 256\n\n    for s in 1:length(RKA)\n        stage_time = time + RKC[s] * dt\n        rhs!(dQ, Q, mrip.p, stage_time, increment = true)\n\n        # update solution and scale RHS\n        τ = (stage_time - mrip.ts) / mrip.Δts\n        event = Event(array_device(Q))\n        event = lsrk_mri_update!(array_device(Q), groupsize)(\n            rv_dQ,\n            rv_Q,\n            RKA[s % length(RKA) + 1],\n            RKB[s],\n            τ,\n            dt,\n            mrip.γs,\n            mrip.Rs;\n            ndrange = length(rv_Q),\n            dependencies = (event,),\n        )\n        wait(array_device(Q), event)\n    end\nend\n\n@kernel function lsrk_mri_update!(dQ, Q, rka, rkb, τ, dt, γs, Rs)\n    i = @index(Global, Linear)\n    @inbounds begin\n        NΓ = length(γs)\n        Ns = length(γs[1])\n        dqi = dQ[i]\n\n        for s in 1:Ns\n            ri = Rs[s][i]\n            sc = γs[NΓ][s]\n            for k in (NΓ - 1):-1:1\n                sc = sc * τ + γs[k][s]\n            end\n            dqi += sc * ri\n        end\n\n        Q[i] += rkb * dt * dqi\n        dQ[i] = rka * dqi\n    end\nend\n\n\n\"\"\"\n    LSRKEulerMethod(f, Q; dt, t0 = 0)\n\nThis function returns a [`LowStorageRungeKutta2N`](@ref) time stepping object\nfor explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q} = f(Q, t)\n```\n\nwith the required time step size `dt` and optional initial time `t0`.  This\ntime stepping object is intended to be passed to the `solve!` command.\n\nThis method uses the LSRK2N framework to implement a simple Eulerian forward time stepping scheme for the use of debugging.\n\n### References\n\n\"\"\"\nfunction LSRKEulerMethod(\n    F,\n    Q::AT;\n    dt = nothing,\n    t0 = 0,\n) where {AT <: AbstractArray}\n    T = eltype(Q)\n    RT = real(T)\n\n    RKA = (RT(0),)\n\n    RKB = (RT(1),)\n\n    RKC = (RT(0),)\n\n    LowStorageRungeKutta2N(F, RKA, RKB, RKC, Q; dt = dt, t0 = t0)\nend\n\n\"\"\"\n    LSRK54CarpenterKennedy(f, Q; dt, t0 = 0)\n\nThis function returns a [`LowStorageRungeKutta2N`](@ref) time stepping object\nfor explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q} = f(Q, t)\n```\n\nwith the required time step size `dt` and optional initial time `t0`.  This\ntime stepping object is intended to be passed to the `solve!` command.\n\nThis uses the fourth-order, low-storage, Runge--Kutta scheme of Carpenter\nand Kennedy (1994) (in their notation (5,4) 2N-Storage RK scheme).\n\n### References\n\n    @TECHREPORT{CarpenterKennedy1994,\n      author = {M.~H. Carpenter and C.~A. Kennedy},\n      title = {Fourth-order {2N-storage} {Runge-Kutta} schemes},\n      institution = {National Aeronautics and Space Administration},\n      year = {1994},\n      number = {NASA TM-109112},\n      address = {Langley Research Center, Hampton, VA},\n    }\n\"\"\"\nfunction LSRK54CarpenterKennedy(\n    F,\n    Q::AT;\n    dt = 0,\n    t0 = 0,\n) where {AT <: AbstractArray}\n    T = eltype(Q)\n    RT = real(T)\n\n    RKA = (\n        RT(0),\n        RT(-567301805773 // 1357537059087),\n        RT(-2404267990393 // 2016746695238),\n        RT(-3550918686646 // 2091501179385),\n        RT(-1275806237668 // 842570457699),\n    )\n\n    RKB = (\n        RT(1432997174477 // 9575080441755),\n        RT(5161836677717 // 13612068292357),\n        RT(1720146321549 // 2090206949498),\n        RT(3134564353537 // 4481467310338),\n        RT(2277821191437 // 14882151754819),\n    )\n\n    RKC = (\n        RT(0),\n        RT(1432997174477 // 9575080441755),\n        RT(2526269341429 // 6820363962896),\n        RT(2006345519317 // 3224310063776),\n        RT(2802321613138 // 2924317926251),\n    )\n\n    LowStorageRungeKutta2N(F, RKA, RKB, RKC, Q; dt = dt, t0 = t0)\nend\n\n\"\"\"\n    LSRK144NiegemannDiehlBusch((f, Q; dt, t0 = 0)\n\nThis function returns a [`LowStorageRungeKutta2N`](@ref) time stepping object\nfor explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q} = f(Q, t)\n```\n\nwith the required time step size `dt` and optional initial time `t0`.  This\ntime stepping object is intended to be passed to the `solve!` command.\n\nThis uses the fourth-order, 14-stage, low-storage, Runge--Kutta scheme of\nNiegemann, Diehl, and Busch (2012) with optimized stability region\n\n### References\n - [Niegemann2012](@cite)\n\"\"\"\nfunction LSRK144NiegemannDiehlBusch(\n    F,\n    Q::AT;\n    dt = 0,\n    t0 = 0,\n) where {AT <: AbstractArray}\n    T = eltype(Q)\n    RT = real(T)\n\n    RKA = (\n        RT(0),\n        RT(-0.7188012108672410),\n        RT(-0.7785331173421570),\n        RT(-0.0053282796654044),\n        RT(-0.8552979934029281),\n        RT(-3.9564138245774565),\n        RT(-1.5780575380587385),\n        RT(-2.0837094552574054),\n        RT(-0.7483334182761610),\n        RT(-0.7032861106563359),\n        RT(0.0013917096117681),\n        RT(-0.0932075369637460),\n        RT(-0.9514200470875948),\n        RT(-7.1151571693922548),\n    )\n\n    RKB = (\n        RT(0.0367762454319673),\n        RT(0.3136296607553959),\n        RT(0.1531848691869027),\n        RT(0.0030097086818182),\n        RT(0.3326293790646110),\n        RT(0.2440251405350864),\n        RT(0.3718879239592277),\n        RT(0.6204126221582444),\n        RT(0.1524043173028741),\n        RT(0.0760894927419266),\n        RT(0.0077604214040978),\n        RT(0.0024647284755382),\n        RT(0.0780348340049386),\n        RT(5.5059777270269628),\n    )\n\n    RKC = (\n        RT(0),\n        RT(0.0367762454319673),\n        RT(0.1249685262725025),\n        RT(0.2446177702277698),\n        RT(0.2476149531070420),\n        RT(0.2969311120382472),\n        RT(0.3978149645802642),\n        RT(0.5270854589440328),\n        RT(0.6981269994175695),\n        RT(0.8190890835352128),\n        RT(0.8527059887098624),\n        RT(0.8604711817462826),\n        RT(0.8627060376969976),\n        RT(0.8734213127600976),\n    )\n\n    LowStorageRungeKutta2N(F, RKA, RKB, RKC, Q; dt = dt, t0 = t0)\nend\n"
  },
  {
    "path": "src/Numerics/ODESolvers/MultirateInfinitesimalGARKDecoupledImplicit.jl",
    "content": "export MRIGARKDecoupledImplicit\nexport MRIGARKIRK21aSandu,\n    MRIGARKESDIRK34aSandu,\n    MRIGARKESDIRK46aSandu,\n    MRIGARKESDIRK23LSA,\n    MRIGARKESDIRK24LSA\n\n\"\"\"\n    MRIGARKDecoupledImplicit(f!, backward_euler_solver, fastsolver, Γs, γ̂s, Q,\n                             Δt, t0)\n\nConstruct a decoupled implicit MultiRate Infinitesimal General-structure\nAdditive Runge--Kutta (MRI-GARK) scheme to solve\n\n```math\n    \\\\dot{y} = f(y, t) + g(y, t)\n```\n\nwhere `f` is the slow tendency function and `g` is the fast tendency function;\nsee Sandu (2019).\n\nThe fast tendency is integrated using the `fastsolver` and the slow tendency\nusing the MRI-GARK scheme. Since this is a decoupled, implicit MRI-GARK there is no implicit coupling between the fast and slow tendencies.\n\nThe `backward_euler_solver` should be of type `AbstractBackwardEulerSolver` or\n`LinearBackwardEulerSolver`, and is used to perform the backward Euler solves\nfor `y` given the slow tendency function, namely\n\n```math\n   y = z + α f(y, t; p)\n```\n\nCurrently only [`LowStorageRungeKutta2N`](@ref) schemes are supported for\n`fastsolver`\n\nThe coefficients defined by `γ̂s` can be used for an embedded scheme (only the\nlast stage is different).\n\nThe available concrete implementations are:\n\n  - [`MRIGARKIRK21aSandu`](@ref)\n  - [`MRIGARKESDIRK34aSandu`](@ref)\n  - [`MRIGARKESDIRK46aSandu`](@ref)\n\n### References\n - [Sandu2019](@cite)\n\"\"\"\nmutable struct MRIGARKDecoupledImplicit{\n    T,\n    RT,\n    AT,\n    Nstages,\n    NΓ,\n    FS,\n    Nx,\n    Ny,\n    Nx_Ny,\n    BE,\n} <: AbstractODESolver\n    \"time step\"\n    dt::RT\n    \"time\"\n    t::RT\n    \"elapsed time steps\"\n    steps::Int\n    \"rhs function\"\n    slowrhs!::Any\n    \"backwark Euler solver\"\n    besolver!::BE\n    \"Storage for RHS during the `MRIGARKDecoupledImplicit` update\"\n    Rstages::NTuple{Nstages, AT}\n    \"Storage for the implicit solver data vector\"\n    Qhat::AT\n    \"RK coefficient matrices for coupling coefficients\"\n    Γs::NTuple{NΓ, SArray{Tuple{Nx, Ny}, RT, 2, Nx_Ny}}\n    \"RK coefficient matrices for embedded scheme\"\n    γ̂s::NTuple{NΓ, SArray{NTuple{1, Ny}, RT, 1, Ny}}\n    \"RK coefficient vector C (time scaling)\"\n    Δc::SArray{NTuple{1, Nstages}, RT, 1, Nstages}\n    \"fast solver\"\n    fastsolver::FS\n\n    function MRIGARKDecoupledImplicit(\n        slowrhs!,\n        backward_euler_solver,\n        fastsolver,\n        Γs,\n        γ̂s,\n        Q::AT,\n        dt,\n        t0,\n    ) where {AT <: AbstractArray}\n        NΓ = length(Γs)\n\n        T = eltype(Q)\n        RT = real(T)\n\n        # Compute the Δc coefficients (only explicit values kept)\n        Δc = sum(Γs[1], dims = 2)\n\n        # Couple of sanity checks on the assumptions of coefficients being of\n        # the decoupled implicit structure of Sandu (2019)\n        @assert all(isapprox.(Δc[2:2:end], 0; atol = 2 * eps(RT)))\n\n        Δc = Δc[1:2:(end - 1)]\n\n        # number of slow RHS values we need to keep\n        Nstages = length(Δc)\n\n        # Couple more sanity checks on the decoupled implicit structure\n        @assert Nstages == size(Γs[1], 2) - 1\n        @assert Nstages == div(size(Γs[1], 1), 2)\n\n        # Scale in the Δc to the Γ and γ̂, and convert to real type\n        Γs = ntuple(k -> RT.(Γs[k]), NΓ)\n        γ̂s = ntuple(k -> RT.(γ̂s[k]), NΓ)\n\n        # Convert to real type\n        Δc = RT.(Δc)\n\n        # create storage for the stage values\n        Rstages = ntuple(i -> similar(Q), Nstages)\n        Qhat = similar(Q)\n\n        FS = typeof(fastsolver)\n        Nx, Ny = size(Γs[1])\n\n        # Set up the backward Euler solver with the initial value of α\n        α = dt * Γs[1][2, 2]\n        besolver! =\n            setup_backward_Euler_solver(backward_euler_solver, Q, α, slowrhs!)\n        @assert besolver! isa AbstractBackwardEulerSolver\n        BE = typeof(besolver!)\n\n        new{T, RT, AT, Nstages, NΓ, FS, Nx, Ny, Nx * Ny, BE}(\n            RT(dt),\n            RT(t0),\n            0,\n            slowrhs!,\n            besolver!,\n            Rstages,\n            Qhat,\n            Γs,\n            γ̂s,\n            Δc,\n            fastsolver,\n        )\n    end\nend\n\nfunction updatedt!(mrigark::MRIGARKDecoupledImplicit, dt)\n    @assert Δt_is_adjustable(mrigark.besolver!)\n    α = dt * mrigark.Γs[1][2, 2]\n    update_backward_Euler_solver!(mrigark.besolver!, mrigark.Qhat, α)\n    mrigark.dt = dt\nend\n\nfunction dostep!(Q, mrigark::MRIGARKDecoupledImplicit, param, time::Real)\n    dt = mrigark.dt\n    fast = mrigark.fastsolver\n\n    Rs = mrigark.Rstages\n    Δc = mrigark.Δc\n\n    Nstages = length(Δc)\n    groupsize = 256\n\n    slowrhs! = mrigark.slowrhs!\n    Γs = mrigark.Γs\n    NΓ = length(Γs)\n\n    ts = time\n    groupsize = 256\n\n    besolver! = mrigark.besolver!\n    Qhat = mrigark.Qhat\n    # Since decoupled implicit methods are being used, there is an purely\n    # explicit stage followed by an implicit correction stage, hence the divide\n    # by two\n    for s in 1:Nstages\n        # Stage dt\n        dts = Δc[s] * dt\n        stage_end_time = ts + dts\n\n        # initialize the slow tendency stage value\n        slowrhs!(Rs[s], Q, param, ts, increment = false)\n\n        # # advance fast solution to time stage_end_time\n        γs = ntuple(k -> ntuple(j -> Γs[k][2s - 1, j] / Δc[s], s), NΓ)\n        mriparam = MRIParam(param, γs, realview.(Rs[1:s]), ts, dts)\n        updatetime!(mrigark.fastsolver, ts)\n        solve!(Q, mrigark.fastsolver, mriparam; timeend = stage_end_time)\n\n        # correct with implicit slow solve\n        # Qhat = Q + ∑_j Σ_k Γ_{sjk} dt Rs[j] / k\n        # (Divide by k arises from the integration γ_{ij}(τ) in Sandu (2019);\n        # see Equation (2.2b) and Definition 2.2\n        γs = ntuple(k -> ntuple(j -> dt * Γs[k][2s, j] / k, s), NΓ)\n        event = Event(array_device(Q))\n        event = mri_create_Qhat!(array_device(Q), groupsize)(\n            realview(Qhat),\n            realview(Q),\n            γs,\n            mriparam.Rs;\n            ndrange = length(realview(Q)),\n            dependencies = (event,),\n        )\n        wait(array_device(Q), event)\n\n        # Solve: Q = Qhat + α fslow(Q, stage_end_time)\n        α = dt * Γs[1][2s, s + 1]\n        besolver!(Q, Qhat, α, param, stage_end_time)\n\n        # update time\n        ts += dts\n    end\nend\n\n# Compute: Qhat = Q + ∑_j Σ_k Γ_{sjk} dt Rs[j] / k\n@kernel function mri_create_Qhat!(Qhat, Q, γs, Rs)\n    i = @index(Global, Linear)\n    @inbounds begin\n        NΓ = length(γs)\n        Ns = length(γs[1])\n        qhat = Q[i]\n\n        for s in 1:Ns\n            ri = Rs[s][i]\n            sc = γs[1][s]\n            for k in 2:NΓ\n                sc += γs[k][s]\n            end\n            qhat += sc * ri\n        end\n        Qhat[i] = qhat\n    end\nend\n\n\"\"\"\n    MRIGARKIRK21aSandu(f!, fastsolver, Q; dt, t0 = 0)\n\nThe 2rd order, 2 stage implicit scheme from Sandu (2019).\n\"\"\"\nfunction MRIGARKIRK21aSandu(\n    slowrhs!,\n    backward_euler_solver,\n    fastsolver,\n    Q;\n    dt,\n    t0 = 0,\n)\n    #! format: off\n    Γ0 = [ 1 // 1 0 // 1\n          -1 // 2 1 // 2 ]\n    γ̂0 = [-1 // 2 1 // 2 ]\n    #! format: on\n    MRIGARKDecoupledImplicit(\n        slowrhs!,\n        backward_euler_solver,\n        fastsolver,\n        (Γ0,),\n        (γ̂0,),\n        Q,\n        dt,\n        t0,\n    )\nend\n\n\"\"\"\n    MRIGARKESDIRK34aSandu(f!, fastsolver, Q; dt, t0=0)\n\nThe 3rd order, 4 stage decoupled implicit scheme from Sandu (2019).\n\"\"\"\nfunction MRIGARKESDIRK34aSandu(\n    slowrhs!,\n    backward_euler_solver,\n    fastsolver,\n    Q;\n    dt,\n    t0 = 0,\n)\n    T = real(eltype(Q))\n    μ = acot(2 * sqrt(T(2))) / 3\n    λ = 1 - cos(μ) / sqrt(T(2)) + sqrt(T(3 // 2)) * sin(μ)\n    @assert isapprox(-1 + 9λ - 18 * λ^2 + 6 * λ^3, 0, atol = 2 * eps(T))\n\n    #! format: off\n    Γ0 = [\n          T(1 // 3)               0                        0           0\n          -λ                      λ                        0           0\n          (3-10λ) / (24λ-6)       (5-18λ) / (6-24λ)        0           0\n          (-24λ^2+6λ+1) / (6-24λ) (-48λ^2+12λ+1) / (24λ-6) λ           0\n          (3-16λ) / (12-48λ)      (48λ^2-21λ+2) / (12λ-3)  (3-16λ) / 4 0\n          -λ                      0                        0           λ\n         ]\n    γ̂0 = [ 0                      0                        0           0]\n    #! format: on\n    MRIGARKDecoupledImplicit(\n        slowrhs!,\n        backward_euler_solver,\n        fastsolver,\n        (Γ0,),\n        (γ̂0,),\n        Q,\n        dt,\n        t0,\n    )\nend\n\n\"\"\"\n    MRIGARKESDIRK46aSandu(f!, fastsolver, Q; dt, t0=0)\n\nThe 4th order, 6 stage decoupled implicit scheme from Sandu (2019).\n\"\"\"\nfunction MRIGARKESDIRK46aSandu(\n    slowrhs!,\n    implicitsolve!,\n    fastsolver,\n    Q;\n    dt,\n    t0 = 0,\n)\n    T = real(eltype(Q))\n    μ = acot(2 * sqrt(T(2))) / 3\n    λ = 1 - cos(μ) / sqrt(T(2)) + sqrt(T(3 // 2)) * sin(μ)\n    @assert isapprox(-1 + 9λ - 18 * λ^2 + 6 * λ^3, 0, atol = 2 * eps(T))\n\n    #! format: off\n    Γ0 = [\n                         1 // 5                             0 // 1                             0 // 1                              0 // 1                          0 // 1           0 // 1\n                        -1 // 4                             1 // 4                             0 // 1                              0 // 1                          0 // 1           0 // 1\n             1771023115159 // 1929363690800    -1385150376999 // 1929363690800                 0 // 1                              0 // 1                          0 // 1           0 // 1\n                    914009 // 345800                 -1000459 // 345800                        1 // 4                              0 // 1                          0 // 1           0 // 1\n            18386293581909 // 36657910125200       5506531089 // 80566835440       -178423463189 // 482340922700                   0 // 1                          0 // 1           0 // 1\n                  36036097 // 8299200                    4621 // 118560                -38434367 // 8299200                        1 // 4                          0 // 1           0 // 1\n          -247809665162987 // 146631640500800  10604946373579 // 14663164050080   10838126175385 // 5865265620032    -24966656214317 // 36657910125200             0 // 1           0 // 1\n                  38519701 // 11618880               10517363 // 9682400               -23284701 // 19364800               -10018609 // 2904720                    1 // 4           0 // 1\n           -52907807977903 // 33838070884800   74846944529257 // 73315820250400  365022522318171 // 146631640500800  -20513210406809 // 109973730375600  -2918009798 // 1870301537  0 // 1\n                        19 // 100                         -73 // 300                         127 // 300                          127 // 300                     -313 // 300         1 // 4\n         ]\n\n    Γ1 = [\n                         0 // 1                             0 // 1                               0 // 1                              0 // 1                          0 // 1           0 // 1\n                         0 // 1                             0 // 1                               0 // 1                              0 // 1                          0 // 1           0 // 1\n            -1674554930619 // 964681845400      1674554930619 // 964681845400                    0 // 1                              0 // 1                          0 // 1           0 // 1\n                  -1007739 // 172900                  1007739 // 172900                          0 // 1                              0 // 1                          0 // 1           0 // 1\n            -8450070574289 // 18328955062600     -39429409169 // 40283417720          173621393067 // 120585230675                   0 // 1                          0 // 1           0 // 1\n                -122894383 // 16598400                  14501 // 237120                  121879313 // 16598400                       0 // 1                          0 // 1           0 // 1\n            32410002731287 // 15434909526400  -46499276605921 // 29326328100160    -34914135774643 // 11730531240064    45128506783177 // 18328955062600             0 // 1           0 // 1\n                -128357303 // 23237760              -35433927 // 19364800                 71038479 // 38729600                 8015933 // 1452360                    0 // 1           0 // 1\n           136721604296777 // 67676141769600 -349632444539303 // 146631640500800 -1292744859249609 // 293263281001600    8356250416309 // 54986865187800   17282943803 // 3740603074  0 // 1\n                         3 // 25                          -29 // 300                            71 // 300                           71 // 300                     -149 // 300         0 // 1\n         ]\n\n    γ̂0 = [-1 // 4 5595 // 8804 -2445 // 8804 -4225 // 8804 2205 // 4402 -567 // 4402]\n    γ̂1 = [ 0 // 1    0 // 1        0 // 1        0 // 1       0 // 1       0 // 1   ]\n    #! format: on\n    MRIGARKDecoupledImplicit(\n        slowrhs!,\n        implicitsolve!,\n        fastsolver,\n        (Γ0, Γ1),\n        (γ̂0, γ̂1),\n        Q,\n        dt,\n        t0,\n    )\nend\n\n\"\"\"\n    MRIGARKESDIRK23LSA(f!, fastsolver, Q; dt, t0 = 0, δ = 0\n\nA 2nd order, 3 stage decoupled implicit scheme. It is based on L-Stable,\nstiffly-accurate ESDIRK scheme of Bank et al (1985); see also Kennedy and\nCarpenter (2016).\n\nThe free parameter `δ` can take any values for accuracy.\n\n### References\n - [Bank1985](@cite)\n - [KennedyCarpenter2016](@cite)\n\"\"\"\nfunction MRIGARKESDIRK23LSA(\n    slowrhs!,\n    implicitsolve!,\n    fastsolver,\n    Q;\n    dt,\n    t0 = 0,\n    δ = 0,\n)\n    T = real(eltype(Q))\n    rt2 = sqrt(T(2))\n\n    #! format: off\n    Γ0 = [\n          2 - rt2                      0                       0\n          (1 - rt2) / rt2              (rt2 - 1) / rt2         0\n          δ                            rt2 - 1 - δ             0\n          (3 - 2rt2 * (1 + δ)) / 2rt2  (δ * 2rt2 - 1) / 2rt2   (rt2 - 1) / rt2\n         ]\n\n    γ̂0 = [0 0 0]\n    #! format: on\n    Δc = sum(Γ0, dims = 2)\n\n    # Check that the explicit steps match the Δc values:\n    @assert Γ0[1, 1] ≈ Δc[1]\n    @assert Γ0[3, 1] + Γ0[3, 2] ≈ Δc[3]\n\n    # Check the implicit stages have no Δc\n    @assert isapprox(Γ0[2, 1] + Γ0[2, 2], 0, atol = eps(T))\n    @assert isapprox(Γ0[4, 1] + Γ0[4, 2] + Γ0[4, 3], 0, atol = eps(T))\n\n    # Check consistency with the original scheme\n    @assert Γ0[1, 1] + Γ0[2, 1] ≈ 1 - 1 / rt2\n    @assert Γ0[2, 2] ≈ 1 - 1 / rt2\n\n    @assert Γ0[1, 1] + Γ0[2, 1] + Γ0[3, 1] + Γ0[4, 1] ≈ 1 / (2 * rt2)\n    @assert Γ0[2, 2] + Γ0[3, 2] + Γ0[4, 2] ≈ 1 / (2 * rt2)\n    @assert Γ0[4, 3] ≈ 1 - 1 / rt2\n\n\n    MRIGARKDecoupledImplicit(\n        slowrhs!,\n        implicitsolve!,\n        fastsolver,\n        (Γ0,),\n        (γ̂0,),\n        Q,\n        dt,\n        t0,\n    )\nend\n\n\"\"\"\n    MRIGARKESDIRK24LSA(f!,\n                       fastsolver,\n                       Q;\n                       dt,\n                       t0 = 0,\n                       γ = 0.2,\n                       c3 = (2γ + 1) / 2,\n                       a32 = 0.2,\n                       α = -0.1,\n                       β1 = c3 / 10,\n                       β2 = c3 / 10,\n                       )\n\nA 2nd order, 4 stage decoupled implicit scheme. It is based on an L-Stable,\nstiffly-accurate ESDIRK.\n\"\"\"\nfunction MRIGARKESDIRK24LSA(\n    slowrhs!,\n    implicitsolve!,\n    fastsolver,\n    Q;\n    dt,\n    t0 = 0,\n    γ = 0.2,\n    c3 = (2γ + 1) / 2,\n    a32 = 0.2,\n    α = -0.1,\n    β1 = c3 / 10,\n    β2 = c3 / 10,\n)\n    T = real(eltype(Q))\n\n    # Check L-Stability constraint; bound comes from Kennedy and Carpenter\n    # (2016) Table 5.\n    @assert 0.1804253064293985641345831 ≤ γ < 1 // 2\n\n    # check the stage times are increasing\n    @assert 2γ < c3 < 1\n\n    # Original RK scheme\n    # Enforce L-Stability\n    b3 = (2 * (1 - γ)^2 - 1) / 4 / a32\n\n    # Enforce 2nd order accuracy\n    b2 = (1 - 2γ - 2b3 * c3) / 4γ\n\n    A = [\n        0 0 0 0\n        γ γ 0 0\n        c3 - a32-γ a32 γ 0\n        1 - b2 - b3-γ b2 b3 γ\n    ]\n    c = sum(A, dims = 2)\n    b = A[end, :]\n\n    # Check 2nd order accuracy\n    @assert sum(b) ≈ 1\n    @assert 2 * sum(A' * b) ≈ 1\n\n    # Setup the GARK Tableau\n    Δc = [c[2], 0, c[3] - c[2], 0, c[4] - c[3], 0]\n\n    Γ0 = zeros(T, 6, 4)\n    Γ0[1, 1] = Δc[1]\n\n    Γ0[2, 1] = A[2, 1] - Γ0[1, 1]\n    Γ0[2, 2] = A[2, 2]\n\n    Γ0[3, 1] = α\n    Γ0[3, 2] = Δc[3] - Γ0[3, 1]\n\n    Γ0[4, 1] = A[3, 1] - Γ0[1, 1] - Γ0[2, 1] - Γ0[3, 1]\n    Γ0[4, 2] = A[3, 2] - Γ0[1, 2] - Γ0[2, 2] - Γ0[3, 2]\n    Γ0[4, 3] = A[3, 3]\n\n    Γ0[5, 1] = β1\n    Γ0[5, 2] = β2\n    Γ0[5, 3] = Δc[5] - Γ0[5, 1] - Γ0[5, 2]\n\n    Γ0[6, 1] = A[4, 1] - Γ0[1, 1] - Γ0[2, 1] - Γ0[3, 1] - Γ0[4, 1] - Γ0[5, 1]\n    Γ0[6, 2] = A[4, 2] - Γ0[1, 2] - Γ0[2, 2] - Γ0[3, 2] - Γ0[4, 2] - Γ0[5, 2]\n    Γ0[6, 3] = A[4, 3] - Γ0[1, 3] - Γ0[2, 3] - Γ0[3, 3] - Γ0[4, 3] - Γ0[5, 3]\n    Γ0[6, 4] = A[4, 4]\n\n    γ̂0 = [0 0 0 0]\n\n    # Check consistency with original scheme\n    @assert all(A ≈ [0 0 0 0; accumulate(+, Γ0, dims = 1)[2:2:end, :]])\n    @assert all(Δc ≈ sum(Γ0, dims = 2))\n\n    MRIGARKDecoupledImplicit(\n        slowrhs!,\n        implicitsolve!,\n        fastsolver,\n        (Γ0,),\n        (γ̂0,),\n        Q,\n        dt,\n        t0,\n    )\nend\n"
  },
  {
    "path": "src/Numerics/ODESolvers/MultirateInfinitesimalGARKExplicit.jl",
    "content": "export MRIGARKExplicit\nexport MRIGARKERK33aSandu, MRIGARKERK45aSandu\n\n\"\"\"\n    MRIParam(p, γs, Rs, ts, Δts)\n\nConstruct a type for passing the data around for the `MRIGARKExplicit` explicit\ntime stepper to follow on methods. `p` is the original user defined ODE\nparameters, `γs` and `Rs` are the MRI parameters and stage values, respectively.\n`ts` and `Δts` are the stage time and stage time step.\n\"\"\"\nstruct MRIParam{P, T, AT, N, M}\n    p::P\n    γs::NTuple{M, SArray{NTuple{1, N}, T, 1, N}}\n    Rs::NTuple{N, AT}\n    ts::T\n    Δts::T\n    function MRIParam(\n        p::P,\n        γs::NTuple{M},\n        Rs::NTuple{N, AT},\n        ts,\n        Δts,\n    ) where {P, M, N, AT}\n        T = eltype(γs[1])\n        new{P, T, AT, N, M}(p, γs, Rs, ts, Δts)\n    end\nend\n\n# We overload get property to access the original param\nfunction Base.getproperty(mriparam::MRIParam, s::Symbol)\n    if s === :p\n        p = getfield(mriparam, :p)\n        return p isa MRIParam ? p.p : p\n    else\n        getfield(mriparam, s)\n    end\nend\n\n\"\"\"\n    MRIGARKExplicit(f!, fastsolver, Γs, γ̂s, Q, Δt, t0)\n\nConstruct an explicit MultiRate Infinitesimal General-structure Additive\nRunge--Kutta (MRI-GARK) scheme to solve\n\n```math\n    \\\\dot{y} = f_{slow}(y, t) + f_{fast}(y, t)\n```\n\nwhere `f_{slow}` is the slow tendency function and `f_{fast}` is the fast tendency function;\nsee Sandu (2019).\n\nThe fast tendency is integrated using the `fastsolver` and the slow tendency\nusing the MRI-GARK scheme. Namely, at each stage the scheme solves\n\n```math\n\\\\begin{aligned}\n               v(T_i) &= Y_i \\\\\\\\\n             \\\\dot{v} &= f(v, t) + \\\\sum_{j=1}^{i} \\\\bar{γ}_{ij}(t) R_j \\\\\\\\\n    \\\\bar{γ}_{ijk}(t) &= \\\\sum_{k=0}^{NΓ-1} γ_{ijk} τ(t)^k / Δc_s \\\\\\\\\n                 τ(t) &= (t - t_s) / Δt \\\\\\\\\n              Y_{i+1} &= v(T_i + c_s * Δt)\n\\\\end{aligned}\n```\n\nwhere ``Y_1 = y_n`` and ``y_{n+1} = Y_{Nstages+1}``.\n\nHere ``R_j = g(Y_j, t_0 + c_j * Δt)`` is the tendency for stage ``j``,\n``γ_{ijk}`` are the GARK coupling coefficients,\n``NΓ`` is the number of sets of GARK coupling coefficients there are\n``Δc_s = \\\\sum_{j=1}^{Nstages} γ_{sj1} = c_{s+1} - c_s`` is the scaling\nincrement between stage times. The ODE for ``v(t)`` is solved using the\n`fastsolver`.  Note that this form of the scheme is based on Definition 2.2 of\nSandu (2019), but ODE for ``v(t)`` is written to go from ``t_s`` to\n``T_i + c_s * Δt`` as opposed to ``0`` to ``1``.\n\nCurrently only [`LowStorageRungeKutta2N`](@ref) schemes are supported for\n`fastsolver`\n\nThe coefficients defined by `γ̂s` can be used for an embedded scheme (only the\nlast stage is different).\n\nThe available concrete implementations are:\n\n  - [`MRIGARKERK33aSandu`](@ref)\n  - [`MRIGARKERK45aSandu`](@ref)\n\n### References\n - [Sandu2019](@cite)\n\"\"\"\nmutable struct MRIGARKExplicit{T, RT, AT, Nstages, NΓ, FS, Nstages_sq} <:\n               AbstractODESolver\n    \"time step\"\n    dt::RT\n    \"time\"\n    t::RT\n    \"elapsed time steps\"\n    steps::Int\n    \"rhs function\"\n    slowrhs!::Any\n    \"Storage for RHS during the `MRIGARKExplicit` update\"\n    Rstages::NTuple{Nstages, AT}\n    \"RK coefficient matrices for coupling coefficients\"\n    Γs::NTuple{NΓ, SArray{NTuple{2, Nstages}, RT, 2, Nstages_sq}}\n    \"RK coefficient matrices for embedded scheme\"\n    γ̂s::NTuple{NΓ, SArray{NTuple{1, Nstages}, RT, 1, Nstages}}\n    \"RK coefficient vector C (time scaling)\"\n    Δc::SArray{NTuple{1, Nstages}, RT, 1, Nstages}\n    \"fast solver\"\n    fastsolver::FS\n\n    function MRIGARKExplicit(\n        slowrhs!,\n        fastsolver,\n        Γs,\n        γ̂s,\n        Q::AT,\n        dt,\n        t0,\n    ) where {AT <: AbstractArray}\n        NΓ = length(Γs)\n        Nstages = size(Γs[1], 1)\n        T = eltype(Q)\n        RT = real(T)\n\n        # Compute the Δc coefficients\n        Δc = sum(Γs[1], dims = 2)[:]\n\n        # Scale in the Δc to the Γ and γ̂, and convert to real type\n        Γs = ntuple(k -> RT.(Γs[k] ./ Δc), NΓ)\n        γ̂s = ntuple(k -> RT.(γ̂s[k] / Δc[Nstages]), NΓ)\n\n        # Convert to real type\n        Δc = RT.(Δc)\n\n        # create storage for the stage values\n        Rstages = ntuple(i -> similar(Q), Nstages)\n\n        FS = typeof(fastsolver)\n        new{T, RT, AT, Nstages, NΓ, FS, Nstages^2}(\n            RT(dt),\n            RT(t0),\n            0,\n            slowrhs!,\n            Rstages,\n            Γs,\n            γ̂s,\n            Δc,\n            fastsolver,\n        )\n    end\nend\n\nfunction dostep!(Q, mrigark::MRIGARKExplicit, param, time::Real)\n    dt = mrigark.dt\n    fast = mrigark.fastsolver\n\n    Rs = mrigark.Rstages\n    Δc = mrigark.Δc\n    Nstages = length(Δc)\n    slowrhs! = mrigark.slowrhs!\n    Γs = mrigark.Γs\n    NΓ = length(Γs)\n\n    ts = time\n    groupsize = 256\n    for s in 1:Nstages\n        # Stage dt\n        dts = Δc[s] * dt\n\n        p = param isa MRIParam ? param.p : param\n        slowrhs!(Rs[s], Q, p, ts, increment = false)\n        if param isa MRIParam\n            # fraction of the step slower stage increment we are on\n            τ = (ts - param.ts) / param.Δts\n            event = Event(array_device(Q))\n            event = mri_update_rate!(array_device(Q), groupsize)(\n                realview(Rs[s]),\n                τ,\n                param.γs,\n                param.Rs;\n                ndrange = length(realview(Rs[s])),\n                dependencies = (event,),\n            )\n            wait(array_device(Q), event)\n        end\n\n        γs = ntuple(k -> ntuple(j -> Γs[k][s, j], s), NΓ)\n        mriparam = MRIParam(param, γs, realview.(Rs[1:s]), ts, dts)\n        updatetime!(mrigark.fastsolver, ts)\n        solve!(Q, mrigark.fastsolver, mriparam; timeend = ts + dts)\n\n        # update time\n        ts += dts\n    end\nend\n\n@kernel function mri_update_rate!(dQ, τ, γs, Rs)\n    i = @index(Global, Linear)\n    @inbounds begin\n        NΓ = length(γs)\n        Ns = length(γs[1])\n        dqi = dQ[i]\n\n        for s in 1:Ns\n            ri = Rs[s][i]\n            sc = γs[NΓ][s]\n            for k in (NΓ - 1):-1:1\n                sc = sc * τ + γs[k][s]\n            end\n            dqi += sc * ri\n        end\n\n        dQ[i] = dqi\n    end\nend\n\n\"\"\"\n    MRIGARKERK33aSandu(f!, fastsolver, Q; dt, t0 = 0, δ = -1 // 2)\n\nThe 3rd order, 3 stage scheme from Sandu (2019). The parameter `δ` defaults to\nthe value suggested by Sandu, but can be varied.\n\"\"\"\nfunction MRIGARKERK33aSandu(slowrhs!, fastsolver, Q; dt, t0 = 0, δ = -1 // 2)\n    T = eltype(Q)\n    RT = real(T)\n    #! format: off\n    Γ0 = [\n                1 // 3           0 // 1          0 // 1\n        (-6δ - 7) // 12  (6δ + 11) // 12         0 // 1\n                0 // 1    (6δ - 5) // 12  (3 - 2δ) // 4\n    ]\n    γ̂0 = [     1 // 12          -1 // 3          7 // 12]\n\n    Γ1 = [\n               0 // 1          0 // 1   0 // 1\n        (2δ + 1) // 2  -(2δ + 1) // 2   0 // 1\n               1 // 2  -(2δ + 1) // 2   δ // 1\n    ]\n    γ̂1 = [     0 // 1          0 // 1   0 // 1]\n    #! format: on\n    MRIGARKExplicit(slowrhs!, fastsolver, (Γ0, Γ1), (γ̂0, γ̂1), Q, dt, t0)\nend\n\n\"\"\"\n    MRIGARKERK45aSandu(f!, fastsolver, Q; dt, t0 = 0)\n\nThe 4th order, 5 stage scheme from Sandu (2019).\n\"\"\"\nfunction MRIGARKERK45aSandu(slowrhs!, fastsolver, Q; dt, t0 = 0)\n    T = eltype(Q)\n    RT = real(T)\n    #! format: off\n    Γ0 = [\n                  1 // 5                 0 // 1                 0 // 1               0 // 1             0 // 1\n                -53 // 16              281 // 80                0 // 1               0 // 1             0 // 1\n         -36562993 // 71394880    34903117 // 17848720  -88770499 // 71394880        0 // 1             0 // 1\n          -7631593 // 71394880  -166232021 // 35697440    6068517 // 1519040   8644289 // 8924360       0 // 1\n            277061 // 303808       -209323 // 1139280    -1360217 // 1139280   -148789 // 56964    147889 // 45120\n    ]\n    γ̂0 = [-1482837 // 759520        175781 // 71205       -790577 // 1139280     -6379 // 56964        47 // 96]\n    Γ1 = [\n               0 // 1                0 // 1               0 // 1               0 // 1             0 // 1\n             503 // 80            -503 // 80              0 // 1               0 // 1             0 // 1\n        -1365537 // 35697440   4963773 // 7139488  -1465833 // 2231090         0 // 1             0 // 1\n        66974357 // 35697440  21445367 // 7139488        -3 // 1        -8388609 // 4462180       0 // 1\n          -18227 // 7520             2 // 1               1 // 1               5 // 1        -41933 // 7520\n    ]\n    γ̂1 = [  6213 // 1880         -6213 // 1880            0 // 1               0 // 1             0 // 1]\n    #! format: on\n    MRIGARKExplicit(slowrhs!, fastsolver, (Γ0, Γ1), (γ̂0, γ̂1), Q, dt, t0)\nend\n"
  },
  {
    "path": "src/Numerics/ODESolvers/MultirateInfinitesimalStepMethod.jl",
    "content": "\nexport MultirateInfinitesimalStep,\n    TimeScaledRHS,\n    MISRK1,\n    MIS2,\n    MISRK2a,\n    MISRK2b,\n    MIS3C,\n    MISRK3,\n    MIS4,\n    MIS4a,\n    MISKWRK43,\n    TVDMISA,\n    TVDMISB,\n    getnsubsteps\n\n\"\"\"\n    TimeScaledRHS(a, b, rhs!)\n\nWhen evaluate at time `t`, evaluates `rhs!` at time `a + bt`.\n\"\"\"\nmutable struct TimeScaledRHS{N, RT}\n    a::RT\n    b::RT\n    rhs!::Any\n    function TimeScaledRHS(a, b, rhs!)\n        RT = typeof(a)\n        if isa(rhs!, Tuple)\n            N = length(rhs!)\n        else\n            N = 1\n        end\n        new{N, RT}(a, b, rhs!)\n    end\nend\n\nfunction (o::TimeScaledRHS{1, RT} where {RT})(dQ, Q, params, tau; increment)\n    o.rhs!(dQ, Q, params, o.a + o.b * tau; increment = increment)\nend\n\nfunction (o::TimeScaledRHS{2, RT} where {RT})(dQ, Q, params, tau, i; increment)\n    o.rhs![i](dQ, Q, params, o.a + o.b * tau; increment = increment)\nend\n\nmutable struct OffsetRHS{AT}\n    offset::AT\n    rhs!::Any\n    function OffsetRHS(offset, rhs!)\n        AT = typeof(offset)\n        new{AT}(offset, rhs!)\n    end\nend\n\nfunction (o::OffsetRHS{AT} where {AT})(dQ, Q, params, tau; increment)\n    o.rhs!(dQ, Q, params, tau; increment = increment)\n    dQ .+= o.offset\nend\n\n\"\"\"\n    MultirateInfinitesimalStep(slowrhs!, fastrhs!, fastmethod,\n                               α, β, γ,\n                               Q::AT; dt=0, t0=0) where {AT<:AbstractArray}\n\nThis is a time stepping object for explicitly time stepping the partitioned differential\nequation given by right-hand-side functions `f_fast` and `f_slow` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q} = f_{fast}(Q, t) + f_{slow}(Q, t)\n```\n\nwith the required time step size `dt` and optional initial time `t0`.  This\ntime stepping object is intended to be passed to the `solve!` command.\n\nThe constructor builds a multirate infinitesimal step Runge-Kutta scheme\nbased on the provided `α`, `β` and `γ` tableaux and `fastmethod` for solving\nthe fast modes.\n\nThe available concrete implementations are:\n\n  - [`MISRK1`](@ref)\n  - [`MIS2`](@ref)\n  - [`MISRK2a`](@ref)\n  - [`MISRK2b`](@ref)\n  - [`MIS3C`](@ref)\n  - [`MISRK3`](@ref)\n  - [`MIS4`](@ref)\n  - [`MIS4a`](@ref)\n  - [`MISKWRK43`](@ref)\n  - [`TVDMISA`](@ref)\n  - [`TVDMISB`](@ref)\n\n### References\n - [KnothWensch2014](@cite)\n - [WickerSkamarock2002](@cite)\n - [KnothWolke1998](@cite)\n\"\"\"\nmutable struct MultirateInfinitesimalStep{\n    T,\n    RT,\n    AT,\n    FS,\n    Nstages,\n    Nstagesm1,\n    Nstagesm2,\n    Nstages_sq,\n} <: AbstractODESolver\n    \"time step\"\n    dt::RT\n    \"time\"\n    t::RT\n    \"elapsed time steps\"\n    steps::Int\n    \"storage for y_n\"\n    yn::AT\n    \"Storage for ``Y_nj - y_n``\"\n    ΔYnj::NTuple{Nstagesm2, AT}\n    \"Storage for ``f(Y_nj)``\"\n    fYnj::NTuple{Nstagesm1, AT}\n    \"Storage for offset\"\n    offset::AT\n    \"slow rhs function\"\n    slowrhs!::Any\n    \"RHS for fast solver\"\n    tsfastrhs!::TimeScaledRHS{N, RT} where {N}\n    \"fast rhs method\"\n    fastsolver::FS\n    \"number of substeps per stage\"\n    nsubsteps::Int\n    α::SArray{NTuple{2, Nstages}, RT, 2, Nstages_sq}\n    β::SArray{NTuple{2, Nstages}, RT, 2, Nstages_sq}\n    γ::SArray{NTuple{2, Nstages}, RT, 2, Nstages_sq}\n    d::SArray{NTuple{1, Nstages}, RT, 1, Nstages}\n    c::SArray{NTuple{1, Nstages}, RT, 1, Nstages}\n    c̃::SArray{NTuple{1, Nstages}, RT, 1, Nstages}\n    function MultirateInfinitesimalStep(\n        slowrhs!,\n        fastrhs!,\n        fastmethod,\n        nsubsteps,\n        α,\n        β,\n        γ,\n        Q::AT;\n        dt = 0,\n        t0 = 0,\n    ) where {AT <: AbstractArray}\n\n        T = eltype(Q)\n        RT = real(T)\n\n        Nstages = size(α, 1)\n\n        yn = similar(Q)\n        ΔYnj = ntuple(_ -> similar(Q), Nstages - 2)\n        fYnj = ntuple(_ -> similar(Q), Nstages - 1)\n        offset = similar(Q)\n        tsfastrhs! = TimeScaledRHS(RT(0), RT(0), fastrhs!)\n        fastsolver = fastmethod(tsfastrhs!, Q)\n\n        d = sum(β, dims = 2)\n\n        c = similar(d)\n        for i in eachindex(c)\n            c[i] = d[i]\n            if i > 1\n                c[i] += sum(j -> (α[i, j] + γ[i, j]) * c[j], 1:(i - 1))\n            end\n\n            # When d[i] = 0, we do not perform fast substepping, therefore\n            # we do not need to scale the β, γ coefficients\n            if !(abs(d[i]) < 1.e-10)\n                β[i, :] ./= d[i]\n                γ[i, :] ./= d[i]\n            end\n        end\n        c̃ = α * c\n\n        new{\n            T,\n            RT,\n            AT,\n            typeof(fastsolver),\n            Nstages,\n            Nstages - 1,\n            Nstages - 2,\n            Nstages^2,\n        }(\n            RT(dt),\n            RT(t0),\n            0,\n            yn,\n            ΔYnj,\n            fYnj,\n            offset,\n            slowrhs!,\n            tsfastrhs!,\n            fastsolver,\n            nsubsteps,\n            α,\n            β,\n            γ,\n            d,\n            c,\n            c̃,\n        )\n    end\nend\n\nfunction MultirateInfinitesimalStep(\n    mis,\n    op::TimeScaledRHS{2, RT} where {RT},\n    fastmethod,\n    Q = nothing;\n    dt = 0,\n    t0 = 0,\n    nsubsteps = 1,\n) where {AT <: AbstractArray}\n\n    return mis(\n        op.rhs![1],\n        op.rhs![2],\n        fastmethod,\n        nsubsteps,\n        Q;\n        dt = dt,\n        t0 = t0,\n    )\nend\n\nfunction dostep!(\n    Q,\n    mis::MultirateInfinitesimalStep,\n    p,\n    time::Real,\n    nsubsteps::Int,\n    iStage::Int,\n    slow_δ = nothing,\n    slow_rv_dQ = nothing,\n    slow_scaling = nothing,\n)\n    if isa(mis.slowrhs!, OffsetRHS{AT} where {AT})\n        mis.slowrhs!.offset = slow_rv_dQ\n    else\n        mis.slowrhs! = OffsetRHS(slow_rv_dQ, mis.slowrhs!)\n    end\n    for i in 1:nsubsteps\n        dostep!(Q, mis, p, time)\n        time += mis.fastsolver.dt\n    end\nend\n\nfunction dostep!(Q, mis::MultirateInfinitesimalStep, p, time)\n    dt = mis.dt\n    FT = eltype(dt)\n    α = mis.α\n    β = mis.β\n    γ = mis.γ\n    yn = mis.yn\n    ΔYnj = mis.ΔYnj\n    fYnj = mis.fYnj\n    offset = mis.offset\n    d = mis.d\n    c = mis.c\n    c̃ = mis.c̃\n    slowrhs! = mis.slowrhs!\n    fastsolver = mis.fastsolver\n    fastrhs! = mis.tsfastrhs!\n    nsubsteps = mis.nsubsteps\n\n    nstages = size(α, 1)\n\n    copyto!(yn, Q) # first stage\n    for i in 2:nstages\n        slowrhs!(fYnj[i - 1], Q, p, time + c[i - 1] * dt, increment = false)\n\n        groupsize = 256\n        event = Event(array_device(Q))\n        event = update!(array_device(Q), groupsize)(\n            realview(Q),\n            realview(offset),\n            Val(i),\n            realview(yn),\n            map(realview, ΔYnj[1:(i - 2)]),\n            map(realview, fYnj[1:(i - 1)]),\n            α[i, :],\n            β[i, :],\n            γ[i, :],\n            dt;\n            ndrange = length(realview(Q)),\n            dependencies = (event,),\n        )\n        wait(array_device(Q), event)\n\n        # When d[i] = 0, we do not perform fast substepping;\n        # instead we just update the slow tendency\n        if iszero(d[i])\n            Q .+= dt .* offset\n        else\n            fastrhs!.a = time + c̃[i] * dt\n            fastrhs!.b = (c[i] - c̃[i]) / d[i]\n\n            τ = zero(FT)\n            nsubstepsLoc = ceil(Int, nsubsteps * d[i])\n            dτ = d[i] * dt / nsubstepsLoc\n            updatetime!(fastsolver, τ)\n            updatedt!(fastsolver, dτ)\n            # TODO: we want to be able to write\n            #   solve!(Q, fastsolver, p; numberofsteps = mis.nsubsteps)  #(1c)\n            # especially if we want to use StormerVerlet, but need some way to pass in `offset`\n            dostep!(\n                Q,\n                fastsolver,\n                p,\n                τ,\n                nsubstepsLoc,\n                i,\n                FT(1),\n                realview(offset),\n                nothing,\n            )  #(1c)\n        end\n    end\nend\n\n@kernel function update!(\n    Q,\n    offset,\n    ::Val{i},\n    yn,\n    ΔYnj,\n    fYnj,\n    αi,\n    βi,\n    γi,\n    dt,\n) where {i}\n    e = @index(Global, Linear)\n    @inbounds begin\n        if i > 2\n            ΔYnj[i - 2][e] = Q[e] - yn[e] # is 0 for i == 2\n        end\n        Q[e] = yn[e] # (1a)\n        offset[e] = (βi[1]) .* fYnj[1][e] # (1b)\n        @unroll for j in 2:(i - 1)\n            Q[e] += αi[j] .* ΔYnj[j - 1][e] # (1a cont.)\n            offset[e] += (γi[j] / dt) * ΔYnj[j - 1][e] + βi[j] * fYnj[j][e] # (1b cont.)\n        end\n    end\nend\n\n\"\"\"\n    MISRK1(slowrhs!, fastrhs!, fastmethod, nsubsteps, Q; dt = 0, t0 = 0)\n\nThe `MISRK1` method is a 1st-order accurate MIS method based\non the RK1 (explicit Euler) method.\n\n### References\n - [KnothWensch2014](@cite)\n\"\"\"\nfunction MISRK1(\n    slowrhs!,\n    fastrhs!,\n    fastmethod,\n    nsubsteps,\n    Q::AT;\n    dt = 0,\n    t0 = 0,\n) where {AT <: AbstractArray}\n    FT = eltype(Q)\n    RT = real(FT)\n    α = zeros(2, 2)\n    β = beta(MISRK1, RT)\n    γ = zeros(2, 2)\n    MultirateInfinitesimalStep(\n        slowrhs!,\n        fastrhs!,\n        fastmethod,\n        nsubsteps,\n        α,\n        β,\n        γ,\n        Q;\n        dt = dt,\n        t0 = t0,\n    )\nend\n\nfunction beta(::typeof(MISRK1), RT::DataType)\n    β = [\n        0 0\n        1 0\n    ]\nend\n\n\"\"\"\n    MIS2(slowrhs!, fastrhs!, fastmethod, nsubsteps, Q; dt = 0, t0 = 0)\n\nThe `MIS2` method is a 2nd-order accurate, 3-stage MIS method whose\nconstruction is summarized in Table 1 of [KnothWensch2014](@cite).\n\n### References\n - [KnothWensch2014](@cite)\n\"\"\"\nfunction MIS2(\n    slowrhs!,\n    fastrhs!,\n    fastmethod,\n    nsubsteps,\n    Q::AT;\n    dt = 0,\n    t0 = 0,\n) where {AT <: AbstractArray}\n    FT = eltype(Q)\n    RT = real(FT)\n\n    α = [\n        0 0 0 0\n        0 0 0 0\n        0 RT(0.536946566710) 0 0\n        0 RT(0.480892968551) RT(0.500561163566) 0\n    ]\n\n    β = beta(MIS2, RT)\n\n    γ = [\n        0 0 0 0\n        0 0 0 0\n        0 RT(0.652465126004) 0 0\n        0 RT(-0.0732769849457) RT(0.144902430420) 0\n    ]\n\n    MultirateInfinitesimalStep(\n        slowrhs!,\n        fastrhs!,\n        fastmethod,\n        nsubsteps,\n        α,\n        β,\n        γ,\n        Q;\n        dt = dt,\n        t0 = t0,\n    )\nend\n\nfunction beta(::typeof(MIS2), RT::DataType)\n    β = [\n        0 0 0 0\n        RT(0.126848494553) 0 0 0\n        RT(-0.784838278826) RT(1.37442675268) 0 0\n        RT(-0.0456727081749) RT(-0.00875082271190) RT(0.524775788629) 0\n    ]\nend\n\n\"\"\"\n    MISRK2a(slowrhs!, fastrhs!, fastmethod, nsubsteps, Q; dt = 0, t0 = 0)\n\nThe `MISRK2a` method is a 2nd-order accurate, 2-stage MIS method\nbased on the approach detailed by Wicker and Skamarock in\n[WickerSkamarock2002](@cite).\n\n### References\n - [WickerSkamarock2002](@cite)\n\"\"\"\nfunction MISRK2a(\n    slowrhs!,\n    fastrhs!,\n    fastmethod,\n    nsubsteps,\n    Q::AT;\n    dt = 0,\n    t0 = 0,\n) where {AT <: AbstractArray}\n    FT = eltype(Q)\n    RT = real(FT)\n\n    α = [\n        0 0 0\n        0 0 0\n        0 1 0\n    ]\n\n    β = beta(MISRK2a, RT)\n\n    γ = zeros(3, 3)\n\n    MultirateInfinitesimalStep(\n        slowrhs!,\n        fastrhs!,\n        fastmethod,\n        nsubsteps,\n        α,\n        β,\n        γ,\n        Q;\n        dt = dt,\n        t0 = t0,\n    )\nend\n\nfunction beta(::typeof(MISRK2a), RT::DataType)\n    β = [\n        0 0 0\n        RT(0.5) 0 0\n        RT(-0.5) 1 0\n    ]\nend\n\n\"\"\"\n    MISRK2b(slowrhs!, fastrhs!, fastmethod, nsubsteps, Q; dt = 0, t0 = 0)\n\nThe `MISRK2b` method is a 2nd-order accurate, 2-stage MIS method and a\nvariant of the `MISRK2a` method based on the approach detailed by Wicker\nand Skamarock in [WickerSkamarock2002](@cite).\n\n### References\n - [WickerSkamarock2002](@cite)\n\"\"\"\nfunction MISRK2b(\n    slowrhs!,\n    fastrhs!,\n    fastmethod,\n    nsubsteps,\n    Q::AT;\n    dt = 0,\n    t0 = 0,\n) where {AT <: AbstractArray}\n    FT = eltype(Q)\n    RT = real(FT)\n\n    α = [\n        0 0 0\n        0 0 0\n        0 1 0\n    ]\n\n    β = beta(MISRK2b, RT)\n\n    γ = zeros(3, 3)\n\n    MultirateInfinitesimalStep(\n        slowrhs!,\n        fastrhs!,\n        fastmethod,\n        nsubsteps,\n        α,\n        β,\n        γ,\n        Q;\n        dt = dt,\n        t0 = t0,\n    )\nend\n\nfunction beta(::typeof(MISRK2b), RT::DataType)\n    β = [\n        0 0 0\n        1 0 0\n        RT(-0.5) RT(0.5) 0\n    ]\nend\n\n\"\"\"\n    MIS3C(slowrhs!, fastrhs!, fastmethod, nsubsteps, Q; dt = 0, t0 = 0)\n\nThe `MIS3C` method is a 3rd-order accurate, 3-stage MIS method whose\nconstruction is summarized in Table 2 of [KnothWensch2014](@cite).\n\n### References\n - [KnothWensch2014](@cite)\n\"\"\"\nfunction MIS3C(\n    slowrhs!,\n    fastrhs!,\n    fastmethod,\n    nsubsteps,\n    Q::AT;\n    dt = 0,\n    t0 = 0,\n) where {AT <: AbstractArray}\n    FT = eltype(Q)\n    RT = real(FT)\n\n    α = [\n        0 0 0 0\n        0 0 0 0\n        0 RT(0.589557277145) 0 0\n        0 RT(0.544036601551) RT(0.565511042564) 0\n    ]\n\n    β = beta(MIS3C, RT)\n\n    γ = [\n        0 0 0 0\n        0 0 0 0\n        0 RT(0.142798786398) 0 0\n        0 RT(-0.0428918957402) RT(0.0202720980282) 0\n    ]\n\n    MultirateInfinitesimalStep(\n        slowrhs!,\n        fastrhs!,\n        fastmethod,\n        nsubsteps,\n        α,\n        β,\n        γ,\n        Q;\n        dt = dt,\n        t0 = t0,\n    )\nend\n\nfunction beta(::typeof(MIS3C), RT::DataType)\n    β = [\n        0 0 0 0\n        RT(0.397525189225) 0 0 0\n        RT(-0.227036463644) RT(0.624528794618) 0 0\n        RT(-0.00295238076840) RT(-0.270971764284) RT(0.671323159437) 0\n    ]\nend\n\n\"\"\"\n    MISRK3(slowrhs!, fastrhs!, fastmethod, nsubsteps, Q; dt = 0, t0 = 0)\n\nThe `MISRK3` method is a 3rd-order accurate, 3-stage MIS method\nbased on the approach detailed by Wicker and Skamarock in\n[WickerSkamarock2002](@cite).\n\n### References\n - [WickerSkamarock2002](@cite)\n\"\"\"\nfunction MISRK3(\n    slowrhs!,\n    fastrhs!,\n    fastmethod,\n    nsubsteps,\n    Q::AT;\n    dt = 0,\n    t0 = 0,\n) where {AT <: AbstractArray}\n    FT = eltype(Q)\n    RT = real(FT)\n    α = zeros(4, 4)\n    β = beta(MISRK3, RT)\n    γ = zeros(4, 4)\n    MultirateInfinitesimalStep(\n        slowrhs!,\n        fastrhs!,\n        fastmethod,\n        nsubsteps,\n        α,\n        β,\n        γ,\n        Q;\n        dt = dt,\n        t0 = t0,\n    )\nend\n\nfunction beta(::typeof(MISRK3), RT::DataType)\n    β = [\n        0 0 0 0\n        RT(0.3333333333333333) 0 0 0\n        0 RT(0.5) 0 0\n        0 0 1 0\n    ]\nend\n\n\"\"\"\n    MIS4(slowrhs!, fastrhs!, fastmethod, nsubsteps, Q; dt = 0, t0 = 0)\n\nThe `MIS4` method is a 3rd-order accurate, 4-stage MIS method whose\nconstruction is summarized in Table 3 of [KnothWensch2014](@cite).\n\n### References\n - [KnothWensch2014](@cite)\n\"\"\"\nfunction MIS4(\n    slowrhs!,\n    fastrhs!,\n    fastmethod,\n    nsubsteps,\n    Q::AT;\n    dt = 0,\n    t0 = 0,\n) where {AT <: AbstractArray}\n    FT = eltype(Q)\n    RT = real(FT)\n\n    α = [\n        0 0 0 0 0\n        0 0 0 0 0\n        0 RT(0.914092810304) 0 0 0\n        0 RT(1.14274417397) RT(-0.295211246188) 0 0\n        0 RT(0.112965282231) RT(0.337369411296) RT(0.503747183119) 0\n    ]\n\n    β = beta(MIS4, RT)\n\n    γ = [\n        0 0 0 0 0\n        0 0 0 0 0\n        0 RT(0.678951983291) 0 0 0\n        0 RT(-1.38974164070) RT(0.503864576302) 0 0\n        0 RT(-0.375328608282) RT(0.320925021109) RT(-0.158259688945) 0\n    ]\n\n    MultirateInfinitesimalStep(\n        slowrhs!,\n        fastrhs!,\n        fastmethod,\n        nsubsteps,\n        α,\n        β,\n        γ,\n        Q;\n        dt = dt,\n        t0 = t0,\n    )\nend\n\nfunction beta(::typeof(MIS4), RT::DataType)\n    β = [\n        0 0 0 0 0\n        RT(0.136296478423) 0 0 0 0\n        RT(0.280462398979) RT(-0.0160351333596) 0 0 0\n        RT(0.904713355208) RT(-1.04011183154) RT(0.652337563489) 0 0\n        RT(0.0671969845546) RT(-0.365621862610) RT(-0.154861470835) RT(0.970362444469) 0\n    ]\nend\n\n\"\"\"\n    MIS4a(slowrhs!, fastrhs!, fastmethod, nsubsteps, Q; dt = 0, t0 = 0)\n\nThe `MIS4a` method is a 3rd-order accurate, 4-stage MIS method whose\nconstruction is summarized in Table 4 of [KnothWensch2014](@cite).\n\n### References\n - [KnothWensch2014](@cite)\n\"\"\"\nfunction MIS4a(\n    slowrhs!,\n    fastrhs!,\n    fastmethod,\n    nsubsteps,\n    Q::AT;\n    dt = 0,\n    t0 = 0,\n) where {AT <: AbstractArray}\n    FT = eltype(Q)\n    RT = real(FT)\n\n    α = [\n        0 0 0 0 0\n        0 0 0 0 0\n        0 RT(0.52349249922385610) 0 0 0\n        0 RT(1.1683374366893629) RT(-0.75762080241712637) 0 0\n        0 RT(-0.036477233846797109) RT(0.56936148730740477) RT(0.47746263002599681) 0\n    ]\n\n    # β[5,1] in the paper is incorrect\n    # the correct value is used below (from authors)\n    β = beta(MIS4a, RT)\n\n    γ = [\n        0 0 0 0 0\n        0 0 0 0 0\n        0 RT(0.13145089796226542) 0 0 0\n        0 RT(-0.36855857648747881) RT(0.33159232636600550) 0 0\n        0 RT(-0.065767130537473045) RT(0.040591093109036858) RT(0.064902111640806712) 0\n    ]\n\n    MultirateInfinitesimalStep(\n        slowrhs!,\n        fastrhs!,\n        fastmethod,\n        nsubsteps,\n        α,\n        β,\n        γ,\n        Q;\n        dt = dt,\n        t0 = t0,\n    )\nend\n\nfunction beta(::typeof(MIS4a), RT::DataType)\n    β = [\n        0 0 0 0 0\n        RT(0.38758444641450318) 0 0 0 0\n        RT(-0.025318448354142823) RT(0.38668943087310403) 0 0 0\n        RT(0.20899983523553325) RT(-0.45856648476371231) RT(0.43423187573425748) 0 0\n        RT(-0.10048822195663100) RT(-0.46186171956333327) RT(0.83045062122462809) RT(0.27014914900250392) 0\n    ]\nend\n\n\"\"\"\n    MISKWRK43(slowrhs!, fastrhs!, fastmethod, nsubsteps, Q; dt = 0, t0 = 0)\n\nThe `MISKWRK43` method is a 3rd-order accurate, 4-stage MIS method. It is the\nMIS analog of an RK43 method, based on the approach detailed in\n[KnothWolke1998](@cite).\n\n### References\n - [KnothWolke1998](@cite)\n\"\"\"\nfunction MISKWRK43(\n    slowrhs!,\n    fastrhs!,\n    fastmethod,\n    nsubsteps,\n    Q::AT;\n    dt = 0,\n    t0 = 0,\n) where {AT <: AbstractArray}\n    FT = eltype(Q)\n    RT = real(FT)\n\n    α = [\n        0 0 0 0 0\n        0 0 0 0 0\n        0 1 0 0 0\n        0 0 1 0 0\n        0 0 0 1 0\n    ]\n\n    β = beta(MISKWRK43, RT)\n\n    γ = zeros(5, 5)\n\n    MultirateInfinitesimalStep(\n        slowrhs!,\n        fastrhs!,\n        fastmethod,\n        nsubsteps,\n        α,\n        β,\n        γ,\n        Q;\n        dt = dt,\n        t0 = t0,\n    )\nend\n\nfunction beta(::typeof(MISKWRK43), RT::DataType)\n    β = [\n        0 0 0 0 0\n        0.5 0 0 0 0\n        -RT(2 // 3) RT(2 // 3) 0 0 0\n        RT(0.5) -1 1 0 0\n        -RT(1 // 6) RT(2 // 3) -RT(2 // 3) RT(1 // 6) 0\n    ]\nend\n\n\"\"\"\n    TVDMISA(slowrhs!, fastrhs!, fastmethod, nsubsteps, Q; dt = 0, t0 = 0)\n\nThe `TVDMISA` method is a 3rd-order accurate, 3-stage MIS method whose\nconstruction is summarized in Table 6 of [KnothWensch2014](@cite).\n\n### References\n - [KnothWensch2014](@cite)\n\"\"\"\nfunction TVDMISA(\n    slowrhs!,\n    fastrhs!,\n    fastmethod,\n    nsubsteps,\n    Q::AT;\n    dt = 0,\n    t0 = 0,\n) where {AT <: AbstractArray}\n    FT = eltype(Q)\n    RT = real(FT)\n\n    α = [\n        0 0 0 0\n        0 0 0 0\n        0 RT(0.1946360605647457) 0 0\n        0 RT(0.3971200136786614) RT(0.2609434606211801) 0\n    ]\n\n    β = beta(TVDMISA, RT)\n\n    γ = [\n        0 0 0 0\n        0 0 0 0\n        0 RT(0.5624048933209129) 0 0\n        0 RT(0.4408467475713277) RT(-0.2459300561692391) 0\n    ]\n\n    MultirateInfinitesimalStep(\n        slowrhs!,\n        fastrhs!,\n        fastmethod,\n        nsubsteps,\n        α,\n        β,\n        γ,\n        Q;\n        dt = dt,\n        t0 = t0,\n    )\nend\n\nfunction beta(::typeof(TVDMISA), RT::DataType)\n    β = [\n        0 0 0 0\n        RT(2 // 3) 0 0 0\n        RT(-0.28247174703488398) RT(4 // 9) 0 0\n        RT(-0.31198081960042401) RT(0.18082737579913699) RT(9 // 16) 0\n    ]\nend\n\n\"\"\"\n    TVDMISB(slowrhs!, fastrhs!, fastmethod, nsubsteps, Q; dt = 0, t0 = 0)\n\nThe `TVDMISB` method is a 3rd-order accurate, 3-stage MIS method whose\nconstruction is summarized in Table 7 of [KnothWensch2014](@cite).\n\n### References\n - [KnothWensch2014](@cite)\n\"\"\"\nfunction TVDMISB(\n    slowrhs!,\n    fastrhs!,\n    fastmethod,\n    nsubsteps,\n    Q::AT;\n    dt = 0,\n    t0 = 0,\n) where {AT <: AbstractArray}\n    FT = eltype(Q)\n    RT = real(FT)\n\n    α = [\n        0 0 0 0\n        0 0 0 0\n        0 RT(0.42668232863311001) 0 0\n        0 RT(0.26570779016173801) RT(0.41489966891866698) 0\n    ]\n\n    β = beta(TVDMISB, RT)\n\n    γ = [\n        0 0 0 0\n        0 0 0 0\n        0 RT(0.28904389120139701) 0 0\n        0 RT(0.45113560071334202) RT(-0.25006656847591002) 0\n    ]\n\n    MultirateInfinitesimalStep(\n        slowrhs!,\n        fastrhs!,\n        fastmethod,\n        nsubsteps,\n        α,\n        β,\n        γ,\n        Q;\n        dt = dt,\n        t0 = t0,\n    )\nend\n\nfunction beta(::typeof(TVDMISB), RT::DataType)\n    β = [\n        0 0 0 0\n        RT(2 // 3) 0 0 0\n        RT(-0.25492859100078202) RT(4 // 9) 0 0\n        RT(-0.26452517179288798) RT(0.11424084424766399) RT(9 // 16) 0\n    ]\nend\n\nfunction getnsubsteps(mis, ns::Int, RT::DataType)\n    d = sum(beta(mis, RT), dims = 2)\n    return d ./ ns\nend\n"
  },
  {
    "path": "src/Numerics/ODESolvers/MultirateRungeKuttaMethod.jl",
    "content": "\nexport MultirateRungeKutta\n\nLSRK2N = LowStorageRungeKutta2N\n\n\"\"\"\n    MultirateRungeKutta(slow_solver, fast_solver; dt, t0 = 0)\n\nThis is a time stepping object for explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q} = f_fast(Q, t) + f_slow(Q, t)\n```\n\nwith the required time step size `dt` and optional initial time `t0`.  This\ntime stepping object is intended to be passed to the `solve!` command.\n\nThe constructor builds a multirate Runge-Kutta scheme using two different RK\nsolvers. This is based on\n\nCurrently only the low storage RK methods can be used as slow solvers\n\n### References\n - [Schlegel2012](@cite)\n\"\"\"\nmutable struct MultirateRungeKutta{SS, FS, RT} <: AbstractODESolver\n    \"slow solver\"\n    slow_solver::SS\n    \"fast solver\"\n    fast_solver::FS\n    \"time step\"\n    dt::RT\n    \"time\"\n    t::RT\n    \"elapsed time steps\"\n    steps::Int\n\n    function MultirateRungeKutta(\n        slow_solver::LSRK2N,\n        fast_solver,\n        Q = nothing;\n        dt = getdt(slow_solver),\n        t0 = slow_solver.t,\n    ) where {AT <: AbstractArray}\n        SS = typeof(slow_solver)\n        FS = typeof(fast_solver)\n        RT = real(eltype(slow_solver.dQ))\n        new{SS, FS, RT}(slow_solver, fast_solver, RT(dt), RT(t0), 0)\n    end\nend\n\nfunction MultirateRungeKutta(\n    solvers::Tuple,\n    Q = nothing;\n    dt = getdt(solvers[1]),\n    t0 = solvers[1].t,\n) where {AT <: AbstractArray}\n    if length(solvers) < 2\n        error(\"Must specify atleast two solvers\")\n    elseif length(solvers) == 2\n        fast_solver = solvers[2]\n    else\n        fast_solver = MultirateRungeKutta(solvers[2:end], Q; dt = dt, t0 = t0)\n    end\n\n    slow_solver = solvers[1]\n\n    MultirateRungeKutta(slow_solver, fast_solver, Q; dt = dt, t0 = t0)\nend\n\nfunction MultirateRungeKutta(\n    mrk,\n    op::TimeScaledRHS{2, RT} where {RT},\n    Q = nothing;\n    dt = 0,\n    t0 = 0,\n    steps = 0,\n) where {AT <: AbstractArray}\n\n    slow_solver = mrk(op.rhs![1], Q, dt = dt, t0 = t0)\n    fast_solver = mrk(op.rhs![2], Q, dt = dt, t0 = t0)\n\n    MultirateRungeKutta(slow_solver, fast_solver, Q; dt = dt, t0 = t0)\nend\n\nfunction dostep!(\n    Q,\n    mrrk::MultirateRungeKutta{SS},\n    param,\n    time::Real,\n    nsteps::Int,\n    iStage::Int,\n    slow_δ = nothing,\n    slow_rv_dQ = nothing,\n    slow_scaling = nothing,\n) where {SS <: LSRK2N}\n    for i in 1:nsteps\n        dostep!(Q, mrrk, param, time, slow_δ, slow_rv_dQ, slow_scaling)\n        time += mrrk.fast_solver.dt\n    end\nend\n\nfunction dostep!(\n    Q,\n    mrrk::MultirateRungeKutta{SS},\n    param,\n    time,\n    in_slow_δ = nothing,\n    in_slow_rv_dQ = nothing,\n    in_slow_scaling = nothing,\n) where {SS <: LSRK2N}\n    dt = mrrk.dt\n\n    slow = mrrk.slow_solver\n    fast = mrrk.fast_solver\n\n    slow_rv_dQ = realview(slow.dQ)\n\n    groupsize = 256\n\n    fast_dt_in = getdt(fast)\n\n    for slow_s in 1:length(slow.RKA)\n        # Currnent slow state time\n        slow_stage_time = time + slow.RKC[slow_s] * dt\n\n        # Evaluate the slow mode\n        slow.rhs!(slow.dQ, Q, param, slow_stage_time, increment = true)\n\n        if in_slow_δ !== nothing\n            slow_scaling = nothing\n            if slow_s == length(slow.RKA)\n                slow_scaling = in_slow_scaling\n            end\n            # update solution and scale RHS\n            event = Event(array_device(Q))\n            event = update!(array_device(Q), groupsize)(\n                slow_rv_dQ,\n                in_slow_rv_dQ,\n                in_slow_δ,\n                slow_scaling;\n                ndrange = length(realview(Q)),\n                dependencies = (event,),\n            )\n            wait(array_device(Q), event)\n        end\n\n        # Fractional time for slow stage\n        if slow_s == length(slow.RKA)\n            γ = 1 - slow.RKC[slow_s]\n        else\n            γ = slow.RKC[slow_s + 1] - slow.RKC[slow_s]\n        end\n\n        # RKB for the slow with fractional time factor remove (since full\n        # integration of fast will result in scaling by γ)\n        slow_δ = slow.RKB[slow_s] / (γ)\n\n        # RKB for the slow with fractional time factor remove (since full\n        # integration of fast will result in scaling by γ)\n        nsubsteps = fast_dt_in > 0 ? ceil(Int, γ * dt / fast_dt_in) : 1\n        fast_dt = γ * dt / nsubsteps\n\n        updatedt!(fast, fast_dt)\n\n        for substep in 1:nsubsteps\n            slow_rka = nothing\n            if substep == nsubsteps\n                slow_rka = slow.RKA[slow_s % length(slow.RKA) + 1]\n            end\n            fast_time = slow_stage_time + (substep - 1) * fast_dt\n            dostep!(Q, fast, param, fast_time, slow_δ, slow_rv_dQ, slow_rka)\n        end\n    end\n    updatedt!(fast, fast_dt_in)\nend\n\n@kernel function update!(fast_dQ, slow_dQ, δ, slow_rka = nothing)\n    i = @index(Global, Linear)\n    @inbounds begin\n        fast_dQ[i] += δ * slow_dQ[i]\n        if slow_rka !== nothing\n            slow_dQ[i] *= slow_rka\n        end\n    end\nend\n"
  },
  {
    "path": "src/Numerics/ODESolvers/ODESolvers.jl",
    "content": "\"\"\"\n    ODESolvers\n\nOrdinary differential equation solvers\n\"\"\"\nmodule ODESolvers\n\nusing LinearAlgebra\nusing KernelAbstractions\nusing KernelAbstractions.Extras: @unroll\nusing StaticArrays\nusing ..SystemSolvers\nusing ..MPIStateArrays: array_device, realview\nusing ..GenericCallbacks\n\nexport AbstractODESolver, solve!, updatedt!, gettime, getsteps\n\nabstract type AbstractODESolver end\n\n\"\"\"\n    gettime(solver::AbstractODESolver)\n\nReturns the current simulation time of the ODE solver `solver`\n\"\"\"\ngettime(solver::AbstractODESolver) = solver.t\n\n\"\"\"\n    getdt(solver::AbstractODESolver)\n\nReturns the current simulation time step of the ODE solver `solver`\n\"\"\"\ngetdt(solver::AbstractODESolver) = solver.dt\n\n\"\"\"\n    getsteps(solver::AbstractODESolver)\n\nReturns the number of completed time steps of the ODE solver `solver`\n\"\"\"\ngetsteps(solver::AbstractODESolver) = solver.steps\n\n\"\"\"\n    ODESolvers.general_dostep!(Q, solver::AbstractODESolver, p,\n                               timeend::Real, adjustfinalstep::Bool)\n\nUse the solver to step `Q` forward in time from the current time, to the time\n`timeend`. If `adjustfinalstep == true` then `dt` is adjusted so that the step\ndoes not take the solution beyond the `timeend`.\n\"\"\"\nfunction general_dostep!(\n    Q,\n    solver::AbstractODESolver,\n    p,\n    timeend::Real;\n    adjustfinalstep::Bool,\n)\n    time, dt = gettime(solver), getdt(solver)\n    final_step = false\n    if adjustfinalstep && time + dt > timeend\n        orig_dt = dt\n        dt = timeend - time\n        updatedt!(solver, dt)\n        final_step = true\n    end\n    @assert dt > 0\n\n    dostep!(Q, solver, p, time)\n\n    if !final_step\n        updatetime!(solver, time + dt)\n    else\n        updatedt!(solver, orig_dt)\n        updatetime!(solver, timeend)\n    end\nend\n\n\"\"\"\n    updatetime!(solver::AbstractODESolver, time)\n\nChange the current time to `time` for the ODE solver `solver`.\n\"\"\"\nupdatetime!(solver::AbstractODESolver, time) = (solver.t = time)\n\n\"\"\"\n    updatedt!(solver::AbstractODESolver, dt)\n\nChange the time step size to `dt` for the ODE solver `solver`.\n\"\"\"\nupdatedt!(solver::AbstractODESolver, dt) = (solver.dt = dt)\n\n\"\"\"\n    updatesteps!(solver::AbstractODESolver, dt)\n\nSet the number of elapsed time steps for the ODE solver `solver`.\n\"\"\"\nupdatesteps!(solver::AbstractODESolver, steps) = (solver.steps = steps)\n\nisadjustable(solver::AbstractODESolver) = true\n\n# {{{ run!\n\"\"\"\n    solve!(Q, solver::AbstractODESolver; timeend,\n           stopaftertimeend=true, numberofsteps, callbacks)\n\nSolves an ODE using the `solver` starting from a state `Q`. The state `Q` is\nupdated inplace. The final time `timeend` or `numberofsteps` must be specified.\n\nA series of optional callback functions can be specified using the tuple\n`callbacks`; see the `GenericCallbacks` module.\n\"\"\"\nfunction solve!(\n    Q,\n    solver::AbstractODESolver,\n    param = nothing;\n    timeend::Real = Inf,\n    adjustfinalstep = true,\n    numberofsteps::Integer = 0,\n    callbacks = (),\n)\n\n    @assert isfinite(timeend) || numberofsteps > 0\n    if adjustfinalstep && !isadjustable(solver)\n        error(\"$solver does not support time step adjustments. Can only be used with `adjustfinalstep=false`.\")\n    end\n    t0 = gettime(solver)\n\n    # Loop through an initialize callbacks (if they need it)\n    GenericCallbacks.init!(callbacks, solver, Q, param, t0)\n\n    step = 0\n    time = t0\n    while time < timeend\n        step += 1\n        updatesteps!(solver, step)\n\n        time = general_dostep!(\n            Q,\n            solver,\n            param,\n            timeend;\n            adjustfinalstep = adjustfinalstep,\n        )\n\n        val = GenericCallbacks.call!(callbacks, solver, Q, param, time)\n        if val !== nothing && val > 0\n            return gettime(solver)\n        end\n\n        # Figure out if we should stop\n        if step == numberofsteps\n            break\n        end\n    end\n\n    # Loop through to fini callbacks\n    GenericCallbacks.fini!(callbacks, solver, Q, param, time)\n\n    return gettime(solver)\nend\n# }}}\n\ninclude(\"BackwardEulerSolvers.jl\")\ninclude(\"MultirateInfinitesimalGARKExplicit.jl\")\ninclude(\"MultirateInfinitesimalGARKDecoupledImplicit.jl\")\ninclude(\"MultirateInfinitesimalStepMethod.jl\")\ninclude(\"LowStorageRungeKuttaMethod.jl\")\ninclude(\"LowStorageRungeKutta3NMethod.jl\")\ninclude(\"StrongStabilityPreservingRungeKuttaMethod.jl\")\ninclude(\"AdditiveRungeKuttaMethod.jl\")\ninclude(\"MultirateRungeKuttaMethod.jl\")\ninclude(\"SplitExplicitMethod.jl\")\ninclude(\"DifferentialEquations.jl\")\n\nend # module\n"
  },
  {
    "path": "src/Numerics/ODESolvers/SplitExplicitMethod.jl",
    "content": "export SplitExplicitSolver\n\nusing ..BalanceLaws:\n    initialize_states!,\n    tendency_from_slow_to_fast!,\n    cummulate_fast_solution!,\n    reconcile_from_fast_to_slow!\n\nLSRK2N = LowStorageRungeKutta2N\n\n@doc \"\"\"\n    SplitExplicitSolver(slow_solver, fast_solver; dt, t0 = 0, coupled = true)\n\nThis is a time stepping object for explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q_{fast}} = f_{fast}(Q_{fast}, Q_{slow}, t)\n  \\\\dot{Q_{slow}} = f_{slow}(Q_{slow}, Q_{fast}, t)\n```\n\nwith the required time step size `dt` and optional initial time `t0`.  This\ntime stepping object is intended to be passed to the `solve!` command.\n\nThis method performs an operator splitting to timestep the vertical average\nof the model at a faster rate than the full model. This results in a first-\norder time stepper.\n\n\"\"\" SplitExplicitSolver\nmutable struct SplitExplicitSolver{SS, FS, RT, MSA} <: AbstractODESolver\n    \"slow solver\"\n    slow_solver::SS\n    \"fast solver\"\n    fast_solver::FS\n    \"time step\"\n    dt::RT\n    \"time\"\n    t::RT\n    \"elapsed time steps\"\n    steps::Int\n    \"storage for transfer tendency\"\n    dQ2fast::MSA\n\n    function SplitExplicitSolver(\n        slow_solver::LSRK2N,\n        fast_solver,\n        Q = nothing;\n        dt = getdt(slow_solver),\n        t0 = slow_solver.t,\n    ) where {AT <: AbstractArray}\n        SS = typeof(slow_solver)\n        FS = typeof(fast_solver)\n        RT = real(eltype(slow_solver.dQ))\n\n        dQ2fast = similar(slow_solver.dQ)\n        dQ2fast .= -0\n        MSA = typeof(dQ2fast)\n\n        return new{SS, FS, RT, MSA}(\n            slow_solver,\n            fast_solver,\n            RT(dt),\n            RT(t0),\n            0,\n            dQ2fast,\n        )\n    end\nend\n\nfunction dostep!(\n    Qslow,\n    split::SplitExplicitSolver{SS},\n    param,\n    time::Real,\n) where {SS <: LSRK2N}\n    slow = split.slow_solver\n    fast = split.fast_solver\n\n    Qfast = slow.rhs!.modeldata.Q_2D\n\n    dQslow = slow.dQ\n    dQ2fast = split.dQ2fast\n\n    slow_bl = slow.rhs!.balance_law\n    fast_bl = fast.rhs!.balance_law\n\n    groupsize = 256\n\n    slow_dt = getdt(slow)\n    fast_dt_in = getdt(fast)\n\n    for slow_s in 1:length(slow.RKA)\n        # Current slow state time\n        slow_stage_time = time + slow.RKC[slow_s] * slow_dt\n\n        # Initialize fast model and tendency adjustment\n        # before evalution of slow mode\n        initialize_states!(slow_bl, fast_bl, slow.rhs!, fast.rhs!, Qslow, Qfast)\n\n        # Evaluate the slow mode\n        # --> save tendency for the fast\n        slow.rhs!(dQ2fast, Qslow, param, slow_stage_time, increment = false)\n\n        # vertically integrate slow tendency to advance fast equation\n        # and use vertical mean for slow model (negative source)\n        # ---> work with dQ2fast as input\n        tendency_from_slow_to_fast!(\n            slow_bl,\n            fast_bl,\n            slow.rhs!,\n            fast.rhs!,\n            Qslow,\n            Qfast,\n            dQ2fast,\n        )\n\n        # Compute (and RK update) slow tendency\n        slow.rhs!(dQslow, Qslow, param, slow_stage_time, increment = true)\n\n        # Fractional time for slow stage\n        if slow_s == length(slow.RKA)\n            γ = 1 - slow.RKC[slow_s]\n        else\n            γ = slow.RKC[slow_s + 1] - slow.RKC[slow_s]\n        end\n\n        # RKB for the slow with fractional time factor remove (since full\n        # integration of fast will result in scaling by γ)\n        nsubsteps = fast_dt_in > 0 ? ceil(Int, γ * slow_dt / fast_dt_in) : 1\n        fast_dt = γ * slow_dt / nsubsteps\n\n        updatedt!(fast, fast_dt)\n\n        for substep in 1:nsubsteps\n            fast_time = slow_stage_time + (substep - 1) * fast_dt\n            dostep!(Qfast, fast, param, fast_time)\n            cummulate_fast_solution!(\n                slow_bl,\n                fast_bl,\n                fast.rhs!,\n                Qfast,\n                fast_time,\n                fast_dt,\n                substep,\n            )\n        end\n\n        # Update (RK-stage) slow state\n        event = Event(array_device(Qslow))\n        event = update!(array_device(Qslow), groupsize)(\n            realview(dQslow),\n            realview(Qslow),\n            slow.RKA[slow_s % length(slow.RKA) + 1],\n            slow.RKB[slow_s],\n            slow_dt,\n            nothing,\n            nothing,\n            nothing;\n            ndrange = length(realview(Qslow)),\n            dependencies = (event,),\n        )\n        wait(array_device(Qslow), event)\n\n        # reconcile slow equation using fast equation\n        reconcile_from_fast_to_slow!(\n            slow_bl,\n            fast_bl,\n            slow.rhs!,\n            fast.rhs!,\n            Qslow,\n            Qfast,\n        )\n    end\n    updatedt!(fast, fast_dt_in)\n\n    return nothing\nend\n"
  },
  {
    "path": "src/Numerics/ODESolvers/StrongStabilityPreservingRungeKuttaMethod.jl",
    "content": "export StrongStabilityPreservingRungeKutta\nexport SSPRK22Heuns, SSPRK22Ralstons, SSPRK33ShuOsher, SSPRK34SpiteriRuuth\n\n\"\"\"\n    StrongStabilityPreservingRungeKutta(f, RKA, RKB, RKC, Q; dt, t0 = 0)\n\nThis is a time stepping object for explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q} = f(Q, t)\n```\n\nwith the required time step size `dt` and optional initial time `t0`.  This\ntime stepping object is intended to be passed to the `solve!` command.\n\nThe constructor builds a strong-stability-preserving Runge--Kutta scheme\nbased on the provided `RKA`, `RKB` and `RKC` coefficient arrays.\n\nThe available concrete implementations are:\n\n  - [`SSPRK33ShuOsher`](@ref)\n  - [`SSPRK34SpiteriRuuth`](@ref)\n\"\"\"\nmutable struct StrongStabilityPreservingRungeKutta{T, RT, AT, Nstages} <:\n               AbstractODESolver\n    \"time step\"\n    dt::RT\n    \"time\"\n    t::RT\n    \"elapsed time steps\"\n    steps::Int\n    \"rhs function\"\n    rhs!::Any\n    \"Storage for RHS during the `StrongStabilityPreservingRungeKutta` update\"\n    Rstage::AT\n    \"Storage for the stage state during the `StrongStabilityPreservingRungeKutta` update\"\n    Qstage::AT\n    \"RK coefficient vector A (rhs scaling)\"\n    RKA::Array{RT, 2}\n    \"RK coefficient vector B (rhs add in scaling)\"\n    RKB::Array{RT, 1}\n    \"RK coefficient vector C (time scaling)\"\n    RKC::Array{RT, 1}\n\n    function StrongStabilityPreservingRungeKutta(\n        rhs!,\n        RKA,\n        RKB,\n        RKC,\n        Q::AT;\n        dt = 0,\n        t0 = 0,\n    ) where {AT <: AbstractArray}\n        T = eltype(Q)\n        RT = real(T)\n        new{T, RT, AT, length(RKB)}(\n            RT(dt),\n            RT(t0),\n            0,\n            rhs!,\n            similar(Q),\n            similar(Q),\n            RKA,\n            RKB,\n            RKC,\n        )\n    end\nend\n\n\"\"\"\n    dostep!(Q, ssp::StrongStabilityPreservingRungeKutta, p, time::Real,\n            nsteps::Int, iStage::Int, [slow_δ, slow_rv_dQ, slow_scaling])\n\nWrapper function to use the strong stability preserving Runge--Kutta method `ssp`\nas the fast solver for a Multirate Infinitesimal Step method by calling dostep!(Q,\nssp::StrongStabilityPreservingRungeKutta, p, time::Real, [slow_δ, slow_rv_dQ,\nslow_scaling]) nsubsteps times.\n\"\"\"\nfunction dostep!(\n    Q,\n    ssp::StrongStabilityPreservingRungeKutta,\n    p,\n    time::Real,\n    nsteps::Int,\n    iStage::Int,\n    slow_δ = nothing,\n    slow_rv_dQ = nothing,\n    slow_scaling = nothing,\n)\n    for i in 1:nsteps\n        dostep!(Q, ssp, p, time, slow_δ, slow_rv_dQ, slow_scaling)\n        time += ssp.dt\n    end\nend\n\n\"\"\"\n    ODESolvers.dostep!(Q, ssp::StrongStabilityPreservingRungeKutta, p,\n                       time::Real, [slow_δ, slow_rv_dQ, slow_scaling])\n\nUse the strong stability preserving Runge--Kutta method `ssp` to step `Q`\nforward in time from the current time `time` to final time `time + getdt(ssp)`.\n\nIf the optional parameter `slow_δ !== nothing` then `slow_rv_dQ * slow_δ` is\nadded as an additional ODE right-hand side source. If the optional parameter\n`slow_scaling !== nothing` then after the final stage update the scaling\n`slow_rv_dQ *= slow_scaling` is performed.\n\"\"\"\nfunction dostep!(\n    Q,\n    ssp::StrongStabilityPreservingRungeKutta,\n    p,\n    time,\n    slow_δ = nothing,\n    slow_rv_dQ = nothing,\n    in_slow_scaling = nothing,\n)\n    dt = ssp.dt\n\n    RKA, RKB, RKC = ssp.RKA, ssp.RKB, ssp.RKC\n    rhs! = ssp.rhs!\n    Rstage, Qstage = ssp.Rstage, ssp.Qstage\n\n    rv_Q = realview(Q)\n    rv_Rstage = realview(Rstage)\n    rv_Qstage = realview(Qstage)\n    groupsize = 256\n\n    rv_Qstage .= rv_Q\n    for s in 1:length(RKB)\n        rhs!(Rstage, Qstage, p, time + RKC[s] * dt, increment = false)\n\n        slow_scaling = nothing\n        if s == length(RKB)\n            slow_scaling = in_slow_scaling\n        end\n        event = Event(array_device(Q))\n        event = update!(array_device(Q), groupsize)(\n            rv_Rstage,\n            rv_Q,\n            rv_Qstage,\n            RKA[s, 1],\n            RKA[s, 2],\n            RKB[s],\n            dt,\n            slow_δ,\n            slow_rv_dQ,\n            slow_scaling;\n            ndrange = length(rv_Q),\n            dependencies = (event,),\n        )\n        wait(array_device(Q), event)\n    end\n    rv_Q .= rv_Qstage\nend\n\n@kernel function update!(\n    dQ,\n    Q,\n    Qstage,\n    rka1,\n    rka2,\n    rkb,\n    dt,\n    slow_δ,\n    slow_dQ,\n    slow_scaling,\n)\n    i = @index(Global, Linear)\n    @inbounds begin\n        if slow_δ !== nothing\n            dQ[i] += slow_δ * slow_dQ[i]\n        end\n        Qstage[i] = rka1 * Q[i] + rka2 * Qstage[i] + dt * rkb * dQ[i]\n        if slow_scaling !== nothing\n            slow_dQ[i] *= slow_scaling\n        end\n    end\nend\n\n\"\"\"\n    SSPRK22Heuns(f, Q; dt, t0 = 0)\n\nThis function returns a [`StrongStabilityPreservingRungeKutta`](@ref) time stepping object\nfor explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q} = f(Q, t)\n```\n\nwith the required time step size `dt` and optional initial time `t0`.  This\ntime stepping object is intended to be passed to the `solve!` command.\n\nThis uses the second-order, 2-stage, strong-stability-preserving, Runge--Kutta scheme\nof Shu and Osher (1988) (also known as Heun's method.)\nExact choice of coefficients from wikipedia page for Heun's method :)\n\n### References\n - [Shu1988](@cite)\n - [Heun1900](@cite)\n\"\"\"\nfunction SSPRK22Heuns(F, Q::AT; dt = 0, t0 = 0) where {AT <: AbstractArray}\n    T = eltype(Q)\n    RT = real(T)\n    RKA = [RT(1) RT(0); RT(1 // 2) RT(1 // 2)]\n    RKB = [RT(1), RT(1 // 2)]\n    RKC = [RT(0), RT(1)]\n    StrongStabilityPreservingRungeKutta(F, RKA, RKB, RKC, Q; dt = dt, t0 = t0)\nend\n\n\"\"\"\n    SSPRK22Ralstons(f, Q; dt, t0 = 0)\n\nThis function returns a [`StrongStabilityPreservingRungeKutta`](@ref) time stepping object\nfor explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q} = f(Q, t)\n```\n\nwith the required time step size `dt` and optional initial time `t0`.  This\ntime stepping object is intended to be passed to the `solve!` command.\n\nThis uses the second-order, 2-stage, strong-stability-preserving, Runge--Kutta scheme\nof Shu and Osher (1988) (also known as Ralstons's method.)\nExact choice of coefficients from wikipedia page for Heun's method :)\n\n### References\n - [Shu1988](@cite)\n - [Ralston1962](@cite)\n\"\"\"\nfunction SSPRK22Ralstons(F, Q::AT; dt = 0, t0 = 0) where {AT <: AbstractArray}\n    T = eltype(Q)\n    RT = real(T)\n    RKA = [RT(1) RT(0); RT(5 // 8) RT(3 // 8)]\n    RKB = [RT(2 // 3), RT(3 // 4)]\n    RKC = [RT(0), RT(2 // 3)]\n    StrongStabilityPreservingRungeKutta(F, RKA, RKB, RKC, Q; dt = dt, t0 = t0)\nend\n\n\"\"\"\n    SSPRK33ShuOsher(f, Q; dt, t0 = 0)\n\nThis function returns a [`StrongStabilityPreservingRungeKutta`](@ref) time stepping object\nfor explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q} = f(Q, t)\n```\n\nwith the required time step size `dt` and optional initial time `t0`.  This\ntime stepping object is intended to be passed to the `solve!` command.\n\nThis uses the third-order, 3-stage, strong-stability-preserving, Runge--Kutta scheme\nof Shu and Osher (1988)\n\n### References\n - [Shu1988](@cite)\n\"\"\"\nfunction SSPRK33ShuOsher(F, Q::AT; dt = 0, t0 = 0) where {AT <: AbstractArray}\n    T = eltype(Q)\n    RT = real(T)\n    RKA = [RT(1) RT(0); RT(3 // 4) RT(1 // 4); RT(1 // 3) RT(2 // 3)]\n    RKB = [RT(1), RT(1 // 4), RT(2 // 3)]\n    RKC = [RT(0), RT(1), RT(1 // 2)]\n    StrongStabilityPreservingRungeKutta(F, RKA, RKB, RKC, Q; dt = dt, t0 = t0)\nend\n\n\"\"\"\n    SSPRK34SpiteriRuuth(f, Q; dt, t0 = 0)\n\nThis function returns a [`StrongStabilityPreservingRungeKutta`](@ref) time stepping object\nfor explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q} = f(Q, t)\n```\n\nwith the required time step size `dt` and optional initial time `t0`.  This\ntime stepping object is intended to be passed to the `solve!` command.\n\nThis uses the third-order, 4-stage, strong-stability-preserving, Runge--Kutta scheme\nof Spiteri and Ruuth (1988)\n\n### References\n - [Spiteri2002](@cite)\n\"\"\"\nfunction SSPRK34SpiteriRuuth(\n    F,\n    Q::AT;\n    dt = 0,\n    t0 = 0,\n) where {AT <: AbstractArray}\n    T = eltype(Q)\n    RT = real(T)\n    RKA = [RT(1) RT(0); RT(0) RT(1); RT(2 // 3) RT(1 // 3); RT(0) RT(1)]\n    RKB = [RT(1 // 2); RT(1 // 2); RT(1 // 6); RT(1 // 2)]\n    RKC = [RT(0); RT(1 // 2); RT(1); RT(1 // 2)]\n    StrongStabilityPreservingRungeKutta(F, RKA, RKB, RKC, Q; dt = dt, t0 = t0)\nend\n"
  },
  {
    "path": "src/Numerics/SystemSolvers/SystemSolvers.jl",
    "content": "module SystemSolvers\n\nusing ..MPIStateArrays\nusing ..MPIStateArrays: array_device, realview\n\nusing ..Mesh.Grids\nimport ..Mesh.Grids: polynomialorders, dimensionality\nusing ..Mesh.Topologies\nusing ..DGMethods\nusing ..DGMethods: DGModel, DGFVModel, SpaceDiscretization\nimport ..DGMethods.FVReconstructions: width\nusing ..BalanceLaws\n\nusing Adapt\nusing CUDA\nusing LinearAlgebra\nusing LazyArrays\nusing StaticArrays\nusing KernelAbstractions\n\nconst weighted_norm = false\n\n# just for testing SystemSolvers\nLinearAlgebra.norm(A::MVector, p::Real, weighted::Bool) = norm(A, p)\nLinearAlgebra.norm(A::MVector, weighted::Bool) = norm(A, 2, weighted)\nLinearAlgebra.dot(A::MVector, B::MVector, weighted) = dot(A, B)\nLinearAlgebra.norm(A::AbstractVector, p::Real, weighted::Bool) = norm(A, p)\nLinearAlgebra.norm(A::AbstractVector, weighted::Bool) = norm(A, 2, weighted)\nLinearAlgebra.dot(A::AbstractVector, B::AbstractVector, weighted) = dot(A, B)\n\nexport linearsolve!,\n    settolerance!, prefactorize, construct_preconditioner, preconditioner_solve!\nexport AbstractSystemSolver,\n    AbstractIterativeSystemSolver, AbstractNonlinearSolver\nexport nonlinearsolve!\n\n\"\"\"\n    AbstractSystemSolver\n\nThis is an abstract type representing a generic linear solver.\n\"\"\"\nabstract type AbstractSystemSolver end\n\n\"\"\"\n    AbstractNonlinearSolver\n\nThis is an abstract type representing a generic nonlinear solver.\n\"\"\"\nabstract type AbstractNonlinearSolver <: AbstractSystemSolver end\n\n\"\"\"\n    LSOnly\n\nOnly applies the linear solver (no Newton solver)\n\"\"\"\nstruct LSOnly <: AbstractNonlinearSolver\n    linearsolver::Any\nend\n\nfunction donewtoniteration!(\n    rhs!,\n    linearoperator!,\n    preconditioner,\n    Q,\n    Qrhs,\n    solver::LSOnly,\n    args...,\n)\n    @info \"donewtoniteration! linearsolve!\", args...\n    linearsolve!(\n        linearoperator!,\n        preconditioner,\n        solver.linearsolver,\n        Q,\n        Qrhs,\n        args...;\n        max_iters = getmaxiterations(solver.linearsolver),\n    )\nend\n\n\n\"\"\"\n\nSolving rhs!(Q) = Qrhs via Newton,\n\nwhere `F = rhs!(Q) - Qrhs`\n\ndF/dQ(Q^n) ΔQ ≈ jvp!(ΔQ;  Q^n, F(Q^n))\n\npreconditioner ≈ dF/dQ(Q)\n\n\"\"\"\nfunction nonlinearsolve!(\n    rhs!,\n    jvp!,\n    preconditioner,\n    solver::AbstractNonlinearSolver,\n    Q::AT,\n    Qrhs,\n    args...;\n    max_newton_iters = 10,\n    cvg = Ref{Bool}(),\n) where {AT}\n\n    FT = eltype(Q)\n    tol = solver.tol\n    converged = false\n    iters = 0\n\n    if preconditioner === nothing\n        preconditioner = NoPreconditioner()\n    end\n\n    # Initialize NLSolver, compute initial residual\n    initial_residual_norm = initialize!(rhs!, Q, Qrhs, solver, args...)\n    if initial_residual_norm < tol\n        converged = true\n    end\n    converged && return iters\n\n\n    while !converged && iters < max_newton_iters\n\n        # dF/dQ(Q^n) ΔQ ≈ jvp!(ΔQ;  Q^n, F(Q^n)), update Q^n in jvp!\n        update_Q!(jvp!, Q, args...)\n\n        # update preconditioner based on finite difference, with jvp!\n        preconditioner_update!(jvp!, rhs!.f!, preconditioner, args...)\n\n        # do newton iteration with Q^{n+1} = Q^{n} - dF/dQ(Q^n)⁻¹ (rhs!(Q) - Qrhs)\n        residual_norm, linear_iterations = donewtoniteration!(\n            rhs!,\n            jvp!,\n            preconditioner,\n            Q,\n            Qrhs,\n            solver,\n            args...,\n        )\n        # @info \"Linear solver converged in $linear_iterations iterations\"\n        iters += 1\n\n        preconditioner_counter_update!(preconditioner)\n\n\n        if !isfinite(residual_norm)\n            error(\"norm of residual is not finite after $iters iterations of `donewtoniteration!`\")\n        end\n\n        # Check residual_norm / norm(R0)\n        # Comment: Should we check \"correction\" magitude?\n        # ||Delta Q|| / ||Q|| ?\n        relresidual = residual_norm / initial_residual_norm\n        if relresidual < tol || residual_norm < tol\n            # @info \"Newton converged in $iters iterations!\"\n            converged = true\n        end\n    end\n\n    converged ||\n        @warn \"Nonlinear solver did not converge after $iters iterations\"\n    cvg[] = converged\n\n    iters\nend\n\n\"\"\"\n    AbstractIterativeSystemSolver\n\nThis is an abstract type representing a generic iterative\nlinear solver.\n\nThe available concrete implementations are:\n\n  - [`GeneralizedConjugateResidual`](@ref)\n  - [`GeneralizedMinimalResidual`](@ref)\n\"\"\"\nabstract type AbstractIterativeSystemSolver <: AbstractSystemSolver end\n\n\"\"\"\n    settolerance!(solver::AbstractIterativeSystemSolver, tolerance, relative)\n\nSets the relative or absolute tolerance of the iterative linear solver\n`solver` to `tolerance`.\n\"\"\"\nsettolerance!(\n    solver::AbstractIterativeSystemSolver,\n    tolerance,\n    relative = true,\n) = (relative ? (solver.rtol = tolerance) : (solver.atol = tolerance))\n\ndoiteration!(\n    linearoperator!,\n    preconditioner,\n    Q,\n    Qrhs,\n    solver::AbstractIterativeSystemSolver,\n    threshold,\n    args...,\n) = throw(MethodError(\n    doiteration!,\n    (linearoperator!, preconditioner, Q, Qrhs, solver, tolerance, args...),\n))\n\ninitialize!(\n    linearoperator!,\n    Q,\n    Qrhs,\n    solver::AbstractIterativeSystemSolver,\n    args...,\n) = throw(MethodError(initialize!, (linearoperator!, Q, Qrhs, solver, args...)))\n\n\"\"\"\n    prefactorize(linop!, linearsolver, args...)\n\nPrefactorize the in-place linear operator `linop!` for use with `linearsolver`.\n\"\"\"\nfunction prefactorize(\n    linop!,\n    linearsolver::AbstractIterativeSystemSolver,\n    args...,\n)\n    return nothing\nend\n\n\"\"\"\n    linearsolve!(linearoperator!, solver::AbstractIterativeSystemSolver, Q, Qrhs, args...)\n\nSolves a linear problem defined by the `linearoperator!` function and the state\n`Qrhs`, i.e,\n\n```math\nL(Q) = Q_{rhs}\n```\n\nusing the `solver` and the initial guess `Q`. After the call `Q` contains the\nsolution.  The arguments `args` is passed to `linearoperator!` when it is\ncalled.\n\"\"\"\nfunction linearsolve!(\n    linearoperator!,\n    preconditioner,\n    solver::AbstractIterativeSystemSolver,\n    Q,\n    Qrhs,\n    args...;\n    max_iters = length(Q),\n    cvg = Ref{Bool}(),\n)\n    converged = false\n    iters = 0\n\n    if preconditioner === nothing\n        preconditioner = NoPreconditioner()\n    end\n\n    converged, threshold =\n        initialize!(linearoperator!, Q, Qrhs, solver, args...)\n    converged && return iters\n\n    while !converged && iters < max_iters\n        converged, inner_iters, residual_norm = doiteration!(\n            linearoperator!,\n            preconditioner,\n            Q,\n            Qrhs,\n            solver,\n            threshold,\n            args...,\n        )\n\n        iters += inner_iters\n\n        if !isfinite(residual_norm)\n            error(\"norm of residual is not finite after $iters iterations of `doiteration!`\")\n        end\n\n        achieved_tolerance = residual_norm / threshold * solver.rtol\n    end\n\n    converged ||\n        @warn \"Solver did not attain convergence after $iters iterations\"\n    cvg[] = converged\n\n    iters\nend\n\n@kernel function linearcombination!(Q, cs, Xs, increment::Bool)\n    i = @index(Global, Linear)\n    if !increment\n        @inbounds Q[i] = -zero(eltype(Q))\n    end\n    @inbounds for j in 1:length(cs)\n        Q[i] += cs[j] * Xs[j][i]\n    end\nend\n\ninclude(\"generalized_minimal_residual_solver.jl\")\ninclude(\"generalized_conjugate_residual_solver.jl\")\ninclude(\"conjugate_gradient_solver.jl\")\ninclude(\"columnwise_lu_solver.jl\")\ninclude(\"preconditioners.jl\")\ninclude(\"batched_generalized_minimal_residual_solver.jl\")\ninclude(\"jacobian_free_newton_krylov_solver.jl\")\n\nend\n"
  },
  {
    "path": "src/Numerics/SystemSolvers/batched_generalized_minimal_residual_solver.jl",
    "content": "\nusing CUDA\n\nexport BatchedGeneralizedMinimalResidual\n\n\"\"\"\n    BatchedGeneralizedMinimalResidual(\n        Q,\n        dofperbatch,\n        Nbatch;\n        M = min(20, length(Q)),\n        rtol = √eps(eltype(AT)),\n        atol = eps(eltype(AT)),\n        forward_reshape = size(Q),\n        forward_permute = Tuple(1:length(size(Q))),\n    )\n\n# BGMRES\nThis is an object for solving batched linear systems using the GMRES algorithm.\nThe constructor parameter `M` is the number of steps after which the algorithm\nis restarted (if it has not converged), `Q` is a reference state used only\nto allocate the solver internal state, `dofperbatch` is the size of each batched\nsystem (assumed to be the same throughout), `Nbatch` is the total number\nof independent linear systems, and `rtol` specifies the convergence\ncriterion based on the relative residual norm (max across all batched systems).\nThe argument `forward_reshape` is a tuple of integers denoting the reshaping\n(if required) of the solution vectors for batching the Arnoldi routines.\nThe argument `forward_permute` describes precisely which indices of the\narray `Q` to permute. This object is intended to be passed to\nthe [`linearsolve!`](@ref) command.\n\nThis uses a batched-version of the restarted Generalized Minimal Residual method\nof Saad and Schultz (1986).\n\n# Note\nEventually, we'll want to do something like this:\n\n    i = @index(Global)\n    linearoperator!(Q[:, :, :, i], args...)\n\nThis will help stop the need for constantly\nreshaping the work arrays. It would also potentially\nsave us some memory.\n\"\"\"\nmutable struct BatchedGeneralizedMinimalResidual{\n    I,\n    T,\n    AT,\n    BKT,\n    OmT,\n    HT,\n    gT,\n    sT,\n    resT,\n    res0T,\n    FRS,\n    FPR,\n    BRS,\n    BPR,\n} <: AbstractIterativeSystemSolver\n\n    \"global Krylov basis at present step\"\n    krylov_basis::AT\n    \"global Krylov basis at previous step\"\n    krylov_basis_prev::AT\n    \"global batched Krylov basis\"\n    batched_krylov_basis::BKT\n    \"Storage for the Givens rotation matrices\"\n    Ω::OmT\n    \"Hessenberg matrix in each column\"\n    H::HT\n    \"rhs of the least squares problem in each column\"\n    g0::gT\n    \"The GMRES iterate in each batched column\"\n    sol::sT\n    \"Relative tolerance\"\n    rtol::T\n    \"Absolute tolerance\"\n    atol::T\n    \"Maximum number of GMRES iterations (global across all columns)\"\n    max_iter::I\n    \"total number of batched columns\"\n    batch_size::I\n    \"total number of dofs per batched column\"\n    dofperbatch::I\n    \"residual norm in each column\"\n    resnorms::resT\n    \"initial residual norm in each column\"\n    initial_resnorms::res0T\n    forward_reshape::FRS\n    forward_permute::FPR\n    backward_reshape::BRS\n    backward_permute::BPR\n\n    function BatchedGeneralizedMinimalResidual(\n        Q::AT,\n        dofperbatch,\n        Nbatch;\n        M = min(20, length(Q)),\n        rtol = √eps(eltype(AT)),\n        atol = eps(eltype(AT)),\n        forward_reshape = size(Q),\n        forward_permute = Tuple(1:length(size(Q))),\n    ) where {AT}\n        # Get ArrayType information\n        if isa(array_device(Q), CPU)\n            ArrayType = Array\n        else\n            # Sanity check since we don't support anything else\n            @assert isa(array_device(Q), CUDADevice)\n            ArrayType = CuArray\n        end\n\n        # FIXME: If we can batch the application of linearoperator!, then we dont\n        # need these two temporary work vectors (unpermuted/reshaped)\n        krylov_basis = similar(Q)\n        krylov_basis_prev = similar(Q)\n\n        FT = eltype(AT)\n        # Create storage for holding the batched Krylov basis\n        batched_krylov_basis =\n            fill!(ArrayType{FT}(undef, M + 1, dofperbatch, Nbatch), 0)\n\n        # Create storage for doing the batched Arnoldi process\n        Ω = fill!(ArrayType{FT}(undef, Nbatch, 2 * M), 0)\n        H = fill!(ArrayType{FT}(undef, Nbatch, M + 1, M), 0)\n        g0 = fill!(ArrayType{FT}(undef, Nbatch, M + 1), 0)\n\n        # Create storage for constructing the global gmres iterate\n        # and recording column-norms\n        sol = fill!(ArrayType{FT}(undef, dofperbatch, Nbatch), 0)\n        resnorms = fill!(ArrayType{FT}(undef, Nbatch), 0)\n        initial_resnorms = fill!(ArrayType{FT}(undef, Nbatch), 0)\n\n        @assert dofperbatch * Nbatch == length(Q)\n\n        # Define the back permutation and reshape\n        backward_permute = Tuple(sortperm([forward_permute...]))\n        tmp_reshape_tuple_b = [forward_reshape...]\n        permute!(tmp_reshape_tuple_b, [forward_permute...])\n        backward_reshape = Tuple(tmp_reshape_tuple_b)\n\n        # FIXME: Is there a better way of doing this?\n        BKT = typeof(batched_krylov_basis)\n        OmT = typeof(Ω)\n        HT = typeof(H)\n        gT = typeof(g0)\n        sT = typeof(sol)\n        resT = typeof(resnorms)\n        res0T = typeof(initial_resnorms)\n        FRS = typeof(forward_reshape)\n        FPR = typeof(forward_permute)\n        BRS = typeof(backward_reshape)\n        BPR = typeof(backward_permute)\n\n        return new{\n            typeof(Nbatch),\n            eltype(Q),\n            AT,\n            BKT,\n            OmT,\n            HT,\n            gT,\n            sT,\n            resT,\n            res0T,\n            FRS,\n            FPR,\n            BRS,\n            BPR,\n        }(\n            krylov_basis,\n            krylov_basis_prev,\n            batched_krylov_basis,\n            Ω,\n            H,\n            g0,\n            sol,\n            rtol,\n            atol,\n            M,\n            Nbatch,\n            dofperbatch,\n            resnorms,\n            initial_resnorms,\n            forward_reshape,\n            forward_permute,\n            backward_reshape,\n            backward_permute,\n        )\n    end\nend\n\n\"\"\"\n    BatchedGeneralizedMinimalResidual(\n        dg::SpaceDiscretization,\n        Q::MPIStateArray;\n        atol = sqrt(eps(eltype(Q))),\n        rtol = sqrt(eps(eltype(Q))),\n        max_subspace_size = nothing,\n        independent_states = false,\n    )\n\n# Description\nSpecialized constructor for `BatchedGeneralizedMinimalResidual` struct, using\na `SpaceDiscretization` to infer state-information and determine appropriate reshaping\nand permutations.\n\n# Arguments\n- `dg`: (SpaceDiscretization) A `SpaceDiscretization` containing all relevant grid and topology\n        information.\n- `Q` : (MPIStateArray) An `MPIStateArray` containing field information.\n\n# Keyword Arguments\n- `atol`: (float) absolute tolerance. `DEFAULT = sqrt(eps(eltype(Q)))`\n- `rtol`: (float) relative tolerance. `DEFAULT = sqrt(eps(eltype(Q)))`\n- `max_subspace_size` : (Int).    Maximal dimension of each (batched)\n                                  Krylov subspace. DEFAULT = nothing\n- `independent_states`: (boolean) An optional flag indicating whether\n                                  or not degrees of freedom are coupled\n                                  internally (within a column).\n                                  `DEFAULT = false`\n# Return\ninstance of `BatchedGeneralizedMinimalResidual` struct\n\"\"\"\nfunction BatchedGeneralizedMinimalResidual(\n    dg::SpaceDiscretization,\n    Q::MPIStateArray;\n    atol = sqrt(eps(eltype(Q))),\n    rtol = sqrt(eps(eltype(Q))),\n    max_subspace_size = nothing,\n    independent_states = false,\n)\n    grid = dg.grid\n    topology = grid.topology\n    dim = dimensionality(grid)\n    N = polynomialorders(grid)\n\n    # Number of Gauss-Lobatto quadrature points in each direction\n    Nq = N .+ 1\n\n    # Number of states and elements (in vertical and horizontal directions)\n    num_states = size(Q)[2]\n    nelem = length(topology.realelems)\n    nvertelem = topology.stacksize\n    nhorzelem = div(nelem, nvertelem)\n\n    # Definition of a \"column\" here is a vertical stack of degrees\n    # of freedom. For example, consider a mesh consisting of a single\n    # linear element:\n    #    o----------o\n    #    |\\ d1   d2 |\\\n    #    | \\        | \\\n    #    |  \\ d3    d4 \\\n    #    |   o----------o\n    #    o--d5---d6-o   |\n    #     \\  |       \\  |\n    #      \\ |        \\ |\n    #       \\|d7    d8 \\|\n    #        o----------o\n    # There are 4 total 1-D columns, each containing two\n    # degrees of freedom. In general, a mesh of stacked elements will\n    # have `Nq[1] * Nq[2] * nhorzelem` total 1-D columns.\n    # A single 1-D column has `Nq[3] * nvertelem * num_states`\n    # degrees of freedom.\n    #\n    # nql = length(Nq)\n    # indices:      (1...nql, nql + 1 , nql + 2, nql + 3)\n\n    # for 3d case, this is [ni, nj, nk, num_states, nvertelem, nhorzelem]\n    # here ni, nj, nk are number of Gauss quadrature points in each element in x-y-z directions\n    # Q = reshape(Q, reshaping_tup), leads to the column-wise fashion Q\n    reshaping_tup = (Nq..., num_states, nvertelem, nhorzelem)\n\n    @inbounds Nqv = Nq[dim]\n    @inbounds Nqh = dim == 2 ? Nq[1] : Nq[1] * Nq[2]\n    if independent_states\n        m = Nqv * nvertelem\n        n = Nqh * nhorzelem * num_states\n    else\n        m = Nqv * nvertelem * num_states\n        n = Nqh * nhorzelem\n    end\n\n    if max_subspace_size === nothing\n        max_subspace_size = m\n    end\n\n    # permute [ni, nj, nk, num_states, nvertelem, nhorzelem]\n    # to      [nvertelem, nk, num_states, ni, nj, nhorzelem]\n    permute_size = length(reshaping_tup)\n    permute_tuple_f = (dim + 1, dim, dim + 2, (1:(dim - 1))..., permute_size)\n\n    return BatchedGeneralizedMinimalResidual(\n        Q,\n        m,\n        n;\n        M = max_subspace_size,\n        atol = atol,\n        rtol = rtol,\n        forward_reshape = reshaping_tup,\n        forward_permute = permute_tuple_f,\n    )\nend\n\nfunction initialize!(\n    linearoperator!,\n    Q,\n    Qrhs,\n    solver::BatchedGeneralizedMinimalResidual,\n    args...;\n    restart = false,\n)\n    g0 = solver.g0\n    krylov_basis = solver.krylov_basis\n    rtol, atol = solver.rtol, solver.atol\n\n    batched_krylov_basis = solver.batched_krylov_basis\n    Ndof = solver.dofperbatch\n    forward_reshape = solver.forward_reshape\n    forward_permute = solver.forward_permute\n    resnorms = solver.resnorms\n    initial_resnorms = solver.initial_resnorms\n    max_iter = solver.max_iter\n\n    # Device and groupsize information\n    device = array_device(Q)\n    groupsize = 256\n\n    @assert size(Q) == size(krylov_basis)\n\n    # PRECONDITIONER:  PQ0 ->  P*Q0,\n    # the first basis is (J Pinv)PQ0 = b, kry1 = b - J Q0\n    linearoperator!(krylov_basis, Q, args...)\n    krylov_basis .= Qrhs .- krylov_basis\n\n    # Convert into a batched Krylov basis vector\n    # REMARK: Ugly hack on the GPU. Can we fix this?\n    tmp_array = similar(batched_krylov_basis, size(batched_krylov_basis)[2:3])\n    convert_structure!(\n        tmp_array,\n        krylov_basis,\n        forward_reshape,\n        forward_permute,\n    )\n    batched_krylov_basis[1, :, :] .= tmp_array\n\n    # Now we initialize across all columns (solver.batch_size).\n    # This function also computes the residual norm in each column\n    event = Event(device)\n    event = batched_initialize!(device, groupsize)(\n        resnorms,\n        g0,\n        batched_krylov_basis,\n        Ndof,\n        max_iter;\n        ndrange = solver.batch_size,\n        dependencies = (event,),\n    )\n    wait(device, event)\n\n    # When restarting, we do not want to overwrite the initial threshold,\n    # otherwise we may not get an accurate indication that we have sufficiently\n    # reduced the GMRES residual.\n    if !restart\n        initial_resnorms .= resnorms\n    end\n    residual_norm = maximum(resnorms)\n    initial_residual_norm = maximum(initial_resnorms)\n    converged =\n        check_convergence(residual_norm, initial_residual_norm, atol, rtol)\n\n    converged, residual_norm\nend\n\nfunction doiteration!(\n    linearoperator!,\n    preconditioner,\n    Q,\n    Qrhs,\n    solver::BatchedGeneralizedMinimalResidual,\n    threshold,\n    args...,\n)\n    FT = eltype(Q)\n    krylov_basis = solver.krylov_basis\n    krylov_basis_prev = solver.krylov_basis_prev\n    Hs = solver.H\n    g0s = solver.g0\n    Ωs = solver.Ω\n    sols = solver.sol\n    batched_krylov_basis = solver.batched_krylov_basis\n    Ndof = solver.dofperbatch\n    rtol, atol = solver.rtol, solver.atol\n    max_iter = solver.max_iter\n\n    forward_reshape = solver.forward_reshape\n    forward_permute = solver.forward_permute\n    backward_reshape = solver.backward_reshape\n    backward_permute = solver.backward_permute\n    resnorms = solver.resnorms\n    initial_resnorms = solver.initial_resnorms\n    initial_residual_norm = maximum(initial_resnorms)\n\n    # Device and groupsize information\n    device = array_device(Q)\n    groupsize = 256\n\n    # Main batched-GMRES iteration cycle\n    converged = false\n    residual_norm = typemax(FT)\n    j = 1\n    for outer j in 1:max_iter\n        # FIXME: Remove this back-and-forth reshaping by exploiting the\n        # data layout in a similar way that the ColumnwiseLU solver does\n\n        convert_structure!(\n            krylov_basis_prev,\n            view(batched_krylov_basis, j, :, :),\n            backward_reshape,\n            backward_permute,\n        )\n\n        # PRECONDITIONER: batched_krylov_basis[j+1] =  J P^{-1}batched_krylov_basis[j]\n        # set krylov_basis_prev = P^{-1}batched_krylov_basis[j]\n        preconditioner_solve!(preconditioner, krylov_basis_prev)\n\n        # Global operator application to get new Krylov basis vector\n        linearoperator!(krylov_basis, krylov_basis_prev, args...)\n\n        # Now that we have a global Krylov vector, we reshape and batch\n        # the Arnoldi iterations across all columns\n        convert_structure!(\n            view(batched_krylov_basis, j + 1, :, :),\n            krylov_basis,\n            forward_reshape,\n            forward_permute,\n        )\n\n        event = Event(device)\n        event = batched_arnoldi_process!(device, groupsize)(\n            resnorms,\n            g0s,\n            Hs,\n            Ωs,\n            batched_krylov_basis,\n            j,\n            Ndof;\n            ndrange = solver.batch_size,\n            dependencies = (event,),\n        )\n        wait(device, event)\n\n        # Current stopping criteria is based on the maximal column norm\n        # TODO: Once we are able to batch the operator application, we\n        # should revisit the termination criteria.\n        residual_norm = maximum(resnorms)\n        converged =\n            check_convergence(residual_norm, initial_residual_norm, atol, rtol)\n        if converged\n            break\n        end\n    end\n\n    # Reshape the solution vector to construct the new GMRES iterate\n    # PRECONDITIONER Q =  Q0 + Pinv PΔQ = Q0 + Pinv (Kry * y)\n    # sol = PΔQ = Kry * y\n    sols .= 0\n\n    # Solve the triangular system (minimization problem for optimal linear coefficients\n    # in the GMRES iterate) and construct the current iterate in each column\n    event = Event(device)\n    event = construct_batched_gmres_iterate!(device, groupsize)(\n        batched_krylov_basis,\n        Hs,\n        g0s,\n        sols,\n        j,\n        Ndof;\n        ndrange = solver.batch_size,\n        dependencies = (event,),\n    )\n    wait(device, event)\n\n    # Use krylov_basis_prev as container for ΔQ\n    ΔQ = krylov_basis_prev\n    # Unwind reshaping and return solution in standard format\n    convert_structure!(ΔQ, sols, backward_reshape, backward_permute)\n    # PRECONDITIONER: Q ->  Pinv Q\n    preconditioner_solve!(preconditioner, ΔQ)\n    Q .+= ΔQ\n\n    # if not converged, then restart\n    converged ||\n        initialize!(linearoperator!, Q, Qrhs, solver, args...; restart = true)\n\n    (converged, j, residual_norm)\nend\n\n@kernel function batched_initialize!(\n    resnorms,\n    g0,\n    batched_krylov_basis,\n    Ndof,\n    M,\n)\n    cidx = @index(Global)\n    FT = eltype(batched_krylov_basis)\n\n    # Initialize entire RHS storage\n    @inbounds for j in 1:(M + 1)\n        g0[cidx, j] = FT(0.0)\n    end\n\n    # Now we compute the first element of g0[cidx, :],\n    # which is determined by the column norm of the initial residual ∥r0∥_2:\n    # g0 = ∥r0∥_2 e1\n    @inbounds for j in 1:Ndof\n        g0[cidx, 1] +=\n            batched_krylov_basis[1, j, cidx] * batched_krylov_basis[1, j, cidx]\n    end\n    @inbounds g0[cidx, 1] = sqrt(g0[cidx, 1])\n\n    # Normalize the batched_krylov_basis by the (local) residual norm\n    @inbounds for j in 1:Ndof\n        batched_krylov_basis[1, j, cidx] /= g0[cidx, 1]\n    end\n\n    # Record initialize residual norm in the column\n    @inbounds resnorms[cidx] = g0[cidx, 1]\n\n    nothing\nend\n\n@kernel function batched_arnoldi_process!(\n    resnorms,\n    g0,\n    H,\n    Ω,\n    batched_krylov_basis,\n    j,\n    Ndof,\n)\n    cidx = @index(Global)\n    FT = eltype(batched_krylov_basis)\n\n    #  Arnoldi process in the local column `cidx`\n    @inbounds for i in 1:j\n        H[cidx, i, j] = FT(0.0)\n        # Modified Gram-Schmidt procedure to generate the Hessenberg matrix\n        for k in 1:Ndof\n            H[cidx, i, j] +=\n                batched_krylov_basis[j + 1, k, cidx] *\n                batched_krylov_basis[i, k, cidx]\n        end\n        # Orthogonalize new Krylov vector against previous one\n        for k in 1:Ndof\n            batched_krylov_basis[j + 1, k, cidx] -=\n                H[cidx, i, j] * batched_krylov_basis[i, k, cidx]\n        end\n    end\n\n    # And finally, normalize latest Krylov basis vector\n    local_norm = FT(0.0)\n    @inbounds for i in 1:Ndof\n        local_norm +=\n            batched_krylov_basis[j + 1, i, cidx] *\n            batched_krylov_basis[j + 1, i, cidx]\n    end\n    @inbounds H[cidx, j + 1, j] = sqrt(local_norm)\n    @inbounds for i in 1:Ndof\n        batched_krylov_basis[j + 1, i, cidx] /= H[cidx, j + 1, j]\n    end\n\n    # Loop over previously computed Krylov basis vectors\n    # and apply the Givens rotations\n    @inbounds for i in 1:(j - 1)\n        cos_tmp = Ω[cidx, 2 * i - 1]\n        sin_tmp = Ω[cidx, 2 * i]\n\n        # Apply the Givens rotations\n        # | cos -sin | | hi   |\n        # | sin  cos | | hi+1 |\n        tmp1 = cos_tmp * H[cidx, i, j] + sin_tmp * H[cidx, i + 1, j]\n        H[cidx, i + 1, j] =\n            -sin_tmp * H[cidx, i, j] + cos_tmp * H[cidx, i + 1, j]\n        H[cidx, i, j] = tmp1\n    end\n\n    # Eliminate the last element hj+1 and update the rotation matrix\n    # | cos -sin | | hj   |  = | hj'|\n    # | sin  cos | | hj+1 |  = | 0  |\n    # where cos, sin = hj+1/sqrt(hj^2 + hj+1^2), hj/sqrt(hj^2 + hj+1^2),\n    # and update for next iteration\n    @inbounds begin\n        Ω[cidx, 2 * j - 1] = H[cidx, j, j]\n        Ω[cidx, 2 * j] = H[cidx, j + 1, j]\n        H[cidx, j, j] = sqrt(Ω[cidx, 2 * j - 1]^2 + Ω[cidx, 2 * j]^2)\n        H[cidx, j + 1, j] = FT(0.0)\n        Ω[cidx, 2 * j - 1] /= H[cidx, j, j]\n        Ω[cidx, 2 * j] /= H[cidx, j, j]\n\n        # And now to the rhs g0\n        cos_tmp = Ω[cidx, 2 * j - 1]\n        sin_tmp = Ω[cidx, 2 * j]\n        tmp1 = cos_tmp * g0[cidx, j] + sin_tmp * g0[cidx, j + 1]\n        g0[cidx, j + 1] = -sin_tmp * g0[cidx, j] + cos_tmp * g0[cidx, j + 1]\n        g0[cidx, j] = tmp1\n\n        # Record estimate for the gmres residual\n        resnorms[cidx] = abs(g0[cidx, j + 1])\n    end\n\n    nothing\nend\n\n@kernel function construct_batched_gmres_iterate!(\n    batched_krylov_basis,\n    Hs,\n    g0s,\n    sols,\n    j,\n    Ndof,\n)\n    # Solve for the GMRES coefficients (yⱼ) at the `j`-th\n    # iteration that minimizes ∥ b - A xⱼ ∥_2, where\n    # xⱼ = ∑ᵢ yᵢ Ψᵢ, with Ψᵢ denoting the Krylov basis vectors\n    cidx = @index(Global)\n\n    # Do the upper-triangular backsolve\n    @inbounds for i in j:-1:1\n        g0s[cidx, i] /= Hs[cidx, i, i]\n        for k in 1:(i - 1)\n            g0s[cidx, k] -= Hs[cidx, k, i] * g0s[cidx, i]\n        end\n    end\n\n    # Having determined yᵢ, we now construct the GMRES solution\n    # in each column: xⱼ = ∑ᵢ yᵢ Ψᵢ\n    @inbounds for i in 1:j\n        for k in 1:Ndof\n            sols[k, cidx] += g0s[cidx, i] * batched_krylov_basis[i, k, cidx]\n        end\n    end\n\n    nothing\nend\n\n\"\"\"\n    convert_structure!(\n        x,\n        y,\n        reshape_tuple,\n        permute_tuple,\n    )\n\nComputes a tensor transpose and stores result in x\n\n# Arguments\n- `x`: (array) [OVERWRITTEN]. target destination for storing the y data\n- `y`: (array). data that we want to copy\n- `reshape_tuple`: (tuple) reshapes y to be like that of x, up to a permutation\n- `permute_tuple`: (tuple) permutes the reshaped array into the correct structure\n\"\"\"\n@inline function convert_structure!(x, y, reshape_tuple, permute_tuple)\n    alias_y = reshape(y, reshape_tuple)\n    permute_y = permutedims(alias_y, permute_tuple)\n    copyto!(x, reshape(permute_y, size(x)))\n    nothing\nend\n@inline convert_structure!(x, y::MPIStateArray, reshape_tuple, permute_tuple) =\n    convert_structure!(x, y.realdata, reshape_tuple, permute_tuple)\n@inline convert_structure!(x::MPIStateArray, y, reshape_tuple, permute_tuple) =\n    convert_structure!(x.realdata, y, reshape_tuple, permute_tuple)\n\nfunction check_convergence(residual_norm, initial_residual_norm, atol, rtol)\n    relative_residual = residual_norm / initial_residual_norm\n    converged = false\n    if (residual_norm ≤ atol || relative_residual ≤ rtol)\n        converged = true\n    end\n    return converged\nend\n"
  },
  {
    "path": "src/Numerics/SystemSolvers/columnwise_lu_solver.jl",
    "content": "#### Columnwise LU Solver\n\nexport ManyColumnLU, SingleColumnLU\n\nabstract type AbstractColumnLUSolver <: AbstractSystemSolver end\n\n\"\"\"\n    ManyColumnLU()\n\nThis solver is used for systems that are block diagonal where each block is\nassociated with a column of the mesh.  The systems are solved using a\nnon-pivoted LU factorization.\n\"\"\"\nstruct ManyColumnLU <: AbstractColumnLUSolver end\n\n\"\"\"\n    SingleColumnLU()\n\nThis solver is used for systems that are block diagonal where each block is\nassociated with a column of the mesh.  Moreover, each block is assumed to be\nthe same.  The systems are solved using a non-pivoted LU factorization.\n\"\"\"\nstruct SingleColumnLU <: AbstractColumnLUSolver end\n\nstruct ColumnwiseLU{AT}\n    A::AT\nend\n\nstruct DGColumnBandedMatrix{D, P, NS, EH, EV, EB, SC, AT}\n    data::AT\nend\nDGColumnBandedMatrix(\n    A::DGColumnBandedMatrix{D, P, NS, EH, EV, EB, SC},\n    data,\n) where {D, P, NS, EH, EV, EB, SC} =\n    DGColumnBandedMatrix{D, P, NS, EH, EV, EB, SC, typeof(data)}(data)\nBase.eltype(A::DGColumnBandedMatrix) = eltype(A.data)\nBase.size(A::DGColumnBandedMatrix) = size(A.data)\ndimensionality(::DGColumnBandedMatrix{D}) where {D} = D\npolynomialorders(::DGColumnBandedMatrix{D, P}) where {D, P} = P\n# polynomialorders is polynomial orders P, which  is a tuple,\n# vertical_polynomialorder is the vertical polynomial order\nvertical_polynomialorder(::DGColumnBandedMatrix{D, P}) where {D, P} = P[end]\nnum_state(::DGColumnBandedMatrix{D, P, NS}) where {D, P, NS} = NS\nnum_horz_elem(::DGColumnBandedMatrix{D, P, NS, EH}) where {D, P, NS, EH} = EH\nnum_vert_elem(\n    ::DGColumnBandedMatrix{D, P, NS, EH, EV},\n) where {D, P, NS, EH, EV} = EV\nelem_band(\n    ::DGColumnBandedMatrix{D, P, NS, EH, EV, EB},\n) where {D, P, NS, EH, EV, EB} = EB\nsingle_column(\n    ::DGColumnBandedMatrix{D, P, NS, EH, EV, EB, SC},\n) where {D, P, NS, EH, EV, EB, SC} = SC\n\n# DG: lower_bandwidth is Nq_v*nstate * eband - 1, (does not include itself) \n# eband = 1 for inviscid, since nodal point at the face communicate to the overlaping point to its neighbor\n# and other points only communicate to points in the same element\n# eband = 2 for visous\n#\n# FV: lower_bandwidth is nstate * (stencil_width + 1 + 1) - 1,  \n# since the reconstruction states depend on stencil_width points on each side, \n# and the flux depends on stencil_width + 1 points on each side\n# eband = (stencil_width + 2) for inviscid\n# eband = max{ (stencil_width + 2), 3} for viscous, \n# since the viscous flux is computed by applying first order FD twice\n#  \n# The lower_bandwidth ls formulated as Nq_v*nstate * eband - 1\nlower_bandwidth(N, nstate, eband) = (N + 1) * nstate * eband - 1\nlower_bandwidth(A::DGColumnBandedMatrix) =\n    lower_bandwidth(vertical_polynomialorder(A), num_state(A), elem_band(A))\nupper_bandwidth(N, nstate, eband) = lower_bandwidth(N, nstate, eband)\nupper_bandwidth(A::DGColumnBandedMatrix) =\n    upper_bandwidth(vertical_polynomialorder(A), num_state(A), elem_band(A))\nBase.reshape(A::DGColumnBandedMatrix, args...) =\n    DGColumnBandedMatrix(A, reshape(A.data, args...))\nAdapt.adapt_structure(to, A::DGColumnBandedMatrix) =\n    DGColumnBandedMatrix(A, adapt(to, A.data))\n\n\nBase.@propagate_inbounds function Base.getindex(A::DGColumnBandedMatrix, I...)\n    return A.data[I...]\nend\nBase.@propagate_inbounds function Base.setindex!(\n    A::DGColumnBandedMatrix,\n    val,\n    I...,\n)\n    A.data[I...] = val\nend\n\nfunction prefactorize(op, solver::AbstractColumnLUSolver, Q, args...)\n    dg = op.f!\n\n    # TODO: can we get away with just passing the grid?\n    A = banded_matrix(\n        op,\n        dg,\n        similar(Q),\n        similar(Q),\n        args...;\n        single_column = typeof(solver) <: SingleColumnLU,\n    )\n\n    band_lu!(A)\n\n    ColumnwiseLU(A)\nend\n\nfunction linearsolve!(\n    linop,\n    clu::ColumnwiseLU,\n    ::AbstractColumnLUSolver,\n    Q,\n    Qrhs,\n    args...,\n)\n    A = clu.A\n    Q .= Qrhs\n\n    band_forward!(Q, A)\n    band_back!(Q, A)\nend\n\n\"\"\"\n    band_lu!(A)\n\n\"\"\"\nfunction band_lu!(A)\n    device = array_device(A.data)\n\n    nstate = num_state(A)\n    Nq = polynomialorders(A) .+ 1\n    @inbounds Nq_h = Nq[1]\n    @inbounds Nqj = dimensionality(A) == 2 ? 1 : Nq[2]\n    nhorzelem = num_horz_elem(A)\n\n    groupsize = (Nq_h, Nqj)\n    ndrange = (nhorzelem * Nq_h, Nqj)\n\n    if single_column(A)\n        # single column case\n        #\n        # TODO Would it be faster to copy the matrix to the host and factorize it\n        # there?\n        groupsize = (1, 1)\n        ndrange = groupsize\n        A = reshape(A, 1, 1, size(A)..., 1)\n    end\n\n    event = Event(device)\n    event = band_lu_kernel!(device, groupsize)(\n        A,\n        ndrange = ndrange,\n        dependencies = (event,),\n    )\n    wait(device, event)\nend\n\nfunction band_forward!(Q, A)\n    device = array_device(Q)\n\n    Nq = polynomialorders(A) .+ 1\n    @inbounds Nq_h = Nq[1]\n    @inbounds Nqj = dimensionality(A) == 2 ? 1 : Nq[2]\n    nhorzelem = num_horz_elem(A)\n\n    event = Event(device)\n    event = band_forward_kernel!(device, (Nq_h, Nqj))(\n        Q.data,\n        A,\n        ndrange = (nhorzelem * Nq_h, Nqj),\n        dependencies = (event,),\n    )\n    wait(device, event)\nend\n\nfunction band_back!(Q, A)\n    device = array_device(Q)\n\n    Nq = polynomialorders(A) .+ 1\n    @inbounds Nq_h = Nq[1]\n    @inbounds Nqj = dimensionality(A) == 2 ? 1 : Nq[2]\n    nhorzelem = num_horz_elem(A)\n\n    event = Event(device)\n    event = band_back_kernel!(device, (Nq_h, Nqj))(\n        Q.data,\n        A,\n        ndrange = (nhorzelem * Nq_h, Nqj),\n        dependencies = (event,),\n    )\n    wait(device, event)\nend\n\n\n\"\"\"\n    banded_matrix(\n        dg::SpaceDiscretization,\n        Q::MPIStateArray = MPIStateArray(dg),\n        dQ::MPIStateArray = MPIStateArray(dg);\n        single_column = false,\n    )\n\nForms the banded matrices for each the column operator defined by the `SpaceDiscretization`\ndg.  If `single_column=false` then a banded matrix is stored for each column and\nif `single_column=true` only the banded matrix associated with the first column\nof the first element is stored. The bandwidth of the DG column banded matrix is\n`p = q = (vertical_polynomial + 1) * nstate * eband - 1`  with `p` and `q` being\nthe upper and lower bandwidths.\n\nThe banded matrices are stored in the LAPACK band storage format\n<https://www.netlib.org/lapack/lug/node124.html>.\n\nThe banded matrices are returned as an arrays where the array type matches that\nof `Q`. If `single_column=false` then the returned array has 5 dimensions, which\nare:\n- first horizontal column index\n- second horizontal column index\n- band index (-q:p)\n- vertical DOF index with state `s`, vertical DOF index `k`, and vertical\n  element `ev` mapping to `s + nstate * (k - 1) + nstate * nvertelem * (ev - 1)`\n- horizontal element index\n\nIf the `single_column=true` then the returned array has 2 dimensions which are\nthe band index and the vertical DOF index.\n\"\"\"\nfunction banded_matrix(\n    dg::SpaceDiscretization,\n    Q::MPIStateArray = MPIStateArray(dg),\n    dQ::MPIStateArray = MPIStateArray(dg);\n    single_column = false,\n)\n    banded_matrix(\n        (dQ, Q) -> dg(dQ, Q, nothing, 0; increment = false),\n        dg,\n        Q,\n        dQ;\n        single_column = single_column,\n    )\nend\n\n\"\"\"\n    banded_matrix(\n        f!,\n        dg::SpaceDiscretization,\n        Q::MPIStateArray = MPIStateArray(dg),\n        dQ::MPIStateArray = MPIStateArray(dg),\n        args...;\n        single_column = false,\n    )\n\nForms the banded matrices for each the column operator defined by the linear\noperator `f!` which is assumed to have the same banded structure as the\n`SpaceDiscretization` dg.  If `single_column=false` then a banded matrix is stored for each\ncolumn and if `single_column=true` only the banded matrix associated with the\nfirst column of the first element is stored. The bandwidth of the DG column\nbanded matrix is `p = q = (vertical_polynomial + 1) * nstate * eband - 1` with\n`p` and `q` being the upper and lower bandwidths.\n\nThe banded matrices are stored in the LAPACK band storage format\n<https://www.netlib.org/lapack/lug/node124.html>.\n\nThe banded matrices are returned as an arrays where the array type matches that\nof `Q`. If `single_column=false` then the returned array has 5 dimensions, which\nare:\n- first horizontal column index\n- second horizontal column index\n- band index (-q:p)\n- vertical DOF index with state `s`, vertical DOF index `k`, and vertical\n  element `ev` mapping to `s + nstate * (k - 1) + nstate * nvertelem * (ev - 1)`\n- horizontal element index\n\nIf the `single_column=true` then the returned array has 2 dimensions which are\nthe band index and the vertical DOF index.\n\nHere `args` are passed to `f!`.\n\"\"\"\nfunction banded_matrix(\n    f!,\n    dg::SpaceDiscretization,\n    Q::MPIStateArray = MPIStateArray(dg),\n    dQ::MPIStateArray = MPIStateArray(dg),\n    args...;\n    single_column = false,\n)\n    # Initialize banded matrix data structure\n    A = empty_banded_matrix(dg, Q; single_column = single_column)\n\n    # Populate matrix with data\n    update_banded_matrix!(\n        A,\n        f!,\n        dg,\n        Q,\n        dQ,\n        args...;\n        single_column = single_column,\n    )\n\n    A\nend\n\n\"\"\"\n    empty_banded_matrix(\n        dg::SpaceDiscretization,\n        Q::MPIStateArray;\n        single_column = false,\n    )\n\nInitializes an empty banded matrix stored in the LAPACK band storage format\n<https://www.netlib.org/lapack/lug/node124.html>.\n\"\"\"\nfunction empty_banded_matrix(\n    dg::SpaceDiscretization,\n    Q::MPIStateArray;\n    single_column = false,\n)\n    bl = dg.balance_law\n    grid = dg.grid\n    topology = grid.topology\n    @assert isstacked(topology)\n    @assert typeof(dg.direction) <: VerticalDirection\n\n    FT = eltype(Q.data)\n    device = array_device(Q)\n\n    nstate = number_states(bl, Prognostic())\n    N = polynomialorders(grid)\n    dim = dimensionality(grid)\n    Nq = N .+ 1\n    @inbounds begin\n        Nq_h = Nq[1]\n        Nqj = dim == 2 ? 1 : Nq[2]\n        Nq_v = Nq[dim]\n    end\n\n\n    eband =\n        (typeof(dg) <: DGModel) ?\n        (number_states(bl, GradientFlux()) == 0 ? 1 : 2) :\n        (\n            number_states(bl, GradientFlux()) == 0 ?\n            width(dg.fv_reconstruction) + 2 :\n            max(width(dg.fv_reconstruction) + 2, 3)\n        ) # else: DGFVModel\n\n    p = lower_bandwidth(N[dim], nstate, eband)\n    q = upper_bandwidth(N[dim], nstate, eband)\n\n    nrealelem = length(topology.realelems)\n    nvertelem = topology.stacksize\n    nhorzelem = div(nrealelem, nvertelem)\n\n    # first horizontal DOF index\n    # second horizontal DOF index\n    # band index -q:p\n    # vertical DOF index\n    # horizontal element index\n    A = if single_column\n        similar(Q.data, p + q + 1, Nq_v * nstate * nvertelem)\n    else\n        similar(Q.data, Nq_h, Nqj, p + q + 1, Nq_v * nstate * nvertelem, nhorzelem)\n    end\n    fill!(A, zero(FT))\n\n    A = DGColumnBandedMatrix{\n        dim,\n        N,\n        nstate,\n        nhorzelem,\n        nvertelem,\n        eband,\n        single_column,\n        typeof(A),\n    }(\n        A,\n    )\n\n    A\nend\n\n\"\"\"\n    update_banded_matrix!(\n        A::DGColumnBandedMatrix,\n        f!,\n        dg::SpaceDiscretization,\n        Q::MPIStateArray = MPIStateArray(dg),\n        dQ::MPIStateArray = MPIStateArray(dg),\n        args...;\n        single_column = false,\n    )\n\nUpdates the banded matrices for each the column operator defined by the linear\noperator `f!` which is assumed to have the same banded structure as the\n`SpaceDiscretization` dg.  If `single_column=false` then a banded matrix is stored for each\ncolumn and if `single_column=true` only the banded matrix associated with the\nfirst column of the first element is stored. The bandwidth of the DG column\nbanded matrix is `p = q = (vertical_polynomial + 1) * nstate * eband - 1`  with\n`p` and `q` being the upper and lower bandwidths.\n\nHere `args` are passed to `f!`.\n\"\"\"\nfunction update_banded_matrix!(\n    A::DGColumnBandedMatrix,\n    f!,\n    dg::SpaceDiscretization,\n    Q::MPIStateArray = MPIStateArray(dg),\n    dQ::MPIStateArray = MPIStateArray(dg),\n    args...;\n    single_column = false,\n)\n    bl = dg.balance_law\n    grid = dg.grid\n    topology = grid.topology\n    @assert isstacked(topology)\n    @assert typeof(dg.direction) <: VerticalDirection\n\n    FT = eltype(Q.data)\n    device = array_device(Q)\n\n    nstate = number_states(bl, Prognostic())\n    N = polynomialorders(grid)\n    dim = dimensionality(grid)\n    Nq = N .+ 1\n    @inbounds begin\n        Nq_h = Nq[1]\n        Nqj = dim == 2 ? 1 : Nq[2]\n        Nq_v = Nq[dim]\n    end\n\n    eband = elem_band(A)\n\n    nrealelem = length(topology.realelems)\n    nvertelem = topology.stacksize\n    nhorzelem = div(nrealelem, nvertelem)\n\n    # loop through all DOFs in a column and compute the matrix column\n    # loop only the first min(nvertelem, 2eband+1) elements\n    # in each element loop, updating these columns correspond\n    # to elements (ev :2eband+1 : nvertelem)\n    for ev in 1:min(nvertelem, 2eband + 1)\n        for s in 1:nstate\n            for k in 1:Nq_v\n                # Set a single 1 per column and rest 0\n                event = Event(device)\n                event = kernel_set_banded_data!(device, (Nq_h, Nqj, Nq_v))(\n                    Q.data,\n                    A,\n                    k,\n                    s,\n                    ev,\n                    1:nhorzelem,\n                    1:nvertelem;\n                    ndrange = (nvertelem * Nq_h, nhorzelem * Nqj, Nq_v),\n                    dependencies = (event,),\n                )\n                wait(device, event)\n\n                # Get the matrix column\n                f!(dQ, Q, args...)\n\n                # Store the banded matrix\n                event = Event(device)\n                event = kernel_set_banded_matrix!(device, (Nq_h, Nqj, Nq_v))(\n                    A,\n                    dQ.data,\n                    k,\n                    s,\n                    ev,\n                    1:nhorzelem,\n                    (-eband):eband;\n                    ndrange = ((2eband + 1) * Nq_h, nhorzelem * Nqj, Nq_v),\n                    dependencies = (event,),\n                )\n                wait(device, event)\n            end\n        end\n    end\nend\n\n\"\"\"\n    banded_matrix_vector_product!(\n        A,\n        dQ::MPIStateArray,\n        Q::MPIStateArray\n    )\n\nCompute a matrix vector product `dQ = A * Q` where `A` is assumed to be a matrix\ncreated using the `banded_matrix` function.\n\nThis function is primarily for testing purposes.\n\"\"\"\nfunction banded_matrix_vector_product!(A, dQ::MPIStateArray, Q::MPIStateArray)\n    device = array_device(Q)\n\n    Nq = polynomialorders(A) .+ 1\n    @inbounds begin\n        Nq_h = Nq[1]\n        Nqj = dimensionality(A) == 2 ? 1 : Nq[2]\n        Nq_v = Nq[end]\n    end\n    nvertelem = num_vert_elem(A)\n    nhorzelem = num_horz_elem(A)\n\n    event = Event(device)\n    event = kernel_banded_matrix_vector_product!(device, (Nq_h, Nqj, Nq_v))(\n        dQ.data,\n        A,\n        Q.data,\n        1:nhorzelem,\n        1:nvertelem;\n        ndrange = (nvertelem * Nq_h, nhorzelem * Nqj, Nq_v),\n        dependencies = (event,),\n    )\n    wait(device, event)\nend\n\nusing StaticArrays\nusing KernelAbstractions.Extras: @unroll\n\n@doc \"\"\"\n    band_lu_kernel!(A)\n\nThis performs Band Gaussian Elimination (Algorithm 4.3.1 of Golub and Van\nLoan).  The array `A` contains a band matrix for each vertical column.  For\nexample, `A[i, j, :, :, h]`, is the band matrix associated with the `(i, j)`th\ndegree of freedom in the horizontal element `h`.\n\nEach `n` by `n` band matrix is assumed to have upper bandwidth `q` and lower\nbandwidth `p` where `n = nstate * Nq * nvertelem` and\n`p = q = (vertical_polynomial + 1) * nstate * eband - 1` \n\nEach band matrix is stored in the [LAPACK band storage](https://www.netlib.org/lapack/lug/node124.html).\nFor example the band matrix\n\n    B = [b₁₁ b₁₂ 0   0   0\n         b₂₁ b₂₂ b₂₃ 0   0\n         b₃₁ b₃₂ b₃₃ b₃₄ 0\n         0   b₄₂ b₄₃ b₄₄ b₄₅\n         0   0   b₅₃ b₅₄ b₅₅]\n\nis stored as\n\n    B = [0   b₁₂ b₂₃ b₃₄ b₄₅\n         b₁₁ b₂₂ b₃₃ b₄₄ b₅₅\n         b₂₁ b₃₂ b₄₃ b₅₄ 0\n         b₃₁ b₄₂ b₅₃ 0   0]\n\n### Reference\n\n - [GolubVanLoan2013](@cite)\n\n\"\"\" band_lu_kernel!\n@kernel function band_lu_kernel!(A)\n    @uniform begin\n        Nq = polynomialorders(A) .+ 1\n        @inbounds Nq_h = Nq[1]\n        @inbounds Nq_v = Nq[end]\n        nstate = num_state(A)\n        nvertelem = num_vert_elem(A)\n        n = nstate * Nq_v * nvertelem\n        p, q = lower_bandwidth(A), upper_bandwidth(A)\n    end\n\n    h = @index(Group, Linear)\n    i, j = @index(Local, NTuple)\n\n    @inbounds begin\n        for v in 1:nvertelem\n            for k in 1:Nq_v\n                for s in 1:nstate\n                    kk = s + (k - 1) * nstate + (v - 1) * nstate * Nq_v\n\n                    Aq = A[i, j, q + 1, kk, h]\n                    for ii in 1:p\n                        A[i, j, q + ii + 1, kk, h] /= Aq\n                    end\n\n                    for jj in 1:q\n                        if jj + kk ≤ n\n                            Ajj = A[i, j, q - jj + 1, jj + kk, h]\n                            for ii in 1:p\n                                A[i, j, q + ii - jj + 1, jj + kk, h] -=\n                                    A[i, j, q + ii + 1, kk, h] * Ajj\n                            end\n                        end\n                    end\n                end\n            end\n        end\n    end\nend\n\n@doc \"\"\"\n    band_forward_kernel!(b, LU)\n\nThis performs Band Forward Substitution (Algorithm 4.3.2 of Golub and Van\nLoan), i.e., the right-hand side `b` is replaced with the solution of `L*x=b`.\n\nThe array `b` is of the size `(Nq * Nqj * Nq, nstate, nvertelem * nhorzelem)`.\n\nThe LU-factorization array `LU` contains a single band matrix or one\nfor each vertical column, see [`band_lu!`](@ref).\n\nEach `n` by `n` band matrix is assumed to have upper bandwidth `q` and lower\nbandwidth `p` where `n = nstate * Nq * nvertelem` and\n`p = q = (vertical_polynomial + 1) * nstate * eband - 1` \n\n### Reference\n\n - [GolubVanLoan2013](@cite)\n\n\"\"\" band_forward_kernel!\n@kernel function band_forward_kernel!(b, LU)\n    @uniform begin\n        FT = eltype(b)\n        nstate = num_state(LU)\n        Nq = polynomialorders(LU) .+ 1\n        @inbounds begin\n            Nq_h = Nq[1]\n            Nqj = dimensionality(LU) == 2 ? 1 : Nq[2]\n            Nq_v = Nq[end]\n        end\n        nvertelem = num_vert_elem(LU)\n        n = nstate * Nq_v * nvertelem\n        eband = elem_band(LU)\n        p, q = lower_bandwidth(LU), upper_bandwidth(LU)\n\n        l_b = MArray{Tuple{p + 1}, FT}(undef)\n    end\n\n    h = @index(Group, Linear)\n    i, j = @index(Local, NTuple)\n\n    @inbounds begin\n        @unroll for v in 1:eband\n            @unroll for k in 1:Nq_v\n                @unroll for s in 1:nstate\n                    ijk = i + Nqj * (j - 1) + Nq_h * Nqj * (k - 1)\n                    ee = v + nvertelem * (h - 1)\n                    ii = s + (k - 1) * nstate + (v - 1) * nstate * Nq_v\n                    l_b[ii] = nvertelem ≥ v ? b[ijk, s, ee] : zero(FT)\n                end\n            end\n        end\n\n        for v in 1:nvertelem\n            @unroll for k in 1:Nq_v\n                @unroll for s in 1:nstate\n                    jj = s + (k - 1) * nstate + (v - 1) * nstate * Nq_v\n\n                    @unroll for ii in 2:(p + 1)\n                        Lii =\n                            single_column(LU) ? LU[ii + q, jj] :\n                            LU[i, j, ii + q, jj, h]\n                        l_b[ii] -= Lii * l_b[1]\n                    end\n\n                    ijk = i + Nqj * (j - 1) + Nq_h * Nqj * (k - 1)\n                    ee = v + nvertelem * (h - 1)\n\n                    b[ijk, s, ee] = l_b[1]\n\n                    @unroll for ii in 1:p\n                        l_b[ii] = l_b[ii + 1]\n                    end\n\n                    if jj + p < n\n                        (idx, si) = fldmod1(jj + p + 1, nstate)\n                        (vi, ki) = fldmod1(idx, Nq_v)\n\n                        ijk = i + Nqj * (j - 1) + Nq_h * Nqj * (ki - 1)\n                        ee = vi + nvertelem * (h - 1)\n\n                        l_b[p + 1] = b[ijk, si, ee]\n                    end\n                end\n            end\n        end\n    end\nend\n\n@doc \"\"\"\n    band_back_kernel!(b, LU)\n\nThis performs Band Back Substitution (Algorithm 4.3.3 of Golub and Van\nLoan), i.e., the right-hand side `b` is replaced with the solution of `U*x=b`.\n\nThe array `b` is of the size `(Nq * Nqj * Nq, nstate, nvertelem * nhorzelem)`.\n\nThe LU-factorization array `LU` contains a single band matrix or one\nfor each vertical column, see [`band_lu!`](@ref).\n\nEach `n` by `n` band matrix is assumed to have upper bandwidth `q` and lower\nbandwidth `p` where `n = nstate * Nq * nvertelem` and\n`p = q = (vertical_polynomial + 1) * nstate * eband - 1` \n\n### Reference\n\n - [GolubVanLoan2013](@cite)\n\n\"\"\" band_back_kernel!\n@kernel function band_back_kernel!(b, LU)\n    @uniform begin\n        FT = eltype(b)\n        nstate = num_state(LU)\n        Nq = polynomialorders(LU) .+ 1\n        @inbounds begin\n            Nq_h = Nq[1]\n            Nqj = dimensionality(LU) == 2 ? 1 : Nq[2]\n            Nq_v = Nq[end]\n        end\n        nvertelem = num_vert_elem(LU)\n        n = nstate * Nq_h * nvertelem\n        q = upper_bandwidth(LU)\n        eband = elem_band(LU)\n\n        l_b = MArray{Tuple{q + 1}, FT}(undef)\n    end\n\n    h = @index(Group, Linear)\n    i, j = @index(Local, NTuple)\n\n    @inbounds begin\n        @unroll for v in nvertelem:-1:(nvertelem - eband + 1)\n            @unroll for k in Nq_v:-1:1\n                @unroll for s in nstate:-1:1\n                    vi = eband - nvertelem + v\n                    ii = s + (k - 1) * nstate + (vi - 1) * nstate * Nq_v\n\n                    ijk = i + Nqj * (j - 1) + Nq_h * Nqj * (k - 1)\n                    ee = v + nvertelem * (h - 1)\n\n                    l_b[ii] = b[ijk, s, ee]\n                end\n            end\n        end\n\n        for v in nvertelem:-1:1\n            @unroll for k in Nq_v:-1:1\n                @unroll for s in nstate:-1:1\n                    jj = s + (k - 1) * nstate + (v - 1) * nstate * Nq_v\n\n                    l_b[q + 1] /=\n                        single_column(LU) ? LU[q + 1, jj] :\n                        LU[i, j, q + 1, jj, h]\n\n                    @unroll for ii in 1:q\n                        Uii =\n                            single_column(LU) ? LU[ii, jj] : LU[i, j, ii, jj, h]\n                        l_b[ii] -= Uii * l_b[q + 1]\n                    end\n\n                    ijk = i + Nqj * (j - 1) + Nq_h * Nqj * (k - 1)\n                    ee = v + nvertelem * (h - 1)\n\n                    b[ijk, s, ee] = l_b[q + 1]\n\n                    @unroll for ii in q:-1:1\n                        l_b[ii + 1] = l_b[ii]\n                    end\n\n                    if jj - q > 1\n                        (idx, si) = fldmod1(jj - q - 1, nstate)\n                        (vi, ki) = fldmod1(idx, Nq_v)\n\n                        ijk = i + Nqj * (j - 1) + Nq_h * Nqj * (ki - 1)\n                        ee = vi + nvertelem * (h - 1)\n\n                        l_b[1] = b[ijk, si, ee]\n                    end\n                end\n            end\n        end\n    end\nend\n\n\n\n### TODO: Document this\n@kernel function kernel_set_banded_data!(\n    Q,\n    A::DGColumnBandedMatrix,\n    kin,\n    sin,\n    evin0,\n    helems,\n    velems,\n)\n    @uniform begin\n        FT = eltype(Q)\n        nstate = num_state(A)\n        Nq = polynomialorders(A) .+ 1\n        @inbounds begin\n            Nq_h = Nq[1]\n            Nq_v = Nq[end]\n            Nqj = dimensionality(A) == 2 ? 1 : Nq[2]\n        end\n        nvertelem = num_vert_elem(A)\n        eband = elem_band(A)\n    end\n\n    ev, eh = @index(Group, NTuple)\n    i, j, k = @index(Local, NTuple)\n\n    @inbounds begin\n        e = ev + (eh - 1) * nvertelem\n        ijk = i + Nqj * (j - 1) + Nq_h * Nqj * (k - 1)\n        @unroll for s in 1:nstate\n            if k == kin && s == sin && ((ev - evin0) % (2eband + 1) == 0)\n                Q[ijk, s, e] = 1\n            else\n                Q[ijk, s, e] = 0\n            end\n        end\n    end\nend\n\n\n@kernel function kernel_set_banded_matrix!(\n    A,\n    dQ,\n    kin,\n    sin,\n    evin0,\n    helems,\n    vpelems,\n)\n    @uniform begin\n        FT = eltype(A)\n        nstate = num_state(A)\n        Nq = polynomialorders(A) .+ 1\n        @inbounds begin\n            Nq_h = Nq[1]\n            Nqj = dimensionality(A) == 2 ? 1 : Nq[2]\n            Nq_v = Nq[end]\n        end\n        nvertelem = num_vert_elem(A)\n        p = lower_bandwidth(A)\n        q = upper_bandwidth(A)\n\n        eband = elem_band(A)\n        eshift = elem_band(A) + 1\n    end\n\n    ep, eh = @index(Group, NTuple)\n    ep = ep - eshift\n    i, j, k = @index(Local, NTuple)\n\n    for evin in evin0:(2eband + 1):nvertelem\n        # sin, kin, evin are the state, vertical dof, and vert element we are\n        # handling\n        # column index of matrix\n        jj = sin + (kin - 1) * nstate + (evin - 1) * nstate * Nq_v\n\n        # one thread is launch for dof that might contribute to column jj's band\n        @inbounds begin\n            # ep is the shift we need to add to evin to get the element we need to\n            # consider\n            ev = ep + evin\n            if 1 ≤ ev ≤ nvertelem\n                e = ev + (eh - 1) * nvertelem\n                ijk = i + Nqj * (j - 1) + Nq_h * Nqj * (k - 1)\n                @unroll for s in 1:nstate\n                    # row index of matrix\n                    ii = s + (k - 1) * nstate + (ev - 1) * nstate * Nq_v\n                    # row band index\n                    bb = ii - jj\n                    # make sure we're in the bandwidth\n                    if -q ≤ bb ≤ p\n                        if !single_column(A)\n                            A[i, j, bb + q + 1, jj, eh] = dQ[ijk, s, e]\n                        else\n                            if (i, j, eh) == (1, 1, 1)\n                                A[bb + q + 1, jj] = dQ[ijk, s, e]\n                            end\n                        end\n                    end\n                end\n            end\n        end\n    end\nend\n\n@kernel function kernel_banded_matrix_vector_product!(dQ, A, Q, helems, velems)\n    @uniform begin\n        FT = eltype(A)\n        nstate = num_state(A)\n\n        Nq = polynomialorders(A) .+ 1\n        @inbounds begin\n            Nq_h = Nq[1]\n            Nq_v = Nq[end]\n            Nqj = dimensionality(A) == 2 ? 1 : Nq[2]\n        end\n        eband = elem_band(A)\n        nvertelem = num_vert_elem(A)\n        p = lower_bandwidth(A)\n        q = upper_bandwidth(A)\n\n        elo = eband - 1\n        eup = eband - 1\n    end\n\n    ev, eh = @index(Group, NTuple)\n    i, j, k = @index(Local, NTuple)\n\n    # matrix row loops\n    @inbounds begin\n        e = ev + nvertelem * (eh - 1)\n        @unroll for s in 1:nstate\n            Ax = -zero(FT)\n            ii = s + (k - 1) * nstate + (ev - 1) * nstate * Nq_v\n\n            # banded matrix column loops\n            @unroll for evv in max(1, ev - elo):min(nvertelem, ev + eup)\n                ee = evv + nvertelem * (eh - 1)\n                @unroll for kk in 1:Nq_v\n                    ijk = i + Nqj * (j - 1) + Nq_h * Nqj * (kk - 1)\n                    @unroll for ss in 1:nstate\n                        jj = ss + (kk - 1) * nstate + (evv - 1) * nstate * Nq_v\n                        bb = ii - jj\n                        if -q ≤ bb ≤ p\n                            if !single_column(A)\n                                Ax +=\n                                    A[i, j, bb + q + 1, jj, eh] * Q[ijk, ss, ee]\n                            else\n                                Ax += A[bb + q + 1, jj] * Q[ijk, ss, ee]\n                            end\n                        end\n                    end\n                end\n            end\n            ijk = i + Nqj * (j - 1) + Nq_h * Nqj * (k - 1)\n            dQ[ijk, s, e] = Ax\n        end\n    end\nend\n"
  },
  {
    "path": "src/Numerics/SystemSolvers/conjugate_gradient_solver.jl",
    "content": "#### Conjugate Gradient solver\n\nexport ConjugateGradient\n\nstruct ConjugateGradient{AT1, AT2, FT, RD, RT, IT} <:\n       AbstractIterativeSystemSolver\n    # tolerances (2)\n    rtol::FT\n    atol::FT\n    # arrays of size reshape_tuple (7)\n    r0::AT1\n    z0::AT1\n    p0::AT1\n    r1::AT1\n    z1::AT1\n    p1::AT1\n    Lp::AT1\n    # arrays of size(MPIStateArray) which are aliased to two of the previous dimensions (2)\n    alias_p0::AT2\n    alias_Lp::AT2\n    # reduction dimension (1)\n    dims::RD\n    # reshape dimension (1)\n    reshape_tuple::RT\n    # maximum number of iterations (1)\n    max_iter::IT\n\nend\n\n# Define the outer constructor for the ConjugateGradient struct\n\"\"\"\n    ConjugateGradient(\n        Q::AT;\n        rtol = eps(eltype(Q)),\n        atol = eps(eltype(Q)),\n        max_iter = length(Q),\n        dims = :,\n        reshape_tuple = size(Q),\n    ) where {AT}\n\n# ConjugateGradient\n\n# Description\n- Outer constructor for the ConjugateGradient struct\n\n# Arguments\n- `Q`:(array). The kind of object that linearoperator! acts on.\n\n# Keyword Arguments\n- `rtol`: (float). relative tolerance\n- `atol`: (float). absolute tolerance\n- `dims`: (tuple or : ). the dimensions to reduce over\n- `reshape_tuple`: (tuple). the dimensions that the conjugate gradient solver operators over\n\n# Comment\n- The reshape tuple is necessary in case the linearoperator! is defined over vectors of a different size as compared to what plays nicely with the dimension reduction in the ConjugateGradient. It also allows the user to define preconditioners over arrays that are more convenienently shaped.\n\n# Return\n- ConjugateGradient struct\n\"\"\"\nfunction ConjugateGradient(\n    Q::AT;\n    rtol = eps(eltype(Q)),\n    atol = eps(eltype(Q)),\n    max_iter = length(Q),\n    dims = :,\n    reshape_tuple = size(Q),\n) where {AT}\n\n    # allocate arrays (5)\n    r0 = reshape(similar(Q), reshape_tuple)\n    z0 = reshape(similar(Q), reshape_tuple)\n    r1 = reshape(similar(Q), reshape_tuple)\n    z1 = reshape(similar(Q), reshape_tuple)\n    p1 = reshape(similar(Q), reshape_tuple)\n    # allocate array of different shape (2)\n    alias_p0 = similar(Q)\n    alias_Lp = similar(Q)\n    # allocate create aliased arrays (2)\n    p0 = reshape(alias_p0, reshape_tuple)\n    Lp = reshape(alias_Lp, reshape_tuple)\n\n    container = [\n        rtol,\n        atol,\n        r0,\n        z0,\n        p0,\n        r1,\n        z1,\n        p1,\n        Lp,\n        alias_p0,\n        alias_Lp,\n        dims,\n        reshape_tuple,\n        max_iter,\n    ]\n    # create struct instance by splatting the container into the default constructor\n    return ConjugateGradient{\n        typeof(z0),\n        typeof(Q),\n        eltype(Q),\n        typeof(dims),\n        typeof(reshape_tuple),\n        typeof(max_iter),\n    }(container...)\nend\n\n# Define the outer constructor for the ConjugateGradient struct\n\"\"\"\n    ConjugateGradient(\n        Q::MPIStateArray;\n        rtol = eps(eltype(Q)),\n        atol = eps(eltype(Q)),\n        max_iter = length(Q),\n        dims = :,\n        reshape_tuple = size(Q),\n    )\n\n# Description\nOuter constructor for the ConjugateGradient struct with MPIStateArrays. THIS IS A HACK DUE TO RESHAPE FUNCTIONALITY ON MPISTATEARRAYS.\n\n# Arguments\n- `Q`:(array). The kind of object that linearoperator! acts on.\n\n# Keyword Arguments\n- `rtol`: (float). relative tolerance\n- `atol`: (float). absolute tolerance\n- `dims`: (tuple or : ). the dimensions to reduce over\n- `reshape_tuple`: (tuple). the dimensions that the conjugate gradient solver operators over\n\n# Comment\n- The reshape tuple is necessary in case the linearoperator! is defined over vectors of a different size as compared to what plays nicely with the dimension reduction in the ConjugateGradient. It also allows the user to define preconditioners over arrays that are more convenienently shaped.\n\n# Return\n- ConjugateGradient struct\n\"\"\"\nfunction ConjugateGradient(\n    Q::MPIStateArray;\n    rtol = eps(eltype(Q)),\n    atol = eps(eltype(Q)),\n    max_iter = length(Q),\n    dims = :,\n    reshape_tuple = size(Q),\n)\n\n    # create empty container for pushing struct objects\n    # allocate arrays (5)\n    r0 = reshape(similar(Q.data), reshape_tuple)\n    z0 = reshape(similar(Q.data), reshape_tuple)\n    r1 = reshape(similar(Q.data), reshape_tuple)\n    z1 = reshape(similar(Q.data), reshape_tuple)\n    p1 = reshape(similar(Q.data), reshape_tuple)\n    # allocate array of different shape (2)\n    alias_p0 = similar(Q)\n    alias_Lp = similar(Q)\n    # allocate create aliased arrays (2)\n    p0 = reshape(alias_p0.data, reshape_tuple)\n    Lp = reshape(alias_Lp.data, reshape_tuple)\n    container = [\n        rtol,\n        atol,\n        r0,\n        z0,\n        p0,\n        r1,\n        z1,\n        p1,\n        Lp,\n        alias_p0,\n        alias_Lp,\n        dims,\n        reshape_tuple,\n        max_iter,\n    ]\n    # create struct instance by splatting the container into the default constructor\n    return ConjugateGradient{\n        typeof(z0),\n        typeof(Q),\n        eltype(Q),\n        typeof(dims),\n        typeof(reshape_tuple),\n        typeof(max_iter),\n    }(container...)\nend\n\n\n\"\"\"\n    initialize!(\n        linearoperator!,\n        Q,\n        Qrhs,\n        solver::ConjugateGradient,\n        args...,\n    )\n\n# Description\n\n- This function initializes the iterative solver. It is called as part of the AbstractIterativeSystemSolver routine. SEE CODEREF for documentation on AbstractIterativeSystemSolver\n\n# Arguments\n\n- `linearoperator!`: (function). This applies the predefined linear operator on an array. Applies a linear operator to object \"y\" and overwrites object \"z\". The function argument i s linearoperator!(z,y, args...) and it returns nothing.\n- `Q`: (array). This is an object that linearoperator! outputs\n- `Qrhs`: (array). This is an object that linearoperator! acts on\n- `solver`: (struct). This is a scruct for dispatch, in this case for ColumnwisePreconditionedConjugateGradient\n- `args...`: (arbitrary). This is optional arguments that can be passed into linearoperator! function.\n\n# Keyword Arguments\n\n- There are no keyword arguments\n\n# Return\n- `converged`: (bool). A boolean to say whether or not the iterative solver has converged.\n- `threshold`: (float). The value of the residual for the first timestep\n\n# Comment\n- This function does nothing for conjugate gradient\n\n\"\"\"\nfunction initialize!(\n    linearoperator!,\n    Q,\n    Qrhs,\n    solver::ConjugateGradient,\n    args...,\n)\n\n    return false, Inf\nend\n\n\n\"\"\"\n    doiteration!(\n        linearoperator!,\n        preconditioner,\n        Q,\n        Qrhs,\n        solver::ConjugateGradient,\n        threshold,\n        args...;\n        applyPC! = (x, y) -> x .= y,\n    )\n\n# Description\n\n- This function enacts the iterative solver. It is called as part of the AbstractIterativeSystemSolver routine. SEE CODEREF for documentation on AbstractIterativeSystemSolver\n\n# Arguments\n\n- `linearoperator!`: (function). This applies the predefined linear operator on an array. Applies a linear operator to object \"y\" and overwrites object \"z\". It is a function with arguments linearoperator!(z,y, args...), where \"z\" gets overwritten by \"y\" and \"args...\" are additional arguments passed to the linear operator. The linear operator is assumed to return nothing.\n- `Q`: (array). This is an object that linearoperator! overwrites\n- `Qrhs`: (array). This is an object that linearoperator! acts on. This is the rhs to the linear system\n- `solver`: (struct). This is a scruct for dispatch, in this case for ConjugateGradient\n- `threshold`: (float). Either an absolute or relative tolerance\n- `applyPC!`: (function). Applies a preconditioner to objecy \"y\" and overwrites object \"z\". applyPC!(z,y)\n- `args...`: (arbitrary). This is necessary for the linearoperator! function which has a signature linearoperator!(b, x, args....)\n\n# Keyword Arguments\n\n- There are no keyword arguments\n\n# Return\n- `converged`: (bool). A boolean to say whether or not the iterative solver has converged.\n- `iteration`: (int). Iteration number for the iterative solver\n- `threshold`: (float). The value of the residual for the first timestep\n\n# Comment\n- This function does conjugate gradient\n\n\"\"\"\nfunction doiteration!(\n    linearoperator!,\n    preconditioner,\n    Q,\n    Qrhs,\n    solver::ConjugateGradient,\n    threshold,\n    args...;\n    applyPC! = (x, y) -> x .= y,\n)\n\n\n    # unroll names for convenience\n\n    rtol = solver.rtol\n    atol = solver.atol\n    residual_norm = typemax(eltype(Q))\n    dims = solver.dims\n    converged = false\n\n    max_iter = solver.max_iter\n    r0 = solver.r0\n    z0 = solver.z0\n    p0 = solver.p0\n    r1 = solver.r1\n    z1 = solver.z1\n    p1 = solver.p1\n    Lp = solver.Lp\n    alias_p0 = solver.alias_p0\n    alias_Lp = solver.alias_Lp\n    alias_Q = reshape(Q, solver.reshape_tuple)\n\n    # Smack residual by linear operator\n    linearoperator!(alias_Lp, Q, args...)\n    # make sure that arrays are of the appropriate size\n    alias_p0 .= Qrhs\n    r0 .= p0 - Lp\n    # apply the preconditioner\n    applyPC!(z0, r0)\n    # update p0\n    p0 .= z0\n\n    # TODO: FIX THIS\n    absolute_residual = maximum(sqrt.(sum(r0 .* r0, dims = dims)))\n    relative_residual =\n        absolute_residual / maximum(sqrt.(sum(Qrhs .* Qrhs, dims = :)))\n    # TODO: FIX THIS\n    if (absolute_residual <= atol) || (relative_residual <= rtol)\n        # wow! what a great guess\n        converged = true\n        return converged, 1, absolute_residual\n    end\n\n    for j in 1:max_iter\n        linearoperator!(alias_Lp, alias_p0, args...)\n\n        α = sum(r0 .* z0, dims = dims) ./ sum(p0 .* Lp, dims = dims)\n\n        # Update along preconditioned direction, (note that broadcast will indeed work as expected)\n        @. alias_Q += α * p0\n\n        @. r1 = r0 - α * Lp\n\n        # TODO: FIX THIS\n        absolute_residual = maximum(sqrt.(sum(r1 .* r1, dims = dims)))\n        relative_residual =\n            absolute_residual / maximum(sqrt.(sum(Qrhs .* Qrhs, dims = :)))\n        # TODO: FIX THIS\n        converged = false\n        if (absolute_residual <= atol) || (relative_residual <= rtol)\n            converged = true\n            return converged, j, absolute_residual\n        end\n\n        applyPC!(z1, r1)\n\n        β = sum(z1 .* r1, dims = dims) ./ sum(z0 .* r0, dims = dims)\n\n        # Update\n        @. p0 = z1 + β * p0\n        @. z0 = z1\n        @. r0 = r1\n\n    end\n\n    # TODO: FIX THIS\n    converged = true\n    return converged, max_iter, absolute_residual\nend\n\n\"\"\"\n    doiteration!(\n        linearoperator!,\n        preconditioner,\n        Q::MPIStateArray,\n        Qrhs::MPIStateArray,\n        solver::ConjugateGradient,\n        threshold,\n        args...;\n        applyPC! = (x, y) -> x .= y,\n    )\n\n# Description\n\nThis function enacts the iterative solver. It is called as part of the AbstractIterativeSystemSolver routine. SEE CODEREF for documentation on AbstractIterativeSystemSolver. THIS IS A HACK TO WORK WITH MPISTATEARRAYS. THE ISSUE IS WITH RESHAPE.\n\n# Arguments\n\n- `linearoperator!`: (function). This applies the predefined linear operator on an array. Applies a linear operator to object \"y\" and overwrites object \"z\". It is a function with arguments linearoperator!(z,y, args...), where \"z\" gets overwritten by \"y\" and \"args...\" are additional arguments passed to the linear operator. The linear operator is assumed to return nothing.\n- `Q`: (array). This is an object that linearoperator! overwrites\n- `Qrhs`: (array). This is an object that linearoperator! acts on. This is the rhs to the linear system\n- `solver`: (struct). This is a scruct for dispatch, in this case for ConjugateGradient\n- `threshold`: (float). Either an absolute or relative tolerance\n- `applyPC!`: (function). Applies a preconditioner to objecy \"y\" and overwrites object \"z\". applyPC!(z,y)\n- `args...`: (arbitrary). This is necessary for the linearoperator! function which has a signature linearoperator!(b, x, args....)\n\n# Keyword Arguments\n\n- There are no keyword arguments\n\n# Return\n- `converged`: (bool). A boolean to say whether or not the iterative solver has converged.\n- `iteration`: (int). Iteration number for the iterative solver\n- `threshold`: (float). The value of the residual for the first timestep\n\n# Comment\n- This function does conjugate gradient\n\n\"\"\"\nfunction doiteration!(\n    linearoperator!,\n    preconditioner,\n    Q::MPIStateArray,\n    Qrhs::MPIStateArray,\n    solver::ConjugateGradient,\n    threshold,\n    args...;\n    applyPC! = (x, y) -> x .= y,\n)\n\n\n    # unroll names for convenience\n\n    rtol = solver.rtol\n    atol = solver.atol\n    residual_norm = typemax(eltype(Q))\n    dims = solver.dims\n    converged = false\n\n    max_iter = solver.max_iter\n    r0 = solver.r0\n    z0 = solver.z0\n    p0 = solver.p0\n    r1 = solver.r1\n    z1 = solver.z1\n    p1 = solver.p1\n    Lp = solver.Lp\n    alias_p0 = solver.alias_p0\n    alias_Lp = solver.alias_Lp\n    alias_Q = reshape(Q.data, solver.reshape_tuple)\n\n    # Smack residual by linear operator\n    linearoperator!(alias_Lp, Q, args...)\n    # make sure that arrays are of the appropriate size\n    alias_p0 .= Qrhs.data\n    r0 .= p0 - Lp\n    # apply the preconditioner\n    applyPC!(z0, r0)\n    # update p0\n    p0 .= z0\n\n    # TODO: FIX THIS\n    absolute_residual = maximum(sqrt.(sum(r0 .* r0, dims = dims)))\n    relative_residual =\n        absolute_residual / maximum(sqrt.(sum(Qrhs .* Qrhs, dims = :)))\n    # TODO: FIX THIS\n    if (absolute_residual <= atol) || (relative_residual <= rtol)\n        # wow! what a great guess\n        converged = true\n        return converged, 1, absolute_residual\n    end\n\n    for j in 1:max_iter\n        linearoperator!(alias_Lp, alias_p0, args...)\n\n        α = sum(r0 .* z0, dims = dims) ./ sum(p0 .* Lp, dims = dims)\n\n        # Update along preconditioned direction, (note that broadcast will indeed work as expected)\n        @. alias_Q += α * p0\n\n        @. r1 = r0 - α * Lp\n\n        # TODO: FIX THIS\n        absolute_residual = maximum(sqrt.(sum(r1 .* r1, dims = dims)))\n        relative_residual =\n            absolute_residual / maximum(sqrt.(sum(Qrhs .* Qrhs, dims = :)))\n        # TODO: FIX THIS\n        converged = false\n        if (absolute_residual <= atol) || (relative_residual <= rtol)\n            converged = true\n            return converged, j, absolute_residual\n        end\n\n        applyPC!(z1, r1)\n\n        β = sum(z1 .* r1, dims = dims) ./ sum(z0 .* r0, dims = dims)\n\n        # Update\n        @. p0 = z1 + β * p0\n        @. z0 = z1\n        @. r0 = r1\n\n    end\n\n    # TODO: FIX THIS\n    converged = true\n    return converged, max_iter, absolute_residual\nend\n"
  },
  {
    "path": "src/Numerics/SystemSolvers/generalized_conjugate_residual_solver.jl",
    "content": "#### Generalized Conjugate Residual Solver\n\nexport GeneralizedConjugateResidual\n\n\"\"\"\n    GeneralizedConjugateResidual(K, Q; rtol, atol)\n\n# Conjugate Residual\n\nThis is an object for solving linear systems using an iterative Krylov method.\nThe constructor parameter `K` is the number of steps after which the algorithm\nis restarted (if it has not converged), `Q` is a reference state used only\nto allocate the solver internal state, and `tolerance` specifies the convergence\ncriterion based on the relative residual norm. The amount of memory\nrequired by the solver state is roughly `(2K + 2) * size(Q)`.\nThis object is intended to be passed to the [`linearsolve!`](@ref) command.\n\nThis uses the restarted Generalized Conjugate Residual method of Eisenstat (1983).\n\n## References\n\n - [Eisenstat1983](@cite)\n\"\"\"\nmutable struct GeneralizedConjugateResidual{K, T, AT} <:\n               AbstractIterativeSystemSolver\n    residual::AT\n    L_residual::AT\n    p::NTuple{K, AT}\n    L_p::NTuple{K, AT}\n    alpha::MArray{Tuple{K}, T, 1, K}\n    normsq::MArray{Tuple{K}, T, 1, K}\n    rtol::T\n    atol::T\n\n    function GeneralizedConjugateResidual(\n        K,\n        Q::AT;\n        rtol = √eps(eltype(AT)),\n        atol = eps(eltype(AT)),\n    ) where {AT}\n        T = eltype(Q)\n\n        residual = similar(Q)\n        L_residual = similar(Q)\n        p = ntuple(i -> similar(Q), K)\n        L_p = ntuple(i -> similar(Q), K)\n        alpha = @MArray zeros(K)\n        normsq = @MArray zeros(K)\n\n        new{K, T, AT}(residual, L_residual, p, L_p, alpha, normsq, rtol, atol)\n    end\nend\n\nfunction initialize!(\n    linearoperator!,\n    Q,\n    Qrhs,\n    solver::GeneralizedConjugateResidual,\n    args...,\n)\n    residual = solver.residual\n    p = solver.p\n    L_p = solver.L_p\n\n    @assert size(Q) == size(residual)\n    rtol, atol = solver.rtol, solver.atol\n\n    threshold = rtol * norm(Qrhs, weighted_norm)\n    linearoperator!(residual, Q, args...)\n    residual .-= Qrhs\n\n    converged = false\n    residual_norm = norm(residual, weighted_norm)\n    if residual_norm < threshold\n        converged = true\n        return converged, threshold\n    end\n\n    p[1] .= residual\n    linearoperator!(L_p[1], p[1], args...)\n\n    threshold = max(atol, threshold)\n\n    converged, threshold\nend\n\nfunction doiteration!(\n    linearoperator!,\n    preconditioner,\n    Q,\n    Qrhs,\n    solver::GeneralizedConjugateResidual{K},\n    threshold,\n    args...,\n) where {K}\n\n    residual = solver.residual\n    p = solver.p\n    L_residual = solver.L_residual\n    L_p = solver.L_p\n    normsq = solver.normsq\n    alpha = solver.alpha\n\n    residual_norm = typemax(eltype(Q))\n    for k in 1:K\n        normsq[k] = norm(L_p[k], weighted_norm)^2\n        beta = -dot(residual, L_p[k], weighted_norm) / normsq[k]\n\n        Q .+= beta * p[k]\n        residual .+= beta * L_p[k]\n\n        residual_norm = norm(residual, weighted_norm)\n\n        if residual_norm <= threshold\n            return (true, k, residual_norm)\n        end\n\n        linearoperator!(L_residual, residual, args...)\n\n        for l in 1:k\n            alpha[l] = -dot(L_residual, L_p[l], weighted_norm) / normsq[l]\n        end\n\n        if k < K\n            rv_nextp = realview(p[k + 1])\n            rv_L_nextp = realview(L_p[k + 1])\n        else # restart\n            rv_nextp = realview(p[1])\n            rv_L_nextp = realview(L_p[1])\n        end\n\n        rv_residual = realview(residual)\n        rv_p = realview.(p)\n        rv_L_p = realview.(L_p)\n        rv_L_residual = realview(L_residual)\n\n        groupsize = 256\n        T = eltype(alpha)\n\n        event = Event(array_device(Q))\n        event = linearcombination!(array_device(Q), groupsize)(\n            rv_nextp,\n            (one(T), alpha[1:k]...),\n            (rv_residual, rv_p[1:k]...),\n            false;\n            ndrange = length(rv_nextp),\n            dependencies = (event,),\n        )\n\n        event = linearcombination!(array_device(Q), groupsize)(\n            rv_L_nextp,\n            (one(T), alpha[1:k]...),\n            (rv_L_residual, rv_L_p[1:k]...),\n            false;\n            ndrange = length(rv_nextp),\n            dependencies = (event,),\n        )\n        wait(array_device(Q), event)\n    end\n\n    (false, K, residual_norm)\nend\n"
  },
  {
    "path": "src/Numerics/SystemSolvers/generalized_minimal_residual_solver.jl",
    "content": "#### Generalized Minimal Residual Solver\n\nexport GeneralizedMinimalResidual\n\n\"\"\"\n    GeneralizedMinimalResidual(Q; M, rtol, atol)\n\n# GMRES\nThis is an object for solving linear systems using an iterative Krylov method.\nThe constructor parameter `M` is the number of steps after which the algorithm\nis restarted (if it has not converged), `Q` is a reference state used only\nto allocate the solver internal state, and `rtol` specifies the convergence\ncriterion based on the relative residual norm. The amount of memory\nrequired for the solver state is roughly `(M + 1) * size(Q)`.\nThis object is intended to be passed to the [`linearsolve!`](@ref) command.\n\nThis uses the restarted Generalized Minimal Residual method of Saad and Schultz (1986).\n\n## References\n\n - [Saad1986](@cite)\n\n\"\"\"\nmutable struct GeneralizedMinimalResidual{M, MP1, MMP1, T, AT} <:\n               AbstractIterativeSystemSolver\n    krylov_basis::NTuple{MP1, AT}\n    \"Hessenberg matrix\"\n    H::MArray{Tuple{MP1, M}, T, 2, MMP1}\n    \"rhs of the least squares problem\"\n    g0::MArray{Tuple{MP1, 1}, T, 2, MP1}\n    rtol::T\n    atol::T\n\n    function GeneralizedMinimalResidual(\n        Q::AT;\n        M = min(20, eltype(Q)),\n        rtol = √eps(eltype(AT)),\n        atol = eps(eltype(AT)),\n    ) where {AT}\n        krylov_basis = ntuple(i -> similar(Q), M + 1)\n        H = @MArray zeros(M + 1, M)\n        g0 = @MArray zeros(M + 1)\n\n        new{M, M + 1, M * (M + 1), eltype(Q), AT}(\n            krylov_basis,\n            H,\n            g0,\n            rtol,\n            atol,\n        )\n    end\nend\n\nfunction initialize!(\n    linearoperator!,\n    Q,\n    Qrhs,\n    solver::GeneralizedMinimalResidual,\n    args...,\n)\n    g0 = solver.g0\n    krylov_basis = solver.krylov_basis\n    rtol, atol = solver.rtol, solver.atol\n\n    @assert size(Q) == size(krylov_basis[1])\n\n    # store the initial residual in krylov_basis[1]\n    linearoperator!(krylov_basis[1], Q, args...)\n    @. krylov_basis[1] = Qrhs - krylov_basis[1]\n\n    threshold = rtol * norm(krylov_basis[1], weighted_norm)\n    residual_norm = norm(krylov_basis[1], weighted_norm)\n\n    converged = false\n    # FIXME: Should only be true for threshold zero\n    if threshold < atol\n        converged = true\n        return converged, threshold\n    end\n\n    fill!(g0, 0)\n    g0[1] = residual_norm\n    krylov_basis[1] ./= residual_norm\n\n    converged, max(threshold, atol)\nend\n\nfunction doiteration!(\n    linearoperator!,\n    preconditioner,\n    Q,\n    Qrhs,\n    solver::GeneralizedMinimalResidual{M},\n    threshold,\n    args...,\n) where {M}\n\n    krylov_basis = solver.krylov_basis\n    H = solver.H\n    g0 = solver.g0\n\n    converged = false\n    residual_norm = typemax(eltype(Q))\n\n    Ω = LinearAlgebra.Rotation{eltype(Q)}([])\n    j = 1\n    for outer j in 1:M\n\n        # Arnoldi using the Modified Gram Schmidt orthonormalization\n        linearoperator!(krylov_basis[j + 1], krylov_basis[j], args...)\n        for i in 1:j\n            H[i, j] = dot(krylov_basis[j + 1], krylov_basis[i], weighted_norm)\n            @. krylov_basis[j + 1] -= H[i, j] * krylov_basis[i]\n        end\n        H[j + 1, j] = norm(krylov_basis[j + 1], weighted_norm)\n        krylov_basis[j + 1] ./= H[j + 1, j]\n\n        # apply the previous Givens rotations to the new column of H\n        @views H[1:j, j:j] .= Ω * H[1:j, j:j]\n\n        # compute a new Givens rotation to zero out H[j + 1, j]\n        G, _ = givens(H, j, j + 1, j)\n\n        # apply the new rotation to H and the rhs\n        H .= G * H\n        g0 .= G * g0\n\n        # compose the new rotation with the others\n        Ω = lmul!(G, Ω)\n\n        residual_norm = abs(g0[j + 1])\n\n        if residual_norm < threshold\n            converged = true\n            break\n        end\n    end\n\n    # solve the triangular system\n    y = SVector{j}(@views UpperTriangular(H[1:j, 1:j]) \\ g0[1:j])\n\n    ## compose the solution\n    rv_Q = realview(Q)\n    rv_krylov_basis = realview.(krylov_basis)\n    groupsize = 256\n    event = Event(array_device(Q))\n    event = linearcombination!(array_device(Q), groupsize)(\n        rv_Q,\n        y,\n        rv_krylov_basis,\n        true;\n        ndrange = length(rv_Q),\n        dependencies = (event,),\n    )\n    wait(array_device(Q), event)\n\n    # if not converged restart\n    converged || initialize!(linearoperator!, Q, Qrhs, solver, args...)\n\n    (converged, j, residual_norm)\nend\n"
  },
  {
    "path": "src/Numerics/SystemSolvers/jacobian_free_newton_krylov_solver.jl",
    "content": "\nexport JacobianFreeNewtonKrylovSolver, JacobianAction\n\n\"\"\"\nmutable struct JacobianAction{FT, AT}\n    rhs!\n    ϵ::FT\n    Q::AT\n    Qdq::AT\n    Fq::AT\n    Fqdq::AT\nend\n\nSolve for Frhs = F(q), the Jacobian action is computed\n\n    ∂F(Q)      F(Q + eΔQ) - F(Q)\n    ---- ΔQ ≈ -------------------\n     ∂Q                e\n\n\n...\n# Arguments     \n- `rhs!`           : nonlinear operator F(Q)\n- `ϵ::FT`          : ϵ used for finite difference, e = e(Q, ϵ)\n- `Q::AT`          : cache for Q\n- `Qdq::AT`        : container for Q + ϵΔQ\n- `Fq::AT`         : cache for F(Q)\n- `Fqdq::AT`       : container for F(Q + ϵΔQ)\n...\n\"\"\"\nmutable struct JacobianAction{FT, AT}\n    rhs!::Any\n    ϵ::FT\n    Q::AT\n    Qdq::AT\n    Fq::AT\n    Fqdq::AT\nend\n\nfunction JacobianAction(rhs!, Q, ϵ)\n    return JacobianAction(\n        rhs!,\n        ϵ,\n        similar(Q),\n        similar(Q),\n        similar(Q),\n        similar(Q),\n    )\nend\n\n\"\"\"\nApproximates the action of the Jacobian of a nonlinear\nform on a vector `ΔQ` using the difference quotient:\n\n      ∂F(Q)      F(Q + e ΔQ) - F(Q)\nJΔQ = ---- ΔQ ≈ -------------------\n       ∂Q                e\n\n\nCompute  JΔQ with cached Q and F(Q), and the direction  dQ\n\"\"\"\nfunction (op::JacobianAction)(JΔQ, dQ, args...)\n    rhs! = op.rhs!\n    Q = op.Q\n    Qdq = op.Qdq\n    ϵ = op.ϵ\n    Fq = op.Fq\n    Fqdq = op.Fqdq\n\n    FT = eltype(dQ)\n    n = length(dQ)\n    normdQ = norm(dQ, weighted_norm)\n\n    β = √ϵ\n\n    if normdQ > ϵ\n        # for preconditioner reconstruction, it goes into this part\n        # e depends only on the active freedoms in the preconditioner reconstruction\n        factor = FT(1 / (n * normdQ))\n        Qdq .= Q .* (abs.(dQ) .> 0)\n        e = factor * β * norm(Qdq, 1, false) + β\n    else\n        factor = FT(1 / n)\n        e = factor * β * norm(Q, 1, false) + β\n    end\n\n\n\n    Qdq .= Q .+ e .* dQ\n\n    rhs!(Fqdq, Qdq, args...)\n\n    JΔQ .= (Fqdq .- Fq) ./ e\n\nend\n\n\"\"\"\nupdate cached Q and F(Q) before each Newton iteration\n\"\"\"\nfunction update_Q!(op::JacobianAction, Q, args...)\n    op.Q .= Q\n    Fq = op.Fq\n\n    op.rhs!(Fq, Q, args...)\nend\n\n\"\"\"\nSolve for Frhs = F(Q), by finite difference\n\n    ∂F(Q)      F(Q + eΔQ) - F(Q)\n    ---- ΔQ ≈ -------------------\n     ∂Q                e\n\n     Q^n+1 = Q^n - dF/dQ(Q^{n})⁻¹ (F(Q^n) - Frhs)\n\n     set ΔQ = F(Q^n) - Frhs\n\"\"\"\nmutable struct JacobianFreeNewtonKrylovSolver{FT, AT} <: AbstractNonlinearSolver\n    # small number used for finite difference\n    ϵ::FT\n    # tolerances for convergence\n    tol::FT\n    # Max number of Newton iterations\n    M::Int\n    # Linear solver for the Jacobian system\n    linearsolver::Any\n    # container for unknows ΔQ, which is updated for the linear solver\n    ΔQ::AT\n    # contrainer for F(Q)\n    residual::AT\nend\n\n\"\"\"\nJacobianFreeNewtonKrylovSolver constructor\n\"\"\"\nfunction JacobianFreeNewtonKrylovSolver(\n    Q,\n    linearsolver;\n    ϵ = 1.e-8,\n    tol = 1.e-6,\n    M = 30,\n)\n    FT = eltype(Q)\n    residual = similar(Q)\n    ΔQ = similar(Q)\n    return JacobianFreeNewtonKrylovSolver(\n        FT(ϵ),\n        FT(tol),\n        M,\n        linearsolver,\n        ΔQ,\n        residual,\n    )\nend\n\n\"\"\"\nJacobianFreeNewtonKrylovSolver initialize the residual\n\"\"\"\nfunction initialize!(\n    rhs!,\n    Q,\n    Qrhs,\n    solver::JacobianFreeNewtonKrylovSolver,\n    args...,\n)\n    # where R = Qrhs - F(Q)\n    R = solver.residual\n    # Computes F(Q) and stores in R\n    rhs!(R, Q, args...)\n    # Computes R = R - Qrhs\n    R .-= Qrhs\n    return norm(R, weighted_norm)\nend\n\n\"\"\"\nSolve for Frhs = F(Q), by finite difference\n\nQ^n+1 = Q^n - dF/dQ(Q^{n})⁻¹ (F(Q^n) - Frhs)\n\nset ΔQ = F(Q^n) - Frhs\n\n...\n# Arguments \n- `rhs!`:  functor rhs!(Q) =  F(Q)\n- `jvp!`:  Jacobian action jvp!(ΔQ)  = dF/dQ(Q) ⋅ ΔQ\n- `preconditioner`: approximation of dF/dQ(Q)\n- `Q` : Q^n\n- `Qrhs` : Frhs\n- `solver`: linear solver\n...\n\"\"\"\nfunction donewtoniteration!(\n    rhs!,\n    jvp!,\n    preconditioner::AbstractPreconditioner,\n    Q,\n    Qrhs,\n    solver::JacobianFreeNewtonKrylovSolver,\n    args...,\n)\n\n    FT = eltype(Q)\n    ΔQ = solver.ΔQ\n    ΔQ .= FT(0.0)\n\n    # R(Q) == 0, R = F(Q) - Qrhs, where F = rhs!\n    # Compute right-hand side for Jacobian system:\n    # J(Q)ΔQ = -R\n    # where R = Qrhs - F(Q), which is computed at the end of last step or in the initialize function\n    R = solver.residual\n\n    # R = F(Q^n) - Frhs\n    # ΔQ = dF/dQ(Q^{n})⁻¹ (Frhs - F(Q^n)) = -dF/dQ(Q^{n})⁻¹ R\n    iters =\n        linearsolve!(jvp!, preconditioner, solver.linearsolver, ΔQ, -R, args...)\n\n    # Newton correction Q^{n+1} = Q^n + dF/dQ(Q^{n})⁻¹ (Frhs - F(Q^n))\n    Q .+= ΔQ\n\n    # Compute residual norm and residual for next step\n    rhs!(R, Q, args...)\n    R .-= Qrhs\n    resnorm = norm(R, weighted_norm)\n\n    return resnorm, iters\nend\n"
  },
  {
    "path": "src/Numerics/SystemSolvers/preconditioners.jl",
    "content": "export AbstractPreconditioner,\n    ColumnwiseLUPreconditioner,\n    NoPreconditioner,\n    preconditioner_update!,\n    preconditioner_solve!,\n    preconditioner_counter_update!\n\n\"\"\"\n    Abstract base type for all preconditioners.\n\"\"\"\nabstract type AbstractPreconditioner end\n\n\"\"\"\nmutable struct NoPreconditioner\nend\n\nDo nothing\n\"\"\"\nmutable struct NoPreconditioner <: AbstractPreconditioner end\n\n\"\"\"\nDo nothing, when there is no preconditioner, preconditioner = Nothing\n\"\"\"\nfunction preconditioner_update!(\n    op,\n    dg,\n    preconditioner::NoPreconditioner,\n    args...,\n) end\n\n\"\"\"\nDo nothing, when there is no preconditioner, preconditioner = Nothing\n\"\"\"\nfunction preconditioner_solve!(preconditioner::NoPreconditioner, Q) end\n\n\"\"\"\nDo nothing, when there is no preconditioner, preconditioner = Nothing\n\"\"\"\nfunction preconditioner_counter_update!(preconditioner::NoPreconditioner) end\n\n\"\"\"\nmutable struct ColumnwiseLUPreconditioner{AT}\n    A::DGColumnBandedMatrix\n    Q::AT\n    PQ::AT\n    counter::Int\n    update_freq::Int\nend\n\n...\n# Arguments\n- `A`: the lu factor of the precondition (approximated Jacobian), in the DGColumnBandedMatrix format\n- `Q`: MPIArray container, used to update A\n- `PQ`: MPIArray container, used to update A\n- `counter`: count the number of Newton, when counter > update_freq or counter < 0, update precondition\n- `update_freq`: preconditioner update frequency\n...\n\"\"\"\nmutable struct ColumnwiseLUPreconditioner{AT} <: AbstractPreconditioner\n    A::DGColumnBandedMatrix\n    Q::AT\n    PQ::AT\n    counter::Int\n    update_freq::Int\nend\n\n\"\"\"\nColumnwiseLUPreconditioner constructor\nbuild an empty ColumnwiseLUPreconditioner\n\n...\n# Arguments\n- `dg`: DG model, use only the grid information\n- `Q0`: MPIArray, use only its size\n- `counter`: = -1, which indicates the preconditioner is empty\n- `update_freq`: preconditioner update frequency\n...\n\"\"\"\nfunction ColumnwiseLUPreconditioner(dg, Q0, update_freq = 100)\n    single_column = false\n    Q = similar(Q0)\n    PQ = similar(Q0)\n\n    A = empty_banded_matrix(dg, Q; single_column = single_column)\n\n    # counter = -1, which indicates the preconditioner is empty\n    ColumnwiseLUPreconditioner(A, Q, PQ, -1, update_freq)\nend\n\n\"\"\"\nupdate the DGColumnBandedMatrix by the finite difference approximation\n...\n# Arguments\n- `op`: operator used to compute the finte difference information\n- `dg`: the DG model, use only the grid information\n...\n\"\"\"\nfunction preconditioner_update!(\n    op,\n    dg,\n    preconditioner::ColumnwiseLUPreconditioner,\n    args...,\n)\n\n    # preconditioner.counter < 0, means newly constructed empty preconditioner\n    if preconditioner.counter >= 0 &&\n       (preconditioner.counter < preconditioner.update_freq)\n        return\n    end\n\n    A = preconditioner.A\n    Q = preconditioner.Q\n    PQ = preconditioner.PQ\n\n    update_banded_matrix!(A, op, dg, Q, PQ, args...)\n    band_lu!(A)\n\n    preconditioner.counter = 0\nend\n\n\"\"\"\nInplace applying the preconditioner\n\nQ = P⁻¹ * Q\n\"\"\"\nfunction preconditioner_solve!(preconditioner::ColumnwiseLUPreconditioner, Q)\n    A = preconditioner.A\n    band_forward!(Q, A)\n    band_back!(Q, A)\n\nend\n\n\"\"\"\nUpdate the preconditioner counter, after each Newton iteration\n\"\"\"\nfunction preconditioner_counter_update!(\n    preconditioner::ColumnwiseLUPreconditioner,\n)\n    preconditioner.counter += 1\nend\n"
  },
  {
    "path": "src/Ocean/HydrostaticBoussinesq/Courant.jl",
    "content": "using Logging, Printf\nusing LinearAlgebra: norm\n\nusing ...Mesh.Grids:\n    VerticalDirection, HorizontalDirection, EveryDirection, min_node_distance\nusing ...DGMethods: courant\n\nimport ...Courant:\n    advective_courant, nondiffusive_courant, diffusive_courant, viscous_courant\n\nimport ...DGMethods: calculate_dt\n\n\"\"\"\n    advective_courant(::HBModel)\n\ncalculates the CFL condition due to advection\n\n\"\"\"\n@inline function advective_courant(\n    m::HBModel,\n    Q::Vars,\n    A::Vars,\n    D::Vars,\n    Δx,\n    Δt,\n    t,\n    direction = VerticalDirection(),\n)\n    if direction isa VerticalDirection\n        ū = norm(A.w)\n    elseif direction isa HorizontalDirection\n        ū = norm(Q.u)\n    else\n        v = @SVector [Q.u[1], Q.u[2], A.w]\n        ū = norm(v)\n    end\n\n    return Δt * ū / Δx\nend\n\n\"\"\"\n    nondiffusive_courant(::HBModel)\n\ncalculates the CFL condition due to gravity waves\n\n\"\"\"\n@inline function nondiffusive_courant(\n    m::HBModel,\n    Q::Vars,\n    A::Vars,\n    D::Vars,\n    Δx,\n    Δt,\n    t,\n    direction = HorizontalDirection(),\n)\n    return Δt * m.cʰ / Δx\nend\n\"\"\"\n    viscous_courant(::HBModel)\n\ncalculates the CFL condition due to viscosity\n\n\"\"\"\n@inline function viscous_courant(\n    m::HBModel,\n    Q::Vars,\n    A::Vars,\n    D::Vars,\n    Δx,\n    Δt,\n    t,\n    direction = VerticalDirection(),\n)\n    ν̄ = norm_viscosity(m, direction)\n\n    return Δt * ν̄ / Δx^2\nend\n\n@inline norm_viscosity(m::HBModel, ::VerticalDirection) = m.νᶻ\n@inline norm_viscosity(m::HBModel, ::HorizontalDirection) = sqrt(2) * m.νʰ\n@inline norm_viscosity(m::HBModel, ::EveryDirection) = sqrt(2 * m.νʰ^2 + m.νᶻ^2)\n\n\n\"\"\"\n    diffusive_courant(::HBModel)\n\ncalculates the CFL condition due to temperature diffusivity\nfactor of 1000 is for convective adjustment\n\n\"\"\"\n@inline function diffusive_courant(\n    m::HBModel,\n    Q::Vars,\n    A::Vars,\n    D::Vars,\n    Δx,\n    Δt,\n    t,\n    direction = VerticalDirection(),\n)\n    κ̄ = norm_diffusivity(m, direction)\n\n    return Δt * κ̄ / Δx^2\nend\n\n@inline norm_diffusivity(m::HBModel, ::VerticalDirection) = 1000 * m.κᶻ\n@inline norm_diffusivity(m::HBModel, ::HorizontalDirection) = sqrt(2) * m.κʰ\n@inline norm_diffusivity(m::HBModel, ::EveryDirection) =\n    sqrt(2 * m.κʰ^2 + (1000 * m.κᶻ)^2)\n\n\"\"\"\n    calculate_dt(dg, model::HBModel, Q, Courant_number, direction::EveryDirection, t)\n\ncalculates the time step based on grid spacing and model parameters\ntakes minimum of advective, gravity wave, diffusive, and viscous CFL\n\n\"\"\"\n@inline function calculate_dt(\n    dg,\n    model::HBModel,\n    Q,\n    Courant_number,\n    t,\n    ::EveryDirection,\n)\n    Δt = one(eltype(Q))\n\n    CFL_advective =\n        courant(advective_courant, dg, model, Q, Δt, t, VerticalDirection())\n    CFL_gravity = courant(\n        nondiffusive_courant,\n        dg,\n        model,\n        Q,\n        Δt,\n        t,\n        HorizontalDirection(),\n    )\n    CFL_viscous =\n        courant(viscous_courant, dg, model, Q, Δt, t, VerticalDirection())\n    CFL_diffusive =\n        courant(diffusive_courant, dg, model, Q, Δt, t, VerticalDirection())\n\n    CFLs = [CFL_advective, CFL_gravity, CFL_viscous, CFL_diffusive]\n    dts = [Courant_number / CFL for CFL in CFLs]\n    dt = min(dts...)\n\n    @info @sprintf(\n        \"\"\"Calculating timestep\n         Advective Constraint    = %.1f seconds\n         Nondiffusive Constraint = %.1f seconds\n         Viscous Constraint      = %.1f seconds\n         Diffusive Constrait     = %.1f seconds\n         Timestep                = %.1f seconds\"\"\",\n        dts...,\n        dt\n    )\n\n    return dt\nend\n\n\n\"\"\"\n    calculate_dt(dg, bl::LinearHBModel, Q, Courant_number,\n                 direction::EveryDirection)\n\ncalculates the time step based on grid spacing and model parameters\ntakes minimum of gravity wave, diffusive, and viscous CFL\n\n\"\"\"\n@inline function calculate_dt(\n    dg,\n    model::LinearHBModel,\n    Q,\n    Courant_number,\n    t,\n    ::EveryDirection,\n)\n    Δt = one(eltype(Q))\n    ocean = model.ocean\n\n    CFL_advective =\n        courant(advective_courant, dg, ocean, Q, Δt, t, VerticalDirection())\n    CFL_gravity = courant(\n        nondiffusive_courant,\n        dg,\n        ocean,\n        Q,\n        Δt,\n        t,\n        HorizontalDirection(),\n    )\n    CFL_viscous =\n        courant(viscous_courant, dg, ocean, Q, Δt, t, HorizontalDirection())\n    CFL_diffusive =\n        courant(diffusive_courant, dg, ocean, Q, Δt, t, HorizontalDirection())\n\n    CFLs = [CFL_advective, CFL_gravity, CFL_viscous, CFL_diffusive]\n    dts = [Courant_number / CFL for CFL in CFLs]\n    dt = min(dts...)\n\n    @info @sprintf(\n        \"\"\"Calculating timestep\n         Advective Constraint    = %.1f seconds\n         Nondiffusive Constraint = %.1f seconds\n         Viscous Constraint      = %.1f seconds\n         Diffusive Constrait     = %.1f seconds\n         Timestep                = %.1f seconds\"\"\",\n        dts...,\n        dt\n    )\n\n\n    return dt\nend\n"
  },
  {
    "path": "src/Ocean/HydrostaticBoussinesq/HydrostaticBoussinesq.jl",
    "content": "module HydrostaticBoussinesq\n\nexport HydrostaticBoussinesqModel, Forcing\n\nusing StaticArrays\nusing LinearAlgebra: dot, Diagonal\nusing CLIMAParameters.Planet: grav\n\nusing ..Ocean\nusing ...VariableTemplates\nusing ...MPIStateArrays\nusing ...Mesh.Filters: apply!\nusing ...Mesh.Grids: VerticalDirection\nusing ...Mesh.Geometry\nusing ...DGMethods\nusing ...DGMethods: init_state_auxiliary!\nusing ...DGMethods.NumericalFluxes\nusing ...DGMethods.NumericalFluxes: RusanovNumericalFlux\nusing ...BalanceLaws\n\nimport ..Ocean: coriolis_parameter\nimport ...DGMethods.NumericalFluxes: update_penalty!\n\nimport ...BalanceLaws:\n    vars_state,\n    init_state_prognostic!,\n    init_state_auxiliary!,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    flux_first_order!,\n    flux_second_order!,\n    source!,\n    wavespeed,\n    boundary_state!,\n    boundary_conditions,\n    update_auxiliary_state!,\n    update_auxiliary_state_gradient!,\n    integral_load_auxiliary_state!,\n    integral_set_auxiliary_state!,\n    indefinite_stack_integral!,\n    reverse_indefinite_stack_integral!,\n    reverse_integral_load_auxiliary_state!,\n    reverse_integral_set_auxiliary_state!\n\nimport ..Ocean:\n    ocean_init_state!,\n    ocean_init_aux!,\n    ocean_boundary_state!,\n    _ocean_boundary_state!\n\n×(a::SVector, b::SVector) = StaticArrays.cross(a, b)\n⋅(a::SVector, b::SVector) = StaticArrays.dot(a, b)\n⊗(a::SVector, b::SVector) = a * b'\n\ninclude(\"hydrostatic_boussinesq_model.jl\")\ninclude(\"bc_velocity.jl\")\ninclude(\"bc_temperature.jl\")\ninclude(\"LinearHBModel.jl\")\ninclude(\"Courant.jl\")\n\nend\n"
  },
  {
    "path": "src/Ocean/HydrostaticBoussinesq/LinearHBModel.jl",
    "content": "export LinearHBModel\n\n# Linear model for 1D IMEX\n\"\"\"\n    LinearHBModel <: BalanceLaw\n\nA `BalanceLaw` for modeling vertical diffusion implicitly.\n\nwrite out the equations here\n\n# Usage\n\n    model = HydrostaticBoussinesqModel(problem)\n    linear = LinearHBModel(model)\n\n\"\"\"\nstruct LinearHBModel{M} <: BalanceLaw\n    ocean::M\n    function LinearHBModel(ocean::M) where {M}\n        return new{M}(ocean)\n    end\nend\n\n\"\"\"\n    Copy over state, aux, and diff variables from HBModel\n\"\"\"\nvars_state(lm::LinearHBModel, ::Prognostic, FT) =\n    vars_state(lm.ocean, Prognostic(), FT)\nvars_state(lm::LinearHBModel, st::Gradient, FT) = vars_state(lm.ocean, st, FT)\nvars_state(lm::LinearHBModel, ::GradientFlux, FT) =\n    vars_state(lm.ocean, GradientFlux(), FT)\nvars_state(lm::LinearHBModel, st::Auxiliary, FT) = vars_state(lm.ocean, st, FT)\nvars_state(lm::LinearHBModel, ::UpwardIntegrals, FT) = @vars()\n\n\"\"\"\n    No integration, hyperbolic flux, or source terms\n\"\"\"\n@inline integrate_aux!(::LinearHBModel, _...) = nothing\n@inline flux_first_order!(::LinearHBModel, _...) = nothing\n@inline source!(::LinearHBModel, _...) = nothing\n\n\"\"\"\n    No need to init, initialize by full model\n\"\"\"\ninit_state_auxiliary!(\n    lm::LinearHBModel,\n    state_auxiliary::MPIStateArray,\n    grid,\n    direction,\n) = nothing\ninit_state_prognostic!(lm::LinearHBModel, Q::Vars, A::Vars, localgeo, t) =\n    nothing\n\n\"\"\"\n    compute_gradient_argument!(::LinearHBModel)\n\ncopy u and θ to var_gradient\nthis computation is done pointwise at each nodal point\n\n# arguments:\n- `m`: model in this case HBModel\n- `G`: array of gradient variables\n- `Q`: array of state variables\n- `A`: array of aux variables\n- `t`: time, not used\n\"\"\"\n@inline function compute_gradient_argument!(\n    m::LinearHBModel,\n    G::Vars,\n    Q::Vars,\n    A,\n    t,\n)\n    G.∇u = Q.u\n    G.∇θ = Q.θ\n\n    return nothing\nend\n\n\"\"\"\n    compute_gradient_flux!(::LinearHBModel)\n\ncopy ν∇u and κ∇θ to var_diffusive\nthis computation is done pointwise at each nodal point\n\n# arguments:\n- `m`: model in this case HBModel\n- `D`: array of diffusive variables\n- `G`: array of gradient variables\n- `Q`: array of state variables\n- `A`: array of aux variables\n- `t`: time, not used\n\"\"\"\n@inline function compute_gradient_flux!(\n    lm::LinearHBModel,\n    D::Vars,\n    G::Grad,\n    Q::Vars,\n    A::Vars,\n    t,\n)\n    ν = viscosity_tensor(lm.ocean)\n    D.ν∇u = -ν * G.∇u\n\n    κ = diffusivity_tensor(lm.ocean, G.∇θ[3])\n    D.κ∇θ = -κ * G.∇θ\n\n    return nothing\nend\n\n\"\"\"\n    flux_second_order!(::HBModel)\n\ncalculates the parabolic flux contribution to state variables\nthis computation is done pointwise at each nodal point\n\n# arguments:\n- `m`: model in this case HBModel\n- `F`: array of fluxes for each state variable\n- `Q`: array of state variables\n- `D`: array of diff variables\n- `A`: array of aux variables\n- `t`: time, not used\n\n# computations\n∂ᵗu = -∇⋅(ν∇u)\n∂ᵗθ = -∇⋅(κ∇θ)\n\"\"\"\n@inline function flux_second_order!(\n    lm::LinearHBModel,\n    F::Grad,\n    Q::Vars,\n    D::Vars,\n    HD::Vars,\n    A::Vars,\n    t::Real,\n)\n    F.u += D.ν∇u\n    F.θ += D.κ∇θ\n\n    return nothing\nend\n\n\"\"\"\n    wavespeed(::LinaerHBModel)\n\ncalculates the wavespeed for rusanov flux\n\"\"\"\nfunction wavespeed(lm::LinearHBModel, n⁻, _...)\n    C = abs(SVector(lm.ocean.cʰ, lm.ocean.cʰ, lm.ocean.cᶻ)' * n⁻)\n    return C\nend\n\nboundary_conditions(linear::LinearHBModel) = boundary_conditions(linear.ocean)\n\n\"\"\"\n    boundary_state!(nf, ::LinearHBModel, args...)\n\napplies boundary conditions for the hyperbolic fluxes\ndispatches to a function in OceanBoundaryConditions.jl based on bytype defined by a problem such as SimpleBoxProblem.jl\n\"\"\"\n@inline function boundary_state!(nf, bc, linear::LinearHBModel, args...)\n    return _ocean_boundary_state!(nf, bc, linear.ocean, args...)\nend\n"
  },
  {
    "path": "src/Ocean/HydrostaticBoussinesq/bc_temperature.jl",
    "content": "using ..Ocean: surface_flux\n\n\"\"\"\n    ocean_temperature_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::Insulating, ::HBModel)\n\napply insulating boundary condition for temperature\nsets transmissive ghost point\n\"\"\"\nfunction ocean_temperature_boundary_state!(\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    bc_temperature::Insulating,\n    ocean,\n    Q⁺,\n    A⁺,\n    n,\n    Q⁻,\n    A⁻,\n    t,\n)\n    Q⁺.θ = Q⁻.θ\n\n    return nothing\nend\n\n\"\"\"\n    ocean_temperature_boundary_state!(::NumericalFluxSecondOrder, ::Insulating, ::HBModel)\n\napply insulating boundary condition for velocity\nsets ghost point to have no numerical flux on the boundary for κ∇θ\n\"\"\"\n@inline function ocean_temperature_boundary_state!(\n    nf::NumericalFluxSecondOrder,\n    bc_temperature::Insulating,\n    ocean,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    Q⁺.θ = Q⁻.θ\n    D⁺.κ∇θ = n⁻ * -0\n\n    return nothing\nend\n\n\"\"\"\n    ocean_temperature_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::TemperatureFlux, ::HBModel)\n\napply temperature flux boundary condition for velocity\napplies insulating conditions for first-order and gradient fluxes\n\"\"\"\nfunction ocean_temperature_boundary_state!(\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    bc_velocity::TemperatureFlux,\n    ocean,\n    args...,\n)\n    return ocean_temperature_boundary_state!(nf, Insulating(), ocean, args...)\nend\n\n\"\"\"\n    ocean_temperature_boundary_state!(::NumericalFluxSecondOrder, ::TemperatureFlux, ::HBModel)\n\napply insulating boundary condition for velocity\nsets ghost point to have specified flux on the boundary for κ∇θ\n\"\"\"\n@inline function ocean_temperature_boundary_state!(\n    nf::NumericalFluxSecondOrder,\n    bc_temperature::TemperatureFlux,\n    ocean,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    Q⁺.θ = Q⁻.θ\n    D⁺.κ∇θ = n⁻ * surface_flux(ocean.problem, A⁻.y, Q⁻.θ)\n\n    return nothing\nend\n"
  },
  {
    "path": "src/Ocean/HydrostaticBoussinesq/bc_velocity.jl",
    "content": "using ..Ocean: kinematic_stress\n\n\"\"\"\n    ocean_velocity_boundary_state!(::NumericalFluxFirstOrder, ::Impenetrable{NoSlip}, ::HBModel)\n\napply no slip boundary condition for velocity\nsets reflective ghost point\n\"\"\"\nfunction ocean_velocity_boundary_state!(\n    nf::NumericalFluxFirstOrder,\n    bc_velocity::Impenetrable{NoSlip},\n    ocean,\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n)\n    Q⁺.u = -Q⁻.u\n    A⁺.w = -A⁻.w\n\n    return nothing\nend\n\n\"\"\"\n    ocean_velocity_boundary_state!(::NumericalFluxGradient, ::Impenetrable{NoSlip}, ::HBModel)\n\napply no slip boundary condition for velocity\nset numerical flux to zero for u\n\"\"\"\nfunction ocean_velocity_boundary_state!(\n    nf::NumericalFluxGradient,\n    bc_velocity::Impenetrable{NoSlip},\n    ocean,\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n)\n    FT = eltype(Q⁺)\n    Q⁺.u = SVector(-zero(FT), -zero(FT))\n    A⁺.w = -zero(FT)\n\n    return nothing\nend\n\n\"\"\"\n    ocean_velocity_boundary_state!(::NumericalFluxSecondOrder, ::Impenetrable{NoSlip}, ::HBModel)\n\napply no slip boundary condition for velocity\nsets ghost point to have no numerical flux on the boundary for u\n\"\"\"\n@inline function ocean_velocity_boundary_state!(\n    nf::NumericalFluxSecondOrder,\n    bc_velocity::Impenetrable{NoSlip},\n    ocean,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    Q⁺.u = -Q⁻.u\n    A⁺.w = -A⁻.w\n    D⁺.ν∇u = D⁻.ν∇u\n\n    return nothing\nend\n\n\"\"\"\n    ocean_velocity_boundary_state!(::NumericalFluxFirstOrder, ::Impenetrable{FreeSlip}, ::HBModel)\n\napply free slip boundary condition for velocity\nsets reflective ghost point\n\"\"\"\nfunction ocean_velocity_boundary_state!(\n    nf::NumericalFluxFirstOrder,\n    bc_velocity::Impenetrable{FreeSlip},\n    ocean,\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n)\n    v⁻ = @SVector [Q⁻.u[1], Q⁻.u[2], A⁻.w]\n    v⁺ = v⁻ - 2 * n⁻ ⋅ v⁻ .* SVector(n⁻)\n    Q⁺.u = @SVector [v⁺[1], v⁺[2]]\n    A⁺.w = v⁺[3]\n\n    return nothing\nend\n\n\"\"\"\n    ocean_velocity_boundary_state!(::NumericalFluxGradient, ::Impenetrable{FreeSlip}, ::HBModel)\n\napply free slip boundary condition for velocity\nsets non-reflective ghost point\n\"\"\"\nfunction ocean_velocity_boundary_state!(\n    nf::NumericalFluxGradient,\n    bc_velocity::Impenetrable{FreeSlip},\n    ocean,\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n)\n    v⁻ = @SVector [Q⁻.u[1], Q⁻.u[2], A⁻.w]\n    v⁺ = v⁻ - n⁻ ⋅ v⁻ .* SVector(n⁻)\n    Q⁺.u = @SVector [v⁺[1], v⁺[2]]\n    A⁺.w = v⁺[3]\n\n    return nothing\nend\n\n\"\"\"\n    ocean_velocity_normal_boundary_flux_second_order!(::NumericalFluxSecondOrder, ::Impenetrable{FreeSlip}, ::HBModel)\n\napply free slip boundary condition for velocity\napply zero numerical flux in the normal direction\n\"\"\"\nfunction ocean_velocity_boundary_state!(\n    nf::NumericalFluxSecondOrder,\n    bc_velocity::Impenetrable{FreeSlip},\n    ocean,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    Q⁺.u = Q⁻.u\n    A⁺.w = A⁻.w\n    D⁺.ν∇u = n⁻ * (@SVector [-0, -0])'\n\n    return nothing\nend\n\n\"\"\"\n    ocean_velocity_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::Penetrable{FreeSlip}, ::HBModel)\n\napply free slip boundary condition for velocity\nsets non-reflective ghost point\n\"\"\"\nfunction ocean_velocity_boundary_state!(\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    bc_velocity::Penetrable{FreeSlip},\n    ocean,\n    args...,\n)\n    return nothing\nend\n\n\"\"\"\n    ocean_velocity_boundary_state!(::NumericalFluxSecondOrder, ::Penetrable{FreeSlip}, ::HBModel)\n\napply free slip boundary condition for velocity\nsets non-reflective ghost point\n\"\"\"\nfunction ocean_velocity_boundary_state!(\n    nf::NumericalFluxSecondOrder,\n    bc_velocity::Penetrable{FreeSlip},\n    ocean,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    Q⁺.u = Q⁻.u\n    A⁺.w = A⁻.w\n    D⁺.ν∇u = n⁻ * (@SVector [-0, -0])'\n\n    return nothing\nend\n\n\"\"\"\n    ocean_velocity_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::Impenetrable{KinematicStress}, ::HBModel)\n\napply kinematic stress boundary condition for velocity\napplies free slip conditions for first-order and gradient fluxes\n\"\"\"\nfunction ocean_velocity_boundary_state!(\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    bc_velocity::Impenetrable{<:KinematicStress},\n    ocean,\n    args...,\n)\n    return ocean_velocity_boundary_state!(\n        nf,\n        Impenetrable(FreeSlip()),\n        ocean,\n        args...,\n    )\nend\n\n\"\"\"\n    ocean_velocity_boundary_state!(::NumericalFluxSecondOrder, ::Impenetrable{KinematicStress}, ::HBModel)\n\napply kinematic stress boundary condition for velocity\nsets ghost point to have specified flux on the boundary for ν∇u\n\"\"\"\n@inline function ocean_velocity_boundary_state!(\n    nf::NumericalFluxSecondOrder,\n    bc_velocity::Impenetrable{<:KinematicStress},\n    ocean,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    Q⁺.u = Q⁻.u\n    D⁺.ν∇u =\n        n⁻ * kinematic_stress(ocean.problem, A⁻.y, ocean.ρₒ, bc_velocity.drag)'\n\n    return nothing\nend\n\n\"\"\"\n    ocean_velocity_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::Penetrable{KinematicStress}, ::HBModel)\n\napply kinematic stress boundary condition for velocity\napplies free slip conditions for first-order and gradient fluxes\n\"\"\"\nfunction ocean_velocity_boundary_state!(\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    bc_velocity::Penetrable{<:KinematicStress},\n    ocean,\n    args...,\n)\n    return ocean_velocity_boundary_state!(\n        nf,\n        Penetrable(FreeSlip()),\n        ocean,\n        args...,\n    )\nend\n\n\"\"\"\n    ocean_velocity_boundary_state!(::NumericalFluxSecondOrder, ::Penetrable{KinematicStress}, ::HBModel)\n\napply kinematic stress boundary condition for velocity\nsets ghost point to have specified flux on the boundary for ν∇u\n\"\"\"\n@inline function ocean_velocity_boundary_state!(\n    nf::NumericalFluxSecondOrder,\n    bc_velocity::Penetrable{<:KinematicStress},\n    ocean,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    Q⁺.u = Q⁻.u\n    D⁺.ν∇u =\n        n⁻ *\n        kinematic_stress(\n            ocean.problem,\n            A⁻.y,\n            ocean.ρₒ,\n            bc_velocity.drag.stress,\n        )'\n\n    return nothing\nend\n"
  },
  {
    "path": "src/Ocean/HydrostaticBoussinesq/hydrostatic_boussinesq_model.jl",
    "content": "\"\"\"\n    HydrostaticBoussinesqModel <: BalanceLaw\n\nA `BalanceLaw` for ocean modeling.\n\nwrite out the equations here\n\nρₒ = reference density of sea water\ncʰ = maximum horizontal wave speed\ncᶻ = maximum vertical wave speed\nαᵀ = thermal expansitivity coefficient\nνʰ = horizontal viscosity\nνᶻ = vertical viscosity\nκʰ = horizontal diffusivity\nκᶻ = vertical diffusivity\nfₒ = first coriolis parameter (constant term)\nβ  = second coriolis parameter (linear term)\n\n# Usage\n\n    HydrostaticBoussinesqModel(problem)\n\n\"\"\"\nstruct HydrostaticBoussinesqModel{C, PS, P, MA, TA, F, FT, I} <: BalanceLaw\n    param_set::PS\n    problem::P\n    coupling::C\n    momentum_advection::MA\n    tracer_advection::TA\n    forcing::F\n    state_filter::I\n    ρₒ::FT\n    cʰ::FT\n    cᶻ::FT\n    αᵀ::FT\n    νʰ::FT\n    νᶻ::FT\n    κʰ::FT\n    κᶻ::FT\n    κᶜ::FT\n    fₒ::FT\n    β::FT\n    function HydrostaticBoussinesqModel{FT}(\n        param_set::PS,\n        problem::P;\n        coupling::C = Uncoupled(),\n        momentum_advection::MA = nothing,\n        tracer_advection::TA = NonLinearAdvectionTerm(),\n        forcing::F = Forcing(),\n        state_filter::I = nothing,\n        ρₒ = FT(1000),  # kg / m^3\n        cʰ = FT(0),     # m/s\n        cᶻ = FT(0),     # m/s\n        αᵀ = FT(2e-4),  # (m/s)^2 / K\n        νʰ = FT(5e3),   # m^2 / s\n        νᶻ = FT(5e-3),  # m^2 / s\n        κʰ = FT(1e3),   # m^2 / s # horizontal diffusivity\n        κᶻ = FT(1e-4),  # m^2 / s # background vertical diffusivity\n        κᶜ = FT(1e-1),  # m^2 / s # diffusivity for convective adjustment\n        fₒ = FT(1e-4),  # Hz\n        β = FT(1e-11), # Hz / m\n    ) where {FT <: AbstractFloat, PS, P, C, MA, TA, F, I}\n        return new{C, PS, P, MA, TA, F, FT, I}(\n            param_set,\n            problem,\n            coupling,\n            momentum_advection,\n            tracer_advection,\n            forcing,\n            state_filter,\n            ρₒ,\n            cʰ,\n            cᶻ,\n            αᵀ,\n            νʰ,\n            νᶻ,\n            κʰ,\n            κᶻ,\n            κᶜ,\n            fₒ,\n            β,\n        )\n    end\nend\n\nHBModel = HydrostaticBoussinesqModel\n\nboundary_conditions(ocean::HBModel) = ocean.problem.boundary_conditions\n\n@inline noforcing(args...) = 0\n\nfunction Forcing(; u = noforcing, v = noforcing, η = noforcing, θ = noforcing)\n    return (u = u, v = v, η = η, θ = θ)\nend\n\n\"\"\"\n    vars_state(::HBModel, ::Prognostic)\n\nprognostic variables evolved forward in time\n\nu = (u,v) = (zonal velocity, meridional velocity)\nη = sea surface height\nθ = temperature\n\"\"\"\nfunction vars_state(m::HBModel, ::Prognostic, T)\n    @vars begin\n        u::SVector{2, T}\n        η::T # real a 2-D variable TODO: should be 2D\n        θ::T\n    end\nend\n\n\"\"\"\n    init_state_prognostic!(::HBModel)\n\nsets the initial value for state variables\ndispatches to ocean_init_state! which is defined in a problem file such as SimpleBoxProblem.jl\n\"\"\"\nfunction init_state_prognostic!(m::HBModel, Q::Vars, A::Vars, local_geometry, t)\n    return ocean_init_state!(m, m.problem, Q, A, local_geometry, t)\nend\n\n\"\"\"\n    vars_state(::HBModel, ::Auxiliary)\nhelper variables for computation\n\nsecond half is because there is no dedicated integral kernels\nthese variables are used to compute vertical integrals\nw = vertical velocity\nwz0 = w at z = 0\npkin = bulk hydrostatic pressure contribution\n\n\nfirst half of these are fields that are used for computation\ny = north-south coordinate\n\n\"\"\"\nfunction vars_state(m::HBModel, ::Auxiliary, T)\n    @vars begin\n        y::T     # y-coordinate of the box\n        w::T     # ∫(-∇⋅u)\n        pkin::T  # ∫(-αᵀθ)\n        wz0::T   # w at z=0\n        uᵈ::SVector{2, T}    # velocity deviation from vertical mean\n        ΔGᵘ::SVector{2, T}   # vertically averaged tendency\n    end\nend\n\nfunction ocean_init_aux! end\n\n\"\"\"\n    init_state_auxiliary!(::HBModel)\n\nsets the initial value for auxiliary variables (those that aren't related to vertical integrals)\ndispatches to ocean_init_aux! which is defined in a problem file such as SimpleBoxProblem.jl\n\"\"\"\nfunction init_state_auxiliary!(\n    m::HBModel,\n    state_auxiliary::MPIStateArray,\n    grid,\n    direction,\n)\n    init_state_auxiliary!(\n        m,\n        (m, A, tmp, geom) -> ocean_init_aux!(m, m.problem, A, geom),\n        state_auxiliary,\n        grid,\n        direction,\n    )\nend\n\n\"\"\"\n    vars_state(::HBModel, ::Gradient)\n\nvariables that you want to take a gradient of\nthese are just copies in our model\n\"\"\"\nfunction vars_state(m::HBModel, ::Gradient, T)\n    @vars begin\n        ∇u::SVector{2, T}\n        ∇uᵈ::SVector{2, T}\n        ∇θ::T\n    end\nend\n\n\"\"\"\n    compute_gradient_argument!(::HBModel)\n\ncopy u and θ to var_gradient\nthis computation is done pointwise at each nodal point\n\n# arguments:\n- `m`: model in this case HBModel\n- `G`: array of gradient variables\n- `Q`: array of state variables\n- `A`: array of aux variables\n- `t`: time, not used\n\"\"\"\n@inline function compute_gradient_argument!(m::HBModel, G::Vars, Q::Vars, A, t)\n    G.∇θ = Q.θ\n\n    velocity_gradient_argument!(m, m.coupling, G, Q, A, t)\n\n    return nothing\nend\n\n@inline function velocity_gradient_argument!(\n    m::HBModel,\n    ::Uncoupled,\n    G,\n    Q,\n    A,\n    t,\n)\n    G.∇u = Q.u\n\n    return nothing\nend\n\n\"\"\"\n    vars_state(::HBModel, ::GradientFlux, FT)\n\nthe output of the gradient computations\nmultiplies ∇u by viscosity tensor and ∇θ by the diffusivity tensor\n\"\"\"\nfunction vars_state(m::HBModel, ::GradientFlux, T)\n    @vars begin\n        ∇ʰu::T\n        ν∇u::SMatrix{3, 2, T, 6}\n        κ∇θ::SVector{3, T}\n    end\nend\n\n\"\"\"\n    compute_gradient_flux!(::HBModel)\n\ncopy ∇u and ∇θ to var_diffusive\nthis computation is done pointwise at each nodal point\n\n# arguments:\n- `m`: model in this case HBModel\n- `D`: array of diffusive variables\n- `G`: array of gradient variables\n- `Q`: array of state variables\n- `A`: array of aux variables\n- `t`: time, not used\n\"\"\"\n@inline function compute_gradient_flux!(\n    m::HBModel,\n    D::Vars,\n    G::Grad,\n    Q::Vars,\n    A::Vars,\n    t,\n)\n    # store ∇ʰu for continuity equation (convert gradient to divergence)\n    D.∇ʰu = G.∇u[1, 1] + G.∇u[2, 2]\n\n    velocity_gradient_flux!(m, m.coupling, D, G, Q, A, t)\n\n    κ = diffusivity_tensor(m, G.∇θ[3])\n    D.κ∇θ = -κ * G.∇θ\n\n    return nothing\nend\n\n@inline function velocity_gradient_flux!(m::HBModel, ::Uncoupled, D, G, Q, A, t)\n    ν = viscosity_tensor(m)\n    D.ν∇u = -ν * G.∇u\n\n    return nothing\nend\n\n\"\"\"\n    viscosity_tensor(::HBModel)\n\nuniform viscosity with different values for horizontal and vertical directions\n\n# Arguments\n- `m`: model object to dispatch on and get viscosity parameters\n\"\"\"\n@inline viscosity_tensor(m::HBModel) = Diagonal(@SVector [m.νʰ, m.νʰ, m.νᶻ])\n\n\"\"\"\n    diffusivity_tensor(::HBModel)\n\nuniform diffusivity in the horizontal direction\napplies convective adjustment in the vertical, bump by 1000 if ∂θ∂z < 0\n\n# Arguments\n- `m`: model object to dispatch on and get diffusivity parameters\n- `∂θ∂z`: value of the derivative of temperature in the z-direction\n\"\"\"\n@inline function diffusivity_tensor(m::HBModel, ∂θ∂z)\n    ∂θ∂z < 0 ? κ = m.κᶜ : κ = m.κᶻ\n\n    return Diagonal(@SVector [m.κʰ, m.κʰ, κ])\nend\n\n\"\"\"\n    vars_integral(::HBModel)\n\nlocation to store integrands for bottom up integrals\n∇hu = the horizontal divegence of u, e.g. dw/dz\n\"\"\"\nfunction vars_state(m::HBModel, ::UpwardIntegrals, T)\n    @vars begin\n        ∇ʰu::T\n        αᵀθ::T\n    end\nend\n\n\"\"\"\n    integral_load_auxiliary_state!(::HBModel)\n\ncopy w to var_integral\nthis computation is done pointwise at each nodal point\n\narguments:\nm -> model in this case HBModel\nI -> array of integrand variables\nQ -> array of state variables\nA -> array of aux variables\n\"\"\"\n@inline function integral_load_auxiliary_state!(\n    m::HBModel,\n    I::Vars,\n    Q::Vars,\n    A::Vars,\n)\n    I.∇ʰu = A.w # borrow the w value from A...\n    I.αᵀθ = -m.αᵀ * Q.θ # integral will be reversed below\n\n    return nothing\nend\n\n\"\"\"\n    integral_set_auxiliary_state!(::HBModel)\n\ncopy integral results back out to aux\nthis computation is done pointwise at each nodal point\n\narguments:\nm -> model in this case HBModel\nA -> array of aux variables\nI -> array of integrand variables\n\"\"\"\n@inline function integral_set_auxiliary_state!(m::HBModel, A::Vars, I::Vars)\n    A.w = I.∇ʰu\n    A.pkin = I.αᵀθ\n\n    return nothing\nend\n\n\"\"\"\n    vars_reverse_integral(::HBModel)\n\nlocation to store integrands for top down integrals\nαᵀθ = density perturbation\n\"\"\"\nfunction vars_state(m::HBModel, ::DownwardIntegrals, T)\n    @vars begin\n        αᵀθ::T\n    end\nend\n\n\"\"\"\n    reverse_integral_load_auxiliary_state!(::HBModel)\n\ncopy αᵀθ to var_reverse_integral\nthis computation is done pointwise at each nodal point\n\narguments:\nm -> model in this case HBModel\nI -> array of integrand variables\nA -> array of aux variables\n\"\"\"\n@inline function reverse_integral_load_auxiliary_state!(\n    m::HBModel,\n    I::Vars,\n    Q::Vars,\n    A::Vars,\n)\n    I.αᵀθ = A.pkin\n\n    return nothing\nend\n\n\"\"\"\n    reverse_integral_set_auxiliary_state!(::HBModel)\n\ncopy reverse integral results back out to aux\nthis computation is done pointwise at each nodal point\n\narguments:\nm -> model in this case HBModel\nA -> array of aux variables\nI -> array of integrand variables\n\"\"\"\n@inline function reverse_integral_set_auxiliary_state!(\n    m::HBModel,\n    A::Vars,\n    I::Vars,\n)\n    A.pkin = I.αᵀθ\n\n    return nothing\nend\n\n\"\"\"\n    flux_first_order!(::HBModel)\n\ncalculates the hyperbolic flux contribution to state variables\nthis computation is done pointwise at each nodal point\n\n# arguments:\nm -> model in this case HBModel\nF -> array of fluxes for each state variable\nQ -> array of state variables\nA -> array of aux variables\nt -> time, not used\n\n# computations\n∂ᵗu = ∇⋅(g*η + g∫αᵀθdz + v⋅u)\n∂ᵗθ = ∇⋅(vθ) where v = (u,v,w)\n\"\"\"\n@inline function flux_first_order!(\n    m::HBModel,\n    F::Grad,\n    Q::Vars,\n    A::Vars,\n    t::Real,\n    direction,\n)\n    # ∇ʰ • (g η)\n    hydrostatic_pressure!(m, m.coupling, F, Q, A, t)\n\n    # ∇ʰ • (- ∫(αᵀ θ))\n    kinematic_pressure!(m, F, Q, A, t)\n\n    # ∇ʰ • (v ⊗ u)\n    momentum_advection!(m, m.momentum_advection, F, Q, A, t)\n\n    # ∇ • (u θ)\n    tracer_advection!(m, m.tracer_advection, F, Q, A, t)\n\n    return nothing\nend\n\n@inline function hydrostatic_pressure!(m::HBModel, ::Uncoupled, F, Q, A, t)\n    η = Q.η\n    Iʰ = @SMatrix [\n        1 -0\n        -0 1\n        -0 -0\n    ]\n\n    F.u += grav(parameter_set(m)) * η * Iʰ\n\n    return nothing\nend\n\n@inline function kinematic_pressure!(m::HBModel, F, Q, A, t)\n    pkin = A.pkin\n    Iʰ = @SMatrix [\n        1 -0\n        -0 1\n        -0 -0\n    ]\n    F.u += grav(parameter_set(m)) * pkin * Iʰ\n\n    return nothing\nend\n\nmomentum_advection!(::HBModel, ::Nothing, _...) = nothing\n@inline function momentum_advection!(\n    ::HBModel,\n    ::NonLinearAdvectionTerm,\n    F,\n    Q,\n    A,\n    t,\n)\n    u = Q.u\n    @inbounds v = @SVector [Q.u[1], Q.u[2], A.w]\n\n    F.u += v * u'\n\n    return nothing\nend\n\ntracer_advection!(::HBModel, ::Nothing, _...) = nothing\n@inline function tracer_advection!(\n    ::HBModel,\n    ::NonLinearAdvectionTerm,\n    F,\n    Q,\n    A,\n    t,\n)\n    θ = Q.θ\n    @inbounds v = @SVector [Q.u[1], Q.u[2], A.w]\n\n    F.θ += v * θ\n\n    return nothing\nend\n\n\"\"\"\n    flux_second_order!(::HBModel)\n\ncalculates the parabolic flux contribution to state variables\nthis computation is done pointwise at each nodal point\n\n# arguments:\n- `m`: model in this case HBModel\n- `F`: array of fluxes for each state variable\n- `Q`: array of state variables\n- `D`: array of diff variables\n- `A`: array of aux variables\n- `t`: time, not used\n\n# computations\n∂ᵗu = -∇⋅(ν∇u)\n∂ᵗθ = -∇⋅(κ∇θ)\n\"\"\"\n@inline function flux_second_order!(\n    m::HBModel,\n    F::Grad,\n    Q::Vars,\n    D::Vars,\n    HD::Vars,\n    A::Vars,\n    t::Real,\n)\n    F.u += D.ν∇u\n    F.θ += D.κ∇θ\n\n    return nothing\nend\n\n\"\"\"\n    source!(::HBModel)\n\nCalculates the source term contribution to state variables.\nThis computation is done pointwise at each nodal point.\n\nArguments:\n    m -> model in this case HBModel\n    F -> array of fluxes for each state variable\n    Q -> array of state variables\n    A -> array of aux variables\n    t -> time, not used\n\nComputations:\n    ∂ᵗu = -f × u\n    ∂ᵗη = w|(z=0)\n\"\"\"\n@inline function source!(\n    m::HBModel,\n    S::Vars,\n    Q::Vars,\n    D::Vars,\n    A::Vars,\n    t::Real,\n    direction,\n)\n    # explicit forcing for SSH\n    wz0 = A.wz0\n    S.η += wz0\n\n    coriolis_force!(m, m.coupling, S, Q, A, t)\n\n    # Arguments for forcing functions\n    # args = y, t, u, v, w, η, θ\n    args = tuple(A.y, t, Q.u[1], Q.u[2], A.w, Q.η, Q.θ)\n\n    Su = m.forcing.u(args...)\n    Sv = m.forcing.v(args...)\n\n    S.u += @SVector [Su, Sv]\n    S.η += m.forcing.η(args...)\n    S.θ += m.forcing.θ(args...)\n\n    return nothing\nend\n\n@inline function coriolis_force!(m::HBModel, ::Uncoupled, S, Q, A, t)\n    # f × u\n    f = coriolis_parameter(m, m.problem, A.y)\n    u, v = Q.u # Horizontal components of velocity\n    S.u -= @SVector [-f * v, f * u]\n\n    return nothing\nend\n\n\"\"\"\n    wavespeed(::HBModel)\n\ncalculates the wavespeed for rusanov flux\n\"\"\"\n@inline wavespeed(m::HBModel, n⁻, _...) = abs(SVector(m.cʰ, m.cʰ, m.cᶻ)' * n⁻)\n\n\"\"\"\n    update_penalty(::HBModel)\n    set Δη = 0 when computing numerical fluxes\n\"\"\"\n# We want not have jump penalties on η (since not a flux variable)\nfunction update_penalty!(\n    ::RusanovNumericalFlux,\n    ::HBModel,\n    n⁻,\n    λ,\n    ΔQ::Vars,\n    Q⁻,\n    A⁻,\n    Q⁺,\n    A⁺,\n    t,\n)\n    ΔQ.η = -0\n\n    return nothing\nend\n\nfilter_state!(Q, filter::Nothing, grid) = nothing\nfilter_state!(Q, filter, grid) = apply!(Q, UnitRange(1, size(Q, 2)), grid)\n\n\"\"\"\n    update_auxiliary_state!(::HBModel)\n\nApplies the vertical filter to the zonal and meridional velocities to preserve numerical incompressibility\nApplies an exponential filter to θ to anti-alias the non-linear advective term\n\nDoesn't actually touch the aux variables any more, but we need a better filter interface than this anyways\n\"\"\"\nfunction update_auxiliary_state!(\n    dg::DGModel,\n    m::HBModel,\n    Q::MPIStateArray,\n    t::Real,\n    elems::UnitRange,\n)\n    FT = eltype(Q)\n    MD = dg.modeldata\n\n    # `update_aux!` gets called twice, once for the real elements and once for\n    # the ghost elements.  Only apply the filters to the real elems.\n    if elems == dg.grid.topology.realelems\n        # required to ensure that after integration velocity field is divergence free\n        vert_filter = MD.vert_filter\n        apply!(Q, (:u,), dg.grid, vert_filter, direction = VerticalDirection())\n\n        exp_filter = MD.exp_filter\n        apply!(Q, (:θ,), dg.grid, exp_filter, direction = VerticalDirection())\n\n        filter_state!(Q, m.state_filter, dg.grid)\n    end\n\n    compute_flow_deviation!(dg, m, m.coupling, Q, t)\n\n    return true\nend\n\n@inline compute_flow_deviation!(dg, ::HBModel, ::Uncoupled, _...) = nothing\n\n\n\"\"\"\n    update_auxiliary_state_gradient!(::HBModel)\n\n    ∇hu to w for integration\n    performs integration for w and pkin (should be moved to its own integral kernels)\n    copies down w and wz0 because we don't have 2D structures\n\"\"\"\nfunction update_auxiliary_state_gradient!(\n    dg::DGModel,\n    m::HBModel,\n    Q::MPIStateArray,\n    t::Real,\n    elems::UnitRange,\n)\n    FT = eltype(Q)\n    A = dg.state_auxiliary\n    D = dg.state_gradient_flux\n\n    # load -∇ʰu as ∂ᶻw\n    index_w = varsindex(vars_state(m, Auxiliary(), FT), :w)\n    index_∇ʰu = varsindex(vars_state(m, GradientFlux(), FT), :∇ʰu)\n    @views @. A.data[:, index_w, elems] = -D.data[:, index_∇ʰu, elems]\n\n    # compute integrals for w and pkin\n    indefinite_stack_integral!(dg, m, Q, A, t, elems) # bottom -> top\n    reverse_indefinite_stack_integral!(dg, m, Q, A, t, elems) # top -> bottom\n\n    # We are unable to use vars (ie A.w) for this because this operation will\n    # return a SubArray, and adapt (used for broadcasting along reshaped arrays)\n    # has a limited recursion depth for the types allowed.\n    number_aux = number_states(m, Auxiliary())\n    index_wz0 = varsindex(vars_state(m, Auxiliary(), FT), :wz0)\n    info = basic_grid_info(dg)\n    Nqh, Nqk = info.Nqh, info.Nqk\n    nelemv, nelemh = info.nvertelem, info.nhorzelem\n    nrealelemh = info.nhorzrealelem\n\n    # project w(z=0) down the stack\n    data = reshape(A.data, Nqh, Nqk, number_aux, nelemv, nelemh)\n    flat_wz0 = @view data[:, end:end, index_w, end:end, 1:nrealelemh]\n    boxy_wz0 = @view data[:, :, index_wz0, :, 1:nrealelemh]\n    boxy_wz0 .= flat_wz0\n\n    return true\nend\n\n\"\"\"\n    boundary_state!(nf, bc, ::HBModel, args...)\n\napplies boundary conditions for the hyperbolic fluxes\ndispatches to a function in OceanBoundaryConditions.jl based on bytype defined by a problem such as SimpleBoxProblem.jl\n\"\"\"\n@inline boundary_state!(nf, bc, ocean::HBModel, args...) =\n    _ocean_boundary_state!(nf, bc, ocean, args...)\n\n#=\n\"\"\"\n    boundary_state!(nf, ::HBModel, args...)\n\napplies boundary conditions for the hyperbolic fluxes\ndispatches to a function in OceanBoundaryConditions.jl based on bytype defined by a problem such as SimpleBoxProblem.jl\n\"\"\"\n@inline function boundary_state!(nf, ocean::HBModel, args...)\n    boundary_conditions = ocean.problem.boundary_conditions\n    return ocean_boundary_state!(nf, boundary_conditions, ocean, args...)\nend\n=#\n\n\"\"\"\n    ocean_boundary_state!(nf, bc::OceanBC, ::HBModel, args...)\n\nsplits boundary condition application into velocity and temperature conditions\n\"\"\"\n@inline function ocean_boundary_state!(nf, bc::OceanBC, ocean::HBModel, args...)\n    ocean_velocity_boundary_state!(nf, bc.velocity, ocean, args...)\n    ocean_temperature_boundary_state!(nf, bc.temperature, ocean, args...)\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(nf, boundaries::Tuple, ::HBModel,\n                          Q⁺, A⁺, n, Q⁻, A⁻, bctype)\n\napplies boundary conditions for the first-order and gradient fluxes\ndispatches to a function in OceanBoundaryConditions.jl based on bytype defined by a problem such as SimpleBoxProblem.jl\n\"\"\"\n@generated function ocean_boundary_state!(\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    boundaries::Tuple,\n    ocean,\n    Q⁺,\n    A⁺,\n    n,\n    Q⁻,\n    A⁻,\n    bctype,\n    t,\n    args...,\n)\n    N = fieldcount(boundaries)\n    return quote\n        Base.Cartesian.@nif(\n            $(N + 1),\n            i -> bctype == i, # conditionexpr\n            i -> ocean_boundary_state!(\n                nf,\n                boundaries[i],\n                ocean,\n                Q⁺,\n                A⁺,\n                n,\n                Q⁻,\n                A⁻,\n                t,\n            ), # expr\n            i -> error(\"Invalid boundary tag\")\n        ) # elseexpr\n        return nothing\n    end\nend\n\n\"\"\"\n    ocean_boundary_state!(nf, boundaries::Tuple, ::HBModel,\n                          Q⁺, A⁺, D⁺, n, Q⁻, A⁻, D⁻, bctype)\n\napplies boundary conditions for the second-order fluxes\ndispatches to a function in OceanBoundaryConditions.jl based on bytype defined by a problem such as SimpleBoxProblem.jl\n\"\"\"\n@generated function ocean_boundary_state!(\n    nf::NumericalFluxSecondOrder,\n    boundaries::Tuple,\n    ocean,\n    Q⁺,\n    D⁺,\n    HD⁺,\n    A⁺,\n    n,\n    Q⁻,\n    D⁻,\n    HD⁻,\n    A⁻,\n    bctype,\n    t,\n    args...,\n)\n    N = fieldcount(boundaries)\n    return quote\n        Base.Cartesian.@nif(\n            $(N + 1),\n            i -> bctype == i, # conditionexpr\n            i -> ocean_boundary_state!(\n                nf,\n                boundaries[i],\n                ocean,\n                Q⁺,\n                D⁺,\n                A⁺,\n                n,\n                Q⁻,\n                D⁻,\n                A⁻,\n                t,\n            ), # expr\n            i -> error(\"Invalid boundary tag\")\n        ) # elseexpr\n        return nothing\n    end\nend\n"
  },
  {
    "path": "src/Ocean/JLD2Writer.jl",
    "content": "module JLD2Writers\n\nusing JLD2\n\nusing ..CartesianDomains: DiscontinuousSpectralElementGrid\nusing ..Ocean: current_step, current_time\nusing ..CartesianFields: SpectralElementField\n\nstruct JLD2Writer{A, F, M, O}\n    filepath::F\n    model::M\n    outputs::O\n    array_type::A\nend\n\nfunction Base.show(io::IO, writer::JLD2Writer{A}) where {A}\n\n    header = \"JLD2Writer{$A}\"\n    filepath = \"    ├── filepath: $(writer.filepath)\"\n    outputs = \"    └── $(length(writer.outputs)) outputs: $(keys(ow.outputs))\"\n\n    print(io, header, '\\n', filepath, '\\n', outputs)\n\n    return nothing\nend\n\n\"\"\"\n    JLD2Writer(model, outputs=model.fields; filepath, array_type=Array, overwrite_existing=true)\n\nReturns a utility for writing field output to JLD2 files, `overwrite_existing` file at `filepath`.\n\n`write!(jld2_writer::JLD2Writer)` writes `outputs` to `filepath`, where `outputs` is either a `NamedTuple` or\n`Dict`ionary of `fields` or functions of the form `output(model)`.\n\nField data is converted to `array_type` before outputting.\n\"\"\"\nfunction JLD2Writer(\n    model,\n    outputs = model.fields;\n    filepath,\n    array_type = Array,\n    overwrite_existing = true,\n)\n\n    # Convert grid to CPU\n    cpu_grid =\n        DiscontinuousSpectralElementGrid(model.domain, array_type = array_type)\n\n    # Initialize output\n    overwrite_existing && isfile(filepath) && rm(filepath; force = true)\n\n    file = jldopen(filepath, \"a+\")\n\n    file[\"domain\"] = model.domain\n    file[\"grid\"] = cpu_grid\n\n    close(file)\n\n    writer = JLD2Writer(filepath, model, outputs, array_type)\n\n    write!(writer, first = true)\n\n    return writer\nend\n\ninitialize_output!(file, args...) = nothing\n\ninitialize_output!(file, field::SpectralElementField, name) =\n    file[\"$name/meta/realelems\"] = field.realelems\n\nfunction write!(writer::JLD2Writer; first = false)\n\n    model = writer.model\n    filepath = writer.filepath\n    outputs = writer.outputs\n\n    # Add new data to file\n    file = jldopen(filepath, \"a+\")\n\n    N_output = first ? 0 : length(keys(file[\"times\"]))\n\n    step = current_step(model)\n    time = current_time(model)\n\n    file[\"times/$N_output\"] = time\n    file[\"steps/$N_output\"] = step\n\n    for (name, output) in zip(keys(outputs), values(outputs))\n        first && initialize_output!(file, output, name)\n        write_single_output!(file, output, name, N_output, writer)\n    end\n\n    close(file)\n\n    return nothing\nend\n\nfunction write_field!(file, field, name, N_output, writer)\n    data = convert(writer.array_type, field.data)\n    file[\"$name/$N_output\"] = data\n    return nothing\nend\n\nwrite_single_output!(file, field::SpectralElementField, args...) =\n    write_field!(file, field, args...)\n\nfunction write_single_output!(file, output, name, N_output, writer)\n    data = output(writer.model)\n    file[\"$name/$N_output\"] = data\n    return nothing\nend\n\nstruct OutputTimeSeries{F, N, D, G, T, S}\n    filepath::F\n    name::N\n    domain::D\n    grid::G\n    times::T\n    steps::S\nend\n\nfunction OutputTimeSeries(name, filepath)\n    file = jldopen(filepath)\n\n    domain, grid, times, steps = [nothing for i in 1:4]\n\n    try\n        domain = file[\"domain\"]\n        grid = file[\"grid\"]\n\n        output_indices = keys(file[\"times\"])\n\n        times = [file[\"times/$i\"] for i in output_indices]\n        steps = [file[\"steps/$i\"] for i in output_indices]\n\n    catch err\n        @warn \"Could not build time series of $name from $filepath because $(sprint(showerror, err))\"\n\n    finally\n        close(file)\n\n    end\n\n    return OutputTimeSeries(filepath, name, domain, grid, times, steps)\nend\n\nfunction Base.length(timeseries::OutputTimeSeries)\n    file = jldopen(timeseries.filepath)\n\n    timeseries_length = length(keys(file[\"times\"]))\n\n    close(file)\n\n    return timeseries_length\nend\n\n\nfunction Base.getindex(timeseries::OutputTimeSeries, i)\n\n    name = timeseries.name\n    domain = timeseries.domain\n    grid = timeseries.grid\n\n    file = jldopen(timeseries.filepath)\n\n    data = file[\"$name/$(i-1)\"]\n    realelems = file[\"$name/meta/realelems\"]\n\n    close(file)\n\n    realdata = view(data, :, realelems)\n\n    return SpectralElementField(domain, grid, realdata, data, realelems)\nend\n\nend # module\n"
  },
  {
    "path": "src/Ocean/Ocean.jl",
    "content": "module Ocean\n\nusing ..BalanceLaws\nusing ..CartesianDomains\nusing ..CartesianFields\nusing ..Problems\n\nexport AbstractOceanModel,\n    AbstractOceanProblem,\n    AbstractOceanCoupling,\n    Uncoupled,\n    Coupled,\n    AdvectionTerm,\n    NonLinearAdvectionTerm,\n    InitialConditions\n\nabstract type AbstractOceanModel <: BalanceLaw end\nabstract type AbstractOceanProblem <: AbstractProblem end\n\nabstract type AbstractOceanCoupling end\nstruct Uncoupled <: AbstractOceanCoupling end\nstruct Coupled <: AbstractOceanCoupling end\n\nabstract type AdvectionTerm end\nstruct NonLinearAdvectionTerm <: AdvectionTerm end\n\nfunction ocean_init_state! end\nfunction ocean_init_aux! end\nfunction ocean_boundary_state! end\n\nfunction coriolis_parameter end\nfunction kinematic_stress end\nfunction surface_flux end\n\ninclude(\"OceanBC.jl\")\n\ninclude(\"HydrostaticBoussinesq/HydrostaticBoussinesq.jl\")\n\nusing .HydrostaticBoussinesq: HydrostaticBoussinesqModel, Forcing\n\ninclude(\"ShallowWater/ShallowWaterModel.jl\")\ninclude(\"SplitExplicit/SplitExplicitModel.jl\")\ninclude(\"SplitExplicit01/SplitExplicitModel.jl\")\ninclude(\"OceanProblems/OceanProblems.jl\")\n\ninclude(\"SuperModels.jl\")\n\nusing .OceanProblems: InitialConditions\nusing .SuperModels:\n    HydrostaticBoussinesqSuperModel, current_time, current_step, Δt\n\ninclude(\"JLD2Writer.jl\")\n\nusing .JLD2Writers: JLD2Writer, OutputTimeSeries, write!\n\nend\n"
  },
  {
    "path": "src/Ocean/OceanBC.jl",
    "content": "export OceanBC,\n    VelocityBC,\n    VelocityDragBC,\n    TemperatureBC,\n    Impenetrable,\n    Penetrable,\n    NoSlip,\n    FreeSlip,\n    KinematicStress,\n    Insulating,\n    TemperatureFlux\n\nusing StaticArrays\n\nusing ..BalanceLaws\nusing ..DGMethods.NumericalFluxes\n\n\"\"\"\n    OceanBC(velocity    = Impenetrable(NoSlip())\n            temperature = Insulating())\n\nThe standard boundary condition for OceanModel. The default options imply a \"no flux\" boundary condition.\n\"\"\"\nBase.@kwdef struct OceanBC{M, T}\n    velocity::M = Impenetrable(NoSlip())\n    temperature::T = Insulating()\nend\n\nabstract type VelocityBC end\nabstract type VelocityDragBC end\nabstract type TemperatureBC end\n\n\"\"\"\n    Impenetrable(drag::VelocityDragBC) :: VelocityBC\n\nDefines an impenetrable wall model for velocity. This implies:\n  - no flow in the direction normal to the boundary, and\n  - flow parallel to the boundary is subject to the `drag` condition.\n\"\"\"\nstruct Impenetrable{D <: VelocityDragBC} <: VelocityBC\n    drag::D\nend\n\n\"\"\"\n    Penetrable(drag::VelocityDragBC) :: VelocityBC\n\nDefines an penetrable wall model for velocity. This implies:\n  - no constraint on flow in the direction normal to the boundary, and\n  - flow parallel to the boundary is subject to the `drag` condition.\n\"\"\"\nstruct Penetrable{D <: VelocityDragBC} <: VelocityBC\n    drag::D\nend\n\n\"\"\"\n    NoSlip() :: VelocityDragBC\n\nZero velocity at the boundary.\n\"\"\"\nstruct NoSlip <: VelocityDragBC end\n\n\"\"\"\n    FreeSlip() :: VelocityDragBC\n\nNo surface drag on velocity parallel to the boundary.\n\"\"\"\nstruct FreeSlip <: VelocityDragBC end\n\n\"\"\"\n    KinematicStress(stress) :: VelocityDragBC\n\nApplies the specified kinematic stress on velocity normal to the boundary.\nPrescribe the net inward kinematic stress across the boundary by `stress`,\na function with signature `stress(problem, state, aux, t)`, returning the flux (in m²/s²).\n\"\"\"\nstruct KinematicStress{S} <: VelocityDragBC\n    stress::S\n\n    function KinematicStress(stress::S = nothing) where {S}\n        new{S}(stress)\n    end\nend\n\nkinematic_stress(problem, y, ρ₀) = @SVector [0, 0] # fallback for generic problems\nkinematic_stress(problem, y, ρ₀, ::Nothing) = kinematic_stress(problem, y, ρ₀)\nkinematic_stress(problem, y, ρ₀, stress) = stress(y)\n\n\"\"\"\n    Insulating() :: TemperatureBC\n\nNo temperature flux across the boundary\n\"\"\"\nstruct Insulating <: TemperatureBC end\n\n\"\"\"\n    TemperatureFlux(flux) :: TemperatureBC\n\nPrescribe the net inward temperature flux across the boundary by `flux`,\na function with signature `flux(problem, state, aux, t)`, returning the flux (in m⋅K/s).\n\"\"\"\nstruct TemperatureFlux{T} <: TemperatureBC\n    flux::T\n\n    function TemperatureFlux(flux::T = nothing) where {T}\n        new{T}(flux)\n    end\nend\n\n# these functions just trim off the extra arguments\nfunction _ocean_boundary_state!(\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    bc,\n    ocean,\n    Q⁺,\n    A⁺,\n    n,\n    Q⁻,\n    A⁻,\n    t,\n    _...,\n)\n    return ocean_boundary_state!(nf, bc, ocean, Q⁺, A⁺, n, Q⁻, A⁻, t)\nend\n\nfunction _ocean_boundary_state!(\n    nf::NumericalFluxSecondOrder,\n    bc,\n    ocean,\n    Q⁺,\n    D⁺,\n    HD⁺,\n    A⁺,\n    n,\n    Q⁻,\n    D⁻,\n    HD⁻,\n    A⁻,\n    t,\n    _...,\n)\n    return ocean_boundary_state!(nf, bc, ocean, Q⁺, D⁺, A⁺, n, Q⁻, D⁻, A⁻, t)\nend\n"
  },
  {
    "path": "src/Ocean/OceanProblems/OceanProblems.jl",
    "content": "module OceanProblems\n\nexport SimpleBox, Fixed, Rotating, HomogeneousBox, OceanGyre\n\nusing StaticArrays\nusing CLIMAParameters.Planet: grav\n\nusing ...Problems\n\nusing ..Ocean\nusing ..BalanceLaws: parameter_set\nusing ..HydrostaticBoussinesq\nusing ..ShallowWater\nusing ..SplitExplicit01\n\nimport ..Ocean:\n    ocean_init_state!,\n    ocean_init_aux!,\n    kinematic_stress,\n    surface_flux,\n    coriolis_parameter\n\nHBModel = HydrostaticBoussinesqModel\nSWModel = ShallowWaterModel\n\ninclude(\"simple_box_problem.jl\")\ninclude(\"homogeneous_box.jl\")\n\ninclude(\"shallow_water_initial_states.jl\")\n\nfunction ocean_init_state!(\n    m::SWModel,\n    p::HomogeneousBox,\n    Q,\n    A,\n    local_geometry,\n    t,\n)\n    if t == 0\n        null_init_state!(p, m.turbulence, Q, A, local_geometry, 0)\n    else\n        gyre_init_state!(m, p, m.turbulence, Q, A, local_geometry, t)\n    end\nend\n\n@inline coriolis_parameter(m::SWModel, p::HomogeneousBox, y) =\n    m.fₒ + m.β * (y - p.Lʸ / 2)\n\ninclude(\"ocean_gyre.jl\")\n\ninclude(\"initial_value_problem.jl\")\n\nend # module\n"
  },
  {
    "path": "src/Ocean/OceanProblems/homogeneous_box.jl",
    "content": "##########################\n# Homogenous wind stress #\n# Constant temperature   #\n##########################\n\n\"\"\"\n    HomogeneousBox <: AbstractSimpleBoxProblem\n\nContainer structure for a simple box problem with wind-stress.\nLˣ = zonal (east-west) length\nLʸ = meridional (north-south) length\nH  = height of the ocean\nτₒ = maximum value of wind-stress (amplitude)\n\"\"\"\nstruct HomogeneousBox{T, BC} <: AbstractSimpleBoxProblem\n    Lˣ::T\n    Lʸ::T\n    H::T\n    τₒ::T\n    boundary_conditions::BC\n    function HomogeneousBox{FT}(\n        Lˣ,             # m\n        Lʸ,             # m\n        H;              # m\n        τₒ = FT(1e-1),  # N/m²\n        BC = (\n            OceanBC(Impenetrable(NoSlip()), Insulating()),\n            OceanBC(Impenetrable(NoSlip()), Insulating()),\n            OceanBC(Penetrable(KinematicStress()), Insulating()),\n        ),\n    ) where {FT <: AbstractFloat}\n        return new{FT, typeof(BC)}(Lˣ, Lʸ, H, τₒ, BC)\n    end\nend\n\n\"\"\"\n    ocean_init_state!(::HomogeneousBox)\n\ninitialize u,v with random values, η with 0, and θ with a constant (20)\n\n# Arguments\n- `p`: HomogeneousBox problem object, used to dispatch on\n- `Q`: state vector\n- `A`: auxiliary state vector, not used\n- `localgeo`: the local geometry, not used\n- `t`: time to evaluate at, not used\n\"\"\"\nfunction ocean_init_state!(m::HBModel, p::HomogeneousBox, Q, A, localgeo, t)\n    Q.u = @SVector [0, 0]\n    Q.η = 0\n    Q.θ = 20\n\n    return nothing\nend\n\n\"\"\"\n    kinematic_stress(::HomogeneousBox)\n\njet stream like windstress\n\n# Arguments\n- `p`: problem object to dispatch on and get additional parameters\n- `y`: y-coordinate in the box\n\"\"\"\n@inline kinematic_stress(p::HomogeneousBox, y, ρ) =\n    @SVector [(p.τₒ / ρ) * cos(y * π / p.Lʸ), -0]\n\n@inline kinematic_stress(p::HomogeneousBox, y) =\n    @SVector [-p.τₒ * cos(π * y / p.Lʸ), -0]\n"
  },
  {
    "path": "src/Ocean/OceanProblems/initial_value_problem.jl",
    "content": "#####\n##### Initial value problem\n#####\n\nstruct InitialValueProblem{FT, IC, BC} <: AbstractSimpleBoxProblem\n    Lˣ::FT\n    Lʸ::FT\n    H::FT\n    initial_conditions::IC\n    boundary_conditions::BC\n\n    \"\"\"\n        InitialValueProblem{FT}(; dimensions, initial_conditions=InitialConditions(),\n                                boundary_conditions = (OceanBC(Impenetrable(FreeSlip()), Insulating()),\n                                                       OceanBC(Penetrable(FreeSlip()), Insulating())))\n\n    Returns an `InitialValueProblem` with `dimensions = (Lˣ, Lʸ, H)`, `initial_conditions`,\n    and `boundary_conditions`.\n\n    The default `initial_conditions` are resting with no temperature perturbation;\n    the default list of `boundary_conditions` provide implementations for an impenetrable, insulating\n    boundary, and a penetrable, insulating boundary.\n    \"\"\"\n    function InitialValueProblem{FT}(;\n        dimensions,\n        initial_conditions = InitialConditions(),\n        boundary_conditions = (\n            OceanBC(Impenetrable(FreeSlip()), Insulating()),\n            OceanBC(Penetrable(FreeSlip()), Insulating()),\n        ),\n    ) where {FT}\n\n        return new{FT, typeof(initial_conditions), typeof(boundary_conditions)}(\n            FT.(dimensions)...,\n            initial_conditions,\n            boundary_conditions,\n        )\n    end\n\nend\n\n\n#####\n##### Initial conditions\n#####\n\nresting(x, y, z) = 0\n\nstruct InitialConditions{U, V, T, E}\n    u::U\n    v::V\n    θ::T\n    η::E\nend\n\n\n\"\"\"\n    InitialConditions(; u=resting, v=resting, θ=resting, η=resting)\n\nStores initial conditions for each prognostic variable provided as functions of `x, y, z`.\n\nExample\n=======\n\n# A Gaussian surface perturbation\na = 0.1 # m, amplitude\nL = 1e5 # m, horizontal scale of the perturbation\n\nηᵢ(x, y, z) = a * exp(-(x^2 + y^2) / 2L^2)\n\nics = InitialConditions(η=ηᵢ)\n\"\"\"\nInitialConditions(; u = resting, v = resting, θ = resting, η = resting) =\n    InitialConditions(u, v, θ, η)\n\n\"\"\"\n    ocean_init_state!(::HydrostaticBoussinesqModel, ic::InitialCondition, state, aux, local_geometry, time)\n\nInitialize the state variables `u = (u, v)` (a vector), `θ`, and `η`. Mutates `state`.\n\nThis function is called by `init_state_prognostic!(::HydrostaticBoussinesqModel, ...)`.\n\"\"\"\nfunction ocean_init_state!(\n    ::HydrostaticBoussinesqModel,\n    ivp::InitialValueProblem,\n    state,\n    aux,\n    local_geometry,\n    time,\n)\n\n    ics = ivp.initial_conditions\n    x, y, z = local_geometry.coord\n\n    state.u = @SVector [ics.u(x, y, z), ics.v(x, y, z)]\n    state.θ = ics.θ(x, y, z)\n    state.η = ics.η(x, y, z)\n\n    return nothing\nend\n"
  },
  {
    "path": "src/Ocean/OceanProblems/ocean_gyre.jl",
    "content": "\"\"\"\n    OceanGyre <: AbstractSimpleBoxProblem\n\nContainer structure for a simple box problem with wind-stress, coriolis force, and temperature forcing.\nLˣ = zonal (east-west) length\nLʸ = meridional (north-south) length\nH  = height of the ocean\nτₒ = maximum value of wind-stress (amplitude)\nλʳ = temperature relaxation penetration constant (meters / second)\nθᴱ = maximum surface temperature\n\"\"\"\nstruct OceanGyre{T, BC} <: AbstractSimpleBoxProblem\n    Lˣ::T\n    Lʸ::T\n    H::T\n    τₒ::T\n    λʳ::T\n    θᴱ::T\n    boundary_conditions::BC\n    function OceanGyre{FT}(\n        Lˣ,                  # m\n        Lʸ,                  # m\n        H;                   # m\n        τₒ = FT(1e-1),       # N/m²\n        λʳ = FT(4 // 86400), # m/s\n        θᴱ = FT(10),         # K\n        BC = (\n            OceanBC(Impenetrable(NoSlip()), Insulating()),\n            OceanBC(Impenetrable(NoSlip()), Insulating()),\n            OceanBC(Penetrable(KinematicStress()), TemperatureFlux()),\n        ),\n    ) where {FT <: AbstractFloat}\n        return new{FT, typeof(BC)}(Lˣ, Lʸ, H, τₒ, λʳ, θᴱ, BC)\n    end\nend\n\n\"\"\"\n    ocean_init_state!(::OceanGyre)\n\ninitialize u,v,η with 0 and θ linearly distributed between 9 at z=0 and 1 at z=H\n\n# Arguments\n- `p`: OceanGyre problem object, used to dispatch on and obtain ocean height H\n- `Q`: state vector\n- `A`: auxiliary state vector, not used\n- `localgeo`: the local geometry information\n- `t`: time to evaluate at, not used\n\"\"\"\nfunction ocean_init_state!(\n    ::Union{HBModel, OceanModel},\n    p::OceanGyre,\n    Q,\n    A,\n    localgeo,\n    t,\n)\n    coords = localgeo.coord\n    @inbounds y = coords[2]\n    @inbounds z = coords[3]\n    @inbounds H = p.H\n\n    Q.u = @SVector [-0, -0]\n    Q.η = -0\n    Q.θ = (5 + 4 * cos(y * π / p.Lʸ)) * (1 + z / H)\n\n    return nothing\nend\n\nfunction ocean_init_state!(\n    ::Union{SWModel, BarotropicModel},\n    ::OceanGyre,\n    Q,\n    A,\n    localgeo,\n    t,\n)\n    Q.U = @SVector [-0, -0]\n    Q.η = -0\n\n    return nothing\nend\n\n\"\"\"\n    kinematic_stress(::OceanGyre)\n\njet stream like windstress\n\n# Arguments\n- `p`: problem object to dispatch on and get additional parameters\n- `y`: y-coordinate in the box\n\"\"\"\n@inline kinematic_stress(p::OceanGyre, y, ρ) =\n    @SVector [(p.τₒ / ρ) * cos(y * π / p.Lʸ), -0]\n\n@inline kinematic_stress(p::OceanGyre, y) =\n    @SVector [-p.τₒ * cos(π * y / p.Lʸ), -0]\n\n\"\"\"\n    surface_flux(::OceanGyre)\n\ncool-warm north-south linear temperature gradient\n\n# Arguments\n- `p`: problem object to dispatch on and get additional parameters\n- `y`: y-coordinate in the box\n- `θ`: temperature within element on boundary\n\"\"\"\n@inline function surface_flux(p::OceanGyre, y, θ)\n    Lʸ = p.Lʸ\n    θᴱ = p.θᴱ\n    λʳ = p.λʳ\n\n    θʳ = θᴱ * (1 - y / Lʸ)\n    return λʳ * (θ - θʳ)\nend\n"
  },
  {
    "path": "src/Ocean/OceanProblems/shallow_water_initial_states.jl",
    "content": "using ..ShallowWater: TurbulenceClosure, LinearDrag, ConstantViscosity\n\nfunction null_init_state!(\n    ::HomogeneousBox,\n    ::TurbulenceClosure,\n    state,\n    aux,\n    local_geometry,\n    t,\n)\n    T = eltype(state.U)\n    state.U = @SVector zeros(T, 2)\n    state.η = 0\n    return nothing\nend\n\nη_lsw(x, y, t) = cos(π * x) * cos(π * y) * cos(√2 * π * t)\nu_lsw(x, y, t) = 2^(-0.5) * sin(π * x) * cos(π * y) * sin(√2 * π * t)\nv_lsw(x, y, t) = 2^(-0.5) * cos(π * x) * sin(π * y) * sin(√2 * π * t)\n\nfunction lsw_init_state!(\n    m::ShallowWaterModel,\n    p::HomogeneousBox,\n    state,\n    aux,\n    local_geometry,\n    t,\n)\n\n    coords = local_geometry.coord\n\n    state.U = @SVector [\n        u_lsw(coords[1], coords[2], t),\n        v_lsw(coords[1], coords[2], t),\n    ]\n\n    state.η = η_lsw(coords[1], coords[2], t)\n\n    return nothing\nend\n\nv_lkw(x, y, t) = 0\nu_lkw(x, y, t) = exp(-0.5 * y^2) * exp(-0.5 * (x - t + 5)^2)\nη_lkw(x, y, t) = 1 + u_lkw(x, y, t)\n\nfunction lkw_init_state!(\n    m::ShallowWaterModel,\n    p::HomogeneousBox,\n    state,\n    aux,\n    local_geometry,\n    t,\n)\n\n    coords = local_geometry.coord\n\n    state.U = @SVector [\n        u_lkw(coords[1], coords[2], t),\n        v_lkw(coords[1], coords[2], t),\n    ]\n\n    state.η = η_lkw(coords[1], coords[2], t)\n\n    return nothing\nend\n\nR₋(ϵ) = (-1 - sqrt(1 + (2 * π * ϵ)^2)) / (2ϵ)\nR₊(ϵ) = (-1 + sqrt(1 + (2 * π * ϵ)^2)) / (2ϵ)\nD(ϵ) =\n    (R₊(ϵ) * (exp(R₋(ϵ)) - 1) + R₋(ϵ) * (1 - exp(R₊(ϵ)))) /\n    (exp(R₊(ϵ)) - exp(R₋(ϵ)))\nR₂(x₁, ϵ) =\n    (1 / D(ϵ)) * (\n        (\n            (R₊(ϵ) * (exp(R₋(ϵ)) - 1)) * exp(R₊(ϵ) * x₁) +\n            (R₋(ϵ) * (1 - exp(R₊(ϵ)))) * exp(R₋(ϵ) * x₁)\n        ) / (exp(R₊(ϵ)) - exp(R₋(ϵ)))\n    )\nR₁(x₁, ϵ) =\n    (π / D(ϵ)) * (\n        1 .+\n        (\n            (exp(R₋(ϵ)) - 1) * exp(R₊(ϵ) * x₁) .+\n            (1 - exp(R₊(ϵ))) * exp(R₋(ϵ) * x₁)\n        ) / (exp(R₊(ϵ)) - exp(R₋(ϵ)))\n    )\n\n𝒱(x₁, y₁, ϵ) = R₂(x₁, ϵ) * sin.(π * y₁)\n𝒰(x₁, y₁, ϵ) = -R₁(x₁, ϵ) * cos.(π * y₁)\nℋ(x₁, y₁, ϵ, βᵖ, fₒ, γ) =\n    (R₂(x₁, ϵ) / (π * fₒ)) * γ * cos(π * y₁) +\n    (R₁(x₁, ϵ) / π) *\n    (sin(π * y₁) * (1.0 + βᵖ * (y₁ - 0.5)) + (βᵖ / π) * cos(π * y₁))\n\nfunction gyre_init_state!(\n    m::SWModel,\n    p::HomogeneousBox,\n    T::LinearDrag,\n    state,\n    aux,\n    local_geometry,\n    t,\n)\n\n    coords = local_geometry.coord\n\n    FT = eltype(state)\n    τₒ = p.τₒ\n    fₒ = m.fₒ\n    β = m.β\n    Lˣ = p.Lˣ\n    Lʸ = p.Lʸ\n    H = p.H\n\n    γ = T.λ\n\n    βᵖ = β * Lʸ / fₒ\n    ϵ = γ / (Lˣ * β)\n\n    _grav::FT = grav(parameter_set(m))\n\n    uˢ(ϵ) = (τₒ * D(ϵ)) / (H * γ * π)\n    hˢ(ϵ) = (fₒ * Lˣ * uˢ(ϵ)) / _grav\n\n    u = uˢ(ϵ) * 𝒰(coords[1] / Lˣ, coords[2] / Lʸ, ϵ)\n    v = uˢ(ϵ) * 𝒱(coords[1] / Lˣ, coords[2] / Lʸ, ϵ)\n    h = hˢ(ϵ) * ℋ(coords[1] / Lˣ, coords[2] / Lʸ, ϵ, βᵖ, fₒ, γ)\n\n    state.U = @SVector [H * u, H * v]\n\n    state.η = h\n\n    return nothing\nend\n\nt1(x, δᵐ) = cos((√3 * x) / (2 * δᵐ)) + (√3^-1) * sin((√3 * x) / (2 * δᵐ))\nt2(x, δᵐ) = 1 - exp((-x) / (2 * δᵐ)) * t1(x, δᵐ)\nt3(y, Lʸ) = π * sin(π * y / Lʸ)\nt4(x, Lˣ, C) = C * (1 - x / Lˣ)\n\nη_munk(x, y, Lˣ, Lʸ, δᵐ, C) = t4(x, Lˣ, C) * t3(y, Lʸ) * t2(x, δᵐ)\n\nfunction gyre_init_state!(\n    m::SWModel,\n    p::HomogeneousBox,\n    V::ConstantViscosity,\n    state,\n    aux,\n    local_geometry,\n    t,\n)\n\n    coords = local_geometry.coord\n\n    FT = eltype(state.U)\n    _grav::FT = grav(parameter_set(m))\n\n    τₒ = p.τₒ\n    fₒ = m.fₒ\n    β = m.β\n    Lˣ = p.Lˣ\n    Lʸ = p.Lʸ\n    H = p.H\n\n    ν = V.ν\n\n    δᵐ = (ν / β)^(1 / 3)\n    C = τₒ / (_grav * H) * (fₒ / β)\n\n    state.η = η_munk(coords[1], coords[2], Lˣ, Lʸ, δᵐ, C)\n    state.U = @SVector zeros(FT, 2)\n\n    return nothing\nend\n"
  },
  {
    "path": "src/Ocean/OceanProblems/simple_box_problem.jl",
    "content": "abstract type AbstractSimpleBoxProblem <: AbstractOceanProblem end\n\n\"\"\"\n    ocean_init_aux!(::HBModel, ::AbstractSimpleBoxProblem)\n\nsave y coordinate for computing coriolis, wind stress, and sea surface temperature\n\n# Arguments\n- `m`: model object to dispatch on and get viscosities and diffusivities\n- `p`: problem object to dispatch on and get additional parameters\n- `A`: auxiliary state vector\n- `geom`: geometry stuff\n\"\"\"\nfunction ocean_init_aux!(::HBModel, ::AbstractSimpleBoxProblem, A, geom)\n    FT = eltype(A)\n    @inbounds A.y = geom.coord[2]\n\n    # needed for proper CFL condition calculation\n    A.w = -0\n    A.pkin = -0\n    A.wz0 = -0\n\n    A.uᵈ = @SVector [-0, -0]\n    A.ΔGᵘ = @SVector [-0, -0]\n\n    return nothing\nend\n\nfunction ocean_init_aux!(::OceanModel, ::AbstractSimpleBoxProblem, A, geom)\n    FT = eltype(A)\n    @inbounds A.y = geom.coord[2]\n\n    # needed for proper CFL condition calculation\n    A.w = -0\n    A.pkin = -0\n    A.wz0 = -0\n\n    A.u_d = @SVector [-0, -0]\n    A.ΔGu = @SVector [-0, -0]\n\n    return nothing\nend\n\nfunction ocean_init_aux!(::SWModel, ::AbstractSimpleBoxProblem, A, geom)\n    @inbounds A.y = geom.coord[2]\n\n    A.Gᵁ = @SVector [-0, -0]\n    A.Δu = @SVector [-0, -0]\n\n    return nothing\nend\n\nfunction ocean_init_aux!(::BarotropicModel, ::AbstractSimpleBoxProblem, A, geom)\n    @inbounds A.y = geom.coord[2]\n\n    A.Gᵁ = @SVector [-0, -0]\n    A.U_c = @SVector [-0, -0]\n    A.η_c = -0\n    A.U_s = @SVector [-0, -0]\n    A.η_s = -0\n    A.Δu = @SVector [-0, -0]\n    A.η_diag = -0\n    A.Δη = -0\n\n    return nothing\nend\n\n\"\"\"\n    coriolis_parameter\n\nnorthern hemisphere coriolis\n\n# Arguments\n- `m`: model object to dispatch on and get coriolis parameters\n- `y`: y-coordinate in the box\n\"\"\"\n@inline coriolis_parameter(\n    m::Union{HBModel, OceanModel},\n    ::AbstractSimpleBoxProblem,\n    y,\n) = m.fₒ + m.β * y\n@inline coriolis_parameter(\n    m::Union{SWModel, BarotropicModel},\n    ::AbstractSimpleBoxProblem,\n    y,\n) = m.fₒ + m.β * y\n\n############################\n# Basic box problem        #\n# Set up dimensions of box #\n############################\n\nabstract type AbstractRotation end\nstruct Rotating <: AbstractRotation end\nstruct Fixed <: AbstractRotation end\n\n\"\"\"\n    SimpleBoxProblem <: AbstractSimpleBoxProblem\n\nStub structure with the dimensions of the box.\nLˣ = zonal (east-west) length\nLʸ = meridional (north-south) length\nH  = height of the ocean\n\"\"\"\nstruct SimpleBox{R, T, BC} <: AbstractSimpleBoxProblem\n    rotation::R\n    Lˣ::T\n    Lʸ::T\n    H::T\n    boundary_conditions::BC\n    function SimpleBox{FT}(\n        Lˣ, # m\n        Lʸ, # m\n        H;  # m\n        rotation = Fixed(),\n        BC = (\n            OceanBC(Impenetrable(FreeSlip()), Insulating()),\n            OceanBC(Penetrable(FreeSlip()), Insulating()),\n        ),\n    ) where {FT <: AbstractFloat}\n        return new{typeof(rotation), FT, typeof(BC)}(rotation, Lˣ, Lʸ, H, BC)\n    end\nend\n\n@inline coriolis_parameter(\n    m::Union{HBModel, OceanModel},\n    ::SimpleBox{R},\n    y,\n) where {R <: Fixed} = -0\n@inline coriolis_parameter(\n    m::Union{SWModel, BarotropicModel},\n    ::SimpleBox{R},\n    y,\n) where {R <: Fixed} = -0\n\n@inline coriolis_parameter(\n    m::Union{HBModel, OceanModel},\n    ::SimpleBox{R},\n    y,\n) where {R <: Rotating} = m.fₒ\n@inline coriolis_parameter(\n    m::Union{SWModel, BarotropicModel},\n    ::SimpleBox{R},\n    y,\n) where {R <: Rotating} = m.fₒ\n\nfunction ocean_init_state!(\n    m::Union{SWModel, BarotropicModel},\n    p::SimpleBox,\n    Q,\n    A,\n    local_geometry,\n    t,\n)\n    coords = local_geometry.coord\n\n    k = (2π / p.Lˣ, 2π / p.Lʸ, 2π / p.H)\n    ν = viscosity(m)\n\n    gH = gravity_speed(m)\n    @inbounds f = coriolis_parameter(m, p, coords[2])\n\n    U, V, η = barotropic_state!(p.rotation, (coords..., t), ν, k, (gH, f))\n\n    Q.U = @SVector [U, V]\n    Q.η = η\n\n    return nothing\nend\n\nviscosity(m::SWModel) = (m.turbulence.ν, m.turbulence.ν, -0)\nviscosity(m::BarotropicModel) = (m.baroclinic.νʰ, m.baroclinic.νʰ, -0)\n\ngravity_speed(m::SWModel) = grav(parameter_set(m)) * m.problem.H\ngravity_speed(m::BarotropicModel) =\n    grav(parameter_set(m)) * m.baroclinic.problem.H\n\nfunction ocean_init_state!(\n    m::Union{HBModel, OceanModel},\n    p::SimpleBox,\n    Q,\n    A,\n    local_geometry,\n    t,\n)\n\n    coords = local_geometry.coord\n\n    k = (2π / p.Lˣ, 2π / p.Lʸ, 2π / p.H)\n    ν = (m.νʰ, m.νʰ, m.νᶻ)\n\n    gH = grav(parameter_set(m)) * p.H\n    @inbounds f = coriolis_parameter(m, p, coords[2])\n\n    U, V, η = barotropic_state!(p.rotation, (coords..., t), ν, k, (gH, f))\n    u°, v° = baroclinic_deviation(p.rotation, (coords..., t), ν, k, f)\n\n    u = u° + U / p.H\n    v = v° + V / p.H\n\n    Q.u = @SVector [u, v]\n    Q.η = η\n    Q.θ = -0\n\n    return nothing\nend\n\nfunction barotropic_state!(\n    ::Fixed,\n    (x, y, z, t),\n    (νˣ, νʸ, νᶻ),\n    (kˣ, kʸ, kᶻ),\n    params,\n)\n    gH, _ = params\n\n    M = @SMatrix [-νˣ*kˣ^2 gH*kˣ; -kˣ 0]\n    A = exp(M * t) * @SVector [1, 1]\n\n    U = A[1] * sin(kˣ * x)\n    V = -0\n    η = A[2] * cos(kˣ * x)\n\n    return (U = U, V = V, η = η)\nend\n\nfunction baroclinic_deviation(\n    ::Fixed,\n    (x, y, z, t),\n    (νˣ, νʸ, νᶻ),\n    (kˣ, kʸ, kᶻ),\n    f,\n)\n    λ = νˣ * kˣ^2 + νᶻ * kᶻ^2\n\n    u° = exp(-λ * t) * cos(kᶻ * z) * sin(kˣ * x)\n    v° = -0\n\n    return (u° = u°, v° = v°)\nend\n\nfunction barotropic_state!(\n    ::Rotating,\n    (x, y, z, t),\n    (νˣ, νʸ, νᶻ),\n    (kˣ, kʸ, kᶻ),\n    params,\n)\n    gH, f = params\n\n    M = @SMatrix [-νˣ*kˣ^2 f gH*kˣ; -f -νˣ*kˣ^2 0; -kˣ 0 0]\n    A = exp(M * t) * @SVector [1, 1, 1]\n\n    U = A[1] * sin(kˣ * x)\n    V = A[2] * sin(kˣ * x)\n    η = A[3] * cos(kˣ * x)\n\n    return (U = U, V = V, η = η)\nend\n\nfunction baroclinic_deviation(\n    ::Rotating,\n    (x, y, z, t),\n    (νˣ, νʸ, νᶻ),\n    (kˣ, kʸ, kᶻ),\n    f,\n)\n    λ = νˣ * kˣ^2 + νᶻ * kᶻ^2\n\n    M = @SMatrix[-λ f; -f -λ]\n    A = exp(M * t) * @SVector[1, 1]\n\n    u° = A[1] * cos(kᶻ * z) * sin(kˣ * x)\n    v° = A[2] * cos(kᶻ * z) * sin(kˣ * x)\n\n    return (u° = u°, v° = v°)\nend\n\n@inline kinematic_stress(p::SimpleBox, y) = @SVector [-0, -0]\n"
  },
  {
    "path": "src/Ocean/README.md",
    "content": "Code for shallow water problem configurations and for specializing ClimateMachine core tools to ocean scenarios.\n\n# Code structure (aspirational)\n\n# Equations\n     - ExplicitHydrostaticBoussinesq\n     - SplitExplicitHydrostaticBoussinesq\n     - ShallowWater\n\n# Models\n     - ExplicitHydrostaticBoussinesq\n     - SplitExplicitHydrostaticBoussinesq\n     - ShallowWater\n"
  },
  {
    "path": "src/Ocean/ShallowWater/ShallowWaterModel.jl",
    "content": "module ShallowWater\n\nexport ShallowWaterModel\n\nusing StaticArrays\nusing ...MPIStateArrays: MPIStateArray\nusing LinearAlgebra: dot, Diagonal\nusing CLIMAParameters.Planet: grav\n\nusing ..Ocean\nusing ...VariableTemplates\nusing ...Mesh.Geometry\nusing ...DGMethods\nusing ...DGMethods.NumericalFluxes\nusing ...BalanceLaws\nusing ..Ocean: kinematic_stress, coriolis_parameter\n\nimport ...DGMethods.NumericalFluxes: update_penalty!\nimport ...BalanceLaws:\n    vars_state,\n    init_state_prognostic!,\n    init_state_auxiliary!,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    flux_first_order!,\n    flux_second_order!,\n    source!,\n    wavespeed,\n    boundary_conditions,\n    boundary_state!\nimport ..Ocean:\n    ocean_init_state!,\n    ocean_init_aux!,\n    ocean_boundary_state!,\n    _ocean_boundary_state!\n\nusing ...Mesh.Geometry: LocalGeometry\n\n×(a::SVector, b::SVector) = StaticArrays.cross(a, b)\n⋅(a::SVector, b::SVector) = StaticArrays.dot(a, b)\n⊗(a::SVector, b::SVector) = a * b'\n\nabstract type TurbulenceClosure end\nstruct LinearDrag{L} <: TurbulenceClosure\n    λ::L\nend\nstruct ConstantViscosity{L} <: TurbulenceClosure\n    ν::L\nend\n\n\"\"\"\n    ShallowWaterModel <: BalanceLaw\n\nA `BalanceLaw` for shallow water modeling.\n\nwrite out the equations here\n\n# Usage\n\n    ShallowWaterModel(problem)\n\n\"\"\"\nstruct ShallowWaterModel{C, PS, P, T, A, FT} <: BalanceLaw\n    param_set::PS\n    problem::P\n    coupling::C\n    turbulence::T\n    advection::A\n    c::FT\n    fₒ::FT\n    β::FT\n    function ShallowWaterModel{FT}(\n        param_set::PS,\n        problem::P,\n        turbulence::T,\n        advection::A;\n        coupling::C = Uncoupled(),\n        c = FT(0), # m/s\n        fₒ = FT(1e-4), # Hz\n        β = FT(1e-11), # Hz / m\n    ) where {FT <: AbstractFloat, PS, P, T, A, C}\n        return new{C, PS, P, T, A, FT}(\n            param_set,\n            problem,\n            coupling,\n            turbulence,\n            advection,\n            c,\n            fₒ,\n            β,\n        )\n    end\nend\nSWModel = ShallowWaterModel\n\nfunction vars_state(m::SWModel, ::Prognostic, T)\n    @vars begin\n        η::T\n        U::SVector{2, T}\n    end\nend\n\nfunction init_state_prognostic!(m::SWModel, state::Vars, aux::Vars, localgeo, t)\n    ocean_init_state!(m, m.problem, state, aux, localgeo, t)\nend\n\nfunction vars_state(m::SWModel, ::Auxiliary, T)\n    @vars begin\n        y::T\n        Gᵁ::SVector{2, T} # integral of baroclinic tendency\n        Δu::SVector{2, T} # reconciliation Δu = 1/H * (Ū - ∫u)\n    end\nend\n\nfunction init_state_auxiliary!(\n    m::SWModel,\n    state_auxiliary::MPIStateArray,\n    grid,\n    direction,\n)\n    init_state_auxiliary!(\n        m,\n        (m, A, tmp, geom) -> ocean_init_aux!(m, m.problem, A, geom),\n        state_auxiliary,\n        grid,\n        direction,\n    )\nend\n\nfunction vars_state(m::SWModel, ::Gradient, T)\n    @vars begin\n        ∇U::SVector{2, T}\n    end\nend\n\nfunction compute_gradient_argument!(\n    m::SWModel,\n    f::Vars,\n    q::Vars,\n    α::Vars,\n    t::Real,\n)\n    compute_gradient_argument!(m.turbulence, f, q, α, t)\nend\n\ncompute_gradient_argument!(::LinearDrag, _...) = nothing\n\n@inline function compute_gradient_argument!(\n    T::ConstantViscosity,\n    f::Vars,\n    q::Vars,\n    α::Vars,\n    t::Real,\n)\n    f.∇U = q.U\n\n    return nothing\nend\n\nfunction vars_state(m::SWModel, ::GradientFlux, T)\n    @vars begin\n        ν∇U::SMatrix{3, 2, T, 6}\n    end\nend\n\nfunction compute_gradient_flux!(\n    m::SWModel,\n    σ::Vars,\n    δ::Grad,\n    q::Vars,\n    α::Vars,\n    t::Real,\n)\n    compute_gradient_flux!(m, m.turbulence, σ, δ, q, α, t)\nend\n\ncompute_gradient_flux!(::SWModel, ::LinearDrag, _...) = nothing\n\n@inline function compute_gradient_flux!(\n    ::SWModel,\n    T::ConstantViscosity,\n    σ::Vars,\n    δ::Grad,\n    q::Vars,\n    α::Vars,\n    t::Real,\n)\n    ν = Diagonal(@SVector [T.ν, T.ν, -0])\n    ∇U = δ.∇U\n\n    σ.ν∇U = -ν * ∇U\n\n    return nothing\nend\n\n@inline function flux_first_order!(\n    m::SWModel,\n    F::Grad,\n    q::Vars,\n    α::Vars,\n    t::Real,\n    direction,\n)\n    U = @SVector [q.U[1], q.U[2], -0]\n    η = q.η\n    H = m.problem.H\n    Iʰ = @SMatrix [\n        1 -0\n        -0 1\n        -0 -0\n    ]\n\n    F.η += U\n    F.U += grav(parameter_set(m)) * H * η * Iʰ\n\n    advective_flux!(m, m.advection, F, q, α, t)\n\n    return nothing\nend\n\nadvective_flux!(::SWModel, ::Nothing, _...) = nothing\n\n@inline function advective_flux!(\n    m::SWModel,\n    ::NonLinearAdvectionTerm,\n    F::Grad,\n    q::Vars,\n    α::Vars,\n    t::Real,\n)\n    U = q.U\n    H = m.problem.H\n    V = @SVector [U[1], U[2], -0]\n\n    F.U += 1 / H * V ⊗ U\n\n    return nothing\nend\n\nfunction flux_second_order!(\n    m::SWModel,\n    G::Grad,\n    q::Vars,\n    σ::Vars,\n    ::Vars,\n    α::Vars,\n    t::Real,\n)\n    flux_second_order!(m, m.turbulence, G, q, σ, α, t)\nend\n\nflux_second_order!(::SWModel, ::LinearDrag, _...) = nothing\n\n@inline function flux_second_order!(\n    ::SWModel,\n    ::ConstantViscosity,\n    G::Grad,\n    q::Vars,\n    σ::Vars,\n    α::Vars,\n    t::Real,\n)\n    G.U += σ.ν∇U\n\n    return nothing\nend\n\n@inline wavespeed(m::SWModel, n⁻, q::Vars, α::Vars, t::Real, direction) = m.c\n\n@inline function source!(\n    m::SWModel{P},\n    S::Vars,\n    q::Vars,\n    d::Vars,\n    α::Vars,\n    t::Real,\n    direction,\n) where {P}\n    # f × u\n    U, V = q.U\n    f = coriolis_parameter(m, m.problem, α.y)\n    S.U -= @SVector [-f * V, f * U]\n\n    forcing_term!(m, m.coupling, S, q, α, t)\n    linear_drag!(m.turbulence, S, q, α, t)\n\n    return nothing\nend\n\n@inline function forcing_term!(m::SWModel, ::Uncoupled, S, Q, A, t)\n    S.U += kinematic_stress(m.problem, A.y)\n\n    return nothing\nend\n\nlinear_drag!(::ConstantViscosity, _...) = nothing\n\n@inline function linear_drag!(T::LinearDrag, S::Vars, q::Vars, α::Vars, t::Real)\n    λ = T.λ\n    U = q.U\n\n    S.U -= λ * U\n\n    return nothing\nend\n\nboundary_conditions(shallow::SWModel) = shallow.problem.boundary_conditions\n\n\"\"\"\n    boundary_state!(nf, ::SWModel, args...)\n\napplies boundary conditions for the hyperbolic fluxes\ndispatches to a function in OceanBoundaryConditions.jl based on bytype defined by a problem such as SimpleBoxProblem.jl\n\"\"\"\n@inline function boundary_state!(nf, bc, shallow::SWModel, args...)\n    return _ocean_boundary_state!(nf, bc, shallow, args...)\nend\n\n\"\"\"\n    ocean_boundary_state!(nf, bc::OceanBC, ::SWModel)\n\nsplits boundary condition application into velocity\n\"\"\"\n@inline function ocean_boundary_state!(nf, bc::OceanBC, m::SWModel, args...)\n    return ocean_boundary_state!(nf, bc.velocity, m, m.turbulence, args...)\nend\n\ninclude(\"bc_velocity.jl\")\n\n\nend\n"
  },
  {
    "path": "src/Ocean/ShallowWater/bc_velocity.jl",
    "content": "\"\"\"\n    ocean_boundary_state!(::NumericalFluxFirstOrder, ::Impenetrable{FreeSlip}, ::SWModel)\n\napply free slip boundary condition for velocity\nsets reflective ghost point\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::NumericalFluxFirstOrder,\n    ::Impenetrable{FreeSlip},\n    ::SWModel,\n    ::TurbulenceClosure,\n    q⁺,\n    a⁺,\n    n⁻,\n    q⁻,\n    a⁻,\n    t,\n    args...,\n)\n    q⁺.η = q⁻.η\n\n    V⁻ = @SVector [q⁻.U[1], q⁻.U[2], -0]\n    V⁺ = V⁻ - 2 * n⁻ ⋅ V⁻ .* SVector(n⁻)\n    q⁺.U = @SVector [V⁺[1], V⁺[2]]\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::Union{NumericalFluxGradient, NumericalFluxSecondOrder}, ::Impenetrable{FreeSlip}, ::SWModel)\n\nno second order flux computed for linear drag\n\"\"\"\nocean_boundary_state!(\n    ::Union{NumericalFluxGradient, NumericalFluxSecondOrder},\n    ::VelocityBC,\n    ::SWModel,\n    ::LinearDrag,\n    _...,\n) = nothing\n\n\"\"\"\n    ocean_boundary_state!(::NumericalFluxGradient, ::Impenetrable{FreeSlip}, ::SWModel)\n\napply free slip boundary condition for velocity\nsets non-reflective ghost point\n\"\"\"\nfunction ocean_boundary_state!(\n    ::NumericalFluxGradient,\n    ::Impenetrable{FreeSlip},\n    ::SWModel,\n    ::ConstantViscosity,\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n    args...,\n)\n    V⁻ = @SVector [Q⁻.U[1], Q⁻.U[2], -0]\n    V⁺ = V⁻ - n⁻ ⋅ V⁻ .* SVector(n⁻)\n    Q⁺.U = @SVector [V⁺[1], V⁺[2]]\n\n    return nothing\nend\n\n\"\"\"\n    shallow_normal_boundary_flux_second_order!(::NumericalFluxSecondOrder, ::Impenetrable{FreeSlip}, ::SWModel)\n\napply free slip boundary condition for velocity\napply zero numerical flux in the normal direction\n\"\"\"\nfunction ocean_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    ::Impenetrable{FreeSlip},\n    ::SWModel,\n    ::ConstantViscosity,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n    args...,\n)\n    Q⁺.U = Q⁻.U\n    D⁺.ν∇U = n⁻ * (@SVector [-0, -0])'\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::NumericalFluxFirstOrder, ::Impenetrable{NoSlip}, ::SWModel)\n\napply no slip boundary condition for velocity\nsets reflective ghost point\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::NumericalFluxFirstOrder,\n    ::Impenetrable{NoSlip},\n    ::SWModel,\n    ::TurbulenceClosure,\n    q⁺,\n    α⁺,\n    n⁻,\n    q⁻,\n    α⁻,\n    t,\n    args...,\n)\n    q⁺.η = q⁻.η\n    q⁺.U = -q⁻.U\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::NumericalFluxGradient, ::Impenetrable{NoSlip}, ::SWModel)\n\napply no slip boundary condition for velocity\nset numerical flux to zero for U\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::NumericalFluxGradient,\n    ::Impenetrable{NoSlip},\n    ::SWModel,\n    ::ConstantViscosity,\n    q⁺,\n    α⁺,\n    n⁻,\n    q⁻,\n    α⁻,\n    t,\n    args...,\n)\n    FT = eltype(q⁺)\n    q⁺.U = @SVector zeros(FT, 2)\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::NumericalFluxSecondOrder, ::Impenetrable{NoSlip}, ::SWModel)\n\napply no slip boundary condition for velocity\nsets ghost point to have no numerical flux on the boundary for U\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    ::Impenetrable{NoSlip},\n    ::SWModel,\n    ::ConstantViscosity,\n    q⁺,\n    σ⁺,\n    α⁺,\n    n⁻,\n    q⁻,\n    σ⁻,\n    α⁻,\n    t,\n    args...,\n)\n    q⁺.U = -q⁻.U\n    σ⁺.ν∇U = σ⁻.ν∇U\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::Penetrable{FreeSlip}, ::SWModel)\n\nno mass boundary condition for penetrable\n\"\"\"\nocean_boundary_state!(\n    ::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    ::Penetrable{FreeSlip},\n    ::SWModel,\n    ::ConstantViscosity,\n    _...,\n) = nothing\n\n\"\"\"\n    ocean_boundary_state!(::NumericalFluxSecondOrder, ::Penetrable{FreeSlip}, ::SWModel)\n\napply free slip boundary condition for velocity\napply zero numerical flux in the normal direction\n\"\"\"\nfunction ocean_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    ::Penetrable{FreeSlip},\n    ::SWModel,\n    ::ConstantViscosity,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n    args...,\n)\n    Q⁺.U = Q⁻.U\n    D⁺.ν∇U = n⁻ * (@SVector [-0, -0])'\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::Impenetrable{KinematicStress}, ::HBModel)\n\napply kinematic stress boundary condition for velocity\napplies free slip conditions for first-order and gradient fluxes\n\"\"\"\nfunction ocean_boundary_state!(\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    ::Impenetrable{<:KinematicStress},\n    shallow::SWModel,\n    turb::TurbulenceClosure,\n    args...,\n)\n    return ocean_boundary_state!(\n        nf,\n        Impenetrable(FreeSlip()),\n        shallow,\n        turb,\n        args...,\n    )\nend\n\n\"\"\"\n    ocean_boundary_state!(::NumericalFluxSecondOrder, ::Impenetrable{KinematicStress}, ::HBModel)\n\napply kinematic stress boundary condition for velocity\nsets ghost point to have specified flux on the boundary for ν∇u\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    ::Impenetrable{<:KinematicStress},\n    shallow::SWModel,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    Q⁺.U = Q⁻.U\n    D⁺.ν∇U = n⁻ * kinematic_stress(shallow.problem, A⁻.y, 1000)'\n    # applies windstress for now, will be fixed in a later PR\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::Penetrable{KinematicStress}, ::HBModel)\n\napply kinematic stress boundary condition for velocity\napplies free slip conditions for first-order and gradient fluxes\n\"\"\"\nfunction ocean_boundary_state!(\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    ::Penetrable{<:KinematicStress},\n    shallow::SWModel,\n    turb::TurbulenceClosure,\n    args...,\n)\n    return ocean_boundary_state!(\n        nf,\n        Penetrable(FreeSlip()),\n        shallow,\n        turb,\n        args...,\n    )\nend\n\n\"\"\"\n    ocean_boundary_state!(::NumericalFluxSecondOrder, ::Penetrable{KinematicStress}, ::HBModel)\n\napply kinematic stress boundary condition for velocity\nsets ghost point to have specified flux on the boundary for ν∇u\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    ::Penetrable{<:KinematicStress},\n    shallow::SWModel,\n    ::TurbulenceClosure,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    Q⁺.u = Q⁻.u\n    D⁺.ν∇u = n⁻ * kinematic_stress(shallow.problem, A⁻.y, 1000)'\n    # applies windstress for now, will be fixed in a later PR\n\n    return nothing\nend\n"
  },
  {
    "path": "src/Ocean/SplitExplicit/Communication.jl",
    "content": "@inline function initialize_states!(\n    baroclinic::HBModel{C},\n    barotropic::SWModel{C},\n    model_bc,\n    model_bt,\n    state_bc,\n    state_bt,\n) where {C <: Coupled}\n    model_bc.state_auxiliary.ΔGᵘ .= -0\n\n    return nothing\nend\n\n@inline function tendency_from_slow_to_fast!(\n    baroclinic::HBModel{C},\n    barotropic::SWModel{C},\n    model_bc,\n    model_bt,\n    state_bc,\n    state_bt,\n    forcing_tendency,\n) where {C <: Coupled}\n    FT = eltype(state_bc)\n    info = basic_grid_info(model_bc)\n    Nqh, Nqk = info.Nqh, info.Nqk\n    nelemv, nelemh = info.nvertelem, info.nhorzelem\n    nrealelemh = info.nhorzrealelem\n\n    #### integrate the tendency\n    model_int = model_bc.modeldata.integral_model\n    integral = model_int.balance_law\n    update_auxiliary_state!(model_int, integral, forcing_tendency, 0)\n\n    ### properly shape MPIStateArrays\n    num_aux_int = number_states(integral, Auxiliary())\n    data_int = model_int.state_auxiliary.data\n    data_int = reshape(data_int, Nqh, Nqk, num_aux_int, nelemv, nelemh)\n\n    num_aux_bt = number_states(barotropic, Auxiliary())\n    data_bt = model_bt.state_auxiliary.data\n    data_bt = reshape(data_bt, Nqh, num_aux_bt, nelemh)\n\n    num_aux_bc = number_states(baroclinic, Auxiliary())\n    data_bc = model_bc.state_auxiliary.data\n    data_bc = reshape(data_bc, Nqh, Nqk, num_aux_bc, nelemv, nelemh)\n\n    ### get vars indices\n    index_∫du = varsindex(vars_state(integral, Auxiliary(), FT), :(∫x))\n    index_Gᵁ = varsindex(vars_state(barotropic, Auxiliary(), FT), :Gᵁ)\n    index_ΔGᵘ = varsindex(vars_state(baroclinic, Auxiliary(), FT), :ΔGᵘ)\n\n    ### get top value (=integral over full depth) of ∫du\n    ∫du = @view data_int[:, end, index_∫du, end, 1:nrealelemh]\n\n    ### copy into Gᵁ of barotropic model\n    Gᵁ = @view data_bt[:, index_Gᵁ, 1:nrealelemh]\n    Gᵁ .= ∫du\n\n    ### get top value (=integral over full depth) of ∫du\n    ∫du = @view data_int[:, end:end, index_∫du, end:end, 1:nrealelemh]\n\n    ### save vertically averaged tendency to remove from 3D tendency\n    ### need to reshape for the broadcast\n    ΔGᵘ = @view data_bc[:, :, index_ΔGᵘ, :, 1:nrealelemh]\n    ΔGᵘ .-= ∫du / baroclinic.problem.H\n\n    return nothing\nend\n\n@inline function cummulate_fast_solution!(\n    baroclinic::HBModel{C},\n    barotropic::SWModel{C},\n    model_bt,\n    state_bt,\n    fast_time,\n    fast_dt,\n    substep,\n) where {C <: Coupled}\n    return nothing\nend\n\n@inline function reconcile_from_fast_to_slow!(\n    baroclinic::HBModel{C},\n    barotropic::SWModel{C},\n    model_bc,\n    model_bt,\n    state_bc,\n    state_bt,\n) where {C <: Coupled}\n    FT = eltype(state_bc)\n    info = basic_grid_info(model_bc)\n    Nqh, Nqk = info.Nqh, info.Nqk\n    nelemv, nelemh = info.nvertelem, info.nhorzelem\n    nrealelemh = info.nhorzrealelem\n\n    ### integrate the horizontal velocity\n    model_int = model_bc.modeldata.integral_model\n    integral = model_int.balance_law\n    update_auxiliary_state!(model_int, integral, state_bc, 0)\n\n    ### properly shape MPIStateArrays\n    num_aux_int = number_states(integral, Auxiliary())\n    data_int = model_int.state_auxiliary.data\n    data_int = reshape(data_int, Nqh, Nqk, num_aux_int, nelemv, nelemh)\n\n    num_aux_bt = number_states(barotropic, Auxiliary())\n    data_bt_aux = model_bt.state_auxiliary.data\n    data_bt_aux = reshape(data_bt_aux, Nqh, num_aux_bt, nelemh)\n\n    num_state_bt = number_states(barotropic, Prognostic())\n    data_bt_state = state_bt.data\n    data_bt_state = reshape(data_bt_state, Nqh, num_state_bt, nelemh)\n\n    num_state_bc = number_states(baroclinic, Prognostic())\n    data_bc_state = state_bc.data\n    data_bc_state =\n        reshape(data_bc_state, Nqh, Nqk, num_state_bc, nelemv, nelemh)\n\n    ### get vars indices\n    index_∫u = varsindex(vars_state(integral, Auxiliary(), FT), :(∫x))\n    index_Δu = varsindex(vars_state(barotropic, Auxiliary(), FT), :Δu)\n    index_U = varsindex(vars_state(barotropic, Prognostic(), FT), :U)\n    index_u = varsindex(vars_state(baroclinic, Prognostic(), FT), :u)\n    index_η_3D = varsindex(vars_state(baroclinic, Prognostic(), FT), :η)\n    index_η_2D = varsindex(vars_state(barotropic, Prognostic(), FT), :η)\n\n    ### get top value (=integral over full depth)\n    ∫u = @view data_int[:, end, index_∫u, end, 1:nrealelemh]\n\n    ### Δu is a place holder for 1/H * (Ū - ∫u)\n    Δu = @view data_bt_aux[:, index_Δu, 1:nrealelemh]\n    U = @view data_bt_state[:, index_U, 1:nrealelemh]\n    Δu .= 1 / baroclinic.problem.H * (U - ∫u)\n\n    ### copy the 2D contribution down the 3D solution\n    ### need to reshape for the broadcast\n    data_bt_aux = reshape(data_bt_aux, Nqh, 1, num_aux_bt, 1, nelemh)\n    Δu = @view data_bt_aux[:, :, index_Δu, :, 1:nrealelemh]\n    u = @view data_bc_state[:, :, index_u, :, 1:nrealelemh]\n    u .+= Δu\n\n    ### copy η from barotropic mode to baroclinic mode\n    ### need to reshape for the broadcast\n    data_bt_state = reshape(data_bt_state, Nqh, 1, num_state_bt, 1, nelemh)\n    η_2D = @view data_bt_state[:, :, index_η_2D, :, 1:nrealelemh]\n    η_3D = @view data_bc_state[:, :, index_η_3D, :, 1:nrealelemh]\n    η_3D .= η_2D\n\n    return nothing\nend\n"
  },
  {
    "path": "src/Ocean/SplitExplicit/HydrostaticBoussinesqCoupling.jl",
    "content": "using ..Ocean: coriolis_parameter\nusing ..HydrostaticBoussinesq\nusing ...DGMethods\n\nimport ...BalanceLaws\nimport ..HydrostaticBoussinesq:\n    viscosity_tensor,\n    coriolis_force!,\n    velocity_gradient_argument!,\n    velocity_gradient_flux!,\n    hydrostatic_pressure!,\n    compute_flow_deviation!\n\n@inline function velocity_gradient_argument!(m::HBModel, ::Coupled, G, Q, A, t)\n    G.∇u = Q.u\n    G.∇uᵈ = A.uᵈ\n\n    return nothing\nend\n\n@inline function velocity_gradient_flux!(m::HBModel, ::Coupled, D, G, Q, A, t)\n    ν = viscosity_tensor(m)\n    ∇u = @SMatrix [\n        G.∇uᵈ[1, 1] G.∇uᵈ[1, 2]\n        G.∇uᵈ[2, 1] G.∇uᵈ[2, 2]\n        G.∇u[3, 1] G.∇u[3, 2]\n    ]\n    D.ν∇u = -ν * ∇u\n\n    return nothing\nend\n\n@inline hydrostatic_pressure!(::HBModel, ::Coupled, _...) = nothing\n\n@inline function coriolis_force!(m::HBModel, ::Coupled, S, Q, A, t)\n    # f × u\n    f = coriolis_parameter(m, m.problem, A.y)\n    uᵈ, vᵈ = A.uᵈ # Horizontal components of velocity\n    S.u -= @SVector [-f * vᵈ, f * uᵈ]\n\n    return nothing\nend\n\n# Compute Horizontal Flow deviation from vertical mean\n@inline function compute_flow_deviation!(dg, m::HBModel, ::Coupled, Q, t)\n    FT = eltype(Q)\n    info = basic_grid_info(dg)\n    Nqh, Nqk = info.Nqh, info.Nqk\n    nelemv, nelemh = info.nvertelem, info.nhorzelem\n    nrealelemh = info.nhorzrealelem\n\n    #### integrate the tendency\n    model_int = dg.modeldata.integral_model\n    integral = model_int.balance_law\n    update_auxiliary_state!(model_int, integral, Q, 0)\n\n    ### properly shape MPIStateArrays\n    num_int = number_states(integral, Auxiliary())\n    data_int = model_int.state_auxiliary.data\n    data_int = reshape(data_int, Nqh, Nqk, num_int, nelemv, nelemh)\n\n    num_aux = number_states(m, Auxiliary())\n    data_aux = dg.state_auxiliary.data\n    data_aux = reshape(data_aux, Nqh, Nqk, num_aux, nelemv, nelemh)\n\n    num_state = number_states(m, Prognostic())\n    data_state = reshape(Q.data, Nqh, Nqk, num_state, nelemv, nelemh)\n\n    ### get vars indices\n    index_∫u = varsindex(vars_state(integral, Auxiliary(), FT), :(∫x))\n    index_uᵈ = varsindex(vars_state(m, Auxiliary(), FT), :uᵈ)\n    index_u = varsindex(vars_state(m, Prognostic(), FT), :u)\n\n    ### get top value (=integral over full depth)\n    ∫u = @view data_int[:, end:end, index_∫u, end:end, 1:nrealelemh]\n    uᵈ = @view data_aux[:, :, index_uᵈ, :, 1:nrealelemh]\n    u = @view data_state[:, :, index_u, :, 1:nrealelemh]\n\n    ## make a copy of horizontal velocity\n    ## and remove vertical mean velocity\n    uᵈ .= u\n    uᵈ .-= ∫u / m.problem.H\n\n    return nothing\nend\n"
  },
  {
    "path": "src/Ocean/SplitExplicit/ShallowWaterCoupling.jl",
    "content": "import ..ShallowWater: forcing_term!\n\n@inline function forcing_term!(::SWModel, ::Coupled, S, Q, A, t)\n    S.U += A.Gᵁ\n\n    return nothing\nend\n"
  },
  {
    "path": "src/Ocean/SplitExplicit/SplitExplicitModel.jl",
    "content": "module SplitExplicit\n\nusing StaticArrays\n\nusing ..Ocean\nusing ..HydrostaticBoussinesq\nusing ..ShallowWater\n\nusing ...VariableTemplates\nusing ...MPIStateArrays\nusing ...Mesh.Geometry\nusing ...DGMethods\nusing ...BalanceLaws\n\nimport ...BalanceLaws:\n    initialize_states!,\n    tendency_from_slow_to_fast!,\n    cummulate_fast_solution!,\n    reconcile_from_fast_to_slow!,\n    boundary_conditions\n\nHBModel = HydrostaticBoussinesqModel\nSWModel = ShallowWaterModel\n\nfunction initialize_states!(\n    ::HBModel{C},\n    ::SWModel{C},\n    _...,\n) where {C <: Uncoupled}\n    return nothing\nend\nfunction tendency_from_slow_to_fast!(\n    ::HBModel{C},\n    ::SWModel{C},\n    _...,\n) where {C <: Uncoupled}\n    return nothing\nend\nfunction cummulate_fast_solution!(\n    ::HBModel{C},\n    ::SWModel{C},\n    _...,\n) where {C <: Uncoupled}\n    return nothing\nend\nfunction reconcile_from_fast_to_slow!(\n    ::HBModel{C},\n    ::SWModel{C},\n    _...,\n) where {C <: Uncoupled}\n    return nothing\nend\n\ninclude(\"VerticalIntegralModel.jl\")\ninclude(\"Communication.jl\")\ninclude(\"ShallowWaterCoupling.jl\")\ninclude(\"HydrostaticBoussinesqCoupling.jl\")\n\nend\n"
  },
  {
    "path": "src/Ocean/SplitExplicit/VerticalIntegralModel.jl",
    "content": "import ...BalanceLaws:\n    vars_state,\n    init_state_prognostic!,\n    init_state_auxiliary!,\n    update_auxiliary_state!,\n    integral_load_auxiliary_state!,\n    integral_set_auxiliary_state!\n\nstruct VerticalIntegralModel{M} <: BalanceLaw\n    ocean::M\n    function VerticalIntegralModel(ocean::M) where {M}\n        return new{M}(ocean)\n    end\nend\n\nvars_state(tm::VerticalIntegralModel, st::Prognostic, FT) =\n    vars_state(tm.ocean, st, FT)\n\nfunction vars_state(m::VerticalIntegralModel, ::Auxiliary, T)\n    @vars begin\n        ∫x::SVector{2, T}\n    end\nend\n\ninit_state_auxiliary!(\n    tm::VerticalIntegralModel,\n    A::MPIStateArray,\n    grid,\n    direction,\n) = nothing\n\nfunction vars_state(m::VerticalIntegralModel, ::UpwardIntegrals, T)\n    @vars begin\n        ∫x::SVector{2, T}\n    end\nend\n\n@inline function integral_load_auxiliary_state!(\n    m::VerticalIntegralModel,\n    I::Vars,\n    Q::Vars,\n    A::Vars,\n)\n    I.∫x = A.∫x\n\n    return nothing\nend\n\n@inline function integral_set_auxiliary_state!(\n    m::VerticalIntegralModel,\n    A::Vars,\n    I::Vars,\n)\n    A.∫x = I.∫x\n\n    return nothing\nend\n\nfunction update_auxiliary_state!(\n    dg::DGModel,\n    tm::VerticalIntegralModel,\n    x::MPIStateArray,\n    t::Real,\n)\n    A = dg.state_auxiliary\n\n    # copy tendency vector to aux state for integration\n    function f!(::VerticalIntegralModel, x, A, t)\n        @inbounds begin\n            A.∫x = @SVector [x.u[1], x.u[2]]\n        end\n\n        return nothing\n    end\n    update_auxiliary_state!(f!, dg, tm, x, t)\n\n    # compute integral for Gᵁ\n    indefinite_stack_integral!(dg, tm, x, A, t) # bottom -> top\n\n    return true\nend\n"
  },
  {
    "path": "src/Ocean/SplitExplicit01/BarotropicModel.jl",
    "content": "struct BarotropicModel{M} <: AbstractOceanModel\n    baroclinic::M\n    function BarotropicModel(baroclinic::M) where {M}\n        return new{M}(baroclinic)\n    end\nend\n\nparameter_set(m::BarotropicModel) = parameter_set(m.baroclinic)\n\nfunction vars_state(m::BarotropicModel, ::Prognostic, T)\n    @vars begin\n        U::SVector{2, T}\n        η::T\n    end\nend\n\nfunction init_state_prognostic!(\n    m::BarotropicModel,\n    Q::Vars,\n    A::Vars,\n    localgeo,\n    t,\n)\n    Q.U = @SVector [-0, -0]\n    Q.η = -0\n    return nothing\nend\n\nfunction vars_state(m::BarotropicModel, ::Auxiliary, T)\n    @vars begin\n        Gᵁ::SVector{2, T}  # integral of baroclinic tendency\n        U_c::SVector{2, T} # cumulate U value over fast time-steps\n        η_c::T             # cumulate η value over fast time-steps\n        U_s::SVector{2, T} # starting U field value\n        η_s::T             # starting η field value\n        Δu::SVector{2, T}  # reconciliation adjustment to u, Δu = 1/H * (U_averaged - ∫u)\n        η_diag::T          # η from baroclinic model (for diagnostic)\n        Δη::T              # diagnostic difference: η_barotropic - η_baroclinic\n        y::T               # y-coordinate of grid\n    end\nend\n\nfunction init_state_auxiliary!(\n    m::BarotropicModel,\n    state_aux::MPIStateArray,\n    grid,\n    direction,\n)\n    init_state_auxiliary!(\n        m,\n        (m, A, tmp, geom) -> ocean_init_aux!(m, m.baroclinic.problem, A, geom),\n        state_aux,\n        grid,\n        direction,\n    )\nend\n\nfunction vars_state(m::BarotropicModel, ::Gradient, T)\n    @vars begin\n        U::SVector{2, T}\n    end\nend\n\n@inline function compute_gradient_argument!(\n    m::BarotropicModel,\n    G::Vars,\n    Q::Vars,\n    A,\n    t,\n)\n    G.U = Q.U\n    return nothing\nend\n\nfunction vars_state(m::BarotropicModel, ::GradientFlux, T)\n    @vars begin\n        ν∇U::SMatrix{3, 2, T, 6}\n    end\nend\n\n@inline function compute_gradient_flux!(\n    m::BarotropicModel,\n    D::Vars,\n    G::Grad,\n    Q::Vars,\n    A::Vars,\n    t,\n)\n    ν = viscosity_tensor(m)\n    D.ν∇U = -ν * G.U\n\n    return nothing\nend\n\n@inline function viscosity_tensor(bm::BarotropicModel)\n    m = bm.baroclinic\n    return Diagonal(@SVector [m.νʰ, m.νʰ, 0])\nend\n\nvars_state(m::BarotropicModel, ::UpwardIntegrals, T) = @vars()\nvars_state(m::BarotropicModel, ::DownwardIntegrals, T) = @vars()\n\n@inline function flux_first_order!(\n    m::BarotropicModel,\n    F::Grad,\n    Q::Vars,\n    A::Vars,\n    t::Real,\n    direction,\n)\n    @inbounds begin\n        U = @SVector [Q.U[1], Q.U[2], 0]\n        η = Q.η\n        H = m.baroclinic.problem.H\n        g = m.baroclinic.grav\n        Iʰ = @SMatrix [\n            1 0\n            0 1\n            0 0\n        ]\n\n        F.η += U\n        F.U += g * H * η * Iʰ\n    end\nend\n\n@inline function flux_second_order!(\n    m::BarotropicModel,\n    F::Grad,\n    Q::Vars,\n    D::Vars,\n    HD::Vars,\n    A::Vars,\n    t::Real,\n)\n    # numerical diffusivity for stability\n    F.U += D.ν∇U\n\n    return nothing\nend\n\n@inline function source!(\n    m::BarotropicModel,\n    S::Vars,\n    Q::Vars,\n    D::Vars,\n    A::Vars,\n    t::Real,\n    direction,\n)\n    @inbounds begin\n        U = Q.U\n\n        # f × u\n        f = coriolis_force(m.baroclinic, A.y)\n        S.U -= @SVector [-f * U[2], f * U[1]]\n\n        # vertically integrated baroclinic model tendency\n        S.U += A.Gᵁ\n    end\nend\n\n@inline wavespeed(m::BarotropicModel, n⁻, _...) =\n    abs(SVector(m.baroclinic.cʰ, m.baroclinic.cʰ, m.baroclinic.cᶻ)' * n⁻)\n\n# We want not have jump penalties on η (since not a flux variable)\nfunction update_penalty!(\n    ::RusanovNumericalFlux,\n    ::BarotropicModel,\n    n⁻,\n    λ,\n    ΔQ::Vars,\n    Q⁻,\n    A⁻,\n    Q⁺,\n    A⁺,\n    t,\n)\n    ΔQ.η = -0\n\n    return nothing\nend\n\nboundary_conditions(bm::BarotropicModel) =\n    (bm.baroclinic.problem.boundary_conditions[1],)\n\n\n\"\"\"\n    boundary_state!(nf, bc, ::BarotropicModel, args...)\n\napplies boundary conditions for this model\ndispatches to a function in OceanBoundaryConditions.jl based on BC type defined by a problem such as SimpleBoxProblem.jl\n\"\"\"\n@inline function boundary_state!(nf, bc, bm::BarotropicModel, args...)\n    return ocean_model_boundary!(bm, bc, nf, args...)\nend\n"
  },
  {
    "path": "src/Ocean/SplitExplicit01/Communication.jl",
    "content": "import ...BalanceLaws:\n    tendency_from_slow_to_fast!,\n    cummulate_fast_solution!,\n    reconcile_from_fast_to_slow!\n\nusing Printf\n\n@inline function set_fast_for_stepping!(\n    slow::OceanModel,\n    fast::BarotropicModel,\n    dgFast,\n    Qfast,\n    S_fast,\n    slow_dt,\n    rkC,\n    rkW,\n    s,\n    nStages,\n    fast_time_rec,\n    fast_steps,\n)\n    FT = typeof(slow_dt)\n\n    #- inverse ratio of additional fast time steps (for weighted average)\n    #  --> do 1/add more time-steps and average from: 1 - 1/add up to: 1 + 1/add\n    add = slow.add_fast_substeps\n\n    #- set time-step\n    #  Warning: only make sense for LS3NRK33Heuns\n    #  where 12 is lowest common mutiple (LCM) of all RK-Coeff inverse\n    fast_dt = fast_time_rec[1]\n    steps = fast_dt > 0 ? ceil(Int, slow_dt / fast_dt / FT(12)) : 1\n    ntsFull = 12 * steps\n    fast_dt = slow_dt / ntsFull\n    add = add > 0 ? floor(Int, ntsFull / add) : 0\n\n    #- time to start fast time-stepping (fast_time_rec[3]) for this stage:\n    if s == nStages\n        #  Warning: only works with few RK-scheme such as LS3NRK33Heuns\n        fast_time_rec[3] = rkW[1]\n        fract_dt = (1 - fast_time_rec[3])\n        fast_time_rec[3] *= slow_dt\n        fast_steps[2] = 1\n    else\n        fast_time_rec[3] = 0.0\n        fract_dt = rkC[s + 1] - fast_time_rec[3]\n        fast_steps[2] = 0\n    end\n\n    #- set number of sub-steps we need\n    #  will time-average fast over: fast_steps[1] , fast_steps[3]\n    #  centered on fract_dt*slow_dt which corresponds to advance in time of slow\n    steps = ceil(Int, fract_dt * slow_dt / fast_dt)\n    add = min(add, steps - 1)\n    fast_steps[1] = steps - add\n    fast_steps[3] = steps + add\n    fast_time_rec[1] = fract_dt * slow_dt / steps\n\n    #- select which fast time-step (fast_steps[2]) solution to save for next time-step\n    #  Warning: only works with few RK-scheme such as LS3NRK33Heuns\n    fast_steps[2] *= steps\n    if s == 1\n        fast_steps[2] = round(Int, ntsFull * rkW[1])\n    end\n    # @printf(\"Update @ s= %i : frac_dt = %.6f , dt_fast = %.1f , steps= %i , add= %i\\n\",\n    #          s, fract_dt, fast_time_rec[1], steps, add)\n    # println(\" fast_time_rec = \",fast_time_rec)\n    # println(\" fast_steps = \",fast_steps)\n\n    # set starting point for fast-state solution\n    #  Warning: only works with few RK-scheme such as LS3NRK33Heuns\n    if s == 1\n        S_fast.η .= Qfast.η\n        S_fast.U .= Qfast.U\n    elseif s == nStages\n        Qfast.η .= dgFast.state_auxiliary.η_s\n        Qfast.U .= dgFast.state_auxiliary.U_s\n    else\n        Qfast.η .= S_fast.η\n        Qfast.U .= S_fast.U\n    end\n\n    # initialise cumulative arrays\n    fast_time_rec[2] = 0.0\n    dgFast.state_auxiliary.η_c .= -0\n    dgFast.state_auxiliary.U_c .= (@SVector [-0, -0])'\n\n    return nothing\nend\n\n@inline function initialize_fast_state!(\n    slow::OceanModel,\n    fast::BarotropicModel,\n    dgSlow,\n    dgFast,\n    Qslow,\n    Qfast,\n    slow_dt,\n    fast_time_rec,\n    fast_steps;\n    firstStage = false,\n)\n\n    #- inverse ratio of additional fast time steps (for weighted average)\n    #  --> do 1/add more time-steps and average from: 1 - 1/add up to: 1 + 1/add\n    add = slow.add_fast_substeps\n\n    #- set time-step and number of sub-steps we need\n    #  will time-average fast over: fast_steps[1] , fast_steps[3]\n    #  centered on fast_steps[2] which corresponds to advance in time of slow\n    fast_dt = fast_time_rec[1]\n    if add == 0\n        steps = fast_dt > 0 ? ceil(Int, slow_dt / fast_dt) : 1\n        fast_steps[1:3] = [1 1 1] * steps\n    else\n        steps = fast_dt > 0 ? ceil(Int, slow_dt / fast_dt / add) : 1\n        fast_steps[2] = add * steps\n        fast_steps[1] = (add - 1) * steps\n        fast_steps[3] = (add + 1) * steps\n    end\n    fast_time_rec[1] = slow_dt / fast_steps[2]\n    fast_time_rec[2] = 0.0\n    # @printf(\"Update: frac_dt = %.1f , dt_fast = %.1f , nsubsteps= %i\\n\",\n    #          slow_dt,fast_time_rec[1],fast_steps[3])\n    # println(\" fast_steps = \",fast_steps)\n\n    dgFast.state_auxiliary.η_c .= -0\n    dgFast.state_auxiliary.U_c .= (@SVector [-0, -0])'\n\n    # set fast-state to previously stored value\n    if !firstStage\n        Qfast.η .= dgFast.state_auxiliary.η_s\n        Qfast.U .= dgFast.state_auxiliary.U_s\n    end\n\n    return nothing\nend\n\n@inline function initialize_adjustment!(\n    slow::OceanModel,\n    fast::BarotropicModel,\n    dgSlow,\n    dgFast,\n    Qslow,\n    Qfast,\n)\n    ## reset tendency adjustment before calling Baroclinic Model\n    dgSlow.state_auxiliary.ΔGu .= 0\n\n    return nothing\nend\n\n@inline function tendency_from_slow_to_fast!(\n    slow::OceanModel,\n    fast::BarotropicModel,\n    dgSlow,\n    dgFast,\n    Qslow,\n    Qfast,\n    dQslow2fast,\n)\n    FT = eltype(Qslow)\n\n    # integrate the tendency\n    tendency_dg = dgSlow.modeldata.tendency_dg\n    tend = tendency_dg.balance_law\n    grid = dgSlow.grid\n    elems = grid.topology.elems\n    update_auxiliary_state!(tendency_dg, tend, dQslow2fast, 0, elems)\n\n    info = basic_grid_info(dgSlow)\n    Nqh, Nqk = info.Nqh, info.Nqk\n    nelemv, nelemh = info.nvertelem, info.nhorzelem\n    nrealelemh = info.nhorzrealelem\n\n    ## get top value (=integral over full depth) of ∫du\n    nb_aux_tnd = number_states(tend, Auxiliary())\n    data_tnd = reshape(\n        tendency_dg.state_auxiliary.data,\n        Nqh,\n        Nqk,\n        nb_aux_tnd,\n        nelemv,\n        nelemh,\n    )\n    index_∫du = varsindex(vars_state(tend, Auxiliary(), FT), :∫du)\n    flat_∫du = @view data_tnd[:, end, index_∫du, end, 1:nrealelemh]\n\n    ## copy into Gᵁ of dgFast\n    nb_aux_fst = number_states(fast, Auxiliary())\n    data_fst = reshape(dgFast.state_auxiliary.data, Nqh, nb_aux_fst, nelemh)\n    index_Gᵁ = varsindex(vars_state(fast, Auxiliary(), FT), :Gᵁ)\n    boxy_Gᵁ = @view data_fst[:, index_Gᵁ, 1:nrealelemh]\n    boxy_Gᵁ .= flat_∫du\n\n    ## scale by -1/H and copy back to ΔGu\n    # note: since tendency_dg.state_auxiliary.∫du is not used after this, could be\n    #   re-used to store a 3-D copy of \"-Gu\"\n    nb_aux_slw = number_states(slow, Auxiliary())\n    data_slw = reshape(\n        dgSlow.state_auxiliary.data,\n        Nqh,\n        Nqk,\n        nb_aux_slw,\n        nelemv,\n        nelemh,\n    )\n    index_ΔGu = varsindex(vars_state(slow, Auxiliary(), FT), :ΔGu)\n    boxy_ΔGu = @view data_slw[:, :, index_ΔGu, :, 1:nrealelemh]\n    boxy_∫du = @view data_tnd[:, end:end, index_∫du, end:end, 1:nrealelemh]\n    boxy_ΔGu .= -boxy_∫du / slow.problem.H\n\n    return nothing\nend\n\n@inline function cummulate_fast_solution!(\n    fast::BarotropicModel,\n    dgFast,\n    Qfast,\n    fast_time,\n    fast_dt,\n    substep,\n    fast_steps,\n    fast_time_rec,\n)\n    #- might want to use some of the weighting factors: weights_η & weights_U\n    #- should account for case where fast_dt < fast.param.dt\n\n    # cumulate Fast solution:\n    if substep >= fast_steps[1]\n        dgFast.state_auxiliary.U_c .+= Qfast.U\n        dgFast.state_auxiliary.η_c .+= Qfast.η\n        fast_time_rec[2] += 1.0\n    end\n\n    # save mid-point solution to start from the next time-step\n    if substep == fast_steps[2]\n        dgFast.state_auxiliary.U_s .= Qfast.U\n        dgFast.state_auxiliary.η_s .= Qfast.η\n    end\n\n    return nothing\nend\n\n@inline function reconcile_from_fast_to_slow!(\n    slow::OceanModel,\n    fast::BarotropicModel,\n    dgSlow,\n    dgFast,\n    Qslow,\n    Qfast,\n    fast_time_rec;\n    lastStage = false,\n)\n    FT = eltype(Qslow)\n    info = basic_grid_info(dgSlow)\n    Nqh, Nqk = info.Nqh, info.Nqk\n    nelemv, nelemh = info.nvertelem, info.nhorzelem\n    nrealelemh = info.nhorzrealelem\n    grid = dgSlow.grid\n    elems = grid.topology.elems\n\n    # need to calculate int_u using integral kernels\n    # u_slow := u_slow + (1/H) * (u_fast - \\int_{-H}^{0} u_slow)\n\n    ## get time weighted averaged out of cumulative arrays\n    dgFast.state_auxiliary.U_c .*= 1 / fast_time_rec[2]\n    dgFast.state_auxiliary.η_c .*= 1 / fast_time_rec[2]\n    # @printf(\" reconcile_from_fast_to_slow! @ s= %i : time_Count = %6.3f\\n\",\n    #          0, fast_time_rec[2])\n    #   #      s, fast_time_rec[2])\n\n    ## Compute: \\int_{-H}^{0} u_slow\n\n    # integrate vertically horizontal velocity\n    flowintegral_dg = dgSlow.modeldata.flowintegral_dg\n    flowint = flowintegral_dg.balance_law\n    update_auxiliary_state!(flowintegral_dg, flowint, Qslow, 0, elems)\n\n    # get top value (=integral over full depth)\n    nb_aux_flw = number_states(flowint, Auxiliary())\n    data_flw = reshape(\n        flowintegral_dg.state_auxiliary.data,\n        Nqh,\n        Nqk,\n        nb_aux_flw,\n        nelemv,\n        nelemh,\n    )\n    index_∫u = varsindex(vars_state(flowint, Auxiliary(), FT), :∫u)\n    flat_∫u = @view data_flw[:, end, index_∫u, end, 1:nrealelemh]\n\n    ## substract ∫u from U and divide by H\n\n    # Δu is a place holder for 1/H * (Ū - ∫u)\n    Δu = dgFast.state_auxiliary.Δu\n    Δu .= dgFast.state_auxiliary.U_c\n\n    nb_aux_fst = number_states(fast, Auxiliary())\n    data_fst = reshape(dgFast.state_auxiliary.data, Nqh, nb_aux_fst, nelemh)\n    index_Δu = varsindex(vars_state(fast, Auxiliary(), FT), :Δu)\n    boxy_Δu = @view data_fst[:, index_Δu, 1:nrealelemh]\n    boxy_Δu .-= flat_∫u\n    boxy_Δu ./= slow.problem.H\n\n    ## apply the 2D correction to the 3D solution\n    nb_cons_slw = number_states(slow, Prognostic())\n    data_slw = reshape(Qslow.data, Nqh, Nqk, nb_cons_slw, nelemv, nelemh)\n    index_u = varsindex(vars_state(slow, Prognostic(), FT), :u)\n    boxy_u = @view data_slw[:, :, index_u, :, 1:nrealelemh]\n    boxy_u .+= reshape(boxy_Δu, Nqh, 1, 2, 1, nrealelemh)\n\n    ## save Eta from 3D model into η_diag (aux var of 2D model)\n    ## and store difference between η from Barotropic Model and η_diag\n    ## Note: since 3D η is not used (just in output), only do this at last stage\n    ##       (save computation and get Δη diagnose over full time-step)\n    if lastStage\n        index_η = varsindex(vars_state(slow, Prognostic(), FT), :η)\n        boxy_η_3D = @view data_slw[:, :, index_η, :, 1:nrealelemh]\n        flat_η = @view data_slw[:, end, index_η, end, 1:nrealelemh]\n        index_η_diag = varsindex(vars_state(fast, Auxiliary(), FT), :η_diag)\n        boxy_η_diag = @view data_fst[:, index_η_diag, 1:nrealelemh]\n        boxy_η_diag .= flat_η\n        dgFast.state_auxiliary.Δη .=\n            dgFast.state_auxiliary.η_c - dgFast.state_auxiliary.η_diag\n\n        ## copy 2D model Eta over to 3D model\n        index_η_c = varsindex(vars_state(fast, Auxiliary(), FT), :η_c)\n        boxy_η_2D = @view data_fst[:, index_η_c, 1:nrealelemh]\n        boxy_η_3D .= reshape(boxy_η_2D, Nqh, 1, 1, 1, nrealelemh)\n\n        # reset fast-state to end of time-step value\n        Qfast.η .= dgFast.state_auxiliary.η_s\n        Qfast.U .= dgFast.state_auxiliary.U_s\n    end\n\n    return nothing\nend\n"
  },
  {
    "path": "src/Ocean/SplitExplicit01/Continuity3dModel.jl",
    "content": "struct Continuity3dModel{M} <: AbstractOceanModel\n    ocean::M\n    function Continuity3dModel(ocean::M) where {M}\n        return new{M}(ocean)\n    end\nend\nvars_state(cm::Continuity3dModel, ::Prognostic, FT) =\n    vars_state(cm.ocean, Prognostic(), FT)\n\n# Continuity3dModel is used to compute the horizontal divergence of u\n\nvars_state(cm::Continuity3dModel, ::Auxiliary, T) = @vars()\nvars_state(cm::Continuity3dModel, ::Gradient, T) = @vars()\nvars_state(cm::Continuity3dModel, ::GradientFlux, T) = @vars()\nvars_state(cm::Continuity3dModel, ::UpwardIntegrals, T) = @vars()\ninit_state_auxiliary!(cm::Continuity3dModel, _...) = nothing\ninit_state_prognostic!(cm::Continuity3dModel, _...) = nothing\n@inline flux_second_order!(cm::Continuity3dModel, _...) = nothing\n@inline source!(cm::Continuity3dModel, _...) = nothing\n@inline update_penalty!(::RusanovNumericalFlux, ::Continuity3dModel, _...) =\n    nothing\n\n# This allows the balance law framework to compute the horizontal gradient of u\n# (which will be stored back in the field θ)\n@inline function flux_first_order!(\n    m::Continuity3dModel,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n)\n    @inbounds begin\n        u = state.u # Horizontal components of velocity\n        v = @SVector [u[1], u[2], -0]\n\n        # ∇ • (v)\n        # Just using θ to store w = ∇h • u\n        flux.θ += v\n    end\n\n    return nothing\nend\n\n# This is zero because when taking the horizontal gradient we're piggy-backing\n# on θ and want to ensure we do not use it's jump\n@inline wavespeed(cm::Continuity3dModel, n⁻, _...) = -zero(eltype(n⁻))\n\nboundary_conditions(cm::Continuity3dModel) = (\n    cm.ocean.problem.boundary_conditions[1],\n    cm.ocean.problem.boundary_conditions[1],\n    cm.ocean.problem.boundary_conditions[1],\n)\n\nboundary_state!(\n    ::Union{NumericalFluxGradient, NumericalFluxSecondOrder},\n    bc,\n    cm::Continuity3dModel,\n    _...,\n) = nothing\n\n\"\"\"\n    boundary_state!(nf, bc, ::Continuity3dModel, args...)\n\napplies boundary conditions for the hyperbolic fluxes\ndispatches to a function in OceanBoundaryConditions.jl based on BC type defined by a problem such as SimpleBoxProblem.jl\n\"\"\"\n\n@inline function boundary_state!(\n    nf::NumericalFluxFirstOrder,\n    bc,\n    cm::Continuity3dModel,\n    args...,\n)\n    return ocean_model_boundary!(cm, bc, nf, args...)\nend\n"
  },
  {
    "path": "src/Ocean/SplitExplicit01/IVDCModel.jl",
    "content": "# Linear model equations, for split-explicit ocean model implicit vertical diffusion\n# convective adjustment step.\n#\n# In this version the operator is tweked to be the indentity for testing\n\n\"\"\"\n IVDCModel{M} <: BalanceLaw\n\n This code defines DG `BalanceLaw` terms for an operator, L, that is evaluated from iterative\n implicit solver to solve an equation of the form\n\n (L + 1/Δt) ϕ^{n+1} = ϕ^{n}/Δt\n\n  where L is a vertical diffusion operator with a spatially varying diffusion\n  coefficient.\n\n # Usage\n\n parent_model  = OceanModel{FT}(prob...)\n linear_model  = IVDCModel( parent_model )\n\n\"\"\"\n\n# Create a new child linear model instance, attached to whatever parent\n# BalanceLaw instantiates this.\n# (Not sure we need parent, but maybe we will get some parameters from it)\nstruct IVDCModel{M} <: AbstractOceanModel\n    parent_om::M\n    function IVDCModel(parent_om::M;) where {M}\n        return new{M}(parent_om)\n    end\nend\n\n\"\"\"\n Set model state variables and operators\n\"\"\"\n\n# State variable and initial value, just one for now, θ\n\nvars_state(m::IVDCModel, ::Prognostic, FT) = @vars(θ::FT)\n\nfunction init_state_prognostic!(m::IVDCModel, Q::Vars, A::Vars, localgeo, t)\n    @inbounds begin\n        Q.θ = -0\n    end\n    return nothing\nend\n\nvars_state(m::IVDCModel, ::Auxiliary, FT) = @vars(θ_init::FT)\nfunction init_state_auxiliary!(m::IVDCModel, A::Vars, _...)\n    @inbounds begin\n        A.θ_init = -0\n    end\n    return nothing\nend\n\n# Variables and operations used in differentiating first derivatives\n\nvars_state(m::IVDCModel, ::Gradient, FT) = @vars(∇θ::FT, ∇θ_init::FT,)\n\n@inline function compute_gradient_argument!(\n    m::IVDCModel,\n    G::Vars,\n    Q::Vars,\n    A,\n    t,\n)\n    G.∇θ = Q.θ\n    G.∇θ_init = A.θ_init\n\n    return nothing\nend\n\n# Variables and operations used in differentiating second derivatives\n\nvars_state(m::IVDCModel, ::GradientFlux, FT) = @vars(κ∇θ::SVector{3, FT})\n\n@inline function compute_gradient_flux!(\n    m::IVDCModel,\n    D::Vars,\n    G::Grad,\n    Q::Vars,\n    A::Vars,\n    t,\n)\n\n    κ = diffusivity_tensor(m, G.∇θ_init[3])\n    D.κ∇θ = -κ * G.∇θ\n\n    return nothing\nend\n\n@inline function diffusivity_tensor(m::IVDCModel, ∂θ∂z)\n    κᶻ = m.parent_om.κᶻ * 0.5\n    κᶜ = m.parent_om.κᶜ\n    ∂θ∂z < 0 ? κ = (@SVector [0, 0, κᶜ]) : κ = (@SVector [0, 0, κᶻ])\n    # ∂θ∂z <= 1e-10 ? κ = (@SVector [0, 0, κᶜ]) : κ = (@SVector [0, 0, κᶻ])\n\n    return Diagonal(-κ)\nend\n\n# Function to apply I to state variable\n\n@inline function source!(\n    m::IVDCModel,\n    S::Vars,\n    Q::Vars,\n    D::Vars,\n    A::Vars,\n    t,\n    direction,\n)\n    #ivdc_dt = m.ivdc_dt\n    ivdc_dt = m.parent_om.ivdc_dt\n    @inbounds begin\n        S.θ = Q.θ / ivdc_dt\n        # S.θ = 0\n    end\n\n    return nothing\nend\n\n## Numerical fluxes and boundaries\n\nfunction flux_first_order!(::IVDCModel, _...) end\n\nfunction flux_second_order!(\n    ::IVDCModel,\n    F::Grad,\n    S::Vars,\n    D::Vars,\n    H::Vars,\n    A::Vars,\n    t,\n)\n    F.θ += D.κ∇θ\n    # F.θ = 0\n\nend\n\nfunction wavespeed(m::IVDCModel, n⁻, _...)\n    C = abs(SVector(m.parent_om.cʰ, m.parent_om.cʰ, m.parent_om.cᶻ)' * n⁻)\n    # C = abs(SVector(m.parent_om.cʰ, m.parent_om.cʰ, 50)' * n⁻)\n    # C = abs(SVector(1, 1, 1)' * n⁻)\n    ### C = abs(SVector(10, 10, 10)' * n⁻)\n    # C = abs(SVector(50, 50, 50)' * n⁻)\n    # C = abs(SVector( 0,  0,  0)' * n⁻)\n    return C\nend\n\nboundary_conditions(m::IVDCModel) = boundary_conditions(m.parent_om)\nfunction boundary_state!(\n    nf::Union{\n        NumericalFluxFirstOrder,\n        NumericalFluxGradient,\n        CentralNumericalFluxGradient,\n    },\n    bc,\n    m::IVDCModel,\n    Q⁺,\n    A⁺,\n    n,\n    Q⁻,\n    A⁻,\n    t,\n    _...,\n)\n    Q⁺.θ = Q⁻.θ\n\n    return nothing\nend\n\n###    From -  function numerical_boundary_flux_gradient! , DGMethods/NumericalFluxes.jl\n###    boundary_state!(\n###        numerical_flux,\n###        balance_law,\n###        state_conservative⁺,\n###        state_auxiliary⁺,\n###        normal_vector,\n###        state_conservative⁻,\n###        state_auxiliary⁻,\n###        bctype,\n###        t,\n###        state1⁻,\n###        aux1⁻,\n###    )\n\nfunction boundary_state!(\n    nf::Union{NumericalFluxSecondOrder, CentralNumericalFluxSecondOrder},\n    bctype,\n    m::IVDCModel,\n    Q⁺,\n    D⁺,\n    HD⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    HD⁻,\n    A⁻,\n    t,\n    _...,\n)\n    Q⁺.θ = Q⁻.θ\n    D⁺.κ∇θ = n⁻ * -0\n    # D⁺.κ∇θ = n⁻ * -0 + 7000\n    # D⁺.κ∇θ = -D⁻.κ∇θ\n\n    return nothing\nend\n\n###    boundary_state!(\n###        numerical_flux,\n###        balance_law,\n###        state_conservative⁺,\n###        state_gradient_flux⁺,\n###        state_auxiliary⁺,\n###        normal_vector,\n###        state_conservative⁻,\n###        state_gradient_flux⁻,\n###        state_auxiliary⁻,\n###        bctype,\n###        t,\n###        state1⁻,\n###        diff1⁻,\n###        aux1⁻,\n###    )\n\n###    boundary_flux_second_order!(\n###        numerical_flux,\n###        balance_law,\n###        Grad{S}(flux),\n###        state_conservative⁺,\n###        state_gradient_flux⁺,\n###        state_hyperdiffusive⁺,\n###        state_auxiliary⁺,\n###        normal_vector,\n###        state_conservative⁻,\n###        state_gradient_flux⁻,\n###        state_hyperdiffusive⁻,\n###        state_auxiliary⁻,\n###        bctype,\n###        t,\n###        state1⁻,\n###        diff1⁻,\n###        aux1⁻,\n###    )\n"
  },
  {
    "path": "src/Ocean/SplitExplicit01/OceanBoundaryConditions.jl",
    "content": "abstract type OceanBoundaryCondition end\n\n\"\"\"\n    Defining dummy structs to dispatch on for boundary conditions.\n\"\"\"\nstruct CoastlineFreeSlip <: OceanBoundaryCondition end\nstruct CoastlineNoSlip <: OceanBoundaryCondition end\nstruct OceanFloorFreeSlip <: OceanBoundaryCondition end\nstruct OceanFloorNoSlip <: OceanBoundaryCondition end\nstruct OceanFloorLinearDrag <: OceanBoundaryCondition end\nstruct OceanSurfaceNoStressNoForcing <: OceanBoundaryCondition end\nstruct OceanSurfaceStressNoForcing <: OceanBoundaryCondition end\nstruct OceanSurfaceNoStressForcing <: OceanBoundaryCondition end\nstruct OceanSurfaceStressForcing <: OceanBoundaryCondition end\n\n# these functions just trim off the extra arguments\nfunction ocean_model_boundary!(\n    model::AbstractOceanModel,\n    bc,\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    Q⁺,\n    A⁺,\n    n,\n    Q⁻,\n    A⁻,\n    t,\n    _...,\n)\n    return ocean_boundary_state!(model, bc, nf, Q⁺, A⁺, n, Q⁻, A⁻, t)\nend\n\nfunction ocean_model_boundary!(\n    model::AbstractOceanModel,\n    bc,\n    nf::NumericalFluxSecondOrder,\n    Q⁺,\n    D⁺,\n    HD⁺,\n    A⁺,\n    n,\n    Q⁻,\n    D⁻,\n    HD⁻,\n    A⁻,\n    t,\n    _...,\n)\n    return ocean_boundary_state!(model, bc, nf, Q⁺, D⁺, A⁺, n, Q⁻, D⁻, A⁻, t)\nend\n\n\"\"\"\n    CoastlineFreeSlip\n\napplies boundary condition ∇u = 0 and ∇θ = 0\n\"\"\"\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::CoastlineFreeSlip, ::NumericalFluxFirstOrder)\n\napply free slip boundary conditions for velocity\napply no penetration boundary for temperature\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::AbstractOceanModel,\n    ::CoastlineFreeSlip,\n    ::NumericalFluxFirstOrder,\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n)\n    u⁻ = Q⁻.u\n    n = @SVector [n⁻[1], n⁻[2]]\n\n    # Q⁺.u = u⁻ - 2 * (n⋅u⁻) * n\n    Q⁺.u = u⁻ - 2 * (n ∘ u⁻) * n\n\n    return nothing\nend\n\n@inline function ocean_boundary_state!(\n    ::BarotropicModel,\n    ::CoastlineFreeSlip,\n    ::NumericalFluxFirstOrder,\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n)\n    U⁻ = Q⁻.U\n    n = @SVector [n⁻[1], n⁻[2]]\n\n    # Q⁺.U = U⁻ - 2 * (n⋅U⁻) * n\n    Q⁺.U = U⁻ - 2 * (n ∘ U⁻) * n\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::CoastlineFreeSlip, ::NumericalFluxGradient)\n\napply free slip boundary conditions for velocity\napply no penetration boundary for temperature\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::AbstractOceanModel,\n    ::CoastlineFreeSlip,\n    ::NumericalFluxGradient,\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n)\n    u⁻ = Q⁻.u\n    ud⁻ = A⁻.u_d\n    n = @SVector [n⁻[1], n⁻[2]]\n\n    # Q⁺.u = u⁻ - (n⋅u⁻) * n\n    Q⁺.u = u⁻ - (n ∘ u⁻) * n\n    A⁺.u_d = ud⁻ - (n ∘ ud⁻) * n\n\n    return nothing\nend\n\n@inline function ocean_boundary_state!(\n    ::BarotropicModel,\n    ::CoastlineFreeSlip,\n    ::NumericalFluxGradient,\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n)\n    U⁻ = Q⁻.U\n    n = @SVector [n⁻[1], n⁻[2]]\n\n    # Q⁺.U = U⁻ - (n⋅U⁻) * n\n    Q⁺.U = U⁻ - (n ∘ U⁻) * n\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::CoastlineFreeSlip, ::NumericalFluxSecondOrder)\n\napply free slip boundary conditions for velocity\napply no penetration boundary for temperature\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::AbstractOceanModel,\n    ::CoastlineFreeSlip,\n    ::NumericalFluxSecondOrder,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    D⁺.ν∇u = n⁻ * (@SVector [-0, -0])'\n    D⁺.κ∇θ = n⁻ * -0\n\n    return nothing\nend\n\n@inline function ocean_boundary_state!(\n    ::BarotropicModel,\n    ::CoastlineFreeSlip,\n    ::NumericalFluxSecondOrder,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    D⁺.ν∇U = n⁻ * (@SVector [-0, -0])'\n\n    return nothing\nend\n\n\"\"\"\n    CoastlineNoSlip\n\napplies boundary condition u = 0 and ∇θ = 0\n\"\"\"\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::CoastlineNoSlip, ::NumericalFluxFirstOrder)\n\napply no slip boundary condition for velocity\napply no penetration boundary for temperature\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::AbstractOceanModel,\n    ::CoastlineNoSlip,\n    ::NumericalFluxFirstOrder,\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n)\n    Q⁺.u = -Q⁻.u\n\n    return nothing\nend\n\n@inline function ocean_boundary_state!(\n    ::BarotropicModel,\n    ::CoastlineNoSlip,\n    ::NumericalFluxFirstOrder,\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n)\n    Q⁺.U = -Q⁻.U\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::CoastlineNoSlip, ::NumericalFluxGradient)\n\napply no slip boundary condition for velocity\napply no penetration boundary for temperature\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::AbstractOceanModel,\n    ::CoastlineNoSlip,\n    ::NumericalFluxGradient,\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n)\n    FT = eltype(Q⁺)\n    Q⁺.u = SVector(-zero(FT), -zero(FT))\n    A⁺.u_d = SVector(-zero(FT), -zero(FT))\n\n    return nothing\nend\n\n@inline function ocean_boundary_state!(\n    ::BarotropicModel,\n    ::CoastlineNoSlip,\n    ::NumericalFluxGradient,\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n)\n    FT = eltype(Q⁺)\n    Q⁺.U = SVector(-zero(FT), -zero(FT))\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::CoastlineNoSlip, ::NumericalFluxSecondOrder)\n\napply no slip boundary condition for velocity\napply no penetration boundary for temperature\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::AbstractOceanModel,\n    ::CoastlineNoSlip,\n    ::NumericalFluxSecondOrder,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    D⁺.ν∇u = D⁻.ν∇u\n    D⁺.κ∇θ = n⁻ * -0\n\n    return nothing\nend\n\n@inline function ocean_boundary_state!(\n    ::BarotropicModel,\n    ::CoastlineNoSlip,\n    ::NumericalFluxSecondOrder,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    D⁺.ν∇U = D⁻.ν∇U\n\n    return nothing\nend\n\n\"\"\"\n    OceanFloorFreeSlip\n\napplies boundary condition ∇u = 0 and ∇θ = 0\n\"\"\"\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::OceanFloorFreeSlip, ::NumericalFluxFirstOrder)\n\napply free slip boundary conditions for velocity\napply no penetration boundary for temperature\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::AbstractOceanModel,\n    ::OceanFloorFreeSlip,\n    ::NumericalFluxFirstOrder,\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n)\n    A⁺.w = -A⁻.w\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::OceanFloorFreeSlip, ::NumericalFluxGradient)\n\napply free slip boundary condition for velocity\napply no penetration boundary for temperature\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::AbstractOceanModel,\n    ::OceanFloorFreeSlip,\n    ::NumericalFluxGradient,\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n)\n    FT = eltype(Q⁺)\n    A⁺.w = -zero(FT)\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::OceanFloorFreeSlip, ::NumericalFluxSecondOrder)\n\napply free slip boundary conditions for velocity\napply no penetration boundary for temperature\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::AbstractOceanModel,\n    ::OceanFloorFreeSlip,\n    ::NumericalFluxSecondOrder,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    D⁺.ν∇u = n⁻ * (@SVector [-0, -0])'\n    D⁺.κ∇θ = n⁻ * -0\n\n    return nothing\nend\n\n\"\"\"\n    OceanFloorNoSlip\n\napplies boundary condition u = 0 and ∇θ = 0\n\"\"\"\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::Union{OceanFloorNoSlip, OceanFloorLinearDrag}, ::NumericalFluxFirstOrder)\n\napply no slip boundary condition for velocity\napply no penetration boundary for temperature\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::AbstractOceanModel,\n    ::Union{OceanFloorNoSlip, OceanFloorLinearDrag},\n    ::NumericalFluxFirstOrder,\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n)\n    Q⁺.u = -Q⁻.u\n    A⁺.w = -A⁻.w\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::Union{OceanFloorNoSlip, OceanFloorLinearDrag}, ::NumericalFluxGradient)\n\napply no slip boundary condition for velocity\napply no penetration boundary for temperature\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::AbstractOceanModel,\n    ::Union{OceanFloorNoSlip, OceanFloorLinearDrag},\n    ::NumericalFluxGradient,\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n)\n    FT = eltype(Q⁺)\n    Q⁺.u = SVector(-zero(FT), -zero(FT))\n    A⁺.w = -zero(FT)\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::OceanFloorNoSlip, ::NumericalFluxSecondOrder)\n\napply no slip boundary condition for velocity\napply no penetration boundary for temperature\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::AbstractOceanModel,\n    ::OceanFloorNoSlip,\n    ::NumericalFluxSecondOrder,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    D⁺.ν∇u = D⁻.ν∇u\n    D⁺.κ∇θ = n⁻ * -0\n\n    return nothing\nend\n\n\"\"\"\n    OceanFloorLinearDrag\n\napplies boundary condition u = 0 with linear drag on viscous-flux and ∇θ = 0\n\"\"\"\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::OceanFloorLinearDrag, ::NumericalFluxSecondOrder)\n\napply linear drag boundary condition for velocity\napply no penetration boundary for temperature\n\"\"\"\n@inline function ocean_boundary_state!(\n    m::AbstractOceanModel,\n    ::OceanFloorLinearDrag,\n    ::NumericalFluxSecondOrder,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    u, v = Q⁻.u\n\n    D⁺.ν∇u = -m.problem.Cd_lin * @SMatrix [-0 -0; -0 -0; u v]\n    D⁺.κ∇θ = n⁻ * -0\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::Union{OceanSurface*}, ::Union{NumericalFluxFirstOrder, NumericalFluxGradient})\napplying neumann boundary conditions, so don't need to do anything for these numerical fluxes\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::AbstractOceanModel,\n    ::Union{\n        OceanSurfaceNoStressNoForcing,\n        OceanSurfaceStressNoForcing,\n        OceanSurfaceNoStressForcing,\n        OceanSurfaceStressForcing,\n    },\n    ::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    Q⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    A⁻,\n    t,\n)\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::OceanSurfaceNoStressNoForcing, ::NumericalFluxSecondOrder)\n\napply no flux boundary condition for velocity\napply no flux boundary condition for temperature\n\"\"\"\n@inline function ocean_boundary_state!(\n    ::AbstractOceanModel,\n    ::OceanSurfaceNoStressNoForcing,\n    ::NumericalFluxSecondOrder,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    D⁺.ν∇u = n⁻ * (@SVector [-0, -0])'\n    D⁺.κ∇θ = n⁻ * -0\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::OceanSurfaceStressNoForcing, ::NumericalFluxSecondOrder)\n\napply wind-stress boundary condition for velocity\napply no flux boundary condition for temperature\n\"\"\"\n@inline function ocean_boundary_state!(\n    m::AbstractOceanModel,\n    ::OceanSurfaceStressNoForcing,\n    ::NumericalFluxSecondOrder,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    τᶻ = velocity_flux(m.problem, A⁻.y, m.ρₒ)\n    D⁺.ν∇u = n⁻ * (@SVector [-τᶻ, -0])'\n    D⁺.κ∇θ = n⁻ * -0\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::OceanSurfaceNoStressForcing, ::NumericalFluxSecondOrder)\n\napply no flux boundary condition for velocity\napply forcing boundary condition for temperature\n\"\"\"\n@inline function ocean_boundary_state!(\n    m::AbstractOceanModel,\n    ::OceanSurfaceNoStressForcing,\n    ::NumericalFluxSecondOrder,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    σᶻ = temperature_flux(m.problem, A⁻.y, Q⁻.θ)\n    D⁺.ν∇u = n⁻ * (@SVector [-0, -0])'\n    D⁺.κ∇θ = -n⁻ * σᶻ\n\n    return nothing\nend\n\n\"\"\"\n    ocean_boundary_state!(::AbstractOceanModel, ::OceanSurfaceStressForcing, ::NumericalFluxSecondOrder)\n\napply wind-stress boundary condition for velocity\napply forcing boundary condition for temperature\n\"\"\"\n@inline function ocean_boundary_state!(\n    m::AbstractOceanModel,\n    ::OceanSurfaceStressForcing,\n    ::NumericalFluxSecondOrder,\n    Q⁺,\n    D⁺,\n    A⁺,\n    n⁻,\n    Q⁻,\n    D⁻,\n    A⁻,\n    t,\n)\n    τᶻ = velocity_flux(m.problem, A⁻.y, m.ρₒ)\n    σᶻ = temperature_flux(m.problem, A⁻.y, Q⁻.θ)\n    D⁺.ν∇u = n⁻ * (@SVector [-τᶻ, -0])'\n    D⁺.κ∇θ = -n⁻ * σᶻ\n\n    return nothing\nend\n\n@inline velocity_flux(p::AbstractOceanProblem, y, ρ) =\n    -(p.τₒ / ρ) * cos(y * π / p.Lʸ)\n\n@inline function temperature_flux(p::AbstractOceanProblem, y, θ)\n    θʳ = p.θᴱ * (1 - y / p.Lʸ)\n    return p.λʳ * (θʳ - θ)\nend\n"
  },
  {
    "path": "src/Ocean/SplitExplicit01/OceanModel.jl",
    "content": "struct OceanModel{P, T} <: AbstractOceanModel\n    problem::P\n    grav::T\n    ρₒ::T\n    cʰ::T\n    cᶻ::T\n    add_fast_substeps::T\n    numImplSteps::T\n    ivdc_dt::T\n    αᵀ::T\n    νʰ::T\n    νᶻ::T\n    κʰ::T\n    κᶻ::T\n    κᶜ::T\n    fₒ::T\n    β::T\n    function OceanModel{FT}(\n        problem;\n        grav = FT(10),  # m/s^2\n        ρₒ = FT(1000),  # kg/m^3\n        cʰ = FT(0),     # m/s\n        cᶻ = FT(0),     # m/s\n        add_fast_substeps = 0,\n        numImplSteps = 0,\n        ivdc_dt = FT(1),\n        αᵀ = FT(2e-4),  # 1/K\n        νʰ = FT(5e3),   # m^2/s\n        νᶻ = FT(5e-3),  # m^2/s\n        κʰ = FT(1e3),   # m^2/s\n        κᶻ = FT(1e-4),  # vertical diffusivity, m^2/s\n        κᶜ = FT(1e-4),  # convective adjustment vertical diffusivity, m^2/s\n        fₒ = FT(1e-4),  # Hz\n        β = FT(1e-11),  # Hz/m\n    ) where {FT <: AbstractFloat}\n        return new{typeof(problem), FT}(\n            problem,\n            grav,\n            ρₒ,\n            cʰ,\n            cᶻ,\n            add_fast_substeps,\n            numImplSteps,\n            ivdc_dt,\n            αᵀ,\n            νʰ,\n            νᶻ,\n            κʰ,\n            κᶻ,\n            κᶜ,\n            fₒ,\n            β,\n        )\n    end\nend\n\nfunction calculate_dt(grid, ::OceanModel, _...)\n    #=\n      minΔx = min_node_distance(grid, HorizontalDirection())\n      minΔz = min_node_distance(grid, VerticalDirection())\n\n      CFL_gravity = minΔx / model.cʰ\n      CFL_diffusive = minΔz^2 / (1000 * model.κᶻ)\n      CFL_viscous = minΔz^2 / model.νᶻ\n\n      dt = 1 // 2 * minimum([CFL_gravity, CFL_diffusive, CFL_viscous])\n    =#\n    # FT = eltype(grid)\n    # dt = FT(1)\n\n    return nothing\nend\n\n\"\"\"\n    OceanDGModel()\n\nhelper function to add required filtering\nnot used in the Driver+Config setup\n\"\"\"\nfunction OceanDGModel(\n    bl::OceanModel,\n    grid,\n    numfluxnondiff,\n    numfluxdiff,\n    gradnumflux;\n    kwargs...,\n)\n    N = polynomialorders(grid)\n    Nvert = N[end]\n    vert_filter = CutoffFilter(grid, Nvert - 1)\n    exp_filter = ExponentialFilter(grid, 1, 8)\n\n    flowintegral_dg = DGModel(\n        FlowIntegralModel(bl),\n        grid,\n        numfluxnondiff,\n        numfluxdiff,\n        gradnumflux,\n    )\n\n    tendency_dg = DGModel(\n        TendencyIntegralModel(bl),\n        grid,\n        numfluxnondiff,\n        numfluxdiff,\n        gradnumflux,\n    )\n\n    conti3d_dg = DGModel(\n        Continuity3dModel(bl),\n        grid,\n        numfluxnondiff,\n        numfluxdiff,\n        gradnumflux,\n    )\n    FT = eltype(grid)\n    conti3d_Q = init_ode_state(conti3d_dg, FT(0); init_on_cpu = true)\n\n    ivdc_dg = DGModel(\n        IVDCModel(bl),\n        grid,\n        numfluxnondiff,\n        numfluxdiff,\n        gradnumflux;\n        direction = VerticalDirection(),\n    )\n    ivdc_Q = init_ode_state(ivdc_dg, FT(0); init_on_cpu = true) # Not sure this is needed since we set values later,\n    # but we'll do it just in case!\n\n    ivdc_RHS = init_ode_state(ivdc_dg, FT(0); init_on_cpu = true) # Not sure this is needed since we set values later,\n    # but we'll do it just in case!\n\n    ivdc_bgm_solver = BatchedGeneralizedMinimalResidual(\n        ivdc_dg,\n        ivdc_Q;\n        max_subspace_size = 10,\n    )\n\n    modeldata = (\n        vert_filter = vert_filter,\n        exp_filter = exp_filter,\n        flowintegral_dg = flowintegral_dg,\n        tendency_dg = tendency_dg,\n        conti3d_dg = conti3d_dg,\n        conti3d_Q = conti3d_Q,\n        ivdc_dg = ivdc_dg,\n        ivdc_Q = ivdc_Q,\n        ivdc_RHS = ivdc_RHS,\n        ivdc_bgm_solver = ivdc_bgm_solver,\n    )\n\n    return DGModel(\n        bl,\n        grid,\n        numfluxnondiff,\n        numfluxdiff,\n        gradnumflux;\n        kwargs...,\n        modeldata = modeldata,\n    )\nend\n\nfunction vars_state(m::OceanModel, ::Prognostic, T)\n    @vars begin\n        u::SVector{2, T}\n        η::T\n        θ::T\n    end\nend\n\nfunction init_state_prognostic!(m::OceanModel, Q::Vars, A::Vars, localgeo, t)\n    return ocean_init_state!(m.problem, Q, A, localgeo, t)\nend\n\nfunction vars_state(m::OceanModel, ::Auxiliary, T)\n    @vars begin\n        w::T\n        pkin::T         # kinematic pressure: ∫(-g αᵀ θ)\n        wz0::T          # w at z=0\n        u_d::SVector{2, T}  # velocity deviation from vertical mean\n        ΔGu::SVector{2, T}\n        y::T     # y-coordinate of the box\n    end\nend\n\nfunction init_state_auxiliary!(\n    m::OceanModel,\n    state_aux::MPIStateArray,\n    grid,\n    direction,\n)\n    init_state_auxiliary!(\n        m,\n        (m, A, tmp, geom) -> ocean_init_aux!(m, m.problem, A, geom),\n        state_aux,\n        grid,\n        direction,\n    )\nend\n\nfunction vars_state(m::OceanModel, ::Gradient, T)\n    @vars begin\n        u::SVector{2, T}\n        ud::SVector{2, T}\n        θ::T\n    end\nend\n\n@inline function compute_gradient_argument!(\n    m::OceanModel,\n    G::Vars,\n    Q::Vars,\n    A,\n    t,\n)\n    G.u = Q.u\n    G.ud = A.u_d\n    G.θ = Q.θ\n\n    return nothing\nend\n\nfunction vars_state(m::OceanModel, ::GradientFlux, T)\n    @vars begin\n        ν∇u::SMatrix{3, 2, T, 6}\n        κ∇θ::SVector{3, T}\n    end\nend\n\n@inline function compute_gradient_flux!(\n    m::OceanModel,\n    D::Vars,\n    G::Grad,\n    Q::Vars,\n    A::Vars,\n    t,\n)\n    ν = viscosity_tensor(m)\n    #  D.ν∇u = ν * G.u\n    D.ν∇u =\n        -@SMatrix [\n            m.νʰ*G.ud[1, 1] m.νʰ*G.ud[1, 2]\n            m.νʰ*G.ud[2, 1] m.νʰ*G.ud[2, 2]\n            m.νᶻ*G.u[3, 1] m.νᶻ*G.u[3, 2]\n        ]\n\n    κ = diffusivity_tensor(m, G.θ[3])\n    D.κ∇θ = -κ * G.θ\n\n    return nothing\nend\n\n@inline viscosity_tensor(m::OceanModel) = Diagonal(@SVector [m.νʰ, m.νʰ, m.νᶻ])\n\n@inline function diffusivity_tensor(m::OceanModel, ∂θ∂z)\n\n    if m.numImplSteps > 0\n        κ = (@SVector [m.κʰ, m.κʰ, m.κᶻ * 0.5])\n    else\n        ∂θ∂z < 0 ? κ = (@SVector [m.κʰ, m.κʰ, m.κᶜ]) :\n        κ = (@SVector [m.κʰ, m.κʰ, m.κᶻ])\n    end\n\n    return Diagonal(κ)\nend\n\n\"\"\"\n    vars_integral(::OceanModel)\n\nlocation to store integrands for bottom up integrals\n∇hu = the horizontal divegence of u, e.g. dw/dz\n\"\"\"\nfunction vars_state(m::OceanModel, ::UpwardIntegrals, T)\n    @vars begin\n        ∇hu::T\n        buoy::T\n        #       ∫u::SVector{2, T}\n    end\nend\n\n\"\"\"\n    integral_load_auxiliary_state!(::OceanModel)\n\ncopy w to var_integral\nthis computation is done pointwise at each nodal point\n\narguments:\nm -> model in this case OceanModel\nI -> array of integrand variables\nQ -> array of state variables\nA -> array of aux variables\n\"\"\"\n@inline function integral_load_auxiliary_state!(\n    m::OceanModel,\n    I::Vars,\n    Q::Vars,\n    A::Vars,\n)\n    I.∇hu = A.w # borrow the w value from A...\n    I.buoy = m.grav * m.αᵀ * Q.θ # buoyancy to integrate vertically from top (=reverse)\n    #   I.∫u = Q.u\n\n    return nothing\nend\n\n\"\"\"\n    integral_set_auxiliary_state!(::OceanModel)\n\ncopy integral results back out to aux\nthis computation is done pointwise at each nodal point\n\narguments:\nm -> model in this case OceanModel\nA -> array of aux variables\nI -> array of integrand variables\n\"\"\"\n@inline function integral_set_auxiliary_state!(m::OceanModel, A::Vars, I::Vars)\n    A.w = I.∇hu\n    A.pkin = -I.buoy\n    #   A.∫u = I.∫u\n\n    return nothing\nend\n\n\"\"\"\n    vars_reverse_integral(::OceanModel)\n\nlocation to store integrands for top down integrals\nαᵀθ = density perturbation\n\"\"\"\nfunction vars_state(m::OceanModel, ::DownwardIntegrals, T)\n    @vars begin\n        buoy::T\n    end\nend\n\n\"\"\"\n    reverse_integral_load_auxiliary_state!(::OceanModel)\n\ncopy αᵀθ to var_reverse_integral\nthis computation is done pointwise at each nodal point\n\narguments:\nm -> model in this case OceanModel\nI -> array of integrand variables\nA -> array of aux variables\n\"\"\"\n@inline function reverse_integral_load_auxiliary_state!(\n    m::OceanModel,\n    I::Vars,\n    Q::Vars,\n    A::Vars,\n)\n    I.buoy = A.pkin\n\n    return nothing\nend\n\n\"\"\"\n    reverse_integral_set_auxiliary_state!(::OceanModel)\n\ncopy reverse integral results back out to aux\nthis computation is done pointwise at each nodal point\n\narguments:\nm -> model in this case OceanModel\nA -> array of aux variables\nI -> array of integrand variables\n\"\"\"\n@inline function reverse_integral_set_auxiliary_state!(\n    m::OceanModel,\n    A::Vars,\n    I::Vars,\n)\n    A.pkin = I.buoy\n\n    return nothing\nend\n\n@inline function flux_first_order!(\n    m::OceanModel,\n    F::Grad,\n    Q::Vars,\n    A::Vars,\n    t::Real,\n    direction,\n)\n    @inbounds begin\n        u = Q.u # Horizontal components of velocity\n        θ = Q.θ\n        w = A.w   # vertical velocity\n        pkin = A.pkin\n        v = @SVector [u[1], u[2], w]\n        Iʰ = @SMatrix [\n            1 -0\n            -0 1\n            -0 -0\n        ]\n\n        # ∇h • (g η)\n        #- jmc: put back this term to check\n        #  η = Q.η\n        #  F.u += m.grav * η * Iʰ\n\n        # ∇ • (u θ)\n        F.θ += v * θ\n\n        # ∇h • pkin\n        F.u += pkin * Iʰ\n\n        # ∇h • (v ⊗ u)\n        # F.u += v * u'\n    end\n\n    return nothing\nend\n\n@inline function flux_second_order!(\n    m::OceanModel,\n    F::Grad,\n    Q::Vars,\n    D::Vars,\n    HD::Vars,\n    A::Vars,\n    t::Real,\n)\n    #- vertical viscosity only (horizontal fluxes in horizontal model)\n    #  F.u -= @SVector([0, 0, 1]) * D.ν∇u[3, :]'\n    #- all 3 direction viscous flux for horizontal momentum tendency\n    F.u += D.ν∇u\n\n    F.θ += D.κ∇θ\n\n    return nothing\nend\n\n@inline function source!(\n    m::OceanModel,\n    S::Vars,\n    Q::Vars,\n    D::Vars,\n    A::Vars,\n    t::Real,\n    direction,\n)\n    @inbounds begin\n        u = Q.u    # Horizontal components of velocity\n        ud = A.u_d # Horizontal velocity deviation from vertical mean\n\n        # f × u\n        f = coriolis_force(m, A.y)\n        #  S.u -= @SVector [-f * u[2], f * u[1]]\n        S.u -= @SVector [-f * ud[2], f * ud[1]]\n\n        #- borotropic tendency adjustment\n        S.u += A.ΔGu\n\n        # switch this to S.η if you comment out the fast mode in MultistateMultirateRungeKutta\n        S.η += A.wz0\n    end\n\n    return nothing\nend\n\n@inline coriolis_force(m::OceanModel, y) = m.fₒ + m.β * y\n\nfunction update_auxiliary_state!(\n    dg::DGModel,\n    m::OceanModel,\n    Q::MPIStateArray,\n    t::Real,\n    elems::UnitRange,\n)\n    FT = eltype(Q)\n    A = dg.state_auxiliary\n    MD = dg.modeldata\n\n    # `update_auxiliary_state!` gets called twice, once for the real elements\n    # and once for the ghost elements. Only apply the filters to the real elems.\n    if elems == dg.grid.topology.realelems\n        # required to ensure that after integration velocity field is divergence free\n        vert_filter = MD.vert_filter\n        # Q[1] = u[1] = u, Q[2] = u[2] = v\n        apply!(Q, (1, 2), dg.grid, vert_filter, direction = VerticalDirection())\n\n        exp_filter = MD.exp_filter\n        # Q[4] = θ\n        apply!(Q, (4,), dg.grid, exp_filter, direction = VerticalDirection())\n    end\n\n    #----------\n    # Compute Divergence of Horizontal Flow field using \"conti3d_dg\" DGmodel\n\n    conti3d_dg = dg.modeldata.conti3d_dg\n    # ct3d_Q = dg.modeldata.conti3d_Q\n    # ct3d_dQ = similar(ct3d_Q)\n    # fill!(ct3d_dQ, 0)\n    #- Instead, use directly conti3d_Q to store dQ (since we will not update the state)\n    ct3d_dQ = dg.modeldata.conti3d_Q\n\n    ct3d_bl = conti3d_dg.balance_law\n\n    # call \"conti3d_dg\" DGmodel\n    # note: with \"increment = false\", just return tendency (no state update)\n    p = nothing\n    conti3d_dg(ct3d_dQ, Q, p, t; increment = false)\n\n    # Copy from ct3d_dQ.θ which is realy ∇h•u into A.w (which will be integrated)\n    function f!(::OceanModel, dQ, A, t)\n        @inbounds begin\n            A.w = dQ.θ\n        end\n    end\n    update_auxiliary_state!(f!, dg, m, ct3d_dQ, t, elems)\n    #----------\n\n    info = basic_grid_info(dg)\n    Nqh, Nqk = info.Nqh, info.Nqk\n    nelemv, nelemh = info.nvertelem, info.nhorzelem\n    nrealelemh = info.nhorzrealelem\n\n    # compute integrals for w and pkin\n    indefinite_stack_integral!(dg, m, Q, A, t, elems) # bottom -> top\n    reverse_indefinite_stack_integral!(dg, m, Q, A, t, elems) # top -> bottom\n\n    # copy down wz0\n    # We are unable to use vars (ie A.w) for this because this operation will\n    # return a SubArray, and adapt (used for broadcasting along reshaped arrays)\n    # has a limited recursion depth for the types allowed.\n    nb_aux_m = number_states(m, Auxiliary())\n    data_m = reshape(A.data, Nqh, Nqk, nb_aux_m, nelemv, nelemh)\n\n    # project w(z=0) down the stack\n    index_w = varsindex(vars_state(m, Auxiliary(), FT), :w)\n    index_wz0 = varsindex(vars_state(m, Auxiliary(), FT), :wz0)\n    flat_wz0 = @view data_m[:, end:end, index_w, end:end, 1:nrealelemh]\n    boxy_wz0 = @view data_m[:, :, index_wz0, :, 1:nrealelemh]\n    boxy_wz0 .= flat_wz0\n\n    # Compute Horizontal Flow deviation from vertical mean\n\n    flowintegral_dg = dg.modeldata.flowintegral_dg\n    flowint = flowintegral_dg.balance_law\n    update_auxiliary_state!(flowintegral_dg, flowint, Q, 0, elems)\n\n    ## get top value (=integral over full depth)\n    nb_aux_flw = number_states(flowint, Auxiliary())\n    data_flw = reshape(\n        flowintegral_dg.state_auxiliary.data,\n        Nqh,\n        Nqk,\n        nb_aux_flw,\n        nelemv,\n        nelemh,\n    )\n    index_∫u = varsindex(vars_state(flowint, Auxiliary(), FT), :∫u)\n\n    flat_∫u = @view data_flw[:, end:end, index_∫u, end:end, 1:nrealelemh]\n\n    ## make a copy of horizontal velocity\n    A.u_d .= Q.u\n\n    ## and remove vertical mean velocity\n    index_ud = varsindex(vars_state(m, Auxiliary(), FT), :u_d)\n    boxy_ud = @view data_m[:, :, index_ud, :, 1:nrealelemh]\n    boxy_ud .-= flat_∫u / m.problem.H\n\n    return true\nend\n\n@inline wavespeed(m::OceanModel, n⁻, _...) =\n    abs(SVector(m.cʰ, m.cʰ, m.cᶻ)' * n⁻)\n\n# We want not have jump penalties on η (since not a flux variable)\nfunction update_penalty!(\n    ::RusanovNumericalFlux,\n    ::OceanModel,\n    n⁻,\n    λ,\n    ΔQ::Vars,\n    Q⁻,\n    A⁻,\n    Q⁺,\n    A⁺,\n    t,\n)\n    ΔQ.η = -0\n\n    return nothing\nend\n\nboundary_conditions(ocean::OceanModel) = ocean.problem.boundary_conditions\n\n\"\"\"\n    boundary_state!(nf, bc, ::OceanModel, args...)\n\napplies boundary conditions for this model\ndispatches to a function in OceanBoundaryConditions.jl based on BC type defined by a problem such as SimpleBoxProblem.jl\n\"\"\"\n\n@inline function boundary_state!(nf, bc, ocean::OceanModel, args...)\n    return ocean_model_boundary!(ocean, bc, nf, args...)\nend\n"
  },
  {
    "path": "src/Ocean/SplitExplicit01/SplitExplicitLSRK2nMethod.jl",
    "content": "export SplitExplicitLSRK2nSolver\n\nusing KernelAbstractions\nusing KernelAbstractions.Extras: @unroll\nusing StaticArrays\nusing ...SystemSolvers\nusing ...MPIStateArrays: array_device, realview\nusing ...GenericCallbacks\n\nusing ...ODESolvers:\n    AbstractODESolver, LowStorageRungeKutta2N, update!, updatedt!, getdt\nimport ...ODESolvers: dostep!\n\nusing ...BalanceLaws:\n    #   initialize_fast_state!,\n    #   initialize_adjustment!,\n    tendency_from_slow_to_fast!,\n    cummulate_fast_solution!,\n    reconcile_from_fast_to_slow!\n\nLSRK2N = LowStorageRungeKutta2N\n\n@doc \"\"\"\n    SplitExplicitLSRK2nSolver(slow_solver, fast_solver; dt, t0 = 0, coupled = true)\n\nThis is a time stepping object for explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q_fast} = f_fast(Q_fast, Q_slow, t)\n  \\\\dot{Q_slow} = f_slow(Q_slow, Q_fast, t)\n```\n\nwith the required time step size `dt` and optional initial time `t0`.  This\ntime stepping object is intended to be passed to the `solve!` command.\n\nThis method performs an operator splitting to timestep the Sea-Surface elevation\nand vertically averaged horizontal velocity of the model at a faster rate than\nthe full model, using LowStorageRungeKutta2N time-stepping.\n\n\"\"\" SplitExplicitLSRK2nSolver\nmutable struct SplitExplicitLSRK2nSolver{SS, FS, RT, MSA} <: AbstractODESolver\n    \"slow solver\"\n    slow_solver::SS\n    \"fast solver\"\n    fast_solver::FS\n    \"time step\"\n    dt::RT\n    \"time\"\n    t::RT\n    \"elapsed time steps\"\n    steps::Int\n    \"storage for transfer tendency\"\n    dQ2fast::MSA\n\n    function SplitExplicitLSRK2nSolver(\n        slow_solver::LSRK2N,\n        fast_solver,\n        Q = nothing;\n        dt = getdt(slow_solver),\n        t0 = slow_solver.t,\n    ) where {AT <: AbstractArray}\n        SS = typeof(slow_solver)\n        FS = typeof(fast_solver)\n        RT = real(eltype(slow_solver.dQ))\n\n        dQ2fast = similar(slow_solver.dQ)\n        dQ2fast .= -0.0\n        MSA = typeof(dQ2fast)\n        return new{SS, FS, RT, MSA}(\n            slow_solver,\n            fast_solver,\n            RT(dt),\n            RT(t0),\n            0,\n            dQ2fast,\n        )\n    end\nend\n\nfunction dostep!(\n    Qvec,\n    split::SplitExplicitLSRK2nSolver{SS},\n    param,\n    time::Real,\n) where {SS <: LSRK2N}\n    slow = split.slow_solver\n    fast = split.fast_solver\n\n    Qslow = Qvec.slow\n    Qfast = Qvec.fast\n\n    dQslow = slow.dQ\n    dQ2fast = split.dQ2fast\n\n    slow_bl = slow.rhs!.balance_law\n    fast_bl = fast.rhs!.balance_law\n\n    groupsize = 256\n\n    slow_dt = getdt(slow)\n    fast_dt_in = getdt(fast)\n\n    for slow_s in 1:length(slow.RKA)\n        # Current slow state time\n        slow_stage_time = time + slow.RKC[slow_s] * slow_dt\n\n        # Fractional time for slow stage\n        if slow_s == length(slow.RKA)\n            fract_dt = (1 - slow.RKC[slow_s]) * slow_dt\n        else\n            fract_dt = (slow.RKC[slow_s + 1] - slow.RKC[slow_s]) * slow_dt\n        end\n\n        # Initialize fast model and set time-step and number of substeps we need\n        # Note: to reproduce previous Fast output, 1) remove \"firstStage = ...\" line\n        fast_steps = [0 0 0]\n        FT = typeof(slow_dt)\n        fast_time_rec = [fast_dt_in FT(0)]\n        initialize_fast_state!(\n            slow_bl,\n            fast_bl,\n            slow.rhs!,\n            fast.rhs!,\n            Qslow,\n            Qfast,\n            fract_dt,\n            fast_time_rec,\n            fast_steps;\n            firstStage = (slow_s == 1),\n        )\n        # Initialize tentency adjustment before evaluation of slow mode\n        initialize_adjustment!(\n            slow_bl,\n            fast_bl,\n            slow.rhs!,\n            fast.rhs!,\n            Qslow,\n            Qfast,\n        )\n\n        # Evaluate the slow mode\n        # --> save tendency for the fast\n        slow.rhs!(dQ2fast, Qslow, param, slow_stage_time, increment = false)\n\n        # vertically integrate slow tendency to advance fast equation\n        # and use vertical mean for slow model (negative source)\n        # ---> work with dQ2fast as input\n        tendency_from_slow_to_fast!(\n            slow_bl,\n            fast_bl,\n            slow.rhs!,\n            fast.rhs!,\n            Qslow,\n            Qfast,\n            dQ2fast,\n        )\n\n        # Compute (and RK update) slow tendency\n        slow.rhs!(dQslow, Qslow, param, slow_stage_time, increment = true)\n\n        # Update (RK-stage) slow state\n        event = Event(array_device(Qslow))\n        event = update!(array_device(Qslow), groupsize)(\n            realview(dQslow),\n            realview(Qslow),\n            slow.RKA[slow_s % length(slow.RKA) + 1],\n            slow.RKB[slow_s],\n            slow_dt,\n            nothing,\n            nothing,\n            nothing;\n            ndrange = length(realview(Qslow)),\n            dependencies = (event,),\n        )\n        wait(array_device(Qslow), event)\n\n        # Determine number of substeps we need\n        fast_dt = fast_time_rec[1]\n        nsubsteps = fast_steps[3]\n\n        updatedt!(fast, fast_dt)\n\n        for substep in 1:nsubsteps\n            fast_time = slow_stage_time + (substep - 1) * fast_dt\n            dostep!(Qfast, fast, param, fast_time)\n\n            # cumulate fast solution\n            cummulate_fast_solution!(\n                fast_bl,\n                fast.rhs!,\n                Qfast,\n                fast_time,\n                fast_dt,\n                substep,\n                fast_steps,\n                fast_time_rec,\n            )\n        end\n\n        # Reconcile slow equation using fast equation\n        # Note: to reproduce previous Fast output, 2) remove \"lastStage = ...\" line\n        reconcile_from_fast_to_slow!(\n            slow_bl,\n            fast_bl,\n            slow.rhs!,\n            fast.rhs!,\n            Qslow,\n            Qfast,\n            fast_time_rec;\n            lastStage = (slow_s == length(slow.RKA)),\n        )\n\n    end\n\n    # reset fast time-step to original value\n    updatedt!(fast, fast_dt_in)\n\n    # now do implicit mixing step\n    nImplSteps = slow_bl.numImplSteps\n    if nImplSteps > 0\n        # 1. get implicit mising model, model state variable array and solver handles\n        ivdc_dg = slow.rhs!.modeldata.ivdc_dg\n        ivdc_bl = ivdc_dg.balance_law\n        ivdc_Q = slow.rhs!.modeldata.ivdc_Q\n        ivdc_solver = slow.rhs!.modeldata.ivdc_bgm_solver\n        # ivdc_solver_dt = getdt(ivdc_solver) # would work if solver time-step was set\n        # FT = typeof(slow_dt)\n        # ivdc_solver_dt = slow_dt / FT(nImplSteps) # just recompute time-step\n        ivdc_solver_dt = ivdc_bl.parent_om.ivdc_dt\n        # println(\"ivdc_solver_dt = \",ivdc_solver_dt )\n        # 2. setup start RHS, initial guess and values for computing mixing coeff\n        ivdc_Q.θ .= Qslow.θ\n        ivdc_RHS = slow.rhs!.modeldata.ivdc_RHS\n        ivdc_RHS.θ .= Qslow.θ\n        ivdc_RHS.θ .= ivdc_RHS.θ ./ ivdc_solver_dt\n        ivdc_dg.state_auxiliary.θ_init .= ivdc_Q.θ\n        # 3. Invoke iterative solver\n\n        println(\"BEFORE maximum(ivdc_Q.θ[:]): \", maximum(ivdc_Q.realdata[:]))\n        println(\"BEFORE minimum(ivdc_Q.θ[:]): \", minimum(ivdc_Q.realdata[:]))\n\n        lm!(y, x) = ivdc_dg(y, x, nothing, 0; increment = false)\n        solve_tot = 0\n        iter_tot = 0\n        for i in 1:nImplSteps\n            solve_time = @elapsed iters =\n                linearsolve!(lm!, nothing, ivdc_solver, ivdc_Q, ivdc_RHS)\n            solve_tot = solve_tot + solve_time\n            iter_tot = iter_tot + iters\n            # Set new RHS and initial values\n            ivdc_RHS.θ .= ivdc_Q.θ ./ ivdc_solver_dt\n            ivdc_dg.state_auxiliary.θ_init .= ivdc_Q.θ\n        end\n        println(\"solver iters, time: \", iter_tot, \", \", solve_tot)\n\n        println(\"AFTER  maximum(ivdc_Q.θ[:]): \", maximum(ivdc_Q.realdata[:]))\n        println(\"AFTER  minimum(ivdc_Q.θ[:]): \", minimum(ivdc_Q.realdata[:]))\n\n        # exit()\n        # Now update\n        Qslow.θ .= ivdc_Q.θ\n\n    end\n\n    return nothing\nend\n"
  },
  {
    "path": "src/Ocean/SplitExplicit01/SplitExplicitLSRK3nMethod.jl",
    "content": "export SplitExplicitLSRK3nSolver\n\nusing KernelAbstractions\nusing KernelAbstractions.Extras: @unroll\nusing StaticArrays\nusing ...SystemSolvers\nusing ...MPIStateArrays: array_device, realview\nusing ...GenericCallbacks\n#using Printf\n\nusing ...ODESolvers:\n    AbstractODESolver, LowStorageRungeKutta3N, update!, updatedt!, getdt\nimport ...ODESolvers: dostep!\n\nusing ...BalanceLaws:\n    tendency_from_slow_to_fast!,\n    cummulate_fast_solution!,\n    reconcile_from_fast_to_slow!\n\nLSRK3N = LowStorageRungeKutta3N\n\n@doc \"\"\"\n    SplitExplicitLSRK3nSolver(slow_solver, fast_solver; dt, t0 = 0, coupled = true)\n\nThis is a time stepping object for explicitly time stepping the differential\nequation given by the right-hand-side function `f` with the state `Q`, i.e.,\n\n```math\n  \\\\dot{Q_fast} = f_fast(Q_fast, Q_slow, t)\n  \\\\dot{Q_slow} = f_slow(Q_slow, Q_fast, t)\n```\n\nwith the required time step size `dt` and optional initial time `t0`.  This\ntime stepping object is intended to be passed to the `solve!` command.\n\nThis method performs an operator splitting to timestep the Sea-Surface elevation\nand vertically averaged horizontal velocity of the model at a faster rate than\nthe full model, using LowStorageRungeKutta3N time-stepping.\n\n\"\"\" SplitExplicitLSRK3nSolver\nmutable struct SplitExplicitLSRK3nSolver{SS, FS, RT, MSA, MSB} <:\n               AbstractODESolver\n    \"slow solver\"\n    slow_solver::SS\n    \"fast solver\"\n    fast_solver::FS\n    \"time step\"\n    dt::RT\n    \"time\"\n    t::RT\n    \"elapsed time steps\"\n    steps::Int\n    \"storage for transfer tendency\"\n    dQ2fast::MSA\n    \"saving original fast state\"\n    S_fast::MSB\n\n    function SplitExplicitLSRK3nSolver(\n        slow_solver::LSRK3N,\n        fast_solver,\n        Q = nothing;\n        dt = getdt(slow_solver),\n        t0 = slow_solver.t,\n    ) where {AT <: AbstractArray}\n        SS = typeof(slow_solver)\n        FS = typeof(fast_solver)\n        RT = real(eltype(slow_solver.dQ))\n\n        dQ2fast = similar(slow_solver.dQ)\n        dQ2fast .= -0.0\n        #- Warning: Number of fast-solution to save (here only 1, in S_fast) should be as\n        #   large as number of non zero Butcher Coeff (including Weight \"b\") below 2 diagonal,\n        #   i.e., with ns= Number of Stages and a(ns+1,:) = b(:), all non zero a(i,j)|_{i > j+1}.\n        #  Saving only 1 fast-solution workd for LS3NRK33Heuns.\n        S_fast = similar(fast_solver.dQ)\n        S_fast .= -0.0\n        MSA = typeof(dQ2fast)\n        MSB = typeof(S_fast)\n        return new{SS, FS, RT, MSA, MSB}(\n            slow_solver,\n            fast_solver,\n            RT(dt),\n            RT(t0),\n            0,\n            dQ2fast,\n            S_fast,\n        )\n    end\nend\n\nfunction dostep!(\n    Qvec,\n    split::SplitExplicitLSRK3nSolver{SS},\n    param,\n    time::Real,\n) where {SS <: LSRK3N}\n    slow = split.slow_solver\n    fast = split.fast_solver\n\n    Qslow = Qvec.slow\n    Qfast = Qvec.fast\n\n    dQslow = slow.dQ\n    dRslow = slow.dR\n    dQ2fast = split.dQ2fast\n    S_fast = split.S_fast\n\n    slow_bl = slow.rhs!.balance_law\n    fast_bl = fast.rhs!.balance_law\n\n    rv_Q = realview(Qslow)\n    rv_dQ = realview(dQslow)\n    rv_dR = realview(dRslow)\n    groupsize = 256\n\n    slow_dt = getdt(slow)\n    fast_dt_in = getdt(fast)\n    rkA = slow.RKA\n    rkB = slow.RKB\n    rkC = slow.RKC\n    rkW = slow.RKW\n    nStages = length(rkC)\n\n    rv_dR .= -0\n    for s in 1:nStages\n        # Current slow state time\n        slow_stage_time = time + rkC[s] * slow_dt\n        # @printf(\"-- main dostep! stage s=%3i , t= %10.2f\\n\",s,slow_stage_time)\n\n        # Initialize fast model and set time-step and number of substeps we need\n        fast_steps = [0 0 0]\n        FT = typeof(slow_dt)\n        fast_time_rec = [fast_dt_in FT(0) FT(0)]\n        set_fast_for_stepping!(\n            slow_bl,\n            fast_bl,\n            fast.rhs!,\n            Qfast,\n            S_fast,\n            slow_dt,\n            rkC,\n            rkW,\n            s,\n            nStages,\n            fast_time_rec,\n            fast_steps,\n        )\n        # Initialize tentency adjustment before evaluation of slow mode\n        initialize_adjustment!(\n            slow_bl,\n            fast_bl,\n            slow.rhs!,\n            fast.rhs!,\n            Qslow,\n            Qfast,\n        )\n\n        # Evaluate the slow mode\n        # --> save tendency for the fast\n        slow.rhs!(dQ2fast, Qslow, param, slow_stage_time, increment = false)\n\n        # vertically integrate slow tendency to advance fast equation\n        # and use vertical mean for slow model (negative source)\n        # ---> work with dQ2fast as input\n        tendency_from_slow_to_fast!(\n            slow_bl,\n            fast_bl,\n            slow.rhs!,\n            fast.rhs!,\n            Qslow,\n            Qfast,\n            dQ2fast,\n        )\n\n        # Compute (and RK update) slow tendency\n        slow.rhs!(dQslow, Qslow, param, slow_stage_time, increment = true)\n\n        # Update (RK-stage) slow state\n        event = Event(array_device(Qslow))\n        event = update!(array_device(Qslow), groupsize)(\n            rv_dQ,\n            rv_dR,\n            rv_Q,\n            rkA[s % nStages + 1, 1],\n            rkA[s % nStages + 1, 2],\n            rkB[s, 1],\n            rkB[s, 2],\n            slow_dt,\n            nothing,\n            nothing,\n            nothing;\n            ndrange = length(rv_Q),\n            dependencies = (event,),\n        )\n        wait(array_device(Qslow), event)\n\n        # Determine number of substeps we need\n        fast_dt = fast_time_rec[1]\n        nsubsteps = fast_steps[3]\n\n        updatedt!(fast, fast_dt)\n\n        for substep in 1:nsubsteps\n            fast_time = time + fast_time_rec[3] + (substep - 1) * fast_dt\n            # @printf(\"-- main dostep! substep=%3i , t= %10.2f\\n\",substep,fast_time)\n            dostep!(Qfast, fast, param, fast_time)\n\n            # cumulate fast solution\n            cummulate_fast_solution!(\n                fast_bl,\n                fast.rhs!,\n                Qfast,\n                fast_time,\n                fast_dt,\n                substep,\n                fast_steps,\n                fast_time_rec,\n            )\n        end\n\n        # reconcile slow equation using fast equation\n        reconcile_from_fast_to_slow!(\n            slow_bl,\n            fast_bl,\n            slow.rhs!,\n            fast.rhs!,\n            Qslow,\n            Qfast,\n            fast_time_rec;\n            lastStage = (s == nStages),\n        )\n\n    end\n\n    # reset fast time-step to original value\n    updatedt!(fast, fast_dt_in)\n\n    # now do implicit mixing step\n    nImplSteps = slow_bl.numImplSteps\n    if nImplSteps > 0\n        # 1. get implicit mising model, model state variable array and solver handles\n        ivdc_dg = slow.rhs!.modeldata.ivdc_dg\n        ivdc_bl = ivdc_dg.balance_law\n        ivdc_Q = slow.rhs!.modeldata.ivdc_Q\n        ivdc_solver = slow.rhs!.modeldata.ivdc_bgm_solver\n        # ivdc_solver_dt = getdt(ivdc_solver) # would work if solver time-step was set\n        # FT = typeof(slow_dt)\n        # ivdc_solver_dt = slow_dt / FT(nImplSteps) # just recompute time-step\n        ivdc_solver_dt = ivdc_bl.parent_om.ivdc_dt\n        # println(\"ivdc_solver_dt = \",ivdc_solver_dt )\n        # 2. setup start RHS, initial guess and values for computing mixing coeff\n        ivdc_Q.θ .= Qslow.θ\n        ivdc_RHS = slow.rhs!.modeldata.ivdc_RHS\n        ivdc_RHS.θ .= Qslow.θ\n        ivdc_RHS.θ .= ivdc_RHS.θ ./ ivdc_solver_dt\n        ivdc_dg.state_auxiliary.θ_init .= ivdc_Q.θ\n        # 3. Invoke iterative solver\n\n        println(\"BEFORE maximum(ivdc_Q.θ[:]): \", maximum(ivdc_Q.realdata[:]))\n        println(\"BEFORE minimum(ivdc_Q.θ[:]): \", minimum(ivdc_Q.realdata[:]))\n\n        lm!(y, x) = ivdc_dg(y, x, nothing, 0; increment = false)\n        solve_tot = 0\n        iter_tot = 0\n        for i in 1:nImplSteps\n            solve_time = @elapsed iters =\n                linearsolve!(lm!, nothing, ivdc_solver, ivdc_Q, ivdc_RHS)\n            solve_tot = solve_tot + solve_time\n            iter_tot = iter_tot + iters\n            # Set new RHS and initial values\n            ivdc_RHS.θ .= ivdc_Q.θ ./ ivdc_solver_dt\n            ivdc_dg.state_auxiliary.θ_init .= ivdc_Q.θ\n        end\n        println(\"solver iters, time: \", iter_tot, \", \", solve_tot)\n\n        println(\"AFTER  maximum(ivdc_Q.θ[:]): \", maximum(ivdc_Q.realdata[:]))\n        println(\"AFTER  minimum(ivdc_Q.θ[:]): \", minimum(ivdc_Q.realdata[:]))\n\n        # exit()\n        # Now update\n        Qslow.θ .= ivdc_Q.θ\n\n    end\n\n    return nothing\nend\n"
  },
  {
    "path": "src/Ocean/SplitExplicit01/SplitExplicitModel.jl",
    "content": "module SplitExplicit01\n\nexport OceanDGModel,\n    OceanModel,\n    Continuity3dModel,\n    HorizontalModel,\n    BarotropicModel,\n    AbstractOceanProblem\n\n#using Printf\nusing StaticArrays\nusing LinearAlgebra: I, dot, Diagonal\n\nusing ...VariableTemplates\nusing ...MPIStateArrays\nusing ...DGMethods: init_ode_state, basic_grid_info\nusing ...Mesh.Filters: CutoffFilter, apply!, ExponentialFilter\nusing ...Mesh.Grids:\n    polynomialorders,\n    dimensionality,\n    dofs_per_element,\n    VerticalDirection,\n    HorizontalDirection,\n    min_node_distance\n\nusing ...BalanceLaws\n#import ...BalanceLaws: nodal_update_auxiliary_state!\n\nusing ...DGMethods.NumericalFluxes:\n    NumericalFluxFirstOrder,\n    NumericalFluxGradient,\n    NumericalFluxSecondOrder,\n    RusanovNumericalFlux,\n    CentralNumericalFluxFirstOrder,\n    CentralNumericalFluxGradient,\n    CentralNumericalFluxSecondOrder\n\nusing ..Ocean: AbstractOceanProblem\n\nimport ...DGMethods.NumericalFluxes:\n    update_penalty!, numerical_flux_second_order!, NumericalFluxFirstOrder\n\nimport ...DGMethods: LocalGeometry, DGModel, calculate_dt\n\nimport ...BalanceLaws:\n    vars_state,\n    flux_first_order!,\n    flux_second_order!,\n    source!,\n    wavespeed,\n    parameter_set,\n    boundary_conditions,\n    boundary_state!,\n    update_auxiliary_state!,\n    update_auxiliary_state_gradient!,\n    compute_gradient_argument!,\n    init_state_auxiliary!,\n    init_state_prognostic!,\n    compute_gradient_flux!,\n    indefinite_stack_integral!,\n    reverse_indefinite_stack_integral!,\n    integral_load_auxiliary_state!,\n    integral_set_auxiliary_state!,\n    reverse_integral_load_auxiliary_state!,\n    reverse_integral_set_auxiliary_state!\n\nimport ...SystemSolvers: BatchedGeneralizedMinimalResidual, linearsolve!\n\n×(a::SVector, b::SVector) = StaticArrays.cross(a, b)\n∘(a::SVector, b::SVector) = StaticArrays.dot(a, b)\n\nabstract type AbstractOceanModel <: BalanceLaw end\n\nfunction ocean_init_aux! end\nfunction ocean_init_state! end\nfunction ocean_model_boundary! end\nfunction set_fast_for_stepping! end\nfunction initialize_fast_state! end\nfunction initialize_adjustment! end\n\ninclude(\"SplitExplicitLSRK2nMethod.jl\")\ninclude(\"SplitExplicitLSRK3nMethod.jl\")\ninclude(\"OceanModel.jl\")\ninclude(\"Continuity3dModel.jl\")\ninclude(\"VerticalIntegralModel.jl\")\ninclude(\"BarotropicModel.jl\")\ninclude(\"IVDCModel.jl\")\ninclude(\"Communication.jl\")\ninclude(\"OceanBoundaryConditions.jl\")\n\nend\n"
  },
  {
    "path": "src/Ocean/SplitExplicit01/VerticalIntegralModel.jl",
    "content": "struct TendencyIntegralModel{M} <: AbstractOceanModel\n    ocean::M\n    function TendencyIntegralModel(ocean::M) where {M}\n        return new{M}(ocean)\n    end\nend\nvars_state(tm::TendencyIntegralModel, ::Prognostic, FT) =\n    vars_state(tm.ocean, Prognostic(), FT)\nvars_state(tm::TendencyIntegralModel, ::GradientFlux, FT) = @vars()\n\nfunction vars_state(m::TendencyIntegralModel, ::Auxiliary, T)\n    @vars begin\n        ∫du::SVector{2, T}\n    end\nend\n\nfunction vars_state(m::TendencyIntegralModel, ::UpwardIntegrals, T)\n    @vars begin\n        ∫du::SVector{2, T}\n    end\nend\n\n@inline function integral_load_auxiliary_state!(\n    m::TendencyIntegralModel,\n    I::Vars,\n    Q::Vars,\n    A::Vars,\n)\n    I.∫du = A.∫du\n\n    return nothing\nend\n\n@inline function integral_set_auxiliary_state!(\n    m::TendencyIntegralModel,\n    A::Vars,\n    I::Vars,\n)\n    A.∫du = I.∫du\n\n    return nothing\nend\n\ninit_state_auxiliary!(tm::TendencyIntegralModel, A::Vars, _...) = nothing\n\nfunction update_auxiliary_state!(\n    dg::DGModel,\n    tm::TendencyIntegralModel,\n    dQ::MPIStateArray,\n    t::Real,\n    elems::UnitRange,\n)\n    A = dg.state_auxiliary\n\n    # copy tendency vector to aux state for integration\n    function f!(::TendencyIntegralModel, dQ, A, t)\n        @inbounds begin\n            A.∫du = @SVector [dQ.u[1], dQ.u[2]]\n        end\n\n        return nothing\n    end\n    update_auxiliary_state!(f!, dg, tm, dQ, t)\n\n    # compute integral for Gᵁ\n    indefinite_stack_integral!(dg, tm, dQ, A, t, elems) # bottom -> top\n\n    return true\nend\n\n#-------------------------------------------------------------------------------\nstruct FlowIntegralModel{M} <: AbstractOceanModel\n    ocean::M\n    function FlowIntegralModel(ocean::M) where {M}\n        return new{M}(ocean)\n    end\nend\nvars_state(fm::FlowIntegralModel, ::Prognostic, FT) =\n    vars_state(fm.ocean, Prognostic(), FT)\nvars_state(fm::FlowIntegralModel, ::GradientFlux, FT) = @vars()\n\nfunction vars_state(m::FlowIntegralModel, ::Auxiliary, T)\n    @vars begin\n        ∫u::SVector{2, T}\n    end\nend\n\nfunction vars_state(m::FlowIntegralModel, ::UpwardIntegrals, T)\n    @vars begin\n        ∫u::SVector{2, T}\n    end\nend\n\n@inline function integral_load_auxiliary_state!(\n    m::FlowIntegralModel,\n    I::Vars,\n    Q::Vars,\n    A::Vars,\n)\n    I.∫u = Q.u\n\n    return nothing\nend\n\n@inline function integral_set_auxiliary_state!(\n    m::FlowIntegralModel,\n    A::Vars,\n    I::Vars,\n)\n    A.∫u = I.∫u\n\n    return nothing\nend\n\ninit_state_auxiliary!(fm::FlowIntegralModel, A::Vars, _...) = nothing\n\nfunction update_auxiliary_state!(\n    dg::DGModel,\n    fm::FlowIntegralModel,\n    Q::MPIStateArray,\n    t::Real,\n    elems::UnitRange,\n)\n    A = dg.state_auxiliary\n\n    # compute vertical integral of u\n    indefinite_stack_integral!(dg, fm, Q, A, t, elems) # bottom -> top\n\n    return true\nend\n"
  },
  {
    "path": "src/Ocean/SuperModels.jl",
    "content": "module SuperModels\n\nusing MPI\n\nusing ClimateMachine\n\nusing ClimateMachine: Settings\nusing ...BalanceLaws: parameter_set\n\nusing ...DGMethods.NumericalFluxes\n\nusing ..HydrostaticBoussinesq:\n    HydrostaticBoussinesqModel, NonLinearAdvectionTerm, Forcing\n\nusing ..OceanProblems: InitialValueProblem, InitialConditions\nusing ..Ocean: FreeSlip, Impenetrable, Insulating, OceanBC, Penetrable\nusing ..CartesianFields: SpectralElementField\n\nusing ...Mesh.Filters: CutoffFilter, ExponentialFilter\nusing ...Mesh.Grids: polynomialorders, DiscontinuousSpectralElementGrid\n\nusing ClimateMachine:\n    LS3NRK33Heuns,\n    OceanBoxGCMConfigType,\n    OceanBoxGCMSpecificInfo,\n    DriverConfiguration\n\nimport ClimateMachine: SolverConfiguration\n\n#####\n##### It's super good\n#####\n\nstruct HydrostaticBoussinesqSuperModel{D, G, E, S, F, N, T, C}\n    domain::D\n    grid::G\n    equations::E\n    state::S\n    fields::F\n    numerical_fluxes::N\n    timestepper::T\n    solver_configuration::C\nend\n\n\"\"\"\n    HydrostaticBoussinesqSuperModel(; domain, time_step, parameters,\n         initial_conditions = InitialConditions(),\n                  advection = (momentum = NonLinearAdvectionTerm(), tracers = NonLinearAdvectionTerm()),\n         turbulence_closure = (νʰ=0, νᶻ=0, κʰ=0, κᶻ=0),\n                   coriolis = (f₀=0, β=0),\n        rusanov_wave_speeds = (cʰ=0, cᶻ=0),\n                   buoyancy = (αᵀ=0,)\n           numerical_fluxes = ( first_order = RusanovNumericalFlux(),\n                               second_order = CentralNumericalFluxSecondOrder(),\n                                   gradient = CentralNumericalFluxGradient())\n                timestepper = ClimateMachine.ExplicitSolverType(solver_method=LS3NRK33Heuns),\n    )\n\nBuilds a `SuperModel` that solves the Hydrostatic Boussinesq equations.\n\"\"\"\nfunction HydrostaticBoussinesqSuperModel(;\n    domain,\n    parameters,\n    time_step, # We don't want to have to provide this here, but neverthless it's required.\n    initial_conditions = InitialConditions(),\n    advection = (\n        momentum = NonLinearAdvectionTerm(),\n        tracers = NonLinearAdvectionTerm(),\n    ),\n    turbulence_closure = (νʰ = 0, νᶻ = 0, κʰ = 0, κᶻ = 0),\n    coriolis = (f₀ = 0, β = 0),\n    rusanov_wave_speeds = (cʰ = 0, cᶻ = 0),\n    buoyancy = (αᵀ = 0,),\n    forcing = Forcing(),\n    numerical_fluxes = (\n        first_order = RusanovNumericalFlux(),\n        second_order = CentralNumericalFluxSecondOrder(),\n        gradient = CentralNumericalFluxGradient(),\n    ),\n    timestepper = ClimateMachine.ExplicitSolverType(\n        solver_method = LS3NRK33Heuns,\n    ),\n    filters = nothing,\n    modeldata = NamedTuple(),\n    array_type = Settings.array_type,\n    mpicomm = MPI.COMM_WORLD,\n    init_on_cpu = true,\n    boundary_tags = ((0, 0), (0, 0), (1, 2)),\n    boundary_conditions = (\n        OceanBC(Impenetrable(FreeSlip()), Insulating()),\n        OceanBC(Penetrable(FreeSlip()), Insulating()),\n    ),\n)\n\n    #####\n    ##### Build the grid\n    #####\n\n    # Change global setting if its set here\n    Settings.array_type = array_type\n\n    grid = DiscontinuousSpectralElementGrid(\n        domain;\n        boundary_tags = boundary_tags,\n        mpicomm = mpicomm,\n        array_type = array_type,\n    )\n\n    FT = eltype(domain)\n\n    #####\n    ##### Construct generic problem type InitialValueProblem\n    #####\n\n    problem = InitialValueProblem{FT}(\n        dimensions = (domain.L.x, domain.L.y, domain.L.z),\n        initial_conditions = initial_conditions,\n        boundary_conditions = boundary_conditions,\n    )\n\n    #####\n    ##### Build HydrostaticBoussinesqModel/Equations\n    #####\n\n    equations = HydrostaticBoussinesqModel{eltype(domain)}(\n        parameters,\n        problem,\n        momentum_advection = advection.momentum,\n        tracer_advection = advection.tracers,\n        forcing = forcing,\n        cʰ = convert(FT, rusanov_wave_speeds.cʰ),\n        cᶻ = convert(FT, rusanov_wave_speeds.cᶻ),\n        αᵀ = convert(FT, buoyancy.αᵀ),\n        νʰ = convert(FT, turbulence_closure.νʰ),\n        νᶻ = convert(FT, turbulence_closure.νᶻ),\n        κʰ = convert(FT, turbulence_closure.κʰ),\n        κᶻ = convert(FT, turbulence_closure.κᶻ),\n        fₒ = convert(FT, coriolis.f₀),\n        β = FT(coriolis.β),\n    )\n\n    ####\n    #### \"modeldata\"\n    ####\n    #### OceanModels require filters (?). If one was not provided, we build a default.\n    ####\n\n    # Default vertical filter and horizontal exponential filter:\n    if isnothing(filters)\n        filters = (\n            vert_filter = CutoffFilter(grid, polynomialorders(grid)),\n            exp_filter = ExponentialFilter(grid, 1, 8),\n        )\n    end\n\n    modeldata = merge(modeldata, filters)\n\n    ####\n    #### We build a DriverConfiguration here for the purposes of building\n    #### a SolverConfiguration. Then we throw it away.\n    ####\n\n    driver_configuration = DriverConfiguration(\n        OceanBoxGCMConfigType(),\n        \"\",\n        (domain.Np, domain.Np),\n        eltype(domain),\n        array_type,\n        parameter_set(equations),\n        equations,\n        MPI.COMM_WORLD,\n        grid,\n        numerical_fluxes.first_order,\n        numerical_fluxes.second_order,\n        numerical_fluxes.gradient,\n        nothing,\n        nothing, # filter\n        OceanBoxGCMSpecificInfo(),\n    )\n\n    ####\n    #### Pass through the SolverConfiguration interface so that we use\n    #### the checkpointing infrastructure\n    ####\n\n    solver_configuration = ClimateMachine.SolverConfiguration(\n        zero(FT),\n        convert(FT, time_step),\n        driver_configuration,\n        init_on_cpu = init_on_cpu,\n        ode_dt = convert(FT, time_step),\n        ode_solver_type = timestepper,\n        Courant_number = 0.4,\n        modeldata = modeldata,\n    )\n\n    state = solver_configuration.Q\n\n    u = SpectralElementField(domain, grid, state, 1)\n    v = SpectralElementField(domain, grid, state, 2)\n    η = SpectralElementField(domain, grid, state, 3)\n    θ = SpectralElementField(domain, grid, state, 4)\n\n    fields = (u = u, v = v, η = η, θ = θ)\n\n    return HydrostaticBoussinesqSuperModel(\n        domain,\n        grid,\n        equations,\n        state,\n        fields,\n        numerical_fluxes,\n        timestepper,\n        solver_configuration,\n    )\nend\n\ncurrent_time(model::HydrostaticBoussinesqSuperModel) =\n    model.solver_configuration.solver.t\nΔt(model::HydrostaticBoussinesqSuperModel) =\n    model.solver_configuration.solver.dt\ncurrent_step(model::HydrostaticBoussinesqSuperModel) =\n    model.solver_configuration.solver.steps\n\nend # module\n"
  },
  {
    "path": "src/Utilities/SingleStackUtils/SingleStackUtils.jl",
    "content": "module SingleStackUtils\n\nexport get_vars_from_nodal_stack,\n    get_vars_from_element_stack,\n    get_horizontal_variance,\n    get_horizontal_mean,\n    reduce_nodal_stack,\n    reduce_element_stack,\n    horizontally_average!,\n    dict_of_nodal_states,\n    NodalStack,\n    single_stack_diagnostics\n\nusing OrderedCollections\nusing UnPack\nusing StaticArrays\nimport KernelAbstractions: CPU\n\nusing ..BalanceLaws\nusing ..DGMethods\nusing ..DGMethods.Grids\nusing ..MPIStateArrays\nusing ..VariableTemplates\n\n\"\"\"\n    get_vars_from_nodal_stack(\n        grid::DiscontinuousSpectralElementGrid{T, dim, N},\n        Q::MPIStateArray,\n        vars;\n        vrange::UnitRange = 1:size(Q, 3),\n        i::Int = 1,\n        j::Int = 1,\n        exclude::Vector{String} = String[],\n        interp = false,\n    ) where {T, dim, N}\n\nReturn a dictionary whose keys are the `flattenednames()` of the variables\nspecified in `vars` (as returned by e.g. `vars_state`), and\nwhose values are arrays of the values for that variable along the vertical\ndimension in `Q`. Only a single element is expected in the horizontal as\nthis is intended for the single stack configuration and `i` and `j` identify\nthe horizontal nodal coordinates.\n\nVariables listed in `exclude` are skipped.\n\"\"\"\nfunction get_vars_from_nodal_stack(\n    grid::DiscontinuousSpectralElementGrid{T, dim, N},\n    Q::MPIStateArray,\n    vars;\n    vrange::UnitRange = 1:size(Q, 3),\n    i::Int = 1,\n    j::Int = 1,\n    exclude::Vector{String} = String[],\n    interp = false,\n) where {T, dim, N}\n\n    # extract grid information and bring `Q` to the host if needed\n    FT = eltype(Q)\n    Nq = N .+ 1\n    # Code assumes the same polynomial order in all horizontal directions\n    @inbounds begin\n        Nq1 = Nq[1]\n        Nq2 = Nq[2]\n        Nqk = dim == 2 ? 1 : Nq[dim]\n    end\n    Np = dofs_per_element(grid)\n    state_data = array_device(Q) isa CPU ? Q.realdata : Array(Q.realdata)\n\n    # set up the dictionary to be returned\n    var_names = flattenednames(vars)\n    stack_vals = OrderedDict()\n    num_vars = varsize(vars)\n    vars_wanted = Int[]\n    @inbounds for vi in 1:num_vars\n        if !(var_names[vi] in exclude)\n            stack_vals[var_names[vi]] = FT[]\n            push!(vars_wanted, vi)\n        end\n    end\n    elemtobndy = convert(Array, grid.elemtobndy)\n    vmap⁻ = convert(Array, grid.vmap⁻)\n    vmap⁺ = convert(Array, grid.vmap⁺)\n    vgeo = convert(Array, grid.vgeo)\n    # extract values from `state_data`\n    @inbounds for ev in vrange, k in 1:Nqk, v in vars_wanted\n        if interp && k == 1 && elemtobndy[5, ev] == 0\n            # Get face degree of freedom number\n            n = i + Nq1 * ((j - 1))\n            # get the element numbers\n            ev⁻ = ev\n            # Get neighboring id data\n            id⁻, id⁺ = vmap⁻[n, 5, ev⁻], vmap⁺[n, 5, ev⁻]\n            ev⁺ = ((id⁺ - 1) ÷ Np) + 1\n            # get the volume degree of freedom numbers\n            vid⁻, vid⁺ = ((id⁻ - 1) % Np) + 1, ((id⁺ - 1) % Np) + 1\n\n            J⁻, J⁺ = vgeo[vid⁻, Grids._M, ev⁻], vgeo[vid⁺, Grids._M, ev⁺]\n            state_local = J⁻ * state_data[vid⁻, v, ev⁻]\n            state_local += J⁺ * state_data[vid⁺, v, ev⁺]\n            state_local /= (J⁻ + J⁺)\n            push!(stack_vals[var_names[v]], state_local)\n        elseif interp && k == Nqk && elemtobndy[6, ev] == 0\n            # Get face degree of freedom number\n            n = i + Nq1 * ((j - 1))\n            # get the element numbers\n            ev⁻ = ev\n            # Get neighboring id data\n            id⁻, id⁺ = vmap⁻[n, 6, ev⁻], vmap⁺[n, 6, ev⁻]\n            # periodic and need to handle this point (otherwise handled above)\n            if id⁺ == id⁻\n                vid⁻ = ((id⁻ - 1) % Np) + 1\n\n                state_local = state_data[vid⁻, v, ev⁻]\n                push!(stack_vals[var_names[v]], state_local)\n            end\n        else\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            state_local = state_data[ijk, v, ev]\n            push!(stack_vals[var_names[v]], state_local)\n        end\n    end\n\n    return stack_vals\nend\n\n\"\"\"\n    get_vars_from_element_stack(\n        grid::DiscontinuousSpectralElementGrid{T, dim, N},\n        Q::MPIStateArray,\n        vars;\n        vrange::UnitRange = 1:size(Q, 3),\n        exclude::Vector{String} = String[],\n        interp = false,\n    ) where {T, dim, N}\n\nReturn an array of [`get_vars_from_nodal_stack()`](@ref)s whose dimensions\nare the number of nodal points per element in the horizontal plane.\n\nVariables listed in `exclude` are skipped.\n\"\"\"\nfunction get_vars_from_element_stack(\n    grid::DiscontinuousSpectralElementGrid{T, dim, N},\n    Q::MPIStateArray,\n    vars;\n    vrange::UnitRange = 1:size(Q, 3),\n    exclude::Vector{String} = String[],\n    interp = false,\n) where {T, dim, N}\n\n    Nq = N .+ 1\n    @inbounds Nq1 = Nq[1]\n    @inbounds Nq2 = Nq[2]\n\n    return [\n        get_vars_from_nodal_stack(\n            grid,\n            Q,\n            vars,\n            vrange = vrange,\n            i = i,\n            j = j,\n            exclude = exclude,\n            interp = interp,\n        ) for i in 1:Nq1, j in 1:Nq2\n    ]\nend\n\n\"\"\"\n    get_horizontal_mean(\n        grid::DiscontinuousSpectralElementGrid{T, dim, N},\n        Q::MPIStateArray,\n        vars;\n        vrange::UnitRange = 1:size(Q, 3),\n        exclude::Vector{String} = String[],\n        interp = false,\n    ) where {T, dim, N}\n\nReturn a dictionary whose keys are the `flattenednames()` of the variables\nspecified in `vars` (as returned by e.g. `vars_state`), and\nwhose values are arrays of the horizontal averages for that variable along\nthe vertical dimension in `Q`. Only a single element is expected in the\nhorizontal as this is intended for the single stack configuration.\n\nVariables listed in `exclude` are skipped.\n\"\"\"\nfunction get_horizontal_mean(\n    grid::DiscontinuousSpectralElementGrid{T, dim, N},\n    Q::MPIStateArray,\n    vars;\n    vrange::UnitRange = 1:size(Q, 3),\n    exclude::Vector{String} = String[],\n    interp = false,\n) where {T, dim, N}\n\n    Nq = N .+ 1\n    @inbounds Nq1 = Nq[1]\n    @inbounds Nq2 = Nq[2]\n\n    vars_avg = OrderedDict()\n    vars_sq = OrderedDict()\n    for i in 1:Nq1\n        for j in 1:Nq2\n            vars_nodal = get_vars_from_nodal_stack(\n                grid,\n                Q,\n                vars,\n                vrange = vrange,\n                i = i,\n                j = j,\n                exclude = exclude,\n                interp = interp,\n            )\n            vars_avg = merge(+, vars_avg, vars_nodal)\n        end\n    end\n    map!(x -> x ./ Nq1 / Nq1, values(vars_avg))\n    return vars_avg\nend\n\n\"\"\"\n    get_horizontal_variance(\n        grid::DiscontinuousSpectralElementGrid{T, dim, N},\n        Q::MPIStateArray,\n        vars;\n        vrange::UnitRange = 1:size(Q, 3),\n        exclude::Vector{String} = String[],\n        interp = false,\n    ) where {T, dim, N}\n\nReturn a dictionary whose keys are the `flattenednames()` of the variables\nspecified in `vars` (as returned by e.g. `vars_state`), and\nwhose values are arrays of the horizontal variance for that variable along\nthe vertical dimension in `Q`. Only a single element is expected in the\nhorizontal as this is intended for the single stack configuration.\n\nVariables listed in `exclude` are skipped.\n\"\"\"\nfunction get_horizontal_variance(\n    grid::DiscontinuousSpectralElementGrid{T, dim, N},\n    Q::MPIStateArray,\n    vars;\n    vrange::UnitRange = 1:size(Q, 3),\n    exclude::Vector{String} = String[],\n    interp = false,\n) where {T, dim, N}\n\n    Nq = N .+ 1\n    @inbounds Nq1 = Nq[1]\n    @inbounds Nq2 = Nq[2]\n\n    vars_avg = OrderedDict()\n    vars_sq = OrderedDict()\n    for i in 1:Nq1\n        for j in 1:Nq2\n            vars_nodal = get_vars_from_nodal_stack(\n                grid,\n                Q,\n                vars,\n                vrange = vrange,\n                i = i,\n                j = j,\n                exclude = exclude,\n                interp = interp,\n            )\n            vars_nodal_sq = OrderedDict(vars_nodal)\n            map!(x -> x .^ 2, values(vars_nodal_sq))\n            vars_avg = merge(+, vars_avg, vars_nodal)\n            vars_sq = merge(+, vars_sq, vars_nodal_sq)\n        end\n    end\n    map!(x -> (x ./ Nq1 / Nq1) .^ 2, values(vars_avg))\n    map!(x -> x ./ Nq1 / Nq1, values(vars_sq))\n    vars_var = merge(-, vars_sq, vars_avg)\n    return vars_var\nend\n\n\"\"\"\n    reduce_nodal_stack(\n        op::Function,\n        grid::DiscontinuousSpectralElementGrid{T, dim, N},\n        Q::MPIStateArray,\n        vars::NamedTuple,\n        var::String;\n        vrange::UnitRange = 1:size(Q, 3),\n    ) where {T, dim, N}\n\nReduce `var` from `vars` within `Q` over all nodal points in the specified\n`vrange` of elements with `op`. Return a tuple `(result, z)` where `result` is\nthe final value returned by `op` and `z` is the index within `vrange` where the\n`result` was determined.\n\"\"\"\nfunction reduce_nodal_stack(\n    op::Function,\n    grid::DiscontinuousSpectralElementGrid{T, dim, N},\n    Q::MPIStateArray,\n    vars::Type,\n    var::String;\n    vrange::UnitRange = 1:size(Q, 3),\n    i::Int = 1,\n    j::Int = 1,\n) where {T, dim, N}\n\n    Nq = N .+ 1\n    @inbounds begin\n        Nq1 = Nq[1]\n        Nq2 = Nq[2]\n        Nqk = dim == 2 ? 1 : Nq[dim]\n    end\n\n    var_names = flattenednames(vars)\n    var_ind = findfirst(s -> s == var, var_names)\n    if var_ind === nothing\n        return\n    end\n\n    state_data = array_device(Q) isa CPU ? Q.realdata : Array(Q.realdata)\n    z = vrange.start\n    FT = eltype(state_data)\n\n    # Initialize result with identity operation for operator\n    result = if op isa typeof(+)\n        zero(FT)\n    elseif op isa typeof(*)\n        one(FT)\n    elseif op isa typeof(min)\n        floatmax(FT)\n    elseif op isa typeof(max)\n        -floatmax(FT)\n    else\n        error(\"unknown operator: $op\")\n    end\n\n    for ev in vrange\n        for k in 1:Nqk\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            new_result = op(result, state_data[ijk, var_ind, ev])\n            if !isequal(new_result, result)\n                result = new_result\n                z = ev\n            end\n        end\n    end\n\n    return (result, z)\nend\n\n\"\"\"\n    reduce_element_stack(\n        op::Function,\n        grid::DiscontinuousSpectralElementGrid{T, dim, N},\n        Q::MPIStateArray,\n        vars::NamedTuple,\n        var::String;\n        vrange::UnitRange = 1:size(Q, 3),\n    ) where {T, dim, N}\n\nReduce `var` from `vars` within `Q` over all nodal points in the specified\n`vrange` of elements with `op`. Return a tuple `(result, z)` where `result` is\nthe final value returned by `op` and `z` is the index within `vrange` where the\n`result` was determined.\n\"\"\"\nfunction reduce_element_stack(\n    op::Function,\n    grid::DiscontinuousSpectralElementGrid{T, dim, N},\n    Q::MPIStateArray,\n    vars::Type,\n    var::String;\n    vrange::UnitRange = 1:size(Q, 3),\n) where {T, dim, N}\n\n    Nq = N .+ 1\n    @inbounds Nq1 = Nq[1]\n    @inbounds Nq2 = Nq[2]\n\n    return [\n        reduce_nodal_stack(\n            op,\n            grid,\n            Q,\n            vars,\n            var,\n            vrange = vrange,\n            i = i,\n            j = j,\n        ) for i in 1:Nq1, j in 1:Nq2\n    ]\nend\n\n\"\"\"\n    horizontally_average!(\n        grid::DiscontinuousSpectralElementGrid{T, dim, N},\n        Q::MPIStateArray,\n        i_vars,\n    ) where {T, dim, N}\n\nHorizontally average variables, from variable\nindexes `i_vars`, in `MPIStateArray` `Q`.\n\n!!! note\n    These are not proper horizontal averages-- the main\n    purpose of this method is to ensure that there are\n    no horizontal fluxes for a single stack configuration.\n\"\"\"\nfunction horizontally_average!(\n    grid::DiscontinuousSpectralElementGrid{T, dim, N},\n    Q::MPIStateArray,\n    i_vars,\n) where {T, dim, N}\n\n    Nq = N .+ 1\n    @inbounds begin\n        Nq1 = Nq[1]\n        Nq2 = Nq[2]\n        Nqk = dim == 2 ? 1 : Nq[dim]\n    end\n\n    ArrType = typeof(Q.data)\n    state_data = array_device(Q) isa CPU ? Q.realdata : Array(Q.realdata)\n\n    for ev in 1:size(state_data, 3), k in 1:Nqk, i_v in i_vars\n        Q_sum = 0\n        for i in 1:Nq1, j in 1:Nq2\n            Q_sum += state_data[i + Nq1 * ((j - 1) + Nq2 * (k - 1)), i_v, ev]\n        end\n        Q_ave = Q_sum / (Nq1 * Nq2)\n        for i in 1:Nq1, j in 1:Nq2\n            ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n            state_data[ijk, i_v, ev] = Q_ave\n        end\n    end\n    Q.realdata .= ArrType(state_data)\nend\n\nget_data(solver_config, ::Prognostic) = solver_config.Q\nget_data(solver_config, ::Auxiliary) = solver_config.dg.state_auxiliary\nget_data(solver_config, ::GradientFlux) = solver_config.dg.state_gradient_flux\n\n\"\"\"\n    dict_of_nodal_states(\n        solver_config,\n        state_types = (Prognostic(), Auxiliary());\n        aux_excludes = [],\n        interp = false,\n        )\n\nA dictionary of single stack prognostic and auxiliary\nvariables at the `i=1`,`j=1` node given\n - `solver_config` a `SolverConfiguration`\n - `aux_excludes` a vector of strings containing the\n    variables to exclude from the auxiliary state.\n\"\"\"\nfunction dict_of_nodal_states(\n    solver_config,\n    state_types = (Prognostic(), Auxiliary());\n    aux_excludes = String[],\n    interp = false,\n)\n    FT = eltype(solver_config.Q)\n    all_state_vars = []\n    for st in state_types\n        state_vars = get_vars_from_nodal_stack(\n            solver_config.dg.grid,\n            get_data(solver_config, st),\n            vars_state(solver_config.dg.balance_law, st, FT),\n            exclude = st isa Auxiliary ? aux_excludes : String[],\n            interp = interp,\n        )\n        push!(all_state_vars, state_vars...)\n    end\n    return OrderedDict(all_state_vars...)\nend\n\n# A container for holding various\n# global or point-wise states:\nstruct States{P, A, D, HD}\n    prog::P\n    aux::A\n    diffusive::D\n    hyperdiffusive::HD\nend\n\n\"\"\"\n    NodalStack(\n            bl::BalanceLaw,\n            grid::DiscontinuousSpectralElementGrid,\n            prognostic,\n            auxiliary,\n            diffusive,\n            hyperdiffusive;\n            i = 1,\n            j = 1,\n            interp = true,\n        )\n\nA struct whose `iterate(::NodalStack)` traverses\nthe nodal stack and returns a NamedTuple of\npoint-wise fields (`Vars`).\n\n# Example\n```julia\nfor state_local in NodalStack(\n        bl,\n        grid,\n        prognostic, # global field along nodal stack\n        auxiliary,\n        diffusive,\n        hyperdiffusive\n    )\nprog = state_local.prog # point-wise field along nodal stack\nend\n```\n\n## TODO: Make `prognostic`, `auxiliary`, `diffusive`, `hyperdiffusive` optional\n\n# Arguments\n - `bl` the balance law\n - `grid` the discontinuous spectral element grid\n - `prognostic` the global prognostic state\n - `auxiliary` the global auxiliary state\n - `diffusive` the global diffusive state (gradient-flux)\n - `hyperdiffusive` the global hyperdiffusive state\n - `i,j` the `i,j`'th nodal stack (in the horizontal directions)\n - `interp` a bool indicating whether to\n    interpolate the duplicate Gauss-Lebotto\n    points at the element faces.\n\n!!! warn\n    Before iterating, the data is transferred from the\n    device (GPU) to the host (CPU), as this is intended\n    for debugging / diagnostics usage.\n\"\"\"\nstruct NodalStack{N, BL, G, S, VR, TI, TJ, CI, IN}\n    bl::BL\n    grid::G\n    states::S\n    vrange::VR\n    i::TI\n    j::TJ\n    cart_ind::CI\n    interp::IN\n    function NodalStack(\n        bl::BalanceLaw,\n        grid::DiscontinuousSpectralElementGrid;\n        prognostic,\n        auxiliary,\n        diffusive,\n        hyperdiffusive,\n        i = 1,\n        j = 1,\n        interp = true,\n    )\n        states = States(prognostic, auxiliary, diffusive, hyperdiffusive)\n        vrange = 1:size(prognostic, 3)\n        grid_info = basic_grid_info(grid)\n        @unpack Nqk = grid_info\n        if last(polynomialorders(grid)) == 0\n            interp = false\n        end\n        # Store cartesian indices, so we can map the iter_state\n        # to the cartesian space `Q[i, var, j]`\n        if interp\n            cart_ind = CartesianIndices(((Nqk - 1), size(prognostic, 3)))\n        else\n            cart_ind = CartesianIndices((Nqk, size(prognostic, 3)))\n        end\n        args = (bl, grid, states, vrange, i, j, cart_ind, interp)\n        BL, G, S, VR, TI, TJ, CI, IN = typeof.(args)\n        if interp\n            len = size(prognostic, 3) * (Nqk - 1) + 1\n        else\n            len = size(prognostic, 3) * Nqk\n        end\n        new{len, BL, G, S, VR, TI, TJ, CI, IN}(args...)\n    end\nend\n\nBase.length(gs::NodalStack{N}) where {N} = N\n\n# Helper function\nget_state(v, state, vid⁻, vid⁺, ev⁻, ev⁺, J⁻, J⁺) =\n    (J⁻ * state[vid⁻, v, ev⁻] + J⁺ * state[vid⁺, v, ev⁺]) / (J⁻ + J⁺)\n\nto_cpu(state) =\n    array_device(state) isa CPU ? state.realdata : Array(state.realdata)\n\nfunction interp_top(state, args, n_vars, bl, st)\n    vs = Vars{vars_state(bl, st, eltype(state))}\n    if n_vars ≠ 0\n        return vs(map(v -> get_state(v, state, args...), 1:n_vars))\n    else\n        return nothing\n    end\nend\n\nfunction interp_bot(state, vid⁻, ev⁻, n_vars, bl, st)\n    vs = Vars{vars_state(bl, st, eltype(state))}\n    if n_vars ≠ 0\n        return vs(map(v -> state[vid⁻, v, ev⁻], 1:n_vars))\n    else\n        return nothing\n    end\nend\n\nfunction no_interp(state, ijk, ev, n_vars, bl, st)\n    vs = Vars{vars_state(bl, st, eltype(state))}\n    if n_vars ≠ 0\n        return vs(map(v -> state[ijk, v, ev], 1:n_vars))\n    else\n        return nothing\n    end\nend\n\nfunction Base.iterate(gs::NodalStack, iter_state = 1)\n    iter_state > length(gs) && return nothing\n\n    # extract grid information\n    grid = gs.grid\n    FT = eltype(grid)\n    grid_info = basic_grid_info(grid)\n    @unpack N, Nq, Np, Nqk = grid_info\n    @inbounds Nq1, Nq2 = Nq[1], Nq[2]\n    states = gs.states\n    bl = gs.bl\n    interp = gs.interp\n\n    # bring `Q` to the host if needed\n\n    prognostic = to_cpu(states.prog)\n    auxiliary = to_cpu(states.aux)\n    diffusive = to_cpu(states.diffusive)\n    hyperdiffusive = to_cpu(states.hyperdiffusive)\n\n    n_vars_prog = size(prognostic, 2)\n    n_vars_aux = size(auxiliary, 2)\n    n_vars_diff = size(diffusive, 2)\n    n_vars_hd = size(hyperdiffusive, 2)\n\n    elemtobndy = convert(Array, grid.elemtobndy)\n    vmap⁻ = convert(Array, grid.vmap⁻)\n    vmap⁺ = convert(Array, grid.vmap⁺)\n    vgeo = convert(Array, grid.vgeo)\n    i, j = gs.i, gs.j\n    if iter_state == length(gs)\n        ijk_cart = (Nqk, size(states.prog, 3))\n    else\n        ijk_cart = Tuple(gs.cart_ind[iter_state])\n    end\n    ev = ijk_cart[2]\n    k = ijk_cart[1]\n    iter_state⁺ = iter_state + 1\n    if interp && k == 1 && elemtobndy[5, ev] == 0\n        # Get face degree of freedom number\n        n = i + Nq1 * ((j - 1))\n        # get the element numbers\n        ev⁻ = ev\n        # Get neighboring id data\n        id⁻, id⁺ = vmap⁻[n, 5, ev⁻], vmap⁺[n, 5, ev⁻]\n        ev⁺ = ((id⁺ - 1) ÷ Np) + 1\n        # get the volume degree of freedom numbers\n        vid⁻, vid⁺ = ((id⁻ - 1) % Np) + 1, ((id⁺ - 1) % Np) + 1\n        J⁻, J⁺ = vgeo[vid⁻, Grids._M, ev⁻], vgeo[vid⁺, Grids._M, ev⁺]\n\n        args = (vid⁻, vid⁺, ev⁻, ev⁺, J⁻, J⁺)\n#! format: off\n        prog = interp_top(prognostic, args, n_vars_prog, bl, Prognostic())\n        aux = interp_top(auxiliary, args, n_vars_aux, bl, Auxiliary())\n        ∇flux = interp_top(diffusive, args, n_vars_diff, bl, GradientFlux())\n        hyperdiff = interp_top(hyperdiffusive, args, n_vars_hd, bl, Hyperdiffusive())\n#! format: on\n\n        return ((; prog, aux, ∇flux, hyperdiff), iter_state⁺)\n\n    elseif interp && k == Nqk && elemtobndy[6, ev] == 0\n        # Get face degree of freedom number\n        n = i + Nq1 * ((j - 1))\n        # get the element numbers\n        ev⁻ = ev\n        # Get neighboring id data\n        id⁻, id⁺ = vmap⁻[n, 6, ev⁻], vmap⁺[n, 6, ev⁻]\n        # periodic and need to handle this point (otherwise handled above)\n        if id⁺ == id⁻\n            vid⁻ = ((id⁻ - 1) % Np) + 1\n\n#! format: off\n            prog = interp_bot(prognostic, vid⁻, ev⁻, n_vars_prog, bl, Prognostic())\n            aux = interp_bot(auxiliary, vid⁻, ev⁻, n_vars_aux, bl, Auxiliary())\n            ∇flux = interp_bot(diffusive, vid⁻, ev⁻, n_vars_diff, bl, GradientFlux())\n            hyperdiff = interp_bot( hyperdiffusive, vid⁻, ev⁻, n_vars_hd, bl, Hyperdiffusive())\n#! format: on\n\n            return ((; prog, aux, ∇flux, hyperdiff), iter_state⁺)\n        else\n            error(\"uncaught case in iterate(::NodalStack)\")\n        end\n    else\n        ijk = i + Nq1 * ((j - 1) + Nq2 * (k - 1))\n\n#! format: off\n        prog = no_interp(prognostic, ijk, ev, n_vars_prog, bl, Prognostic())\n        aux = no_interp(auxiliary, ijk, ev, n_vars_aux, bl, Auxiliary())\n        ∇flux = no_interp(diffusive, ijk, ev, n_vars_diff, bl, GradientFlux())\n        hyperdiff = no_interp(hyperdiffusive, ijk, ev, n_vars_hd, bl, Hyperdiffusive())\n#! format: on\n\n        return ((; prog, aux, ∇flux, hyperdiff), iter_state⁺)\n    end\nend\n\ninclude(\"single_stack_diagnostics.jl\")\n\nend # module\n"
  },
  {
    "path": "src/Utilities/SingleStackUtils/single_stack_diagnostics.jl",
    "content": "using ..Orientations\nimport ..VariableTemplates: flattened_named_tuple\nusing ..VariableTemplates\n\n# Sometimes `NodalStack` returns local states\n# that is `nothing`. Here, we return `nothing`\n# to preserve the keys (e.g., `hyperdiff`)\n# when misssing.\nflattened_named_tuple(v::Nothing, ft::FlattenType = FlattenArr()) = nothing\n\n\"\"\"\n    single_stack_diagnostics(\n        grid::DiscontinuousSpectralElementGrid,\n        bl::BalanceLaw,\n        t::Real,\n        direction;\n        kwargs...,\n    )\n\n# Arguments\n - `grid` the grid\n - `bl` the balance law\n - `t` time\n - `direction` direction\n - `kwargs` keyword arguments, passed to [`NodalStack`](@ref).\n\nAn array of nested NamedTuples, containing results of\n - `z` - altitude\n - `prog` - the prognostic state\n - `aux` - the auxiliary state\n - `∇flux` - the gradient-flux (diffusive) state\n - `hyperdiff` - the hyperdiffusive state\n\nand all the nested NamedTuples, merged together,\nfrom the `precompute` methods.\n\"\"\"\nfunction single_stack_diagnostics(\n    grid::DiscontinuousSpectralElementGrid,\n    bl::BalanceLaw,\n    t::Real,\n    direction;\n    kwargs...,\n)\n    return [\n        begin\n            @unpack prog, aux, ∇flux, hyperdiff = local_states\n            diffusive = ∇flux\n            state = prog\n            hyperdiffusive = hyperdiff\n\n            _args_fx1 = (; state, aux, t, direction)\n            _args_fx2 = (; state, aux, t, diffusive, hyperdiffusive)\n            _args_src = (; state, aux, t, direction, diffusive)\n\n            cache_fx1 = precompute(bl, _args_fx1, Flux{FirstOrder}())\n            cache_fx2 = precompute(bl, _args_fx2, Flux{SecondOrder}())\n            cache_src = precompute(bl, _args_src, Source())\n\n            z = altitude(bl, aux)\n\n            nt = (;\n                z = altitude(bl, aux),\n                prog = flattened_named_tuple(prog), # Vars -> flattened NamedTuples\n                aux = flattened_named_tuple(aux), # Vars -> flattened NamedTuples\n                ∇flux = flattened_named_tuple(∇flux), # Vars -> flattened NamedTuples\n                hyperdiff = flattened_named_tuple(hyperdiff), # Vars -> flattened NamedTuples\n                cache_fx1,\n                cache_fx2,\n                cache_src,\n            )\n            # Flatten top level:\n            flattened_named_tuple(nt)\n        end for local_states in NodalStack(bl, grid; kwargs...)\n    ]\nend\n"
  },
  {
    "path": "src/Utilities/TicToc/TicToc.jl",
    "content": "\"\"\"\n    TicToc -- timing measurement\n\nLow-overhead time interval measurement via minimally invasive macros.\n\n\"\"\"\nmodule TicToc\n\nusing Printf\n\nexport @tic, @toc, tictoc\n\n# explicitly enable due to issues with pre-compilation\nconst tictoc_enabled = false\n\n# disable to reduce overhead\nconst tictoc_track_memory = true\n\nif tictoc_track_memory\n\n    mutable struct TimingInfo\n        ncalls::Int\n        time::UInt64\n        allocd::Int64\n        gctime::UInt64\n        curr::UInt64\n        mem::Base.GC_Num\n    end\n    TimingInfo() = TimingInfo(\n        0,\n        0,\n        0,\n        0,\n        0,\n        Base.GC_Num(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),\n    )\n\nelse # !tictoc_track_memory\n\n    mutable struct TimingInfo\n        ncalls::Int\n        time::UInt64\n        curr::UInt64\n    end\n    TimingInfo() = TimingInfo(0, 0, 0)\n\nend # if tictoc_track_memory\n\nconst timing_infos = TimingInfo[]\nconst timing_info_names = Symbol[]\nconst atexit_function_registered = Ref(false)\n\n# `@tic` helper\nfunction _tic(nm)\n    @static if !tictoc_enabled\n        return quote end\n    end\n    exti = Symbol(\"tictoc__\", nm)\n    global timing_info_names\n    if exti in timing_info_names\n        err_ex = quote\n            throw(ArgumentError(\"$(nm) already used in @tic\"))\n        end\n    else\n        err_ex = quote end\n    end\n    push!(timing_info_names, exti)\n    @static if tictoc_track_memory\n        quote\n            $(err_ex)\n            global $(exti)\n            $(exti).curr = time_ns()\n            $(exti).mem = Base.gc_num()\n        end\n    else\n        quote\n            $(err_ex)\n            global $(exti)\n            $(exti).curr = time_ns()\n        end\n    end\nend\n\n\"\"\"\n    @tic nm\n\nIndicate the start of the interval `nm`.\n\"\"\"\nmacro tic(args...)\n    na = length(args)\n    if na != 1\n        throw(ArgumentError(\"wrong number of arguments in @tic\"))\n    end\n    ex = args[1]\n    if !isa(ex, Symbol)\n        throw(ArgumentError(\"need a name argument to @tic\"))\n    end\n    return _tic(ex)\nend\n\n# `@toc` helper\nfunction _toc(nm)\n    @static if !tictoc_enabled\n        return quote end\n    end\n    exti = Symbol(\"tictoc__\", nm)\n    @static if tictoc_track_memory\n        quote\n            global $(exti)\n            $(exti).time += time_ns() - $(exti).curr\n            $(exti).ncalls += 1\n            local diff = Base.GC_Diff(Base.gc_num(), $(exti).mem)\n            $(exti).allocd += diff.allocd\n            $(exti).gctime += diff.total_time\n        end\n    else\n        quote\n            global $(exti)\n            $(exti).time += time_ns() - $(exti).curr\n            $(exti).ncalls += 1\n        end\n    end\nend\n\n\"\"\"\n    @toc nm\n\nIndicate the end of the interval `nm`.\n\"\"\"\nmacro toc(args...)\n    na = length(args)\n    if na != 1\n        throw(ArgumentError(\"wrong number of arguments in @toc\"))\n    end\n    ex = args[1]\n    if !isa(ex, Symbol)\n        throw(ArgumentError(\"need a name argument to @toc\"))\n    end\n    return _toc(ex)\nend\n\n\"\"\"\n    print_timing_info()\n\n`atexit()` function, writes all information about every interval to\n`stdout`.\n\"\"\"\nfunction print_timing_info()\n    println(\"TicToc timing information\")\n    @static if tictoc_track_memory\n        println(\"name,ncalls,tottime(ns),allocbytes,gctime\")\n    else\n        println(\"name,ncalls,tottime(ns)\")\n    end\n    for i in 1:length(timing_info_names)\n        @static if tictoc_track_memory\n            s = @sprintf(\n                \"%s,%d,%d,%d,%d\",\n                timing_info_names[i],\n                timing_infos[i].ncalls,\n                timing_infos[i].time,\n                timing_infos[i].allocd,\n                timing_infos[i].gctime\n            )\n        else\n            s = @sprintf(\n                \"%s,%d,%d\",\n                timing_info_names[i],\n                timing_infos[i].ncalls,\n                timing_infos[i].time\n            )\n        end\n        println(s)\n    end\nend\n\n\"\"\"\n    tictoc()\n\nCall at program start (only once!) to set up the globals used by the\nmacros and to register the at-exit callback.\n\"\"\"\nfunction tictoc()\n    @static if !tictoc_enabled\n        return 0\n    end\n    global timing_info_names\n    for nm in timing_info_names\n        exti = Symbol(nm)\n        isdefined(@__MODULE__, exti) && continue\n        expr = quote\n            const $exti = $TimingInfo()\n        end\n        eval(Expr(:toplevel, expr))\n        push!(timing_infos, getfield(@__MODULE__, exti))\n    end\n    if parse(Int, get(ENV, \"TICTOC_PRINT_RESULTS\", \"0\")) == 1\n        if !atexit_function_registered[]\n            atexit(print_timing_info)\n            atexit_function_registered[] = true\n        end\n    end\n    return length(timing_info_names)\nend\n\nend # module\n"
  },
  {
    "path": "src/Utilities/VariableTemplates/VariableTemplates.jl",
    "content": "module VariableTemplates\n\nexport varsize, Vars, Grad, @vars, varsindex, varsindices\n\nusing StaticArrays\nusing LinearAlgebra\n\n\"\"\"\n    varsindex(S, p::Symbol, [sp::Symbol...])\n\nReturn a range of indices corresponding to the property `p` and\n(optionally) its subproperties `sp` based on the template type `S`.\n\n# Examples\n```julia-repl\njulia> S = @vars(x::Float64, y::Float64)\njulia> varsindex(S, :y)\n2:2\n\njulia> S = @vars(x::Float64, y::@vars(α::Float64, β::SVector{3, Float64}))\njulia> varsindex(S, :y, :β)\n3:5\n```\n\"\"\"\nfunction varsindex(::Type{S}, insym::Symbol) where {S <: NamedTuple}\n    offset = 0\n    for varsym in fieldnames(S)\n        T = fieldtype(S, varsym)\n        if T <: Real\n            offset += 1\n            varrange = offset:offset\n        elseif T <: SHermitianCompact\n            LT = StaticArrays.lowertriangletype(T)\n            N = length(LT)\n            varrange = offset .+ (1:N)\n            offset += N\n        elseif T <: StaticArray\n            N = length(T)\n            varrange = offset .+ (1:N)\n            offset += N\n        else\n            varrange = offset .+ (1:varsize(T))\n            offset += varsize(T)\n        end\n        if insym == varsym\n            return varrange\n        end\n    end\n    error(\"symbol '$insym' not found\")\nend\n\n# return `Symbol`s unchanged.\nwrap_val(sym) = sym\n# wrap integer in `Val`\nwrap_val(i::Int) = Val(i)\n\n# We enforce that calls to `varsindex` on\n# an `NTuple` must be unrapped in `Val`.\n# This is enforced to synchronize failures\n# on the CPU and GPU, rather than allowing\n# CPU-working and GPU-breaking versions.\n# This means that users _must_ wrap `sym`\n# in `Val`, which can be done with `wrap_val`\n# above.\nBase.@propagate_inbounds function varsindex(\n    ::Type{S},\n    sym::Symbol,\n    rest...,\n) where {S <: Union{NamedTuple, Tuple}}\n    vi = varsindex(fieldtype(S, sym), rest...)\n    return varsindex(S, sym)[vi]\nend\nBase.@propagate_inbounds function varsindex(\n    ::Type{S},\n    ::Val{i},\n    rest...,\n) where {i, S <: Union{NamedTuple, Tuple}}\n    et = eltype(S)\n    offset = (i - 1) * varsize(et)\n    vi = varsindex(et, rest...)\n    return (vi.start + offset):(vi.stop + offset)\nend\n\nBase.@propagate_inbounds function varsindex(\n    ::Type{S},\n    ::Val{i},\n) where {i, S <: SArray}\n    return i:i\nend\n\n\"\"\"\n    varsindices(S, ps::Tuple)\n    varsindices(S, ps...)\n\nReturn a tuple of indices corresponding to the properties\nspecified by `ps` based on the template type `S`. Properties\ncan be specified using either symbols or strings.\n\n# Examples\n```julia-repl\njulia> S = @vars(x::Float64, y::Float64, z::Float64)\njulia> varsindices(S, (:x, :z))\n(1, 3)\n\njulia> S = @vars(x::Float64, y::@vars(α::Float64, β::SVector{3, Float64}))\njulia> varsindices(S, \"x\", \"y.β\")\n(1, 3, 4, 5)\n```\n\"\"\"\nfunction varsindices(::Type{S}, vars::Tuple) where {S <: NamedTuple}\n    indices = Int[]\n    for var in vars\n        splitvar = split(string(var), '.')\n        append!(indices, collect(varsindex(S, map(Symbol, splitvar)...)))\n    end\n    Tuple(indices)\nend\nvarsindices(::Type{S}, vars...) where {S <: NamedTuple} = varsindices(S, vars)\n\n\"\"\"\n    varsize(S)\n\nThe number of elements specified by the template type `S`.\n\"\"\"\nvarsize(::Type{T}) where {T <: Real} = 1\nvarsize(::Type{Tuple{}}) = 0\nvarsize(::Type{NamedTuple{(), Tuple{}}}) = 0\nvarsize(::Type{SArray{S, T, N} where L}) where {S, T, N} = prod(S.parameters)\n\ninclude(\"var_names.jl\")\n\n# TODO: should be possible to get rid of @generated\n@generated function varsize(::Type{S}) where {S}\n    types = fieldtypes(S)\n    isempty(types) ? 0 : sum(varsize, types)\nend\n\nfunction process_vars!(syms, typs, expr)\n    if expr isa LineNumberNode\n        return\n    elseif expr isa Expr && expr.head == :block\n        for arg in expr.args\n            process_vars!(syms, typs, arg)\n        end\n        return\n    elseif expr.head == :(::)\n        push!(syms, expr.args[1])\n        push!(typs, expr.args[2])\n        return\n    else\n        error(\"Invalid expression\")\n    end\nend\n\n\"\"\"\n    @vars(var1::Type1, var2::Type2)\n\nA convenient syntax for describing a `NamedTuple` type.\n\n# Example\n```julia\njulia> @vars(a::Float64, b::Float64)\nNamedTuple{(:a, :b),Tuple{Float64,Float64}}\n```\n\"\"\"\nmacro vars(args...)\n    syms = Any[]\n    typs = Any[]\n    for arg in args\n        process_vars!(syms, typs, arg)\n    end\n    :(NamedTuple{$(tuple(syms...)), Tuple{$(esc.(typs)...)}})\nend\n\nstruct GetVarError <: Exception\n    sym::Symbol\nend\nstruct SetVarError <: Exception\n    sym::Symbol\nend\n\nabstract type AbstractVars{S, A, offset} end\n\n\"\"\"\n    Vars{S,A,offset}(array::A)\n\nDefines property overloading for `array` using the type `S` as a template. `offset` is used to shift the starting element of the array.\n\"\"\"\nstruct Vars{S, A, offset} <: AbstractVars{S, A, offset}\n    array::A\nend\nVars{S}(array) where {S} = Vars{S, typeof(array), 0}(array)\n\nBase.parent(v::AbstractVars) = getfield(v, :array)\nBase.eltype(v::AbstractVars) = eltype(parent(v))\nBase.propertynames(::AbstractVars{S}) where {S} = fieldnames(S)\nBase.similar(v::AbstractVars) = typeof(v)(similar(parent(v)))\n\n@generated function Base.getproperty(\n    v::Vars{S, A, offset},\n    sym::Symbol,\n) where {S, A, offset}\n    expr = quote\n        Base.@_inline_meta\n        array = parent(v)\n    end\n    for k in fieldnames(S)\n        T = fieldtype(S, k)\n        if T <: Real\n            retexpr = :($T(array[$(offset + 1)]))\n            offset += 1\n        elseif T <: SHermitianCompact\n            LT = StaticArrays.lowertriangletype(T)\n            N = length(LT)\n            retexpr = :($T($LT($([:(array[$(offset + i)]) for i in 1:N]...))))\n            offset += N\n        elseif T <: StaticArray\n            N = length(T)\n            retexpr = :($T($([:(array[$(offset + i)]) for i in 1:N]...)))\n            offset += N\n        else\n            retexpr = :(Vars{$T, A, $offset}(array))\n            offset += varsize(T)\n        end\n        push!(expr.args, :(\n            if sym == $(QuoteNode(k))\n                return @inbounds $retexpr\n            end\n        ))\n    end\n    push!(expr.args, :(throw(GetVarError(sym))))\n    expr\nend\n\n@generated function Base.setproperty!(\n    v::Vars{S, A, offset},\n    sym::Symbol,\n    val,\n) where {S, A, offset}\n    expr = quote\n        Base.@_inline_meta\n        array = parent(v)\n    end\n    for k in fieldnames(S)\n        T = fieldtype(S, k)\n        if T <: Real\n            retexpr = :(array[$(offset + 1)] = val)\n            offset += 1\n        elseif T <: SHermitianCompact\n            LT = StaticArrays.lowertriangletype(T)\n            N = length(LT)\n            retexpr = :(\n                array[($(offset + 1)):($(offset + N))] .=\n                    $T(val).lowertriangle\n            )\n            offset += N\n        elseif T <: StaticArray\n            N = length(T)\n            retexpr = :(array[($(offset + 1)):($(offset + N))] .= val[:])\n            offset += N\n        else\n            offset += varsize(T)\n            continue\n        end\n        push!(expr.args, :(\n            if sym == $(QuoteNode(k))\n                return @inbounds $retexpr\n            end\n        ))\n    end\n    push!(expr.args, :(throw(SetVarError(sym))))\n    expr\nend\n\n\"\"\"\n    Grad{S,A,offset}(array::A)\n\nDefines property overloading along slices of the second dimension of `array` using the type `S` as a template. `offset` is used to shift the starting element of the array.\n\"\"\"\nstruct Grad{S, A, offset} <: AbstractVars{S, A, offset}\n    array::A\nend\nGrad{S}(array) where {S} = Grad{S, typeof(array), 0}(array)\n\n@generated function Base.getproperty(\n    v::Grad{S, A, offset},\n    sym::Symbol,\n) where {S, A, offset}\n    if A <: SubArray\n        M = size(fieldtype(A, 1), 1)\n    else\n        M = size(A, 1)\n    end\n    expr = quote\n        Base.@_inline_meta\n        array = parent(v)\n    end\n    for k in fieldnames(S)\n        T = fieldtype(S, k)\n        if T <: Real\n            retexpr = :(SVector{$M, $T}(\n                $([:(array[$i, $(offset + 1)]) for i in 1:M]...),\n            ))\n            offset += 1\n        elseif T <: StaticArray\n            N = length(T)\n            retexpr = :(SMatrix{$M, $N, $(eltype(T))}(\n                $([:(array[$i, $(offset + j)]) for i in 1:M, j in 1:N]...),\n            ))\n            offset += N\n        else\n            retexpr = :(Grad{$T, A, $offset}(array))\n            offset += varsize(T)\n        end\n        push!(expr.args, :(\n            if sym == $(QuoteNode(k))\n                return @inbounds $retexpr\n            end\n        ))\n    end\n    push!(expr.args, :(throw(GetVarError(sym))))\n    expr\nend\n\n@generated function Base.setproperty!(\n    v::Grad{S, A, offset},\n    sym::Symbol,\n    val::AbstractArray,\n) where {S, A, offset}\n    if A <: SubArray\n        M = size(fieldtype(A, 1), 1)\n    else\n        M = size(A, 1)\n    end\n    expr = quote\n        Base.@_inline_meta\n        array = parent(v)\n    end\n    for k in fieldnames(S)\n        T = fieldtype(S, k)\n        if T <: Real\n            retexpr = :(array[:, $(offset + 1)] = val)\n            offset += 1\n        elseif T <: StaticArray\n            N = length(T)\n            retexpr = :(\n                array[\n                    :,\n                    # static range is used here to force dispatch to\n                    # StaticArrays setindex! because generic setindex! is slow\n                    StaticArrays.SUnitRange($(offset + 1), $(offset + N)),\n                ] = val\n            )\n            offset += N\n        else\n            offset += varsize(T)\n            continue\n        end\n        push!(expr.args, :(\n            if sym == $(QuoteNode(k))\n                return @inbounds $retexpr\n            end\n        ))\n    end\n    push!(expr.args, :(throw(SetVarError(sym))))\n    expr\nend\n\nexport unroll_map, @unroll_map\n\"\"\"\n    @unroll_map(f::F, N::Int, args...) where {F}\n    unroll_map(f::F, N::Int, args...) where {F}\n\nUnroll N-expressions and wrap arguments in `Val`.\n\"\"\"\n@generated function unroll_map(f::F, ::Val{N}, args...) where {F, N}\n    quote\n        Base.@_inline_meta\n        Base.Cartesian.@nexprs $N i -> f(Val(i), args...)\n    end\nend\nmacro unroll_map(func, N, args...)\n    @assert func.head == :(->)\n    body = func.args[2]\n    pushfirst!(body.args, :(Base.@_inline_meta))\n    quote\n        $unroll_map($(esc(func)), Val($(esc(N))), $(esc(args))...)\n    end\nend\n\nexport vuntuple\n\"\"\"\n    vuntuple(f::F, N::Int)\n\nVal-Unroll ntuple: wrap `ntuple`\narguments in `Val` for unrolling.\n\"\"\"\nvuntuple(f::F, N::Int) where {F} = ntuple(i -> f(Val(i)), Val(N))\n\n# Inside unroll_map expressions, all indexes `i`\n# are wrapped in `Val`, so we must redirect\n# these methods:\nBase.@propagate_inbounds Base.getindex(t::Tuple, ::Val{i}) where {i} =\n    Base.getindex(t, i)\nBase.@propagate_inbounds Base.getindex(a::SArray, ::Val{i}) where {i} =\n    Base.getindex(a, i)\n\nBase.@propagate_inbounds function Base.getindex(\n    v::Vars{NTuple{N, T}, A, offset},\n    ::Val{i},\n) where {N, T, A, offset, i} # 1 <= i <= N\n    return Vars{T, A, offset + (i - 1) * varsize(T)}(parent(v))\nend\n\nBase.@propagate_inbounds function Base.getindex(\n    v::Grad{NTuple{N, T}, A, offset},\n    ::Val{i},\n) where {N, T, A, offset, i} # 1 <= i <= N\n    return Grad{T, A, offset + (i - 1) * varsize(T)}(parent(v))\nend\n\n\"\"\"\n    getpropertyorindex\n\nAn interchangeably and nested-friendly\n`getproperty`/`getindex`.\n\"\"\"\nfunction getpropertyorindex end\n\n# Redirect to Base getproperty/getindex:\nBase.@propagate_inbounds getpropertyorindex(t::Tuple, ::Val{i}) where {i} =\n    Base.getindex(t, i)\nBase.@propagate_inbounds getpropertyorindex(\n    a::AbstractArray,\n    ::Val{i},\n) where {i} = Base.getindex(a, i)\nBase.@propagate_inbounds getpropertyorindex(v::AbstractVars, s::Symbol) =\n    Base.getproperty(v, s)\nBase.@propagate_inbounds getpropertyorindex(\n    v::AbstractVars,\n    ::Val{i},\n) where {i} = Base.getindex(v, Val(i))\n\n# Only one element left:\nBase.@propagate_inbounds getpropertyorindex(\n    v::AbstractVars,\n    t::Tuple{A},\n) where {A} = getpropertyorindex(v, t[1])\nBase.@propagate_inbounds getpropertyorindex(\n    a::AbstractArray,\n    t::Tuple{A},\n) where {A} = getpropertyorindex(a, t[1])\n\n# Peel first element from tuple and recurse:\nBase.@propagate_inbounds getpropertyorindex(v::AbstractVars, t::Tuple) =\n    getpropertyorindex(getpropertyorindex(v, t[1]), Tuple(t[2:end]))\n\n# Redirect to getpropertyorindex:\nBase.@propagate_inbounds Base.getproperty(v::AbstractVars, tup_chain::Tuple) =\n    getpropertyorindex(v, tup_chain)\nBase.@propagate_inbounds Base.getindex(v::AbstractVars, tup_chain::Tuple) =\n    getpropertyorindex(v, tup_chain)\n\ninclude(\"flattened_tup_chain.jl\")\n\nfunction Base.show(io::IO, v::AbstractVars)\n    s = \"$(nameof(typeof(v))) object:\\n\"\n    for tup in flattened_tup_chain(v, RetainArr())\n        name = join(tup, \"_\")\n        val = getproperty(v, wrap_val.(tup))\n        s *= \"    $name = $val\\n\"\n    end\n    print(io, s)\nend\n\nend # module\n"
  },
  {
    "path": "src/Utilities/VariableTemplates/flattened_tup_chain.jl",
    "content": "using LinearAlgebra\n\nexport flattened_tup_chain, flattened_named_tuple, flattened_tuple\nexport FlattenType, FlattenArr, RetainArr\n\nabstract type FlattenType end\n\n\"\"\"\n    FlattenArr\n\nFlatten arrays in `flattened_tup_chain`\nand `flattened_named_tuple`.\n\"\"\"\nstruct FlattenArr <: FlattenType end\n\n\"\"\"\n    RetainArr\n\nDo _not_ flatten arrays in `flattened_tup_chain`\nand `flattened_named_tuple`.\n\"\"\"\nstruct RetainArr <: FlattenType end\n\n# The Vars instance has many empty entries.\n# Keeping all of the keys results in many\n# duplicated values. So, it's best we\n# \"prune\" the tree by removing the keys:\nflattened_tup_chain(\n    ::Type{NamedTuple{(), Tuple{}}},\n    ::FlattenType = FlattenArr();\n    prefix = (Symbol(),),\n) = ()\n\nflattened_tup_chain(\n    ::Type{T},\n    ::FlattenType;\n    prefix = (Symbol(),),\n) where {T <: Real} = (prefix,)\n\nflattened_tup_chain(\n    ::Type{T},\n    ::RetainArr;\n    prefix = (Symbol(),),\n) where {T <: SArray} = (prefix,)\nflattened_tup_chain(\n    ::Type{T},\n    ::FlattenArr;\n    prefix = (Symbol(),),\n) where {T <: SArray} = ntuple(i -> (prefix..., i), length(T))\n\nflattened_tup_chain(\n    ::Type{T},\n    ::RetainArr;\n    prefix = (Symbol(),),\n) where {T <: SHermitianCompact} = (prefix,)\nflattened_tup_chain(\n    ::Type{T},\n    ::FlattenType;\n    prefix = (Symbol(),),\n) where {T <: SHermitianCompact} =\n    ntuple(i -> (prefix..., i), length(StaticArrays.lowertriangletype(T)))\n\nflattened_tup_chain(\n    ::Type{T},\n    ::RetainArr;\n    prefix = (Symbol(),),\n) where {N, TA, T <: Diagonal{N, TA}} = (prefix,)\nflattened_tup_chain(\n    ::Type{T},\n    ::FlattenArr;\n    prefix = (Symbol(),),\n) where {N, TA, T <: Diagonal{N, TA}} = ntuple(i -> (prefix..., i), length(TA))\n\nflattened_tup_chain(::Type{T}, ::FlattenType; prefix = (Symbol(),)) where {T} =\n    (prefix,)\n\n\"\"\"\n    flattened_tup_chain(::Type{T}) where {T <: Union{NamedTuple,NTuple}}\n\nAn array of tuples, containing symbols\nand integers for every combination of\neach field in the `Vars` array.\n\"\"\"\nfunction flattened_tup_chain(\n    ::Type{T},\n    ft::FlattenType = FlattenArr();\n    prefix = (Symbol(),),\n) where {T <: Union{NamedTuple, NTuple}}\n    map(1:fieldcount(T)) do i\n        Ti = fieldtype(T, i)\n        name = fieldname(T, i)\n        sname = name isa Int ? name : Symbol(name)\n        flattened_tup_chain(\n            Ti,\n            ft;\n            prefix = prefix == (Symbol(),) ? (sname,) : (prefix..., sname),\n        )\n    end |>\n    Iterators.flatten |>\n    collect\nend\nflattened_tup_chain(\n    ::AbstractVars{S},\n    ft::FlattenType = FlattenArr(),\n) where {S} = flattened_tup_chain(S, ft)\n\n\"\"\"\n    flattened_named_tuple\n\nA flattened NamedTuple, given a\n`Vars` or nested `NamedTuple` instance.\n\n# Example:\n\n```julia\nusing Test\nusing ClimateMachine.VariableTemplates\nnt = (x = 1, a = (y = 2, z = 3, b = ((a = 1,), (a = 2,), (a = 3,))));\nfnt = flattened_named_tuple(nt);\n@test keys(fnt) == (:x, :a_y, :a_z, :a_b_1_a, :a_b_2_a, :a_b_3_a)\n@test length(fnt) == 6\n@test fnt.x == 1\n@test fnt.a_y == 2\n@test fnt.a_z == 3\n@test fnt.a_b_1_a == 1\n@test fnt.a_b_2_a == 2\n@test fnt.a_b_3_a == 3\n```\n\"\"\"\nfunction flattened_named_tuple end\n\nfunction flattened_named_tuple(v::AbstractVars, ft::FlattenType = FlattenArr())\n    ftc = flattened_tup_chain(v, ft)\n    keys_ = Symbol.(join.(ftc, :_))\n    vals = map(x -> getproperty(v, wrap_val.(x)), ftc)\n    length(keys_) == length(vals) || error(\"key-value mismatch\")\n    return (; zip(keys_, vals)...)\nend\n\nfunction flattened_named_tuple(nt::NamedTuple, ft::FlattenType = FlattenArr())\n    ftc = flattened_tup_chain(typeof(nt), ft)\n    keys_ = Symbol.(join.(ftc, :_))\n    vals = flattened_tuple(ft, nt)\n    length(keys_) == length(vals) || error(\"key-value mismatch\")\n    return (; zip(keys_, vals)...)\nend\n\nflattened_tuple(::FlattenArr, a::AbstractArray) = tuple(a...)\nflattened_tuple(::RetainArr, a::AbstractArray) = tuple(a)\n\nflattened_tuple(::FlattenArr, a::Diagonal) = tuple(a.diag...)\nflattened_tuple(::RetainArr, a::Diagonal) = tuple(a.diag)\n\nflattened_tuple(::FlattenArr, a::SHermitianCompact) = tuple(a.lowertriangle...)\nflattened_tuple(::RetainArr, a::SHermitianCompact) = tuple(a.lowertriangle)\n\n# when we splat an empty tuple `b` into `flattened_tuple(ft, b...)`\nflattened_tuple(::FlattenType) = ()\n\n# for structs\nflattened_tuple(::FlattenType, a) = (a,)\n\n# Divide and conquer:\nflattened_tuple(ft::FlattenType, a, b...) =\n    tuple(flattened_tuple(ft, a)..., flattened_tuple(ft, b...)...)\n\nflattened_tuple(ft::FlattenType, a::Tuple) = flattened_tuple(ft, a...)\n\nflattened_tuple(ft::FlattenType, a::NamedTuple) = flattened_tuple(ft, Tuple(a))\n"
  },
  {
    "path": "src/Utilities/VariableTemplates/var_names.jl",
    "content": "export flattenednames\n\nflattenednames(nt::Type{NTuple{N, T}}; prefix = \"\") where {N, T} =\n    Iterators.flatten([\n        flattenednames(T; prefix = \"$(prefix)[$i]\") for i in 1:N\n    ]) |> collect\nflattenednames(::Type{NamedTuple{(), Tuple{}}}; prefix = \"\") = ()\nflattenednames(::Type{T}; prefix = \"\") where {T <: Real} = (prefix,)\nflattenednames(::Type{T}; prefix = \"\") where {T <: SArray} =\n    ntuple(i -> \"$prefix[$i]\", length(T))\nfunction flattenednames(::Type{T}; prefix = \"\") where {T <: SHermitianCompact}\n    N = size(T, 1)\n    [[\"$prefix[$i,$j]\" for i in j:N] for j in 1:N] |>\n    Iterators.flatten |>\n    collect\nend\nfunction flattenednames(::Type{T}; prefix = \"\") where {T <: NamedTuple}\n    map(1:fieldcount(T)) do i\n        Ti = fieldtype(T, i)\n        name = fieldname(T, i)\n        flattenednames(\n            Ti,\n            prefix = prefix == \"\" ? string(name) : string(prefix, '.', name),\n        )\n    end |>\n    Iterators.flatten |>\n    collect\nend\n"
  },
  {
    "path": "test/Arrays/basics.jl",
    "content": "using Test, MPI\n\nusing ClimateMachine\nusing ClimateMachine.MPIStateArrays\n\nClimateMachine.init()\nconst ArrayType = ClimateMachine.array_type()\nconst mpicomm = MPI.COMM_WORLD\n\n@testset \"MPIStateArray basics\" begin\n    Q = MPIStateArray{Float32}(mpicomm, ArrayType, 4, 6, 8)\n\n    @test eltype(Q) == Float32\n    @test size(Q) == (4, 6, 8)\n\n    fillval = 0.5f0\n    fill!(Q, fillval)\n\n    ClimateMachine.gpu_allowscalar(true)\n    @test Q[1] == fillval\n    @test Q[2, 3, 4] == fillval\n    @test Q[end] == fillval\n\n    @test Array(Q) == fill(fillval, 4, 6, 8)\n\n    Q[2, 3, 4] = 2fillval\n    @test Q[2, 3, 4] != fillval\n    ClimateMachine.gpu_allowscalar(false)\n\n    Qp = copy(Q)\n\n    @test typeof(Qp) == typeof(Q)\n    @test eltype(Qp) == eltype(Q)\n    @test size(Qp) == size(Q)\n    @test Array(Qp) == Array(Q)\n\n    Qp = similar(Q)\n\n    @test typeof(Qp) == typeof(Q)\n    @test eltype(Qp) == eltype(Q)\n    @test size(Qp) == size(Q)\n\n    copyto!(Qp, Q)\n    @test Array(Qp) == Array(Q)\nend\n"
  },
  {
    "path": "test/Arrays/broadcasting.jl",
    "content": "using Test, MPI\n\nusing ClimateMachine\nusing ClimateMachine.MPIStateArrays\n\nClimateMachine.init()\nconst ArrayType = ClimateMachine.array_type()\nconst mpicomm = MPI.COMM_WORLD\n\n@testset \"MPIStateArray broadcasting\" begin\n    let\n        localsize = (4, 6, 8)\n        A = rand(Float32, localsize)\n        B = rand(Float32, localsize)\n\n        QA = MPIStateArray{Float32}(mpicomm, ArrayType, localsize...)\n        QB = similar(QA)\n\n        QA .= A\n        QB .= B\n\n        @test Array(QA) == A\n        @test Array(QB) == B\n\n        QC = QA .+ QB\n        @test typeof(QC) == typeof(QA)\n        C = Array(QC)\n        @test C == A .+ B\n\n        QC = QA .+ sqrt.(QB)\n        C = Array(QC)\n        @test C ≈ A .+ sqrt.(B)\n\n        QC = QA .+ sqrt.(QB) .* exp.(QA .- QB .^ 2)\n        C = Array(QC)\n        @test C ≈ A .+ sqrt.(B) .* exp.(A .- B .^ 2)\n\n        # writing to an existing array instead of creating a new one\n        fill!(QC, 0)\n        QC .= QA .+ sqrt.(QB) .* exp.(QA .- QB .^ 2)\n        C = Array(QC)\n        @test C ≈ A .+ sqrt.(B) .* exp.(A .- B .^ 2)\n    end\n\n    let\n        numelems = 12\n        realelems = 1:7\n        ghostelems = 8:12\n\n        QA = MPIStateArray{Int}(\n            mpicomm,\n            ArrayType,\n            1,\n            1,\n            numelems,\n            realelems = realelems,\n            ghostelems = ghostelems,\n        )\n        QB = similar(QA)\n\n        fill!(QA, 1)\n        fill!(QB, 3)\n\n        QB .= QA .+ QB\n\n        @test all(Array(QB)[realelems] .== 4)\n        @test all(Array(QB)[ghostelems] .== 3)\n    end\nend\n"
  },
  {
    "path": "test/Arrays/mpi_comm.jl",
    "content": "using Test\nusing MPI\nusing ClimateMachine\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.Mesh.BrickMesh\nusing Pkg\nusing KernelAbstractions\n\nClimateMachine.init()\nconst ArrayType = ClimateMachine.array_type()\nconst comm = MPI.COMM_WORLD\n\n\nfunction main()\n\n    crank = MPI.Comm_rank(comm)\n    csize = MPI.Comm_size(comm)\n\n\n    @assert csize == 3\n\n    if crank == 0\n        numreal = 4\n        numghost = 3\n\n        nabrtorank = [1, 2]\n\n        sendelems = [1, 2, 3, 4, 1, 4]\n        nabrtorecv = [1:2, 3:3]\n        nabrtosend = [1:4, 5:6]\n\n        vmaprecv = [\n            37,\n            38,\n            39,\n            40,\n            42,\n            43,\n            44,\n            45,\n            46,\n            49,\n            52,\n            53,\n            54,\n            57,\n            60,\n            61,\n            62,\n            63,\n        ]\n        vmapsend =\n            [3, 6, 9, 10, 11, 12, 19, 22, 25, 34, 35, 36, 1, 2, 3, 28, 31, 34]\n\n        nabrtovmaprecv = [1:13, 14:18]\n        nabrtovmapsend = [1:12, 13:18]\n\n        expectedghostdata = [\n            1001,\n            1002,\n            1003,\n            1004,\n            1006,\n            1007,\n            1008,\n            1009,\n            1010,\n            1013,\n            1016,\n            1017,\n            1018,\n            2003,\n            2006,\n            2007,\n            2008,\n            2009,\n        ]\n    elseif crank == 1\n        numreal = 2\n        numghost = 4\n\n        nabrtorank = [0]\n\n        sendelems = [1, 2]\n        nabrtorecv = [1:4]\n        nabrtosend = [1:2]\n\n        vmaprecv = [21, 24, 27, 28, 29, 30, 37, 40, 43, 52, 53, 54]\n        vmapsend = [1, 2, 3, 4, 6, 7, 8, 9, 10, 13, 16, 17, 18]\n\n        nabrtovmaprecv = [1:length(vmaprecv)]\n        nabrtovmapsend = [1:length(vmapsend)]\n\n        expectedghostdata = [3, 6, 9, 10, 11, 12, 19, 22, 25, 34, 35, 36]\n    elseif crank == 2\n        numreal = 1\n        numghost = 2\n\n        nabrtorank = [0]\n\n        sendelems = [1]\n        nabrtorecv = [1:2]\n        nabrtosend = [1:1]\n\n        vmaprecv = [10, 11, 12, 19, 22, 25]\n        vmapsend = [3, 6, 7, 8, 9]\n\n        nabrtovmaprecv = [1:length(vmaprecv)]\n        nabrtovmapsend = [1:length(vmapsend)]\n\n        expectedghostdata = [1, 2, 3, 28, 31, 34]\n    end\n\n    numelem = numreal + numghost\n\n    realelems = 1:numreal\n    ghostelems = numreal .+ (1:numghost)\n\n    weights = Array{Int64}(undef, (0, 0, 0))\n\n    A = MPIStateArray{Int64}(\n        comm,\n        ArrayType,\n        9,\n        2,\n        numelem,\n        realelems,\n        ghostelems,\n        ArrayType(vmaprecv),\n        ArrayType(vmapsend),\n        nabrtorank,\n        nabrtovmaprecv,\n        nabrtovmapsend,\n        ArrayType(weights),\n    )\n\n    Q = Array(A.data)\n    Q .= -1\n    shift = 100\n    Q[:, 1, realelems] .=\n        reshape((crank * 1000) .+ (1:(9 * numreal)), 9, numreal)\n    Q[:, 2, realelems] .=\n        reshape((crank * 1000) .+ shift .+ (1:(9 * numreal)), 9, numreal)\n    copyto!(A.data, Q)\n\n    event = Event(array_device(A))\n    event = MPIStateArrays.begin_ghost_exchange!(A; dependencies = event)\n    event = MPIStateArrays.end_ghost_exchange!(A; dependencies = event)\n    wait(array_device(A), event)\n\n    Q = Array(A.data)\n    @test all(expectedghostdata .== Q[:, 1, :][:][vmaprecv])\n    @test all(shift .+ expectedghostdata .== Q[:, 2, :][:][vmaprecv])\n\nend\n\nmain()\n"
  },
  {
    "path": "test/Arrays/reductions.jl",
    "content": "using Test, MPI\nusing LinearAlgebra\n\nusing ClimateMachine\nusing ClimateMachine.MPIStateArrays\n\nClimateMachine.init()\nconst ArrayType = ClimateMachine.array_type()\nconst mpicomm = MPI.COMM_WORLD\n\nmpisize = MPI.Comm_size(mpicomm)\nmpirank = MPI.Comm_rank(mpicomm)\n\n@testset \"MPIStateArray reductions\" begin\n\n    localsize = (4, 6, 8)\n\n    A = Array{Float32}(reshape(1:prod(localsize), localsize))\n    globalA = vcat([A for _ in 1:mpisize]...)\n\n    QA = MPIStateArray{Float32}(mpicomm, ArrayType, localsize...)\n    QA .= A\n\n\n    @test norm(QA, 1) ≈ norm(globalA, 1)\n    @test norm(QA) ≈ norm(globalA)\n    @test norm(QA, Inf) ≈ norm(globalA, Inf)\n\n    @test norm(QA; dims = (1, 3)) ≈ mapslices(norm, globalA; dims = (1, 3))\n    @test norm(QA, 1; dims = (1, 3)) ≈\n          mapslices(S -> norm(S, 1), globalA, dims = (1, 3))\n    @test norm(QA, Inf; dims = (1, 3)) ≈\n          mapslices(S -> norm(S, Inf), globalA, dims = (1, 3))\n\n    B = Array{Float32}(reshape(reverse(1:prod(localsize)), localsize))\n    globalB = vcat([B for _ in 1:mpisize]...)\n\n    QB = similar(QA)\n    QB .= B\n\n    @test isapprox(euclidean_distance(QA, QB), norm(globalA .- globalB))\n    @test isapprox(dot(QA, QB), dot(globalA, globalB))\n\n    C = fill(Float32(mpirank + 1), localsize)\n    globalC = vcat([fill(i, localsize) for i in 1:mpisize]...)\n    QC = similar(QA)\n    QC .= C\n\n    @test sum(QC) == sum(globalC)\n    @test Array(sum(QC; dims = (1, 3))) == sum(globalC; dims = (1, 3))\n    @test maximum(QC) == maximum(globalC)\n    @test Array(maximum(QC; dims = (1, 3))) == maximum(globalC; dims = (1, 3))\n    @test minimum(QC) == minimum(globalC)\n    @test Array(minimum(QC; dims = (1, 3))) == minimum(globalC; dims = (1, 3))\nend\n"
  },
  {
    "path": "test/Arrays/runtests.jl",
    "content": "using Test\ninclude(joinpath(\"..\", \"testhelpers.jl\"))\n\n@testset \"MPIStateArrays reductions\" begin\n    runmpi(joinpath(@__DIR__, \"basics.jl\"))\n    runmpi(joinpath(@__DIR__, \"broadcasting.jl\"))\n    runmpi(joinpath(@__DIR__, \"reductions.jl\"))\n    runmpi(joinpath(@__DIR__, \"reductions.jl\"), ntasks = 3)\n    runmpi(joinpath(@__DIR__, \"varsindex.jl\"))\nend\n"
  },
  {
    "path": "test/Arrays/varsindex.jl",
    "content": "using Test, MPI\n\nusing ClimateMachine\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.MPIStateArrays: getstateview\nusing ClimateMachine.VariableTemplates: @vars, varsindex, varsindices\nusing StaticArrays\n\nClimateMachine.init()\nconst ArrayType = ClimateMachine.array_type()\nconst mpicomm = MPI.COMM_WORLD\n\nconst V = @vars begin\n    a::Float32\n    b::SVector{3, Float32}\n    c::SMatrix{3, 8, Float32}\n    d::Float32\n    e::@vars begin\n        a::Float32\n        b::SVector{3, Float32}\n        d::Float32\n    end\nend\n\nconst VNT = @vars begin\n    a::Float32\n    e::Tuple{ntuple(3) do i\n        @vars(b::SVector{3, Float32})\n    end...}\nend\n\n@testset \"MPIStateArray varsindex\" begin\n    # check with invalid vars size\n    @test_throws ErrorException MPIStateArray{Float32, V}(\n        mpicomm,\n        ArrayType,\n        4,\n        1,\n        8,\n    )\n\n    Q = MPIStateArray{Float32, V}(mpicomm, ArrayType, 4, 34, 8)\n    @test Q.a === view(MPIStateArrays.realview(Q), :, 1:1, :)\n    @test Q.b === view(MPIStateArrays.realview(Q), :, 2:4, :)\n    @test Q.c === view(MPIStateArrays.realview(Q), :, 5:28, :)\n    @test Q.d === view(MPIStateArrays.realview(Q), :, 29:29, :)\n    @test Q.e === view(MPIStateArrays.realview(Q), :, 30:34, :)\n\n    @test getstateview(Q, \"a\") === view(MPIStateArrays.realview(Q), :, 1:1, :)\n    @test getstateview(Q, \"b\") === view(MPIStateArrays.realview(Q), :, 2:4, :)\n    @test getstateview(Q, \"c\") === view(MPIStateArrays.realview(Q), :, 5:28, :)\n    @test getstateview(Q, \"d\") === view(MPIStateArrays.realview(Q), :, 29:29, :)\n    @test getstateview(Q, \"e\") === view(MPIStateArrays.realview(Q), :, 30:34, :)\n    @test getstateview(Q, \"e.a\") ===\n          view(MPIStateArrays.realview(Q), :, 30:30, :)\n    @test getstateview(Q, \"e.b\") ===\n          view(MPIStateArrays.realview(Q), :, 31:33, :)\n    @test getstateview(Q, \"e.d\") ===\n          view(MPIStateArrays.realview(Q), :, 34:34, :)\n\n    @test getstateview(Q, :(a)) === view(MPIStateArrays.realview(Q), :, 1:1, :)\n    @test getstateview(Q, :(b)) === view(MPIStateArrays.realview(Q), :, 2:4, :)\n    @test getstateview(Q, :(c)) === view(MPIStateArrays.realview(Q), :, 5:28, :)\n    @test getstateview(Q, :(d)) ===\n          view(MPIStateArrays.realview(Q), :, 29:29, :)\n    @test getstateview(Q, :(e)) ===\n          view(MPIStateArrays.realview(Q), :, 30:34, :)\n    @test getstateview(Q, :(e.a)) ===\n          view(MPIStateArrays.realview(Q), :, 30:30, :)\n    @test getstateview(Q, :(e.b)) ===\n          view(MPIStateArrays.realview(Q), :, 31:33, :)\n    @test getstateview(Q, :(e.d)) ===\n          view(MPIStateArrays.realview(Q), :, 34:34, :)\n\n    @test_throws ErrorException Q.aa\n    @test_throws ErrorException getstateview(Q, \"aa\")\n\n    P = similar(Q)\n    @test P.a === view(MPIStateArrays.realview(P), :, 1:1, :)\n    @test P.b === view(MPIStateArrays.realview(P), :, 2:4, :)\n    @test P.c === view(MPIStateArrays.realview(P), :, 5:28, :)\n    @test P.d === view(MPIStateArrays.realview(P), :, 29:29, :)\n    @test P.e === view(MPIStateArrays.realview(P), :, 30:34, :)\n\n    @test getstateview(P, \"a\") === view(MPIStateArrays.realview(P), :, 1:1, :)\n    @test getstateview(P, \"b\") === view(MPIStateArrays.realview(P), :, 2:4, :)\n    @test getstateview(P, \"c\") === view(MPIStateArrays.realview(P), :, 5:28, :)\n    @test getstateview(P, \"d\") === view(MPIStateArrays.realview(P), :, 29:29, :)\n    @test getstateview(P, \"e\") === view(MPIStateArrays.realview(P), :, 30:34, :)\n    @test getstateview(P, \"e.a\") ===\n          view(MPIStateArrays.realview(P), :, 30:30, :)\n    @test getstateview(P, \"e.b\") ===\n          view(MPIStateArrays.realview(P), :, 31:33, :)\n    @test getstateview(P, \"e.d\") ===\n          view(MPIStateArrays.realview(P), :, 34:34, :)\n\n    @test getstateview(P, :(a)) === view(MPIStateArrays.realview(P), :, 1:1, :)\n    @test getstateview(P, :(b)) === view(MPIStateArrays.realview(P), :, 2:4, :)\n    @test getstateview(P, :(c)) === view(MPIStateArrays.realview(P), :, 5:28, :)\n    @test getstateview(P, :(d)) ===\n          view(MPIStateArrays.realview(P), :, 29:29, :)\n    @test getstateview(P, :(e)) ===\n          view(MPIStateArrays.realview(P), :, 30:34, :)\n    @test getstateview(P, :(e.a)) ===\n          view(MPIStateArrays.realview(P), :, 30:30, :)\n    @test getstateview(P, :(e.b)) ===\n          view(MPIStateArrays.realview(P), :, 31:33, :)\n    @test getstateview(P, :(e.d)) ===\n          view(MPIStateArrays.realview(P), :, 34:34, :)\n\n    A = MPIStateArray{Float32}(mpicomm, ArrayType, 4, 29, 8)\n    @test_throws ErrorException A.a\n    @test_throws ErrorException getstateview(A, \"a\")\nend\n\n@testset \"MPIStateArray show_not_finite_fields\" begin\n    post_msg = \"are not finite (has NaNs or Inf)\"\n    Q = MPIStateArray{Float32, V}(mpicomm, ArrayType, 4, 34, 8)\n    Q .= 1\n    Qv = view(MPIStateArrays.realview(Q), :, 1:1, :)\n    Qv .= NaN\n    msg = \"Field(s) (a) \" * post_msg\n    @test_logs (:warn, msg) show_not_finite_fields(Q)\n\n    Qv = view(MPIStateArrays.realview(Q), :, 2:2, :)\n    Qv .= NaN\n    msg = \"Field(s) (a, and b[1]) \" * post_msg\n    @test_logs (:warn, msg) show_not_finite_fields(Q)\n\n    Qv = view(MPIStateArrays.realview(Q), :, 31:31, :)\n    Qv .= NaN\n    msg = \"Field(s) (a, b[1], and e.b[1]) \" * post_msg\n    @test_logs (:warn, msg) show_not_finite_fields(Q)\n\n    Q .= 1\n    @test show_not_finite_fields(Q) == nothing\nend\n\n@testset \"MPIStateArray show_not_finite_fields - ntuple vars\" begin\n    post_msg = \"are not finite (has NaNs or Inf)\"\n    Q = MPIStateArray{Float32, VNT}(mpicomm, ArrayType, 4, 10, 8)\n    Q .= 1\n    Qv = view(MPIStateArrays.realview(Q), :, 1:1, :)\n    Qv .= NaN\n    msg = \"Field(s) (a) \" * post_msg\n    @test_logs (:warn, msg) show_not_finite_fields(Q)\n\n    Qv = view(MPIStateArrays.realview(Q), :, 2:2, :)\n    Qv .= NaN\n    msg = \"Field(s) (a, and e[1].b[1]) \" * post_msg\n    @test_logs (:warn, msg) show_not_finite_fields(Q)\n\n    Qv = view(MPIStateArrays.realview(Q), :, 6:6, :)\n    Qv .= NaN\n    msg = \"Field(s) (a, e[1].b[1], and e[2].b[2]) \" * post_msg\n    @test_logs (:warn, msg) show_not_finite_fields(Q)\nend\n"
  },
  {
    "path": "test/Atmos/EDMF/Artifacts.toml",
    "content": "[PyCLES_output]\ngit-tree-sha1 = \"61af161b398cb0daabeb2eb1d3c57c7ba3629514\"\n"
  },
  {
    "path": "test/Atmos/EDMF/bomex_edmf.jl",
    "content": "using ClimateMachine\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.Checkpoint\nusing ClimateMachine.DGMethods\nusing ClimateMachine.SystemSolvers\nimport ClimateMachine.DGMethods: custom_filter!\nusing ClimateMachine.Mesh.Filters: apply!\nusing ClimateMachine.BalanceLaws: vars_state\nusing JLD2, FileIO\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\n\ninclude(joinpath(clima_dir, \"experiments\", \"AtmosLES\", \"bomex_model.jl\"))\ninclude(joinpath(\"helper_funcs\", \"diagnostics_configuration.jl\"))\ninclude(\"edmf_model.jl\")\ninclude(\"edmf_kernels.jl\")\n\n\"\"\"\n    init_state_prognostic!(\n            turbconv::EDMF{FT},\n            m::AtmosModel{FT},\n            state::Vars,\n            aux::Vars,\n            localgeo,\n            t::Real,\n        ) where {FT}\n\nInitialize EDMF state variables.\nThis method is only called at `t=0`.\n\"\"\"\nfunction init_state_prognostic!(\n    turbconv::EDMF{FT},\n    m::AtmosModel{FT},\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t::Real,\n) where {FT}\n    # Aliases:\n    gm = state\n    en = state.turbconv.environment\n    up = state.turbconv.updraft\n    N_up = n_updrafts(turbconv)\n    # GCM setting - Initialize the grid mean profiles of prognostic variables (ρ,e_int,q_tot,u,v,w)\n    z = altitude(m, aux)\n\n    # SCM setting - need to have separate cases coded and called from a folder - see what LES does\n    # a moist_thermo state is used here to convert the input θ,q_tot to e_int, q_tot profile\n    e_int = internal_energy(m, state, aux)\n    param_set = parameter_set(m)\n    if moisture_model(m) isa DryModel\n        ρq_tot = FT(0)\n        ts = PhaseDry(param_set, e_int, state.ρ)\n    else\n        ρq_tot = gm.moisture.ρq_tot\n        ts = PhaseEquil_ρeq(param_set, state.ρ, e_int, ρq_tot / state.ρ)\n    end\n    T = air_temperature(ts)\n    p = air_pressure(ts)\n    q = PhasePartition(ts)\n    θ_liq = liquid_ice_pottemp(ts)\n\n    a_min = turbconv.subdomains.a_min\n    @unroll_map(N_up) do i\n        up[i].ρa = gm.ρ * a_min\n        up[i].ρaw = gm.ρu[3] * a_min\n        up[i].ρaθ_liq = gm.ρ * a_min * θ_liq\n        up[i].ρaq_tot = ρq_tot * a_min\n    end\n\n    # initialize environment covariance with zero for now\n    if z <= FT(2500)\n        en.ρatke = gm.ρ * (FT(1) - z / FT(3000))\n    else\n        en.ρatke = FT(0)\n    end\n    en.ρaθ_liq_cv = FT(1e-5) / max(z, FT(10))\n    en.ρaq_tot_cv = FT(1e-5) / max(z, FT(10))\n    en.ρaθ_liq_q_tot_cv = FT(1e-7) / max(z, FT(10))\n    return nothing\nend;\n\nstruct ZeroVerticalVelocityFilter <: AbstractCustomFilter end\nfunction custom_filter!(::ZeroVerticalVelocityFilter, bl, state, aux)\n    state.ρu = SVector(state.ρu[1], state.ρu[2], 0)\nend\n\nfunction main(::Type{FT}, cl_args) where {FT}\n\n    surface_flux = cl_args[\"surface_flux\"]\n\n    # DG polynomial order\n    N = 4\n    nelem_vert = 20\n\n    # Prescribe domain parameters\n    zmax = FT(3000)\n\n    t0 = FT(0)\n\n    # Simulation time\n    timeend = FT(400)\n    CFLmax = FT(1.2)\n\n    config_type = SingleStackConfigType\n\n    ode_solver_type = ClimateMachine.IMEXSolverType(\n        implicit_model = AtmosAcousticGravityLinearModel,\n        implicit_solver = SingleColumnLU,\n        solver_method = ARK2GiraldoKellyConstantinescu,\n        split_explicit_implicit = true,\n        discrete_splitting = false,\n    )\n\n    N_updrafts = 1\n    N_quad = 3\n    turbconv = EDMF(FT, N_updrafts, N_quad, param_set)\n\n    model =\n        bomex_model(FT, config_type, zmax, surface_flux; turbconv = turbconv)\n\n    # Assemble configuration\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"BOMEX_EDMF\",\n        N,\n        nelem_vert,\n        zmax,\n        param_set,\n        model;\n        hmax = zmax,\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFLmax,\n    )\n\n    # --- Zero-out horizontal variations:\n    vsp = vars_state(model, Prognostic(), FT)\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.Q,\n        varsindex(vsp, :turbconv),\n    )\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.Q,\n        varsindex(vsp, :energy, :ρe),\n    )\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.Q,\n        varsindex(vsp, :moisture, :ρq_tot),\n    )\n\n    vsa = vars_state(model, Auxiliary(), FT)\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.dg.state_auxiliary,\n        varsindex(vsa, :turbconv),\n    )\n    # ---\n\n    dgn_config =\n        config_diagnostics(driver_config, timeend; interval = \"50ssecs\")\n\n    cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            (\"moisture.ρq_tot\", turbconv_filters(turbconv)...),\n            solver_config.dg.grid,\n            TMARFilter(),\n        )\n        Filters.apply!(\n            ZeroVerticalVelocityFilter(),\n            solver_config.dg.grid,\n            solver_config.dg.balance_law,\n            solver_config.Q,\n            solver_config.dg.state_auxiliary,\n        )\n        nothing\n    end\n\n    diag_arr = [single_stack_diagnostics(solver_config)]\n    time_data = FT[0]\n\n    # Define the number of outputs from `t0` to `timeend`\n    n_outputs = 10\n    # This equates to exports every ceil(Int, timeend/n_outputs) time-step:\n    every_x_simulation_time = ceil(Int, timeend / n_outputs)\n\n    cb_data_vs_time =\n        GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n            diag_vs_z = single_stack_diagnostics(solver_config)\n\n            nstep = getsteps(solver_config.solver)\n            # Save to disc (for debugging):\n            # @save \"bomex_edmf_nstep=$nstep.jld2\" diag_vs_z\n\n            push!(diag_arr, diag_vs_z)\n            push!(time_data, gettime(solver_config.solver))\n            nothing\n        end\n\n    check_cons = (\n        ClimateMachine.ConservationCheck(\"ρ\", \"3000steps\", FT(0.001)),\n        ClimateMachine.ConservationCheck(\"energy.ρe\", \"3000steps\", FT(0.0025)),\n    )\n\n    cb_print_step = GenericCallbacks.EveryXSimulationSteps(100) do\n        @show getsteps(solver_config.solver)\n        nothing\n    end\n\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        check_cons = check_cons,\n        user_callbacks = (cbtmarfilter, cb_data_vs_time, cb_print_step),\n        check_euclidean_distance = true,\n    )\n\n    diag_vs_z = single_stack_diagnostics(solver_config)\n    push!(diag_arr, diag_vs_z)\n    push!(time_data, gettime(solver_config.solver))\n\n    return solver_config, diag_arr, time_data\nend\n\n\n# add a command line argument to specify the kind of surface flux\n# TODO: this will move to the future namelist functionality\nbomex_args = ArgParseSettings(autofix_names = true)\nadd_arg_group!(bomex_args, \"BOMEX\")\n@add_arg_table! bomex_args begin\n    \"--surface-flux\"\n    help = \"specify surface flux for energy and moisture\"\n    metavar = \"prescribed|bulk\"\n    arg_type = String\n    default = \"prescribed\"\nend\n\ncl_args = ClimateMachine.init(\n    parse_clargs = true,\n    custom_clargs = bomex_args,\n    output_dir = get(ENV, \"CLIMATEMACHINE_SETTINGS_OUTPUT_DIR\", \"output\"),\n    fix_rng_seed = true,\n)\n\nsolver_config, diag_arr, time_data = main(Float64, cl_args)\n\ninclude(joinpath(@__DIR__, \"report_mse_bomex.jl\"))\n\nnothing\n"
  },
  {
    "path": "test/Atmos/EDMF/closures/entr_detr.jl",
    "content": "#### Entrainment-Detrainment kernels\n\nfunction entr_detr(\n    bl::AtmosModel{FT},\n    state::Vars,\n    aux::Vars,\n    ts_up,\n    ts_en,\n    env,\n    buoy,\n) where {FT}\n    turbconv = turbconv_model(bl)\n    EΔ_up = vuntuple(n_updrafts(turbconv)) do i\n        entr_detr(bl, turbconv.entr_detr, state, aux, ts_up, ts_en, env, buoy, i)\n    end\n    E_dyn, Δ_dyn, E_trb = ntuple(i -> map(x -> x[i], EΔ_up), 3)\n    return E_dyn, Δ_dyn, E_trb\nend\n\n\"\"\"\n    entr_detr(\n        m::AtmosModel{FT},\n        entr::EntrainmentDetrainment,\n        state::Vars,\n        aux::Vars,\n        ts_up,\n        ts_en,\n        env,\n        buoy,\n        i,\n    ) where {FT}\n\nReturns the dynamic entrainment and detrainment rates,\nas well as the turbulent entrainment rate, following\nCohen et al. (JAMES, 2020), given:\n - `m`, an `AtmosModel`\n - `entr`, an `EntrainmentDetrainment` model\n - `state`, state variables\n - `aux`, auxiliary variables\n - `ts_up`, updraft thermodynamic states\n - `ts_en`, environment thermodynamic states\n - `env`, NamedTuple of environment variables\n - `buoy`, NamedTuple of environment and updraft buoyancies\n - `i`, index of the updraft\n\"\"\"\nfunction entr_detr(\n    m::AtmosModel{FT},\n    entr::EntrainmentDetrainment,\n    state::Vars,\n    aux::Vars,\n    ts_up,\n    ts_en,\n    env,\n    buoy,\n    i,\n) where {FT}\n\n    # Alias convention:\n    gm = state\n    en = state.turbconv.environment\n    up = state.turbconv.updraft\n    en_aux = aux.turbconv.environment\n    up_aux = aux.turbconv.updraft\n    turbconv = turbconv_model(m)\n    N_up = n_updrafts(turbconv)\n    ρ_inv = 1 / gm.ρ\n    a_up_i = up[i].ρa * ρ_inv\n    lim_E = entr.lim_ϵ\n    lim_amp = entr.lim_amp\n    w_min = entr.w_min\n    # precompute vars\n    w_up_i = fix_void_up(up[i].ρa, up[i].ρaw / up[i].ρa)\n    sqrt_tke = sqrt(max(en.ρatke, 0) * ρ_inv / env.a)\n    # ensure far from zero\n    Δw = filter_w(w_up_i - env.w, w_min)\n    w_up_i = filter_w(w_up_i, w_min)\n    Δb = buoy.up[i] - buoy.en\n    D_E, D_δ, M_δ, M_E = nondimensional_exchange_functions(\n        m,\n        entr,\n        state,\n        aux,\n        ts_up,\n        ts_en,\n        env,\n        buoy,\n        i,\n    )\n\n    # I am commenting this out for now, to make sure there is no slowdown here\n    Λ_w = abs(Δb / Δw)\n    Λ_tke = entr.c_λ * abs(Δb / (max(en.ρatke * ρ_inv, 0) + w_min))\n    λ = lamb_smooth_minimum(\n        SVector(Λ_w, Λ_tke),\n        turbconv.mix_len.smin_ub,\n        turbconv.mix_len.smin_rm,\n    )\n\n    # compute entrainment/detrainment components\n    # TO DO: Add updraft height dependency (non-local)\n    E_trb = 2 * up[i].ρa * entr.c_t * sqrt_tke / turbconv.pressure.H_up_min\n    E_dyn = up[i].ρa * λ * (D_E + M_E)\n    Δ_dyn = up[i].ρa * λ * (D_δ + M_δ)\n\n    E_dyn = max(E_dyn, FT(0))\n    Δ_dyn = max(Δ_dyn, FT(0))\n    E_trb = max(E_trb, FT(0))\n    return E_dyn, Δ_dyn, E_trb\nend;\n"
  },
  {
    "path": "test/Atmos/EDMF/closures/mixing_length.jl",
    "content": "#### Mixing length model kernels\n\"\"\"\n    mixing_length(\n        m::AtmosModel{FT},\n        ml::MixingLengthModel,\n        args,\n        Δ::Tuple,\n        Et::Tuple,\n        ts_gm,\n        ts_en,\n        env,\n    ) where {FT}\n\nReturns the mixing length used in the diffusive turbulence closure, given:\n - `m`, an `AtmosModel`\n - `ml`, a `MixingLengthModel`\n - `args`, the top-level arguments\n - `Δ`, the detrainment rate\n - `Et`, the turbulent entrainment rate\n - `ts_gm`, grid-mean thermodynamic states\n - `ts_en`, environment thermodynamic states\n - `env`, NamedTuple of environment variables\n\"\"\"\nfunction mixing_length(\n    m::AtmosModel{FT},\n    ml::MixingLengthModel,\n    args,\n    Δ::Tuple,\n    Et::Tuple,\n    Shear²,\n    ts_gm,\n    ts_en,\n    env,\n) where {FT}\n    @unpack state, aux, diffusive, t = args\n    # TODO: use functions: obukhov_length, ustar, ϕ_m\n    turbconv = turbconv_model(m)\n    # Alias convention:\n    gm = state\n    en = state.turbconv.environment\n    up = state.turbconv.updraft\n    gm_aux = aux\n    N_up = n_updrafts(turbconv)\n\n    z = altitude(m, aux)\n    param_set = parameter_set(m)\n    _grav::FT = grav(param_set)\n    ρinv = 1 / gm.ρ\n\n    tke_en = max(en.ρatke, 0) * ρinv / env.a\n\n    # buoyancy related functions\n    # compute obukhov_length and ustar from SurfaceFlux.jl here\n    ustar = turbconv.surface.ustar\n    obukhov_length = turbconv.surface.obukhov_length\n\n    ∂b∂z, Nˢ_eff = compute_buoyancy_gradients(m, args, ts_gm, ts_en)\n    Grad_Ri = ∇Richardson_number(∂b∂z, Shear², 1 / ml.max_length, ml.Ri_c)\n    Pr_t = turbulent_Prandtl_number(ml.Pr_n, Grad_Ri, ml.ω_pr)\n\n    # compute L1\n    Nˢ_fact = (sign(Nˢ_eff - eps(FT)) + 1) / 2\n    coeff = min(ml.c_b * sqrt(tke_en) / Nˢ_eff, ml.max_length)\n    L_Nˢ = coeff * Nˢ_fact + ml.max_length * (FT(1) - Nˢ_fact)\n\n    # compute L2 - law of the wall\n    # TODO: use zLL from altitude\n    surf_vals = subdomain_surface_values(m, gm, gm_aux, turbconv.surface.zLL)\n\n    L_W = ml.κ * max(z, 5) / (sqrt(turbconv.surface.κ_star²) * ml.c_m)\n    if obukhov_length < -eps(FT)\n        L_W *= min((FT(1) - ml.a2 * z / obukhov_length)^ml.a1, 1 / ml.κ)\n    end\n\n    # compute L3 - entrainment detrainment sources\n    # Production/destruction terms\n    a = ml.c_m * (Shear² - ∂b∂z / Pr_t) * sqrt(tke_en)\n    # Dissipation term\n    b = FT(0)\n    a_up = vuntuple(i -> up[i].ρa * ρinv, N_up)\n    w_up = vuntuple(N_up) do i\n        fix_void_up(up[i].ρa, up[i].ρaw / up[i].ρa)\n    end\n    b = sum(\n        ntuple(N_up) do i\n            Δ[i] / gm.ρ / env.a *\n            ((w_up[i] - env.w) * (w_up[i] - env.w) / 2 - tke_en) -\n            (w_up[i] - env.w) * Et[i] / gm.ρ * env.w / env.a\n        end,\n    )\n\n    c_neg = ml.c_d * tke_en * sqrt(tke_en)\n    if abs(a) > ml.random_minval && 4 * a * c_neg > -b^2\n        l_entdet =\n            max(-b / FT(2) / a + sqrt(b^2 + 4 * a * c_neg) / 2 / a, FT(0))\n    elseif abs(a) < eps(FT) && abs(b) > eps(FT)\n        l_entdet = c_neg / b\n    else\n        l_entdet = FT(0)\n    end\n    L_tke = l_entdet\n\n    if L_Nˢ < eps(FT) || L_Nˢ > ml.max_length\n        L_Nˢ = ml.max_length\n    end\n    if L_W < eps(FT) || L_W > ml.max_length\n        L_W = ml.max_length\n    end\n    if L_tke < eps(FT) || L_tke > ml.max_length\n        L_tke = ml.max_length\n    end\n\n    l_mix =\n        lamb_smooth_minimum(SVector(L_Nˢ, L_W, L_tke), ml.smin_ub, ml.smin_rm)\n    return l_mix, ∂b∂z, Pr_t\nend;\n"
  },
  {
    "path": "test/Atmos/EDMF/closures/pressure.jl",
    "content": "#### Pressure model kernels\n\nfunction perturbation_pressure(bl::AtmosModel{FT}, args, env, buoy) where {FT}\n    dpdz = vuntuple(n_updrafts(turbconv_model(bl))) do i\n        perturbation_pressure(bl, turbconv_model(bl).pressure, args, env, buoy, i)\n    end\n    return dpdz\nend\n\n\"\"\"\n    perturbation_pressure(\n        m::AtmosModel{FT},\n        press::PressureModel,\n        args,\n        env,\n        buoy,\n        i,\n    ) where {FT}\n\nReturns the value of perturbation pressure gradient\nfor updraft i following He et al. (JAMES, 2020), given:\n\n - `m`, an `AtmosModel`\n - `press`, a `PressureModel`\n - `args`, top-level arguments\n - `env`, NamedTuple of environment variables\n - `buoy`, NamedTuple of environment and updraft buoyancies\n - `i`, index of the updraft\n\"\"\"\nfunction perturbation_pressure(\n    m::AtmosModel{FT},\n    press::PressureModel,\n    args,\n    env,\n    buoy,\n    i,\n) where {FT}\n    @unpack state, diffusive, aux = args\n    # Alias convention:\n    up = state.turbconv.updraft\n    up_aux = aux.turbconv.updraft\n    up_dif = diffusive.turbconv.updraft\n\n    w_up_i = fix_void_up(up[i].ρa, up[i].ρaw / up[i].ρa)\n\n    nh_press_buoy = press.α_b * buoy.up[i]\n    nh_pressure_adv = -press.α_a * w_up_i * up_dif[i].∇w[3]\n    # TO DO: Add updraft height dependency (non-local)\n    nh_pressure_drag =\n        press.α_d * (w_up_i - env.w) * abs(w_up_i - env.w) / press.H_up_min\n\n    dpdz = nh_press_buoy + nh_pressure_adv + nh_pressure_drag\n\n    return dpdz\nend;\n"
  },
  {
    "path": "test/Atmos/EDMF/closures/surface_functions.jl",
    "content": "#### Surface model kernels\n\nusing Statistics\n\n\"\"\"\n    subdomain_surface_values(\n        atmos::AtmosModel{FT},\n        state::Vars,\n        aux::Vars,\n        zLL::FT,\n    ) where {FT}\n\nReturns the surface values of updraft area fraction, updraft\nliquid water potential temperature (`θ_liq`), updraft total\nwater specific humidity (`q_tot`), environmental variances of\n`θ_liq` and `q_tot`, environmental covariance of `θ_liq` with\n`q_tot`, and environmental TKE, given:\n\n - `atmos`, an `AtmosModel`\n - `state`, state variables\n - `aux`, auxiliary variables\n - `zLL`, height of the lowest nodal level\n\"\"\"\nfunction subdomain_surface_values(\n    atmos::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    zLL,\n)\n    turbconv = turbconv_model(atmos)\n    subdomain_surface_values(turbconv.surface, turbconv, atmos, state, aux, zLL)\nend\n\nfunction subdomain_surface_values(\n    surf::SurfaceModel,\n    turbconv::EDMF{FT},\n    atmos::AtmosModel{FT},\n    state::Vars,\n    aux::Vars,\n    zLL::FT,\n) where {FT}\n\n    N_up = n_updrafts(turbconv)\n    gm = state\n    # TODO: change to new_thermo_state\n    ts = recover_thermo_state(atmos, state, aux)\n    q = PhasePartition(ts)\n    _cp_m = cp_m(ts)\n    lv = latent_heat_vapor(ts)\n    Π = exner(ts)\n    ρ_inv = 1 / gm.ρ\n    upd_surface_std = turbconv.surface.upd_surface_std\n\n    θ_liq_surface_flux = surf.shf / Π / _cp_m\n    q_tot_surface_flux = surf.lhf / lv\n    # these value should be given from the SurfaceFluxes.jl once it is merged\n    oblength = turbconv.surface.obukhov_length\n    ustar = turbconv.surface.ustar\n\n    unstable = oblength < -eps(FT)\n    fact = unstable ? (1 - surf.ψϕ_stab * zLL / oblength)^(-FT(2 // 3)) : 1\n    tke_fact = unstable ? cbrt(zLL / oblength * zLL / oblength) : 0\n    ustar² = ustar^2\n    θ_liq_cv = 4 * (θ_liq_surface_flux * θ_liq_surface_flux) / (ustar²) * fact\n    q_tot_cv = 4 * (q_tot_surface_flux * q_tot_surface_flux) / (ustar²) * fact\n    θ_liq_q_tot_cv =\n        4 * (θ_liq_surface_flux * q_tot_surface_flux) / (ustar²) * fact\n    tke = ustar² * (surf.κ_star² + tke_fact)\n\n    a_up_surf = ntuple(i -> FT(surf.a / N_up), N_up)\n    e_int = internal_energy(atmos, state, aux)\n    ts_new = new_thermo_state(atmos, state, aux)\n    θ_liq = liquid_ice_pottemp(ts_new)\n\n    θ_liq_up_surf = ntuple(N_up) do i\n        θ_liq + upd_surface_std[i] * sqrt(max(θ_liq_cv, 0))\n    end\n\n    ρq_tot = moisture_model(atmos) isa DryModel ? FT(0) : gm.moisture.ρq_tot\n    q_tot_up_surf = ntuple(N_up) do i\n        ρq_tot * ρ_inv + upd_surface_std[i] * sqrt(max(q_tot_cv, 0))\n    end\n\n    return (;\n        a_up_surf,\n        θ_liq_up_surf,\n        q_tot_up_surf,\n        θ_liq_cv,\n        q_tot_cv,\n        θ_liq_q_tot_cv,\n        tke,\n    )\nend;\n\nfunction subdomain_surface_values(\n    surf::NeutralDrySurfaceModel,\n    turbconv::EDMF{FT},\n    atmos::AtmosModel{FT},\n    state::Vars,\n    aux::Vars,\n    zLL::FT,\n) where {FT}\n\n    N_up = n_updrafts(turbconv)\n    θ_liq_cv = FT(0)\n    q_tot_cv = FT(0)\n    θ_liq_q_tot_cv = FT(0)\n    tke = surf.κ_star² * surf.ustar * surf.ustar\n\n    ts_new = new_thermo_state(atmos, state, aux)\n    a_up_surf = ntuple(i -> FT(surf.a / N_up), N_up)\n    q_tot_up_surf = ntuple(i -> FT(0), N_up)\n    θ_liq_up_surf = ntuple(i -> liquid_ice_pottemp(ts_new), N_up)\n\n    return (;\n        a_up_surf,\n        θ_liq_up_surf,\n        q_tot_up_surf,\n        θ_liq_cv,\n        q_tot_cv,\n        θ_liq_q_tot_cv,\n        tke,\n    )\nend;\n\n\"\"\"\n    percentile_bounds_mean_norm(\n        low_percentile::FT,\n        high_percentile::FT,\n        n_samples::Int,\n    ) where {FT <: AbstractFloat}\n\nReturns the mean of all instances of a standard Gaussian random\nvariable that have a CDF higher than low_percentile and lower\nthan high_percentile, given a total of n_samples of the standard\nGaussian, given:\n - `low_percentile`, lower limit of the CDF\n - `high_percentile`, higher limit of the CDF\n - `n_samples`, the total number of samples drawn from the Gaussian\n\"\"\"\nfunction percentile_bounds_mean_norm(\n    low_percentile::FT,\n    high_percentile::FT,\n    n_samples::Int,\n) where {FT <: AbstractFloat}\n    x = rand(Normal(), n_samples)\n    xp_low = quantile(Normal(), low_percentile)\n    xp_high = quantile(Normal(), high_percentile)\n    filter!(y -> xp_low < y < xp_high, x)\n    return Statistics.mean(x)\nend\n"
  },
  {
    "path": "test/Atmos/EDMF/closures/turbulence_functions.jl",
    "content": "#### Turbulence model kernels\n\n\"\"\"\n    thermo_variables(ts::ThermodynamicState)\n\nA NamedTuple of thermodynamic variables,\ncomputed from the given thermodynamic state.\n\"\"\"\nfunction thermo_variables(ts::ThermodynamicState)\n    return (\n        θ_dry = dry_pottemp(ts),\n        θ_liq = liquid_ice_pottemp(ts),\n        q_tot = total_specific_humidity(ts),\n        T = air_temperature(ts),\n        R_m = gas_constant_air(ts),\n        q_vap = vapor_specific_humidity(ts),\n        q_liq = liquid_specific_humidity(ts),\n        q_ice = ice_specific_humidity(ts),\n    )\nend\n\n\"\"\"\n    compute_buoyancy_gradients(\n        m::AtmosModel{FT},\n        args,\n        ts_gm,\n        ts_en\n    ) where {FT}\nReturns the environmental buoyancy gradient following Tan et al. (JAMES, 2018)\nand the effective environmental static stability following\nLopez-Gomez et al. (JAMES, 2020), given:\n - `m`, an `AtmosModel`\n - `args`, top-level arguments\n - `ts_gm`, grid-mean thermodynamic state\n - `ts_en`, environment thermodynamic state\n\"\"\"\nfunction compute_buoyancy_gradients(\n    m::AtmosModel{FT},\n    args,\n    ts_gm,\n    ts_en,\n) where {FT}\n    @unpack state, aux, diffusive = args\n    # Alias convention:\n    gm = state\n    en_dif = diffusive.turbconv.environment\n    N_up = n_updrafts(turbconv_model(m))\n    param_set = parameter_set(m)\n\n    _grav::FT = grav(param_set)\n    _R_d::FT = R_d(param_set)\n    _R_v::FT = R_v(param_set)\n    ε_v::FT = 1 / molmass_ratio(param_set)\n    p = air_pressure(ts_gm)\n\n    q_tot_en = total_specific_humidity(ts_en)\n    θ_liq_en = liquid_ice_pottemp(ts_en)\n    lv = latent_heat_vapor(ts_en)\n    T = air_temperature(ts_en)\n    Π = exner(ts_en)\n    q_liq = liquid_specific_humidity(ts_en)\n    _cp_m = cp_m(ts_en)\n    θ_virt = virtual_pottemp(ts_en)\n\n    (ts_dry, ts_cloudy, cloud_frac) =\n        compute_subdomain_statistics(m, args, ts_gm, ts_en)\n    cloudy = thermo_variables(ts_cloudy)\n    dry = thermo_variables(ts_dry)\n\n    prefactor = _grav * (_R_d * gm.ρ / p * Π)\n\n    ∂b∂θl_dry = prefactor * (1 + (ε_v - 1) * dry.q_tot)\n    ∂b∂qt_dry = prefactor * dry.θ_liq * (ε_v - 1)\n\n    if cloud_frac > FT(0)\n        num =\n            prefactor *\n            (1 + ε_v * (1 + lv / _R_v / cloudy.T) * cloudy.q_vap - cloudy.q_tot)\n        den = 1 + lv * lv / _cp_m / _R_v / cloudy.T / cloudy.T * cloudy.q_vap\n        ∂b∂θl_cloudy = num / den\n        ∂b∂qt_cloudy =\n            (lv / _cp_m / cloudy.T * ∂b∂θl_cloudy - prefactor) * cloudy.θ_dry\n    else\n        ∂b∂θl_cloudy = FT(0)\n        ∂b∂qt_cloudy = FT(0)\n    end\n\n    ∂b∂θl = (cloud_frac * ∂b∂θl_cloudy + (1 - cloud_frac) * ∂b∂θl_dry)\n    ∂b∂qt = (cloud_frac * ∂b∂qt_cloudy + (1 - cloud_frac) * ∂b∂qt_dry)\n\n    # Partial buoyancy gradients\n    ∂b∂z_θl = en_dif.∇θ_liq[3] * ∂b∂θl\n    ∂b∂z_qt = en_dif.∇q_tot[3] * ∂b∂qt\n    ∂b∂z = ∂b∂z_θl + ∂b∂z_qt\n\n    # Computation of buoyancy frequency based on θ_lv\n    ∂θvl∂θ_liq = 1 + (ε_v - 1) * q_tot_en\n    ∂θvl∂qt = (ε_v - 1) * θ_liq_en\n    # apply chain-rule\n    ∂θvl∂z = ∂θvl∂θ_liq * en_dif.∇θ_liq[3] + ∂θvl∂qt * en_dif.∇q_tot[3]\n\n    ∂θv∂θvl = exp(lv * q_liq / _cp_m / T)\n    λ_stb = cloud_frac\n\n    Nˢ_eff =\n        _grav / θ_virt *\n        ((1 - λ_stb) * en_dif.∇θv[3] + λ_stb * ∂θvl∂z * ∂θv∂θvl)\n    return ∂b∂z, Nˢ_eff\nend;\n\n\"\"\"\n    ∇Richardson_number(\n        ∂b∂z::FT,\n        Shear²::FT,\n        minval::FT,\n        Ri_c::FT,\n    ) where {FT}\n\nReturns the gradient Richardson number, given:\n - `∂b∂z`, the vertical buoyancy gradient\n - `Shear²`, the squared vertical gradient of horizontal velocity\n - `maxval`, maximum value of the output, typically the critical Ri number\n\"\"\"\nfunction ∇Richardson_number(\n    ∂b∂z::FT,\n    Shear²::FT,\n    minval::FT,\n    Ri_c::FT,\n) where {FT}\n    return min(∂b∂z / max(Shear², minval), Ri_c)\nend;\n\n\"\"\"\n    turbulent_Prandtl_number(\n        Pr_n::FT,\n        Grad_Ri::FT,\n        ω_pr::FT\n    ) where {FT}\n\nReturns the turbulent Prandtl number, given:\n - `Pr_n`, the turbulent Prandtl number under neutral conditions\n - `Grad_Ri`, the gradient Richardson number\n\"\"\"\nfunction turbulent_Prandtl_number(Pr_n::FT, Grad_Ri::FT, ω_pr::FT) where {FT}\n    if Grad_Ri > FT(0)\n        factor =\n            2 * Grad_Ri /\n            (1 + ω_pr * Grad_Ri - sqrt((1 + ω_pr * Grad_Ri)^2 - 4 * Grad_Ri))\n    else\n        factor = FT(1)\n    end\n    return Pr_n * factor\nend;\n"
  },
  {
    "path": "test/Atmos/EDMF/compute_mse.jl",
    "content": "using ClimateMachine\n\nif parse(Bool, get(ENV, \"CLIMATEMACHINE_PLOT_EDMF_COMPARISON\", \"false\"))\n    using Plots\nend\n\nusing OrderedCollections\nusing Test\nusing NCDatasets\nusing Dierckx\nusing PrettyTables\nusing Printf\nusing ArtifactWrappers\n\n# Get PyCLES_output dataset folder:\n#! format: off\nPyCLES_output_dataset = ArtifactWrapper(\n    @__DIR__,\n    isempty(get(ENV, \"CI\", \"\")),\n    \"PyCLES_output\",\n    ArtifactFile[\n    # ArtifactFile(url = \"https://caltech.box.com/shared/static/johlutwhohvr66wn38cdo7a6rluvz708.nc\", filename = \"Rico.nc\",),\n    ArtifactFile(url = \"https://caltech.box.com/shared/static/zraeiftuzlgmykzhppqwrym2upqsiwyb.nc\", filename = \"Gabls.nc\",),\n    # ArtifactFile(url = \"https://caltech.box.com/shared/static/toyvhbwmow3nz5bfa145m5fmcb2qbfuz.nc\", filename = \"DYCOMS_RF01.nc\",),\n    # ArtifactFile(url = \"https://caltech.box.com/shared/static/ivo4751camlph6u3k68ftmb1dl4z7uox.nc\", filename = \"TRMM_LBA.nc\",),\n    # ArtifactFile(url = \"https://caltech.box.com/shared/static/4osqp0jpt4cny8fq2ukimgfnyi787vsy.nc\", filename = \"ARM_SGP.nc\",),\n    ArtifactFile(url = \"https://caltech.box.com/shared/static/jci8l11qetlioab4cxf5myr1r492prk6.nc\", filename = \"Bomex.nc\",),\n    # ArtifactFile(url = \"https://caltech.box.com/shared/static/pzuu6ii99by2s356ij69v5cb615200jq.nc\", filename = \"Soares.nc\",),\n    # ArtifactFile(url = \"https://caltech.box.com/shared/static/7upt639siyc2umon8gs6qsjiqavof5cq.nc\", filename = \"Nieuwstadt.nc\",),\n    ],\n)\nPyCLES_output_dataset_path = get_data_folder(PyCLES_output_dataset)\n#! format: on\n\ninclude(\"variable_map.jl\")\n\nfunction compute_mse(\n    grid,\n    bl,\n    time_cm,\n    dons_arr,\n    ds,\n    experiment,\n    best_mse,\n    t_compare,\n    plot_dir = nothing,\n)\n    mse = Dict()\n\n    # Ensure domain matches:\n    z_les = ds[\"z_half\"][:]\n    z_cm = get_z(grid; rm_dupes = true)\n    @info \"Z extent for LES vs CLIMA:\"\n    @show extrema(z_cm)\n    @show extrema(z_les)\n\n    time_les = ds[\"t\"][:]\n\n    # Find the nearest matching final time:\n    t_cmp = min(time_cm[end], time_les[end])\n\n    # Accidentally running a short simulation\n    # could improve MSE. So, let's test that\n    # we run for at least t_compare. We should\n    # increase this as we can reach higher CFL.\n    @test t_cmp >= t_compare\n\n    # Ensure z_cm and dons_arr fields are consistent lengths:\n    @test length(z_cm) == length(dons_arr[1][first(keys(dons_arr[1]))])\n\n    data_cm = Dict()\n    dons_cont = Dict()\n    cm_variables = []\n    computed_mse = []\n    table_best_mse = []\n    mse_reductions = []\n    pycles_variables = []\n    data_scales = []\n    pycles_weight = []\n    for (ftc) in keys(best_mse)\n        # Only compare fields defined for var_map\n        tup = var_map(ftc)\n        tup == nothing && continue\n\n        # Unpack the data\n        LES_var = tup[1]\n        facts = tup[2]\n        push!(cm_variables, ftc)\n        push!(pycles_variables, LES_var)\n        data_ds = ds.group[\"profiles\"]\n        data_les = data_ds[LES_var][:]\n\n        # Scale the data for comparison\n        ρ = data_ds[\"rho\"][:]\n        a_up = data_ds[\"updraft_fraction\"][:]\n        a_en = 1 .- data_ds[\"updraft_fraction\"][:]\n        ρa_up = ρ .* a_up\n        ρa_en = ρ .* a_en\n        ρa = occursin(\"updraft\", ftc) in ftc ? ρa_up : ρa_en\n        if :a in facts && :ρ in facts\n            data_les .*= ρa\n            push!(pycles_weight, \"ρa\")\n        elseif :ρ in facts\n            data_les .*= ρ\n            push!(pycles_weight, \"ρ\")\n        else\n            push!(pycles_weight, \"1\")\n        end\n\n        # Interpolate data\n        steady_data = length(size(data_les)) == 1\n        if steady_data\n            data_les_cont = Spline1D(z_les, data_les)\n        else # unsteady data\n            data_les_cont = Spline2D(time_les, z_les, data_les')\n        end\n        data_cm_arr_ = [\n            dons_arr[i][ftc][i_z]\n            for i in 1:length(time_cm), i_z in 1:length(z_cm)\n        ]\n        data_cm_arr = reshape(data_cm_arr_, (length(time_cm), length(z_cm)))\n        data_cm[ftc] = Spline2D(time_cm, z_cm, data_cm_arr)\n\n        # Compute data scale\n        data_scale = sum(abs.(data_les)) / length(data_les)\n        push!(data_scales, data_scale)\n\n        # Plot comparison\n        if plot_dir ≠ nothing\n            p = plot()\n            if steady_data\n                data_les_cont_mapped = map(z -> data_les_cont(z), z_cm)\n            else\n                data_les_cont_mapped = map(z -> data_les_cont(t_cmp, z), z_cm)\n            end\n            plot!(\n                data_les_cont_mapped,\n                z_cm ./ 10^3,\n                xlabel = ftc,\n                ylabel = \"z [km]\",\n                label = \"PyCLES\",\n            )\n            plot!(\n                map(z -> data_cm[ftc](t_cmp, z), z_cm),\n                z_cm ./ 10^3,\n                xlabel = ftc,\n                ylabel = \"z [km]\",\n                label = \"CM\",\n            )\n            mkpath(plot_dir)\n            ftc_name = replace(ftc, \".\" => \"_\")\n            savefig(joinpath(plot_dir, \"$ftc_name.png\"))\n        end\n\n        # Compute mean squared error (mse)\n        if steady_data\n            mse_single_var = sum(map(z_cm) do z\n                (data_les_cont(z) - data_cm[ftc](t_cmp, z))^2\n            end)\n        else\n            mse_single_var = sum(map(z_cm) do z\n                (data_les_cont(t_cmp, z) - data_cm[ftc](t_cmp, z))^2\n            end)\n        end\n        # Normalize by data scale\n        mse[ftc] = mse_single_var / data_scale^2\n\n        push!(mse_reductions, (best_mse[ftc] - mse[ftc]) / best_mse[ftc] * 100)\n        push!(computed_mse, mse[ftc])\n        push!(table_best_mse, best_mse[ftc])\n    end\n\n    # Tabulate output\n    header = [\n        \"Variable\" \"Variable\" \"Weight\" \"Data scale\" \"MSE\" \"MSE\" \"MSE\"\n        \"ClimateMachine (EDMF)\" \"PyCLES\" \"PyCLES\" \"\" \"Computed\" \"Best\" \"Reduction (%)\"\n    ]\n    table_data = hcat(\n        cm_variables,\n        pycles_variables,\n        pycles_weight,\n        data_scales,\n        computed_mse,\n        table_best_mse,\n        mse_reductions,\n    )\n\n    @info @sprintf(\n        \"Experiment comparison: %s at time t=%s\\n\",\n        experiment,\n        t_cmp\n    )\n    hl_worsened_mse = Highlighter(\n        (data, i, j) -> !sufficient_mse(data[i, 5], data[i, 6]) && j == 5,\n        crayon\"red bold\",\n    )\n    hl_worsened_mse_reduction = Highlighter(\n        (data, i, j) -> !sufficient_mse(data[i, 5], data[i, 6]) && j == 7,\n        crayon\"red bold\",\n    )\n    hl_improved_mse = Highlighter(\n        (data, i, j) -> sufficient_mse(data[i, 5], data[i, 6]) && j == 7,\n        crayon\"green bold\",\n    )\n    pretty_table(\n        table_data,\n        header,\n        formatters = ft_printf(\"%.16e\", 5:6),\n        header_crayon = crayon\"yellow bold\",\n        subheader_crayon = crayon\"green bold\",\n        highlighters = (\n            hl_worsened_mse,\n            hl_improved_mse,\n            hl_worsened_mse_reduction,\n        ),\n        crop = :none,\n    )\n\n    return mse\nend\n\nsufficient_mse(computed_mse, best_mse) = computed_mse <= best_mse + sqrt(eps())\n\nfunction test_mse(computed_mse, best_mse, key)\n    mse_not_regressed = sufficient_mse(computed_mse[key], best_mse[key])\n    @test mse_not_regressed\n    mse_not_regressed || @show key\nend\n\nfunction dons(diag_vs_z)\n    return Dict(map(keys(first(diag_vs_z))) do k\n        string(k) => [getproperty(ca, k) for ca in diag_vs_z]\n    end)\nend\n\nget_dons_arr(diag_arr) = [dons(diag_vs_z) for diag_vs_z in diag_arr]\n\ndons_arr = get_dons_arr(diag_arr)\n"
  },
  {
    "path": "test/Atmos/EDMF/edmf_kernels.jl",
    "content": "#### EDMF model kernels\n\nusing CLIMAParameters.Planet: e_int_v0, grav, day, R_d, R_v, molmass_ratio\nusing Printf\nusing ClimateMachine.Atmos: nodal_update_auxiliary_state!, Advect\n\nusing ClimateMachine.BalanceLaws\n\nusing ClimateMachine.MPIStateArrays: MPIStateArray\nusing ClimateMachine.DGMethods: LocalGeometry, DGModel\n\nimport ClimateMachine.Mesh.Filters: vars_state_filtered\n\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    prognostic_vars,\n    prognostic_to_primitive!,\n    primitive_to_prognostic!,\n    get_prog_state,\n    get_specific_state,\n    flux,\n    precompute,\n    source,\n    eq_tends,\n    update_auxiliary_state!,\n    init_state_prognostic!,\n    compute_gradient_argument!,\n    compute_gradient_flux!\n\nimport ClimateMachine.TurbulenceConvection:\n    init_aux_turbconv!,\n    turbconv_nodal_update_auxiliary_state!,\n    turbconv_boundary_state!,\n    turbconv_normal_boundary_flux_second_order!\n\nusing Thermodynamics: air_pressure, air_density\n\n\ninclude(joinpath(\"helper_funcs\", \"nondimensional_exchange_functions.jl\"))\ninclude(joinpath(\"helper_funcs\", \"lamb_smooth_minimum.jl\"))\ninclude(joinpath(\"helper_funcs\", \"utility_funcs.jl\"))\ninclude(joinpath(\"helper_funcs\", \"subdomain_statistics.jl\"))\ninclude(joinpath(\"helper_funcs\", \"diagnose_environment.jl\"))\ninclude(joinpath(\"helper_funcs\", \"subdomain_thermo_states.jl\"))\ninclude(joinpath(\"helper_funcs\", \"save_subdomain_temperature.jl\"))\ninclude(joinpath(\"closures\", \"entr_detr.jl\"))\ninclude(joinpath(\"closures\", \"pressure.jl\"))\ninclude(joinpath(\"closures\", \"mixing_length.jl\"))\ninclude(joinpath(\"closures\", \"turbulence_functions.jl\"))\ninclude(joinpath(\"closures\", \"surface_functions.jl\"))\n\n\nfunction vars_state(m::NTuple{N, Updraft}, st::Auxiliary, FT) where {N}\n    return Tuple{ntuple(i -> vars_state(m[i], st, FT), N)...}\nend\n\nvars_state(::Updraft, ::Auxiliary, FT) = @vars(T::FT)\nvars_state(::Environment, ::Auxiliary, FT) = @vars(T::FT)\n\nfunction vars_state(m::EDMF, st::Auxiliary, FT)\n    @vars(\n        environment::vars_state(m.environment, st, FT),\n        updraft::vars_state(m.updraft, st, FT)\n    )\nend\n\nfunction vars_state(::Updraft, ::Prognostic, FT)\n    @vars(ρa::FT, ρaw::FT, ρaθ_liq::FT, ρaq_tot::FT,)\nend\n\nfunction vars_state(::Environment, ::Prognostic, FT)\n    @vars(ρatke::FT, ρaθ_liq_cv::FT, ρaq_tot_cv::FT, ρaθ_liq_q_tot_cv::FT,)\nend\n\nfunction vars_state(::Updraft, ::Primitive, FT)\n    @vars(a::FT, aw::FT, aθ_liq::FT, aq_tot::FT,)\nend\n\nfunction vars_state(::Environment, ::Primitive, FT)\n    @vars(atke::FT, aθ_liq_cv::FT, aq_tot_cv::FT, aθ_liq_q_tot_cv::FT,)\nend\n\nfunction vars_state(\n    m::NTuple{N, Updraft},\n    st::Union{Prognostic, Primitive},\n    FT,\n) where {N}\n    return Tuple{ntuple(i -> vars_state(m[i], st, FT), N)...}\nend\n\nfunction vars_state(m::EDMF, st::Union{Prognostic, Primitive}, FT)\n    @vars(\n        environment::vars_state(m.environment, st, FT),\n        updraft::vars_state(m.updraft, st, FT)\n    )\nend\n\nfunction vars_state(::Updraft, ::Gradient, FT)\n    @vars(w::FT,)\nend\n\nfunction vars_state(::Environment, ::Gradient, FT)\n    @vars(\n        θ_liq::FT,\n        q_tot::FT,\n        w::FT,\n        tke::FT,\n        θ_liq_cv::FT,\n        q_tot_cv::FT,\n        θ_liq_q_tot_cv::FT,\n        θv::FT,\n        h_tot::FT,\n    )\nend\n\nfunction vars_state(m::NTuple{N, Updraft}, st::Gradient, FT) where {N}\n    return Tuple{ntuple(i -> vars_state(m[i], st, FT), N)...}\nend\n\nfunction vars_state(m::EDMF, st::Gradient, FT)\n    @vars(\n        environment::vars_state(m.environment, st, FT),\n        updraft::vars_state(m.updraft, st, FT),\n        u::FT,\n        v::FT\n    )\nend\n\nfunction vars_state(m::NTuple{N, Updraft}, st::GradientFlux, FT) where {N}\n    return Tuple{ntuple(i -> vars_state(m[i], st, FT), N)...}\nend\n\nfunction vars_state(::Updraft, st::GradientFlux, FT)\n    @vars(∇w::SVector{3, FT},)\nend\n\nfunction vars_state(::Environment, ::GradientFlux, FT)\n    @vars(\n        ∇θ_liq::SVector{3, FT},\n        ∇q_tot::SVector{3, FT},\n        ∇w::SVector{3, FT},\n        ∇tke::SVector{3, FT},\n        ∇θ_liq_cv::SVector{3, FT},\n        ∇q_tot_cv::SVector{3, FT},\n        ∇θ_liq_q_tot_cv::SVector{3, FT},\n        ∇θv::SVector{3, FT},\n        ∇h_tot::SVector{3, FT},\n    )\n\nend\n\nfunction vars_state(m::EDMF, st::GradientFlux, FT)\n    @vars(\n        environment::vars_state(m.environment, st, FT),\n        updraft::vars_state(m.updraft, st, FT),\n        ∇u::SVector{3, FT},\n        ∇v::SVector{3, FT}\n    )\nend\n\nfunction vars_state_filtered(::Updraft, FT)\n    @vars(a::FT, aw::FT, aθ_liq::FT, aq_tot::FT,)\nend\n\nfunction vars_state_filtered(m::NTuple{N, Updraft}, FT) where {N}\n    return Tuple{ntuple(i -> vars_state_filtered(m[i], FT), N)...}\nend\n\nfunction vars_state_filtered(::Environment, FT)\n    @vars(atke::FT, aθ_liq_cv::FT, aq_tot_cv::FT, aθ_liq_q_tot_cv::FT,)\nend\n\nfunction vars_state_filtered(m::EDMF, FT)\n    @vars(\n        environment::vars_state_filtered(m.environment, FT),\n        updraft::vars_state_filtered(m.updraft, FT)\n    )\nend\n\nabstract type EDMFPrognosticVariable <: AbstractPrognosticVariable end\n\nabstract type EnvironmentPrognosticVariable <: EDMFPrognosticVariable end\nstruct en_ρatke <: EnvironmentPrognosticVariable end\nstruct en_ρaθ_liq_cv <: EnvironmentPrognosticVariable end\nstruct en_ρaq_tot_cv <: EnvironmentPrognosticVariable end\nstruct en_ρaθ_liq_q_tot_cv <: EnvironmentPrognosticVariable end\n\nabstract type UpdraftPrognosticVariable{i} <: EDMFPrognosticVariable end\nstruct up_ρa{i} <: UpdraftPrognosticVariable{i} end\nstruct up_ρaw{i} <: UpdraftPrognosticVariable{i} end\nstruct up_ρaθ_liq{i} <: UpdraftPrognosticVariable{i} end\nstruct up_ρaq_tot{i} <: UpdraftPrognosticVariable{i} end\n\nprognostic_vars(m::EDMF) =\n    (prognostic_vars(m.environment)..., prognostic_vars(m.updraft)...)\nprognostic_vars(m::Environment) =\n    (en_ρatke(), en_ρaθ_liq_cv(), en_ρaq_tot_cv(), en_ρaθ_liq_q_tot_cv())\n\nfunction prognostic_vars(m::NTuple{N, Updraft}) where {N}\n    t_ρa = vuntuple(i -> up_ρa{i}(), N)\n    t_ρaw = vuntuple(i -> up_ρaw{i}(), N)\n    t_ρaθ_liq = vuntuple(i -> up_ρaθ_liq{i}(), N)\n    t_ρaq_tot = vuntuple(i -> up_ρaq_tot{i}(), N)\n    t = (t_ρa..., t_ρaw..., t_ρaθ_liq..., t_ρaq_tot...)\n    return t\nend\n\nget_prog_state(state, ::en_ρatke) = (state.turbconv.environment, :ρatke)\nget_prog_state(state, ::en_ρaθ_liq_cv) =\n    (state.turbconv.environment, :ρaθ_liq_cv)\nget_prog_state(state, ::en_ρaq_tot_cv) =\n    (state.turbconv.environment, :ρaq_tot_cv)\nget_prog_state(state, ::en_ρaθ_liq_q_tot_cv) =\n    (state.turbconv.environment, :ρaθ_liq_q_tot_cv)\n\nget_prog_state(state, ::up_ρa{i}) where {i} = (state.turbconv.updraft[i], :ρa)\nget_prog_state(state, ::up_ρaw{i}) where {i} = (state.turbconv.updraft[i], :ρaw)\nget_prog_state(state, ::up_ρaθ_liq{i}) where {i} =\n    (state.turbconv.updraft[i], :ρaθ_liq)\nget_prog_state(state, ::up_ρaq_tot{i}) where {i} =\n    (state.turbconv.updraft[i], :ρaq_tot)\n\nget_specific_state(state, ::en_ρatke) = (state.turbconv.environment, :atke)\nget_specific_state(state, ::en_ρaθ_liq_cv) =\n    (state.turbconv.environment, :aθ_liq_cv)\nget_specific_state(state, ::en_ρaq_tot_cv) =\n    (state.turbconv.environment, :aq_tot_cv)\nget_specific_state(state, ::en_ρaθ_liq_q_tot_cv) =\n    (state.turbconv.environment, :aθ_liq_q_tot_cv)\n\nget_specific_state(state, ::up_ρa{i}) where {i} =\n    (state.turbconv.updraft[i], :a)\nget_specific_state(state, ::up_ρaw{i}) where {i} =\n    (state.turbconv.updraft[i], :aw)\nget_specific_state(state, ::up_ρaθ_liq{i}) where {i} =\n    (state.turbconv.updraft[i], :aθ_liq)\nget_specific_state(state, ::up_ρaq_tot{i}) where {i} =\n    (state.turbconv.updraft[i], :aq_tot)\n\nstruct EntrDetr{N_up} <: TendencyDef{Source} end\nstruct PressSource{N_up} <: TendencyDef{Source} end\nstruct BuoySource{N_up} <: TendencyDef{Source} end\nstruct ShearSource <: TendencyDef{Source} end\nstruct DissSource <: TendencyDef{Source} end\nstruct GradProdSource <: TendencyDef{Source} end\n\nprognostic_vars(::EntrDetr{N_up}) where {N_up} = (\n    vuntuple(i -> up_ρa{i}, N_up)...,\n    vuntuple(i -> up_ρaw{i}, N_up)...,\n    vuntuple(i -> up_ρaθ_liq{i}, N_up)...,\n    vuntuple(i -> up_ρaq_tot{i}, N_up)...,\n    en_ρatke(),\n    en_ρaθ_liq_cv(),\n    en_ρaq_tot_cv(),\n    en_ρaθ_liq_q_tot_cv(),\n)\nprognostic_vars(::PressSource{N_up}) where {N_up} =\n    vuntuple(i -> up_ρaw{i}(), N_up)\n\nprognostic_vars(::BuoySource{N_up}) where {N_up} =\n    vuntuple(i -> up_ρaw{i}(), N_up)\n\nEntrDetr(m::EDMF) = EntrDetr{n_updrafts(m)}()\nBuoySource(m::EDMF) = BuoySource{n_updrafts(m)}()\nPressSource(m::EDMF) = PressSource{n_updrafts(m)}()\n\n# Dycore tendencies\neq_tends(\n    pv::Union{Momentum, Energy, TotalMoisture},\n    m::EDMF,\n    flux::Flux{SecondOrder},\n) = eq_tends(pv, m.coupling, flux)\n\neq_tends(\n    pv::Union{Momentum, Energy, TotalMoisture},\n    ::Decoupled,\n    ::Flux{SecondOrder},\n) = ()\n\neq_tends(\n    pv::Union{Momentum, Energy, TotalMoisture},\n    ::Coupled,\n    ::Flux{SecondOrder},\n) = (SGSFlux(),)\n\n# Turbconv tendencies\neq_tends(pv::EDMFPrognosticVariable, m::AtmosModel, tt::Flux{O}) where {O} =\n    eq_tends(pv, turbconv_model(m), tt)\n\neq_tends(::EDMFPrognosticVariable, m::EDMF, ::Flux{O}) where {O} = ()\n\neq_tends(::EnvironmentPrognosticVariable, m::EDMF, ::Flux{SecondOrder}) =\n    (Diffusion(),)\n\neq_tends(pv::EDMFPrognosticVariable, m::EDMF, ::Flux{FirstOrder}) = (Advect(),)\n\neq_tends(pv::PV, m::EDMF, ::Source) where {PV} = ()\n\neq_tends(::EDMFPrognosticVariable, m::EDMF, ::Source) = (EntrDetr(m),)\n\neq_tends(pv::en_ρatke, m::EDMF, ::Source) =\n    (EntrDetr(m), PressSource(m), BuoySource(m), ShearSource(), DissSource())\n\neq_tends(\n    ::Union{en_ρaθ_liq_cv, en_ρaq_tot_cv, en_ρaθ_liq_q_tot_cv},\n    m::EDMF,\n    ::Source,\n) = (EntrDetr(m), DissSource(), GradProdSource())\n\neq_tends(::up_ρaw, m::EDMF, ::Source) =\n    (EntrDetr(m), PressSource(m), BuoySource(m))\n\nstruct SGSFlux <: TendencyDef{Flux{SecondOrder}} end\n\n\"\"\"\n    init_aux_turbconv!(\n        turbconv::EDMF{FT},\n        m::AtmosModel{FT},\n        aux::Vars,\n        geom::LocalGeometry,\n    ) where {FT}\n\nInitialize EDMF auxiliary variables.\n\"\"\"\nfunction init_aux_turbconv!(\n    turbconv::EDMF{FT},\n    m::AtmosModel{FT},\n    aux::Vars,\n    geom::LocalGeometry,\n) where {FT} end;\n\nfunction turbconv_nodal_update_auxiliary_state!(\n    turbconv::EDMF{FT},\n    m::AtmosModel{FT},\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) where {FT}\n    save_subdomain_temperature!(m, state, aux)\nend;\n\nfunction prognostic_to_primitive!(\n    turbconv::EDMF,\n    atmos,\n    moist::DryModel,\n    prim::Vars,\n    prog::Vars,\n)\n    N_up = n_updrafts(turbconv)\n    prim_en = prim.turbconv.environment\n    prog_en = prog.turbconv.environment\n    prim_up = prim.turbconv.updraft\n    prog_up = prog.turbconv.updraft\n\n    ρ_inv = 1 / prog.ρ\n    prim_en.atke = prog_en.ρatke * ρ_inv\n    prim_en.aθ_liq_cv = prog_en.ρaθ_liq_cv * ρ_inv\n    prim_en.aθ_liq_q_tot_cv = prog_en.ρaθ_liq_q_tot_cv * ρ_inv\n\n    if moist isa DryModel\n        prim_en.aq_tot_cv = 0\n    else\n        prim_en.aq_tot_cv = prog_en.ρaq_tot_cv * ρ_inv\n    end\n    @unroll_map(N_up) do i\n        prim_up[i].a = prog_up[i].ρa * ρ_inv\n        prim_up[i].aw = prog_up[i].ρaw * ρ_inv\n        prim_up[i].aθ_liq = prog_up[i].ρaθ_liq * ρ_inv\n        if moist isa DryModel\n            prim_up[i].aq_tot = 0\n        else\n            prim_up[i].aq_tot = prog_up[i].ρaq_tot * ρ_inv\n        end\n    end\nend\n\nfunction primitive_to_prognostic!(\n    turbconv::EDMF,\n    atmos,\n    moist::DryModel,\n    prog::Vars,\n    prim::Vars,\n)\n\n    N_up = n_updrafts(turbconv)\n    prim_en = prim.turbconv.environment\n    prog_en = prog.turbconv.environment\n    prim_up = prim.turbconv.updraft\n    prog_up = prog.turbconv.updraft\n\n    ρ_gm = prog.ρ\n    prog_en.ρatke = prim_en.atke * ρ_gm\n    prog_en.ρaθ_liq_cv = prim_en.aθ_liq_cv * ρ_gm\n    prog_en.ρaθ_liq_q_tot_cv = prim_en.aθ_liq_q_tot_cv * ρ_gm\n\n    if moist isa DryModel\n        prog_en.ρaq_tot_cv = 0\n    else\n        prog_en.ρaq_tot_cv = prim_en.aq_tot_cv * ρ_gm\n    end\n    @unroll_map(N_up) do i\n        prog_up[i].ρa = prim_up[i].a * ρ_gm\n        prog_up[i].ρaw = prim_up[i].aw * ρ_gm\n        prog_up[i].ρaθ_liq = prim_up[i].aθ_liq * ρ_gm\n        if moist isa DryModel\n            prog_up[i].ρaq_tot = 0\n        else\n            prog_up[i].ρaq_tot = prim_up[i].aq_tot * ρ_gm\n        end\n    end\n\nend\n\nfunction compute_gradient_argument!(\n    turbconv::EDMF{FT},\n    m::AtmosModel{FT},\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) where {FT}\n    N_up = n_updrafts(turbconv)\n    z = altitude(m, aux)\n\n    # Aliases:\n    gm_tf = transform.turbconv\n    up_tf = transform.turbconv.updraft\n    en_tf = transform.turbconv.environment\n    gm = state\n    up = state.turbconv.updraft\n    en = state.turbconv.environment\n\n    # Recover thermo states\n    ts = recover_thermo_state_all(m, state, aux)\n\n    # Get environment variables\n    env = environment_vars(state, N_up)\n    param_set = parameter_set(m)\n    @unroll_map(N_up) do i\n        up_tf[i].w = fix_void_up(up[i].ρa, up[i].ρaw / up[i].ρa)\n    end\n    _grav::FT = grav(param_set)\n\n    ρ_inv = 1 / gm.ρ\n    θ_liq_en = liquid_ice_pottemp(ts.en)\n\n    if moisture_model(m) isa DryModel\n        q_tot_en = FT(0)\n    else\n        q_tot_en = total_specific_humidity(ts.en)\n    end\n\n    # populate gradient arguments\n    en_tf.θ_liq = θ_liq_en\n    en_tf.q_tot = q_tot_en\n    en_tf.w = env.w\n\n    en_tf.tke = en.ρatke / (env.a * gm.ρ)\n    en_tf.θ_liq_cv = en.ρaθ_liq_cv / (env.a * gm.ρ)\n\n    if moisture_model(m) isa DryModel\n        en_tf.q_tot_cv = FT(0)\n        en_tf.θ_liq_q_tot_cv = FT(0)\n    else\n        en_tf.q_tot_cv = en.ρaq_tot_cv / (env.a * gm.ρ)\n        en_tf.θ_liq_q_tot_cv = en.ρaθ_liq_q_tot_cv / (env.a * gm.ρ)\n    end\n\n    en_tf.θv = virtual_pottemp(ts.en)\n    en_e_kin =\n        FT(1 // 2) * ((gm.ρu[1] * ρ_inv)^2 + (gm.ρu[2] * ρ_inv)^2 + env.w^2) # TBD: Check\n    en_e_tot = total_energy(en_e_kin, _grav * z, ts.en)\n    en_tf.h_tot = total_specific_enthalpy(ts.en, en_e_tot)\n\n    gm_tf.u = gm.ρu[1] * ρ_inv\n    gm_tf.v = gm.ρu[2] * ρ_inv\nend;\n\nfunction compute_gradient_flux!(\n    turbconv::EDMF{FT},\n    m::AtmosModel{FT},\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) where {FT}\n    args = (; diffusive, state, aux, t)\n    N_up = n_updrafts(turbconv)\n\n    # Aliases:\n    gm = state\n    gm_dif = diffusive.turbconv\n    gm_∇tf = ∇transform.turbconv\n    up_dif = diffusive.turbconv.updraft\n    up_∇tf = ∇transform.turbconv.updraft\n    en = state.turbconv.environment\n    en_dif = diffusive.turbconv.environment\n    en_∇tf = ∇transform.turbconv.environment\n\n    @unroll_map(N_up) do i\n        up_dif[i].∇w = up_∇tf[i].w\n    end\n\n    env = environment_vars(state, N_up)\n    ρa₀ = gm.ρ * env.a\n    # first moment grid mean coming from environment gradients only\n    en_dif.∇θ_liq = en_∇tf.θ_liq\n    en_dif.∇q_tot = en_∇tf.q_tot\n    en_dif.∇w = en_∇tf.w\n    # second moment env cov\n    en_dif.∇tke = en_∇tf.tke\n    en_dif.∇θ_liq_cv = en_∇tf.θ_liq_cv\n    en_dif.∇q_tot_cv = en_∇tf.q_tot_cv\n    en_dif.∇θ_liq_q_tot_cv = en_∇tf.θ_liq_q_tot_cv\n\n    en_dif.∇θv = en_∇tf.θv\n    en_dif.∇h_tot = en_∇tf.h_tot\n\n    gm_dif.∇u = gm_∇tf.u\n    gm_dif.∇v = gm_∇tf.v\nend;\n\nfunction source(::up_ρa{i}, ::EntrDetr, atmos, args) where {i}\n    @unpack E_dyn, Δ_dyn, ρa_up = args.precomputed.turbconv\n    return fix_void_up(ρa_up[i], E_dyn[i] - Δ_dyn[i])\nend\n\nfunction source(::up_ρaw{i}, ::EntrDetr, atmos, args) where {i}\n    @unpack E_dyn, Δ_dyn, E_trb, env, ρa_up, w_up = args.precomputed.turbconv\n    up = args.state.turbconv.updraft\n    entr = fix_void_up(ρa_up[i], (E_dyn[i] + E_trb[i]) * env.w)\n    detr = fix_void_up(ρa_up[i], (Δ_dyn[i] + E_trb[i]) * w_up[i])\n\n    return entr - detr\nend\n\nfunction source(::up_ρaθ_liq{i}, ::EntrDetr, atmos, args) where {i}\n    @unpack E_dyn, Δ_dyn, E_trb, env, ρa_up, ts_en = args.precomputed.turbconv\n    up = args.state.turbconv.updraft\n    θ_liq_en = liquid_ice_pottemp(ts_en)\n    entr = fix_void_up(ρa_up[i], (E_dyn[i] + E_trb[i]) * θ_liq_en)\n    detr =\n        fix_void_up(ρa_up[i], (Δ_dyn[i] + E_trb[i]) * up[i].ρaθ_liq / ρa_up[i])\n\n    return entr - detr\nend\n\nfunction source(::up_ρaq_tot{i}, ::EntrDetr, atmos, args) where {i}\n    @unpack E_dyn, Δ_dyn, E_trb, ρa_up, ts_en = args.precomputed.turbconv\n    up = args.state.turbconv.updraft\n    q_tot_en = total_specific_humidity(ts_en)\n    entr = fix_void_up(ρa_up[i], (E_dyn[i] + E_trb[i]) * q_tot_en)\n    detr =\n        fix_void_up(ρa_up[i], (Δ_dyn[i] + E_trb[i]) * up[i].ρaq_tot / ρa_up[i])\n\n    return entr - detr\nend\n\nfunction source(::en_ρatke, ::EntrDetr, atmos, args)\n    @unpack E_dyn, Δ_dyn, E_trb, env, ρa_up, w_up = args.precomputed.turbconv\n    @unpack state = args\n    up = state.turbconv.updraft\n    en = state.turbconv.environment\n    gm = state\n    N_up = n_updrafts(turbconv_model(atmos))\n    ρ_inv = 1 / gm.ρ\n    tke_en = enforce_positivity(en.ρatke) * ρ_inv / env.a\n\n    entr_detr = vuntuple(N_up) do i\n        fix_void_up(\n            ρa_up[i],\n            E_trb[i] * (env.w - gm.ρu[3] * ρ_inv) * (env.w - w_up[i]) -\n            (E_dyn[i] + E_trb[i]) * tke_en +\n            Δ_dyn[i] * (w_up[i] - env.w) * (w_up[i] - env.w) / 2,\n        )\n    end\n    return sum(entr_detr)\nend\n\nfunction source(::en_ρaθ_liq_cv, ::EntrDetr, atmos, args)\n    @unpack E_dyn, Δ_dyn, E_trb, ρa_up, ts_en = args.precomputed.turbconv\n    @unpack state = args\n    ts_gm = args.precomputed.ts\n    up = state.turbconv.updraft\n    en = state.turbconv.environment\n    N_up = n_updrafts(turbconv_model(atmos))\n    θ_liq = liquid_ice_pottemp(ts_gm)\n    θ_liq_en = liquid_ice_pottemp(ts_en)\n\n    entr_detr = vuntuple(N_up) do i\n        fix_void_up(\n            ρa_up[i],\n            Δ_dyn[i] *\n            (up[i].ρaθ_liq / ρa_up[i] - θ_liq_en) *\n            (up[i].ρaθ_liq / ρa_up[i] - θ_liq_en) +\n            E_trb[i] *\n            (θ_liq_en - θ_liq) *\n            (θ_liq_en - up[i].ρaθ_liq / ρa_up[i]) +\n            E_trb[i] *\n            (θ_liq_en - θ_liq) *\n            (θ_liq_en - up[i].ρaθ_liq / ρa_up[i]) -\n            (E_dyn[i] + E_trb[i]) * en.ρaθ_liq_cv,\n        )\n    end\n    return sum(entr_detr)\nend\n\nfunction source(::en_ρaq_tot_cv, ::EntrDetr, atmos, args)\n    @unpack E_dyn, Δ_dyn, E_trb, ρa_up, ts_en = args.precomputed.turbconv\n    @unpack state = args\n    FT = eltype(state)\n    up = state.turbconv.updraft\n    en = state.turbconv.environment\n    gm = state\n    N_up = n_updrafts(turbconv_model(atmos))\n    q_tot_en = total_specific_humidity(ts_en)\n    ρ_inv = 1 / gm.ρ\n    ρq_tot = moisture_model(atmos) isa DryModel ? FT(0) : gm.moisture.ρq_tot\n\n    entr_detr = vuntuple(N_up) do i\n        fix_void_up(\n            ρa_up[i],\n            Δ_dyn[i] *\n            (up[i].ρaq_tot / ρa_up[i] - q_tot_en) *\n            (up[i].ρaq_tot / ρa_up[i] - q_tot_en) +\n            E_trb[i] *\n            (q_tot_en - ρq_tot * ρ_inv) *\n            (q_tot_en - up[i].ρaq_tot / ρa_up[i]) +\n            E_trb[i] *\n            (q_tot_en - ρq_tot * ρ_inv) *\n            (q_tot_en - up[i].ρaq_tot / ρa_up[i]) -\n            (E_dyn[i] + E_trb[i]) * en.ρaq_tot_cv,\n        )\n    end\n    return sum(entr_detr)\nend\n\nfunction source(::en_ρaθ_liq_q_tot_cv, ::EntrDetr, atmos, args)\n    @unpack E_dyn, Δ_dyn, E_trb, ρa_up, ts_en = args.precomputed.turbconv\n    @unpack state = args\n    FT = eltype(state)\n    ts_gm = args.precomputed.ts\n    up = state.turbconv.updraft\n    en = state.turbconv.environment\n    gm = state\n    N_up = n_updrafts(turbconv_model(atmos))\n    q_tot_en = total_specific_humidity(ts_en)\n    θ_liq = liquid_ice_pottemp(ts_gm)\n    θ_liq_en = liquid_ice_pottemp(ts_en)\n    ρ_inv = 1 / gm.ρ\n    ρq_tot = moisture_model(atmos) isa DryModel ? FT(0) : gm.moisture.ρq_tot\n\n    entr_detr = vuntuple(N_up) do i\n        fix_void_up(\n            ρa_up[i],\n            Δ_dyn[i] *\n            (up[i].ρaθ_liq / ρa_up[i] - θ_liq_en) *\n            (up[i].ρaq_tot / ρa_up[i] - q_tot_en) +\n            E_trb[i] *\n            (θ_liq_en - θ_liq) *\n            (q_tot_en - up[i].ρaq_tot / ρa_up[i]) +\n            E_trb[i] *\n            (q_tot_en - ρq_tot * ρ_inv) *\n            (θ_liq_en - up[i].ρaθ_liq / ρa_up[i]) -\n            (E_dyn[i] + E_trb[i]) * en.ρaθ_liq_q_tot_cv,\n        )\n    end\n    return sum(entr_detr)\nend\n\nfunction source(::en_ρatke, ::PressSource, atmos, args)\n    @unpack env, ρa_up, dpdz, w_up = args.precomputed.turbconv\n    up = args.state.turbconv.updraft\n    N_up = n_updrafts(turbconv_model(atmos))\n    press_tke = vuntuple(N_up) do i\n        fix_void_up(ρa_up[i], ρa_up[i] * (w_up[i] - env.w) * dpdz[i])\n    end\n    return sum(press_tke)\nend\n\nfunction source(::en_ρatke, ::ShearSource, atmos, args)\n    @unpack env, K_m, Shear² = args.precomputed.turbconv\n    gm = args.state\n    ρa₀ = gm.ρ * env.a\n    # production from mean gradient and Dissipation\n    return ρa₀ * K_m * Shear² # tke Shear source\nend\n\nfunction source(::en_ρatke, ::BuoySource, atmos, args)\n    @unpack env, K_h, ∂b∂z_env = args.precomputed.turbconv\n    gm = args.state\n    ρa₀ = gm.ρ * env.a\n    return -ρa₀ * K_h * ∂b∂z_env   # tke Buoyancy source\nend\n\nfunction source(::en_ρatke, ::DissSource, atmos, args)\n    @unpack Diss₀ = args.precomputed.turbconv\n    en = args.state.turbconv.environment\n    return -Diss₀ * en.ρatke  # tke Dissipation\nend\n\nfunction source(::en_ρaθ_liq_cv, ::DissSource, atmos, args)\n    @unpack Diss₀ = args.precomputed.turbconv\n    en = args.state.turbconv.environment\n    return -Diss₀ * en.ρaθ_liq_cv\nend\n\nfunction source(::en_ρaq_tot_cv, ::DissSource, atmos, args)\n    @unpack Diss₀ = args.precomputed.turbconv\n    en = args.state.turbconv.environment\n    return -Diss₀ * en.ρaq_tot_cv\nend\n\nfunction source(::en_ρaθ_liq_q_tot_cv, ::DissSource, atmos, args)\n    @unpack Diss₀ = args.precomputed.turbconv\n    en = args.state.turbconv.environment\n    return -Diss₀ * en.ρaθ_liq_q_tot_cv\nend\n\nfunction source(::en_ρaθ_liq_cv, ::GradProdSource, atmos, args)\n    @unpack env, K_h = args.precomputed.turbconv\n    gm = args.state\n    en_dif = args.diffusive.turbconv.environment\n    ρa₀ = gm.ρ * env.a\n    return ρa₀ * (2 * K_h * en_dif.∇θ_liq[3] * en_dif.∇θ_liq[3])\nend\n\nfunction source(::en_ρaq_tot_cv, ::GradProdSource, atmos, args)\n    @unpack env, K_h = args.precomputed.turbconv\n    gm = args.state\n    en_dif = args.diffusive.turbconv.environment\n    ρa₀ = gm.ρ * env.a\n    return ρa₀ * (2 * K_h * en_dif.∇q_tot[3] * en_dif.∇q_tot[3])\nend\n\nfunction source(::en_ρaθ_liq_q_tot_cv, ::GradProdSource, atmos, args)\n    @unpack env, K_h = args.precomputed.turbconv\n    gm = args.state\n    en_dif = args.diffusive.turbconv.environment\n    ρa₀ = gm.ρ * env.a\n    return ρa₀ * (2 * K_h * en_dif.∇θ_liq[3] * en_dif.∇q_tot[3])\nend\n\nfunction source(::up_ρaw{i}, ::BuoySource, atmos, args) where {i}\n    @unpack buoy = args.precomputed.turbconv\n    up = args.state.turbconv.updraft\n    return up[i].ρa * buoy.up[i]\nend\n\nfunction source(::up_ρaw{i}, ::PressSource, atmos, args) where {i}\n    @unpack dpdz = args.precomputed.turbconv\n    up = args.state.turbconv.updraft\n    return -up[i].ρa * dpdz[i]\nend\n\nfunction compute_ρa_up(atmos, state, aux)\n    # Aliases:\n    turbconv = turbconv_model(atmos)\n    gm = state\n    up = state.turbconv.updraft\n    N_up = n_updrafts(turbconv)\n    a_min = turbconv.subdomains.a_min\n    a_max = turbconv.subdomains.a_max\n    # in future GCM implementations we need to think about grid mean advection\n    ρa_up = vuntuple(N_up) do i\n        gm.ρ * enforce_unit_bounds(up[i].ρa / gm.ρ, a_min, a_max)\n    end\n    return ρa_up\nend\n\nfunction flux(::up_ρa{i}, ::Advect, atmos, args) where {i}\n    @unpack state, aux = args\n    @unpack ρa_up = args.precomputed.turbconv\n    up = state.turbconv.updraft\n    ẑ = vertical_unit_vector(atmos, aux)\n    return fix_void_up(ρa_up[i], up[i].ρaw) * ẑ\nend\nfunction flux(::up_ρaw{i}, ::Advect, atmos, args) where {i}\n    @unpack state, aux = args\n    @unpack ρa_up, w_up = args.precomputed.turbconv\n    up = state.turbconv.updraft\n    ẑ = vertical_unit_vector(atmos, aux)\n    return fix_void_up(ρa_up[i], up[i].ρaw * w_up[i]) * ẑ\n\nend\nfunction flux(::up_ρaθ_liq{i}, ::Advect, atmos, args) where {i}\n    @unpack state, aux = args\n    @unpack ρa_up, w_up = args.precomputed.turbconv\n    up = state.turbconv.updraft\n    ẑ = vertical_unit_vector(atmos, aux)\n    return fix_void_up(ρa_up[i], w_up[i] * up[i].ρaθ_liq) * ẑ\n\nend\nfunction flux(::up_ρaq_tot{i}, ::Advect, atmos, args) where {i}\n    @unpack state, aux = args\n    @unpack ρa_up, w_up = args.precomputed.turbconv\n    up = state.turbconv.updraft\n    ẑ = vertical_unit_vector(atmos, aux)\n    return fix_void_up(ρa_up[i], w_up[i] * up[i].ρaq_tot) * ẑ\n\nend\n\nfunction flux(::en_ρatke, ::Advect, atmos, args)\n    @unpack state, aux = args\n    @unpack env = args.precomputed.turbconv\n    en = state.turbconv.environment\n    ẑ = vertical_unit_vector(atmos, aux)\n    return en.ρatke * env.w * ẑ\nend\nfunction flux(::en_ρaθ_liq_cv, ::Advect, atmos, args)\n    @unpack state, aux = args\n    @unpack env = args.precomputed.turbconv\n    en = state.turbconv.environment\n    ẑ = vertical_unit_vector(atmos, aux)\n    return en.ρaθ_liq_cv * env.w * ẑ\nend\nfunction flux(::en_ρaq_tot_cv, ::Advect, atmos, args)\n    @unpack state, aux = args\n    @unpack env = args.precomputed.turbconv\n    en = state.turbconv.environment\n    ẑ = vertical_unit_vector(atmos, aux)\n    return en.ρaq_tot_cv * env.w * ẑ\nend\nfunction flux(::en_ρaθ_liq_q_tot_cv, ::Advect, atmos, args)\n    @unpack state, aux = args\n    @unpack env = args.precomputed.turbconv\n    en = state.turbconv.environment\n    ẑ = vertical_unit_vector(atmos, aux)\n    return en.ρaθ_liq_q_tot_cv * env.w * ẑ\nend\n\nfunction precompute(::EDMF, bl, args, ts, ::Flux{FirstOrder})\n    @unpack state, aux = args\n    FT = eltype(state)\n    turbconv = turbconv_model(bl)\n    env = environment_vars(state, n_updrafts(turbconv))\n    ρa_up = compute_ρa_up(bl, state, aux)\n    up = state.turbconv.updraft\n    gm = state\n    N_up = n_updrafts(turbconv)\n    ρ_inv = 1 / gm.ρ\n    w_up = vuntuple(N_up) do i\n        fix_void_up(ρa_up[i], up[i].ρaw / ρa_up[i])\n    end\n\n    θ_liq_up = vuntuple(N_up) do i\n        fix_void_up(up[i].ρa, up[i].ρaθ_liq / up[i].ρa, liquid_ice_pottemp(ts))\n    end\n    a_up = vuntuple(N_up) do i\n        fix_void_up(up[i].ρa, up[i].ρa * ρ_inv)\n    end\n    if !(moisture_model(bl) isa DryModel)\n        q_tot_up = vuntuple(N_up) do i\n            fix_void_up(up[i].ρa, up[i].ρaq_tot / up[i].ρa, gm.moisture.ρq_tot)\n        end\n    else\n        q_tot_up = vuntuple(i -> FT(0), N_up)\n    end\n\n    return (; env, a_up, q_tot_up, ρa_up, θ_liq_up, w_up)\nend\n\nfunction precompute(::EDMF, bl, args, ts, ::Flux{SecondOrder})\n    @unpack state, aux, diffusive, t = args\n    en_dif = diffusive.turbconv.environment\n    up = state.turbconv.updraft\n    gm = state\n    ts_gm = ts\n    FT = eltype(state)\n    z = altitude(bl, aux)\n    param_set = parameter_set(bl)\n    turbconv = turbconv_model(bl)\n    N_up = n_updrafts(turbconv)\n    _grav::FT = grav(param_set)\n    ρ_inv = 1 / gm.ρ\n\n    env = environment_vars(state, N_up)\n    ts_en = new_thermo_state_en(bl, moisture_model(bl), state, aux, ts_gm)\n    ts_up = new_thermo_state_up(bl, moisture_model(bl), state, aux, ts_gm)\n\n    buoy = compute_buoyancy(bl, state, env, ts_en, ts_up, aux.ref_state)\n\n    E_dyn, Δ_dyn, E_trb = entr_detr(bl, state, aux, ts_up, ts_en, env, buoy)\n\n    Shear² =\n        diffusive.turbconv.∇u[3]^2 +\n        diffusive.turbconv.∇v[3]^2 +\n        diffusive.turbconv.environment.∇w[3]^2\n    l_mix, ∂b∂z_env, Pr_t = mixing_length(\n        bl,\n        turbconv.mix_len,\n        args,\n        Δ_dyn,\n        E_trb,\n        Shear²,\n        ts_gm,\n        ts_en,\n        env,\n    )\n    ρa_up = compute_ρa_up(bl, state, aux)\n\n    en = state.turbconv.environment\n    tke_en = enforce_positivity(en.ρatke) / env.a / state.ρ\n    K_m = turbconv.mix_len.c_m * l_mix * sqrt(tke_en)\n    K_h = K_m / Pr_t\n    ρaw_up = vuntuple(i -> up[i].ρaw, N_up)\n\n    w_up = vuntuple(N_up) do i\n        fix_void_up(ρa_up[i], up[i].ρaw / ρa_up[i])\n    end\n\n    ρu_gm_tup = Tuple(gm.ρu)\n    ρa_en = gm.ρ * env.a\n    # TODO: Consider turbulent contribution:\n\n    e_kin_up =\n        FT(1 / 2) .* ntuple(N_up) do i\n            (ρu_gm_tup[1] * ρ_inv)^2 + (ρu_gm_tup[2] * ρ_inv)^2 + w_up[i]^2\n        end\n    e_kin_en =\n        FT(1 // 2) * ((gm.ρu[1] * ρ_inv)^2 + (gm.ρu[2] * ρ_inv)^2 + env.w)^2\n\n    e_tot_up = ntuple(i -> total_energy(e_kin_up[i], _grav * z, ts_up[i]), N_up)\n    h_tot_up = ntuple(i -> total_specific_enthalpy(ts_up[i], e_tot_up[i]), N_up)\n\n    e_tot_en = total_energy(e_kin_en, _grav * z, ts_en)\n    h_tot_en = total_specific_enthalpy(ts_en, e_tot_en)\n    h_tot_gm = total_specific_enthalpy(ts, gm.energy.ρe * ρ_inv)\n\n    massflux_h_tot = sum(\n        ntuple(N_up) do i\n            fix_void_up(\n                ρa_up[i],\n                ρa_up[i] *\n                (h_tot_gm - h_tot_up[i]) *\n                (gm.ρu[3] * ρ_inv - ρaw_up[i] / ρa_up[i]),\n            )\n        end,\n    )\n    massflux_h_tot +=\n        (ρa_en * (h_tot_gm - h_tot_en) * (ρu_gm_tup[3] * ρ_inv - env.w))\n\n    ρh_sgs_flux = SVector{3, FT}(\n        0,\n        0,\n        -gm.ρ * env.a * K_h * en_dif.∇h_tot[3] + massflux_h_tot,\n    )\n\n    return (;\n        env,\n        ρa_up,\n        ρaw_up,\n        ts_en,\n        ts_up,\n        E_dyn,\n        Δ_dyn,\n        E_trb,\n        l_mix,\n        ∂b∂z_env,\n        K_h,\n        K_m,\n        Pr_t,\n        ρh_sgs_flux,\n    )\nend\n\n\"\"\"\n    compute_buoyancy(\n        bl::BalanceLaw,\n        state::Vars,\n        env::NamedTuple,\n        ts_en::ThermodynamicState,\n        ts_up,\n        ref_state::Vars\n    )\n\nCompute buoyancies of subdomains\n\"\"\"\nfunction compute_buoyancy(\n    bl::BalanceLaw,\n    state::Vars,\n    env::NamedTuple,\n    ts_en::ThermodynamicState,\n    ts_up,\n    ref_state::Vars,\n)\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n    N_up = n_updrafts(turbconv_model(bl))\n    _grav::FT = grav(param_set)\n    gm = state\n    ρ_inv = 1 / gm.ρ\n    buoyancy_en = -_grav * (air_density(ts_en) - ref_state.ρ) * ρ_inv\n    up = state.turbconv.updraft\n\n    a_up = vuntuple(N_up) do i\n        fix_void_up(up[i].ρa, up[i].ρa * ρ_inv)\n    end\n\n    abs_buoyancy_up = vuntuple(N_up) do i\n        -_grav * (air_density(ts_up[i]) - ref_state.ρ) * ρ_inv\n    end\n    b_gm = grid_mean_b(env, a_up, N_up, abs_buoyancy_up, buoyancy_en)\n\n    # remove the gm_b from all subdomains\n    buoyancy_up = vuntuple(N_up) do i\n        abs_buoyancy_up[i] - b_gm\n    end\n\n    buoyancy_en -= b_gm\n    return (; up = buoyancy_up, en = buoyancy_en)\nend\n\nfunction precompute(::EDMF, bl, args, ts, ::Source)\n    @unpack state, aux, diffusive, t = args\n    ts_gm = ts\n    gm = state\n    up = state.turbconv.updraft\n    turbconv = turbconv_model(bl)\n    N_up = n_updrafts(turbconv)\n    # Get environment variables\n    env = environment_vars(state, N_up)\n    # Recover thermo states\n    ts_en = new_thermo_state_en(bl, moisture_model(bl), state, aux, ts_gm)\n    ts_up = new_thermo_state_up(bl, moisture_model(bl), state, aux, ts_gm)\n    ρa_up = compute_ρa_up(bl, state, aux)\n\n    Shear² =\n        diffusive.turbconv.∇u[3]^2 +\n        diffusive.turbconv.∇v[3]^2 +\n        diffusive.turbconv.environment.∇w[3]^2\n\n    buoy = compute_buoyancy(bl, state, env, ts_en, ts_up, aux.ref_state)\n    E_dyn, Δ_dyn, E_trb = entr_detr(bl, state, aux, ts_up, ts_en, env, buoy)\n\n    dpdz = perturbation_pressure(bl, args, env, buoy)\n\n    l_mix, ∂b∂z_env, Pr_t = mixing_length(\n        bl,\n        turbconv.mix_len,\n        args,\n        Δ_dyn,\n        E_trb,\n        Shear²,\n        ts_gm,\n        ts_en,\n        env,\n    )\n\n    w_up = vuntuple(N_up) do i\n        fix_void_up(ρa_up[i], up[i].ρaw / ρa_up[i])\n    end\n\n    en = state.turbconv.environment\n    tke_en = enforce_positivity(en.ρatke) / env.a / state.ρ\n    K_m = turbconv.mix_len.c_m * l_mix * sqrt(tke_en)\n    K_h = K_m / Pr_t\n    Diss₀ = turbconv.mix_len.c_d * sqrt(tke_en) / l_mix\n    tke_buoy_prod = -gm.ρ * env.a * K_h * ∂b∂z_env   # tke Buoyancy source\n\n    return (;\n        env,\n        Diss₀,\n        buoy,\n        K_m,\n        K_h,\n        ρa_up,\n        w_up,\n        ts_en,\n        ts_up,\n        E_dyn,\n        Δ_dyn,\n        E_trb,\n        dpdz,\n        l_mix,\n        ∂b∂z_env,\n        Pr_t,\n        Shear²,\n        tke_buoy_prod,\n    )\nend\n\nfunction flux(::Energy, ::SGSFlux, atmos, args)\n    @unpack state, aux, diffusive = args\n    @unpack ts = args.precomputed\n    @unpack ρh_sgs_flux = args.precomputed.turbconv\n    return ρh_sgs_flux\nend\n\nfunction flux(::TotalMoisture, ::SGSFlux, atmos, args)\n    @unpack state, diffusive = args\n    @unpack env, K_h, ρa_up, ρaw_up, ts_en = args.precomputed.turbconv\n    FT = eltype(state)\n    en_dif = diffusive.turbconv.environment\n    up = state.turbconv.updraft\n    gm = state\n    ρ_inv = 1 / gm.ρ\n    N_up = n_updrafts(turbconv_model(atmos))\n    ρq_tot = moisture_model(atmos) isa DryModel ? FT(0) : gm.moisture.ρq_tot\n    ρaq_tot_up = vuntuple(i -> up[i].ρaq_tot, N_up)\n    ρa_en = gm.ρ * env.a\n    q_tot_en = total_specific_humidity(ts_en)\n\n    ρu_gm_tup = Tuple(gm.ρu)\n\n    massflux_q_tot = sum(\n        ntuple(N_up) do i\n            fix_void_up(\n                ρa_up[i],\n                ρa_up[i] *\n                (ρq_tot * ρ_inv - ρaq_tot_up[i] / ρa_up[i]) *\n                (ρu_gm_tup[3] * ρ_inv - ρaw_up[i] / ρa_up[i]),\n            )\n        end,\n    )\n    massflux_q_tot +=\n        (ρa_en * (ρq_tot * ρ_inv - q_tot_en) * (ρu_gm_tup[3] * ρ_inv - env.w))\n    ρq_tot_sgs_flux = -gm.ρ * env.a * K_h * en_dif.∇q_tot[3] + massflux_q_tot\n    return SVector{3, FT}(0, 0, ρq_tot_sgs_flux)\nend\n\nfunction flux(::Momentum, ::SGSFlux, atmos, args)\n    @unpack state, diffusive = args\n    @unpack env, K_m, ρa_up, ρaw_up = args.precomputed.turbconv\n    FT = eltype(state)\n    en_dif = diffusive.turbconv.environment\n    gm_dif = diffusive.turbconv\n    up = state.turbconv.updraft\n    gm = state\n    ρ_inv = 1 / gm.ρ\n    N_up = n_updrafts(turbconv_model(atmos))\n    ρa_en = gm.ρ * env.a\n\n    ρu_gm_tup = Tuple(gm.ρu)\n\n    massflux_w = sum(\n        ntuple(N_up) do i\n            fix_void_up(\n                ρa_up[i],\n                ρa_up[i] *\n                (ρu_gm_tup[3] * ρ_inv - ρaw_up[i] / ρa_up[i]) *\n                (ρu_gm_tup[3] * ρ_inv - ρaw_up[i] / ρa_up[i]),\n            )\n        end,\n    )\n    massflux_w += (\n        ρa_en * (ρu_gm_tup[3] * ρ_inv - env.w) * (ρu_gm_tup[3] * ρ_inv - env.w)\n    )\n    ρw_sgs_flux = -gm.ρ * env.a * K_m * en_dif.∇w[3] + massflux_w\n    ρu_sgs_flux = -gm.ρ * env.a * K_m * gm_dif.∇u[3]\n    ρv_sgs_flux = -gm.ρ * env.a * K_m * gm_dif.∇v[3]\n    return SMatrix{3, 3, FT, 9}(\n        0,\n        0,\n        ρu_sgs_flux,\n        0,\n        0,\n        ρv_sgs_flux,\n        0,\n        0,\n        ρw_sgs_flux,\n    )\nend\n\nfunction flux(::en_ρaθ_liq_cv, ::Diffusion, atmos, args)\n    @unpack state, aux, diffusive = args\n    @unpack env, l_mix, Pr_t, K_h = args.precomputed.turbconv\n    en_dif = diffusive.turbconv.environment\n    gm = state\n    ẑ = vertical_unit_vector(atmos, aux)\n    return -gm.ρ * env.a * K_h * en_dif.∇θ_liq_cv[3] * ẑ\nend\nfunction flux(::en_ρaq_tot_cv, ::Diffusion, atmos, args)\n    @unpack state, aux, diffusive = args\n    @unpack env, l_mix, Pr_t, K_h = args.precomputed.turbconv\n    en_dif = diffusive.turbconv.environment\n    gm = state\n    ẑ = vertical_unit_vector(atmos, aux)\n    return -gm.ρ * env.a * K_h * en_dif.∇q_tot_cv[3] * ẑ\nend\nfunction flux(::en_ρaθ_liq_q_tot_cv, ::Diffusion, atmos, args)\n    @unpack state, aux, diffusive = args\n    @unpack env, l_mix, Pr_t, K_h = args.precomputed.turbconv\n    en_dif = diffusive.turbconv.environment\n    gm = state\n    ẑ = vertical_unit_vector(atmos, aux)\n    return -gm.ρ * env.a * K_h * en_dif.∇θ_liq_q_tot_cv[3] * ẑ\nend\nfunction flux(::en_ρatke, ::Diffusion, atmos, args)\n    @unpack state, aux, diffusive = args\n    @unpack env, K_m = args.precomputed.turbconv\n    gm = state\n    en_dif = diffusive.turbconv.environment\n    ẑ = vertical_unit_vector(atmos, aux)\n    return -gm.ρ * env.a * K_m * en_dif.∇tke[3] * ẑ\nend\n\n# First order boundary conditions\nfunction turbconv_boundary_state!(\n    nf,\n    bc::EDMFBottomBC,\n    atmos::AtmosModel{FT},\n    state⁺::Vars,\n    args,\n) where {FT}\n    @unpack state⁻, aux⁻, aux_int⁻ = args\n    turbconv = turbconv_model(atmos)\n    N_up = n_updrafts(turbconv)\n    up⁺ = state⁺.turbconv.updraft\n    en⁺ = state⁺.turbconv.environment\n    gm⁻ = state⁻\n    gm_a⁻ = aux⁻\n\n    zLL = altitude(atmos, aux_int⁻)\n    surf_vals = subdomain_surface_values(atmos, gm⁻, gm_a⁻, zLL)\n    a_up_surf = surf_vals.a_up_surf\n\n    @unroll_map(N_up) do i\n        up⁺[i].ρaw = FT(0)\n        up⁺[i].ρa = gm⁻.ρ * a_up_surf[i]\n        up⁺[i].ρaθ_liq = gm⁻.ρ * a_up_surf[i] * surf_vals.θ_liq_up_surf[i]\n        if !(moisture_model(atmos) isa DryModel)\n            up⁺[i].ρaq_tot = gm⁻.ρ * a_up_surf[i] * surf_vals.q_tot_up_surf[i]\n        else\n            up⁺[i].ρaq_tot = FT(0)\n        end\n    end\n\n    a_en = environment_area(gm⁻, N_up)\n    en⁺.ρatke = gm⁻.ρ * a_en * surf_vals.tke\n    en⁺.ρaθ_liq_cv = gm⁻.ρ * a_en * surf_vals.θ_liq_cv\n    if !(moisture_model(atmos) isa DryModel)\n        en⁺.ρaq_tot_cv = gm⁻.ρ * a_en * surf_vals.q_tot_cv\n        en⁺.ρaθ_liq_q_tot_cv = gm⁻.ρ * a_en * surf_vals.θ_liq_q_tot_cv\n    else\n        en⁺.ρaq_tot_cv = FT(0)\n        en⁺.ρaθ_liq_q_tot_cv = FT(0)\n    end\nend;\nfunction turbconv_boundary_state!(\n    nf,\n    bc::EDMFTopBC,\n    atmos::AtmosModel{FT},\n    state⁺::Vars,\n    args,\n) where {FT}\n    N_up = n_updrafts(turbconv_model(atmos))\n    up⁺ = state⁺.turbconv.updraft\n    @unroll_map(N_up) do i\n        up⁺[i].ρaw = FT(0)\n    end\nend;\n\n\n# The boundary conditions for second-order unknowns\n# (here we prescribe a flux at state⁺ to match that at state⁻ so that the flux divergence is zero)\nfunction turbconv_normal_boundary_flux_second_order!(\n    nf,\n    bc::EDMFBottomBC,\n    atmos::AtmosModel,\n    fluxᵀn::Vars,\n    args,\n)\n    @unpack state⁻, aux⁻, diffusive⁻, hyperdiff⁻, t, n⁻ = args\n    en_flx = fluxᵀn.turbconv.environment\n    tend_type = Flux{SecondOrder}()\n    _args⁻ = (;\n        state = state⁻,\n        aux = aux⁻,\n        t,\n        diffusive = diffusive⁻,\n        hyperdiffusive = hyperdiff⁻,\n    )\n    pargs = merge(_args⁻, (precomputed = precompute(atmos, _args⁻, tend_type),))\n\n    total_flux = Σfluxes(\n        en_ρatke(),\n        eq_tends(en_ρatke(), atmos, tend_type),\n        atmos,\n        pargs,\n    )\n    nd_ρatke = dot(n⁻, total_flux)\n    en_flx.ρatke = nd_ρatke\n\n    total_flux = Σfluxes(\n        en_ρaθ_liq_cv(),\n        eq_tends(en_ρaθ_liq_cv(), atmos, tend_type),\n        atmos,\n        pargs,\n    )\n    nd_ρaθ_liq_cv = dot(n⁻, total_flux)\n    en_flx.ρaθ_liq_cv = nd_ρaθ_liq_cv\n\n    if !(moisture_model(atmos) isa DryModel)\n        total_flux = Σfluxes(\n            en_ρaq_tot_cv(),\n            eq_tends(en_ρaq_tot_cv(), atmos, tend_type),\n            atmos,\n            pargs,\n        )\n        nd_ρaq_tot_cv = dot(n⁻, total_flux)\n        en_flx.ρaq_tot_cv = nd_ρaq_tot_cv\n\n        total_flux = Σfluxes(\n            en_ρaθ_liq_q_tot_cv(),\n            eq_tends(en_ρaθ_liq_q_tot_cv(), atmos, tend_type),\n            atmos,\n            pargs,\n        )\n        nd_ρaθ_liq_q_tot_cv = dot(n⁻, total_flux)\n        en_flx.ρaθ_liq_q_tot_cv = nd_ρaθ_liq_q_tot_cv\n    end\nend;\n\nfunction turbconv_normal_boundary_flux_second_order!(\n    nf,\n    bc::EDMFTopBC,\n    atmos::AtmosModel{FT},\n    fluxᵀn::Vars,\n    args,\n) where {FT}\n\n    turbconv = turbconv_model(atmos)\n    N_up = n_updrafts(turbconv)\n    up_flx = fluxᵀn.turbconv.updraft\n    en_flx = fluxᵀn.turbconv.environment\n    @unroll_map(N_up) do i\n        up_flx[i].ρa = FT(0)\n        up_flx[i].ρaθ_liq = FT(0)\n        up_flx[i].ρaq_tot = FT(0)\n    end\n    en_flx.ρatke = FT(0)\n    en_flx.ρaθ_liq_cv = FT(0)\n    en_flx.ρaq_tot_cv = FT(0)\n    en_flx.ρaθ_liq_q_tot_cv = FT(0)\n\nend;\n"
  },
  {
    "path": "test/Atmos/EDMF/edmf_model.jl",
    "content": "#### EDMF model\nusing DocStringExtensions\nusing CLIMAParameters: AbstractEarthParameterSet\nusing CLIMAParameters.Atmos.EDMF\nusing CLIMAParameters.SubgridScale\n\n\"\"\"\n    EntrainmentDetrainment\n\nAn Entrainment-Detrainment model for EDMF, containing\nall related model and free parameters.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nBase.@kwdef struct EntrainmentDetrainment{FT <: AbstractFloat}\n    \"Entrainment TKE scale\"\n    c_λ::FT\n    \"Entrainment factor\"\n    c_ε::FT\n    \"Detrainment factor\"\n    c_δ::FT\n    \"Turbulent Entrainment factor\"\n    c_t::FT\n    \"Detrainment RH power\"\n    β::FT\n    \"Logistic function scale ‵[1/s]‵\"\n    μ_0::FT\n    \"Updraft mixing fraction\"\n    χ::FT\n    \"Minimum updraft velocity\"\n    w_min::FT\n    \"Exponential area limiter scale\"\n    lim_ϵ::FT\n    \"Exponential area limiter amplitude\"\n    lim_amp::FT\nend\n\n\"\"\"\n    EntrainmentDetrainment{FT}(param_set) where {FT}\n\nConstructor for `EntrainmentDetrainment` for EDMF, given:\n - `param_set`, an AbstractEarthParameterSet\n\"\"\"\nfunction EntrainmentDetrainment{FT}(\n    param_set::AbstractEarthParameterSet,\n) where {FT}\n    c_λ_ = c_λ(param_set)\n    c_ε_ = c_ε(param_set)\n    c_δ_ = c_δ(param_set)\n    c_t_ = c_t(param_set)\n    β_ = β(param_set)\n    μ_0_ = μ_0(param_set)\n    χ_ = χ(param_set)\n    w_min_ = w_min(param_set)\n    lim_ϵ_ = lim_ϵ(param_set)\n    lim_amp_ = lim_amp(param_set)\n\n    args = (c_λ_, c_ε_, c_δ_, c_t_, β_, μ_0_, χ_, w_min_, lim_ϵ_, lim_amp_)\n\n    return EntrainmentDetrainment{FT}(args...)\nend\n\n\"\"\"\n    SubdomainModel\n\nA subdomain model for EDMF, containing\nall related model and free parameters.\n\nTODO: `a_max` is valid for all subdomains,\n    but it is insufficient to ensure `a_en`\n    is not negative. Limits can be imposed\n    for updrafts, but this is a limit\n    is dictated by 1 - Σᵢ aᵢ, which must be\n    somehow satisfied by regularizing prognostic\n    source terms.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nBase.@kwdef struct SubdomainModel{FT <: AbstractFloat}\n    \"Minimum area fraction for any subdomain\"\n    a_min::FT\n    \"Maximum area fraction for any subdomain\"\n    a_max::FT\nend\n\nfunction SubdomainModel(\n    ::Type{FT},\n    N_up;\n    a_min::FT = FT(0),\n    a_max::FT = 1 - N_up * a_min,\n) where {FT}\n    return SubdomainModel(; a_min = a_min, a_max = a_max)\nend\n\n\"\"\"\n    SurfaceModel\n\nA surface model for EDMF, containing all boundary\nvalues and parameters needed by the model.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nBase.@kwdef struct SurfaceModel{FT <: AbstractFloat, SV}\n    \"Area\"\n    a::FT\n    \"Surface covariance stability coefficient\"\n    ψϕ_stab::FT\n    \"Square ratio of rms turbulent velocity to friction velocity\"\n    κ_star²::FT\n    \"Updraft normalized standard deviation at the surface\"\n    upd_surface_std::SV\n    # The following will be deleted after SurfaceFlux coupling\n    \"Liquid water potential temperature ‵[k]‵\"\n    θ_liq::FT = 299.1\n    \"Specific humidity ‵[kg/kg]‵\"\n    q_tot::FT = 22.45e-3\n    \"Sensible heat flux ‵[w/m^2]‵\"\n    shf::FT = 9.5\n    \"Latent heat flux ‵[w/m^2]‵\"\n    lhf::FT = 147.2\n    \"Friction velocity\"\n    ustar::FT = 0.28\n    \"Monin - Obukhov length\"\n    obukhov_length::FT = 0\n    \"Height of the lowest level\"\n    zLL::FT = 60\nend\n\n\"\"\"\n    SurfaceModel{FT}(N_up, param_set) where {FT}\n\nConstructor for `SurfaceModel` for EDMF, given:\n - `N_up`, the number of updrafts\n - `param_set`, an AbstractEarthParameterSet\n\"\"\"\nfunction SurfaceModel{FT}(N_up, param_set::AbstractEarthParameterSet) where {FT}\n    a_surf_ = a_surf(param_set)\n    κ_star²_ = κ_star²(param_set)\n    ψϕ_stab_ = ψϕ_stab(param_set)\n\n    if a_surf_ > FT(0)\n        upd_surface_std = SVector(\n            ntuple(N_up) do i\n                percentile_bounds_mean_norm(\n                    1 - a_surf_ + (i - 1) * FT(a_surf_ / N_up),\n                    1 - a_surf_ + i * FT(a_surf_ / N_up),\n                    1000,\n                )\n            end,\n        )\n    else\n        upd_surface_std = SVector(ntuple(i -> FT(0), N_up))\n    end\n    SV = typeof(upd_surface_std)\n    return SurfaceModel{FT, SV}(;\n        upd_surface_std = upd_surface_std,\n        a = a_surf_,\n        κ_star² = κ_star²_,\n        ψϕ_stab = ψϕ_stab_,\n    )\nend\n\n\"\"\"\n    NeutralDrySurfaceModel\n\nA surface model for EDMF simulations in a\ndry, neutral environment, containing all boundary\nvalues and parameters needed by the model.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nBase.@kwdef struct NeutralDrySurfaceModel{FT <: AbstractFloat}\n    \"Area\"\n    a::FT\n    \"Square ratio of rms turbulent velocity to friction velocity\"\n    κ_star²::FT\n    \"Friction velocity\"\n    ustar::FT = 0.3\n    \"Height of the lowest level\"\n    zLL::FT = 60\n    \"Monin - Obukhov length\"\n    obukhov_length::FT = 0\nend\n\n\"\"\"\n    NeutralDrySurfaceModel{FT}(N_up, param_set) where {FT}\n\nConstructor for `NeutralDrySurfaceModel` for EDMF, given:\n - `param_set`, an AbstractEarthParameterSet\n\"\"\"\nfunction NeutralDrySurfaceModel{FT}(\n    param_set::AbstractEarthParameterSet,\n) where {FT}\n    a_surf_ = a_surf(param_set)\n    κ_star²_ = κ_star²(param_set)\n    return NeutralDrySurfaceModel{FT}(; a = a_surf_, κ_star² = κ_star²_)\nend\n\n\"\"\"\n    PressureModel\n\nA pressure model for EDMF, containing\nall related model and free parameters.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nBase.@kwdef struct PressureModel{FT <: AbstractFloat}\n    \"Pressure drag\"\n    α_d::FT\n    \"Pressure advection\"\n    α_a::FT\n    \"Pressure buoyancy\"\n    α_b::FT\n    \"Minimum diagnostic updraft height for closures\"\n    H_up_min::FT\nend\n\n\"\"\"\n    PressureModel{FT}(param_set) where {FT}\n\nConstructor for `PressureModel` for EDMF, given:\n - `param_set`, an AbstractEarthParameterSet\n\"\"\"\nfunction PressureModel{FT}(param_set::AbstractEarthParameterSet) where {FT}\n    α_d_ = α_d(param_set)\n    α_a_ = α_a(param_set)\n    α_b_ = α_b(param_set)\n    H_up_min_ = H_up_min(param_set)\n\n    args = (α_d_, α_a_, α_b_, H_up_min_)\n\n    return PressureModel{FT}(args...)\nend\n\n\"\"\"\n    MixingLengthModel\n\nA mixing length model for EDMF, containing\nall related model and free parameters.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nBase.@kwdef struct MixingLengthModel{FT <: AbstractFloat}\n    \"dissipation coefficient\"\n    c_d::FT\n    \"Eddy Viscosity\"\n    c_m::FT\n    \"Static Stability coefficient\"\n    c_b::FT\n    \"Empirical stability function coefficient\"\n    a1::FT\n    \"Empirical stability function coefficient\"\n    a2::FT\n    \"Von Karmen constant\"\n    κ::FT\n    \"Prandtl number empirical coefficient\"\n    ω_pr::FT\n    \"Prandtl number scale\"\n    Pr_n::FT\n    \"Critical Richardson number\"\n    Ri_c::FT\n    \"smooth minimum's fractional upper bound\"\n    smin_ub::FT\n    \"smooth minimum's regularization minimum\"\n    smin_rm::FT\n    \"Maximum mixing length\"\n    max_length::FT\n    \"Random small number variable that should be addressed\"\n    random_minval::FT\nend\n\n\"\"\"\n    MixingLengthModel{FT}(param_set) where {FT}\n\nConstructor for `MixingLengthModel` for EDMF, given:\n - `param_set`, an AbstractEarthParameterSet\n\"\"\"\nfunction MixingLengthModel{FT}(param_set::AbstractEarthParameterSet) where {FT}\n    c_d_ = c_d(param_set)\n    c_m_ = c_m(param_set)\n    c_b_ = c_b(param_set)\n    a1_ = a1(param_set)\n    a2_ = a2(param_set)\n    κ = von_karman_const(param_set)\n    ω_pr_ = ω_pr(param_set)\n    Pr_n_ = Pr_n(param_set)\n    Ri_c_ = Ri_c(param_set)\n    smin_ub_ = smin_ub(param_set)\n    smin_rm_ = smin_rm(param_set)\n    max_length = 1e6\n    random_minval = 1e-9\n\n    args = (\n        c_d_,\n        c_m_,\n        c_b_,\n        a1_,\n        a2_,\n        κ,\n        ω_pr_,\n        Pr_n_,\n        Ri_c_,\n        smin_ub_,\n        smin_rm_,\n        max_length,\n        random_minval,\n    )\n\n    return MixingLengthModel{FT}(args...)\nend\n\nabstract type AbstractStatisticalModel end\nstruct SubdomainMean <: AbstractStatisticalModel end\nstruct GaussQuad <: AbstractStatisticalModel end\nstruct LogNormalQuad <: AbstractStatisticalModel end\n\n\"\"\"\n    MicrophysicsModel\n\nA microphysics model for EDMF, containing\nall related model and free parameters and\nassumed subdomain distributions.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nBase.@kwdef struct MicrophysicsModel{FT <: AbstractFloat, SM}\n    \"Subdomain statistical model\"\n    statistical_model::SM\nend\n\n\"\"\"\n    MicrophysicsModel(\n        FT;\n        statistical_model = SubdomainMean()\n    )\n\nConstructor for `MicrophysicsModel` for EDMF, given:\n - `FT`, the float type used\n - `statistical_model`, the assumed environmental distribution\n            of thermodynamic variables.\n\"\"\"\nfunction MicrophysicsModel(FT; statistical_model = SubdomainMean())\n    args = (statistical_model,)\n    return MicrophysicsModel{FT, typeof(statistical_model)}(args...)\nend\n\n\"\"\"\n    Environment <: BalanceLaw\nA `BalanceLaw` for the environment subdomain arising in EDMF.\n\"\"\"\nBase.@kwdef struct Environment{FT <: AbstractFloat, N_quad} <: BalanceLaw end\n\n\"\"\"\n    Updraft <: BalanceLaw\nA `BalanceLaw` for the updraft subdomains arising in EDMF.\n\"\"\"\nBase.@kwdef struct Updraft{FT <: AbstractFloat} <: BalanceLaw end\n\nabstract type Coupling end\n\n\"\"\"\n    Decoupled <: Coupling\n\nDispatch on decoupled model (default)\n\n - The EDMF SGS tendencies do not modify the grid-mean equations.\n\"\"\"\nstruct Decoupled <: Coupling end\n\n\"\"\"\n    Coupled <: Coupling\n\nDispatch on coupled model\n\n - The EDMF SGS tendencies modify the grid-mean equations.\n\"\"\"\nstruct Coupled <: Coupling end\n\n\"\"\"\n    EDMF <: TurbulenceConvectionModel\n\nA turbulence convection model for the EDMF\nscheme, containing all closure models and\nfree parameters.\n\n# Fields\n$(DocStringExtensions.FIELDS)\n\"\"\"\nBase.@kwdef struct EDMF{\n    FT <: AbstractFloat,\n    N,\n    UP,\n    EN,\n    ED,\n    P,\n    S,\n    MP,\n    ML,\n    SD,\n    C,\n} <: TurbulenceConvectionModel\n    \"Updrafts\"\n    updraft::UP\n    \"Environment\"\n    environment::EN\n    \"Entrainment-Detrainment model\"\n    entr_detr::ED\n    \"Pressure model\"\n    pressure::P\n    \"Surface model\"\n    surface::S\n    \"Microphysics model\"\n    micro_phys::MP\n    \"Mixing length model\"\n    mix_len::ML\n    \"Subdomain model\"\n    subdomains::SD\n    \"Coupling mode\"\n    coupling::C\nend\n\n\"\"\"\n    EDMF(\n        FT, N_up, N_quad, param_set;\n        updraft = ntuple(i -> Updraft{FT}(), N_up),\n        environment = Environment{FT, N_quad}(),\n        entr_detr = EntrainmentDetrainment{FT}(param_set),\n        pressure = PressureModel{FT}(param_set),\n        surface = SurfaceModel{FT}(N_up, param_set),\n        micro_phys = MicrophysicsModel(FT),\n        mix_len = MixingLengthModel{FT}(param_set),\n        subdomain = SubdomainModel(FT, N_up),\n    )\nConstructor for `EDMF` subgrid-scale scheme, given:\n - `FT`, the AbstractFloat type used\n - `N_up`, the number of updrafts\n - `N_quad`, the quadrature order. `N_quad^2` is\n        the total number of quadrature points\n        used for environmental distributions.\n - `updraft`, a tuple containing N_up updraft BalanceLaws\n - `environment`, the environment BalanceLaw\n - `entr_detr`, an `EntrainmentDetrainment` model\n - `pressure`, a `PressureModel`\n - `surface`, a `SurfaceModel`\n - `micro_phys`, a `MicrophysicsModel`\n - `mix_len`, a `MixingLengthModel`\n - `subdomain`, a `SubdomainModel`\n - `coupling`, a coupling type\n\"\"\"\nfunction EDMF(\n    FT,\n    N_up,\n    N_quad,\n    param_set;\n    updraft = ntuple(i -> Updraft{FT}(), N_up),\n    environment = Environment{FT, N_quad}(),\n    entr_detr = EntrainmentDetrainment{FT}(param_set),\n    pressure = PressureModel{FT}(param_set),\n    surface = SurfaceModel{FT}(N_up, param_set),\n    micro_phys = MicrophysicsModel(FT),\n    mix_len = MixingLengthModel{FT}(param_set),\n    subdomain = SubdomainModel(FT, N_up),\n    coupling = Decoupled(),\n)\n    args = (\n        updraft,\n        environment,\n        entr_detr,\n        pressure,\n        surface,\n        micro_phys,\n        mix_len,\n        subdomain,\n        coupling,\n    )\n    return EDMF{FT, N_up, typeof.(args)...}(args...)\nend\n\n\nimport ClimateMachine.TurbulenceConvection: turbconv_sources, turbconv_bcs\n\n\"\"\"\n    EDMFTopBC <: TurbConvBC\n\nBoundary conditions for the top of the EDMF.\n\"\"\"\nstruct EDMFTopBC <: TurbConvBC end\n\n\"\"\"\n    EDMFBottomBC <: TurbConvBC\n\nBoundary conditions for the bottom of the EDMF.\n\"\"\"\nstruct EDMFBottomBC <: TurbConvBC end\n\n\nn_updrafts(m::EDMF{FT, N_up}) where {FT, N_up} = N_up\nn_updrafts(m::TurbulenceConvectionModel) = 0\nturbconv_filters(m::TurbulenceConvectionModel) = ()\nturbconv_filters(m::EDMF) = (\n    \"turbconv.environment.ρatke\",\n    \"turbconv.environment.ρaθ_liq_cv\",\n    \"turbconv.environment.ρaq_tot_cv\",\n    \"turbconv.updraft\",\n)\nn_quad_points(m::Environment{FT, N_quad}) where {FT, N_quad} = N_quad\nturbconv_sources(m::EDMF) = ()\nturbconv_bcs(::EDMF) = (EDMFBottomBC(), EDMFTopBC())\n"
  },
  {
    "path": "test/Atmos/EDMF/ekman_layer.jl",
    "content": "#!/usr/bin/env julia --project\n#=\n# This driver file simulates the ekman_layer_model.jl in a single column setting.\n# \n# The user may select in main() the following configurations:\n# - DG or FV vertical discretization by changing the boolean `finite_volume`,\n# - Compressible() or Anelastic1D() changing the compressibility,\n# - Constant kinematic viscosity, Smagorinsky-Lilly or EDMF SGS fluxes.\n#\n# The default is DG, Anelastic1D(), constant kinematic viscosity of 0.1. \n#\n=#\n\nusing JLD2, FileIO\nusing ClimateMachine\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.Checkpoint\nusing ClimateMachine.BalanceLaws: vars_state\nusing ClimateMachine.Atmos\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\nimport CLIMAParameters\nimport ClimateMachine.DGMethods.FVReconstructions: FVLinear\n\ninclude(joinpath(clima_dir, \"experiments\", \"AtmosLES\", \"ekman_layer_model.jl\"))\ninclude(joinpath(\"helper_funcs\", \"diagnostics_configuration.jl\"))\ninclude(\"edmf_model.jl\")\ninclude(\"edmf_kernels.jl\")\n\nCLIMAParameters.Planet.T_surf_ref(::EarthParameterSet) = 290.0\nCLIMAParameters.Atmos.EDMF.a_surf(::EarthParameterSet) = 0.0\nfunction set_clima_parameters(filename)\n    eval(:(include($filename)))\nend\n\n\"\"\"\n    init_state_prognostic!(\n            turbconv::EDMF{FT},\n            m::AtmosModel{FT},\n            state::Vars,\n            aux::Vars,\n            localgeo,\n            t::Real,\n        ) where {FT}\n\nInitialize EDMF state variables if turbconv=EDMF(...) is selected.\nThis method is only called at `t=0`.\n\"\"\"\nfunction init_state_prognostic!(\n    turbconv::EDMF{FT},\n    m::AtmosModel{FT},\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t::Real,\n) where {FT}\n    # Aliases:\n    gm = state\n    en = state.turbconv.environment\n    up = state.turbconv.updraft\n    N_up = n_updrafts(turbconv)\n    z = altitude(m, aux)\n\n    param_set = parameter_set(m)\n    ts = new_thermo_state(m, state, aux)\n    θ_liq = liquid_ice_pottemp(ts)\n\n    a_min = turbconv.subdomains.a_min\n    @unroll_map(N_up) do i\n        up[i].ρa = gm.ρ * a_min\n        up[i].ρaw = gm.ρu[3] * a_min\n        up[i].ρaθ_liq = gm.ρ * a_min * θ_liq\n        up[i].ρaq_tot = FT(0)\n    end\n\n    en.ρatke =\n        z > FT(250) ? FT(0) :\n        gm.ρ *\n        FT(0.3375) *\n        FT(1 - z / 250.0) *\n        FT(1 - z / 250.0) *\n        FT(1 - z / 250.0)\n    en.ρaθ_liq_cv = FT(0)\n    en.ρaq_tot_cv = FT(0)\n    en.ρaθ_liq_q_tot_cv = FT(0)\n    return nothing\nend;\n\nfunction main(::Type{FT}, cl_args) where {FT}\n    # Change boolean to control vertical discretization type\n    finite_volume = false\n\n    # Choice of compressibility and CFL\n    # compressibility = Compressible()\n    compressibility = Anelastic1D()\n    str_comp = compressibility == Compressible() ? \"COMPRESS\" : \"ANELASTIC\"\n\n    # Choice of SGS model\n    # turbconv = NoTurbConv()\n    N_updrafts = 1\n    N_quad = 3\n    turbconv = EDMF(\n        FT,\n        N_updrafts,\n        N_quad,\n        param_set,\n        surface = NeutralDrySurfaceModel{FT}(param_set),\n    )\n\n    C_smag_ = C_smag(param_set)\n    turbulence = ConstantKinematicViscosity(FT(0.1))\n    # turbulence = SmagorinskyLilly{FT}(C_smag_)\n\n    # Prescribe domain parameters\n    zmax = FT(400)\n    # Simulation time\n    t0 = FT(0)\n    timeend = FT(3600 * 2) # Change to 7h for low-level jet\n    CFLmax = compressibility == Compressible() ? FT(1) : FT(100)\n\n    config_type = SingleStackConfigType\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK144NiegemannDiehlBusch,\n    )\n\n    if finite_volume\n        N = (1, 0)\n        nelem_vert = 80\n        ref_state = HydrostaticState(\n            DryAdiabaticProfile{FT}(param_set),\n            ;\n            subtract_off = false,\n        )\n        output_prefix = string(\"EL_\", str_comp, \"_FVM\")\n        fv_reconstruction = FVLinear()\n    else\n        N = 4\n        nelem_vert = 20\n        ref_state = HydrostaticState(DryAdiabaticProfile{FT}(param_set),)\n        output_prefix = string(\"EL_\", str_comp, \"_DG\")\n        fv_reconstruction = nothing\n    end\n\n    surface_flux = cl_args[\"surface_flux\"]\n    model = ekman_layer_model(\n        FT,\n        config_type,\n        zmax,\n        surface_flux;\n        turbulence = turbulence,\n        turbconv = turbconv,\n        ref_state = ref_state,\n        compressibility = compressibility,\n    )\n    # Assemble configuration\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        output_prefix,\n        N,\n        nelem_vert,\n        zmax,\n        param_set,\n        model;\n        hmax = FT(40),\n        fv_reconstruction = fv_reconstruction,\n    )\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFLmax,\n    )\n\n    # --- Zero-out horizontal variations:\n    vsp = vars_state(model, Prognostic(), FT)\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.Q,\n        varsindex(vsp, :turbconv),\n    )\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.Q,\n        varsindex(vsp, :energy, :ρe),\n    )\n    vsa = vars_state(model, Auxiliary(), FT)\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.dg.state_auxiliary,\n        varsindex(vsa, :turbconv),\n    )\n    # ---\n\n    dgn_config = config_diagnostics(driver_config)\n\n    # boyd vandeven filter\n    cb_boyd = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            AtmosSpecificFilterPerturbations(driver_config.bl),\n            solver_config.dg.grid,\n            BoydVandevenFilter(solver_config.dg.grid, 1, 4);\n            state_auxiliary = solver_config.dg.state_auxiliary,\n        )\n        nothing\n    end\n\n    diag_arr = [single_stack_diagnostics(solver_config)]\n    time_data = FT[0]\n\n    # Define the number of outputs from `t0` to `timeend`\n    n_outputs = 5\n    # This equates to exports every ceil(Int, timeend/n_outputs) time-step:\n    every_x_simulation_time = ceil(Int, timeend / n_outputs)\n\n    cb_data_vs_time =\n        GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n            diag_vs_z = single_stack_diagnostics(solver_config)\n\n            nstep = getsteps(solver_config.solver)\n\n            push!(diag_arr, diag_vs_z)\n            push!(time_data, gettime(solver_config.solver))\n            nothing\n        end\n\n    # Mass tendencies = 0 for Anelastic1D model,\n    # so mass should be completely conserved:\n    Δρ_lim = compressibility == Compressible() ? FT(0.001) : FT(0.00000001)\n    check_cons = (ClimateMachine.ConservationCheck(\"ρ\", \"3000steps\", Δρ_lim),)\n\n    cb_print_step = GenericCallbacks.EveryXSimulationSteps(100) do\n        @show getsteps(solver_config.solver)\n        nothing\n    end\n\n    if !isnothing(cl_args[\"cparam_file\"])\n        ClimateMachine.Settings.output_dir = cl_args[\"cparam_file\"] * \".output\"\n    end\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        check_cons = check_cons,\n        user_callbacks = (cb_boyd, cb_data_vs_time, cb_print_step),\n        check_euclidean_distance = true,\n    )\n\n    diag_vs_z = single_stack_diagnostics(solver_config)\n    push!(diag_arr, diag_vs_z)\n    push!(time_data, gettime(solver_config.solver))\n\n    return solver_config, diag_arr, time_data\nend\n\n# ArgParse in global scope to modify Clima Parameters\nsl_args = ArgParseSettings(autofix_names = true)\nadd_arg_group!(sl_args, \"EkmanLayer\")\n@add_arg_table! sl_args begin\n    \"--cparam-file\"\n    help = \"specify CLIMAParameters file\"\n    arg_type = Union{String, Nothing}\n    default = nothing\n\n    \"--surface-flux\"\n    help = \"specify surface flux for energy and moisture\"\n    metavar = \"prescribed|bulk|custom_sl\"\n    arg_type = String\n    default = \"prescribed\"\nend\n\ncl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = sl_args)\nif !isnothing(cl_args[\"cparam_file\"])\n    filename = cl_args[\"cparam_file\"]\n    set_clima_parameters(filename)\nend\n\nsolver_config, diag_arr, time_data = main(Float64, cl_args)\n\n## Uncomment lines to save output using JLD2\n# output_dir = @__DIR__;\n# mkpath(output_dir);\n# function dons(diag_vs_z)\n#     return Dict(map(keys(first(diag_vs_z))) do k\n#         string(k) => [getproperty(ca, k) for ca in diag_vs_z]\n#     end)\n# end\n# get_dons_arr(diag_arr) = [dons(diag_vs_z) for diag_vs_z in diag_arr]\n# dons_arr = get_dons_arr(diag_arr)\n# println(dons_arr[1].keys)\n# z = get_z(solver_config.dg.grid; rm_dupes = true);\n# save(\n#     string(output_dir, \"/ekman.jld2\"),\n#     \"dons_arr\",\n#     dons_arr,\n#     \"time_data\",\n#     time_data,\n#     \"z\",\n#     z,\n# )\n\nnothing\n"
  },
  {
    "path": "test/Atmos/EDMF/helper_funcs/diagnose_environment.jl",
    "content": "#### Diagnose environment variables\n\n\"\"\"\n    environment_vars(state::Vars, N_up::Int)\n\nA NamedTuple of environment variables\n\"\"\"\nfunction environment_vars(state::Vars, N_up::Int)\n    return (a = environment_area(state, N_up), w = environment_w(state, N_up))\nend\n\n\"\"\"\n    environment_area(\n        state::Vars,\n        N_up::Int,\n    )\n\nReturns the environmental area fraction, given:\n - `state`, state variables\n - `N_up`, number of updrafts\n\"\"\"\nfunction environment_area(state::Vars, N_up::Int)\n    up = state.turbconv.updraft\n    return 1 - sum(vuntuple(i -> up[i].ρa, N_up)) / state.ρ\nend\n\n\"\"\"\n    environment_w(state::Vars, N_up::Int)\n\nReturns the environmental vertical velocity, given:\n - `state`, state variables\n - `N_up`, number of updrafts\n\"\"\"\nfunction environment_w(state::Vars, N_up::Int)\n    ρ_inv = 1 / state.ρ\n    a_en = environment_area(state, N_up)\n    up = state.turbconv.updraft\n    return (state.ρu[3] - sum(vuntuple(i -> up[i].ρaw, N_up))) / a_en * ρ_inv\nend\n\n\"\"\"\n    grid_mean_b(env, a_up, N_up::Int, buoyancy_up, buoyancy_en)\n\nReturns the grid-mean buoyancy with respect to the\nreference state, given:\n - `env`, environment variables\n - `a_up`, updraft area fractions\n - `N_up`, number of updrafts\n - `buoyancy_up`, updraft buoyancies\n - `buoyancy_en`, environment buoyancy\n\"\"\"\nfunction grid_mean_b(env, a_up, N_up::Int, buoyancy_up, buoyancy_en)\n    ∑abuoyancy_up = sum(vuntuple(i -> buoyancy_up[i] * a_up[i], N_up))\n    return env.a * buoyancy_en + ∑abuoyancy_up\nend\n"
  },
  {
    "path": "test/Atmos/EDMF/helper_funcs/diagnostics_configuration.jl",
    "content": "\"\"\"\n    config_diagnostics(driver_config, timeend; interval=nothing)\n\nReturns the state and tendency diagnostic groups\n\"\"\"\nfunction config_diagnostics(driver_config, timeend; interval = nothing)\n    FT = eltype(driver_config.grid)\n    info = driver_config.config_info\n    if interval == nothing\n        interval = \"$(cld(timeend, 2) + 10)ssecs\"\n        #interval = \"10steps\"\n    end\n\n    boundaries = [\n        FT(0) FT(0) FT(0)\n        FT(info.hmax) FT(info.hmax) FT(info.zmax)\n    ]\n    axes = (\n        [FT(1)],\n        [FT(1)],\n        collect(range(boundaries[1, 3], boundaries[2, 3], step = FT(50)),),\n    )\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries;\n        axes = axes,\n    )\n    ds_dgngrp = setup_dump_state_diagnostics(\n        SingleStackConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n    dt_dgngrp = setup_dump_tendencies_diagnostics(\n        SingleStackConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([ds_dgngrp, dt_dgngrp])\nend\n"
  },
  {
    "path": "test/Atmos/EDMF/helper_funcs/lamb_smooth_minimum.jl",
    "content": "using LambertW\n\n\"\"\"\n    lambertw_gpu(N)\n\nReturns `real(LambertW.lambertw(Float64(N - 1) / MathConstants.e))`,\nvalid for `N = 2` and `N = 3`.\n\nTODO: add `LambertW.lambertw` support to KernelAbstractions.\n\"\"\"\nfunction lambertw_gpu(N)\n    if !(N == 2 || N == 3)\n        error(\"Bad N in lambertw_gpu\")\n    end\n    return (0.2784645427610738, 0.46305551336554884)[N - 1]\nend\n\n\"\"\"\n    lamb_smooth_minimum(\n        l::AbstractArray{FT};\n        frac_upper_bound::FT,\n        reg_min::FT,\n    ) where {FT}\n\nReturns the smooth minimum of the elements of an array\nfollowing the formulation of\nLopez-Gomez et al. (JAMES, 2020), Appendix A, given:\n - `l`, an array of candidate elements\n - `frac_upper_bound`, defines the upper bound of the\n        smooth minimum as `smin(x) = min(x)*(1+frac_upper_bound)`\n - `reg_min`, defines the minimum value of the regularizer Λ\n\"\"\"\nfunction lamb_smooth_minimum(\n    l::AbstractArray{FT},\n    frac_upper_bound::FT,\n    reg_min::FT,\n) where {FT}\n\n    xmin = minimum(l)\n\n    # Get regularizer for exponential weights\n    N_l = length(l)\n    denom = FT(lambertw_gpu(N_l))\n    Λ = max(FT(xmin) * frac_upper_bound / denom, reg_min)\n\n    num = sum(i -> l[i] * exp(-(l[i] - xmin) / Λ), 1:N_l)\n    den = sum(i -> exp(-(l[i] - xmin) / Λ), 1:N_l)\n    smin = num / den\n\n    return smin\nend\n"
  },
  {
    "path": "test/Atmos/EDMF/helper_funcs/nondimensional_exchange_functions.jl",
    "content": "\"\"\"\n    nondimensional_exchange_functions(\n        m::AtmosModel{FT},\n        entr::EntrainmentDetrainment,\n        state::Vars,\n        aux::Vars,\n        t::Real,\n        ts_up,\n        ts_en,\n        env,\n        buoy,\n        i,\n    ) where {FT}\n\nReturns the nondimensional entrainment and detrainment\nfunctions following Cohen et al. (JAMES, 2020), given:\n - `m`, an `AtmosModel`\n - `entr`, an `EntrainmentDetrainment` model\n - `state`, state variables\n - `aux`, auxiliary variables\n - `ts_up`, updraft thermodynamic states\n - `ts_en`, environment thermodynamic states\n - `env`, NamedTuple of environment variables\n - `buoy`, NamedTuple of environment and updraft buoyancies\n - `i`, the updraft index\n\"\"\"\nfunction nondimensional_exchange_functions(\n    m::AtmosModel{FT},\n    entr::EntrainmentDetrainment,\n    state::Vars,\n    aux::Vars,\n    ts_up,\n    ts_en,\n    env,\n    buoy,\n    i,\n) where {FT}\n\n    # Alias convention:\n    gm = state\n    up = state.turbconv.updraft\n    up_aux = aux.turbconv.updraft\n    en_aux = aux.turbconv.environment\n\n    # precompute vars\n    w_min = entr.w_min\n    N_up = n_updrafts(turbconv_model(m))\n    ρinv = 1 / gm.ρ\n    a_up_i = up[i].ρa * ρinv\n    w_up_i = fix_void_up(up[i].ρa, up[i].ρaw / up[i].ρa)\n\n    # thermodynamic variables\n    RH_up = relative_humidity(ts_up[i])\n    RH_en = relative_humidity(ts_en)\n\n    Δw = filter_w(w_up_i - env.w, w_min)\n    Δb = buoy.up[i] - buoy.en\n\n    c_δ = sign(condensate(ts_en) + condensate(ts_up[i])) * entr.c_δ\n\n    # compute dry and moist aux functions\n    μ_ij = (entr.χ - a_up_i / (a_up_i + env.a)) * Δb / Δw\n    D_ε = entr.c_ε / (1 + exp(-μ_ij / entr.μ_0))\n    M_ε = c_δ * (max((RH_en^entr.β - RH_up^entr.β), 0))^(1 / entr.β)\n    D_δ = entr.c_ε / (1 + exp(μ_ij / entr.μ_0))\n    M_δ = c_δ * (max((RH_up^entr.β - RH_en^entr.β), 0))^(1 / entr.β)\n    return D_ε, D_δ, M_δ, M_ε\nend;\n"
  },
  {
    "path": "test/Atmos/EDMF/helper_funcs/save_subdomain_temperature.jl",
    "content": "# Convenience wrapper\nsave_subdomain_temperature!(m, state, aux) =\n    save_subdomain_temperature!(m, moisture_model(m), state, aux)\n\nusing KernelAbstractions: @print\n\n\"\"\"\n    save_subdomain_temperature!(\n        m::AtmosModel,\n        moist::EquilMoist,\n        state::Vars,\n        aux::Vars,\n    )\n\nUpdates the subdomain sensible temperature, given:\n - `m`, an `AtmosModel`\n - `moist`, an `EquilMoist` model\n - `state`, state variables\n - `aux`, auxiliary variables\n\"\"\"\nfunction save_subdomain_temperature!(\n    m::AtmosModel,\n    moist::EquilMoist,\n    state::Vars,\n    aux::Vars,\n)\n    N_up = n_updrafts(turbconv_model(m))\n    ts = recover_thermo_state(m, state, aux)\n    ts_up = new_thermo_state_up(m, state, aux, ts)\n    ts_en = new_thermo_state_en(m, state, aux, ts)\n\n    @unroll_map(N_up) do i\n        aux.turbconv.updraft[i].T = air_temperature(ts_up[i])\n    end\n    aux.turbconv.environment.T = air_temperature(ts_en)\n    return nothing\nend\n\n# No need to save temperature for DryModel.\nfunction save_subdomain_temperature!(\n    m::AtmosModel,\n    moist::DryModel,\n    state::Vars,\n    aux::Vars,\n) end\n"
  },
  {
    "path": "test/Atmos/EDMF/helper_funcs/subdomain_statistics.jl",
    "content": "#### Subdomain statistics\n\nfunction compute_subdomain_statistics(m::AtmosModel, args, ts_gm, ts_en)\n    turbconv = turbconv_model(m)\n    return compute_subdomain_statistics(\n        turbconv.micro_phys.statistical_model,\n        m,\n        args,\n        ts_gm,\n        ts_en,\n    )\nend\n\n\"\"\"\n    compute_subdomain_statistics(\n        statistical_model::SubdomainMean,\n        m::AtmosModel{FT},\n        args,\n        ts_gm,\n        ts_en,\n    ) where {FT}\n\nReturns a cloud fraction and cloudy and dry thermodynamic\nstates in the subdomain.\n\"\"\"\nfunction compute_subdomain_statistics(\n    statistical_model::SubdomainMean,\n    m::AtmosModel{FT},\n    args,\n    ts_gm,\n    ts_en,\n) where {FT}\n    cloud_frac = has_condensate(ts_en) ? FT(1) : FT(0)\n    dry = ts_en\n    cloudy = ts_en\n    return (dry = dry, cloudy = cloudy, cloud_frac = cloud_frac)\nend\n"
  },
  {
    "path": "test/Atmos/EDMF/helper_funcs/subdomain_thermo_states.jl",
    "content": "#### thermo states for subdomains\n\nusing KernelAbstractions: @print\n\nexport new_thermo_state_up,\n    new_thermo_state_en,\n    recover_thermo_state_all,\n    recover_thermo_state_up,\n    recover_thermo_state_en\n\n####\n#### Interface\n####\n\n\"\"\"\n    new_thermo_state_up(bl, state, aux)\n\nUpdraft thermodynamic states given:\n - `bl`, parent `BalanceLaw`\n - `state`, state variables\n - `aux`, auxiliary variables\n\n!!! note\n    This method calls saturation adjustment for\n    EquilMoist models.\n\"\"\"\nnew_thermo_state_up(\n    bl::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    ts::ThermodynamicState = recover_thermo_state(bl, state, aux),\n) = new_thermo_state_up(bl, moisture_model(bl), state, aux, ts)\n\n\"\"\"\n    new_thermo_state_en(bl, state, aux)\n\nEnvironment thermodynamic state given:\n - `bl`, parent `BalanceLaw`\n - `state`, state variables\n - `aux`, auxiliary variables\n\n!!! note\n    This method calls saturation adjustment for\n    EquilMoist models.\n\"\"\"\nnew_thermo_state_en(\n    bl::AtmosModel,\n    state::Vars,\n    aux::Vars,\n    ts::ThermodynamicState = recover_thermo_state(bl, state, aux),\n) = new_thermo_state_en(bl, moisture_model(bl), state, aux, ts)\n\n\"\"\"\n    recover_thermo_state_all(bl, state, aux)\n\nRecover NamedTuple of all thermo states\n\n# TODO: Define/call `recover_thermo_state` when it's safely implemented\n  (see https://github.com/CliMA/ClimateMachine.jl/issues/1648)\n\"\"\"\nfunction recover_thermo_state_all(bl, state, aux)\n    ts = new_thermo_state(bl, state, aux)\n    return (\n        gm = ts,\n        en = new_thermo_state_en(bl, moisture_model(bl), state, aux, ts),\n        up = new_thermo_state_up(bl, moisture_model(bl), state, aux, ts),\n    )\nend\n\n\"\"\"\n    recover_thermo_state_up(bl, state, aux, ts = new_thermo_state(bl, state, aux))\n\nRecover the updraft thermodynamic states given:\n - `bl`, parent `BalanceLaw`\n - `state`, state variables\n - `aux`, auxiliary variables\n\n!!! warn\n    Right now we are directly calling new_thermo_state_up to avoid\n    inconsistent aux states in kernels where the aux states are\n    out of sync with the boundary state.\n\n# TODO: Define/call `recover_thermo_state` when it's safely implemented\n  (see https://github.com/CliMA/ClimateMachine.jl/issues/1648)\n\"\"\"\nfunction recover_thermo_state_up(\n    bl,\n    state,\n    aux,\n    ts = new_thermo_state(bl, state, aux),\n)\n    return new_thermo_state_up(bl, moisture_model(bl), state, aux, ts)\nend\n\n\"\"\"\n    recover_thermo_state_en(bl, state, aux, ts = recover_thermo_state(bl, state, aux))\n\nRecover the environment thermodynamic state given:\n - `bl`, parent `BalanceLaw`\n - `state`, state variables\n - `aux`, auxiliary variables\n\n!!! warn\n    Right now we are directly calling new_thermo_state_up to avoid\n    inconsistent aux states in kernels where the aux states are\n    out of sync with the boundary state.\n\n# TODO: Define/call `recover_thermo_state` when it's safely implemented\n  (see https://github.com/CliMA/ClimateMachine.jl/issues/1648)\n\"\"\"\nfunction recover_thermo_state_en(\n    bl,\n    state,\n    aux,\n    ts = new_thermo_state(bl, state, aux),\n)\n    return new_thermo_state_en(bl, moisture_model(bl), state, aux, ts)\nend\n\n####\n#### Implementation\n####\n\nfunction new_thermo_state_up(\n    m::AtmosModel{FT},\n    moist::DryModel,\n    state::Vars,\n    aux::Vars,\n    ts::ThermodynamicState,\n) where {FT}\n    N_up = n_updrafts(turbconv_model(m))\n    up = state.turbconv.updraft\n    p = air_pressure(ts)\n    param_set = parameter_set(m)\n    # compute thermo state for updrafts\n    ts_up = vuntuple(N_up) do i\n        ρa_up = up[i].ρa\n        ρaθ_liq_up = up[i].ρaθ_liq\n        θ_liq_up =\n            fix_void_up(ρa_up, ρaθ_liq_up / ρa_up, liquid_ice_pottemp(ts))\n        PhaseDry_pθ(param_set, p, θ_liq_up)\n    end\n    return ts_up\nend\n\nfunction new_thermo_state_up(\n    m::AtmosModel{FT},\n    moist::EquilMoist,\n    state::Vars,\n    aux::Vars,\n    ts::ThermodynamicState,\n) where {FT}\n\n    N_up = n_updrafts(turbconv_model(m))\n    up = state.turbconv.updraft\n    p = air_pressure(ts)\n    param_set = parameter_set(m)\n    # compute thermo state for updrafts\n    ts_up = vuntuple(N_up) do i\n        ρa_up = up[i].ρa\n        ρaθ_liq_up = up[i].ρaθ_liq\n        ρaq_tot_up = up[i].ρaq_tot\n        θ_liq_up =\n            fix_void_up(ρa_up, ρaθ_liq_up / ρa_up, liquid_ice_pottemp(ts))\n        q_tot_up = fix_void_up(\n            ρa_up,\n            ρaq_tot_up / ρa_up,\n            total_specific_humidity(ts),\n        )\n        PhaseEquil_pθq(param_set, p, θ_liq_up, q_tot_up)\n    end\n    return ts_up\nend\n\nfunction new_thermo_state_en(\n    m::AtmosModel,\n    moist::DryModel,\n    state::Vars,\n    aux::Vars,\n    ts::ThermodynamicState,\n)\n    turbconv = turbconv_model(m)\n    N_up = n_updrafts(turbconv)\n    up = state.turbconv.updraft\n\n    # diagnose environment thermo state\n    ρ_inv = 1 / state.ρ\n    p = air_pressure(ts)\n    θ_liq = liquid_ice_pottemp(ts)\n    a_en = environment_area(state, N_up)\n    ρaθ_liq_up = vuntuple(N_up) do i\n        fix_void_up(up[i].ρa, up[i].ρaθ_liq)\n    end\n    θ_liq_en = (θ_liq - sum(vuntuple(j -> ρaθ_liq_up[j] * ρ_inv, N_up))) / a_en\n    a_min = turbconv.subdomains.a_min\n    a_max = turbconv.subdomains.a_max\n    param_set = parameter_set(m)\n    if !(0 <= θ_liq_en)\n        @print(\"ρaθ_liq_up = \", ρaθ_liq_up[Val(1)], \"\\n\")\n        @print(\"θ_liq = \", θ_liq, \"\\n\")\n        @print(\"θ_liq_en = \", θ_liq_en, \"\\n\")\n        error(\"Environment θ_liq_en out-of-bounds in new_thermo_state_en\")\n    end\n    ts_en = PhaseDry_pθ(param_set, p, θ_liq_en)\n    return ts_en\nend\n\nfunction new_thermo_state_en(\n    m::AtmosModel,\n    moist::EquilMoist,\n    state::Vars,\n    aux::Vars,\n    ts::ThermodynamicState,\n)\n    turbconv = turbconv_model(m)\n    N_up = n_updrafts(turbconv)\n    up = state.turbconv.updraft\n\n    # diagnose environment thermo state\n    ρ_inv = 1 / state.ρ\n    p = air_pressure(ts)\n    θ_liq = liquid_ice_pottemp(ts)\n    q_tot = total_specific_humidity(ts)\n    a_en = environment_area(state, N_up)\n    θ_liq_en = (θ_liq - sum(vuntuple(j -> up[j].ρaθ_liq * ρ_inv, N_up))) / a_en\n    q_tot_en = (q_tot - sum(vuntuple(j -> up[j].ρaq_tot * ρ_inv, N_up))) / a_en\n    a_min = turbconv.subdomains.a_min\n    a_max = turbconv.subdomains.a_max\n    param_set = parameter_set(m)\n    if !(0 <= θ_liq_en)\n        @print(\"θ_liq_en = \", θ_liq_en, \"\\n\")\n        error(\"Environment θ_liq_en out-of-bounds in new_thermo_state_en\")\n    end\n    if !(0 <= q_tot_en <= 1)\n        @print(\"q_tot_en = \", q_tot_en, \"\\n\")\n        error(\"Environment q_tot_en out-of-bounds in new_thermo_state_en\")\n    end\n    ts_en = PhaseEquil_pθq(param_set, p, θ_liq_en, q_tot_en)\n    return ts_en\nend\n"
  },
  {
    "path": "test/Atmos/EDMF/helper_funcs/utility_funcs.jl",
    "content": "\"\"\"\n    filter_w(w::FT, w_min::FT) where {FT}\n\nReturn velocity such that\n`abs(filter_w(w, w_min)) >= abs(w_min)`\nwhile preserving the sign of `w`.\n\"\"\"\nfilter_w(w::FT, w_min::FT) where {FT} =\n    max(abs(w), abs(w_min)) * (w < 0 ? sign(w) : 1)\n\n\"\"\"\n    enforce_unit_bounds(a_up_i::FT, a_min::FT, a_max::FT) where {FT}\n\nEnforce variable to be positive.\n\nIdeally, this safety net will be removed once we\nhave robust positivity preserving methods. For now,\nwe need this to avoid domain error in certain\ncircumstances.\n\"\"\"\nenforce_unit_bounds(a_up_i::FT, a_min::FT, a_max::FT) where {FT} =\n    clamp(a_up_i, a_min, a_max)\n\n\"\"\"\n    enforce_positivity(x::FT) where {FT}\n\nEnforce variable to be positive.\n\nIdeally, this safety net will be removed once we\nhave robust positivity preserving methods. For now,\nwe need this to avoid domain error in certain\ncircumstances.\n\"\"\"\nenforce_positivity(x::FT) where {FT} = max(x, FT(0))\n\n\"\"\"\n    fix_void_up(ρa_up_i::FT, val::FT, fallback = FT(0)) where {FT}\n\nSubstitute value by a consistent fallback in case of\nnegligible area fraction (void updraft).\n\"\"\"\nfunction fix_void_up(ρa_up_i::FT, val::FT, fallback = FT(0)) where {FT}\n    tol = sqrt(eps(FT))\n    return ρa_up_i > tol ? val : fallback\nend\n"
  },
  {
    "path": "test/Atmos/EDMF/report_mse_bomex.jl",
    "content": "using ClimateMachine\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\n\nif parse(Bool, get(ENV, \"CLIMATEMACHINE_PLOT_EDMF_COMPARISON\", \"false\"))\n    plot_dir = joinpath(clima_dir, \"output\", \"bomex_edmf\", \"pycles_comparison\")\nelse\n    plot_dir = nothing\nend\n\ninclude(joinpath(@__DIR__, \"compute_mse.jl\"))\n\ndata_file = Dataset(joinpath(PyCLES_output_dataset_path, \"Bomex.nc\"), \"r\")\n\n#! format: off\nbest_mse = OrderedDict()\nbest_mse[\"prog_ρ\"] = 3.4936203419421122e-02\nbest_mse[\"prog_ρu_1\"] = 3.0715584660655654e+03\nbest_mse[\"prog_ρu_2\"] = 1.2897432819935302e-03\nbest_mse[\"prog_moisture_ρq_tot\"] = 4.1572900459802775e-02\nbest_mse[\"prog_turbconv_environment_ρatke\"] = 8.5590220998989253e+02\nbest_mse[\"prog_turbconv_environment_ρaθ_liq_cv\"] = 8.5667227621106434e+01\nbest_mse[\"prog_turbconv_environment_ρaq_tot_cv\"] = 1.6437582130429374e+02\nbest_mse[\"prog_turbconv_updraft_1_ρa\"] = 8.0846471185616252e+01\nbest_mse[\"prog_turbconv_updraft_1_ρaw\"] = 8.5071186636845333e-02\nbest_mse[\"prog_turbconv_updraft_1_ρaθ_liq\"] = 9.0386637425608303e+00\nbest_mse[\"prog_turbconv_updraft_1_ρaq_tot\"] = 1.0803377515313473e+01\n#! format: on\n\ncomputed_mse = compute_mse(\n    solver_config.dg.grid,\n    solver_config.dg.balance_law,\n    time_data,\n    dons_arr,\n    data_file,\n    \"Bomex\",\n    best_mse,\n    400,\n    plot_dir,\n)\n\n@testset \"BOMEX EDMF Solution Quality Assurance (QA) tests\" begin\n    #! format: off\n    test_mse(computed_mse, best_mse, \"prog_ρ\")\n    test_mse(computed_mse, best_mse, \"prog_ρu_1\")\n    test_mse(computed_mse, best_mse, \"prog_ρu_2\")\n    test_mse(computed_mse, best_mse, \"prog_moisture_ρq_tot\")\n    test_mse(computed_mse, best_mse, \"prog_turbconv_environment_ρatke\")\n    test_mse(computed_mse, best_mse, \"prog_turbconv_environment_ρaθ_liq_cv\")\n    test_mse(computed_mse, best_mse, \"prog_turbconv_environment_ρaq_tot_cv\")\n    test_mse(computed_mse, best_mse, \"prog_turbconv_updraft_1_ρa\")\n    test_mse(computed_mse, best_mse, \"prog_turbconv_updraft_1_ρaw\")\n    test_mse(computed_mse, best_mse, \"prog_turbconv_updraft_1_ρaθ_liq\")\n    test_mse(computed_mse, best_mse, \"prog_turbconv_updraft_1_ρaq_tot\")\n    #! format: on\nend\n"
  },
  {
    "path": "test/Atmos/EDMF/report_mse_sbl_anelastic.jl",
    "content": "using ClimateMachine\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\n\nif parse(Bool, get(ENV, \"CLIMATEMACHINE_PLOT_EDMF_COMPARISON\", \"false\"))\n    plot_dir = joinpath(clima_dir, \"output\", \"sbl_edmf\", \"pycles_comparison\")\nelse\n    plot_dir = nothing\nend\n\ninclude(joinpath(@__DIR__, \"compute_mse.jl\"))\n\ndata_file = Dataset(joinpath(PyCLES_output_dataset_path, \"Gabls.nc\"), \"r\")\n\n#! format: off\nbest_mse = OrderedDict()\nbest_mse[\"prog_ρ\"] = 9.3809207150296822e-03\nbest_mse[\"prog_ρu_1\"] = 6.7269975116623837e+03\nbest_mse[\"prog_ρu_2\"] = 6.8630628605220889e-01\n#! format: on\n\ncomputed_mse = compute_mse(\n    solver_config.dg.grid,\n    solver_config.dg.balance_law,\n    time_data,\n    dons_arr,\n    data_file,\n    \"Gabls\",\n    best_mse,\n    1800,\n    plot_dir,\n)\n\n@testset \"SBL Anelastic Solution Quality Assurance (QA) tests\" begin\n    #! format: off\n    test_mse(computed_mse, best_mse, \"prog_ρ\")\n    test_mse(computed_mse, best_mse, \"prog_ρu_1\")\n    test_mse(computed_mse, best_mse, \"prog_ρu_2\")\n    #! format: on\nend\n"
  },
  {
    "path": "test/Atmos/EDMF/report_mse_sbl_coupled_edmf_an1d.jl",
    "content": "using ClimateMachine\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\n\nif parse(Bool, get(ENV, \"CLIMATEMACHINE_PLOT_EDMF_COMPARISON\", \"false\"))\n    plot_dir = joinpath(clima_dir, \"output\", \"sbl_edmf\", \"pycles_comparison\")\nelse\n    plot_dir = nothing\nend\n\ninclude(joinpath(@__DIR__, \"compute_mse.jl\"))\n\ndata_file = Dataset(joinpath(PyCLES_output_dataset_path, \"Gabls.nc\"), \"r\")\n\n#! format: off\nbest_mse = OrderedDict()\nbest_mse[\"prog_ρ\"] = 9.3808142287632006e-03\nbest_mse[\"prog_ρu_1\"] = 6.7427140307178524e+03\nbest_mse[\"prog_ρu_2\"] = 7.2306841961112966e-01\nbest_mse[\"prog_turbconv_environment_ρatke\"] = 2.9810806322235749e+02\nbest_mse[\"prog_turbconv_environment_ρaθ_liq_cv\"] = 8.1270487249851797e+01\nbest_mse[\"prog_turbconv_updraft_1_ρa\"] = 2.7223017351724246e+02\nbest_mse[\"prog_turbconv_updraft_1_ρaw\"] = 5.5909371368198686e+02\nbest_mse[\"prog_turbconv_updraft_1_ρaθ_liq\"] = 2.7933498788454813e+02\n#! format: on\n\ncomputed_mse = compute_mse(\n    solver_config.dg.grid,\n    solver_config.dg.balance_law,\n    time_data,\n    dons_arr,\n    data_file,\n    \"Gabls\",\n    best_mse,\n    1800,\n    plot_dir,\n)\n\n@testset \"SBL Coupled EDMF Solution Quality Assurance (QA) tests\" begin\n    #! format: off\n    test_mse(computed_mse, best_mse, \"prog_ρ\")\n    test_mse(computed_mse, best_mse, \"prog_ρu_1\")\n    test_mse(computed_mse, best_mse, \"prog_ρu_2\")\n    test_mse(computed_mse, best_mse, \"prog_turbconv_environment_ρatke\")\n    test_mse(computed_mse, best_mse, \"prog_turbconv_environment_ρaθ_liq_cv\")\n    test_mse(computed_mse, best_mse, \"prog_turbconv_updraft_1_ρa\")\n    test_mse(computed_mse, best_mse, \"prog_turbconv_updraft_1_ρaw\")\n    test_mse(computed_mse, best_mse, \"prog_turbconv_updraft_1_ρaθ_liq\")\n    #! format: on\nend\n"
  },
  {
    "path": "test/Atmos/EDMF/report_mse_sbl_edmf.jl",
    "content": "using ClimateMachine\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\n\nif parse(Bool, get(ENV, \"CLIMATEMACHINE_PLOT_EDMF_COMPARISON\", \"false\"))\n    plot_dir = joinpath(clima_dir, \"output\", \"sbl_edmf\", \"pycles_comparison\")\nelse\n    plot_dir = nothing\nend\n\ninclude(joinpath(@__DIR__, \"compute_mse.jl\"))\n\ndata_file = Dataset(joinpath(PyCLES_output_dataset_path, \"Gabls.nc\"), \"r\")\n\n#! format: off\nbest_mse = OrderedDict()\nbest_mse[\"prog_ρ\"] = 6.8041561724036673e-03\nbest_mse[\"prog_ρu_1\"] = 6.2586216904022822e+03\nbest_mse[\"prog_ρu_2\"] = 1.2965826326450741e-04\nbest_mse[\"prog_turbconv_environment_ρatke\"] = 4.9035225536000081e+02\nbest_mse[\"prog_turbconv_environment_ρaθ_liq_cv\"] = 8.7727377301948991e+01\nbest_mse[\"prog_turbconv_updraft_1_ρa\"] = 1.8213581913998944e+01\nbest_mse[\"prog_turbconv_updraft_1_ρaw\"] = 1.7800899665237452e-01\nbest_mse[\"prog_turbconv_updraft_1_ρaθ_liq\"] = 1.3358308964295857e+01\n#! format: on\n\ncomputed_mse = compute_mse(\n    solver_config.dg.grid,\n    solver_config.dg.balance_law,\n    time_data,\n    dons_arr,\n    data_file,\n    \"Gabls\",\n    best_mse,\n    60,\n    plot_dir,\n)\n\n@testset \"SBL EDMF Solution Quality Assurance (QA) tests\" begin\n    #! format: off\n    test_mse(computed_mse, best_mse, \"prog_ρ\")\n    test_mse(computed_mse, best_mse, \"prog_ρu_1\")\n    test_mse(computed_mse, best_mse, \"prog_ρu_2\")\n    test_mse(computed_mse, best_mse, \"prog_turbconv_environment_ρatke\")\n    test_mse(computed_mse, best_mse, \"prog_turbconv_environment_ρaθ_liq_cv\")\n    test_mse(computed_mse, best_mse, \"prog_turbconv_updraft_1_ρa\")\n    test_mse(computed_mse, best_mse, \"prog_turbconv_updraft_1_ρaw\")\n    test_mse(computed_mse, best_mse, \"prog_turbconv_updraft_1_ρaθ_liq\")\n    #! format: on\nend\n"
  },
  {
    "path": "test/Atmos/EDMF/report_mse_sbl_ss_implicit.jl",
    "content": "using ClimateMachine\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\n\nif parse(Bool, get(ENV, \"CLIMATEMACHINE_PLOT_EDMF_COMPARISON\", \"false\"))\n    plot_dir = joinpath(clima_dir, \"output\", \"sbl_edmf\", \"pycles_comparison\")\nelse\n    plot_dir = nothing\nend\n\ninclude(joinpath(@__DIR__, \"compute_mse.jl\"))\n\ndata_file = Dataset(joinpath(PyCLES_output_dataset_path, \"Gabls.nc\"), \"r\")\n\n#! format: off\nbest_mse = OrderedDict()\nbest_mse[\"prog_ρ\"] = 7.9878085788692155e-03\nbest_mse[\"prog_ρu_1\"] = 3.0923908085978383e+03\nbest_mse[\"prog_ρu_2\"] = 4.4228530597867703e+01\n#! format: on\n\ncomputed_mse = compute_mse(\n    solver_config.dg.grid,\n    solver_config.dg.balance_law,\n    time_data,\n    dons_arr,\n    data_file,\n    \"Gabls\",\n    best_mse,\n    3600 * 6,\n    plot_dir,\n)\n\n@testset \"SBL Implicit Solution Quality Assurance (QA) tests\" begin\n    #! format: off\n    test_mse(computed_mse, best_mse, \"prog_ρ\")\n    test_mse(computed_mse, best_mse, \"prog_ρu_1\")\n    test_mse(computed_mse, best_mse, \"prog_ρu_2\")\n    #! format: on\nend\n"
  },
  {
    "path": "test/Atmos/EDMF/stable_bl_anelastic1d.jl",
    "content": "using JLD2, FileIO\nusing ClimateMachine\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.Checkpoint\nusing ClimateMachine.BalanceLaws: vars_state\nimport ClimateMachine.BalanceLaws: projection\nimport ClimateMachine.DGMethods\nusing ClimateMachine.Atmos\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\nimport CLIMAParameters\n\ninclude(joinpath(clima_dir, \"experiments\", \"AtmosLES\", \"stable_bl_model.jl\"))\ninclude(\"edmf_model.jl\")\ninclude(\"edmf_kernels.jl\")\n\n# CLIMAParameters.Planet.T_surf_ref(::EarthParameterSet) = 290.0 # default\nCLIMAParameters.Planet.T_surf_ref(::EarthParameterSet) = 265\nCLIMAParameters.Atmos.EDMF.a_surf(::EarthParameterSet) = 0.0\nfunction set_clima_parameters(filename)\n    eval(:(include($filename)))\nend\n\n\"\"\"\n    init_state_prognostic!(\n            turbconv::EDMF{FT},\n            m::AtmosModel{FT},\n            state::Vars,\n            aux::Vars,\n            localgeo,\n            t::Real,\n        ) where {FT}\n\nInitialize EDMF state variables.\nThis method is only called at `t=0`.\n\"\"\"\nfunction init_state_prognostic!(\n    turbconv::EDMF{FT},\n    m::AtmosModel{FT},\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t::Real,\n) where {FT}\n    # Aliases:\n    gm = state\n    en = state.turbconv.environment\n    up = state.turbconv.updraft\n    N_up = n_updrafts(turbconv)\n    # GCM setting - Initialize the grid mean profiles of prognostic variables (ρ,e_int,q_tot,u,v,w)\n    z = altitude(m, aux)\n\n    # SCM setting - need to have separate cases coded and called from a folder - see what LES does\n    # a thermo state is used here to convert the input θ to e_int profile\n    e_int = internal_energy(m, state, aux)\n    param_set = parameter_set(m)\n    ts = PhaseDry(param_set, e_int, state.ρ)\n    T = air_temperature(ts)\n    p = air_pressure(ts)\n    q = PhasePartition(ts)\n    θ_liq = liquid_ice_pottemp(ts)\n\n    a_min = turbconv.subdomains.a_min\n    @unroll_map(N_up) do i\n        up[i].ρa = gm.ρ * a_min\n        up[i].ρaw = gm.ρu[3] * a_min\n        up[i].ρaθ_liq = gm.ρ * a_min * θ_liq\n        up[i].ρaq_tot = FT(0)\n    end\n\n    # initialize environment covariance with zero for now\n    if z <= FT(250)\n        en.ρatke =\n            gm.ρ *\n            FT(0.4) *\n            FT(1 - z / 250.0) *\n            FT(1 - z / 250.0) *\n            FT(1 - z / 250.0)\n        en.ρaθ_liq_cv =\n            gm.ρ *\n            FT(0.4) *\n            FT(1 - z / 250.0) *\n            FT(1 - z / 250.0) *\n            FT(1 - z / 250.0)\n    else\n        en.ρatke = FT(0)\n        en.ρaθ_liq_cv = FT(0)\n    end\n    en.ρaq_tot_cv = FT(0)\n    en.ρaθ_liq_q_tot_cv = FT(0)\n    return nothing\nend;\n\nfunction main(::Type{FT}, cl_args) where {FT}\n\n    surface_flux = cl_args[\"surface_flux\"]\n\n    # Choice of compressibility and CFL\n    # compressibility = Compressible()\n    compressibility = Anelastic1D()\n    str_comp = compressibility == Compressible() ? \"COMPRESS\" : \"ANELASTIC\"\n\n    # DG polynomial order\n    N = 4\n    nelem_vert = 20\n\n    # Prescribe domain parameters\n    zmax = FT(400)\n    # Simulation time\n    t0 = FT(0)\n    timeend = FT(1800 * 1)\n    CFLmax = compressibility == Compressible() ? FT(1) : FT(100)\n\n    config_type = SingleStackConfigType\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK144NiegemannDiehlBusch,\n    )\n\n    # Choice of SGS model\n    N_updrafts = 1\n    N_quad = 3\n    turbconv = NoTurbConv()\n    # turbconv = EDMF(\n    #     FT,\n    #     N_updrafts,\n    #     N_quad,\n    #     param_set,\n    #     surface = NeutralDrySurfaceModel{FT}(param_set),\n    # )\n\n    C_smag_ = C_smag(param_set)\n    # turbulence = ConstantKinematicViscosity(FT(0.1))\n    turbulence = SmagorinskyLilly{FT}(C_smag_)\n    model = stable_bl_model(\n        FT,\n        config_type,\n        zmax,\n        surface_flux;\n        turbulence = turbulence,\n        turbconv = turbconv,\n        compressibility = compressibility,\n    )\n\n    # Assemble configuration\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        string(\"SBL_\", str_comp, \"_1D\"),\n        N,\n        nelem_vert,\n        zmax,\n        param_set,\n        model;\n        hmax = FT(40),\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFLmax,\n    )\n\n    # --- Zero-out horizontal variations:\n    vsp = vars_state(model, Prognostic(), FT)\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.Q,\n        varsindex(vsp, :turbconv),\n    )\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.Q,\n        varsindex(vsp, :energy, :ρe),\n    )\n    vsa = vars_state(model, Auxiliary(), FT)\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.dg.state_auxiliary,\n        varsindex(vsa, :turbconv),\n    )\n    # ---\n\n    dgn_config = config_diagnostics(driver_config)\n\n    # boyd vandeven filter\n    cb_boyd = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            (\"energy.ρe\",),\n            solver_config.dg.grid,\n            BoydVandevenFilter(\n                solver_config.dg.grid,\n                1, #default=0\n                4, #default=32\n            ),\n        )\n        nothing\n    end\n\n    diag_arr = [single_stack_diagnostics(solver_config)]\n    time_data = FT[0]\n\n    # Define the number of outputs from `t0` to `timeend`\n    n_outputs = 5\n    # This equates to exports every ceil(Int, timeend/n_outputs) time-step:\n    every_x_simulation_time = ceil(Int, timeend / n_outputs)\n\n    cb_data_vs_time =\n        GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n            diag_vs_z = single_stack_diagnostics(solver_config)\n\n            nstep = getsteps(solver_config.solver)\n\n            push!(diag_arr, diag_vs_z)\n            push!(time_data, gettime(solver_config.solver))\n            nothing\n        end\n\n    # Mass tendencies = 0 for Anelastic1D model,\n    # so mass should be completely conserved:\n    check_cons =\n        (ClimateMachine.ConservationCheck(\"ρ\", \"3000steps\", FT(0.00000001)),)\n\n    cb_print_step = GenericCallbacks.EveryXSimulationSteps(100) do\n        @show getsteps(solver_config.solver)\n        nothing\n    end\n\n    if !isnothing(cl_args[\"cparam_file\"])\n        ClimateMachine.Settings.output_dir = cl_args[\"cparam_file\"] * \".output\"\n    end\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        check_cons = check_cons,\n        user_callbacks = (cb_boyd, cb_data_vs_time, cb_print_step),\n        check_euclidean_distance = true,\n    )\n\n    diag_vs_z = single_stack_diagnostics(solver_config)\n    push!(diag_arr, diag_vs_z)\n    push!(time_data, gettime(solver_config.solver))\n\n    return solver_config, diag_arr, time_data\nend\n\n# ArgParse in global scope to modify Clima Parameters\nsbl_args = ArgParseSettings(autofix_names = true)\nadd_arg_group!(sbl_args, \"StableBoundaryLayer\")\n@add_arg_table! sbl_args begin\n    \"--cparam-file\"\n    help = \"specify CLIMAParameters file\"\n    arg_type = Union{String, Nothing}\n    default = nothing\n\n    \"--surface-flux\"\n    help = \"specify surface flux for energy and moisture\"\n    metavar = \"prescribed|bulk|custom_sbl\"\n    arg_type = String\n    default = \"custom_sbl\"\nend\n\ncl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = sbl_args)\nif !isnothing(cl_args[\"cparam_file\"])\n    filename = cl_args[\"cparam_file\"]\n    set_clima_parameters(filename)\nend\n\nsolver_config, diag_arr, time_data = main(Float64, cl_args)\n\ninclude(joinpath(@__DIR__, \"report_mse_sbl_anelastic.jl\"))\n\nnothing\n"
  },
  {
    "path": "test/Atmos/EDMF/stable_bl_coupled_edmf_an1d.jl",
    "content": "using JLD2, FileIO\nusing ClimateMachine\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.Checkpoint\nusing ClimateMachine.BalanceLaws: vars_state\nimport ClimateMachine.BalanceLaws: projection\nimport ClimateMachine.DGMethods\nusing ClimateMachine.Atmos\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\nimport CLIMAParameters\n\ninclude(joinpath(clima_dir, \"experiments\", \"AtmosLES\", \"stable_bl_model.jl\"))\ninclude(\"edmf_model.jl\")\ninclude(\"edmf_kernels.jl\")\n\nCLIMAParameters.Planet.T_surf_ref(::EarthParameterSet) = 265\nCLIMAParameters.Atmos.EDMF.a_surf(::EarthParameterSet) = 0.0\nfunction set_clima_parameters(filename)\n    eval(:(include($filename)))\nend\n\n\"\"\"\n    init_state_prognostic!(\n            turbconv::EDMF{FT},\n            m::AtmosModel{FT},\n            state::Vars,\n            aux::Vars,\n            localgeo,\n            t::Real,\n        ) where {FT}\n\nInitialize EDMF state variables.\nThis method is only called at `t=0`.\n\"\"\"\nfunction init_state_prognostic!(\n    turbconv::EDMF{FT},\n    m::AtmosModel{FT},\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t::Real,\n) where {FT}\n    # Aliases:\n    gm = state\n    en = state.turbconv.environment\n    up = state.turbconv.updraft\n    N_up = n_updrafts(turbconv)\n    # GCM setting - Initialize the grid mean profiles of prognostic variables (ρ,e_int,q_tot,u,v,w)\n    z = altitude(m, aux)\n\n    # SCM setting - need to have separate cases coded and called from a folder - see what LES does\n    # a thermo state is used here to convert the input θ to e_int profile\n    e_int = internal_energy(m, state, aux)\n    param_set = parameter_set(m)\n    ts = PhaseDry(param_set, e_int, state.ρ)\n    T = air_temperature(ts)\n    p = air_pressure(ts)\n    q = PhasePartition(ts)\n    θ_liq = liquid_ice_pottemp(ts)\n\n    a_min = turbconv.subdomains.a_min\n    @unroll_map(N_up) do i\n        up[i].ρa = gm.ρ * a_min\n        up[i].ρaw = gm.ρu[3] * a_min\n        up[i].ρaθ_liq = gm.ρ * a_min * θ_liq\n        up[i].ρaq_tot = FT(0)\n    end\n\n    # initialize environment covariance with zero for now\n    if z <= FT(250)\n        en.ρatke =\n            gm.ρ *\n            FT(0.4) *\n            FT(1 - z / 250.0) *\n            FT(1 - z / 250.0) *\n            FT(1 - z / 250.0)\n        en.ρaθ_liq_cv =\n            gm.ρ *\n            FT(0.4) *\n            FT(1 - z / 250.0) *\n            FT(1 - z / 250.0) *\n            FT(1 - z / 250.0)\n    else\n        en.ρatke = FT(0)\n        en.ρaθ_liq_cv = FT(0)\n    end\n    en.ρaq_tot_cv = FT(0)\n    en.ρaθ_liq_q_tot_cv = FT(0)\n    return nothing\nend;\n\nfunction main(::Type{FT}, cl_args) where {FT}\n\n    surface_flux = cl_args[\"surface_flux\"]\n\n    # Choice of compressibility and CFL\n    # compressibility = Compressible()\n    compressibility = Anelastic1D()\n    str_comp = compressibility == Compressible() ? \"COMPRESS\" : \"ANELASTIC\"\n\n    # DG polynomial order\n    N = 4\n    nelem_vert = 20\n\n    # Prescribe domain parameters\n    zmax = FT(400)\n    # Simulation time\n    t0 = FT(0)\n    timeend = FT(1800 * 1)\n    CFLmax = compressibility == Compressible() ? FT(1) : FT(100)\n\n    config_type = SingleStackConfigType\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK144NiegemannDiehlBusch,\n    )\n\n    # Choice of SGS model\n    N_updrafts = 1\n    N_quad = 3\n    turbconv = EDMF(\n        FT,\n        N_updrafts,\n        N_quad,\n        param_set,\n        surface = NeutralDrySurfaceModel{FT}(param_set),\n        coupling = Coupled(),\n    )\n\n    turbulence = ConstantKinematicViscosity(FT(0.0))\n    model = stable_bl_model(\n        FT,\n        config_type,\n        zmax,\n        surface_flux;\n        turbulence = turbulence,\n        turbconv = turbconv,\n        compressibility = compressibility,\n    )\n\n    # Assemble configuration\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        string(\"SBL_COUPLED_\", str_comp, \"_1D\"),\n        N,\n        nelem_vert,\n        zmax,\n        param_set,\n        model;\n        hmax = FT(40),\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFLmax,\n    )\n\n    # --- Zero-out horizontal variations:\n    vsp = vars_state(model, Prognostic(), FT)\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.Q,\n        varsindex(vsp, :turbconv),\n    )\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.Q,\n        varsindex(vsp, :energy, :ρe),\n    )\n    vsa = vars_state(model, Auxiliary(), FT)\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.dg.state_auxiliary,\n        varsindex(vsa, :turbconv),\n    )\n    # ---\n\n    dgn_config = config_diagnostics(driver_config)\n\n    # boyd vandeven filter\n    num_state_prognostic = number_states(driver_config.bl, Prognostic())\n    cb_boyd = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            1:num_state_prognostic,\n            solver_config.dg.grid,\n            BoydVandevenFilter(\n                solver_config.dg.grid,\n                1, #default=0\n                4, #default=32\n            ),\n        )\n        nothing\n    end\n\n    diag_arr = [single_stack_diagnostics(solver_config)]\n    time_data = FT[0]\n\n    # Define the number of outputs from `t0` to `timeend`\n    n_outputs = 5\n    # This equates to exports every ceil(Int, timeend/n_outputs) time-step:\n    every_x_simulation_time = ceil(Int, timeend / n_outputs)\n\n    cb_data_vs_time =\n        GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n            diag_vs_z = single_stack_diagnostics(solver_config)\n\n            nstep = getsteps(solver_config.solver)\n\n            push!(diag_arr, diag_vs_z)\n            push!(time_data, gettime(solver_config.solver))\n            nothing\n        end\n\n    # Mass tendencies = 0 for Anelastic1D model,\n    # so mass should be completely conserved:\n    check_cons =\n        (ClimateMachine.ConservationCheck(\"ρ\", \"3000steps\", FT(0.00000001)),)\n\n    cb_print_step = GenericCallbacks.EveryXSimulationSteps(100) do\n        @show getsteps(solver_config.solver)\n        nothing\n    end\n\n    if !isnothing(cl_args[\"cparam_file\"])\n        ClimateMachine.Settings.output_dir = cl_args[\"cparam_file\"] * \".output\"\n    end\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        check_cons = check_cons,\n        user_callbacks = (cb_boyd, cb_data_vs_time, cb_print_step),\n        check_euclidean_distance = true,\n    )\n\n    diag_vs_z = single_stack_diagnostics(solver_config)\n    push!(diag_arr, diag_vs_z)\n    push!(time_data, gettime(solver_config.solver))\n\n    return solver_config, diag_arr, time_data\nend\n\n# ArgParse in global scope to modify Clima Parameters\nsbl_args = ArgParseSettings(autofix_names = true)\nadd_arg_group!(sbl_args, \"StableBoundaryLayer\")\n@add_arg_table! sbl_args begin\n    \"--cparam-file\"\n    help = \"specify CLIMAParameters file\"\n    arg_type = Union{String, Nothing}\n    default = nothing\n\n    \"--surface-flux\"\n    help = \"specify surface flux for energy and moisture\"\n    metavar = \"prescribed|bulk|custom_sbl\"\n    arg_type = String\n    default = \"custom_sbl\"\nend\n\ncl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = sbl_args)\nif !isnothing(cl_args[\"cparam_file\"])\n    filename = cl_args[\"cparam_file\"]\n    set_clima_parameters(filename)\nend\n\nsolver_config, diag_arr, time_data = main(Float64, cl_args)\n\n# Uncomment lines to save output using JLD2\noutput_dir = @__DIR__;\nmkpath(output_dir);\nfunction dons(diag_vs_z)\n    return Dict(map(keys(first(diag_vs_z))) do k\n        string(k) => [getproperty(ca, k) for ca in diag_vs_z]\n    end)\nend\nget_dons_arr(diag_arr) = [dons(diag_vs_z) for diag_vs_z in diag_arr]\ndons_arr = get_dons_arr(diag_arr)\nprintln(dons_arr[1].keys)\nz = get_z(solver_config.dg.grid; rm_dupes = true);\nsave(\n    string(output_dir, \"/sbl_coupled.jld2\"),\n    \"dons_arr\",\n    dons_arr,\n    \"time_data\",\n    time_data,\n    \"z\",\n    z,\n)\n\ninclude(joinpath(@__DIR__, \"report_mse_sbl_coupled_edmf_an1d.jl\"))\n\nnothing\n"
  },
  {
    "path": "test/Atmos/EDMF/stable_bl_edmf.jl",
    "content": "using JLD2, FileIO\nusing ClimateMachine\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.Checkpoint\nusing ClimateMachine.BalanceLaws: vars_state\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\nimport CLIMAParameters\n\ninclude(joinpath(clima_dir, \"experiments\", \"AtmosLES\", \"stable_bl_model.jl\"))\ninclude(joinpath(\"helper_funcs\", \"diagnostics_configuration.jl\"))\ninclude(\"edmf_model.jl\")\ninclude(\"edmf_kernels.jl\")\n\nCLIMAParameters.Planet.T_surf_ref(::EarthParameterSet) = 265\nCLIMAParameters.Atmos.EDMF.a_surf(::EarthParameterSet) = 0.0\n\n\"\"\"\n    init_state_prognostic!(\n            turbconv::EDMF{FT},\n            m::AtmosModel{FT},\n            state::Vars,\n            aux::Vars,\n            localgeo,\n            t::Real,\n        ) where {FT}\n\nInitialize EDMF state variables.\nThis method is only called at `t=0`.\n\"\"\"\nfunction init_state_prognostic!(\n    turbconv::EDMF{FT},\n    m::AtmosModel{FT},\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t::Real,\n) where {FT}\n    # Aliases:\n    gm = state\n    en = state.turbconv.environment\n    up = state.turbconv.updraft\n    N_up = n_updrafts(turbconv)\n    # GCM setting - Initialize the grid mean profiles of prognostic variables (ρ,e_int,q_tot,u,v,w)\n    z = altitude(m, aux)\n\n    # SCM setting - need to have separate cases coded and called from a folder - see what LES does\n    # a thermo state is used here to convert the input θ to e_int profile\n    e_int = internal_energy(m, state, aux)\n    param_set = parameter_set(m)\n    ts = PhaseDry(param_set, e_int, state.ρ)\n    T = air_temperature(ts)\n    p = air_pressure(ts)\n    q = PhasePartition(ts)\n    θ_liq = liquid_ice_pottemp(ts)\n\n    a_min = turbconv.subdomains.a_min\n    @unroll_map(N_up) do i\n        up[i].ρa = gm.ρ * a_min\n        up[i].ρaw = gm.ρu[3] * a_min\n        up[i].ρaθ_liq = gm.ρ * a_min * θ_liq\n        up[i].ρaq_tot = FT(0)\n    end\n\n    # initialize environment covariance with zero for now\n    if z <= FT(250)\n        en.ρatke =\n            gm.ρ *\n            FT(0.4) *\n            FT(1 - z / 250.0) *\n            FT(1 - z / 250.0) *\n            FT(1 - z / 250.0)\n        en.ρaθ_liq_cv =\n            gm.ρ *\n            FT(0.4) *\n            FT(1 - z / 250.0) *\n            FT(1 - z / 250.0) *\n            FT(1 - z / 250.0)\n    else\n        en.ρatke = FT(0)\n        en.ρaθ_liq_cv = FT(0)\n    end\n    en.ρaq_tot_cv = FT(0)\n    en.ρaθ_liq_q_tot_cv = FT(0)\n    return nothing\nend;\n\nfunction main(::Type{FT}, cl_args) where {FT}\n\n    surface_flux = cl_args[\"surface_flux\"]\n\n    # DG polynomial order\n    N = 4\n    nelem_vert = 15\n\n    # Prescribe domain parameters\n    zmax = FT(400)\n\n    t0 = FT(0)\n\n    # Simulation time\n    timeend = FT(60)\n    CFLmax = FT(0.50)\n\n    config_type = SingleStackConfigType\n\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK144NiegemannDiehlBusch,\n    )\n\n    N_updrafts = 1\n    N_quad = 3 # Using N_quad = 1 leads to norm(Q) = NaN at init.\n    turbconv = EDMF(\n        FT,\n        N_updrafts,\n        N_quad,\n        param_set,\n        surface = NeutralDrySurfaceModel{FT}(param_set),\n    )\n\n    model = stable_bl_model(\n        FT,\n        config_type,\n        zmax,\n        surface_flux;\n        turbconv = turbconv,\n    )\n\n    # Assemble configuration\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"SBL_EDMF\",\n        N,\n        nelem_vert,\n        zmax,\n        param_set,\n        model;\n        hmax = FT(40),\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFLmax,\n    )\n\n    # --- Zero-out horizontal variations:\n    vsp = vars_state(model, Prognostic(), FT)\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.Q,\n        varsindex(vsp, :turbconv),\n    )\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.Q,\n        varsindex(vsp, :energy, :ρe),\n    )\n    vsa = vars_state(model, Auxiliary(), FT)\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.dg.state_auxiliary,\n        varsindex(vsa, :turbconv),\n    )\n    # ---\n\n    dgn_config =\n        config_diagnostics(driver_config, timeend; interval = \"10ssecs\")\n\n    cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            (turbconv_filters(turbconv)...,),\n            solver_config.dg.grid,\n            TMARFilter(),\n        )\n        nothing\n    end\n\n    diag_arr = [single_stack_diagnostics(solver_config)]\n    time_data = FT[0]\n\n    # Define the number of outputs from `t0` to `timeend`\n    n_outputs = 5\n    # This equates to exports every ceil(Int, timeend/n_outputs) time-step:\n    every_x_simulation_time = ceil(Int, timeend / n_outputs)\n\n    cb_data_vs_time =\n        GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n            diag_vs_z = single_stack_diagnostics(solver_config)\n\n            nstep = getsteps(solver_config.solver)\n            # Save to disc (for debugging):\n            # @save \"bomex_edmf_nstep=$nstep.jld2\" diag_vs_z\n\n            push!(diag_arr, diag_vs_z)\n            push!(time_data, gettime(solver_config.solver))\n            nothing\n        end\n\n    check_cons = (\n        ClimateMachine.ConservationCheck(\"ρ\", \"3000steps\", FT(0.001)),\n        ClimateMachine.ConservationCheck(\"energy.ρe\", \"3000steps\", FT(0.1)),\n    )\n\n    cb_print_step = GenericCallbacks.EveryXSimulationSteps(100) do\n        @show getsteps(solver_config.solver)\n        nothing\n    end\n\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        check_cons = check_cons,\n        user_callbacks = (cbtmarfilter, cb_data_vs_time, cb_print_step),\n        check_euclidean_distance = true,\n    )\n\n    diag_vs_z = single_stack_diagnostics(solver_config)\n    push!(diag_arr, diag_vs_z)\n    push!(time_data, gettime(solver_config.solver))\n\n    return solver_config, diag_arr, time_data\nend\n\n\n# add a command line argument to specify the kind of surface flux\n# TODO: this will move to the future namelist functionality\nsbl_args = ArgParseSettings(autofix_names = true)\nadd_arg_group!(sbl_args, \"StableBoundaryLayer\")\n@add_arg_table! sbl_args begin\n    \"--surface-flux\"\n    help = \"specify surface flux for energy and moisture\"\n    metavar = \"prescribed|bulk|custom_sbl\"\n    arg_type = String\n    default = \"custom_sbl\"\nend\n\ncl_args = ClimateMachine.init(\n    parse_clargs = true,\n    custom_clargs = sbl_args,\n    output_dir = get(ENV, \"CLIMATEMACHINE_SETTINGS_OUTPUT_DIR\", \"output\"),\n    fix_rng_seed = true,\n)\n\nsolver_config, diag_arr, time_data = main(Float64, cl_args)\n\n## Uncomment lines to save output using JLD2\n# output_dir = @__DIR__;\n# mkpath(output_dir);\n# function dons(diag_vs_z)\n#     return Dict(map(keys(first(diag_vs_z))) do k\n#         string(k) => [getproperty(ca, k) for ca in diag_vs_z]\n#     end)\n# end\n# get_dons_arr(diag_arr) = [dons(diag_vs_z) for diag_vs_z in diag_arr]\n# dons_arr = get_dons_arr(diag_arr)\n# println(dons_arr[1].keys)\n# z = get_z(solver_config.dg.grid; rm_dupes = true);\n# save(\n#     string(output_dir, \"/sbl_edmf.jld2\"),\n#     \"dons_arr\",\n#     dons_arr,\n#     \"time_data\",\n#     time_data,\n#     \"z\",\n#     z,\n# )\n\ninclude(joinpath(@__DIR__, \"report_mse_sbl_edmf.jl\"))\n\nnothing\n"
  },
  {
    "path": "test/Atmos/EDMF/stable_bl_edmf_fvm.jl",
    "content": "using JLD2, FileIO\nusing ClimateMachine\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.Checkpoint\nusing ClimateMachine.BalanceLaws: vars_state\nimport ClimateMachine.DGMethods.FVReconstructions: FVLinear\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\nimport CLIMAParameters\n\ninclude(joinpath(clima_dir, \"experiments\", \"AtmosLES\", \"stable_bl_model.jl\"))\ninclude(\"edmf_model.jl\")\ninclude(\"edmf_kernels.jl\")\n\nCLIMAParameters.Planet.T_surf_ref(::EarthParameterSet) = 265\nCLIMAParameters.Atmos.EDMF.a_surf(::EarthParameterSet) = 0.0\n\n\"\"\"\n    init_state_prognostic!(\n            turbconv::EDMF{FT},\n            m::AtmosModel{FT},\n            state::Vars,\n            aux::Vars,\n            localgeo,\n            t::Real,\n        ) where {FT}\n\nInitialize EDMF state variables.\nThis method is only called at `t=0`.\n\"\"\"\nfunction init_state_prognostic!(\n    turbconv::EDMF{FT},\n    m::AtmosModel{FT},\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t::Real,\n) where {FT}\n    # Aliases:\n    gm = state\n    en = state.turbconv.environment\n    up = state.turbconv.updraft\n    N_up = n_updrafts(turbconv)\n    # GCM setting - Initialize the grid mean profiles of prognostic variables (ρ,e_int,q_tot,u,v,w)\n    z = altitude(m, aux)\n\n    # SCM setting - need to have separate cases coded and called from a folder - see what LES does\n    # a thermo state is used here to convert the input θ to e_int profile\n    e_int = internal_energy(m, state, aux)\n    param_set = parameter_set(m)\n    ts = PhaseDry(param_set, e_int, state.ρ)\n    T = air_temperature(ts)\n    p = air_pressure(ts)\n    q = PhasePartition(ts)\n    θ_liq = liquid_ice_pottemp(ts)\n\n    a_min = turbconv.subdomains.a_min\n    @unroll_map(N_up) do i\n        up[i].ρa = gm.ρ * a_min\n        up[i].ρaw = gm.ρu[3] * a_min\n        up[i].ρaθ_liq = gm.ρ * a_min * θ_liq\n        up[i].ρaq_tot = FT(0)\n    end\n\n    # initialize environment covariance with zero for now\n    if z <= FT(250)\n        en.ρatke =\n            gm.ρ *\n            FT(0.4) *\n            FT(1 - z / 250.0) *\n            FT(1 - z / 250.0) *\n            FT(1 - z / 250.0)\n        en.ρaθ_liq_cv =\n            gm.ρ *\n            FT(0.4) *\n            FT(1 - z / 250.0) *\n            FT(1 - z / 250.0) *\n            FT(1 - z / 250.0)\n    else\n        en.ρatke = FT(0)\n        en.ρaθ_liq_cv = FT(0)\n    end\n    en.ρaq_tot_cv = FT(0)\n    en.ρaθ_liq_q_tot_cv = FT(0)\n    return nothing\nend;\n\nfunction main(::Type{FT}, cl_args) where {FT}\n\n    surface_flux = cl_args[\"surface_flux\"]\n\n    # DG polynomial order\n    N = (1, 0)\n    nelem_vert = 80\n\n    # Prescribe domain parameters\n    zmax = FT(400)\n\n    t0 = FT(0)\n\n    # Simulation time\n    timeend = FT(60)\n    CFLmax = FT(0.50)\n\n    config_type = SingleStackConfigType\n\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        # solver_method = LSRK144NiegemannDiehlBusch,\n        solver_method = LSRK54CarpenterKennedy,\n    )\n\n    N_updrafts = 1\n    N_quad = 3 # Using N_quad = 1 leads to norm(Q) = NaN at init.\n    turbconv = EDMF(\n        FT,\n        N_updrafts,\n        N_quad,\n        param_set,\n        surface = NeutralDrySurfaceModel{FT}(param_set),\n    )\n\n    model = stable_bl_model(\n        FT,\n        config_type,\n        zmax,\n        surface_flux;\n        turbconv = turbconv,\n        ref_state = HydrostaticState(\n            DecayingTemperatureProfile{FT}(param_set);\n            subtract_off = false,\n        ),\n    )\n\n    # Assemble configuration\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"SBL_EDMF\",\n        N,\n        nelem_vert,\n        zmax,\n        param_set,\n        model;\n        hmax = FT(40),\n        numerical_flux_first_order = RoeNumericalFlux(),\n        fv_reconstruction = HBFVReconstruction(model, FVLinear()),\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFLmax,\n    )\n\n    # --- Zero-out horizontal variations:\n    vsp = vars_state(model, Prognostic(), FT)\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.Q,\n        varsindex(vsp, :turbconv),\n    )\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.Q,\n        varsindex(vsp, :energy, :ρe),\n    )\n    vsa = vars_state(model, Auxiliary(), FT)\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.dg.state_auxiliary,\n        varsindex(vsa, :turbconv),\n    )\n    # ---\n\n    dgn_config = config_diagnostics(driver_config)\n\n    cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(100) do\n        nstep = getsteps(solver_config.solver)\n        Filters.apply!(\n            solver_config.Q,\n            (turbconv_filters(turbconv)...,),\n            solver_config.dg.grid,\n            TMARFilter(),\n        )\n        nothing\n    end\n\n    diag_arr = [single_stack_diagnostics(solver_config)]\n    time_data = FT[0]\n\n    # Define the number of outputs from `t0` to `timeend`\n    n_outputs = 5\n    # This equates to exports every ceil(Int, timeend/n_outputs) time-step:\n    every_x_simulation_time = ceil(Int, timeend / n_outputs)\n\n    cb_data_vs_time =\n        GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n            diag_vs_z = single_stack_diagnostics(solver_config)\n\n            nstep = getsteps(solver_config.solver)\n            # Save to disc (for debugging):\n            # @save \"bomex_edmf_nstep=$nstep.jld2\" diag_vs_z\n\n            push!(diag_arr, diag_vs_z)\n            push!(time_data, gettime(solver_config.solver))\n            nothing\n        end\n\n    check_cons = (\n        ClimateMachine.ConservationCheck(\"ρ\", \"3000steps\", FT(0.01)),\n        ClimateMachine.ConservationCheck(\"energy.ρe\", \"3000steps\", FT(0.1)),\n    )\n\n    cb_print_step = GenericCallbacks.EveryXSimulationSteps(500) do\n        @show getsteps(solver_config.solver)\n        nothing\n    end\n\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        check_cons = check_cons,\n        user_callbacks = (cbtmarfilter, cb_data_vs_time, cb_print_step),\n        check_euclidean_distance = true,\n    )\n\n    diag_vs_z = single_stack_diagnostics(solver_config)\n    push!(diag_arr, diag_vs_z)\n    push!(time_data, gettime(solver_config.solver))\n\n    return solver_config, diag_arr, time_data\nend\n\n# add a command line argument to specify the kind of surface flux\n# TODO: this will move to the future namelist functionality\nsbl_args = ArgParseSettings(autofix_names = true)\nadd_arg_group!(sbl_args, \"StableBoundaryLayer\")\n@add_arg_table! sbl_args begin\n    \"--surface-flux\"\n    help = \"specify surface flux for energy and moisture\"\n    metavar = \"prescribed|bulk|custom_sbl\"\n    arg_type = String\n    default = \"custom_sbl\"\nend\n\ncl_args = ClimateMachine.init(\n    parse_clargs = true,\n    custom_clargs = sbl_args,\n    output_dir = get(ENV, \"CLIMATEMACHINE_SETTINGS_OUTPUT_DIR\", \"output\"),\n    fix_rng_seed = true,\n)\n\nsolver_config, diag_arr, time_data = main(Float64, cl_args)\n\ninclude(joinpath(@__DIR__, \"report_mse_sbl_edmf.jl\"))\n\nnothing\n"
  },
  {
    "path": "test/Atmos/EDMF/stable_bl_single_stack_implicit.jl",
    "content": "using ClimateMachine\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.Checkpoint\nusing ClimateMachine.BalanceLaws: vars_state\nusing JLD2, FileIO\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\n\ninclude(joinpath(clima_dir, \"experiments\", \"AtmosLES\", \"stable_bl_model.jl\"))\ninclude(\"edmf_model.jl\")\ninclude(\"edmf_kernels.jl\")\n\nfunction main(::Type{FT}, cl_args) where {FT}\n\n    surface_flux = cl_args[\"surface_flux\"]\n\n    # DG polynomial order\n    N = 1\n    nelem_vert = 50\n\n    # Prescribe domain parameters\n    zmax = FT(400)\n\n    t0 = FT(0)\n\n    # Simulation time\n    timeend = FT(3600 * 6)\n    CFLmax = FT(40.0)\n\n    config_type = SingleStackConfigType\n\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK144NiegemannDiehlBusch,\n    )\n\n    N_updrafts = 1\n    N_quad = 3 # Using N_quad = 1 leads to norm(Q) = NaN at init.\n    turbconv = NoTurbConv()\n\n    C_smag = FT(0.23)\n\n    model = stable_bl_model(\n        FT,\n        config_type,\n        zmax,\n        surface_flux;\n        turbulence = SmagorinskyLilly{FT}(C_smag),\n        turbconv = turbconv,\n    )\n\n    # Assemble configuration\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"SBL_EDMF\",\n        N,\n        nelem_vert,\n        zmax,\n        param_set,\n        model;\n        hmax = FT(40),\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFLmax,\n    )\n\n    #################### Change the ode_solver to implicit solver\n\n    dg = solver_config.dg\n    Q = solver_config.Q\n\n\n    vdg = DGModel(\n        driver_config;\n        state_auxiliary = dg.state_auxiliary,\n        direction = VerticalDirection(),\n    )\n\n\n    # linear solver relative tolerance rtol which should be slightly smaller than the nonlinear solver tol\n    linearsolver = BatchedGeneralizedMinimalResidual(\n        dg,\n        Q;\n        max_subspace_size = 30,\n        atol = -1.0,\n        rtol = 5e-5,\n    )\n\n    \"\"\"\n    N(q)(Q) = Qhat  => F(Q) = N(q)(Q) - Qhat\n\n    F(Q) == 0\n    ||F(Q^i) || / ||F(Q^0) || < tol\n\n    \"\"\"\n    # ϵ is a sensity parameter for this problem, it determines the finite difference Jacobian dF = (F(Q + ϵdQ) - F(Q))/ϵ\n    # I have also try larger tol, but tol = 1e-3 does not work\n    nonlinearsolver =\n        JacobianFreeNewtonKrylovSolver(Q, linearsolver; tol = 1e-4, ϵ = 1.e-10)\n\n    # this is a second order time integrator, to change it to a first order time integrator\n    # change it ARK1ForwardBackwardEuler, which can reduce the cost by half at the cost of accuracy\n    # and stability\n    # preconditioner_update_freq = 50 means updating the preconditioner every 50 Newton solves,\n    # update it more freqent will accelerate the convergence of linear solves, but updating it\n    # is very expensive\n    ode_solver = ARK2ImplicitExplicitMidpoint(\n        dg,\n        vdg,\n        NonLinearBackwardEulerSolver(\n            nonlinearsolver;\n            isadjustable = true,\n            preconditioner_update_freq = 50,\n        ),\n        Q;\n        dt = solver_config.dt,\n        t0 = 0,\n        split_explicit_implicit = false,\n        variant = NaiveVariant(),\n    )\n\n    solver_config.solver = ode_solver\n\n    #######################################\n\n    # --- Zero-out horizontal variations:\n    vsp = vars_state(model, Prognostic(), FT)\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.Q,\n        varsindex(vsp, :turbconv),\n    )\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.Q,\n        varsindex(vsp, :energy, :ρe),\n    )\n    vsa = vars_state(model, Auxiliary(), FT)\n    horizontally_average!(\n        driver_config.grid,\n        solver_config.dg.state_auxiliary,\n        varsindex(vsa, :turbconv),\n    )\n    # ---\n\n    dgn_config = config_diagnostics(driver_config)\n\n    cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            (turbconv_filters(turbconv)...,),\n            solver_config.dg.grid,\n            TMARFilter(),\n        )\n        nothing\n    end\n\n    diag_arr = [single_stack_diagnostics(solver_config)]\n    time_data = FT[0]\n\n    # Define the number of outputs from `t0` to `timeend`\n    n_outputs = 5\n    # This equates to exports every ceil(Int, timeend/n_outputs) time-step:\n    every_x_simulation_time = ceil(Int, timeend / n_outputs)\n\n    cb_data_vs_time =\n        GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n            diag_vs_z = single_stack_diagnostics(solver_config)\n\n            nstep = getsteps(solver_config.solver)\n            # Save to disc (for debugging):\n            # @save \"bomex_edmf_nstep=$nstep.jld2\" diag_vs_z\n\n            push!(diag_arr, diag_vs_z)\n            push!(time_data, gettime(solver_config.solver))\n            nothing\n        end\n\n    check_cons =\n        (ClimateMachine.ConservationCheck(\"ρ\", \"3000steps\", FT(0.001)),)\n\n    cb_print_step = GenericCallbacks.EveryXSimulationSteps(100) do\n        @show getsteps(solver_config.solver)\n        nothing\n    end\n\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        check_cons = check_cons,\n        user_callbacks = (cbtmarfilter, cb_data_vs_time, cb_print_step),\n        check_euclidean_distance = true,\n    )\n\n    diag_vs_z = single_stack_diagnostics(solver_config)\n    push!(diag_arr, diag_vs_z)\n    push!(time_data, gettime(solver_config.solver))\n\n    return solver_config, diag_arr, time_data\nend\n\n# add a command line argument to specify the kind of surface flux\n# TODO: this will move to the future namelist functionality\nsbl_args = ArgParseSettings(autofix_names = true)\nadd_arg_group!(sbl_args, \"StableBoundaryLayer\")\n@add_arg_table! sbl_args begin\n    \"--surface-flux\"\n    help = \"specify surface flux for energy and moisture\"\n    metavar = \"prescribed|bulk|custom_sbl\"\n    arg_type = String\n    default = \"custom_sbl\"\nend\n\ncl_args = ClimateMachine.init(\n    parse_clargs = true,\n    custom_clargs = sbl_args,\n    output_dir = get(ENV, \"CLIMATEMACHINE_SETTINGS_OUTPUT_DIR\", \"output\"),\n    fix_rng_seed = true,\n)\n\nsolver_config, diag_arr, time_data = main(Float64, cl_args)\n\ninclude(joinpath(@__DIR__, \"report_mse_sbl_ss_implicit.jl\"))\n\nnothing\n"
  },
  {
    "path": "test/Atmos/EDMF/variable_map.jl",
    "content": "#! format: off\nvar_map(s::String) = var_map(Val(Symbol(s)))\nvar_map(::Val{T}) where {T} = nothing\n\nvar_map(::Val{:prog_ρ}) = (\"rho\", ())\nvar_map(::Val{:prog_ρu_1}) = (\"u_mean\", (:ρ,))\nvar_map(::Val{:prog_ρu_2}) = (\"v_mean\", (:ρ,))\nvar_map(::Val{:prog_moisture_ρq_tot}) = (\"qt_mean\", (:ρ,))\nvar_map(::Val{:prog_turbconv_updraft_1_ρa}) = (\"updraft_fraction\", (:ρ,))\nvar_map(::Val{:prog_turbconv_updraft_1_ρaw}) = (\"updraft_w\", (:ρ, :a))\nvar_map(::Val{:prog_turbconv_updraft_1_ρaq_tot}) = (\"updraft_qt\", (:ρ, :a))\nvar_map(::Val{:prog_turbconv_updraft_1_ρaθ_liq}) = (\"updraft_thetali\", (:ρ, :a))\nvar_map(::Val{:prog_turbconv_environment_ρatke}) = (\"tke_mean\", (:ρ, :a))\nvar_map(::Val{:prog_turbconv_environment_ρaθ_liq_cv}) = (\"env_thetali2\", (:ρ, :a))\nvar_map(::Val{:prog_turbconv_environment_ρaq_tot_cv}) = (\"env_qt2\", (:ρ, :a))\n#! format: on\n"
  },
  {
    "path": "test/Atmos/Model/Artifacts.toml",
    "content": "[ref_state]\ngit-tree-sha1 = \"f6b0e460e5660732eb9e755b1e06d395ea3fb518\"\n"
  },
  {
    "path": "test/Atmos/Model/discrete_hydrostatic_balance.jl",
    "content": "using ClimateMachine\nClimateMachine.init(parse_clargs = true)\n\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.Atmos\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.SystemSolvers: ManyColumnLU\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.Mesh.Grids\nusing Thermodynamics.TemperatureProfiles\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\n\nusing LinearAlgebra\nusing StaticArrays\nusing Test\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nfunction init_to_ref_state!(problem, bl, state, aux, localgeo, t)\n    FT = eltype(state)\n    state.ρ = aux.ref_state.ρ\n    state.ρu = SVector{3, FT}(0, 0, 0)\n    state.energy.ρe = aux.ref_state.ρe\nend\n\nfunction config_balanced(\n    FT,\n    poly_order,\n    temp_profile,\n    numflux,\n    (config_type, config_fun, config_args),\n)\n    ref_state = HydrostaticState(temp_profile; subtract_off = false)\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = ConstantDynamicViscosity(FT(0)),\n        hyperdiffusion = NoHyperDiffusion(),\n        moisture = DryModel(),\n    )\n    model = AtmosModel{FT}(\n        config_type,\n        physics;\n        source = (Gravity(),),\n        init_state_prognostic = init_to_ref_state!,\n    )\n\n    config = config_fun(\n        \"balanced state\",\n        poly_order,\n        config_args...,\n        param_set,\n        nothing;\n        model = model,\n        numerical_flux_first_order = numflux,\n    )\n\n    return config\nend\n\nfunction main()\n    FT = Float64\n    poly_order = 4\n\n    timestart = FT(0)\n    timeend = FT(100)\n    domain_height = FT(50e3)\n\n    LES_params = let\n        LES_resolution = ntuple(_ -> domain_height / 3poly_order, 3)\n        LES_domain = ntuple(_ -> domain_height, 3)\n        (LES_resolution, LES_domain...)\n    end\n\n    GCM_params = let\n        GCM_resolution = (3, 3)\n        (GCM_resolution, domain_height)\n    end\n\n    GCM = (AtmosGCMConfigType, ClimateMachine.AtmosGCMConfiguration, GCM_params)\n    LES = (AtmosLESConfigType, ClimateMachine.AtmosLESConfiguration, LES_params)\n\n    imex_solver_type = ClimateMachine.IMEXSolverType(\n        splitting_type = HEVISplitting(),\n        implicit_model = AtmosAcousticGravityLinearModel,\n        implicit_solver = ManyColumnLU,\n        solver_method = ARK2GiraldoKellyConstantinescu,\n    )\n    explicit_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK54CarpenterKennedy,\n    )\n\n    @testset for config in (LES, GCM)\n        @testset for ode_solver_type in (explicit_solver_type, imex_solver_type)\n            @testset for numflux in (\n                CentralNumericalFluxFirstOrder(),\n                RoeNumericalFlux(),\n                HLLCNumericalFlux(),\n            )\n                @testset for temp_profile in (\n                    IsothermalProfile(param_set, FT),\n                    DecayingTemperatureProfile{FT}(param_set),\n                )\n                    driver_config = config_balanced(\n                        FT,\n                        poly_order,\n                        temp_profile,\n                        numflux,\n                        config,\n                    )\n\n                    solver_config = ClimateMachine.SolverConfiguration(\n                        timestart,\n                        timeend,\n                        driver_config,\n                        Courant_number = FT(0.1),\n                        init_on_cpu = true,\n                        ode_solver_type = ode_solver_type,\n                        CFL_direction = EveryDirection(),\n                        diffdir = HorizontalDirection(),\n                    )\n\n                    Qinit = similar(solver_config.Q)\n                    Qinit .= solver_config.Q\n\n                    ClimateMachine.invoke!(solver_config)\n\n                    error =\n                        euclidean_distance(solver_config.Q, Qinit) / norm(Qinit)\n                    @test error <= 100 * eps(FT)\n                end\n            end\n        end\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Atmos/Model/get_atmos_ref_states.jl",
    "content": "using ClimateMachine\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Atmos\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.Checkpoint\nusing ClimateMachine.Mesh.Grids\nusing Thermodynamics\nusing Thermodynamics.TemperatureProfiles\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.VariableTemplates\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing Test\nClimateMachine.init()\n\nfunction get_atmos_ref_states(nelem_vert, N_poly, RH)\n\n    FT = Float64\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = HydrostaticState(\n            DecayingTemperatureProfile{FT}(param_set),\n            RH,\n        ),\n    )\n    model = AtmosModel{FT}(\n        SingleStackConfigType,\n        physics;\n        init_state_prognostic = (_...) -> nothing,\n    )\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"ref_state\",\n        N_poly,\n        nelem_vert,\n        FT(25e3),\n        param_set,\n        model,\n    )\n    solver_config = ClimateMachine.SolverConfiguration(\n        FT(0),\n        FT(10),\n        driver_config;\n        skip_update_aux = true,\n        ode_dt = FT(1),\n    )\n\n    return solver_config\nend\n"
  },
  {
    "path": "test/Atmos/Model/ref_state.jl",
    "content": "include(\"get_atmos_ref_states.jl\")\nusing JLD2\nusing Pkg.Artifacts\nusing ArtifactWrappers\nusing Thermodynamics\nconst TD = Thermodynamics\n\n@testset \"Hydrostatic reference states - regression test\" begin\n    ref_state_dataset = ArtifactWrapper(\n        @__DIR__,\n        isempty(get(ENV, \"CI\", \"\")),\n        \"ref_state\",\n        ArtifactFile[ArtifactFile(\n            url = \"https://caltech.box.com/shared/static/gyq292ns79wm9xpmy1sse3qtnpcxw54q.jld2\",\n            filename = \"ref_state.jld2\",\n        ),],\n    )\n    ref_state_dataset_path = get_data_folder(ref_state_dataset)\n    data_file = joinpath(ref_state_dataset_path, \"ref_state.jld2\")\n\n    RH = 0.5\n    (nelem_vert, N_poly) = (20, 4)\n    solver_config = get_atmos_ref_states(nelem_vert, N_poly, RH)\n    dons_arr = dict_of_nodal_states(solver_config, (Auxiliary(),))\n    T = dons_arr[\"ref_state.T\"]\n    p = dons_arr[\"ref_state.p\"]\n    ρ = dons_arr[\"ref_state.ρ\"]\n\n    @load \"$data_file\" T_ref p_ref ρ_ref\n    @test all(isapprox.(T, T_ref; rtol = 1e-6))\n    @test all(p .≈ p_ref)\n    @test all(ρ .≈ ρ_ref)\nend\n\n@testset \"Hydrostatic reference states - correctness\" begin\n\n    RH = 0.5\n    # Fails on (80, 1)\n    for (nelem_vert, N_poly) in [(40, 2), (20, 4)]\n        solver_config = get_atmos_ref_states(nelem_vert, N_poly, RH)\n        dons_arr = dict_of_nodal_states(solver_config)\n        phase_type = PhaseEquil\n        T = dons_arr[\"ref_state.T\"]\n        p = dons_arr[\"ref_state.p\"]\n        ρ = dons_arr[\"ref_state.ρ\"]\n        q_tot = dons_arr[\"ref_state.ρq_tot\"] ./ ρ\n        q_pt = PhasePartition.(q_tot)\n\n        # TODO: test that ρ and p are in discrete hydrostatic balance\n\n        # Test state for thermodynamic consistency (with ideal gas law)\n        T_igl =\n            TD.air_temperature_from_ideal_gas_law.(Ref(param_set), p, ρ, q_pt)\n        @test all(T .≈ T_igl)\n\n        # Test that relative humidity in reference state is approximately\n        # input relative humidity\n        RH_ref = relative_humidity.(Ref(param_set), T, p, Ref(phase_type), q_pt)\n        @show max(abs.(RH .- RH_ref)...)\n        @test all(isapprox.(RH, RH_ref, atol = 0.05))\n    end\n\nend\n"
  },
  {
    "path": "test/Atmos/Model/runtests.jl",
    "content": "using Test\n\n@testset \"Atmos Model\" begin\n    include(\"ref_state.jl\")\nend\n"
  },
  {
    "path": "test/Atmos/Parameterizations/Microphysics/KM_ice.jl",
    "content": "using Dierckx\n\ninclude(\"KinematicModel.jl\")\n\n# speed up the relaxation timescales for cloud water and cloud ice\nCLIMAParameters.Atmos.Microphysics.τ_cond_evap(::AbstractParameterSet) = 0.5\nCLIMAParameters.Atmos.Microphysics.τ_sub_dep(::AbstractParameterSet) = 0.5\n\nfunction vars_state(m::KinematicModel, ::Prognostic, FT)\n    @vars begin\n        ρ::FT\n        ρu::SVector{3, FT}\n        ρe::FT\n        ρq_tot::FT\n        ρq_liq::FT\n        ρq_ice::FT\n        ρq_rai::FT\n        ρq_sno::FT\n    end\nend\n\nfunction vars_state(m::KinematicModel, ::Auxiliary, FT)\n    @vars begin\n        # defined in init_state_auxiliary\n        p::FT\n        z_coord::FT\n        x_coord::FT\n        # defined in update_aux\n        u::FT\n        w::FT\n        q_tot::FT\n        q_vap::FT\n        q_liq::FT\n        q_ice::FT\n        q_rai::FT\n        q_sno::FT\n        e_tot::FT\n        e_kin::FT\n        e_pot::FT\n        e_int::FT\n        T::FT\n        S_liq::FT\n        S_ice::FT\n        RH::FT\n        rain_w::FT\n        snow_w::FT\n        # more diagnostics\n        src_cloud_liq::FT\n        src_cloud_ice::FT\n        src_rain_acnv::FT\n        src_snow_acnv::FT\n        src_liq_rain_accr::FT\n        src_liq_snow_accr::FT\n        src_ice_snow_accr::FT\n        src_ice_rain_accr::FT\n        src_snow_rain_accr::FT\n        src_rain_accr_sink::FT\n        src_rain_evap::FT\n        src_snow_subl::FT\n        src_snow_melt::FT\n        flag_cloud_liq::FT\n        flag_cloud_ice::FT\n        flag_rain::FT\n        flag_snow::FT\n        # helpers for bc\n        ρe_init::FT\n        ρq_tot_init::FT\n    end\nend\n\nfunction init_kinematic_eddy!(eddy_model, state, aux, localgeo, t, spline_fun)\n\n    FT = eltype(state)\n    _grav::FT = grav(param_set)\n\n    dc = eddy_model.data_config\n\n    (x, y, z) = localgeo.coord\n    (xc, yc, zc) = localgeo.center_coord\n\n    @inbounds begin\n\n        init_T, init_qt, init_p, init_ρ, init_dρ = spline_fun\n\n        # density\n        q_pt_0 = PhasePartition(init_qt(z))\n        R_m, cp_m, cv_m, γ = gas_constants(param_set, q_pt_0)\n        T::FT = init_T(z)\n        ρ::FT = init_ρ(z)\n        state.ρ = ρ\n        aux.p = init_p(z)\n\n        # moisture\n        state.ρq_tot = ρ * init_qt(z)\n        state.ρq_liq = ρ * q_pt_0.liq\n        state.ρq_ice = ρ * q_pt_0.ice\n        state.ρq_rai = ρ * FT(0)\n        state.ρq_sno = ρ * FT(0)\n\n        # [Grabowski1998](@cite)\n        # velocity (derivative of streamfunction)\n        # This is actually different than what comes out from taking a\n        # derivative of Ψ from the paper. I have sin(π/2/X(x-xc)).\n        # This setup makes more sense to me though.\n        _Z::FT = FT(15000)\n        _X::FT = FT(10000)\n        _xc::FT = FT(30000)\n        _A::FT = FT(4.8 * 1e4)\n        _S::FT = FT(2.5 * 1e-2) * FT(0.01) #TODO\n        _ρ_00::FT = FT(1)\n        ρu::FT = FT(0)\n        ρw::FT = FT(0)\n        fact =\n            _A / _ρ_00 * (\n                init_ρ(z) * FT(π) / _Z * cos(FT(π) / _Z * z) +\n                init_dρ(z) * sin(FT(π) / _Z * z)\n            )\n\n        if zc < _Z\n            if x >= (_xc + _X)\n                ρu = _S * z - fact\n                ρw = FT(0)\n            elseif x <= (_xc - _X)\n                ρu = _S * z + fact\n                ρw = FT(0)\n            else\n                ρu = _S * z - fact * sin(FT(π / 2.0) / _X * (x - _xc))\n                ρw =\n                    _A * init_ρ(z) / _ρ_00 * FT(π / 2.0) / _X *\n                    sin(FT(π) / _Z * z) *\n                    cos(FT(π / 2.0) / _X * (x - _xc))\n            end\n        else\n            ρu = _S * z\n            ρw = FT(0)\n        end\n        state.ρu = SVector(ρu, FT(0), ρw)\n        u::FT = ρu / ρ\n        w::FT = ρw / ρ\n\n        # energy\n        e_kin::FT = 1 // 2 * (u^2 + w^2)\n        e_pot::FT = _grav * z\n        e_int::FT = internal_energy(param_set, T, q_pt_0)\n        e_tot::FT = e_kin + e_pot + e_int\n        state.ρe = ρ * e_tot\n    end\n    return nothing\nend\n\nfunction nodal_update_auxiliary_state!(\n    m::KinematicModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    FT = eltype(state)\n    _grav::FT = grav(param_set)\n    _T_freeze::FT = T_freeze(param_set)\n\n    @inbounds begin\n\n        if t == FT(0)\n            aux.ρe_init = state.ρe\n            aux.ρq_tot_init = state.ρq_tot\n        end\n\n        # velocity\n        aux.u = state.ρu[1] / state.ρ\n        aux.w = state.ρu[3] / state.ρ\n        # water\n        aux.q_tot = state.ρq_tot / state.ρ\n        aux.q_liq = state.ρq_liq / state.ρ\n        aux.q_ice = state.ρq_ice / state.ρ\n        aux.q_rai = state.ρq_rai / state.ρ\n        aux.q_sno = state.ρq_sno / state.ρ\n        aux.q_vap = aux.q_tot - aux.q_liq - aux.q_ice\n        # energy\n        aux.e_tot = state.ρe / state.ρ\n        aux.e_kin = 1 // 2 * (aux.u^2 + aux.w^2)\n        aux.e_pot = _grav * aux.z_coord\n        aux.e_int = aux.e_tot - aux.e_kin - aux.e_pot\n        # supersaturation\n        q = PhasePartition(aux.q_tot, aux.q_liq, aux.q_ice)\n        aux.T = air_temperature(param_set, aux.e_int, q)\n        ts_neq = PhaseNonEquil_ρTq(param_set, state.ρ, aux.T, q)\n        aux.S_liq = max(0, supersaturation(ts_neq, Liquid()))\n        aux.S_ice = max(0, supersaturation(ts_neq, Ice()))\n        aux.RH = relative_humidity(ts_neq) * FT(100)\n\n        aux.rain_w =\n            terminal_velocity(param_set, CM1M.RainType(), state.ρ, aux.q_rai)\n        aux.snow_w =\n            terminal_velocity(param_set, CM1M.SnowType(), state.ρ, aux.q_sno)\n\n        # more diagnostics\n        ts_eq = PhaseEquil_ρTq(param_set, state.ρ, aux.T, aux.q_tot)\n        q_eq = PhasePartition(ts_eq)\n\n        aux.src_cloud_liq =\n            conv_q_vap_to_q_liq_ice(param_set, CM1M.LiquidType(), q_eq, q)\n        aux.src_cloud_ice =\n            conv_q_vap_to_q_liq_ice(param_set, CM1M.IceType(), q_eq, q)\n\n        aux.src_rain_acnv = conv_q_liq_to_q_rai(param_set, aux.q_liq)\n        aux.src_snow_acnv = conv_q_ice_to_q_sno(param_set, q, state.ρ, aux.T)\n\n        aux.src_liq_rain_accr = accretion(\n            param_set,\n            CM1M.LiquidType(),\n            CM1M.RainType(),\n            aux.q_liq,\n            aux.q_rai,\n            state.ρ,\n        )\n        aux.src_liq_snow_accr = accretion(\n            param_set,\n            CM1M.LiquidType(),\n            CM1M.SnowType(),\n            aux.q_liq,\n            aux.q_sno,\n            state.ρ,\n        )\n        aux.src_ice_snow_accr = accretion(\n            param_set,\n            CM1M.IceType(),\n            CM1M.SnowType(),\n            aux.q_ice,\n            aux.q_sno,\n            state.ρ,\n        )\n        aux.src_ice_rain_accr = accretion(\n            param_set,\n            CM1M.IceType(),\n            CM1M.RainType(),\n            aux.q_ice,\n            aux.q_rai,\n            state.ρ,\n        )\n\n        aux.src_rain_accr_sink =\n            accretion_rain_sink(param_set, aux.q_ice, aux.q_rai, state.ρ)\n\n        if aux.T < _T_freeze\n            aux.src_snow_rain_accr = accretion_snow_rain(\n                param_set,\n                CM1M.SnowType(),\n                CM1M.RainType(),\n                aux.q_sno,\n                aux.q_rai,\n                state.ρ,\n            )\n        else\n            aux.src_snow_rain_accr = accretion_snow_rain(\n                param_set,\n                CM1M.RainType(),\n                CM1M.SnowType(),\n                aux.q_rai,\n                aux.q_sno,\n                state.ρ,\n            )\n        end\n\n        aux.src_rain_evap = evaporation_sublimation(\n            param_set,\n            CM1M.RainType(),\n            q,\n            aux.q_rai,\n            state.ρ,\n            aux.T,\n        )\n        aux.src_snow_subl = evaporation_sublimation(\n            param_set,\n            CM1M.SnowType(),\n            q,\n            aux.q_sno,\n            state.ρ,\n            aux.T,\n        )\n\n        aux.src_snow_melt = snow_melt(param_set, aux.q_sno, state.ρ, aux.T)\n\n        aux.flag_cloud_liq = FT(0)\n        aux.flag_cloud_ice = FT(0)\n        aux.flag_rain = FT(0)\n        aux.flag_snow = FT(0)\n        if (aux.q_liq >= FT(0))\n            aux.flag_cloud_liq = FT(1)\n        end\n        if (aux.q_ice >= FT(0))\n            aux.flag_cloud_ice = FT(1)\n        end\n        if (aux.q_rai >= FT(0))\n            aux.flag_rain = FT(1)\n        end\n        if (aux.q_sno >= FT(0))\n            aux.flag_snow = FT(1)\n        end\n    end\nend\n\nfunction boundary_state!(\n    ::RusanovNumericalFlux,\n    bctype,\n    m::KinematicModel,\n    state⁺,\n    aux⁺,\n    n,\n    state⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    # 1 - left     (x = 0,   z = ...)\n    # 2 - right    (x = -1,  z = ...)\n    # 3,4 - y boundary (periodic)\n    # 5 - bottom   (x = ..., z = 0)\n    # 6 - top      (x = ..., z = -1)\n\n    FT = eltype(state⁻)\n    @inbounds state⁺.ρ = state⁻.ρ\n    @inbounds state⁺.ρe = aux⁻.ρe_init\n    @inbounds state⁺.ρq_tot = aux⁻.ρq_tot_init\n    @inbounds state⁺.ρq_liq = FT(0) #state⁻.ρq_liq\n    @inbounds state⁺.ρq_ice = FT(0) #state⁻.ρq_ice\n    @inbounds state⁺.ρq_rai = FT(0)\n    @inbounds state⁺.ρq_sno = FT(0)\n\n    if bctype == 1\n        @inbounds state⁺.ρu = SVector(state⁻.ρu[1], FT(0), FT(0))\n    end\n    if bctype == 2\n        @inbounds state⁺.ρu = SVector(state⁻.ρu[1], FT(0), FT(0))\n        @inbounds state⁺.ρe = state⁻.ρe\n        @inbounds state⁺.ρq_tot = state⁻.ρq_tot\n        @inbounds state⁺.ρq_liq = state⁻.ρq_liq\n        @inbounds state⁺.ρq_ice = state⁻.ρq_ice\n    end\n    if bctype == 5\n        @inbounds state⁺.ρu -= 2 * dot(state⁻.ρu, n) .* SVector(n)\n    end\n    if bctype == 6\n        @inbounds state⁺.ρu = SVector(state⁻.ρu[1], FT(0), state⁻.ρu[3])\n    end\nend\n\n@inline function wavespeed(\n    m::KinematicModel,\n    nM,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    _...,\n)\n    FT = eltype(state)\n    @inbounds begin\n        u = state.ρu / state.ρ\n        q_rai::FT = state.ρq_rai / state.ρ\n        q_sno::FT = state.ρq_sno / state.ρ\n        rain_w = terminal_velocity(param_set, CM1M.RainType(), state.ρ, q_rai)\n        snow_w = terminal_velocity(param_set, CM1M.SnowType(), state.ρ, q_sno)\n\n        nu =\n            nM[1] * u[1] +\n            nM[3] * max(u[3], rain_w, snow_w, u[3] - rain_w, u[3] - snow_w)\n    end\n    return abs(nu)\nend\n\n@inline function flux_first_order!(\n    m::KinematicModel,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    _...,\n)\n    FT = eltype(state)\n    @inbounds begin\n        q_rai::FT = state.ρq_rai / state.ρ\n        q_sno::FT = state.ρq_sno / state.ρ\n        rain_w = terminal_velocity(param_set, CM1M.RainType(), state.ρ, q_rai)\n        snow_w = terminal_velocity(param_set, CM1M.SnowType(), state.ρ, q_sno)\n\n        # advect moisture ...\n        flux.ρ = SVector(state.ρu[1], FT(0), state.ρu[3])\n        flux.ρq_tot = SVector(\n            state.ρu[1] * state.ρq_tot / state.ρ,\n            FT(0),\n            state.ρu[3] * state.ρq_tot / state.ρ,\n        )\n        flux.ρq_liq = SVector(\n            state.ρu[1] * state.ρq_liq / state.ρ,\n            FT(0),\n            state.ρu[3] * state.ρq_liq / state.ρ,\n        )\n        flux.ρq_ice = SVector(\n            state.ρu[1] * state.ρq_ice / state.ρ,\n            FT(0),\n            state.ρu[3] * state.ρq_ice / state.ρ,\n        )\n        flux.ρq_rai = SVector(\n            state.ρu[1] * state.ρq_rai / state.ρ,\n            FT(0),\n            (state.ρu[3] / state.ρ - rain_w) * state.ρq_rai,\n        )\n        flux.ρq_sno = SVector(\n            state.ρu[1] * state.ρq_sno / state.ρ,\n            FT(0),\n            (state.ρu[3] / state.ρ - snow_w) * state.ρq_sno,\n        )\n        # ... energy ...\n        flux.ρe = SVector(\n            state.ρu[1] / state.ρ * (state.ρe + aux.p),\n            FT(0),\n            state.ρu[3] / state.ρ * (state.ρe + aux.p),\n        )\n        # ... and don't advect momentum (kinematic setup)\n    end\nend\n\nfunction source!(\n    m::KinematicModel,\n    source::Vars,\n    state::Vars,\n    diffusive::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n)\n    FT = eltype(state)\n\n    _grav::FT = grav(param_set)\n\n    _e_int_v0::FT = e_int_v0(param_set)\n    _e_int_i0::FT = e_int_i0(param_set)\n\n    _cv_d::FT = cv_d(param_set)\n    _cv_v::FT = cv_v(param_set)\n    _cv_l::FT = cv_l(param_set)\n    _cv_i::FT = cv_i(param_set)\n\n    _T_0::FT = T_0(param_set)\n    _T_freeze = T_freeze(param_set)\n\n    @inbounds begin\n        e_tot = state.ρe / state.ρ\n        q_tot = state.ρq_tot / state.ρ\n        q_liq = state.ρq_liq / state.ρ\n        q_ice = state.ρq_ice / state.ρ\n        q_rai = state.ρq_rai / state.ρ\n        q_sno = state.ρq_sno / state.ρ\n        u = state.ρu[1] / state.ρ\n        w = state.ρu[3] / state.ρ\n        ρ = state.ρ\n        e_int = e_tot - 1 // 2 * (u^2 + w^2) - _grav * aux.z_coord\n\n        q = PhasePartition(q_tot, q_liq, q_ice)\n        T = air_temperature(param_set, e_int, q)\n        _Lf = latent_heat_fusion(param_set, T)\n        # equilibrium state at current T\n        ts_eq = PhaseEquil_ρTq(param_set, state.ρ, T, q_tot)\n        q_eq = PhasePartition(ts_eq)\n\n        # zero out the source terms\n        source.ρq_tot = FT(0)\n        source.ρq_liq = FT(0)\n        source.ρq_ice = FT(0)\n        source.ρq_rai = FT(0)\n        source.ρq_sno = FT(0)\n        source.ρe = FT(0)\n\n        # vapour -> cloud liquid water\n        source.ρq_liq +=\n            ρ * conv_q_vap_to_q_liq_ice(param_set, CM1M.LiquidType(), q_eq, q)\n        # vapour -> cloud ice\n        source.ρq_ice +=\n            ρ * conv_q_vap_to_q_liq_ice(param_set, CM1M.IceType(), q_eq, q)\n\n        ## cloud liquid water -> rain\n        acnv = ρ * conv_q_liq_to_q_rai(param_set, q_liq)\n        source.ρq_liq -= acnv\n        source.ρq_tot -= acnv\n        source.ρq_rai += acnv\n        source.ρe -= acnv * (_cv_l - _cv_d) * (T - _T_0)\n\n        ## cloud ice -> snow\n        acnv = ρ * conv_q_ice_to_q_sno(param_set, q, state.ρ, T)\n        source.ρq_ice -= acnv\n        source.ρq_tot -= acnv\n        source.ρq_sno += acnv\n        source.ρe -= acnv * ((_cv_i - _cv_d) * (T - _T_0) - _e_int_i0)\n\n        # cloud liquid water + rain -> rain\n        accr =\n            ρ * accretion(\n                param_set,\n                CM1M.LiquidType(),\n                CM1M.RainType(),\n                q_liq,\n                q_rai,\n                state.ρ,\n            )\n        source.ρq_liq -= accr\n        source.ρq_tot -= accr\n        source.ρq_rai += accr\n        source.ρe -= accr * (_cv_l - _cv_d) * (T - _T_0)\n\n        # cloud ice + snow -> snow\n        accr =\n            ρ * accretion(\n                param_set,\n                CM1M.IceType(),\n                CM1M.SnowType(),\n                q_ice,\n                q_sno,\n                state.ρ,\n            )\n        source.ρq_ice -= accr\n        source.ρq_tot -= accr\n        source.ρq_sno += accr\n        source.ρe -= accr * ((_cv_i - _cv_d) * (T - _T_0) - _e_int_i0)\n\n        # cloud liquid water + snow -> snow or rain\n        accr =\n            ρ * accretion(\n                param_set,\n                CM1M.LiquidType(),\n                CM1M.SnowType(),\n                q_liq,\n                q_sno,\n                state.ρ,\n            )\n        if T < _T_freeze\n            source.ρq_liq -= accr\n            source.ρq_tot -= accr\n            source.ρq_sno += accr\n            source.ρe -= accr * ((_cv_i - _cv_d) * (T - _T_0) - _e_int_i0)\n        else\n            source.ρq_liq -= accr\n            source.ρq_tot -= accr\n            source.ρq_sno -= accr * (_cv_l / _Lf * (T - _T_freeze))\n            source.ρq_rai += accr * (FT(1) + _cv_l / _Lf * (T - _T_freeze))\n            source.ρe +=\n                -accr * ((_cv_l - _cv_d) * (T - _T_0) + _cv_l * (T - _T_freeze))\n        end\n\n        # cloud ice + rain -> snow\n        accr =\n            ρ * accretion(\n                param_set,\n                CM1M.IceType(),\n                CM1M.RainType(),\n                q_ice,\n                q_rai,\n                state.ρ,\n            )\n        accr_rain_sink =\n            ρ * accretion_rain_sink(param_set, q_ice, q_rai, state.ρ)\n        source.ρq_ice -= accr\n        source.ρq_tot -= accr\n        source.ρq_rai -= accr_rain_sink\n        source.ρq_sno += accr + accr_rain_sink\n        source.ρe +=\n            accr_rain_sink * _Lf -\n            accr * ((_cv_i - _cv_d) * (T - _T_0) - _e_int_i0)\n\n        # rain + snow -> snow or rain\n        if T < _T_freeze\n            accr =\n                ρ * accretion_snow_rain(\n                    param_set,\n                    CM1M.SnowType(),\n                    CM1M.RainType(),\n                    q_sno,\n                    q_rai,\n                    state.ρ,\n                )\n            source.ρq_sno += accr\n            source.ρq_rai -= accr\n            source.ρe += accr * _Lf\n        else\n            accr =\n                ρ * accretion_snow_rain(\n                    param_set,\n                    CM1M.RainType(),\n                    CM1M.SnowType(),\n                    q_rai,\n                    q_sno,\n                    state.ρ,\n                )\n            source.ρq_rai += accr\n            source.ρq_sno -= accr\n            source.ρe -= accr * _Lf\n        end\n\n        # rain -> vapour\n        evap =\n            ρ * evaporation_sublimation(\n                param_set,\n                CM1M.RainType(),\n                q,\n                q_rai,\n                state.ρ,\n                T,\n            )\n        source.ρq_rai += evap\n        source.ρq_tot -= evap\n        source.ρe -= evap * (_cv_l - _cv_d) * (T - _T_0)\n\n        # snow -> vapour\n        subl =\n            ρ * evaporation_sublimation(\n                param_set,\n                CM1M.SnowType(),\n                q,\n                q_sno,\n                state.ρ,\n                T,\n            )\n        source.ρq_sno += subl\n        source.ρq_tot -= subl\n        source.ρe -= subl * ((_cv_i - _cv_d) * (T - _T_0) - _e_int_i0)\n\n        # snow -> rain\n        melt = ρ * snow_melt(param_set, q_sno, state.ρ, T)\n\n        source.ρq_sno -= melt\n        source.ρq_rai += melt\n        source.ρe -= melt * _Lf\n    end\nend\n\nfunction main()\n    # Working precision\n    FT = Float64\n    # DG polynomial order\n    N = 4\n    # Domain resolution and size\n    Δx = FT(500)\n    Δy = FT(1)\n    Δz = FT(250)\n    resolution = (Δx, Δy, Δz)\n    # Domain extents\n    xmax = 90000\n    ymax = 10\n    zmax = 16000\n    # initial configuration\n    wmax = FT(0.6)  # max velocity of the eddy  [m/s]\n    θ_0 = FT(289) # init. theta value (const) [K]\n    p_0 = FT(101500) # surface pressure [Pa]\n    p_1000 = FT(100000) # reference pressure in theta definition [Pa]\n    qt_0 = FT(7.5 * 1e-3) # init. total water specific humidity (const) [kg/kg]\n    z_0 = FT(0) # surface height\n\n    # time stepping\n    t_ini = FT(0)\n    t_end = FT(5 * 60) #FT(4 * 60 * 60) #TODO\n    dt = FT(0.25)\n    #CFL = FT(1.75)\n    filter_freq = 1\n    output_freq = 1200\n    interval = \"1200steps\"\n\n    # periodicity and boundary numbers\n    periodicity_x = false\n    periodicity_y = true\n    periodicity_z = false\n    idx_bc_left = 1\n    idx_bc_right = 2\n    idx_bc_front = 3\n    idx_bc_back = 4\n    idx_bc_bottom = 5\n    idx_bc_top = 6\n\n\n    #! format: off\n    z_range = [\n        0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 2500,\n        2600, 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400, 3500, 3600, 3700, 3800, 3900, 4000, 4100, 4200, 4300, 4400, 4500, 4600, 4700, 4800, 4900,\n        5000, 5100, 5200, 5300, 5400, 5500, 5600, 5700, 5800, 5900, 6000, 6100, 6200, 6300, 6400, 6500, 6600, 6700, 6800, 6900, 7000, 7100, 7200, 7300,\n        7400, 7500, 7600, 7700, 7800, 7900, 8000, 8100, 8200, 8300, 8400, 8500, 8600, 8700, 8800, 8900, 9000, 9100, 9200, 9300, 9400, 9500, 9600, 9700,\n        9800, 9900, 10000, 10100, 10200, 10300, 10400, 10500, 10600, 10700, 10800, 10900, 11000, 11100, 11200, 11300, 11400, 11500, 11600, 11700,\n        11800, 11900, 12000, 12100, 12200, 12300, 12400, 12500, 12600, 12700, 12800, 12900, 13000, 13100, 13200, 13300, 13400, 13500, 13600, 13700,\n        13800, 13900, 14000, 14100, 14200, 14300, 14400, 14500, 14600, 14700, 14800, 14900, 15000, 15100, 15200, 15300, 15400, 15500, 15600, 15700,\n        15800, 15900, 16000, 16100, 16200, 16300, 16400, 16500, 16600, 16700, 16800, 16900, 17000]\n\n    T_range = [\n        299.184, 297.628892697526, 296.498576625316, 295.708541362848, 295.174276489603, 294.811271585061, 294.5350162287, 294.261,\n        293.920421043582, 293.507311764631, 293.031413133474, 292.502466120435, 291.930211695841, 291.324390830018, 290.694744493293, 290.051013655991,\n        289.402939288437, 288.760262346828, 288.130849318341, 287.515505258001, 286.913372692428, 286.323594148241, 285.74531215206, 285.177669230506,\n        284.619807910198, 284.070870717755, 283.530000179798, 282.996338822946, 282.469029173819, 281.947213759037, 281.43003510522, 280.916635738988,\n        280.406158186959, 279.897744975755, 279.390538631995, 278.883681682298, 278.376316653285, 277.867586071576, 277.356632463789, 276.842598356546,\n        276.324685598782, 275.802667484647, 275.276636664165, 274.746689276312, 274.212921460063, 273.675429354393, 273.134309098278, 272.589656830692,\n        272.041568690611, 271.49014081701, 270.935469348864, 270.377650425148, 269.816780184838, 269.252954766908, 268.686270310335, 268.116822954093,\n        267.544708837157, 266.970024098502, 266.392864877104, 265.813327311938, 265.231507541979, 264.64747745881, 264.060960130126, 263.47141707779,\n        262.878303486873, 262.281074542448, 261.679185429586, 261.072091333358, 260.459247438837, 259.840108931094, 259.214130995201, 258.580768816228,\n        257.939477579249, 257.289712469334, 256.630928671556, 255.962581370986, 255.284125752695, 254.595017001755, 253.894710327766, 253.182887991267,\n        252.46001426264, 251.726721646298, 250.983642646651, 250.231409768111, 249.470655515088, 248.702012391995, 247.926112903242, 247.14358955324,\n        246.355074846402, 245.561201287137, 244.762601379858, 243.959907628976, 243.153728314245, 242.344349049499, 241.531825963971, 240.716210279609,\n        239.897553218363, 239.075906002182, 238.251319853016, 237.423845992814, 236.593535643524, 235.760440027097, 234.924610365481, 234.086097880626,\n        233.244953794481, 232.401229328996, 231.554975706119, 230.7062441478, 229.855085875989, 229.001552112634, 228.145694079684, 227.287574051742,\n        226.42757986392, 225.566470305038, 224.705024343724, 223.84402094861, 222.984239088323, 222.126457731494, 221.271455846752, 220.420012402726,\n        219.572906368047, 218.730916711342, 217.894822401242, 217.065402406377, 216.243435695375, 215.429701236865, 214.624977999479, 213.830040741192,\n        213.045586818019, 212.272245655601, 211.51064437337, 210.761410090759, 210.0251699272, 209.302551002126, 208.594180434969, 207.90068534516,\n        207.222682432974, 206.560708873131, 205.915265185628, 205.286851694754, 204.675968724799, 204.083116600052, 203.508795644802, 202.95350618334,\n        202.417748539953, 201.902023577843, 201.407120974388, 200.934594512862, 200.486122895611, 200.063384824981, 199.668059003318, 199.301824132969,\n        198.96635891628, 198.663342055596, 198.394452253265, 198.161279286222, 197.963547643009, 197.799211358657, 197.666154525954, 197.562261237686,\n        197.485415586642, 197.433501665607, 197.40440356737, 197.396005384718, 197.406191210439, 197.432899562191, 197.47476703369, 197.530913717737,\n        197.600469393593, 197.682563840515, 197.776326837764]\n\n    q_range = [\n        0.0162321692080669, 0.0167673003973785, 0.0169842024326598, 0.0169337519538153, 0.0166663497239751, 0.0162321692080669, 0.015681352854829,\n        0.0150641570577189, 0.0144310460364482, 0.0138327343680399, 0.013320177602368, 0.012928720727755, 0.0126305522364067, 0.0123819207241334,\n        0.0121389587958033, 0.0118577075098814, 0.0115066332638913, 0.0111042588941075, 0.010681699769686, 0.0102701709364724, 0.0099009900990099,\n        0.00959624936114117, 0.00934081059469477, 0.00911016542683696, 0.00887975233624587, 0.0086249628234361, 0.00832855696312967, 0.0080029519599734,\n        0.00766802230757524, 0.00734369421478042, 0.007049945387747, 0.00680137732291751, 0.00659092388806814, 0.00640607314371358, 0.00623428890344645,\n        0.00606301560481065, 0.00588287130354119, 0.005697230175774, 0.00551266483334836, 0.00533575758863248, 0.00517309988062077, 0.00502869447640062,\n        0.004896158939909, 0.00476650506495816, 0.00463073495294506, 0.00447984071677452, 0.00430803604057719, 0.00412246330674869, 0.0039335098478028,\n        0.00375157835183152, 0.00358708648864089, 0.00344726315880705, 0.00332653452987796, 0.00321611598159287, 0.00310721394018452, 0.00299102691924227,\n        0.00286116318208214, 0.00272089896736183, 0.00257593295768495, 0.00243197016394643, 0.00229472213908012, 0.00216860732664965, 0.00205285001385606,\n        0.00194537329026098, 0.00184409796137261, 0.00174694285001248, 0.0016522111336286, 0.00155974942036123, 0.00146979043510184, 0.0013825670343689,\n        0.00129831219414761, 0.00121701485317001, 0.001137687161242, 0.00105909638608903, 0.000980009009141718,  0.00089919072834449, 0.000815997019873583,\n        0.000732145660692171, 0.00064994635423271, 0.000571710312185518, 0.000499750124937531, 0.000435858502312957, 0.000379744409038409,\n        0.000330595173284745, 0.000287597487595375, 0.000249937515621095, 0.000216895300299521, 0.000188127832006747, 0.000163386230957203,\n        0.000142421466578296, 0.000124984376952881, 0.000110689772327441, 9.86086827423503e-05, 8.7676088112503e-05, 7.68268601082915e-05,\n        6.49957752746072e-05, 5.15108887771554e-05, 3.72737503739521e-05, 2.35794100864691e-05, 1.17230575137195e-05, 2.999991000027e-06,\n        1.69619792857815e-06, 3.07897588648539e-06, 2.26364535967942e-06, 3.65542843474416e-07, 1.49999775000338e-06, 2.45283190408077e-06,\n        2.55351934700394e-06, 2.09779045350945e-06, 1.38137398580681e-06, 6.99999510000343e-07, 2.86443287526343e-07, 1.21662921693633e-07,\n        1.23660803334821e-07, 2.10439089554136e-07, 2.99999910000027e-07, 3.27788493770303e-07, 2.99022297121994e-07, 2.36361815382337e-07,\n        1.62467544245733e-07, 9.9999990000001e-08, 6.64022211520834e-08, 5.82475139663064e-08, 6.68916919016523e-08, 8.36905765913686e-08,\n        9.9999990000001e-08, 1.09002484657468e-07, 1.11187531664659e-07, 1.0887133139317e-07, 1.04370084083517e-07, 9.9999990000001e-08,\n        9.75877787592422e-08, 9.70022971221656e-08, 9.76229211090621e-08, 9.88290267308115e-08, 9.9999990000001e-08, 1.00646340200832e-07,\n        1.00803219684934e-07, 1.0063692406888e-07, 1.00313748968563e-07, 9.9999990000001e-08, 9.98268004299175e-08, 9.97847641264901e-08,\n        9.9829322608123e-08, 9.99159173931719e-08, 9.9999990000001e-08, 1.00046398078965e-07, 1.00057663808278e-07, 1.0004572549811e-07,\n        1.00022521458629e-07, 9.9999990000001e-08, 9.99875472541912e-08, 9.99845206403455e-08, 9.99877153994048e-08, 9.99939367723098e-08,\n        9.9999990000001e-08, 1.00003352904274e-07, 1.00004193630342e-07, 1.00003352904274e-07, 1.00001671452137e-07, 9.9999990000001e-08,\n        9.99989811287191e-08, 9.99986448382919e-08, 9.99988129835055e-08, 9.99993174191465e-08, 9.9999990000001e-08, 1.00000662580856e-07,\n        1.00001167016497e-07, 1.0000133516171e-07, 1.00000998871283e-07, 9.9999990000001e-08]\n\n    p_range = [\n        101500, 100315.025749516, 99139.9660271538, 97974.7710180798, 96819.3909074598, 95673.7758804597, 94537.876176083, 93411.6422486837,\n        92295.0246064536, 91187.9737719499, 90090.4404029628, 89002.3752464385, 87923.7291106931, 86854.4528968052, 85794.49764766, 84743.814421206,\n        83702.3543252787, 82670.0686672607, 81646.9088044215, 80632.826109327, 79627.7720985403, 78631.6983820215, 77644.5566288927, 76666.2986022276,\n        75696.8762101083, 74736.2413760207, 73784.3460742183, 72841.1424820254, 71906.5828275339, 70980.6193544154, 70063.204453007, 69154.2906087673,\n        68253.8303673938, 67361.7763702768, 66478.0814065118, 65602.6982808849, 64735.579849904, 63876.6791769641, 63025.949377182, 62183.3435815507,\n        61348.8150705175, 60522.317221463, 59703.8034731598, 58893.2273619179, 58090.542574603, 57295.7028140736, 56508.6618359204, 55729.3736066619,\n        54957.7921455483, 54193.8714880201, 53437.5658219265, 52688.8294339685, 51947.6166734621, 51213.8819892159, 50487.5799836161, 49768.6652753625,\n        49057.0925369589, 48352.8166561238, 47655.7925743794, 46965.9752497711, 46283.3197958903, 45607.7814272171, 44939.3154221458, 44277.8771606411,\n        43623.4221794563, 42975.9060319998, 42335.2843266238, 41701.5128914544, 41074.5476095614, 40454.3443808926, 39840.8592642808, 39234.0484216161,\n        38633.868080086, 38040.274570662, 37453.2243845259, 36872.6740298782, 36298.5800710783, 35730.8992971221, 35169.5885531642, 34614.6047016162,\n        34065.9047673383, 33523.4458805626, 32987.1852382959, 32457.0801436939, 31933.0880637786, 31415.1664829788, 30903.2729431824, 30397.3652161132,\n        29897.4011309542, 29403.3385345499, 28915.135440009, 28432.7499682896, 27956.1403087067, 27485.2647592591, 27020.0817857294, 26560.5498717234,\n        26106.6275597007, 25658.2736275352, 25215.4469119545, 24778.106267783, 24346.2107202062, 23919.7194049202, 23498.5915276787, 23082.7864056438,\n        22672.2635279751, 22266.9824021027, 21866.9025958119, 21471.9839183077, 21082.1862391501, 20697.469446465, 20317.7936031548, 19943.1188855014,\n        19573.4055416778, 19208.6139342067, 18848.7046021555, 18493.6381033452, 18143.3750575744, 17797.8763325512, 17457.1028579612, 17121.0155825635,\n        16789.575634671, 16462.7442590809, 16140.4827744667, 15822.752617039, 15509.5154044811, 15200.7327737537, 14896.3664255555, 14596.378315538,\n        14300.7304630906, 14009.3849072278, 13722.3038717104, 13439.4497001572, 13160.7848122237, 12886.2717485712, 12615.8732366994, 12349.552023955,\n        12087.2709233428, 11828.9930105004, 11574.6814267238, 11324.2993335354, 11077.8100828701, 10835.1771502091, 10596.3640894538, 10361.3345792892,\n        10130.0524910676, 9902.48171660711, 9678.58621552163, 9458.33021860609, 9241.67802445076, 9028.59395243382, 8819.04251762682, 8612.98836336888,\n        8410.3962187252, 8211.2309379189, 8015.45756519932, 7823.04116500174, 7633.94687886457, 7448.14015673894, 7265.58652567913, 7086.25151273945,\n        6910.10064497419, 6910.10064497419, 6910.10064497419, 6910.10064497419, 6910.10064497419, 6910.10064497419, 6910.10064497419, 6910.10064497419,\n        6910.10064497419, 6910.10064497419, 6910.10064497419 ]\n\n    ρ_range = [\n        1.17051362179725, 1.16251824590627, 1.15313016112624, 1.1426566568231, 1.13140764159872, 1.1196895485888, 1.10780103484676, 1.0960305075832,\n        1.08459745274435, 1.07348344546654, 1.06261389733783, 1.05192695939713, 1.04140273218196, 1.03103180334705, 1.02080494033636, 1.01071303046314,\n        1.00073949487493, 0.990838128123433, 0.980963129632192, 0.971094063252625, 0.961216822759604, 0.951323251200104, 0.941426814080701,\n        0.931545806522527, 0.921697879536919, 0.911900034022987, 0.902164578162167, 0.892487313537494, 0.882860249793038, 0.873275726601859,\n        0.863726411598162, 0.854208105591224, 0.844727922437057, 0.8352954667279, 0.825919969478444, 0.816610286976537, 0.807373343850795,\n        0.798209593686815, 0.789117941453129, 0.780097337389561, 0.771146612530646, 0.762264270399455, 0.753452744759353, 0.74471551136303,\n        0.736055874178972, 0.72747696742561, 0.718980350842218, 0.7105619137831, 0.70221632469523, 0.693938467344086, 0.685723438615901,\n        0.677567861578885, 0.669473709625074, 0.661444143644113, 0.653482167320322, 0.645590627902435, 0.637771283999691, 0.630022091606339,\n        0.622340165988717, 0.614722736979947, 0.607167148420651, 0.599671383580347, 0.592236161810936, 0.584863205035121, 0.577554164218497,\n        0.570310604494631, 0.563133875176496, 0.556024730482117, 0.548983747911074, 0.542011464088805, 0.535108377645103, 0.528275027848979,\n        0.521512222336075, 0.514820790002398, 0.508201503057759, 0.501655076786909, 0.495181994058148, 0.488781994213898, 0.482454636660537,\n        0.476199055919507, 0.470012952419531, 0.463893943289264, 0.457840307961496, 0.451850519497349, 0.445923093407251, 0.440056584487831,\n        0.434249561420135, 0.428500536139493, 0.422808039743611, 0.41717064648228, 0.411586974922854, 0.406055719826046, 0.40057578774956,\n        0.395146674561446, 0.389768253950893, 0.384440397458089, 0.37916287643901, 0.373935102771167, 0.36875641578014, 0.363626174398787,\n        0.358543758947717, 0.353508656205969, 0.348520707739897, 0.343579834253573, 0.338685947906162, 0.333838950271578, 0.329038686145689,\n        0.324284810799633, 0.319576938026155, 0.314914687777366, 0.310297688380068, 0.305725572307512, 0.301197591585183, 0.296712545171569,\n        0.292269251091959, 0.287866570849034, 0.283503406532332, 0.279178694598262, 0.27489141222907, 0.270640579366203, 0.266425260909222,\n        0.262244566294465, 0.258097650982816, 0.25398371429053, 0.249901999425458, 0.245851790781137, 0.241832414286703, 0.237843244370408,\n        0.233883781756254, 0.229953637104228, 0.226052458592054, 0.222179928272777, 0.218335761470863, 0.214519707224664, 0.210731549222293,\n        0.206971102905131, 0.203238226271481, 0.199532888953038, 0.195855122091922, 0.192204980437394, 0.188582544697456, 0.184987920053912,\n        0.181421234993557, 0.177882641628151, 0.174372316360016, 0.170890455921146, 0.167437037955477, 0.164011457709294, 0.160613093688105,\n        0.157241413931045, 0.15389597846219, 0.150576437441233, 0.147282529608045, 0.144014082002788, 0.140771009961674, 0.137553374518383,\n        0.134362580606833, 0.131201172349935, 0.128071588633207, 0.124976110750845, 0.121916861907264, 0.121948919193911, 0.121966894890357,\n        0.121972083953105, 0.121965790399749, 0.121949291096701, 0.121923436104917, 0.121888780280552, 0.121845875238272, 0.121795274570523,\n        0.121737533130168]\n\n    dρ_range = [\n        -3.59937517425616e-05, -8.74236524613322e-05, -9.98245979099523e-05, -0.00010912795442958, -0.000115340910679925, -0.000118522777615024,\n        -0.000118766703148947, -0.000116181583122055, -0.00011260864631165, -0.000109795778726265, -0.000107714951035596, -0.000106039943861968,\n        -0.000104460270820561, -0.000102973683474333, -0.000101578762228199, -0.000100274450938608, -9.92861102161202e-05, -9.88281212364185e-05,\n        -9.86963271464388e-05, -9.87085122902761e-05, -9.88588230322522e-05, -9.8981065311496e-05, -9.89171829920543e-05, -9.86735488551799e-05,\n        -9.82566574041453e-05, -9.76729921159812e-05, -9.70500064028184e-05, -9.65086017246105e-05, -9.60454400625446e-05, -9.56572439955241e-05,\n        -9.5340728706457e-05, -9.5008763743794e-05, -9.45788919208772e-05, -9.40548370115828e-05, -9.3440348194004e-05, -9.27391611164088e-05,\n        -9.20016033219379e-05, -9.12752248054719e-05, -9.05595697070144e-05, -8.98541831357713e-05, -8.91654815701201e-05, -8.84752762943482e-05,\n        -8.77494447742739e-05, -8.69897168393693e-05, -8.61978039593136e-05, -8.53753836002353e-05, -8.45662005091732e-05, -8.38114256450538e-05,\n        -8.31088828495735e-05, -8.24564348214017e-05, -8.1851961718636e-05, -8.12540449384666e-05, -8.06237270285041e-05, -7.99625864890717e-05,\n        -7.92721952020196e-05, -7.85541037865398e-05, -7.78377768942751e-05, -7.71508783726086e-05, -7.64922509147597e-05, -7.58607550079355e-05,\n        -7.5255257575603e-05, -7.46578263440909e-05, -7.40437160173945e-05, -7.34126676822411e-05, -7.27655403821961e-05, -7.21031866632626e-05,\n        -7.14303672571544e-05, -7.07515642299482e-05, -7.00671933172211e-05, -6.93776513635009e-05, -6.86833098465315e-05, -6.79822059852417e-05,\n        -6.72725239627913e-05, -6.65548356116998e-05, -6.58297114361597e-05, -6.50977145039132e-05, -6.43646771280071e-05, -6.36360540138981e-05,\n        -6.29118551436968e-05, -6.22041292322341e-05, -6.15221570675246e-05, -6.08606412866085e-05, -6.02146109367996e-05, -5.95836319123706e-05,\n        -5.89672983766289e-05, -5.8365225270403e-05, -5.7777757303502e-05, -5.72051962789751e-05, -5.66471081172719e-05, -5.61030583593601e-05,\n        -5.55726043916327e-05, -5.50543285425413e-05, -5.45449966705289e-05, -5.40374636372204e-05, -5.35311627048121e-05, -5.30261985318617e-05,\n        -5.25253559659224e-05, -5.20312184290027e-05, -5.15435901963297e-05, -5.10622691485893e-05, -5.05870395077161e-05, -5.01151309694529e-05,\n        -4.96439716171912e-05, -4.91736441117515e-05, -4.87042469224382e-05, -4.82358875773372e-05, -4.77700486961607e-05, -4.73081018750182e-05,\n        -4.68499874419861e-05, -4.63956358750055e-05, -4.59449597549036e-05, -4.54985344751462e-05, -4.50631284786667e-05, -4.46397691588802e-05,\n        -4.42280098023119e-05, -4.38274214139499e-05, -4.34376409961601e-05, -4.30583023124663e-05, -4.26889789379916e-05, -4.23292356052957e-05,\n        -4.19786196104028e-05, -4.16366783570754e-05, -4.13029635571471e-05, -4.09770310849877e-05, -4.06584588726149e-05, -4.03468387207095e-05,\n        -4.00417403212974e-05, -3.9742477928175e-05, -3.94474194224351e-05, -3.91560587225531e-05, -3.88680420136295e-05, -3.85830382145498e-05,\n        -3.83007173685112e-05, -3.80207275262849e-05, -3.77427401195186e-05, -3.74664417323129e-05, -3.71911016163793e-05, -3.69155924693975e-05,\n        -3.66396540346307e-05, -3.6363043157771e-05, -3.60854967755032e-05, -3.58067813502568e-05, -3.55266676411338e-05, -3.52449049917705e-05,\n        -3.49612716252254e-05, -3.46756639284376e-05, -3.43938806523058e-05, -3.41187600097204e-05, -3.38494075224641e-05, -3.35849193859056e-05,\n        -3.33243734832797e-05, -3.3066881627664e-05, -3.28115645135523e-05, -3.25575278845173e-05, -3.23039138342865e-05, -3.20463462400501e-05,\n        -3.17652064416084e-05, -3.14588936623928e-05, -3.11289784299643e-05, -3.07770405929711e-05, -1.50046969840055e-05, -2.48015587883801e-07,\n        -1.13657079374249e-07, -7.70202792434148e-09, -1.15984516793898e-07, -2.12713494118585e-07, -3.03471417808635e-07, -3.88725590955189e-07,\n        -4.68452477358432e-07, -5.42635999340642e-07, -6.1126695981267e-07]\n    #! format: on\n\n    init_T = Spline1D(z_range, T_range)\n    init_qt = Spline1D(z_range, q_range)\n    init_p = Spline1D(z_range, p_range)\n    init_ρ = Spline1D(z_range, ρ_range)\n    init_dρ = Spline1D(z_range, dρ_range)\n\n    driver_config, ode_solver_type = config_kinematic_eddy(\n        FT,\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        wmax,\n        θ_0,\n        p_0,\n        p_1000,\n        qt_0,\n        z_0,\n        periodicity_x,\n        periodicity_y,\n        periodicity_z,\n        idx_bc_left,\n        idx_bc_right,\n        idx_bc_front,\n        idx_bc_back,\n        idx_bc_bottom,\n        idx_bc_top,\n    )\n    solver_config = ClimateMachine.SolverConfiguration(\n        t_ini,\n        t_end,\n        driver_config,\n        (init_T, init_qt, init_p, init_ρ, init_dρ),\n        ode_solver_type = ode_solver_type,\n        ode_dt = dt,\n        init_on_cpu = true,\n        #Courant_number = CFL,\n    )\n\n    model = driver_config.bl\n\n    mpicomm = MPI.COMM_WORLD\n\n    # get state variables indices for filtering\n    ρq_liq_ind = varsindex(vars_state(model, Prognostic(), FT), :ρq_liq)\n    ρq_ice_ind = varsindex(vars_state(model, Prognostic(), FT), :ρq_ice)\n    ρq_rai_ind = varsindex(vars_state(model, Prognostic(), FT), :ρq_rai)\n    ρq_sno_ind = varsindex(vars_state(model, Prognostic(), FT), :ρq_sno)\n    # get aux variables indices for testing\n    q_tot_ind = varsindex(vars_state(model, Auxiliary(), FT), :q_tot)\n    q_vap_ind = varsindex(vars_state(model, Auxiliary(), FT), :q_vap)\n    q_liq_ind = varsindex(vars_state(model, Auxiliary(), FT), :q_liq)\n    q_ice_ind = varsindex(vars_state(model, Auxiliary(), FT), :q_ice)\n    q_rai_ind = varsindex(vars_state(model, Auxiliary(), FT), :q_rai)\n    q_sno_ind = varsindex(vars_state(model, Auxiliary(), FT), :q_sno)\n    S_liq_ind = varsindex(vars_state(model, Auxiliary(), FT), :S_liq)\n    S_ice_ind = varsindex(vars_state(model, Auxiliary(), FT), :S_ice)\n    rain_w_ind = varsindex(vars_state(model, Auxiliary(), FT), :rain_w)\n    snow_w_ind = varsindex(vars_state(model, Auxiliary(), FT), :snow_w)\n\n    # filter out negative values\n    cb_tmar_filter =\n        GenericCallbacks.EveryXSimulationSteps(filter_freq) do (init = false)\n            Filters.apply!(\n                solver_config.Q,\n                (:ρq_tot, :ρq_liq, :ρq_ice, :ρq_rai, :ρq_sno),\n                solver_config.dg.grid,\n                TMARFilter(),\n            )\n            nothing\n        end\n    cb_boyd_filter =\n        GenericCallbacks.EveryXSimulationSteps(filter_freq) do (init = false)\n            Filters.apply!(\n                solver_config.Q,\n                (:ρq_tot, :ρq_liq, :ρq_ice, :ρq_rai, :ρq_sno, :ρe, :ρ),\n                solver_config.dg.grid,\n                BoydVandevenFilter(solver_config.dg.grid, 1, 8),\n            )\n        end\n\n    # output for paraview\n\n    # initialize base output prefix directory from rank 0\n    vtkdir = abspath(joinpath(ClimateMachine.Settings.output_dir, \"vtk\"))\n    if MPI.Comm_rank(mpicomm) == 0\n        mkpath(vtkdir)\n    end\n    MPI.Barrier(mpicomm)\n\n    vtkstep = [0]\n    cb_vtk =\n        GenericCallbacks.EveryXSimulationSteps(output_freq) do (init = false)\n            out_dirname = @sprintf(\n                \"microphysics_test_4_mpirank%04d_step%04d\",\n                MPI.Comm_rank(mpicomm),\n                vtkstep[1]\n            )\n            out_path_prefix = joinpath(vtkdir, out_dirname)\n            @info \"doing VTK output\" out_path_prefix\n            writevtk(\n                out_path_prefix,\n                solver_config.Q,\n                solver_config.dg,\n                flattenednames(vars_state(model, Prognostic(), FT)),\n                solver_config.dg.state_auxiliary,\n                flattenednames(vars_state(model, Auxiliary(), FT)),\n            )\n            vtkstep[1] += 1\n            nothing\n        end\n\n    # output for netcdf\n    boundaries = [\n        FT(0) FT(0) FT(0)\n        xmax ymax zmax\n    ]\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries,\n        resolution,\n    )\n    dgngrps = [\n        setup_dump_state_diagnostics(\n            AtmosLESConfigType(),\n            interval,\n            driver_config.name,\n            interpol = interpol,\n        ),\n        setup_dump_aux_diagnostics(\n            AtmosLESConfigType(),\n            interval,\n            driver_config.name,\n            interpol = interpol,\n        ),\n    ]\n    dgn_config = ClimateMachine.DiagnosticsConfiguration(dgngrps)\n\n    # call solve! function for time-integrator\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (cb_boyd_filter, cb_tmar_filter),\n        check_euclidean_distance = true,\n    )\n\n    max_q_tot = maximum(abs.(solver_config.dg.state_auxiliary[:, q_tot_ind, :]))\n    @test !isnan(max_q_tot)\n\n    max_q_vap = maximum(abs.(solver_config.dg.state_auxiliary[:, q_vap_ind, :]))\n    @test !isnan(max_q_vap)\n\n    max_q_liq = maximum(abs.(solver_config.dg.state_auxiliary[:, q_liq_ind, :]))\n    @test !isnan(max_q_liq)\n\n    max_q_ice = maximum(abs.(solver_config.dg.state_auxiliary[:, q_ice_ind, :]))\n    @test !isnan(max_q_ice)\n\n    max_q_rai = maximum(abs.(solver_config.dg.state_auxiliary[:, q_rai_ind, :]))\n    @test !isnan(max_q_rai)\n\n    max_q_sno = maximum(abs.(solver_config.dg.state_auxiliary[:, q_sno_ind, :]))\n    @test !isnan(max_q_sno)\n\nend\n\nmain()\n"
  },
  {
    "path": "test/Atmos/Parameterizations/Microphysics/KM_saturation_adjustment.jl",
    "content": "include(\"KinematicModel.jl\")\n\nfunction vars_state(m::KinematicModel, ::Prognostic, FT)\n    @vars begin\n        ρ::FT\n        ρu::SVector{3, FT}\n        ρe::FT\n        ρq_tot::FT\n    end\nend\n\nfunction vars_state(m::KinematicModel, ::Auxiliary, FT)\n    @vars begin\n        # defined in init_state_auxiliary\n        p::FT\n        x_coord::FT\n        z_coord::FT\n        # defined in update_aux\n        u::FT\n        w::FT\n        q_tot::FT\n        q_vap::FT\n        q_liq::FT\n        q_ice::FT\n        e_tot::FT\n        e_kin::FT\n        e_pot::FT\n        e_int::FT\n        T::FT\n        S_liq::FT\n        RH::FT\n    end\nend\n\nfunction init_kinematic_eddy!(eddy_model, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n\n    FT = eltype(state)\n\n    _grav::FT = grav(param_set)\n\n    dc = eddy_model.data_config\n    @inbounds begin\n        # density\n        q_pt_0 = PhasePartition(dc.qt_0)\n        R_m, cp_m, cv_m, γ = gas_constants(param_set, q_pt_0)\n        T::FT = dc.θ_0 * (aux.p / dc.p_1000)^(R_m / cp_m)\n        ρ::FT = aux.p / R_m / T\n        state.ρ = ρ\n\n        # moisture\n        state.ρq_tot = ρ * dc.qt_0\n\n        # velocity (derivative of streamfunction)\n        ρu::FT =\n            dc.wmax * dc.xmax / dc.zmax *\n            cos(FT(π) * z / dc.zmax) *\n            cos(2 * FT(π) * x / dc.xmax)\n        ρw::FT =\n            2 * dc.wmax * sin(FT(π) * z / dc.zmax) * sin(2 * π * x / dc.xmax)\n        state.ρu = SVector(ρu, FT(0), ρw)\n        u::FT = ρu / ρ\n        w::FT = ρw / ρ\n\n        # energy\n        e_kin::FT = 1 // 2 * (u^2 + w^2)\n        e_pot::FT = _grav * z\n        e_int::FT = internal_energy(param_set, T, q_pt_0)\n        e_tot::FT = e_kin + e_pot + e_int\n        state.ρe = ρ * e_tot\n    end\n    return nothing\nend\n\nfunction nodal_update_auxiliary_state!(\n    m::KinematicModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    FT = eltype(state)\n    _grav::FT = grav(param_set)\n    @inbounds begin\n        aux.u = state.ρu[1] / state.ρ\n        aux.w = state.ρu[3] / state.ρ\n\n        aux.q_tot = state.ρq_tot / state.ρ\n\n        aux.e_tot = state.ρe / state.ρ\n        aux.e_kin = 1 // 2 * (aux.u^2 + aux.w^2)\n        aux.e_pot = _grav * aux.z_coord\n        aux.e_int = aux.e_tot - aux.e_kin - aux.e_pot\n\n        # saturation adjustment happens here\n        ts = PhaseEquil_ρeq(param_set, state.ρ, aux.e_int, aux.q_tot)\n        q = PhasePartition(ts)\n\n        aux.T = ts.T\n        aux.q_vap = vapor_specific_humidity(q)\n        aux.q_liq = q.liq\n        aux.q_ice = q.ice\n\n        # TODO: add super_saturation method in moist thermo\n        #aux.S = max(0, aux.q_vap / q_vap_saturation(ts) - FT(1)) * FT(100)\n        aux.S_liq = max(0, supersaturation(ts, Liquid()))\n        aux.RH = relative_humidity(ts)\n    end\nend\n\nfunction boundary_state!(\n    ::RusanovNumericalFlux,\n    bctype,\n    m::KinematicModel,\n    state⁺,\n    aux⁺,\n    n,\n    state⁻,\n    aux⁻,\n    t,\n    args...,\n) end\n\n@inline function wavespeed(\n    m::KinematicModel,\n    nM,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    _...,\n)\n    @inbounds u = state.ρu / state.ρ\n    return abs(dot(nM, u))\nend\n\n@inline function flux_first_order!(\n    m::KinematicModel,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n)\n    FT = eltype(state)\n    @inbounds begin\n        # advect moisture ...\n        flux.ρq_tot = SVector(\n            state.ρu[1] * state.ρq_tot / state.ρ,\n            FT(0),\n            state.ρu[3] * state.ρq_tot / state.ρ,\n        )\n        # ... energy ...\n        flux.ρe = SVector(\n            state.ρu[1] / state.ρ * (state.ρe + aux.p),\n            FT(0),\n            state.ρu[3] / state.ρ * (state.ρe + aux.p),\n        )\n        # ... and don't advect momentum (kinematic setup)\n    end\nend\n\nsource!(::KinematicModel, _...) = nothing\n\nfunction main()\n    # Working precision\n    FT = Float64\n    # DG polynomial order\n    N = 4\n    # Domain resolution and size\n    Δx = FT(20)\n    Δy = FT(1)\n    Δz = FT(20)\n    resolution = (Δx, Δy, Δz)\n    # Domain extents\n    xmax = 1500\n    ymax = 10\n    zmax = 1500\n    # initial configuration\n    wmax = FT(0.6)  # max velocity of the eddy  [m/s]\n    θ_0 = FT(289) # init. theta value (const) [K]\n    p_0 = FT(101500) # surface pressure [Pa]\n    p_1000 = FT(100000) # reference pressure in theta definition [Pa]\n    qt_0 = FT(7.5 * 1e-3) # init. total water specific humidity (const) [kg/kg]\n    z_0 = FT(0) # surface height\n\n    # time stepping\n    t_ini = FT(0)\n    t_end = FT(60 * 30)\n    dt = 40\n    output_freq = 9\n    interval = \"9steps\"\n\n    # periodicity and boundary numbers\n    periodicity_x = true\n    periodicity_y = true\n    periodicity_z = false\n    idx_bc_left = 0\n    idx_bc_right = 0\n    idx_bc_front = 0\n    idx_bc_back = 0\n    idx_bc_bottom = 1\n    idx_bc_top = 2\n\n    driver_config, ode_solver_type = config_kinematic_eddy(\n        FT,\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        wmax,\n        θ_0,\n        p_0,\n        p_1000,\n        qt_0,\n        z_0,\n        periodicity_x,\n        periodicity_y,\n        periodicity_z,\n        idx_bc_left,\n        idx_bc_right,\n        idx_bc_front,\n        idx_bc_back,\n        idx_bc_bottom,\n        idx_bc_top,\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t_ini,\n        t_end,\n        driver_config;\n        ode_solver_type = ode_solver_type,\n        ode_dt = dt,\n        init_on_cpu = true,\n        #Courant_number = CFL,\n    )\n\n    mpicomm = MPI.COMM_WORLD\n\n    # output for paraview\n    # initialize base prefix directory from rank 0\n    vtkdir = abspath(joinpath(ClimateMachine.Settings.output_dir, \"vtk\"))\n    if MPI.Comm_rank(mpicomm) == 0\n        mkpath(vtkdir)\n    end\n    MPI.Barrier(mpicomm)\n\n    model = driver_config.bl\n    vtkstep = [0]\n    cbvtk = GenericCallbacks.EveryXSimulationSteps(output_freq) do\n        out_dirname = @sprintf(\n            \"new_ex_1_mpirank%04d_step%04d\",\n            MPI.Comm_rank(mpicomm),\n            vtkstep[1]\n        )\n        out_path_prefix = joinpath(vtkdir, out_dirname)\n        @info \"doing VTK output\" out_path_prefix\n        writevtk(\n            out_path_prefix,\n            solver_config.Q,\n            solver_config.dg,\n            flattenednames(vars_state(model, Prognostic(), FT)),\n            solver_config.dg.state_auxiliary,\n            flattenednames(vars_state(model, Auxiliary(), FT)),\n        )\n        vtkstep[1] += 1\n        nothing\n    end\n\n    # output for netcdf\n    boundaries = [\n        FT(0) FT(0) FT(0)\n        xmax ymax zmax\n    ]\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries,\n        resolution,\n    )\n    dgngrps = [\n        setup_dump_state_diagnostics(\n            AtmosLESConfigType(),\n            interval,\n            driver_config.name,\n            interpol = interpol,\n        ),\n        setup_dump_aux_diagnostics(\n            AtmosLESConfigType(),\n            interval,\n            driver_config.name,\n            interpol = interpol,\n        ),\n    ]\n    dgn_config = ClimateMachine.DiagnosticsConfiguration(dgngrps)\n\n    # get aux variables indices for testing\n    q_tot_ind = varsindex(vars_state(model, Auxiliary(), FT), :q_tot)\n    q_vap_ind = varsindex(vars_state(model, Auxiliary(), FT), :q_vap)\n    q_liq_ind = varsindex(vars_state(model, Auxiliary(), FT), :q_liq)\n    q_ice_ind = varsindex(vars_state(model, Auxiliary(), FT), :q_ice)\n    S_liq_ind = varsindex(vars_state(model, Auxiliary(), FT), :S_liq)\n\n    # call solve! function for time-integrator\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (cbvtk,),\n        check_euclidean_distance = true,\n    )\n\n    # no supersaturation\n    max_S_liq = maximum(abs.(solver_config.dg.state_auxiliary[:, S_liq_ind, :]))\n    @test isequal(max_S_liq, FT(0))\n\n    # qt is conserved\n    max_q_tot = maximum(abs.(solver_config.dg.state_auxiliary[:, q_tot_ind, :]))\n    min_q_tot = minimum(abs.(solver_config.dg.state_auxiliary[:, q_tot_ind, :]))\n    @test isapprox(max_q_tot, qt_0; rtol = 1e-3)\n    @test isapprox(min_q_tot, qt_0; rtol = 1e-3)\n\n    # q_vap + q_liq = q_tot\n    max_water_diff = maximum(abs.(\n        solver_config.dg.state_auxiliary[:, q_tot_ind, :] .-\n        solver_config.dg.state_auxiliary[:, q_vap_ind, :] .-\n        solver_config.dg.state_auxiliary[:, q_liq_ind, :],\n    ))\n    @test isequal(max_water_diff, FT(0))\n\n    # no ice\n    max_q_ice = maximum(abs.(solver_config.dg.state_auxiliary[:, q_ice_ind, :]))\n    @test isequal(max_q_ice, FT(0))\n\n    # q_liq ∈ reference range\n    max_q_liq = maximum(solver_config.dg.state_auxiliary[:, q_liq_ind, :])\n    min_q_liq = minimum(solver_config.dg.state_auxiliary[:, q_liq_ind, :])\n    @test max_q_liq < FT(1e-3)\n    @test isequal(min_q_liq, FT(0))\nend\n\nmain()\n"
  },
  {
    "path": "test/Atmos/Parameterizations/Microphysics/KM_warm_rain.jl",
    "content": "include(\"KinematicModel.jl\")\n\nfunction vars_state(m::KinematicModel, ::Prognostic, FT)\n    @vars begin\n        ρ::FT\n        ρu::SVector{3, FT}\n        ρe::FT\n        ρq_tot::FT\n        ρq_liq::FT\n        ρq_ice::FT\n        ρq_rai::FT\n    end\nend\n\nfunction vars_state(m::KinematicModel, ::Auxiliary, FT)\n    @vars begin\n        # defined in init_state_auxiliary\n        p::FT\n        x_coord::FT\n        z_coord::FT\n        # defined in update_aux\n        u::FT\n        w::FT\n        q_tot::FT\n        q_vap::FT\n        q_liq::FT\n        q_ice::FT\n        q_rai::FT\n        e_tot::FT\n        e_kin::FT\n        e_pot::FT\n        e_int::FT\n        T::FT\n        S_liq::FT\n        RH::FT\n        rain_w::FT\n        # more diagnostics\n        src_cloud_liq::FT\n        src_cloud_ice::FT\n        src_acnv::FT\n        src_accr::FT\n        src_rain_evap::FT\n        flag_rain::FT\n        flag_cloud_liq::FT\n        flag_cloud_ice::FT\n    end\nend\n\nfunction init_kinematic_eddy!(eddy_model, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n\n    FT = eltype(state)\n\n    _grav::FT = grav(param_set)\n\n    dc = eddy_model.data_config\n\n    @inbounds begin\n        # density\n        q_pt_0 = PhasePartition(dc.qt_0)\n        R_m, cp_m, cv_m, γ = gas_constants(param_set, q_pt_0)\n        T::FT = dc.θ_0 * (aux.p / dc.p_1000)^(R_m / cp_m)\n        ρ::FT = aux.p / R_m / T\n        state.ρ = ρ\n\n        # moisture\n        state.ρq_tot = ρ * dc.qt_0\n        state.ρq_liq = ρ * q_pt_0.liq\n        state.ρq_ice = ρ * q_pt_0.ice\n        state.ρq_rai = ρ * FT(0)\n\n        # velocity (derivative of streamfunction)\n        ρu::FT =\n            dc.wmax * dc.xmax / dc.zmax *\n            cos(π * z / dc.zmax) *\n            cos(2 * π * x / dc.xmax)\n        ρw::FT = 2 * dc.wmax * sin(π * z / dc.zmax) * sin(2 * π * x / dc.xmax)\n        state.ρu = SVector(ρu, FT(0), ρw)\n        u::FT = ρu / ρ\n        w::FT = ρw / ρ\n\n        # energy\n        e_kin::FT = 1 // 2 * (u^2 + w^2)\n        e_pot::FT = _grav * z\n        e_int::FT = internal_energy(param_set, T, q_pt_0)\n        e_tot::FT = e_kin + e_pot + e_int\n        state.ρe = ρ * e_tot\n    end\n    return nothing\nend\n\nfunction nodal_update_auxiliary_state!(\n    m::KinematicModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    FT = eltype(state)\n\n    _grav::FT = grav(param_set)\n\n    @inbounds begin\n        # velocity\n        aux.u = state.ρu[1] / state.ρ\n        aux.w = state.ρu[3] / state.ρ\n        # water\n        aux.q_tot = state.ρq_tot / state.ρ\n        aux.q_liq = state.ρq_liq / state.ρ\n        aux.q_ice = state.ρq_ice / state.ρ\n        aux.q_rai = state.ρq_rai / state.ρ\n        q = PhasePartition(aux.q_tot, aux.q_liq, aux.q_ice)\n        aux.q_vap = vapor_specific_humidity(q)\n        # energy\n        aux.e_tot = state.ρe / state.ρ\n        aux.e_kin = 1 // 2 * (aux.u^2 + aux.w^2)\n        aux.e_pot = _grav * aux.z_coord\n        aux.e_int = aux.e_tot - aux.e_kin - aux.e_pot\n        # supersaturation\n        q = PhasePartition(aux.q_tot, aux.q_liq, aux.q_ice)\n        aux.T = air_temperature(param_set, aux.e_int, q)\n        ts_neq = PhaseNonEquil_ρTq(param_set, state.ρ, aux.T, q)\n        aux.S_liq = max(0, supersaturation(ts_neq, Liquid()))\n        aux.RH = relative_humidity(ts_neq) * FT(100)\n\n        aux.rain_w =\n            terminal_velocity(param_set, CM1M.RainType(), state.ρ, aux.q_rai)\n\n        # more diagnostics\n        ts_eq = PhaseEquil_ρTq(param_set, state.ρ, aux.T, aux.q_tot)\n        q_eq = PhasePartition(ts_eq)\n\n        aux.src_cloud_liq =\n            conv_q_vap_to_q_liq_ice(param_set, CM1M.LiquidType(), q_eq, q)\n        aux.src_cloud_ice =\n            conv_q_vap_to_q_liq_ice(param_set, CM1M.IceType(), q_eq, q)\n        aux.src_acnv = conv_q_liq_to_q_rai(param_set, aux.q_liq)\n        aux.src_accr = accretion(\n            param_set,\n            CM1M.LiquidType(),\n            CM1M.RainType(),\n            aux.q_liq,\n            aux.q_rai,\n            state.ρ,\n        )\n        aux.src_rain_evap = evaporation_sublimation(\n            param_set,\n            CM1M.RainType(),\n            q,\n            aux.q_rai,\n            state.ρ,\n            aux.T,\n        )\n        aux.flag_cloud_liq = FT(0)\n        aux.flag_cloud_ice = FT(0)\n        aux.flag_rain = FT(0)\n        if (aux.q_liq >= FT(0))\n            aux.flag_cloud_liq = FT(1)\n        end\n        if (aux.q_ice >= FT(0))\n            aux.flag_cloud_ice = FT(1)\n        end\n        if (aux.q_rai >= FT(0))\n            aux.flag_rain = FT(1)\n        end\n    end\nend\n\nfunction boundary_state!(\n    ::RusanovNumericalFlux,\n    bctype,\n    m::KinematicModel,\n    state⁺,\n    aux⁺,\n    n,\n    state⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    FT = eltype(state⁻)\n\n    #state⁺.ρu -= 2 * dot(state⁻.ρu, n) .* SVector(n)\n\n    #state⁺.ρq_rai = -state⁻.ρq_rai\n    @inbounds state⁺.ρq_rai = FT(0)\n\nend\n\n@inline function wavespeed(\n    m::KinematicModel,\n    nM,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    _...,\n)\n    FT = eltype(state)\n\n    @inbounds begin\n        u = state.ρu / state.ρ\n        q_rai::FT = state.ρq_rai / state.ρ\n\n        rain_w = terminal_velocity(param_set, CM1M.RainType(), state.ρ, q_rai)\n        nu = nM[1] * u[1] + nM[3] * max(u[3], rain_w, u[3] - rain_w)\n    end\n    return abs(nu)\nend\n\n@inline function flux_first_order!(\n    m::KinematicModel,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    _...,\n)\n    FT = eltype(state)\n\n    @inbounds begin\n        q_rai::FT = state.ρq_rai / state.ρ\n        rain_w = terminal_velocity(param_set, CM1M.RainType(), state.ρ, q_rai)\n\n        # advect moisture ...\n        flux.ρq_tot = SVector(\n            state.ρu[1] * state.ρq_tot / state.ρ,\n            FT(0),\n            state.ρu[3] * state.ρq_tot / state.ρ,\n        )\n        flux.ρq_liq = SVector(\n            state.ρu[1] * state.ρq_liq / state.ρ,\n            FT(0),\n            state.ρu[3] * state.ρq_liq / state.ρ,\n        )\n        flux.ρq_ice = SVector(\n            state.ρu[1] * state.ρq_ice / state.ρ,\n            FT(0),\n            state.ρu[3] * state.ρq_ice / state.ρ,\n        )\n        flux.ρq_rai = SVector(\n            state.ρu[1] * state.ρq_rai / state.ρ,\n            FT(0),\n            (state.ρu[3] / state.ρ - rain_w) * state.ρq_rai,\n        )\n        # ... energy ...\n        flux.ρe = SVector(\n            state.ρu[1] / state.ρ * (state.ρe + aux.p),\n            FT(0),\n            state.ρu[3] / state.ρ * (state.ρe + aux.p),\n        )\n        # ... and don't advect momentum (kinematic setup)\n    end\nend\n\nfunction source!(\n    m::KinematicModel,\n    source::Vars,\n    state::Vars,\n    diffusive::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n)\n    FT = eltype(state)\n    _grav::FT = grav(param_set)\n    _e_int_v0::FT = e_int_v0(param_set)\n    _cv_v::FT = cv_v(param_set)\n    _cv_d::FT = cv_d(param_set)\n    _T_0::FT = T_0(param_set)\n\n    @inbounds begin\n        e_tot = state.ρe / state.ρ\n        q_tot = state.ρq_tot / state.ρ\n        q_liq = state.ρq_liq / state.ρ\n        q_ice = state.ρq_ice / state.ρ\n        q_rai = state.ρq_rai / state.ρ\n        u = state.ρu[1] / state.ρ\n        w = state.ρu[3] / state.ρ\n        e_int = e_tot - 1 // 2 * (u^2 + w^2) - _grav * aux.z_coord\n\n        q = PhasePartition(q_tot, q_liq, q_ice)\n        T = air_temperature(param_set, e_int, q)\n        # equilibrium state at current T\n        ts_eq = PhaseEquil_ρTq(param_set, state.ρ, T, q_tot)\n        q_eq = PhasePartition(ts_eq)\n\n        # zero out the source terms\n        source.ρq_tot = FT(0)\n        source.ρq_liq = FT(0)\n        source.ρq_ice = FT(0)\n        source.ρq_rai = FT(0)\n        source.ρe = FT(0)\n\n        # cloud water and ice condensation/evaporation\n        source.ρq_liq +=\n            state.ρ *\n            conv_q_vap_to_q_liq_ice(param_set, CM1M.LiquidType(), q_eq, q)\n        source.ρq_ice +=\n            state.ρ *\n            conv_q_vap_to_q_liq_ice(param_set, CM1M.IceType(), q_eq, q)\n\n        # tendencies from rain\n        src_q_rai_acnv = conv_q_liq_to_q_rai(param_set, q_liq)\n        src_q_rai_accr = accretion(\n            param_set,\n            CM1M.LiquidType(),\n            CM1M.RainType(),\n            q_liq,\n            q_rai,\n            state.ρ,\n        )\n        src_q_rai_evap = evaporation_sublimation(\n            param_set,\n            CM1M.RainType(),\n            q,\n            q_rai,\n            state.ρ,\n            T,\n        )\n\n        src_q_rai_tot = src_q_rai_acnv + src_q_rai_accr + src_q_rai_evap\n\n        source.ρq_liq -= state.ρ * (src_q_rai_acnv + src_q_rai_accr)\n        source.ρq_rai += state.ρ * src_q_rai_tot\n        source.ρq_tot -= state.ρ * src_q_rai_tot\n        source.ρe -=\n            state.ρ * src_q_rai_tot * (_e_int_v0 - (_cv_v - _cv_d) * (T - _T_0))\n    end\nend\n\nfunction main()\n    # Working precision\n    FT = Float64\n    # DG polynomial order\n    N = 4\n    # Domain resolution and size\n    Δx = FT(20)\n    Δy = FT(1)\n    Δz = FT(20)\n    resolution = (Δx, Δy, Δz)\n    # Domain extents\n    xmax = 1500\n    ymax = 10\n    zmax = 1500\n    # initial configuration\n    wmax = FT(0.6)  # max velocity of the eddy  [m/s]\n    θ_0 = FT(289) # init. theta value (const) [K]\n    p_0 = FT(101500) # surface pressure [Pa]\n    p_1000 = FT(100000) # reference pressure in theta definition [Pa]\n    qt_0 = FT(7.5 * 1e-3) # init. total water specific humidity (const) [kg/kg]\n    z_0 = FT(0) # surface height\n\n    # time stepping\n    t_ini = FT(0)\n    t_end = FT(30 * 60)\n    dt = FT(5)\n    #CFL = FT(1.75)\n    filter_freq = 1\n    output_freq = 72\n    interval = \"9steps\"\n\n    # periodicity and boundary numbers\n    periodicity_x = true\n    periodicity_y = true\n    periodicity_z = false\n    idx_bc_left = 0\n    idx_bc_right = 0\n    idx_bc_front = 0\n    idx_bc_back = 0\n    idx_bc_bottom = 1\n    idx_bc_top = 2\n\n    driver_config, ode_solver_type = config_kinematic_eddy(\n        FT,\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        wmax,\n        θ_0,\n        p_0,\n        p_1000,\n        qt_0,\n        z_0,\n        periodicity_x,\n        periodicity_y,\n        periodicity_z,\n        idx_bc_left,\n        idx_bc_right,\n        idx_bc_front,\n        idx_bc_back,\n        idx_bc_bottom,\n        idx_bc_top,\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t_ini,\n        t_end,\n        driver_config;\n        ode_solver_type = ode_solver_type,\n        ode_dt = dt,\n        init_on_cpu = true,\n        #Courant_number = CFL,\n    )\n\n    model = driver_config.bl\n\n    mpicomm = MPI.COMM_WORLD\n\n    # get state variables indices for filtering\n    ρq_liq_ind = varsindex(vars_state(model, Prognostic(), FT), :ρq_liq)\n    ρq_ice_ind = varsindex(vars_state(model, Prognostic(), FT), :ρq_ice)\n    ρq_rai_ind = varsindex(vars_state(model, Prognostic(), FT), :ρq_rai)\n    # get aux variables indices for testing\n    q_tot_ind = varsindex(vars_state(model, Auxiliary(), FT), :q_tot)\n    q_vap_ind = varsindex(vars_state(model, Auxiliary(), FT), :q_vap)\n    q_liq_ind = varsindex(vars_state(model, Auxiliary(), FT), :q_liq)\n    q_ice_ind = varsindex(vars_state(model, Auxiliary(), FT), :q_ice)\n    q_rai_ind = varsindex(vars_state(model, Auxiliary(), FT), :q_rai)\n    S_liq_ind = varsindex(vars_state(model, Auxiliary(), FT), :S_liq)\n    rain_w_ind = varsindex(vars_state(model, Auxiliary(), FT), :rain_w)\n\n    # filter out negative values\n    cb_tmar_filter =\n        GenericCallbacks.EveryXSimulationSteps(filter_freq) do (init = false)\n            Filters.apply!(\n                solver_config.Q,\n                (:ρq_liq, :ρq_ice, :ρq_rai),\n                solver_config.dg.grid,\n                TMARFilter(),\n            )\n            nothing\n        end\n\n    # output for paraview\n\n    # initialize base output prefix directory from rank 0\n    vtkdir = abspath(joinpath(ClimateMachine.Settings.output_dir, \"vtk\"))\n    if MPI.Comm_rank(mpicomm) == 0\n        mkpath(vtkdir)\n    end\n    MPI.Barrier(mpicomm)\n\n    # vtk output\n    vtkstep = [0]\n    cb_vtk =\n        GenericCallbacks.EveryXSimulationSteps(output_freq) do (init = false)\n            out_dirname = @sprintf(\n                \"microphysics_test_3_mpirank%04d_step%04d\",\n                MPI.Comm_rank(mpicomm),\n                vtkstep[1]\n            )\n            out_path_prefix = joinpath(vtkdir, out_dirname)\n            @info \"doing VTK output\" out_path_prefix\n            writevtk(\n                out_path_prefix,\n                solver_config.Q,\n                solver_config.dg,\n                flattenednames(vars_state(model, Prognostic(), FT)),\n                solver_config.dg.state_auxiliary,\n                flattenednames(vars_state(model, Auxiliary(), FT)),\n            )\n            vtkstep[1] += 1\n            nothing\n        end\n\n    # output for netcdf\n    boundaries = [\n        FT(0) FT(0) FT(0)\n        xmax ymax zmax\n    ]\n    interpol = ClimateMachine.InterpolationConfiguration(\n        driver_config,\n        boundaries,\n        resolution,\n    )\n    dgngrps = [\n        setup_dump_state_diagnostics(\n            AtmosLESConfigType(),\n            interval,\n            driver_config.name,\n            interpol = interpol,\n        ),\n        setup_dump_aux_diagnostics(\n            AtmosLESConfigType(),\n            interval,\n            driver_config.name,\n            interpol = interpol,\n        ),\n    ]\n    dgn_config = ClimateMachine.DiagnosticsConfiguration(dgngrps)\n\n    # call solve! function for time-integrator\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (cb_tmar_filter, cb_vtk),\n        check_euclidean_distance = true,\n    )\n\n    # supersaturation in the model\n    max_S_liq = maximum(abs.(solver_config.dg.state_auxiliary[:, S_liq_ind, :]))\n    @test max_S_liq < FT(0.25)\n    @test max_S_liq > FT(0)\n\n    # qt < reference number\n    max_q_tot = maximum(abs.(solver_config.dg.state_auxiliary[:, q_tot_ind, :]))\n    @test max_q_tot < FT(0.0077)\n\n    # no ice\n    max_q_ice = maximum(abs.(solver_config.dg.state_auxiliary[:, q_ice_ind, :]))\n    @test isequal(max_q_ice, FT(0))\n\n    # q_liq ∈ reference range\n    max_q_liq = maximum(solver_config.dg.state_auxiliary[:, q_liq_ind, :])\n    min_q_liq = minimum(solver_config.dg.state_auxiliary[:, q_liq_ind, :])\n    @test max_q_liq < FT(1e-3)\n    @test abs(min_q_liq) < FT(1e-5)\n\n    # q_rai ∈ reference range\n    max_q_rai = maximum(solver_config.dg.state_auxiliary[:, q_rai_ind, :])\n    min_q_rai = minimum(solver_config.dg.state_auxiliary[:, q_rai_ind, :])\n    @test max_q_rai < FT(3e-5)\n    @test abs(min_q_rai) < FT(7e-8)\n\n    # terminal velocity ∈ reference range\n    max_rain_w = maximum(solver_config.dg.state_auxiliary[:, rain_w_ind, :])\n    min_rain_w = minimum(solver_config.dg.state_auxiliary[:, rain_w_ind, :])\n    @test max_rain_w < FT(4)\n    @test isequal(min_rain_w, FT(0))\nend\n\nmain()\n"
  },
  {
    "path": "test/Atmos/Parameterizations/Microphysics/KinematicModel.jl",
    "content": "# The set-up was designed for the\n# 8th International Cloud Modelling Workshop\n# ([Muhlbauer2013](@cite))\n#\n# See chapter 2 in [Arabas2015](@cite) for setup details:\n\nusing Dates\nusing DocStringExtensions\nusing LinearAlgebra\nusing Logging\nusing MPI\nusing Printf\nusing StaticArrays\nusing Test\n\nusing ClimateMachine\nClimateMachine.init(diagnostics = \"default\")\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.Grids\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Topologies\nusing Thermodynamics:\n    gas_constants,\n    PhaseEquil,\n    PhaseEquil_ρeq,\n    PhasePartition_equil,\n    PhasePartition,\n    internal_energy,\n    q_vap_saturation,\n    relative_humidity,\n    PhaseEquil_ρTq,\n    PhaseNonEquil_ρTq,\n    air_temperature,\n    latent_heat_fusion,\n    Liquid,\n    Ice,\n    supersaturation,\n    vapor_specific_humidity\n\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.VTK\n\nusing CloudMicrophysics.Microphysics_0M\nusing CloudMicrophysics.Microphysics_1M\nimport CloudMicrophysics\nconst CM1M = CloudMicrophysics.Microphysics_1M\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet:\n    R_d, cp_d, cv_d, cv_v, cv_l, cv_i, T_0, T_freeze, e_int_v0, e_int_i0, grav\n\nusing CLIMAParameters.Atmos.Microphysics\n\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux, Hyperdiffusive\n\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    init_state_prognostic!,\n    init_state_auxiliary!,\n    nodal_init_state_auxiliary!,\n    nodal_update_auxiliary_state!,\n    flux_first_order!,\n    flux_second_order!,\n    wavespeed,\n    parameter_set,\n    boundary_conditions,\n    boundary_state!,\n    source!\n\nimport ClimateMachine.DGMethods: DGModel\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\n\nstruct KinematicModelConfig{FT}\n    xmax::FT\n    ymax::FT\n    zmax::FT\n    wmax::FT\n    θ_0::FT\n    p_0::FT\n    p_1000::FT\n    qt_0::FT\n    z_0::FT\n    periodicity_x::Bool\n    periodicity_y::Bool\n    periodicity_z::Bool\n    idx_bc_left::Int\n    idx_bc_right::Int\n    idx_bc_front::Int\n    idx_bc_back::Int\n    idx_bc_bottom::Int\n    idx_bc_top::Int\nend\n\nstruct KinematicModel{FT, PS, O, M, P, S, BC, IS, DC} <: BalanceLaw\n    param_set::PS\n    orientation::O\n    moisture::M\n    precipitation::P\n    source::S\n    boundarycondition::BC\n    init_state_prognostic::IS\n    data_config::DC\nend\n\nparameter_set(m::KinematicModel) = m.param_set\n\nfunction KinematicModel{FT}(\n    ::Type{AtmosLESConfigType},\n    param_set::AbstractParameterSet;\n    orientation::O = FlatOrientation(),\n    moisture::M = nothing,\n    precipitation::P = nothing,\n    source::S = nothing,\n    boundarycondition::BC = nothing,\n    init_state_prognostic::IS = nothing,\n    data_config::DC = nothing,\n) where {FT <: AbstractFloat, O, M, P, S, BC, IS, DC}\n\n    @assert param_set ≠ nothing\n    @assert init_state_prognostic ≠ nothing\n\n    atmos = (\n        param_set,\n        orientation,\n        moisture,\n        precipitation,\n        source,\n        boundarycondition,\n        init_state_prognostic,\n        data_config,\n    )\n\n    return KinematicModel{FT, typeof.(atmos)...}(atmos...)\nend\n\nvars_state(m::KinematicModel, ::Gradient, FT) = @vars()\n\nvars_state(m::KinematicModel, ::GradientFlux, FT) = @vars()\n\nfunction nodal_init_state_auxiliary!(\n    m::KinematicModel,\n    aux::Vars,\n    tmp::Vars,\n    geom::LocalGeometry,\n)\n    FT = eltype(aux)\n    x, y, z = geom.coord\n    dc = m.data_config\n    param_set = parameter_set(m)\n\n    _R_d::FT = R_d(param_set)\n    _cp_d::FT = cp_d(param_set)\n    _grav::FT = grav(param_set)\n\n    # TODO - should R_d and cp_d here be R_m and cp_m?\n    R_m, cp_m, cv_m, γ = gas_constants(param_set, PhasePartition(dc.qt_0))\n\n    # Pressure profile assuming hydrostatic and constant θ and qt profiles.\n    # It is done this way to be consistent with Arabas paper.\n    # It's not necessarily the best way to initialize with our model variables.\n    p =\n        dc.p_1000 *\n        (\n            (dc.p_0 / dc.p_1000)^(_R_d / _cp_d) -\n            _R_d / _cp_d * _grav / dc.θ_0 / R_m * (z - dc.z_0)\n        )^(_cp_d / _R_d)\n\n    @inbounds begin\n        aux.p = p\n        aux.x_coord = x\n        aux.z_coord = z\n    end\nend\n\nfunction init_state_prognostic!(\n    m::KinematicModel,\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t,\n    args...,\n)\n    m.init_state_prognostic(m, state, aux, localgeo, t, args...)\nend\nboundary_conditions(::KinematicModel) = (1, 2, 3, 4, 5, 6)\n\nfunction boundary_state!(\n    ::CentralNumericalFluxSecondOrder,\n    bctype,\n    m::KinematicModel,\n    state⁺,\n    aux⁺,\n    n,\n    state⁻,\n    aux⁻,\n    t,\n    args...,\n) end\n\n@inline function flux_second_order!(\n    m::KinematicModel,\n    flux::Grad,\n    state::Vars,\n    diffusive::Vars,\n    hyperdiffusive::Vars,\n    aux::Vars,\n    t::Real,\n) end\n\n@inline function flux_second_order!(\n    m::KinematicModel,\n    flux::Grad,\n    state::Vars,\n    τ,\n    d_h_tot,\n) end\n\nfunction config_kinematic_eddy(\n    FT,\n    N,\n    resolution,\n    xmax,\n    ymax,\n    zmax,\n    wmax,\n    θ_0,\n    p_0,\n    p_1000,\n    qt_0,\n    z_0,\n    periodicity_x,\n    periodicity_y,\n    periodicity_z,\n    idx_bc_left,\n    idx_bc_right,\n    idx_bc_front,\n    idx_bc_back,\n    idx_bc_bottom,\n    idx_bc_top,\n)\n    # Choose explicit solver\n    ode_solver = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK144NiegemannDiehlBusch,\n    )\n\n    kmc = KinematicModelConfig(\n        FT(xmax),\n        FT(ymax),\n        FT(zmax),\n        FT(wmax),\n        FT(θ_0),\n        FT(p_0),\n        FT(p_1000),\n        FT(qt_0),\n        FT(z_0),\n        Bool(periodicity_x),\n        Bool(periodicity_y),\n        Bool(periodicity_z),\n        Int(idx_bc_left),\n        Int(idx_bc_right),\n        Int(idx_bc_front),\n        Int(idx_bc_back),\n        Int(idx_bc_bottom),\n        Int(idx_bc_top),\n    )\n\n    # Set up the model\n    model = KinematicModel{FT}(\n        AtmosLESConfigType,\n        param_set;\n        init_state_prognostic = init_kinematic_eddy!,\n        data_config = kmc,\n    )\n\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"KinematicModel\",\n        N,\n        resolution,\n        FT(xmax),\n        FT(ymax),\n        FT(zmax),\n        param_set,\n        init_kinematic_eddy!,\n        boundary = (\n            (Int(idx_bc_left), Int(idx_bc_right)),\n            (Int(idx_bc_front), Int(idx_bc_back)),\n            (Int(idx_bc_bottom), Int(idx_bc_top)),\n        ),\n        periodicity = (\n            Bool(periodicity_x),\n            Bool(periodicity_y),\n            Bool(periodicity_z),\n        ),\n        xmin = FT(0),\n        ymin = FT(0),\n        zmin = FT(0),\n        model = model,\n    )\n\n    return config, ode_solver\nend\n"
  },
  {
    "path": "test/Atmos/prog_prim_conversion/runtests.jl",
    "content": "module TestPrimitivePrognosticConversion\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet\nusing Test\nusing StaticArrays\nusing UnPack\n\nusing ClimateMachine\nClimateMachine.init()\nconst ArrayType = ClimateMachine.array_type()\n\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)))\n\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.VariableTemplates\nusing Thermodynamics\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics.TestedProfiles\nusing ClimateMachine.Atmos\nusing ClimateMachine.BalanceLaws:\n    prognostic_to_primitive!, primitive_to_prognostic!\nconst BL = BalanceLaws\n\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\natol_temperature = 5e-1\natol_energy = cv_d(param_set) * atol_temperature\n\nimport ClimateMachine.BalanceLaws: vars_state\n\n# Assign aux.ref_state different than state for general testing\nfunction assign!(aux, bl, nt, st::Auxiliary)\n    @unpack p, ρ, e_pot, = nt\n    aux.ref_state.ρ = ρ / 2\n    aux.ref_state.p = p / 2\n    aux.orientation.Φ = e_pot\nend\n\nfunction assign!(state, bl, nt, st::Prognostic, aux)\n    @unpack u, v, w, ρ, e_kin, e_pot, T, q_pt = nt\n    state.ρ = ρ\n    assign!(state, bl, compressibility_model(bl), nt, st, aux)\n    state.ρu = SVector(state.ρ * u, state.ρ * v, state.ρ * w)\n    param_set = parameter_set(bl)\n    state.energy.ρe = state.ρ * total_energy(param_set, e_kin, e_pot, T, q_pt)\n    assign!(state, bl, moisture_model(bl), nt, st)\nend\nassign!(state, bl, moisture::DryModel, nt, ::Prognostic) = nothing\nassign!(state, bl, moisture::EquilMoist, nt, ::Prognostic) =\n    (state.moisture.ρq_tot = state.ρ * nt.q_pt.tot)\nfunction assign!(state, bl, moisture::NonEquilMoist, nt, ::Prognostic)\n    state.moisture.ρq_tot = state.ρ * nt.q_pt.tot\n    state.moisture.ρq_liq = state.ρ * nt.q_pt.liq\n    state.moisture.ρq_ice = state.ρ * nt.q_pt.ice\nend\n# Assign prog.ρ = aux.ref_state.ρ in anelastic1D\nassign!(state, bl, ::Compressible, nt, ::Prognostic, aux) = nothing\nfunction assign!(state, bl, ::Anelastic1D, nt, ::Prognostic, aux)\n    state.ρ = aux.ref_state.ρ\nend\n\nfunction assign!(state, bl, nt, st::Primitive, aux)\n    @unpack u, v, w, ρ, p = nt\n    state.ρ = ρ\n    state.u = SVector(u, v, w)\n    state.p = p\n    assign!(state, bl, compressibility_model(bl), nt, st, aux)\n    assign!(state, bl, moisture_model(bl), nt, st)\nend\nassign!(state, bl, moisture::DryModel, nt, ::Primitive) = nothing\nassign!(state, bl, moisture::EquilMoist, nt, ::Primitive) =\n    (state.moisture.q_tot = nt.q_pt.tot)\nfunction assign!(state, bl, moisture::NonEquilMoist, nt, ::Primitive)\n    state.moisture.q_tot = nt.q_pt.tot\n    state.moisture.q_liq = nt.q_pt.liq\n    state.moisture.q_ice = nt.q_pt.ice\nend\n# Assign prim.p = aux.ref_state.p in anelastic1D\nassign!(state, bl, ::Compressible, nt, ::Primitive, aux) = nothing\nfunction assign!(state, bl, ::Anelastic1D, nt, ::Primitive, aux)\n    state.p = aux.ref_state.p\nend\n\n@testset \"Prognostic-Primitive conversion (dry)\" begin\n    FT = Float64\n    compressibility = (Anelastic1D(), Compressible())\n    for comp in compressibility\n        physics = AtmosPhysics{FT}(\n            param_set;\n            moisture = DryModel(),\n            compressibility = comp,\n        )\n        bl = AtmosModel{FT}(\n            AtmosLESConfigType,\n            physics;\n            init_state_prognostic = x -> x,\n        )\n        vs_prog = vars_state(bl, Prognostic(), FT)\n        vs_prim = vars_state(bl, Primitive(), FT)\n        vs_aux = vars_state(bl, Auxiliary(), FT)\n        prog_arr = zeros(varsize(vs_prog))\n        prim_arr = zeros(varsize(vs_prim))\n        aux_arr = zeros(varsize(vs_aux))\n        prog = Vars{vs_prog}(prog_arr)\n        prim = Vars{vs_prim}(prim_arr)\n        aux = Vars{vs_aux}(aux_arr)\n        for nt in TestedProfiles.PhaseDryProfiles(param_set, ArrayType)\n            assign!(aux, bl, nt, Auxiliary())\n\n            # Test prognostic_to_primitive! identity\n            assign!(prog, bl, nt, Prognostic(), aux)\n            prog_0 = deepcopy(parent(prog))\n            prim_arr .= 0\n            prognostic_to_primitive!(bl, prim, prog, aux)\n            @test !all(parent(prim) .≈ parent(prog)) # ensure not calling fallback\n            primitive_to_prognostic!(bl, prog, prim, aux)\n            @test all(parent(prog) .≈ prog_0)\n\n            # Test primitive_to_prognostic! identity\n            assign!(prim, bl, nt, Primitive(), aux)\n            prim_0 = deepcopy(parent(prim))\n            prog_arr .= 0\n            primitive_to_prognostic!(bl, prog, prim, aux)\n            @test !all(parent(prim) .≈ parent(prog)) # ensure not calling fallback\n            prognostic_to_primitive!(bl, prim, prog, aux)\n            @test all(parent(prim) .≈ prim_0)\n        end\n    end\nend\n\n@testset \"Prognostic-Primitive conversion (EquilMoist)\" begin\n    FT = Float64\n    compressibility = (Compressible(),) # Anelastic1D() does not converge\n    for comp in compressibility\n        physics = AtmosPhysics{FT}(\n            param_set;\n            moisture = EquilMoist(; maxiter = 5), # maxiter=3 does not converge\n            compressibility = comp,\n        )\n        bl = AtmosModel{FT}(\n            AtmosLESConfigType,\n            physics;\n            init_state_prognostic = x -> x,\n        )\n        vs_prog = vars_state(bl, Prognostic(), FT)\n        vs_prim = vars_state(bl, Primitive(), FT)\n        vs_aux = vars_state(bl, Auxiliary(), FT)\n        prog_arr = zeros(varsize(vs_prog))\n        prim_arr = zeros(varsize(vs_prim))\n        aux_arr = zeros(varsize(vs_aux))\n        prog = Vars{vs_prog}(prog_arr)\n        prim = Vars{vs_prim}(prim_arr)\n        aux = Vars{vs_aux}(aux_arr)\n        err_max_fwd = 0\n        err_max_bwd = 0\n        for nt in TestedProfiles.PhaseEquilProfiles(param_set, ArrayType)\n            assign!(aux, bl, nt, Auxiliary())\n\n            # Test prognostic_to_primitive! identity\n            assign!(prog, bl, nt, Prognostic(), aux)\n            prog_0 = deepcopy(parent(prog))\n            prim_arr .= 0\n            prognostic_to_primitive!(bl, prim, prog, aux)\n            @test !all(parent(prim) .≈ parent(prog)) # ensure not calling fallback\n            primitive_to_prognostic!(bl, prog, prim, aux)\n            @test all(parent(prog)[1:4] .≈ prog_0[1:4])\n            @test isapprox(parent(prog)[5], prog_0[5]; atol = atol_energy)\n            # @test all(parent(prog)[5] .≈ prog_0[5]) # fails\n            @test all(parent(prog)[6] .≈ prog_0[6])\n            err_max_fwd = max(abs(parent(prog)[5] .- prog_0[5]), err_max_fwd)\n\n            # Test primitive_to_prognostic! identity\n            assign!(prim, bl, nt, Primitive(), aux)\n            prim_0 = deepcopy(parent(prim))\n            prog_arr .= 0\n            primitive_to_prognostic!(bl, prog, prim, aux)\n            @test !all(parent(prim) .≈ parent(prog)) # ensure not calling fallback\n            prognostic_to_primitive!(bl, prim, prog, aux)\n            @test all(parent(prim)[1:4] .≈ prim_0[1:4])\n            # @test all(parent(prim)[5] .≈ prim_0[5]) # fails\n            @test isapprox(parent(prim)[5], prim_0[5]; atol = atol_energy)\n            @test all(parent(prim)[6] .≈ prim_0[6])\n            err_max_bwd = max(abs(parent(prim)[5] .- prim_0[5]), err_max_bwd)\n        end\n    end\n    # We may want/need to improve this later, so leaving debug info:\n    # @show err_max_fwd\n    # @show err_max_bwd\nend\n\n@testset \"Prognostic-Primitive conversion (NonEquilMoist)\" begin\n    FT = Float64\n    compressibility = (Anelastic1D(), Compressible())\n    for comp in compressibility\n        physics = AtmosPhysics{FT}(\n            param_set;\n            moisture = NonEquilMoist(),\n            compressibility = comp,\n        )\n        bl = AtmosModel{FT}(\n            AtmosLESConfigType,\n            physics;\n            init_state_prognostic = x -> x,\n        )\n        vs_prog = vars_state(bl, Prognostic(), FT)\n        vs_prim = vars_state(bl, Primitive(), FT)\n        vs_aux = vars_state(bl, Auxiliary(), FT)\n        prog_arr = zeros(varsize(vs_prog))\n        prim_arr = zeros(varsize(vs_prim))\n        aux_arr = zeros(varsize(vs_aux))\n        prog = Vars{vs_prog}(prog_arr)\n        prim = Vars{vs_prim}(prim_arr)\n        aux = Vars{vs_aux}(aux_arr)\n        for nt in TestedProfiles.PhaseEquilProfiles(param_set, ArrayType)\n            assign!(aux, bl, nt, Auxiliary())\n\n            # Test prognostic_to_primitive! identity\n            assign!(prog, bl, nt, Prognostic(), aux)\n            prog_0 = deepcopy(parent(prog))\n            prim_arr .= 0\n            prognostic_to_primitive!(bl, prim, prog, aux)\n            @test !all(parent(prim) .≈ parent(prog)) # ensure not calling fallback\n            primitive_to_prognostic!(bl, prog, prim, aux)\n            @test all(parent(prog) .≈ prog_0)\n\n            # Test primitive_to_prognostic! identity\n            assign!(prim, bl, nt, Primitive(), aux)\n            prim_0 = deepcopy(parent(prim))\n            prog_arr .= 0\n            primitive_to_prognostic!(bl, prog, prim, aux)\n            @test !all(parent(prim) .≈ parent(prog)) # ensure not calling fallback\n            prognostic_to_primitive!(bl, prim, prog, aux)\n            @test all(parent(prim) .≈ prim_0)\n        end\n    end\nend\n\n@testset \"Prognostic-Primitive conversion (array interface)\" begin\n    FT = Float64\n    physics = AtmosPhysics{FT}(param_set; moisture = DryModel())\n    bl = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        init_state_prognostic = x -> x,\n    )\n    vs_prog = vars_state(bl, Prognostic(), FT)\n    vs_prim = vars_state(bl, Primitive(), FT)\n    vs_aux = vars_state(bl, Auxiliary(), FT)\n    prog_arr = zeros(varsize(vs_prog))\n    prim_arr = zeros(varsize(vs_prim))\n    aux_arr = zeros(varsize(vs_aux))\n    prog = Vars{vs_prog}(prog_arr)\n    prim = Vars{vs_prim}(prim_arr)\n    aux = Vars{vs_aux}(aux_arr)\n    for nt in TestedProfiles.PhaseDryProfiles(param_set, ArrayType)\n        assign!(aux, bl, nt, Auxiliary())\n\n        # Test prognostic_to_primitive! identity\n        assign!(prog, bl, nt, Prognostic(), aux)\n        prog_0 = deepcopy(parent(prog))\n        prim_arr .= 0\n        BL.prognostic_to_primitive!(bl, prim_arr, prog_arr, aux_arr)\n        @test !all(prog_arr .≈ prim_arr) # ensure not calling fallback\n        BL.primitive_to_prognostic!(bl, prog_arr, prim_arr, aux_arr)\n        @test all(parent(prog) .≈ prog_0)\n\n        # Test primitive_to_prognostic! identity\n        assign!(prim, bl, nt, Primitive(), aux)\n        prim_0 = deepcopy(parent(prim))\n        prog_arr .= 0\n        BL.primitive_to_prognostic!(bl, prog_arr, prim_arr, aux_arr)\n        @test !all(prog_arr .≈ prim_arr) # ensure not calling fallback\n        BL.prognostic_to_primitive!(bl, prim_arr, prog_arr, aux_arr)\n        @test all(parent(prim) .≈ prim_0)\n    end\nend\n\nend\n"
  },
  {
    "path": "test/Atmos/runtests.jl",
    "content": "using Test, Pkg\n\n@testset \"Atmos\" begin\n    all_tests = isempty(ARGS) || \"all\" in ARGS ? true : false\n    for submodule in [\"Model\", \"prog_prim_conversion\"]\n        if all_tests ||\n           \"$submodule\" in ARGS ||\n           \"Atmos/$submodule\" in ARGS ||\n           \"Atmos\" in ARGS\n            include_test(submodule)\n        end\n    end\nend\n"
  },
  {
    "path": "test/BalanceLaws/runtests.jl",
    "content": "using Test\nusing Random\nusing StaticArrays: SVector\nRandom.seed!(1234)\n\nusing ClimateMachine.VariableTemplates: @vars, varsize, Vars\nusing ClimateMachine.BalanceLaws\nconst BL = BalanceLaws\nimport ClimateMachine.BalanceLaws: vars_state, eq_tends, prognostic_vars\n\nstruct TestBL <: BalanceLaw end\nstruct X <: AbstractPrognosticVariable end\nstruct Y <: AbstractPrognosticVariable end\nstruct F1 <: TendencyDef{Flux{FirstOrder}} end\nstruct F2 <: TendencyDef{Flux{SecondOrder}} end\nstruct S <: TendencyDef{Source} end\n\nprognostic_vars(::TestBL) = (X(), Y())\neq_tends(::X, ::TestBL, ::Flux{FirstOrder}) = (F1(),)\neq_tends(::Y, ::TestBL, ::Flux{FirstOrder}) = (F1(),)\neq_tends(::X, ::TestBL, ::Flux{SecondOrder}) = (F2(),)\neq_tends(::Y, ::TestBL, ::Flux{SecondOrder}) = (F2(),)\neq_tends(::X, ::TestBL, ::Source) = (S(),)\neq_tends(::Y, ::TestBL, ::Source) = (S(),)\n\n@testset \"BalanceLaws\" begin\n    bl = TestBL()\n    @test prognostic_vars(bl) == (X(), Y())\n    show_tendencies(bl)\n    show_tendencies(bl; include_module = true)\n    show_tendencies(bl; table_complete = true)\nend\n\nvars_state(bl::TestBL, st::Prognostic, FT) = @vars begin\n    ρ::FT\n    ρu::SVector{3, FT}\nend\n\nvars_state(bl::TestBL, st::Auxiliary, FT) = @vars()\n\n@testset \"Prognostic-Primitive conversion (identity)\" begin\n    FT = Float64\n    bl = TestBL()\n    vs_prog = vars_state(bl, Prognostic(), FT)\n    vs_prim = vars_state(bl, Primitive(), FT)\n    vs_aux = vars_state(bl, Auxiliary(), FT)\n    prim_arr = zeros(varsize(vs_prim))\n    prog_arr = zeros(varsize(vs_prog))\n    aux_arr = zeros(varsize(vs_aux))\n\n    # Test prognostic_to_primitive! identity\n    prog_arr .= rand(varsize(vs_prog))\n    prog_0 = deepcopy(prog_arr)\n    prim_arr .= 0\n    BL.prognostic_to_primitive!(bl, prim_arr, prog_arr, aux_arr)\n    BL.primitive_to_prognostic!(bl, prog_arr, prim_arr, aux_arr)\n    @test all(prog_arr .≈ prog_0)\n\n    # Test primitive_to_prognostic! identity\n    prim_arr .= rand(varsize(vs_prim))\n    prim_0 = deepcopy(prim_arr)\n    prog_arr .= 0\n    BL.primitive_to_prognostic!(bl, prog_arr, prim_arr, aux_arr)\n    BL.prognostic_to_primitive!(bl, prim_arr, prog_arr, aux_arr)\n    @test all(prim_arr .≈ prim_0)\nend\n"
  },
  {
    "path": "test/Common/CartesianDomains/runtests.jl",
    "content": "using ClimateMachine\n\nClimateMachine.init()\n\nusing ClimateMachine.CartesianDomains\n\n@testset \"Cartesian domains\" begin\n\n    for FT in (Float64, Float32)\n        domain = RectangularDomain(\n            FT,\n            Ne = (16, 24, 1),\n            Np = 4,\n            x = (0, π),\n            y = (0, 1.1),\n            z = (-1, 0),\n            periodicity = (false, false, false),\n        )\n\n        @test eltype(domain) == FT\n        @test domain.Ne == (x = 16, y = 24, z = 1)\n        @test domain.Np == 4\n        @test domain.L.x == FT(π)\n        @test domain.L.y == FT(1.1)\n        @test domain.L.z == FT(1)\n    end\nend\n"
  },
  {
    "path": "test/Common/CartesianFields/runtests.jl",
    "content": "using ClimateMachine\n\nClimateMachine.init()\n\nusing ClimateMachine.CartesianFields: RectangularElement, assemble\n\n@testset \"Cartesian fields\" begin\n\n    Nx = 3\n    Ny = 4\n    Nz = 5\n\n    data = rand(Nx, Ny, Nz)\n    x = repeat(range(0.0, 1.0, length = Nx), 1, Ny, Nz)\n    y = repeat(range(0.0, 1.1, length = Ny), Nx, 1, Nz)\n    z = repeat(range(0.0, 1.2, length = Nz), Nx, Ny, 1)\n\n    element = RectangularElement(data, x, y, z)\n\n    @test size(element) == (Nx, Ny, Nz)\n    @test maximum(element) == maximum(data)\n    @test minimum(element) == minimum(data)\n    @test maximum(abs, element) == maximum(abs, data)\n    @test minimum(abs, element) == minimum(abs, data)\n    @test element[1, 1, 1] == data[1, 1, 1]\n\n    east_element = RectangularElement(2 .* data, x .+ x[end, 1, 1], y, z)\n    north_element = RectangularElement(3 .* data, x, y .+ y[1, end, 1], z)\n    top_element = RectangularElement(4 .* data, x, y, z .+ z[1, 1, end])\n\n    west_east = assemble(Val(1), element, east_element)\n    south_north = assemble(Val(2), element, north_element)\n    bottom_top = assemble(Val(3), element, top_element)\n\n    @test west_east.x[1, 1, 1] == x[1, 1, 1]\n    @test west_east.x[end, 1, 1] == 2 * x[end, 1, 1]\n\n    @test south_north.y[1, 1, 1] == y[1, 1, 1]\n    @test south_north.y[1, end, 1] == 2 * y[1, end, 1]\n\n    @test bottom_top.z[1, 1, 1] == z[1, 1, 1]\n    @test bottom_top.z[1, 1, end] == 2 * z[1, 1, end]\n\n    northeast_element =\n        RectangularElement(5 .* data, x .+ x[end, 1, 1], y .+ y[1, end, 1], z)\n\n    # Remember that matrix literals are transposed, so \"northeast\"\n    # is the bottom right corner (for example).\n    four_elements = [\n        element north_element\n        east_element northeast_element\n    ]\n\n    four_elements = reshape(four_elements, 2, 2, 1)\n\n    four_way = assemble(four_elements)\n\n    @test four_way.x[end, 1, 1] == west_east.x[end, 1, 1]\n    @test four_way.y[1, end, 1] == south_north.y[1, end, 1]\n    @test four_way.z[1, 1, end] == z[1, 1, end]\nend\n"
  },
  {
    "path": "test/Common/Spectra/gcm_standalone_visual_test.jl",
    "content": "# Standalone test file that tests spectra visually\n#using Plots # uncomment when using the plotting code below\n\nusing ClimateMachine.ConfigTypes\n\nusing ClimateMachine.Spectra:\n    compute_gaussian!,\n    compute_legendre!,\n    SpectralSphericalMesh,\n    trans_grid_to_spherical!,\n    power_spectrum_1d,\n    power_spectrum_2d,\n    compute_wave_numbers\nusing FFTW\n\n\ninclude(\"spherical_helper_test.jl\")\n\nFT = Float64\n# -- TEST 1: power_spectrum_1d(AtmosGCMConfigType(), var_grid, z, lat, lon, weight)\nnlats = 32\n\n# Setup grid\nsinθ, wts = compute_gaussian!(nlats)\nyarray = asin.(sinθ) .* 180 / π\nxarray = 180.0 ./ nlats * collect(FT, 1:1:(2nlats))[:] .- 180.0\nz = 1\n\n# Setup variable\nmass_weight = ones(Float64, length(z));\nvar_grid =\n    1.0 * reshape(\n        sin.(xarray / xarray[end] * 5.0 * 2π) .* (yarray .* 0.0 .+ 1.0)',\n        length(xarray),\n        length(yarray),\n        1,\n    ) +\n    1.0 * reshape(\n        sin.(xarray / xarray[end] * 10.0 * 2π) .* (yarray .* 0.0 .+ 1.0)',\n        length(xarray),\n        length(yarray),\n        1,\n    )\nnm_spectrum, wave_numbers = power_spectrum_1d(\n    AtmosGCMConfigType(),\n    var_grid,\n    z,\n    yarray,\n    xarray,\n    mass_weight,\n);\n\n\n# Check visually\nplot(wave_numbers[:, 16, 1], nm_spectrum[:, 16, 1], xlims = (0, 20))\ncontourf(var_grid[:, :, 1])\ncontourf(nm_spectrum[2:20, :, 1])\n\n# -- TEST 2: power_spectrum_gcm_2d\n# Setup grid\nsinθ, wts = compute_gaussian!(nlats)\nyarray = asin.(sinθ) .* 180 / π\nxarray = 180.0 ./ nlats * collect(FT, 1:1:(2nlats))[:] .- 180.0\nz = 1\n\n# Setup variable: use an example analytical P_nm function\nP_32 = sqrt(105 / 8) * (sinθ .- sinθ .^ 3)\nvar_grid =\n    1.0 * reshape(\n        sin.(xarray / xarray[end] * 3.0 * π) .* P_32',\n        length(xarray),\n        length(yarray),\n        1,\n    )\n\nmass_weight = ones(Float64, z);\nspectrum, wave_numbers, spherical, mesh =\n    power_spectrum_2d(AtmosGCMConfigType(), var_grid, mass_weight)\n\n# Grid to spherical to grid reconstruction\nreconstruction = trans_spherical_to_grid!(mesh, spherical)\n\n# Check visually\ncontourf(var_grid[:, :, 1])\ncontourf(reconstruction[:, :, 1])\ncontourf(var_grid[:, :, 1] .- reconstruction[:, :, 1])\n\n# Spectrum\ncontourf(\n    collect(0:1:(mesh.num_fourier - 1))[:],\n    collect(0:1:(mesh.num_spherical - 1))[:],\n    (spectrum[:, :, 1])',\n    xlabel = \"m\",\n    ylabel = \"n\",\n)\n\n# Check magnitude\nprintln(0.5 .* sum(spectrum))\n\ndθ = π / length(wts)\ncosθ = sqrt.(1 .- sinθ .^ 2)\narea_factor = reshape(cosθ .* dθ .^ 2 / 4π, (1, length(cosθ)))\n\nprintln(sum(0.5 .* var_grid[:, :, 1] .^ 2 .* area_factor))\n\n# NB: can verify against published packages, e.g., https://github.com/jswhit/pyspharm \n"
  },
  {
    "path": "test/Common/Spectra/runtests.jl",
    "content": "module TestSpectra\n\nusing Test\nusing FFTW\n\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Spectra\nusing ClimateMachine.Spectra:\n    compute_gaussian!,\n    compute_legendre!,\n    SpectralSphericalMesh,\n    trans_grid_to_spherical!,\n    compute_wave_numbers\n\ninclude(\"spherical_helper_test.jl\")\n\n@testset \"power_spectrum_1d (GCM)\" begin\n    FT = Float64\n    # -- TEST 1: power_spectrum_1d(AtmosGCMConfigType(), var_grid, z, lat, lon, weight)\n    nlats = 32\n\n    # Setup grid\n    sinθ, wts = compute_gaussian!(nlats)\n    yarray = asin.(sinθ) .* 180 / π\n    xarray = 180.0 ./ nlats * collect(FT, 1:1:(2nlats))[:] .- 180.0\n    z = 1\n\n    # Setup variable\n    mass_weight = ones(Float64, length(z))\n    var_grid =\n        1.0 * reshape(\n            sin.(xarray / xarray[end] * 5.0 * 2π) .* (yarray .* 0.0 .+ 1.0)',\n            length(xarray),\n            length(yarray),\n            1,\n        ) +\n        1.0 * reshape(\n            sin.(xarray / xarray[end] * 10.0 * 2π) .* (yarray .* 0.0 .+ 1.0)',\n            length(xarray),\n            length(yarray),\n            1,\n        )\n    nm_spectrum, wave_numbers = power_spectrum_1d(\n        AtmosGCMConfigType(),\n        var_grid,\n        z,\n        yarray,\n        xarray,\n        mass_weight,\n    )\n\n    nm_spectrum_ = nm_spectrum[:, 10, 1]\n    var_grid_ = var_grid[:, 10, 1]\n    sum_spec = sum(nm_spectrum_)\n    sum_grid = sum(var_grid_ .^ 2) / length(var_grid_)\n\n    sum_res = (sum_spec - sum_grid) / sum_grid\n\n    @test sum_res < 0.1\nend\n\n@testset \"power_spectrum_2d (GCM)\" begin\n    # -- TEST 2: power_spectrum_2d\n    # Setup grid\n    FT = Float64\n    nlats = 32\n    sinθ, wts = compute_gaussian!(nlats)\n    cosθ = sqrt.(1 .- sinθ .^ 2)\n    yarray = asin.(sinθ) .* 180 / π\n    xarray = 180.0 ./ nlats * collect(FT, 1:1:(2nlats))[:] .- 180.0\n    z = 1\n\n    # Setup variable: use an example analytical P_nm function\n    P_32 = sqrt(105 / 8) * (sinθ .- sinθ .^ 3)\n    var_grid =\n        1.0 * reshape(\n            sin.(xarray / xarray[end] * 3.0 * π) .* P_32',\n            length(xarray),\n            length(yarray),\n            1,\n        )\n\n    mass_weight = ones(Float64, z)\n    spectrum, wave_numbers, spherical, mesh =\n        power_spectrum_2d(AtmosGCMConfigType(), var_grid, mass_weight)\n\n    # Grid to spherical to grid reconstruction\n    reconstruction = trans_spherical_to_grid!(mesh, spherical)\n\n    sum_spec = sum((0.5 * spectrum))\n    dθ = π / length(wts)\n    area_factor = reshape(cosθ .* dθ .^ 2 / 4π, (1, length(cosθ)))\n\n    sum_grid = sum(0.5 .* var_grid[:, :, 1] .^ 2 .* area_factor) # scaled to average over Earth's area (units: m2/s2)\n    sum_reco = sum(0.5 .* reconstruction[:, :, 1] .^ 2 .* area_factor)\n\n    sum_res_1 = (sum_spec - sum_grid) / sum_grid\n    sum_res_2 = (sum_reco - sum_grid) / sum_grid\n\n    @test abs(sum_res_1) < 0.1\n    @test abs(sum_res_2) < 0.1\nend\n\nend\n"
  },
  {
    "path": "test/Common/Spectra/spherical_helper_test.jl",
    "content": "# additional helper functions for spherical harmonics spectra\n\n\"\"\"\n    TransSphericalToGrid!(mesh, snm )\n\nTransforms a variable expressed in spherical harmonics (var_spherical[num_fourier+1, num_spherical+1]) onto a Gaussian grid (pfield[nλ, nθ])\n\n[THIS IS USED FOR TESTING ONLY]\n\n    With F_{m,n} = (-1)^m F_{-m,n}*\n    P_{m,n} = (-1)^m P_{-m,n}\n\n    F(λ, η) = ∑_{m= -N}^{N} ∑_{n=|m|}^{N} F_{m,n} P_{m,n}(η) e^{imλ}\n    = ∑_{m= 0}^{N} ∑_{n=m}^{N} F_{m,n} P_{m,n} e^{imλ} + ∑_{m= 1}^{N} ∑_{n=m}^{N} F_{-m,n} P_{-m,n} e^{-imλ}\n\n    Here η = sinθ, N = num_fourier, and denote\n    ! extra coeffients in snm n > N are not used.\n\n    ∑_{n=m}^{N} F_{m,n} P_{m,n}     = g_{m}(η) m = 1, ... N\n    ∑_{n=m}^{N} F_{m,n} P_{m,n}/2.0 = g_{m}(η) m = 0\n\n    We have\n\n    F(λ, η) = ∑_{m= 0}^{N} g_{m}(η) e^{imλ} + ∑_{m= 0}^{N} g_{m}(η)* e^{-imλ}\n    = 2real{ ∑_{m= 0}^{N} g_{m}(η) e^{imλ} }\n\n    snm = F_{m,n}         # Complex{Float64} [num_fourier+1, num_spherical+1]\n    qnm = P_{m,n,η}         # Float64[num_fourier+1, num_spherical+1, nθ]\n    fourier_g = g_{m, η} # Complex{Float64} nλ×nθ with padded 0s fourier_g[num_fourier+2, :] == 0.0\n    pfiled = F(λ, η)      # Float64[nλ, nθ]\n\n    ! use all spherical harmonic modes\n\n\n# Arguments\n- mesh: struct with mesh information\n- snm: spherical variable\n\n# References\n- Ehrendorfer, M., Spectral Numerical Weather Prediction Models, Appendix B, Society for Industrial and Applied Mathematics, 2011\n\"\"\"\nfunction trans_spherical_to_grid!(mesh, snm)\n    num_fourier, num_spherical = mesh.num_fourier, mesh.num_spherical\n    nλ, nθ, nd = mesh.nλ, mesh.nθ, mesh.nd\n\n    qnm = mesh.qnm\n\n    fourier_g = mesh.var_fourier .* 0.0\n    fourier_s = mesh.var_fourier .* 0.0\n\n    @assert(nθ % 2 == 0)\n    nθ_half = div(nθ, 2)\n    for m in 1:(num_fourier + 1)\n        for n in m:num_spherical\n            snm_t = transpose(snm[m, n, :, 1:nθ_half]) #snm[m,n, :] is complex number\n            if (n - m) % 2 == 0\n                fourier_s[m, 1:nθ_half, :] .+=\n                    qnm[m, n, 1:nθ_half] .* sum(snm_t, dims = 1)   #even function part\n            else\n                fourier_s[m, (nθ_half + 1):nθ, :] .+=\n                    qnm[m, n, 1:nθ_half] .* sum(snm_t, dims = 1)   #odd function part\n            end\n        end\n    end\n    fourier_g[:, 1:nθ_half, :] .=\n        fourier_s[:, 1:nθ_half, :] .+ fourier_s[:, (nθ_half + 1):nθ, :]\n    fourier_g[:, nθ:-1:(nθ_half + 1), :] .=\n        fourier_s[:, 1:nθ_half, :] .- fourier_s[:, (nθ_half + 1):nθ, :] # this got ignored...\n\n    fourier_g[1, :, :] ./= 2.0\n    pfield = zeros(Float64, nλ, nθ, nd)\n    for j in 1:nθ\n        pfield[:, j, :] .= 2.0 * nλ * real.(ifft(fourier_g[:, j, :], 1)) #fourier for the first dimension\n    end\n    return pfield\nend\n"
  },
  {
    "path": "test/Common/runtests.jl",
    "content": "using Test, Pkg\n\n@testset \"Common\" begin\n    all_tests = isempty(ARGS) || \"all\" in ARGS ? true : false\n    for submodule in [\"CartesianDomains\", \"CartesianFields\"]\n        if all_tests ||\n           \"$submodule\" in ARGS ||\n           \"Common/$submodule\" in ARGS ||\n           \"Common\" in ARGS\n            include_test(submodule)\n        end\n    end\n\nend\n"
  },
  {
    "path": "test/Diagnostics/Debug/test_statecheck.jl",
    "content": "using Test\n\n# # State debug statistics\n#\n# Set up a basic environment\nusing MPI\nusing StaticArrays\nusing Random\nusing ClimateMachine\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.StateCheck\nusing ClimateMachine.GenericCallbacks\n\n@testset \"$(@__FILE__)\" begin\n\n    ClimateMachine.init()\n    FT = Float64\n\n    # Define some dummy vector and tensor abstract variables with associated types\n    # and dimensions\n    F1 = @vars begin\n        ν∇u::SMatrix{3, 2, FT, 6}\n        κ∇θ::SVector{3, FT}\n    end\n    F2 = @vars begin\n        u::SVector{2, FT}\n        θ::SVector{1, FT}\n    end\n\n    # Create ```MPIStateArray``` variables with arrays to hold elements of the \n    # vectors and tensors\n    Q1 = MPIStateArray{Float32, F1}(\n        MPI.COMM_WORLD,\n        ClimateMachine.array_type(),\n        4,\n        9,\n        8,\n    )\n    Q2 = MPIStateArray{Float64, F2}(\n        MPI.COMM_WORLD,\n        ClimateMachine.array_type(),\n        4,\n        3,\n        8,\n    )\n\n    # ### Create a call-back\n    cb = ClimateMachine.StateCheck.sccreate(\n        [(Q1, \"My gradients\"), (Q2, \"My fields\")],\n        1;\n        prec = 15,\n    )\n\n    # ### Invoke the call-back\n    #     Compare on local \"realdata\", fill via broadcast to keep GPU happy.\n    Q1.data = rand(MersenneTwister(0), Float32, size(Q1.data))\n    Q2.data = rand(MersenneTwister(0), Float64, size(Q2.data))\n    Q1.realdata .= Q1.data\n    Q2.realdata .= Q2.data\n    GenericCallbacks.init!(cb, nothing, nothing, nothing, nothing)\n    GenericCallbacks.call!(cb, nothing, nothing, nothing, nothing)\n\n    # ### Check with reference values\n    include(\"test_statecheck_refvals.jl\")\n\n    # This should return true (MPI rank != 0 always returns true)\n    test_stat_01 = ClimateMachine.StateCheck.scdocheck(cb, (varr, parr))\n\n    # This should return false (MPI rank != 0 always returns true)\n    varr[1][3] = varr[1][3] * 10.0\n    test_stat_02 = ClimateMachine.StateCheck.scdocheck(cb, (varr, parr))\n    if MPI.Comm_rank(MPI.COMM_WORLD) != 0\n        test_stat_02 = false\n    end\n\n    test_stat = test_stat_01 && !test_stat_02\n\n    @test test_stat\n\nend\n"
  },
  {
    "path": "test/Diagnostics/Debug/test_statecheck_refvals.jl",
    "content": "#\n# Example of a set of reference values that StateCheck will use to test current vlaues against.\n# These are generated by the function scprintref() and parsed by the scdocheck()\n#\n\n#! format: off\nvarr = [\n [ \"My gradients\", \"ν∇u[1]\",  1.34348869323730468750e-04,  9.84732866287231445313e-01,  5.23545503616333007813e-01,  3.08209930764271777814e-01 ],\n [ \"My gradients\", \"ν∇u[2]\",  1.16317868232727050781e-01,  9.92088317871093750000e-01,  4.83800649642944335938e-01,  2.83350456014221541157e-01 ],\n [ \"My gradients\", \"ν∇u[3]\",  1.05845928192138671875e-03,  9.51775908470153808594e-01,  4.65474426746368408203e-01,  2.73615551085745090099e-01 ],\n [ \"My gradients\", \"ν∇u[4]\",  5.97668886184692382813e-02,  9.68048095703125000000e-01,  5.42618036270141601563e-01,  2.81570862027933854765e-01 ],\n [ \"My gradients\", \"ν∇u[5]\",  8.31030607223510742188e-02,  9.35931921005249023438e-01,  5.05405902862548828125e-01,  2.46073509972619536290e-01 ],\n [ \"My gradients\", \"ν∇u[6]\",  3.09681892395019531250e-02,  9.98341441154479980469e-01,  4.54375565052032470703e-01,  3.09461067853178561915e-01 ],\n [ \"My gradients\", \"κ∇θ[1]\",  8.47448110580444335938e-02,  9.94180679321289062500e-01,  5.27157366275787353516e-01,  2.92455951648181833313e-01 ],\n [ \"My gradients\", \"κ∇θ[2]\",  1.20514631271362304688e-02,  9.93527650833129882813e-01,  4.71063584089279174805e-01,  2.96449027197666359346e-01 ],\n [ \"My gradients\", \"κ∇θ[3]\",  8.14980268478393554688e-02,  9.55443382263183593750e-01,  5.05038917064666748047e-01,  2.77201022741208891187e-01 ],\n [    \"My fields\",   \"u[1]\",  3.53445491472876849315e-02,  9.73118774570230771204e-01,  4.53566376673364857197e-01,  3.28622448223506280485e-01 ],\n [    \"My fields\",   \"u[2]\",  4.23016659320296639635e-02,  9.67799553619200114696e-01,  5.40307230728526155517e-01,  2.94603153327737565803e-01 ],\n [    \"My fields\",   \"θ[1]\",  6.23675581701588210848e-02,  9.76550123041147521974e-01,  5.16046312418334207628e-01,  2.81983244164903890105e-01 ],\n]\nparr = [\n [ \"My gradients\", \"ν∇u[1]\",    16,     7,    16,     0 ],\n [ \"My gradients\", \"ν∇u[2]\",    16,     7,    16,     0 ],\n [ \"My gradients\", \"ν∇u[3]\",    16,     7,    16,     0 ],\n [ \"My gradients\", \"ν∇u[4]\",    16,     7,    16,     0 ],\n [ \"My gradients\", \"ν∇u[5]\",    16,     7,    16,     0 ],\n [ \"My gradients\", \"ν∇u[6]\",    16,     7,    16,     0 ],\n [ \"My gradients\", \"κ∇θ[1]\",    16,    16,    16,     0 ],\n [ \"My gradients\", \"κ∇θ[2]\",    16,    16,    16,     0 ],\n [ \"My gradients\", \"κ∇θ[3]\",    16,    16,    16,     0 ],\n [    \"My fields\",   \"u[1]\",    16,    16,    16,     0 ],\n [    \"My fields\",   \"u[2]\",    16,    16,    16,     0 ],\n [    \"My fields\",   \"θ[1]\",    16,    16,    16,     0 ],\n]\n#! format: on\n"
  },
  {
    "path": "test/Diagnostics/diagnostic_fields_test.jl",
    "content": "using Test, MPI\nusing Random\nusing StaticArrays\nusing Test\n\nusing ClimateMachine\nClimateMachine.init()\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Mesh.Filters\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nimport ClimateMachine.Mesh.Grids: _x1, _x2, _x3\nimport ClimateMachine.BalanceLaws: vars_state\nimport ClimateMachine.VariableTemplates.varsindex\n\n# ------------------------ Description ------------------------- #\n# 1) Dry Rising Bubble (circular potential temperature perturbation)\n# 2) Boundaries - `All Walls` : Impenetrable(FreeSlip())\n#                               Laterally periodic\n# 3) Domain - 2500m[horizontal] x 2500m[horizontal] x 2500m[vertical]\n# 4) Timeend - 1000s\n# 5) Mesh Aspect Ratio (Effective resolution) 1:1\n# 7) Overrides defaults for\n#               `init_on_cpu`\n#               `solver_type`\n#               `sources`\n#               `C_smag`\n# 8) Default settings can be found in `src/Driver/Configurations.jl`\n# ------------------------ Description ------------------------- #\nfunction init_risingbubble!(problem, bl, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n    R_gas::FT = R_d(param_set)\n    c_p::FT = cp_d(param_set)\n    c_v::FT = cv_d(param_set)\n    γ::FT = c_p / c_v\n    p0::FT = MSLP(param_set)\n    _grav::FT = grav(param_set)\n\n    xc::FT = 1250\n    yc::FT = 1250\n    zc::FT = 1000\n    r = sqrt((x - xc)^2 + (y - yc)^2 + (z - zc)^2)\n    rc::FT = 500\n    θ_ref::FT = 300\n    Δθ::FT = 0\n\n    if r <= rc\n        Δθ = FT(5) * cospi(r / rc / 2)\n    end\n\n    #Perturbed state:\n    θ = θ_ref + Δθ # potential temperature\n    π_exner = FT(1) - _grav / (c_p * θ) * z # exner pressure\n    ρ = p0 / (R_gas * θ) * (π_exner)^(c_v / R_gas) # density\n    q_tot = FT(0)\n    ts = PhaseEquil_ρθq(param_set, ρ, θ, q_tot)\n    q_pt = PhasePartition(ts)\n\n    ρu = SVector(FT(0), FT(0), FT(0))\n\n    #State (prognostic) variable assignment\n    e_kin = FT(0)\n    e_pot = gravitational_potential(bl.orientation, aux)\n    ρe_tot = ρ * total_energy(e_kin, e_pot, ts)\n    state.ρ = ρ\n    state.ρu = ρu\n    state.energy.ρe = ρe_tot\n    state.moisture.ρq_tot = ρ * q_pt.tot\nend\n\nfunction config_risingbubble(FT, N, resolution, xmax, ymax, zmax)\n\n    # Set up the model\n    T_profile = DryAdiabaticProfile{FT}(param_set)\n    C_smag = FT(0.23)\n    ref_state = HydrostaticState(T_profile)\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = SmagorinskyLilly{FT}(C_smag),\n    )\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        init_state_prognostic = init_risingbubble!,\n        source = (Gravity(),),\n    )\n\n    # Problem configuration\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"DryRisingBubble\",\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        init_risingbubble!,\n        model = model,\n    )\n    return config\nend\n\nfunction config_diagnostics(driver_config)\n    interval = \"10000steps\"\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosLESConfigType(),\n        interval,\n        driver_config.name,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp])\nend\n#-------------------------------------------------------------------------\nfunction run_brick_diagostics_fields_test()\n    DA = ClimateMachine.array_type()\n    mpicomm = MPI.COMM_WORLD\n    root = 0\n    pid = MPI.Comm_rank(mpicomm)\n    npr = MPI.Comm_size(mpicomm)\n    toler = Dict(Float64 => 1e-8, Float32 => 1e-4)\n    # Working precision\n    for FT in (Float32, Float64)\n        # DG polynomial order\n        N = (4, 5)\n        # Domain resolution and size\n        Δh = FT(50)\n        Δv = FT(50)\n        resolution = (Δh, Δh, Δv)\n        # Domain extents\n        xmax = FT(2500)\n        ymax = FT(2500)\n        zmax = FT(2500)\n        # Simulation time\n        t0 = FT(0)\n        timeend = FT(1000)\n\n        # Courant number\n        CFL = FT(20)\n\n        driver_config = config_risingbubble(FT, N, resolution, xmax, ymax, zmax)\n\n        # Choose explicit multirate solver\n        ode_solver_type = ClimateMachine.MultirateSolverType(\n            fast_model = AtmosAcousticGravityLinearModel,\n            slow_method = LSRK144NiegemannDiehlBusch,\n            fast_method = LSRK144NiegemannDiehlBusch,\n            timestep_ratio = 10,\n        )\n\n        solver_config = ClimateMachine.SolverConfiguration(\n            t0,\n            timeend,\n            driver_config;\n            ode_solver_type = ode_solver_type,\n            init_on_cpu = true,\n            Courant_number = CFL,\n        )\n        #------------------------------------------------------------------------\n\n        model = driver_config.bl\n        Q = solver_config.Q\n        dg = solver_config.dg\n        grid = dg.grid\n        vgeo = grid.vgeo\n        Nel = size(Q.realdata, 3)\n        Npl = size(Q.realdata, 1)\n\n        ind = [\n            varsindex(vars_state(model, Prognostic(), FT), :ρ)\n            varsindex(vars_state(model, Prognostic(), FT), :ρu)\n        ]\n        _ρ, _ρu, _ρv, _ρw = ind[1], ind[2], ind[3], ind[4]\n\n\n        x1 = view(vgeo, :, _x1, 1:Nel)\n        x2 = view(vgeo, :, _x2, 1:Nel)\n        x3 = view(vgeo, :, _x3, 1:Nel)\n\n        fcn0(x, y, z, xmax, ymax, zmax) =\n            sin.(pi * x ./ xmax) .* cos.(pi * y ./ ymax) .* cos.(pi * z ./ zmax)    # sample function\n\n        fcnx(x, y, z, xmax, ymax, zmax) =\n            cos.(pi * x ./ xmax) .* cos.(pi * y ./ ymax) .*\n            cos.(pi * z ./ zmax) .* pi ./ xmax # ∂/∂x\n        fcny(x, y, z, xmax, ymax, zmax) =\n            -sin.(pi * x ./ xmax) .* sin.(pi * y ./ ymax) .*\n            cos.(pi * z ./ zmax) .* pi ./ ymax # ∂/∂y\n        fcnz(x, y, z, xmax, ymax, zmax) =\n            -sin.(pi * x ./ xmax) .* cos.(pi * y ./ ymax) .*\n            sin.(pi * z ./ zmax) .* pi ./ zmax # ∂/∂z\n\n        Q.data[:, _ρ, 1:Nel] .= 1.0 .+ fcn0(x1, x2, x3, xmax, ymax, zmax) * 5.0\n        Q.data[:, _ρu, 1:Nel] .=\n            Q.data[:, _ρ, 1:Nel] .* fcn0(x1, x2, x3, xmax, ymax, zmax)\n        Q.data[:, _ρv, 1:Nel] .=\n            Q.data[:, _ρ, 1:Nel] .* fcn0(x1, x2, x3, xmax, ymax, zmax)\n        Q.data[:, _ρw, 1:Nel] .=\n            Q.data[:, _ρ, 1:Nel] .* fcn0(x1, x2, x3, xmax, ymax, zmax)\n        #-----------------------------------------------------------------------\n        vgrad = Diagnostics.VectorGradients(dg, Q)\n        vort = Diagnostics.Vorticity(dg, vgrad)\n        #----------------------------------------------------------------------------\n        Ω₁_exact =\n            fcny(x1, x2, x3, xmax, ymax, zmax) -\n            fcnz(x1, x2, x3, xmax, ymax, zmax)\n        Ω₂_exact =\n            fcnz(x1, x2, x3, xmax, ymax, zmax) -\n            fcnx(x1, x2, x3, xmax, ymax, zmax)\n        Ω₃_exact =\n            fcnx(x1, x2, x3, xmax, ymax, zmax) -\n            fcny(x1, x2, x3, xmax, ymax, zmax)\n\n        err = zeros(FT, 12)\n\n        err[1] = maximum(abs.(fcnx(x1, x2, x3, xmax, ymax, zmax) - vgrad.∂₁u₁))\n        err[2] = maximum(abs.(fcny(x1, x2, x3, xmax, ymax, zmax) - vgrad.∂₂u₁))\n        err[3] = maximum(abs.(fcnz(x1, x2, x3, xmax, ymax, zmax) - vgrad.∂₃u₁))\n\n        err[4] = maximum(abs.(fcnx(x1, x2, x3, xmax, ymax, zmax) - vgrad.∂₁u₂))\n        err[5] = maximum(abs.(fcny(x1, x2, x3, xmax, ymax, zmax) - vgrad.∂₂u₂))\n        err[6] = maximum(abs.(fcnz(x1, x2, x3, xmax, ymax, zmax) - vgrad.∂₃u₂))\n\n        err[7] = maximum(abs.(fcnx(x1, x2, x3, xmax, ymax, zmax) - vgrad.∂₁u₃))\n        err[8] = maximum(abs.(fcny(x1, x2, x3, xmax, ymax, zmax) - vgrad.∂₂u₃))\n        err[9] = maximum(abs.(fcnz(x1, x2, x3, xmax, ymax, zmax) - vgrad.∂₃u₃))\n\n        err[10] = maximum(abs.(vort.Ω₁ - Ω₁_exact))\n        err[11] = maximum(abs.(vort.Ω₂ - Ω₂_exact))\n        err[12] = maximum(abs.(vort.Ω₃ - Ω₃_exact))\n\n        errg = MPI.Allreduce(err, max, mpicomm)\n        @test maximum(errg) < toler[FT]\n    end\nend\n#----------------------------------------------------------------------------\n@testset \"Diagnostics Fields tests\" begin\n    run_brick_diagostics_fields_test()\nend\n#------------------------------------------------\n"
  },
  {
    "path": "test/Diagnostics/dm_tests.jl",
    "content": "using Dates\nusing FileIO\nusing KernelAbstractions\nusing MPI\nusing NCDatasets\nusing Printf\nusing Random\nusing StaticArrays\nusing Test\n\nusing ClimateMachine\nClimateMachine.init(diagnostics = \"1steps\")\nusing ClimateMachine.Atmos\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.DiagnosticsMachine\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.MPIStateArrays\nusing Thermodynamics\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: grav, MSLP\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n# Need to import these to define new diagnostic variables and groups.\nimport ..DiagnosticsMachine:\n    Settings,\n    dv_name,\n    dv_attrib,\n    dv_args,\n    dv_project,\n    dv_scale,\n    dv_PointwiseDiagnostic,\n    dv_HorizontalAverage\n\n# Define some new diagnostic variables.\n@horizontal_average(\n    AtmosLESConfigType,\n    yvel,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    states.prognostic.ρu[2] / states.prognostic.ρ\nend\n\n@pointwise_diagnostic(\n    AtmosLESConfigType,\n    zvel,\n) do (atmos::AtmosModel, states::States, curr_time, cache)\n    states.prognostic.ρu[3] / states.prognostic.ρ\nend\n\n# Define a new diagnostics group with some pre-defined diagnostic\n# variables as well as the new ones above.\n@diagnostics_group(\n    \"DMTest\",\n    AtmosLESConfigType,\n    Nothing,\n    (_...) -> nothing,\n    NoInterpolation,\n    u,\n    v,\n    w,\n    rho,\n    yvel,\n    zvel,\n)\n\n# Make sure DiagnosticsMachine did what it was supposed to do.\n@testset \"DiagnosticsMachine interface\" begin\n    @test_throws UndefVarError ALESCT_HA_foo()\n    yv = ALESCT_HA_yvel()\n    @test yv isa HorizontalAverage\n    yvname = dv_name(AtmosLESConfigType(), yv)\n    @test yvname == \"yvel\"\n    @test yvname ∈\n          keys(DiagnosticsMachine.AllDiagnosticVars[AtmosLESConfigType])\n\n    @test_throws UndefVarError ALESCT_PD_bar()\n    zv = ALESCT_PD_zvel()\n    @test zv isa PointwiseDiagnostic\n    zvname = dv_name(AtmosLESConfigType(), zv)\n    @test zvname == \"zvel\"\n    @test zvname ∈\n          keys(DiagnosticsMachine.AllDiagnosticVars[AtmosLESConfigType])\nend\n\n# Set up a simple experiment to run the diagnostics group.\n\ninclude(\"sin_init.jl\")\n\nfunction main()\n    FT = Float64\n\n    # DG polynomial order\n    N = 4\n\n    # Domain resolution and size\n    Δh = FT(25)\n    Δv = FT(25)\n    resolution = (Δh, Δh, Δv)\n\n    xmax = FT(500)\n    ymax = FT(500)\n    zmax = FT(500)\n\n    t0 = FT(0)\n    dt = FT(0.01)\n    timeend = dt\n\n    driver_config = ClimateMachine.AtmosLESConfiguration(\n        \"DMTest\",\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        init_sin_test!,\n    )\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n        init_on_cpu = true,\n    )\n    dm_dgngrp = DMTest(\"1steps\", driver_config.name)\n    dgn_config = ClimateMachine.DiagnosticsConfiguration([dm_dgngrp])\n\n    ClimateMachine.invoke!(solver_config, diagnostics_config = dgn_config)\n\n    # Check the output from the diagnostics group.\n    @testset \"DiagnosticsMachine correctness\" begin\n        mpicomm = solver_config.mpicomm\n        mpirank = MPI.Comm_rank(mpicomm)\n        if mpirank == 0\n            nm = driver_config.name * \"_DMTest.nc\"\n            ds = Dataset(joinpath(ClimateMachine.Settings.output_dir, nm), \"r\")\n            ds_u = ds[\"u\"][:]\n            ds_yvel = ds[\"yvel\"][:]\n            ds_zvel = ds[\"zvel\"][:]\n            close(ds)\n        end\n        Q =\n            array_device(solver_config.Q) isa CPU ? solver_config.Q :\n            Array(solver_config.Q)\n        havg_rho = compute_havg(solver_config, view(Q, :, 1:1, :))\n        havg_u = compute_havg(solver_config, view(Q, :, 2:2, :))\n        v = view(Q, :, 3:3, :) ./ view(Q, :, 1:1, :)\n        havg_v = compute_havg(solver_config, v)\n        if mpirank == 0\n            havg_u ./= havg_rho\n            @test all(ds_u[:, 3] .≈ havg_u)\n            @test all(ds_yvel[:, 3] .≈ havg_v)\n\n            realelems = solver_config.dg.grid.topology.realelems\n            w = view(Q, :, 4, realelems) ./ view(Q, :, 1, realelems)\n            @test all(ds_zvel[:, :, 3] .≈ w)\n        end\n    end\n\n    nothing\nend\n\nfunction compute_havg(solver_config, field)\n    mpicomm = solver_config.mpicomm\n    mpirank = MPI.Comm_rank(mpicomm)\n    grid = solver_config.dg.grid\n    grid_info = basic_grid_info(grid)\n    topl_info = basic_topology_info(grid.topology)\n    Nqh = grid_info.Nqh\n    Nqk = grid_info.Nqk\n    nvertelem = topl_info.nvertelem\n    nhorzrealelem = topl_info.nhorzrealelem\n\n    function arrange_array(A, dim = :)\n        A = array_device(A) isa CPU ? A : Array(A)\n        reshape(\n            view(A, :, dim, grid.topology.realelems),\n            Nqh,\n            Nqk,\n            nvertelem,\n            nhorzrealelem,\n        )\n    end\n\n    field = arrange_array(field)\n    MH = arrange_array(grid.vgeo, grid.MHid)\n    full_field = MPI.Reduce!(sum(field .* MH, dims = (1, 4))[:], +, 0, mpicomm)\n    full_MH = MPI.Reduce!(sum(MH, dims = (1, 4))[:], +, 0, mpicomm)\n    if mpirank == 0\n        return full_field ./ full_MH\n    else\n        return nothing\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Diagnostics/runtests.jl",
    "content": "using MPI, Test\n\ninclude(joinpath(\"..\", \"testhelpers.jl\"))\n\n@testset \"Diagnostics\" begin\n    runmpi(joinpath(@__DIR__, \"sin_test.jl\"), ntasks = 2)\n    runmpi(joinpath(@__DIR__, \"dm_tests.jl\"), ntasks = 2)\n    runmpi(joinpath(@__DIR__, \"Debug/test_statecheck.jl\"), ntasks = 2)\nend\n"
  },
  {
    "path": "test/Diagnostics/sin_init.jl",
    "content": "function init_sin_test!(problem, bl, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n\n    z = FT(z)\n    _grav::FT = grav(param_set)\n    _MSLP::FT = MSLP(param_set)\n\n    # These constants are those used by Stevens et al. (2005)\n    qref = FT(9.0e-3)\n    q_pt_sfc = PhasePartition(qref)\n    Rm_sfc = FT(gas_constant_air(param_set, q_pt_sfc))\n    T_sfc = FT(292.5)\n    P_sfc = _MSLP\n\n    # Specify moisture profiles\n    q_liq = FT(0)\n    q_ice = FT(0)\n    zb = FT(600)         # initial cloud bottom\n    zi = FT(840)         # initial cloud top\n    dz_cloud = zi - zb\n    q_liq_peak = FT(0.00045)     # cloud mixing ratio at z_i\n\n    if z > zb && z <= zi\n        q_liq = (z - zb) * q_liq_peak / dz_cloud\n    end\n\n    if z <= zi\n        θ_liq = FT(289.0)\n        q_tot = qref\n    else\n        θ_liq = FT(297.5) + (z - zi)^(FT(1 / 3))\n        q_tot = FT(1.5e-3)\n    end\n\n    w = FT(10 + 0.5 * sin(2 * π * ((x / 1500) + (y / 1500))))\n    u = (5 + 2 * sin(2 * π * ((x / 1500) + (y / 1500))))\n    v = FT(5 + 2 * sin(2 * π * ((x / 1500) + (y / 1500))))\n\n    # Pressure\n    H = Rm_sfc * T_sfc / _grav\n    p = P_sfc * exp(-z / H)\n\n    # Density, Temperature\n    ts = PhaseEquil_pθq(param_set, p, θ_liq, q_tot)\n    #ρ = air_density(ts)\n    ρ = one(FT)\n\n    e_kin = FT(1 / 2) * FT((u^2 + v^2 + w^2))\n    e_pot = _grav * z\n    E = ρ * total_energy(e_kin, e_pot, ts)\n\n    state.ρ = ρ\n    state.ρu = SVector(ρ * u, ρ * v, ρ * w)\n    state.energy.ρe = E\n    state.moisture.ρq_tot = ρ * q_tot\nend\n"
  },
  {
    "path": "test/Diagnostics/sin_test.jl",
    "content": "using Dates\nusing FileIO\nusing MPI\nusing NCDatasets\nusing Printf\nusing Random\nusing StaticArrays\nusing Test\n\nusing ClimateMachine\nClimateMachine.init()\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Mesh.Filters\nusing Thermodynamics\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Writers\nusing ClimateMachine.GenericCallbacks\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: grav, MSLP\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\ninclude(\"sin_init.jl\")\n\nfunction config_sin_test(FT, N, resolution, xmax, ymax, zmax)\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"Diagnostics SIN test\",\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        init_sin_test!,\n    )\n\n    return config\nend\n\nfunction config_diagnostics(driver_config)\n    interval = \"100steps\"\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosLESConfigType(),\n        interval,\n        replace(driver_config.name, \" \" => \"_\"),\n        writer = NetCDFWriter(),\n    )\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp])\nend\n\nfunction main()\n    # Disable driver diagnostics as we're testing it here\n    ClimateMachine.Settings.diagnostics = \"never\"\n\n    FT = Float64\n\n    # DG polynomial order\n    N = 4\n\n    # Domain resolution and size\n    Δh = FT(50)\n    Δv = FT(20)\n    resolution = (Δh, Δh, Δv)\n\n    xmax = FT(1500)\n    ymax = FT(1500)\n    zmax = FT(1500)\n\n    t0 = FT(0)\n    dt = FT(0.01)\n    timeend = dt\n\n    driver_config = config_sin_test(FT, N, resolution, xmax, ymax, zmax)\n\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK54CarpenterKennedy,\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        ode_dt = dt,\n        init_on_cpu = true,\n    )\n    dgn_config = config_diagnostics(driver_config)\n\n    mpicomm = solver_config.mpicomm\n    dg = solver_config.dg\n    Q = solver_config.Q\n    solver = solver_config.solver\n\n    outdir = mktempdir()\n    currtime = ODESolvers.gettime(solver)\n    starttime = replace(string(now()), \":\" => \".\")\n    Diagnostics.init(mpicomm, param_set, dg, Q, starttime, outdir, false)\n    GenericCallbacks.init!(\n        dgn_config.groups[1],\n        nothing,\n        nothing,\n        nothing,\n        currtime,\n    )\n\n    ClimateMachine.invoke!(solver_config)\n\n    # Check results\n    mpirank = MPI.Comm_rank(mpicomm)\n    if mpirank == 0\n        dgngrp = dgn_config.groups[1]\n        nm = @sprintf(\"%s_%s.nc\", dgngrp.out_prefix, dgngrp.name)\n        ds = NCDataset(joinpath(outdir, nm), \"r\")\n        ds_u = ds[\"u\"][:]\n        ds_cov_w_u = ds[\"cov_w_u\"][:]\n        N = size(ds_u, 1)\n        err = 0\n        err1 = 0\n        for i in 1:N\n            u = ds_u[i]\n            cov_w_u = ds_cov_w_u[i]\n            err += (cov_w_u - 0.5)^2\n            err1 += (u - 5)^2\n        end\n        close(ds)\n        err = sqrt(err / N)\n        @test err1 <= 1e-16\n        @test err <= 2e-15\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Driver/cr_unit_tests.jl",
    "content": "using MPI\nusing Printf\nusing StaticArrays\nusing Test\nimport KernelAbstractions: CPU\n\nusing ClimateMachine\nClimateMachine.init()\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.Checkpoint\nusing ClimateMachine.ConfigTypes\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Grids\nusing ClimateMachine.ODESolvers\nimport ClimateMachine.MPIStateArrays: array_device\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nBase.@kwdef struct AcousticWaveSetup{FT}\n    domain_height::FT = 10e3\n    T_ref::FT = 300\n    α::FT = 3\n    γ::FT = 100\n    nv::Int = 1\nend\n\nfunction (setup::AcousticWaveSetup)(problem, bl, state, aux, localgeo, t)\n    # callable to set initial conditions\n    FT = eltype(state)\n\n    λ = longitude(bl, aux)\n    φ = latitude(bl, aux)\n    z = altitude(bl, aux)\n\n    β = min(FT(1), setup.α * acos(cos(φ) * cos(λ)))\n    f = (1 + cos(FT(π) * β)) / 2\n    g = sin(setup.nv * FT(π) * z / setup.domain_height)\n    Δp = setup.γ * f * g\n    p = aux.ref_state.p + Δp\n    param_set = parameter_set(bl)\n\n    ts = PhaseDry_pT(param_set, p, setup.T_ref)\n    q_pt = PhasePartition(ts)\n    e_pot = gravitational_potential(bl.orientation, aux)\n    e_int = internal_energy(ts)\n\n    state.ρ = air_density(ts)\n    state.ρu = SVector{3, FT}(0, 0, 0)\n    state.energy.ρe = state.ρ * (e_int + e_pot)\n    return nothing\nend\n\nfunction main()\n    FT = Float64\n\n    # DG polynomial order\n    N = 4\n\n    # Domain resolution\n    nelem_horz = 4\n    nelem_vert = 6\n    resolution = (nelem_horz, nelem_vert)\n\n    t0 = FT(0)\n    timeend = FT(1800)\n    # Timestep size (s)\n    dt = FT(600)\n\n    ode_solver_type = ClimateMachine.MISSolverType(;\n        splitting_type = ClimateMachine.SlowFastSplitting(),\n        nsubsteps = (20,),\n    )\n\n    setup = AcousticWaveSetup{FT}()\n    T_profile = IsothermalProfile(param_set, setup.T_ref)\n    ref_state = HydrostaticState(T_profile)\n    turbulence = ConstantDynamicViscosity(FT(0))\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = turbulence,\n        moisture = DryModel(),\n    )\n    model = AtmosModel{FT}(\n        AtmosGCMConfigType,\n        physics;\n        init_state_prognostic = setup,\n        source = (Gravity(),),\n    )\n\n    driver_config = ClimateMachine.AtmosGCMConfiguration(\n        \"Checkpoint unit tests\",\n        N,\n        resolution,\n        setup.domain_height,\n        param_set,\n        setup;\n        model = model,\n    )\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        ode_dt = dt,\n    )\n\n    isdir(ClimateMachine.Settings.checkpoint_dir) ||\n        mkpath(ClimateMachine.Settings.checkpoint_dir)\n\n    @testset \"Checkpoint/restart unit tests\" begin\n        rm_checkpoint(\n            ClimateMachine.Settings.checkpoint_dir,\n            solver_config.name,\n            solver_config.mpicomm,\n            0,\n        )\n        write_checkpoint(\n            solver_config,\n            ClimateMachine.Settings.checkpoint_dir,\n            solver_config.name,\n            solver_config.mpicomm,\n            0,\n        )\n\n        nm = replace(solver_config.name, \" \" => \"_\")\n        cname = @sprintf(\n            \"%s_checkpoint_mpirank%04d_num%04d.jld2\",\n            nm,\n            MPI.Comm_rank(solver_config.mpicomm),\n            0,\n        )\n        cfull = joinpath(ClimateMachine.Settings.checkpoint_dir, cname)\n        @test isfile(cfull)\n\n        s_Q, s_aux, s_t = try\n            read_checkpoint(\n                ClimateMachine.Settings.checkpoint_dir,\n                nm,\n                driver_config.array_type,\n                solver_config.mpicomm,\n                0,\n            )\n        catch\n            (nothing, nothing, nothing)\n        end\n        @test s_Q !== nothing\n        @test s_aux !== nothing\n        @test s_t !== nothing\n        if Array ∉ typeof(s_Q).parameters\n            s_Q = Array(s_Q)\n            s_aux = Array(s_aux)\n        end\n\n        dg = solver_config.dg\n        Q = solver_config.Q\n        if array_device(Q) isa CPU\n            h_Q = Q.realdata\n            h_aux = dg.state_auxiliary.realdata\n        else\n            h_Q = Array(Q.realdata)\n            h_aux = Array(dg.state_auxiliary.realdata)\n        end\n        t = ODESolvers.gettime(solver_config.solver)\n\n        @test h_Q == s_Q\n        @test h_aux == s_aux\n        @test t == s_t\n\n        rm_checkpoint(\n            ClimateMachine.Settings.checkpoint_dir,\n            solver_config.name,\n            solver_config.mpicomm,\n            0,\n        )\n        @test !isfile(cfull)\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Driver/gcm_driver_test.jl",
    "content": "using StaticArrays\nusing Test\n\nusing ClimateMachine\nClimateMachine.init()\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.Checkpoint\nusing ClimateMachine.ConfigTypes\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Grids\nusing ClimateMachine.ODESolvers\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nBase.@kwdef struct AcousticWaveSetup{FT}\n    domain_height::FT = 10e3\n    T_ref::FT = 300\n    α::FT = 3\n    γ::FT = 100\n    nv::Int = 1\nend\n\nfunction (setup::AcousticWaveSetup)(problem, bl, state, aux, localgeo, t)\n    # callable to set initial conditions\n    FT = eltype(state)\n\n    λ = longitude(bl, aux)\n    φ = latitude(bl, aux)\n    z = altitude(bl, aux)\n\n    β = min(FT(1), setup.α * acos(cos(φ) * cos(λ)))\n    f = (1 + cos(FT(π) * β)) / 2\n    g = sin(setup.nv * FT(π) * z / setup.domain_height)\n    Δp = setup.γ * f * g\n    p = aux.ref_state.p + Δp\n    param_set = parameter_set(bl)\n\n    ts = PhaseDry_pT(param_set, p, setup.T_ref)\n    q_pt = PhasePartition(ts)\n    e_pot = gravitational_potential(bl.orientation, aux)\n    e_int = internal_energy(ts)\n\n    state.ρ = air_density(ts)\n    state.ρu = SVector{3, FT}(0, 0, 0)\n    state.energy.ρe = state.ρ * (e_int + e_pot)\n    return nothing\nend\n\nfunction main()\n    FT = Float64\n\n    # DG polynomial orders\n    N = (4, 4)\n\n    # Domain resolution\n    nelem_horz = 4\n    nelem_vert = 6\n    resolution = (nelem_horz, nelem_vert)\n\n    t0 = FT(0)\n    timeend = FT(3600)\n    # Timestep size (s)\n    dt = FT(1800)\n\n    setup = AcousticWaveSetup{FT}()\n    T_profile = IsothermalProfile(param_set, setup.T_ref)\n    ref_state = HydrostaticState(T_profile)\n    turbulence = ConstantDynamicViscosity(FT(0))\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = turbulence,\n        moisture = DryModel(),\n    )\n    model = AtmosModel{FT}(\n        AtmosGCMConfigType,\n        physics;\n        init_state_prognostic = setup,\n        source = (Gravity(),),\n    )\n\n    ode_solver_type = ClimateMachine.MultirateSolverType(\n        splitting_type = ClimateMachine.HEVISplitting(),\n        fast_model = AtmosAcousticGravityLinearModel,\n        implicit_solver_adjustable = true,\n        slow_method = LSRK54CarpenterKennedy,\n        fast_method = ARK2ImplicitExplicitMidpoint,\n        timestep_ratio = 300,\n    )\n    driver_config = ClimateMachine.AtmosGCMConfiguration(\n        \"GCM Driver test\",\n        N,\n        resolution,\n        setup.domain_height,\n        param_set,\n        setup;\n        model = model,\n    )\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        ode_dt = dt,\n    )\n\n    cb_test = 0\n    result = ClimateMachine.invoke!(\n        solver_config;\n        user_info_callback = () -> cb_test += 1,\n    )\n    @test cb_test > 0\nend\n\nmain()\n"
  },
  {
    "path": "test/Driver/les_driver_test.jl",
    "content": "using StaticArrays\nusing Test\n\nusing ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.Mesh.Grids\nusing Thermodynamics\nusing ClimateMachine.VariableTemplates\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: grav, MSLP\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nfunction init_test!(problem, bl, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n\n    z = FT(z)\n    _grav::FT = grav(param_set)\n    _MSLP::FT = MSLP(param_set)\n\n    # These constants are those used by Stevens et al. (2005)\n    qref = FT(9.0e-3)\n    q_pt_sfc = PhasePartition(qref)\n    Rm_sfc = FT(gas_constant_air(param_set, q_pt_sfc))\n    T_sfc = FT(290.4)\n    P_sfc = _MSLP\n\n    # Specify moisture profiles\n    q_liq = FT(0)\n    q_ice = FT(0)\n\n    θ_liq = FT(289.0)\n    q_tot = qref\n\n    ugeo = FT(7)\n    vgeo = FT(-5.5)\n    u, v, w = ugeo, vgeo, FT(0)\n\n    # Pressure\n    H = Rm_sfc * T_sfc / _grav\n    p = P_sfc * exp(-z / H)\n\n    # Density, Temperature\n    ts = PhaseEquil_pθq(param_set, p, θ_liq, q_tot)\n    ρ = air_density(ts)\n\n    e_kin = FT(1 / 2) * FT((u^2 + v^2 + w^2))\n    e_pot = _grav * z\n    E = ρ * total_energy(e_kin, e_pot, ts)\n\n    state.ρ = ρ\n    state.ρu = SVector(ρ * u, ρ * v, ρ * w)\n    state.energy.ρe = E\n    state.moisture.ρq_tot = ρ * q_tot\n\n    return nothing\nend\n\nfunction main()\n    @test_throws ArgumentError ClimateMachine.init(dsisable_gpu = true)\n    ClimateMachine.init()\n\n    FT = Float64\n\n    # DG polynomial orders\n    N = (4, 4)\n\n    # Domain resolution and size\n    Δh = FT(40)\n    Δv = FT(40)\n    resolution = (Δh, Δh, Δv)\n\n    xmax = FT(320)\n    ymax = FT(320)\n    zmax = FT(400)\n\n    t0 = FT(0)\n    timeend = FT(10)\n    CFL = FT(0.4)\n\n    driver_config = ClimateMachine.AtmosLESConfiguration(\n        \"Driver test\",\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        init_test!,\n    )\n    ode_solver_type = ClimateMachine.ExplicitSolverType()\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        Courant_number = CFL,\n    )\n\n    # Test the courant wrapper\n    # by default the CFL should be less than what asked for\n    CFL_nondiff = ClimateMachine.DGMethods.courant(\n        ClimateMachine.Courant.nondiffusive_courant,\n        solver_config,\n    )\n    @test CFL_nondiff < CFL\n    CFL_adv = ClimateMachine.DGMethods.courant(\n        ClimateMachine.Courant.advective_courant,\n        solver_config,\n    )\n    CFL_adv_v = ClimateMachine.DGMethods.courant(\n        ClimateMachine.Courant.advective_courant,\n        solver_config;\n        direction = VerticalDirection(),\n    )\n    CFL_adv_h = ClimateMachine.DGMethods.courant(\n        ClimateMachine.Courant.advective_courant,\n        solver_config;\n        direction = HorizontalDirection(),\n    )\n\n    # compute known advective Courant number (based on initial conditions)\n    ugeo_abs = FT(7)\n    vgeo_abs = FT(5.5)\n    Δt = solver_config.dt\n    ca_h = ugeo_abs * (Δt / Δh) + vgeo_abs * (Δt / Δh)\n    # vertical velocity is 0\n    caᵥ = FT(0.0)\n    @test isapprox(CFL_adv_v, caᵥ, atol = 10 * eps(FT))\n    @test isapprox(CFL_adv_h, ca_h, atol = 0.0005)\n    @test isapprox(CFL_adv, ca_h, atol = 0.0005)\n\n    cb_test = 0\n    result = ClimateMachine.invoke!(solver_config)\n    # cb_test should be zero since user_info_callback not specified\n    @test cb_test == 0\n\n    result = ClimateMachine.invoke!(\n        solver_config,\n        user_info_callback = () -> cb_test += 1,\n    )\n    # cb_test should be greater than one if the user_info_callback got called\n    @test cb_test > 0\n\n    # Test that if dt is not adjusted based on final time the CFL is correct\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        Courant_number = CFL,\n        timeend_dt_adjust = false,\n    )\n\n    CFL_nondiff = ClimateMachine.DGMethods.courant(\n        ClimateMachine.Courant.nondiffusive_courant,\n        solver_config,\n    )\n    @test CFL_nondiff ≈ CFL\n\n    # Test that if dt is not adjusted based on final time the CFL is correct\n    for fixed_number_of_steps in (-1, 0, 10)\n        solver_config = ClimateMachine.SolverConfiguration(\n            t0,\n            timeend,\n            driver_config,\n            Courant_number = CFL,\n            fixed_number_of_steps = fixed_number_of_steps,\n        )\n\n        if fixed_number_of_steps < 0\n            @test solver_config.timeend == timeend\n        else\n            @test solver_config.timeend ==\n                  solver_config.dt * fixed_number_of_steps\n            @test solver_config.numberofsteps == fixed_number_of_steps\n        end\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Driver/mms3.jl",
    "content": "using ClimateMachine\nClimateMachine.init()\nusing ClimateMachine.Atmos\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.Orientations: NoOrientation\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Topologies\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\n\nimport Thermodynamics: total_specific_enthalpy\nimport ClimateMachine.BalanceLaws: source, prognostic_vars\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nimport CLIMAParameters\n# Assume zero reference temperature\nCLIMAParameters.Planet.T_0(::EarthParameterSet) = 0\n\nusing LinearAlgebra\nusing MPI\nusing StaticArrays\nusing Test\nusing UnPack\n\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\ninclude(joinpath(\n    clima_dir,\n    \"test\",\n    \"Numerics\",\n    \"DGMethods\",\n    \"compressible_Navier_Stokes\",\n    \"mms_solution_generated.jl\",\n))\n\ntotal_specific_enthalpy(ts::PhaseDry{FT}, e_tot::FT) where {FT <: Real} =\n    zero(FT)\n\nfunction mms3_init_state!(problem, bl, state::Vars, aux::Vars, localgeo, t)\n    (x1, x2, x3) = localgeo.coord\n    state.ρ = ρ_g(t, x1, x2, x3, Val(3))\n    state.ρu = SVector(\n        U_g(t, x1, x2, x3, Val(3)),\n        V_g(t, x1, x2, x3, Val(3)),\n        W_g(t, x1, x2, x3, Val(3)),\n    )\n    state.energy.ρe = E_g(t, x1, x2, x3, Val(3))\nend\n\nstruct MMSSource{N} <: TendencyDef{Source} end\n\nprognostic_vars(::MMSSource{N}) where {N} = (Mass(), Momentum(), Energy())\n\nfunction source(::Mass, s::MMSSource{N}, m, args) where {N}\n    @unpack aux, t = args\n    x1, x2, x3 = aux.coord\n    return Sρ_g(t, x1, x2, x3, Val(N))\nend\nfunction source(::Momentum, s::MMSSource{N}, m, args) where {N}\n    @unpack aux, t = args\n    x1, x2, x3 = aux.coord\n    return SVector(\n        SU_g(t, x1, x2, x3, Val(N)),\n        SV_g(t, x1, x2, x3, Val(N)),\n        SW_g(t, x1, x2, x3, Val(N)),\n    )\nend\nfunction source(::Energy, s::MMSSource{N}, m, args) where {N}\n    @unpack aux, t = args\n    x1, x2, x3 = aux.coord\n    return SE_g(t, x1, x2, x3, Val(N))\nend\n\nfunction main()\n    FT = Float64\n\n    # DG polynomial order\n    N = 4\n\n    t0 = FT(0)\n    timeend = FT(1)\n\n    ode_dt = 0.00125\n    nsteps = ceil(Int64, timeend / ode_dt)\n    ode_dt = timeend / nsteps\n\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK54CarpenterKennedy,\n    )\n\n    expected_result = FT(3.403104838700577e-02)\n\n    problem = AtmosProblem(\n        boundaryconditions = (InitStateBC(),),\n        init_state_prognostic = mms3_init_state!,\n    )\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = NoReferenceState(),\n        turbulence = ConstantDynamicViscosity(FT(μ_exact), WithDivergence()),\n        moisture = DryModel(),\n    )\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        problem = problem,\n        orientation = NoOrientation(),\n        source = (MMSSource{3}(),),\n    )\n\n    brickrange = (\n        range(FT(0); length = 5, stop = 1),\n        range(FT(0); length = 5, stop = 1),\n        range(FT(0); length = 5, stop = 1),\n    )\n    topl = BrickTopology(\n        MPI.COMM_WORLD,\n        brickrange,\n        periodicity = (false, false, false),\n        connectivity = :face,\n    )\n    warpfun =\n        (x1, x2, x3) -> begin\n            (\n                x1 + (x1 - 1 / 2) * cos(2 * π * x2 * x3) / 4,\n                x2 + exp(sin(2π * (x1 * x2 + x3))) / 20,\n                x3 + x1 / 4 + x2^2 / 2 + sin(x1 * x2 * x3),\n            )\n        end\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ClimateMachine.array_type(),\n        polynomialorder = N,\n        meshwarp = warpfun,\n    )\n    driver_config = ClimateMachine.DriverConfiguration(\n        AtmosLESConfigType(),\n        \"MMS3\",\n        (N, N),\n        FT,\n        ClimateMachine.array_type(),\n        param_set,\n        model,\n        MPI.COMM_WORLD,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n        nothing,\n        nothing, # filter\n        ClimateMachine.AtmosLESSpecificInfo(),\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        ode_dt = ode_dt,\n    )\n    Q₀ = solver_config.Q\n\n    # turn on checkpointing\n    ClimateMachine.Settings.checkpoint = \"300steps\"\n    ClimateMachine.Settings.checkpoint_keep_one = false\n\n    # run the simulation\n    ClimateMachine.invoke!(solver_config)\n\n    # turn off checkpointing and set up a restart\n    ClimateMachine.Settings.checkpoint = \"never\"\n    ClimateMachine.Settings.restart_from_num = 2\n\n    # the solver configuration is where the restart is set up\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        ode_dt = ode_dt,\n    )\n\n    # run the restarted simulation\n    ClimateMachine.invoke!(solver_config)\n\n    # test correctness\n    dg = DGModel(driver_config)\n    Qe = init_ode_state(dg, timeend)\n    result = euclidean_distance(Q₀, Qe)\n    @test result ≈ expected_result\nend\n\nmain()\n"
  },
  {
    "path": "test/Driver/runtests.jl",
    "content": "using MPI, Test\n\ninclude(\"../testhelpers.jl\")\n\n@testset \"Driver\" begin\n    runmpi(joinpath(@__DIR__, \"cr_unit_tests.jl\"), ntasks = 1)\n    runmpi(joinpath(@__DIR__, \"les_driver_test.jl\"), ntasks = 1)\n    runmpi(joinpath(@__DIR__, \"mms3.jl\"), ntasks = 2)\nend\n"
  },
  {
    "path": "test/InputOutput/VTK/runtests.jl",
    "content": "module TestVTK\n\nusing Test\nusing GaussQuadrature: legendre, both\nusing ClimateMachine.VTK: writemesh_highorder, writemesh_raw\n\n@testset \"VTK\" begin\n    for writemesh in (writemesh_highorder, writemesh_raw)\n        Ns = writemesh == writemesh_raw ? ((4, 4, 4), (3, 5, 7)) : ((4, 4, 4),)\n        for N in Ns\n            for dim in 1:3\n                nelem = 3\n                T = Float64\n\n                Nq = N .+ 1\n                r = legendre(T, Nq[1], both)[1]\n                s = dim < 2 ? [0] : legendre(T, Nq[2], both)[1]\n                t = dim < 3 ? [0] : legendre(T, Nq[3], both)[1]\n                Nq1 = length(r)\n                Nq2 = length(s)\n                Nq3 = length(t)\n                Np = Nq1 * Nq2 * Nq3\n\n                x1 = Array{T, 4}(undef, Nq1, Nq2, Nq3, nelem)\n                x2 = Array{T, 4}(undef, Nq1, Nq2, Nq3, nelem)\n                x3 = Array{T, 4}(undef, Nq1, Nq2, Nq3, nelem)\n\n                for e in 1:nelem, k in 1:Nq3, j in 1:Nq2, i in 1:Nq1\n                    xoffset = nelem + 1 - 2e\n                    x1[i, j, k, e], x2[i, j, k, e], x3[i, j, k, e] =\n                        r[i] - xoffset, s[j], t[k]\n                end\n\n                if dim == 1\n                    x1 = x1 .^ 3\n                elseif dim == 2\n                    x1, x2 = x1 + sin.(π * x2) / 5, x2 + exp.(-x1 .^ 2)\n                else\n                    x1, x2, x3 = x1 + sin.(π * x2) / 5,\n                    x2 + exp.(-hypot.(x1, x3) .^ 2),\n                    x3 + sin.(π * x1) / 5\n                end\n                d = exp.(sin.(hypot.(x1, x2, x3)))\n                s = copy(d)\n\n                if dim == 1\n                    @test \"test$(dim)d.vtu\" == writemesh(\n                        \"test$(dim)d\",\n                        x1;\n                        fields = ((\"d\", d), (\"s\", s)),\n                    )[1]\n                    @test \"test$(dim)d.vtu\" == writemesh(\n                        \"test$(dim)d\",\n                        x1;\n                        x2 = x2,\n                        fields = ((\"d\", d), (\"s\", s)),\n                    )[1]\n                    @test \"test$(dim)d.vtu\" == writemesh(\n                        \"test$(dim)d\",\n                        x1;\n                        x2 = x2,\n                        x3 = x3,\n                        fields = ((\"d\", d), (\"s\", s)),\n                    )[1]\n                elseif dim == 2\n                    @test \"test$(dim)d.vtu\" == writemesh(\n                        \"test$(dim)d\",\n                        x1,\n                        x2;\n                        fields = ((\"d\", d), (\"s\", s)),\n                    )[1]\n                    @test \"test$(dim)d.vtu\" == writemesh(\n                        \"test$(dim)d\",\n                        x1,\n                        x2;\n                        x3 = x3,\n                        fields = ((\"d\", d), (\"s\", s)),\n                    )[1]\n                elseif dim == 3\n                    @test \"test$(dim)d.vtu\" == writemesh(\n                        \"test$(dim)d\",\n                        x1,\n                        x2,\n                        x3;\n                        fields = ((\"d\", d), (\"s\", s)),\n                    )[1]\n                end\n            end\n        end\n    end\nend\n\n\nusing MPI\nMPI.Initialized() || MPI.Init()\nusing ClimateMachine.Mesh.Topologies: BrickTopology\nusing ClimateMachine.Mesh.Grids: DiscontinuousSpectralElementGrid\nusing ClimateMachine.VTK: writevtk_helper\n\nlet\n    mpicomm = MPI.COMM_SELF\n    for FT in (Float64,) #Float32)\n        for dim in 2:3\n            for _N in ((2, 3, 4), (0, 2, 5), (3, 0, 0), (0, 0, 0))\n                N = _N[1:dim]\n                if dim == 2\n                    Ne = (4, 5)\n                    brickrange = (\n                        range(FT(0); length = Ne[1] + 1, stop = 1),\n                        range(FT(0); length = Ne[2] + 1, stop = 1),\n                    )\n                    topl = BrickTopology(\n                        mpicomm,\n                        brickrange,\n                        periodicity = (false, false),\n                        connectivity = :face,\n                    )\n                    warpfun =\n                        (x1, x2, _) -> begin\n                            (x1 + sin(x1 * x2), x2 + sin(2 * x1 * x2), 0)\n                        end\n                elseif dim == 3\n                    Ne = (3, 4, 5)\n                    brickrange = (\n                        range(FT(0); length = Ne[1] + 1, stop = 1),\n                        range(FT(0); length = Ne[2] + 1, stop = 1),\n                        range(FT(0); length = Ne[3] + 1, stop = 1),\n                    )\n                    topl = BrickTopology(\n                        mpicomm,\n                        brickrange,\n                        periodicity = (false, false, false),\n                        connectivity = :face,\n                    )\n                    warpfun =\n                        (x1, x2, x3) -> begin\n                            (\n                                x1 + (x1 - 1 / 2) * cos(2 * π * x2 * x3) / 4,\n                                x2 + exp(sin(2π * (x1 * x2 + x3))) / 20,\n                                x3 + x1 / 4 + x2^2 / 2 + sin(x1 * x2 * x3),\n                            )\n                        end\n                end\n                grid = DiscontinuousSpectralElementGrid(\n                    topl,\n                    FloatType = FT,\n                    DeviceArray = Array,\n                    polynomialorder = N,\n                    meshwarp = warpfun,\n                )\n                Q = rand(FT, prod(N .+ 1), 3, prod(Ne))\n                prefix = \"test$(dim)d_raw$(prod(ntuple(i->\"_$(N[i])\", dim)))\"\n                @test \"$(prefix).vtu\" == writevtk_helper(\n                    prefix,\n                    grid.vgeo,\n                    Q,\n                    grid,\n                    (\"a\", \"b\", \"c\");\n                    number_sample_points = 0,\n                )[1]\n                prefix = \"test$(dim)d_high_order$(prod(ntuple(i->\"_$(N[i])\", dim)))\"\n                @test \"$(prefix).vtu\" == writevtk_helper(\n                    prefix,\n                    grid.vgeo,\n                    Q,\n                    grid,\n                    (\"a\", \"b\", \"c\");\n                    number_sample_points = 10,\n                )[1]\n            end\n        end\n    end\nend\n\nend #module TestVTK\n"
  },
  {
    "path": "test/InputOutput/Writers/runtests.jl",
    "content": "module TestWriters\n\nusing Dates\nusing NCDatasets\nusing OrderedCollections\nusing Test\nusing ClimateMachine.Writers\n\n@testset \"Writers\" begin\n    odims = OrderedDict(\n        \"x\" => (collect(1:5), Dict()),\n        \"y\" => (collect(1:5), Dict()),\n        \"z\" => (collect(1010:10:1050), Dict()),\n    )\n    ovartypes = OrderedDict(\n        \"v1\" => ((\"x\", \"y\", \"z\"), Float64, Dict()),\n        \"v2\" => ((\"x\", \"y\", \"z\"), Float64, Dict()),\n    )\n    vals1 = rand(5, 5, 5)\n    vals2 = rand(5, 5, 5)\n\n    nc = NetCDFWriter()\n    nfn, _ = mktemp()\n    nfull = full_name(nc, nfn)\n\n    touch(nfull)\n    @test_throws ErrorException init_data(nc, nfn, true, odims, ovartypes)\n    rm(nfull)\n\n    init_data(nc, nfn, false, odims, ovartypes)\n    append_data(nc, OrderedDict(\"v1\" => vals1, \"v2\" => vals2), 2.0)\n\n    NCDataset(nfull, \"r\") do nds\n        xdim = nds[\"x\"][:]\n        ydim = nds[\"y\"][:]\n        zdim = nds[\"z\"][:]\n        @test xdim == odims[\"x\"][1]\n        @test ydim == odims[\"y\"][1]\n        @test zdim == odims[\"z\"][1]\n        @test try\n            adim = nds[\"a\"][:]\n            adim == ones(5)\n            false\n        catch e\n            true\n        end\n        t = nds[\"time\"][:]\n        v1 = nds[\"v1\"][:]\n        v2 = nds[\"v2\"][:]\n        @test length(t) == 1\n        @test t[1] == DateTime(1900, 1, 1, 0, 0, 2)\n        @test v1[:, :, :, 1] == vals1\n        @test v2[:, :, :, 1] == vals2\n    end\nend\n\nend #module TestWriters\n"
  },
  {
    "path": "test/InputOutput/runtests.jl",
    "content": "using Test, Pkg\n\n@testset \"InputOutput\" begin\n    all_tests = isempty(ARGS) || \"all\" in ARGS ? true : false\n    for submodule in [\"VTK\", \"Writers\"]\n        if all_tests ||\n           \"$submodule\" in ARGS ||\n           \"InputOutput/$submodule\" in ARGS ||\n           \"InputOutput\" in ARGS\n            include_test(submodule)\n        end\n    end\n\nend\n"
  },
  {
    "path": "test/Land/Model/Artifacts.toml",
    "content": "[richards]\ngit-tree-sha1 = \"ff73fa6a0b6a807e71a6921f7ef7d0befe776edd\"\n\n[richards_sand]\ngit-tree-sha1 = \"b0dc82dd02159c646e909bfb61170d3b9dc347f3\"\n\n[tiltedv]\ngit-tree-sha1 = \"db27235cb7ce2b7674607876da15d1635906b512\""
  },
  {
    "path": "test/Land/Model/freeze_thaw_alone.jl",
    "content": "# Test that freeze thaw alone conserves water mass\n\nusing MPI\nusing OrderedCollections\nusing StaticArrays\nusing Statistics\nusing Test\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\nusing CLIMAParameters.Planet: ρ_cloud_liq\nusing CLIMAParameters.Planet: ρ_cloud_ice\n\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.SoilWaterParameterizations\nusing ClimateMachine.Land.SoilHeatParameterizations\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods: BalanceLaw, LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux, vars_state\n\n@testset \"Freeze thaw alone\" begin\n    struct tmp_model <: BalanceLaw end\n    struct tmp_param_set <: AbstractParameterSet end\n\n    function get_grid_spacing(\n        N_poly::Int64,\n        nelem_vert::Int64,\n        zmax::FT,\n        zmin::FT,\n    ) where {FT}\n        test_config = ClimateMachine.SingleStackConfiguration(\n            \"TmpModel\",\n            N_poly,\n            nelem_vert,\n            zmax,\n            tmp_param_set(),\n            tmp_model();\n            zmin = zmin,\n        )\n\n        Δ = min_node_distance(test_config.grid)\n        return Δ\n    end\n\n    function init_soil_water!(land, state, aux, coordinates, time)\n        ϑ_l = eltype(state)(land.soil.water.initialϑ_l(aux))\n        θ_i = eltype(state)(land.soil.water.initialθ_i(aux))\n        state.soil.water.ϑ_l = ϑ_l\n        state.soil.water.θ_i = θ_i\n        param_set = land.param_set\n\n        θ_l =\n            volumetric_liquid_fraction(ϑ_l, land.soil.param_functions.porosity)\n        ρc_ds = land.soil.param_functions.ρc_ds\n        ρc_s = volumetric_heat_capacity(θ_l, θ_i, ρc_ds, param_set)\n\n        state.soil.heat.ρe_int = volumetric_internal_energy(\n            θ_i,\n            ρc_s,\n            land.soil.heat.initialT(aux),\n            param_set,\n        )\n    end\n\n    FT = Float64\n    ClimateMachine.init()\n\n    N_poly = 1\n    nelem_vert = 30\n    zmax = FT(0)\n    zmin = FT(-1)\n    t0 = FT(0)\n    timeend = FT(60 * 60 * 24)\n    dt = FT(1800)\n\n    Δ = get_grid_spacing(N_poly, nelem_vert, zmax, zmin)\n    freeze_thaw_source = PhaseChange{FT}(Δz = Δ)\n    ρp = FT(2700) # kg/m^3\n    ρc_ds = FT(2e06) # J/m^3/K\n\n    Ksat = FT(0.0)\n    S_s = FT(1e-4)\n    wpf = WaterParamFunctions(FT; Ksat = Ksat, S_s = S_s)\n    soil_param_functions = SoilParamFunctions(\n        FT;\n        porosity = 0.75,\n        ν_ss_gravel = 0.0,\n        ν_ss_om = 0.0,\n        ν_ss_quartz = 0.5,\n        ρc_ds = ρc_ds,\n        ρp = ρp,\n        κ_solid = 1.0,\n        κ_sat_unfrozen = 1.0,\n        κ_sat_frozen = 1.0,\n        water = wpf,\n    )\n\n\n    bottom_flux = (aux, t) -> eltype(aux)(0.0)\n    surface_flux = (aux, t) -> eltype(aux)(0.0)\n    bc = LandDomainBC(\n        bottom_bc = LandComponentBC(\n            soil_water = Neumann(bottom_flux),\n            soil_heat = Dirichlet((aux, t) -> eltype(aux)(280)),\n        ),\n        surface_bc = LandComponentBC(\n            soil_water = Neumann(surface_flux),\n            soil_heat = Dirichlet((aux, t) -> eltype(aux)(290)),\n        ),\n    )\n    ϑ_l0 = (aux) -> eltype(aux)(1e-10)\n    θ_i0 = (aux) -> eltype(aux)(0.33)\n\n    soil_water_model = SoilWaterModel(FT; initialϑ_l = ϑ_l0, initialθ_i = θ_i0)\n\n    T_init = (aux) -> eltype(aux)(aux.z * 10 + 290)\n    soil_heat_model = SoilHeatModel(FT; initialT = T_init)\n\n    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)\n    sources = (freeze_thaw_source,)\n    m = LandModel(\n        param_set,\n        m_soil;\n        boundary_conditions = bc,\n        source = sources,\n        init_state_prognostic = init_soil_water!,\n    )\n\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"LandModel\",\n        N_poly,\n        nelem_vert,\n        zmax,\n        param_set,\n        m;\n        zmin = zmin,\n        numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n    )\n\n\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n    state_types = (Prognostic(), Auxiliary())\n    initial =\n        Dict[dict_of_nodal_states(solver_config, state_types; interp = true)]\n    ClimateMachine.invoke!(solver_config)\n    final =\n        Dict[dict_of_nodal_states(solver_config, state_types; interp = true)]\n    m_init =\n        ρ_cloud_liq(param_set) * sum(initial[1][\"soil.water.ϑ_l\"]) .+\n        ρ_cloud_ice(param_set) * sum(initial[1][\"soil.water.θ_i\"])\n    m_final =\n        ρ_cloud_liq(param_set) * sum(final[1][\"soil.water.ϑ_l\"]) .+\n        ρ_cloud_ice(param_set) * sum(final[1][\"soil.water.θ_i\"])\n\n    @test abs(m_final - m_init) < 1e-10\nend\n"
  },
  {
    "path": "test/Land/Model/haverkamp_test.jl",
    "content": "# Test that Richard's equation agrees with solution from Bonan's book,\n# simulation 8.2\nusing MPI\nusing OrderedCollections\nusing StaticArrays\nusing Statistics\nusing Dierckx\nusing Test\nusing Pkg.Artifacts\nusing DelimitedFiles\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.SoilWaterParameterizations\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods: BalanceLaw, LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux, vars_state\nusing ArtifactWrappers\n\nhaverkamp_dataset = ArtifactWrapper(\n    @__DIR__,\n    isempty(get(ENV, \"CI\", \"\")),\n    \"richards\",\n    ArtifactFile[ArtifactFile(\n        url = \"https://caltech.box.com/shared/static/dfijf07io7h5dk1k87saaewgsg9apq8d.csv\",\n        filename = \"bonan_haverkamp_data.csv\",\n    ),],\n)\nhaverkamp_dataset_path = get_data_folder(haverkamp_dataset)\n\nbonan_sand_dataset = ArtifactWrapper(\n    @__DIR__,\n    isempty(get(ENV, \"CI\", \"\")),\n    \"richards_sand\",\n    ArtifactFile[ArtifactFile(\n        url = \"https://caltech.box.com/shared/static/2vk7bvyjah8xd5b7wxcqy72yfd2myjss.csv\",\n        filename = \"sand_bonan_sp801.csv\",\n    ),],\n)\nbonan_sand_dataset_path = get_data_folder(bonan_sand_dataset)\n\n@testset \"Richard's equation - Haverkamp test\" begin\n    ClimateMachine.init()\n    FT = Float64\n\n    function init_soil_water!(land, state, aux, localgeo, time)\n        myfloat = eltype(aux)\n        state.soil.water.ϑ_l = myfloat(land.soil.water.initialϑ_l(aux))\n        state.soil.water.θ_i = myfloat(land.soil.water.initialθ_i(aux))\n    end\n\n    soil_heat_model = PrescribedTemperatureModel()\n    Ksat = FT(0.0443 / (3600 * 100))\n    S_s = FT(1e-3)\n    water_param_functions = WaterParamFunctions(FT; Ksat = Ksat, S_s = S_s)\n\n    soil_param_functions =\n        SoilParamFunctions(FT; porosity = 0.495, water = water_param_functions)\n    surface_state = (aux, t) -> eltype(aux)(0.494)\n    bottom_flux = (aux, t) -> aux.soil.water.K * eltype(aux)(-1)\n    ϑ_l0 = (aux) -> eltype(aux)(0.24)\n\n    bc = LandDomainBC(\n        bottom_bc = LandComponentBC(soil_water = Neumann(bottom_flux)),\n        surface_bc = LandComponentBC(soil_water = Dirichlet(surface_state)),\n    )\n    soil_water_model = SoilWaterModel(\n        FT;\n        moisture_factor = MoistureDependent{FT}(),\n        hydraulics = Haverkamp(FT;),\n        initialϑ_l = ϑ_l0,\n    )\n\n    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)\n    sources = ()\n    m = LandModel(\n        param_set,\n        m_soil;\n        boundary_conditions = bc,\n        source = sources,\n        init_state_prognostic = init_soil_water!,\n    )\n\n\n    N_poly = 5\n    nelem_vert = 10\n\n    # Specify the domain boundaries\n    zmax = FT(0)\n    zmin = FT(-1)\n\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"LandModel\",\n        N_poly,\n        nelem_vert,\n        zmax,\n        param_set,\n        m;\n        zmin = zmin,\n        numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n    )\n\n    t0 = FT(0)\n    timeend = FT(60 * 60 * 24)\n\n    dt = FT(6)\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n    mygrid = solver_config.dg.grid\n    Q = solver_config.Q\n    aux = solver_config.dg.state_auxiliary\n\n    ClimateMachine.invoke!(solver_config)\n    ϑ_l_ind = varsindex(vars_state(m, Prognostic(), FT), :soil, :water, :ϑ_l)\n    ϑ_l = Array(Q[:, ϑ_l_ind, :][:])\n    z_ind = varsindex(vars_state(m, Auxiliary(), FT), :z)\n    z = Array(aux[:, z_ind, :][:])\n\n    # Compare with Bonan simulation data at 1 day.\n    data = joinpath(haverkamp_dataset_path, \"bonan_haverkamp_data.csv\")\n    ds_bonan = readdlm(data, ',')\n    bonan_moisture = reverse(ds_bonan[:, 1])\n    bonan_z = reverse(ds_bonan[:, 2]) ./ 100.0\n\n\n    # Create an interpolation from the Bonan data\n    bonan_moisture_continuous = Spline1D(bonan_z, bonan_moisture)\n    bonan_at_clima_z = bonan_moisture_continuous.(z)\n    MSE = mean((bonan_at_clima_z .- ϑ_l) .^ 2.0)\n    @test MSE < 1e-5\nend\n\n\n@testset \"Richard's equation - Haverkamp implicit test\" begin\n    ClimateMachine.init()\n    FT = Float64\n\n    function init_soil_water!(land, state, aux, localgeo, time)\n        myfloat = eltype(aux)\n        state.soil.water.ϑ_l = myfloat(land.soil.water.initialϑ_l(aux))\n        state.soil.water.θ_i = myfloat(land.soil.water.initialθ_i(aux))\n    end\n\n    soil_heat_model = PrescribedTemperatureModel()\n    Ksat = FT(0.0443 / (3600 * 100))\n    S_s = FT(1e-3)\n    water_param_functions = WaterParamFunctions(FT; Ksat = Ksat, S_s = S_s)\n    soil_param_functions =\n        SoilParamFunctions(FT; porosity = 0.495, water = water_param_functions)\n    surface_value = FT(0.494)\n    bottom_flux_multiplier = FT(1.0)\n    initial_moisture = FT(0.24)\n\n    surface_state = (aux, t) -> surface_value\n    bottom_flux = (aux, t) -> aux.soil.water.K * bottom_flux_multiplier\n    ϑ_l0 = (aux) -> initial_moisture\n\n    bc = LandDomainBC(\n        bottom_bc = LandComponentBC(soil_water = Neumann(bottom_flux)),\n        surface_bc = LandComponentBC(soil_water = Dirichlet(surface_state)),\n    )\n\n    soil_water_model = SoilWaterModel(\n        FT;\n        moisture_factor = MoistureDependent{FT}(),\n        hydraulics = Haverkamp(FT;),\n        initialϑ_l = ϑ_l0,\n    )\n\n    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)\n    sources = ()\n    m = LandModel(\n        param_set,\n        m_soil;\n        boundary_conditions = bc,\n        source = sources,\n        init_state_prognostic = init_soil_water!,\n    )\n\n\n    N_poly = 5\n    nelem_vert = 10\n\n    # Specify the domain boundaries\n    zmax = FT(0)\n    zmin = FT(-1)\n\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"LandModel\",\n        N_poly,\n        nelem_vert,\n        zmax,\n        param_set,\n        m;\n        zmin = zmin,\n        numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n    )\n\n    t0 = FT(0)\n    timeend = FT(60 * 60 * 24)\n\n    dt = FT(200)\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n\n    #################### Change the ode_solver\n\n    dg = solver_config.dg\n    Q = solver_config.Q\n\n    vdg = DGModel(\n        driver_config;\n        state_auxiliary = dg.state_auxiliary,\n        direction = VerticalDirection(),\n    )\n\n\n\n    linearsolver = BatchedGeneralizedMinimalResidual(\n        dg,\n        Q;\n        max_subspace_size = 30,\n        atol = -1.0,\n        rtol = 1e-9,\n    )\n\n    \"\"\"\n    N(q)(Q) = Qhat  => F(Q) = N(q)(Q) - Qhat\n\n    F(Q) == 0\n    ||F(Q^i) || / ||F(Q^0) || < tol\n\n    \"\"\"\n    nonlinearsolver =\n        JacobianFreeNewtonKrylovSolver(Q, linearsolver; tol = 1e-9)\n\n    ode_solver = ARK548L2SA2KennedyCarpenter(\n        dg,\n        vdg,\n        NonLinearBackwardEulerSolver(\n            nonlinearsolver;\n            isadjustable = true,\n            preconditioner_update_freq = 100,\n        ),\n        Q;\n        dt = dt,\n        t0 = 0,\n        split_explicit_implicit = false,\n        variant = NaiveVariant(),\n    )\n\n    solver_config.solver = ode_solver\n\n    #######################################\n\n    mygrid = solver_config.dg.grid\n    aux = solver_config.dg.state_auxiliary\n    ClimateMachine.invoke!(solver_config)\n    ϑ_l_ind = varsindex(vars_state(m, Prognostic(), FT), :soil, :water, :ϑ_l)\n    ϑ_l = Array(Q[:, ϑ_l_ind, :][:])\n    z_ind = varsindex(vars_state(m, Auxiliary(), FT), :z)\n    z = Array(aux[:, z_ind, :][:])\n\n    # Compare with Bonan simulation data at 1 day.\n    data = joinpath(haverkamp_dataset_path, \"bonan_haverkamp_data.csv\")\n    ds_bonan = readdlm(data, ',')\n    bonan_moisture = reverse(ds_bonan[:, 1])\n    bonan_z = reverse(ds_bonan[:, 2]) ./ 100.0\n\n\n    # Create an interpolation from the Bonan data\n    bonan_moisture_continuous = Spline1D(bonan_z, bonan_moisture)\n    bonan_at_clima_z = bonan_moisture_continuous.(z)\n    MSE = mean((bonan_at_clima_z .- ϑ_l) .^ 2.0)\n    @test MSE < 1e-5\nend\n\n\n\n@testset \"Richard's equation - Sand van Genuchten test\" begin\n    ClimateMachine.init()\n    FT = Float64\n\n    function init_soil_water!(land, state, aux, localgeo, time)\n        myfloat = eltype(aux)\n        state.soil.water.ϑ_l = myfloat(land.soil.water.initialϑ_l(aux))\n        state.soil.water.θ_i = myfloat(land.soil.water.initialθ_i(aux))\n    end\n\n    soil_heat_model = PrescribedTemperatureModel()\n    wpf = WaterParamFunctions(\n        FT;\n        Ksat = 34 / (3600 * 100),\n        θ_r = 0.075,\n        S_s = 1e-3,\n    )\n    soil_param_functions = SoilParamFunctions(FT; porosity = 0.287, water = wpf)\n\n    surface_state = (aux, t) -> eltype(aux)(0.267)\n    bottom_flux = (aux, t) -> aux.soil.water.K * eltype(aux)(-1)\n    ϑ_l0 = (aux) -> eltype(aux)(0.1)\n\n    bc = LandDomainBC(\n        bottom_bc = LandComponentBC(soil_water = Neumann(bottom_flux)),\n        surface_bc = LandComponentBC(soil_water = Dirichlet(surface_state)),\n    )\n\n    soil_water_model = SoilWaterModel(\n        FT;\n        moisture_factor = MoistureDependent{FT}(),\n        hydraulics = vanGenuchten(FT; n = 3.96, α = 2.7),\n        initialϑ_l = ϑ_l0,\n    )\n\n    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)\n    sources = ()\n    m = LandModel(\n        param_set,\n        m_soil;\n        boundary_conditions = bc,\n        source = sources,\n        init_state_prognostic = init_soil_water!,\n    )\n\n\n    N_poly = 1\n    nelem_vert = 150\n\n    # Specify the domain boundaries\n    zmax = FT(0)\n    zmin = FT(-1.5)\n\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"LandModel\",\n        N_poly,\n        nelem_vert,\n        zmax,\n        param_set,\n        m;\n        zmin = zmin,\n        numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n    )\n\n    t0 = FT(0)\n    timeend = FT(60 * 60 * 0.8)\n\n    dt = FT(0.5)\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n    mygrid = solver_config.dg.grid\n    Q = solver_config.Q\n    aux = solver_config.dg.state_auxiliary\n\n    ClimateMachine.invoke!(solver_config)\n    ϑ_l_ind = varsindex(vars_state(m, Prognostic(), FT), :soil, :water, :ϑ_l)\n    ϑ_l = Array(Q[:, ϑ_l_ind, :][:])\n    z_ind = varsindex(vars_state(m, Auxiliary(), FT), :z)\n    z = Array(aux[:, z_ind, :][:])\n\n    # Compare with Bonan simulation data at 0.8 h\n    data = joinpath(bonan_sand_dataset_path, \"sand_bonan_sp801.csv\")\n    ds_bonan = readdlm(data, ',')\n    bonan_moisture = reverse(ds_bonan[:, 1])\n    bonan_z = reverse(ds_bonan[:, 2]) ./ 100.0\n\n\n    # Create an interpolation from the Bonan data\n    bonan_moisture_continuous = Spline1D(bonan_z, bonan_moisture)\n    bonan_at_clima_z = bonan_moisture_continuous.(z)\n    MSE = mean((bonan_at_clima_z .- ϑ_l) .^ 2.0)\n    @test MSE < 1e-5\nend\n"
  },
  {
    "path": "test/Land/Model/heat_analytic_unit_test.jl",
    "content": "# Test heat equation agrees with analytic solution to problem 55 on page 28 in https://ocw.mit.edu/courses/mathematics/18-303-linear-partial-differential-equations-fall-2006/lecture-notes/heateqni.pdf\nusing MPI\nusing OrderedCollections\nusing StaticArrays\nusing Statistics\nusing Dierckx\nusing Test\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.SoilWaterParameterizations\nusing ClimateMachine.Land.SoilHeatParameterizations\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods: BalanceLaw, LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.BalanceLaws\n\nfunction init_soil!(land, state, aux, localgeo, time)\n    FT = eltype(state)\n    ϑ_l, θ_i = get_water_content(land.soil.water, aux, state, time)\n    θ_l = volumetric_liquid_fraction(ϑ_l, land.soil.param_functions.porosity)\n    param_set = parameter_set(land)\n    ρc_s = volumetric_heat_capacity(\n        θ_l,\n        θ_i,\n        land.soil.param_functions.ρc_ds,\n        param_set,\n    )\n\n    state.soil.heat.ρe_int = FT(volumetric_internal_energy(\n        θ_i,\n        ρc_s,\n        land.soil.heat.initialT(aux),\n        param_set,\n    ))\nend\n\nconst FT = Float64\n\n@testset \"Heat analytic unit test\" begin\n    ClimateMachine.init()\n\n    soil_param_functions = SoilParamFunctions(\n        FT;\n        porosity = 0.495,\n        ν_ss_gravel = 0.1,\n        ν_ss_om = 0.1,\n        ν_ss_quartz = 0.1,\n        ρc_ds = 0.43314518988433487,\n        κ_solid = 8.0,\n        ρp = 2700.0,\n        κ_sat_unfrozen = 0.57,\n        κ_sat_frozen = 2.29,\n    )\n\n    heat_surface_state = (aux, t) -> eltype(aux)(0.0)\n\n    tau = FT(1) # period (sec)\n    A = FT(5) # amplitude (K)\n    ω = FT(2 * pi / tau)\n    heat_bottom_state = (aux, t) -> A * cos(ω * t)\n    T_init = (aux) -> eltype(aux)(0.0)\n\n    soil_water_model = PrescribedWaterModel(\n        (aux, t) -> eltype(aux)(0.0),\n        (aux, t) -> eltype(aux)(0.0),\n    )\n\n    bc = LandDomainBC(\n        bottom_bc = LandComponentBC(soil_heat = Dirichlet(heat_bottom_state)),\n        surface_bc = LandComponentBC(soil_heat = Dirichlet(heat_surface_state)),\n    )\n    soil_heat_model = SoilHeatModel(FT; initialT = T_init)\n\n    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)\n    sources = ()\n    m = LandModel(\n        param_set,\n        m_soil;\n        boundary_conditions = bc,\n        source = sources,\n        init_state_prognostic = init_soil!,\n    )\n\n    N_poly = 5\n    nelem_vert = 10\n\n    # Specify the domain boundaries\n    zmax = FT(1)\n    zmin = FT(0)\n\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"LandModel\",\n        N_poly,\n        nelem_vert,\n        zmax,\n        param_set,\n        m;\n        zmin = zmin,\n        numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n    )\n\n    t0 = FT(0)\n    timeend = FT(2)\n    dt = FT(1e-4)\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n    mygrid = solver_config.dg.grid\n    aux = solver_config.dg.state_auxiliary\n    ClimateMachine.invoke!(solver_config)\n    t = ODESolvers.gettime(solver_config.solver)\n\n    z_ind = varsindex(vars_state(m, Auxiliary(), FT), :z)\n    z = Array(aux[:, z_ind, :][:])\n\n    T_ind = varsindex(vars_state(m, Auxiliary(), FT), :soil, :heat, :T)\n    T = Array(aux[:, T_ind, :][:])\n\n    num =\n        exp.(sqrt(ω / 2) * (1 + im) * (1 .- z)) .-\n        exp.(-sqrt(ω / 2) * (1 + im) * (1 .- z))\n    denom = exp(sqrt(ω / 2) * (1 + im)) - exp.(-sqrt(ω / 2) * (1 + im))\n    analytic_soln = real(num .* A * exp(im * ω * timeend) / denom)\n    MSE = mean((analytic_soln .- T) .^ 2.0)\n    @test eltype(aux) == FT\n    @test MSE < 1e-5\nend\n"
  },
  {
    "path": "test/Land/Model/prescribed_twice.jl",
    "content": "# Test that the land model still runs, even with the lowest/simplest\n# version of soil (prescribed heat and prescribed water - no state\n# variables)\nusing MPI\nusing OrderedCollections\nusing StaticArrays\nusing Test\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.SoilWaterParameterizations\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods: BalanceLaw, LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux, vars_state\n\n@testset \"Prescribed Models\" begin\n    ClimateMachine.init()\n    FT = Float64\n\n    function init_soil_water!(land, state, aux, localgeo, time) end\n\n    soil_water_model = PrescribedWaterModel()\n    soil_heat_model = PrescribedTemperatureModel()\n    soil_param_functions = nothing\n    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)\n    sources = ()\n    m = LandModel(\n        param_set,\n        m_soil;\n        source = sources,\n        init_state_prognostic = init_soil_water!,\n    )\n\n\n    N_poly = 5\n    nelem_vert = 10\n\n    # Specify the domain boundaries\n    zmax = FT(0)\n    zmin = FT(-1)\n\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"LandModel\",\n        N_poly,\n        nelem_vert,\n        zmax,\n        param_set,\n        m;\n        zmin = zmin,\n        numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n    )\n\n\n    t0 = FT(0)\n    timeend = FT(60)\n    dt = FT(1)\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n    mygrid = solver_config.dg.grid\n    Q = solver_config.Q\n    aux = solver_config.dg.state_auxiliary\n\n    ClimateMachine.invoke!(solver_config;)\n\n    t = ODESolvers.gettime(solver_config.solver)\n    state_vars = SingleStackUtils.get_vars_from_nodal_stack(\n        mygrid,\n        Q,\n        vars_state(m, Prognostic(), FT),\n    )\n    aux_vars = SingleStackUtils.get_vars_from_nodal_stack(\n        mygrid,\n        aux,\n        vars_state(m, Auxiliary(), FT),\n    )\n    #Make sure it runs, and that there are no state variables, and only \"x,y,z\" as aux.\n    @test t == timeend\n    @test size(Base.collect(keys(aux_vars)))[1] == 3\n    @test size(Base.collect(keys(state_vars)))[1] == 0\nend\n"
  },
  {
    "path": "test/Land/Model/runtests.jl",
    "content": "using Test, Pkg\n@testset \"Land\" begin\n    include(\"test_heat_parameterizations.jl\")\n    include(\"test_water_parameterizations.jl\")\n    include(\"prescribed_twice.jl\")\n    include(\"freeze_thaw_alone.jl\")\n    include(\"test_overland_flow_analytic.jl\")\n    include(\"test_physical_bc.jl\")\n    include(\"test_radiative_energy_flux_functions.jl\")\nend\n"
  },
  {
    "path": "test/Land/Model/soil_heterogeneity.jl",
    "content": "using MPI\nusing OrderedCollections\nusing StaticArrays\nusing Statistics\nusing Dierckx\nusing Test\nusing Pkg.Artifacts\nusing DelimitedFiles\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.SoilWaterParameterizations\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods: BalanceLaw, LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux, vars_state\n\n\n@testset \"hydrostatic test 1\" begin\n    ClimateMachine.init()\n    FT = Float64\n\n    function init_soil_water!(land, state, aux, localgeo, time)\n        myfloat = eltype(aux)\n        state.soil.water.ϑ_l = myfloat(land.soil.water.initialϑ_l(aux))\n        state.soil.water.θ_i = myfloat(land.soil.water.initialθ_i(aux))\n    end\n\n    soil_heat_model = PrescribedTemperatureModel()\n\n    Ksat = (aux) -> eltype(aux)(0.0443 / (3600 * 100))\n    S_s = (aux) -> eltype(aux)((1e-3) * exp(-0.2 * aux.z))\n    vgn = FT(2)\n    wpf = WaterParamFunctions(FT; Ksat = Ksat, S_s = S_s)\n\n    soil_param_functions = SoilParamFunctions(FT; porosity = 0.495, water = wpf)\n    bottom_flux = (aux, t) -> eltype(aux)(0.0)\n    surface_flux = bottom_flux\n    ϑ_l0 = (aux) -> eltype(aux)(0.494)\n    bc = LandDomainBC(\n        bottom_bc = LandComponentBC(soil_water = Neumann(bottom_flux)),\n        surface_bc = LandComponentBC(soil_water = Neumann(surface_flux)),\n    )\n    soil_water_model = SoilWaterModel(\n        FT;\n        moisture_factor = MoistureDependent{FT}(),\n        hydraulics = vanGenuchten(FT; n = vgn),\n        initialϑ_l = ϑ_l0,\n    )\n\n    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)\n    sources = ()\n    m = LandModel(\n        param_set,\n        m_soil;\n        boundary_conditions = bc,\n        source = sources,\n        init_state_prognostic = init_soil_water!,\n    )\n\n\n    N_poly = 2\n    nelem_vert = 20\n\n    # Specify the domain boundaries\n    zmax = FT(0)\n    zmin = FT(-10)\n\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"LandModel\",\n        N_poly,\n        nelem_vert,\n        zmax,\n        param_set,\n        m;\n        zmin = zmin,\n        numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n    )\n\n    t0 = FT(0)\n    timeend = FT(60 * 60 * 24 * 200)\n\n    dt = FT(500)\n    n_outputs = 3\n    every_x_simulation_time = ceil(Int, timeend / n_outputs)\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n    aux = solver_config.dg.state_auxiliary\n    state_types = (Prognostic(), Auxiliary())\n    dons_arr =\n        Dict[dict_of_nodal_states(solver_config, state_types; interp = true)]\n    time_data = FT[0]\n    callback = GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n        dons = dict_of_nodal_states(solver_config, state_types; interp = true)\n        push!(dons_arr, dons)\n        push!(time_data, gettime(solver_config.solver))\n        nothing\n    end\n    ClimateMachine.invoke!(solver_config; user_callbacks = (callback,))\n    z = dons_arr[1][\"z\"]\n    interface_z = -1.0395\n    function hydrostatic_profile(z, zm, porosity, n, α)\n        myf = eltype(z)\n        m = FT(1 - 1 / n)\n        S = FT((FT(1) + (α * (z - zm))^n)^(-m))\n        return FT(S * porosity)\n    end\n    function soln(z, interface, porosity, n, α, δ, S_s)\n        if z < interface\n            return porosity + S_s * (interface - z) * exp(-δ * z)\n        else\n            return hydrostatic_profile(z, interface, porosity, n, α)\n        end\n    end\n\n    MSE = mean(\n        (\n            soln.(z, interface_z, 0.495, vgn, 2.6, 0.2, 1e-3) .-\n            dons_arr[4][\"soil.water.ϑ_l\"]\n        ) .^ 2.0,\n    )\n    @test MSE < 1e-4\nend\n\n\n@testset \"hydrostatic test 2\" begin\n    ClimateMachine.init()\n    FT = Float64\n\n    function init_soil_water!(land, state, aux, localgeo, time)\n        myfloat = eltype(aux)\n        state.soil.water.ϑ_l = myfloat(land.soil.water.initialϑ_l(aux))\n        state.soil.water.θ_i = myfloat(land.soil.water.initialθ_i(aux))\n    end\n\n    soil_heat_model = PrescribedTemperatureModel()\n\n    Ksat = (4.42 / 3600 / 100)\n    S_s = 1e-3\n    wpf = WaterParamFunctions(FT; Ksat = Ksat, S_s = S_s)\n\n    soil_param_functions = SoilParamFunctions(FT, porosity = 0.6, water = wpf)\n    bottom_flux = (aux, t) -> eltype(aux)(0.0)\n    surface_flux = bottom_flux\n    bc = LandDomainBC(\n        bottom_bc = LandComponentBC(soil_water = Neumann(bottom_flux)),\n        surface_bc = LandComponentBC(soil_water = Neumann(surface_flux)),\n    )\n    sigmoid(x, offset, width) =\n        typeof(x)(exp((x - offset) / width) / (1 + exp((x - offset) / width)))\n    function soln(z::f, interface::f, porosity::f) where {f}\n        function hydrostatic_profile(\n            z::f,\n            interface::f,\n            porosity::f,\n            n::f,\n            α::f,\n            m::f,\n        )\n            ψ_interface = f(-1)\n            ψ = -(z - interface) + ψ_interface\n            S = (f(1) + (-α * ψ)^n)^(-m)\n            return S * porosity\n        end\n        if z < interface\n            return hydrostatic_profile(\n                z,\n                interface,\n                porosity,\n                f(1.31),\n                f(1.9),\n                f(1) - f(1) / f(1.31),\n            )\n        else\n            return hydrostatic_profile(\n                z,\n                interface,\n                porosity,\n                f(1.89),\n                f(7.5),\n                f(1) - f(1) / f(1.89),\n            )\n        end\n    end\n    ϑ_l0 = (aux) -> soln(aux.z, -1.0, 0.6)\n\n    vgα(aux) = aux.z < -1.0 ? 1.9 : 7.5\n    vgn(aux) = aux.z < -1.0 ? 1.31 : 1.89\n\n    soil_water_model = SoilWaterModel(\n        FT;\n        moisture_factor = MoistureDependent{FT}(),\n        hydraulics = vanGenuchten(FT; n = vgn, α = vgα),\n        initialϑ_l = ϑ_l0,\n    )\n\n    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)\n    sources = ()\n    m = LandModel(\n        param_set,\n        m_soil;\n        boundary_conditions = bc,\n        source = sources,\n        init_state_prognostic = init_soil_water!,\n    )\n\n\n    N_poly = 1\n    nelem_vert = 80\n\n    # Specify the domain boundaries\n    zmax = FT(0)\n    zmin = FT(-2)\n\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"LandModel\",\n        N_poly,\n        nelem_vert,\n        zmax,\n        param_set,\n        m;\n        zmin = zmin,\n        numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n    )\n\n    t0 = FT(0)\n    timeend = FT(60 * 60 * 12)\n\n    dt = FT(5)\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n\n    ClimateMachine.invoke!(solver_config)\n\n    state_types = (Prognostic(), Auxiliary())\n    dons = dict_of_nodal_states(solver_config, state_types; interp = true)\n    z = dons[\"z\"]\n\n    RMSE =\n        mean(\n            (soln.(z, Ref(-1.0), Ref(0.6)) .- dons[\"soil.water.ϑ_l\"]) .^ 2.0,\n        )^0.5\n    @test RMSE < 2.0 * eps(FT)\nend\n"
  },
  {
    "path": "test/Land/Model/test_bc.jl",
    "content": "# Test that the way we specify boundary conditions works as expected\nusing MPI\nusing OrderedCollections\nusing StaticArrays\nusing Statistics\nusing Test\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.SoilWaterParameterizations\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods: BalanceLaw, LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux, vars_state\n\n@testset \"Boundary condition functions\" begin\n    ClimateMachine.init()\n\n    FT = Float64\n\n    function init_soil_water!(land, state, aux, localgeo, time)\n        myfloat = eltype(state)\n        state.soil.water.ϑ_l = myfloat(land.soil.water.initialϑ_l(aux))\n        state.soil.water.θ_i = myfloat(land.soil.water.initialθ_i(aux))\n    end\n\n    wpf = WaterParamFunctions(FT; Ksat = 1e-7, S_s = 1e-3)\n    soil_param_functions = SoilParamFunctions(FT; porosity = 0.75, water = wpf)\n    bottom_flux_amplitude = FT(-3.0)\n    f = FT(pi * 2.0 / 300.0)\n    bottom_flux =\n        (aux, t) -> bottom_flux_amplitude * sin(f * t) * aux.soil.water.K\n    surface_state = (aux, t) -> eltype(aux)(0.2)\n    ϑ_l0 = (aux) -> eltype(aux)(0.2)\n    bc = LandDomainBC(\n        bottom_bc = LandComponentBC(soil_water = Neumann(bottom_flux)),\n        surface_bc = LandComponentBC(soil_water = Dirichlet(surface_state)),\n    )\n    soil_water_model = SoilWaterModel(FT; initialϑ_l = ϑ_l0)\n    soil_heat_model = PrescribedTemperatureModel()\n\n    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)\n    sources = ()\n    m = LandModel(\n        param_set,\n        m_soil;\n        boundary_conditions = bc,\n        source = sources,\n        init_state_prognostic = init_soil_water!,\n    )\n\n\n    N_poly = 5\n    nelem_vert = 50\n\n\n    # Specify the domain boundaries\n    zmax = FT(0)\n    zmin = FT(-1)\n\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"LandModel\",\n        N_poly,\n        nelem_vert,\n        zmax,\n        param_set,\n        m;\n        zmin = zmin,\n        numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n    )\n\n\n    t0 = FT(0)\n    timeend = FT(300)\n    dt = FT(0.05)\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n    mygrid = solver_config.dg.grid\n    Q = solver_config.Q\n    aux = solver_config.dg.state_auxiliary\n    grads = solver_config.dg.state_gradient_flux\n    K∇h_vert_ind =\n        varsindex(vars_state(m, GradientFlux(), FT), :soil, :water)[3]\n    K_ind = varsindex(vars_state(m, Auxiliary(), FT), :soil, :water, :K)\n    n_outputs = 30\n\n    every_x_simulation_time = ceil(Int, timeend / n_outputs)\n\n    dons_arr = Dict([k => Dict() for k in 1:n_outputs]...)\n\n    iostep = [1]\n    callback = GenericCallbacks.EveryXSimulationTime(\n        every_x_simulation_time,\n    ) do (init = false)\n        t = ODESolvers.gettime(solver_config.solver)\n        K = aux[:, K_ind, :]\n        K∇h_vert = grads[:, K∇h_vert_ind, :]\n        all_vars = Dict{String, Array}(\n            \"t\" => [t],\n            \"K\" => K,\n            \"K∇h_vert\" => K∇h_vert,\n        )\n        dons_arr[iostep[1]] = all_vars\n        iostep[1] += 1\n        nothing\n    end\n\n    ClimateMachine.invoke!(solver_config; user_callbacks = (callback,))\n    t = ODESolvers.gettime(solver_config.solver)\n    K = aux[:, K_ind, :]\n    K∇h_vert = grads[:, K∇h_vert_ind, :]\n    all_vars = Dict{String, Array}(\"t\" => [t], \"K\" => K, \"K∇h_vert\" => K∇h_vert)\n    dons_arr[n_outputs] = all_vars\n\n\n    computed_bottom_∇h =\n        [dons_arr[k][\"K∇h_vert\"][1] for k in 1:n_outputs] ./ [dons_arr[k][\"K\"][1] for k in 1:n_outputs]\n\n\n    t = [dons_arr[k][\"t\"][1] for k in 1:n_outputs]\n    # we need a -1 out in front here because the flux BC is on -K∇h\n    prescribed_bottom_∇h = t -> FT(-1) * FT(-3.0 * sin(pi * 2.0 * t / 300.0))\n\n    MSE = mean((prescribed_bottom_∇h.(t) .- computed_bottom_∇h) .^ 2.0)\n    @test MSE < 1e-7\nend\n"
  },
  {
    "path": "test/Land/Model/test_bc_3d.jl",
    "content": "# Test that the way we specify boundary conditions works as expected\nusing MPI\nusing OrderedCollections\nusing StaticArrays\nusing Statistics\nusing Test\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.SoilWaterParameterizations\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods: BalanceLaw, LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux, vars_state\n\n@testset \"Boundary condition functions\" begin\n    ClimateMachine.init()\n\n    FT = Float64\n\n    function init_soil_water!(land, state, aux, localgeo, time)\n        myfloat = eltype(state)\n        state.soil.water.ϑ_l = myfloat(land.soil.water.initialϑ_l(aux))\n        state.soil.water.θ_i = myfloat(land.soil.water.initialθ_i(aux))\n    end\n    wpf = WaterParamFunctions(FT; Ksat = 1e-7, S_s = 1e-3)\n    soil_param_functions = SoilParamFunctions(FT; porosity = 0.75, water = wpf)\n    bottom_flux_amplitude = FT(-3.0)\n    f = FT(pi * 2.0 / 300.0)\n    # nota bene: the flux is -K∇h\n    bottom_flux =\n        (aux, t) -> bottom_flux_amplitude * sin(f * t) * aux.soil.water.K\n    surface_state = (aux, t) -> eltype(aux)(0.2)\n    lateral_state = (aux, t) -> eltype(aux)(0.0)\n    ϑ_l0 = (aux) -> eltype(aux)(0.2)\n\n    bc = LandDomainBC(\n        bottom_bc = LandComponentBC(soil_water = Neumann(bottom_flux)),\n        surface_bc = LandComponentBC(soil_water = Dirichlet(surface_state)),\n        minx_bc = LandComponentBC(soil_water = Neumann(lateral_state)),\n        maxx_bc = LandComponentBC(soil_water = Neumann(lateral_state)),\n        miny_bc = LandComponentBC(soil_water = Neumann(lateral_state)),\n        maxy_bc = LandComponentBC(soil_water = Neumann(lateral_state)),\n    )\n    soil_water_model = SoilWaterModel(FT; initialϑ_l = ϑ_l0)\n    soil_heat_model = PrescribedTemperatureModel()\n\n    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)\n    sources = ()\n    m = LandModel(\n        param_set,\n        m_soil;\n        boundary_conditions = bc,\n        source = sources,\n        init_state_prognostic = init_soil_water!,\n    )\n\n\n    N_poly = 5\n    xres = FT(0.2)\n    yres = FT(0.2)\n    zres = FT(0.01)\n    # Specify the domain boundaries.\n    zmax = FT(0)\n    zmin = FT(-1)\n    xmax = FT(1)\n    ymax = FT(1)\n\n    driver_config = ClimateMachine.MultiColumnLandModel(\n        \"LandModel\",\n        (N_poly, N_poly),\n        (xres, yres, zres),\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        m;\n        zmin = zmin,\n    )\n\n\n    t0 = FT(0)\n    timeend = FT(300)\n    dt = FT(0.5)\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n    n_outputs = 30\n    every_x_simulation_time = ceil(Int, timeend / n_outputs)\n\n    state_types = (Auxiliary(), GradientFlux())\n    dons_arr =\n        Dict[dict_of_nodal_states(solver_config, state_types; interp = false)]\n    time_data = FT[0] # store time data\n\n    # We specify a function which evaluates `every_x_simulation_time` and returns\n    # the state vector, appending the variables we are interested in into\n    # `all_data`.\n\n    callback = GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n        dons = dict_of_nodal_states(solver_config, state_types; interp = false)\n        push!(dons_arr, dons)\n        push!(time_data, gettime(solver_config.solver))\n        nothing\n    end\n\n    # # Run the integration\n    ClimateMachine.invoke!(solver_config; user_callbacks = (callback,))\n    computed_bottom_∇h =\n        [dons_arr[k][\"soil.water.K∇h[3]\"][1] for k in 2:n_outputs] ./ [dons_arr[k][\"soil.water.K\"][1] for k in 2:n_outputs]\n\n\n    t = time_data[2:n_outputs]\n    # we need a -1 out in front here because the flux BC is on -K∇h\n    prescribed_bottom_∇h = t -> FT(-1) * FT(-3.0 * sin(pi * 2.0 * t / 300.0))\n\n    MSE = mean((prescribed_bottom_∇h.(t) .- computed_bottom_∇h) .^ 2.0)\n    computed_y1_∇h = maximum(abs.(\n        [dons_arr[k][\"soil.water.K∇h[2]\"][1] for k in 2:n_outputs] ./ [dons_arr[k][\"soil.water.K\"][1] for k in 2:n_outputs],\n    ))\n    computed_x1_∇h = maximum(abs.(\n        [dons_arr[k][\"soil.water.K∇h[1]\"][1] for k in 2:n_outputs] ./ [dons_arr[k][\"soil.water.K\"][1] for k in 2:n_outputs],\n    ))\n    @test MSE < 1e-4\n    @test computed_x1_∇h < 1e-10\n    @test computed_y1_∇h < 1e-10\nend\n"
  },
  {
    "path": "test/Land/Model/test_heat_parameterizations.jl",
    "content": "using MPI\nusing OrderedCollections\nusing StaticArrays\nusing Test\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: ρ_cloud_liq, ρ_cloud_ice, cp_l, cp_i, T_0, LH_f0\nusing CLIMAParameters.Atmos.Microphysics: K_therm\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing ClimateMachine\nusing ClimateMachine.Land.SoilHeatParameterizations\nusing ClimateMachine.Land\n\n@testset \"Land heat parameterizations\" begin\n    FT = Float64\n    # Density of liquid water (kg/m``^3``)\n    _ρ_l = FT(ρ_cloud_liq(param_set))\n    # Density of ice water (kg/m``^3``)\n    _ρ_i = FT(ρ_cloud_ice(param_set))\n    # Volum. isobaric heat capacity liquid water (J/m3/K)\n    _ρcp_l = FT(cp_l(param_set) * _ρ_l)\n    # Volumetric isobaric heat capacity ice (J/m3/K)\n    _ρcp_i = FT(cp_i(param_set) * _ρ_i)\n    # Reference temperature (K)\n    _T_ref = FT(T_0(param_set))\n    # Latent heat of fusion at ``T_0`` (J/kg)\n    _LH_f0 = FT(LH_f0(param_set))\n    # Thermal conductivity of dry air\n    κ_air = FT(K_therm(param_set))\n\n    @test temperature_from_ρe_int(5.4e7, 0.05, 2.1415e6, param_set) ==\n          FT(_T_ref + (5.4e7 + 0.05 * _ρ_i * _LH_f0) / 2.1415e6)\n\n    @test volumetric_heat_capacity(0.25, 0.05, 1e6, param_set) ==\n          FT(1e6 + 0.25 * _ρcp_l + 0.05 * _ρcp_i)\n\n    @test volumetric_internal_energy(0.05, 2.1415e6, 300.0, param_set) ==\n          FT(2.1415e6 * (300.0 - _T_ref) - 0.05 * _ρ_i * _LH_f0)\n\n    @test saturated_thermal_conductivity(0.25, 0.05, 0.57, 2.29) ==\n          FT(0.57^(0.25 / (0.05 + 0.25)) * 2.29^(0.05 / (0.05 + 0.25)))\n\n    @test saturated_thermal_conductivity(0.0, 0.0, 0.57, 2.29) == FT(0.0)\n\n    @test relative_saturation(0.25, 0.05, 0.4) == FT((0.25 + 0.05) / 0.4)\n\n    # Test branching in kersten_number\n    soil_param_functions = SoilParamFunctions(\n        FT;\n        porosity = 0.2,\n        ν_ss_gravel = 0.1,\n        ν_ss_om = 0.1,\n        ν_ss_quartz = 0.1,\n        κ_solid = 0.1,\n        ρp = 1.0,\n    )\n    # ice fraction = 0\n    @test kersten_number(0.0, 0.75, soil_param_functions) == FT(\n        0.75^((FT(1) + 0.1 - 0.24 * 0.1 - 0.1) / FT(2)) *\n        (\n            (FT(1) + exp(-18.1 * 0.75))^(-FT(3)) -\n            ((FT(1) - 0.75) / FT(2))^FT(3)\n        )^(FT(1) - 0.1),\n    )\n\n    # ice fraction ~= 0\n    @test kersten_number(0.05, 0.75, soil_param_functions) ==\n          FT(0.75^(FT(1) + 0.1))\n\n    @test thermal_conductivity(1.5, 0.7287, 0.7187) ==\n          FT(0.7287 * 0.7187 + (FT(1) - 0.7287) * 1.5)\n\n    @test volumetric_internal_energy_liq(300.0, param_set) ==\n          FT(_ρcp_l * (300.0 - _T_ref))\n\n    @test k_solid(FT(0.5), FT(0.25), FT(2.0), FT(3.0), FT(2.0)) ==\n          FT(2)^FT(0.5) * FT(2)^FT(0.25) * FT(3.0)^FT(0.25)\n\n    @test ksat_frozen(FT(0.5), FT(0.1), FT(0.4)) ==\n          FT(0.5)^FT(0.9) * FT(0.4)^FT(0.1)\n\n    @test ksat_unfrozen(FT(0.5), FT(0.1), FT(0.4)) ==\n          FT(0.5)^FT(0.9) * FT(0.4)^FT(0.1)\n\n    @test k_dry(param_set, soil_param_functions) ==\n          ((FT(0.053) * FT(0.1) - κ_air) * FT(0.8) + κ_air * FT(1.0)) /\n          (FT(1.0) - (FT(1.0) - FT(0.053)) * FT(0.8))\nend\n"
  },
  {
    "path": "test/Land/Model/test_overland_flow_analytic.jl",
    "content": "using MPI\nusing OrderedCollections\nusing StaticArrays\nusing Test\nusing Statistics\nusing DelimitedFiles\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.SurfaceFlow\nusing ClimateMachine.Land.SoilWaterParameterizations\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods: BalanceLaw, LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux, vars_state\nusing ArtifactWrappers\n\n\n# Test that the land model with no surface flow works correctly\n@testset \"NoSurfaceFlow Model\" begin\n    function init_land_model!(land, state, aux, localgeo, time) end\n    ClimateMachine.init()\n    FT = Float64\n\n    soil_water_model = PrescribedWaterModel()\n    soil_heat_model = PrescribedTemperatureModel()\n    soil_param_functions = nothing\n\n    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)\n    m_surface = NoSurfaceFlowModel()\n\n    sources = ()\n    m = LandModel(\n        param_set,\n        m_soil;\n        surface = m_surface,\n        source = sources,\n        init_state_prognostic = init_land_model!,\n    )\n\n    N_poly = 5\n    nelem_vert = 10\n\n    # Specify the domain boundaries\n    zmax = FT(0)\n    zmin = FT(-1)\n\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"LandModel\",\n        N_poly,\n        nelem_vert,\n        zmax,\n        param_set,\n        m;\n        zmin = zmin,\n        numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n    )\n\n    t0 = FT(0)\n    timeend = FT(10)\n    dt = FT(1)\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n    mygrid = solver_config.dg.grid\n    Q = solver_config.Q\n    aux = solver_config.dg.state_auxiliary\n\n    ClimateMachine.invoke!(solver_config;)\n\n    t = ODESolvers.gettime(solver_config.solver)\n    state_vars = SingleStackUtils.get_vars_from_nodal_stack(\n        mygrid,\n        Q,\n        vars_state(m, Prognostic(), FT),\n    )\n    aux_vars = SingleStackUtils.get_vars_from_nodal_stack(\n        mygrid,\n        aux,\n        vars_state(m, Auxiliary(), FT),\n    )\n    #Make sure it runs, and that there are no state variables, and only \"x,y,z\" as aux.\n    @test t == timeend\n    @test size(Base.collect(keys(aux_vars)))[1] == 3\n    @test size(Base.collect(keys(state_vars)))[1] == 0\nend\n\n\n# Constant slope analytical test case defined as Model 1 / Eqn 6\n# DOI: 10.1061/(ASCE)0733-9429(2007)133:2(217)\n@testset \"Analytical Overland Model\" begin\n    function warp_constant_slope(\n        xin,\n        yin,\n        zin;\n        topo_max = 0.2,\n        zmin = -0.1,\n        xmax = 400,\n    )\n        FT = eltype(xin)\n        zmax = FT((FT(1.0) - xin / xmax) * topo_max)\n        alpha = FT(1.0) - zmax / zmin\n        zout = zmin + (zin - zmin) * alpha\n        x, y, z = xin, yin, zout\n        return x, y, z\n    end\n    ClimateMachine.init()\n    FT = Float64\n\n    soil_water_model = PrescribedWaterModel()\n    soil_heat_model = PrescribedTemperatureModel()\n    soil_param_functions = nothing\n\n    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)\n\n    m_surface = OverlandFlowModel(\n        (x, y) -> eltype(x)(-0.0016),\n        (x, y) -> eltype(x)(0.0);\n        mannings = (x, y) -> eltype(x)(0.025),\n    )\n\n\n    bc = LandDomainBC(\n        minx_bc = LandComponentBC(\n            surface = Dirichlet((aux, t) -> eltype(aux)(0)),\n        ),\n    )\n\n    function init_land_model!(land, state, aux, localgeo, time)\n        state.surface.height = eltype(state)(0)\n    end\n\n    # units in m / s \n    precip(x, y, t) = t < (30 * 60) ? 1.4e-5 : 0.0\n\n    sources = (Precip{FT}(precip),)\n\n    m = LandModel(\n        param_set,\n        m_soil;\n        surface = m_surface,\n        boundary_conditions = bc,\n        source = sources,\n        init_state_prognostic = init_land_model!,\n    )\n\n    N_poly = 1\n    xres = FT(2.286)\n    yres = FT(0.25)\n    zres = FT(0.1)\n    # Specify the domain boundaries.\n    zmax = FT(0)\n    zmin = FT(-0.1)\n    xmax = FT(182.88)\n    ymax = FT(1.0)\n    topo_max = FT(0.0016 * xmax)\n\n    driver_config = ClimateMachine.MultiColumnLandModel(\n        \"LandModel\",\n        (N_poly, N_poly),\n        (xres, yres, zres),\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        m;\n        zmin = zmin,\n        meshwarp = (x...) -> warp_constant_slope(\n            x...;\n            topo_max = topo_max,\n            zmin = zmin,\n            xmax = xmax,\n        ),\n    )\n\n    t0 = FT(0)\n    timeend = FT(60 * 60)\n    dt = FT(10)\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n    mygrid = solver_config.dg.grid\n    Q = solver_config.Q\n\n    h_index = varsindex(vars_state(m, Prognostic(), FT), :surface, :height)\n    n_outputs = 60\n\n    every_x_simulation_time = ceil(Int, timeend / n_outputs)\n\n    dons = Dict([k => Dict() for k in 1:n_outputs]...)\n\n    iostep = [1]\n    callback = GenericCallbacks.EveryXSimulationTime(\n        every_x_simulation_time,\n    ) do (init = false)\n        t = ODESolvers.gettime(solver_config.solver)\n        h = Q[:, h_index, :]\n        all_vars = Dict{String, Array}(\"t\" => [t], \"h\" => h)\n        dons[iostep[1]] = all_vars\n        iostep[1] += 1\n        return\n    end\n\n    ClimateMachine.invoke!(solver_config; user_callbacks = (callback,))\n\n    # Compare flowrate analytical derivation\n    aux = solver_config.dg.state_auxiliary\n\n    # get all nodal points at the max X bound of the domain\n    mask = Array(aux[:, 1, :] .== 182.88)\n    n_outputs = length(dons)\n    # get prognostic variable height from nodal state (m^2)\n    height = [mean(Array(dons[k][\"h\"])[mask[:]]) for k in 1:n_outputs]\n    # get similation timesteps (s)\n    time_data = [dons[l][\"t\"][1] for l in 1:n_outputs]\n\n\n    alpha = sqrt(0.0016) / 0.025\n    i = 1.4e-5\n    L = xmax\n    m = 5 / 3\n    t_c = (L * i^(1 - m) / alpha)^(1 / m)\n    t_r = 30 * 60\n\n    q = height .^ (m) .* alpha\n\n    function g(m, y, i, t_r, L, alpha, t)\n        output = L / alpha - y^(m) / i - y^(m - 1) * m * (t - t_r)\n        return output\n    end\n\n    function dg(m, y, i, t_r, L, alpha, t)\n        output = -y^(m - 1) * m / i - y^(m - 2) * m * (m - 1) * (t - t_r)\n        return output\n    end\n\n    function analytic(t, alpha, t_c, t_r, i, L, m)\n        if t < t_c\n            return alpha * (i * t)^(m)\n        end\n\n        if t <= t_r && t > t_c\n            return alpha * (i * t_c)^(m)\n        end\n\n        if t > t_r\n            yL = (i * (t - t_r))\n            delta = 1\n            error = g(m, yL, i, t_r, L, alpha, t)\n            while abs(error) > 1e-4\n                delta =\n                    -g(m, yL, i, t_r, L, alpha, t) /\n                    dg(m, yL, i, t_r, L, alpha, t)\n                yL = yL + delta\n                error = g(m, yL, i, t_r, L, alpha, t)\n            end\n            return alpha * yL^m\n\n        end\n\n    end\n\n    q = Array(q) # copy to host if GPU array\n    @test sqrt_rmse_over_max_q =\n        sqrt(mean(\n            (analytic.(time_data, alpha, t_c, t_r, i, L, m) .- q) .^ 2.0,\n        )) / maximum(q) < 3e-3\nend\n"
  },
  {
    "path": "test/Land/Model/test_overland_flow_vcatchment.jl",
    "content": "using Test\nusing Statistics\nusing DelimitedFiles\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.SurfaceFlow\nusing ClimateMachine.Land.SoilWaterParameterizations\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods: BalanceLaw, LocalGeometry\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux, vars_state\nusing ArtifactWrappers\n\n\n@testset \"V Catchment Maxwell River Model\" begin\n    tv_dataset = ArtifactWrapper(\n        @__DIR__,\n        isempty(get(ENV, \"CI\", \"\")),\n        \"tiltedv\",\n        ArtifactFile[ArtifactFile(\n            url = \"https://caltech.box.com/shared/static/qi2gftjw2vu2j66b0tyfef427xxj3ug7.csv\",\n            filename = \"TiltedVOutput.csv\",\n        ),],\n    )\n    tv_dataset_path = get_data_folder(tv_dataset)\n\n    ClimateMachine.init()\n    FT = Float64\n\n    soil_water_model = PrescribedWaterModel()\n    soil_heat_model = PrescribedTemperatureModel()\n    soil_param_functions = nothing\n\n    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)\n\n    function x_slope(x, y)\n        MFT = eltype(x)\n        if x < MFT(800)\n            MFT(-0.05)\n        elseif x <= MFT(820)\n            MFT(0)\n        else\n            MFT(0.05)\n        end\n    end\n\n    function y_slope(x, y)\n        MFT = eltype(x)\n        MFT(-0.02)\n    end\n\n    function channel_mannings(x, y)\n        MFT = eltype(x)\n        return x >= MFT(800) && x <= MFT(820) ? MFT(2.5 * 60 * 10^-3) :\n               MFT(2.5 * 60 * 10^-4)\n    end\n\n    m_surface = OverlandFlowModel(x_slope, y_slope; mannings = channel_mannings)\n\n    bc = LandDomainBC(\n        miny_bc = LandComponentBC(\n            surface = Dirichlet((aux, t) -> eltype(aux)(0)),\n        ),\n        minx_bc = LandComponentBC(\n            surface = Dirichlet((aux, t) -> eltype(aux)(0)),\n        ),\n        maxx_bc = LandComponentBC(\n            surface = Dirichlet((aux, t) -> eltype(aux)(0)),\n        ),\n    )\n\n    function init_land_model!(land, state, aux, localgeo, time)\n        state.surface.height = eltype(state)(0)\n    end\n\n    # units in m / s \n    precip(x, y, t) = t < eltype(t)(90 * 60) ? eltype(t)(3e-6) : eltype(t)(0.0)\n\n    sources = (Precip{FT}(precip),)\n\n    m = LandModel(\n        param_set,\n        m_soil;\n        surface = m_surface,\n        boundary_conditions = bc,\n        source = sources,\n        init_state_prognostic = init_land_model!,\n    )\n\n    N_poly_hori = 1\n    N_poly_vert = 1\n    xres = FT(20)\n    yres = FT(20)\n    zres = FT(1)\n    # Specify the domain boundaries.\n    zmax = FT(1)\n    zmin = FT(0)\n    xmax = FT(1620)\n    ymax = FT(1000)\n\n\n    driver_config = ClimateMachine.MultiColumnLandModel(\n        \"LandModel\",\n        (N_poly_hori, N_poly_vert),\n        (xres, yres, zres),\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        m;\n        zmin = zmin,\n        numerical_flux_first_order = RusanovNumericalFlux(),\n    )\n\n    t0 = FT(0)\n    timeend = FT(180 * 60)\n    dt = FT(0.5)\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n    mygrid = solver_config.dg.grid\n    Q = solver_config.Q\n\n    h_index = varsindex(vars_state(m, Prognostic(), FT), :surface, :height)\n    n_outputs = 60\n\n    every_x_simulation_time = ceil(Int, timeend / n_outputs)\n\n    dons = Dict([k => Dict() for k in 1:n_outputs]...)\n\n    iostep = [1]\n    callback = GenericCallbacks.EveryXSimulationTime(\n        every_x_simulation_time,\n    ) do (init = false)\n        t = ODESolvers.gettime(solver_config.solver)\n        h = Q[:, h_index, :]\n        all_vars = Dict{String, Array}(\"t\" => [t], \"h\" => h)\n        dons[iostep[1]] = all_vars\n        iostep[1] += 1\n        return\n    end\n\n    ClimateMachine.invoke!(solver_config; user_callbacks = (callback,))\n\n    aux = solver_config.dg.state_auxiliary\n    x = Array(aux[:, 1, :])\n    y = Array(aux[:, 2, :])\n    z = Array(aux[:, 3, :])\n    # Get points at outlet (y = ymax)\n    mask2 = (Float64.(y .== 1000.0)) .== 1\n    n_outputs = length(dons)\n    function compute_Q(h, xv)\n        height = max.(h, 0.0)\n        v = calculate_velocity(m_surface, xv, 1000.0, height)# put in y = 1000.0\n        speed = sqrt(v[1]^2.0 + v[2]^2.0 + v[3]^2.0)\n        Q_outlet = speed .* height .* 60.0 # multiply by 60 so it is per minute, not per second\n        return Q_outlet\n    end\n    # We divide by 4 because we have 4 nodal points with the same value at each x (z = 0, 1)\n    # Multiply by xres because the solution at each point roughly represents that the solution for that range in x\n    Q =\n        [\n            sum(compute_Q.(Array(dons[k][\"h\"])[:][mask2[:]], x[mask2[:]]))\n            for k in 1:n_outputs\n        ] ./ 4.0 .* xres\n    data = joinpath(tv_dataset_path, \"TiltedVOutput.csv\")\n    ds_tv = readdlm(data, ',')\n    error = sqrt(mean(Q .- ds_tv))\n    @test error < 5e-7\nend\n"
  },
  {
    "path": "test/Land/Model/test_physical_bc.jl",
    "content": "# Test that the way we specify boundary conditions works as expected\nusing MPI\nusing OrderedCollections\nusing StaticArrays\nusing Statistics\nusing Test\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.SoilWaterParameterizations\nusing ClimateMachine.Land.Runoff\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods: BalanceLaw, LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux, vars_state\n\nimport ClimateMachine.DGMethods.FVReconstructions: FVLinear\n\n@testset \"NoRunoff\" begin\n    ClimateMachine.init()\n\n    FT = Float64\n\n    function init_soil_water!(land, state, aux, localgeo, time)\n        myfloat = eltype(state)\n        state.soil.water.ϑ_l = myfloat(land.soil.water.initialϑ_l(aux))\n        state.soil.water.θ_i = myfloat(land.soil.water.initialθ_i(aux))\n    end\n\n    wpf = WaterParamFunctions(FT; Ksat = 1e-7, S_s = 1e-3)\n\n    soil_param_functions = SoilParamFunctions(FT; porosity = 0.75, water = wpf)\n    surface_precip_amplitude = FT(3e-8)\n    f = FT(pi * 2.0 / 300.0)\n    precip = (t) -> surface_precip_amplitude * sin(f * t)\n    ϑ_l0 = (aux) -> eltype(aux)(0.2)\n    bc = LandDomainBC(\n        bottom_bc = LandComponentBC(\n            soil_water = Neumann((aux, t) -> eltype(aux)(0.0)),\n        ),\n        surface_bc = LandComponentBC(\n            soil_water = SurfaceDrivenWaterBoundaryConditions(\n                FT;\n                precip_model = DrivenConstantPrecip{FT}(precip),\n                runoff_model = NoRunoff(),\n            ),\n        ),\n    )\n\n    soil_water_model = SoilWaterModel(FT; initialϑ_l = ϑ_l0)\n    soil_heat_model = PrescribedTemperatureModel()\n\n    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)\n    sources = ()\n    m = LandModel(\n        param_set,\n        m_soil;\n        boundary_conditions = bc,\n        source = sources,\n        init_state_prognostic = init_soil_water!,\n    )\n\n\n    N_poly = 5\n    nelem_vert = 50\n\n\n    # Specify the domain boundaries\n    zmax = FT(0)\n    zmin = FT(-1)\n\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"LandModel\",\n        N_poly,\n        nelem_vert,\n        zmax,\n        param_set,\n        m;\n        zmin = zmin,\n        numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n    )\n\n\n    t0 = FT(0)\n    timeend = FT(150)\n    dt = FT(0.05)\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n    n_outputs = 60\n    mygrid = solver_config.dg.grid\n    every_x_simulation_time = ceil(Int, timeend / n_outputs)\n    state_types = (Prognostic(), Auxiliary(), GradientFlux())\n    dons_arr =\n        Dict[dict_of_nodal_states(solver_config, state_types; interp = true)]\n    time_data = FT[0] # store time data\n\n    # We specify a function which evaluates `every_x_simulation_time` and returns\n    # the state vector, appending the variables we are interested in into\n    # `dons_arr`.\n\n    callback = GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n        dons = dict_of_nodal_states(solver_config, state_types; interp = true)\n        push!(dons_arr, dons)\n        push!(time_data, gettime(solver_config.solver))\n        nothing\n    end\n\n    # # Run the integration\n    ClimateMachine.invoke!(solver_config; user_callbacks = (callback,))\n\n    dons = dict_of_nodal_states(solver_config, state_types; interp = true)\n    push!(dons_arr, dons)\n    push!(time_data, gettime(solver_config.solver))\n\n    # Get z-coordinate\n    z = get_z(solver_config.dg.grid; rm_dupes = true)\n    N = length(dons_arr)\n    # Note - we take the indices 2:N here to avoid the t = 0 spot. Gradients\n    # are not calculated before the invoke! command, so we cannot compare at t=0.\n    computed_surface_flux = [dons_arr[k][\"soil.water.K∇h[3]\"][end] for k in 2:N]\n    t = time_data[2:N]\n    prescribed_surface_flux = t -> FT(-1) * FT(3e-8 * sin(pi * 2.0 * t / 300.0))\n    MSE = mean((prescribed_surface_flux.(t) .- computed_surface_flux) .^ 2.0)\n    @test MSE < 5e-7\nend\n\n\n\n@testset \"Explicit Dirichlet BC Comparison\" begin\n    # This tests the \"if\" branch of compute_surface_grad_bc\n    ClimateMachine.init()\n    FT = Float64\n    function init_soil_water!(land, state, aux, localgeo, time)\n        myfloat = eltype(state)\n        state.soil.water.ϑ_l = myfloat(land.soil.water.initialϑ_l(aux))\n        state.soil.water.θ_i = myfloat(land.soil.water.initialθ_i(aux))\n    end\n\n    Ksat = FT(0.0443 / (3600 * 100))\n    S_s = FT(1e-4)\n    wpf = WaterParamFunctions(FT; Ksat = Ksat, S_s = S_s)\n\n    soil_param_functions = SoilParamFunctions(FT; porosity = 0.495, water = wpf)\n    surface_precip_amplitude = FT(-5e-4)\n\n    precip = (t) -> surface_precip_amplitude\n    hydraulics = vanGenuchten(FT; n = 2.0)\n    function hydrostatic_profile(z, zm, porosity, n, α)\n        myf = eltype(z)\n        m = FT(1 - 1 / n)\n        S = FT((FT(1) + (α * (z - zm))^n)^(-m))\n        return FT(S * porosity)\n    end\n    N_poly = 2\n    nelem_vert = 50\n\n    # Specify the domain boundaries\n    zmax = FT(0)\n    zmin = FT(-0.35)\n    Δz = abs(FT((zmin - zmax) / nelem_vert / 2))\n\n    ϑ_l0 =\n        (aux) -> eltype(aux)(hydrostatic_profile(\n            aux.z,\n            -0.35,\n            0.495,\n            hydraulics.n,\n            hydraulics.α,\n        ))\n\n    soil_water_model = SoilWaterModel(\n        FT;\n        moisture_factor = MoistureDependent{FT}(),\n        hydraulics = hydraulics,\n        initialϑ_l = ϑ_l0,\n    )\n    soil_heat_model = PrescribedTemperatureModel()\n\n    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)\n    sources = ()\n\n\n    bc = LandDomainBC(\n        bottom_bc = LandComponentBC(\n            soil_water = Neumann((aux, t) -> eltype(aux)(0.0)),\n        ),\n        surface_bc = LandComponentBC(\n            soil_water = SurfaceDrivenWaterBoundaryConditions(\n                FT;\n                precip_model = DrivenConstantPrecip{FT}(precip),\n                runoff_model = CoarseGridRunoff{FT}(Δz),\n            ),\n        ),\n    )\n\n\n    m = LandModel(\n        param_set,\n        m_soil;\n        boundary_conditions = bc,\n        source = sources,\n        init_state_prognostic = init_soil_water!,\n    )\n\n\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"LandModel\",\n        N_poly,\n        nelem_vert,\n        zmax,\n        param_set,\n        m;\n        zmin = zmin,\n        numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n        #fv_reconstruction = FVLinear(),\n    )\n\n\n    t0 = FT(0)\n    timeend = FT(60 * 60)\n    dt = FT(0.1)\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n    state_types = (Prognostic(), Auxiliary(), GradientFlux())\n\n    ClimateMachine.invoke!(solver_config;)# user_callbacks = (callback,))\n    srf_dons = dict_of_nodal_states(solver_config, state_types; interp = true)\n\n\n    ###### Repeat with explicit dirichlet BC\n    soil_water_model2 = SoilWaterModel(\n        FT;\n        moisture_factor = MoistureDependent{FT}(),\n        hydraulics = hydraulics,\n        initialϑ_l = ϑ_l0,\n    )\n    soil_heat_model2 = PrescribedTemperatureModel()\n\n    m_soil2 =\n        SoilModel(soil_param_functions, soil_water_model2, soil_heat_model2)\n    bc2 = LandDomainBC(\n        bottom_bc = LandComponentBC(\n            soil_water = Neumann((aux, t) -> eltype(aux)(0.0)),\n        ),\n        surface_bc = LandComponentBC(\n            soil_water = Dirichlet((aux, t) -> eltype(aux)(0.495)),\n        ),\n    )\n\n\n    m2 = LandModel(\n        param_set,\n        m_soil2;\n        boundary_conditions = bc2,\n        source = sources,\n        init_state_prognostic = init_soil_water!,\n    )\n\n\n    driver_config2 = ClimateMachine.SingleStackConfiguration(\n        \"LandModel\",\n        N_poly,\n        nelem_vert,\n        zmax,\n        param_set,\n        m2;\n        zmin = zmin,\n        numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n        #  fv_reconstruction = FVLinear(),\n    )\n\n    solver_config2 = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config2,\n        ode_dt = dt,\n    )\n\n    ClimateMachine.invoke!(solver_config2;)\n    dir_dons = dict_of_nodal_states(solver_config2, state_types; interp = true)\n    # Here we take the solution near the surface, where changes between the two\n    # would occur.\n    @test mean(\n        (\n            srf_dons[\"soil.water.ϑ_l\"][(end - 15):end] .-\n            dir_dons[\"soil.water.ϑ_l\"][(end - 15):end]\n        ) .^ 2.0,\n    ) < 5e-5\nend\n\n@testset \"Explicit Flux BC Comparison\" begin\n    # This tests the else branch of compute_surface_grad_bc\n    ClimateMachine.init()\n    FT = Float64\n    function init_soil_water!(land, state, aux, localgeo, time)\n        myfloat = eltype(state)\n        state.soil.water.ϑ_l = myfloat(land.soil.water.initialϑ_l(aux))\n        state.soil.water.θ_i = myfloat(land.soil.water.initialθ_i(aux))\n    end\n\n    Ksat = FT(0.0443 / (3600 * 100))\n    S_s = FT(1e-4)\n    wpf = WaterParamFunctions(FT; Ksat = Ksat, S_s = S_s)\n\n    soil_param_functions = SoilParamFunctions(FT; porosity = 0.495, water = wpf)\n    surface_precip_amplitude = FT(0)\n\n    precip = (t) -> surface_precip_amplitude\n    hydraulics = vanGenuchten(FT; n = 2.0)\n    function hydrostatic_profile(z, zm, porosity, n, α)\n        myf = eltype(z)\n        m = FT(1 - 1 / n)\n        S = FT((FT(1) + (α * (z - zm))^n)^(-m))\n        return FT(S * porosity)\n    end\n\n    ϑ_l0 =\n        (aux) -> eltype(aux)(hydrostatic_profile(\n            aux.z,\n            -1,\n            0.495,\n            hydraulics.n,\n            hydraulics.α,\n        ))\n\n    soil_water_model = SoilWaterModel(\n        FT;\n        moisture_factor = MoistureDependent{FT}(),\n        hydraulics = hydraulics,\n        initialϑ_l = ϑ_l0,\n    )\n    soil_heat_model = PrescribedTemperatureModel()\n\n    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)\n    sources = ()\n    N_poly = (1, 0)\n    nelem_vert = 200\n\n    # Specify the domain boundaries\n    zmax = FT(0)\n    zmin = FT(-1)\n    Δz = FT(1 / 200 / 2)\n    bc = LandDomainBC(\n        bottom_bc = LandComponentBC(\n            soil_water = Neumann((aux, t) -> eltype(aux)(0.0)),\n        ),\n        surface_bc = LandComponentBC(\n            soil_water = SurfaceDrivenWaterBoundaryConditions(\n                FT;\n                precip_model = DrivenConstantPrecip{FT}(precip),\n                runoff_model = CoarseGridRunoff{FT}(Δz),\n            ),\n        ),\n    )\n\n\n    m = LandModel(\n        param_set,\n        m_soil;\n        boundary_conditions = bc,\n        source = sources,\n        init_state_prognostic = init_soil_water!,\n    )\n\n\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"LandModel\",\n        N_poly,\n        nelem_vert,\n        zmax,\n        param_set,\n        m;\n        zmin = zmin,\n        numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n        fv_reconstruction = FVLinear(),\n    )\n\n\n    t0 = FT(0)\n    timeend = FT(100000)\n    dt = FT(4)\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n    state_types = (Prognostic(), Auxiliary(), GradientFlux())\n\n    ClimateMachine.invoke!(solver_config;)\n\n    srf_dons = dict_of_nodal_states(solver_config, state_types; interp = true)\n    # Note - we only look at differences between the solutions at the surface, because\n    # near the bottom they will agree.\n    z = srf_dons[\"z\"][150:end]\n    error1 = sqrt(mean(\n        (\n            srf_dons[\"soil.water.ϑ_l\"][150:end] .-\n            hydrostatic_profile.(z, -1, 0.495, hydraulics.n, hydraulics.α)\n        ) .^ 2.0,\n    ))\n    error2 = sqrt(mean(srf_dons[\"soil.water.K∇h[3]\"][150:end] .^ 2.0))\n    @test error1 < 1e-5\n    @test error2 < eps(FT)\nend\n"
  },
  {
    "path": "test/Land/Model/test_radiative_energy_flux_functions.jl",
    "content": "# Test functions used in runoff modeling.\nusing MPI\nusing OrderedCollections\nusing StaticArrays\nusing Statistics\nusing Test\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.RadiativeEnergyFlux\nusing ClimateMachine.Land.SoilWaterParameterizations\nusing ClimateMachine.Land.SoilHeatParameterizations\nusing ClimateMachine.Land.Runoff\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods: BalanceLaw, LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux, vars_state\n\n@testset \"Radiative energy flux testing\" begin\n    F = Float32\n    user_nswf = t -> F(2 * t)\n    user_swf = t -> F(2 * t)\n    user_α = t -> F(0.2 * t)\n    prescribed_swf_and_a =\n        PrescribedSwFluxAndAlbedo(F; α = user_α, swf = user_swf)\n    prescribed_nswf = PrescribedNetSwFlux(F; nswf = user_nswf)\n    flux_from_prescribed_swf_and_albedo =\n        compute_net_radiative_energy_flux.(\n            Ref(prescribed_swf_and_a),\n            [1, 2, 3, 4],\n        )\n\n    flux_from_prescribed_nswf =\n        compute_net_radiative_energy_flux.(Ref(prescribed_nswf), [1, 2, 3, 4])\n\n    @test flux_from_prescribed_swf_and_albedo ≈\n          F.(([0.8, 0.6, 0.4, 0.2]) .* ([2, 4, 6, 8]))\n    @test eltype(flux_from_prescribed_swf_and_albedo) == F\n\n    @test flux_from_prescribed_nswf ≈ F.([2, 4, 6, 8])\n    @test eltype(flux_from_prescribed_nswf) == F\nend\n\n@testset \"Heat analytic unit test\" begin\n    ClimateMachine.init()\n\n    FT = Float64\n\n    function init_soil!(land, state, aux, localgeo, time)\n        myfloat = eltype(state)\n        ϑ_l, θ_i = get_water_content(land.soil.water, aux, state, time)\n        θ_l =\n            volumetric_liquid_fraction(ϑ_l, land.soil.param_functions.porosity)\n        ρc_s = volumetric_heat_capacity(\n            θ_l,\n            θ_i,\n            land.soil.param_functions.ρc_ds,\n            land.param_set,\n        )\n\n        state.soil.heat.ρe_int = myfloat(volumetric_internal_energy(\n            θ_i,\n            ρc_s,\n            land.soil.heat.initialT(aux),\n            land.param_set,\n        ))\n    end\n\n\n    soil_param_functions = SoilParamFunctions(\n        FT;\n        porosity = 0.4,\n        ν_ss_gravel = 0.2,\n        ν_ss_om = 0.2,\n        ν_ss_quartz = 0.2,\n        ρc_ds = 1.0, # diffusivity = k/ρc. When no water, ρc = ρc_ds.\n        κ_solid = 1.0,\n        ρp = 1.0,\n        κ_sat_unfrozen = 0.57,\n        κ_sat_frozen = 2.29,\n    )\n\n    #  Prescribed net short wave flux, initial temperature\n    A = FT(5)\n    prescribed_nswf = t -> FT(-A * t)\n    T_init = FT(300.0)\n    T_init_func = aux -> T_init\n\n    # Flux entering = flux leaving soil column\n    bc = LandDomainBC(\n        bottom_bc = LandComponentBC(\n            soil_heat = Neumann((aux, t) -> FT(-A * t)),\n        ),\n        surface_bc = LandComponentBC(\n            soil_heat = SurfaceDrivenHeatBoundaryConditions(\n                FT;\n                nswf_model = PrescribedNetSwFlux(FT; nswf = prescribed_nswf),\n            ),\n        ),\n    )\n\n    soil_water_model = PrescribedWaterModel()\n    soil_heat_model = SoilHeatModel(FT; initialT = T_init_func)\n\n    m_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model)\n    sources = ()\n    m = LandModel(\n        param_set,\n        m_soil;\n        boundary_conditions = bc,\n        source = sources,\n        init_state_prognostic = init_soil!,\n    )\n\n    N_poly = 5\n    nelem_vert = 10\n\n    # Specify the domain boundaries\n    zmax = FT(1)\n    zmin = FT(0)\n\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"LandModel\",\n        N_poly,\n        nelem_vert,\n        zmax,\n        param_set,\n        m;\n        zmin = zmin,\n        numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n    )\n\n    t0 = FT(0)\n    timeend = FT(1)\n    dt = FT(1e-4)\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n    mygrid = solver_config.dg.grid\n    aux = solver_config.dg.state_auxiliary\n    ClimateMachine.invoke!(solver_config)\n    t = ODESolvers.gettime(solver_config.solver)\n\n    z_ind = varsindex(vars_state(m, Auxiliary(), FT), :z)\n    z = Array(aux[:, z_ind, :][:])\n\n    T_ind = varsindex(vars_state(m, Auxiliary(), FT), :soil, :heat, :T)\n    T = Array(aux[:, T_ind, :][:])\n\n    k = k_dry(param_set, soil_param_functions)\n    diffusivity = k / soil_param_functions.ρc_ds\n\n    A = A / k # Because the flux is applied on κ∇T. κ∇T = A*t in clima, whereas ∇T = A*t in the analytic soln.\n    approx_sum_term = sum(\n        (\n            -2 * A / (diffusivity * pi^4) *\n            cos.(n * pi * z) *\n            (pi * n * sin(pi * n) + cos(pi * n) - 1) *\n            (1 - exp(-timeend * diffusivity * (pi * n)^2)) / n^4\n        ) for n in 1:100\n    )\n\n    approx_analytic_soln =\n        A * timeend .* z .+ T_init .- A * timeend / 2 .+ approx_sum_term\n\n    MSE = mean((approx_analytic_soln .- T) .^ 2.0)\n\n    @test eltype(aux) == FT\n    @test MSE < 1e-5\nend\n"
  },
  {
    "path": "test/Land/Model/test_water_parameterizations.jl",
    "content": "# Test the branching of the SoilWaterParameterizations functions,\n# test the instantiation of them works as expected, and that they\n# return what we expect.\nusing MPI\nusing OrderedCollections\nusing StaticArrays\nusing Test\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.SoilWaterParameterizations\n\n@testset \"Land water parameterizations\" begin\n    FT = Float64\n    test_array = [0.5, 1.0]\n    vg_model = vanGenuchten(FT;)\n    mm = MoistureDependent{FT}()\n    bc_model = BrooksCorey(FT;)\n    hk_model = Haverkamp(FT;)\n\n    #Use an array to confirm that extra arguments are unused.\n    @test viscosity_factor.(Ref(ConstantViscosity{FT}()), test_array) ≈\n          [1.0, 1.0]\n    @test impedance_factor.(Ref(NoImpedance{FT}()), test_array) ≈ [1.0, 1.0]\n    @test moisture_factor.(\n        Ref(MoistureIndependent{FT}()),\n        Ref(vg_model),\n        test_array,\n    ) ≈ [1.0, 1.0]\n\n    viscosity_model = TemperatureDependentViscosity{FT}(; T_ref = FT(1.0))\n    @test viscosity_factor(viscosity_model, FT(1.0)) == 1\n    impedance_model = IceImpedance{FT}(; Ω = 2.0)\n    @test impedance_factor(impedance_model, 0.5) == FT(0.1)\n\n\n    imp_f = impedance_factor(impedance_model, 0.5)\n    vis_f = viscosity_factor(viscosity_model, 1.0)\n    m_f = moisture_factor(MoistureDependent{FT}(), vg_model, 1.0)\n    Ksat = 1.0\n    @test hydraulic_conductivity(Ksat, imp_f, vis_f, m_f) == FT(0.1)\n\n    @test_throws Exception effective_saturation(0.5, -1.0, 0.0)\n    @test effective_saturation(0.5, 0.25, 0.05) - 4.0 / 9.0 < eps(FT)\n\n    n = vg_model.n\n    m = 1.0 - 1.0 / n\n    α = vg_model.α\n    S_s = 0.001\n    θ_r = 0.0\n    ν = 1.0\n    @test moisture_factor.(Ref(mm), Ref(vg_model), test_array) ==\n          sqrt.(test_array) .*\n          (FT(1) .- (FT(1) .- test_array .^ (FT(1) / m)) .^ m) .^ FT(2)\n    @test moisture_factor.(Ref(mm), Ref(bc_model), test_array) ==\n          test_array .^ (FT(2) * bc_model.m + FT(3))\n    ψ =\n        -(\n            (test_array .^ (-FT(1) / hk_model.m) .- FT(1)) .*\n            hk_model.α^(-hk_model.n)\n        ) .^ (FT(1) / hk_model.n)\n    @test moisture_factor.(Ref(mm), Ref(hk_model), test_array) ==\n          hk_model.A ./ (hk_model.A .+ abs.(ψ) .^ hk_model.k)\n\n    @test pressure_head.(\n        Ref(vg_model),\n        Ref(ν),\n        Ref(S_s),\n        Ref(θ_r),\n        test_array,\n        Ref(0.0),\n    ) ≈ .-((-1 .+ test_array .^ (-1 / m)) .* α^(-n)) .^ (1 / n)\n    #test branching in pressure head\n    @test pressure_head(vg_model, ν, S_s, θ_r, 1.5, 0.0) == 500\n    @test pressure_head.(\n        Ref(hk_model),\n        Ref(ν),\n        Ref(S_s),\n        Ref(θ_r),\n        test_array,\n        Ref(0.0),\n    ) ≈ .-((-1 .+ test_array .^ (-1 / m)) .* α^(-n)) .^ (1 / n)\n    m = FT(0.5)\n    ψb = FT(0.1656)\n    @test pressure_head(bc_model, ν, S_s, θ_r, 0.5, 0.0) ≈ -ψb * 0.5^(-m)\n    @test volumetric_liquid_fraction.([0.5, 1.5], Ref(0.75)) ≈ [0.5, 0.75]\n\n    @test inverse_matric_potential(\n        bc_model,\n        matric_potential(bc_model, FT(0.5)),\n    ) - FT(0.5) < eps(FT)\n    @test inverse_matric_potential(\n        vg_model,\n        matric_potential(vg_model, FT(0.5)),\n    ) == FT(0.5)\n    @test inverse_matric_potential(\n        hk_model,\n        matric_potential(hk_model, FT(0.5)),\n    ) == FT(0.5)\n\n    @test_throws Exception inverse_matric_potential(bc_model, 1)\n    @test_throws Exception inverse_matric_potential(hk_model, 1)\n    @test_throws Exception inverse_matric_potential(vg_model, 1)\n\n\n    # Test that spatial dependence works properly\n    vg_spatial = vanGenuchten(FT; n = (x) -> 2 * x, α = (x) -> x^2)\n    @test supertype(typeof(vg_spatial.n)) == Function\n    vg_point = vg_spatial(FT(2.0))\n    @test typeof(vg_point.n) == FT\n    @test vg_point.n == 4.0\n    @test vg_point.α == 4.0\n    @test vg_point.m == 0.75\nend\n"
  },
  {
    "path": "test/Land/runtests.jl",
    "content": "using Test, Pkg\n\n@testset \"Land\" begin\n    all_tests = isempty(ARGS) || \"all\" in ARGS ? true : false\n    for submodule in [\"Model\"]\n        if all_tests ||\n           \"$submodule\" in ARGS ||\n           \"Land/$submodule\" in ARGS ||\n           \"Land\" in ARGS\n            include_test(submodule)\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/Euler/acousticwave_1d_imex.jl",
    "content": "using ClimateMachine\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Mesh.Topologies:\n    StackedCubedSphereTopology, equiangular_cubed_sphere_warp, grid1d\nusing ClimateMachine.Mesh.Grids:\n    DiscontinuousSpectralElementGrid, VerticalDirection\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.DGMethods: DGModel, init_ode_state, remainder_DGModel\nusing ClimateMachine.DGMethods.NumericalFluxes:\n    RusanovNumericalFlux,\n    CentralNumericalFluxGradient,\n    CentralNumericalFluxSecondOrder\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.VTK: writevtk, writepvtu\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing Thermodynamics:\n    air_density, soundspeed_air, internal_energy, PhaseDry_pT, PhasePartition\nusing Thermodynamics.TemperatureProfiles: IsothermalProfile\nusing ClimateMachine.Atmos:\n    AtmosPhysics,\n    AtmosModel,\n    DryModel,\n    NoPrecipitation,\n    NoRadiation,\n    NTracers,\n    vars_state,\n    Gravity,\n    HydrostaticState,\n    AtmosAcousticGravityLinearModel,\n    parameter_set\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.Orientations:\n    SphericalOrientation, gravitational_potential, altitude, latitude, longitude\nusing ClimateMachine.VariableTemplates: flattenednames\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: planet_radius\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing MPI, Logging, StaticArrays, LinearAlgebra, Printf, Dates, Test\n\nconst output_vtk = false\n\nconst ntracers = 1\n\nfunction main()\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 5\n    numelem_horz = 10\n    numelem_vert = 5\n\n    timeend = 60 * 60\n    # timeend = 33 * 60 * 60 # Full simulation\n    outputtime = 60 * 60\n\n    expected_result = Dict()\n    expected_result[Float32] = 9.5066030866432000e+13\n    expected_result[Float64] = 9.5073452847149594e+13\n\n    for FT in (Float64, Float32)\n        for split_explicit_implicit in (false, true)\n            result = test_run(\n                mpicomm,\n                polynomialorder,\n                numelem_horz,\n                numelem_vert,\n                timeend,\n                outputtime,\n                ArrayType,\n                FT,\n                split_explicit_implicit,\n            )\n            @test result ≈ expected_result[FT]\n        end\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    polynomialorder,\n    numelem_horz,\n    numelem_vert,\n    timeend,\n    outputtime,\n    ArrayType,\n    FT,\n    split_explicit_implicit,\n)\n\n    setup = AcousticWaveSetup{FT}()\n\n    _planet_radius::FT = planet_radius(param_set)\n    vert_range = grid1d(\n        _planet_radius,\n        FT(_planet_radius + setup.domain_height),\n        nelem = numelem_vert,\n    )\n    topology = StackedCubedSphereTopology(mpicomm, numelem_horz, vert_range)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorder,\n        meshwarp = equiangular_cubed_sphere_warp,\n    )\n\n    T_profile = IsothermalProfile(param_set, setup.T_ref)\n    δ_χ = @SVector [FT(ii) for ii in 1:ntracers]\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = HydrostaticState(T_profile),\n        turbulence = ConstantDynamicViscosity(FT(0)),\n        moisture = DryModel(),\n        tracers = NTracers{length(δ_χ), FT}(δ_χ),\n    )\n    model = AtmosModel{FT}(\n        AtmosGCMConfigType,\n        physics;\n        init_state_prognostic = setup,\n        source = (Gravity(),),\n    )\n    linearmodel = AtmosAcousticGravityLinearModel(model)\n\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    lineardg = DGModel(\n        linearmodel,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient();\n        direction = VerticalDirection(),\n        state_auxiliary = dg.state_auxiliary,\n    )\n\n    # determine the time step\n    element_size = (setup.domain_height / numelem_vert)\n    acoustic_speed = soundspeed_air(param_set, FT(setup.T_ref))\n    dt_factor = 445\n    dt = dt_factor * element_size / acoustic_speed / polynomialorder^2\n    # Adjust the time step so we exactly hit 1 hour for VTK output\n    dt = 60 * 60 / ceil(60 * 60 / dt)\n    nsteps = ceil(Int, timeend / dt)\n\n    Q = init_ode_state(dg, FT(0))\n\n    linearsolver = ManyColumnLU()\n    # linearsolver = BatchedGeneralizedMinimalResidual(\n    #    lineardg,\n    #    Q;\n    #    atol = 1.0e-6, #sqrt(eps(FT)) * 0.01,\n    #    rtol = 1.0e-8, #sqrt(eps(FT)) * 0.01,\n    #    # Maximum number of Krylov iterations in a column\n    #)\n\n\n    if split_explicit_implicit\n        rem_dg = remainder_DGModel(\n            dg,\n            (lineardg,);\n            numerical_flux_first_order = (\n                dg.numerical_flux_first_order,\n                (lineardg.numerical_flux_first_order,),\n            ),\n        )\n    end\n    odesolver = ARK2GiraldoKellyConstantinescu(\n        split_explicit_implicit ? rem_dg : dg,\n        lineardg,\n        LinearBackwardEulerSolver(\n            linearsolver;\n            isadjustable = true,\n            preconditioner_update_freq = -1,\n        ),\n        Q;\n        dt = dt,\n        t0 = 0,\n        split_explicit_implicit = split_explicit_implicit,\n    )\n    @test getsteps(odesolver) == 0\n\n    filterorder = 18\n    filter = ExponentialFilter(grid, 0, filterorder)\n    cbfilter = EveryXSimulationSteps(1) do\n        Filters.apply!(Q, :, grid, filter, direction = VerticalDirection())\n        nothing\n    end\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n                      ArrayType       = %s\n                      FT              = %s\n                      polynomialorder = %d\n                      numelem_horz    = %d\n                      numelem_vert    = %d\n                      dt              = %.16e\n                      norm(Q₀)        = %.16e\n                      \"\"\" \"$ArrayType\" \"$FT\" polynomialorder numelem_horz numelem_vert dt eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            runtime = Dates.format(\n                convert(DateTime, now() - starttime[]),\n                dateformat\"HH:MM:SS\",\n            )\n            @info @sprintf \"\"\"Update\n                              simtime = %.16e\n                              runtime = %s\n                              norm(Q) = %.16e\n                              \"\"\" gettime(odesolver) runtime energy\n        end\n    end\n    callbacks = (cbinfo, cbfilter)\n\n    if output_vtk\n        # create vtk dir\n        vtkdir =\n            \"vtk_acousticwave\" *\n            \"_poly$(polynomialorder)_horz$(numelem_horz)_vert$(numelem_vert)\" *\n            \"_dt$(dt_factor)x_$(ArrayType)_$(FT)\"\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # output initial step\n        do_output(mpicomm, vtkdir, vtkstep, dg, Q, model)\n\n        # setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(odesolver))\n            do_output(mpicomm, vtkdir, vtkstep, dg, Q, model)\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(\n        Q,\n        odesolver;\n        numberofsteps = nsteps,\n        adjustfinalstep = false,\n        callbacks = callbacks,\n    )\n\n    @test getsteps(odesolver) == nsteps\n\n    # final statistics\n    engf = norm(Q)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    \"\"\" engf engf / eng0 engf - eng0\n    engf\nend\n\nBase.@kwdef struct AcousticWaveSetup{FT}\n    domain_height::FT = 10e3\n    T_ref::FT = 300\n    α::FT = 3\n    γ::FT = 100\n    nv::Int = 1\nend\n\nfunction (setup::AcousticWaveSetup)(problem, bl, state, aux, localgeo, t)\n    # callable to set initial conditions\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n\n    λ = longitude(bl, aux)\n    φ = latitude(bl, aux)\n    z = altitude(bl, aux)\n\n    β = min(FT(1), setup.α * acos(cos(φ) * cos(λ)))\n    f = (1 + cos(FT(π) * β)) / 2\n    g = sin(setup.nv * FT(π) * z / setup.domain_height)\n    Δp = setup.γ * f * g\n    p = aux.ref_state.p + Δp\n\n    ts = PhaseDry_pT(param_set, p, setup.T_ref)\n    q_pt = PhasePartition(ts)\n    e_pot = gravitational_potential(bl.orientation, aux)\n    e_int = internal_energy(ts)\n\n    state.ρ = air_density(ts)\n    state.ρu = SVector{3, FT}(0, 0, 0)\n    state.energy.ρe = state.ρ * (e_int + e_pot)\n\n    state.tracers.ρχ = @SVector [FT(ii) for ii in 1:ntracers]\n    nothing\nend\n\nfunction do_output(\n    mpicomm,\n    vtkdir,\n    vtkstep,\n    dg,\n    Q,\n    model,\n    testname = \"acousticwave\",\n)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    auxnames = flattenednames(vars_state(model, Auxiliary(), eltype(Q)))\n    writevtk(filename, Q, dg, statenames, dg.state_auxiliary, auxnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(pvtuprefix, prefixes, (statenames..., auxnames...), eltype(Q))\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/DGMethods/Euler/acousticwave_mrigark.jl",
    "content": "using ClimateMachine\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Mesh.Topologies:\n    StackedCubedSphereTopology, equiangular_cubed_sphere_warp, grid1d\nusing ClimateMachine.Mesh.Grids:\n    DiscontinuousSpectralElementGrid,\n    VerticalDirection,\n    HorizontalDirection,\n    EveryDirection,\n    min_node_distance\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.DGMethods: DGModel, init_ode_state, remainder_DGModel\nusing ClimateMachine.DGMethods.NumericalFluxes:\n    RusanovNumericalFlux,\n    CentralNumericalFluxGradient,\n    CentralNumericalFluxSecondOrder\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.VTK: writevtk, writepvtu\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing Thermodynamics:\n    air_density, soundspeed_air, internal_energy, PhaseDry_pT, PhasePartition\nusing Thermodynamics.TemperatureProfiles: IsothermalProfile\nusing ClimateMachine.Atmos:\n    AtmosPhysics,\n    AtmosModel,\n    DryModel,\n    NoPrecipitation,\n    NoRadiation,\n    NTracers,\n    ConstantDynamicViscosity,\n    vars_state,\n    Gravity,\n    HydrostaticState,\n    AtmosAcousticGravityLinearModel,\n    AtmosAcousticLinearModel,\n    parameter_set\nusing ClimateMachine.Orientations:\n    SphericalOrientation, gravitational_potential, altitude, latitude, longitude\nusing ClimateMachine.VariableTemplates: flattenednames\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: planet_radius\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing MPI, Logging, StaticArrays, LinearAlgebra, Printf, Dates, Test\n\nconst output_vtk = false\n\nconst ntracers = 1\n\nfunction main()\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 5\n    numelem_horz = 10\n    numelem_vert = 5\n\n    timeend = 60 * 60\n    # timeend = 33 * 60 * 60 # Full simulation\n    outputtime = 60 * 60\n\n    expected_result = Dict()\n    expected_result[Float64, true] = 9.5073337869322578e+13\n    expected_result[Float64, false] = 9.5073455070673781e+13\n\n    @testset \"acoustic wave\" begin\n        for FT in (Float64,)# Float32)\n            for explicit in (true, false)\n                result = test_run(\n                    mpicomm,\n                    polynomialorder,\n                    numelem_horz,\n                    numelem_vert,\n                    timeend,\n                    outputtime,\n                    ArrayType,\n                    FT,\n                    explicit,\n                )\n                @test result ≈ expected_result[FT, explicit]\n            end\n        end\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    polynomialorder,\n    numelem_horz,\n    numelem_vert,\n    timeend,\n    outputtime,\n    ArrayType,\n    FT,\n    explicit_solve,\n)\n\n    setup = AcousticWaveSetup{FT}()\n\n    _planet_radius::FT = planet_radius(param_set)\n    vert_range = grid1d(\n        _planet_radius,\n        FT(_planet_radius + setup.domain_height),\n        nelem = numelem_vert,\n    )\n    topology = StackedCubedSphereTopology(mpicomm, numelem_horz, vert_range)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorder,\n        meshwarp = equiangular_cubed_sphere_warp,\n    )\n    hmnd = min_node_distance(grid, HorizontalDirection())\n    vmnd = min_node_distance(grid, VerticalDirection())\n\n    T_profile = IsothermalProfile(param_set, setup.T_ref)\n    δ_χ = @SVector [FT(ii) for ii in 1:ntracers]\n\n    fullphysics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = HydrostaticState(T_profile),\n        turbulence = ConstantDynamicViscosity(FT(0)),\n        moisture = DryModel(),\n        tracers = NTracers{length(δ_χ), FT}(δ_χ),\n    )\n    fullmodel = AtmosModel{FT}(\n        AtmosLESConfigType,\n        fullphysics;\n        orientation = SphericalOrientation(),\n        init_state_prognostic = setup,\n        source = (Gravity(),),\n    )\n    dg = DGModel(\n        fullmodel,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n    Q = init_ode_state(dg, FT(0))\n\n    # The linear model which contains the fast modes\n    # acousticmodel = AtmosAcousticLinearModel(fullmodel)\n    acousticmodel = AtmosAcousticGravityLinearModel(fullmodel)\n\n    vacoustic_dg = DGModel(\n        acousticmodel,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient();\n        direction = VerticalDirection(),\n        state_auxiliary = dg.state_auxiliary,\n    )\n\n    # Advection model is the difference between the fullmodel and acousticmodel.\n    # This will be handled with explicit substepping (time step in between the\n    # vertical and horizontally acoustic models)\n    rem_dg = remainder_DGModel(dg, (vacoustic_dg,))\n\n    # determine the time step for the model components\n    acoustic_speed = soundspeed_air(param_set, FT(setup.T_ref))\n    advection_speed = 1 # What's a reasonable number here?\n\n    vacoustic_dt = vmnd / acoustic_speed\n    remainder_dt = min(min(hmnd, vmnd) / advection_speed, hmnd / acoustic_speed)\n    odesolver = if explicit_solve\n        remainder_dt = 5vacoustic_dt\n\n        nsteps_output = ceil(outputtime / remainder_dt)\n        remainder_dt = outputtime / nsteps_output\n        nsteps = ceil(Int, timeend / remainder_dt)\n        @assert nsteps * remainder_dt ≈ timeend\n\n        vacoustic_solver =\n            LSRK54CarpenterKennedy(vacoustic_dg, Q; dt = vacoustic_dt)\n        rem_solver = MRIGARKERK45aSandu(\n            rem_dg,\n            vacoustic_solver,\n            Q;\n            dt = remainder_dt,\n        )\n\n        rem_solver\n    else\n        vacoustic_dt = 200vacoustic_dt\n        element_size = (setup.domain_height / numelem_vert)\n\n        nsteps_output = ceil(outputtime / vacoustic_dt)\n        vacoustic_dt = outputtime / nsteps_output\n        nsteps = ceil(Int, timeend / vacoustic_dt)\n        @assert nsteps * vacoustic_dt ≈ timeend\n\n        rem_solver = LSRK54CarpenterKennedy(rem_dg, Q; dt = remainder_dt)\n        vacoustic_solver = MRIGARKESDIRK24LSA(\n            vacoustic_dg,\n            LinearBackwardEulerSolver(ManyColumnLU(); isadjustable = false),\n            rem_solver,\n            Q;\n            dt = vacoustic_dt,\n        )\n\n        vacoustic_solver\n    end\n\n\n    filterorder = 18\n    filter = ExponentialFilter(grid, 0, filterorder)\n    cbfilter = EveryXSimulationSteps(1) do\n        Filters.apply!(Q, :, grid, filter, direction = VerticalDirection())\n        nothing\n    end\n\n    eng0 = norm(Q)\n    @info @sprintf(\n        \"\"\"Starting\n           ArrayType       = %s\n           FT              = %s\n           polynomialorder = %d\n           numelem_horz    = %d\n           numelem_vert    = %d\n           acoustic dt     = %.16e\n           remainder_dt    = %.16e\n           norm(Q₀)        = %.16e\n           \"\"\",\n        \"$ArrayType\",\n        \"$FT\",\n        polynomialorder,\n        numelem_horz,\n        numelem_vert,\n        vacoustic_dt,\n        remainder_dt,\n        eng0\n    )\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            runtime = Dates.format(\n                convert(DateTime, now() - starttime[]),\n                dateformat\"HH:MM:SS\",\n            )\n            @info @sprintf \"\"\"Update\n                              simtime = %.16e\n                              runtime = %s\n                              norm(Q) = %.16e\n                              \"\"\" gettime(odesolver) runtime energy\n        end\n    end\n    callbacks = (cbinfo, cbfilter)\n\n    if output_vtk\n        # create vtk dir\n        vtkdir =\n            \"vtk_acousticwave\" *\n            \"_poly$(polynomialorder)_horz$(numelem_horz)_vert$(numelem_vert)\" *\n            \"_$(ArrayType)_$(FT)\" *\n            \"_$(explicit_solve ? \"Explicit_Explicit\" : \"Implicit_Explicit\")\"\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # output initial step\n        do_output(mpicomm, vtkdir, vtkstep, dg, Q, fullmodel)\n\n        # setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(odesolver))\n            do_output(mpicomm, vtkdir, vtkstep, dg, Q, fullmodel)\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(\n        Q,\n        odesolver;\n        numberofsteps = nsteps,\n        adjustfinalstep = false,\n        callbacks = callbacks,\n    )\n\n    # final statistics\n    engf = norm(Q)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    \"\"\" engf engf / eng0 engf - eng0\n    engf\nend\n\nBase.@kwdef struct AcousticWaveSetup{FT}\n    domain_height::FT = 10e3\n    T_ref::FT = 300\n    α::FT = 3\n    γ::FT = 100\n    nv::Int = 1\nend\n\nfunction (setup::AcousticWaveSetup)(problem, bl, state, aux, localgeo, t)\n    # callable to set initial conditions\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n\n    λ = longitude(bl, aux)\n    φ = latitude(bl, aux)\n    z = altitude(bl, aux)\n\n    β = min(FT(1), setup.α * acos(cos(φ) * cos(λ)))\n    f = (1 + cos(FT(π) * β)) / 2\n    g = sin(setup.nv * FT(π) * z / setup.domain_height)\n    Δp = setup.γ * f * g\n    p = aux.ref_state.p + Δp\n\n    ts = PhaseDry_pT(param_set, p, setup.T_ref)\n    q_pt = PhasePartition(ts)\n    e_pot = gravitational_potential(bl.orientation, aux)\n    e_int = internal_energy(ts)\n\n    state.ρ = air_density(ts)\n    state.ρu = SVector{3, FT}(0, 0, 0)\n    state.energy.ρe = state.ρ * (e_int + e_pot)\n\n    state.tracers.ρχ = @SVector [FT(ii) for ii in 1:ntracers]\n    nothing\nend\n\nfunction do_output(\n    mpicomm,\n    vtkdir,\n    vtkstep,\n    dg,\n    Q,\n    model,\n    testname = \"acousticwave\",\n)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    auxnames = flattenednames(vars_state(model, Auxiliary(), eltype(Q)))\n    writevtk(filename, Q, dg, statenames, dg.state_auxiliary, auxnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(pvtuprefix, prefixes, (statenames..., auxnames...), eltype(Q))\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/DGMethods/Euler/acousticwave_variable_degree.jl",
    "content": "using ClimateMachine\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Mesh.Topologies:\n    StackedCubedSphereTopology, equiangular_cubed_sphere_warp, grid1d\nusing ClimateMachine.Mesh.Grids:\n    DiscontinuousSpectralElementGrid, VerticalDirection\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.DGMethods: DGModel, init_ode_state, remainder_DGModel\nusing ClimateMachine.DGMethods.NumericalFluxes:\n    RusanovNumericalFlux,\n    CentralNumericalFluxGradient,\n    CentralNumericalFluxSecondOrder\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.VTK: writevtk, writepvtu\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing Thermodynamics:\n    air_density, soundspeed_air, internal_energy, PhaseDry_pT, PhasePartition\nusing Thermodynamics.TemperatureProfiles: IsothermalProfile\nusing ClimateMachine.Atmos:\n    AtmosPhysics,\n    AtmosModel,\n    DryModel,\n    NoPrecipitation,\n    NoRadiation,\n    NTracers,\n    vars_state,\n    Gravity,\n    HydrostaticState,\n    AtmosAcousticGravityLinearModel,\n    parameter_set\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.Orientations:\n    SphericalOrientation, gravitational_potential, altitude, latitude, longitude\nusing ClimateMachine.VariableTemplates: flattenednames\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: planet_radius\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing MPI, Logging, StaticArrays, LinearAlgebra, Printf, Dates, Test\n\nconst ntracers = 1\n\nfunction main()\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    # 5th order in the horizontal, cubic in the vertical\n    polynomialorder = (5, 3)\n    numelem_horz = 10\n    numelem_vert = 6\n\n    timeend = 60 * 60\n    # timeend = 33 * 60 * 60 # Full simulation\n    outputtime = 60 * 60\n\n    expected_result = Dict()\n    expected_result[Float32] = 9.5075065f13\n    expected_result[Float64] = 9.507349773781483e13\n\n    for FT in (Float64, Float32)\n        for split_explicit_implicit in (false, true)\n            result = test_run(\n                mpicomm,\n                polynomialorder,\n                numelem_horz,\n                numelem_vert,\n                timeend,\n                outputtime,\n                ArrayType,\n                FT,\n                split_explicit_implicit,\n            )\n            @test result ≈ expected_result[FT]\n        end\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    polynomialorder,\n    numelem_horz,\n    numelem_vert,\n    timeend,\n    outputtime,\n    ArrayType,\n    FT,\n    split_explicit_implicit,\n)\n\n    setup = AcousticWaveSetup{FT}()\n\n    _planet_radius::FT = planet_radius(param_set)\n    vert_range = grid1d(\n        _planet_radius,\n        FT(_planet_radius + setup.domain_height),\n        nelem = numelem_vert,\n    )\n    topology = StackedCubedSphereTopology(mpicomm, numelem_horz, vert_range)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorder,\n        meshwarp = equiangular_cubed_sphere_warp,\n    )\n\n    T_profile = IsothermalProfile(param_set, setup.T_ref)\n    δ_χ = @SVector [FT(ii) for ii in 1:ntracers]\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = HydrostaticState(T_profile),\n        turbulence = ConstantDynamicViscosity(FT(0)),\n        moisture = DryModel(),\n        tracers = NTracers{length(δ_χ), FT}(δ_χ),\n    )\n    model = AtmosModel{FT}(\n        AtmosGCMConfigType,\n        physics;\n        init_state_prognostic = setup,\n        source = (Gravity(),),\n    )\n\n    linearmodel = AtmosAcousticGravityLinearModel(model)\n\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    lineardg = DGModel(\n        linearmodel,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient();\n        direction = VerticalDirection(),\n        state_auxiliary = dg.state_auxiliary,\n    )\n\n    # determine the time step\n    element_size = (setup.domain_height / numelem_vert)\n    acoustic_speed = soundspeed_air(param_set, FT(setup.T_ref))\n    dt_factor = 445\n    Nmax = maximum(polynomialorder)\n    dt = dt_factor * element_size / acoustic_speed / Nmax^2\n    # Adjust the time step so we exactly hit 1 hour for VTK output\n    dt = 60 * 60 / ceil(60 * 60 / dt)\n    nsteps = ceil(Int, timeend / dt)\n\n    Q = init_ode_state(dg, FT(0))\n\n    linearsolver = ManyColumnLU()\n\n    if split_explicit_implicit\n        rem_dg = remainder_DGModel(\n            dg,\n            (lineardg,);\n            numerical_flux_first_order = (\n                dg.numerical_flux_first_order,\n                (lineardg.numerical_flux_first_order,),\n            ),\n        )\n    end\n    odesolver = ARK2GiraldoKellyConstantinescu(\n        split_explicit_implicit ? rem_dg : dg,\n        lineardg,\n        LinearBackwardEulerSolver(\n            linearsolver;\n            isadjustable = true,\n            preconditioner_update_freq = -1,\n        ),\n        Q;\n        dt = dt,\n        t0 = 0,\n        split_explicit_implicit = split_explicit_implicit,\n    )\n    @test getsteps(odesolver) == 0\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n    ArrayType       = %s\n    FT              = %s\n    poly order horz = %d\n    poly order vert = %d\n    numelem_horz    = %d\n    numelem_vert    = %d\n    dt              = %.16e\n    norm(Q₀)        = %.16e\n    \"\"\" \"$ArrayType\" \"$FT\" polynomialorder... numelem_horz numelem_vert dt eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            runtime = Dates.format(\n                convert(DateTime, now() - starttime[]),\n                dateformat\"HH:MM:SS\",\n            )\n            @info @sprintf \"\"\"Update\n                              simtime = %.16e\n                              runtime = %s\n                              norm(Q) = %.16e\n                              \"\"\" gettime(odesolver) runtime energy\n        end\n    end\n\n    # Look ma, no filters!\n    callbacks = (cbinfo,)\n\n    solve!(\n        Q,\n        odesolver;\n        numberofsteps = nsteps,\n        adjustfinalstep = false,\n        callbacks = callbacks,\n    )\n\n    @test getsteps(odesolver) == nsteps\n\n    # final statistics\n    engf = norm(Q)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    \"\"\" engf engf / eng0 engf - eng0\n    return engf\nend\n\nBase.@kwdef struct AcousticWaveSetup{FT}\n    domain_height::FT = 10e3\n    T_ref::FT = 300\n    α::FT = 3\n    γ::FT = 100\n    nv::Int = 1\nend\n\nfunction (setup::AcousticWaveSetup)(problem, bl, state, aux, localgeo, t)\n    # callable to set initial conditions\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n\n    λ = longitude(bl, aux)\n    φ = latitude(bl, aux)\n    z = altitude(bl, aux)\n\n    β = min(FT(1), setup.α * acos(cos(φ) * cos(λ)))\n    f = (1 + cos(FT(π) * β)) / 2\n    g = sin(setup.nv * FT(π) * z / setup.domain_height)\n    Δp = setup.γ * f * g\n    p = aux.ref_state.p + Δp\n\n    ts = PhaseDry_pT(param_set, p, setup.T_ref)\n    q_pt = PhasePartition(ts)\n    e_pot = gravitational_potential(bl.orientation, aux)\n    e_int = internal_energy(ts)\n\n    state.ρ = air_density(ts)\n    state.ρu = SVector{3, FT}(0, 0, 0)\n    state.energy.ρe = state.ρ * (e_int + e_pot)\n\n    state.tracers.ρχ = @SVector [FT(ii) for ii in 1:ntracers]\n    nothing\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/DGMethods/Euler/fvm_balance.jl",
    "content": "using ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.FVReconstructions: FVLinear\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing Thermodynamics.TemperatureProfiles\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Mesh.Geometry\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Orientations\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: planet_radius\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing Test, MPI, Logging, StaticArrays, LinearAlgebra, Printf, Dates\n\nfunction main()\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = (4, 0)\n    FT = Float64\n    NumericalFlux = RoeNumericalFlux\n    @info @sprintf \"\"\"Configuration\n                      ArrayType     = %s\n                      FT            = %s\n                      NumericalFlux = %s\n                      \"\"\" ArrayType FT NumericalFlux\n\n    numelem_horz = 10\n    numelem_vert = 32\n\n    @testset for domain_type in (:box, :sphere)\n        err = test_run(\n            mpicomm,\n            ArrayType,\n            polynomialorder,\n            numelem_horz,\n            numelem_vert,\n            NumericalFlux,\n            FT,\n            domain_type,\n        )\n        @test err < FT(6e-12)\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    polynomialorder,\n    numelem_horz,\n    numelem_vert,\n    NumericalFlux,\n    FT,\n    domain_type,\n)\n    domain_height = 10e3\n    if domain_type === :box\n        domain_width = 20e3\n        horz_range =\n            range(FT(0), length = numelem_horz + 1, stop = FT(domain_width))\n        vert_range = range(0, length = numelem_vert + 1, stop = domain_height)\n        brickrange = (horz_range, horz_range, vert_range)\n        periodicity = (true, true, false)\n        topology =\n            StackedBrickTopology(mpicomm, brickrange; periodicity = periodicity)\n        meshwarp = (x...) -> identity(x)\n    elseif domain_type === :sphere\n        _planet_radius::FT = planet_radius(param_set)\n        vert_range = grid1d(\n            _planet_radius,\n            FT(_planet_radius + domain_height),\n            nelem = numelem_vert,\n        )\n        topology = StackedCubedSphereTopology(mpicomm, numelem_horz, vert_range)\n        meshwarp = equiangular_cubed_sphere_warp\n    end\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorder,\n        meshwarp = meshwarp,\n    )\n\n    T0 = FT(300)\n    temp_profile = IsothermalProfile(param_set, T0)\n    ref_state = HydrostaticState(temp_profile; subtract_off = false)\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = ConstantDynamicViscosity(FT(0)),\n        moisture = DryModel(),\n    )\n\n    problem = AtmosProblem(;\n        physics = physics,\n        init_state_prognostic = initialcondition!,\n    )\n\n    if domain_type === :box\n        configtype = AtmosLESConfigType\n        source = (Gravity(),)\n    elseif domain_type === :sphere\n        configtype = AtmosGCMConfigType\n        source = (Gravity(), Coriolis())\n    end\n\n    model =\n        AtmosModel{FT}(configtype, physics; problem = problem, source = source)\n\n    dg = DGFVModel(\n        model,\n        grid,\n        HBFVReconstruction(model, FVLinear()),\n        NumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    timeend = FT(100)\n\n    # determine the time step\n    cfl = 0.2\n    dz = step(vert_range)\n    dt = cfl * dz / soundspeed_air(param_set, T0)\n    nsteps = ceil(Int, timeend / dt)\n    dt = timeend / nsteps\n\n    Q = init_ode_state(dg, FT(0))\n    lsrk = LSRK54CarpenterKennedy(dg, Q; dt = dt, t0 = 0)\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n                      domain_type   = %s\n                      numelem_horz  = %d\n                      numelem_vert  = %d\n                      dt            = %.16e\n                      norm(Q₀)      = %.16e\n                      \"\"\" domain_type numelem_horz numelem_vert dt eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @views begin\n                ρu = extrema(Array(Q.data[:, 2, :]))\n                ρv = extrema(Array(Q.data[:, 3, :]))\n                ρw = extrema(Array(Q.data[:, 4, :]))\n            end\n            runtime = Dates.format(\n                convert(DateTime, now() - starttime[]),\n                dateformat\"HH:MM:SS\",\n            )\n            @info @sprintf \"\"\"Update\n                              simtime = %.16e\n                              runtime = %s\n                              ρu = %.16e, %.16e\n                              ρv = %.16e, %.16e\n                              ρw = %.16e, %.16e\n                              norm(Q) = %.16e\n                              \"\"\" gettime(lsrk) runtime ρu... ρv... ρw... energy\n        end\n    end\n    callbacks = (cbinfo,)\n\n    solve!(Q, lsrk; timeend = timeend, callbacks = callbacks)\n\n    # final statistics\n    Qe = init_ode_state(dg, timeend)\n    engf = norm(Q)\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    errr = errf / engfe\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" engf engf / eng0 engf - eng0 errf errr\n    errr\nend\n\nfunction initialcondition!(problem, bl, state, aux, coords, t, args...)\n    state.ρ = aux.ref_state.ρ\n    state.ρu = SVector(0, 0, 0)\n    state.energy.ρe = aux.ref_state.ρe\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/DGMethods/Euler/fvm_isentropicvortex.jl",
    "content": "using ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods.FVReconstructions: FVConstant, FVLinear\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Mesh.Geometry\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Orientations\nusing ClimateMachine.SystemSolvers\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.VTK\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: kappa_d\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing MPI, Logging, StaticArrays, LinearAlgebra, Printf, Dates, Test\n\ninclude(\"isentropicvortex_setup.jl\")\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nconst output_vtk = false\n\nfunction main()\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    numlevels = integration_testing ? 4 : 1\n\n    expected_error = Dict()\n\n    # Just to make it shorter and aligning\n    Roe = RoeNumericalFlux\n\n    # Float64, Dim 2, degree 4 in the horizontal, FV order 1, refinement level\n    expected_error[Float64, FVConstant(), Roe, 1] = 3.5317756615538940e+01\n    expected_error[Float64, FVConstant(), Roe, 2] = 2.5104217086071472e+01\n    expected_error[Float64, FVConstant(), Roe, 3] = 1.6169569358521223e+01\n    expected_error[Float64, FVConstant(), Roe, 4] = 9.4731749125284708e+00\n\n    expected_error[Float64, FVLinear(), Roe, 1] = 2.6132907774912638e+01\n    expected_error[Float64, FVLinear(), Roe, 2] = 8.8198392537283006e+00\n    expected_error[Float64, FVLinear(), Roe, 3] = 2.4517109427575416e+00\n    expected_error[Float64, FVLinear(), Roe, 4] = 7.4154384427579900e-01\n\n\n    dims = 2\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64,)\n            for NumericalFlux in (Roe,)\n                setup = IsentropicVortexSetup{FT}()\n                for fvmethod in (FVConstant(), FVLinear())\n                    @info @sprintf \"\"\"Configuration\n                                      FT                = %s\n                                      ArrayType         = %s\n                                      FV Reconstruction = %s\n                                      NumericalFlux     = %s\n                                      dims              = %d\n                                      \"\"\" FT ArrayType fvmethod NumericalFlux dims\n                    errors = Vector{FT}(undef, numlevels)\n\n                    for level in 1:numlevels\n\n                        # Match element numbers\n                        numelems = (\n                            2^(level - 1) * 5,\n                            2^(level - 1) * 5 * polynomialorder,\n                        )\n\n                        errors[level] = test_run(\n                            mpicomm,\n                            ArrayType,\n                            fvmethod,\n                            polynomialorder,\n                            numelems,\n                            NumericalFlux,\n                            setup,\n                            FT,\n                            dims,\n                            level,\n                        )\n\n                        @test errors[level] ≈\n                              expected_error[FT, fvmethod, NumericalFlux, level]\n                    end\n                    @info begin\n                        msg = \"\"\n                        for l in 1:(numlevels - 1)\n                            rate = log2(errors[l]) - log2(errors[l + 1])\n                            msg *= @sprintf(\n                                \"\\n  rate for level %d = %e\\n\",\n                                l,\n                                rate\n                            )\n                        end\n                        msg\n                    end\n                end\n\n            end\n        end\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    fvmethod,\n    polynomialorder,\n    numelems,\n    NumericalFlux,\n    setup,\n    FT,\n    dims,\n    level,\n)\n    brickrange = ntuple(dims) do dim\n        range(\n            -setup.domain_halflength;\n            length = numelems[dim] + 1,\n            stop = setup.domain_halflength,\n        )\n    end\n    connectivity = dims == 3 ? :full : :face\n\n    topology = StackedBrickTopology(\n        mpicomm,\n        brickrange;\n        periodicity = ntuple(_ -> true, dims),\n        connectivity = connectivity,\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = (polynomialorder, 0),\n    )\n\n    problem =\n        AtmosProblem(boundaryconditions = (), init_state_prognostic = setup)\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = NoReferenceState(),\n        turbulence = ConstantDynamicViscosity(FT(0)),\n        moisture = DryModel(),\n    )\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        problem = problem,\n        orientation = NoOrientation(),\n        source = (),\n    )\n\n    dgfvm = DGFVModel(\n        model,\n        grid,\n        fvmethod,\n        NumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    timeend = FT(2 * setup.domain_halflength / 10 / setup.translation_speed)\n\n    # Determine the time step\n    elementsize = minimum(step.(brickrange))\n    dt = elementsize / soundspeed_air(param_set, setup.T∞) / polynomialorder^2\n    nsteps = ceil(Int, timeend / dt)\n    dt = timeend / nsteps\n\n    Q = init_ode_state(dgfvm, FT(0))\n    lsrk = LSRK54CarpenterKennedy(dgfvm, Q; dt = dt, t0 = 0)\n\n    eng0 = norm(Q)\n    dims == 2 && (numelems = (numelems..., 0))\n    @info @sprintf \"\"\"Starting refinement level %d\n                      numelems  = (%d, %d, %d)\n                      dt        = %.16e\n                      norm(Q₀)  = %.16e\n                      \"\"\" level numelems... dt eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            runtime = Dates.format(\n                convert(DateTime, now() - starttime[]),\n                dateformat\"HH:MM:SS\",\n            )\n            @info @sprintf \"\"\"Update\n                              simtime = %.16e\n                              runtime = %s\n                              norm(Q) = %.16e\n                              \"\"\" gettime(lsrk) runtime energy\n        end\n    end\n    callbacks = (cbinfo,)\n\n    if output_vtk\n        # Create vtk dir\n        vtkdir =\n            \"vtk_isentropicvortex\" *\n            \"_poly$(polynomialorder)_dims$(dims)_$(ArrayType)_$(FT)_level$(level)\"\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # Output initial step\n        do_output(mpicomm, vtkdir, vtkstep, dgfvm, Q, Q, model)\n\n        # Setup the output callback\n        outputtime = timeend\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dgfvm, gettime(lsrk), setup)\n            do_output(mpicomm, vtkdir, vtkstep, dgfvm, Q, Qe, model)\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(Q, lsrk; timeend = timeend, callbacks = callbacks)\n\n    # Final statistics\n    Qe = init_ode_state(dgfvm, timeend, setup)\n    engf = norm(Q)\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished refinement level %d\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" level engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nfunction do_output(\n    mpicomm,\n    vtkdir,\n    vtkstep,\n    dgfvm,\n    Q,\n    Qe,\n    model,\n    testname = \"isentropicvortex\",\n)\n    ## Name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dgfvm, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## Name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/DGMethods/Euler/isentropicvortex.jl",
    "content": "using ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Mesh.Geometry\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Orientations\nusing ClimateMachine.SystemSolvers\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.VTK\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: kappa_d\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing MPI, Logging, StaticArrays, LinearAlgebra, Printf, Dates, Test\n\ninclude(\"isentropicvortex_setup.jl\")\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nconst output_vtk = false\n\nfunction main()\n    ClimateMachine.init(parse_clargs = true)\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    numlevels = integration_testing ? 4 : 1\n\n    expected_error = Dict()\n\n    # just to make it shorter and aligning\n    Rusanov = RusanovNumericalFlux()\n    Central = CentralNumericalFluxFirstOrder()\n    Roe = RoeNumericalFlux()\n    HLLC = HLLCNumericalFlux()\n    RoeMoist = RoeNumericalFluxMoist()\n    RoeMoistLM = RoeNumericalFluxMoist(; LM = true)\n    RoeMoistHH = RoeNumericalFluxMoist(; HH = true)\n    RoeMoistLV = RoeNumericalFluxMoist(; LV = true)\n    RoeMoistLVPP = RoeNumericalFluxMoist(; LVPP = true)\n\n    expected_error[Float64, 2, Rusanov, 1] = 1.1990999506538110e+01\n    expected_error[Float64, 2, Rusanov, 2] = 2.0813000228865612e+00\n    expected_error[Float64, 2, Rusanov, 3] = 6.3752572004789149e-02\n    expected_error[Float64, 2, Rusanov, 4] = 2.0984975076420455e-03\n\n    expected_error[Float64, 2, Central, 1] = 2.0840574601661153e+01\n    expected_error[Float64, 2, Central, 2] = 2.9255455365299827e+00\n    expected_error[Float64, 2, Central, 3] = 3.6935849488949657e-01\n    expected_error[Float64, 2, Central, 4] = 8.3528804679907434e-03\n\n    expected_error[Float64, 2, Roe, 1] = 1.2891386634733328e+01\n    expected_error[Float64, 2, Roe, 2] = 1.3895805145495934e+00\n    expected_error[Float64, 2, Roe, 3] = 6.6174934435569849e-02\n    expected_error[Float64, 2, Roe, 4] = 2.1917769287815940e-03\n\n    expected_error[Float64, 2, RoeMoist, 1] = 1.2415957884123003e+01\n    expected_error[Float64, 2, RoeMoist, 2] = 1.4188653323424882e+00\n    expected_error[Float64, 2, RoeMoist, 3] = 6.7913325894248130e-02\n    expected_error[Float64, 2, RoeMoist, 4] = 2.0377963111049128e-03\n\n    expected_error[Float64, 2, RoeMoistLM, 1] = 1.2316906651180444e+01\n    expected_error[Float64, 2, RoeMoistLM, 2] = 1.4359406523244560e+00\n    expected_error[Float64, 2, RoeMoistLM, 3] = 6.8650238542101505e-02\n    expected_error[Float64, 2, RoeMoistLM, 4] = 2.0000156591586842e-03\n\n    expected_error[Float64, 2, RoeMoistHH, 1] = 1.2425625606467793e+01\n    expected_error[Float64, 2, RoeMoistHH, 2] = 1.4029458093339020e+00\n    expected_error[Float64, 2, RoeMoistHH, 3] = 6.8648208937091268e-02\n    expected_error[Float64, 2, RoeMoistHH, 4] = 2.0711985861781648e-03\n\n    expected_error[Float64, 2, RoeMoistLV, 1] = 1.2415957884123003e+01\n    expected_error[Float64, 2, RoeMoistLV, 2] = 1.4188653323424882e+00\n    expected_error[Float64, 2, RoeMoistLV, 3] = 6.7913325894248130e-02\n    expected_error[Float64, 2, RoeMoistLV, 4] = 2.0377963111049128e-03\n\n    expected_error[Float64, 2, RoeMoistLVPP, 1] = 1.2441813136310969e+01\n    expected_error[Float64, 2, RoeMoistLVPP, 2] = 2.0219325767566727e+00\n    expected_error[Float64, 2, RoeMoistLVPP, 3] = 6.7716921628626484e-02\n    expected_error[Float64, 2, RoeMoistLVPP, 4] = 2.1051129944994005e-03\n\n    expected_error[Float64, 2, HLLC, 1] = 1.2889756097329746e+01\n    expected_error[Float64, 2, HLLC, 2] = 1.3895808565455936e+00\n    expected_error[Float64, 2, HLLC, 3] = 6.6175116756217900e-02\n    expected_error[Float64, 2, HLLC, 4] = 2.1917772135679118e-03\n\n    expected_error[Float64, 3, Rusanov, 1] = 3.7918869862613858e+00\n    expected_error[Float64, 3, Rusanov, 2] = 6.5816485664822677e-01\n    expected_error[Float64, 3, Rusanov, 3] = 2.0160333422867591e-02\n    expected_error[Float64, 3, Rusanov, 4] = 6.6360317881818034e-04\n\n    expected_error[Float64, 3, Central, 1] = 6.5903683487905749e+00\n    expected_error[Float64, 3, Central, 2] = 9.2513872939749997e-01\n    expected_error[Float64, 3, Central, 3] = 1.1680141169828175e-01\n    expected_error[Float64, 3, Central, 4] = 2.6414127301659534e-03\n\n    expected_error[Float64, 3, Roe, 1] = 4.0766143963611068e+00\n    expected_error[Float64, 3, Roe, 2] = 4.3942394181655547e-01\n    expected_error[Float64, 3, Roe, 3] = 2.0926351682882375e-02\n    expected_error[Float64, 3, Roe, 4] = 6.9310072176312712e-04\n\n    expected_error[Float64, 3, RoeMoist, 1] = 3.9262706246552574e+00\n    expected_error[Float64, 3, RoeMoist, 2] = 4.4868461432545598e-01\n    expected_error[Float64, 3, RoeMoist, 3] = 2.1476079330305119e-02\n    expected_error[Float64, 3, RoeMoist, 4] = 6.4440777504566171e-04\n\n    expected_error[Float64, 3, RoeMoistLM, 1] = 3.8949478745407458e+00\n    expected_error[Float64, 3, RoeMoistLM, 2] = 4.5408430461734528e-01\n    expected_error[Float64, 3, RoeMoistLM, 3] = 2.1709111570716800e-02\n    expected_error[Float64, 3, RoeMoistLM, 4] = 6.3246048386171073e-04\n\n    expected_error[Float64, 3, RoeMoistHH, 1] = 3.9293278268948622e+00\n    expected_error[Float64, 3, RoeMoistHH, 2] = 4.4365041912830866e-01\n    expected_error[Float64, 3, RoeMoistHH, 3] = 2.1708469753267460e-02\n    expected_error[Float64, 3, RoeMoistHH, 4] = 6.5497050185153694e-04\n\n    expected_error[Float64, 3, RoeMoistLV, 1] = 3.9262706246552574e+00\n    expected_error[Float64, 3, RoeMoistLV, 2] = 4.4868461432545598e-01\n    expected_error[Float64, 3, RoeMoistLV, 3] = 2.1476079330305119e-02\n    expected_error[Float64, 3, RoeMoistLV, 4] = 6.4440777504566171e-04\n\n    expected_error[Float64, 3, RoeMoistLVPP, 1] = 3.9344467732944071e+00\n    expected_error[Float64, 3, RoeMoistLVPP, 2] = 6.3939122178436703e-01\n    expected_error[Float64, 3, RoeMoistLVPP, 3] = 2.1413970848172485e-02\n    expected_error[Float64, 3, RoeMoistLVPP, 4] = 6.6569517942933988e-04\n\n    expected_error[Float64, 3, HLLC, 1] = 4.0760987751605402e+00\n    expected_error[Float64, 3, HLLC, 2] = 4.3942404996518236e-01\n    expected_error[Float64, 3, HLLC, 3] = 2.0926409337758904e-02\n    expected_error[Float64, 3, HLLC, 4] = 6.9310081182571569e-04\n\n    expected_error[Float32, 2, Rusanov, 1] = 1.1990781784057617e+01\n    expected_error[Float32, 2, Rusanov, 2] = 2.0813269615173340e+00\n    expected_error[Float32, 2, Rusanov, 3] = 6.7035309970378876e-02\n    expected_error[Float32, 2, Rusanov, 4] = 5.3008597344160080e-02\n\n    expected_error[Float32, 2, Central, 1] = 2.0840391159057617e+01\n    expected_error[Float32, 2, Central, 2] = 2.9256355762481689e+00\n    expected_error[Float32, 2, Central, 3] = 3.7092915177345276e-01\n    expected_error[Float32, 2, Central, 4] = 1.1543693393468857e-01\n\n    expected_error[Float32, 2, Roe, 1] = 1.2891359329223633e+01\n    expected_error[Float32, 2, Roe, 2] = 1.3895936012268066e+00\n    expected_error[Float32, 2, Roe, 3] = 6.8037144839763641e-02\n    expected_error[Float32, 2, Roe, 4] = 3.8893952965736389e-02\n\n    expected_error[Float32, 2, RoeMoist, 1] = 1.2415886878967285e+01\n    expected_error[Float32, 2, RoeMoist, 2] = 1.4188879728317261e+00\n    expected_error[Float32, 2, RoeMoist, 3] = 6.9743692874908447e-02\n    expected_error[Float32, 2, RoeMoist, 4] = 3.7607192993164063e-02\n\n    expected_error[Float32, 2, RoeMoistLM, 1] = 1.2316809654235840e+01\n    expected_error[Float32, 2, RoeMoistLM, 2] = 4.5408430461734528e+00\n    expected_error[Float32, 2, RoeMoistLM, 3] = 7.0370830595493317e-02\n    expected_error[Float32, 2, RoeMoistLM, 4] = 3.7792034447193146e-02\n\n    expected_error[Float32, 2, RoeMoistHH, 1] = 1.2425449371337891e+01\n    expected_error[Float32, 2, RoeMoistHH, 2] = 1.4030106067657471e+00\n    expected_error[Float32, 2, RoeMoistHH, 3] = 7.0363849401473999e-02\n    expected_error[Float32, 2, RoeMoistHH, 4] = 3.7904966622591019e-02\n\n    expected_error[Float32, 2, RoeMoistLV, 1] = 1.2415886878967285e+01\n    expected_error[Float32, 2, RoeMoistLV, 2] = 1.4188879728317261e+00\n    expected_error[Float32, 2, RoeMoistLV, 3] = 6.9743692874908447e-02\n    expected_error[Float32, 2, RoeMoistLV, 4] = 3.7607192993164063e-02\n\n    expected_error[Float32, 2, RoeMoistLVPP, 1] = 1.2441481590270996e+01\n    expected_error[Float32, 2, RoeMoistLVPP, 2] = 2.0217459201812744e+00\n    expected_error[Float32, 2, RoeMoistLVPP, 3] = 7.0483185350894928e-02\n    expected_error[Float32, 2, RoeMoistLVPP, 4] = 5.1601748913526535e-02\n\n    expected_error[Float32, 2, HLLC, 1] = 1.2889801025390625e+01\n    expected_error[Float32, 2, HLLC, 2] = 1.3895059823989868e+00\n    expected_error[Float32, 2, HLLC, 3] = 6.8006515502929688e-02\n    expected_error[Float32, 2, HLLC, 4] = 3.8637656718492508e-02\n\n    expected_error[Float32, 3, Rusanov, 1] = 3.7918186187744141e+00\n    expected_error[Float32, 3, Rusanov, 2] = 6.5816193819046021e-01\n    expected_error[Float32, 3, Rusanov, 3] = 2.0893247798085213e-02\n    expected_error[Float32, 3, Rusanov, 4] = 1.1554701253771782e-02\n\n    expected_error[Float32, 3, Central, 1] = 6.5903329849243164e+00\n    expected_error[Float32, 3, Central, 2] = 9.2512512207031250e-01\n    expected_error[Float32, 3, Central, 3] = 1.1707859486341476e-01\n    expected_error[Float32, 3, Central, 4] = 2.1001411601901054e-02\n\n    expected_error[Float32, 3, Roe, 1] = 4.0765657424926758e+00\n    expected_error[Float32, 3, Roe, 2] = 4.3941807746887207e-01\n    expected_error[Float32, 3, Roe, 3] = 2.1365188062191010e-02\n    expected_error[Float32, 3, Roe, 4] = 9.3323951587080956e-03\n\n    expected_error[Float32, 3, RoeMoist, 1] = 3.9262301921844482e+00\n    expected_error[Float32, 3, RoeMoist, 2] = 4.4864514470100403e-01\n    expected_error[Float32, 3, RoeMoist, 3] = 2.1889146417379379e-02\n    expected_error[Float32, 3, RoeMoist, 4] = 8.8266804814338684e-03\n\n    expected_error[Float32, 3, RoeMoistLM, 1] = 3.8948786258697510e+00\n    expected_error[Float32, 3, RoeMoistLM, 2] = 4.5405751466751099e-01\n    expected_error[Float32, 3, RoeMoistLM, 3] = 2.2112159058451653e-02\n    expected_error[Float32, 3, RoeMoistLM, 4] = 8.7371272966265678e-03\n\n    expected_error[Float32, 3, RoeMoistHH, 1] = 3.9292929172515869e+00\n    expected_error[Float32, 3, RoeMoistHH, 2] = 4.4363334774971008e-01\n    expected_error[Float32, 3, RoeMoistHH, 3] = 2.2118536755442619e-02\n    expected_error[Float32, 3, RoeMoistHH, 4] = 8.9262928813695908e-03\n\n    expected_error[Float32, 3, RoeMoistLV, 1] = 3.9262151718139648e+00\n    expected_error[Float32, 3, RoeMoistLV, 2] = 4.4865489006042480e-01\n    expected_error[Float32, 3, RoeMoistLV, 3] = 2.1889505907893181e-02\n    expected_error[Float32, 3, RoeMoistLV, 4] = 8.8385939598083496e-03\n\n    expected_error[Float32, 3, RoeMoistLVPP, 1] = 3.9343423843383789e+00\n    expected_error[Float32, 3, RoeMoistLVPP, 2] = 6.3935810327529907e-01\n    expected_error[Float32, 3, RoeMoistLVPP, 3] = 2.1930629387497902e-02\n    expected_error[Float32, 3, RoeMoistLVPP, 4] = 1.0632344521582127e-02\n\n    expected_error[Float32, 3, HLLC, 1] = 4.0760631561279297e+00\n    expected_error[Float32, 3, HLLC, 2] = 4.3940672278404236e-01\n    expected_error[Float32, 3, HLLC, 3] = 2.1352596580982208e-02\n    expected_error[Float32, 3, HLLC, 4] = 9.2315869405865669e-03\n\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64, Float32), dims in (2, 3)\n            for NumericalFlux in (\n                Rusanov,\n                Central,\n                Roe,\n                HLLC,\n                RoeMoist,\n                RoeMoistLM,\n                RoeMoistHH,\n                RoeMoistLV,\n                RoeMoistLVPP,\n            )\n                @info @sprintf \"\"\"Configuration\n                                  ArrayType     = %s\n                                  FT        = %s\n                                  NumericalFlux = %s\n                                  dims          = %d\n                                  \"\"\" ArrayType \"$FT\" \"$NumericalFlux\" dims\n\n                setup = IsentropicVortexSetup{FT}()\n                errors = Vector{FT}(undef, numlevels)\n\n                for level in 1:numlevels\n                    numelems =\n                        ntuple(dim -> dim == 3 ? 1 : 2^(level - 1) * 5, dims)\n                    errors[level] = test_run(\n                        mpicomm,\n                        ArrayType,\n                        polynomialorder,\n                        numelems,\n                        NumericalFlux,\n                        setup,\n                        FT,\n                        dims,\n                        level,\n                    )\n\n                    rtol = sqrt(eps(FT))\n                    # increase rtol for comparing with GPU results using Float32\n                    if FT === Float32 && ArrayType !== Array\n                        rtol *= 10 # why does this factor have to be so big :(\n                    end\n                    @test isapprox(\n                        errors[level],\n                        expected_error[FT, dims, NumericalFlux, level];\n                        rtol = rtol,\n                    )\n                end\n\n                rates = @. log2(\n                    first(errors[1:(numlevels - 1)]) /\n                    first(errors[2:numlevels]),\n                )\n                numlevels > 1 && @info \"Convergence rates\\n\" * join(\n                    [\n                        \"rate for levels $l → $(l + 1) = $(rates[l])\"\n                        for l in 1:(numlevels - 1)\n                    ],\n                    \"\\n\",\n                )\n            end\n        end\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    polynomialorder,\n    numelems,\n    NumericalFlux,\n    setup,\n    FT,\n    dims,\n    level,\n)\n    brickrange = ntuple(dims) do dim\n        range(\n            -setup.domain_halflength;\n            length = numelems[dim] + 1,\n            stop = setup.domain_halflength,\n        )\n    end\n\n    topology = BrickTopology(\n        mpicomm,\n        brickrange;\n        periodicity = ntuple(_ -> true, dims),\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorder,\n    )\n\n    problem =\n        AtmosProblem(boundaryconditions = (), init_state_prognostic = setup)\n    if NumericalFlux isa RoeNumericalFluxMoist\n        moisture = EquilMoist()\n    else\n        moisture = DryModel()\n    end\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = NoReferenceState(),\n        turbulence = ConstantDynamicViscosity(FT(0)),\n        moisture = moisture,\n    )\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        problem = problem,\n        orientation = NoOrientation(),\n        source = (),\n    )\n\n    dg = DGModel(\n        model,\n        grid,\n        NumericalFlux,\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    timeend = FT(2 * setup.domain_halflength / 10 / setup.translation_speed)\n\n    # determine the time step\n    elementsize = minimum(step.(brickrange))\n    dt = elementsize / soundspeed_air(param_set, setup.T∞) / polynomialorder^2\n    nsteps = ceil(Int, timeend / dt)\n    dt = timeend / nsteps\n\n    Q = init_ode_state(dg, FT(0))\n    lsrk = LSRK54CarpenterKennedy(dg, Q; dt = dt, t0 = 0)\n\n    eng0 = norm(Q)\n    dims == 2 && (numelems = (numelems..., 0))\n    @info @sprintf \"\"\"Starting refinement level %d\n                      numelems  = (%d, %d, %d)\n                      dt        = %.16e\n                      norm(Q₀)  = %.16e\n                      \"\"\" level numelems... dt eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            runtime = Dates.format(\n                convert(DateTime, now() - starttime[]),\n                dateformat\"HH:MM:SS\",\n            )\n            @info @sprintf \"\"\"Update\n                              simtime = %.16e\n                              runtime = %s\n                              norm(Q) = %.16e\n                              \"\"\" gettime(lsrk) runtime energy\n        end\n    end\n    callbacks = (cbinfo,)\n\n    if output_vtk\n        # create vtk dir\n        vtkdir =\n            \"vtk_isentropicvortex\" *\n            \"_poly$(polynomialorder)_dims$(dims)_$(ArrayType)_$(FT)_level$(level)\"\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # output initial step\n        do_output(mpicomm, vtkdir, vtkstep, dg, Q, Q, model)\n\n        # setup the output callback\n        outputtime = timeend\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(lsrk), setup)\n            do_output(mpicomm, vtkdir, vtkstep, dg, Q, Qe, model)\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(Q, lsrk; timeend = timeend, callbacks = callbacks)\n\n    # final statistics\n    Qe = init_ode_state(dg, timeend, setup)\n    engf = norm(Q)\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished refinement level %d\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" level engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nfunction do_output(\n    mpicomm,\n    vtkdir,\n    vtkstep,\n    dg,\n    Q,\n    Qe,\n    model,\n    testname = \"isentropicvortex\",\n)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dg, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/DGMethods/Euler/isentropicvortex_imex.jl",
    "content": "using ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Mesh.Geometry\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Orientations\nusing ClimateMachine.SystemSolvers\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.VTK\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: kappa_d\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing MPI, Logging, StaticArrays, LinearAlgebra, Printf, Dates, Test\n\ninclude(\"isentropicvortex_setup.jl\")\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nconst output_vtk = false\n\nfunction main()\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    numlevels = integration_testing ? 4 : 1\n\n    expected_error = Dict()\n\n    expected_error[Float64, false, 1] = 2.3225467541870387e+01\n    expected_error[Float64, false, 2] = 5.2663709730295070e+00\n    expected_error[Float64, false, 3] = 1.2183770894070467e-01\n    expected_error[Float64, false, 4] = 2.8660813871243937e-03\n\n    expected_error[Float64, true, 1] = 2.3225467618783981e+01\n    expected_error[Float64, true, 2] = 5.2663709730207771e+00\n    expected_error[Float64, true, 3] = 1.2183770891083319e-01\n    expected_error[Float64, true, 4] = 2.8660813810759854e-03\n\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64,), dims in 2\n            for split_explicit_implicit in (false, true)\n                let\n                    split =\n                        split_explicit_implicit ? \"(Nonlinear, Linear)\" :\n                        \"(Full, Linear)\"\n                    @info @sprintf \"\"\"Configuration\n                                      ArrayType = %s\n                                      FT    = %s\n                                      dims      = %d\n                                      splitting = %s\n                                      \"\"\" ArrayType \"$FT\" dims split\n                end\n\n                setup = IsentropicVortexSetup{FT}()\n                errors = Vector{FT}(undef, numlevels)\n\n                for level in 1:numlevels\n                    numelems =\n                        ntuple(dim -> dim == 3 ? 1 : 2^(level - 1) * 5, dims)\n                    errors[level] = test_run(\n                        mpicomm,\n                        ArrayType,\n                        polynomialorder,\n                        numelems,\n                        setup,\n                        split_explicit_implicit,\n                        FT,\n                        dims,\n                        level,\n                    )\n\n                    @test errors[level] ≈\n                          expected_error[FT, split_explicit_implicit, level]\n                end\n\n                rates = @. log2(\n                    first(errors[1:(numlevels - 1)]) /\n                    first(errors[2:numlevels]),\n                )\n                numlevels > 1 && @info \"Convergence rates\\n\" * join(\n                    [\n                        \"rate for levels $l → $(l + 1) = $(rates[l])\"\n                        for l in 1:(numlevels - 1)\n                    ],\n                    \"\\n\",\n                )\n            end\n        end\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    polynomialorder,\n    numelems,\n    setup,\n    split_explicit_implicit,\n    FT,\n    dims,\n    level,\n)\n    brickrange = ntuple(dims) do dim\n        range(\n            -setup.domain_halflength;\n            length = numelems[dim] + 1,\n            stop = setup.domain_halflength,\n        )\n    end\n\n    topology = BrickTopology(\n        mpicomm,\n        brickrange;\n        periodicity = ntuple(_ -> true, dims),\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorder,\n    )\n\n    problem =\n        AtmosProblem(boundaryconditions = (), init_state_prognostic = setup)\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = IsentropicVortexReferenceState{FT}(setup),\n        turbulence = ConstantDynamicViscosity(FT(0)),\n        moisture = DryModel(),\n    )\n\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        problem = problem,\n        orientation = NoOrientation(),\n        source = (),\n    )\n\n    linear_model = AtmosAcousticLinearModel(model)\n\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    dg_linear = DGModel(\n        linear_model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient();\n        state_auxiliary = dg.state_auxiliary,\n    )\n\n    if split_explicit_implicit\n        dg_nonlinear = remainder_DGModel(dg, (dg_linear,))\n    end\n\n    timeend = FT(2 * setup.domain_halflength / setup.translation_speed)\n\n    # determine the time step\n    elementsize = minimum(step.(brickrange))\n    dt = elementsize / soundspeed_air(param_set, setup.T∞) / polynomialorder^2\n    nsteps = ceil(Int, timeend / dt)\n    dt = timeend / nsteps\n\n    Q = init_ode_state(dg, FT(0))\n\n    linearsolver = GeneralizedMinimalResidual(Q; M = 10, rtol = 1e-10)\n    ode_solver = ARK2GiraldoKellyConstantinescu(\n        split_explicit_implicit ? dg_nonlinear : dg,\n        dg_linear,\n        LinearBackwardEulerSolver(linearsolver; isadjustable = true),\n        Q;\n        dt = dt,\n        t0 = 0,\n        split_explicit_implicit = split_explicit_implicit,\n        paperversion = true,\n    )\n\n    eng0 = norm(Q)\n    dims == 2 && (numelems = (numelems..., 0))\n    @info @sprintf \"\"\"Starting refinement level %d\n                      numelems  = (%d, %d, %d)\n                      dt        = %.16e\n                      norm(Q₀)  = %.16e\n                      \"\"\" level numelems... dt eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            runtime = Dates.format(\n                convert(DateTime, now() - starttime[]),\n                dateformat\"HH:MM:SS\",\n            )\n            @info @sprintf \"\"\"Update\n                              simtime = %.16e\n                              runtime = %s\n                              norm(Q) = %.16e\n                              \"\"\" gettime(ode_solver) runtime energy\n        end\n    end\n    callbacks = (cbinfo,)\n\n    if output_vtk\n        # create vtk dir\n        vtkdir =\n            \"vtk_isentropicvortex_imex\" *\n            \"_poly$(polynomialorder)_dims$(dims)_$(ArrayType)_$(FT)_level$(level)\" *\n            \"_$(split_explicit_implicit)\"\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # output initial step\n        do_output(mpicomm, vtkdir, vtkstep, dg, Q, Q, model)\n\n        # setup the output callback\n        outputtime = timeend\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(ode_solver))\n            do_output(mpicomm, vtkdir, vtkstep, dg, Q, Qe, model)\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(Q, ode_solver; timeend = timeend, callbacks = callbacks)\n\n    # final statistics\n    Qe = init_ode_state(dg, timeend)\n    engf = norm(Q)\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished refinement level %d\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" level engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nfunction do_output(\n    mpicomm,\n    vtkdir,\n    vtkstep,\n    dg,\n    Q,\n    Qe,\n    model,\n    testname = \"isentropicvortex_imex\",\n)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dg, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/DGMethods/Euler/isentropicvortex_lmars.jl",
    "content": "using ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Mesh.Geometry\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Orientations\nusing ClimateMachine.SystemSolvers\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing Thermodynamics.TemperatureProfiles\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.VTK\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: kappa_d\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing MPI, Logging, StaticArrays, LinearAlgebra, Printf, Dates, Test\n\ninclude(\"isentropicvortex_setup.jl\")\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nconst output_vtk = false\n\nfunction main()\n    ClimateMachine.init(parse_clargs = true)\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    numlevels = integration_testing ? 4 : 4\n\n    expected_error = Dict()\n\n    # just to make it shorter and aligning\n    LMARS = LMARSNumericalFlux()\n\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64,), dims in (2, 3), polynomialorder in (4,)\n            for NumericalFlux in (LMARS,)\n                @info @sprintf \"\"\"Configuration\n                                  ArrayType     = %s\n                                  FT        = %s\n                                  NumericalFlux = %s\n                                  dims          = %d\n                                  N_poly        = %d\n                                  \"\"\" ArrayType \"$FT\" \"$NumericalFlux\" dims polynomialorder\n\n                setup = IsentropicVortexSetup{FT}()\n                errors = Vector{FT}(undef, numlevels)\n\n                for level in 1:numlevels\n                    numelems =\n                        ntuple(dim -> dim == 3 ? 1 : 2^(level - 1) * 5, dims)\n                    errors[level] = test_run(\n                        mpicomm,\n                        ArrayType,\n                        polynomialorder,\n                        numelems,\n                        NumericalFlux,\n                        setup,\n                        FT,\n                        dims,\n                        level,\n                    )\n\n                    @test isapprox(errors[level], FT(1.0); rtol = 1e-5)\n\n                end\n\n            end\n        end\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    polynomialorder,\n    numelems,\n    NumericalFlux,\n    setup,\n    FT,\n    dims,\n    level,\n)\n    brickrange = ntuple(dims) do dim\n        range(\n            -setup.domain_halflength;\n            length = numelems[dim] + 1,\n            stop = setup.domain_halflength,\n        )\n    end\n\n    topology = BrickTopology(\n        mpicomm,\n        brickrange;\n        periodicity = ntuple(_ -> true, dims),\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorder,\n    )\n\n    problem =\n        AtmosProblem(boundaryconditions = (), init_state_prognostic = setup)\n    if NumericalFlux isa RoeNumericalFluxMoist\n        moisture = EquilMoist()\n    else\n        moisture = DryModel()\n    end\n\n    if NumericalFlux isa LMARSNumericalFlux\n        ref_state = NoReferenceState()\n    end\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = ConstantDynamicViscosity(FT(0)),\n        moisture = moisture,\n    )\n\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        problem = problem,\n        orientation = NoOrientation(),\n        source = (),\n    )\n\n    dg = DGModel(\n        model,\n        grid,\n        NumericalFlux,\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    timeend = FT(2 * setup.domain_halflength / 10 / setup.translation_speed)\n\n    # determine the time step\n    elementsize = minimum(step.(brickrange))\n    dt = elementsize / soundspeed_air(param_set, setup.T∞) / polynomialorder^2\n    nsteps = ceil(Int, timeend / dt)\n    dt = timeend / nsteps\n\n    Q = init_ode_state(dg, FT(0))\n    lsrk = LSRK54CarpenterKennedy(dg, Q; dt = dt, t0 = 0)\n\n    eng0 = norm(Q)\n    dims == 2 && (numelems = (numelems..., 0))\n    @info @sprintf \"\"\"Starting refinement level %d\n                      polyorder = %d\n                      numelems  = (%d, %d, %d)\n                      dt        = %.16e\n                      norm(Q₀)  = %.16e\n                      FT        = %s\n                      \"\"\" level polynomialorder numelems... dt eng0 FT\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            runtime = Dates.format(\n                convert(DateTime, now() - starttime[]),\n                dateformat\"HH:MM:SS\",\n            )\n            @info @sprintf \"\"\"Update\n                              simtime = %.16e\n                              runtime = %s\n                              norm(Q) = %.16e\n                              \"\"\" gettime(lsrk) runtime energy\n        end\n    end\n    callbacks = (cbinfo,)\n\n    if output_vtk\n        # create vtk dir\n        vtkdir =\n            \"vtk_isentropicvortex\" *\n            \"$(typeof(NumericalFlux))\" *\n            \"_poly$(polynomialorder)_dims$(dims)_$(ArrayType)_$(FT)_level$(level)\"\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # output initial step\n        do_output(mpicomm, vtkdir, vtkstep, dg, Q, Q, model)\n\n        # setup the output callback\n        outputtime = timeend / 10\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(lsrk), setup)\n            do_output(mpicomm, vtkdir, vtkstep, dg, Q, Qe, model)\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(Q, lsrk; timeend = timeend, callbacks = callbacks)\n\n    # final statistics\n    Qe = init_ode_state(dg, timeend, setup)\n    engf = norm(Q)\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished refinement level %d\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" level engf engf / eng0 engf - eng0 errf errf / engfe\n    engf / eng0\nend\n\nfunction do_output(\n    mpicomm,\n    vtkdir,\n    vtkstep,\n    dg,\n    Q,\n    Qe,\n    model,\n    testname = \"isentropicvortex_lmars\",\n)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dg, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/DGMethods/Euler/isentropicvortex_mrigark.jl",
    "content": "using ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Mesh.Geometry\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Orientations\nusing ClimateMachine.SystemSolvers\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.VTK\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: kappa_d\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing MPI, Logging, StaticArrays, LinearAlgebra, Printf, Dates, Test\n\ninclude(\"isentropicvortex_setup.jl\")\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nconst output_vtk = false\n\nfunction main()\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    numlevels = integration_testing ? 4 : 1\n\n    expected_error = Dict()\n    expected_error[Float64, MRIGARKERK33aSandu, 1] = 2.3357934866477940e+01\n    expected_error[Float64, MRIGARKERK33aSandu, 2] = 5.3328129440361121e+00\n    expected_error[Float64, MRIGARKERK33aSandu, 3] = 1.2991232057877919e-01\n    expected_error[Float64, MRIGARKERK33aSandu, 4] = 6.1056067013518876e-03\n    expected_error[Float64, MRIGARKERK45aSandu, 1] = 2.3207510164213900e+01\n    expected_error[Float64, MRIGARKERK45aSandu, 2] = 5.2787446598866872e+00\n    expected_error[Float64, MRIGARKERK45aSandu, 3] = 1.2151170640665301e-01\n    expected_error[Float64, MRIGARKERK45aSandu, 4] = 2.1001271191583956e-03\n\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64,), dims in 2\n            for mrigark_method in (MRIGARKERK33aSandu, MRIGARKERK45aSandu)\n                @info @sprintf \"\"\"Configuration\n                                  ArrayType  = %s\n                                  mrigark_method = %s\n                                  FT     = %s\n                                  dims       = %d\n                                  \"\"\" ArrayType \"$mrigark_method\" \"$FT\" dims\n\n                setup = IsentropicVortexSetup{FT}()\n                errors = Vector{FT}(undef, numlevels)\n\n                for level in 1:numlevels\n                    numelems =\n                        ntuple(dim -> dim == 3 ? 1 : 2^(level - 1) * 5, dims)\n                    errors[level] = test_run(\n                        mpicomm,\n                        ArrayType,\n                        polynomialorder,\n                        numelems,\n                        setup,\n                        FT,\n                        mrigark_method,\n                        dims,\n                        level,\n                    )\n\n                    @test errors[level] ≈\n                          expected_error[FT, mrigark_method, level]\n                end\n\n                rates = @. log2(\n                    first(errors[1:(numlevels - 1)]) /\n                    first(errors[2:numlevels]),\n                )\n                numlevels > 1 && @info \"Convergence rates\\n\" * join(\n                    [\n                        \"rate for levels $l → $(l + 1) = $(rates[l])\"\n                        for l in 1:(numlevels - 1)\n                    ],\n                    \"\\n\",\n                )\n            end\n        end\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    polynomialorder,\n    numelems,\n    setup,\n    FT,\n    mrigark_method,\n    dims,\n    level,\n)\n    brickrange = ntuple(dims) do dim\n        range(\n            -setup.domain_halflength;\n            length = numelems[dim] + 1,\n            stop = setup.domain_halflength,\n        )\n    end\n\n    topology = BrickTopology(\n        mpicomm,\n        brickrange;\n        periodicity = ntuple(_ -> true, dims),\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorder,\n    )\n\n    problem =\n        AtmosProblem(boundaryconditions = (), init_state_prognostic = setup)\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = IsentropicVortexReferenceState{FT}(setup),\n        turbulence = ConstantDynamicViscosity(FT(0)),\n        moisture = DryModel(),\n    )\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        problem = problem,\n        orientation = NoOrientation(),\n        source = (),\n    )\n    # The linear model has the fast time scales\n    fast_model = AtmosAcousticLinearModel(model)\n\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n    fast_dg = DGModel(\n        fast_model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient();\n        state_auxiliary = dg.state_auxiliary,\n    )\n    slow_dg = remainder_DGModel(dg, (fast_dg,))\n\n    timeend = FT(2 * setup.domain_halflength / setup.translation_speed)\n    # determine the slow time step\n    elementsize = minimum(step.(brickrange))\n    slow_dt =\n        2 * elementsize / soundspeed_air(param_set, setup.T∞) /\n        polynomialorder^2\n    nsteps = ceil(Int, timeend / slow_dt)\n    slow_dt = timeend / nsteps\n\n    # arbitrary and not needed for stability, just for testing\n    fast_dt = slow_dt / 3\n\n    Q = init_ode_state(dg, FT(0), setup)\n\n    fastsolver = LSRK144NiegemannDiehlBusch(fast_dg, Q; dt = fast_dt)\n\n    ode_solver = mrigark_method(slow_dg, fastsolver, Q, dt = slow_dt)\n\n    eng0 = norm(Q)\n    dims == 2 && (numelems = (numelems..., 0))\n    @info @sprintf \"\"\"Starting refinement level %d\n                      numelems  = (%d, %d, %d)\n                      slow_dt   = %.16e\n                      fast_dt   = %.16e\n                      norm(Q₀)  = %.16e\n                      \"\"\" level numelems... slow_dt fast_dt eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            runtime = Dates.format(\n                convert(DateTime, now() - starttime[]),\n                dateformat\"HH:MM:SS\",\n            )\n            @info @sprintf \"\"\"Update\n                              simtime = %.16e\n                              runtime = %s\n                              norm(Q) = %.16e\n                              \"\"\" gettime(ode_solver) runtime energy\n        end\n    end\n    callbacks = (cbinfo,)\n\n    if output_vtk\n        # create vtk dir\n        vtkdir =\n            \"vtk_isentropicvortex_mrigark\" *\n            \"_poly$(polynomialorder)_dims$(dims)_$(ArrayType)_$(FT)\" *\n            \"_$(FastMethod)_level$(level)\"\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # output initial step\n        do_output(mpicomm, vtkdir, vtkstep, dg, Q, Q, model)\n\n        # setup the output callback\n        outputtime = timeend\n        cbvtk = EveryXSimulationSteps(floor(outputtime / slow_dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(ode_solver))\n            do_output(mpicomm, vtkdir, vtkstep, dg, Q, Qe, model)\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(Q, ode_solver; timeend = timeend, callbacks = callbacks)\n\n    # final statistics\n    Qe = init_ode_state(dg, timeend)\n    engf = norm(Q)\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished refinement level %d\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" level engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nfunction do_output(\n    mpicomm,\n    vtkdir,\n    vtkstep,\n    dg,\n    Q,\n    Qe,\n    model,\n    testname = \"isentropicvortex_mrigark\",\n)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dg, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/DGMethods/Euler/isentropicvortex_mrigark_implicit.jl",
    "content": "using ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Mesh.Geometry\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Orientations\nusing ClimateMachine.SystemSolvers\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.VTK\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: kappa_d\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing MPI, Logging, StaticArrays, LinearAlgebra, Printf, Dates, Test\n\ninclude(\"isentropicvortex_setup.jl\")\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nconst output_vtk = false\n\nfunction main()\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    numlevels = integration_testing ? 4 : 1\n\n    expected_error = Dict()\n    expected_error[Float64, MRIGARKIRK21aSandu, 1] = 2.3236071337679274e+01\n    expected_error[Float64, MRIGARKIRK21aSandu, 2] = 5.2652585224989430e+00\n    expected_error[Float64, MRIGARKIRK21aSandu, 3] = 1.2100430848052603e-01\n    expected_error[Float64, MRIGARKIRK21aSandu, 4] = 2.1974838909870273e-03\n\n    expected_error[Float64, MRIGARKESDIRK34aSandu, 1] = 2.3235626679098608e+01\n    expected_error[Float64, MRIGARKESDIRK34aSandu, 2] = 5.2672845223341218e+00\n    expected_error[Float64, MRIGARKESDIRK34aSandu, 3] = 1.2097276468825705e-01\n    expected_error[Float64, MRIGARKESDIRK34aSandu, 4] = 2.0920468129065205e-03\n\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64,), dims in 2\n            for mrigark_method in (MRIGARKIRK21aSandu, MRIGARKESDIRK34aSandu)\n                @info @sprintf \"\"\"Configuration\n                                  ArrayType      = %s\n                                  mrigark_method = %s\n                                  FT             = %s\n                                  dims           = %d\n                                  \"\"\" ArrayType \"$mrigark_method\" \"$FT\" dims\n\n                setup = IsentropicVortexSetup{FT}()\n                errors = Vector{FT}(undef, numlevels)\n\n                for level in 1:numlevels\n                    numelems =\n                        ntuple(dim -> dim == 3 ? 1 : 2^(level - 1) * 5, dims)\n                    errors[level] = test_run(\n                        mpicomm,\n                        ArrayType,\n                        polynomialorder,\n                        numelems,\n                        setup,\n                        mrigark_method,\n                        FT,\n                        dims,\n                        level,\n                    )\n\n                    @test errors[level] ≈\n                          expected_error[FT, mrigark_method, level]\n                end\n\n                rates = @. log2(\n                    first(errors[1:(numlevels - 1)]) /\n                    first(errors[2:numlevels]),\n                )\n                numlevels > 1 && @info \"Convergence rates\\n\" * join(\n                    [\n                        \"rate for levels $l → $(l + 1) = $(rates[l])\"\n                        for l in 1:(numlevels - 1)\n                    ],\n                    \"\\n\",\n                )\n            end\n        end\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    polynomialorder,\n    numelems,\n    setup,\n    mrigark_method,\n    FT,\n    dims,\n    level,\n)\n    brickrange = ntuple(dims) do dim\n        range(\n            -setup.domain_halflength;\n            length = numelems[dim] + 1,\n            stop = setup.domain_halflength,\n        )\n    end\n\n    topology = BrickTopology(\n        mpicomm,\n        brickrange;\n        periodicity = ntuple(_ -> true, dims),\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorder,\n    )\n\n    problem =\n        AtmosProblem(boundaryconditions = (), init_state_prognostic = setup)\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = IsentropicVortexReferenceState{FT}(setup),\n        turbulence = ConstantDynamicViscosity(FT(0)),\n        moisture = DryModel(),\n    )\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        problem = problem,\n        orientation = NoOrientation(),\n        source = (),\n    )\n    # This is a bad idea; this test is just testing how\n    # implicit GARK composes with explicit methods\n    # The linear model has the fast time scales but will be\n    # treated implicitly (outer solver)\n    slow_model = AtmosAcousticLinearModel(model)\n\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n    slow_dg = DGModel(\n        slow_model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient();\n        state_auxiliary = dg.state_auxiliary,\n    )\n    fast_dg = remainder_DGModel(dg, (slow_dg,))\n\n    timeend = FT(2 * setup.domain_halflength / setup.translation_speed)\n\n    # determine the time step\n    elementsize = minimum(step.(brickrange))\n    dt =\n        elementsize / soundspeed_air(param_set, setup.T∞) / polynomialorder^2 /\n        5\n    nsteps = ceil(Int, timeend / dt)\n    dt = timeend / nsteps\n\n    Q = init_ode_state(dg, FT(0))\n\n    fastsolver = LSRK54CarpenterKennedy(fast_dg, Q; dt = dt)\n\n    linearsolver = GeneralizedMinimalResidual(Q; M = 50, rtol = 1e-10)\n\n    ode_solver = mrigark_method(\n        slow_dg,\n        LinearBackwardEulerSolver(linearsolver; isadjustable = true),\n        fastsolver,\n        Q;\n        dt = dt,\n        t0 = 0,\n    )\n\n    eng0 = norm(Q)\n    dims == 2 && (numelems = (numelems..., 0))\n    @info @sprintf \"\"\"Starting refinement level %d\n                      numelems  = (%d, %d, %d)\n                      dt        = %.16e\n                      norm(Q₀)  = %.16e\n                      \"\"\" level numelems... dt eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            runtime = Dates.format(\n                convert(DateTime, now() - starttime[]),\n                dateformat\"HH:MM:SS\",\n            )\n            @info @sprintf \"\"\"Update\n                              simtime = %.16e\n                              runtime = %s\n                              norm(Q) = %.16e\n                              \"\"\" gettime(ode_solver) runtime energy\n        end\n    end\n    callbacks = (cbinfo,)\n\n    if output_vtk\n        # create vtk dir\n        vtkdir =\n            \"vtk_isentropicvortex_mrigark\" *\n            \"_poly$(polynomialorder)_dims$(dims)_$(ArrayType)_$(FT)\" *\n            \"_$(FastMethod)_level$(level)\"\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # output initial step\n        do_output(mpicomm, vtkdir, vtkstep, dg, Q, Q, model)\n\n        # setup the output callback\n        outputtime = timeend\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(ode_solver))\n            do_output(mpicomm, vtkdir, vtkstep, dg, Q, Qe, model)\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(Q, ode_solver; timeend = timeend, callbacks = callbacks)\n\n    # final statistics\n    Qe = init_ode_state(dg, timeend)\n    engf = norm(Q)\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished refinement level %d\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" level engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nfunction do_output(\n    mpicomm,\n    vtkdir,\n    vtkstep,\n    dg,\n    Q,\n    Qe,\n    model,\n    testname = \"isentropicvortex_mrigark\",\n)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dg, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/DGMethods/Euler/isentropicvortex_multirate.jl",
    "content": "using ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Mesh.Geometry\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Orientations\nusing ClimateMachine.SystemSolvers\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.VTK\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: kappa_d\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing MPI, Logging, StaticArrays, LinearAlgebra, Printf, Dates, Test\n\ninclude(\"isentropicvortex_setup.jl\")\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nconst output_vtk = false\n\nfunction main()\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    numlevels = integration_testing ? 4 : 1\n\n    expected_error = Dict()\n    expected_error[Float64, SSPRK33ShuOsher, 1] = 2.3222373077778794e+01\n    expected_error[Float64, SSPRK33ShuOsher, 2] = 5.2782503174265516e+00\n    expected_error[Float64, SSPRK33ShuOsher, 3] = 1.2281763287878383e-01\n    expected_error[Float64, SSPRK33ShuOsher, 4] = 2.3761870907666096e-03\n\n    expected_error[Float64, ARK2GiraldoKellyConstantinescu, 1] =\n        2.3245216640111998e+01\n    expected_error[Float64, ARK2GiraldoKellyConstantinescu, 2] =\n        5.2626584944153949e+00\n    expected_error[Float64, ARK2GiraldoKellyConstantinescu, 3] =\n        1.2324230746483673e-01\n    expected_error[Float64, ARK2GiraldoKellyConstantinescu, 4] =\n        3.8777995619211627e-03\n\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64,), dims in 2\n            for FastMethod in (SSPRK33ShuOsher, ARK2GiraldoKellyConstantinescu)\n                @info @sprintf \"\"\"Configuration\n                                  ArrayType  = %s\n                                  FastMethod = %s\n                                  FT     = %s\n                                  dims       = %d\n                                  \"\"\" ArrayType \"$FastMethod\" \"$FT\" dims\n\n                setup = IsentropicVortexSetup{FT}()\n                errors = Vector{FT}(undef, numlevels)\n\n                for level in 1:numlevels\n                    numelems =\n                        ntuple(dim -> dim == 3 ? 1 : 2^(level - 1) * 5, dims)\n                    errors[level] = test_run(\n                        mpicomm,\n                        ArrayType,\n                        polynomialorder,\n                        numelems,\n                        setup,\n                        FT,\n                        FastMethod,\n                        dims,\n                        level,\n                    )\n\n                    @test errors[level] ≈ expected_error[FT, FastMethod, level]\n                end\n\n                rates = @. log2(\n                    first(errors[1:(numlevels - 1)]) /\n                    first(errors[2:numlevels]),\n                )\n                numlevels > 1 && @info \"Convergence rates\\n\" * join(\n                    [\n                        \"rate for levels $l → $(l + 1) = $(rates[l])\"\n                        for l in 1:(numlevels - 1)\n                    ],\n                    \"\\n\",\n                )\n            end\n        end\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    polynomialorder,\n    numelems,\n    setup,\n    FT,\n    FastMethod,\n    dims,\n    level,\n)\n    brickrange = ntuple(dims) do dim\n        range(\n            -setup.domain_halflength;\n            length = numelems[dim] + 1,\n            stop = setup.domain_halflength,\n        )\n    end\n\n    topology = BrickTopology(\n        mpicomm,\n        brickrange;\n        periodicity = ntuple(_ -> true, dims),\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorder,\n    )\n\n    problem =\n        AtmosProblem(boundaryconditions = (), init_state_prognostic = setup)\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = IsentropicVortexReferenceState{FT}(setup),\n        turbulence = ConstantDynamicViscosity(FT(0)),\n        moisture = DryModel(),\n    )\n\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        problem = problem,\n        orientation = NoOrientation(),\n        source = (),\n    )\n    # The linear model has the fast time scales\n    fast_model = AtmosAcousticLinearModel(model)\n    # The nonlinear model has the slow time scales\n\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n    fast_dg = DGModel(\n        fast_model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient();\n        state_auxiliary = dg.state_auxiliary,\n    )\n    slow_dg = remainder_DGModel(dg, (fast_dg,))\n\n    timeend = FT(2 * setup.domain_halflength / setup.translation_speed)\n    # determine the slow time step\n    elementsize = minimum(step.(brickrange))\n    slow_dt =\n        8 * elementsize / soundspeed_air(param_set, setup.T∞) /\n        polynomialorder^2\n    nsteps = ceil(Int, timeend / slow_dt)\n    slow_dt = timeend / nsteps\n\n    # arbitrary and not needed for stability, just for testing\n    fast_dt = slow_dt / 3\n\n    Q = init_ode_state(dg, FT(0), setup)\n\n    slow_ode_solver = LSRK144NiegemannDiehlBusch(slow_dg, Q; dt = slow_dt)\n\n    # check if FastMethod is ARK, is there a better way ?\n    if FastMethod == ARK2GiraldoKellyConstantinescu\n\n        linearsolver = GeneralizedMinimalResidual(Q; M = 10, rtol = 1e-10)\n        # splitting the fast part into full and linear but the fast part\n        # is already linear so full_dg == linear_dg == fast_dg\n        fast_ode_solver = FastMethod(\n            fast_dg,\n            fast_dg,\n            LinearBackwardEulerSolver(linearsolver; isadjustable = true),\n            Q;\n            dt = fast_dt,\n            paperversion = true,\n        )\n    else\n        fast_ode_solver = FastMethod(fast_dg, Q; dt = fast_dt)\n    end\n\n    ode_solver = MultirateRungeKutta((slow_ode_solver, fast_ode_solver))\n\n    eng0 = norm(Q)\n    dims == 2 && (numelems = (numelems..., 0))\n    @info @sprintf \"\"\"Starting refinement level %d\n                      numelems  = (%d, %d, %d)\n                      slow_dt   = %.16e\n                      fast_dt   = %.16e\n                      norm(Q₀)  = %.16e\n                      \"\"\" level numelems... slow_dt fast_dt eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            runtime = Dates.format(\n                convert(DateTime, now() - starttime[]),\n                dateformat\"HH:MM:SS\",\n            )\n            @info @sprintf \"\"\"Update\n                              simtime = %.16e\n                              runtime = %s\n                              norm(Q) = %.16e\n                              \"\"\" gettime(ode_solver) runtime energy\n        end\n    end\n    callbacks = (cbinfo,)\n\n    if output_vtk\n        # create vtk dir\n        vtkdir =\n            \"vtk_isentropicvortex_multirate\" *\n            \"_poly$(polynomialorder)_dims$(dims)_$(ArrayType)_$(FT)\" *\n            \"_$(FastMethod)_level$(level)\"\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # output initial step\n        do_output(mpicomm, vtkdir, vtkstep, dg, Q, Q, model)\n\n        # setup the output callback\n        outputtime = timeend\n        cbvtk = EveryXSimulationSteps(floor(outputtime / slow_dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(ode_solver), setup)\n            do_output(mpicomm, vtkdir, vtkstep, dg, Q, Qe, model)\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(Q, ode_solver; timeend = timeend, callbacks = callbacks)\n\n    # final statistics\n    Qe = init_ode_state(dg, timeend, setup)\n    engf = norm(Q)\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished refinement level %d\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" level engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nfunction do_output(\n    mpicomm,\n    vtkdir,\n    vtkstep,\n    dg,\n    Q,\n    Qe,\n    model,\n    testname = \"isentropicvortex_mutirate\",\n)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dg, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/DGMethods/Euler/isentropicvortex_setup.jl",
    "content": "import ClimateMachine.Atmos: atmos_init_aux!, vars_state\n\nBase.@kwdef struct IsentropicVortexSetup{FT}\n    p∞::FT = 10^5\n    T∞::FT = 300\n    ρ∞::FT = air_density(param_set, FT(T∞), FT(p∞))\n    translation_speed::FT = 150\n    translation_angle::FT = pi / 4\n    vortex_speed::FT = 50\n    vortex_radius::FT = 1 // 200\n    domain_halflength::FT = 1 // 20\nend\n\nfunction (setup::IsentropicVortexSetup)(\n    problem,\n    bl,\n    state,\n    aux,\n    localgeo,\n    t,\n    args...,\n)\n    FT = eltype(state)\n    x = MVector(localgeo.coord)\n    param_set = parameter_set(bl)\n\n    ρ∞ = setup.ρ∞\n    p∞ = setup.p∞\n    T∞ = setup.T∞\n    translation_speed = setup.translation_speed\n    α = setup.translation_angle\n    vortex_speed = setup.vortex_speed\n    R = setup.vortex_radius\n    L = setup.domain_halflength\n\n    u∞ = SVector(translation_speed * cos(α), translation_speed * sin(α), 0)\n\n    x .-= u∞ * t\n    # make the function periodic\n    x .-= floor.((x .+ L) / 2L) * 2L\n\n    @inbounds begin\n        r = sqrt(x[1]^2 + x[2]^2)\n        δu_x = -vortex_speed * x[2] / R * exp(-(r / R)^2 / 2)\n        δu_y = vortex_speed * x[1] / R * exp(-(r / R)^2 / 2)\n    end\n    u = u∞ .+ SVector(δu_x, δu_y, 0)\n\n    _kappa_d::FT = kappa_d(param_set)\n    T = T∞ * (1 - _kappa_d * vortex_speed^2 / 2 * ρ∞ / p∞ * exp(-(r / R)^2))\n    # adiabatic/isentropic relation\n    p = p∞ * (T / T∞)^(FT(1) / _kappa_d)\n    ρ = air_density(param_set, T, p)\n\n    state.ρ = ρ\n    state.ρu = ρ * u\n    e_kin = u' * u / 2\n    state.energy.ρe = ρ * total_energy(param_set, e_kin, FT(0), T)\n    if !(moisture_model(bl) isa DryModel)\n        state.moisture.ρq_tot = FT(0)\n    end\nend\n\nstruct IsentropicVortexReferenceState{FT} <: ReferenceState\n    setup::IsentropicVortexSetup{FT}\nend\nvars_state(::IsentropicVortexReferenceState, ::Auxiliary, FT) =\n    @vars(ρ::FT, ρe::FT, p::FT, T::FT)\nfunction atmos_init_aux!(\n    atmos::AtmosModel,\n    m::IsentropicVortexReferenceState,\n    state_auxiliary::MPIStateArray,\n    grid,\n    direction,\n)\n    init_state_auxiliary!(\n        atmos,\n        (args...) -> init_vortex_ref_state!(m, args...),\n        state_auxiliary,\n        grid,\n        direction,\n    )\nend\nfunction init_vortex_ref_state!(\n    m::IsentropicVortexReferenceState,\n    atmos::AtmosModel,\n    aux::Vars,\n    tmp::Vars,\n    geom::LocalGeometry,\n)\n    setup = m.setup\n    ρ∞ = setup.ρ∞\n    p∞ = setup.p∞\n    T∞ = setup.T∞\n    param_set = parameter_set(atmos)\n    aux.ref_state.ρ = ρ∞\n    aux.ref_state.p = p∞\n    aux.ref_state.T = T∞\n    aux.ref_state.ρe = ρ∞ * internal_energy(param_set, T∞)\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/advection_diffusion_model.jl",
    "content": "using StaticArrays\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw,\n    Prognostic,\n    Auxiliary,\n    Gradient,\n    GradientFlux,\n    GradientLaplacian,\n    Hyperdiffusive\n\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    number_states,\n    flux_first_order!,\n    flux_second_order!,\n    source!,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    nodal_init_state_auxiliary!,\n    update_auxiliary_state!,\n    init_state_prognostic!,\n    boundary_conditions,\n    boundary_state!,\n    wavespeed,\n    transform_post_gradient_laplacian!\n\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\nusing ClimateMachine.DGMethods: SpaceDiscretization\nusing ClimateMachine.DGMethods.NumericalFluxes:\n    NumericalFluxFirstOrder,\n    NumericalFluxSecondOrder,\n    NumericalFluxGradient,\n    CentralNumericalFluxDivergence,\n    CentralNumericalFluxHigherOrder\n\nimport ClimateMachine.DGMethods.NumericalFluxes:\n    numerical_flux_first_order!, boundary_flux_second_order!\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nstruct Advection{N} <: BalanceLaw end\nstruct NoAdvection <: BalanceLaw end\n\nstruct Diffusion{N} <: BalanceLaw end\nstruct NoDiffusion <: BalanceLaw end\n\nstruct HyperDiffusion{N} <: BalanceLaw end\nstruct NoHyperDiffusion <: BalanceLaw end\n\nabstract type AdvectionDiffusionProblem end\n\n# Boundary condition types\n\n# boundary condition for operator of order O\n# O = 0 -> state BC (Dirichlet)\n# O = 1 -> gradient BC (Neumann)\n# O = 2 -> laplacian BC\n# O = 3 -> gradient laplacian BC\nabstract type AbstractBC{O} end\nstruct HomogeneousBC{O} <: AbstractBC{O} end\nstruct InhomogeneousBC{O} <: AbstractBC{O} end\n\nany_isa(bcs::AbstractBC, bc) = bcs isa bc\nany_isa(bcs::Tuple, bc) = mapreduce(x -> x isa bc, |, bcs)\n\n\"\"\"\n    AdvectionDiffusion{N} <: BalanceLaw\n\nA balance law describing a system of `N` advection-diffusion-hyperdiffusion\nequations:\n\n```\n∂ρ\n-- = - ∇ • (u ρ - σ + η)\n∂t\n\nσ = D ∇ ρ\nη = H ∇ Δρ\n```\nWhere\n\n - `ρ` is the solution vector\n - `u` is the advection velocity\n - `σ` is the DG diffusion auxiliary variable\n - `D` is the diffusion tensor\n - `η` is the DG hyperdiffusion auxiliary variable\n - `H` is the hyperdiffusion tensor\n\"\"\"\nstruct AdvectionDiffusion{N, dim, P, fluxBC, A, D, HD, BC} <: BalanceLaw\n    problem::P\n    advection::A\n    diffusion::D\n    hyperdiffusion::HD\n    boundary_conditions::BC\n\n    function AdvectionDiffusion{dim}(\n        problem::P,\n        boundary_conditions::BC = ();\n        num_equations = 1,\n        flux_bc = false,\n        advection::Bool = true,\n        diffusion::Bool = true,\n        hyperdiffusion::Bool = false,\n    ) where {dim, P <: AdvectionDiffusionProblem, BC}\n        N = num_equations\n        adv = advection ? Advection{N}() : NoAdvection()\n        A = typeof(adv)\n        diff = diffusion ? Diffusion{N}() : NoDiffusion()\n        D = typeof(diff)\n        hyperdiff = hyperdiffusion ? HyperDiffusion{N}() : NoHyperDiffusion()\n        HD = typeof(hyperdiff)\n        new{N, dim, P, flux_bc, A, D, HD, BC}(\n            problem,\n            adv,\n            diff,\n            hyperdiff,\n            boundary_conditions,\n        )\n    end\nend\n\n# Auxiliary variables, always store\n# `coord` coordinate points (needed for BCs)\nfunction vars_state(m::AdvectionDiffusion, st::Auxiliary, FT)\n    @vars begin\n        coord::SVector{3, FT}\n        advection::vars_state(m.advection, st, FT)\n        diffusion::vars_state(m.diffusion, st, FT)\n        hyperdiffusion::vars_state(m.hyperdiffusion, st, FT)\n    end\nend\n\n#   `u` advection velocity\nvars_state(::Advection{1}, ::Auxiliary, FT) = @vars(u::SVector{3, FT})\nvars_state(::Advection{N}, ::Auxiliary, FT) where {N} =\n    @vars(u::SMatrix{3, N, FT, 3N})\n#   `D` diffusion tensor\nvars_state(::Diffusion{1}, ::Auxiliary, FT) = @vars(D::SMatrix{3, 3, FT, 9})\nvars_state(::Diffusion{N}, ::Auxiliary, FT) where {N} =\n    @vars(D::SArray{Tuple{3, 3, N}, FT, 3, 9N})\n#   `H` hyperdiffusion tensor\nvars_state(::HyperDiffusion{1}, ::Auxiliary, FT) =\n    @vars(H::SMatrix{3, 3, FT, 9})\nvars_state(::HyperDiffusion{N}, ::Auxiliary, FT) where {N} =\n    @vars(H::SArray{Tuple{3, 3, N}, FT, 3, 9N})\n\n# Density `ρ` is the only state\nvars_state(::AdvectionDiffusion{1}, ::Prognostic, FT) = @vars(ρ::FT)\nvars_state(::AdvectionDiffusion{N}, ::Prognostic, FT) where {N} =\n    @vars(ρ::SVector{N, FT})\n\nfunction vars_state(m::AdvectionDiffusion{N}, ::Gradient, FT) where {N}\n    # For pure advection we don't need gradients\n    if m.diffusion isa NoDiffusion && m.hyperdiffusion isa NoHyperDiffusion\n        return @vars()\n    else  # Take the gradient of density\n        return N == 1 ? @vars(ρ::FT) : @vars(ρ::SVector{N, FT})\n    end\nend\n\n# Take the gradient of laplacian of density ρ\nvars_state(::HyperDiffusion{1}, ::GradientLaplacian, FT) = @vars(ρ::FT)\nvars_state(::HyperDiffusion{N}, ::GradientLaplacian, FT) where {N} =\n    @vars(ρ::SVector{N, FT})\nvars_state(m::AdvectionDiffusion, st::GradientLaplacian, FT) =\n    vars_state(m.hyperdiffusion, st, FT)\n\n# The DG diffusion auxiliary variable: σ = D ∇ρ\nvars_state(::Diffusion{1}, ::GradientFlux, FT) = @vars(σ::SVector{3, FT})\nvars_state(::Diffusion{N}, ::GradientFlux, FT) where {N} =\n    @vars(σ::SMatrix{3, N, FT, 3N})\nvars_state(m::AdvectionDiffusion, st::GradientFlux, FT) =\n    vars_state(m.diffusion, st, FT)\n\n# The DG hyperdiffusion auxiliary variable: η = H ∇ Δρ\nvars_state(::HyperDiffusion{1}, ::Hyperdiffusive, FT) = @vars(η::SVector{3, FT})\nvars_state(::HyperDiffusion{N}, ::Hyperdiffusive, FT) where {N} =\n    @vars(η::SMatrix{3, N, FT, 3N})\nvars_state(m::AdvectionDiffusion, st::Hyperdiffusive, FT) =\n    vars_state(m.hyperdiffusion, st, FT)\n\n\"\"\"\n    flux_first_order!(::Advection, flux::Grad, state::Vars, aux::Vars)\n\nComputes non-diffusive flux `F_adv = u ρ` where\n\n - `u` is the advection velocity\n - `ρ` is the advected quantity\n\"\"\"\nfunction flux_first_order!(\n    ::Advection{N},\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n) where {N}\n    ρ = state.ρ\n    u = aux.advection.u\n    flux.ρ += u .* ρ'\nend\nflux_first_order!(::NoAdvection, flux::Grad, state::Vars, aux::Vars) = nothing\nflux_first_order!(\n    m::AdvectionDiffusion,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n) = flux_first_order!(m.advection, flux, state, aux)\n\n\"\"\"\n    flux_second_order!(::Diffusion, flux::Grad, auxDG::Vars)\n\nComputes diffusive flux `F_diff = -σ` where:\n\n - `σ` is DG diffusion auxiliary variable (`σ = D ∇ ρ`\n    with `D` being the diffusion tensor)\n\"\"\"\nfunction flux_second_order!(::Diffusion, flux::Grad, auxDG::Vars)\n    σ = auxDG.σ\n    flux.ρ += -σ\nend\nflux_second_order!(::NoDiffusion, flux::Grad, auxDG::Vars) = nothing\n\n\"\"\"\n    flux_second_order!(::HyperDiffusion, flux::Grad, auxHDG::Vars)\n\nComputes hyperdiffusive flux `F_hyperdiff = η` where:\n\n - `η` is DG hyperdiffusion auxiliary variable (`η = H ∇ Δρ`\n    with `H` being the hyperdiffusion tensor)\n\"\"\"\nfunction flux_second_order!(::HyperDiffusion, flux::Grad, auxHDG::Vars)\n    η = auxHDG.η\n    flux.ρ += η\nend\nflux_second_order!(::NoHyperDiffusion, flux::Grad, auxHDG::Vars) = nothing\n\nfunction flux_second_order!(\n    m::AdvectionDiffusion,\n    flux::Grad,\n    state::Vars,\n    auxDG::Vars,\n    auxHDG::Vars,\n    aux::Vars,\n    t::Real,\n)\n    flux_second_order!(m.diffusion, flux, auxDG)\n    flux_second_order!(m.hyperdiffusion, flux, auxHDG)\nend\n\n\n\"\"\"\n    compute_gradient_argument!(m::AdvectionDiffusion, transform::Vars, state::Vars,\n                   aux::Vars, t::Real)\n\nSet the variable to take the gradient of (`ρ` in this case)\n\"\"\"\nfunction compute_gradient_argument!(\n    m::AdvectionDiffusion,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    transform.ρ = state.ρ\nend\n\n\"\"\"\n    compute_gradient_flux!(::Diffusion, auxDG::Vars, gradvars::Grad, aux::Vars)\n\nComputes the DG diffusion auxiliary variable `σ = D ∇ ρ` where `D` is\nthe diffusion tensor.\n\"\"\"\nfunction compute_gradient_flux!(\n    ::Diffusion{N},\n    auxDG::Vars,\n    gradvars::Grad,\n    aux::Vars,\n) where {N}\n    ∇ρ = gradvars.ρ\n    D = aux.diffusion.D\n    if N == 1\n        auxDG.σ = D * ∇ρ\n    else\n        auxDG.σ = hcat(ntuple(n -> D[:, :, n] * ∇ρ[:, n], Val(N))...)\n    end\nend\ncompute_gradient_flux!(::NoDiffusion, auxDG::Vars, gradvars::Grad, aux::Vars) =\n    nothing\ncompute_gradient_flux!(\n    m::AdvectionDiffusion,\n    auxDG::Vars,\n    gradvars::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) = compute_gradient_flux!(m.diffusion, auxDG, gradvars, aux)\n\n\n\"\"\"\n    transform_post_gradient_laplacian!(::AdvectionDiffusion, auxHDG::Vars,\n        gradvars::Grad, state::Vars, aux::Vars, t::Real)\n\nComputes the DG hyperdiffusion auxiliary variable `η = H ∇ Δρ` where `H` is\nthe hyperdiffusion tensor.\n\"\"\"\nfunction transform_post_gradient_laplacian!(\n    m::AdvectionDiffusion{N},\n    auxHDG::Vars,\n    gradvars::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) where {N}\n    ∇Δρ = gradvars.ρ\n    H = aux.hyperdiffusion.H\n    if N == 1\n        auxHDG.η = H * ∇Δρ\n    else\n        auxHDG.η = hcat(ntuple(n -> H[:, :, n] * ∇Δρ[:, n], Val(N))...)\n    end\nend\n\n\"\"\"\n    source!(m::AdvectionDiffusion, _...)\n\nThere is no source in the advection-diffusion model\n\"\"\"\nsource!(m::AdvectionDiffusion, _...) = nothing\n\n\"\"\"\n    wavespeed(m::AdvectionDiffusion, nM, state::Vars, aux::Vars, t::Real)\n\nWavespeed with respect to vector `nM`\n\"\"\"\nwavespeed(\n    m::AdvectionDiffusion,\n    nM,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n) = wavespeed(m.advection, nM, aux)\nfunction wavespeed(::Advection{N}, nM, aux::Vars) where {N}\n    u = aux.advection.u\n    if N == 1\n        abs(nM' * u)\n    else\n        SVector(ntuple(n -> abs(nM' * u[:, n]), Val(N)))\n    end\nend\nwavespeed(::NoAdvection, nM, aux::Vars) = 0\n\nfunction nodal_init_state_auxiliary!(\n    m::AdvectionDiffusion,\n    aux::Vars,\n    tmp::Vars,\n    geom::LocalGeometry,\n)\n    aux.coord = geom.coord\n    init_velocity_diffusion!(m.problem, aux, geom)\nend\n\nhas_variable_coefficients(::AdvectionDiffusionProblem) = false\nfunction update_auxiliary_state!(\n    spacedisc::SpaceDiscretization,\n    m::AdvectionDiffusion,\n    Q::MPIStateArray,\n    t::Real,\n    elems::UnitRange,\n)\n    if has_variable_coefficients(m.problem)\n        update_auxiliary_state!(spacedisc, m, Q, t, elems) do m, state, aux, t\n            update_velocity_diffusion!(m.problem, m, state, aux, t)\n        end\n        return true\n    end\n    return false\nend\n\nfunction init_state_prognostic!(\n    m::AdvectionDiffusion,\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t::Real,\n)\n    initial_condition!(m.problem, state, aux, localgeo, t)\nend\n\n\"\"\"\n    inhomogeneous_data!(::Val{O}, problem, data, aux, x, t)\n\nPrescribes `problem` boundary condition data for an operator of order `O`\n\"\"\"\nfunction inhomogeneous_data! end\n\nboundary_conditions(m::AdvectionDiffusion) = m.boundary_conditions\nfunction boundary_state!(\n    nf,\n    bcs,\n    m::AdvectionDiffusion{N},\n    stateP::Vars,\n    auxP::Vars,\n    nM,\n    stateM::Vars,\n    auxM::Vars,\n    t,\n    _...,\n) where {N}\n    if any_isa(bcs, InhomogeneousBC{0}) # Dirichlet\n        inhomogeneous_data!(\n            Val(0),\n            m.problem,\n            stateP,\n            auxP,\n            (coord = auxP.coord,),\n            t,\n        )\n    elseif any_isa(bcs, AbstractBC{1}) # Neumann\n        stateP.ρ = stateM.ρ\n    elseif any_isa(bcs, HomogeneousBC{0}) # zero Dirichlet\n        stateP.ρ = N == 1 ? 0 : zeros(typeof(stateP.ρ))\n    end\nend\n\nfunction boundary_state!(\n    nf::CentralNumericalFluxSecondOrder,\n    bcs,\n    m::AdvectionDiffusion,\n    state⁺::Vars,\n    diff⁺::Vars,\n    hyperdiff⁺::Vars,\n    aux⁺::Vars,\n    n⁻::SVector,\n    state⁻::Vars,\n    diff⁻::Vars,\n    hyperdiff⁻::Vars,\n    aux⁻::Vars,\n    t,\n    _...,\n)\n    if m.diffusion isa NoDiffusion && m.hyperdiffusion isa NoHyperDiffusion\n        return nothing\n    end\n\n    if m.diffusion isa Diffusion\n        if any_isa(bcs, AbstractBC{0}) # Dirchlet\n            # Just use the minus side values since Dirchlet\n            diff⁺.σ = diff⁻.σ\n        elseif any_isa(bcs, InhomogeneousBC{1}) # Neumann with data\n            FT = eltype(diff⁺)\n            ngrad = number_states(m, Gradient())\n            ∇state = Grad{vars_state(m, Gradient(), FT)}(similar(\n                parent(diff⁺),\n                Size(3, ngrad),\n            ))\n            # Get analytic gradient\n            inhomogeneous_data!(Val(1), m.problem, ∇state, aux⁻, aux⁻.coord, t)\n            compute_gradient_flux!(m.diffusion, diff⁺, ∇state, aux⁻)\n            # compute the diffusive flux using the boundary state\n        elseif any_isa(bcs, HomogeneousBC{1}) # zero Neumann\n            FT = eltype(diff⁺)\n            ngrad = number_states(m, Gradient())\n            ∇state = Grad{vars_state(m, Gradient(), FT)}(similar(\n                parent(diff⁺),\n                Size(3, ngrad),\n            ))\n            # Get analytic gradient\n            ∇state.ρ = zeros(typeof(∇state.ρ))\n            # convert to auxDG variables\n            compute_gradient_flux!(m.diffusion, diff⁺, ∇state, aux⁻)\n        end\n    end\n\n    if m.hyperdiffusion isa HyperDiffusion\n        if any_isa(bcs, InhomogeneousBC{3})\n            FT = eltype(hyperdiff⁺)\n            ngradlap = number_states(m, GradientLaplacian())\n            ∇Δstate = Grad{vars_state(m, GradientLaplacian(), FT)}(similar(\n                parent(hyperdiff⁺),\n                Size(3, ngradlap),\n            ))\n            # Get analytic gradient of laplacian\n            inhomogeneous_data!(Val(3), m.problem, ∇Δstate, aux⁻, aux⁻.coord, t)\n            transform_post_gradient_laplacian!(\n                m,\n                hyperdiff⁺,\n                ∇Δstate,\n                state⁻,\n                aux⁻,\n                t,\n            )\n        elseif any_isa(bcs, HomogeneousBC{3})\n            FT = eltype(hyperdiff⁺)\n            ngradlap = number_states(m, GradientLaplacian())\n            ∇Δstate =\n                Grad{vars_state(m, GradientLaplacian(), FT)}(zeros(SMatrix{\n                    3,\n                    ngradlap,\n                    FT,\n                }))\n            transform_post_gradient_laplacian!(\n                m,\n                hyperdiff⁺,\n                ∇Δstate,\n                state⁻,\n                aux⁻,\n                t,\n            )\n        end\n    end\n    nothing\nend\n\nfunction boundary_flux_second_order!(\n    nf::CentralNumericalFluxSecondOrder,\n    bcs,\n    m::AdvectionDiffusion{N, dim, P, true},\n    F,\n    state⁺,\n    diff⁺,\n    hyperdiff⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    diff⁻,\n    hyperdiff⁻,\n    aux⁻,\n    t,\n    _...,\n) where {N, dim, P}\n    if m.diffusion isa NoDiffusion && m.hyperdiffusion isa NoHyperDiffusion\n        return nothing\n    end\n\n    # Default initialize flux to minus side\n    if any_isa(bcs, AbstractBC{0}) # Dirchlet\n        # Just use the minus side values since Dirchlet\n        flux_second_order!(m, F, state⁻, diff⁻, hyperdiff⁻, aux⁻, t)\n    elseif any_isa(bcs, InhomogeneousBC{1}) # Neumann data\n        FT = eltype(diff⁺)\n        ngrad = number_states(m, Gradient())\n        ∇state = Grad{vars_state(m, Gradient(), FT)}(similar(\n            parent(diff⁺),\n            Size(3, ngrad),\n        ))\n        # Get analytic gradient\n        inhomogeneous_data!(Val(1), m.problem, ∇state, aux⁻, aux⁻.coord, t)\n        # get the diffusion coefficient\n        D = aux⁻.diffusion.D\n        # exact the exact data\n        ∇ρ = ∇state.ρ\n        # set the flux\n        if N == 1\n            F.ρ = -D * ∇ρ\n        else\n            F.ρ = hcat(ntuple(n -> -D[:, :, n] * ∇ρ[:, n], Val(N))...)\n        end\n    elseif any_isa(bcs, HomogeneousBC{1}) # Zero Neumann\n        F.ρ = zeros(typeof(F.ρ))\n    end\n    nothing\nend\n\nfunction boundary_state!(\n    nf::CentralNumericalFluxDivergence,\n    bcs,\n    m::AdvectionDiffusion,\n    grad⁺::Grad,\n    aux⁺::Vars,\n    n⁻::SVector,\n    grad⁻::Grad,\n    aux⁻::Vars,\n    t,\n)\n    if m.hyperdiffusion isa NoHyperDiffusion\n        return nothing\n    end\n\n    if any_isa(bcs, InhomogeneousBC{1})\n        # Get analytic gradient\n        inhomogeneous_data!(Val(1), m.problem, grad⁺, aux⁻, aux⁻.coord, t)\n    elseif any_isa(bcs, HomogeneousBC{1})\n        grad⁺.ρ = zeros(typeof(grad⁺.ρ))\n    end\n    nothing\nend\n\nfunction boundary_state!(\n    ::CentralNumericalFluxHigherOrder,\n    bcs,\n    m::AdvectionDiffusion{N},\n    state⁺::Vars,\n    aux⁺::Vars,\n    lap⁺::Vars,\n    n⁻::SVector,\n    state⁻::Vars,\n    aux⁻::Vars,\n    lap⁻::Vars,\n    t,\n) where {N}\n    if m.hyperdiffusion isa NoHyperDiffusion\n        return nothing\n    end\n\n    if any_isa(bcs, InhomogeneousBC{2})\n        # Get analytic laplacian\n        inhomogeneous_data!(Val(2), m.problem, lap⁺, aux⁻, aux⁻.coord, t)\n    elseif any_isa(bcs, HomogeneousBC{2})\n        lap⁺.ρ = N == 1 ? 0 : zeros(typeof(lap⁺.ρ))\n    end\n    nothing\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/advection_diffusion_model_1dimex_bgmres.jl",
    "content": "using MPI\nusing ClimateMachine\nusing Logging\nusing Test\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.SystemSolvers: BatchedGeneralizedMinimalResidual\nusing ClimateMachine.ODESolvers\nusing LinearAlgebra\nusing Printf\nusing Dates\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing ClimateMachine.VTK: writevtk, writepvtu\n\nif !@isdefined integration_testing\n    if length(ARGS) > 0\n        const integration_testing = parse(Bool, ARGS[1])\n    else\n        const integration_testing = parse(\n            Bool,\n            lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n        )\n    end\nend\n\nconst output = parse(Bool, lowercase(get(ENV, \"JULIA_CLIMA_OUTPUT\", \"false\")))\n\ninclude(\"advection_diffusion_model.jl\")\n\nstruct Pseudo1D{n, α, β, μ, δ} <: AdvectionDiffusionProblem end\n\nfunction init_velocity_diffusion!(\n    ::Pseudo1D{n, α, β},\n    aux::Vars,\n    geom::LocalGeometry,\n) where {n, α, β}\n    # Direction of flow is n with magnitude α\n    aux.advection.u = α * n\n\n    # Diffusion of strength β in the n direction\n    aux.diffusion.D = β * n * n'\nend\n\nfunction initial_condition!(\n    ::Pseudo1D{n, α, β, μ, δ},\n    state,\n    aux,\n    localgeo,\n    t,\n) where {n, α, β, μ, δ}\n    ξn = dot(n, localgeo.coord)\n    # ξT = SVector(localgeo.coord) - ξn * n\n    state.ρ = exp(-(ξn - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\nend\ninhomogeneous_data!(::Val{0}, P::Pseudo1D, x...) = initial_condition!(P, x...)\nfunction inhomogeneous_data!(\n    ::Val{1},\n    ::Pseudo1D{n, α, β, μ, δ},\n    ∇state,\n    aux,\n    x,\n    t,\n) where {n, α, β, μ, δ}\n    ξn = dot(n, x)\n    ∇state.ρ =\n        -(\n            2n * (ξn - μ - α * t) / (4 * β * (δ + t)) *\n            exp(-(ξn - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\n        )\nend\n\nfunction do_output(mpicomm, vtkdir, vtkstep, dg, Q, Qe, model, testname)\n    ## Name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dg, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## Name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## Name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    dim,\n    topl,\n    N,\n    timeend,\n    FT,\n    dt,\n    n,\n    α,\n    β,\n    μ,\n    δ,\n    vtkdir,\n    outputtime,\n    linearsolvertype,\n    fluxBC,\n)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n    bcs = (\n        InhomogeneousBC{0}(),\n        InhomogeneousBC{1}(),\n        HomogeneousBC{0}(),\n        HomogeneousBC{1}(),\n    )\n    model = AdvectionDiffusion{dim}(\n        Pseudo1D{n, α, β, μ, δ}(),\n        bcs,\n        flux_bc = fluxBC,\n    )\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n        direction = EveryDirection(),\n    )\n\n    vdg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n        state_auxiliary = dg.state_auxiliary,\n        direction = VerticalDirection(),\n    )\n\n\n    Q = init_ode_state(dg, FT(0))\n\n    linearsolver = BatchedGeneralizedMinimalResidual(\n        dg,\n        Q;\n        atol = sqrt(eps(FT)) * 0.01,\n        rtol = sqrt(eps(FT)) * 0.01,\n    )\n\n    ode_solver = ARK548L2SA2KennedyCarpenter(\n        dg,\n        vdg,\n        LinearBackwardEulerSolver(linearsolver; isadjustable = true),\n        Q;\n        dt = dt,\n        t0 = 0,\n        split_explicit_implicit = false,\n    )\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\"\"\" eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                gettime(ode_solver),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n    callbacks = (cbinfo,)\n    if ~isnothing(vtkdir)\n        # Create vtk dir\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # Output initial step\n        do_output(\n            mpicomm,\n            vtkdir,\n            vtkstep,\n            dg,\n            Q,\n            Q,\n            model,\n            \"advection_diffusion\",\n        )\n\n        # Setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(ode_solver))\n            do_output(\n                mpicomm,\n                vtkdir,\n                vtkstep,\n                dg,\n                Q,\n                Qe,\n                model,\n                \"advection_diffusion\",\n            )\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    numberofsteps = convert(Int64, cld(timeend, dt))\n    dt = timeend / numberofsteps\n\n    @info \"time step\" dt numberofsteps dt * numberofsteps timeend\n\n    solve!(\n        Q,\n        ode_solver;\n        numberofsteps = numberofsteps,\n        callbacks = callbacks,\n        adjustfinalstep = false,\n    )\n\n    # Print some end of the simulation information\n    engf = norm(Q)\n    Qe = init_ode_state(dg, FT(timeend))\n\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    base_num_elem = 4\n\n    expected_result = Dict()\n    expected_result[2, 1, Float64] = 7.2801198255507391e-02\n    expected_result[2, 2, Float64] = 6.8160295851506783e-03\n    expected_result[2, 3, Float64] = 1.4439137164205592e-04\n    expected_result[2, 4, Float64] = 2.4260727323386998e-06\n    expected_result[3, 1, Float64] = 1.0462203776357534e-01\n    expected_result[3, 2, Float64] = 1.0280535683502070e-02\n    expected_result[3, 3, Float64] = 2.0631857053908848e-04\n    expected_result[3, 4, Float64] = 3.3460492914169325e-06\n    expected_result[2, 1, Float32] = 7.2801239788532257e-02\n    expected_result[2, 2, Float32] = 6.8159680813550949e-03\n    expected_result[2, 3, Float32] = 1.4439738879445940e-04\n    # This is near roundoff so we will not check it\n    # expected_result[2, 4, Float32] = 2.6432753656990826e-06\n    expected_result[3, 1, Float32] = 1.0462204366922379e-01\n    expected_result[3, 2, Float32] = 1.0280583053827286e-02\n    expected_result[3, 3, Float32] = 2.0646647317335010e-04\n    expected_result[3, 4, Float32] = 2.0226731066941284e-05\n\n    numlevels = integration_testing ? 4 : 1\n\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64, Float32)\n            result = zeros(FT, numlevels)\n            for dim in 2:3\n                for fluxBC in (true, false)\n                    d = dim == 2 ? FT[1, 10, 0] : FT[1, 1, 10]\n                    n = SVector{3, FT}(d ./ norm(d))\n\n                    α = FT(1)\n                    β = FT(1 // 100)\n                    μ = FT(-1 // 2)\n                    δ = FT(1 // 10)\n                    connectivity = dim == 2 ? :face : :full\n                    linearsolvertype = \"Batched GMRES\"\n                    for l in 1:numlevels\n                        Ne = 2^(l - 1) * base_num_elem\n                        brickrange = (\n                            ntuple(\n                                j -> range(FT(-1); length = Ne + 1, stop = 1),\n                                dim - 1,\n                            )...,\n                            range(FT(-5); length = 5Ne + 1, stop = 5),\n                        )\n\n                        periodicity = ntuple(j -> false, dim)\n                        topl = StackedBrickTopology(\n                            mpicomm,\n                            brickrange;\n                            periodicity = periodicity,\n                            boundary = (\n                                ntuple(j -> (1, 2), dim - 1)...,\n                                (3, 4),\n                            ),\n                            connectivity = connectivity,\n                        )\n                        dt = (α / 4) / (Ne * polynomialorder^2)\n\n                        outputtime = 0.01\n                        timeend = 0.5\n\n                        @info (ArrayType, FT, dim, linearsolvertype, l, fluxBC)\n                        vtkdir =\n                            output ?\n                            \"vtk_advection\" *\n                            \"_poly$(polynomialorder)\" *\n                            \"_dim$(dim)_$(ArrayType)_$(FT)\" *\n                            \"_$(linearsolvertype)_level$(l)\" :\n                            nothing\n                        result[l] = test_run(\n                            mpicomm,\n                            ArrayType,\n                            dim,\n                            topl,\n                            polynomialorder,\n                            timeend,\n                            FT,\n                            dt,\n                            n,\n                            α,\n                            β,\n                            μ,\n                            δ,\n                            vtkdir,\n                            outputtime,\n                            linearsolvertype,\n                            fluxBC,\n                        )\n                        # Test the errors significantly larger than floating point epsilon\n                        if !(dim == 2 && l == 4 && FT == Float32)\n                            @test result[l] ≈ FT(expected_result[dim, l, FT])\n                        end\n                    end\n                    @info begin\n                        msg = \"\"\n                        for l in 1:(numlevels - 1)\n                            rate = log2(result[l]) - log2(result[l + 1])\n                            msg *= @sprintf(\n                                \"\\n  rate for level %d = %e\\n\",\n                                l,\n                                rate\n                            )\n                        end\n                        msg\n                    end\n                end\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/advection_diffusion_model_1dimex_bjfnks.jl",
    "content": "using MPI\nusing ClimateMachine\nusing Logging\nusing Test\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.ODESolvers\nusing LinearAlgebra\nusing Printf\nusing Dates\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing ClimateMachine.VTK: writevtk, writepvtu\n\nif !@isdefined integration_testing\n    if length(ARGS) > 0\n        const integration_testing = parse(Bool, ARGS[1])\n    else\n        const integration_testing = parse(\n            Bool,\n            lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n        )\n    end\nend\n\nconst output = parse(Bool, lowercase(get(ENV, \"JULIA_CLIMA_OUTPUT\", \"false\")))\n\ninclude(\"advection_diffusion_model.jl\")\n\nstruct Pseudo1D{n, α, β, μ, δ} <: AdvectionDiffusionProblem end\n\nfunction init_velocity_diffusion!(\n    ::Pseudo1D{n, α, β},\n    aux::Vars,\n    geom::LocalGeometry,\n) where {n, α, β}\n    # Direction of flow is n with magnitude α\n    aux.advection.u = α * n\n\n    # diffusion of strength β in the n direction\n    aux.diffusion.D = β * n * n'\nend\n\nfunction initial_condition!(\n    ::Pseudo1D{n, α, β, μ, δ},\n    state,\n    aux,\n    localgeo,\n    t,\n) where {n, α, β, μ, δ}\n    ξn = dot(n, localgeo.coord)\n    # ξT = SVector(localgeo.coord) - ξn * n\n    state.ρ = exp(-(ξn - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\nend\ninhomogeneous_data!(::Val{0}, P::Pseudo1D, x...) = initial_condition!(P, x...)\nfunction inhomogeneous_data!(\n    ::Val{1},\n    ::Pseudo1D{n, α, β, μ, δ},\n    ∇state,\n    aux,\n    x,\n    t,\n) where {n, α, β, μ, δ}\n    ξn = dot(n, x)\n    ∇state.ρ =\n        -(\n            2n * (ξn - μ - α * t) / (4 * β * (δ + t)) *\n            exp(-(ξn - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\n        )\nend\n\nfunction do_output(mpicomm, vtkdir, vtkstep, dg, Q, Qe, model, testname)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dg, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    dim,\n    topl,\n    N,\n    timeend,\n    FT,\n    dt,\n    n,\n    α,\n    β,\n    μ,\n    δ,\n    vtkdir,\n    outputtime,\n    linearsolvertype,\n    fluxBC,\n)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n    bcs = (\n        InhomogeneousBC{0}(),\n        InhomogeneousBC{1}(),\n        HomogeneousBC{0}(),\n        HomogeneousBC{1}(),\n    )\n    model = AdvectionDiffusion{dim}(\n        Pseudo1D{n, α, β, μ, δ}(),\n        bcs,\n        flux_bc = fluxBC,\n    )\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n        direction = EveryDirection(),\n    )\n\n    vdg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n        state_auxiliary = dg.state_auxiliary,\n        direction = VerticalDirection(),\n    )\n\n    linvdg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n        state_auxiliary = dg.state_auxiliary,\n        direction = VerticalDirection(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n\n    # linearsolver = GeneralizedMinimalResidual(Q; M = 30, rtol = 1e-5)\n    linearsolver =\n        BatchedGeneralizedMinimalResidual(dg, Q; atol = -1.0, rtol = 1e-5)\n\n    nonlinearsolver =\n        JacobianFreeNewtonKrylovSolver(Q, linearsolver; tol = 1e-4)\n\n    ode_solver = ARK548L2SA2KennedyCarpenter(\n        dg,\n        vdg,\n        NonLinearBackwardEulerSolver(\n            nonlinearsolver;\n            isadjustable = true,\n            preconditioner_update_freq = 1000,\n        ),\n        Q;\n        dt = dt,\n        t0 = 0,\n        split_explicit_implicit = false,\n    )\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\"\"\" eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                gettime(ode_solver),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n    callbacks = (cbinfo,)\n    if ~isnothing(vtkdir)\n        # create vtk dir\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # output initial step\n        do_output(\n            mpicomm,\n            vtkdir,\n            vtkstep,\n            dg,\n            Q,\n            Q,\n            model,\n            \"advection_diffusion\",\n        )\n\n        # setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(ode_solver))\n            do_output(\n                mpicomm,\n                vtkdir,\n                vtkstep,\n                dg,\n                Q,\n                Qe,\n                model,\n                \"advection_diffusion\",\n            )\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    numberofsteps = convert(Int64, cld(timeend, dt))\n    dt = timeend / numberofsteps\n\n    @info \"time step\" dt numberofsteps dt * numberofsteps timeend\n\n    solve!(\n        Q,\n        ode_solver;\n        numberofsteps = numberofsteps,\n        callbacks = callbacks,\n        adjustfinalstep = false,\n    )\n\n    # Print some end of the simulation information\n    engf = norm(Q)\n    Qe = init_ode_state(dg, FT(timeend))\n\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    base_num_elem = 4\n\n    expected_result = Dict()\n    expected_result[2, 1, Float64] = 7.2801198255507391e-02\n    expected_result[2, 2, Float64] = 6.8160295851506783e-03\n    expected_result[2, 3, Float64] = 1.4439137164205592e-04\n    expected_result[2, 4, Float64] = 2.4260727323386998e-06\n    expected_result[3, 1, Float64] = 1.0462203776357534e-01\n    expected_result[3, 2, Float64] = 1.0280535683502070e-02\n    expected_result[3, 3, Float64] = 2.0631857053908848e-04\n    expected_result[3, 4, Float64] = 3.3460492914169325e-06\n    expected_result[2, 1, Float32] = 7.2801239788532257e-02\n    expected_result[2, 2, Float32] = 6.8159680813550949e-03\n    expected_result[2, 3, Float32] = 1.4439738879445940e-04\n    # This is near roundoff so we will not check it\n    # expected_result[2, 4, Float32] = 2.6432753656990826e-06\n    expected_result[3, 1, Float32] = 1.0462204366922379e-01\n    expected_result[3, 2, Float32] = 1.0280583053827286e-02\n    expected_result[3, 3, Float32] = 2.0646647317335010e-04\n    expected_result[3, 4, Float32] = 2.0226731066941284e-05\n\n    numlevels = integration_testing ? 4 : 1\n\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64, Float32)\n            result = zeros(FT, numlevels)\n            for dim in 2:3\n                for fluxBC in (true, false)\n                    d = dim == 2 ? FT[1, 10, 0] : FT[1, 1, 10]\n                    n = SVector{3, FT}(d ./ norm(d))\n\n                    α = FT(1)\n                    β = FT(1 // 100)\n                    μ = FT(-1 // 2)\n                    δ = FT(1 // 10)\n\n                    linearsolvertype = \"Batched GMRES\"\n                    for l in 1:numlevels\n                        Ne = 2^(l - 1) * base_num_elem\n                        brickrange = (\n                            ntuple(\n                                j -> range(FT(-1); length = Ne + 1, stop = 1),\n                                dim - 1,\n                            )...,\n                            range(FT(-5); length = 5Ne + 1, stop = 5),\n                        )\n\n                        periodicity = ntuple(j -> false, dim)\n                        topl = StackedBrickTopology(\n                            mpicomm,\n                            brickrange;\n                            periodicity = periodicity,\n                            boundary = (\n                                ntuple(j -> (1, 2), dim - 1)...,\n                                (3, 4),\n                            ),\n                        )\n                        dt = (α / 4) / (Ne * polynomialorder^2)\n\n                        outputtime = 0.01\n                        timeend = 0.5\n\n                        @info (ArrayType, FT, dim, linearsolvertype, l, fluxBC)\n                        vtkdir =\n                            output ?\n                            \"vtk_advection\" *\n                            \"_poly$(polynomialorder)\" *\n                            \"_dim$(dim)_$(ArrayType)_$(FT)\" *\n                            \"_$(linearsolvertype)_level$(l)\" :\n                            nothing\n                        result[l] = test_run(\n                            mpicomm,\n                            ArrayType,\n                            dim,\n                            topl,\n                            polynomialorder,\n                            timeend,\n                            FT,\n                            dt,\n                            n,\n                            α,\n                            β,\n                            μ,\n                            δ,\n                            vtkdir,\n                            outputtime,\n                            linearsolvertype,\n                            fluxBC,\n                        )\n                        # test the errors significantly larger than floating point epsilon\n                        if !(dim == 2 && l == 4 && FT == Float32)\n                            @test result[l] ≈ FT(expected_result[dim, l, FT])\n                        end\n                    end\n                    @info begin\n                        msg = \"\"\n                        for l in 1:(numlevels - 1)\n                            rate = log2(result[l]) - log2(result[l + 1])\n                            msg *= @sprintf(\n                                \"\\n  rate for level %d = %e\\n\",\n                                l,\n                                rate\n                            )\n                        end\n                        msg\n                    end\n                end\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/advection_sphere.jl",
    "content": "using MPI\nusing ClimateMachine\nusing Logging\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.BalanceLaws: update_auxiliary_state!\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Atmos: SphericalOrientation, latitude, longitude\nusing ClimateMachine.Orientations\nusing LinearAlgebra\nusing Printf\nusing Dates\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing ClimateMachine.VTK: writevtk, writepvtu\nimport ClimateMachine.BalanceLaws: boundary_state!\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nconst output = parse(Bool, lowercase(get(ENV, \"JULIA_CLIMA_OUTPUT\", \"false\")))\n\ninclude(\"advection_diffusion_model.jl\")\n\n# This is a setup similar to the one presented in [Williamson1992](@cite)\nstruct SolidBodyRotation <: AdvectionDiffusionProblem end\nfunction init_velocity_diffusion!(\n    ::SolidBodyRotation,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    FT = eltype(aux)\n    λ = longitude(SphericalOrientation(), aux)\n    φ = latitude(SphericalOrientation(), aux)\n    r = norm(geom.coord)\n\n    uλ = 2 * FT(π) * cos(φ) * r\n    uφ = 0\n    aux.advection.u = SVector(\n        -uλ * sin(λ) - uφ * cos(λ) * sin(φ),\n        +uλ * cos(λ) - uφ * sin(λ) * sin(φ),\n        +uφ * cos(φ),\n    )\nend\nfunction initial_condition!(::SolidBodyRotation, state, aux, localgeo, t)\n    λ = longitude(SphericalOrientation(), aux)\n    φ = latitude(SphericalOrientation(), aux)\n    state.ρ = exp(-((3λ)^2 + (3φ)^2))\nend\nfinaltime(::SolidBodyRotation) = 1\nu_scale(::SolidBodyRotation) = 2π\n\n# This is a setup similar to the one presented in [Lauritzen2012](@cite)\nstruct ReversingDeformationalFlow <: AdvectionDiffusionProblem end\ninit_velocity_diffusion!(\n    ::ReversingDeformationalFlow,\n    aux::Vars,\n    geom::LocalGeometry,\n) = nothing\nfunction initial_condition!(::ReversingDeformationalFlow, state, aux, coord, t)\n    x, y, z = aux.coord\n    r = norm(aux.coord)\n    h_max = 0.95\n    b = 5\n    state.ρ = 0\n    for (λ, φ) in ((5π / 6, 0), (7π / 6, 0))\n        xi = r * cos(φ) * cos(λ)\n        yi = r * cos(φ) * sin(λ)\n        zi = r * sin(φ)\n        state.ρ += h_max * exp(-b * ((x - xi)^2 + (y - yi)^2 + (z - zi)^2))\n    end\nend\nhas_variable_coefficients(::ReversingDeformationalFlow) = true\nfunction update_velocity_diffusion!(\n    ::ReversingDeformationalFlow,\n    ::AdvectionDiffusion,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    FT = eltype(aux)\n    λ = longitude(SphericalOrientation(), aux)\n    φ = latitude(SphericalOrientation(), aux)\n    r = norm(aux.coord)\n    T = FT(5)\n    λp = λ - FT(2π) * t / T\n    uλ =\n        10 * r / T * sin(λp)^2 * sin(2φ) * cos(FT(π) * t / T) +\n        FT(2π) * r / T * cos(φ)\n    uφ = 10 * r / T * sin(2λp) * cos(φ) * cos(FT(π) * t / T)\n    aux.advection.u = SVector(\n        -uλ * sin(λ) - uφ * cos(λ) * sin(φ),\n        +uλ * cos(λ) - uφ * sin(λ) * sin(φ),\n        +uφ * cos(φ),\n    )\nend\nu_scale(::ReversingDeformationalFlow) = 2.9\nfinaltime(::ReversingDeformationalFlow) = 5\n\nfunction advective_courant(\n    m::AdvectionDiffusion,\n    state::Vars,\n    aux::Vars,\n    diffusive::Vars,\n    Δx,\n    Δt,\n    direction,\n)\n    return Δt * norm(aux.advection.u) / Δx\nend\n\nstruct NoFlowBC end\nfunction boundary_state!(\n    ::RusanovNumericalFlux,\n    ::NoFlowBC,\n    ::AdvectionDiffusion,\n    stateP::Vars,\n    auxP::Vars,\n    nM,\n    stateM::Vars,\n    auxM::Vars,\n    t,\n    _...,\n)\n    auxP.advection.u = -auxM.advection.u\nend\n\nfunction do_output(mpicomm, vtkdir, vtkstep, dg, Q, Qe, model, testname)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dg, statenames, Qe, exactnames)\n\n    ## generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    topl,\n    problem,\n    explicit_method,\n    cfl,\n    N,\n    timeend,\n    FT,\n    vtkdir,\n    outputtime,\n)\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n        meshwarp = equiangular_cubed_sphere_warp,\n    )\n\n    dx = min_node_distance(grid, HorizontalDirection())\n    dt = FT(cfl * dx / u_scale(problem()))\n    dt = outputtime / ceil(Int64, outputtime / dt)\n\n    bcs = (NoFlowBC(),)\n    model = AdvectionDiffusion{3}(problem(), bcs, diffusion = false)\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n\n    odesolver = explicit_method(dg, Q; dt = dt, t0 = 0)\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n    problem   = %s\n    method    = %s\n    time step = %.16e\n    norm(Q₀)  = %.16e\"\"\" problem explicit_method dt eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                gettime(odesolver),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n    cbcfl = EveryXSimulationSteps(10) do\n        dt = ODESolvers.getdt(odesolver)\n        cfl = DGMethods.courant(\n            advective_courant,\n            dg,\n            model,\n            Q,\n            dt,\n            HorizontalDirection(),\n        )\n        @info @sprintf(\n            \"\"\"Courant number\n            simtime = %.16e\n            courant = %.16e\"\"\",\n            gettime(odesolver),\n            cfl\n        )\n    end\n    callbacks = (cbinfo,)\n    if ~isnothing(vtkdir)\n        # create vtk dir\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # output initial step\n        do_output(mpicomm, vtkdir, vtkstep, dg, Q, Q, model, \"advection_sphere\")\n\n        # setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(odesolver))\n            do_output(\n                mpicomm,\n                vtkdir,\n                vtkstep,\n                dg,\n                Q,\n                Qe,\n                model,\n                \"advection_sphere\",\n            )\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(Q, odesolver; timeend = timeend, callbacks = callbacks)\n\n    # Print some end of the simulation information\n    engf = norm(Q)\n    Qe = init_ode_state(dg, FT(timeend))\n\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    Δmass = abs(weightedsum(Q) - weightedsum(Qe)) / weightedsum(Qe)\n    @info @sprintf \"\"\"Finished\n    Δmass                   = %.16e\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" Δmass engf engf / eng0 engf - eng0 errf errf / engfe\n    return errf, Δmass\nend\n\nusing Test\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    base_num_elem = 2\n\n    max_cfl = Dict(\n        LSRK144NiegemannDiehlBusch => 5.0,\n        SSPRK33ShuOsher => 1.0,\n        SSPRK34SpiteriRuuth => 1.5,\n    )\n\n    expected_result = Dict()\n\n    expected_result[SolidBodyRotation, LSRK144NiegemannDiehlBusch, 1] =\n        1.3199024557832748e-01\n    expected_result[SolidBodyRotation, LSRK144NiegemannDiehlBusch, 2] =\n        1.9868931633120656e-02\n    expected_result[SolidBodyRotation, LSRK144NiegemannDiehlBusch, 3] =\n        1.4052110916915061e-03\n    expected_result[SolidBodyRotation, LSRK144NiegemannDiehlBusch, 4] =\n        9.0193766298676310e-05\n\n    expected_result[SolidBodyRotation, SSPRK33ShuOsher, 1] =\n        1.1055145388897809e-01\n    expected_result[SolidBodyRotation, SSPRK33ShuOsher, 2] =\n        1.5510740467668628e-02\n    expected_result[SolidBodyRotation, SSPRK33ShuOsher, 3] =\n        1.8629481690361454e-03\n    expected_result[SolidBodyRotation, SSPRK33ShuOsher, 4] =\n        2.3567040048588889e-04\n\n    expected_result[SolidBodyRotation, SSPRK34SpiteriRuuth, 1] =\n        1.2641959922001456e-01\n    expected_result[SolidBodyRotation, SSPRK34SpiteriRuuth, 2] =\n        2.2780375948714751e-02\n    expected_result[SolidBodyRotation, SSPRK34SpiteriRuuth, 3] =\n        3.1274951764826459e-03\n    expected_result[SolidBodyRotation, SSPRK34SpiteriRuuth, 4] =\n        3.9734060514021565e-04\n\n    expected_result[ReversingDeformationalFlow, LSRK144NiegemannDiehlBusch, 1] =\n        5.5387951598735408e-01\n    expected_result[ReversingDeformationalFlow, LSRK144NiegemannDiehlBusch, 2] =\n        3.7610388138383732e-01\n    expected_result[ReversingDeformationalFlow, LSRK144NiegemannDiehlBusch, 3] =\n        1.7823508719111605e-01\n    expected_result[ReversingDeformationalFlow, LSRK144NiegemannDiehlBusch, 4] =\n        3.8639493470255713e-02\n\n    expected_result[ReversingDeformationalFlow, SSPRK33ShuOsher, 1] =\n        5.5353962032596349e-01\n    expected_result[ReversingDeformationalFlow, SSPRK33ShuOsher, 2] =\n        3.7645487928038762e-01\n    expected_result[ReversingDeformationalFlow, SSPRK33ShuOsher, 3] =\n        1.7823263736245307e-01\n    expected_result[ReversingDeformationalFlow, SSPRK33ShuOsher, 4] =\n        3.8605903366230925e-02\n\n    expected_result[ReversingDeformationalFlow, SSPRK34SpiteriRuuth, 1] =\n        5.5404045660832824e-01\n    expected_result[ReversingDeformationalFlow, SSPRK34SpiteriRuuth, 2] =\n        3.7788858038003154e-01\n    expected_result[ReversingDeformationalFlow, SSPRK34SpiteriRuuth, 3] =\n        1.8007113931230376e-01\n    expected_result[ReversingDeformationalFlow, SSPRK34SpiteriRuuth, 4] =\n        3.9941331660544775e-02\n\n    numlevels =\n        integration_testing || ClimateMachine.Settings.integration_testing ? 4 :\n        1\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64,)\n            for problem in (SolidBodyRotation, ReversingDeformationalFlow)\n                for explicit_method in (\n                    LSRK144NiegemannDiehlBusch,\n                    SSPRK33ShuOsher,\n                    SSPRK34SpiteriRuuth,\n                )\n                    cfl = max_cfl[explicit_method]\n                    result = zeros(FT, numlevels)\n                    for l in 1:numlevels\n                        numelems_horizontal = 2^(l - 1) * base_num_elem\n                        numelems_vertical = 1\n\n                        topl = StackedCubedSphereTopology(\n                            mpicomm,\n                            numelems_horizontal,\n                            range(\n                                FT(1),\n                                stop = 2,\n                                length = numelems_vertical + 1,\n                            ),\n                        )\n\n                        timeend = finaltime(problem())\n                        outputtime = timeend\n\n                        @info (ArrayType, FT)\n                        vtkdir =\n                            output ?\n                            \"vtk_advection_sphere\" *\n                            \"_$problem\" *\n                            \"_$explicit_method\" *\n                            \"_poly$(polynomialorder)\" *\n                            \"_$(ArrayType)_$(FT)\" *\n                            \"_level$(l)\" :\n                            nothing\n\n                        result[l], Δmass = test_run(\n                            mpicomm,\n                            ArrayType,\n                            topl,\n                            problem,\n                            explicit_method,\n                            cfl,\n                            polynomialorder,\n                            timeend,\n                            FT,\n                            vtkdir,\n                            outputtime,\n                        )\n                        @test result[l] ≈\n                              FT(expected_result[problem, explicit_method, l])\n                        @test Δmass <= FT(5e-14)\n                    end\n                    @info begin\n                        msg = \"\"\n                        for l in 1:(numlevels - 1)\n                            rate = log2(result[l]) - log2(result[l + 1])\n                            msg *= @sprintf(\n                                \"\\n  rate for level %d = %e\\n\",\n                                l,\n                                rate\n                            )\n                        end\n                        msg\n                    end\n                end\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/diffusion_hyperdiffusion_sphere.jl",
    "content": "using MPI\nusing ClimateMachine\nusing Logging\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing LinearAlgebra\nusing Printf\nusing Dates\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VTK: writevtk, writepvtu\nusing ClimateMachine.Mesh.Grids: min_node_distance\n\nconst output = parse(Bool, lowercase(get(ENV, \"JULIA_CLIMA_OUTPUT\", \"false\")))\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\ninclude(\"advection_diffusion_model.jl\")\n\nstruct DiffusionSphere <: AdvectionDiffusionProblem end\n\nfunction init_velocity_diffusion!(\n    problem::DiffusionSphere,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    FT = eltype(aux)\n    IM = SMatrix{3, 3, FT}(I)\n    ZM = zeros(SMatrix{3, 3, FT})\n    μ = FT(1 / 10000)\n    aux.diffusion.D = μ * hcat(IM, ZM)\n    aux.hyperdiffusion.H = μ * hcat(ZM, IM)\nend\n\nfunction initial_condition!(problem::DiffusionSphere, state, aux, localgeo, t)\n    coord = localgeo.coord\n    x, y, z = coord\n    r = norm(coord)\n    θ = atan(sqrt(x^2 + y^2), z)\n    φ = atan(y, x)\n    @inbounds begin\n        # m = 1 l = 2 spherical harmonic\n        ρ₀ = cos(φ) * sin(θ) * cos(θ)\n        l = 2\n        c = l * (l + 1) / r^2\n        μ = aux.diffusion.D[1]\n        state.ρ = (ρ₀ * exp(-c * μ * t), ρ₀ * exp(-c^2 * μ * t))\n    end\nend\n\nfunction do_output(mpicomm, vtkdir, vtkstep, dg, Q, Qe, model, testname)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dg, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\nfunction run(mpicomm, ArrayType, topl, N, timeend, FT, vtkdir, outputtime)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n        meshwarp = equiangular_cubed_sphere_warp,\n    )\n\n    dx = min_node_distance(grid)\n    dt = 300 * dx^4\n    @info \"time step\" dt\n    dt = outputtime / ceil(Int64, outputtime / dt)\n\n    model = AdvectionDiffusion{3}(\n        DiffusionSphere(),\n        advection = false,\n        diffusion = true,\n        hyperdiffusion = true,\n        num_equations = 2,\n    )\n    dg = DGModel(\n        model,\n        grid,\n        CentralNumericalFluxFirstOrder(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n        diffusion_direction = HorizontalDirection(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n\n    lsrk = LSRK54CarpenterKennedy(dg, Q; dt = dt, t0 = 0)\n\n    eng0 = norm(Q, dims = (1, 3))\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\"\"\" eng0[1]\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                gettime(lsrk),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n    callbacks = (cbinfo,)\n    if ~isnothing(vtkdir)\n        # create vtk dir\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # output initial step\n        do_output(mpicomm, vtkdir, vtkstep, dg, Q, Q, model, \"diffusion_sphere\")\n\n        # setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(lsrk))\n            do_output(\n                mpicomm,\n                vtkdir,\n                vtkstep,\n                dg,\n                Q,\n                Qe,\n                model,\n                \"diffusion_sphere\",\n            )\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(Q, lsrk; timeend = timeend, callbacks = callbacks)\n\n    # Print some end of the simulation information\n    engf = norm(Q, dims = (1, 3))\n    Qe = init_ode_state(dg, FT(timeend))\n\n    engfe = norm(Qe, dims = (1, 3))\n    errf = norm(Q .- Qe, dims = (1, 3))\n\n    metrics = @. (engf, engf / eng0, engf - eng0, errf, errf / engfe)\n\n    @info @sprintf \"\"\"Finished\n    Diffusion:\n      norm(Q)                 = %.16e\n      norm(Q) / norm(Q₀)      = %.16e\n      norm(Q) - norm(Q₀)      = %.16e\n      norm(Q - Qe)            = %.16e\n      norm(Q - Qe) / norm(Qe) = %.16e\n    HyperDiffusion:\n      norm(Q)                 = %.16e\n      norm(Q) / norm(Q₀)      = %.16e\n      norm(Q) - norm(Q₀)      = %.16e\n      norm(Q - Qe)            = %.16e\n      norm(Q - Qe) / norm(Qe) = %.16e\n      \"\"\" first.(metrics)... last.(metrics)...\n    errf\nend\n\nusing Test\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 3\n    base_num_elem = 4\n\n    expected_result = Dict()\n    expected_result[Diffusion, 1] = 2.6002775334282785e-06\n    expected_result[Diffusion, 2] = 4.1602462623838931e-07\n    expected_result[Diffusion, 3] = 5.8997889725858304e-08\n\n    expected_result[HyperDiffusion, 1] = 9.3032674316702424e-05\n    expected_result[HyperDiffusion, 2] = 1.0149377619104328e-05\n    expected_result[HyperDiffusion, 3] = 1.2985297333025857e-06\n\n    numlevels =\n        integration_testing || ClimateMachine.Settings.integration_testing ? 3 :\n        1\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64,)\n            result = Dict()\n            for l in 1:numlevels\n                Ne = 2^(l - 1) * base_num_elem\n                vert_range = grid1d(1, 2, nelem = 1)\n                topl = StackedCubedSphereTopology(\n                    mpicomm,\n                    Ne,\n                    vert_range,\n                    boundary = (0, 0),\n                )\n\n                timeend = FT(1)\n                outputtime = FT(2)\n\n                @info (ArrayType, FT)\n                vtkdir =\n                    output ?\n                    \"vtk_diffusion_sphere\" *\n                    \"_poly$(polynomialorder)\" *\n                    \"_$(ArrayType)_$(FT)\" *\n                    \"_level$(l)\" :\n                    nothing\n                result[l] = run(\n                    mpicomm,\n                    ArrayType,\n                    topl,\n                    polynomialorder,\n                    timeend,\n                    FT,\n                    vtkdir,\n                    outputtime,\n                )\n                @test result[l][1] ≈ expected_result[Diffusion, l]\n                @test result[l][2] ≈ expected_result[HyperDiffusion, l]\n            end\n            @info begin\n                msg = \"\"\n                for l in 1:(numlevels - 1)\n                    rate = @. log2(result[l]) - log2(result[l + 1])\n                    msg *= @sprintf(\n                        \"\\n  rates for level %d Diffusion = %e\",\n                        l,\n                        rate[1]\n                    )\n                    msg *= @sprintf(\", HyperDiffusion = %e\\n\", rate[2])\n                end\n                msg\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/direction_splitting_advection_diffusion.jl",
    "content": "using Test\nusing MPI\nusing ClimateMachine\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing LinearAlgebra\nusing Printf\nusing Random\n\ninclude(\"advection_diffusion_model.jl\")\n\nstruct Box{dim} end\nstruct Sphere end\n\nvertical_unit_vector(::Box{2}, ::SVector{3}) = SVector(0, 1, 0)\nvertical_unit_vector(::Box{3}, ::SVector{3}) = SVector(0, 0, 1)\nvertical_unit_vector(::Sphere, coord::SVector{3}) = coord / norm(coord)\n\nprojection(::EveryDirection, ::SVector{3}) = I\nprojection(::VerticalDirection, k::SVector{3}) = k * k'\nprojection(::HorizontalDirection, k::SVector{3}) = I - k * k'\n\nstruct TestProblem{adv, diff, dir, topo} <: AdvectionDiffusionProblem end\n\ninitial_ρ(::Box, x) = prod(sin.(π * x))\nfunction initial_ρ(::Sphere, x)\n    r = norm(x)\n    φ = atan(x[2], x[1])\n    θ = atan(sqrt(x[1]^2 + x[2]^2), x[3])\n    return sin(π * (r - 1)) * sin(φ) * sin(θ)\nend\n\nvelocity(::Box, x) = sin.(π * x)\nfunction velocity(::Sphere, x)\n    r = norm(x)\n    φ = atan(x[2], x[1])\n    θ = atan(sqrt(x[1]^2 + x[2]^2), x[3])\n    return sin(π * (r - 1)) .*\n           SVector(cos(φ) * cos(θ), sin(φ) * cos(θ), cos(φ) * sin(θ))\nend\n\nfunction init_velocity_diffusion!(\n    ::TestProblem{adv, diff, dir, topo},\n    aux::Vars,\n    geom::LocalGeometry,\n) where {adv, diff, dir, topo}\n    k = vertical_unit_vector(topo, geom.coord)\n    P = projection(dir, k)\n    aux.advection.u =\n        !isnothing(adv) ? P * velocity(topo, geom.coord) : zeros(SVector{3})\n    aux.diffusion.D =\n        !isnothing(diff) ? SMatrix{3, 3}(P) / 200 : zeros(SMatrix{3, 3})\nend\n\nfunction initial_condition!(\n    ::TestProblem{adv, diff, dir, topo},\n    state,\n    aux,\n    localgeo,\n    t,\n) where {adv, diff, dir, topo}\n    state.ρ = initial_ρ(topo, localgeo.coord)\nend\n\nfunction create_topology(::Box{dim}, mpicomm, Ne, FT) where {dim}\n    brickrange = ntuple(j -> range(FT(0); length = Ne + 1, stop = 1), dim)\n    periodicity = ntuple(j -> false, dim)\n    bc = ntuple(j -> (1, 1), dim)\n    connectivity = dim == 3 ? :full : :face\n    StackedBrickTopology(\n        mpicomm,\n        brickrange;\n        periodicity = periodicity,\n        boundary = bc,\n        connectivity = connectivity,\n    )\nend\n\nfunction create_topology(::Sphere, mpicomm, Ne, FT)\n    vert_range = grid1d(FT(1), FT(2), nelem = Ne)\n    StackedCubedSphereTopology(mpicomm, Ne, vert_range, boundary = (1, 1))\nend\n\ncreate_dg(model, grid, direction) = DGModel(\n    model,\n    grid,\n    RusanovNumericalFlux(),\n    CentralNumericalFluxSecondOrder(),\n    CentralNumericalFluxGradient(),\n    direction = direction,\n)\n\nfunction test_run(\n    adv,\n    diff,\n    topo,\n    mpicomm,\n    ArrayType,\n    FT,\n    polynomialorder,\n    Ne,\n    level,\n)\n    topology = create_topology(topo(), mpicomm, Ne, FT)\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorder,\n        meshwarp = topo == Sphere ? equiangular_cubed_sphere_warp :\n                   (x...) -> identity(x),\n    )\n\n    problems = (\n        p = TestProblem{adv, diff, EveryDirection(), topo()}(),\n        vp = TestProblem{adv, diff, VerticalDirection(), topo()}(),\n        hp = TestProblem{adv, diff, HorizontalDirection(), topo()}(),\n    )\n\n    bcs = (HomogeneousBC{0}(),)\n    models = map(p -> AdvectionDiffusion{3}(p, bcs), problems)\n\n    dgmodels = map(models) do m\n        (\n            dg = create_dg(m, grid, EveryDirection()),\n            vdg = create_dg(m, grid, VerticalDirection()),\n            hdg = create_dg(m, grid, HorizontalDirection()),\n        )\n    end\n\n    Q = init_ode_state(dgmodels.p.dg, FT(0), init_on_cpu = true)\n    # do one Euler step to trigger numerical fluxes in subsequent evaluations\n    let\n        dt = 1e-3\n        dQ = similar(Q)\n        dgmodels.p.dg(dQ, Q, nothing, FT(0))\n        Q .+= dt .* dQ\n    end\n\n    # evaluate all combinations\n    dQ = map(\n        x -> map(dg -> (dQ = similar(Q); dg(dQ, Q, nothing, FT(0)); dQ), x),\n        dgmodels,\n    )\n\n    # set up tolerances\n    atolm = 6e-13\n    atolv = 3e-4 / 5^(level - 1)\n    atolh = 0.0003 / 5^(level - 1)\n\n    @testset \"total\" begin\n        atol = topo <: Box || diff == nothing ? atolm : atolh\n        @test isapprox(norm(dQ.p.dg .- dQ.p.vdg .- dQ.p.hdg), 0, atol = atol)\n        @test isapprox(norm(dQ.vp.dg .- dQ.vp.vdg .- dQ.vp.hdg), 0, atol = atol)\n        @test isapprox(norm(dQ.hp.dg .- dQ.hp.vdg .- dQ.hp.hdg), 0, atol = atol)\n    end\n\n    @testset \"vertical\" begin\n        atol = topo <: Box ? atolm : atolv\n        @test isapprox(norm(dQ.vp.dg .- dQ.vp.vdg), 0, atol = atol)\n        @test isapprox(norm(dQ.vp.hdg), 0, atol = atol)\n        @test isapprox(norm(dQ.vp.dg .- dQ.p.vdg), 0, atol = atol)\n    end\n\n    @testset \"horizontal\" begin\n        atol = topo <: Box ? atolm : atolh\n        @test isapprox(norm(dQ.hp.dg .- dQ.hp.hdg), 0, atol = atol)\n        @test isapprox(norm(dQ.hp.vdg), 0, atol = atol)\n        @test isapprox(norm(dQ.hp.dg - dQ.p.hdg), 0, atol = atol)\n    end\nend\n\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n    mpicomm = MPI.COMM_WORLD\n    FT = Float64\n    numlevels = 2\n    base_num_elem = 4\n\n    # This test doesn't do any heavy computational work,\n    # but compiles a lot of model/discretization combinations.\n    # Compilation times on the GPU are longer, so we only run one\n    # variable-degree case on the GPU\n    if ArrayType == Array\n        polynomialorders = ((4, 4), (4, 2))\n    else\n        polynomialorders = ((4, 2),)\n    end\n\n    @info @sprintf \"\"\"Test parameters:\n    ArrayType                   = %s\n    FloatType                   = %s\n    Polynomial orders           = %s\n      \"\"\" ArrayType FT polynomialorders\n\n    @testset \"$(@__FILE__)\" begin\n        @testset for polyorders in polynomialorders\n            @testset for topo in (Box{2}, Box{3}, Sphere)\n                @testset for (adv, diff) in (\n                    (Advection, nothing),\n                    (nothing, Diffusion),\n                    (Advection, Diffusion),\n                )\n                    @testset for level in 1:numlevels\n                        Ne = 2^(level - 1) * base_num_elem\n                        test_run(\n                            adv,\n                            diff,\n                            topo,\n                            mpicomm,\n                            ArrayType,\n                            FT,\n                            polyorders,\n                            Ne,\n                            level,\n                        )\n                    end\n                end\n            end\n        end\n    end\nend\nnothing\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/fvm_advection.jl",
    "content": "import Printf: @sprintf\nimport LinearAlgebra: dot, norm\nimport Dates\nimport MPI\n\nimport ClimateMachine\nimport ClimateMachine.DGMethods.FVReconstructions: FVConstant, FVLinear\nimport ClimateMachine.DGMethods.NumericalFluxes:\n    RusanovNumericalFlux,\n    CentralNumericalFluxSecondOrder,\n    CentralNumericalFluxGradient\nimport ClimateMachine.DGMethods: DGFVModel, init_ode_state\nimport ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nimport ClimateMachine.MPIStateArrays: MPIStateArray, euclidean_distance\nimport ClimateMachine.Mesh.Grids:\n    DiscontinuousSpectralElementGrid, EveryDirection\nimport ClimateMachine.Mesh.Topologies: StackedBrickTopology\nimport ClimateMachine.ODESolvers: LSRK54CarpenterKennedy, solve!, gettime\nimport ClimateMachine.VTK: writevtk, writepvtu\n\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\nconst output = parse(Bool, lowercase(get(ENV, \"JULIA_CLIMA_OUTPUT\", \"false\")))\n\ninclude(\"advection_diffusion_model.jl\")\n\nstruct Pseudo1D{n, α} <: AdvectionDiffusionProblem end\n\nfunction init_velocity_diffusion!(\n    ::Pseudo1D{n, α},\n    aux::Vars,\n    geom::LocalGeometry,\n) where {n, α}\n    # Direction of flow is n with magnitude α\n    aux.advection.u = α * n\nend\n\nfunction initial_condition!(\n    ::Pseudo1D{n, α},\n    state,\n    aux,\n    localgeo,\n    t,\n) where {n, α}\n    ξn = dot(n, localgeo.coord)\n    state.ρ = sin((ξn - α * t) * pi)\nend\ninhomogeneous_data!(::Val{0}, P::Pseudo1D, x...) = initial_condition!(P, x...)\n\nfunction do_output(mpicomm, vtkdir, vtkstep, dgfvm, Q, Qe, model, testname)\n    ## Name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dgfvm, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## Name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## Name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    fvmethod,\n    dim,\n    topl,\n    N,\n    timeend,\n    FT,\n    dt,\n    n,\n    α,\n    vtkdir,\n    outputtime,\n)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n    bcs = (InhomogeneousBC{0}(),)\n    model = AdvectionDiffusion{dim}(Pseudo1D{n, α}(), bcs, diffusion = false)\n    dgfvm = DGFVModel(\n        model,\n        grid,\n        fvmethod,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient();\n        direction = EveryDirection(),\n    )\n\n    Q = init_ode_state(dgfvm, FT(0))\n\n    lsrk = LSRK54CarpenterKennedy(dgfvm, Q; dt = dt, t0 = 0)\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\"\"\" eng0\n\n    # Set up the information callback\n    starttime = Ref(Dates.now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = Dates.now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                gettime(lsrk),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n    callbacks = (cbinfo,)\n    if ~isnothing(vtkdir)\n        # Create vtk dir\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # Output initial step\n        do_output(\n            mpicomm,\n            vtkdir,\n            vtkstep,\n            dgfvm,\n            Q,\n            Q,\n            model,\n            \"advection_diffusion\",\n        )\n\n        # Setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dgfvm, gettime(lsrk))\n            do_output(\n                mpicomm,\n                vtkdir,\n                vtkstep,\n                dgfvm,\n                Q,\n                Qe,\n                model,\n                \"advection_diffusion\",\n            )\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(Q, lsrk; timeend = timeend, callbacks = callbacks)\n\n    # Print some end of the simulation information\n    engf = norm(Q)\n    Qe = init_ode_state(dgfvm, FT(timeend))\n\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nusing Test\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    base_num_elem = 4\n\n    expected_result = Dict()\n    expected_result[2, 1, Float64, FVConstant()] = 1.0404261715459338e-01\n    expected_result[2, 2, Float64, FVConstant()] = 5.5995868545685376e-02\n    expected_result[2, 3, Float64, FVConstant()] = 2.9383695610072275e-02\n    expected_result[2, 4, Float64, FVConstant()] = 1.5171779426843507e-02\n\n    expected_result[2, 1, Float64, FVLinear()] = 8.3196657944903635e-02\n    expected_result[2, 2, Float64, FVLinear()] = 3.9277132273521774e-02\n    expected_result[2, 3, Float64, FVLinear()] = 1.7155773433218020e-02\n    expected_result[2, 4, Float64, FVLinear()] = 7.6525022056819231e-03\n\n    expected_result[3, 1, Float64, FVConstant()] = 9.6785620234054362e-02\n    expected_result[3, 2, Float64, FVConstant()] = 5.3406412788842651e-02\n    expected_result[3, 3, Float64, FVConstant()] = 2.8471535157235807e-02\n    expected_result[3, 4, Float64, FVConstant()] = 1.4846239937398318e-02\n\n    expected_result[3, 1, Float64, FVLinear()] = 8.5860120005258181e-02\n    expected_result[3, 2, Float64, FVLinear()] = 4.2844889694123235e-02\n    expected_result[3, 3, Float64, FVLinear()] = 1.9302295207100174e-02\n    expected_result[3, 4, Float64, FVLinear()] = 8.6084633401356733e-03\n\n    expected_result[2, 1, Float32, FVConstant()] = 1.0404255986213684e-01\n    expected_result[2, 2, Float32, FVConstant()] = 5.5995877832174301e-02\n    expected_result[2, 3, Float32, FVConstant()] = 2.9383875429630280e-02\n    expected_result[2, 4, Float32, FVConstant()] = 1.5171864069998264e-02\n\n    expected_result[2, 1, Float32, FVLinear()] = 8.3196602761745453e-02\n    expected_result[2, 2, Float32, FVLinear()] = 3.9277125149965286e-02\n    expected_result[2, 3, Float32, FVLinear()] = 1.7155680805444717e-02\n    expected_result[2, 4, Float32, FVLinear()] = 7.6521718874573708e-03\n\n    expected_result[3, 1, Float32, FVConstant()] = 9.6785508096218109e-02\n    expected_result[3, 2, Float32, FVConstant()] = 5.3406376391649246e-02\n    expected_result[3, 3, Float32, FVConstant()] = 2.8471505269408226e-02\n    expected_result[3, 4, Float32, FVConstant()] = 1.4849635772407055e-02\n\n    expected_result[3, 1, Float32, FVLinear()] = 8.5860058665275574e-02\n    expected_result[3, 2, Float32, FVLinear()] = 4.2844854295253754e-02\n    expected_result[3, 3, Float32, FVLinear()] = 1.9302234053611755e-02\n    expected_result[3, 4, Float32, FVLinear()] = 8.6139924824237823e-03\n\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64, Float32)\n            numlevels =\n                integration_testing ||\n                ClimateMachine.Settings.integration_testing ?\n                4 : 1\n            result = zeros(FT, numlevels)\n            for dim in 2:3\n                N = (ntuple(j -> 4, dim - 1)..., 0)\n                n =\n                    dim == 2 ? SVector{3, FT}(1 / sqrt(2), 1 / sqrt(2), 0) :\n                    SVector{3, FT}(1 / sqrt(3), 1 / sqrt(3), 1 / sqrt(3))\n                α = FT(1)\n                connectivity = dim == 2 ? :face : :full\n\n                for fvmethod in (FVConstant(), FVLinear())\n                    @info @sprintf \"\"\"Configuration\n                                      FT                = %s\n                                      ArrayType         = %s\n                                      FV Reconstruction = %s\n                                      dims              = %d\n                                      \"\"\" FT ArrayType fvmethod dim\n                    for l in 1:numlevels\n                        Ne = 2^(l - 1) * base_num_elem\n                        brickrange = (\n                            ntuple(\n                                j -> range(FT(-1); length = Ne + 1, stop = 1),\n                                dim - 1,\n                            )...,\n                            range(FT(-1); length = N[1] * Ne + 1, stop = 1),\n                        )\n                        periodicity = ntuple(j -> false, dim)\n                        bc = ntuple(j -> (1, 1), dim)\n                        topl = StackedBrickTopology(\n                            mpicomm,\n                            brickrange;\n                            periodicity = periodicity,\n                            boundary = bc,\n                            connectivity = connectivity,\n                        )\n                        dt = (α / 4) / (Ne * max(1, maximum(N))^2)\n\n                        timeend = FT(1 // 4)\n                        outputtime = timeend\n\n                        dt = outputtime / ceil(Int64, outputtime / dt)\n\n                        vtkdir =\n                            output ?\n                            \"vtk_advection\" *\n                            \"_poly$(N)\" *\n                            \"_dim$(dim)_$(ArrayType)_$(FT)\" *\n                            \"_level$(l)\" :\n                            nothing\n                        result[l] = test_run(\n                            mpicomm,\n                            ArrayType,\n                            fvmethod,\n                            dim,\n                            topl,\n                            N,\n                            timeend,\n                            FT,\n                            dt,\n                            n,\n                            α,\n                            vtkdir,\n                            outputtime,\n                        )\n                        @test result[l] ≈\n                              FT(expected_result[dim, l, FT, fvmethod])\n                    end\n                    @info begin\n                        msg = \"\"\n                        for l in 1:(numlevels - 1)\n                            rate = log2(result[l]) - log2(result[l + 1])\n                            msg *= @sprintf(\n                                \"\\n  rate for level %d = %e\\n\",\n                                l,\n                                rate\n                            )\n                        end\n                        msg\n                    end\n                end\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/fvm_advection_diffusion.jl",
    "content": "using MPI\nusing ClimateMachine\nusing Logging\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods.FVReconstructions: FVConstant, FVLinear\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing LinearAlgebra\nusing Printf\nusing Test\nimport ClimateMachine.VTK: writevtk, writepvtu\nimport ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing Dates\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nconst output = parse(Bool, lowercase(get(ENV, \"JULIA_CLIMA_OUTPUT\", \"false\")))\n\ninclude(\"advection_diffusion_model.jl\")\n\nstruct Pseudo1D{ns, α, β, μ, δ} <: AdvectionDiffusionProblem end\n\nfunction init_velocity_diffusion!(\n    ::Pseudo1D{ns, α, β},\n    aux::Vars,\n    geom::LocalGeometry,\n) where {ns, α, β}\n    # Direction of flow is ns[i] with magnitude α\n    aux.advection.u = hcat(ntuple(i -> α * ns[i], Val(length(ns)))...)\n\n    # Diffusion of strength β in the ns[i] direction\n    aux.diffusion.D = hcat(ntuple(i -> β * ns[i] * ns[i]', Val(length(ns)))...)\nend\n\nfunction gaussian(x, t, α, β, μ, δ)\n    exp(-(x - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\nend\nfunction ∇gaussian(n, x, t, α, β, μ, δ)\n    -2n * (x - μ - α * t) / (4 * β * (δ + t)) *\n    exp(-(x - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\nend\nfunction initial_condition!(\n    ::Pseudo1D{ns, α, β, μ, δ},\n    state,\n    aux,\n    localgeo,\n    t,\n) where {ns, α, β, μ, δ}\n    ρ = ntuple(Val(length(ns))) do i\n        ξn = dot(ns[i], localgeo.coord)\n        gaussian(ξn, t, α, β, μ, δ)\n    end\n    state.ρ = length(ns) == 1 ? ρ[1] : ρ\nend\n\ninhomogeneous_data!(::Val{0}, P::Pseudo1D, x...) = initial_condition!(P, x...)\n\nfunction inhomogeneous_data!(\n    ::Val{1},\n    ::Pseudo1D{ns, α, β, μ, δ},\n    ∇state,\n    aux,\n    x,\n    t,\n) where {ns, α, β, μ, δ}\n    ∇state.ρ = hcat(ntuple(Val(length(ns))) do i\n        ξn = dot(ns[i], x)\n        ∇gaussian(ns[i], ξn, t, α, β, μ, δ)\n    end...)\nend\n\nfunction do_output(mpicomm, vtkdir, vtkstep, dgfvm, Q, Qe, model, testname)\n    ## Name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dgfvm, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## Name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## Name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\n\nfunction test_run(\n    mpicomm,\n    dim,\n    fvmethod,\n    polynomialorders,\n    level,\n    ArrayType,\n    FT,\n    vtkdir,\n    direction,\n)\n\n    n_hd =\n        dim == 2 ? SVector{3, FT}(1, 0, 0) :\n        SVector{3, FT}(1 / sqrt(2), 1 / sqrt(2), 0)\n\n    n_vd = dim == 2 ? SVector{3, FT}(0, 1, 0) : SVector{3, FT}(0, 0, 1)\n\n    n_dg =\n        dim == 2 ? SVector{3, FT}(1 / sqrt(2), 1 / sqrt(2), 0) :\n        SVector{3, FT}(1 / sqrt(3), 1 / sqrt(3), 1 / sqrt(3))\n    connectivity = dim == 2 ? :face : :full\n    if direction isa EveryDirection\n        ns = (n_hd, n_vd, n_dg)\n    elseif direction isa HorizontalDirection\n        ns = (n_hd,)\n    elseif direction isa VerticalDirection\n        ns = (n_vd,)\n    end\n\n    α = FT(1)\n    β = FT(1 // 100)\n    μ = FT(-1 // 2)\n    δ = FT(1 // 10)\n\n    # Grid/topology information\n    base_num_elem = 4\n    Ne = 2^(level - 1) * base_num_elem\n    N = polynomialorders\n    L = ntuple(j -> FT(j == dim ? 1 : N[1]) / 4, dim)\n    brickrange = ntuple(j -> range(-L[j]; length = Ne + 1, stop = L[j]), dim)\n    periodicity = ntuple(j -> false, dim)\n    bc = ntuple(j -> (1, 2), dim)\n\n    topl = StackedBrickTopology(\n        mpicomm,\n        brickrange;\n        periodicity = periodicity,\n        boundary = bc,\n        connectivity = connectivity,\n    )\n\n    dt = (α / 4) * L[1] / (Ne * polynomialorders[1]^2)\n    timeend = 1\n    outputtime = timeend / 10\n    @info \"time step\" dt\n\n    @info @sprintf \"\"\"Test parameters:\n    FVM Reconstruction          = %s\n    ArrayType                   = %s\n    FloatType                   = %s\n    Dimension                   = %s\n    Direction                   = %s\n    Horizontal polynomial order = %s\n    Vertical polynomial order   = %s\n      \"\"\" fvmethod ArrayType FT dim direction polynomialorders[1] polynomialorders[end]\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorders,\n    )\n\n    bcs = (InhomogeneousBC{0}(), InhomogeneousBC{1}())\n    # Model being tested\n    model = AdvectionDiffusion{dim}(\n        Pseudo1D{ns, α, β, μ, δ}(),\n        bcs,\n        num_equations = length(ns),\n    )\n\n    # Main DG discretization\n    dgfvm = DGFVModel(\n        model,\n        grid,\n        fvmethod,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient();\n        direction = direction,\n    )\n\n    # Initialize all relevant state arrays and create solvers\n    Q = init_ode_state(dgfvm, FT(0))\n\n    eng0 = norm(Q, dims = (1, 3))\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\"\"\" eng0[1]\n\n    solver = LSRK54CarpenterKennedy(dgfvm, Q; dt = dt, t0 = 0)\n\n    # Set up the information callback\n    starttime = Ref(Dates.now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = Dates.now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                gettime(solver),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n    callbacks = (cbinfo,)\n    if ~isnothing(vtkdir)\n        # Create vtk dir\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # Output initial step\n        do_output(\n            mpicomm,\n            vtkdir,\n            vtkstep,\n            dgfvm,\n            Q,\n            Q,\n            model,\n            \"advection_diffusion\",\n        )\n\n        # Setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dgfvm, gettime(solver))\n            do_output(\n                mpicomm,\n                vtkdir,\n                vtkstep,\n                dgfvm,\n                Q,\n                Qe,\n                model,\n                \"advection_diffusion\",\n            )\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(Q, solver; timeend = timeend, callbacks = callbacks)\n\n    # Reference solution\n    engf = norm(Q, dims = (1, 3))\n    Q_ref = init_ode_state(dgfvm, FT(timeend))\n\n    engfe = norm(Q_ref, dims = (1, 3))\n    errf = norm(Q_ref .- Q, dims = (1, 3))\n\n    metrics = @. (engf, engf / eng0, engf - eng0, errf, errf / engfe)\n\n    @info begin\n        j = 1\n        msg = \"Finished\\n\"\n        if direction isa HorizontalDirection || direction isa EveryDirection\n            msg *= @sprintf \"\"\"\n            Horizontal field:\n              norm(Q)                 = %.16e\n              norm(Q) / norm(Q₀)      = %.16e\n              norm(Q) - norm(Q₀)      = %.16e\n              norm(Q - Qe)            = %.16e\n              norm(Q - Qe) / norm(Qe) = %.16e\n            \"\"\" ntuple(f -> metrics[f][j], 5)...\n            j += 1\n        end\n\n        if direction isa VerticalDirection || direction isa EveryDirection\n            msg *= @sprintf \"\"\"\n            Vertical field:\n              norm(Q)                 = %.16e\n              norm(Q) / norm(Q₀)      = %.16e\n              norm(Q) - norm(Q₀)      = %.16e\n              norm(Q - Qe)            = %.16e\n              norm(Q - Qe) / norm(Qe) = %.16e\n            \"\"\" ntuple(f -> metrics[f][j], 5)...\n            j += 1\n        end\n\n        if direction isa EveryDirection\n            msg *= @sprintf \"\"\"\n            Diagonal field:\n              norm(Q)                 = %.16e\n              norm(Q) / norm(Q₀)      = %.16e\n              norm(Q) - norm(Q₀)      = %.16e\n              norm(Q - Qe)            = %.16e\n              norm(Q - Qe) / norm(Qe) = %.16e\n            \"\"\" ntuple(f -> metrics[f][j], 5)...\n        end\n        msg\n    end\n\n    return Tuple(errf)\nend\n\n\"\"\"\n    main()\n\nRun this test problem\n\"\"\"\nfunction main()\n\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n    mpicomm = MPI.COMM_WORLD\n\n    # Dictionary keys: dim, level, and FT\n    expected_result = Dict()\n    expected_result[2, 1, Float32, FVConstant()] =\n        (2.3391991853713989e-02, 5.0738707184791565e-02, 4.1857458651065826e-02)\n    expected_result[2, 2, Float32, FVConstant()] =\n        (2.0331495907157660e-03, 3.3669386059045792e-02, 2.3904174566268921e-02)\n    expected_result[2, 3, Float32, FVConstant()] =\n        (2.6557327146292664e-05, 2.0002065226435661e-02, 1.2790882028639317e-02)\n\n    expected_result[2, 1, Float32, FVLinear()] =\n        (2.3391995579004288e-02, 4.9536284059286118e-02, 3.5911198705434799e-02)\n    expected_result[2, 2, Float32, FVLinear()] =\n        (2.0331430714577436e-03, 2.2621221840381622e-02, 1.5531544573605061e-02)\n    expected_result[2, 3, Float32, FVLinear()] =\n        (2.6568108296487480e-05, 7.5304862111806870e-03, 6.4526572823524475e-03)\n\n    expected_result[3, 1, Float32, FVConstant()] =\n        (8.7377587333321571e-03, 7.1755334734916687e-02, 4.3733172118663788e-02)\n    expected_result[3, 2, Float32, FVConstant()] =\n        (6.2517996411770582e-04, 4.7615684568881989e-02, 2.3986011743545532e-02)\n    expected_result[3, 3, Float32, FVConstant()] =\n        (3.5004395613214001e-05, 2.8287241235375404e-02, 1.2639496475458145e-02)\n\n    expected_result[3, 1, Float32, FVLinear()] =\n        (8.7377615272998810e-03, 7.0054858922958374e-02, 3.7571556866168976e-02)\n    expected_result[3, 2, Float32, FVLinear()] =\n        (6.2518107006326318e-04, 3.1991228461265564e-02, 1.6405479982495308e-02)\n    expected_result[3, 3, Float32, FVLinear()] =\n        (3.5004966775886714e-05, 1.0649790056049824e-02, 7.1499347686767578e-03)\n\n    expected_result[2, 1, Float64, FVConstant()] =\n        (2.3391871809628567e-02, 5.0738761087541523e-02, 4.1857480220018319e-02)\n    expected_result[2, 2, Float64, FVConstant()] =\n        (2.0332783913617892e-03, 3.3669399006499491e-02, 2.3904204301839819e-02)\n    expected_result[2, 3, Float64, FVConstant()] =\n        (2.6572168347086839e-05, 2.0002110124502395e-02, 1.2790993871268752e-02)\n    expected_result[2, 4, Float64, FVConstant()] =\n        (1.9890000550154039e-07, 1.0929144069594809e-02, 6.5110938897763263e-03)\n\n    expected_result[2, 1, Float64, FVLinear()] =\n        (2.3391871809628550e-02, 4.9536356061135857e-02, 3.5911213637569099e-02)\n    expected_result[2, 2, Float64, FVLinear()] =\n        (2.0332783913618278e-03, 2.2621252826152356e-02, 1.5531565558700888e-02)\n    expected_result[2, 3, Float64, FVLinear()] =\n        (2.6572168347111800e-05, 7.5305291439555161e-03, 6.4526740563561544e-03)\n    expected_result[2, 4, Float64, FVLinear()] =\n        (1.9890000549995218e-07, 2.5302226025389427e-03, 2.7792905025059711e-03)\n\n    expected_result[3, 1, Float64, FVConstant()] =\n        (8.7378337431297422e-03, 7.1755444068009461e-02, 4.3733196512722658e-02)\n    expected_result[3, 2, Float64, FVConstant()] =\n        (6.2510740807095622e-04, 4.7615720711942776e-02, 2.3986017606198881e-02)\n    expected_result[3, 3, Float64, FVConstant()] =\n        (3.4995405318038341e-05, 2.8287255414151377e-02, 1.2639742577376042e-02)\n    expected_result[3, 4, Float64, FVConstant()] =\n        (1.4362091045094841e-06, 1.5456143768350493e-02, 6.3677406803847331e-03)\n\n    expected_result[3, 1, Float64, FVLinear()] =\n        (8.7378337431297439e-03, 7.0054986572200995e-02, 3.7571599264936015e-02)\n    expected_result[3, 2, Float64, FVLinear()] =\n        (6.2510740807095286e-04, 3.1991282544615307e-02, 1.6405489038457288e-02)\n    expected_result[3, 3, Float64, FVLinear()] =\n        (3.4995405318035868e-05, 1.0649776447227678e-02, 7.1502921502446283e-03)\n    expected_result[3, 4, Float64, FVLinear()] =\n        (1.4362091045110612e-06, 3.5782751203335653e-03, 3.1186151510318319e-03)\n\n    @testset \"Variable degree DG: advection diffusion model\" begin\n        for FT in (Float32, Float64)\n            for direction in\n                (EveryDirection(), HorizontalDirection(), VerticalDirection())\n                numlevels =\n                    integration_testing ||\n                    ClimateMachine.Settings.integration_testing ?\n                    (FT == Float64 ? 4 : 3) : 1\n                for dim in 2:3\n                    for fvmethod in (FVConstant(), FVLinear(), FVLinear{3}())\n                        polynomialorders = (4, 0)\n                        result = Dict()\n                        for level in 1:numlevels\n                            vtkdir =\n                                output ?\n                                \"vtk_advection\" *\n                                \"_poly$(polynomialorders)\" *\n                                \"_dim$(dim)_$(ArrayType)_$(FT)\" *\n                                \"_fvmethod$(fvmethod)\" *\n                                \"_level$(level)\" :\n                                nothing\n                            result[level] = test_run(\n                                mpicomm,\n                                dim,\n                                fvmethod,\n                                polynomialorders,\n                                level,\n                                ArrayType,\n                                FT,\n                                vtkdir,\n                                direction,\n                            )\n                            fv_key =\n                                fvmethod isa FVLinear ? FVLinear() : fvmethod\n                            if direction isa EveryDirection\n                                @test all(\n                                    result[level] .≈\n                                    FT.(expected_result[\n                                        dim,\n                                        level,\n                                        FT,\n                                        fv_key,\n                                    ]),\n                                )\n                            elseif direction isa HorizontalDirection\n                                @test result[level][1] .≈ FT.(expected_result[\n                                    dim,\n                                    level,\n                                    FT,\n                                    fv_key,\n                                ])[1]\n                            elseif direction isa VerticalDirection\n                                @test result[level][1] .≈ FT.(expected_result[\n                                    dim,\n                                    level,\n                                    FT,\n                                    fv_key,\n                                ])[2]\n                            end\n                        end\n                        @info begin\n                            msg = \"\"\n                            for l in 1:(numlevels - 1)\n                                rate = @. log2(result[l]) - log2(result[l + 1])\n                                msg *= @sprintf(\"\\n  rates for level %d\", l)\n                                j = 1\n                                if direction isa HorizontalDirection ||\n                                   direction isa EveryDirection\n                                    msg *=\n                                        @sprintf(\", Horizontal = %e\", rate[j])\n                                    j += 1\n                                end\n                                if direction isa VerticalDirection ||\n                                   direction isa EveryDirection\n                                    msg *= @sprintf(\", Vertical = %e\", rate[j])\n                                    j += 1\n                                end\n                                if direction isa EveryDirection\n                                    msg *= @sprintf(\", Diagonal = %e\", rate[j])\n                                end\n                                msg *= \"\\n\"\n                            end\n                            msg\n                        end\n                    end\n                end\n            end\n        end\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/fvm_advection_diffusion_model_1dimex_bjfnks.jl",
    "content": "using MPI\nusing ClimateMachine\nusing Logging\nusing Test\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods.FVReconstructions: FVConstant, FVLinear\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.ODESolvers\nusing LinearAlgebra\nusing Printf\nusing Dates\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing ClimateMachine.VTK: writevtk, writepvtu\n\nif !@isdefined integration_testing\n    if length(ARGS) > 0\n        const integration_testing = parse(Bool, ARGS[1])\n    else\n        const integration_testing = parse(\n            Bool,\n            lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n        )\n    end\nend\n\nconst output = parse(Bool, lowercase(get(ENV, \"JULIA_CLIMA_OUTPUT\", \"false\")))\n\ninclude(\"advection_diffusion_model.jl\")\n\nstruct Pseudo1D{n, α, β, μ, δ} <: AdvectionDiffusionProblem end\n\nfunction init_velocity_diffusion!(\n    ::Pseudo1D{n, α, β},\n    aux::Vars,\n    geom::LocalGeometry,\n) where {n, α, β}\n    # Direction of flow is n with magnitude α\n    aux.advection.u = α * n\n\n    # Diffusion of strength β in the n direction\n    aux.diffusion.D = β * n * n'\nend\n\nfunction initial_condition!(\n    ::Pseudo1D{n, α, β, μ, δ},\n    state,\n    aux,\n    localgeo,\n    t,\n) where {n, α, β, μ, δ}\n    ξn = dot(n, localgeo.coord)\n    # ξT = SVector(localgeo.coord) - ξn * n\n    state.ρ = exp(-(ξn - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\nend\ninhomogeneous_data!(::Val{0}, P::Pseudo1D, x...) = initial_condition!(P, x...)\nfunction inhomogeneous_data!(\n    ::Val{1},\n    ::Pseudo1D{n, α, β, μ, δ},\n    ∇state,\n    aux,\n    x,\n    t,\n) where {n, α, β, μ, δ}\n    ξn = dot(n, x)\n    ∇state.ρ =\n        -(\n            2n * (ξn - μ - α * t) / (4 * β * (δ + t)) *\n            exp(-(ξn - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\n        )\nend\n\nfunction do_output(mpicomm, vtkdir, vtkstep, dgfvm, Q, Qe, model, testname)\n    ## Name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dgfvm, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## Name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## Name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    dim,\n    topl,\n    N,\n    fvmethod,\n    timeend,\n    FT,\n    dt,\n    n,\n    α,\n    β,\n    μ,\n    δ,\n    vtkdir,\n    outputtime,\n    fluxBC,\n)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = (N, 0),\n    )\n\n    bcs = (\n        InhomogeneousBC{0}(),\n        InhomogeneousBC{1}(),\n        HomogeneousBC{0}(),\n        HomogeneousBC{1}(),\n    )\n    model = AdvectionDiffusion{dim}(\n        Pseudo1D{n, α, β, μ, δ}(),\n        bcs,\n        flux_bc = fluxBC,\n    )\n    dgfvm = DGFVModel(\n        model,\n        grid,\n        fvmethod,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n        direction = EveryDirection(),\n    )\n\n    vdgfvm = DGFVModel(\n        model,\n        grid,\n        fvmethod,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n        state_auxiliary = dgfvm.state_auxiliary,\n        direction = VerticalDirection(),\n    )\n\n\n    Q = init_ode_state(dgfvm, FT(0))\n\n    linearsolver = BatchedGeneralizedMinimalResidual(\n        dgfvm,\n        Q;\n        max_subspace_size = 5,\n        atol = sqrt(eps(FT)) * 0.01,\n        rtol = sqrt(eps(FT)) * 0.01,\n    )\n\n    nonlinearsolver =\n        JacobianFreeNewtonKrylovSolver(Q, linearsolver; tol = 1e-4)\n\n    ode_solver = ARK2ImplicitExplicitMidpoint(\n        dgfvm,\n        vdgfvm,\n        NonLinearBackwardEulerSolver(\n            nonlinearsolver;\n            isadjustable = true,\n            preconditioner_update_freq = 1000,\n        ),\n        Q;\n        dt = dt,\n        t0 = 0,\n        split_explicit_implicit = false,\n    )\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\"\"\" eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                gettime(ode_solver),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n    callbacks = (cbinfo,)\n    if ~isnothing(vtkdir)\n        # Create vtk dir\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # Output initial step\n        do_output(\n            mpicomm,\n            vtkdir,\n            vtkstep,\n            dgfvm,\n            Q,\n            Q,\n            model,\n            \"advection_diffusion\",\n        )\n\n        # Setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dgfvm, gettime(ode_solver))\n            do_output(\n                mpicomm,\n                vtkdir,\n                vtkstep,\n                dgfvm,\n                Q,\n                Qe,\n                model,\n                \"advection_diffusion\",\n            )\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    numberofsteps = convert(Int64, cld(timeend, dt))\n    dt = timeend / numberofsteps\n\n    @info \"time step\" dt numberofsteps dt * numberofsteps timeend\n\n    solve!(\n        Q,\n        ode_solver;\n        numberofsteps = numberofsteps,\n        callbacks = callbacks,\n        adjustfinalstep = false,\n    )\n\n    # Print some end of the simulation information\n    engf = norm(Q)\n    Qe = init_ode_state(dgfvm, FT(timeend))\n\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    base_num_elem = 4\n\n    expected_result = Dict()\n    # dim, refinement level, FT, vertical scheme\n    expected_result[2, 1, Float64, FVConstant()] = 1.4890228213182394e-01\n    expected_result[2, 2, Float64, FVConstant()] = 1.1396608915201276e-01\n    expected_result[2, 3, Float64, FVConstant()] = 7.9360293305962212e-02\n\n    expected_result[2, 1, Float64, FVLinear()] = 1.0755278583097065e-01\n    expected_result[2, 2, Float64, FVLinear()] = 4.5924219102504410e-02\n    expected_result[2, 3, Float64, FVLinear()] = 1.0775256661783415e-02\n\n    expected_result[3, 1, Float64, FVConstant()] = 2.1167998484526718e-01\n    expected_result[3, 2, Float64, FVConstant()] = 1.5936623436529485e-01\n    expected_result[3, 3, Float64, FVConstant()] = 1.1069147173311458e-01\n\n    expected_result[3, 1, Float64, FVLinear()] = 1.5361757743888277e-01\n    expected_result[3, 2, Float64, FVLinear()] = 6.3622331921472902e-02\n    expected_result[3, 3, Float64, FVLinear()] = 1.4836612761110498e-02\n\n\n    numlevels = integration_testing ? 3 : 1\n\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64,)\n            result = zeros(FT, numlevels)\n            for dim in 2:3\n                connectivity = dim == 3 ? :full : :face\n                for fvmethod in (FVConstant(), FVLinear())\n\n\n                    for fluxBC in (true, false)\n                        d = dim == 2 ? FT[1, 10, 0] : FT[1, 1, 10]\n                        n = SVector{3, FT}(d ./ norm(d))\n\n                        α = FT(1)\n                        β = FT(1 // 100)\n                        μ = FT(-1 // 2)\n                        δ = FT(1 // 10)\n\n                        solvertype = \"HEVI_Nolinearsolver\"\n                        for l in 1:numlevels\n                            Ne = 2^(l - 1) * base_num_elem\n                            brickrange = (\n                                ntuple(\n                                    j -> range(\n                                        FT(-1);\n                                        length = Ne + 1,\n                                        stop = 1,\n                                    ),\n                                    dim - 1,\n                                )...,\n                                range(\n                                    FT(-5);\n                                    length = 5Ne * polynomialorder + 1,\n                                    stop = 5,\n                                ),\n                            )\n\n                            periodicity = ntuple(j -> false, dim)\n                            topl = StackedBrickTopology(\n                                mpicomm,\n                                brickrange;\n                                periodicity = periodicity,\n                                boundary = (\n                                    ntuple(j -> (1, 2), dim - 1)...,\n                                    (3, 4),\n                                ),\n                                connectivity = connectivity,\n                            )\n                            dt = 2 * (α / 4) / (Ne * polynomialorder^2)\n\n                            outputtime = 0.01\n                            timeend = 0.5\n\n                            @info (\n                                ArrayType,\n                                FT,\n                                dim,\n                                solvertype,\n                                l,\n                                polynomialorder,\n                                fvmethod,\n                                fluxBC,\n                            )\n                            vtkdir =\n                                output ?\n                                \"vtk_fvm_advection\" *\n                                \"_poly$(polynomialorder)\" *\n                                \"_dim$(dim)_$(ArrayType)_$(FT)\" *\n                                \"_fvmethod$(fvmethod)\" *\n                                \"_$(solvertype)_level$(l)\" :\n                                nothing\n                            result[l] = test_run(\n                                mpicomm,\n                                ArrayType,\n                                dim,\n                                topl,\n                                polynomialorder,\n                                fvmethod,\n                                timeend,\n                                FT,\n                                dt,\n                                n,\n                                α,\n                                β,\n                                μ,\n                                δ,\n                                vtkdir,\n                                outputtime,\n                                fluxBC,\n                            )\n                            # Test the errors significantly larger than floating point epsilon\n                            if !(dim == 2 && l == 4 && FT == Float32)\n                                @test result[l] ≈\n                                      FT(expected_result[dim, l, FT, fvmethod])\n                            end\n                        end\n                        @info begin\n                            msg = \"\"\n                            for l in 1:(numlevels - 1)\n                                rate = log2(result[l]) - log2(result[l + 1])\n                                msg *= @sprintf(\n                                    \"\\n  rate for level %d = %e\\n\",\n                                    l,\n                                    rate\n                                )\n                            end\n                            msg\n                        end\n                    end\n                end\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/fvm_advection_diffusion_periodic.jl",
    "content": "using MPI\nusing ClimateMachine\nusing Logging\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nimport ClimateMachine.DGMethods.FVReconstructions: FVConstant, FVLinear\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing LinearAlgebra\nusing Printf\nusing Test\nusing Dates\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing ClimateMachine.VTK: writevtk, writepvtu\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nconst output = parse(Bool, lowercase(get(ENV, \"JULIA_CLIMA_OUTPUT\", \"false\")))\n\ninclude(\"advection_diffusion_model.jl\")\n\nstruct Pseudo1D{u, v, ν} <: AdvectionDiffusionProblem end\n\n# This test has two 2D equations : the first one is an advection equation with\n# the Gaussian initial condition the second one is an advection diffusion\n# equation with the Gaussian initial condition\nfunction init_velocity_diffusion!(\n    ::Pseudo1D{u, v, ν},\n    aux::Vars,\n    geom::LocalGeometry,\n) where {u, v, ν}\n    # Advection velocity of the flow is [u, v]\n    uvec = SVector(u, v, 0)\n    aux.advection.u = hcat(uvec, uvec)\n\n    # Diffusion of the flow is νI (isentropic diffusivity)\n    I3 = @SMatrix [1 0 0; 0 1 0; 0 0 1]\n    aux.diffusion.D = hcat(0 * I3, ν * I3)\n\nend\n\nfunction initial_condition!(\n    ::Pseudo1D{u, v, ν},\n    state,\n    aux,\n    localgeo,\n    t,\n) where {u, v, ν}\n    FT = typeof(u)\n    # The computational domain is [-1.5 1.5]×[-1.5 1.5]\n    Lx, Ly = 3, 3\n    x, y, _ = localgeo.coord\n\n    μx, μz, σ = 0, 0, Lx / 10\n    ρ1 = exp.(-(((x - μx) / σ)^2 + ((y - μz) / σ)^2) / 2) / (σ * sqrt(2 * pi))\n    ρ2 = exp.(-(((x - μx) / σ)^2 + ((y - μz) / σ)^2) / 2) / (σ * sqrt(2 * pi))\n\n    state.ρ = (ρ1, ρ2)\nend\n\ninhomogenous_data!(::Val{0}, P::Pseudo1D, x...) = initial_condition!(P, x...)\n\n\nfunction do_output(mpicomm, vtkdir, vtkstep, dgfvm, Q, Qe, model, testname)\n    ## Name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dgfvm, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## Name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## Name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\n\nfunction test_run(\n    mpicomm,\n    vtkdir,\n    fvmethod,\n    polynomialorders,\n    level,\n    ArrayType,\n    FT,\n)\n\n    dim = 2\n    Lx, Ly = FT(3), FT(3)\n    u, v = FT(1), FT(1)\n    ν = FT(1 // 100)\n\n\n    # Grid/topology information\n    base_num_elem = 4\n    Ne = 2^(level - 1) * base_num_elem\n\n    # Match number of points\n    N_dg_point, N_fvm_point = Ne + 1, Ne * polynomialorders[1] + 1\n\n\n    brickrange = (\n        range(-Lx / 2; length = N_dg_point, stop = Lx / 2),\n        range(-Ly / 2; length = N_fvm_point, stop = Ly / 2),\n    )\n\n    periodicity = ntuple(j -> true, dim)\n    bc = ntuple(j -> (1, 2), dim)\n    connectivity = dim == 3 ? :full : :face\n    topl = StackedBrickTopology(\n        mpicomm,\n        brickrange;\n        periodicity = periodicity,\n        boundary = bc,\n        connectivity = connectivity,\n    )\n\n    # One period\n    timeend = Lx / u\n    # dt ≤ CFL (Δx / Np²)/u   u = √2  CFL = 1/√2\n    dt = Lx / (Ne * polynomialorders[1]^2)\n\n    Nt = (Ne * polynomialorders[1]^2)\n\n    outputtime = Ne * dt\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorders,\n    )\n\n    # Model being tested\n    model = AdvectionDiffusion{dim}(Pseudo1D{u, v, ν}(), num_equations = 2)\n\n    # Main DG discretization\n    dgfvm = DGFVModel(\n        model,\n        grid,\n        fvmethod,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient();\n        direction = EveryDirection(),\n    )\n\n    # Initialize all relevant state arrays and create solvers\n    Q = init_ode_state(dgfvm, FT(0))\n\n    solver = LSRK54CarpenterKennedy(dgfvm, Q; dt = dt, t0 = 0)\n\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                gettime(solver),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n    callbacks = (cbinfo,)\n    if ~isnothing(vtkdir)\n        # Create vtk dir\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # Output initial step\n        do_output(\n            mpicomm,\n            vtkdir,\n            vtkstep,\n            dgfvm,\n            Q,\n            Q,\n            model,\n            \"advection_diffusion_periodic\",\n        )\n\n        # Setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dgfvm, gettime(solver))\n            do_output(\n                mpicomm,\n                vtkdir,\n                vtkstep,\n                dgfvm,\n                Q,\n                Qe,\n                model,\n                \"advection_diffusion_periodic\",\n            )\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    numberofsteps = convert(Int64, cld(timeend, dt))\n    dt = timeend / numberofsteps\n\n    @info \"time step\" dt numberofsteps dt * numberofsteps timeend\n\n    solve!(Q, solver; timeend = timeend, callbacks = callbacks)\n\n    engf = norm(Q, dims = (1, 3))\n\n    # Reference solution\n    Q_ref = init_ode_state(dgfvm, FT(0))\n    engfe = norm(Q_ref, dims = (1, 3))\n\n    errf = norm(Q_ref .- Q, dims = (1, 3))\n\n    metrics = [engf; engfe; errf]\n\n    @info @sprintf \"\"\"Finished\n    Advection equation:\n    norm(Q)                 = %.16e\n    norm(Qe)                = %.16e\n    norm(Q - Qe)            = %.16e\n    Advection diffusion equation:\n    norm(Q)                 = %.16e\n    norm(Qe)                = %.16e\n    norm(Q - Qe)            = %.16e\n    \"\"\" metrics[:]...\n\n    return errf\nend\n\n# Run this test problem\nfunction main()\n\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n    mpicomm = MPI.COMM_WORLD\n\n    # Dictionary keys: dim, level, polynomial order, FT, and direction\n    expected_result = Dict()\n\n    # Dim 2, degree 4 in the horizontal, FV order 1, refinement level, Float64, equation number\n    expected_result[2, 4, FVConstant(), 1, Float64, 1] = 4.5196354911392578e-01\n    expected_result[2, 4, FVConstant(), 1, Float64, 2] = 4.8622171647472179e-01\n    expected_result[2, 4, FVConstant(), 2, Float64, 1] = 3.5051983693457450e-01\n    expected_result[2, 4, FVConstant(), 2, Float64, 2] = 4.1168975732563706e-01\n    expected_result[2, 4, FVConstant(), 3, Float64, 1] = 2.5130141068332995e-01\n    expected_result[2, 4, FVConstant(), 3, Float64, 2] = 3.4635415661628755e-01\n    expected_result[2, 4, FVConstant(), 4, Float64, 1] = 1.6320856055402777e-01\n    expected_result[2, 4, FVConstant(), 4, Float64, 2] = 2.9647989774687805e-01\n    expected_result[2, 4, FVConstant(), 5, Float64, 1] = 9.6641259241910610e-02\n    expected_result[2, 4, FVConstant(), 5, Float64, 2] = 2.6372336617960646e-01\n\n    expected_result[2, 4, FVConstant(), 1, Float32, 1] = 4.5196333527565002e-01\n    expected_result[2, 4, FVConstant(), 1, Float32, 2] = 4.8622152209281921e-01\n    expected_result[2, 4, FVConstant(), 2, Float32, 1] = 3.5051953792572021e-01\n    expected_result[2, 4, FVConstant(), 2, Float32, 2] = 4.1168937087059021e-01\n    expected_result[2, 4, FVConstant(), 3, Float32, 1] = 2.5130018591880798e-01\n    expected_result[2, 4, FVConstant(), 3, Float32, 2] = 3.4635293483734131e-01\n    expected_result[2, 4, FVConstant(), 4, Float32, 1] = 1.6320574283599854e-01\n    expected_result[2, 4, FVConstant(), 4, Float32, 2] = 2.9647585749626160e-01\n    expected_result[2, 4, FVConstant(), 5, Float32, 1] = 9.6632070839405060e-02\n    expected_result[2, 4, FVConstant(), 5, Float32, 2] = 2.6370745897293091e-01\n\n\n    # Dim 2, degree 4 in the horizontal, FV order 1, refinement level, Float64, equation number\n\n    expected_result[2, 4, FVLinear(), 1, Float64, 1] = 2.2783152269907422e-01\n    expected_result[2, 4, FVLinear(), 1, Float64, 2] = 3.1823753821941969e-01\n    expected_result[2, 4, FVLinear(), 2, Float64, 1] = 9.2628269590376469e-02\n    expected_result[2, 4, FVLinear(), 2, Float64, 2] = 2.4517823755458742e-01\n    expected_result[2, 4, FVLinear(), 3, Float64, 1] = 3.1401247771425542e-02\n    expected_result[2, 4, FVLinear(), 3, Float64, 2] = 2.2608977452273774e-01\n    expected_result[2, 4, FVLinear(), 4, Float64, 1] = 9.8710350648653390e-03\n    expected_result[2, 4, FVLinear(), 4, Float64, 2] = 2.2377315397364847e-01\n    expected_result[2, 4, FVLinear(), 5, Float64, 1] = 2.9619222883137744e-03\n    expected_result[2, 4, FVLinear(), 5, Float64, 2] = 2.2359698753564772e-01\n\n\n    expected_result[2, 4, FVLinear(), 1, Float32, 1] = 2.2783152269907422e-01\n    expected_result[2, 4, FVLinear(), 1, Float32, 2] = 3.1823753821941969e-01\n    expected_result[2, 4, FVLinear(), 2, Float32, 1] = 9.2628269590376469e-02\n    expected_result[2, 4, FVLinear(), 2, Float32, 2] = 2.4517823755458742e-01\n    expected_result[2, 4, FVLinear(), 3, Float32, 1] = 3.1401247771425542e-02\n    expected_result[2, 4, FVLinear(), 3, Float32, 2] = 2.2608977452273774e-01\n    expected_result[2, 4, FVLinear(), 4, Float32, 1] = 9.8710350648653390e-03\n    expected_result[2, 4, FVLinear(), 4, Float32, 2] = 2.2377315397364847e-01\n    expected_result[2, 4, FVLinear(), 5, Float32, 1] = 2.9614968225359917e-03\n    expected_result[2, 4, FVLinear(), 5, Float32, 2] = 2.2358775138854980e-01\n\n    polynomialorders = (4, 0)\n    numlevels = integration_testing ? 5 : 1\n\n    # Dictionary keys: dim, level, polynomial order, FT, and direction\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64, Float32)\n            result = Dict()\n            for fvmethod in (FVConstant(), FVLinear(), FVLinear{3}())\n                @info @sprintf \"\"\"Test parameters:\n                ArrayType                   = %s\n                FloatType                   = %s\n                FV Reconstruction           = %s\n                Dimension                   = %s\n                Horizontal polynomial order = %s\n                Vertical polynomial order   = %s\n                \"\"\" ArrayType FT fvmethod 2 polynomialorders[1] polynomialorders[end]\n                for level in 1:numlevels\n                    result[level] = test_run(\n                        mpicomm,\n                        output ?\n                        \"vtk_advection_diffusion_2d\" *\n                        \"_poly$(polynomialorders)\" *\n                        \"_$(ArrayType)_$(FT)\" *\n                        \"_level$(level)\" :\n                        nothing,\n                        fvmethod,\n                        polynomialorders,\n                        level,\n                        ArrayType,\n                        FT,\n                    )\n\n\n                    fv_key = fvmethod isa FVLinear ? FVLinear() : fvmethod\n                    @test result[level][1] ≈ FT(expected_result[\n                        2,\n                        polynomialorders[1],\n                        fv_key,\n                        level,\n                        FT,\n                        1,\n                    ])\n                    @test result[level][2] ≈ FT(expected_result[\n                        2,\n                        polynomialorders[1],\n                        fv_key,\n                        level,\n                        FT,\n                        2,\n                    ])\n                end\n\n                @info begin\n                    msg = \"advection equation\"\n                    for l in 1:(numlevels - 1)\n                        rate = log2(result[l][1]) - log2(result[l + 1][1])\n                        msg *= @sprintf(\"\\n  rate for level %d = %e\", l, rate)\n                    end\n                    msg\n                end\n                # Not an analytic solution, convergence doesn't make sense\n                # @info begin\n                #     msg = \"advection-diffusion equation\"\n                #     for l in 1:(numlevels - 1)\n                #         rate = log2(result[l][2]) - log2(result[l + 1][2])\n                #         msg *= @sprintf(\"\\n  rate for level %d = %e\", l, rate)\n                #     end\n                #     msg\n                # end\n            end\n        end\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/fvm_advection_sphere.jl",
    "content": "using MPI\nusing ClimateMachine\nusing Logging\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.BalanceLaws: update_auxiliary_state!\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods.FVReconstructions: FVConstant, FVLinear\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Atmos: SphericalOrientation, latitude, longitude\nusing ClimateMachine.Orientations\nusing LinearAlgebra\nusing Printf\nusing Dates\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing ClimateMachine.VTK: writevtk, writepvtu\nimport ClimateMachine.BalanceLaws: boundary_state!\nusing CLIMAParameters.Planet: planet_radius\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nconst output = parse(Bool, lowercase(get(ENV, \"JULIA_CLIMA_OUTPUT\", \"false\")))\n\ninclude(\"advection_diffusion_model.jl\")\n\n\n# This is a setup similar to the one presented in [Williamson1992](@cite)\nstruct SolidBodyRotation <: AdvectionDiffusionProblem end\nfunction init_velocity_diffusion!(\n    ::SolidBodyRotation,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    FT = eltype(aux)\n    λ = longitude(SphericalOrientation(), aux)\n    φ = latitude(SphericalOrientation(), aux)\n    r = norm(geom.coord)\n\n    uλ = 2 * FT(π) * cos(φ) * r\n    uφ = 0\n    aux.advection.u = SVector(\n        -uλ * sin(λ) - uφ * cos(λ) * sin(φ),\n        +uλ * cos(λ) - uφ * sin(λ) * sin(φ),\n        +uφ * cos(φ),\n    )\nend\nfunction initial_condition!(::SolidBodyRotation, state, aux, localgeo, t)\n    λ = longitude(SphericalOrientation(), aux)\n    φ = latitude(SphericalOrientation(), aux)\n    state.ρ = exp(-((3λ)^2 + (3φ)^2))\nend\nfinaltime(::SolidBodyRotation) = 1\nu_scale(::SolidBodyRotation) = 2π\n\n\"\"\"\nThis is the Divergent flow with smooth initial condition test case, the Case 4 in\n\n@article{nair2010class,\n  title={A class of deformational flow test cases for linear transport problems on the sphere},\n  author={Nair, Ramachandran D and Lauritzen, Peter H},\n  journal={Journal of Computational Physics},\n  volume={229},\n  number={23},\n  pages={8868--8887},\n  year={2010},\n  publisher={Elsevier}\n}\n\n\"\"\"\nstruct ReversingDeformationalFlow <: AdvectionDiffusionProblem end\ninit_velocity_diffusion!(\n    ::ReversingDeformationalFlow,\n    aux::Vars,\n    geom::LocalGeometry,\n) = nothing\nfunction initial_condition!(::ReversingDeformationalFlow, state, aux, coord, t)\n    x, y, z = aux.coord\n    r = norm(aux.coord)\n    h_max = 0.95\n    b = 5\n    state.ρ = 0\n    for (λ, φ) in ((5π / 6, 0), (7π / 6, 0))\n        xi = r * cos(φ) * cos(λ)\n        yi = r * cos(φ) * sin(λ)\n        zi = r * sin(φ)\n        state.ρ +=\n            h_max * exp(-b * ((x - xi)^2 + (y - yi)^2 + (z - zi)^2) / r^2)\n    end\nend\nhas_variable_coefficients(::ReversingDeformationalFlow) = true\nfunction update_velocity_diffusion!(\n    ::ReversingDeformationalFlow,\n    ::AdvectionDiffusion,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    FT = eltype(aux)\n    λ = longitude(SphericalOrientation(), aux)\n    φ = latitude(SphericalOrientation(), aux)\n    r = norm(aux.coord)\n    T = FT(5)\n    λp = λ - FT(2π) * t / T\n    uλ =\n        10 * r / T * sin(λp)^2 * sin(2φ) * cos(FT(π) * t / T) +\n        FT(2π) * r / T * cos(φ)\n    uφ = 10 * r / T * sin(2λp) * cos(φ) * cos(FT(π) * t / T)\n    aux.advection.u = SVector(\n        -uλ * sin(λ) - uφ * cos(λ) * sin(φ),\n        +uλ * cos(λ) - uφ * sin(λ) * sin(φ),\n        +uφ * cos(φ),\n    )\nend\nu_scale(::ReversingDeformationalFlow) = 2.9\nfinaltime(::ReversingDeformationalFlow) = 5\n\nfunction advective_courant(\n    m::AdvectionDiffusion,\n    state::Vars,\n    aux::Vars,\n    diffusive::Vars,\n    Δx,\n    Δt,\n    direction,\n)\n    return Δt * norm(aux.advection.u) / Δx\nend\n\nstruct NoFlowBC end\nfunction boundary_state!(\n    ::RusanovNumericalFlux,\n    ::NoFlowBC,\n    ::AdvectionDiffusion,\n    stateP::Vars,\n    auxP::Vars,\n    nM,\n    stateM::Vars,\n    auxM::Vars,\n    t,\n    _...,\n)\n    auxP.advection.u = -auxM.advection.u\nend\n\nfunction do_output(mpicomm, vtkdir, vtkstep, dgfvm, Q, Qe, model, testname)\n    ## Name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dgfvm, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## Name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## Name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    vert_range,\n    topl,\n    problem,\n    explicit_method,\n    fvmethod,\n    cfl,\n    N,\n    timeend,\n    FT,\n    vtkdir,\n    outputtime,\n)\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = (N, 0),\n        meshwarp = equiangular_cubed_sphere_warp,\n    )\n\n    dx = min_node_distance(grid, HorizontalDirection())\n    dt = FT(cfl * dx / (vert_range[2] * u_scale(problem())) / N)\n    dt = outputtime / ceil(Int64, outputtime / dt)\n\n    bcs = (NoFlowBC(),)\n    model = AdvectionDiffusion{3}(problem(), bcs, diffusion = false)\n    dgfvm = DGFVModel(\n        model,\n        grid,\n        fvmethod,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q = init_ode_state(dgfvm, FT(0))\n\n    odesolver = explicit_method(dgfvm, Q; dt = dt, t0 = 0)\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n    problem           = %s\n    ArrayType         = %s\n    FV Reconstruction = %s\n    method            = %s\n    time step         = %.16e\n    norm(Q₀)          = %.16e\"\"\" problem ArrayType fvmethod explicit_method dt eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                gettime(odesolver),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n    cbcfl = EveryXSimulationSteps(10) do\n        dt = ODESolvers.getdt(odesolver)\n        cfl = DGMethods.courant(\n            advective_courant,\n            dgfvm,\n            model,\n            Q,\n            dt,\n            HorizontalDirection(),\n        )\n        @info @sprintf(\n            \"\"\"Courant number\n            simtime = %.16e\n            courant = %.16e\"\"\",\n            gettime(odesolver),\n            cfl\n        )\n    end\n    callbacks = (cbinfo,)\n    if ~isnothing(vtkdir)\n        # Create vtk dir\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # Output initial step\n        do_output(\n            mpicomm,\n            vtkdir,\n            vtkstep,\n            dgfvm,\n            Q,\n            Q,\n            model,\n            \"fvm_advection_sphere\",\n        )\n\n        # Setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dgfvm, gettime(odesolver))\n            do_output(\n                mpicomm,\n                vtkdir,\n                vtkstep,\n                dgfvm,\n                Q,\n                Qe,\n                model,\n                \"fvm_advection_sphere\",\n            )\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(Q, odesolver; timeend = timeend, callbacks = callbacks)\n\n    # Print some end of the simulation information\n    engf = norm(Q)\n    Qe = init_ode_state(dgfvm, FT(timeend))\n\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    Δmass = abs(weightedsum(Q) - weightedsum(Qe)) / weightedsum(Qe)\n    @info @sprintf \"\"\"Finished\n    Δmass                   = %.16e\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" Δmass engf engf / eng0 engf - eng0 errf errf / engfe\n    return errf, Δmass\nend\n\nusing Test\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    base_num_elem = 3\n\n    max_cfl = Dict(LSRK144NiegemannDiehlBusch => 5.0)\n\n    expected_result = Dict()\n\n    expected_result[SolidBodyRotation, 1, FVConstant] = 1.6249678501611961e+07\n    expected_result[SolidBodyRotation, 2, FVConstant] = 7.2020207047738554e+05\n    expected_result[SolidBodyRotation, 3, FVConstant] = 5.2452627634607365e+04\n    expected_result[SolidBodyRotation, 4, FVConstant] = 3.1132403618328990e+03\n\n    expected_result[SolidBodyRotation, 1, FVLinear] = 1.6649963041466445e+07\n    expected_result[SolidBodyRotation, 2, FVLinear] = 7.2518593691652094e+05\n    expected_result[SolidBodyRotation, 3, FVLinear] = 5.2473203187596591e+04\n    expected_result[SolidBodyRotation, 4, FVLinear] = 3.1133127473480104e+03\n\n    expected_result[ReversingDeformationalFlow, 1, FVConstant] =\n        2.0097347028034222e+08\n    expected_result[ReversingDeformationalFlow, 2, FVConstant] =\n        7.0092390520693690e+07\n    expected_result[ReversingDeformationalFlow, 3, FVConstant] =\n        7.7527847763998993e+06\n    expected_result[ReversingDeformationalFlow, 4, FVConstant] =\n        1.4209138735343830e+05\n\n    expected_result[ReversingDeformationalFlow, 1, FVLinear] =\n        2.0156864805489743e+08\n    expected_result[ReversingDeformationalFlow, 2, FVLinear] =\n        7.0092714938572392e+07\n    expected_result[ReversingDeformationalFlow, 3, FVLinear] =\n        7.7527849036761308e+06\n    expected_result[ReversingDeformationalFlow, 4, FVLinear] =\n        1.4209138776027536e+05\n\n    numlevels =\n        integration_testing || ClimateMachine.Settings.integration_testing ? 4 :\n        1\n    explicit_method = LSRK144NiegemannDiehlBusch\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64,)\n            for problem in (SolidBodyRotation, ReversingDeformationalFlow)\n                for fvmethod in (FVConstant, FVLinear)\n                    cfl = max_cfl[explicit_method]\n                    result = zeros(FT, numlevels)\n                    for l in 1:numlevels\n                        numelems_horizontal = 2^(l - 1) * base_num_elem\n                        numelems_vertical = 3\n\n                        _planet_radius = FT(planet_radius(param_set))\n                        domain_height = FT(10e3)\n                        vert_range =\n                            (_planet_radius, _planet_radius + domain_height)\n\n                        topl = StackedCubedSphereTopology(\n                            mpicomm,\n                            numelems_horizontal,\n                            range(\n                                vert_range[1],\n                                stop = vert_range[2],\n                                length = numelems_vertical + 1,\n                            ),\n                        )\n\n                        timeend = finaltime(problem())\n                        outputtime = timeend\n\n                        @info (ArrayType, FT)\n                        vtkdir =\n                            output ?\n                            \"vtk_fvm_advection_sphere\" *\n                            \"_$problem\" *\n                            \"_$explicit_method\" *\n                            \"_poly$(polynomialorder)\" *\n                            \"_$(ArrayType)_$(FT)\" *\n                            \"_level$(l)\" :\n                            nothing\n\n                        result[l], Δmass = test_run(\n                            mpicomm,\n                            ArrayType,\n                            vert_range,\n                            topl,\n                            problem,\n                            explicit_method,\n                            fvmethod(),\n                            cfl,\n                            polynomialorder,\n                            timeend,\n                            FT,\n                            vtkdir,\n                            outputtime,\n                        )\n                        @test result[l] ≈\n                              FT(expected_result[problem, l, fvmethod])\n                        @test Δmass <= FT(1e-12)\n                    end\n                    @info begin\n                        msg = \"\"\n                        for l in 1:(numlevels - 1)\n                            rate = log2(result[l]) - log2(result[l + 1])\n                            msg *= @sprintf(\n                                \"\\n  rate for level %d = %e\\n\",\n                                l,\n                                rate\n                            )\n                        end\n                        msg\n                    end\n                end\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/fvm_swirl.jl",
    "content": "# This tutorial uses the TMAR Filter from [Light2016](@cite)\n#\n# to reproduce the tutorial in section 4b.  It is a shear swirling\n# flow deformation of a transported quantity from LeVeque 1996.  The exact\n# solution at the final time is the same as the initial condition.\n\nusing MPI\nusing Test\nusing ClimateMachine\nClimateMachine.init()\nusing Logging\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods.FVReconstructions: FVConstant, FVLinear\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing LinearAlgebra\nusing Printf\nusing Dates\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing ClimateMachine.VTK: writevtk, writepvtu\n\nusing ClimateMachine\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)))\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\nconst output = parse(Bool, lowercase(get(ENV, \"JULIA_CLIMA_OUTPUT\", \"false\")))\n\ninclude(\"advection_diffusion_model.jl\")\n\nBase.@kwdef struct SwirlingFlow{FT} <: AdvectionDiffusionProblem\n    period::FT = 5\nend\n\ninit_velocity_diffusion!(::SwirlingFlow, aux::Vars, geom::LocalGeometry) =\n    nothing\n\nfunction initial_condition!(::SwirlingFlow, state, aux, localgeo, t)\n    FT = eltype(state)\n    x, y, _ = aux.coord\n    x0, y0 = FT(1 // 3), FT(1 // 3)\n    τ = 6hypot(x - x0, y - y0)\n    state.ρ = exp(-τ^2)\nend\n\nhas_variable_coefficients(::SwirlingFlow) = true\nfunction update_velocity_diffusion!(\n    problem::SwirlingFlow,\n    ::AdvectionDiffusion,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    x, y, _ = aux.coord\n    sx, cx = sinpi(x), cospi(x)\n    sy, cy = sinpi(y), cospi(y)\n    ct = cospi(t / problem.period)\n\n    u = 2 * sx^2 * sy * cy * ct\n    v = -2 * sy^2 * sx * cx * ct\n    aux.advection.u = SVector(u, v, 0)\nend\n\nfunction do_output(\n    mpicomm,\n    vtkdir,\n    vtkstep,\n    dg,\n    Q,\n    model,\n    testname;\n    number_sample_points = 0,\n)\n    ## Name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n\n    writevtk(\n        filename,\n        Q,\n        dg,\n        statenames;\n        number_sample_points = number_sample_points,\n    )\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## Name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## Name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(pvtuprefix, prefixes, (statenames...,), eltype(Q))\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    fvmethod,\n    topl,\n    problem,\n    dt,\n    N,\n    timeend,\n    FT,\n    vtkdir,\n    outputtime,\n)\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    bcs = (HomogeneousBC{0}(),)\n    model = AdvectionDiffusion{2}(problem, bcs, diffusion = false)\n\n    dg = DGFVModel(\n        model,\n        grid,\n        fvmethod,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n\n    ## We integrate so that the final solution is equal to the initial solution\n    Qe = copy(Q)\n    odesolver = LSRK54CarpenterKennedy(dg, Q; dt = dt, t0 = 0)\n\n    # Set up the information callback\n    starttime = Ref(Dates.now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = Dates.now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                gettime(odesolver),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n    callbacks = (cbinfo,)\n    if ~isnothing(vtkdir)\n        # Create vtk dir\n        if MPI.Comm_rank(mpicomm) == 0\n            mkpath(vtkdir)\n        end\n        MPI.Barrier(mpicomm)\n\n        vtkstep = 0\n        # Output initial step\n        do_output(\n            mpicomm,\n            vtkdir,\n            vtkstep,\n            dg,\n            Q,\n            model,\n            \"swirling_flow\";\n            number_sample_points = N[1] + 1,\n        )\n\n        # Setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(odesolver))\n            do_output(\n                mpicomm,\n                vtkdir,\n                vtkstep,\n                dg,\n                Q,\n                model,\n                \"swirling_flow\";\n                number_sample_points = N[1] + 1,\n            )\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(Q, odesolver; timeend = timeend, callbacks = callbacks)\n\n    error = euclidean_distance(Q, Qe)\n\n    # Print some end of the simulation information\n    eng0 = norm(Qe)\n    engf = norm(Q)\n\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nlet\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    expected_result = Dict()\n    expected_result[1, FVConstant()] = 1.2437314516997458e-01\n    expected_result[2, FVConstant()] = 9.8829388561567838e-02\n    expected_result[3, FVConstant()] = 7.2096312912198937e-02\n    expected_result[4, FVConstant()] = 4.7720226730379636e-02\n    expected_result[1, FVLinear()] = 4.3807967239640234e-02\n    expected_result[2, FVLinear()] = 1.4913083518239653e-02\n    expected_result[3, FVLinear()] = 4.3666055848495802e-03\n    expected_result[4, FVLinear()] = 1.2749240022458719e-03\n\n    @testset \"$(@__FILE__)\" begin\n        numlevels =\n            integration_testing || ClimateMachine.Settings.integration_testing ?\n            4 : 1\n        FT = Float64\n        for fvmethod in (FVConstant(), FVLinear())\n            result = zeros(FT, numlevels)\n            for l in 1:numlevels\n                Ne = 20 * 2^(l - 1)\n                polynomialorder = (4, 0)\n\n                problem = SwirlingFlow()\n\n                brickrange = (\n                    range(FT(0); length = Ne + 1, stop = 1),\n                    range(\n                        FT(0);\n                        length = polynomialorder[1] * Ne + 1,\n                        stop = 1,\n                    ),\n                )\n\n                topology = StackedBrickTopology(\n                    mpicomm,\n                    brickrange,\n                    boundary = ((1, 1), (1, 1)),\n                    connectivity = :face,\n                )\n\n                maxvelocity = 2\n                elementsize = 1 / Ne\n                dx = elementsize / polynomialorder[1]^2\n                CFL = 1\n                dt = CFL * dx / maxvelocity\n\n                vtkdir = abspath(joinpath(\n                    ClimateMachine.Settings.output_dir,\n                    \"fvm_swirl_lvl_$l\",\n                ))\n\n                timeend = problem.period\n\n                outputtime = timeend / 10\n                dt = outputtime / ceil(Int64, outputtime / dt)\n\n                @info @sprintf \"\"\"Starting\n                FT               = %s\n                ArrayType        = %s\n                FV Reconstuction = %s\n                dim              = %d\n                Ne               = %d\n                polynomial order = %d\n                final time       = %.16e\n                time step        = %.16e\n                \"\"\" FT ArrayType fvmethod 2 Ne polynomialorder[1] timeend dt\n\n                result[l] = test_run(\n                    mpicomm,\n                    ArrayType,\n                    fvmethod,\n                    topology,\n                    problem,\n                    dt,\n                    polynomialorder,\n                    timeend,\n                    FT,\n                    output ? vtkdir : nothing,\n                    outputtime,\n                )\n                @test result[l] ≈ FT(expected_result[l, fvmethod])\n            end\n            @info begin\n                msg = \"\"\n                for l in 1:(numlevels - 1)\n                    rate = log2(result[l]) - log2(result[l + 1])\n                    msg *= @sprintf(\"\\n  rate for level %d = %e\\n\", l, rate)\n                end\n                msg\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/hyperdiffusion_bc.jl",
    "content": "using MPI\nusing ClimateMachine\nusing Logging\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing LinearAlgebra\nusing Printf\nusing Dates\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VTK: writevtk, writepvtu\nusing ClimateMachine.Mesh.Grids: min_node_distance\n\nconst output = parse(Bool, lowercase(get(ENV, \"JULIA_CLIMA_OUTPUT\", \"false\")))\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\ninclude(\"advection_diffusion_model.jl\")\n\nstruct ConstantHyperDiffusion{FT} <: AdvectionDiffusionProblem\n    μ::FT\n    k::SVector{3, FT}\nend\n\nfunction init_velocity_diffusion!(\n    problem::ConstantHyperDiffusion,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    FT = eltype(aux)\n    aux.hyperdiffusion.H = problem.μ * SMatrix{3, 3, FT}(I)\nend\n\nfunction initial_condition!(\n    problem::ConstantHyperDiffusion,\n    state,\n    aux,\n    localgeo,\n    t,\n)\n    x, y, z = localgeo.coord\n    k = problem.k\n    k2 = sum(k .^ 2)\n    @inbounds begin\n        state.ρ =\n            cos(k[1] * x) *\n            cos(k[2] * y) *\n            cos(k[3] * z) *\n            exp(-k2^2 * problem.μ * t)\n    end\nend\n\n# Boundary conditions data\ninhomogeneous_data!(::Val{0}, p::ConstantHyperDiffusion, x...) =\n    initial_condition!(p, x...)\nfunction inhomogeneous_data!(\n    ::Val{1},\n    problem::ConstantHyperDiffusion,\n    ∇state,\n    aux,\n    x,\n    t,\n)\n    k = problem.k\n    k2 = sum(k .^ 2)\n    ∇state.ρ =\n        -SVector(\n            k[1] * sin(k[1] * x[1]) * cos(k[2] * x[2]) * cos(k[3] * x[3]),\n            k[2] * cos(k[1] * x[1]) * sin(k[2] * x[2]) * cos(k[3] * x[3]),\n            k[3] * cos(k[1] * x[1]) * cos(k[2] * x[2]) * sin(k[3] * x[3]),\n        ) * exp(-k2^2 * problem.μ * t)\nend\nfunction inhomogeneous_data!(\n    ::Val{2},\n    problem::ConstantHyperDiffusion,\n    Δstate,\n    aux,\n    x,\n    t,\n)\n    k = problem.k\n    k2 = sum(k .^ 2)\n    Δstate.ρ =\n        -k2 *\n        cos(k[1] * x[1]) *\n        cos(k[2] * x[2]) *\n        cos(k[3] * x[3]) *\n        exp(-k2^2 * problem.μ * t)\nend\nfunction inhomogeneous_data!(\n    ::Val{3},\n    problem::ConstantHyperDiffusion,\n    ∇Δstate,\n    aux,\n    x,\n    t,\n)\n    k = problem.k\n    k2 = sum(k .^ 2)\n    ∇Δstate.ρ =\n        k2 *\n        SVector(\n            k[1] * sin(k[1] * x[1]) * cos(k[2] * x[2]) * cos(k[3] * x[3]),\n            k[2] * cos(k[1] * x[1]) * sin(k[2] * x[2]) * cos(k[3] * x[3]),\n            k[3] * cos(k[1] * x[1]) * cos(k[2] * x[2]) * sin(k[3] * x[3]),\n        ) *\n        exp(-k2^2 * problem.μ * t)\nend\n\nfunction do_output(mpicomm, vtkdir, vtkstep, dg, Q, Qe, model, testname)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dg, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    dim,\n    topl,\n    N,\n    timeend,\n    FT,\n    vtkdir,\n    outputtime,\n)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    μ = 1 // 1000\n    dx = min_node_distance(grid)\n    dt = dx^4 / 100 / μ\n    dt = outputtime / ceil(Int64, outputtime / dt)\n\n    bcx1 = (InhomogeneousBC{0}(), InhomogeneousBC{2}())\n    bcx2 = (InhomogeneousBC{0}(), InhomogeneousBC{1}())\n\n    bcy1 = (InhomogeneousBC{3}(), InhomogeneousBC{1}())\n    bcy2 = (InhomogeneousBC{3}(), InhomogeneousBC{2}())\n\n    bcz1 = (HomogeneousBC{3}(), HomogeneousBC{1}())\n    bcz2 = (HomogeneousBC{3}(), HomogeneousBC{1}())\n\n    k = SVector(1, 1, 0)\n    bcs = (bcx1, bcx2, bcy1, bcy2, bcz1, bcz2)\n    model = AdvectionDiffusion{dim}(\n        ConstantHyperDiffusion{FT}(μ, k),\n        bcs;\n        advection = false,\n        diffusion = false,\n        hyperdiffusion = true,\n    )\n    dg = DGModel(\n        model,\n        grid,\n        CentralNumericalFluxFirstOrder(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n\n    lsrk = LSRK54CarpenterKennedy(dg, Q; dt = dt, t0 = 0)\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n    dim      = %d\n    dt       = %.16e\n    norm(Q₀) = %.16e\"\"\" dim dt eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                gettime(lsrk),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n    callbacks = (cbinfo,)\n    if ~isnothing(vtkdir)\n        # create vtk dir\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # output initial step\n        do_output(mpicomm, vtkdir, vtkstep, dg, Q, Q, model, \"hyperdiffusion\")\n\n        # setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(lsrk))\n            do_output(\n                mpicomm,\n                vtkdir,\n                vtkstep,\n                dg,\n                Q,\n                Qe,\n                model,\n                \"hyperdiffusion\",\n            )\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(Q, lsrk; timeend = timeend, callbacks = callbacks)\n\n    # Print some end of the simulation information\n    engf = norm(Q)\n    Qe = init_ode_state(dg, FT(timeend))\n\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nusing Test\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    base_num_elem = 4\n\n    expected_result = Dict()\n    expected_result[2, 1, Float64] = 1.1666787574326038e-03\n    expected_result[2, 2, Float64] = 8.5948289965604964e-05\n    expected_result[2, 3, Float64] = 2.5117423516568199e-06\n\n    expected_result[3, 1, Float64] = 3.0867418520680958e-03\n    expected_result[3, 2, Float64] = 2.2739780086168765e-04\n    expected_result[3, 3, Float64] = 6.6454456228180395e-06\n\n    numlevels =\n        integration_testing || ClimateMachine.Settings.integration_testing ? 3 :\n        1\n\n    for FT in (Float64,)\n        result = zeros(FT, numlevels)\n        for dim in (2, 3)\n            for l in 1:numlevels\n                Ne = 2^(l - 1) * base_num_elem\n                xrange = range(FT(2); length = Ne + 1, stop = FT(9))\n                brickrange = ntuple(j -> xrange, dim)\n                periodicity = ntuple(j -> false, dim)\n                connectivity = dim == 2 ? :face : :full\n                boundary = ((1, 2), (3, 4), (5, 6))[1:dim]\n                topl = StackedBrickTopology(\n                    mpicomm,\n                    brickrange;\n                    periodicity,\n                    boundary,\n                    connectivity,\n                )\n                timeend = 1\n                outputtime = timeend\n\n                @info (ArrayType, FT)\n                vtkdir =\n                    output ?\n                    \"vtk_hyperdiffusion_bc\" *\n                    \"_poly$(polynomialorder)\" *\n                    \"_dim$(dim)_$(ArrayType)_$(FT)\" *\n                    \"_level$(l)\" :\n                    nothing\n                result[l] = test_run(\n                    mpicomm,\n                    ArrayType,\n                    dim,\n                    topl,\n                    polynomialorder,\n                    timeend,\n                    FT,\n                    vtkdir,\n                    outputtime,\n                )\n                @test result[l] ≈ FT(expected_result[dim, l, FT])\n            end\n            @info begin\n                msg = \"\"\n                for l in 1:(numlevels - 1)\n                    rate = log2(result[l]) - log2(result[l + 1])\n                    msg *= @sprintf(\"\\n  rate for level %d = %e\\n\", l, rate)\n                end\n                msg\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/hyperdiffusion_model.jl",
    "content": "using StaticArrays\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw,\n    Prognostic,\n    Auxiliary,\n    Gradient,\n    GradientFlux,\n    GradientLaplacian,\n    Hyperdiffusive\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    flux_first_order!,\n    flux_second_order!,\n    source!,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    nodal_init_state_auxiliary!,\n    init_state_prognostic!,\n    boundary_conditions,\n    boundary_state!,\n    wavespeed,\n    transform_post_gradient_laplacian!\n\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\nusing ClimateMachine.DGMethods.NumericalFluxes:\n    NumericalFluxFirstOrder, NumericalFluxSecondOrder\n\nabstract type HyperDiffusionProblem end\nstruct HyperDiffusion{dim, P} <: BalanceLaw\n    problem::P\n    function HyperDiffusion{dim}(\n        problem::P,\n    ) where {dim, P <: HyperDiffusionProblem}\n        new{dim, P}(problem)\n    end\nend\n\nvars_state(::HyperDiffusion, ::Auxiliary, FT) = @vars(D::SMatrix{3, 3, FT, 9})\n#\n# Density is only state\nvars_state(::HyperDiffusion, ::Prognostic, FT) = @vars(ρ::FT)\n\n# Take the gradient of density\nvars_state(::HyperDiffusion, ::Gradient, FT) = @vars(ρ::FT)\n# Take the gradient of laplacian of density\nvars_state(::HyperDiffusion, ::GradientLaplacian, FT) = @vars(ρ::FT)\n\nvars_state(::HyperDiffusion, ::GradientFlux, FT) = @vars()\n# The hyperdiffusion DG auxiliary variable: D ∇ Δρ\nvars_state(::HyperDiffusion, ::Hyperdiffusive, FT) = @vars(σ::SVector{3, FT})\n\nfunction flux_first_order!(m::HyperDiffusion, _...) end\n\n\"\"\"\n    flux_second_order!(m::HyperDiffusion, flux::Grad, state::Vars,\n                     auxDG::Vars, auxHDG::Vars, aux::Vars, t::Real)\n\nComputes diffusive flux `F` in:\n\n```\n∂ρ\n-- = - ∇ • (σ) = - ∇ • F\n∂t\n```\nWhere\n\n - `σ` is hyperdiffusion DG auxiliary variable (`σ = D ∇ Δρ` with D being the hyperdiffusion tensor)\n\"\"\"\nfunction flux_second_order!(\n    m::HyperDiffusion,\n    flux::Grad,\n    state::Vars,\n    auxDG::Vars,\n    auxHDG::Vars,\n    aux::Vars,\n    t::Real,\n)\n    σ = auxHDG.σ\n    flux.ρ += σ\nend\n\n\"\"\"\n    compute_gradient_argument!(m::HyperDiffusion, transform::Vars, state::Vars,\n                   aux::Vars, t::Real)\n\nSet the variable to take the gradient of (`ρ` in this case)\n\"\"\"\nfunction compute_gradient_argument!(\n    m::HyperDiffusion,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    transform.ρ = state.ρ\nend\n\ncompute_gradient_flux!(m::HyperDiffusion, _...) = nothing\nfunction transform_post_gradient_laplacian!(\n    m::HyperDiffusion,\n    auxHDG::Vars,\n    gradvars::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ∇Δρ = gradvars.ρ\n    D = aux.D\n    auxHDG.σ = D * ∇Δρ\nend\n\n\"\"\"\n    source!(m::HyperDiffusion, _...)\n\nThere is no source in the hyperdiffusion model\n\"\"\"\nsource!(m::HyperDiffusion, _...) = nothing\n\nfunction init_state_prognostic!(\n    m::HyperDiffusion,\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t::Real,\n)\n    initial_condition!(m.problem, state, aux, localgeo, t)\nend\n\nboundary_conditions(::HyperDiffusion) = ()\nboundary_state!(nf, ::HyperDiffusion, _...) = nothing\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/periodic_3D_hyperdiffusion.jl",
    "content": "using MPI\nusing ClimateMachine\nusing Logging\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing LinearAlgebra\nusing Printf\nusing Dates\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VTK: writevtk, writepvtu\nusing ClimateMachine.Mesh.Grids: min_node_distance\n\nconst output = parse(Bool, lowercase(get(ENV, \"JULIA_CLIMA_OUTPUT\", \"false\")))\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\ninclude(\"advection_diffusion_model.jl\")\n\nstruct ConstantHyperDiffusion{dim, dir, FT} <: AdvectionDiffusionProblem\n    D::SMatrix{3, 3, FT, 9}\nend\n\nfunction init_velocity_diffusion!(\n    problem::ConstantHyperDiffusion,\n    aux::Vars,\n    geom::LocalGeometry,\n) where {n, α, β}\n    aux.hyperdiffusion.H = problem.D\nend\n\nfunction initial_condition!(\n    problem::ConstantHyperDiffusion{dim, dir},\n    state,\n    aux,\n    localgeo,\n    t,\n) where {dim, dir}\n    @inbounds begin\n        k = SVector(1, 2, 3)\n        kD = k * k' .* problem.D\n        if dir === EveryDirection()\n            c = sum(abs2, k[SOneTo(dim)]) * sum(kD[SOneTo(dim), SOneTo(dim)])\n        elseif dir === HorizontalDirection()\n            c =\n                sum(abs2, k[SOneTo(dim - 1)]) *\n                sum(kD[SOneTo(dim - 1), SOneTo(dim - 1)])\n        elseif dir === VerticalDirection()\n            c = k[dim]^2 * kD[dim, dim]\n        end\n        x = localgeo.coord\n        state.ρ = sin(dot(k[SOneTo(dim)], x[SOneTo(dim)])) * exp(-c * t)\n    end\nend\n\nfunction do_output(mpicomm, vtkdir, vtkstep, dg, Q, Qe, model, testname)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dg, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    dim,\n    topl,\n    N,\n    timeend,\n    FT,\n    direction,\n    D,\n    vtkdir,\n    outputtime,\n)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    dx = min_node_distance(grid)\n    dt = dx^4 / 25 / sum(D)\n    @info \"time step\" dt\n    dt = outputtime / ceil(Int64, outputtime / dt)\n\n    model = AdvectionDiffusion{dim}(\n        ConstantHyperDiffusion{dim, direction(), FT}(D);\n        advection = false,\n        diffusion = false,\n        hyperdiffusion = true,\n    )\n    dg = DGModel(\n        model,\n        grid,\n        CentralNumericalFluxFirstOrder(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n        direction = direction(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n\n    lsrk = LSRK54CarpenterKennedy(dg, Q; dt = dt, t0 = 0)\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\"\"\" eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                gettime(lsrk),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n    callbacks = (cbinfo,)\n    if ~isnothing(vtkdir)\n        # create vtk dir\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # output initial step\n        do_output(mpicomm, vtkdir, vtkstep, dg, Q, Q, model, \"hyperdiffusion\")\n\n        # setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(lsrk))\n            do_output(\n                mpicomm,\n                vtkdir,\n                vtkstep,\n                dg,\n                Q,\n                Qe,\n                model,\n                \"hyperdiffusion\",\n            )\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(Q, lsrk; timeend = timeend, callbacks = callbacks)\n\n    # Print some end of the simulation information\n    engf = norm(Q)\n    Qe = init_ode_state(dg, FT(timeend))\n\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nusing Test\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n    mpicomm = MPI.COMM_WORLD\n\n    numlevels =\n        integration_testing || ClimateMachine.Settings.integration_testing ? 3 :\n        1\n\n    polynomialorder = 4\n    base_num_elem = 4\n\n    expected_result = Dict()\n    expected_result[2, 1, Float64, EveryDirection] = 7.6772960298563120e-03\n    expected_result[2, 2, Float64, EveryDirection] = 2.3268371815073617e-03\n    expected_result[2, 3, Float64, EveryDirection] = 4.2641957936779901e-05\n    expected_result[2, 1, Float64, HorizontalDirection] = 8.4650675812606650e-04\n    expected_result[2, 2, Float64, HorizontalDirection] = 4.4626814795055979e-05\n    expected_result[2, 3, Float64, HorizontalDirection] = 1.2193396277823764e-06\n    expected_result[2, 1, Float64, VerticalDirection] = 6.1690465335730834e-03\n    expected_result[2, 2, Float64, VerticalDirection] = 2.3407593209031621e-03\n    expected_result[2, 3, Float64, VerticalDirection] = 4.3775160749787010e-05\n\n    expected_result[3, 1, Float64, EveryDirection] = 1.7363355506160003e-01\n    expected_result[3, 2, Float64, EveryDirection] = 7.3049474767548042e-02\n    expected_result[3, 3, Float64, EveryDirection] = 5.8530711333407105e-04\n    expected_result[3, 1, Float64, HorizontalDirection] = 1.9244127301149615e-02\n    expected_result[3, 2, Float64, HorizontalDirection] = 5.8325158696244947e-03\n    expected_result[3, 3, Float64, HorizontalDirection] = 1.0688753745025491e-04\n    expected_result[3, 1, Float64, VerticalDirection] = 1.4412891107361228e-01\n    expected_result[3, 2, Float64, VerticalDirection] = 6.3744013545812925e-02\n    expected_result[3, 3, Float64, VerticalDirection] = 9.0891011404938341e-04\n\n    numlevels = integration_testing ? 3 : 1\n    for FT in (Float64,)\n        D =\n            1 // 100 * SMatrix{3, 3, FT}(\n                9 // 50,\n                3 // 50,\n                5 // 50,\n                3 // 50,\n                7 // 50,\n                4 // 50,\n                5 // 50,\n                4 // 50,\n                10 // 50,\n            )\n\n        result = zeros(FT, numlevels)\n        for dim in (2, 3)\n            connectivity = dim == 2 ? :face : :full\n            for direction in\n                (EveryDirection, HorizontalDirection, VerticalDirection)\n                for l in 1:numlevels\n                    Ne = 2^(l - 1) * base_num_elem\n                    xrange = range(FT(0); length = Ne + 1, stop = FT(2pi))\n                    brickrange = ntuple(j -> xrange, dim)\n                    periodicity = ntuple(j -> true, dim)\n                    topl = StackedBrickTopology(\n                        mpicomm,\n                        brickrange;\n                        periodicity = periodicity,\n                        connectivity = connectivity,\n                    )\n                    timeend = 1\n                    outputtime = 1\n\n                    @info (ArrayType, FT, dim, direction)\n                    vtkdir =\n                        output ?\n                        \"vtk_hyperdiffusion\" *\n                        \"_poly$(polynomialorder)\" *\n                        \"_dim$(dim)_$(ArrayType)_$(FT)_$(direction)\" *\n                        \"_level$(l)\" :\n                        nothing\n                    result[l] = test_run(\n                        mpicomm,\n                        ArrayType,\n                        dim,\n                        topl,\n                        polynomialorder,\n                        timeend,\n                        FT,\n                        direction,\n                        D,\n                        vtkdir,\n                        outputtime,\n                    )\n\n                    @test result[l] ≈ FT(expected_result[dim, l, FT, direction])\n                end\n                @info begin\n                    msg = \"\"\n                    for l in 1:(numlevels - 1)\n                        rate = log2(result[l]) - log2(result[l + 1])\n                        msg *= @sprintf(\"\\n  rate for level %d = %e\\n\", l, rate)\n                    end\n                    msg\n                end\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/pseudo1D_advection_diffusion.jl",
    "content": "using MPI\nusing ClimateMachine\nusing Logging\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing LinearAlgebra\nusing Printf\nusing Dates\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing ClimateMachine.VTK: writevtk, writepvtu\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nconst output = parse(Bool, lowercase(get(ENV, \"JULIA_CLIMA_OUTPUT\", \"false\")))\n\ninclude(\"advection_diffusion_model.jl\")\n\nstruct Pseudo1D{n, α, β, μ, δ} <: AdvectionDiffusionProblem end\n\nfunction init_velocity_diffusion!(\n    ::Pseudo1D{n, α, β},\n    aux::Vars,\n    geom::LocalGeometry,\n) where {n, α, β}\n    # Direction of flow is n with magnitude α\n    aux.advection.u = α * n\n\n    # diffusion of strength β in the n direction\n    aux.diffusion.D = β * n * n'\nend\n\nfunction initial_condition!(\n    ::Pseudo1D{n, α, β, μ, δ},\n    state,\n    aux,\n    localgeo,\n    t,\n) where {n, α, β, μ, δ}\n    ξn = dot(n, localgeo.coord)\n    # ξT = SVector(localgeo.coord) - ξn * n\n    state.ρ = exp(-(ξn - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\nend\ninhomogeneous_data!(::Val{0}, P::Pseudo1D, x...) = initial_condition!(P, x...)\nfunction inhomogeneous_data!(\n    ::Val{1},\n    ::Pseudo1D{n, α, β, μ, δ},\n    ∇state,\n    aux,\n    x,\n    t,\n) where {n, α, β, μ, δ}\n    ξn = dot(n, x)\n    ∇state.ρ =\n        -(\n            2n * (ξn - μ - α * t) / (4 * β * (δ + t)) *\n            exp(-(ξn - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\n        )\nend\n\nfunction do_output(mpicomm, vtkdir, vtkstep, dg, Q, Qe, model, testname)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dg, statenames, Qe, exactnames)\n\n    ## generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    dim,\n    topl,\n    N,\n    timeend,\n    FT,\n    direction,\n    dt,\n    n,\n    α,\n    β,\n    μ,\n    δ,\n    vtkdir,\n    outputtime,\n    fluxBC,\n)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n    bcs = (InhomogeneousBC{0}(), InhomogeneousBC{1}())\n    model = AdvectionDiffusion{dim}(\n        Pseudo1D{n, α, β, μ, δ}(),\n        bcs,\n        flux_bc = fluxBC,\n    )\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n        direction = direction(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n\n    lsrk = LSRK54CarpenterKennedy(dg, Q; dt = dt, t0 = 0)\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\"\"\" eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                gettime(lsrk),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n    callbacks = (cbinfo,)\n    if ~isnothing(vtkdir)\n        # create vtk dir\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # output initial step\n        do_output(\n            mpicomm,\n            vtkdir,\n            vtkstep,\n            dg,\n            Q,\n            Q,\n            model,\n            \"advection_diffusion\",\n        )\n\n        # setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(lsrk))\n            do_output(\n                mpicomm,\n                vtkdir,\n                vtkstep,\n                dg,\n                Q,\n                Qe,\n                model,\n                \"advection_diffusion\",\n            )\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    solve!(Q, lsrk; timeend = timeend, callbacks = callbacks)\n\n    # Print some end of the simulation information\n    engf = norm(Q)\n    Qe = init_ode_state(dg, FT(timeend))\n\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nusing Test\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    base_num_elem = 4\n\n    expected_result = Dict()\n    expected_result[2, 1, Float64, EveryDirection] = 1.2357162985295326e-02\n    expected_result[2, 2, Float64, EveryDirection] = 8.8403537443388974e-04\n    expected_result[2, 3, Float64, EveryDirection] = 4.9490976821250842e-05\n    expected_result[2, 4, Float64, EveryDirection] = 2.0311063939957157e-06\n    expected_result[2, 1, Float64, HorizontalDirection] = 4.6783743619257120e-02\n    expected_result[2, 2, Float64, HorizontalDirection] = 4.0665567827235134e-03\n    expected_result[2, 3, Float64, HorizontalDirection] = 5.3144336694498106e-05\n    expected_result[2, 4, Float64, HorizontalDirection] = 3.9780001102022647e-07\n    expected_result[2, 1, Float64, VerticalDirection] = 4.6783743619257120e-02\n    expected_result[2, 2, Float64, VerticalDirection] = 4.0665567827234102e-03\n    expected_result[2, 3, Float64, VerticalDirection] = 5.3144336694469002e-05\n    expected_result[2, 4, Float64, VerticalDirection] = 3.9780001102679913e-07\n    expected_result[3, 1, Float64, EveryDirection] = 9.6252415559793265e-03\n    expected_result[3, 2, Float64, EveryDirection] = 6.0160564826756122e-04\n    expected_result[3, 3, Float64, EveryDirection] = 4.0359079531195022e-05\n    expected_result[3, 4, Float64, EveryDirection] = 2.9987655364452885e-06\n    expected_result[3, 1, Float64, HorizontalDirection] = 1.7475667486259477e-02\n    expected_result[3, 2, Float64, HorizontalDirection] = 1.2502148161420126e-03\n    expected_result[3, 3, Float64, HorizontalDirection] = 6.9990810635609785e-05\n    expected_result[3, 4, Float64, HorizontalDirection] = 2.8724182090259972e-06\n    expected_result[3, 1, Float64, VerticalDirection] = 6.6162204724938736e-02\n    expected_result[3, 2, Float64, VerticalDirection] = 5.7509797542876833e-03\n    expected_result[3, 3, Float64, VerticalDirection] = 7.5157441716412944e-05\n    expected_result[3, 4, Float64, VerticalDirection] = 5.6257417070312578e-07\n    expected_result[2, 1, Float32, EveryDirection] = 1.2357043102383614e-02\n    expected_result[2, 2, Float32, EveryDirection] = 8.8409741874784231e-04\n    expected_result[2, 3, Float32, EveryDirection] = 4.9193818995263427e-05\n    expected_result[2, 1, Float32, HorizontalDirection] = 4.6783901751041412e-02\n    expected_result[2, 2, Float32, HorizontalDirection] = 4.0663350373506546e-03\n    expected_result[2, 3, Float32, HorizontalDirection] = 5.3044739615870640e-05\n    expected_result[2, 1, Float32, VerticalDirection] = 4.6783857047557831e-02\n    expected_result[2, 2, Float32, VerticalDirection] = 4.0663424879312515e-03\n    expected_result[2, 3, Float32, VerticalDirection] = 5.3172039770288393e-05\n    expected_result[3, 1, Float32, EveryDirection] = 9.6252141520380974e-03\n    expected_result[3, 2, Float32, EveryDirection] = 6.0162233421579003e-04\n    expected_result[3, 3, Float32, EveryDirection] = 4.0522103518014774e-05\n    expected_result[3, 1, Float32, HorizontalDirection] = 1.7475549131631851e-02\n    expected_result[3, 2, Float32, HorizontalDirection] = 1.2503013713285327e-03\n    expected_result[3, 3, Float32, HorizontalDirection] = 7.2867427661549300e-05\n    expected_result[3, 1, Float32, VerticalDirection] = 6.6162362694740295e-02\n    expected_result[3, 2, Float32, VerticalDirection] = 5.7506239973008633e-03\n    expected_result[3, 3, Float32, VerticalDirection] = 1.0193029447691515e-04\n\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64, Float32)\n            numlevels =\n                integration_testing ||\n                ClimateMachine.Settings.integration_testing ?\n                (FT == Float64 ? 4 : 3) : 1\n            result = zeros(FT, numlevels)\n            for dim in 2:3\n                connectivity = dim == 2 ? :face : :full\n                for direction in\n                    (EveryDirection, HorizontalDirection, VerticalDirection)\n                    for fluxBC in (true, false)\n                        if direction <: EveryDirection\n                            n =\n                                dim == 2 ?\n                                SVector{3, FT}(1 / sqrt(2), 1 / sqrt(2), 0) :\n                                SVector{3, FT}(\n                                    1 / sqrt(3),\n                                    1 / sqrt(3),\n                                    1 / sqrt(3),\n                                )\n                        elseif direction <: HorizontalDirection\n                            n =\n                                dim == 2 ? SVector{3, FT}(1, 0, 0) :\n                                SVector{3, FT}(1 / sqrt(2), 1 / sqrt(2), 0)\n                        elseif direction <: VerticalDirection\n                            n =\n                                dim == 2 ? SVector{3, FT}(0, 1, 0) :\n                                SVector{3, FT}(0, 0, 1)\n                        end\n                        α = FT(1)\n                        β = FT(1 // 100)\n                        μ = FT(-1 // 2)\n                        δ = FT(1 // 10)\n                        for l in 1:numlevels\n                            Ne = 2^(l - 1) * base_num_elem\n                            brickrange = ntuple(\n                                j -> range(FT(-1); length = Ne + 1, stop = 1),\n                                dim,\n                            )\n                            periodicity = ntuple(j -> false, dim)\n                            bc = ntuple(j -> (1, 2), dim)\n                            topl = StackedBrickTopology(\n                                mpicomm,\n                                brickrange;\n                                periodicity = periodicity,\n                                boundary = bc,\n                                connectivity = connectivity,\n                            )\n                            dt = (α / 4) / (Ne * polynomialorder^2)\n                            @info \"time step\" dt\n\n                            timeend = 1\n                            outputtime = 1\n\n                            dt = outputtime / ceil(Int64, outputtime / dt)\n\n                            @info (ArrayType, FT, dim, direction, fluxBC)\n                            vtkdir =\n                                output ?\n                                \"vtk_advection\" *\n                                \"_poly$(polynomialorder)\" *\n                                \"_dim$(dim)_$(ArrayType)_$(FT)_$(direction)\" *\n                                \"_level$(l)\" :\n                                nothing\n                            result[l] = test_run(\n                                mpicomm,\n                                ArrayType,\n                                dim,\n                                topl,\n                                polynomialorder,\n                                timeend,\n                                FT,\n                                direction,\n                                dt,\n                                n,\n                                α,\n                                β,\n                                μ,\n                                δ,\n                                vtkdir,\n                                outputtime,\n                                fluxBC,\n                            )\n                            @test result[l] ≈\n                                  FT(expected_result[dim, l, FT, direction])\n                        end\n                        @info begin\n                            msg = \"\"\n                            for l in 1:(numlevels - 1)\n                                rate = log2(result[l]) - log2(result[l + 1])\n                                msg *= @sprintf(\n                                    \"\\n  rate for level %d = %e\\n\",\n                                    l,\n                                    rate\n                                )\n                            end\n                            msg\n                        end\n                    end\n                end\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/pseudo1D_advection_diffusion_1dimex.jl",
    "content": "using MPI\nusing ClimateMachine\nusing Logging\nusing Test\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.ODESolvers\nusing LinearAlgebra\nusing Printf\nusing Dates\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing ClimateMachine.VTK: writevtk, writepvtu\n\nif !@isdefined integration_testing\n    if length(ARGS) > 0\n        const integration_testing = parse(Bool, ARGS[1])\n    else\n        const integration_testing = parse(\n            Bool,\n            lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n        )\n    end\nend\n\nconst output = parse(Bool, lowercase(get(ENV, \"JULIA_CLIMA_OUTPUT\", \"false\")))\n\ninclude(\"advection_diffusion_model.jl\")\n\nstruct Pseudo1D{n, α, β, μ, δ} <: AdvectionDiffusionProblem end\n\nfunction init_velocity_diffusion!(\n    ::Pseudo1D{n, α, β},\n    aux::Vars,\n    geom::LocalGeometry,\n) where {n, α, β}\n    # Direction of flow is n with magnitude α\n    aux.advection.u = α * n\n\n    # diffusion of strength β in the n direction\n    aux.diffusion.D = β * n * n'\nend\n\nfunction initial_condition!(\n    ::Pseudo1D{n, α, β, μ, δ},\n    state,\n    aux,\n    localgeo,\n    t,\n) where {n, α, β, μ, δ}\n    ξn = dot(n, localgeo.coord)\n    # ξT = SVector(localgeo.coord) - ξn * n\n    state.ρ = exp(-(ξn - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\nend\ninhomogeneous_data!(::Val{0}, P::Pseudo1D, x...) = initial_condition!(P, x...)\nfunction inhomogeneous_data!(\n    ::Val{1},\n    ::Pseudo1D{n, α, β, μ, δ},\n    ∇state,\n    aux,\n    x,\n    t,\n) where {n, α, β, μ, δ}\n    ξn = dot(n, x)\n    ∇state.ρ =\n        -(\n            2n * (ξn - μ - α * t) / (4 * β * (δ + t)) *\n            exp(-(ξn - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\n        )\nend\n\nfunction do_output(mpicomm, vtkdir, vtkstep, dg, Q, Qe, model, testname)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dg, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    dim,\n    topl,\n    N,\n    timeend,\n    FT,\n    dt,\n    n,\n    α,\n    β,\n    μ,\n    δ,\n    vtkdir,\n    outputtime,\n    linearsolvertype,\n    fluxBC,\n)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n    bcs = (\n        InhomogeneousBC{0}(),\n        InhomogeneousBC{1}(),\n        HomogeneousBC{0}(),\n        HomogeneousBC{1}(),\n    )\n    model = AdvectionDiffusion{dim}(\n        Pseudo1D{n, α, β, μ, δ}(),\n        bcs,\n        flux_bc = fluxBC,\n    )\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n        direction = EveryDirection(),\n    )\n\n    vdg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n        state_auxiliary = dg.state_auxiliary,\n        direction = VerticalDirection(),\n    )\n\n\n    Q = init_ode_state(dg, FT(0))\n\n    ode_solver = ARK548L2SA2KennedyCarpenter(\n        dg,\n        vdg,\n        LinearBackwardEulerSolver(linearsolvertype(); isadjustable = false),\n        Q;\n        dt = dt,\n        t0 = 0,\n        split_explicit_implicit = false,\n    )\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\"\"\" eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                gettime(ode_solver),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n    callbacks = (cbinfo,)\n    if ~isnothing(vtkdir)\n        # create vtk dir\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # output initial step\n        do_output(\n            mpicomm,\n            vtkdir,\n            vtkstep,\n            dg,\n            Q,\n            Q,\n            model,\n            \"advection_diffusion\",\n        )\n\n        # setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(ode_solver))\n            do_output(\n                mpicomm,\n                vtkdir,\n                vtkstep,\n                dg,\n                Q,\n                Qe,\n                model,\n                \"advection_diffusion\",\n            )\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    numberofsteps = convert(Int64, cld(timeend, dt))\n    dt = timeend / numberofsteps\n\n    @info \"time step\" dt numberofsteps dt * numberofsteps timeend\n\n    solve!(\n        Q,\n        ode_solver;\n        numberofsteps = numberofsteps,\n        callbacks = callbacks,\n        adjustfinalstep = false,\n    )\n\n    # Print some end of the simulation information\n    engf = norm(Q)\n    Qe = init_ode_state(dg, FT(timeend))\n\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    base_num_elem = 4\n\n    expected_result = Dict()\n    expected_result[2, 1, Float64] = 7.2801198255507391e-02\n    expected_result[2, 2, Float64] = 6.8160295851506783e-03\n    expected_result[2, 3, Float64] = 1.4439137164205592e-04\n    expected_result[2, 4, Float64] = 2.4260727323386998e-06\n    expected_result[3, 1, Float64] = 1.0462203776357534e-01\n    expected_result[3, 2, Float64] = 1.0280535683502070e-02\n    expected_result[3, 3, Float64] = 2.0631857053908848e-04\n    expected_result[3, 4, Float64] = 3.3460492914169325e-06\n    expected_result[2, 1, Float32] = 7.2801239788532257e-02\n    expected_result[2, 2, Float32] = 6.8159680813550949e-03\n    expected_result[2, 3, Float32] = 1.4439738879445940e-04\n    # This is near roundoff so we will not check it\n    # expected_result[2, 4, Float32] = 2.6432753656990826e-06\n    expected_result[3, 1, Float32] = 1.0462204366922379e-01\n    expected_result[3, 2, Float32] = 1.0280583053827286e-02\n    expected_result[3, 3, Float32] = 2.0646647317335010e-04\n    expected_result[3, 4, Float32] = 2.0226731066941284e-05\n\n    numlevels = integration_testing ? 4 : 1\n\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64, Float32)\n            result = zeros(FT, numlevels)\n            for dim in 2:3\n                connectivity = dim == 2 ? :face : :full\n                for fluxBC in (true, false)\n                    for linearsolvertype in (SingleColumnLU, ManyColumnLU)\n                        d = dim == 2 ? FT[1, 10, 0] : FT[1, 1, 10]\n                        n = SVector{3, FT}(d ./ norm(d))\n\n                        α = FT(1)\n                        β = FT(1 // 100)\n                        μ = FT(-1 // 2)\n                        δ = FT(1 // 10)\n                        for l in 1:numlevels\n                            Ne = 2^(l - 1) * base_num_elem\n                            brickrange = (\n                                ntuple(\n                                    j -> range(\n                                        FT(-1);\n                                        length = Ne + 1,\n                                        stop = 1,\n                                    ),\n                                    dim - 1,\n                                )...,\n                                range(FT(-5); length = 5Ne + 1, stop = 5),\n                            )\n\n                            periodicity = ntuple(j -> false, dim)\n                            topl = StackedBrickTopology(\n                                mpicomm,\n                                brickrange;\n                                periodicity = periodicity,\n                                boundary = (\n                                    ntuple(j -> (1, 2), dim - 1)...,\n                                    (3, 4),\n                                ),\n                                connectivity = connectivity,\n                            )\n                            dt = (α / 4) / (Ne * polynomialorder^2)\n\n                            outputtime = 0.01\n                            timeend = 0.5\n\n                            @info (\n                                ArrayType,\n                                FT,\n                                dim,\n                                linearsolvertype,\n                                l,\n                                fluxBC,\n                            )\n                            vtkdir =\n                                output ?\n                                \"vtk_advection\" *\n                                \"_poly$(polynomialorder)\" *\n                                \"_dim$(dim)_$(ArrayType)_$(FT)\" *\n                                \"_$(linearsolvertype)_level$(l)\" :\n                                nothing\n                            result[l] = test_run(\n                                mpicomm,\n                                ArrayType,\n                                dim,\n                                topl,\n                                polynomialorder,\n                                timeend,\n                                FT,\n                                dt,\n                                n,\n                                α,\n                                β,\n                                μ,\n                                δ,\n                                vtkdir,\n                                outputtime,\n                                linearsolvertype,\n                                fluxBC,\n                            )\n                            # test the errors significantly larger than floating point epsilon\n                            if !(dim == 2 && l == 4 && FT == Float32)\n                                @test result[l] ≈\n                                      FT(expected_result[dim, l, FT])\n                            end\n                        end\n                        @info begin\n                            msg = \"\"\n                            for l in 1:(numlevels - 1)\n                                rate = log2(result[l]) - log2(result[l + 1])\n                                msg *= @sprintf(\n                                    \"\\n  rate for level %d = %e\\n\",\n                                    l,\n                                    rate\n                                )\n                            end\n                            msg\n                        end\n                    end\n                end\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/pseudo1D_advection_diffusion_mrigark_implicit.jl",
    "content": "using MPI\nusing ClimateMachine\nusing Logging\nusing Test\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.ODESolvers\nusing LinearAlgebra\nusing Printf\nusing Dates\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing ClimateMachine.VTK: writevtk, writepvtu\n\nif !@isdefined integration_testing\n    if length(ARGS) > 0\n        const integration_testing = parse(Bool, ARGS[1])\n    else\n        const integration_testing = parse(\n            Bool,\n            lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n        )\n    end\nend\n\nconst output = parse(Bool, lowercase(get(ENV, \"JULIA_CLIMA_OUTPUT\", \"false\")))\n\ninclude(\"advection_diffusion_model.jl\")\n\nstruct Pseudo1D{n, α, β, μ, δ} <: AdvectionDiffusionProblem end\n\nfunction init_velocity_diffusion!(\n    ::Pseudo1D{n, α, β},\n    aux::Vars,\n    geom::LocalGeometry,\n) where {n, α, β}\n    # Direction of flow is n with magnitude α\n    aux.advection.u = α * n\n\n    # diffusion of strength β in the n direction\n    aux.diffusion.D = β * n * n'\nend\n\nfunction initial_condition!(\n    ::Pseudo1D{n, α, β, μ, δ},\n    state,\n    aux,\n    localgeo,\n    t,\n) where {n, α, β, μ, δ}\n    ξn = dot(n, localgeo.coord)\n    # ξT = SVector(localgeo.coord) - ξn * n\n    state.ρ = exp(-(ξn - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\nend\ninhomogeneous_data!(::Val{0}, P::Pseudo1D, x...) = initial_condition!(P, x...)\nfunction inhomogeneous_data!(\n    ::Val{1},\n    ::Pseudo1D{n, α, β, μ, δ},\n    ∇state,\n    aux,\n    x,\n    t,\n) where {n, α, β, μ, δ}\n    ξn = dot(n, x)\n    ∇state.ρ =\n        -(\n            2n * (ξn - μ - α * t) / (4 * β * (δ + t)) *\n            exp(-(ξn - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\n        )\nend\n\nfunction do_output(mpicomm, vtkdir, vtkstep, dg, Q, Qe, model, testname)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    exactnames = statenames .* \"_exact\"\n\n    writevtk(filename, Q, dg, statenames, Qe, exactnames)\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(\n            pvtuprefix,\n            prefixes,\n            (statenames..., exactnames...),\n            eltype(Q),\n        )\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    dim,\n    topl,\n    N,\n    timeend,\n    FT,\n    dt,\n    n,\n    α,\n    β,\n    μ,\n    δ,\n    vtkdir,\n    outputtime,\n    linearsolvertype,\n    fluxBC,\n)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    bcs = (\n        InhomogeneousBC{0}(),\n        InhomogeneousBC{1}(),\n        HomogeneousBC{0}(),\n        HomogeneousBC{1}(),\n    )\n    model = AdvectionDiffusion{dim}(\n        Pseudo1D{n, α, β, μ, δ}(),\n        bcs,\n        flux_bc = fluxBC,\n    )\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    vdg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n        state_auxiliary = dg.state_auxiliary,\n        direction = VerticalDirection(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n\n    # With diffussion the Vertical + Horizontal ≠ Full because of 2nd\n    # derivative mixing. So we define a custom RHS\n    dQ2 = similar(Q)\n    function rhs!(dQ, Q, p, time; increment)\n        dg(dQ, Q, p, time; increment = increment)\n        vdg(dQ2, Q, p, time; increment = false)\n        dQ .= dQ .- dQ2\n    end\n\n    fastsolver = LSRK144NiegemannDiehlBusch(rhs!, Q; dt = dt)\n\n    # We're dominated by spatial error, so we can get away with a low order time\n    # integrator\n    ode_solver = MRIGARKESDIRK46aSandu(\n        vdg,\n        LinearBackwardEulerSolver(linearsolvertype(); isadjustable = false),\n        fastsolver,\n        Q;\n        dt = dt,\n        t0 = 0,\n    )\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\"\"\" eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                gettime(ode_solver),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n    callbacks = (cbinfo,)\n    if ~isnothing(vtkdir)\n        # create vtk dir\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # output initial step\n        do_output(\n            mpicomm,\n            vtkdir,\n            vtkstep,\n            dg,\n            Q,\n            Q,\n            model,\n            \"advection_diffusion\",\n        )\n\n        # setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            Qe = init_ode_state(dg, gettime(ode_solver))\n            do_output(\n                mpicomm,\n                vtkdir,\n                vtkstep,\n                dg,\n                Q,\n                Qe,\n                model,\n                \"advection_diffusion\",\n            )\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    numberofsteps = convert(Int64, cld(timeend, dt))\n    dt = timeend / numberofsteps\n\n    @info \"time step\" dt numberofsteps dt * numberofsteps timeend\n\n    solve!(\n        Q,\n        ode_solver;\n        numberofsteps = numberofsteps,\n        callbacks = callbacks,\n        adjustfinalstep = false,\n    )\n\n    # Print some end of the simulation information\n    engf = norm(Q)\n    Qe = init_ode_state(dg, FT(timeend))\n\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    base_num_elem = 4\n\n    expected_result = Dict()\n    expected_result[2, 1, Float64] = 7.3188989633310442e-02\n    expected_result[2, 2, Float64] = 6.8155958327716232e-03\n    expected_result[2, 3, Float64] = 1.4832570828897563e-04\n    expected_result[2, 4, Float64] = 3.3905353801669396e-06\n    expected_result[3, 1, Float64] = 1.0501469629884301e-01\n    expected_result[3, 2, Float64] = 1.0341917570778314e-02\n    expected_result[3, 3, Float64] = 2.1032014288411172e-04\n    expected_result[3, 4, Float64] = 4.5797013335024617e-06\n    expected_result[2, 1, Float32] = 7.3186099529266357e-02\n    expected_result[2, 2, Float32] = 6.8112355656921864e-03\n    expected_result[2, 3, Float32] = 1.4748815738130361e-04\n    # This is near roundoff so we will not check it\n    # expected_result[2, 4, Float32] = 2.9435863325488754e-05\n    expected_result[3, 1, Float32] = 1.0500905662775040e-01\n    expected_result[3, 2, Float32] = 1.0342594236135483e-02\n    expected_result[3, 3, Float32] = 4.2716524330899119e-04\n    # This is near roundoff so we will not check it\n    # expected_result[3, 4, Float32] = 1.9564463291317225e-03\n\n    numlevels = integration_testing ? 4 : 1\n\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64, Float32)\n            result = zeros(FT, numlevels)\n            for dim in 2:3\n                connectivity = dim == 3 ? :full : :face\n                for fluxBC in (true, false)\n                    for linearsolvertype in (SingleColumnLU,)# ManyColumnLU)\n                        d = dim == 2 ? FT[1, 10, 0] : FT[1, 1, 10]\n                        n = SVector{3, FT}(d ./ norm(d))\n\n                        α = FT(1)\n                        β = FT(1 // 100)\n                        μ = FT(-1 // 2)\n                        δ = FT(1 // 10)\n                        for l in 1:numlevels\n                            Ne = 2^(l - 1) * base_num_elem\n                            brickrange = (\n                                ntuple(\n                                    j -> range(\n                                        FT(-1);\n                                        length = Ne + 1,\n                                        stop = 1,\n                                    ),\n                                    dim - 1,\n                                )...,\n                                range(FT(-5); length = 5Ne + 1, stop = 5),\n                            )\n\n                            periodicity = ntuple(j -> false, dim)\n                            topl = StackedBrickTopology(\n                                mpicomm,\n                                brickrange;\n                                periodicity = periodicity,\n                                boundary = (\n                                    ntuple(j -> (1, 2), dim - 1)...,\n                                    (3, 4),\n                                ),\n                                connectivity = connectivity,\n                            )\n                            dt = 32 * (α / 4) / (Ne * polynomialorder^2)\n\n                            outputtime = 0.01\n                            timeend = 0.5\n\n                            @info (\n                                ArrayType,\n                                FT,\n                                dim,\n                                linearsolvertype,\n                                l,\n                                fluxBC,\n                            )\n                            vtkdir =\n                                output ?\n                                \"vtk_advection\" *\n                                \"_poly$(polynomialorder)\" *\n                                \"_dim$(dim)_$(ArrayType)_$(FT)\" *\n                                \"_$(linearsolvertype)_level$(l)\" :\n                                nothing\n                            result[l] = test_run(\n                                mpicomm,\n                                ArrayType,\n                                dim,\n                                topl,\n                                polynomialorder,\n                                timeend,\n                                FT,\n                                dt,\n                                n,\n                                α,\n                                β,\n                                μ,\n                                δ,\n                                vtkdir,\n                                outputtime,\n                                linearsolvertype,\n                                fluxBC,\n                            )\n                            # test the errors significantly larger than floating point epsilon\n                            if !(l == 4 && FT == Float32)\n                                @test result[l] ≈\n                                      FT(expected_result[dim, l, FT])\n                            end\n                        end\n                        @info begin\n                            msg = \"\"\n                            for l in 1:(numlevels - 1)\n                                rate = log2(result[l]) - log2(result[l + 1])\n                                msg *= @sprintf(\n                                    \"\\n  rate for level %d = %e\\n\",\n                                    l,\n                                    rate\n                                )\n                            end\n                            msg\n                        end\n                    end\n                end\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/pseudo1D_heat_eqn.jl",
    "content": "using MPI\nusing ClimateMachine\nusing Logging\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing LinearAlgebra\nusing Printf\nusing Dates\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nimport ClimateMachine.DGMethods.NumericalFluxes:\n    normal_boundary_flux_second_order!\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nconst output = parse(Bool, lowercase(get(ENV, \"JULIA_CLIMA_OUTPUT\", \"false\")))\n\ninclude(\"advection_diffusion_model.jl\")\n\nstruct HeatEqn{n, κ, A} <: AdvectionDiffusionProblem end\n\nfunction init_velocity_diffusion!(\n    ::HeatEqn{n},\n    aux::Vars,\n    geom::LocalGeometry,\n) where {n}\n    # diffusion of strength 1 in the n direction\n    aux.diffusion.D = n * n'\nend\n\n# solution is such that\n# u(1, t) = 1\n# ∇u(0,t) = n\nfunction initial_condition!(\n    ::HeatEqn{n, κ, A},\n    state,\n    aux,\n    localgeo,\n    t,\n) where {n, κ, A}\n    ξn = dot(n, localgeo.coord)\n    state.ρ = ξn + sum(A .* cos.(κ * ξn) .* exp.(-κ .^ 2 * t))\nend\ninhomogeneous_data!(::Val{0}, P::HeatEqn, x...) = initial_condition!(P, x...)\n\nfunction normal_boundary_flux_second_order!(\n    ::CentralNumericalFluxSecondOrder,\n    bcs,\n    ::AdvectionDiffusion{1, dim, HeatEqn{nd, κ, A}},\n    fluxᵀn::Vars{S},\n    n⁻,\n    state⁻,\n    diff⁻,\n    hyperdiff⁻,\n    aux⁻,\n    state⁺,\n    diff⁺,\n    hyperdiff⁺,\n    aux⁺,\n    t,\n    _...,\n) where {S, dim, nd, κ, A}\n\n    if any_isa(bcs, InhomogeneousBC{0})\n        fluxᵀn.ρ = -diff⁻.σ' * n⁻\n    elseif any_isa(bcs, InhomogeneousBC{1})\n        # Get exact gradient of ρ\n        x = aux⁻.coord\n        ξn = dot(nd, x)\n        ∇ρ = SVector(ntuple(\n            i ->\n                nd[i] * (1 - sum(A .* κ .* sin.(κ * ξn) .* exp.(-κ .^ 2 * t))),\n            Val(3),\n        ))\n\n        # Compute flux value\n        D = aux⁻.diffusion.D\n        fluxᵀn.ρ = -(D * ∇ρ)' * n⁻\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    dim,\n    topl,\n    N,\n    timeend,\n    FT,\n    direction,\n    dt,\n    n,\n    κ = 10 * FT(π) / 2,\n    A = 1,\n)\n\n    numberofsteps = convert(Int64, cld(timeend, dt))\n    dt = timeend / numberofsteps\n    @info \"time step\" dt numberofsteps dt * numberofsteps timeend\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n    bcs = (InhomogeneousBC{1}(), InhomogeneousBC{0}())\n    model = AdvectionDiffusion{dim}(HeatEqn{n, κ, A}(), bcs; advection = false)\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n        direction = direction(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n\n    lsrk = LSRK144NiegemannDiehlBusch(dg, Q; dt = dt, t0 = 0)\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\"\"\" eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                gettime(lsrk),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n    callbacks = (cbinfo,)\n\n    solve!(\n        Q,\n        lsrk;\n        numberofsteps = numberofsteps,\n        callbacks = callbacks,\n        adjustfinalstep = false,\n    )\n\n    # Print some end of the simulation information\n    engf = norm(Q)\n    Qe = init_ode_state(dg, FT(timeend))\n\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nusing Test\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    base_num_elem = 4\n\n    expected_result = Dict()\n    expected_result[2, 1, Float64, EveryDirection] = 0.005157483268127576\n    expected_result[2, 2, Float64, EveryDirection] = 6.5687731035717e-5\n    expected_result[2, 3, Float64, EveryDirection] = 1.6644861275185443e-6\n    expected_result[2, 1, Float64, HorizontalDirection] = 0.020515449798977983\n    expected_result[2, 2, Float64, HorizontalDirection] = 0.0005686256942296802\n    expected_result[2, 3, Float64, HorizontalDirection] = 1.0132022682547854e-5\n    expected_result[2, 1, Float64, VerticalDirection] = 0.02051544979897792\n    expected_result[2, 2, Float64, VerticalDirection] = 0.0005686256942296017\n    expected_result[2, 3, Float64, VerticalDirection] = 1.0132022682754848e-5\n    expected_result[3, 1, Float64, EveryDirection] = 0.001260581018671256\n    expected_result[3, 2, Float64, EveryDirection] = 2.214908522198975e-5\n    expected_result[3, 3, Float64, EveryDirection] = 5.931735594156876e-7\n    expected_result[3, 1, Float64, HorizontalDirection] = 0.005157483268127569\n    expected_result[3, 2, Float64, HorizontalDirection] = 6.568773103570526e-5\n    expected_result[3, 3, Float64, HorizontalDirection] = 1.6644861273865866e-6\n    expected_result[3, 1, Float64, VerticalDirection] = 0.020515449798978087\n    expected_result[3, 2, Float64, VerticalDirection] = 0.0005686256942297547\n    expected_result[3, 3, Float64, VerticalDirection] = 1.0132022682817856e-5\n\n    expected_result[2, 1, Float32, EveryDirection] = 0.005157135\n    expected_result[2, 2, Float32, EveryDirection] = 6.5721644e-5\n    expected_result[2, 3, Float32, EveryDirection] = 3.280845e-6\n    expected_result[2, 1, Float32, HorizontalDirection] = 0.020514594\n    expected_result[2, 2, Float32, HorizontalDirection] = 0.0005684704\n    expected_result[2, 3, Float32, HorizontalDirection] = 1.02350195e-5\n    expected_result[2, 1, Float32, VerticalDirection] = 0.020514673\n    expected_result[2, 2, Float32, VerticalDirection] = 0.0005684843\n    expected_result[2, 3, Float32, VerticalDirection] = 1.0227403e-5\n    expected_result[3, 1, Float32, EveryDirection] = 0.0012602004\n    expected_result[3, 2, Float32, EveryDirection] = 2.2415780e-5\n    expected_result[3, 3, Float32, EveryDirection] = 1.1309192e-5\n    expected_result[3, 1, Float32, HorizontalDirection] = 0.005157044\n    expected_result[3, 2, Float32, HorizontalDirection] = 6.66792e-5\n    expected_result[3, 3, Float32, HorizontalDirection] = 9.930429e-5\n    expected_result[3, 1, Float32, VerticalDirection] = 0.020514654\n    expected_result[3, 2, Float32, VerticalDirection] = 0.0005684157\n    expected_result[3, 3, Float32, VerticalDirection] = 3.224683e-5\n\n    @testset \"$(@__FILE__)\" begin\n        for FT in (Float64, Float32)\n            numlevels =\n                integration_testing ||\n                ClimateMachine.Settings.integration_testing ?\n                3 : 1\n            result = zeros(FT, numlevels)\n            for dim in 2:3\n                connectivity = dim == 2 ? :face : :full\n                for direction in\n                    (EveryDirection, HorizontalDirection, VerticalDirection)\n                    if direction <: EveryDirection\n                        n =\n                            dim == 2 ?\n                            SVector{3, FT}(1 / sqrt(2), 1 / sqrt(2), 0) :\n                            SVector{3, FT}(\n                                1 / sqrt(3),\n                                1 / sqrt(3),\n                                1 / sqrt(3),\n                            )\n                    elseif direction <: HorizontalDirection\n                        n =\n                            dim == 2 ? SVector{3, FT}(1, 0, 0) :\n                            SVector{3, FT}(1 / sqrt(2), 1 / sqrt(2), 0)\n                    elseif direction <: VerticalDirection\n                        n =\n                            dim == 2 ? SVector{3, FT}(0, 1, 0) :\n                            SVector{3, FT}(0, 0, 1)\n                    end\n                    for l in 1:numlevels\n                        Ne = 2^(l - 1) * base_num_elem\n                        brickrange = ntuple(\n                            j -> range(FT(0); length = Ne + 1, stop = 1),\n                            dim,\n                        )\n                        periodicity = ntuple(j -> false, dim)\n                        bc = ntuple(j -> (1, 2), dim)\n                        topl = StackedBrickTopology(\n                            mpicomm,\n                            brickrange;\n                            periodicity = periodicity,\n                            boundary = bc,\n                            connectivity = connectivity,\n                        )\n                        dt = 1 / (Ne * polynomialorder^2)^2\n\n                        timeend = 0.01\n\n                        @info (ArrayType, FT, dim, direction)\n                        result[l] = test_run(\n                            mpicomm,\n                            ArrayType,\n                            dim,\n                            topl,\n                            polynomialorder,\n                            timeend,\n                            FT,\n                            direction,\n                            dt,\n                            n,\n                        )\n\n\n                        @test (\n                            result[l] ≈\n                            FT(expected_result[dim, l, FT, direction]) ||\n                            result[l] <\n                            FT(expected_result[dim, l, FT, direction])\n                        )\n                    end\n                    @info begin\n                        msg = \"\"\n                        for l in 1:(numlevels - 1)\n                            rate = log2(result[l]) - log2(result[l + 1])\n                            msg *= @sprintf(\n                                \"\\n  rate for level %d = %e\\n\",\n                                l,\n                                rate\n                            )\n                        end\n                        msg\n                    end\n                end\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/DGMethods/advection_diffusion/variable_degree_advection_diffusion.jl",
    "content": "using MPI\nusing ClimateMachine\nusing Logging\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing LinearAlgebra\nusing Printf\nusing Test\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nconst output = parse(Bool, lowercase(get(ENV, \"JULIA_CLIMA_OUTPUT\", \"false\")))\n\ninclude(\"advection_diffusion_model.jl\")\n\nstruct Pseudo1D{n1, n2, α, β, μ, δ} <: AdvectionDiffusionProblem end\n\nfunction init_velocity_diffusion!(\n    ::Pseudo1D{n1, n2, α, β},\n    aux::Vars,\n    geom::LocalGeometry,\n) where {n1, n2, α, β}\n    # Direction of flow is n1 (resp n2) with magnitude α\n    aux.advection.u = hcat(α * n1, α * n2)\n\n    # diffusion of strength β in the n1 and n2 directions\n    aux.diffusion.D = hcat(β * n1 * n1', β * n2 * n2')\nend\n\nfunction initial_condition!(\n    ::Pseudo1D{n1, n2, α, β, μ, δ},\n    state,\n    aux,\n    localgeo,\n    t,\n) where {n1, n2, α, β, μ, δ}\n    ξn1 = dot(n1, localgeo.coord)\n    ξn2 = dot(n2, localgeo.coord)\n    ρ1 = exp(-(ξn1 - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\n    ρ2 = exp(-(ξn2 - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\n    state.ρ = (ρ1, ρ2)\nend\n\ninhomogeneous_data!(::Val{0}, P::Pseudo1D, x...) = initial_condition!(P, x...)\n\nfunction inhomogeneous_data!(\n    ::Val{1},\n    ::Pseudo1D{n1, n2, α, β, μ, δ},\n    ∇state,\n    aux,\n    x,\n    t,\n) where {n1, n2, α, β, μ, δ}\n    ξn1 = dot(n1, x)\n    ξn2 = dot(n2, x)\n    ∇ρ1 =\n        -(\n            2n1 * (ξn1 - μ - α * t) / (4 * β * (δ + t)) *\n            exp(-(ξn1 - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\n        )\n    ∇ρ2 =\n        -(\n            2n2 * (ξn2 - μ - α * t) / (4 * β * (δ + t)) *\n            exp(-(ξn2 - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\n        )\n    ∇state.ρ = hcat(∇ρ1, ∇ρ2)\nend\n\nfunction test_run(mpicomm, dim, polynomialorders, level, ArrayType, FT)\n\n    n_hd =\n        dim == 2 ? SVector{3, FT}(1, 0, 0) :\n        SVector{3, FT}(1 / sqrt(2), 1 / sqrt(2), 0)\n\n    n_vd = dim == 2 ? SVector{3, FT}(0, 1, 0) : SVector{3, FT}(0, 0, 1)\n\n    α = FT(1)\n    β = FT(1 // 100)\n    μ = FT(-1 // 2)\n    δ = FT(1 // 10)\n\n    # Grid/topology information\n    base_num_elem = 4\n    Ne = 2^(level - 1) * base_num_elem\n    brickrange = ntuple(j -> range(FT(-1); length = Ne + 1, stop = 1), dim)\n    periodicity = ntuple(j -> false, dim)\n    bc = ntuple(j -> (1, 2), dim)\n    connectivity = dim == 3 ? :full : :face\n    topl = StackedBrickTopology(\n        mpicomm,\n        brickrange;\n        periodicity = periodicity,\n        boundary = bc,\n        connectivity = connectivity,\n    )\n\n    dt = (α / 4) / (Ne * maximum(polynomialorders)^2)\n    timeend = 1\n    @info \"time step\" dt\n\n    @info @sprintf \"\"\"Test parameters:\n    ArrayType                   = %s\n    FloatType                   = %s\n    Dimension                   = %s\n    Horizontal polynomial order = %s\n    Vertical polynomial order   = %s\n      \"\"\" ArrayType FT dim polynomialorders[1] polynomialorders[end]\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorders,\n    )\n\n    bcs = (InhomogeneousBC{0}(), InhomogeneousBC{1}())\n    # Model being tested\n    model = AdvectionDiffusion{dim}(\n        Pseudo1D{n_hd, n_vd, α, β, μ, δ}(),\n        bcs,\n        num_equations = 2,\n    )\n\n    # Main DG discretization\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n        direction = EveryDirection(),\n    )\n\n    # Initialize all relevant state arrays and create solvers\n    Q = init_ode_state(dg, FT(0))\n\n    eng0 = norm(Q, dims = (1, 3))\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\"\"\" eng0[1]\n\n    solver = LSRK54CarpenterKennedy(dg, Q; dt = dt, t0 = 0)\n    solve!(Q, solver; timeend = timeend)\n\n    # Reference solution\n    engf = norm(Q, dims = (1, 3))\n    Q_ref = init_ode_state(dg, FT(timeend))\n\n    engfe = norm(Q_ref, dims = (1, 3))\n    errf = norm(Q_ref .- Q, dims = (1, 3))\n\n    metrics = @. (engf, engf / eng0, engf - eng0, errf, errf / engfe)\n\n    @info @sprintf \"\"\"Finished\n    Horizontal field:\n      norm(Q)                 = %.16e\n      norm(Q) / norm(Q₀)      = %.16e\n      norm(Q) - norm(Q₀)      = %.16e\n      norm(Q - Qe)            = %.16e\n      norm(Q - Qe) / norm(Qe) = %.16e\n    Vertical field:\n      norm(Q)                 = %.16e\n      norm(Q) / norm(Q₀)      = %.16e\n      norm(Q) - norm(Q₀)      = %.16e\n      norm(Q - Qe)            = %.16e\n      norm(Q - Qe) / norm(Qe) = %.16e\n      \"\"\" first.(metrics)... last.(metrics)...\n\n    return errf\nend\n\n\"\"\"\n    main()\n\nRun this test problem\n\"\"\"\nfunction main()\n\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n    mpicomm = MPI.COMM_WORLD\n\n    # Dictionary keys: dim, level, polynomial order, FT, and direction\n    expected_result = Dict()\n\n    # Dim 2, degree 4 in the horizontal, Float64\n    expected_result[2, 1, 4, Float64, HorizontalDirection] = 0.0467837436192571\n    expected_result[2, 2, 4, Float64, HorizontalDirection] =\n        0.004066556782723549\n    expected_result[2, 3, 4, Float64, HorizontalDirection] =\n        5.3144336694234015e-5\n    expected_result[2, 4, 4, Float64, HorizontalDirection] =\n        3.978000110046181e-7\n\n    # Dim 2, degree 2 in the vertical, Float64\n    expected_result[2, 1, 2, Float64, VerticalDirection] = 0.15362016594121006\n    expected_result[2, 2, 2, Float64, VerticalDirection] = 0.04935353328794371\n    expected_result[2, 3, 2, Float64, VerticalDirection] = 0.015530511948609192\n    expected_result[2, 4, 2, Float64, VerticalDirection] = 0.0006275095484456197\n\n    # Dim 2, degree 2 in the horizontal, Float64\n    expected_result[2, 1, 2, Float64, HorizontalDirection] = 0.15362016594121003\n    expected_result[2, 2, 2, Float64, HorizontalDirection] = 0.04935353328794369\n    expected_result[2, 3, 2, Float64, HorizontalDirection] =\n        0.015530511948609204\n    expected_result[2, 4, 2, Float64, HorizontalDirection] =\n        0.0006275095484455967\n\n    # Dim 2, degree 4 in the vertical, Float64\n    expected_result[2, 1, 4, Float64, VerticalDirection] = 0.04678374361925714\n    expected_result[2, 2, 4, Float64, VerticalDirection] = 0.0040665567827235\n    expected_result[2, 3, 4, Float64, VerticalDirection] = 5.3144336694109365e-5\n    expected_result[2, 4, 4, Float64, VerticalDirection] = 3.978000109805811e-7\n\n    # Dim 3, degree 4 in the horizontal, Float64\n    expected_result[3, 1, 4, Float64, HorizontalDirection] =\n        0.017475667486259432\n    expected_result[3, 2, 4, Float64, HorizontalDirection] =\n        0.0012502148161420109\n    expected_result[3, 3, 4, Float64, HorizontalDirection] =\n        6.999081063570052e-5\n    expected_result[3, 4, 4, Float64, HorizontalDirection] =\n        2.8724182090419642e-6\n\n    # Dim 3, degree 2 in the vertical, Float64\n    expected_result[3, 1, 2, Float64, VerticalDirection] = 0.2172517221280645\n    expected_result[3, 2, 2, Float64, VerticalDirection] = 0.06979643612684193\n    expected_result[3, 3, 2, Float64, VerticalDirection] = 0.02196346062832051\n    expected_result[3, 4, 2, Float64, VerticalDirection] = 0.0008874325139302493\n\n    # Dim 3, degree 2 in the horizontal, Float64\n    expected_result[3, 1, 2, Float64, HorizontalDirection] = 0.10343354980172516\n    expected_result[3, 2, 2, Float64, HorizontalDirection] = 0.03415137756593495\n    expected_result[3, 3, 2, Float64, HorizontalDirection] =\n        0.0035959803480493553\n    expected_result[3, 4, 2, Float64, HorizontalDirection] =\n        0.0002714157844893719\n\n    # Dim 3, degree 4 in the vertical, Float64\n    expected_result[3, 1, 4, Float64, VerticalDirection] = 0.06616220472493903\n    expected_result[3, 2, 4, Float64, VerticalDirection] = 0.005750979754288175\n    expected_result[3, 3, 4, Float64, VerticalDirection] = 7.515744171591452e-5\n    expected_result[3, 4, 4, Float64, VerticalDirection] = 5.625741705890895e-7\n\n    # Dim 2, degree 4 in the horizontal, Float32\n    expected_result[2, 1, 4, Float32, HorizontalDirection] = 0.046783954f0\n    expected_result[2, 2, 4, Float32, HorizontalDirection] = 0.004066328f0\n    expected_result[2, 3, 4, Float32, HorizontalDirection] = 5.327546f-5\n\n    # Dim 2, degree 2 in the vertical, Float32\n    expected_result[2, 1, 2, Float32, VerticalDirection] = 0.15362015f0\n    expected_result[2, 2, 2, Float32, VerticalDirection] = 0.04935346f0\n    expected_result[2, 3, 2, Float32, VerticalDirection] = 0.015530386f0\n\n    # Dim 2, degree 2 in the horizontal, Float32\n    expected_result[2, 1, 2, Float32, HorizontalDirection] = 0.1536202f0\n    expected_result[2, 2, 2, Float32, HorizontalDirection] = 0.04935346f0\n    expected_result[2, 3, 2, Float32, HorizontalDirection] = 0.015530357f0\n\n    # Dim 2, degree 4 in the vertical, Float32\n    expected_result[2, 1, 4, Float32, VerticalDirection] = 0.04678398f0\n    expected_result[2, 2, 4, Float32, VerticalDirection] = 0.0040662177f0\n    expected_result[2, 3, 4, Float32, VerticalDirection] = 5.3401447f-5\n\n    # Dim 3, degree 4 in the horizontal, Float32\n    expected_result[3, 1, 4, Float32, HorizontalDirection] = 0.01747554f0\n    expected_result[3, 2, 4, Float32, HorizontalDirection] = 0.0012502924f0\n    expected_result[3, 3, 4, Float32, HorizontalDirection] = 7.00218f-5\n\n    # Dim 3, degree 2 in the vertical, Float32\n    expected_result[3, 1, 2, Float32, VerticalDirection] = 0.21725166f0\n    expected_result[3, 2, 2, Float32, VerticalDirection] = 0.06979626f0\n    expected_result[3, 3, 2, Float32, VerticalDirection] = 0.021963252f0\n\n    # Dim 3, degree 2 in the horizontal, Float32\n    expected_result[3, 1, 2, Float32, HorizontalDirection] = 0.10343349f0\n    expected_result[3, 2, 2, Float32, HorizontalDirection] = 0.034151305f0\n    expected_result[3, 3, 2, Float32, HorizontalDirection] = 0.0035958516f0\n\n    # Dim 3, degree 4 in the vertical, Float32\n    expected_result[3, 1, 4, Float32, VerticalDirection] = 0.06616244f0\n    expected_result[3, 2, 4, Float32, VerticalDirection] = 0.005750495f0\n    expected_result[3, 3, 4, Float32, VerticalDirection] = 7.538217f-5\n\n    @testset \"Variable degree DG: advection diffusion model\" begin\n        for FT in (Float32, Float64)\n            numlevels =\n                integration_testing ||\n                ClimateMachine.Settings.integration_testing ?\n                (FT == Float64 ? 4 : 3) : 1\n            for dim in 2:3\n                for polynomialorders in ((4, 2), (2, 4))\n                    result = Dict()\n                    for level in 1:numlevels\n                        result[level] = test_run(\n                            mpicomm,\n                            dim,\n                            polynomialorders,\n                            level,\n                            ArrayType,\n                            FT,\n                        )\n                        horiz_poly = polynomialorders[1]\n                        vert_poly = polynomialorders[2]\n                        @test result[level][1] ≈ FT(expected_result[\n                            dim,\n                            level,\n                            horiz_poly,\n                            FT,\n                            HorizontalDirection,\n                        ])\n                        @test result[level][2] ≈ FT(expected_result[\n                            dim,\n                            level,\n                            vert_poly,\n                            FT,\n                            VerticalDirection,\n                        ])\n                    end\n                    @info begin\n                        msg = \"\"\n                        for l in 1:(numlevels - 1)\n                            rate = @. log2(result[l]) - log2(result[l + 1])\n                            msg *= @sprintf(\n                                \"\\n  rates for level %d Horizontal = %e\",\n                                l,\n                                rate[1]\n                            )\n                            msg *= @sprintf(\", Vertical = %e\\n\", rate[2])\n                        end\n                        msg\n                    end\n                end\n            end\n        end\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_Navier_Stokes/density_current_model.jl",
    "content": "using Test\nusing Dates\nusing LinearAlgebra\nusing MPI\nusing Printf\nusing Random\nusing StaticArrays\n\nusing ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Geometry\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.VariableTemplates\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VTK\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: R_d, cp_d, cv_d, grav, MSLP\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\n# -------------- Problem constants ------------------- #\nconst dim = 3\nconst (xmin, xmax) = (0, 12800)\nconst (ymin, ymax) = (0, 400)\nconst (zmin, zmax) = (0, 6400)\nconst Ne = (100, 2, 50)\nconst polynomialorder = 4\nconst dt = 0.01\nconst timeend = 10dt\n\n# ------------- Initial condition function ----------- #\n\"\"\"\n# Reference\n\nSee [Straka1993](@cite)\n\"\"\"\nfunction Initialise_Density_Current!(\n    problem,\n    bl,\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t,\n)\n    (x1, x2, x3) = localgeo.coord\n    param_set = parameter_set(bl)\n    FT = eltype(state)\n    _R_d::FT = R_d(param_set)\n    _grav::FT = grav(param_set)\n    _cp_d::FT = cp_d(param_set)\n    _cv_d::FT = cv_d(param_set)\n    _MSLP::FT = MSLP(param_set)\n    # initialise with dry domain\n    q_tot::FT = 0\n    q_liq::FT = 0\n    q_ice::FT = 0\n    # perturbation parameters for rising bubble\n    rx = 4000\n    rz = 2000\n    xc = 0\n    zc = 3000\n    r = sqrt((x1 - xc)^2 / rx^2 + (x3 - zc)^2 / rz^2)\n    θ_ref::FT = 300\n    θ_c::FT = -15\n    Δθ::FT = 0\n    if r <= 1\n        Δθ = θ_c * (1 + cospi(r)) / 2\n    end\n    qvar = PhasePartition(q_tot)\n    θ = θ_ref + Δθ # potential temperature\n    π_exner = FT(1) - _grav / (_cp_d * θ) * x3 # exner pressure\n    ρ = _MSLP / (_R_d * θ) * (π_exner)^(_cv_d / _R_d) # density\n\n    ts = PhaseEquil_ρθq(param_set, ρ, θ, q_tot)\n    q_pt = PhasePartition(ts)\n\n    U, V, W = FT(0), FT(0), FT(0)  # momentum components\n    # energy definitions\n    e_kin = (U^2 + V^2 + W^2) / (2 * ρ) / ρ\n    e_pot = gravitational_potential(bl, aux)\n    e_int = internal_energy(ts)\n    E = ρ * (e_int + e_kin + e_pot)  #* total_energy(e_kin, e_pot, T, q_tot, q_liq, q_ice)\n    state.ρ = ρ\n    state.ρu = SVector(U, V, W)\n    state.energy.ρe = E\n    state.moisture.ρq_tot = ρ * q_pt.tot\nend\n# --------------- Driver definition ------------------ #\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    topl,\n    dim,\n    Ne,\n    polynomialorder,\n    timeend,\n    FT,\n    dt,\n)\n    # -------------- Define grid ----------------------------------- #\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorder,\n    )\n    # -------------- Define model ---------------------------------- #\n    T_profile = DryAdiabaticProfile{FT}(param_set)\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = HydrostaticState(T_profile),\n        turbulence = AnisoMinDiss{FT}(1),\n    )\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        init_state_prognostic = Initialise_Density_Current!,\n        source = (Gravity(),),\n    )\n    # -------------- Define DGModel --------------------------- #\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n\n    lsrk = LSRK54CarpenterKennedy(dg, Q; dt = dt, t0 = 0)\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\n    ArrayType = %s\n    FloatType = %s\"\"\" eng0 ArrayType FT\n\n    # Set up the information callback (output field dump is via vtk callback: see cbinfo)\n    starttime = Ref(now())\n    cbinfo = GenericCallbacks.EveryXWallTimeSeconds(10, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                ODESolvers.gettime(lsrk),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n\n    vtkstep = [0]\n    cbvtk = GenericCallbacks.EveryXSimulationSteps(3000) do (init = false)\n        mkpath(\"./vtk-dc/\")\n        outprefix = @sprintf(\n            \"./vtk-dc/DC_%dD_mpirank%04d_step%04d\",\n            dim,\n            MPI.Comm_rank(mpicomm),\n            vtkstep[1]\n        )\n        @debug \"doing VTK output\" outprefix\n        writevtk(\n            outprefix,\n            Q,\n            dg,\n            flattenednames(vars_state(model, Prognostic(), FT)),\n            dg.state_auxiliary,\n            flattenednames(vars_state(model, Auxiliary(), FT)),\n        )\n        vtkstep[1] += 1\n        nothing\n    end\n\n\n    solve!(Q, lsrk; timeend = timeend, callbacks = (cbinfo, cbvtk))\n    # End of the simulation information\n    engf = norm(Q)\n    Qe = init_ode_state(dg, FT(timeend))\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" engf engf / eng0 engf - eng0 errf errf / engfe\n    engf / eng0\nend\n# --------------- Test block / Loggers ------------------ #\nusing Test\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n    mpicomm = MPI.COMM_WORLD\n\n    for FT in (Float32, Float64)\n        brickrange = (\n            range(FT(xmin); length = Ne[1] + 1, stop = xmax),\n            range(FT(ymin); length = Ne[2] + 1, stop = ymax),\n            range(FT(zmin); length = Ne[3] + 1, stop = zmax),\n        )\n        topl = StackedBrickTopology(\n            mpicomm,\n            brickrange,\n            periodicity = (false, true, false),\n        )\n        engf_eng0 = test_run(\n            mpicomm,\n            ArrayType,\n            topl,\n            dim,\n            Ne,\n            polynomialorder,\n            timeend,\n            FT,\n            dt,\n        )\n        @test engf_eng0 ≈ FT(9.9999970927037096e-01)\n    end\nend\n\n\n#nothing\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_Navier_Stokes/mms_bc_atmos.jl",
    "content": "using Test\nusing Dates\nusing LinearAlgebra\nusing MPI\nusing Printf\nusing StaticArrays\nusing UnPack\n\nusing ClimateMachine\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Atmos\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.Orientations\nusing ClimateMachine.VariableTemplates\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VTK\n\nimport ClimateMachine.BalanceLaws: source, prognostic_vars\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nimport CLIMAParameters\n# Assume zero reference temperature\nCLIMAParameters.Planet.T_0(::EarthParameterSet) = 0\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\ninclude(\"mms_solution_generated.jl\")\n\nimport Thermodynamics: total_specific_enthalpy\nusing ClimateMachine.Atmos\n\ntotal_specific_enthalpy(ts::PhaseDry{FT}, e_tot::FT) where {FT <: Real} =\n    zero(FT)\n\nfunction mms2_init_state!(problem, bl, state::Vars, aux::Vars, localgeo, t)\n    (x1, x2, x3) = localgeo.coord\n    state.ρ = ρ_g(t, x1, x2, x3, Val(2))\n    state.ρu = SVector(\n        U_g(t, x1, x2, x3, Val(2)),\n        V_g(t, x1, x2, x3, Val(2)),\n        W_g(t, x1, x2, x3, Val(2)),\n    )\n    state.energy.ρe = E_g(t, x1, x2, x3, Val(2))\nend\n\nstruct MMSSource{N} <: TendencyDef{Source} end\n\nprognostic_vars(::MMSSource{N}) where {N} = (Mass(), Momentum(), Energy())\n\nfunction source(::Mass, s::MMSSource{N}, m, args) where {N}\n    @unpack aux, t = args\n    x1, x2, x3 = aux.coord\n    return Sρ_g(t, x1, x2, x3, Val(N))\nend\nfunction source(::Momentum, s::MMSSource{N}, m, args) where {N}\n    @unpack aux, t = args\n    x1, x2, x3 = aux.coord\n    return SVector(\n        SU_g(t, x1, x2, x3, Val(N)),\n        SV_g(t, x1, x2, x3, Val(N)),\n        SW_g(t, x1, x2, x3, Val(N)),\n    )\nend\nfunction source(::Energy, s::MMSSource{N}, m, args) where {N}\n    @unpack aux, t = args\n    x1, x2, x3 = aux.coord\n    return SE_g(t, x1, x2, x3, Val(N))\nend\n\nfunction mms3_init_state!(problem, bl, state::Vars, aux::Vars, localgeo, t)\n    (x1, x2, x3) = localgeo.coord\n    state.ρ = ρ_g(t, x1, x2, x3, Val(3))\n    state.ρu = SVector(\n        U_g(t, x1, x2, x3, Val(3)),\n        V_g(t, x1, x2, x3, Val(3)),\n        W_g(t, x1, x2, x3, Val(3)),\n    )\n    state.energy.ρe = E_g(t, x1, x2, x3, Val(3))\nend\n\n# initial condition\n\nfunction test_run(mpicomm, ArrayType, dim, topl, warpfun, N, timeend, FT, dt)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n        meshwarp = warpfun,\n    )\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = NoReferenceState(),\n        turbulence = ConstantDynamicViscosity(FT(μ_exact), WithDivergence()),\n        moisture = DryModel(),\n    )\n    if dim == 2\n        problem = AtmosProblem(\n            boundaryconditions = (InitStateBC(),),\n            init_state_prognostic = mms2_init_state!,\n        )\n        model = AtmosModel{FT}(\n            AtmosLESConfigType,\n            physics;\n            problem = problem,\n            orientation = NoOrientation(),\n            source = (MMSSource{2}(),),\n        )\n    else\n        problem = AtmosProblem(\n            boundaryconditions = (InitStateBC(),),\n            init_state_prognostic = mms3_init_state!,\n        )\n        model = AtmosModel{FT}(\n            AtmosLESConfigType,\n            physics;\n            problem = problem,\n            orientation = NoOrientation(),\n            source = (MMSSource{3}(),),\n        )\n    end\n    show_tendencies(model)\n\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n    Qcpu = init_ode_state(dg, FT(0); init_on_cpu = true)\n    @test euclidean_distance(Q, Qcpu) < sqrt(eps(FT))\n\n    lsrk = LSRK54CarpenterKennedy(dg, Q; dt = dt, t0 = 0)\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\"\"\" eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = GenericCallbacks.EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                ODESolvers.gettime(lsrk),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n\n    solve!(Q, lsrk; timeend = timeend, callbacks = (cbinfo,))\n    # solve!(Q, lsrk; timeend=timeend, callbacks=(cbinfo, cbvtk))\n\n\n    # Print some end of the simulation information\n    engf = norm(Q)\n    Qe = init_ode_state(dg, FT(timeend))\n\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    base_num_elem = 4\n\n    expected_result = [\n        1.6931876910307017e-01 5.4603193051929394e-03 2.3307776694542282e-04\n        3.3983777728925593e-02 1.7808380837573065e-03 9.176181458773599e-5\n    ]\n    lvls = integration_testing ? size(expected_result, 2) : 1\n\n    @testset \"mms_bc_atmos\" begin\n        for FT in (Float64,) #Float32)\n            result = zeros(FT, lvls)\n            for dim in 2:3\n                for l in 1:lvls\n                    if dim == 2\n                        Ne = (\n                            2^(l - 1) * base_num_elem,\n                            2^(l - 1) * base_num_elem,\n                        )\n                        brickrange = (\n                            range(FT(0); length = Ne[1] + 1, stop = 1),\n                            range(FT(0); length = Ne[2] + 1, stop = 1),\n                        )\n                        topl = BrickTopology(\n                            mpicomm,\n                            brickrange,\n                            periodicity = (false, false),\n                        )\n                        dt = 1e-2 / Ne[1]\n                        warpfun =\n                            (x1, x2, _) -> begin\n                                (x1 + sin(x1 * x2), x2 + sin(2 * x1 * x2), 0)\n                            end\n\n                    elseif dim == 3\n                        Ne = (\n                            2^(l - 1) * base_num_elem,\n                            2^(l - 1) * base_num_elem,\n                        )\n                        brickrange = (\n                            range(FT(0); length = Ne[1] + 1, stop = 1),\n                            range(FT(0); length = Ne[2] + 1, stop = 1),\n                            range(FT(0); length = Ne[2] + 1, stop = 1),\n                        )\n                        topl = BrickTopology(\n                            mpicomm,\n                            brickrange,\n                            periodicity = (false, false, false),\n                        )\n                        dt = 5e-3 / Ne[1]\n                        warpfun =\n                            (x1, x2, x3) -> begin\n                                (\n                                    x1 +\n                                    (x1 - 1 / 2) * cos(2 * π * x2 * x3) / 4,\n                                    x2 + exp(sin(2π * (x1 * x2 + x3))) / 20,\n                                    x3 + x1 / 4 + x2^2 / 2 + sin(x1 * x2 * x3),\n                                )\n                            end\n                    end\n                    timeend = 1\n                    nsteps = ceil(Int64, timeend / dt)\n                    dt = timeend / nsteps\n\n                    @info (ArrayType, FT, dim, nsteps, dt)\n                    result[l] = test_run(\n                        mpicomm,\n                        ArrayType,\n                        dim,\n                        topl,\n                        warpfun,\n                        polynomialorder,\n                        timeend,\n                        FT,\n                        dt,\n                    )\n                    @test result[l] ≈ FT(expected_result[dim - 1, l])\n                end\n                if integration_testing\n                    @info begin\n                        msg = \"\"\n                        for l in 1:(lvls - 1)\n                            rate = log2(result[l]) - log2(result[l + 1])\n                            msg *= @sprintf(\n                                \"\\n  rate for level %d = %e\\n\",\n                                l,\n                                rate\n                            )\n                        end\n                        msg\n                    end\n                end\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_Navier_Stokes/mms_bc_dgmodel.jl",
    "content": "using MPI\nusing ClimateMachine\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.GenericCallbacks\nusing LinearAlgebra\nusing StaticArrays\nusing Logging, Printf, Dates\nusing ClimateMachine.VTK\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\ninclude(\"mms_solution_generated.jl\")\ninclude(\"mms_model.jl\")\n\n\n# initial condition\n\nfunction test_run(mpicomm, ArrayType, dim, topl, warpfun, N, timeend, FT, dt)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n        meshwarp = warpfun,\n    )\n    dg = DGModel(\n        MMSModel{dim}(),\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n\n\n    lsrk = LSRK54CarpenterKennedy(dg, Q; dt = dt, t0 = 0)\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\"\"\" eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = GenericCallbacks.EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %.16e\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                ODESolvers.gettime(lsrk),\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n\n    solve!(Q, lsrk; timeend = timeend, callbacks = (cbinfo,))\n    # solve!(Q, lsrk; timeend=timeend, callbacks=(cbinfo, cbvtk))\n\n\n    # Print some end of the simulation information\n    engf = norm(Q)\n    Qe = init_ode_state(dg, FT(timeend))\n\n    engfe = norm(Qe)\n    errf = euclidean_distance(Q, Qe)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    norm(Q - Qe)            = %.16e\n    norm(Q - Qe) / norm(Qe) = %.16e\n    \"\"\" engf engf / eng0 engf - eng0 errf errf / engfe\n    errf\nend\n\nusing Test\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    base_num_elem = 4\n\n    expected_result = [\n        1.6694721292986181e-01 5.4178750150416337e-03 2.3066867400713085e-04\n        3.3672443923201158e-02 1.7603832251132654e-03 9.1108401774885506e-05\n    ]\n    lvls = integration_testing ? size(expected_result, 2) : 1\n\n    @testset \"mms_bc_dgmodel\" begin\n        for FT in (Float64,) #Float32)\n            result = zeros(FT, lvls)\n            for dim in 2:3\n                for l in 1:lvls\n                    if dim == 2\n                        Ne = (\n                            2^(l - 1) * base_num_elem,\n                            2^(l - 1) * base_num_elem,\n                        )\n                        brickrange = (\n                            range(FT(0); length = Ne[1] + 1, stop = 1),\n                            range(FT(0); length = Ne[2] + 1, stop = 1),\n                        )\n                        topl = BrickTopology(\n                            mpicomm,\n                            brickrange,\n                            periodicity = (false, false),\n                        )\n                        dt = 1e-2 / Ne[1]\n                        warpfun =\n                            (x1, x2, _) -> begin\n                                (x1 + sin(x1 * x2), x2 + sin(2 * x1 * x2), 0)\n                            end\n\n                    elseif dim == 3\n                        Ne = (\n                            2^(l - 1) * base_num_elem,\n                            2^(l - 1) * base_num_elem,\n                        )\n                        brickrange = (\n                            range(FT(0); length = Ne[1] + 1, stop = 1),\n                            range(FT(0); length = Ne[2] + 1, stop = 1),\n                            range(FT(0); length = Ne[2] + 1, stop = 1),\n                        )\n                        topl = BrickTopology(\n                            mpicomm,\n                            brickrange,\n                            periodicity = (false, false, false),\n                        )\n                        dt = 5e-3 / Ne[1]\n                        warpfun =\n                            (x1, x2, x3) -> begin\n                                (\n                                    x1 +\n                                    (x1 - 1 / 2) * cos(2 * π * x2 * x3) / 4,\n                                    x2 + exp(sin(2π * (x1 * x2 + x3))) / 20,\n                                    x3 + x1 / 4 + x2^2 / 2 + sin(x1 * x2 * x3),\n                                )\n                            end\n                    end\n                    timeend = 1\n                    nsteps = ceil(Int64, timeend / dt)\n                    dt = timeend / nsteps\n\n                    @info (ArrayType, FT, dim)\n                    result[l] = test_run(\n                        mpicomm,\n                        ArrayType,\n                        dim,\n                        topl,\n                        warpfun,\n                        polynomialorder,\n                        timeend,\n                        FT,\n                        dt,\n                    )\n                    @test result[l] ≈ FT(expected_result[dim - 1, l])\n                end\n                if integration_testing\n                    @info begin\n                        msg = \"\"\n                        for l in 1:(lvls - 1)\n                            rate = log2(result[l]) - log2(result[l + 1])\n                            msg *= @sprintf(\n                                \"\\n  rate for level %d = %e\\n\",\n                                l,\n                                rate\n                            )\n                        end\n                        msg\n                    end\n                end\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_Navier_Stokes/mms_model.jl",
    "content": "using ClimateMachine.VariableTemplates\n\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux\n\n\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    flux_first_order!,\n    flux_second_order!,\n    source!,\n    wavespeed,\n    boundary_conditions,\n    boundary_state!,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    nodal_init_state_auxiliary!,\n    init_state_prognostic!\n\nimport ClimateMachine.DGMethods: init_ode_state\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\n\n\nstruct MMSModel{dim} <: BalanceLaw end\n\nvars_state(::MMSModel, ::Auxiliary, T) = @vars(x1::T, x2::T, x3::T)\nvars_state(::MMSModel, ::Prognostic, T) =\n    @vars(ρ::T, ρu::T, ρv::T, ρw::T, ρe::T)\nvars_state(::MMSModel, ::Gradient, T) = @vars(u::T, v::T, w::T)\nvars_state(::MMSModel, ::GradientFlux, T) =\n    @vars(τ11::T, τ22::T, τ33::T, τ12::T, τ13::T, τ23::T)\n\nfunction flux_first_order!(\n    ::MMSModel,\n    flux::Grad,\n    state::Vars,\n    auxstate::Vars,\n    t::Real,\n    direction,\n)\n    # preflux\n    T = eltype(flux)\n    γ = T(γ_exact)\n    ρinv = 1 / state.ρ\n    u, v, w = ρinv * state.ρu, ρinv * state.ρv, ρinv * state.ρw\n    P = (γ - 1) * (state.ρe - ρinv * (state.ρu^2 + state.ρv^2 + state.ρw^2) / 2)\n\n    # invisc terms\n    flux.ρ = SVector(state.ρu, state.ρv, state.ρw)\n    flux.ρu = SVector(u * state.ρu + P, v * state.ρu, w * state.ρu)\n    flux.ρv = SVector(u * state.ρv, v * state.ρv + P, w * state.ρv)\n    flux.ρw = SVector(u * state.ρw, v * state.ρw, w * state.ρw + P)\n    flux.ρe =\n        SVector(u * (state.ρe + P), v * (state.ρe + P), w * (state.ρe + P))\nend\n\nfunction flux_second_order!(\n    ::MMSModel,\n    flux::Grad,\n    state::Vars,\n    diffusive::Vars,\n    hyperdiffusive::Vars,\n    auxstate::Vars,\n    t::Real,\n)\n    ρinv = 1 / state.ρ\n    u, v, w = ρinv * state.ρu, ρinv * state.ρv, ρinv * state.ρw\n\n    # viscous terms\n    flux.ρu -= SVector(diffusive.τ11, diffusive.τ12, diffusive.τ13)\n    flux.ρv -= SVector(diffusive.τ12, diffusive.τ22, diffusive.τ23)\n    flux.ρw -= SVector(diffusive.τ13, diffusive.τ23, diffusive.τ33)\n\n    flux.ρe -= SVector(\n        u * diffusive.τ11 + v * diffusive.τ12 + w * diffusive.τ13,\n        u * diffusive.τ12 + v * diffusive.τ22 + w * diffusive.τ23,\n        u * diffusive.τ13 + v * diffusive.τ23 + w * diffusive.τ33,\n    )\nend\n\nfunction compute_gradient_argument!(\n    ::MMSModel,\n    transformstate::Vars,\n    state::Vars,\n    auxstate::Vars,\n    t::Real,\n)\n    ρinv = 1 / state.ρ\n    transformstate.u = ρinv * state.ρu\n    transformstate.v = ρinv * state.ρv\n    transformstate.w = ρinv * state.ρw\nend\n\nfunction compute_gradient_flux!(\n    ::MMSModel,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    auxstate::Vars,\n    t::Real,\n)\n    T = eltype(diffusive)\n    μ = T(μ_exact)\n\n    dudx, dudy, dudz = ∇transform.u\n    dvdx, dvdy, dvdz = ∇transform.v\n    dwdx, dwdy, dwdz = ∇transform.w\n\n    # strains\n    ϵ11 = dudx\n    ϵ22 = dvdy\n    ϵ33 = dwdz\n    ϵ12 = (dudy + dvdx) / 2\n    ϵ13 = (dudz + dwdx) / 2\n    ϵ23 = (dvdz + dwdy) / 2\n\n    # deviatoric stresses\n    diffusive.τ11 = 2μ * (ϵ11 - (ϵ11 + ϵ22 + ϵ33) / 3)\n    diffusive.τ22 = 2μ * (ϵ22 - (ϵ11 + ϵ22 + ϵ33) / 3)\n    diffusive.τ33 = 2μ * (ϵ33 - (ϵ11 + ϵ22 + ϵ33) / 3)\n    diffusive.τ12 = 2μ * ϵ12\n    diffusive.τ13 = 2μ * ϵ13\n    diffusive.τ23 = 2μ * ϵ23\nend\n\nfunction source!(\n    ::MMSModel{dim},\n    source::Vars,\n    state::Vars,\n    diffusive::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n) where {dim}\n    source.ρ = Sρ_g(t, aux.x1, aux.x2, aux.x3, Val(dim))\n    source.ρu = SU_g(t, aux.x1, aux.x2, aux.x3, Val(dim))\n    source.ρv = SV_g(t, aux.x1, aux.x2, aux.x3, Val(dim))\n    source.ρw = SW_g(t, aux.x1, aux.x2, aux.x3, Val(dim))\n    source.ρe = SE_g(t, aux.x1, aux.x2, aux.x3, Val(dim))\nend\n\nfunction wavespeed(::MMSModel, nM, state::Vars, aux::Vars, t::Real, direction)\n    T = eltype(state)\n    γ = T(γ_exact)\n    ρinv = 1 / state.ρ\n    u, v, w = ρinv * state.ρu, ρinv * state.ρv, ρinv * state.ρw\n    P = (γ - 1) * (state.ρe - ρinv * (state.ρu^2 + state.ρv^2 + state.ρw^2) / 2)\n    return abs(nM[1] * u + nM[2] * v + nM[3] * w) + sqrt(ρinv * γ * P)\nend\n\nboundary_conditions(::MMSModel) = (nothing,)\n\nfunction boundary_state!(\n    ::RusanovNumericalFlux,\n    bctype,\n    bl::MMSModel,\n    stateP::Vars,\n    auxP::Vars,\n    nM,\n    stateM::Vars,\n    auxM::Vars,\n    t,\n    _...,\n)\n    init_state_prognostic!(\n        bl,\n        stateP,\n        auxP,\n        (coord = (auxM.x1, auxM.x2, auxM.x3),),\n        t,\n    )\nend\n\n# FIXME: This is probably not right....\nboundary_state!(::CentralNumericalFluxGradient, bc, bl::MMSModel, _...) =\n    nothing\n\nfunction boundary_state!(\n    ::CentralNumericalFluxSecondOrder,\n    bctype,\n    bl::MMSModel,\n    stateP::Vars,\n    diffP::Vars,\n    hyperdiffP::Vars,\n    auxP::Vars,\n    nM,\n    stateM::Vars,\n    diffM::Vars,\n    hyperdiffM::Vars,\n    auxM::Vars,\n    t,\n    _...,\n)\n    init_state_prognostic!(\n        bl,\n        stateP,\n        auxP,\n        (coord = (auxM.x1, auxM.x2, auxM.x3),),\n        t,\n    )\nend\n\nfunction nodal_init_state_auxiliary!(\n    ::MMSModel,\n    aux::Vars,\n    tmp::Vars,\n    g::LocalGeometry,\n)\n    x1, x2, x3 = g.coord\n    aux.x1 = x1\n    aux.x2 = x2\n    aux.x3 = x3\nend\n\nfunction init_state_prognostic!(\n    bl::MMSModel{dim},\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t,\n) where {dim}\n    (x1, x2, x3) = localgeo.coord\n    state.ρ = ρ_g(t, x1, x2, x3, Val(dim))\n    state.ρu = U_g(t, x1, x2, x3, Val(dim))\n    state.ρv = V_g(t, x1, x2, x3, Val(dim))\n    state.ρw = W_g(t, x1, x2, x3, Val(dim))\n    state.ρe = E_g(t, x1, x2, x3, Val(dim))\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_Navier_Stokes/mms_solution.jl",
    "content": "# This file generates the solution used in method of manufactured solutions\nusing LinearAlgebra, SymPy, Printf\nusing CLIMAParameters\nusing CLIMAParameters.Planet\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n@syms x y z t real = true\nμ = 1 // 100\nγ = cp_d(param_set) / cv_d(param_set)\n\noutput = open(\"mms_solution_generated.jl\", \"w\")\n\n@printf output \"const γ_exact = %s\\n\" γ\n@printf output \"const μ_exact = %s\\n\" μ\n\nfor dim in 2:3\n    if dim == 3\n        ρ = cos(π * t) * sin(π * x) * cos(π * y) * cos(π * z) + 3\n        U = cos(π * t) * ρ * sin(π * x) * cos(π * y) * cos(π * z)\n        V = cos(π * t) * ρ * sin(π * x) * cos(π * y) * cos(π * z)\n        W = cos(π * t) * ρ * sin(π * x) * cos(π * y) * sin(π * z)\n        E = cos(π * t) * sin(π * x) * cos(π * y) * cos(π * z) + 100\n    else\n        ρ = cos(π * t) * sin(π * x) * cos(π * y) + 3\n        U = cos(π * t) * ρ * sin(π * x) * cos(π * y)\n        V = cos(π * t) * ρ * sin(π * x) * cos(π * y)\n        W = cos(π * t) * 0\n        E = cos(π * t) * sin(π * x) * cos(π * y) + 100\n    end\n\n\n    P = (γ - 1) * (E - (U^2 + V^2 + W^2) / 2ρ)\n\n    u, v, w = U / ρ, V / ρ, W / ρ\n\n    dudx, dudy, dudz = diff(u, x), diff(u, y), diff(u, z)\n    dvdx, dvdy, dvdz = diff(v, x), diff(v, y), diff(v, z)\n    dwdx, dwdy, dwdz = diff(w, x), diff(w, y), diff(w, z)\n\n    ϵ11 = dudx\n    ϵ22 = dvdy\n    ϵ33 = dwdz\n    ϵ12 = (dudy + dvdx) / 2\n    ϵ13 = (dudz + dwdx) / 2\n    ϵ23 = (dvdz + dwdy) / 2\n\n    τ11 = 2μ * (ϵ11 - (ϵ11 + ϵ22 + ϵ33) / 3)\n    τ22 = 2μ * (ϵ22 - (ϵ11 + ϵ22 + ϵ33) / 3)\n    τ33 = 2μ * (ϵ33 - (ϵ11 + ϵ22 + ϵ33) / 3)\n    τ12 = τ21 = 2μ * ϵ12\n    τ13 = τ31 = 2μ * ϵ13\n    τ23 = τ32 = 2μ * ϵ23\n\n    Fx_x =\n        diff.(\n            [\n                U\n                u * U + P - τ11\n                u * V - τ12\n                u * W - τ13\n                u * (E + P) - u * τ11 - v * τ12 - w * τ13\n            ],\n            x,\n        )\n    Fy_y =\n        diff.(\n            [\n                V\n                v * U - τ21\n                v * V + P - τ22\n                v * W - τ23\n                v * (E + P) - u * τ21 - v * τ22 - w * τ23\n            ],\n            y,\n        )\n    Fz_z =\n        diff.(\n            [\n                W\n                w * U - τ31\n                w * V - τ32\n                w * W + P - τ33\n                w * (E + P) - u * τ31 - v * τ32 - w * τ33\n            ],\n            z,\n        )\n\n    dρdt = simplify(Fx_x[1] + Fy_y[1] + Fz_z[1] + diff(ρ, t))\n    dUdt = simplify(Fx_x[2] + Fy_y[2] + Fz_z[2] + diff(U, t))\n    dVdt = simplify(Fx_x[3] + Fy_y[3] + Fz_z[3] + diff(V, t))\n    dWdt = simplify(Fx_x[4] + Fy_y[4] + Fz_z[4] + diff(W, t))\n    dEdt = simplify(Fx_x[5] + Fy_y[5] + Fz_z[5] + diff(E, t))\n\n\n    @printf output \"@noinline ρ_g(t, x, y, z, ::Val{%d}) = %s\\n\" dim ρ\n    @printf output \"@noinline U_g(t, x, y, z, ::Val{%d}) = %s\\n\" dim U\n    @printf output \"@noinline V_g(t, x, y, z, ::Val{%d}) = %s\\n\" dim V\n    @printf output \"@noinline W_g(t, x, y, z, ::Val{%d}) = %s\\n\" dim W\n    @printf output \"@noinline E_g(t, x, y, z, ::Val{%d}) = %s\\n\" dim E\n    @printf output \"@noinline Sρ_g(t, x, y, z, ::Val{%d}) = %s\\n\" dim dρdt\n    @printf output \"@noinline SU_g(t, x, y, z, ::Val{%d}) = %s\\n\" dim dUdt\n    @printf output \"@noinline SV_g(t, x, y, z, ::Val{%d}) = %s\\n\" dim dVdt\n    @printf output \"@noinline SW_g(t, x, y, z, ::Val{%d}) = %s\\n\" dim dWdt\n    @printf output \"@noinline SE_g(t, x, y, z, ::Val{%d}) = %s\\n\" dim dEdt\nend\n\nclose(output)\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_Navier_Stokes/mms_solution_generated.jl",
    "content": "const γ_exact = 1.4\nconst μ_exact = 1 // 100\n@noinline ρ_g(t, x, y, z, ::Val{2}) =\n    sin(pi * x) * cos(pi * t) * cos(pi * y) + 3\n@noinline U_g(t, x, y, z, ::Val{2}) =\n    (sin(pi * x) * cos(pi * t) * cos(pi * y) + 3) *\n    sin(pi * x) *\n    cos(pi * t) *\n    cos(pi * y)\n@noinline V_g(t, x, y, z, ::Val{2}) =\n    (sin(pi * x) * cos(pi * t) * cos(pi * y) + 3) *\n    sin(pi * x) *\n    cos(pi * t) *\n    cos(pi * y)\n@noinline W_g(t, x, y, z, ::Val{2}) = 0\n@noinline E_g(t, x, y, z, ::Val{2}) =\n    sin(pi * x) * cos(pi * t) * cos(pi * y) + 100\n@noinline Sρ_g(t, x, y, z, ::Val{2}) =\n    pi * (\n        2 * sin(2 * pi * x) - 2 * sin(2 * pi * y) - sin(pi * (2 * t - 2 * x)) +\n        sin(pi * (2 * t + 2 * x)) +\n        sin(pi * (2 * t - 2 * y)) - sin(pi * (2 * t + 2 * y)) +\n        2 * sin(pi * (2 * x + 2 * y)) +\n        sin(pi * (-2 * t + 2 * x + 2 * y)) +\n        sin(pi * (2 * t + 2 * x + 2 * y)) +\n        10 * cos(pi * (-t + x + y)) - 2 * cos(pi * (t - x + y)) +\n        2 * cos(pi * (t + x - y)) +\n        14 * cos(pi * (t + x + y))\n    ) / 8\n@noinline SU_g(t, x, y, z, ::Val{2}) =\n    pi * (\n        -2.0 * sin(pi * t) * sin(pi * x)^2 * cos(pi * t) * cos(pi * y)^2 -\n        3.0 * sin(pi * t) * sin(pi * x) * cos(pi * y) -\n        3.0 * sin(pi * x)^3 * sin(pi * y) * cos(pi * t)^3 * cos(pi * y)^2 -\n        6.0 * sin(pi * x)^2 * sin(pi * y) * cos(pi * t)^2 * cos(pi * y) +\n        1.8 * sin(pi * x)^2 * cos(pi * t)^3 * cos(pi * x) * cos(pi * y)^3 +\n        3.6 * sin(pi * x) * cos(pi * t)^2 * cos(pi * x) * cos(pi * y)^2 +\n        0.0233333333333333 * pi * sin(pi * x) * cos(pi * t) * cos(pi * y) +\n        0.00333333333333333 * pi * sin(pi * y) * cos(pi * t) * cos(pi * x) +\n        0.4 * cos(pi * t) * cos(pi * x) * cos(pi * y)\n    )\n@noinline SV_g(t, x, y, z, ::Val{2}) =\n    pi * (\n        -2.0 * sin(pi * t) * sin(pi * x)^2 * cos(pi * t) * cos(pi * y)^2 -\n        3.0 * sin(pi * t) * sin(pi * x) * cos(pi * y) -\n        1.8 * sin(pi * x)^3 * sin(pi * y) * cos(pi * t)^3 * cos(pi * y)^2 -\n        3.6 * sin(pi * x)^2 * sin(pi * y) * cos(pi * t)^2 * cos(pi * y) +\n        3.0 * sin(pi * x)^2 * cos(pi * t)^3 * cos(pi * x) * cos(pi * y)^3 -\n        0.4 * sin(pi * x) * sin(pi * y) * cos(pi * t) +\n        6.0 * sin(pi * x) * cos(pi * t)^2 * cos(pi * x) * cos(pi * y)^2 +\n        0.0233333333333333 * pi * sin(pi * x) * cos(pi * t) * cos(pi * y) +\n        0.00333333333333333 * pi * sin(pi * y) * cos(pi * t) * cos(pi * x)\n    )\n@noinline SW_g(t, x, y, z, ::Val{2}) = 0\n@noinline SE_g(t, x, y, z, ::Val{2}) =\n    pi * (\n        -1.0 * sin(pi * t) * sin(pi * x) * cos(pi * y) +\n        1.6 * sin(pi * x)^4 * sin(pi * y) * cos(pi * t)^4 * cos(pi * y)^3 +\n        3.6 * sin(pi * x)^3 * sin(pi * y) * cos(pi * t)^3 * cos(pi * y)^2 -\n        1.6 * sin(pi * x)^3 * cos(pi * t)^4 * cos(pi * x) * cos(pi * y)^4 -\n        0.0233333333333333 *\n        pi *\n        sin(pi * x)^2 *\n        sin(pi * y)^2 *\n        cos(pi * t)^2 -\n        2.8 * sin(pi * x)^2 * sin(pi * y) * cos(pi * t)^2 * cos(pi * y) -\n        3.6 * sin(pi * x)^2 * cos(pi * t)^3 * cos(pi * x) * cos(pi * y)^3 +\n        0.0466666666666667 *\n        pi *\n        sin(pi * x)^2 *\n        cos(pi * t)^2 *\n        cos(pi * y)^2 +\n        0.0133333333333333 *\n        pi *\n        sin(pi * x) *\n        sin(pi * y) *\n        cos(pi * t)^2 *\n        cos(pi * x) *\n        cos(pi * y) - 140.0 * sin(pi * x) * sin(pi * y) * cos(pi * t) +\n        2.8 * sin(pi * x) * cos(pi * t)^2 * cos(pi * x) * cos(pi * y)^2 -\n        0.0233333333333333 *\n        pi *\n        cos(pi * t)^2 *\n        cos(pi * x)^2 *\n        cos(pi * y)^2 + 140.0 * cos(pi * t) * cos(pi * x) * cos(pi * y)\n    )\n@noinline ρ_g(t, x, y, z, ::Val{3}) =\n    sin(pi * x) * cos(pi * t) * cos(pi * y) * cos(pi * z) + 3\n@noinline U_g(t, x, y, z, ::Val{3}) =\n    (sin(pi * x) * cos(pi * t) * cos(pi * y) * cos(pi * z) + 3) *\n    sin(pi * x) *\n    cos(pi * t) *\n    cos(pi * y) *\n    cos(pi * z)\n@noinline V_g(t, x, y, z, ::Val{3}) =\n    (sin(pi * x) * cos(pi * t) * cos(pi * y) * cos(pi * z) + 3) *\n    sin(pi * x) *\n    cos(pi * t) *\n    cos(pi * y) *\n    cos(pi * z)\n@noinline W_g(t, x, y, z, ::Val{3}) =\n    (sin(pi * x) * cos(pi * t) * cos(pi * y) * cos(pi * z) + 3) *\n    sin(pi * x) *\n    sin(pi * z) *\n    cos(pi * t) *\n    cos(pi * y)\n@noinline E_g(t, x, y, z, ::Val{3}) =\n    sin(pi * x) * cos(pi * t) * cos(pi * y) * cos(pi * z) + 100\n@noinline Sρ_g(t, x, y, z, ::Val{3}) =\n    pi * (\n        -sin(pi * t) * sin(pi * x) * cos(pi * y) * cos(pi * z) -\n        2 *\n        sin(pi * x)^2 *\n        sin(pi * y) *\n        cos(pi * t)^2 *\n        cos(pi * y) *\n        cos(pi * z)^2 -\n        sin(pi * x)^2 * sin(pi * z)^2 * cos(pi * t)^2 * cos(pi * y)^2 +\n        sin(pi * x)^2 * cos(pi * t)^2 * cos(pi * y)^2 * cos(pi * z)^2 -\n        3 * sin(pi * x) * sin(pi * y) * cos(pi * t) * cos(pi * z) +\n        2 *\n        sin(pi * x) *\n        cos(pi * t)^2 *\n        cos(pi * x) *\n        cos(pi * y)^2 *\n        cos(pi * z)^2 +\n        3 * sin(pi * x) * cos(pi * t) * cos(pi * y) * cos(pi * z) +\n        3 * cos(pi * t) * cos(pi * x) * cos(pi * y) * cos(pi * z)\n    )\n@noinline SU_g(t, x, y, z, ::Val{3}) =\n    pi * (\n        -2.0 *\n        sin(pi * t) *\n        sin(pi * x)^2 *\n        cos(pi * t) *\n        cos(pi * y)^2 *\n        cos(pi * z)^2 -\n        3.0 * sin(pi * t) * sin(pi * x) * cos(pi * y) * cos(pi * z) -\n        3.0 *\n        sin(pi * x)^3 *\n        sin(pi * y) *\n        cos(pi * t)^3 *\n        cos(pi * y)^2 *\n        cos(pi * z)^3 -\n        2.0 *\n        sin(pi * x)^3 *\n        sin(pi * z)^2 *\n        cos(pi * t)^3 *\n        cos(pi * y)^3 *\n        cos(pi * z) +\n        1.0 * sin(pi * x)^3 * cos(pi * t)^3 * cos(pi * y)^3 * cos(pi * z)^3 -\n        6.0 *\n        sin(pi * x)^2 *\n        sin(pi * y) *\n        cos(pi * t)^2 *\n        cos(pi * y) *\n        cos(pi * z)^2 -\n        0.6 *\n        sin(pi * x)^2 *\n        sin(pi * z)^2 *\n        cos(pi * t)^3 *\n        cos(pi * x) *\n        cos(pi * y)^3 *\n        cos(pi * z) -\n        3.0 * sin(pi * x)^2 * sin(pi * z)^2 * cos(pi * t)^2 * cos(pi * y)^2 +\n        1.8 *\n        sin(pi * x)^2 *\n        cos(pi * t)^3 *\n        cos(pi * x) *\n        cos(pi * y)^3 *\n        cos(pi * z)^3 +\n        3.0 * sin(pi * x)^2 * cos(pi * t)^2 * cos(pi * y)^2 * cos(pi * z)^2 -\n        1.2 *\n        sin(pi * x) *\n        sin(pi * z)^2 *\n        cos(pi * t)^2 *\n        cos(pi * x) *\n        cos(pi * y)^2 +\n        3.6 *\n        sin(pi * x) *\n        cos(pi * t)^2 *\n        cos(pi * x) *\n        cos(pi * y)^2 *\n        cos(pi * z)^2 +\n        0.0333333333333333 *\n        pi *\n        sin(pi * x) *\n        cos(pi * t) *\n        cos(pi * y) *\n        cos(pi * z) +\n        0.00333333333333333 *\n        pi *\n        sin(pi * y) *\n        cos(pi * t) *\n        cos(pi * x) *\n        cos(pi * z) -\n        0.00333333333333333 *\n        pi *\n        cos(pi * t) *\n        cos(pi * x) *\n        cos(pi * y) *\n        cos(pi * z) +\n        0.4 * cos(pi * t) * cos(pi * x) * cos(pi * y) * cos(pi * z)\n    )\n@noinline SV_g(t, x, y, z, ::Val{3}) =\n    pi * (\n        -2.0 *\n        sin(pi * t) *\n        sin(pi * x)^2 *\n        cos(pi * t) *\n        cos(pi * y)^2 *\n        cos(pi * z)^2 -\n        3.0 * sin(pi * t) * sin(pi * x) * cos(pi * y) * cos(pi * z) +\n        0.6 *\n        sin(pi * x)^3 *\n        sin(pi * y) *\n        sin(pi * z)^2 *\n        cos(pi * t)^3 *\n        cos(pi * y)^2 *\n        cos(pi * z) -\n        1.8 *\n        sin(pi * x)^3 *\n        sin(pi * y) *\n        cos(pi * t)^3 *\n        cos(pi * y)^2 *\n        cos(pi * z)^3 -\n        2.0 *\n        sin(pi * x)^3 *\n        sin(pi * z)^2 *\n        cos(pi * t)^3 *\n        cos(pi * y)^3 *\n        cos(pi * z) +\n        1.0 * sin(pi * x)^3 * cos(pi * t)^3 * cos(pi * y)^3 * cos(pi * z)^3 +\n        1.2 *\n        sin(pi * x)^2 *\n        sin(pi * y) *\n        sin(pi * z)^2 *\n        cos(pi * t)^2 *\n        cos(pi * y) -\n        3.6 *\n        sin(pi * x)^2 *\n        sin(pi * y) *\n        cos(pi * t)^2 *\n        cos(pi * y) *\n        cos(pi * z)^2 -\n        3.0 * sin(pi * x)^2 * sin(pi * z)^2 * cos(pi * t)^2 * cos(pi * y)^2 +\n        3.0 *\n        sin(pi * x)^2 *\n        cos(pi * t)^3 *\n        cos(pi * x) *\n        cos(pi * y)^3 *\n        cos(pi * z)^3 +\n        3.0 * sin(pi * x)^2 * cos(pi * t)^2 * cos(pi * y)^2 * cos(pi * z)^2 -\n        0.4 * sin(pi * x) * sin(pi * y) * cos(pi * t) * cos(pi * z) +\n        0.00333333333333333 *\n        pi *\n        sin(pi * x) *\n        sin(pi * y) *\n        cos(pi * t) *\n        cos(pi * z) +\n        6.0 *\n        sin(pi * x) *\n        cos(pi * t)^2 *\n        cos(pi * x) *\n        cos(pi * y)^2 *\n        cos(pi * z)^2 +\n        0.0333333333333333 *\n        pi *\n        sin(pi * x) *\n        cos(pi * t) *\n        cos(pi * y) *\n        cos(pi * z) +\n        0.00333333333333333 *\n        pi *\n        sin(pi * y) *\n        cos(pi * t) *\n        cos(pi * x) *\n        cos(pi * z)\n    )\n@noinline SW_g(t, x, y, z, ::Val{3}) =\n    pi *\n    (\n        -2.0 *\n        sin(pi * t) *\n        sin(pi * x)^2 *\n        cos(pi * t) *\n        cos(pi * y)^2 *\n        cos(pi * z) - 3.0 * sin(pi * t) * sin(pi * x) * cos(pi * y) -\n        3.0 *\n        sin(pi * x)^3 *\n        sin(pi * y) *\n        cos(pi * t)^3 *\n        cos(pi * y)^2 *\n        cos(pi * z)^2 -\n        0.8 * sin(pi * x)^3 * sin(pi * z)^2 * cos(pi * t)^3 * cos(pi * y)^3 +\n        2.8 * sin(pi * x)^3 * cos(pi * t)^3 * cos(pi * y)^3 * cos(pi * z)^2 -\n        6.0 *\n        sin(pi * x)^2 *\n        sin(pi * y) *\n        cos(pi * t)^2 *\n        cos(pi * y) *\n        cos(pi * z) +\n        3.0 *\n        sin(pi * x)^2 *\n        cos(pi * t)^3 *\n        cos(pi * x) *\n        cos(pi * y)^3 *\n        cos(pi * z)^2 +\n        7.2 * sin(pi * x)^2 * cos(pi * t)^2 * cos(pi * y)^2 * cos(pi * z) -\n        0.00333333333333333 * pi * sin(pi * x) * sin(pi * y) * cos(pi * t) +\n        6.0 *\n        sin(pi * x) *\n        cos(pi * t)^2 *\n        cos(pi * x) *\n        cos(pi * y)^2 *\n        cos(pi * z) - 0.4 * sin(pi * x) * cos(pi * t) * cos(pi * y) +\n        0.0333333333333333 * pi * sin(pi * x) * cos(pi * t) * cos(pi * y) +\n        0.00333333333333333 * pi * cos(pi * t) * cos(pi * x) * cos(pi * y)\n    ) *\n    sin(pi * z)\n@noinline SE_g(t, x, y, z, ::Val{3}) =\n    pi * (\n        0.001171875 *\n        (1 - cos(2 * pi * x))^2 *\n        (1 - cos(4 * pi * z)) *\n        (cos(2 * pi * t) + 1)^2 *\n        (cos(2 * pi * y) + 1)^2 -\n        1.0 * sin(pi * t) * sin(pi * x) * cos(pi * y) * cos(pi * z) +\n        0.8 *\n        sin(pi * x)^4 *\n        sin(pi * y) *\n        sin(pi * z)^2 *\n        cos(pi * t)^4 *\n        cos(pi * y)^3 *\n        cos(pi * z)^2 +\n        1.6 *\n        sin(pi * x)^4 *\n        sin(pi * y) *\n        cos(pi * t)^4 *\n        cos(pi * y)^3 *\n        cos(pi * z)^4 +\n        0.2 * sin(pi * x)^4 * sin(pi * z)^4 * cos(pi * t)^4 * cos(pi * y)^4 -\n        0.4 * sin(pi * x)^4 * cos(pi * t)^4 * cos(pi * y)^4 * cos(pi * z)^4 +\n        1.8 *\n        sin(pi * x)^3 *\n        sin(pi * y) *\n        sin(pi * z)^2 *\n        cos(pi * t)^3 *\n        cos(pi * y)^2 *\n        cos(pi * z) +\n        3.6 *\n        sin(pi * x)^3 *\n        sin(pi * y) *\n        cos(pi * t)^3 *\n        cos(pi * y)^2 *\n        cos(pi * z)^3 -\n        0.8 *\n        sin(pi * x)^3 *\n        sin(pi * z)^2 *\n        cos(pi * t)^4 *\n        cos(pi * x) *\n        cos(pi * y)^4 *\n        cos(pi * z)^2 +\n        0.6 *\n        sin(pi * x)^3 *\n        sin(pi * z)^2 *\n        cos(pi * t)^3 *\n        cos(pi * y)^3 *\n        cos(pi * z) -\n        1.6 *\n        sin(pi * x)^3 *\n        cos(pi * t)^4 *\n        cos(pi * x) *\n        cos(pi * y)^4 *\n        cos(pi * z)^4 -\n        1.2 * sin(pi * x)^3 * cos(pi * t)^3 * cos(pi * y)^3 * cos(pi * z)^3 -\n        0.01 *\n        pi *\n        sin(pi * x)^2 *\n        sin(pi * y)^2 *\n        sin(pi * z)^2 *\n        cos(pi * t)^2 -\n        0.0233333333333333 *\n        pi *\n        sin(pi * x)^2 *\n        sin(pi * y)^2 *\n        cos(pi * t)^2 *\n        cos(pi * z)^2 -\n        0.0233333333333333 *\n        pi *\n        sin(pi * x)^2 *\n        sin(pi * y) *\n        sin(pi * z)^2 *\n        cos(pi * t)^2 *\n        cos(pi * y) -\n        2.8 *\n        sin(pi * x)^2 *\n        sin(pi * y) *\n        cos(pi * t)^2 *\n        cos(pi * y) *\n        cos(pi * z)^2 -\n        0.01 *\n        pi *\n        sin(pi * x)^2 *\n        sin(pi * y) *\n        cos(pi * t)^2 *\n        cos(pi * y) *\n        cos(pi * z)^2 -\n        1.8 *\n        sin(pi * x)^2 *\n        sin(pi * z)^2 *\n        cos(pi * t)^3 *\n        cos(pi * x) *\n        cos(pi * y)^3 *\n        cos(pi * z) -\n        1.4 * sin(pi * x)^2 * sin(pi * z)^2 * cos(pi * t)^2 * cos(pi * y)^2 +\n        0.0133333333333333 *\n        pi *\n        sin(pi * x)^2 *\n        sin(pi * z)^2 *\n        cos(pi * t)^2 *\n        cos(pi * y)^2 -\n        3.6 *\n        sin(pi * x)^2 *\n        cos(pi * t)^3 *\n        cos(pi * x) *\n        cos(pi * y)^3 *\n        cos(pi * z)^3 +\n        0.0533333333333333 *\n        pi *\n        sin(pi * x)^2 *\n        cos(pi * t)^2 *\n        cos(pi * y)^2 *\n        cos(pi * z)^2 +\n        1.4 * sin(pi * x)^2 * cos(pi * t)^2 * cos(pi * y)^2 * cos(pi * z)^2 +\n        0.0133333333333333 *\n        pi *\n        sin(pi * x) *\n        sin(pi * y) *\n        cos(pi * t)^2 *\n        cos(pi * x) *\n        cos(pi * y) *\n        cos(pi * z)^2 -\n        140.0 * sin(pi * x) * sin(pi * y) * cos(pi * t) * cos(pi * z) +\n        0.0233333333333333 *\n        pi *\n        sin(pi * x) *\n        sin(pi * z)^2 *\n        cos(pi * t)^2 *\n        cos(pi * x) *\n        cos(pi * y)^2 +\n        0.01 *\n        pi *\n        sin(pi * x) *\n        cos(pi * t)^2 *\n        cos(pi * x) *\n        cos(pi * y)^2 *\n        cos(pi * z)^2 +\n        2.8 *\n        sin(pi * x) *\n        cos(pi * t)^2 *\n        cos(pi * x) *\n        cos(pi * y)^2 *\n        cos(pi * z)^2 +\n        140.0 * sin(pi * x) * cos(pi * t) * cos(pi * y) * cos(pi * z) -\n        0.01 *\n        pi *\n        sin(pi * z)^2 *\n        cos(pi * t)^2 *\n        cos(pi * x)^2 *\n        cos(pi * y)^2 -\n        0.0233333333333333 *\n        pi *\n        cos(pi * t)^2 *\n        cos(pi * x)^2 *\n        cos(pi * y)^2 *\n        cos(pi * z)^2 +\n        140.0 * cos(pi * t) * cos(pi * x) * cos(pi * y) * cos(pi * z)\n    )\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/plotting/bigfileofstuff.jl",
    "content": "using ClimateMachine\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Elements\nimport ClimateMachine.Mesh.Elements: baryweights\nusing ClimateMachine.Mesh.Grids: polynomialorders\nusing GaussQuadrature\nusing Base.Threads\n\n\n# Depending on CliMa version \n# old, should return a tuple of polynomial orders\n# polynomialorders(::DiscontinuousSpectralElementGrid{T, dim, N}) where {T, dim, N} = Tuple([N for i in 1:dim])\n# new, should return a tuple of polynomial orders\n# polynomialorders(::DiscontinuousSpectralElementGrid{T, dim, N}) where {T, dim, N} = N\n\n# utils.jl\n\"\"\"\nfunction cellaverage(Q; M = nothing)\n\n# Description\nCompute the cell-average of Q given the mass matrix M.\nAssumes that Q and M are the same size\n\n# Arguments\n`Q`: MPIStateArrays (array)\n\n# Keyword Arguments\n`M`: Mass Matrix (array)\n\n# Return\nThe cell-average of Q\n\"\"\"\nfunction cellaverage(Q; M = nothing)\n    if M == nothing\n        return nothing\n    end\n    return (sum(M .* Q, dims = 1) ./ sum(M, dims = 1))[:]\nend\n\n\"\"\"\nfunction coordinates(grid::DiscontinuousSpectralElementGrid)\n\n# Description\nGets the (x,y,z) coordinates corresponding to the grid\n\n# Arguments\n- `grid`: DiscontinuousSpectralElementGrid\n\n# Return\n- `x, y, z`: views of x, y, z coordinates\n\"\"\"\nfunction coordinates(grid::DiscontinuousSpectralElementGrid)\n    x = view(grid.vgeo, :, grid.x1id, :)   # x-direction\t\n    y = view(grid.vgeo, :, grid.x2id, :)   # y-direction\t\n    z = view(grid.vgeo, :, grid.x3id, :)   # z-direction\n    return x, y, z\nend\n\n\"\"\"\nfunction cellcenters(Q; M = nothing)\n\n# Description\nGet the cell-centers of every element in the grid\n\n# Arguments\n- `grid`: DiscontinuousSpectralElementGrid\n\n# Return\n- Tuple of cell-centers\n\"\"\"\nfunction cellcenters(grid::DiscontinuousSpectralElementGrid)\n    x, y, z = coordinates(grid)\n    M = view(grid.vgeo, :, grid.Mid, :)  # mass matrix\n    xC = cellaverage(x, M = M)\n    yC = cellaverage(y, M = M)\n    zC = cellaverage(z, M = M)\n    return xC[:], yC[:], zC[:]\nend\n\n\"\"\"\nfunction massmatrix(grid; M = nothing)\n\n# Description\nGet the mass matrix of the grid\n\n# Arguments\n- `grid`: DiscontinuousSpectralElementGrid\n\n# Return\n- Tuple of cell-centers\n\"\"\"\nfunction massmatrix(grid)\n    return view(grid.vgeo, :, grid.Mid, :)\nend\n\n# find_element.jl\n# 3D version\nfunction findelement(xC, yC, zC, location, p, lin)\n    ex, ey, ez = size(lin)\n    # i \n    currentmin = ones(1)\n    minind = ones(Int64, 1)\n    currentmin[1] = abs.(xC[p[lin[1, 1, 1]]] .- location[1])\n    for i in 2:ex\n        current = abs.(xC[p[lin[i, 1, 1]]] .- location[1])\n        if current < currentmin[1]\n            currentmin[1] = current\n            minind[1] = i\n        end\n    end\n    i = minind[1]\n    # j \n    currentmin[1] = abs.(yC[p[lin[1, 1, 1]]] .- location[2])\n    minind[1] = 1\n    for i in 2:ey\n        current = abs.(yC[p[lin[1, i, 1]]] .- location[2])\n        if current < currentmin[1]\n            currentmin[1] = current\n            minind[1] = i\n        end\n    end\n    j = minind[1]\n    # k \n    currentmin[1] = abs.(zC[p[lin[1, 1, 1]]] .- location[3])\n    minind[1] = 1\n    for i in 2:ez\n        current = abs.(zC[p[lin[1, 1, i]]] .- location[3])\n        if current < currentmin[1]\n            currentmin[1] = current\n            minind[1] = i\n        end\n    end\n    k = minind[1]\n    return p[lin[i, j, k]]\nend\n\n# 2D version\nfunction findelement(xC, yC, location, p, lin)\n    ex, ey = size(lin)\n    # i \n    currentmin = ones(1)\n    minind = ones(Int64, 1)\n    currentmin[1] = abs.(xC[p[lin[1, 1]]] .- location[1])\n    for i in 2:ex\n        current = abs.(xC[p[lin[i, 1]]] .- location[1])\n        if current < currentmin[1]\n            currentmin[1] = current\n            minind[1] = i\n        end\n    end\n    i = minind[1]\n    # j \n    currentmin[1] = abs.(yC[p[lin[1, 1]]] .- location[2])\n    minind[1] = 1\n    for i in 2:ey\n        current = abs.(yC[p[lin[1, i]]] .- location[2])\n        if current < currentmin[1]\n            currentmin[1] = current\n            minind[1] = i\n        end\n    end\n    j = minind[1]\n    return p[lin[i, j]]\nend\n\n# gridhelper.jl\n\nstruct InterpolationHelper{S, T}\n    points::S\n    quadrature::S\n    interpolation::S\n    cartesianindex::T\nend\n\nfunction InterpolationHelper(g::DiscontinuousSpectralElementGrid)\n    porders = polynomialorders(g)\n    if length(porders) == 3\n        npx, npy, npz = porders\n        rx, wx = GaussQuadrature.legendre(npx + 1, both)\n        ωx = baryweights(rx)\n        ry, wy = GaussQuadrature.legendre(npy + 1, both)\n        ωy = baryweights(ry)\n        rz, wz = GaussQuadrature.legendre(npz + 1, both)\n        ωz = baryweights(rz)\n        linlocal = reshape(\n            collect(1:((npx + 1) * (npy + 1) * (npz + 1))),\n            (npx + 1, npy + 1, npz + 1),\n        )\n        return InterpolationHelper(\n            (rx, ry, rz),\n            (wx, wy, wz),\n            (ωx, ωy, ωz),\n            linlocal,\n        )\n    elseif length(porders) == 2\n        npx, npy = porders\n        rx, wx = GaussQuadrature.legendre(npx + 1, both)\n        ωx = baryweights(rx)\n        ry, wy = GaussQuadrature.legendre(npy + 1, both)\n        ωy = baryweights(ry)\n        linlocal =\n            reshape(collect(1:((npx + 1) * (npy + 1))), (npx + 1, npy + 1))\n        return InterpolationHelper((rx, ry), (wx, wy), (ωx, ωy), linlocal)\n    else\n        println(\"Not supported\")\n        return nothing\n    end\n    return nothing\nend\n\nstruct ElementHelper{S, T, U, Q, V, W}\n    cellcenters::S\n    coordinates::T\n    cartesiansizes::U\n    polynomialorders::Q\n    permutation::V\n    cartesianindex::W\nend\n\naddup(xC, tol) = sum(abs.(xC[1] .- xC) .≤ tol)\n\n# only valid for cartesian domains\nfunction ElementHelper(g::DiscontinuousSpectralElementGrid)\n    porders = polynomialorders(g)\n    x, y, z = coordinates(g)\n    xC, yC, zC = cellcenters(g)\n    ne = size(x)[2]\n    ex = round(Int64, ne / addup(xC, 10^4 * eps(maximum(abs.(x)))))\n    ey = round(Int64, ne / addup(yC, 10^4 * eps(maximum(abs.(y)))))\n    ez = round(Int64, ne / addup(zC, 10^4 * eps(maximum(abs.(z)))))\n\n    check = ne == ex * ey * ez\n    check ? true : error(\"improper counting\")\n    p = getperm(xC, yC, zC, ex, ey, ez)\n    # should use dispatch ...\n    if length(porders) == 3\n        npx, npy, npz = porders\n        lin = reshape(collect(1:length(xC)), (ex, ey, ez))\n        return ElementHelper(\n            (xC, yC, zC),\n            (x, y, z),\n            (ex, ey, ez),\n            porders,\n            p,\n            lin,\n        )\n    elseif length(porders) == 2\n        npx, npy = porders\n        lin = reshape(collect(1:length(xC)), (ex, ey))\n        check = ne == ex * ey\n        check ? true : error(\"improper counting\")\n        return ElementHelper((xC, yC), (x, y), (ex, ey), porders, p, lin)\n    else\n        println(\"no constructor for polynomial order = \", porders)\n        return nothing\n    end\n    return nothing\nend\n\nstruct GridHelper{S, T, V}\n    interpolation::S\n    element::T\n    grid::V\nend\n\nfunction GridHelper(g::DiscontinuousSpectralElementGrid)\n    return GridHelper(InterpolationHelper(g), ElementHelper(g), g)\nend\n\nfunction getvalue(f, location, gridhelper::GridHelper)\n    ih = gridhelper.interpolation\n    eh = gridhelper.element\n    porders = gridhelper.element.polynomialorders\n    if length(porders) == 3\n        npx, npy, npz = gridhelper.element.polynomialorders\n        fl = reshape(f, (npx + 1, npy + 1, npz + 1, prod(eh.cartesiansizes)))\n        ip = getvalue(\n            fl,\n            eh.cellcenters...,\n            location,\n            eh.permutation,\n            eh.cartesianindex,\n            ih.cartesianindex,\n            eh.coordinates...,\n            ih.points...,\n            ih.interpolation...,\n        )\n        return ip\n    elseif length(porders) == 2\n        npx, npy = gridhelper.element.polynomialorders\n        fl = reshape(f, (npx + 1, npy + 1, prod(eh.cartesiansizes)))\n        ip = getvalue(\n            fl,\n            eh.cellcenters...,\n            location,\n            eh.permutation,\n            eh.cartesianindex,\n            ih.cartesianindex,\n            eh.coordinates...,\n            ih.points...,\n            ih.interpolation...,\n        )\n        return ip\n    end\n    return nothing\nend\n\n# lagrange_interpolation.jl\nfunction checkgl(x, rx)\n    for i in eachindex(rx)\n        if abs(x - rx[i]) ≤ eps(rx[i])\n            return i\n        end\n    end\n    return 0\nend\n\nfunction lagrange_eval(f, newx, newy, newz, rx, ry, rz, ωx, ωy, ωz)\n    icheck = checkgl(newx, rx)\n    jcheck = checkgl(newy, ry)\n    kcheck = checkgl(newz, rz)\n    numerator = zeros(1)\n    denominator = zeros(1)\n    for k in eachindex(rz)\n        if kcheck == 0\n            Δz = (newz .- rz[k])\n            polez = ωz[k] ./ Δz\n            kk = k\n        else\n            polez = 1.0\n            k = eachindex(rz)[end]\n            kk = kcheck\n        end\n        for j in eachindex(ry)\n            if jcheck == 0\n                Δy = (newy .- ry[j])\n                poley = ωy[j] ./ Δy\n                jj = j\n            else\n                poley = 1.0\n                j = eachindex(ry)[end]\n                jj = jcheck\n            end\n            for i in eachindex(rx)\n                if icheck == 0\n                    Δx = (newx .- rx[i])\n                    polex = ωx[i] ./ Δx\n                    ii = i\n                else\n                    polex = 1.0\n                    i = eachindex(rx)[end]\n                    ii = icheck\n                end\n                numerator[1] += f[ii, jj, kk] * polex * poley * polez\n                denominator[1] += polex * poley * polez\n            end\n        end\n    end\n    return numerator[1] / denominator[1]\nend\n\nfunction lagrange_eval(f, newx, newy, rx, ry, ωx, ωy)\n    icheck = checkgl(newx, rx)\n    jcheck = checkgl(newy, ry)\n    numerator = zeros(1)\n    denominator = zeros(1)\n    for j in eachindex(ry)\n        if jcheck == 0\n            Δy = (newy .- ry[j])\n            poley = ωy[j] ./ Δy\n            jj = j\n        else\n            poley = 1.0\n            j = eachindex(ry)[end]\n            jj = jcheck\n        end\n        for i in eachindex(rx)\n            if icheck == 0\n                Δx = (newx .- rx[i])\n                polex = ωx[i] ./ Δx\n                ii = i\n            else\n                polex = 1.0\n                i = eachindex(rx)[end]\n                ii = icheck\n            end\n            numerator[1] += f[ii, jj] * polex * poley\n            denominator[1] += polex * poley\n        end\n    end\n    return numerator[1] / denominator[1]\nend\n\n\nfunction lagrange_eval_nocheck(f, newx, newy, newz, rx, ry, rz, ωx, ωy, ωz)\n    numerator = zeros(1)\n    denominator = zeros(1)\n    for k in eachindex(rz)\n        Δz = (newz .- rz[k])\n        polez = ωz[k] ./ Δz\n        for j in eachindex(ry)\n            Δy = (newy .- ry[j])\n            poley = ωy[j] ./ Δy\n            for i in eachindex(rx)\n                Δx = (newx .- rx[i])\n                polex = ωx[i] ./ Δx\n                numerator[1] += f[i, j, k] * polex * poley * polez\n                denominator[1] += polex * poley * polez\n            end\n        end\n    end\n    return numerator[1] / denominator[1]\nend\n\nfunction lagrange_eval_nocheck(f, newx, newy, rx, ry, ωx, ωy)\n    numerator = zeros(1)\n    denominator = zeros(1)\n    for j in eachindex(ry)\n        Δy = (newy .- ry[j])\n        poley = ωy[j] ./ Δy\n        for i in eachindex(rx)\n            Δx = (newx .- rx[i])\n            polex = ωx[i] ./ Δx\n            numerator[1] += f[i, j] * polex * poley\n            denominator[1] += polex * poley\n        end\n    end\n    return numerator[1] / denominator[1]\nend\n\nfunction lagrange_eval_nocheck(f, newx, rx, ωx)\n    numerator = zeros(1)\n    denominator = zeros(1)\n    for i in eachindex(rx)\n        Δx = (newx .- rx[i])\n        polex = ωx[i] ./ Δx\n        numerator[1] += f[i] * polex * poley\n        denominator[1] += polex * poley\n    end\n    return numerator[1] / denominator[1]\nend\n\n# 3D, only valid for rectangles\nfunction getvalue(\n    fl,\n    xC,\n    yC,\n    zC,\n    location,\n    p,\n    lin,\n    linlocal,\n    x,\n    y,\n    z,\n    rx,\n    ry,\n    rz,\n    ωx,\n    ωy,\n    ωz,\n)\n    e = findelement(xC, yC, zC, location, p, lin)\n    # need bounds to rescale, only value for cartesian\n\n    xmax = x[linlocal[length(rx), 1, 1], e]\n    xmin = x[linlocal[1, 1, 1], e]\n    ymax = y[linlocal[1, length(ry), 1], e]\n    ymin = y[linlocal[1, 1, 1], e]\n    zmax = z[linlocal[1, 1, length(rz)], e]\n    zmin = z[linlocal[1, 1, 1], e]\n\n    # rescale new point to [-1,1]³\n    newx = 2 * (location[1] - xmin) / (xmax - xmin) - 1\n    newy = 2 * (location[2] - ymin) / (ymax - ymin) - 1\n    newz = 2 * (location[3] - zmin) / (zmax - zmin) - 1\n\n    return lagrange_eval(\n        view(fl, :, :, :, e),\n        newx,\n        newy,\n        newz,\n        rx,\n        ry,\n        rz,\n        ωx,\n        ωy,\n        ωz,\n    )\nend\n\n# 2D\nfunction getvalue(fl, xC, yC, location, p, lin, linlocal, x, y, rx, ry, ωx, ωy)\n    e = findelement(xC, yC, location, p, lin)\n    # need bounds to rescale\n    xmax = x[linlocal[length(rx), 1, 1], e]\n    xmin = x[linlocal[1, 1, 1], e]\n    ymax = y[linlocal[1, length(ry), 1], e]\n    ymin = y[linlocal[1, 1, 1], e]\n\n    # rescale new point to [-1,1]²\n    newx = 2 * (location[1] - xmin) / (xmax - xmin) - 1\n    newy = 2 * (location[2] - ymin) / (ymax - ymin) - 1\n\n    return lagrange_eval(view(fl, :, :, e), newx, newy, rx, ry, ωx, ωy)\nend\n\n# permutations.jl\nfunction getperm(xC, yC, zC, ex, ey, ez)\n    pz = sortperm(zC)\n    tmpY = reshape(yC[pz], (ex * ey, ez))\n    tmp_py = [sortperm(tmpY[:, i]) for i in 1:ez]\n    py = zeros(Int64, length(pz))\n    for i in eachindex(tmp_py)\n        n = length(tmp_py[i])\n        ii = (i - 1) * n + 1\n        py[ii:(ii + n - 1)] .= tmp_py[i] .+ ii .- 1\n    end\n    tmpX = reshape(xC[pz][py], (ex, ey * ez))\n    tmp_px = [sortperm(tmpX[:, i]) for i in 1:(ey * ez)]\n    px = zeros(Int64, length(pz))\n    for i in eachindex(tmp_px)\n        n = length(tmp_px[i])\n        ii = (i - 1) * n + 1\n        px[ii:(ii + n - 1)] .= tmp_px[i] .+ ii .- 1\n    end\n    p = [pz[py[px[i]]] for i in eachindex(px)]\n    return p\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/plotting/plot_output.jl",
    "content": "using JLD2\nusing GLMakie\n\nfilename = \"/Users/ballen/Projects/Clima/CLIMA/output/vtk_bickley_2D/bickley_jet.jld2\"\nDOF = 32\n\nf = jldopen(filename, \"r+\")\ninclude(\"bigfileofstuff.jl\")\ninclude(\"vizinanigans.jl\")\ninclude(\"ScalarFields.jl\")\n\ndg_grid = f[\"grid\"]\ngridhelper = GridHelper(dg_grid)\nx, y, z = coordinates(dg_grid)\nxC, yC, zC = cellcenters(dg_grid)\n\nϕ = ScalarField(copy(x), gridhelper)\n\nnewx = range(-2π, 2π, length = DOF)\nnewy = range(-2π, 2π, length = DOF)\nnorm(f[\"100\"])\n\n##\n\nQ = f[\"0\"]\ndof, nstates, nelems = size(Q)\n\nstates = Array{Float64, 3}[]\nstatenames = [\"ρ\", \"ρu\", \"ρv\", \"ρθ\"]\n\nfor i in 1:nstates\n    state = zeros(length(newx), length(newy), 101)\n    push!(states, state)\nend\n\nfor i in 0:100\n    println(\"interpolating step \" * string(i))\n    Qⁱ = f[string(i)]\n\n    for j in 1:nstates\n        ϕ .= Qⁱ[:, j, :]\n        states[j][:, :, i + 1] .= ϕ(newx, newy, threads = true)\n    end\nend\n\n# f[\"states\"] = states\nclose(f)\n\n##\n\nscene = volumeslice(states, statenames = statenames)\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/plotting/vizinanigans.jl",
    "content": "using GLMakie, Statistics, Printf\n\n\"\"\"\nvisualize(states::AbstractArray; statenames = string.(1:length(states)), quantiles = (0.1, 0.99), aspect = (1,1,1), resolution = (1920, 1080), statistics = false, title = \"Field = \")\n# Description \nVisualize 3D states \n# Arguments\n- `states`: Array{Array{Float64,3},1}. An array of arrays containing different fields\n# Keyword Arguments\n- `statenames`: Array{String,1}. An array of stringnames\n- `aspect`: Tuple{Int64,Int64,Float64}. Determines aspect ratio of box for volumes\n- `resolution`: Resolution of preliminary makie window\n- `statistics`: boolean. toggle for displaying statistics \n# Return\n- `scene`: Scene. A preliminary scene object for manipulation\n\"\"\"\nfunction visualize(\n    states::AbstractArray;\n    statenames = string.(1:length(states)),\n    units = [\"\" for i in eachindex(states)],\n    aspect = (1, 1, 1),\n    resolution = (1920, 1080),\n    statistics = false,\n    title = \"Field = \",\n    bins = 300,\n)\n    # Create scene\n    scene, layout = layoutscene(resolution = resolution)\n    lscene = layout[2:4, 2:4] = LScene(scene)\n    width = round(Int, resolution[1] / 4) # make menu 1/4 of preliminary resolution\n\n    # Create choices and nodes\n    stateindex = collect(1:length(states))\n    statenode = Node(stateindex[1])\n\n    colorchoices = [:balance, :thermal, :dense, :deep, :curl, :thermometer]\n    colornode = Node(colorchoices[1])\n\n    if statistics\n        llscene =\n            layout[4, 1] = Axis(\n                scene,\n                xlabel = @lift(statenames[$statenode] * units[$statenode]),\n                xlabelcolor = :black,\n                ylabel = \"pdf\",\n                ylabelcolor = :black,\n                xlabelsize = 40,\n                ylabelsize = 40,\n                xticklabelsize = 25,\n                yticklabelsize = 25,\n                xtickcolor = :black,\n                ytickcolor = :black,\n                xticklabelcolor = :black,\n                yticklabelcolor = :black,\n            )\n        layout[3, 1] = Label(scene, \"Statistics\", width = width, textsize = 50)\n    end\n\n    # x,y,z are for determining the aspect ratio of the box\n    if (typeof(aspect) <: Tuple) & (length(aspect) == 3)\n        x, y, z = aspect\n    else\n        x, y, z = size(states[1])\n    end\n\n    # Clim sliders\n    upperclim_slider =\n        Slider(scene, range = range(0, 1, length = 101), startvalue = 0.99)\n    upperclim_node = upperclim_slider.value\n    lowerclim_slider =\n        Slider(scene, range = range(0, 1, length = 101), startvalue = 0.01)\n    lowerclim_node = lowerclim_slider.value\n\n    # Lift Nodes\n    state = @lift(states[$statenode])\n    statename = @lift(statenames[$statenode])\n    clims = @lift((\n        quantile($state[:], $lowerclim_node),\n        quantile($state[:], $upperclim_node),\n    ))\n    cmap_rgb = @lift(to_colormap($colornode))\n    titlename = @lift(title * $statename) # use padding and appropriate centering\n\n    # Statistics\n    if statistics\n        histogram_node = @lift(histogram($state, bins = bins))\n        xs = @lift($histogram_node[1])\n        ys = @lift($histogram_node[2])\n        pdf = GLMakie.AbstractPlotting.barplot!(\n            llscene,\n            xs,\n            ys,\n            color = :red,\n            strokecolor = :red,\n            strokewidth = 1,\n        )\n        @lift(GLMakie.AbstractPlotting.xlims!(llscene, extrema($state)))\n        @lift(GLMakie.AbstractPlotting.ylims!(\n            llscene,\n            extrema($histogram_node[2]),\n        ))\n        vlines!(\n            llscene,\n            @lift($clims[1]),\n            color = :black,\n            linewidth = width / 100,\n        )\n        vlines!(\n            llscene,\n            @lift($clims[2]),\n            color = :black,\n            linewidth = width / 100,\n        )\n    end\n\n    # Volume Plot \n    volume!(\n        lscene,\n        0..x,\n        0..y,\n        0..z,\n        state,\n        camera = cam3d!,\n        colormap = cmap_rgb,\n        colorrange = clims,\n    )\n    # Camera\n    cam = cameracontrols(scene.children[1])\n    eyeposition = Float32[2, 2, 1.3]\n    lookat = Float32[0.82, 0.82, 0.1]\n    # Title\n    supertitle =\n        layout[1, 2:4] = Label(scene, titlename, textsize = 50, color = :black)\n\n\n    # Menus\n    statemenu = Menu(scene, options = zip(statenames, stateindex))\n    on(statemenu.selection) do s\n        statenode[] = s\n    end\n\n    colormenu = Menu(scene, options = zip(colorchoices, colorchoices))\n    on(colormenu.selection) do s\n        colornode[] = s\n    end\n    lowerclim_string = @lift(\n        \"lower clim quantile = \" *\n        @sprintf(\"%0.2f\", $lowerclim_node) *\n        \", value = \" *\n        @sprintf(\"%0.1e\", $clims[1])\n    )\n    upperclim_string = @lift(\n        \"upper clim quantile = \" *\n        @sprintf(\"%0.2f\", $upperclim_node) *\n        \", value = \" *\n        @sprintf(\"%0.1e\", $clims[2])\n    )\n    # depends on makie version, vbox for old, vgrid for new\n    layout[2, 1] = vgrid!(\n        Label(scene, \"State\", width = nothing),\n        statemenu,\n        Label(scene, \"Color\", width = nothing),\n        colormenu,\n        Label(scene, lowerclim_string, width = nothing),\n        lowerclim_slider,\n        Label(scene, upperclim_string, width = nothing),\n        upperclim_slider,\n    )\n    layout[1, 1] = Label(scene, \"Menu\", width = width, textsize = 50)\n\n    # Modify Axis\n    axis = scene.children[1][OldAxis]\n    # axis[:names][:axisnames] = (\"↓ Zonal [m] \", \"Meriodonal [m]↓ \", \"Depth [m]↓ \")\n    axis[:names][:axisnames] = (\"↓\", \"↓ \", \"↓ \")\n    axis[:names][:align] =\n        ((:left, :center), (:right, :center), (:right, :center))\n    # need to adjust size of ticks first and then size of axis names\n    axis[:names][:textsize] = (50.0, 50.0, 50.0)\n    axis[:ticks][:textsize] = (00.0, 00.0, 00.0)\n    # axis[:ticks][:ranges_labels].val # current axis labels\n    xticks = collect(range(-0, aspect[1], length = 2))\n    yticks = collect(range(-0, aspect[2], length = 6))\n    zticks = collect(range(-0, aspect[3], length = 2))\n    ticks = (xticks, yticks, zticks)\n    axis[:ticks][:ranges] = ticks\n    xtickslabels = [@sprintf(\"%0.1f\", (xtick)) for xtick in xticks]\n    xtickslabels[end] = \"1e6\"\n    ytickslabels = [\"\", \"south\", \"\", \"\", \"north\", \"\"]\n    ztickslabels = [@sprintf(\"%0.1f\", (xtick)) for xtick in xticks]\n    labels = (xtickslabels, ytickslabels, ztickslabels)\n    axis[:ticks][:labels] = labels\n\n    display(scene)\n    # Change the default camera position after the fact\n    # note that these change dynamically as the plot is manipulated\n    return scene\nend\n\n\n\"\"\"\nhistogram(array; bins = 100)\n# Description\nreturn arrays for plotting histogram\n\"\"\"\nfunction histogram(\n    array;\n    bins = minimum([100, length(array)]),\n    normalize = true,\n)\n    tmp = zeros(bins)\n    down, up = extrema(array)\n    down, up = down == up ? (down - 1, up + 1) : (down, up) # edge case\n    bucket = collect(range(down, up, length = bins + 1))\n    normalization = normalize ? length(array) : 1\n    for i in eachindex(array)\n        # normalize then multiply by bins\n        val = (array[i] - down) / (up - down) * bins\n        ind = ceil(Int, val)\n        # handle edge cases\n        ind = maximum([ind, 1])\n        ind = minimum([ind, bins])\n        tmp[ind] += 1 / normalization\n    end\n    return (bucket[2:end] + bucket[1:(end - 1)]) .* 0.5, tmp\nend\n\n# 2D visualization\nfunction visualize(\n    states::Array{Array{S, 2}, 1};\n    statenames = string.(1:length(states)),\n    units = [\"\" for i in eachindex(states)],\n    aspect = (1, 1, 1),\n    resolution = (2412, 1158),\n    title = \"Zonal and Temporal Average of \",\n    xlims = (0, 1),\n    ylims = (0, 1),\n    bins = 300,\n) where {S}\n    # Create scene\n    scene, layout = layoutscene(resolution = resolution)\n    lscene =\n        layout[2:4, 2:4] = Axis(\n            scene,\n            xlabel = \"South to North [m]\",\n            xlabelcolor = :black,\n            ylabel = \"Depth [m]\",\n            ylabelcolor = :black,\n            xlabelsize = 40,\n            ylabelsize = 40,\n            xticklabelsize = 25,\n            yticklabelsize = 25,\n            xtickcolor = :black,\n            ytickcolor = :black,\n            xticklabelcolor = :black,\n            yticklabelcolor = :black,\n            titlesize = 50,\n        )\n    width = round(Int, resolution[1] / 4) # make menu 1/4 of preliminary resolution\n\n    # Create choices and nodes\n    stateindex = collect(1:length(states))\n    statenode = Node(stateindex[1])\n\n    colorchoices = [:balance, :thermal, :dense, :deep, :curl, :thermometer]\n    colornode = Node(colorchoices[1])\n\n    interpolationlabels = [\"contour\", \"heatmap\"]\n    interpolationchoices = [true, false]\n    interpolationnode = Node(interpolationchoices[1])\n\n    # Statistics\n    llscene =\n        layout[4, 1] = Axis(\n            scene,\n            xlabel = @lift(statenames[$statenode] * \" \" * units[$statenode]),\n            xlabelcolor = :black,\n            ylabel = \"pdf\",\n            ylabelcolor = :black,\n            xlabelsize = 40,\n            ylabelsize = 40,\n            xticklabelsize = 25,\n            yticklabelsize = 25,\n            xtickcolor = :black,\n            ytickcolor = :black,\n            xticklabelcolor = :black,\n            yticklabelcolor = :black,\n        )\n    layout[3, 1] = Label(scene, \"Statistics\", width = width, textsize = 50)\n\n    # Clim sliders\n    upperclim_slider =\n        Slider(scene, range = range(0, 1, length = 101), startvalue = 0.99)\n    upperclim_node = upperclim_slider.value\n    lowerclim_slider =\n        Slider(scene, range = range(0, 1, length = 101), startvalue = 0.01)\n    lowerclim_node = lowerclim_slider.value\n\n    #ylims = @lift(range($lowerval, $upperval, length = $))\n    # Lift Nodes\n    state = @lift(states[$statenode])\n    statename = @lift(statenames[$statenode])\n    unit = @lift(units[$statenode])\n    oclims = @lift((\n        quantile($state[:], $lowerclim_node),\n        quantile($state[:], $upperclim_node),\n    ))\n    cmap_rgb = @lift(\n        $oclims[1] < $oclims[2] ? to_colormap($colornode) :\n        reverse(to_colormap($colornode))\n    )\n    clims = @lift(\n        $oclims[1] != $oclims[2] ? (minimum($oclims), maximum($oclims)) :\n        (minimum($oclims) - 1, maximum($oclims) + 1)\n    )\n    xlims = Array(range(xlims[1], xlims[2], length = 4)) #collect(range(xlims[1], xlims[2], length = size(states[1])[1]))\n    ylims = Array(range(ylims[1], ylims[2], length = 4)) #@lift(collect(range($lowerval], $upperval, length = size($state)[2])))\n    # newrange = @lift(range($lowerval, $upperval, length = 4))\n    # lscene.yticks = @lift(Array($newrange))\n    titlename = @lift(title * $statename) # use padding and appropriate centering\n    layout[1, 2:4] = Label(scene, titlename, textsize = 50)\n    # heatmap \n    heatmap1 = heatmap!(\n        lscene,\n        xlims,\n        ylims,\n        state,\n        interpolate = interpolationnode,\n        colormap = cmap_rgb,\n        colorrange = clims,\n    )\n\n\n    # statistics\n    histogram_node = @lift(histogram($state, bins = bins))\n    xs = @lift($histogram_node[1])\n    ys = @lift($histogram_node[2])\n    pdf = GLMakie.AbstractPlotting.barplot!(\n        llscene,\n        xs,\n        ys,\n        color = :red,\n        strokecolor = :red,\n        strokewidth = 1,\n    )\n    @lift(GLMakie.AbstractPlotting.xlims!(llscene, extrema($state)))\n    @lift(GLMakie.AbstractPlotting.ylims!(llscene, extrema($histogram_node[2])))\n    vlines!(llscene, @lift($clims[1]), color = :black, linewidth = width / 100)\n    vlines!(llscene, @lift($clims[2]), color = :black, linewidth = width / 100)\n\n    # Menus\n    statemenu = Menu(scene, options = zip(statenames, stateindex))\n    on(statemenu.selection) do s\n        statenode[] = s\n    end\n\n    colormenu = Menu(scene, options = zip(colorchoices, colorchoices))\n    on(colormenu.selection) do s\n        colornode[] = s\n    end\n\n    interpolationmenu =\n        Menu(scene, options = zip(interpolationlabels, interpolationchoices))\n    on(interpolationmenu.selection) do s\n        interpolationnode[] = s\n        heatmap1 = heatmap!(\n            lscene,\n            xlims,\n            ylims,\n            state,\n            interpolate = s,\n            colormap = cmap_rgb,\n            colorrange = clims,\n        )\n    end\n\n    newlabel = @lift($statename * \" \" * $unit)\n    cbar = Colorbar(scene, heatmap1, label = newlabel)\n    cbar.width = Relative(1 / 3)\n    cbar.height = Relative(5 / 6)\n    cbar.halign = :center\n    cbar.flipaxisposition = true\n    # cbar.labelpadding = -350\n    cbar.labelsize = 50\n\n    lowerclim_string = @lift(\n        \"clim quantile = \" *\n        @sprintf(\"%0.2f\", $lowerclim_node) *\n        \", value = \" *\n        @sprintf(\"%0.1e\", $clims[1])\n    )\n    upperclim_string = @lift(\n        \"clim quantile = \" *\n        @sprintf(\"%0.2f\", $upperclim_node) *\n        \", value = \" *\n        @sprintf(\"%0.1e\", $clims[2])\n    )\n\n    # depends on makie version, vbox for old, vgrid for new\n    layout[2, 1] = vgrid!(\n        Label(scene, \"State\", width = nothing),\n        statemenu,\n        Label(\n            scene,\n            \"plotting options\",\n            width = width,\n            textsize = 30,\n            padding = (0, 0, 10, 0),\n        ),\n        interpolationmenu,\n        Label(scene, \"Color\", width = nothing),\n        colormenu,\n        Label(scene, lowerclim_string, width = nothing),\n        lowerclim_slider,\n        Label(scene, upperclim_string, width = nothing),\n        upperclim_slider,\n    )\n\n    layout[2:4, 5] = vgrid!(\n        Label(\n            scene,\n            \"Color Bar\",\n            width = width / 2,\n            textsize = 50,\n            padding = (25, 0, 0, 00),\n        ),\n        cbar,\n    )\n    layout[1, 1] = Label(scene, \"Menu\", width = width, textsize = 50)\n    display(scene)\n    return scene\nend\n\nfunction volumeslice(\n    states::AbstractArray;\n    statenames = string.(1:length(states)),\n    units = [\"\" for i in eachindex(states)],\n    aspect = (1, 1, 32 / 192),\n    resolution = (2678, 1030),\n    statistics = false,\n    title = \"Volume plot of \",\n    bins = 300,\n    statlabelsize = (20, 20),\n)\n    scene, layout = layoutscene(resolution = resolution)\n    volumescene = layout[2:4, 2:4] = LScene(scene)\n    menuwidth = round(Int, 350)\n    layout[1, 1] = Label(scene, \"Menu\", width = menuwidth, textsize = 50)\n\n    slice_slider =\n        Slider(scene, range = range(0, 1, length = 101), startvalue = 0.0)\n    slice_node = slice_slider.value\n\n    directionindex = [1, 2, 3]\n    directionnames = [\"x-slice\", \"y-slice\", \"z-slice\"]\n    directionnode = Node(directionindex[1])\n\n    stateindex = collect(1:length(states))\n    statenode = Node(stateindex[1])\n\n    layout[1, 2:4] =\n        Label(scene, @lift(title * statenames[$statenode]), textsize = 50)\n\n    colorchoices = [:balance, :thermal, :dense, :deep, :curl, :thermometer]\n    colornode = Node(colorchoices[1])\n\n    state = @lift(states[$statenode])\n    statename = @lift(statenames[$statenode])\n    unit = @lift(units[$statenode])\n    nx = @lift(size($state)[1])\n    ny = @lift(size($state)[2])\n    nz = @lift(size($state)[3])\n    nr = @lift([$nx, $ny, $nz])\n\n    nslider = 100\n    xrange = range(0.00, aspect[1], length = nslider)\n    yrange = range(0.00, aspect[2], length = nslider)\n    zrange = range(0.00, aspect[3], length = nslider)\n    constx = collect(reshape(xrange, (nslider, 1, 1)))\n    consty = collect(reshape(yrange, (1, nslider, 1)))\n    constz = collect(reshape(zrange, (1, 1, nslider)))\n    matx = zeros(nslider, nslider, nslider)\n    maty = zeros(nslider, nslider, nslider)\n    matz = zeros(nslider, nslider, nslider)\n    matx .= constx\n    maty .= consty\n    matz .= constz\n    sliceconst = [matx, maty, matz]\n    planeslice = @lift(sliceconst[$directionnode])\n\n    upperclim_slider =\n        Slider(scene, range = range(0, 1, length = 101), startvalue = 0.99)\n    upperclim_node = upperclim_slider.value\n    lowerclim_slider =\n        Slider(scene, range = range(0, 1, length = 101), startvalue = 0.01)\n    lowerclim_node = lowerclim_slider.value\n\n    clims = @lift((\n        quantile($state[:], $lowerclim_node),\n        quantile($state[:], $upperclim_node),\n    ))\n\n    volume!(\n        volumescene,\n        0..aspect[1],\n        0..aspect[2],\n        0..aspect[3],\n        state,\n        overdraw = false,\n        colorrange = clims,\n        colormap = @lift(to_colormap($colornode)),\n    )\n\n\n    alpha_slider =\n        Slider(scene, range = range(0, 1, length = 101), startvalue = 0.5)\n    alphanode = alpha_slider.value\n\n    slicecolormap = @lift(cgrad(:viridis, alpha = $alphanode))\n    v = volume!(\n        volumescene,\n        0..aspect[1],\n        0..aspect[2],\n        0..aspect[3],\n        planeslice,\n        algorithm = :iso,\n        isorange = 0.005,\n        isovalue = @lift($slice_node * aspect[$directionnode]),\n        transparency = true,\n        overdraw = false,\n        visible = true,\n        colormap = slicecolormap,\n        colorrange = [-1, 0],\n    )\n\n    # Volume histogram\n\n    layout[3, 1] = Label(scene, \"Statistics\", textsize = 50)\n    hscene =\n        layout[4, 1] = Axis(\n            scene,\n            xlabel = @lift(statenames[$statenode] * \" \" * units[$statenode]),\n            xlabelcolor = :black,\n            ylabel = \"pdf\",\n            ylabelcolor = :black,\n            xlabelsize = 40,\n            ylabelsize = 40,\n            xticklabelsize = statlabelsize[1],\n            yticklabelsize = statlabelsize[2],\n            xtickcolor = :black,\n            ytickcolor = :black,\n            xticklabelcolor = :black,\n            yticklabelcolor = :black,\n        )\n\n    histogram_node = @lift(histogram($state, bins = bins))\n    vxs = @lift($histogram_node[1])\n    vys = @lift($histogram_node[2])\n    pdf = GLMakie.AbstractPlotting.barplot!(\n        hscene,\n        vxs,\n        vys,\n        color = :red,\n        strokecolor = :red,\n        strokewidth = 1,\n    )\n\n    @lift(GLMakie.AbstractPlotting.xlims!(hscene, extrema($vxs)))\n    @lift(GLMakie.AbstractPlotting.ylims!(hscene, extrema($vys)))\n    vlines!(\n        hscene,\n        @lift($clims[1]),\n        color = :black,\n        linewidth = menuwidth / 100,\n    )\n    vlines!(\n        hscene,\n        @lift($clims[2]),\n        color = :black,\n        linewidth = menuwidth / 100,\n    )\n\n\n    # Slice\n    sliceupperclim_slider =\n        Slider(scene, range = range(0, 1, length = 101), startvalue = 0.99)\n    sliceupperclim_node = sliceupperclim_slider.value\n    slicelowerclim_slider =\n        Slider(scene, range = range(0, 1, length = 101), startvalue = 0.01)\n    slicelowerclim_node = slicelowerclim_slider.value\n\n\n    slicexaxislabel = @lift([\"y\", \"x\", \"x\"][$directionnode])\n    sliceyaxislabel = @lift([\"z\", \"z\", \"y\"][$directionnode])\n\n    slicexaxis = @lift([[1, $ny], [1, $nx], [1, $nx]][$directionnode])\n    sliceyaxis = @lift([[1, $nz], [1, $nz], [1, $ny]][$directionnode])\n\n    slicescene =\n        layout[2:4, 5:6] =\n            Axis(scene, xlabel = slicexaxislabel, ylabel = sliceyaxislabel)\n\n    sliced_state1 = @lift( $state[\n        round(Int, 1 + $slice_node * (size($state)[1] - 1)),\n        1:size($state)[2],\n        1:size($state)[3],\n    ])\n    sliced_state2 = @lift( $state[\n        1:size($state)[1],\n        round(Int, 1 + $slice_node * (size($state)[2] - 1)),\n        1:size($state)[3],\n    ])\n    sliced_state3 = @lift( $state[\n        1:size($state)[1],\n        1:size($state)[2],\n        round(Int, 1 + $slice_node * (size($state)[3] - 1)),\n    ])\n    sliced_states = @lift([$sliced_state1, $sliced_state2, $sliced_state3])\n    sliced_state = @lift($sliced_states[$directionnode])\n\n    oclims = @lift((\n        quantile($sliced_state[:], $slicelowerclim_node),\n        quantile($sliced_state[:], $sliceupperclim_node),\n    ))\n    slicecolormapnode = @lift($oclims[1] < $oclims[2] ? $colornode : $colornode)\n    sliceclims = @lift(\n        $oclims[1] != $oclims[2] ? (minimum($oclims), maximum($oclims)) :\n        (minimum($oclims) - 1, maximum($oclims) + 1)\n    )\n\n    heatmap1 = heatmap!(\n        slicescene,\n        slicexaxis,\n        sliceyaxis,\n        sliced_state,\n        interpolate = true,\n        colormap = slicecolormapnode,\n        colorrange = sliceclims,\n    )\n\n    # Colorbar\n    newlabel = @lift($statename * \" \" * $unit)\n    cbar = Colorbar(scene, heatmap1, label = newlabel)\n    cbar.width = Relative(1 / 3)\n    # cbar.height = Relative(5/6)\n    cbar.halign = :left\n    # cbar.flipaxisposition = true\n    # cbar.labelpadding = -250\n    cbar.labelsize = 50\n\n    @lift(GLMakie.AbstractPlotting.xlims!(slicescene, extrema($slicexaxis)))\n    @lift(GLMakie.AbstractPlotting.ylims!(slicescene, extrema($sliceyaxis)))\n\n    sliceindex = @lift([\n        round(Int, 1 + $slice_node * ($nx - 1)),\n        round(Int, 1 + $slice_node * ($ny - 1)),\n        round(Int, 1 + $slice_node * ($nz - 1)),\n    ][$directionnode])\n    slicestring =\n        @lift(directionnames[$directionnode] * \" of \" * statenames[$statenode])\n    layout[1, 5:6] = Label(scene, slicestring, textsize = 50)\n\n\n    axis = scene.children[1][OldAxis]\n    axis[:names][:axisnames] = (\"↓\", \"↓ \", \"↓ \")\n    axis[:names][:align] =\n        ((:left, :center), (:right, :center), (:right, :center))\n    axis[:names][:textsize] = (50.0, 50.0, 50.0)\n    axis[:ticks][:textsize] = (00.0, 00.0, 00.0)\n\n\n    # Menus\n    statemenu = Menu(scene, options = zip(statenames, stateindex))\n    on(statemenu.selection) do s\n        statenode[] = s\n    end\n\n    colormenu = Menu(scene, options = zip(colorchoices, colorchoices))\n    on(colormenu.selection) do s\n        colornode[] = s\n    end\n\n\n    # Slice Statistics\n    layout[1, 7] = Label(scene, \"Slice Menu\", width = menuwidth, textsize = 50)\n    layout[3, 7] = Label(scene, \"Slice Statistics\", textsize = 50)\n    hslicescene =\n        layout[4, 7] = Axis(\n            scene,\n            xlabel = @lift(statenames[$statenode] * \" \" * units[$statenode]),\n            xlabelcolor = :black,\n            ylabel = \"pdf\",\n            ylabelcolor = :black,\n            xlabelsize = 40,\n            ylabelsize = 40,\n            xticklabelsize = statlabelsize[1],\n            yticklabelsize = statlabelsize[2],\n            xtickcolor = :black,\n            ytickcolor = :black,\n            xticklabelcolor = :black,\n            yticklabelcolor = :black,\n        )\n\n    slicehistogram_node = @lift(histogram($sliced_state, bins = bins))\n    xs = @lift($slicehistogram_node[1])\n    ys = @lift($slicehistogram_node[2])\n    pdf = GLMakie.AbstractPlotting.barplot!(\n        hslicescene,\n        xs,\n        ys,\n        color = :blue,\n        strokecolor = :blue,\n        strokewidth = 1,\n    )\n\n    @lift(GLMakie.AbstractPlotting.xlims!(hslicescene, extrema($xs)))\n    @lift(GLMakie.AbstractPlotting.ylims!(hslicescene, extrema($ys)))\n    vlines!(\n        hslicescene,\n        @lift($sliceclims[1]),\n        color = :black,\n        linewidth = menuwidth / 100,\n    )\n    vlines!(\n        hslicescene,\n        @lift($sliceclims[2]),\n        color = :black,\n        linewidth = menuwidth / 100,\n    )\n\n    interpolationnames = [\"contour\", \"heatmap\"]\n    interpolationchoices = [true, false]\n    interpolationnode = Node(interpolationchoices[1])\n    interpolationmenu =\n        Menu(scene, options = zip(interpolationnames, interpolationchoices))\n\n    on(interpolationmenu.selection) do s\n        interpolationnode[] = s\n        # hack\n        heatmap!(\n            slicescene,\n            slicexaxis,\n            sliceyaxis,\n            sliced_state,\n            interpolate = s,\n            colormap = slicecolormapnode,\n            colorrange = sliceclims,\n        )\n    end\n\n    directionmenu = Menu(scene, options = zip(directionnames, directionindex))\n\n    on(directionmenu.selection) do s\n        directionnode[] = s\n    end\n\n    slicemenustring = @lift(\n        directionnames[$directionnode] *\n        \" at index \" *\n        string(round(Int, 1 + $slice_node * ($nr[$directionnode] - 1)))\n    )\n    lowerclim_string = @lift(\n        \"quantile = \" *\n        @sprintf(\"%0.2f\", $lowerclim_node) *\n        \", value = \" *\n        @sprintf(\"%0.1e\", $clims[1])\n    )\n    upperclim_string = @lift(\n        \"quantile = \" *\n        @sprintf(\"%0.2f\", $upperclim_node) *\n        \", value = \" *\n        @sprintf(\"%0.1e\", $clims[2])\n    )\n    alphastring = @lift(\"Slice alpha = \" * @sprintf(\"%0.2f\", $alphanode))\n    layout[2, 1] = vgrid!(\n        Label(scene, \"State\", width = nothing),\n        statemenu,\n        Label(scene, \"Color\", width = nothing),\n        colormenu,\n        Label(scene, \"Slice Direction\", width = nothing),\n        directionmenu,\n        Label(scene, alphastring, width = nothing),\n        alpha_slider,\n        Label(scene, slicemenustring, width = nothing),\n        slice_slider,\n        Label(scene, lowerclim_string, width = nothing),\n        lowerclim_slider,\n        Label(scene, upperclim_string, width = nothing),\n        upperclim_slider,\n    )\n\n    slicelowerclim_string = @lift(\n        \"quantile = \" *\n        @sprintf(\"%0.2f\", $slicelowerclim_node) *\n        \", value = \" *\n        @sprintf(\"%0.1e\", $sliceclims[1])\n    )\n    sliceupperclim_string = @lift(\n        \"quantile = \" *\n        @sprintf(\"%0.2f\", $sliceupperclim_node) *\n        \", value = \" *\n        @sprintf(\"%0.1e\", $sliceclims[2])\n    )\n\n    layout[2, 7] = vgrid!(\n        Label(scene, \"Contour Plot Type\", width = nothing),\n        interpolationmenu,\n        Label(scene, slicelowerclim_string, width = nothing),\n        slicelowerclim_slider,\n        Label(scene, sliceupperclim_string, width = nothing),\n        sliceupperclim_slider,\n        cbar,\n    )\n\n    display(scene)\n    return scene\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/shared_source/FluidBC.jl",
    "content": "abstract type BoundaryCondition end\n\n\"\"\"\n    FluidBC(momentum    = Impenetrable(NoSlip())\n            temperature = Insulating())\n\nThe standard boundary condition for CNSEModel. The default options imply a \"no flux\" boundary condition.\n\"\"\"\nBase.@kwdef struct FluidBC{M, T} <: BoundaryCondition\n    momentum::M = Impenetrable(NoSlip())\n    temperature::T = Insulating()\nend\n\nabstract type StateBC end\nabstract type MomentumBC <: StateBC end\nabstract type MomentumDragBC <: StateBC end\nabstract type TemperatureBC <: StateBC end\n\n(bc::StateBC)(state, aux, t) = bc.flux(bc.params, state, aux, t)\n\n\"\"\"\n    Impenetrable(drag::MomentumDragBC) :: MomentumBC\n\nDefines an impenetrable wall model for momentum. This implies:\n  - no flow in the direction normal to the boundary, and\n  - flow parallel to the boundary is subject to the `drag` condition.\n\"\"\"\nstruct Impenetrable{D <: MomentumDragBC} <: MomentumBC\n    drag::D\nend\n\n\"\"\"\n    Penetrable(drag::MomentumDragBC) :: MomentumBC\n\nDefines an penetrable wall model for momentum. This implies:\n  - no constraint on flow in the direction normal to the boundary, and\n  - flow parallel to the boundary is subject to the `drag` condition.\n\"\"\"\nstruct Penetrable{D <: MomentumDragBC} <: MomentumBC\n    drag::D\nend\n\n\"\"\"\n    NoSlip() :: MomentumDragBC\n\nZero momentum at the boundary.\n\"\"\"\nstruct NoSlip <: MomentumDragBC end\n\n\"\"\"\n    FreeSlip() :: MomentumDragBC\n\nNo surface drag on momentum parallel to the boundary.\n\"\"\"\nstruct FreeSlip <: MomentumDragBC end\n\n\"\"\"\n    MomentumFlux(stress) :: MomentumDragBC\n\nApplies the specified kinematic stress on momentum normal to the boundary.\nPrescribe the net inward kinematic stress across the boundary by `stress`,\na function with signature `stress(problem, state, aux, t)`, returning the flux (in m²/s²).\n\"\"\"\nBase.@kwdef struct MomentumFlux{𝒯, 𝒫} <: MomentumDragBC\n    flux::𝒯 = nothing\n    params::𝒫 = nothing\nend\n\n\"\"\"\n    Insulating() :: TemperatureBC\n\nNo temperature flux across the boundary\n\"\"\"\nstruct Insulating <: TemperatureBC end\n\n\"\"\"\n    TemperatureFlux(flux) :: TemperatureBC\n\nPrescribe the net inward temperature flux across the boundary by `flux`,\na function with signature `flux(problem, state, aux, t)`, returning the flux (in m⋅K/s).\n\"\"\"\nBase.@kwdef struct TemperatureFlux{𝒯, 𝒫} <: TemperatureBC\n    flux::𝒯 = nothing\n    params::𝒫 = nothing\nend\n\nfunction check_bc(bcs, label)\n    bctype = FluidBC\n\n    bc_ρu = check_bc(bcs, Val(:ρu), label)\n    bc_ρθ = check_bc(bcs, Val(:ρθ), label)\n\n    return bctype(bc_ρu, bc_ρθ)\nend\n\nfunction check_bc(bcs, ::Val{:ρθ}, label)\n    if haskey(bcs, :ρθ)\n        if haskey(bcs[:ρθ], label)\n            return bcs[:ρθ][label]\n        end\n    end\n\n    return Insulating()\nend\n\nfunction check_bc(bcs, ::Val{:ρu}, label)\n    if haskey(bcs, :ρu)\n        if haskey(bcs[:ρu], label)\n            return bcs[:ρu][label]\n        end\n    end\n\n    return Impenetrable(FreeSlip())\nend\n\n# these functions just trim off the extra arguments\nfunction _cnse_boundary_state!(\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    bc,\n    model,\n    state⁺,\n    aux⁺,\n    n,\n    state⁻,\n    aux⁻,\n    t,\n    _...,\n)\n    return cnse_boundary_state!(nf, bc, model, state⁺, aux⁺, n, state⁻, aux⁻, t)\nend\n\nfunction _cnse_boundary_state!(\n    nf::NumericalFluxSecondOrder,\n    bc,\n    model,\n    state⁺,\n    gradflux⁺,\n    hyperflux⁺,\n    aux⁺,\n    n,\n    state⁻,\n    gradflux⁻,\n    hyperflux⁻,\n    aux⁻,\n    t,\n    _...,\n)\n    return cnse_boundary_state!(\n        nf,\n        bc,\n        model,\n        state⁺,\n        gradflux⁺,\n        aux⁺,\n        n,\n        state⁻,\n        gradflux⁻,\n        aux⁻,\n        t,\n    )\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/shared_source/ScalarFields.jl",
    "content": "using Base.Threads, LinearAlgebra\nimport Base: getindex, materialize!, broadcasted\n\nabstract type AbstractField end\nstruct ScalarField{S, T} <: AbstractField\n    data::S\n    grid::T\nend\n\nfunction (ϕ::ScalarField)(x::Tuple)\n    return getvalue(ϕ.data, x, ϕ.grid)\nend\n\nfunction (ϕ::ScalarField)(x::Number, y::Number, z::Number)\n    return getvalue(ϕ.data, (x, y, z), ϕ.grid)\nend\n\nfunction (ϕ::ScalarField)(x::Number, y::Number)\n    return getvalue(ϕ.data, (x, y), ϕ.grid)\nend\n\ngetindex(ϕ::ScalarField, i::Int) = ϕ.data[i]\n\nmaterialize!(ϕ::ScalarField, f::Base.Broadcast.Broadcasted) =\n    materialize!(ϕ.data, f)\nbroadcasted(identity, ϕ::ScalarField) = broadcasted(Base.identity, ϕ.data)\n\nfunction (ϕ::ScalarField)(\n    xlist::StepRangeLen,\n    ylist::StepRangeLen,\n    zlist::StepRangeLen;\n    threads = false,\n)\n    newfield = zeros(length(xlist), length(ylist), length(zlist))\n    if threads\n        @threads for k in eachindex(zlist)\n            for j in eachindex(ylist)\n                for i in eachindex(xlist)\n                    newfield[i, j, k] =\n                        getvalue(ϕ.data, (xlist[i], ylist[j], zlist[k]), ϕ.grid)\n                end\n            end\n        end\n    else\n        for k in eachindex(zlist)\n            for j in eachindex(ylist)\n                for i in eachindex(xlist)\n                    newfield[i, j, k] =\n                        getvalue(ϕ.data, (xlist[i], ylist[j], zlist[k]), ϕ.grid)\n                end\n            end\n        end\n    end\n    return newfield\nend\n\nfunction (ϕ::ScalarField)(\n    xlist::StepRangeLen,\n    ylist::StepRangeLen;\n    threads = false,\n)\n    newfield = zeros(length(xlist), length(ylist))\n    if threads\n        @threads for j in eachindex(ylist)\n            for i in eachindex(xlist)\n                newfield[i, j] = getvalue(ϕ.data, (xlist[i], ylist[j]), ϕ.grid)\n            end\n        end\n    else\n        for j in eachindex(ylist)\n            for i in eachindex(xlist)\n                newfield[i, j] = getvalue(ϕ.data, (xlist[i], ylist[j]), ϕ.grid)\n            end\n        end\n    end\n    return newfield\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/shared_source/VectorFields.jl",
    "content": "import Base: getindex\n\nabstract type AbstractRepresentation end\n\nstruct Cartesian <: AbstractRepresentation end\nstruct Spherical <: AbstractRepresentation end\nstruct Covariant <: AbstractRepresentation end\nstruct Contravariant <: AbstractRepresentation end\n\nBase.@kwdef struct VectorField{S, T, C} <: AbstractField\n    data::S\n    grid::T\n    representation::C = Cartesian()\nend\n\nfunction VectorField(ϕ::VectorField; representation = Cartesian())\n    return VectorField(\n        data = ϕ.data,\n        grid = ϕ.grid,\n        representation = representation,\n    )\nend\n\nfunction (ϕ::VectorField)(representation::AbstractRepresentation)\n    return VectorField(ϕ, representation = representation)\nend\n\nfunction components(data, ::Cartesian)\n    one = @sprintf(\"%0.2e\", data[1])\n    two = @sprintf(\"%0.2e\", data[2])\n    three = @sprintf(\"%0.2e\", data[3])\n    println(one, \"x̂ +\", two, \"ŷ +\", three, \"ẑ \")\n    return data\nend\n\ngetindex(ϕ::VectorField, ijk, e; verbose = true) = components(\n    getindex.(ϕ.data, ijk, e),\n    ϕ.grid,\n    ijk,\n    e,\n    ϕ.representation,\n    verbose = verbose,\n)\n\n## Component Grabber\n\nfunction convenience_print(grid, ijk, e)\n    print(\"location: \")\n    x, y, z = get_position(grid, ijk, e)\n    println(\n        \"x=\",\n        @sprintf(\"%0.2e\", x),\n        \",y=\",\n        @sprintf(\"%0.2e\", y),\n        \",z=\",\n        @sprintf(\"%0.2e\", z),\n        \" \",\n    )\n    print(\"field:     \")\nend\n\nfunction components(v⃗, grid, ijk, e, ::Cartesian; verbose = true)\n    if verbose\n        one = @sprintf(\"%0.2e\", v⃗[1])\n        two = @sprintf(\"%0.2e\", v⃗[2])\n        three = @sprintf(\"%0.2e\", v⃗[3])\n        convenience_print(grid, ijk, e)\n        println(one, \"x̂ +\", two, \"ŷ +\", three, \"ẑ \")\n    end\n    return v⃗\nend\n\nfunction components(v⃗, grid, ijk, e, ::Covariant; verbose = true)\n    v⃗¹, v⃗², v⃗³ = get_contravariant(grid, ijk, e)\n    v₁ = dot(v⃗¹, v⃗)\n    v₂ = dot(v⃗², v⃗)\n    v₃ = dot(v⃗³, v⃗)\n    if verbose\n        one = @sprintf(\"%0.2e\", v₁)\n        two = @sprintf(\"%0.2e\", v₂)\n        three = @sprintf(\"%0.2e\", v₃)\n        convenience_print(grid, ijk, e)\n        println(one, \"v⃗¹ +\", two, \"v⃗² +\", three, \"v⃗³ \")\n    end\n    return (; v₁, v₂, v₃)\nend\n\nfunction components(v⃗, grid, ijk, e, ::Contravariant; verbose = true)\n    v⃗₁, v⃗₂, v⃗₃ = get_covariant(grid, ijk, e)\n    v¹ = dot(v⃗₁, v⃗)\n    v² = dot(v⃗₂, v⃗)\n    v³ = dot(v⃗₃, v⃗)\n    if verbose\n        one = @sprintf(\"%0.2e\", v¹)\n        two = @sprintf(\"%0.2e\", v²)\n        three = @sprintf(\"%0.2e\", v³)\n        convenience_print(grid, ijk, e)\n        println(one, \"v⃗₁ +\", two, \"v⃗₂ +\", three, \"v⃗₃ \")\n    end\n    return (; v¹, v², v³)\nend\n\nfunction components(v⃗, grid, ijk, e, ::Spherical; verbose = true)\n    r̂, θ̂, φ̂ = get_spherical(grid, ijk, e)\n    vʳ = dot(r̂, v⃗)\n    vᶿ = dot(θ̂, v⃗)\n    vᵠ = dot(φ̂, v⃗)\n    if verbose\n        one = @sprintf(\"%0.2e\", vʳ)\n        two = @sprintf(\"%0.2e\", vᶿ)\n        three = @sprintf(\"%0.2e\", vᵠ)\n        convenience_print(grid, ijk, e)\n        println(one, \"r̂ +\", two, \"θ̂ +\", three, \"φ̂ \")\n    end\n    return (; vʳ, vᶿ, vᵠ)\nend\n\n## Helper functions\nfunction get_jacobian(grid, ijk, e)\n    ξ1x1 = grid.vgeo[ijk, grid.ξ1x1id, e]\n    ξ1x2 = grid.vgeo[ijk, grid.ξ1x2id, e]\n    ξ1x3 = grid.vgeo[ijk, grid.ξ1x3id, e]\n\n    ξ2x1 = grid.vgeo[ijk, grid.ξ2x1id, e]\n    ξ2x2 = grid.vgeo[ijk, grid.ξ2x2id, e]\n    ξ2x3 = grid.vgeo[ijk, grid.ξ2x3id, e]\n\n    ξ3x1 = grid.vgeo[ijk, grid.ξ3x1id, e]\n    ξ3x2 = grid.vgeo[ijk, grid.ξ3x2id, e]\n    ξ3x3 = grid.vgeo[ijk, grid.ξ3x3id, e]\n\n    J = [\n        ξ1x1 ξ1x2 ξ1x3\n        ξ2x1 ξ2x2 ξ2x3\n        ξ3x1 ξ3x2 ξ3x3\n    ]\n    # rows are the contravariant vectors\n    # columns of the inverse are the covariant vectors\n    return J\nend\n\nfunction get_contravariant(grid, ijk, e)\n    J = get_jacobian(grid, ijk, e)\n    a⃗¹ = J[1, :]\n    a⃗² = J[2, :]\n    a⃗³ = J[3, :]\n    return (; a⃗¹, a⃗², a⃗³)\nend\n\nfunction get_covariant(grid, ijk, e)\n    J = inv(get_jacobian(grid, ijk, e))\n    a⃗₁ = J[:, 1]\n    a⃗₂ = J[:, 2]\n    a⃗₃ = J[:, 3]\n    return (; a⃗₁, a⃗₂, a⃗₃)\nend\n\nfunction get_spherical(grid, ijk, e)\n    x, y, z = get_position(grid, ijk, e)\n    r̂ = [x, y, z] ./ norm([x, y, z])\n    θ̂ = [x * z, y * z, -(x^2 + y^2)] ./ (norm([x, y, z]) * norm([x, y, 0]))\n    φ̂ = [-y, x, 0] ./ norm([x, y, 0])\n    return (; r̂, θ̂, φ̂)\nend\n\nfunction get_position(grid, ijk, e)\n    x1 = grid.vgeo[ijk, grid.x1id, e]\n    x2 = grid.vgeo[ijk, grid.x2id, e]\n    x3 = grid.vgeo[ijk, grid.x3id, e]\n    r = [x1 x2 x3]\n    return r\nend\n\nfunction construct_determinant(grid)\n    M = grid.vgeo[:, grid.Mid, :]\n\n    ωx = reshape(grid.ω[1], (length(grid.ω[1]), 1, 1, 1))\n    ωy = reshape(grid.ω[2], (1, length(grid.ω[2]), 1, 1))\n    ωz = reshape(grid.ω[3], (1, 1, length(grid.ω[3]), 1))\n    ω = reshape(ωx .* ωy .* ωz, (size(M)[1], 1))\n    J = M ./ ω\n    return J\nend\n\nfunction get_jacobian_determinant(grid, ijk, e)\n    M = grid.vgeo[ijk, grid.Mid, e]\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/shared_source/abstractions.jl",
    "content": "#######\n# useful concepts for dispatch\n#######\n\n\"\"\"\nAdvection terms\n\nright now really only non-linear or ::Nothing\n\"\"\"\nabstract type AdvectionTerm end\nstruct NonLinearAdvectionTerm <: AdvectionTerm end\n\n\"\"\"\nTurbulence Closures\n\nways to handle drag and diffusion and such\n\"\"\"\nabstract type TurbulenceClosure end\n\nstruct LinearDrag{T} <: TurbulenceClosure\n    λ::T\nend\n\nstruct ConstantViscosity{T} <: TurbulenceClosure\n    μ::T\n    ν::T\n    κ::T\n    function ConstantViscosity{T}(;\n        μ = T(1e-6),   # m²/s\n        ν = T(1e-6),   # m²/s\n        κ = T(1e-6),   # m²/s\n    ) where {T <: AbstractFloat}\n        return new{T}(μ, ν, κ)\n    end\nend\n\n\"\"\"\nForcings\n\nways to add body terms and sources\n\"\"\"\nabstract type Forcing end\nabstract type CoriolisForce <: Forcing end\n\nstruct fPlaneCoriolis{T} <: CoriolisForce\n    fₒ::T\n    β::T\n    function fPlaneCoriolis{T}(;\n        fₒ = T(1e-4), # Hz\n        β = T(1e-11), # Hz/m\n    ) where {T <: AbstractFloat}\n        return new{T}(fₒ, β)\n    end\nend\n\nstruct SphereCoriolis{T} <: CoriolisForce\n    Ω::T\n    function SphereCoriolis{T}(;\n        Ω = T(2π / 86400), # Hz\n    ) where {T <: AbstractFloat}\n        return new{T}(Ω)\n    end\nend\nstruct KinematicStress{T} <: Forcing\n    τₒ::T\n    function KinematicStress{T}(; τₒ = T(1e-4)) where {T <: AbstractFloat}\n        return new{T}(τₒ)\n    end\nend\n\nstruct Buoyancy{T} <: Forcing\n    α::T # 1/K\n    g::T # m/s²\n    function Buoyancy{T}(; α = T(2e-4), g = T(10)) where {T <: AbstractFloat}\n        return new{T}(α, g)\n    end\nend\n\n\"\"\"\nGrouping structs\n\"\"\"\nabstract type AbstractModel end\n\nBase.@kwdef struct SpatialModel{𝒜, ℬ, 𝒞, 𝒟, ℰ, ℱ} <: AbstractModel\n    balance_law::𝒜\n    physics::ℬ\n    numerics::𝒞\n    grid::𝒟\n    boundary_conditions::ℰ\n    parameters::ℱ\nend\n\npolynomialorders(model::SpatialModel) = convention(\n    model.grid.resolution.polynomial_order,\n    Val(ndims(model.grid.domain)),\n)\n\nabstract type ModelPhysics end\n\nBase.@kwdef struct FluidPhysics{𝒪, 𝒜, 𝒟, 𝒞, ℬ} <: ModelPhysics\n    orientation::𝒪 = ClimateMachine.Orientations.FlatOrientation()\n    advection::𝒜 = NonLinearAdvectionTerm()\n    dissipation::𝒟 = nothing\n    coriolis::𝒞 = nothing\n    buoyancy::ℬ = nothing\nend\n\nabstract type AbstractInitialValueProblem end\n\nBase.@kwdef struct InitialValueProblem{𝒫, ℐ𝒱} <: AbstractInitialValueProblem\n    params::𝒫 = nothing\n    initial_conditions::ℐ𝒱 = nothing\nend\n\nabstract type AbstractSimulation end\n\nstruct Simulation{𝒜, ℬ, 𝒞, 𝒟, ℰ, ℱ} <: AbstractSimulation\n    model::𝒜\n    state::ℬ\n    timestepper::𝒞\n    initial_conditions::𝒟\n    callbacks::ℰ\n    time::ℱ\nend\n\nfunction Simulation(;\n    model = nothing,\n    state = nothing,\n    timestepper = nothing,\n    initial_conditions = nothing,\n    callbacks = nothing,\n    time = nothing,\n)\n    model = DGModel(model, initial_conditions = initial_conditions)\n\n    FT = eltype(model.grid.vgeo)\n\n    if state == nothing\n        state = init_ode_state(model, FT(0); init_on_cpu = true)\n    end\n    # model = (discrete = dgmodel, spatial = model)\n    return Simulation(\n        model,\n        state,\n        timestepper,\n        initial_conditions,\n        callbacks,\n        time,\n    )\nend\n\ncoordinates(simulation::Simulation) = coordinates(simulation.model.grid)\npolynomialorders(simulation::Simulation) =\n    polynomialorders(simulation.model.grid)\n\nabstract type AbstractTimestepper end\n\nBase.@kwdef struct TimeStepper{S, T} <: AbstractTimestepper\n    method::S\n    timestep::T\nend\n\n\"\"\"\ncalculate_dt(grid, wavespeed = nothing, diffusivity = nothing, viscocity = nothing, cfl = 0.1)\n\"\"\"\nfunction calculate_dt(\n    grid;\n    wavespeed = nothing,\n    diffusivity = nothing,\n    viscocity = nothing,\n    cfl = 1.0,\n)\n    Δx = min_node_distance(grid)\n    Δts = []\n    if wavespeed != nothing\n        push!(Δts, Δx / wavespeed)\n    end\n    if diffusivity != nothing\n        push!(Δts, Δx^2 / diffusivity)\n    end\n    if viscocity != nothing\n        push!(Δts, Δx^2 / viscocity)\n    end\n    if Δts == []\n        @error(\"Please provide characteristic speed or diffusivities\")\n        return nothing\n    end\n    return cfl * minimum(Δts)\nend\n\nfunction calculate_dt(\n    grid::DiscretizedDomain;\n    wavespeed = nothing,\n    diffusivity = nothing,\n    viscocity = nothing,\n    cfl = 1.0,\n)\n    return calculate_dt(\n        grid.numerical;\n        wavespeed = wavespeed,\n        diffusivity = diffusivity,\n        viscocity = viscocity,\n        cfl = cfl,\n    )\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/shared_source/boilerplate.jl",
    "content": "using MPI\nusing JLD2\nusing Test\nusing Dates\nusing Printf\nusing Logging\nusing StaticArrays\nusing LinearAlgebra\n\nusing ClimateMachine\nusing ClimateMachine.VTK\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Mesh.Geometry\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Orientations\n\n# ×(a::SVector, b::SVector) = StaticArrays.cross(a, b)\n⋅(a::SVector, b::SVector) = StaticArrays.dot(a, b)\n⊗(a::SVector, b::SVector) = a * b'\n\nabstract type AbstractFluid <: BalanceLaw end\nstruct Fluid <: AbstractFluid end\n\ninclude(\"domains.jl\")\ninclude(\"grids.jl\")\ninclude(\"abstractions.jl\")\ninclude(\"callbacks.jl\")\ninclude(\"FluidBC.jl\")\ninclude(\"ScalarFields.jl\")\ninclude(\"VectorFields.jl\")\n# include(\"../plotting/bigfileofstuff.jl\")\n# include(\"../plotting/vizinanigans.jl\")\n\n\"\"\"\nfunction coordinates(grid::DiscontinuousSpectralElementGrid)\n# Description\nGets the (x,y,z) coordinates corresponding to the grid\n# Arguments\n- `grid`: DiscontinuousSpectralElementGrid\n# Return\n- `x, y, z`: views of x, y, z coordinates\n\"\"\"\n\nfunction evolve!(simulation, spatial_model; refDat = ())\n    Q = simulation.state\n\n    dg = simulation.model\n    Ns = polynomialorders(spatial_model)\n\n    if haskey(spatial_model.grid.resolution, :overintegration_order)\n        Nover = spatial_model.grid.resolution.overintegration_order\n    else\n        Nover = (0, 0, 0)\n    end\n\n    # only works if Nover > 0\n    overintegration_filter!(Q, dg, Ns, Nover)\n\n    function custom_tendency(tendency, x...; kw...)\n        dg(tendency, x...; kw...)\n        overintegration_filter!(tendency, dg, Ns, Nover)\n    end\n\n    t0 = simulation.time.start\n    Δt = simulation.timestepper.timestep\n    timestepper = simulation.timestepper.method\n\n    odesolver = timestepper(custom_tendency, Q, dt = Δt, t0 = t0)\n\n    cbvector = create_callbacks(simulation, odesolver)\n\n    if isempty(cbvector)\n        solve!(Q, odesolver; timeend = simulation.time.finish)\n    else\n        solve!(\n            Q,\n            odesolver;\n            timeend = simulation.time.finish,\n            callbacks = cbvector,\n        )\n    end\n\n    ## Check results against reference\n\n    ClimateMachine.StateCheck.scprintref(cbvector[end])\n    if length(refDat) > 0\n        @test ClimateMachine.StateCheck.scdocheck(cbvector[end], refDat)\n    end\n\n    return Q\nend\n\nfunction visualize(\n    simulation::Simulation;\n    statenames = [string(i) for i in 1:size(simulation.state)[2]],\n    resolution = (32, 32, 32),\n)\n    a_, statesize, b_ = size(simulation.state)\n    mpistate = simulation.state\n    grid = simulation.model.grid\n    grid_helper = GridHelper(grid)\n    r = coordinates(grid)\n    states = []\n    ϕ = ScalarField(copy(r[1]), grid_helper)\n    r = uniform_grid(Ω, resolution = resolution)\n    # statesymbol = vars(Q).names[i] # doesn't work for vectors\n    for i in 1:statesize\n        ϕ .= mpistate[:, i, :]\n        ϕnew = ϕ(r...)\n        push!(states, ϕnew)\n    end\n    visualize([states...], statenames = statenames)\nend\n\nfunction overintegration_filter!(state_array, dgmodel, Ns, Nover)\n    if sum(Nover) > 0\n        cutoff_order = Ns .+ 1\n\n        cutoff = MassPreservingCutoffFilter(dgmodel.grid, cutoff_order)\n        num_state_prognostic = number_states(dgmodel.balance_law, Prognostic())\n\n        ClimateMachine.Mesh.Filters.apply!(\n            state_array,\n            1:num_state_prognostic,\n            dgmodel.grid,\n            cutoff,\n        )\n    end\n\n    return nothing\nend\n\n# initialized on CPU so not problem, but could do kernel launch?\nfunction set_ic!(ϕ, s::Number, _...)\n    ϕ .= s\n    return nothing\nend\n\nfunction set_ic!(ϕ, s::Function, p, x, y, z)\n    _, nstates, _ = size(ϕ)\n    @inbounds for i in eachindex(x)\n        @inbounds for j in 1:nstates\n            ϕʲ = view(ϕ, :, j, :)\n            ϕʲ[i] = s(p, x[i], y[i], z[i])[j]\n        end\n    end\n    return nothing\nend\n\n\n# filter below here\nusing ClimateMachine.Mesh.Filters\nusing KernelAbstractions\nusing KernelAbstractions.Extras: @unroll\nimport ClimateMachine.Mesh.Filters: apply_async!\nimport ClimateMachine.Mesh.Filters: AbstractFilterTarget\nimport ClimateMachine.Mesh.Filters:\n    number_state_filtered,\n    vars_state_filtered,\n    compute_filter_argument!,\n    compute_filter_result!\n\nfunction modified_filter_matrix(r, Nc, σ)\n    N = length(r) - 1\n    T = eltype(r)\n\n    @assert N >= 0\n    @assert 0 <= Nc\n\n    a, b = GaussQuadrature.legendre_coefs(T, N)\n    V = (N == 0 ? ones(T, 1, 1) : GaussQuadrature.orthonormal_poly(r, a, b))\n\n    Σ = ones(T, N + 1)\n    if Nc ≤ N\n        Σ[(Nc:N) .+ 1] .= σ.(((Nc:N) .- Nc) ./ (N - Nc))\n    end\n\n    V * Diagonal(Σ) / V\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/shared_source/callbacks.jl",
    "content": "abstract type AbstractCallback end\n\nstruct Default <: AbstractCallback end\nstruct Info <: AbstractCallback end\nstruct StateCheck{T} <: AbstractCallback\n    number_of_checks::T\nend\n\nBase.@kwdef struct JLD2State{T, V, B} <: AbstractCallback\n    iteration::T\n    filepath::V\n    overwrite::B = true\nend\n\nBase.@kwdef struct VTKState{T, V, C, B} <: AbstractCallback\n    iteration::T = 1\n    filepath::V = \".\"\n    counter::C = [0]\n    overwrite::B = true\nend\n\nfunction create_callbacks(simulation::Simulation, odesolver)\n    callbacks = simulation.callbacks\n\n    if isempty(callbacks)\n        return ()\n    else\n        cbvector = [\n            create_callback(callback, simulation, odesolver)\n            for callback in callbacks\n        ]\n        return tuple(cbvector...)\n    end\nend\n\nfunction create_callback(::Default, simulation::Simulation, odesolver)\n    cb_info = create_callback(Info(), simulation, odesolver)\n    cb_state_check = create_callback(StateCheck(10), simulation, odesolver)\n\n    return (cb_info, cb_state_check)\nend\n\nfunction create_callback(::Info, simulation::Simulation, odesolver)\n    Q = simulation.state\n    timeend = simulation.time.finish\n    mpicomm = MPI.COMM_WORLD\n\n    starttime = Ref(now())\n    cbinfo = ClimateMachine.GenericCallbacks.EveryXWallTimeSeconds(\n        60,\n        mpicomm,\n    ) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %8.4f / %8.4f\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                ClimateMachine.ODESolvers.gettime(odesolver),\n                timeend,\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n\n            if isnan(energy)\n                error(\"NaNs\")\n            end\n        end\n    end\n\n    return cbinfo\nend\n\nfunction create_callback(callback::StateCheck, simulation::Simulation, _...)\n    sim_length = simulation.time.finish - simulation.time.start\n    timestep = simulation.timestepper.timestep\n    nChecks = callback.number_of_checks\n\n\n    nt_freq = floor(Int, sim_length / timestep / nChecks)\n\n    cbcs_dg = ClimateMachine.StateCheck.sccreate(\n        [(simulation.state, \"state\")],\n        nt_freq,\n    )\n\n    return cbcs_dg\nend\n\nfunction create_callback(output::JLD2State, simulation::Simulation, odesolver)\n    # Initialize output\n    output.overwrite &&\n        isfile(output.filepath) &&\n        rm(output.filepath; force = output.overwrite)\n\n    Q = simulation.state\n    mpicomm = MPI.COMM_WORLD\n    iteration = output.iteration\n\n    steps = ClimateMachine.ODESolvers.getsteps(odesolver)\n    time = ClimateMachine.ODESolvers.gettime(odesolver)\n\n    file = jldopen(output.filepath, \"a+\")\n    JLD2.Group(file, \"state\")\n    JLD2.Group(file, \"time\")\n    file[\"state\"][string(steps)] = Array(Q)\n    file[\"time\"][string(steps)] = time\n    close(file)\n\n\n    jldcallback = ClimateMachine.GenericCallbacks.EveryXSimulationSteps(\n        iteration,\n    ) do (s = false)\n        steps = ClimateMachine.ODESolvers.getsteps(odesolver)\n        time = ClimateMachine.ODESolvers.gettime(odesolver)\n        @info steps, time\n        file = jldopen(output.filepath, \"a+\")\n        file[\"state\"][string(steps)] = Array(Q)\n        file[\"time\"][string(steps)] = time\n        close(file)\n        return nothing\n    end\nend\n\nfunction create_callback(output::VTKState, simulation::Simulation, odesolver)\n    # Initialize output\n    output.overwrite &&\n        isfile(output.filepath) &&\n        rm(output.filepath; force = output.overwrite)\n    mkpath(output.filepath)\n\n    state = simulation.state\n    model = simulation.model\n\n    function do_output(counter, model, state)\n        mpicomm = MPI.COMM_WORLD\n        balance_law = model.balance_law\n        aux_state = model.state_auxiliary\n\n        outprefix = @sprintf(\n            \"%s/mpirank%04d_step%04d\",\n            output.filepath,\n            MPI.Comm_rank(mpicomm),\n            counter[1],\n        )\n\n        @info \"doing VTK output\" outprefix\n\n        state_names =\n            flattenednames(vars_state(balance_law, Prognostic(), eltype(state)))\n        aux_names =\n            flattenednames(vars_state(balance_law, Auxiliary(), eltype(state)))\n\n        writevtk(outprefix, state, model, state_names, aux_state, aux_names)\n\n        counter[1] += 1\n\n        return nothing\n    end\n\n    do_output(output.counter, model, state)\n    cbvtk =\n        ClimateMachine.GenericCallbacks.EveryXSimulationSteps(output.iteration) do (\n            init = false\n        )\n            do_output(output.counter, model, state)\n            return nothing\n        end\n\n    return cbvtk\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/shared_source/domains.jl",
    "content": "import Base: getindex, *, ndims, length, ^\nimport LinearAlgebra: ×\n\nabstract type AbstractDomain end\nabstract type AbstractBoundary end\n\nstruct DomainBoundary <: AbstractBoundary\n    closure::Any\nend\n\nstruct PointDomain{S} <: AbstractDomain\n    point::S\nend\n\nstruct IntervalDomain{AT, BT, PT} <: AbstractDomain\n    min::AT\n    max::BT\n    periodic::PT\nend\n\nfunction IntervalDomain(min, max; periodic = false)\n    @assert min < max\n    return IntervalDomain(min, max, periodic)\nend\n\nfunction Periodic(min, max)\n    @assert min < max\n    return IntervalDomain(min, max, periodic = true)\nend\n\nS¹ = Periodic\n\nfunction Interval(min, max)\n    @assert min < max\n    return IntervalDomain(min, max)\nend\n\nfunction Periodic(min, max)\n    @assert min < max\n    return IntervalDomain(min, max, periodic = true)\nend\n\nfunction Base.show(io::IO, Ω::IntervalDomain)\n    min = Ω.min\n    max = Ω.max\n    printstyled(io, \"[\", color = 226)\n    astring = @sprintf(\"%0.2f\", min)\n    bstring = @sprintf(\"%0.2f\", max)\n    printstyled(astring, \", \", bstring, color = 7)\n    # printstyled(\"$min, $max\", color = 7)\n    Ω.periodic ? printstyled(io, \")\", color = 226) :\n    printstyled(io, \"]\", color = 226)\nend\n\nfunction Base.show(io::IO, o::PointDomain)\n    printstyled(\"{\", o.point, \"}\", color = 201)\nend\n\n# Product Domains\nstruct ProductDomain{DT} <: AbstractDomain\n    domains::DT\nend\n\nfunction Base.show(io::IO, Ω::ProductDomain)\n    for (i, domain) in enumerate(Ω.domains)\n        print(domain)\n        if i != length(Ω.domains)\n            printstyled(io, \"×\", color = 118)\n        end\n    end\nend\n\nndims(p::PointDomain) = 0\nndims(Ω::IntervalDomain) = 1\nndims(Ω::ProductDomain) = +(ndims.(Ω.domains)...)\n\nlength(Ω::IntervalDomain) = Ω.max - Ω.min\nlength(Ω::ProductDomain) = length.(Ω.domains)\n\n×(arg1::AbstractDomain, arg2::AbstractDomain) = ProductDomain((arg1, arg2))\n×(args::ProductDomain, arg2::AbstractDomain) =\n    ProductDomain((args.domains..., arg2))\n×(arg1::AbstractDomain, args::ProductDomain) =\n    ProductDomain((arg1, args.domains...))\n×(arg1::ProductDomain, args::ProductDomain) =\n    ProductDomain((arg1.domains..., args.domains...))\n×(args::AbstractDomain) = ProductDomain(args...)\n*(arg1::AbstractDomain, arg2::AbstractDomain) = arg1 × arg2\n\nfunction ^(Ω::IntervalDomain, T::Int)\n    Ωᵀ = Ω\n    for i in 1:(T - 1)\n        Ωᵀ *= Ω\n    end\n    return Ωᵀ\nend\n\nfunction info(Ω::ProductDomain)\n    println(\"This is a \", ndims(Ω), \"-dimensional tensor product domain.\")\n    print(\"The domain is \")\n    println(Ω, \".\")\n    for (i, domain) in enumerate(Ω.domains)\n        domain_string = domain.periodic ? \"periodic\" : \"wall-bounded\"\n        length = @sprintf(\"%.2f \", domain.max - domain.min)\n        println(\n            \"The dimension $i domain is \",\n            domain_string,\n            \" with length ≈ \",\n            length,\n        )\n    end\n    return nothing\nend\n\nfunction isperiodic(Ω::ProductDomain)\n    max = [Ω.domains[i].periodic for i in eachindex(Ω.domains)]\n    return prod(max)\nend\n\nfunction periodicityof(Ω::ProductDomain)\n    periodicity = ones(Bool, ndims(Ω))\n    for i in 1:ndims(Ω)\n        periodicity[i] = Ω[i].periodic\n    end\n    return Tuple(periodicity)\nend\n\ngetindex(Ω::ProductDomain, i::Int) = Ω.domains[i]\n\n# Boundaries\nstruct Boundaries{S}\n    boundaries::S\nend\n\ngetindex(∂Ω::Boundaries, i) = ∂Ω.boundaries[i]\n\nfunction Base.show(io::IO, ∂Ω::Boundaries)\n    for (i, boundary) in enumerate(∂Ω.boundaries)\n        printstyled(\"boundary \", i, \": \", color = 13)\n        println(boundary)\n    end\nend\n\nfunction ∂(Ω::IntervalDomain)\n    if Ω.periodic\n        return (nothing)\n    else\n        return Boundaries((PointDomain(Ω.min), PointDomain(Ω.max)))\n    end\n    return nothing\nend\n\nfunction ∂(Ω::ProductDomain)\n    ∂Ω = []\n    for domain in Ω.domains\n        push!(∂Ω, ∂(domain))\n    end\n    splitb = []\n    for (i, boundary) in enumerate(∂Ω)\n        tmp = Any[]\n        push!(tmp, Ω.domains...)\n        if boundary != nothing\n            tmp1 = copy(tmp)\n            tmp2 = copy(tmp)\n            tmp1[i] = boundary[1]\n            push!(splitb, ProductDomain(Tuple(tmp1)))\n            tmp2[i] = boundary[2]\n            push!(splitb, ProductDomain(Tuple(tmp2)))\n        end\n    end\n    return Boundaries(Tuple(splitb))\nend\n\nBase.@kwdef struct SphericalShellDomain{ℛ, ℋ, 𝒟} <: AbstractDomain\n    radius::ℛ = 6378e3\n    height::ℋ = 30e3\n    depth::𝒟 = 3e3\nend\n\n\nlength(Ω::SphericalShellDomain) = (Ω.radius, Ω.radius, Ω.height - Ω.depth)\nndims(Ω::SphericalShellDomain) = 3\n\nfunction AtmosDomain(; radius = 6378e3, height = 30e3)\n    depth = 0\n    return SphericalShellDomain(; radius, height, depth)\nend\n\nfunction OceanDomain(; radius = 6378e3, depth = 3e3)\n    height = 0\n    return SphericalShellDomain(; radius, height, depth)\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/shared_source/grids.jl",
    "content": "import ClimateMachine.Mesh.Grids: DiscontinuousSpectralElementGrid\n\nfunction coordinates(grid::DiscontinuousSpectralElementGrid)\n    x = view(grid.vgeo, :, grid.x1id, :)   # x-direction\t\n    y = view(grid.vgeo, :, grid.x2id, :)   # y-direction\t\n    z = view(grid.vgeo, :, grid.x3id, :)   # z-direction\n    return x, y, z\nend\n\n# some convenience functions\nfunction convention(\n    a::NamedTuple{(:vertical, :horizontal), T},\n    ::Val{3},\n) where {T}\n    return (a.horizontal, a.horizontal, a.vertical)\nend\n\nfunction convention(a::Number, ::Val{3})\n    return (a, a, a)\nend\n\nfunction convention(\n    a::NamedTuple{(:vertical, :horizontal), T},\n    ::Val{2},\n) where {T}\n    return (a.horizontal, a.vertical)\nend\n\nfunction convention(a::Number, ::Val{2})\n    return (a, a)\nend\n\nfunction convention(a::Tuple, b)\n    return a\nend\n\n# brick range brickbuilder\nfunction uniform_brick_builder(domain, elements; FT = Float64)\n    dimension = ndims(domain)\n\n    tuple_ranges = []\n    for i in 1:dimension\n        push!(\n            tuple_ranges,\n            range(\n                FT(domain[i].min);\n                length = elements[i] + 1,\n                stop = FT(domain[i].max),\n            ),\n        )\n    end\n\n    brickrange = Tuple(tuple_ranges)\n    return brickrange\nend\n\n# Grid Constructor\n\"\"\"\nfunction DiscontinuousSpectralElementGrid(domain::ProductDomain; elements = nothing, polynomialorder = nothing)\n# Description \nComputes a DiscontinuousSpectralElementGrid as specified by a product domain\n# Arguments\n-`domain`: A product domain object\n# Keyword Arguments \n-`elements`: A tuple of integers ordered by (Nx, Ny, Nz) for number of elements\n-`polynomialorder`: A tupe of integers ordered by (npx, npy, npz) for polynomial order\n-`FT`: floattype, assumed Float64 unless otherwise specified\n-`topology`: default = StackedBrickTopology\n-`mpicomm`: default = MPI.COMM_WORLD\n-`array`: default = ClimateMachine.array_type()\n-`brickbuilder`: default = uniform_brick_builder, \n  brickrange=uniform_brick_builder(domain, elements)\n# Return \nA DiscontinuousSpectralElementGrid object\n\"\"\"\nfunction DiscontinuousSpectralElementGrid(\n    domain::ProductDomain;\n    elements = nothing,\n    polynomialorder = nothing,\n    FT = Float64,\n    mpicomm = MPI.COMM_WORLD,\n    array = ClimateMachine.array_type(),\n    topology = StackedBrickTopology,\n    brick_builder = uniform_brick_builder,\n)\n\n    if elements == nothing\n        error_message = \"Please specify the number of elements as a tuple whose size is commensurate with the domain,\"\n        error_message *= \" e.g., a 3 dimensional domain would need a specification like elements = (10,10,10).\"\n        error_message *= \" or elements = (vertical = 8, horizontal = 5)\"\n\n        @error(error_message)\n        return nothing\n    end\n\n    if polynomialorder == nothing\n        error_message = \"Please specify the polynomial order as a tuple whose size is commensurate with the domain,\"\n        error_message *= \"e.g., a 3 dimensional domain would need a specification like polynomialorder = (3,3,3).\"\n        error_message *= \" or polynomialorder = (vertical = 8, horizontal = 5)\"\n\n        @error(error_message)\n        return nothing\n    end\n\n    dimension = ndims(domain)\n\n    if (dimension < 2) || (dimension > 3)\n        error_message = \"SpectralElementGrid only works with dimensions 2 or 3. \"\n        error_message *= \"The current dimension is \" * string(ndims(domain))\n\n        println(\"The domain is \", domain)\n        @error(error_message)\n        return nothing\n    end\n\n    elements = convention(elements, Val(dimension))\n    if ndims(domain) != length(elements)\n        @error(\"Incorrectly specified elements for the dimension of the domain\")\n        return nothing\n    end\n\n    polynomialorder = convention(polynomialorder, Val(dimension))\n    if ndims(domain) != length(polynomialorder)\n        @error(\"Incorrectly specified polynomialorders for the dimension of the domain\")\n        return nothing\n    end\n\n    brickrange = brick_builder(domain, elements, FT = FT)\n\n    if dimension == 2\n        boundary = ((1, 2), (3, 4))\n    else\n        boundary = ((1, 2), (3, 4), (5, 6))\n    end\n\n    periodicity = periodicityof(domain)\n    connectivity = dimension == 2 ? :face : :full\n\n    topl = topology(\n        mpicomm,\n        brickrange;\n        periodicity = periodicity,\n        boundary = boundary,\n        connectivity = connectivity,\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = array,\n        polynomialorder = polynomialorder,\n    )\n\n    return grid\nend\n\nabstract type AbstractDiscretizedDomain end\n\nstruct DiscretizedDomain{𝒜, ℬ, 𝒞} <: AbstractDiscretizedDomain\n    domain::𝒜\n    resolution::ℬ\n    numerical::𝒞\nend\n\nfunction DiscretizedDomain(\n    domain::ProductDomain;\n    elements = nothing,\n    polynomial_order = nothing,\n    overintegration_order = nothing,\n    FT = Float64,\n    mpicomm = MPI.COMM_WORLD,\n    array = ClimateMachine.array_type(),\n    topology = StackedBrickTopology,\n    brick_builder = uniform_brick_builder,\n)\n\n    grid = DiscontinuousSpectralElementGrid(\n        domain,\n        elements = elements,\n        polynomialorder = polynomial_order .+ overintegration_order,\n        FT = FT,\n        mpicomm = mpicomm,\n        array = array,\n        topology = topology,\n        brick_builder = brick_builder,\n    )\n    return DiscretizedDomain(\n        domain,\n        (; elements, polynomial_order, overintegration_order),\n        grid,\n    )\nend\n\n## SphericalShellDomain\n\nfunction DiscontinuousSpectralElementGrid(\n    Ω::SphericalShellDomain;\n    elements = (vertical = 2, horizontal = 4),\n    polynomialorder = (vertical = 4, horizontal = 4),\n    mpicomm = MPI.COMM_WORLD,\n    boundary = (5, 6),\n    FT = Float64,\n    array = Array,\n)\n    Rrange = grid1d(\n        Ω.radius - Ω.depth,\n        Ω.radius + Ω.height,\n        nelem = elements.vertical,\n    )\n\n    topl = StackedCubedSphereTopology(\n        mpicomm,\n        elements.horizontal,\n        Rrange,\n        boundary = boundary,\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = array,\n        polynomialorder = (\n            polynomialorder.vertical,\n            polynomialorder.horizontal,\n        ),\n        meshwarp = equiangular_cubed_sphere_warp,\n    )\n    return grid\nend\n\nfunction DiscretizedDomain(\n    domain::SphericalShellDomain;\n    elements = nothing,\n    polynomial_order = nothing,\n    overintegration_order = nothing,\n    FT = Float64,\n    mpicomm = MPI.COMM_WORLD,\n    array = ClimateMachine.array_type(),\n    topology = StackedBrickTopology,\n    brick_builder = uniform_brick_builder,\n)\n    new_polynomial_order = convention(polynomial_order, Val(2))\n    new_polynomial_order =\n        new_polynomial_order .+ convention(overintegration_order, Val(2))\n    vertical, horizontal = new_polynomial_order\n    grid = DiscontinuousSpectralElementGrid(\n        domain,\n        elements = elements,\n        polynomialorder = (; vertical, horizontal),\n        FT = FT,\n        mpicomm = mpicomm,\n        array = array,\n    )\n    return DiscretizedDomain(\n        domain,\n        (; elements, polynomial_order, overintegration_order),\n        grid,\n    )\nend\n\n# extensions\ncoordinates(grid::DiscretizedDomain) = coordinates(grid.numerical)\npolynomialorders(grid::DiscretizedDomain) = polynomialorders(grid.numerical)\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/sphere/sphere_helper_functions.jl",
    "content": "rad(x, y, z) = sqrt(x^2 + y^2 + z^2)\nlat(x, y, z) = asin(z / rad(x, y, z)) # ϕ ∈ [-π/2, π/2] \nlon(x, y, z) = atan(y, x) # λ ∈ [-π, π) \n\nr̂ⁿᵒʳᵐ(x, y, z) = norm([x, y, z]) ≈ 0 ? 1 : norm([x, y, z])^(-1)\nϕ̂ⁿᵒʳᵐ(x, y, z) =\n    norm([x, y, 0]) ≈ 0 ? 1 : (norm([x, y, z]) * norm([x, y, 0]))^(-1)\nλ̂ⁿᵒʳᵐ(x, y, z) = norm([x, y, 0]) ≈ 0 ? 1 : norm([x, y, 0])^(-1)\n\nr̂(x, y, z) = r̂ⁿᵒʳᵐ(x, y, z) * @SVector([x, y, z])\nϕ̂(x, y, z) = ϕ̂ⁿᵒʳᵐ(x, y, z) * @SVector [x * z, y * z, -(x^2 + y^2)]\nλ̂(x, y, z) = λ̂ⁿᵒʳᵐ(x, y, z) * @SVector [-y, x, 0]\n\nrfunc(p, x...) = ρuʳᵃᵈ(p, lon(x...), lat(x...), rad(x...)) * r̂(x...)\nϕfunc(p, x...) = ρuˡᵃᵗ(p, lon(x...), lat(x...), rad(x...)) * ϕ̂(x...)\nλfunc(p, x...) = ρuˡᵒⁿ(p, lon(x...), lat(x...), rad(x...)) * λ̂(x...)\n\nρu⃗(p, x...) = rfunc(p, x...) + ϕfunc(p, x...) + λfunc(p, x...)\n\n\nfunction vector_field_representation(\n    u⃗,\n    grid::DiscontinuousSpectralElementGrid,\n    rep::AbstractRepresentation,\n)\n    n_ijk, _, n_e = size(u⃗)\n    uᴿ = copy(u⃗)\n    v⃗ =\n        VectorField(data = (u⃗[:, 1, :], u⃗[:, 2, :], u⃗[:, 3, :]), grid = grid)\n    for ijk in 1:n_ijk, e in 1:n_e, s in 1:3\n        uᴿ[ijk, s, e] = v⃗(rep)[ijk, e, verbose = false][s]\n    end\n    return uᴿ\nend\n\nvector_field_representation(simulation::Simulation, rep) =\n    vector_field_representation(simulation.state.ρu, simulation.model.grid, rep)\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/sphere/test_heat_equation.jl",
    "content": "#!/usr/bin/env julia --project\n\ninclude(\"../shared_source/boilerplate.jl\")\ninclude(\"../three_dimensional/ThreeDimensionalCompressibleNavierStokesEquations.jl\")\ninclude(\"sphere_helper_functions.jl\")\n\nClimateMachine.init()\n\n#! format: off\nrefVals = (\n[\n [ \"state\",     \"ρ\",   9.99999999999998778755e-01,  1.00000000000000111022e+00,  1.00000000000000000000e+00,  4.03070839356576998483e-16 ],\n [ \"state\", \"ρu[1]\",  -6.93674606129580362185e-18,  1.19513727285696382782e-17,  9.77991462267996185035e-19,  2.78015892816367900233e-18 ],\n [ \"state\", \"ρu[2]\",  -1.21340560458612968582e-17,  1.22630936333135748291e-17, -1.30065979768613433360e-21,  2.78919703417897612909e-18 ],\n [ \"state\", \"ρu[3]\",  -1.00545092917566730233e-17,  1.00043577527427175213e-17,  3.76131929192945135398e-19,  2.47195369518944690265e-18 ],\n [ \"state\",    \"ρθ\",  -3.00959038541689510859e-02, -3.00957518135526284897e-02, -3.00958465374152675520e-02,  3.12498428220800724718e-08 ],\n],\n[\n [ \"state\",     \"ρ\",    12,    12,    12,     0 ],\n [ \"state\", \"ρu[1]\",     0,     0,     0,     0 ],\n [ \"state\", \"ρu[2]\",     0,     0,     0,     0 ],\n [ \"state\", \"ρu[3]\",     0,     0,     0,     0 ],\n [ \"state\",    \"ρθ\",    12,    12,    12,     0 ],\n],\n)\n#! format: on\n\n#################\n# RUN THE TESTS #\n#################\n@testset \"$(@__FILE__)\" begin\n\n    ########\n    # Setup physical and numerical domains\n    ########\n    Ω = AtmosDomain(radius = 1, height = 0.2)\n\n    grid = DiscretizedDomain(\n        Ω;\n        elements = (vertical = 1, horizontal = 5),\n        polynomial_order = (vertical = 1 + 0, horizontal = 3 + 0),\n        overintegration_order = (vertical = 1, horizontal = 1),\n    )\n\n    ########\n    # Define physical parameters and parameterizations\n    ########\n    parameters = (\n        ρₒ = 1,        # reference density\n        cₛ = 1e-2,     # sound speed\n        R = Ω.radius, # [m]\n        ω = 0.0,      # [s⁻¹]\n        K = 7.848e-6, # [s⁻¹]\n        n = 4,        # dimensionless\n        Ω = 0.0,      # [s⁻¹] 2π/86400\n    )\n\n    physics = FluidPhysics(;\n        orientation = SphericalOrientation(),\n        advection = NonLinearAdvectionTerm(),\n        dissipation = ConstantViscosity{Float64}(μ = 0, ν = 0.0, κ = 1e-3),\n        coriolis = nothing,\n        buoyancy = Buoyancy{Float64}(α = 2e-4, g = 0),\n    )\n\n    ########\n    # Define timestepping parameters\n    ########\n    Δt = 0.1 * min_node_distance(grid.numerical)^2 / physics.dissipation.κ\n    start_time = 0\n    end_time = 20.0 * Δt\n\n    method = SSPRK22Heuns\n\n    timestepper = TimeStepper(method = method, timestep = Δt)\n\n    callbacks = (Info(), StateCheck(10))\n\n    ########\n    # Define boundary conditions (west east are the ones that are enforced for a sphere)\n    ########\n    ρu_bcs = (bottom = Impenetrable(FreeSlip()), top = Impenetrable(FreeSlip()))\n    ρθ_bcs = (\n        bottom = TemperatureFlux(\n            flux = (p, state, aux, t) -> p.Q,\n            params = (Q = 1e-3,), # positive means removing heat from system\n        ),\n        top = TemperatureFlux(\n            flux = (p, state, aux, t) -> p.Q,\n            params = (Q = 1e-3,), # positive means removing heat from system\n        ),\n    )\n    BC = (ρθ = ρθ_bcs, ρu = ρu_bcs)\n\n    ########\n    # Define initial conditions\n    ########\n\n    # Earth Spherical Representation\n    # longitude: λ ∈ [-π, π), λ = 0 is the Greenwich meridian\n    # latitude:  ϕ ∈ [-π/2, π/2], ϕ = 0 is the equator\n    # radius:    r ∈ [Rₑ - hᵐⁱⁿ, Rₑ + hᵐᵃˣ]\n    # Rₑ = Radius of sphere; hᵐⁱⁿ, hᵐᵃˣ ≥ 0\n\n    ρ₀(p, λ, ϕ, r) = p.ρₒ\n    ρuʳᵃᵈ(p, λ, ϕ, r) = 0.0\n    ρuˡᵃᵗ(p, λ, ϕ, r) = 0.0\n    ρuˡᵒⁿ(p, λ, ϕ, r) = 0.0\n    ρθ₀(p, λ, ϕ, r) = 0.0\n\n    # Cartesian Representation (boiler plate really)\n    ρ₀ᶜᵃʳᵗ(p, x...) = ρ₀(p, lon(x...), lat(x...), rad(x...))\n    ρu⃗₀ᶜᵃʳᵗ(p, x...) = (\n        ρuʳᵃᵈ(p, lon(x...), lat(x...), rad(x...)) * r̂(x...) +\n        ρuˡᵃᵗ(p, lon(x...), lat(x...), rad(x...)) * ϕ̂(x...) +\n        ρuˡᵒⁿ(p, lon(x...), lat(x...), rad(x...)) * λ̂(x...)\n    )\n    ρθ₀ᶜᵃʳᵗ(p, x...) = ρθ₀(p, lon(x...), lat(x...), rad(x...))\n\n    initial_conditions = (ρ = ρ₀ᶜᵃʳᵗ, ρu = ρu⃗₀ᶜᵃʳᵗ, ρθ = ρθ₀ᶜᵃʳᵗ)\n\n    ########\n    # Create the things\n    ########\n\n    model = SpatialModel(\n        balance_law = Fluid3D(),\n        physics = physics,\n        numerics = (flux = RoeNumericalFlux(),),\n        grid = grid,\n        boundary_conditions = BC,\n        parameters = parameters,\n    )\n\n    simulation = Simulation(\n        model = model,\n        initial_conditions = initial_conditions,\n        timestepper = timestepper,\n        callbacks = callbacks,\n        time = (; start = start_time, finish = end_time),\n    )\n\n    ########\n    # Run the model\n    ########\n    tic = Base.time()\n\n    @testset \"State Check\" begin\n        evolve!(simulation, model, refDat = refVals)\n    end\n\n    toc = Base.time()\n    time = toc - tic\n    println(time)\n\n    ## Check the budget\n    θ̄ = weightedsum(simulation.state, 5)\n\n    @testset \"Exact Geometry\" begin\n        # Exact (with exact geometry)\n        R = grid.domain.radius\n        H = grid.domain.height\n\n        θ̄ᴱ = 0\n        θ̄ᴱ -= 4π * R^2 * end_time * BC.ρθ.bottom.params.Q\n        θ̄ᴱ -= 4π * (R + H)^2 * end_time * BC.ρθ.top.params.Q\n\n        δθ̄ᴱ = abs(θ̄ - θ̄ᴱ) / abs(θ̄ᴱ)\n        println(\"The relative error w.r.t. exact geometry \", δθ̄ᴱ)\n\n        @test isapprox(0, δθ̄ᴱ; atol = 1e-9)\n    end\n\n    # TODO: make Approx Geom test MPI safe\n    #=\n    @testset \"Approx Geometry\" begin\n        # Exact (with approximated geometry)\n        n_e = convention(grid.resolution.elements, Val(3))\n        n_ijk = convention(grid.resolution.polynomial_order, Val(3))\n        n_ijk =\n            n_ijk .+ convention(grid.resolution.overintegration_order, Val(3))\n        n_ijk = n_ijk .+ (1, 1, 1)\n\n        Mᴴ = reshape(\n            grid.numerical.vgeo[:, grid.numerical.MHid, :],\n            (n_ijk..., n_e[3], 6 * n_e[1] * n_e[2]),\n        )\n        Aᴮᵒᵗ = sum(Mᴴ[:, :, 1, 1, :])\n        Aᵀᵒᵖ = sum(Mᴴ[:, :, end, end, :])\n\n        θ̄ᴬ = 0\n        θ̄ᴬ -= Aᴮᵒᵗ * end_time * BC.ρθ.bottom.params.Q\n        θ̄ᴬ -= Aᵀᵒᵖ * end_time * BC.ρθ.top.params.Q\n\n        δθ̄ᴬ = abs(θ̄ - θ̄ᴬ) / abs(θ̄ᴬ)\n        println(\"The relative error w.r.t. approximated geometry \", δθ̄ᴬ)\n\n        @test isapprox(0, δθ̄ᴬ; atol = 1e-15)\n    end\n    =#\n\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/sphere/test_hydrostatic_balance.jl",
    "content": "#!/usr/bin/env julia --project\ninclude(\"../shared_source/boilerplate.jl\")\ninclude(\"../three_dimensional/ThreeDimensionalCompressibleNavierStokesEquations.jl\")\ninclude(\"sphere_helper_functions.jl\")\n\nClimateMachine.init()\n\n#! format: off\nrefVals = (\n[\n [ \"state\",     \"ρ\",   1.99999999999600046319e-02,  1.00000000000004396483e+00,  5.10000000000001008083e-01,  2.91613390275248574035e-01 ],\n [ \"state\", \"ρu[1]\",  -3.13311246519756673238e-11,  3.34851852426747048856e-11,  1.50582245544617117858e-13,  4.32064975692569532007e-12 ],\n [ \"state\", \"ρu[2]\",  -3.98988708940419829624e-11,  4.12795751448340701883e-11, -1.66842288197274268178e-14,  4.34989190229689036913e-12 ],\n [ \"state\", \"ρu[3]\",  -4.16490644835465690341e-11,  4.18355107496431387673e-11,  5.18313726923951858909e-14,  4.62898170296138141882e-12 ],\n [ \"state\",    \"ρθ\",  -4.90000000000050732751e+01, -9.79999999998041548821e-01, -2.49900000000000446221e+01,  1.42890561234872102148e+01 ],\n],\n[\n [ \"state\",     \"ρ\",    12,    12,    12,    12 ],\n [ \"state\", \"ρu[1]\",     0,     0,     0,     0 ],\n [ \"state\", \"ρu[2]\",     0,     0,     0,     0 ],\n [ \"state\", \"ρu[3]\",     0,     0,     0,     0 ],\n [ \"state\",    \"ρθ\",    12,    12,    12,    12 ],\n],\n)\n#! format: on\n\n#################\n# RUN THE TESTS #\n#################\n@testset \"$(@__FILE__)\" begin\n\n    ########\n    # Setup physical and numerical domains\n    ########\n    Ω = AtmosDomain(radius = 6e6, height = 1e5)\n    grid = DiscretizedDomain(\n        Ω;\n        elements = (vertical = 4, horizontal = 4),\n        polynomial_order = (vertical = 1, horizontal = 3),\n        overintegration_order = (vertical = 1, horizontal = 1),\n    )\n\n    ########\n    # Define physical parameters and parameterizations\n    ########\n    parameters = (\n        ρₒ = 1, # reference density\n        cₛ = 100.0, # sound speed\n        ν = 1e-5,\n        ∂θ = 0.98 / 1e5,\n        α = 2e-4,\n        g = 10.0,\n        power = 1,\n    )\n\n    ########\n    # Define timestepping parameters\n    ########\n    Δtᴬ = min_node_distance(grid.numerical) / parameters.cₛ * 0.25\n    Δtᴰ = min_node_distance(grid.numerical)^2 / parameters.ν * 0.25\n    Δt = minimum([Δtᴬ, Δtᴰ])\n    start_time = 0\n    end_time = 86400 * 0.5\n\n    method = SSPRK22Heuns\n    timestepper = TimeStepper(method = method, timestep = Δt)\n    callbacks = (Info(), StateCheck(10))# , VTKState(iteration = 2880, filepath = \".\"))\n\n    physics = FluidPhysics(;\n        orientation = SphericalOrientation(),\n        advection = NonLinearAdvectionTerm(),\n        dissipation = ConstantViscosity{Float64}(\n            μ = 0,\n            ν = parameters.ν,\n            κ = 0.0,\n        ),\n        coriolis = SphereCoriolis{Float64}(Ω = 2π / 86400),\n        buoyancy = Buoyancy{Float64}(α = parameters.α, g = parameters.g),\n    )\n\n    ########\n    # Define boundary conditions (west east are the ones that are enforced for a sphere)\n    ########\n    ρu_bcs = (bottom = Impenetrable(NoSlip()), top = Impenetrable(NoSlip()))\n    ρθ_bcs = (bottom = Insulating(), top = Insulating())\n    BC = (ρθ = ρθ_bcs, ρu = ρu_bcs)\n\n    ########\n    # Define initial conditions\n    ########\n    # Earth Spherical Representation\n    # longitude: λ ∈ [-π, π), λ = 0 is the Greenwich meridian\n    # latitude:  ϕ ∈ [-π/2, π/2], ϕ = 0 is the equator\n    # radius:    r ∈ [Rₑ - hᵐⁱⁿ, Rₑ + hᵐᵃˣ], Rₑ = Radius of sphere; hᵐⁱⁿ, hᵐᵃˣ ≥ 0\n\n    ρ₀(p, λ, ϕ, r) =\n        (1 - p.∂θ * (r - 6e6)^p.power / p.power * 1e5^(1 - p.power)) * p.ρₒ\n    ρuʳᵃᵈ(p, λ, ϕ, r) = 0.0\n    ρuˡᵃᵗ(p, λ, ϕ, r) = 0.0\n    ρuˡᵒⁿ(p, λ, ϕ, r) = 0.0\n    ρθ₀(p, λ, ϕ, r) =\n        -ρ₀(p, λ, ϕ, r) *\n        p.∂θ *\n        (r - 6e6)^(p.power - 1) *\n        1e5^(1 - p.power) *\n        (p.cₛ)^2 / (p.α * p.g)\n\n    # Cartesian Representation (boiler plate really)\n    ρ₀ᶜᵃʳᵗ(p, x...) = ρ₀(p, lon(x...), lat(x...), rad(x...))\n    ρu⃗₀ᶜᵃʳᵗ(p, x...) = (\n        ρuʳᵃᵈ(p, lon(x...), lat(x...), rad(x...)) * r̂(x...) +\n        ρuˡᵃᵗ(p, lon(x...), lat(x...), rad(x...)) * ϕ̂(x...) +\n        ρuˡᵒⁿ(p, lon(x...), lat(x...), rad(x...)) * λ̂(x...)\n    )\n    ρθ₀ᶜᵃʳᵗ(p, x...) = ρθ₀(p, lon(x...), lat(x...), rad(x...))\n\n    initial_conditions = (ρ = ρ₀ᶜᵃʳᵗ, ρu = ρu⃗₀ᶜᵃʳᵗ, ρθ = ρθ₀ᶜᵃʳᵗ)\n\n    ########\n    # Create the things\n    ########\n    model = SpatialModel(\n        balance_law = Fluid3D(),\n        physics = physics,\n        numerics = (flux = RoeNumericalFlux(),),\n        grid = grid,\n        boundary_conditions = BC,\n        parameters = parameters,\n    )\n\n    simulation = Simulation(\n        model = model,\n        initial_conditions = initial_conditions,\n        timestepper = timestepper,\n        callbacks = callbacks,\n        time = (; start = start_time, finish = end_time),\n    )\n\n    ########\n    # Run the model\n    ########\n    tic = Base.time()\n    evolve!(simulation, model, refDat = refVals)\n    toc = Base.time()\n    time = toc - tic\n    println(time)\n\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/sphere/test_sphere.jl",
    "content": "#!/usr/bin/env julia --project\n\ninclude(\"../shared_source/boilerplate.jl\")\ninclude(\"../three_dimensional/ThreeDimensionalCompressibleNavierStokesEquations.jl\")\n\nClimateMachine.init()\n\n#! format: off\nrefVals = (\n[\n [ \"state\",     \"ρ\",   9.99999999999999000799e-01,  1.00000000000000066613e+00,  1.00000000000000000000e+00,  3.49433608871729305050e-16 ],\n [ \"state\", \"ρu[1]\",  -1.03159890504820785885e-17,  9.46342010159792552240e-18, -5.38891312633679647218e-19,  2.05248736591058928424e-18 ],\n [ \"state\", \"ρu[2]\",  -1.12556410356665774824e-17,  1.11125313793689735132e-17, -2.17938113394074827612e-22,  2.14911076688864467562e-18 ],\n [ \"state\", \"ρu[3]\",  -1.12757405914899880325e-17,  9.66517285069783167720e-18, -2.95076506235937354400e-19,  2.21386287201671452859e-18 ],\n [ \"state\",    \"ρθ\",   9.99999999999999000799e-01,  1.00000000000000066613e+00,  1.00000000000000000000e+00,  3.49433608871729305050e-16 ],\n],\n[\n [ \"state\",     \"ρ\",    12,    12,    12,     0 ],\n [ \"state\", \"ρu[1]\",     0,     0,     0,     0 ],\n [ \"state\", \"ρu[2]\",     0,     0,     0,     0 ],\n [ \"state\", \"ρu[3]\",     0,     0,     0,     0 ],\n [ \"state\",    \"ρθ\",    12,    12,    12,     0 ],\n],\n)\n#! format: on\n\n#################\n# RUN THE TESTS #\n#################\n@testset \"$(@__FILE__)\" begin\n\n    ########\n    # Setup physical and numerical domains\n    ########\n    Ω = AtmosDomain(radius = 1, height = 0.02)\n    grid = DiscretizedDomain(\n        Ω;\n        elements = (vertical = 1, horizontal = 4),\n        polynomial_order = (vertical = 1, horizontal = 4),\n        overintegration_order = 1,\n    )\n\n    ########\n    # Define timestepping parameters\n    ########\n    start_time = 0\n    end_time = 2.0\n    Δt = 0.05\n    method = SSPRK22Heuns\n    timestepper = TimeStepper(method = method, timestep = Δt)\n    callbacks = (Info(), StateCheck(10))\n\n    ########\n    # Define physical parameters and parameterizations\n    ########\n    parameters = (\n        ρₒ = 1, # reference density\n        cₛ = 1e-2, # sound speed\n    )\n\n    physics = FluidPhysics(;\n        advection = NonLinearAdvectionTerm(),\n        dissipation = ConstantViscosity{Float64}(μ = 0, ν = 0.0, κ = 0.0),\n        coriolis = nothing,\n        buoyancy = Buoyancy{Float64}(α = 2e-4, g = 0),\n    )\n\n    ########\n    # Define boundary conditions (west east are the ones that are enforced for a sphere)\n    ########\n    ρu_bcs = (bottom = Impenetrable(NoSlip()), top = Impenetrable(NoSlip()))\n    ρθ_bcs = (bottom = Insulating(), top = Insulating())\n    BC = (ρθ = ρθ_bcs, ρu = ρu_bcs)\n\n    ########\n    # Define initial conditions\n    ########\n    ρ₀(p, x, y, z) = p.ρₒ\n    ρu₀(p, x...) = ρ₀(p, x...) * -0\n    ρv₀(p, x...) = ρ₀(p, x...) * -0\n    ρw₀(p, x...) = ρ₀(p, x...) * -0\n    ρθ₀(p, x...) = ρ₀(p, x...) * 1.0\n\n    ρu⃗₀(p, x...) = @SVector [ρu₀(p, x...), ρv₀(p, x...), ρw₀(p, x...)]\n    initial_conditions = (ρ = ρ₀, ρu = ρu⃗₀, ρθ = ρθ₀)\n\n    ########\n    # Create the things\n    ########\n    model = SpatialModel(\n        balance_law = Fluid3D(),\n        physics = physics,\n        numerics = (flux = RoeNumericalFlux(),),\n        grid = grid,\n        boundary_conditions = BC,\n        parameters = parameters,\n    )\n\n    simulation = Simulation(\n        model = model,\n        initial_conditions = initial_conditions,\n        timestepper = timestepper,\n        callbacks = callbacks,\n        time = (; start = start_time, finish = end_time),\n    )\n\n    ########\n    # Run the model\n    ########\n    tic = Base.time()\n    evolve!(simulation, model; refDat = refVals)\n    toc = Base.time()\n    time = toc - tic\n    println(time)\n\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/three_dimensional/ThreeDimensionalCompressibleNavierStokesEquations.jl",
    "content": "include(\"../shared_source/boilerplate.jl\")\n\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    init_state_prognostic!,\n    init_state_auxiliary!,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    flux_first_order!,\n    flux_second_order!,\n    source!,\n    wavespeed,\n    boundary_conditions,\n    boundary_state!\nimport ClimateMachine.DGMethods: DGModel\nimport ClimateMachine.NumericalFluxes: numerical_flux_first_order!\nimport ClimateMachine.Orientations: vertical_unit_vector\n\n\"\"\"\n    ThreeDimensionalCompressibleNavierStokesEquations <: BalanceLaw\nA `BalanceLaw` for shallow water modeling.\nwrite out the equations here\n# Usage\n    ThreeDimensionalCompressibleNavierStokesEquations()\n\"\"\"\nabstract type AbstractFluid3D <: AbstractFluid end\nstruct Fluid3D <: AbstractFluid3D end\n\nstruct ThreeDimensionalCompressibleNavierStokesEquations{\n    I,\n    D,\n    O,\n    A,\n    T,\n    C,\n    F,\n    BC,\n    FT,\n} <: AbstractFluid3D\n    initial_value_problem::I\n    domain::D\n    orientation::O\n    advection::A\n    turbulence::T\n    coriolis::C\n    forcing::F\n    boundary_conditions::BC\n    cₛ::FT\n    ρₒ::FT\n    function ThreeDimensionalCompressibleNavierStokesEquations{FT}(\n        initial_value_problem::I,\n        domain::D,\n        orientation::O,\n        advection::A,\n        turbulence::T,\n        coriolis::C,\n        forcing::F,\n        boundary_conditions::BC;\n        cₛ = FT(sqrt(10)),  # m/s\n        ρₒ = FT(1),  #kg/m³\n    ) where {FT <: AbstractFloat, I, D, O, A, T, C, F, BC}\n        return new{I, D, O, A, T, C, F, BC, FT}(\n            initial_value_problem,\n            domain,\n            orientation,\n            advection,\n            turbulence,\n            coriolis,\n            forcing,\n            boundary_conditions,\n            cₛ,\n            ρₒ,\n        )\n    end\nend\n\nCNSE3D = ThreeDimensionalCompressibleNavierStokesEquations\n\nfunction vars_state(m::CNSE3D, ::Prognostic, T)\n    @vars begin\n        ρ::T\n        ρu::SVector{3, T}\n        ρθ::T\n    end\nend\n\nfunction init_state_prognostic!(m::CNSE3D, state::Vars, aux::Vars, localgeo, t)\n    cnse_init_state!(m, state, aux, localgeo, t)\nend\n\n# default initial state if IVP == nothing\nfunction cnse_init_state!(model::CNSE3D, state, aux, localgeo, t)\n\n    x = aux.x\n    y = aux.y\n    z = aux.z\n\n    ρ = model.ρₒ\n    state.ρ = ρ\n    state.ρu = ρ * @SVector [-0, -0, -0]\n    state.ρθ = ρ\n\n    return nothing\nend\n\n# user defined initial state\nfunction cnse_init_state!(\n    model::CNSE3D{<:InitialValueProblem},\n    state,\n    aux,\n    localgeo,\n    t,\n)\n    x = aux.x\n    y = aux.y\n    z = aux.z\n\n    params = model.initial_value_problem.params\n    ic = model.initial_value_problem.initial_conditions\n\n    state.ρ = ic.ρ(params, x, y, z)\n    state.ρu = ic.ρu(params, x, y, z)\n    state.ρθ = ic.ρθ(params, x, y, z)\n\n    return nothing\nend\n\nfunction vars_state(m::CNSE3D, st::Auxiliary, T)\n    @vars begin\n        x::T\n        y::T\n        z::T\n        orientation::vars_state(m.orientation, st, T)\n    end\nend\n\nfunction init_state_auxiliary!(\n    m::CNSE3D,\n    state_auxiliary::MPIStateArray,\n    grid,\n    direction,\n)\n    # update the geopotential Φ in state_auxiliary.orientation.Φ\n    init_state_auxiliary!(\n        m,\n        (m, aux, tmp, geom) ->\n            orientation_nodal_init_aux!(m.orientation, m.domain, aux, geom),\n        state_auxiliary,\n        grid,\n        direction,\n    )\n\n    # update ∇Φ in state_auxiliary.orientation.∇Φ\n    orientation_gradient(m, m.orientation, state_auxiliary, grid, direction)\n\n    # store coordinates and potentially other stuff\n    init_state_auxiliary!(\n        m,\n        (m, aux, tmp, geom) -> cnse_init_aux!(m, aux, geom),\n        state_auxiliary,\n        grid,\n        direction,\n    )\n\n    return nothing\nend\n\nfunction orientation_gradient(\n    model::CNSE3D,\n    ::Orientation,\n    state_auxiliary,\n    grid,\n    direction,\n)\n    auxiliary_field_gradient!(\n        model,\n        state_auxiliary,\n        (\"orientation.∇Φ\",),\n        state_auxiliary,\n        (\"orientation.Φ\",),\n        grid,\n        direction,\n    )\n\n    return nothing\nend\n\nfunction orientation_gradient(::CNSE3D, ::NoOrientation, _...)\n    return nothing\nend\n\nfunction orientation_nodal_init_aux!(\n    ::SphericalOrientation,\n    domain::Tuple,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    norm_R = norm(geom.coord)\n    @inbounds aux.orientation.Φ = norm_R - domain[1]\n\n    return nothing\nend\n\n\"\"\"\nfunction orientation_nodal_init_aux!(\n    ::SuperSphericalOrientation,\n    domain::Tuple,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    norm_R = norm(geom.coord)\n    @inbounds aux.orientation.Φ = 1 / norm_R^2 \nend\n\"\"\"\n\nfunction orientation_nodal_init_aux!(\n    ::FlatOrientation,\n    domain::Tuple,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    @inbounds aux.orientation.Φ = geom.coord[3]\n\n    return nothing\nend\n\nfunction orientation_nodal_init_aux!(\n    ::NoOrientation,\n    domain::Tuple,\n    aux::Vars,\n    geom::LocalGeometry,\n)\n    return nothing\nend\n\nfunction cnse_init_aux!(::CNSE3D, aux, geom)\n    @inbounds begin\n        aux.x = geom.coord[1]\n        aux.y = geom.coord[2]\n        aux.z = geom.coord[3]\n    end\n\n    return nothing\nend\n\nfunction vars_state(m::CNSE3D, ::Gradient, T)\n    @vars begin\n        ∇ρ::T\n        ∇u::SVector{3, T}\n        ∇θ::T\n        ∇p::T\n    end\nend\n\nfunction compute_gradient_argument!(\n    model::CNSE3D,\n    grad::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ρ = state.ρ\n    cₛ = model.cₛ\n    ρₒ = model.ρₒ\n\n    grad.∇p = (cₛ * ρ)^2 / (2 * ρₒ)\n\n    compute_gradient_argument!(model.turbulence, grad, state, aux, t)\nend\n\n@inline function compute_gradient_argument!(\n    ::ConstantViscosity,\n    grad::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ρ = state.ρ\n    ρu = state.ρu\n    ρθ = state.ρθ\n\n    u = ρu\n    θ = ρθ\n\n    grad.∇ρ = ρ\n    grad.∇u = u\n    grad.∇θ = θ\n\n    return nothing\nend\n\nfunction vars_state(m::CNSE3D, ::GradientFlux, T)\n    @vars begin\n        μ∇ρ::SVector{3, T}\n        ν∇u::SMatrix{3, 3, T, 9}\n        κ∇θ::SVector{3, T}\n        ∇p::SVector{3, T}\n    end\nend\n\nfunction compute_gradient_flux!(\n    model::CNSE3D,\n    gradflux::Vars,\n    grad::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n\n    gradflux.∇p = grad.∇p\n\n    compute_gradient_flux!(\n        model,\n        model.turbulence,\n        gradflux,\n        grad,\n        state,\n        aux,\n        t,\n    )\nend\n\n@inline function compute_gradient_flux!(\n    ::CNSE3D,\n    turb::ConstantViscosity,\n    gradflux::Vars,\n    grad::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    μ = turb.μ * I\n    ν = turb.ν * I\n    κ = turb.κ * I\n\n    gradflux.μ∇ρ = -μ * grad.∇ρ\n    gradflux.ν∇u = -ν * grad.∇u\n    gradflux.κ∇θ = -κ * grad.∇θ\n\n    return nothing\nend\n\n@inline function flux_first_order!(\n    model::CNSE3D,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n)\n    ρ = state.ρ\n    ρu = state.ρu\n    ρθ = state.ρθ\n\n    cₛ = model.cₛ\n    ρₒ = model.ρₒ\n\n    flux.ρ += ρu\n    # flux.ρu += (cₛ * ρ)^2 / (2 * ρₒ) * I\n\n    advective_flux!(model, model.advection, flux, state, aux, t)\n\n    return nothing\nend\n\nadvective_flux!(::CNSE3D, ::Nothing, _...) = nothing\n\n@inline function advective_flux!(\n    ::CNSE3D,\n    ::NonLinearAdvectionTerm,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ρ = state.ρ\n    ρu = state.ρu\n    ρθ = state.ρθ\n\n    flux.ρu += ρu ⊗ ρu / ρ\n    flux.ρθ += ρu * ρθ / ρ\n\n    return nothing\nend\n\nfunction flux_second_order!(\n    model::CNSE3D,\n    flux::Grad,\n    state::Vars,\n    gradflux::Vars,\n    ::Vars,\n    aux::Vars,\n    t::Real,\n)\n    flux_second_order!(model, model.turbulence, flux, state, gradflux, aux, t)\nend\n\n@inline function flux_second_order!(\n    ::CNSE3D,\n    ::ConstantViscosity,\n    flux::Grad,\n    state::Vars,\n    gradflux::Vars,\n    aux::Vars,\n    t::Real,\n)\n    flux.ρ += gradflux.μ∇ρ\n    flux.ρu += gradflux.ν∇u\n    flux.ρθ += gradflux.κ∇θ\n\n    return nothing\nend\n\n@inline function source!(\n    model::CNSE3D,\n    source::Vars,\n    state::Vars,\n    gradflux::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n)\n\n    source.ρu -= gradflux.∇p\n\n    coriolis_force!(model, model.coriolis, source, state, aux, t)\n    forcing_term!(model, model.forcing, source, state, aux, t)\n\n    return nothing\nend\n\ncoriolis_force!(::CNSE3D, ::Nothing, _...) = nothing\n\n@inline function coriolis_force!(\n    model::CNSE3D,\n    coriolis::fPlaneCoriolis,\n    source,\n    state,\n    aux,\n    t,\n)\n    # f × u\n    f = [-0, -0, coriolis_parameter(model, coriolis, aux.coords)]\n    ρu = state.ρu\n\n    source.ρu -= f × ρu\n\n    return nothing\nend\n\n@inline function coriolis_force!(\n    model::CNSE3D,\n    coriolis::SphereCoriolis,\n    source,\n    state,\n    aux,\n    t,\n)\n    # f × u\n    Ω = coriolis.Ω\n    f = @SVector [-0, -0, 2Ω]\n    ρu = state.ρu\n    source.ρu -= f × ρu\n    return nothing\nend\n\n\nforcing_term!(::CNSE3D, ::Nothing, _...) = nothing\n\n@inline function forcing_term!(\n    model::CNSE3D,\n    buoy::Buoyancy,\n    source,\n    state,\n    aux,\n    t,\n)\n    α = buoy.α\n    g = buoy.g\n    ρθ = state.ρθ\n\n    # as temperature increase, density decreases\n    B = -α * g * ρθ\n    k̂ = vertical_unit_vector(model.orientation, aux)\n\n    # gravity points downward\n    source.ρu -= B * k̂\nend\n\n@inline vertical_unit_vector(::Orientation, aux) = aux.orientation.∇Φ\n@inline vertical_unit_vector(::NoOrientation, aux) = @SVector [0, 0, 1]\n\n@inline wavespeed(m::CNSE3D, _...) = m.cₛ\n\nroe_average(ρ⁻, ρ⁺, var⁻, var⁺) =\n    (sqrt(ρ⁻) * var⁻ + sqrt(ρ⁺) * var⁺) / (sqrt(ρ⁻) + sqrt(ρ⁺))\n\nfunction numerical_flux_first_order!(\n    ::RoeNumericalFlux,\n    model::CNSE3D,\n    fluxᵀn::Vars{S},\n    n⁻::SVector,\n    state⁻::Vars{S},\n    aux⁻::Vars{A},\n    state⁺::Vars{S},\n    aux⁺::Vars{A},\n    t,\n    direction,\n) where {S, A}\n    numerical_flux_first_order!(\n        CentralNumericalFluxFirstOrder(),\n        model,\n        fluxᵀn,\n        n⁻,\n        state⁻,\n        aux⁻,\n        state⁺,\n        aux⁺,\n        t,\n        direction,\n    )\n\n    FT = eltype(fluxᵀn)\n\n    # constants and normal vectors\n    cₛ = model.cₛ\n    ρₒ = model.ρₒ\n\n    # - states\n    ρ⁻ = state⁻.ρ\n    ρu⁻ = state⁻.ρu\n    ρθ⁻ = state⁻.ρθ\n\n    # constructed states\n    u⁻ = ρu⁻ / ρ⁻\n    θ⁻ = ρθ⁻ / ρ⁻\n    uₙ⁻ = u⁻' * n⁻\n\n    # in general thermodynamics\n    p⁻ = (cₛ * ρ⁻)^2 / (2 * ρₒ)\n    c⁻ = cₛ * sqrt(ρ⁻ / ρₒ)\n\n    # + states\n    ρ⁺ = state⁺.ρ\n    ρu⁺ = state⁺.ρu\n    ρθ⁺ = state⁺.ρθ\n\n    # constructed states\n    u⁺ = ρu⁺ / ρ⁺\n    θ⁺ = ρθ⁺ / ρ⁺\n    uₙ⁺ = u⁺' * n⁻\n\n    # in general thermodynamics\n    p⁺ = (cₛ * ρ⁺)^2 / (2 * ρₒ)\n    c⁺ = cₛ * sqrt(ρ⁺ / ρₒ)\n\n    # construct roe averges\n    ρ = sqrt(ρ⁻ * ρ⁺)\n    u = roe_average(ρ⁻, ρ⁺, u⁻, u⁺)\n    θ = roe_average(ρ⁻, ρ⁺, θ⁻, θ⁺)\n    c = roe_average(ρ⁻, ρ⁺, c⁻, c⁺)\n\n    # construct normal velocity\n    uₙ = u' * n⁻\n\n    # differences\n    Δρ = ρ⁺ - ρ⁻\n    Δp = p⁺ - p⁻\n    Δu = u⁺ - u⁻\n    Δρθ = ρθ⁺ - ρθ⁻\n    Δuₙ = Δu' * n⁻\n\n    # constructed values\n    c⁻² = 1 / c^2\n    w1 = abs(uₙ - c) * (Δp - ρ * c * Δuₙ) * 0.5 * c⁻²\n    w2 = abs(uₙ + c) * (Δp + ρ * c * Δuₙ) * 0.5 * c⁻²\n    w3 = abs(uₙ) * (Δρ - Δp * c⁻²)\n    w4 = abs(uₙ) * ρ\n    w5 = abs(uₙ) * (Δρθ - θ * Δp * c⁻²)\n\n    # fluxes!!!\n    fluxᵀn.ρ -= (w1 + w2 + w3) * 0.5\n    fluxᵀn.ρu -=\n        (\n            w1 * (u - c * n⁻) +\n            w2 * (u + c * n⁻) +\n            w3 * u +\n            w4 * (Δu - Δuₙ * n⁻)\n        ) * 0.5\n    fluxᵀn.ρθ -= ((w1 + w2) * θ + w5) * 0.5\n\n    return nothing\nend\n\nboundary_conditions(model::CNSE3D) = model.boundary_conditions\n\n\"\"\"\n    boundary_state!(nf, ::CNSE3D, args...)\napplies boundary conditions for the hyperbolic fluxes\ndispatches to a function in CNSEBoundaryConditions\n\"\"\"\n@inline function boundary_state!(nf, bc, model::CNSE3D, args...)\n    return _cnse_boundary_state!(nf, bc, model, args...)\nend\n\n\"\"\"\n    cnse_boundary_state!(nf, bc::FluidBC, ::CNSE3D)\nsplits boundary condition application into velocity\n\"\"\"\n@inline function cnse_boundary_state!(nf, bc::FluidBC, m::CNSE3D, args...)\n    cnse_boundary_state!(nf, bc.momentum, m, m.turbulence, args...)\n    cnse_boundary_state!(nf, bc.temperature, m, args...)\n\n    return nothing\nend\n\ninclude(\"bc_momentum.jl\")\ninclude(\"bc_temperature.jl\")\n\n\"\"\"\nSTUFF FOR ANDRE'S WRAPPERS\n\"\"\"\n\nfunction get_boundary_conditions(\n    model::SpatialModel{BL},\n) where {BL <: AbstractFluid3D}\n    bcs = model.boundary_conditions\n\n    west_east = (check_bc(bcs, :west), check_bc(bcs, :east))\n    south_north = (check_bc(bcs, :south), check_bc(bcs, :north))\n    bottom_top = (check_bc(bcs, :bottom), check_bc(bcs, :top))\n\n    return (west_east..., south_north..., bottom_top...)\nend\n\nfunction DGModel(\n    model::SpatialModel{BL};\n    initial_conditions = nothing,\n) where {BL <: AbstractFluid3D}\n    params = model.parameters\n    physics = model.physics\n\n    Lˣ, Lʸ, Lᶻ = length(model.grid.domain)\n    bcs = get_boundary_conditions(model)\n    FT = eltype(model.grid.numerical.vgeo)\n\n    if !isnothing(initial_conditions)\n        initial_conditions = InitialValueProblem(params, initial_conditions)\n    end\n\n    balance_law = CNSE3D{FT}(\n        initial_conditions,\n        (Lˣ, Lʸ, Lᶻ),\n        physics.orientation,\n        physics.advection,\n        physics.dissipation,\n        physics.coriolis,\n        physics.buoyancy,\n        bcs,\n        ρₒ = params.ρₒ,\n        cₛ = params.cₛ,\n    )\n\n    numerical_flux_first_order = model.numerics.flux # should be a function\n\n    rhs = DGModel(\n        balance_law,\n        model.grid.numerical,\n        numerical_flux_first_order,\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    return rhs\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/three_dimensional/bc_momentum.jl",
    "content": "\"\"\"\n    cnse_boundary_state!(::NumericalFluxFirstOrder, ::Impenetrable{FreeSlip}, ::CNSE3D)\napply free slip boundary condition for velocity\nsets reflective ghost point\n\"\"\"\n@inline function cnse_boundary_state!(\n    ::NumericalFluxFirstOrder,\n    ::Impenetrable{FreeSlip},\n    ::CNSE3D,\n    ::TurbulenceClosure,\n    state⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    state⁺.ρ = state⁻.ρ\n\n    ρu⁻ = state⁻.ρu\n    state⁺.ρu = ρu⁻ - 2 * n⁻ ⋅ ρu⁻ .* SVector(n⁻)\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxGradient, ::Impenetrable{FreeSlip}, ::CNSE3D)\napply free slip boundary condition for velocity\nsets non-reflective ghost point\n\"\"\"\nfunction cnse_boundary_state!(\n    ::NumericalFluxGradient,\n    ::Impenetrable{FreeSlip},\n    ::CNSE3D,\n    ::ConstantViscosity,\n    state⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    state⁺.ρ = state⁻.ρ\n\n    ρu⁻ = state⁻.ρu\n    state⁺.ρu = ρu⁻ - n⁻ ⋅ ρu⁻ .* SVector(n⁻)\n\n    return nothing\nend\n\n\"\"\"\n    shallow_normal_boundary_flux_second_order!(::NumericalFluxSecondOrder, ::Impenetrable{FreeSlip}, ::CNSE3D)\napply free slip boundary condition for velocity\napply zero numerical flux in the normal direction\n\"\"\"\nfunction cnse_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    ::Impenetrable{FreeSlip},\n    ::CNSE3D,\n    ::ConstantViscosity,\n    state⁺,\n    gradflux⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    gradflux⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    state⁺.ρu = state⁻.ρu\n    gradflux⁺.ν∇u = n⁻ * (@SVector [-0, -0, -0])'\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxFirstOrder, ::Impenetrable{NoSlip}, ::CNSE3D)\napply no slip boundary condition for velocity\nsets reflective ghost point\n\"\"\"\n@inline function cnse_boundary_state!(\n    ::NumericalFluxFirstOrder,\n    ::Impenetrable{NoSlip},\n    ::CNSE3D,\n    ::TurbulenceClosure,\n    state⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    state⁺.ρ = state⁻.ρ\n    state⁺.ρu = -state⁻.ρu\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxGradient, ::Impenetrable{NoSlip}, ::CNSE3D)\napply no slip boundary condition for velocity\nset numerical flux to zero for U\n\"\"\"\n@inline function cnse_boundary_state!(\n    ::NumericalFluxGradient,\n    ::Impenetrable{NoSlip},\n    ::CNSE3D,\n    ::ConstantViscosity,\n    state⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    FT = eltype(state⁺)\n    state⁺.ρu = @SVector zeros(FT, 3)\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxSecondOrder, ::Impenetrable{NoSlip}, ::CNSE3D)\napply no slip boundary condition for velocity\nsets ghost point to have no numerical flux on the boundary for U\n\"\"\"\n@inline function cnse_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    ::Impenetrable{NoSlip},\n    ::CNSE3D,\n    ::ConstantViscosity,\n    state⁺,\n    gradflux⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    gradflux⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    state⁺.ρu = -state⁻.ρu\n    gradflux⁺.ν∇u = gradflux⁻.ν∇u\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::Penetrable{FreeSlip}, ::CNSE3D)\nno mass boundary condition for penetrable\n\"\"\"\ncnse_boundary_state!(\n    ::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    ::Penetrable{FreeSlip},\n    ::CNSE3D,\n    ::ConstantViscosity,\n    _...,\n) = nothing\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxSecondOrder, ::Penetrable{FreeSlip}, ::CNSE3D)\napply free slip boundary condition for velocity\napply zero numerical flux in the normal direction\n\"\"\"\nfunction cnse_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    ::Penetrable{FreeSlip},\n    ::CNSE3D,\n    ::ConstantViscosity,\n    state⁺,\n    gradflux⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    gradflux⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    state⁺.ρu = state⁻.ρu\n    gradflux⁺.ν∇u = n⁻ * (@SVector [-0, -0, -0])'\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::Impenetrable{MomentumFlux}, ::HBModel)\napply kinematic stress boundary condition for velocity\napplies free slip conditions for first-order and gradient fluxes\n\"\"\"\nfunction cnse_boundary_state!(\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    ::Impenetrable{<:MomentumFlux},\n    model::CNSE3D,\n    turb::TurbulenceClosure,\n    args...,\n)\n    return cnse_boundary_state!(\n        nf,\n        Impenetrable(FreeSlip()),\n        model,\n        turb,\n        args...,\n    )\nend\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxSecondOrder, ::Impenetrable{MomentumFlux}, ::HBModel)\napply kinematic stress boundary condition for velocity\nsets ghost point to have specified flux on the boundary for ν∇u\n\"\"\"\n@inline function cnse_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    bc::Impenetrable{<:MomentumFlux},\n    model::CNSE3D,\n    ::ConstantViscosity,\n    state⁺,\n    gradflux⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    gradflux⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    state⁺.ρu = state⁻.ρu\n    gradflux⁺.ν∇u = n⁻ * bc.drag(state⁻, aux⁻, t)'\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::Penetrable{MomentumFlux}, ::HBModel)\napply kinematic stress boundary condition for velocity\napplies free slip conditions for first-order and gradient fluxes\n\"\"\"\nfunction cnse_boundary_state!(\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    ::Penetrable{<:MomentumFlux},\n    model::CNSE3D,\n    turb::TurbulenceClosure,\n    args...,\n)\n    return cnse_boundary_state!(\n        nf,\n        Penetrable(FreeSlip()),\n        model,\n        turb,\n        args...,\n    )\nend\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxSecondOrder, ::Penetrable{MomentumFlux}, ::HBModel)\napply kinematic stress boundary condition for velocity\nsets ghost point to have specified flux on the boundary for ν∇u\n\"\"\"\n@inline function cnse_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    bc::Penetrable{<:MomentumFlux},\n    shallow::CNSE3D,\n    ::ConstantViscosity,\n    state⁺,\n    gradflux⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    gradflux⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    state⁺.ρu = state⁻.ρu\n    gradflux⁺.ν∇u = n⁻ * bc.drag(state⁻, aux⁻, t)'\n\n    return nothing\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/three_dimensional/bc_temperature.jl",
    "content": "\"\"\"\n    cnse_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::Insulating, ::HBModel)\n\napply insulating boundary condition for temperature\nsets transmissive ghost point\n\"\"\"\nfunction cnse_boundary_state!(\n    ::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    ::Insulating,\n    ::CNSE3D,\n    state⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    aux⁻,\n    t,\n)\n    state⁺.ρθ = state⁻.ρθ\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxSecondOrder, ::Insulating, ::HBModel)\n\napply insulating boundary condition for velocity\nsets ghost point to have no numerical flux on the boundary for κ∇θ\n\"\"\"\n@inline function cnse_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    ::Insulating,\n    ::CNSE3D,\n    state⁺,\n    gradflux⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    gradflux⁻,\n    aux⁻,\n    t,\n)\n    state⁺.ρθ = state⁻.ρθ\n    gradflux⁺.κ∇θ = n⁻ * -0\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::TemperatureFlux, ::HBModel)\n\napply temperature flux boundary condition for velocity\napplies insulating conditions for first-order and gradient fluxes\n\"\"\"\nfunction cnse_boundary_state!(\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    ::TemperatureFlux,\n    model::CNSE3D,\n    args...,\n)\n    return cnse_boundary_state!(nf, Insulating(), model, args...)\nend\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxSecondOrder, ::TemperatureFlux, ::HBModel)\n\napply insulating boundary condition for velocity\nsets ghost point to have specified flux on the boundary for κ∇θ\n\"\"\"\n@inline function cnse_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    bc::TemperatureFlux,\n    ::CNSE3D,\n    state⁺,\n    gradflux⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    gradflux⁻,\n    aux⁻,\n    t,\n)\n    state⁺.ρθ = state⁻.ρθ\n    gradflux⁺.κ∇θ = n⁻ * bc(state⁻, aux⁻, t)\n\n    return nothing\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/three_dimensional/config_sphere.jl",
    "content": "include(\"../CNSE.jl\")\ninclude(\"ThreeDimensionalCompressibleNavierStokesEquations.jl\")\n\nfunction Config(\n    name,\n    resolution,\n    domain,\n    params;\n    numerical_flux_first_order = RoeNumericalFlux(),\n    Nover = 0,\n    boundary = (1, 1),\n    boundary_conditons = (FluidBC(Impenetrable(FreeSlip()), Insulating()),),\n)\n    mpicomm = MPI.COMM_WORLD\n    ArrayType = ClimateMachine.array_type()\n\n    println(string(resolution.Nᶻ) * \" elems in the vertical\")\n\n    vert_range =\n        grid1d(domain.min_height, domain.max_height, nelem = resolution.Nᶻ)\n\n    println(\n        string(resolution.Nʰ) * \"x\" * string(resolution.Nʰ) * \" elems per face\",\n    )\n\n    topology = StackedCubedSphereTopology(\n        mpicomm,\n        resolution.Nʰ,\n        vert_range;\n        boundary = boundary,\n    )\n\n    println(\"poly order is \" * string(resolution.N))\n    println(\"OI order is \" * string(Nover))\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = resolution.N + Nover,\n        meshwarp = equiangular_cubed_sphere_warp,\n    )\n\n    model = CNSE3D{FT}(\n        nothing,\n        (domain.min_height, domain.max_height),\n        ClimateMachine.Orientations.SphericalOrientation(),\n        NonLinearAdvectionTerm(),\n        ConstantViscosity{FT}(μ = params.μ, ν = params.ν, κ = params.κ),\n        nothing,\n        nothing,\n        boundary_conditons;\n        cₛ = params.cₛ,\n        ρₒ = params.ρₒ,\n    )\n\n    dg = DGModel(\n        model,\n        grid,\n        numerical_flux_first_order,\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    return Config(name, dg, Nover, mpicomm, ArrayType)\nend\n\nfunction cnse_init_aux!(::CNSE3D, aux, geom)\n    @inbounds begin\n        aux.x = geom.coord[1]\n        aux.y = geom.coord[2]\n        aux.z = geom.coord[3]\n    end\n\n    return nothing\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/three_dimensional/refvals_bickley_jet.jl",
    "content": "# [\n#  [ MPIStateArray Name, Field Name, Maximum, Minimum, Mean, Standard Deviation ],\n#  [         :                :          :        :      :          :           ],\n# ]\n\n#! format: off\nfirst_order = (\n[\n [ \"state\",     \"ρ\",   9.88641470218911577739e-01,  1.00735927139044068035e+00,  1.00000000000000244249e+00,  1.32618819574356725140e-03 ],\n [ \"state\", \"ρu[1]\",  -2.42850976605026747102e-01,  5.73364756714398238202e-01,  1.59153832831308655882e-01,  9.67668639083398146594e-02 ],\n [ \"state\", \"ρu[2]\",  -4.12933704690272018745e-01,  4.50172304099278386413e-01, -4.55511721449193612529e-14,  1.48850741581273815495e-01 ],\n [ \"state\", \"ρu[3]\",  -3.70076433121673098459e-01,  3.26009261713208653433e-01, -6.46953234297288930041e-14,  8.65065044705036617634e-02 ],\n [ \"state\",    \"ρθ\",  -1.71139241110591289186e+00,  1.79353274817685659492e+00, -7.54540463032282362914e-16,  3.87893456476833764501e-01 ],\n],\n[\n [\"state\",     \"ρ\", 12, 12, 12, 12],\n [\"state\", \"ρu[1]\", 11, 11, 12, 12],\n [\"state\", \"ρu[2]\", 12, 11,  0, 12],\n [\"state\", \"ρu[3]\", 12, 11,  0, 12],\n [\"state\",    \"ρθ\", 11, 10,  0, 12],\n],\n)\n\nfourth_order = (\n[\n [ \"state\",     \"ρ\",   9.79756567265035793746e-01,  1.00986882291859325633e+00,  9.99972252557182250676e-01,  1.27738464370501453651e-03 ],\n [ \"state\", \"ρu[1]\",  -3.85746589538976891731e-01,  7.12308328222086784010e-01,  1.58403896799433618892e-01,  1.03266735858658170732e-01 ],\n [ \"state\", \"ρu[2]\",  -5.86525244955121705104e-01,  6.33258737050533038193e-01,  2.62244534853688972004e-04,  1.37491363204364197559e-01 ],\n [ \"state\", \"ρu[3]\",  -4.58727280284793481613e-01,  4.82564240905019259387e-01, -2.81636420608788126414e-04,  8.90227260571099660025e-02 ],\n [ \"state\",    \"ρθ\",  -4.51696345879803118351e+00,  4.14217705122501378412e+00, -5.08780751729477078403e-04,  3.32659156438805059253e-01 ],\n],\n[\n [\"state\",     \"ρ\", 8, 8, 10, 7],\n [\"state\", \"ρu[1]\", 5, 5,  8, 7],\n [\"state\", \"ρu[2]\", 6, 5,  0, 6],\n [\"state\", \"ρu[3]\", 6, 5,  0, 7],\n [\"state\",    \"ρθ\", 5, 4,  0, 6],\n],\n)\n\n#! format: on\n\nrefVals = (; first_order, fourth_order)\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/three_dimensional/refvals_buoyancy.jl",
    "content": "# [\n#  [ MPIStateArray Name, Field Name, Maximum, Minimum, Mean, Standard Deviation ],\n#  [         :                :          :        :      :          :           ],\n# ]\n\n#! format: off\n\nparr = [\n [\"state\",     \"ρ\", 12, 12, 12, 12],\n [\"state\", \"ρu[1]\",  0,  0,  0,  0],\n [\"state\", \"ρu[2]\",  0,  0,  0,  0],\n [\"state\", \"ρu[3]\", 12,  8, 12, 12],\n [\"state\",    \"ρθ\", 12, 10, 12, 12],\n]\n\nsecond_order_flat = (\n[\n [ \"state\",     \"ρ\",   9.95252314022507689195e-01,  9.99992856011554298590e-01,  9.98330419819817738158e-01,  1.48639562654353791886e-03 ],\n [ \"state\", \"ρu[1]\",  -6.36093867411581375298e-15,  8.28629359748938561747e-15,  5.16470747993988398929e-16,  1.17216775586980107502e-15 ],\n [ \"state\", \"ρu[2]\",  -3.70797682276813316617e-15,  9.42818503691220985643e-15,  6.07164354178963622023e-16,  1.23143801722553508699e-15 ],\n [ \"state\", \"ρu[3]\",  -1.65133743521589989450e-03,  5.29367075491514133403e-09, -8.40309050919211576736e-04,  4.66618546037470796790e-04 ],\n [ \"state\",    \"ρθ\",  -9.95249493245247940365e+00,  1.99973806108046075331e-05, -4.98740538889376860965e+00,  2.91965149708573168397e+00 ],\n],\n parr,\n)\n\nsecond_order = (\n[\n [ \"state\",     \"ρ\",   9.95252314022507689195e-01,  9.99992856011554298590e-01,  9.98330419819817738158e-01,  1.48639562654353791886e-03 ],\n [ \"state\", \"ρu[1]\",  -6.36142448250610767485e-15,  8.28656656189152269018e-15,  5.17120118507278235699e-16,  1.17302082244252352324e-15 ],\n [ \"state\", \"ρu[2]\",  -3.70772154095122499196e-15,  9.42925797553789671672e-15,  6.06575884435408245721e-16,  1.23026363135339100359e-15 ],\n [ \"state\", \"ρu[3]\",  -1.65133743521588883564e-03,  5.29367075398690075732e-09, -8.40309050919211468315e-04,  4.66618546037470417320e-04 ],\n [ \"state\",    \"ρθ\",  -9.95249493245247940365e+00,  1.99973806108054952236e-05, -4.98740538889376860965e+00,  2.91965149708573168397e+00 ],\n],\n parr,\n)\n\nfourth_order_flat = (\n[\n [ \"state\",     \"ρ\",   9.95377495534709000324e-01,  9.99992951378667060958e-01,  9.98321272635789513927e-01,  1.50722816639464090097e-03 ],\n [ \"state\", \"ρu[1]\",  -1.22866254290737312252e-14,  1.95946879007561421956e-14,  1.73741725325975513295e-15,  4.23083086870931785383e-15 ],\n [ \"state\", \"ρu[2]\",  -1.63058197687820133665e-14,  2.71134092821328864807e-14,  1.75302916450504673999e-15,  4.33921909208334743316e-15 ],\n [ \"state\", \"ρu[3]\",  -1.66252523985498286418e-03,  5.55021884647818839491e-08, -8.13842490777055664608e-04,  4.76414940515918383830e-04 ],\n [ \"state\",    \"ρθ\",  -9.95373884733410818626e+00, -4.05672848247591079102e-07, -4.98722877855243940104e+00,  2.97859288054288384728e+00 ],\n],\n parr,\n)\n\nfourth_order = (\n[\n [ \"state\",     \"ρ\",   9.95377495534709000324e-01,  9.99992951378667060958e-01,  9.98321272635789513927e-01,  1.50722816639464068413e-03 ],\n [ \"state\", \"ρu[1]\",  -1.22778750186973996879e-14,  1.95893093780676565332e-14,  1.73744809621522011733e-15,  4.23044883848200063451e-15 ],\n [ \"state\", \"ρu[2]\",  -1.62568523313266733927e-14,  2.71071963864711276060e-14,  1.75189805678757112174e-15,  4.33763908149213610479e-15 ],\n [ \"state\", \"ρu[3]\",  -1.66252523985503642377e-03,  5.55021884639224168229e-08, -8.13842490777055447768e-04,  4.76414940515918817511e-04 ],\n [ \"state\",    \"ρθ\",  -9.95373884733410818626e+00, -4.05672848142820462126e-07, -4.98722877855243940104e+00,  2.97859288054288384728e+00 ],\n],\n parr,\n)\n\n#! format: on\n\nrefVals = (; second_order, fourth_order, second_order_flat, fourth_order_flat)\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/three_dimensional/run_bickley_jet.jl",
    "content": "#!/usr/bin/env julia --project\n\ninclude(\"../shared_source/boilerplate.jl\")\ninclude(\"ThreeDimensionalCompressibleNavierStokesEquations.jl\")\n\nClimateMachine.init()\n\n########\n# Setup physical and numerical domains\n########\nΩˣ = IntervalDomain(-2π, 2π, periodic = true)\nΩʸ = IntervalDomain(-2π, 2π, periodic = true)\nΩᶻ = IntervalDomain(-2π, 2π, periodic = true)\n\ngrid = DiscretizedDomain(\n    Ωˣ × Ωʸ × Ωᶻ;\n    elements = 13,\n    polynomial_order = 4,\n    overintegration_order = 1,\n)\n\n########\n# Define timestepping parameters\n########\nstart_time = 0\nend_time = 200.0\nΔt = 0.004\nmethod = SSPRK22Heuns\n\ntimestepper = TimeStepper(method = method, timestep = Δt)\n\ncallbacks = (Info(), StateCheck(10))\n\n########\n# Define physical parameters and parameterizations\n########\nparameters = (\n    ϵ = 0.1,  # perturbation size for initial condition\n    l = 0.5, # Gaussian width\n    k = 0.5, # Sinusoidal wavenumber\n    ρₒ = 1, # reference density\n    cₛ = sqrt(10), # sound speed\n)\n\nphysics = FluidPhysics(;\n    advection = NonLinearAdvectionTerm(),\n    dissipation = ConstantViscosity{Float64}(μ = 0, ν = 0, κ = 0),\n    coriolis = nothing,\n    buoyancy = nothing,\n)\n\n########\n# Define initial conditions\n########\n\n# The Bickley jet\nU₀(p, x, y, z) = cosh(y)^(-2)\nV₀(p, x, y, z) = 0\nW₀(p, x, y, z) = 0\n\n# Slightly off-center vortical perturbations\nΨ₁(p, x, y, z) =\n    exp(-(y + p.l / 10)^2 / (2 * (p.l^2))) * cos(p.k * x) * cos(p.k * y)\nΨ₂(p, x, y, z) =\n    exp(-(z + p.l / 10)^2 / (2 * (p.l^2))) * cos(p.k * y) * cos(p.k * z)\n\n# Vortical velocity fields (u, v, w) = (-∂ʸ, +∂ˣ, 0) Ψ₁ + (0, -∂ᶻ, +∂ʸ)Ψ₂ \nu₀(p, x, y, z) =\n    Ψ₁(p, x, y, z) * (p.k * tan(p.k * y) + y / (p.l^2) + 1 / (10 * p.l))\nv₀(p, x, y, z) =\n    Ψ₂(p, x, y, z) * (p.k * tan(p.k * z) + z / (p.l^2) + 1 / (10 * p.l)) -\n    Ψ₁(p, x, y, z) * p.k * tan(p.k * x)\nw₀(p, x, y, z) = -Ψ₂(p, x, y, z) * p.k * tan(p.k * y)\nθ₀(p, x, y, z) = sin(p.k * y)\n\nρ₀(p, x, y, z) = p.ρₒ\nρu₀(p, x...) = ρ₀(p, x...) * (p.ϵ * u₀(p, x...) + U₀(p, x...))\nρv₀(p, x...) = ρ₀(p, x...) * (p.ϵ * v₀(p, x...) + V₀(p, x...))\nρw₀(p, x...) = ρ₀(p, x...) * (p.ϵ * w₀(p, x...) + W₀(p, x...))\nρθ₀(p, x...) = ρ₀(p, x...) * θ₀(p, x...)\n\nρu⃗₀(p, x...) = @SVector [ρu₀(p, x...), ρv₀(p, x...), ρw₀(p, x...)]\ninitial_conditions = (ρ = ρ₀, ρu = ρu⃗₀, ρθ = ρθ₀)\n\n########\n# Create the things\n########\n\nmodel = SpatialModel(\n    balance_law = Fluid3D(),\n    physics = physics,\n    numerics = (flux = RoeNumericalFlux(),),\n    grid = grid,\n    boundary_conditions = NamedTuple(),\n    parameters = parameters,\n)\n\nsimulation = Simulation(\n    model = model,\n    initial_conditions = initial_conditions,\n    timestepper = timestepper,\n    callbacks = callbacks,\n    time = (; start = start_time, finish = end_time),\n)\n\n########\n# Run the model\n########\n\ntic = Base.time()\n\nevolve!(simulation, model)\n\ntoc = Base.time()\ntime = toc - tic\nprintln(time)\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/three_dimensional/run_box.jl",
    "content": "#!/usr/bin/env julia --project\n\ninclude(\"../shared_source/boilerplate.jl\")\ninclude(\"ThreeDimensionalCompressibleNavierStokesEquations.jl\")\n\nClimateMachine.init()\n\n########\n# Setup physical and numerical domains\n########\nΩˣ = IntervalDomain(-2π, 2π, periodic = true)\nΩʸ = IntervalDomain(-2π, 2π, periodic = true)\nΩᶻ = IntervalDomain(-2π, 2π, periodic = false)\n\ngrid = DiscretizedDomain(\n    Ωˣ × Ωʸ × Ωᶻ;\n    elements = 8,\n    polynomial_order = 1,\n    overintegration_order = 1,\n)\n\n########\n# Define timestepping parameters\n########\nstart_time = 0\nend_time = 200.0\nΔt = 0.05\nmethod = SSPRK22Heuns\n\ntimestepper = TimeStepper(method = method, timestep = Δt)\n\ncallbacks = (Info(), StateCheck(10))\n\n########\n# Define physical parameters and parameterizations\n########\nparameters = (\n    ρₒ = 1, # reference density\n    cₛ = sqrt(10), # sound speed\n)\n\nphysics = FluidPhysics(;\n    advection = NonLinearAdvectionTerm(),\n    dissipation = ConstantViscosity{Float64}(μ = 0, ν = 1e-2, κ = 1e-2),\n    coriolis = nothing,\n    buoyancy = Buoyancy{Float64}(α = 2e-4, g = 10),\n)\n\n########\n# Define boundary conditions\n########\nρu_bcs = (\n    bottom = Impenetrable(NoSlip()),\n    top = Impenetrable(MomentumFlux(\n        flux = (p, state, aux, t) -> (@SVector [p.τ / state.ρ, -0, -0]),\n        params = (τ = 0.01,),\n    )),\n)\nρθ_bcs = (\n    bottom = Insulating(),\n    top = TemperatureFlux(flux = (p, state, aux, t) -> (p.Q)),\n    params = (Q = 0.1,), # positive means removing heat\n)\nBC = (ρθ = ρθ_bcs, ρu = ρu_bcs)\n\n########\n# Define initial conditions\n########\n\nρ₀(p, x, y, z) = p.ρₒ\nρu₀(p, x...) = ρ₀(p, x...) * -0\nρv₀(p, x...) = ρ₀(p, x...) * -0\nρw₀(p, x...) = ρ₀(p, x...) * -0\nρθ₀(p, x...) = ρ₀(p, x...) * 5\n\nρu⃗₀(p, x...) = @SVector [ρu₀(p, x...), ρv₀(p, x...), ρw₀(p, x...)]\ninitial_conditions = (ρ = ρ₀, ρu = ρu⃗₀, ρθ = ρθ₀)\n\n########\n# Create the things\n########\n\nmodel = SpatialModel(\n    balance_law = Fluid3D(),\n    physics = physics,\n    numerics = (flux = RoeNumericalFlux(),),\n    grid = grid,\n    boundary_conditions = BC,\n    parameters = parameters,\n)\n\nsimulation = Simulation(\n    model = model,\n    initial_conditions = initial_conditions,\n    timestepper = timestepper,\n    callbacks = callbacks,\n    time = (; start = start_time, finish = end_time),\n)\n\n########\n# Run the model\n########\n\ntic = Base.time()\n\nevolve!(simulation, model)\n\ntoc = Base.time()\ntime = toc - tic\nprintln(time)\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/three_dimensional/run_taylor_green_vortex.jl",
    "content": "#!/usr/bin/env julia --project\n\ninclude(\"../shared_source/boilerplate.jl\")\ninclude(\"ThreeDimensionalCompressibleNavierStokesEquations.jl\")\n\nClimateMachine.init()\n\n########\n# Setup physical and numerical domains\n########\nΩˣ = IntervalDomain(-2π, 2π, periodic = true)\nΩʸ = IntervalDomain(-2π, 2π, periodic = true)\nΩᶻ = IntervalDomain(-2π, 2π, periodic = true)\n\ngrid = DiscretizedDomain(\n    Ωˣ × Ωʸ × Ωᶻ;\n    elements = 16,\n    polynomial_order = 1,\n    overintegration_order = 1,\n)\n\n########\n# Define timestepping parameters\n########\nstart_time = 0\nend_time = 200.0\nΔt = 0.01\nmethod = SSPRK22Heuns\n\ntimestepper = TimeStepper(method = method, timestep = Δt)\n\ncallbacks = (Info(), StateCheck(10))\n\n########\n# Define physical parameters and parameterizations\n########\nparameters = (\n    Uₒ = 1, # reference velocity\n    ρₒ = 1, # reference density\n    cₛ = sqrt(10), # sound speed\n)\n\nphysics = FluidPhysics(;\n    advection = NonLinearAdvectionTerm(),\n    dissipation = ConstantViscosity{Float64}(μ = 0, ν = 1e-3, κ = 1e-3),\n    coriolis = nothing,\n    buoyancy = nothing,\n)\n\n########\n# Define initial conditions\n########\n\nu₀(p, x, y, z) = p.Uₒ * sin(x) * cos(y) * cos(z)\nv₀(p, x, y, z) = -p.Uₒ * cos(x) * sin(y) * cos(z)\nw₀(p, x, y, z) = -0\nθ₀(p, x, y, z) = sin(0.5 * z)\n\nρ₀(p, x, y, z) = p.ρₒ\nρu₀(p, x...) = ρ₀(p, x...) * u₀(p, x...)\nρv₀(p, x...) = ρ₀(p, x...) * v₀(p, x...)\nρw₀(p, x...) = ρ₀(p, x...) * w₀(p, x...)\nρθ₀(p, x...) = ρ₀(p, x...) * θ₀(p, x...)\n\nρu⃗₀(p, x...) = @SVector [ρu₀(p, x...), ρv₀(p, x...), ρw₀(p, x...)]\ninitial_conditions = (ρ = ρ₀, ρu = ρu⃗₀, ρθ = ρθ₀)\n\n########\n# Create the things\n########\n\nmodel = SpatialModel(\n    balance_law = Fluid3D(),\n    physics = physics,\n    numerics = (flux = RoeNumericalFlux(),),\n    grid = grid,\n    boundary_conditions = NamedTuple(),\n    parameters = parameters,\n)\n\nsimulation = Simulation(\n    model = model,\n    initial_conditions = initial_conditions,\n    timestepper = timestepper,\n    callbacks = callbacks,\n    time = (; start = start_time, finish = end_time),\n)\n\n########\n# Run the model\n########\n\ntic = Base.time()\n\nevolve!(simulation, model)\n\ntoc = Base.time()\ntime = toc - tic\nprintln(time)\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/three_dimensional/test_bickley_jet.jl",
    "content": "#!/usr/bin/env julia --project\n\ninclude(\"../shared_source/boilerplate.jl\")\ninclude(\"ThreeDimensionalCompressibleNavierStokesEquations.jl\")\n\nClimateMachine.init()\n\n#################\n# RUN THE TESTS #\n#################\n@testset \"$(@__FILE__)\" begin\n\n    include(\"refvals_bickley_jet.jl\")\n\n    ########\n    # Setup physical and numerical domains\n    ########\n    Ωˣ = IntervalDomain(-2π, 2π, periodic = true)\n    Ωʸ = IntervalDomain(-2π, 2π, periodic = true)\n    Ωᶻ = IntervalDomain(-2π, 2π, periodic = true)\n\n    first_order = DiscretizedDomain(\n        Ωˣ × Ωʸ × Ωᶻ;\n        elements = 32,\n        polynomial_order = 1,\n        overintegration_order = 1,\n    )\n\n    fourth_order = DiscretizedDomain(\n        Ωˣ × Ωʸ × Ωᶻ;\n        elements = 13,\n        polynomial_order = 4,\n        overintegration_order = 1,\n    )\n\n    grids = Dict(\"first_order\" => first_order, \"fourth_order\" => fourth_order)\n\n    ########\n    # Define timestepping parameters\n    ########\n    start_time = 0\n    end_time = 100.0\n    Δt = 0.004\n    method = SSPRK22Heuns\n\n    timestepper = TimeStepper(method = method, timestep = Δt)\n\n    callbacks = (Info(), StateCheck(10))\n\n    ########\n    # Define physical parameters and parameterizations\n    ########\n    parameters = (\n        ϵ = 0.1,  # perturbation size for initial condition\n        l = 0.5, # Gaussian width\n        k = 0.5, # Sinusoidal wavenumber\n        ρₒ = 1, # reference density\n        cₛ = sqrt(10), # sound speed\n    )\n\n    physics = FluidPhysics(;\n        advection = NonLinearAdvectionTerm(),\n        dissipation = ConstantViscosity{Float64}(μ = 0, ν = 0, κ = 0),\n        coriolis = nothing,\n        buoyancy = nothing,\n    )\n\n    ########\n    # Define initial conditions\n    ########\n\n    # The Bickley jet\n    U₀(p, x, y, z) = cosh(y)^(-2)\n    V₀(p, x, y, z) = 0\n    W₀(p, x, y, z) = 0\n\n    # Slightly off-center vortical perturbations\n    Ψ₁(p, x, y, z) =\n        exp(-(y + p.l / 10)^2 / (2 * (p.l^2))) * cos(p.k * x) * cos(p.k * y)\n    Ψ₂(p, x, y, z) =\n        exp(-(z + p.l / 10)^2 / (2 * (p.l^2))) * cos(p.k * y) * cos(p.k * z)\n\n    # Vortical velocity fields (u, v, w) = (-∂ʸ, +∂ˣ, 0) Ψ₁ + (0, -∂ᶻ, +∂ʸ)Ψ₂ \n    u₀(p, x, y, z) =\n        Ψ₁(p, x, y, z) * (p.k * tan(p.k * y) + y / (p.l^2) + 1 / (10 * p.l))\n    v₀(p, x, y, z) =\n        Ψ₂(p, x, y, z) * (p.k * tan(p.k * z) + z / (p.l^2) + 1 / (10 * p.l)) -\n        Ψ₁(p, x, y, z) * p.k * tan(p.k * x)\n    w₀(p, x, y, z) = -Ψ₂(p, x, y, z) * p.k * tan(p.k * y)\n    θ₀(p, x, y, z) = sin(p.k * y)\n\n    ρ₀(p, x, y, z) = p.ρₒ\n    ρu₀(p, x...) = ρ₀(p, x...) * (p.ϵ * u₀(p, x...) + U₀(p, x...))\n    ρv₀(p, x...) = ρ₀(p, x...) * (p.ϵ * v₀(p, x...) + V₀(p, x...))\n    ρw₀(p, x...) = ρ₀(p, x...) * (p.ϵ * w₀(p, x...) + W₀(p, x...))\n    ρθ₀(p, x...) = ρ₀(p, x...) * θ₀(p, x...)\n\n    ρu⃗₀(p, x...) = @SVector [ρu₀(p, x...), ρv₀(p, x...), ρw₀(p, x...)]\n    initial_conditions = (ρ = ρ₀, ρu = ρu⃗₀, ρθ = ρθ₀)\n\n    for (key, grid) in grids\n        @testset \"$(key)\" begin\n            model = SpatialModel(\n                balance_law = Fluid3D(),\n                physics = physics,\n                numerics = (flux = RoeNumericalFlux(),),\n                grid = grid,\n                boundary_conditions = NamedTuple(),\n                parameters = parameters,\n            )\n\n            simulation = Simulation(\n                model = model,\n                initial_conditions = initial_conditions,\n                timestepper = timestepper,\n                callbacks = callbacks,\n                time = (; start = start_time, finish = end_time),\n            )\n\n            ########\n            # Run the model\n            ########\n            evolve!(\n                simulation,\n                model;\n                refDat = getproperty(refVals, Symbol(key)),\n            )\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/three_dimensional/test_buoyancy.jl",
    "content": "#!/usr/bin/env julia --project\n\ninclude(\"../shared_source/boilerplate.jl\")\ninclude(\"ThreeDimensionalCompressibleNavierStokesEquations.jl\")\n\nClimateMachine.init()\n\n#################\n# RUN THE TESTS #\n#################\n@testset \"$(@__FILE__)\" begin\n\n    include(\"refvals_buoyancy.jl\")\n\n    ########\n    # Setup physical and numerical domains\n    ########\n    Ωˣ = IntervalDomain(-2π, 2π, periodic = true)\n    Ωʸ = IntervalDomain(-2π, 2π, periodic = true)\n    Ωᶻ = IntervalDomain(0, 4π, periodic = false)\n\n    second_order = DiscretizedDomain(\n        Ωˣ × Ωʸ × Ωᶻ;\n        elements = 5,\n        polynomial_order = 2,\n        overintegration_order = 1,\n    )\n\n    fourth_order = DiscretizedDomain(\n        Ωˣ × Ωʸ × Ωᶻ;\n        elements = 3,\n        polynomial_order = 4,\n        overintegration_order = 1,\n    )\n\n    grids = Dict(\"second_order\" => second_order, \"fourth_order\" => fourth_order)\n\n    ########\n    # Define timestepping parameters\n    ########\n    start_time = 0\n    end_time = 0.1\n    Δt = 0.001\n    method = SSPRK22Heuns\n\n    timestepper = TimeStepper(method = method, timestep = Δt)\n\n    callbacks = (\n        Info(),\n        # VTKState(; iteration = 1, filepath = \"output/buoyancy\"),\n        StateCheck(10),\n    )\n\n    ########\n    # Define physical parameters and parameterizations\n    ########\n    Lˣ, Lʸ, Lᶻ = length(Ωˣ × Ωʸ × Ωᶻ)\n    parameters = (\n        ρₒ = 1, # reference density\n        cₛ = sqrt(10), # sound speed\n        α = 1e-4, # thermal expansion coefficient\n        g = 10, # gravity\n        θₒ = 10, # initial temperature value\n        Lˣ = Lˣ,\n        Lʸ = Lʸ,\n        H = Lᶻ,\n    )\n\n    physics = FluidPhysics(;\n        advection = NonLinearAdvectionTerm(),\n        dissipation = ConstantViscosity{Float64}(μ = 0, ν = 0, κ = 0),\n        coriolis = nothing,\n        buoyancy = Buoyancy{Float64}(α = parameters.α, g = parameters.g),\n    )\n\n    ########\n    # Define initial conditions\n    ########\n\n    u₀(p, x, y, z) = -0\n    v₀(p, x, y, z) = -0\n    w₀(p, x, y, z) = -0\n    θ₀(p, x, y, z) = -p.θₒ * (1 - z / 4π)\n\n    ρ₀(p, x, y, z) =\n        p.ρₒ * (1 - (p.α * p.g / p.cₛ^2) / 2 * (-p.θₒ * (1 - z / 4π))^2)\n    ρu₀(p, x...) = ρ₀(p, x...) * u₀(p, x...)\n    ρv₀(p, x...) = ρ₀(p, x...) * v₀(p, x...)\n    ρw₀(p, x...) = ρ₀(p, x...) * w₀(p, x...)\n    ρθ₀(p, x...) = ρ₀(p, x...) * θ₀(p, x...)\n\n    ρu⃗₀(p, x...) = @SVector [ρu₀(p, x...), ρv₀(p, x...), ρw₀(p, x...)]\n    initial_conditions = (ρ = ρ₀, ρu = ρu⃗₀, ρθ = ρθ₀)\n\n    orientations = Dict(\n        \"\" => ClimateMachine.Orientations.NoOrientation(),\n        \"_flat\" => ClimateMachine.Orientations.FlatOrientation(),\n    )\n\n    for (key1, grid) in grids\n        for (key2, orientation) in orientations\n            key = key1 * key2\n\n            println(\"running \", key)\n\n            local_physics = FluidPhysics(;\n                orientation = orientation,\n                advection = physics.advection,\n                dissipation = physics.dissipation,\n                coriolis = physics.coriolis,\n                buoyancy = physics.buoyancy,\n            )\n\n            @testset \"$(key)\" begin\n                model = SpatialModel(\n                    balance_law = Fluid3D(),\n                    physics = local_physics,\n                    numerics = (flux = RoeNumericalFlux(),),\n                    grid = grid,\n                    boundary_conditions = NamedTuple(),\n                    parameters = parameters,\n                )\n\n                simulation = Simulation(\n                    model = model,\n                    initial_conditions = initial_conditions,\n                    timestepper = timestepper,\n                    callbacks = callbacks,\n                    time = (; start = start_time, finish = end_time),\n                )\n\n                ########\n                # Run the model\n                ########\n                evolve!(\n                    simulation,\n                    model,\n                    refDat = getproperty(refVals, Symbol(key)),\n                )\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/two_dimensional/TwoDimensionalCompressibleNavierStokesEquations.jl",
    "content": "include(\"../shared_source/boilerplate.jl\")\n\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    init_state_prognostic!,\n    init_state_auxiliary!,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    flux_first_order!,\n    flux_second_order!,\n    source!,\n    wavespeed,\n    boundary_conditions,\n    boundary_state!\nimport ClimateMachine.DGMethods: DGModel\nimport ClimateMachine.NumericalFluxes: numerical_flux_first_order!\n\n\"\"\"\n    TwoDimensionalCompressibleNavierStokesEquations <: BalanceLaw\n\nA `BalanceLaw` for shallow water modeling.\n\nwrite out the equations here\n\n# Usage\n\n    TwoDimensionalCompressibleNavierStokesEquations()\n\n\"\"\"\nabstract type AbstractFluid2D <: AbstractFluid end\nstruct Fluid2D <: AbstractFluid2D end\nstruct TwoDimensionalCompressibleNavierStokesEquations{\n    I,\n    D,\n    A,\n    T,\n    C,\n    F,\n    BC,\n    FT,\n} <: AbstractFluid2D\n    initial_value_problem::I\n    domain::D\n    advection::A\n    turbulence::T\n    coriolis::C\n    forcing::F\n    boundary_conditions::BC\n    g::FT\n    c::FT\n    function TwoDimensionalCompressibleNavierStokesEquations{FT}(\n        initial_value_problem::I,\n        domain::D,\n        advection::A,\n        turbulence::T,\n        coriolis::C,\n        forcing::F,\n        boundary_conditions::BC;\n        g = FT(10), # m/s²\n        c = FT(0),  #m/s\n    ) where {FT <: AbstractFloat, I, D, A, T, C, F, BC}\n        return new{I, D, A, T, C, F, BC, FT}(\n            initial_value_problem,\n            domain,\n            advection,\n            turbulence,\n            coriolis,\n            forcing,\n            boundary_conditions,\n            g,\n            c,\n        )\n    end\nend\nCNSE2D = TwoDimensionalCompressibleNavierStokesEquations\n\nfunction vars_state(m::CNSE2D, ::Prognostic, T)\n    @vars begin\n        ρ::T\n        ρu::SVector{2, T}\n        ρθ::T\n    end\nend\n\nfunction init_state_prognostic!(m::CNSE2D, state::Vars, aux::Vars, localgeo, t)\n    cnse_init_state!(m, state, aux, localgeo, t)\nend\n\n# default initial state if IVP == nothing\nfunction cnse_init_state!(model::CNSE2D, state, aux, localgeo, t)\n    ρ = 1\n\n    state.ρ = ρ\n    state.ρu = ρ * @SVector [-0, -0]\n    state.ρθ = ρ\n\n    return nothing\nend\n\n# user defined initial state\nfunction cnse_init_state!(\n    model::CNSE2D{<:InitialValueProblem},\n    state,\n    aux,\n    localgeo,\n    t,\n)\n    x = aux.x\n    y = aux.y\n    z = aux.z\n\n    params = model.initial_value_problem.params\n    ic = model.initial_value_problem.initial_conditions\n\n    state.ρ = ic.ρ(params, x, y, z)\n    state.ρu = ic.ρu(params, x, y, z)\n    state.ρθ = ic.ρθ(params, x, y, z)\n\n    return nothing\nend\n\nfunction vars_state(m::CNSE2D, ::Auxiliary, T)\n    @vars begin\n        x::T\n        y::T\n        z::T\n    end\nend\n\nfunction init_state_auxiliary!(\n    model::CNSE2D,\n    state_auxiliary::MPIStateArray,\n    grid,\n    direction,\n)\n    init_state_auxiliary!(\n        model,\n        (model, aux, tmp, geom) -> cnse_init_aux!(model, aux, geom),\n        state_auxiliary,\n        grid,\n        direction,\n    )\nend\n\nfunction cnse_init_aux!(::CNSE2D, aux, geom)\n    @inbounds begin\n        aux.x = geom.coord[1]\n        aux.y = geom.coord[2]\n        aux.z = geom.coord[3]\n    end\n\n    return nothing\nend\n\nfunction vars_state(m::CNSE2D, ::Gradient, T)\n    @vars begin\n        ∇u::SVector{2, T}\n        ∇θ::T\n    end\nend\n\nfunction compute_gradient_argument!(\n    model::CNSE2D,\n    grad::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    compute_gradient_argument!(model.turbulence, grad, state, aux, t)\nend\n\ncompute_gradient_argument!(::LinearDrag, _...) = nothing\n\n@inline function compute_gradient_argument!(\n    ::ConstantViscosity,\n    grad::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ρ = state.ρ\n    ρu = state.ρu\n    ρθ = state.ρθ\n\n    u = ρu / ρ\n    θ = ρθ / ρ\n\n    grad.∇u = u\n    grad.∇θ = θ\n\n    return nothing\nend\n\nfunction vars_state(m::CNSE2D, ::GradientFlux, T)\n    @vars begin\n        ν∇u::SMatrix{3, 2, T, 6}\n        κ∇θ::SVector{3, T}\n    end\nend\n\nfunction compute_gradient_flux!(\n    model::CNSE2D,\n    gradflux::Vars,\n    grad::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    compute_gradient_flux!(\n        model,\n        model.turbulence,\n        gradflux,\n        grad,\n        state,\n        aux,\n        t,\n    )\nend\n\ncompute_gradient_flux!(::CNSE2D, ::LinearDrag, _...) = nothing\n\n@inline function compute_gradient_flux!(\n    ::CNSE2D,\n    turb::ConstantViscosity,\n    gradflux::Vars,\n    grad::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ν = Diagonal(@SVector [turb.ν, turb.ν, -0])\n    κ = Diagonal(@SVector [turb.κ, turb.κ, -0])\n\n    gradflux.ν∇u = -ν * grad.∇u\n    gradflux.κ∇θ = -κ * grad.∇θ\n\n    return nothing\nend\n\n@inline function flux_first_order!(\n    model::CNSE2D,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n)\n\n    ρ = state.ρ\n    ρu = @SVector [state.ρu[1], state.ρu[2], -0]\n    ρθ = state.ρθ\n\n    ρₜ = flux.ρ\n    ρuₜ = flux.ρu\n    θₜ = flux.ρθ\n\n    g = model.g\n\n    Iʰ = @SMatrix [\n        1 -0\n        -0 1\n        -0 -0\n    ]\n\n    flux.ρ += ρu\n    flux.ρu += g * ρ^2 * Iʰ / 2\n\n    advective_flux!(model, model.advection, flux, state, aux, t)\n\n    return nothing\nend\n\nadvective_flux!(::CNSE2D, ::Nothing, _...) = nothing\n\n@inline function advective_flux!(\n    ::CNSE2D,\n    ::NonLinearAdvectionTerm,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    ρ = state.ρ\n    ρu = state.ρu\n    ρv = @SVector [state.ρu[1], state.ρu[2], -0]\n    ρθ = state.ρθ\n\n    flux.ρu += ρv ⊗ ρu / ρ\n    flux.ρθ += ρv * ρθ / ρ\n\n    return nothing\nend\n\nfunction flux_second_order!(\n    model::CNSE2D,\n    flux::Grad,\n    state::Vars,\n    gradflux::Vars,\n    ::Vars,\n    aux::Vars,\n    t::Real,\n)\n    flux_second_order!(model, model.turbulence, flux, state, gradflux, aux, t)\nend\n\nflux_second_order!(::CNSE2D, ::LinearDrag, _...) = nothing\n\n@inline function flux_second_order!(\n    ::CNSE2D,\n    ::ConstantViscosity,\n    flux::Grad,\n    state::Vars,\n    gradflux::Vars,\n    aux::Vars,\n    t::Real,\n)\n    flux.ρu += gradflux.ν∇u\n    flux.ρθ += gradflux.κ∇θ\n\n    return nothing\nend\n\n@inline function source!(\n    model::CNSE2D,\n    source::Vars,\n    state::Vars,\n    gradflux::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n)\n    coriolis_force!(model, model.coriolis, source, state, aux, t)\n    forcing_term!(model, model.forcing, source, state, aux, t)\n    linear_drag!(model, model.turbulence, source, state, aux, t)\n\n    return nothing\nend\n\ncoriolis_force!(::CNSE2D, ::Nothing, _...) = nothing\n\n@inline function coriolis_force!(\n    model::CNSE2D,\n    coriolis::fPlaneCoriolis,\n    source,\n    state,\n    aux,\n    t,\n)\n    ρu = @SVector [state.ρu[1], state.ρu[2], -0]\n\n    # f × u\n    f = [-0, -0, coriolis_parameter(model, coriolis, aux.coords)]\n    id = @SVector [1, 2]\n    fxρu = (f × ρu)[id]\n\n    source.ρu -= fxρu\n\n    return nothing\nend\n\nforcing_term!(::CNSE2D, ::Nothing, _...) = nothing\n\n@inline function forcing_term!(\n    model::CNSE2D,\n    forcing::KinematicStress,\n    source,\n    state,\n    aux,\n    t,\n)\n    source.ρu += kinematic_stress(model, forcing, aux.coords)\n\n    return nothing\nend\n\nlinear_drag!(::CNSE2D, ::ConstantViscosity, _...) = nothing\n\n@inline function linear_drag!(::CNSE2D, turb::LinearDrag, source, state, aux, t)\n    source.ρu -= turb.λ * state.ρu\n\n    return nothing\nend\n\n@inline wavespeed(m::CNSE2D, _...) = m.c\n\nroe_average(ρ⁻, ρ⁺, var⁻, var⁺) =\n    (sqrt(ρ⁻) * var⁻ + sqrt(ρ⁺) * var⁺) / (sqrt(ρ⁻) + sqrt(ρ⁺))\n\nfunction numerical_flux_first_order!(\n    ::RoeNumericalFlux,\n    model::CNSE2D,\n    fluxᵀn::Vars{S},\n    n⁻::SVector,\n    state⁻::Vars{S},\n    aux⁻::Vars{A},\n    state⁺::Vars{S},\n    aux⁺::Vars{A},\n    t,\n    direction,\n) where {S, A}\n    numerical_flux_first_order!(\n        CentralNumericalFluxFirstOrder(),\n        model,\n        fluxᵀn,\n        n⁻,\n        state⁻,\n        aux⁻,\n        state⁺,\n        aux⁺,\n        t,\n        direction,\n    )\n\n    FT = eltype(fluxᵀn)\n\n    # constants and normal vectors\n    g = model.g\n    @inbounds nˣ = n⁻[1]\n    @inbounds nʸ = n⁻[2]\n\n    # get minus side states\n    ρ⁻ = state⁻.ρ\n    @inbounds ρu⁻ = state⁻.ρu[1]\n    @inbounds ρv⁻ = state⁻.ρu[2]\n    ρθ⁻ = state⁻.ρθ\n\n    u⁻ = ρu⁻ / ρ⁻\n    v⁻ = ρv⁻ / ρ⁻\n    θ⁻ = ρθ⁻ / ρ⁻\n\n    # get plus side states\n    ρ⁺ = state⁺.ρ\n    @inbounds ρu⁺ = state⁺.ρu[1]\n    @inbounds ρv⁺ = state⁺.ρu[2]\n    ρθ⁺ = state⁺.ρθ\n\n    u⁺ = ρu⁺ / ρ⁺\n    v⁺ = ρv⁺ / ρ⁺\n    θ⁺ = ρθ⁺ / ρ⁺\n\n    # averages for roe fluxes\n    ρ = (ρ⁺ + ρ⁻) / 2\n    ρu = (ρu⁺ + ρu⁻) / 2\n    ρv = (ρv⁺ + ρv⁻) / 2\n    ρθ = (ρθ⁺ + ρθ⁻) / 2\n\n    u = roe_average(ρ⁻, ρ⁺, u⁻, u⁺)\n    v = roe_average(ρ⁻, ρ⁺, v⁻, v⁺)\n    θ = roe_average(ρ⁻, ρ⁺, θ⁻, θ⁺)\n\n    # normal and tangent velocities\n    uₙ = nˣ * u + nʸ * v\n    uₚ = nˣ * v - nʸ * u\n\n    # differences for difference vector\n    Δρ = ρ⁺ - ρ⁻\n    Δρu = ρu⁺ - ρu⁻\n    Δρv = ρv⁺ - ρv⁻\n    Δρθ = ρθ⁺ - ρθ⁻\n\n    Δφ = @SVector [Δρ, Δρu, Δρv, Δρθ]\n\n    \"\"\"\n    # jacobian\n    ∂F∂φ = [\n        0 nˣ nʸ 0\n        (nˣ * c^2 - u * uₙ) (uₙ + nˣ * u) (nʸ * u) 0\n        (nʸ * c^2 - v * uₙ) (nˣ * v) (uₙ + nʸ * v) 0\n        (-θ * uₙ) (nˣ * θ) (nʸ * θ) uₙ\n    ]\n\n    # eigen decomposition\n    λ, R = eigen(∂F∂φ)\n    \"\"\"\n\n    # eigen values matrix\n    c = sqrt(g * ρ)\n    λ = @SVector [uₙ, uₙ + c, uₙ - c, uₙ]\n    Λ = Diagonal(abs.(λ))\n\n\n    # eigenvector matrix\n    R = @SMatrix [\n        0 1 1 0\n        -nʸ (u+nˣ * c) (u-nˣ * c) 0\n        nˣ (v+nʸ * c) (v-nʸ * c) 0\n        0 θ θ 1\n    ]\n\n    # inverse of eigenvector matrix\n    R⁻¹ = @SMatrix [\n        -uₚ -nʸ nˣ 0\n        (c - uₙ)/(2c) nˣ/(2c) nʸ/(2c) 0\n        (c + uₙ)/(2c) -nˣ/(2c) -nʸ/(2c) 0\n        -θ 0 0 1\n    ]\n\n    # @test norm(R⁻¹ * R - I) ≈ 0\n\n    # actually calculate flux\n    # parent(fluxᵀn) .-= R * Λ * (R \\ Δφ) / 2\n    parent(fluxᵀn) .-= R * Λ * R⁻¹ * Δφ / 2\n\n    return nothing\nend\n\nboundary_conditions(model::CNSE2D) = model.boundary_conditions\n\n\"\"\"\n    boundary_state!(nf, ::CNSE2D, args...)\n\napplies boundary conditions for the hyperbolic fluxes\ndispatches to a function in CNSEBoundaryConditions\n\"\"\"\n@inline function boundary_state!(nf, bc, model::CNSE2D, args...)\n    return _cnse_boundary_state!(nf, bc, model, args...)\nend\n\n\"\"\"\n    CNSE_boundary_state!(nf, bc::FluidBC, ::CNSE2D)\n\nsplits boundary condition application into velocity\n\"\"\"\n@inline function cnse_boundary_state!(nf, bc::FluidBC, m::CNSE2D, args...)\n    return cnse_boundary_state!(nf, bc.momentum, m, m.turbulence, args...)\n    return cnse_boundary_state!(nf, bc.temperature, m, args...)\nend\n\ninclude(\"bc_momentum.jl\")\ninclude(\"bc_tracer.jl\")\n\n\"\"\"\nSTUFF FOR ANDRE'S WRAPPERS\n\"\"\"\n\nfunction get_boundary_conditions(\n    model::SpatialModel{BL},\n) where {BL <: AbstractFluid2D}\n    bcs = model.boundary_conditions\n\n    west_east = (check_bc(bcs, :west), check_bc(bcs, :east))\n    south_north = (check_bc(bcs, :south), check_bc(bcs, :north))\n\n    return (west_east..., south_north...)\nend\n\nfunction DGModel(\n    model::SpatialModel{BL};\n    initial_conditions = nothing,\n) where {BL <: AbstractFluid2D}\n    params = model.parameters\n    physics = model.physics\n\n    Lˣ, Lʸ = length(model.grid.domain)\n    bcs = get_boundary_conditions(model)\n    FT = eltype(model.grid.numerical.vgeo)\n\n    if !isnothing(initial_conditions)\n        initial_conditions = InitialValueProblem(params, initial_conditions)\n    end\n\n    balance_law = CNSE2D{FT}(\n        initial_conditions,\n        (Lˣ, Lʸ),\n        physics.advection,\n        physics.dissipation,\n        physics.coriolis,\n        nothing,\n        bcs,\n        c = params.c,\n        g = params.g,\n    )\n\n    numerical_flux_first_order = model.numerics.flux # should be a function\n\n    rhs = DGModel(\n        balance_law,\n        model.grid.numerical,\n        numerical_flux_first_order,\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    return rhs\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/two_dimensional/bc_momentum.jl",
    "content": "\"\"\"\n    cnse_boundary_state!(::NumericalFluxFirstOrder, ::Impenetrable{FreeSlip}, ::CNSE2D)\n\napply free slip boundary condition for momentum\nsets reflective ghost point\n\"\"\"\n@inline function cnse_boundary_state!(\n    ::NumericalFluxFirstOrder,\n    ::Impenetrable{FreeSlip},\n    ::CNSE2D,\n    ::TurbulenceClosure,\n    state⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    state⁺.ρ = state⁻.ρ\n\n    ρu⁻ = @SVector [state⁻.ρu[1], state⁻.ρu[2], -0]\n    ρu⁺ = ρu⁻ - 2 * n⁻ ⋅ ρu⁻ .* SVector(n⁻)\n\n    state⁺.ρu = @SVector [ρu⁺[1], ρu⁺[2]]\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::Union{NumericalFluxGradient, NumericalFluxSecondOrder}, ::Impenetrable{FreeSlip}, ::CNSE2D)\n\nno second order flux computed for linear drag\n\"\"\"\ncnse_boundary_state!(\n    ::Union{NumericalFluxGradient, NumericalFluxSecondOrder},\n    ::MomentumBC,\n    ::CNSE2D,\n    ::LinearDrag,\n    _...,\n) = nothing\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxGradient, ::Impenetrable{FreeSlip}, ::CNSE2D)\n\napply free slip boundary condition for momentum\nsets non-reflective ghost point\n\"\"\"\nfunction cnse_boundary_state!(\n    ::NumericalFluxGradient,\n    ::Impenetrable{FreeSlip},\n    ::CNSE2D,\n    ::ConstantViscosity,\n    state⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    state⁺.ρ = state⁻.ρ\n\n    ρu⁻ = @SVector [state⁻.ρu[1], state⁻.ρu[2], -0]\n    ρu⁺ = ρu⁻ - n⁻ ⋅ ρu⁻ .* SVector(n⁻)\n\n    state⁺.ρu = @SVector [ρu⁺[1], ρu⁺[2]]\n\n    return nothing\nend\n\n\"\"\"\n    shallow_normal_boundary_flux_second_order!(::NumericalFluxSecondOrder, ::Impenetrable{FreeSlip}, ::CNSE2D)\n\napply free slip boundary condition for momentum\napply zero numerical flux in the normal direction\n\"\"\"\nfunction cnse_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    ::Impenetrable{FreeSlip},\n    ::CNSE2D,\n    ::ConstantViscosity,\n    state⁺,\n    gradflux⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    gradflux⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    state⁺.ρu = state⁻.ρu\n    gradflux⁺.ν∇u = n⁻ * (@SVector [-0, -0])'\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxFirstOrder, ::Impenetrable{NoSlip}, ::CNSE2D)\n\napply no slip boundary condition for momentum\nsets reflective ghost point\n\"\"\"\n@inline function cnse_boundary_state!(\n    ::NumericalFluxFirstOrder,\n    ::Impenetrable{NoSlip},\n    ::CNSE2D,\n    ::TurbulenceClosure,\n    state⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    state⁺.ρ = state⁻.ρ\n    state⁺.ρu = -state⁻.ρu\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxGradient, ::Impenetrable{NoSlip}, ::CNSE2D)\n\napply no slip boundary condition for momentum\nset numerical flux to zero for U\n\"\"\"\n@inline function cnse_boundary_state!(\n    ::NumericalFluxGradient,\n    ::Impenetrable{NoSlip},\n    ::CNSE2D,\n    ::ConstantViscosity,\n    state⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    FT = eltype(state⁺)\n    state⁺.ρu = @SVector zeros(FT, 2)\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxSecondOrder, ::Impenetrable{NoSlip}, ::CNSE2D)\n\napply no slip boundary condition for momentum\nsets ghost point to have no numerical flux on the boundary for U\n\"\"\"\n@inline function cnse_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    ::Impenetrable{NoSlip},\n    ::CNSE2D,\n    ::ConstantViscosity,\n    state⁺,\n    gradflux⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    gradflux⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    state⁺.ρu = -state⁻.ρu\n    gradflux⁺.ν∇u = gradflux⁻.ν∇u\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::Penetrable{FreeSlip}, ::CNSE2D)\n\nno mass boundary condition for penetrable\n\"\"\"\ncnse_boundary_state!(\n    ::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    ::Penetrable{FreeSlip},\n    ::CNSE2D,\n    ::ConstantViscosity,\n    _...,\n) = nothing\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxSecondOrder, ::Penetrable{FreeSlip}, ::CNSE2D)\n\napply free slip boundary condition for momentum\napply zero numerical flux in the normal direction\n\"\"\"\nfunction cnse_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    ::Penetrable{FreeSlip},\n    ::CNSE2D,\n    ::ConstantViscosity,\n    state⁺,\n    gradflux⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    gradflux⁻,\n    aux⁻,\n    t,\n    args...,\n)\n    state⁺.ρu = state⁻.ρu\n    gradflux⁺.ν∇u = n⁻ * (@SVector [-0, -0])'\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::Impenetrable{MomentumFlux}, ::HBModel)\n\napply kinematic stress boundary condition for momentum\napplies free slip conditions for first-order and gradient fluxes\n\"\"\"\nfunction cnse_boundary_state!(\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    ::Impenetrable{<:MomentumFlux},\n    model::CNSE2D,\n    turb::TurbulenceClosure,\n    args...,\n)\n    return cnse_boundary_state!(\n        nf,\n        Impenetrable(FreeSlip()),\n        model,\n        turb,\n        args...,\n    )\nend\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxSecondOrder, ::Impenetrable{MomentumFlux}, ::HBModel)\n\napply kinematic stress boundary condition for momentum\nsets ghost point to have specified flux on the boundary for ν∇u\n\"\"\"\n@inline function cnse_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    ::Impenetrable{<:MomentumFlux},\n    model::CNSE2D,\n    state⁺,\n    gradflux⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    gradflux⁻,\n    aux⁻,\n    t,\n)\n    state⁺.ρu = state⁻.ρu\n    gradflux⁺.ν∇u = n⁻ * bc.drag(state⁻, aux⁻, t)'\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::Penetrable{MomentumFlux}, ::HBModel)\n\napply kinematic stress boundary condition for momentum\napplies free slip conditions for first-order and gradient fluxes\n\"\"\"\nfunction cnse_boundary_state!(\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    ::Penetrable{<:MomentumFlux},\n    model::CNSE2D,\n    turb::TurbulenceClosure,\n    args...,\n)\n    return cnse_boundary_state!(\n        nf,\n        Penetrable(FreeSlip()),\n        model,\n        turb,\n        args...,\n    )\nend\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxSecondOrder, ::Penetrable{MomentumFlux}, ::HBModel)\n\napply kinematic stress boundary condition for momentum\nsets ghost point to have specified flux on the boundary for ν∇u\n\"\"\"\n@inline function cnse_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    bc::Penetrable{<:MomentumFlux},\n    shallow::CNSE2D,\n    ::TurbulenceClosure,\n    state⁺,\n    gradflux⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    gradflux⁻,\n    aux⁻,\n    t,\n)\n    state⁺.ρu = state⁻.ρu\n    gradflux⁺.ν∇u = n⁻ * bc.drag(state⁻, aux⁻, t)'\n\n    return nothing\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/two_dimensional/bc_tracer.jl",
    "content": "\"\"\"\n    cnse_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::Insulating, ::HBModel)\n\napply insulating boundary condition for temperature\nsets transmissive ghost point\n\"\"\"\nfunction cnse_boundary_state!(\n    ::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    ::Insulating,\n    ::CNSE2D,\n    state⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    aux⁻,\n    t,\n)\n    state⁺.ρθ = state⁻.ρθ\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxSecondOrder, ::Insulating, ::HBModel)\n\napply insulating boundary condition for velocity\nsets ghost point to have no numerical flux on the boundary for κ∇θ\n\"\"\"\n@inline function cnse_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    ::Insulating,\n    ::CNSE2D,\n    state⁺,\n    gradflux⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    gradflux⁻,\n    aux⁻,\n    t,\n)\n    state⁺.ρθ = state⁻.ρθ\n    gradflux⁺.κ∇θ = n⁻ * -0\n\n    return nothing\nend\n\n\"\"\"\n    cnse_boundary_state!(::Union{NumericalFluxFirstOrder, NumericalFluxGradient}, ::TemperatureFlux, ::HBModel)\n\napply temperature flux boundary condition for velocity\napplies insulating conditions for first-order and gradient fluxes\n\"\"\"\nfunction cnse_boundary_state!(\n    nf::Union{NumericalFluxFirstOrder, NumericalFluxGradient},\n    ::TemperatureFlux,\n    model::CNSE2D,\n    args...,\n)\n    return cnse_boundary_state!(nf, Insulating(), model, args...)\nend\n\n\"\"\"\n    cnse_boundary_state!(::NumericalFluxSecondOrder, ::TemperatureFlux, ::HBModel)\n\napply insulating boundary condition for velocity\nsets ghost point to have specified flux on the boundary for κ∇θ\n\"\"\"\n@inline function cnse_boundary_state!(\n    ::NumericalFluxSecondOrder,\n    bc::TemperatureFlux,\n    model::CNSE2D,\n    state⁺,\n    gradflux⁺,\n    aux⁺,\n    n⁻,\n    state⁻,\n    gradflux⁻,\n    aux⁻,\n    t,\n)\n    state⁺.ρθ = state⁻.ρθ\n    gradflux⁺.κ∇θ = n⁻ * bc(state⁻, aux⁻, t)\n\n    return nothing\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/two_dimensional/refvals_bickley_jet.jl",
    "content": "# [\n#  [ MPIStateArray Name, Field Name, Maximum, Minimum, Mean, Standard Deviation ],\n#  [         :                :          :        :      :          :           ],\n# ]\n\nparr = [\n    [\"state\", :ρ, 12, 12, 12, 12],\n    [\"state\", \"ρu[1]\", 12, 11, 12, 12],\n    [\"state\", \"ρu[2]\", 12, 12, 11, 12],\n    [\"state\", :ρθ, 11, 12, 10, 12],\n]\n\n#! format: off\nrusanov_periodic = (\n[\n [ \"state\",     \"ρ\", 9.76557021043823247908e-01,  1.00785052430958410596e+00,  1.00000023257151737788e+00,  5.40633258005515995870e-03 ],\n [ \"state\", \"ρu[1]\", -2.08235364819134516345e-01,  5.42021330588998817568e-01,  1.59191748432813973135e-01,  1.71679977050671228600e-01 ],\n [ \"state\", \"ρu[2]\", -4.44598929671790876750e-01,  3.96193527502323006306e-01, -9.19857600654994111977e-06,  2.23835345394622464710e-01 ],\n [ \"state\",    \"ρθ\", -1.05926174767863745529e+00,  1.78285318416958671328e+00,  3.38466687536443516793e-03,  2.92797512839404083795e-01 ],\n],\n    parr,\n)\n\nroeflux_periodic = (\n[\n [ \"state\",     \"ρ\",  9.76408216346011381681e-01,  1.00685186827167338919e+00,  1.00000020920188337215e+00,  5.28437765352953326553e-03 ],\n [ \"state\", \"ρu[1]\", -2.51377173896010663867e-01,  5.33033893482195431091e-01,  1.59154423274343037598e-01,  1.96922130305308085152e-01 ],\n [ \"state\", \"ρu[2]\", -4.04321943123139071474e-01,  3.81969301036260144855e-01, -2.87040331153030903177e-05,  2.02392226536863034658e-01 ],\n [ \"state\",    \"ρθ\", -1.21186199231530267184e+00,  9.21722158564563742722e-01,  2.34662081315961668082e-03,  2.61922031216240580598e-01 ],\n],\n    parr,\n)\n\nrusanov = (\n[\n [ \"state\",     \"ρ\",  9.76844648039728813416e-01,  1.00662311214286837036e+00,  1.00000043240984126669e+00,  6.05641827304116090597e-03 ],\n [ \"state\", \"ρu[1]\", -6.93213807814652027695e-01,  5.89769972499923134102e-01,  1.42589259313275984464e-01,  2.32576446220999155656e-01 ],\n [ \"state\", \"ρu[2]\", -5.73828377062444716650e-01,  5.07664260030296077275e-01, -4.23962044125134052130e-04,  1.96899492339057097245e-01 ],\n [ \"state\",    \"ρθ\", -3.65527466807722500874e+00,  4.07350442876234186684e+00,  1.27099405499731775426e-02,  5.02926135539447205502e-01 ],\n],\n    parr,\n)\n\nroeflux = (\n[\n [ \"state\",     \"ρ\",  9.72455511248961124160e-01,  1.00730359035806360524e+00,  1.00000004243418749716e+00,  6.29619947553510493632e-03 ],\n [ \"state\", \"ρu[1]\", -3.40062758027173561715e-01,  5.98382642258275421199e-01,  1.61534160931526560301e-01,  1.83683987730847486652e-01 ],\n [ \"state\", \"ρu[2]\", -4.78833188511707363855e-01,  5.60276621431795018857e-01, -5.51384446753656887186e-05,  2.12893889070269098918e-01 ],\n [ \"state\",    \"ρθ\", -1.44374005663885895956e+01,  2.86753217014698513765e+00,  1.48814717217727286724e-03,  5.22593283079209602882e-01 ],\n],\n    parr,\n)\n\nrusanov_overintegration = (\n[\n [ \"state\",     \"ρ\",  9.71215931957259970275e-01,  1.00598091306596182370e+00,  1.00000018331969764418e+00,  6.61958163862229592017e-03 ],\n [ \"state\", \"ρu[1]\", -3.70465052437365993665e-01,  7.21959883918644518275e-01,  1.59085030658594694941e-01,  2.39392692254175726285e-01 ],\n [ \"state\", \"ρu[2]\", -4.40021840550348763976e-01,  4.17225753221437845042e-01,  7.34809696136107232513e-05,  1.50458151629451780673e-01 ],\n [ \"state\",    \"ρθ\", -3.05207824503129643290e+00,  1.86668064854026383159e+00, -1.05021657861818027563e-02,  4.60981422911087401761e-01 ],\n],\n    parr,\n)\n\nroeflux_overintegration = (\n[\n [ \"state\",     \"ρ\",  9.69901051313856066294e-01,  1.00695111308847851106e+00,  1.00000002953972089159e+00,  6.94722590819101589593e-03 ],\n [ \"state\", \"ρu[1]\", -4.21771768146742886962e-01,  6.34790159642324547384e-01,  1.59384804664454482470e-01,  2.22844788492525480716e-01 ],\n [ \"state\", \"ρu[2]\", -4.37678540756429201863e-01,  4.53959896024816400573e-01, -1.22554654623556756642e-04,  1.73930782418124457722e-01 ],\n [ \"state\",    \"ρθ\", -1.13636401173616619076e+00,  1.51145239791767727056e+00,  1.96870293018723595616e-03,  3.82573373360323043535e-01 ],\n],\n    parr,\n)\n#! format: on\n\nrefVals = (;\n    rusanov_periodic,\n    roeflux_periodic,\n    rusanov,\n    roeflux,\n    rusanov_overintegration,\n    roeflux_overintegration,\n)\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/two_dimensional/run_bickley_jet.jl",
    "content": "#!/usr/bin/env julia --project\n\ninclude(\"../shared_source/boilerplate.jl\")\ninclude(\"TwoDimensionalCompressibleNavierStokesEquations.jl\")\n\nClimateMachine.init()\n\n########\n# Setup physical and numerical domains\n########\nΩˣ = IntervalDomain(-2π, 2π, periodic = true)\nΩʸ = IntervalDomain(-2π, 2π, periodic = false)\n\ngrid = DiscretizedDomain(\n    Ωˣ × Ωʸ;\n    elements = 8,\n    polynomial_order = 3,\n    overintegration_order = 1,\n)\n\n########\n# Define timestepping parameters\n########\nstart_time = 0\nend_time = 200.0\nΔt = 0.02\nmethod = SSPRK22Heuns\n\ntimestepper = TimeStepper(method = method, timestep = Δt)\n\ncallbacks = (Info(), StateCheck(10))\n\n########\n# Define physical parameters and parameterizations\n########\nparameters = (\n    ϵ = 0.1,  # perturbation size for initial condition\n    l = 0.5, # Gaussian width\n    k = 0.5, # Sinusoidal wavenumber\n    ρₒ = 1.0, # reference density\n    c = 2,\n    g = 10,\n)\n\nphysics = FluidPhysics(;\n    advection = NonLinearAdvectionTerm(),\n    dissipation = ConstantViscosity{Float64}(μ = 0, ν = 0, κ = 0),\n    coriolis = nothing,\n    buoyancy = nothing,\n)\n\n########\n# Define boundary conditions\n########\nρu_bcs = (south = Impenetrable(FreeSlip()), north = Impenetrable(FreeSlip()))\nρθ_bcs = (south = Insulating(), north = Insulating())\nBC = (ρθ = ρθ_bcs, ρu = ρu_bcs)\n\n########\n# Define initial conditions\n########\n\n# The Bickley jet\nU₀(p, x, y, z) = cosh(y)^(-2)\n\n# Slightly off-center vortical perturbations\nΨ₀(p, x, y, z) =\n    exp(-(y + p.l / 10)^2 / (2 * (p.l^2))) * cos(p.k * x) * cos(p.k * y)\n\n# Vortical velocity fields (ũ, ṽ) = (-∂ʸ, +∂ˣ) ψ̃\nu₀(p, x, y, z) = Ψ₀(p, x, y, z) * (p.k * tan(p.k * y) + y / (p.l^2))\nv₀(p, x, y, z) = -Ψ₀(p, x, y, z) * p.k * tan(p.k * x)\nθ₀(p, x, y, z) = sin(p.k * y)\n\nρ₀(p, x, y, z) = p.ρₒ\nρu₀(p, x...) = ρ₀(p, x...) * (p.ϵ * u₀(p, x...) + U₀(p, x...))\nρv₀(p, x...) = ρ₀(p, x...) * p.ϵ * v₀(p, x...)\nρθ₀(p, x...) = ρ₀(p, x...) * θ₀(p, x...)\n\nρu⃗₀(p, x...) = @SVector [ρu₀(p, x...), ρv₀(p, x...)]\ninitial_conditions = (ρ = ρ₀, ρu = ρu⃗₀, ρθ = ρθ₀)\n\n########\n# Create the things\n########\n\nmodel = SpatialModel(\n    balance_law = Fluid2D(),\n    physics = physics,\n    numerics = (flux = RoeNumericalFlux(),),\n    grid = grid,\n    boundary_conditions = BC,\n    parameters = parameters,\n)\n\nsimulation = Simulation(\n    model = model,\n    initial_conditions = initial_conditions,\n    timestepper = timestepper,\n    callbacks = callbacks,\n    time = (; start = start_time, finish = end_time),\n)\n\n########\n# Run the model\n########\n\ntic = Base.time()\n\nevolve!(simulation, model)\n\ntoc = Base.time()\ntime = toc - tic\nprintln(time)\n"
  },
  {
    "path": "test/Numerics/DGMethods/compressible_navier_stokes_equations/two_dimensional/test_bickley_jet.jl",
    "content": "#!/usr/bin/env julia --project\n\ninclude(\"../shared_source/boilerplate.jl\")\ninclude(\"TwoDimensionalCompressibleNavierStokesEquations.jl\")\n\nClimateMachine.init()\n\nsetups = [\n    (;\n        name = \"rusanov_periodic\",\n        flux = RusanovNumericalFlux(),\n        periodicity = true,\n        Nover = 0,\n    ),\n    (;\n        name = \"roeflux_periodic\",\n        flux = RoeNumericalFlux(),\n        periodicity = true,\n        Nover = 0,\n    ),\n    (;\n        name = \"rusanov\",\n        flux = RusanovNumericalFlux(),\n        periodicity = false,\n        Nover = 0,\n    ),\n    (;\n        name = \"roeflux\",\n        flux = RoeNumericalFlux(),\n        periodicity = false,\n        Nover = 0,\n    ),\n    (;\n        name = \"rusanov_overintegration\",\n        flux = RusanovNumericalFlux(),\n        periodicity = false,\n        Nover = 1,\n    ),\n    (;\n        name = \"roeflux_overintegration\",\n        flux = RoeNumericalFlux(),\n        periodicity = false,\n        Nover = 1,\n    ),\n]\n\n#################\n# RUN THE TESTS #\n#################\n@testset \"$(@__FILE__)\" begin\n\n    include(\"refvals_bickley_jet.jl\")\n\n    ########\n    # Define timestepping parameters\n    ########\n    start_time = 0\n    end_time = 200.0\n    Δt = 0.02\n    method = LSRK54CarpenterKennedy\n\n    timestepper = TimeStepper(method = method, timestep = Δt)\n\n    callbacks = (Info(), StateCheck(10))\n\n    ########\n    # Define physical parameters and parameterizations\n    ########\n    parameters = (\n        ϵ = 0.1,  # perturbation size for initial condition\n        l = 0.5, # Gaussian width\n        k = 0.5, # Sinusoidal wavenumber\n        ρₒ = 1.0, # reference density\n        c = 2,\n        g = 10,\n    )\n\n    physics = FluidPhysics(;\n        advection = NonLinearAdvectionTerm(),\n        dissipation = ConstantViscosity{Float64}(μ = 0, ν = 0, κ = 0),\n        coriolis = nothing,\n        buoyancy = nothing,\n    )\n\n    ########\n    # Define boundary conditions\n    ########\n    ρu_bc = Impenetrable(FreeSlip())\n    ρθ_bc = Insulating()\n    ρu_bcs = (south = ρu_bc, north = ρu_bc)\n    ρθ_bcs = (south = ρθ_bc, north = ρθ_bc)\n    BC = (ρθ = ρθ_bcs, ρu = ρu_bcs)\n\n    ########\n    # Define initial conditions\n    ########\n\n    # The Bickley jet\n    U₀(p, x, y, z) = cosh(y)^(-2)\n\n    # Slightly off-center vortical perturbations\n    Ψ₀(p, x, y, z) =\n        exp(-(y + p.l / 10)^2 / (2 * (p.l^2))) * cos(p.k * x) * cos(p.k * y)\n\n    # Vortical velocity fields (ũ, ṽ) = (-∂ʸ, +∂ˣ) ψ̃\n    u₀(p, x, y, z) = Ψ₀(p, x, y, z) * (p.k * tan(p.k * y) + y / (p.l^2))\n    v₀(p, x, y, z) = -Ψ₀(p, x, y, z) * p.k * tan(p.k * x)\n    θ₀(p, x, y, z) = sin(p.k * y)\n\n    ρ₀(p, x, y, z) = p.ρₒ\n    ρu₀(p, x...) = ρ₀(p, x...) * (p.ϵ * u₀(p, x...) + U₀(p, x...))\n    ρv₀(p, x...) = ρ₀(p, x...) * p.ϵ * v₀(p, x...)\n    ρθ₀(p, x...) = ρ₀(p, x...) * θ₀(p, x...)\n\n    ρu⃗₀(p, x...) = @SVector [ρu₀(p, x...), ρv₀(p, x...)]\n    initial_conditions = (ρ = ρ₀, ρu = ρu⃗₀, ρθ = ρθ₀)\n\n    for setup in setups\n        @testset \"$(setup.name)\" begin\n            Ωˣ = Periodic(-2π, 2π)\n            Ωʸ = IntervalDomain(-2π, 2π, periodic = setup.periodicity)\n\n            grid = DiscretizedDomain(\n                Ωˣ × Ωʸ,\n                elements = 16,\n                polynomial_order = 3,\n                overintegration_order = setup.Nover,\n            )\n\n            model = SpatialModel(\n                balance_law = Fluid2D(),\n                physics = physics,\n                numerics = (flux = setup.flux,),\n                grid = grid,\n                boundary_conditions = BC,\n                parameters = parameters,\n            )\n\n            simulation = Simulation(\n                model = model,\n                initial_conditions = initial_conditions,\n                timestepper = timestepper,\n                callbacks = callbacks,\n                time = (; start = start_time, finish = end_time),\n            )\n\n            ########\n            # Run the model\n            ########\n            evolve!(\n                simulation,\n                model;\n                refDat = getproperty(refVals, Symbol(setup.name)),\n            )\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/conservation/sphere.jl",
    "content": "#=\nHere we solve the equation:\n```math\n q + dot(∇, uq) = 0\n p - dot(∇, up) = 0\n```\non a sphere to test the conservation of the numerics\n\nThe boundary conditions are `p = q` when `dot(n, u) > 0` and\n`q = p` when `dot(n, u) < 0` (i.e., `p` flows into `q` and vice-sersa).\n=#\n\nusing MPI\nusing ClimateMachine\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.BalanceLaws:\n    Prognostic, Auxiliary, AbstractStateType, BalanceLaw\n\nusing LinearAlgebra\nusing StaticArrays\nusing Logging, Printf, Dates\nusing Random\n\nusing ClimateMachine.VariableTemplates\nimport ClimateMachine.BalanceLaws: vars_state\n\nimport ClimateMachine.DGMethods:\n    flux_first_order!,\n    flux_second_order!,\n    source!,\n    boundary_conditions,\n    boundary_state!,\n    nodal_init_state_auxiliary!,\n    init_state_prognostic!\n\nimport ClimateMachine.DGMethods: init_ode_state\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\nimport ClimateMachine.DGMethods.NumericalFluxes:\n    NumericalFluxFirstOrder,\n    numerical_flux_first_order!,\n    numerical_boundary_flux_first_order!\n\nstruct ConservationTestModel <: BalanceLaw end\n\nvars_state(::ConservationTestModel, ::Auxiliary, T) = @vars(vel::SVector{3, T})\nvars_state(::ConservationTestModel, ::Prognostic, T) = @vars(q::T, p::T)\n\nvars_state(::ConservationTestModel, ::AbstractStateType, T) = @vars()\n\nfunction nodal_init_state_auxiliary!(\n    ::ConservationTestModel,\n    aux::Vars,\n    tmp::Vars,\n    g::LocalGeometry,\n)\n    x, y, z = g.coord\n    r = x^2 + y^2 + z^2\n    aux.vel = SVector(\n        cos(10 * π * x) * sin(10 * π * y) + cos(20 * π * z),\n        exp(sin(π * r)),\n        sin(π * (x + y + z)),\n    )\nend\n\nfunction init_state_prognostic!(\n    ::ConservationTestModel,\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t,\n)\n    state.q = rand()\n    state.p = rand()\nend\n\nfunction flux_first_order!(\n    ::ConservationTestModel,\n    flux::Grad,\n    state::Vars,\n    auxstate::Vars,\n    t::Real,\n    direction,\n)\n    vel = auxstate.vel\n    flux.q = state.q .* vel\n    flux.p = -state.p .* vel\nend\n\nflux_second_order!(::ConservationTestModel, _...) = nothing\n\nsource!(::ConservationTestModel, _...) = nothing\n\nstruct ConservationTestModelNumFlux <: NumericalFluxFirstOrder end\n\nboundary_conditions(::ConservationTestModel) = (nothing,)\nboundary_state!(\n    ::CentralNumericalFluxSecondOrder,\n    bc,\n    ::ConservationTestModel,\n    _...,\n) = nothing\n\nfunction numerical_flux_first_order!(\n    ::ConservationTestModelNumFlux,\n    bl::BalanceLaw,\n    fluxᵀn::Vars{S},\n    n::SVector,\n    state⁻::Vars{S},\n    aux⁻::Vars{A},\n    state⁺::Vars{S},\n    aux⁺::Vars{A},\n    t,\n    direction,\n) where {S, A}\n    un⁻ = dot(n, aux⁻.vel)\n    un⁺ = dot(n, aux⁺.vel)\n    un = (un⁺ + un⁻) / 2\n\n    if un > 0\n        fluxᵀn.q = un * state⁻.q\n        fluxᵀn.p = -un * state⁺.p\n    else\n        fluxᵀn.q = un * state⁺.q\n        fluxᵀn.p = -un * state⁻.p\n    end\nend\n\nfunction numerical_boundary_flux_first_order!(\n    ::ConservationTestModelNumFlux,\n    bctype,\n    bl::BalanceLaw,\n    fluxᵀn::Vars{S},\n    n::SVector,\n    state⁻::Vars{S},\n    aux⁻::Vars{A},\n    state⁺::Vars{S},\n    aux⁺::Vars{A},\n    t,\n    direction,\n    state1⁻::Vars{S},\n    aux1⁻::Vars{A},\n) where {S, A}\n    un = dot(n, aux⁻.vel)\n\n    if un > 0\n        fluxᵀn.q = un * state⁻.q\n        fluxᵀn.p = -un * state⁻.q\n    else\n        fluxᵀn.q = un * state⁻.p\n        fluxᵀn.p = -un * state⁻.p\n    end\nend\n\nfunction test_run(mpicomm, ArrayType, N, Nhorz, Rrange, timeend, FT, dt)\n\n    topl = StackedCubedSphereTopology(mpicomm, Nhorz, Rrange)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n        meshwarp = Topologies.equiangular_cubed_sphere_warp,\n    )\n    dg = DGModel(\n        ConservationTestModel(),\n        grid,\n        ConservationTestModelNumFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q = init_ode_state(dg, FT(0); init_on_cpu = true)\n\n    lsrk = LSRK54CarpenterKennedy(dg, Q; dt = dt, t0 = 0)\n\n    eng0 = norm(Q)\n    sum0 = weightedsum(Q)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\n    sum(Q₀) = %.16e\"\"\" eng0 sum0\n\n    max_mass_loss = FT(0)\n    max_mass_gain = FT(0)\n    cbmass = GenericCallbacks.EveryXSimulationSteps(1) do\n        cbsum = weightedsum(Q)\n        max_mass_loss = max(max_mass_loss, sum0 - cbsum)\n        max_mass_gain = max(max_mass_gain, cbsum - sum0)\n    end\n    solve!(Q, lsrk; timeend = timeend, callbacks = (cbmass,))\n\n    # Print some end of the simulation information\n    engf = norm(Q)\n    sumf = weightedsum(Q)\n    @info @sprintf \"\"\"Finished\n    norm(Q)            = %.16e\n    norm(Q) / norm(Q₀) = %.16e\n    norm(Q) - norm(Q₀) = %.16e\n    max mass loss      = %.16e\n    max mass gain      = %.16e\n    initial mass       = %.16e\n    \"\"\" engf engf / eng0 engf - eng0 max_mass_loss max_mass_gain sum0\n    max(max_mass_loss, max_mass_gain) / sum0\nend\n\nusing Test\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n\n    Nhorz = 4\n\n    tolerance = Dict(Float64 => 1e-15, Float32 => 1e-7)\n\n    @testset \"$(@__FILE__)\" for FT in (Float64, Float32)\n        dt = FT(1e-4)\n        timeend = 100 * dt\n        Rrange = range(FT(1), stop = FT(2), step = FT(1 // 4))\n\n        @info (ArrayType, FT)\n        delta_mass = test_run(\n            mpicomm,\n            ArrayType,\n            polynomialorder,\n            Nhorz,\n            Rrange,\n            timeend,\n            FT,\n            dt,\n        )\n        @test abs(delta_mass) < tolerance[FT]\n    end\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/courant.jl",
    "content": "using Test\nusing LinearAlgebra\nusing MPI\nusing StaticArrays\n\nusing ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Courant\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.Mesh.Geometry\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Orientations\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.VTK\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: kappa_d\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nconst p∞ = 10^5\nconst T∞ = 300.0\n\nfunction initialcondition!(problem, bl, state, aux, localgeo, t)\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n\n    coord = localgeo.coord\n\n    translation_speed::FT = 150\n    translation_angle::FT = pi / 4\n    α = translation_angle\n    u∞ = SVector(\n        FT(translation_speed * coord[1]),\n        FT(translation_speed * coord[1]),\n        FT(0),\n    )\n    _kappa_d::FT = kappa_d(param_set)\n\n    u = u∞\n    T = FT(T∞)\n    # adiabatic/isentropic relation\n    p = FT(p∞) * (T / FT(T∞))^(FT(1) / _kappa_d)\n    ρ = air_density(param_set, T, p)\n\n    state.ρ = ρ\n    state.ρu = ρ * u\n    e_kin = u' * u / 2\n    state.energy.ρe = ρ * total_energy(param_set, e_kin, FT(0), T)\n\n    nothing\nend\n\n\nlet\n    # boiler plate MPI stuff\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n    mpicomm = MPI.COMM_WORLD\n\n    # Mesh generation parameters\n    N = 4\n    Nq = N + 1\n    Neh = 10\n    Nev = 4\n\n    @testset \"$(@__FILE__) DGModel matrix\" begin\n        for FT in (Float64, Float32)\n            for dim in (2, 3)\n                connectivity = dim == 3 ? :full : :face\n                if dim == 2\n                    brickrange = (\n                        range(FT(0); length = Neh + 1, stop = 1),\n                        range(FT(1); length = Nev + 1, stop = 2),\n                    )\n                elseif dim == 3\n                    brickrange = (\n                        range(FT(0); length = Neh + 1, stop = 1),\n                        range(FT(0); length = Neh + 1, stop = 1),\n                        range(FT(1); length = Nev + 1, stop = 2),\n                    )\n                end\n                μ = FT(2)\n                topl = StackedBrickTopology(\n                    mpicomm,\n                    brickrange,\n                    connectivity = connectivity,\n                )\n\n\n\n                grid = DiscontinuousSpectralElementGrid(\n                    topl,\n                    FloatType = FT,\n                    DeviceArray = ArrayType,\n                    polynomialorder = N,\n                )\n\n                problem = AtmosProblem(\n                    boundaryconditions = (),\n                    init_state_prognostic = initialcondition!,\n                )\n\n                physics = AtmosPhysics{FT}(\n                    param_set;\n                    ref_state = NoReferenceState(),\n                    turbulence = ConstantDynamicViscosity(μ, WithDivergence()),\n                    moisture = DryModel(),\n                )\n\n                model = AtmosModel{FT}(\n                    AtmosLESConfigType,\n                    physics;\n                    problem = problem,\n                    source = (Gravity(),),\n                )\n\n                dg = DGModel(\n                    model,\n                    grid,\n                    RusanovNumericalFlux(),\n                    CentralNumericalFluxSecondOrder(),\n                    CentralNumericalFluxGradient(),\n                )\n\n                Δt = FT(1 // 200)\n\n                Q = init_ode_state(dg, FT(0))\n\n                Δx = min_node_distance(grid, EveryDirection())\n                Δx_v = min_node_distance(grid, VerticalDirection())\n                Δx_h = min_node_distance(grid, HorizontalDirection())\n\n                translation_speed = FT(norm([150.0, 150.0, 0.0]))\n\n                diff_speed_h = FT(μ / air_density(param_set, FT(T∞), FT(p∞)))\n                diff_speed_v = FT(μ / air_density(param_set, FT(T∞), FT(p∞)))\n                c_h =\n                    Δt *\n                    (translation_speed + soundspeed_air(param_set, FT(T∞))) /\n                    Δx_h\n                c_v = Δt * (soundspeed_air(param_set, FT(T∞))) / Δx_v\n                d_h = Δt * diff_speed_h / Δx_h^2\n                d_v = Δt * diff_speed_v / Δx_v^2\n                simtime = FT(0)\n\n                # tests for non diffusive courant number\n                rtol = FT === Float64 ? 1e-4 : 1f-2\n                @test courant(\n                    nondiffusive_courant,\n                    dg,\n                    model,\n                    Q,\n                    Δt,\n                    simtime,\n                    HorizontalDirection(),\n                ) ≈ c_h rtol = rtol\n                @test courant(\n                    nondiffusive_courant,\n                    dg,\n                    model,\n                    Q,\n                    Δt,\n                    simtime,\n                    VerticalDirection(),\n                ) ≈ c_v rtol = rtol\n\n                # tests for diffusive courant number\n                @test courant(\n                    diffusive_courant,\n                    dg,\n                    model,\n                    Q,\n                    Δt,\n                    simtime,\n                    HorizontalDirection(),\n                ) ≈ d_h\n                @test courant(\n                    diffusive_courant,\n                    dg,\n                    model,\n                    Q,\n                    Δt,\n                    simtime,\n                    VerticalDirection(),\n                ) ≈ d_v\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/DGMethods/custom_filter.jl",
    "content": "using Test\nusing ClimateMachine\nusing ClimateMachine.VariableTemplates: @vars, Vars\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.DGMethods: AbstractCustomFilter, apply!\nimport ClimateMachine\nimport ClimateMachine.BalanceLaws:\n    vars_state, init_state_auxiliary!, init_state_prognostic!\nusing MPI\nusing LinearAlgebra\n\nstruct CustomFilterTestModel <: BalanceLaw end\nstruct CustomTestFilter <: AbstractCustomFilter end\n\nvars_state(::CustomFilterTestModel, ::Auxiliary, FT) = @vars()\nvars_state(::CustomFilterTestModel, ::Prognostic, FT) where {N} = @vars(q::FT)\n\ninit_state_auxiliary!(::CustomFilterTestModel, _...) = nothing\n\nfunction init_state_prognostic!(\n    ::CustomFilterTestModel,\n    state::Vars,\n    aux::Vars,\n    localgeo,\n)\n    coord = localgeo.coord\n    state.q = hypot(coord[1], coord[2])\nend\n\n@testset \"Test custom filter\" begin\n    let\n        ClimateMachine.init()\n        N = 4\n        Ne = (2, 2, 2)\n\n        function ClimateMachine.DGMethods.custom_filter!(\n            ::CustomTestFilter,\n            bl::CustomFilterTestModel,\n            state::Vars,\n            aux::Vars,\n        )\n            state.q = state.q^2\n        end\n\n        @testset for FT in (Float64, Float32)\n            dim = 2\n            brickrange =\n                ntuple(j -> range(FT(-1); length = Ne[j] + 1, stop = 1), dim)\n            topl = ClimateMachine.Mesh.Topologies.BrickTopology(\n                MPI.COMM_WORLD,\n                brickrange,\n                periodicity = ntuple(j -> true, dim),\n            )\n\n            grid = ClimateMachine.Mesh.Grids.DiscontinuousSpectralElementGrid(\n                topl,\n                FloatType = FT,\n                DeviceArray = ClimateMachine.array_type(),\n                polynomialorder = N,\n            )\n\n            model = CustomFilterTestModel()\n            dg = ClimateMachine.DGMethods.DGModel(\n                model,\n                grid,\n                nothing,\n                nothing,\n                nothing;\n                state_gradient_flux = nothing,\n            )\n\n            Q = ClimateMachine.DGMethods.init_ode_state(dg)\n            data = Array(Q.realdata)\n\n            apply!(CustomTestFilter(), grid, model, Q, dg.state_auxiliary)\n\n            @test all(Array(Q.realdata) .== data .^ 2)\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/fv_reconstruction_test.jl",
    "content": "using Test\nusing StaticArrays\nusing ClimateMachine.Atmos:\n    AtmosProblem,\n    NoReferenceState,\n    AtmosPhysics,\n    AtmosModel,\n    DryModel,\n    ConstantDynamicViscosity,\n    AtmosLESConfigType,\n    HBFVReconstruction\nimport ClimateMachine.DGMethods.FVReconstructions: FVConstant, FVLinear, width\nusing ClimateMachine.Orientations\nimport StaticArrays: SUnitRange\nimport ClimateMachine.BalanceLaws:\n    Primitive, Prognostic, vars_state, number_states\nusing ClimateMachine.VariableTemplates: Vars\nusing CLIMAParameters\nusing CLIMAParameters.Planet: grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n\n\n# lin_func, quad_func, third_func, fourth_func\n#\n# ```\n# num_state_primitive::Int64\n# pointwise values::Array{FT,   num_state_primitive by length(ξ)}\n# integration values::Array{FT, num_state_primitive by length(ξ)}\n# ```\n\nfunction lin_func(ξ)\n    return 1, [(2 * ξ .+ 1)';], [(ξ .^ 2 .+ ξ)';]\nend\n\nfunction quad_func(ξ)\n    return 2,\n    [(3 * ξ .^ 2 .+ 1)'; (2 * ξ .+ 1)'],\n    [(ξ .^ 3 .+ ξ)'; (ξ .^ 2 .+ ξ)']\nend\n\nfunction third_func(ξ)\n    return 1, [(4 * ξ .^ 3 .+ 1)';], [(ξ .^ 4 .+ ξ)';]\nend\n\nfunction fourth_func(ξ)\n    return 2,\n    [(5 * ξ .^ 4 .+ 1)'; (3 * ξ .^ 2 .+ 1)'],\n    [(ξ .^ 5 .+ ξ)'; (ξ .^ 3 .+ ξ)']\nend\n\n\n@testset \"Hydrostatic balanced linear reconstruction test\" begin\n\n    function initialcondition!(problem, bl, state, aux, coords, t, args...) end\n\n    fv_recon! = FVLinear()\n    stencil_width = width(fv_recon!)\n    stencil_center = stencil_width + 1\n    stencil_diameter = 2stencil_width + 1\n    @test stencil_width == 1\n    func = lin_func\n\n    for FT in (Float64,)\n        physics = AtmosPhysics{FT}(\n            param_set;\n            ref_state = NoReferenceState(),\n            turbulence = ConstantDynamicViscosity(FT(0)),\n            moisture = DryModel(),\n        )\n        model = AtmosModel{FT}(\n            AtmosLESConfigType,\n            physics;\n            problem = AtmosProblem(;\n                physics = physics,\n                init_state_prognostic = initialcondition!,\n            ),\n            orientation = FlatOrientation(),\n        )\n\n        vars_prim = Vars{vars_state(model, Primitive(), FT)}\n\n        hb_recon! = HBFVReconstruction(model, fv_recon!)\n        _grav = FT(grav(param_set))\n\n        @test width(hb_recon!) == 1\n\n        num_state_prognostic = number_states(model, Prognostic())\n        num_state_primitive = number_states(model, Primitive())\n        local_state_face_primitive = ntuple(Val(2)) do _\n            MArray{Tuple{num_state_primitive}, FT}(undef)\n        end\n\n\n\n        # interior point reconstruction test\n        grid = FT[0; 1; 3; 6]\n        grid_c = (grid[2:end] + grid[1:(end - 1)]) / 2\n        local_cell_weights =\n            MArray{Tuple{stencil_diameter}, FT}(grid[2:end] - grid[1:(end - 1)])\n\n        # linear profile for all variables expect pressure\n        local_state_primitive = SVector(ntuple(Val(stencil_diameter)) do _\n            MArray{Tuple{num_state_primitive}, FT}(undef)\n        end...)\n\n        # values at the cell centers       1     2      3\n        _, uc, _ = func(grid_c)\n        # values at the cell faces     0.5   1.5*   2.5*     3.5\n        _, uf, _ = func(grid)\n        for i_d in 1:stencil_diameter\n            for i_p in 1:num_state_prognostic\n                local_state_primitive[i_d][i_p] = uc[i_d]\n            end\n        end\n\n        # pressure profile is updated to satisfy the discrete hydrostatic balance \n        p_surf = FT(100) # at the bottom wall\n        p_ref = p_surf\n        for i_d in 1:stencil_diameter\n            p_ref -=\n                vars_prim(local_state_primitive[i_d]).ρ *\n                _grav *\n                local_cell_weights[i_d] / 2\n            vars_prim(local_state_primitive[i_d]).p = p_ref\n            p_ref -=\n                vars_prim(local_state_primitive[i_d]).ρ *\n                _grav *\n                local_cell_weights[i_d] / 2\n        end\n\n        local_state_primitive_hb = copy(local_state_primitive)\n\n        # interior point reconstruction\n        hb_recon!(\n            local_state_face_primitive[1],\n            local_state_face_primitive[2],\n            local_state_primitive,\n            local_cell_weights,\n        )\n        # bottom face\n        @test vars_prim(local_state_face_primitive[1]).ρ ≈ uf[2]\n        @test vars_prim(local_state_face_primitive[1]).p ≈\n              p_surf -\n              vars_prim(local_state_primitive[1]).ρ *\n              _grav *\n              local_cell_weights[1]\n        # top face\n        @test vars_prim(local_state_face_primitive[2]).ρ ≈ uf[3]\n        @test vars_prim(local_state_face_primitive[2]).p ≈\n              p_surf -\n              vars_prim(local_state_primitive[1]).ρ *\n              _grav *\n              local_cell_weights[1] -\n              vars_prim(local_state_primitive[2]).ρ *\n              _grav *\n              local_cell_weights[2]\n\n        # make sure the \n        for i_d in 1:stencil_diameter\n            @test all(\n                local_state_primitive[i_d] ≈ local_state_primitive_hb[i_d],\n            )\n        end\n\n        @info \"Start boundary test\"\n\n\n        # boundary point reconstruction  \n        rng = SUnitRange(stencil_center, stencil_center)\n\n        hb_recon!(\n            local_state_face_primitive[1],\n            local_state_face_primitive[2],\n            local_state_primitive[rng],\n            local_cell_weights[rng],\n        )\n\n        # bottom face\n        @test vars_prim(local_state_face_primitive[1]).ρ ≈ uc[stencil_center]\n        @test vars_prim(local_state_face_primitive[1]).p ≈\n              vars_prim(local_state_primitive[stencil_center]).p +\n              uc[stencil_center] * _grav * local_cell_weights[stencil_center] /\n              2\n\n        # top face\n        @test vars_prim(local_state_face_primitive[2]).ρ ≈ uc[stencil_center]\n        @test vars_prim(local_state_face_primitive[2]).p ≈\n              vars_prim(local_state_primitive[stencil_center]).p -\n              uc[stencil_center] * _grav * local_cell_weights[stencil_center] /\n              2\n\n    end\n\n\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/grad_test.jl",
    "content": "using MPI\nusing StaticArrays\nusing ClimateMachine\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.DGMethods\nusing Printf\n\nusing ClimateMachine.BalanceLaws\n\nimport ClimateMachine.BalanceLaws: vars_state, nodal_init_state_auxiliary!\n\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nstruct GradTestModel{dim, dir} <: BalanceLaw end\n\nvars_state(m::GradTestModel, ::Auxiliary, T) = @vars begin\n    a::T\n    ∇a::SVector{3, T}\nend\nvars_state(::GradTestModel, ::Prognostic, T) = @vars()\n\nfunction nodal_init_state_auxiliary!(\n    ::GradTestModel{dim, dir},\n    aux::Vars,\n    tmp::Vars,\n    g::LocalGeometry,\n) where {dim, dir}\n    x, y, z = g.coord\n    if dim == 2\n        aux.a = x^2 + y^3 - x * y\n        if dir isa EveryDirection\n            aux.∇a = SVector(2 * x - y, 3 * y^2 - x, 0)\n        elseif dir isa HorizontalDirection\n            aux.∇a = SVector(2 * x - y, 0, 0)\n        elseif dir isa VerticalDirection\n            aux.∇a = SVector(0, 3 * y^2 - x, 0)\n        end\n    else\n        aux.a = x^2 + y^3 + z^2 * y^2 - x * y * z\n        if dir isa EveryDirection\n            aux.∇a = SVector(\n                2 * x - y * z,\n                3 * y^2 + 2 * z^2 * y - x * z,\n                2 * z * y^2 - x * y,\n            )\n        elseif dir isa HorizontalDirection\n            aux.∇a = SVector(2 * x - y * z, 3 * y^2 + 2 * z^2 * y - x * z, 0)\n        elseif dir isa VerticalDirection\n            aux.∇a = SVector(0, 0, 2 * z * y^2 - x * y)\n        end\n    end\nend\n\nusing Test\nfunction test_run(mpicomm, dim, direction, Ne, N, FT, ArrayType)\n    connectivity = dim == 3 ? :full : :face\n    brickrange = ntuple(j -> range(FT(0); length = Ne[j] + 1, stop = 3), dim)\n    topl = StackedBrickTopology(\n        mpicomm,\n        brickrange,\n        periodicity = ntuple(j -> false, dim),\n        connectivity = connectivity,\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    model = GradTestModel{dim, direction}()\n    dg = DGModel(\n        model,\n        grid,\n        nothing,\n        nothing,\n        nothing;\n        state_gradient_flux = nothing,\n    )\n\n    exact_aux = copy(dg.state_auxiliary)\n\n    auxiliary_field_gradient!(\n        model,\n        dg.state_auxiliary,\n        (:∇a,),\n        dg.state_auxiliary,\n        (:a,),\n        grid,\n        direction,\n    )\n\n    # Wrapping in Array ensure both GPU and CPU code use same approx\n    approx = Array(dg.state_auxiliary.∇a) ≈ Array(exact_aux.∇a)\n    err = euclidean_distance(exact_aux, dg.state_auxiliary)\n    return approx, err\nend\n\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    numelem = (5, 5, 5)\n\n    expected_result = Dict()\n    expected_result[Float64, 2, 1] = 6.2135207410935696e+00\n    expected_result[Float64, 2, 2] = 2.3700094936518794e+00\n    expected_result[Float64, 2, 3] = 8.7105082013050261e-01\n    expected_result[Float64, 2, 4] = 3.1401219279927611e-01\n\n    expected_result[Float64, 3, 1] = 7.9363467666175236e+00\n    expected_result[Float64, 3, 2] = 2.8059223082616098e+00\n    expected_result[Float64, 3, 3] = 9.9204334582727094e-01\n    expected_result[Float64, 3, 4] = 3.5074028853276679e-01\n\n    expected_result[Float32, 2, 1] = 6.2135176658630371e+00\n    expected_result[Float32, 2, 2] = 2.3700129985809326e+00\n    expected_result[Float32, 3, 1] = 7.9363408088684082e+00\n    expected_result[Float32, 3, 2] = 2.8059237003326416e+00\n\n\n    @testset for FT in (Float64, Float32)\n        lvls =\n            integration_testing || ClimateMachine.Settings.integration_testing ?\n            (FT === Float32 ? 2 : 4) : 1\n\n        @testset for polynomialorder in ((4, 4), (4, 0))\n            @testset for dim in 2:3\n                @testset for direction in (\n                    EveryDirection(),\n                    HorizontalDirection(),\n                    VerticalDirection(),\n                )\n                    err = zeros(FT, lvls)\n                    for l in 1:lvls\n                        approx, err[l] = test_run(\n                            mpicomm,\n                            dim,\n                            direction,\n                            ntuple(j -> 2^(l - 1) * numelem[j], dim),\n                            polynomialorder,\n                            FT,\n                            ArrayType,\n                        )\n                        if polynomialorder[end] != 0 ||\n                           direction isa HorizontalDirection\n                            @test approx\n                        else\n                            @test err[l] ≈ expected_result[FT, dim, l]\n                        end\n                    end\n                    if polynomialorder[end] != 0 ||\n                       direction isa HorizontalDirection\n                        @info begin\n                            msg = \"Polynomial order = $polynomialorder, direction = $direction\\n\"\n                            for l in 1:(lvls - 1)\n                                rate = log2(err[l]) - log2(err[l + 1])\n                                msg *= @sprintf(\n                                    \"\\n  rate for level %d = %e\\n\",\n                                    l,\n                                    rate\n                                )\n                            end\n                            msg\n                        end\n                    end\n                end\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/DGMethods/grad_test_sphere.jl",
    "content": "using MPI\nusing StaticArrays\nusing LinearAlgebra\nusing ClimateMachine\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.DGMethods\nusing Printf\n\nusing ClimateMachine.BalanceLaws\n\nimport ClimateMachine.BalanceLaws: vars_state, nodal_init_state_auxiliary!\n\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nstruct GradSphereTestModel{dir} <: BalanceLaw end\n\nvars_state(m::GradSphereTestModel, ::Auxiliary, T) = @vars begin\n    a::T\n    ∇a::SVector{3, T}\nend\nvars_state(::GradSphereTestModel, ::Prognostic, T) = @vars()\n\nfunction nodal_init_state_auxiliary!(\n    ::GradSphereTestModel{dir},\n    aux::Vars,\n    tmp::Vars,\n    g::LocalGeometry,\n) where {dir}\n    x, y, z = g.coord\n    r = hypot(x, y, z)\n    aux.a = r^3\n    if !(dir isa HorizontalDirection)\n        aux.∇a = 3 * r^2 * g.coord / r\n    else\n        aux.∇a = SVector(0, 0, 0)\n    end\nend\n\nusing Test\nfunction test_run(mpicomm, Ne_horz, Ne_vert, N, FT, ArrayType, direction)\n\n    Rrange = range(FT(1 // 2); length = Ne_vert + 1, stop = FT(1))\n    topl = StackedCubedSphereTopology(mpicomm, Ne_horz, Rrange)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n        meshwarp = Topologies.equiangular_cubed_sphere_warp,\n    )\n\n    model = GradSphereTestModel{direction}()\n    dg = DGModel(\n        model,\n        grid,\n        nothing,\n        nothing,\n        nothing;\n        state_gradient_flux = nothing,\n    )\n\n    exact_aux = copy(dg.state_auxiliary)\n\n    auxiliary_field_gradient!(\n        model,\n        dg.state_auxiliary,\n        (:∇a,),\n        dg.state_auxiliary,\n        (:a,),\n        grid,\n        direction,\n    )\n\n    err = euclidean_distance(exact_aux, dg.state_auxiliary)\n    return err\nend\n\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    base_Nhorz = 4\n    base_Nvert = 2\n\n    expected_result = Dict()\n    expected_result[(4, 4), 1] = 4.3759489495202896e-04\n    expected_result[(4, 4), 2] = 2.9065372851175251e-05\n    expected_result[(4, 4), 3] = 1.8457379514995729e-06\n    expected_result[(4, 4), 4] = 1.1582093840084037e-07\n\n    expected_result[(4, 0), 1] = 1.1070045305138052e+00\n    expected_result[(4, 0), 2] = 4.2750547196265593e-01\n    expected_result[(4, 0), 3] = 1.6041478911787385e-01\n    expected_result[(4, 0), 4] = 5.8570590697850776e-02\n\n    lvls =\n        integration_testing || ClimateMachine.Settings.integration_testing ? 4 :\n        1\n\n    @testset for FT in (Float64,)\n        @testset for polynomialorder in ((4, 4), (4, 0))\n            @testset for direction in (\n                EveryDirection(),\n                HorizontalDirection(),\n                VerticalDirection(),\n            )\n                err = zeros(FT, lvls)\n                @testset for l in 1:lvls\n                    Ne_horz = 2^(l - 1) * base_Nhorz\n                    Ne_vert = 2^(l - 1) * base_Nvert\n\n                    err[l] = test_run(\n                        mpicomm,\n                        Ne_horz,\n                        Ne_vert,\n                        polynomialorder,\n                        FT,\n                        ArrayType,\n                        direction,\n                    )\n                    if !(direction isa HorizontalDirection)\n                        @test err[l] ≈ expected_result[polynomialorder, l]\n                    else\n                        @test abs(err[l]) < FT(1.3e-13)\n                    end\n                end\n                if !(direction isa HorizontalDirection)\n                    @info begin\n                        msg = \"Polynomial order = $polynomialorder, direction = $direction\\n\"\n                        for l in 1:(lvls - 1)\n                            rate = log2(err[l]) - log2(err[l + 1])\n                            msg *= @sprintf(\n                                \"\\n  rate for level %d = %e\\n\",\n                                l,\n                                rate\n                            )\n                        end\n                        msg\n                    end\n                end\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/DGMethods/horizontal_integral_test.jl",
    "content": "using MPI\nusing StaticArrays\nusing Logging\nusing Printf\nusing LinearAlgebra\nusing Test\nimport KernelAbstractions: CPU\n\nusing ClimateMachine\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.VariableTemplates\nusing Thermodynamics\n\nfunction run_test1(mpicomm, dim, Ne, N, FT, ArrayType)\n    warpfun =\n        (ξ1, ξ2, ξ3) -> begin\n            x1 = ξ1 + (ξ1 - 1 / 2) * cos(2 * π * ξ2 * ξ3) / 4\n            x2 = ξ2 + (ξ2 - 1 / 2) * cos(2 * π * ξ2 * ξ3) / 4\n            x3 = ξ3 + ξ1 / 4 + sin(2 * π * ξ1) / 16\n            return (x1, x2, x3)\n        end\n\n    brickrange = (\n        range(FT(0); length = Ne + 1, stop = 1),\n        range(FT(0); length = Ne + 1, stop = 1),\n        range(FT(0); length = 2, stop = 1),\n    )\n    topl = StackedBrickTopology(\n        mpicomm,\n        brickrange,\n        periodicity = (false, false, false),\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n        meshwarp = warpfun,\n    )\n\n    nrealelem = length(topl.realelems)\n    Nq = N + 1\n    Nqk = dimensionality(grid) == 2 ? 1 : Nq\n    vgeo = grid.vgeo\n    localvgeo = array_device(vgeo) isa CPU ? vgeo : Array(vgeo)\n\n    S = zeros(Nqk)\n    S1 = zeros(Nqk)\n\n    for e in 1:nrealelem\n        for k in 1:Nqk\n            for j in 1:Nq\n                for i in 1:Nq\n                    ijk = i + Nq * ((j - 1) + Nq * (k - 1))\n                    S[k] +=\n                        localvgeo[ijk, grid.x1id, e] *\n                        localvgeo[ijk, grid.MHid, e]\n                    S1[k] += localvgeo[ijk, grid.MHid, e]\n                end\n            end\n        end\n    end\n\n    Stot = zeros(Nqk)\n    S1tot = zeros(Nqk)\n    Err = 0.0\n\n    for k in 1:Nqk\n        Stot[k] = MPI.Reduce(S[k], +, 0, mpicomm)\n        S1tot[k] = MPI.Reduce(S1[k], +, 0, mpicomm)\n        Err += (0.5 - Stot[k] / S1tot[k])^2\n    end\n    Err = sqrt(Err / Nqk)\n    @test Err < 2e-15\nend\n\nfunction run_test2(mpicomm, dim, Ne, N, FT, ArrayType)\n    warpfun =\n        (ξ1, ξ2, ξ3) -> begin\n            x1 = sin(2 * π * ξ3) / 16 + ξ1\n            x2 = ξ2 + (ξ2 - 1 / 2) * cos(2 * π * ξ2 * ξ3) / 4\n            x3 = ξ3 + sin(2π * (ξ1)) / 20\n            return (x1, x2, x3)\n        end\n\n    brickrange = (\n        range(FT(0); length = Ne + 1, stop = 1),\n        range(FT(0); length = Ne + 1, stop = 1),\n        range(FT(0); length = 2, stop = 1),\n    )\n    topl = StackedBrickTopology(\n        mpicomm,\n        brickrange,\n        periodicity = (false, false, false),\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n        meshwarp = warpfun,\n    )\n\n    nrealelem = length(topl.realelems)\n    Nq = N + 1\n    Nqk = dimensionality(grid) == 2 ? 1 : Nq\n    vgeo = grid.vgeo\n    localvgeo = array_device(vgeo) isa CPU ? vgeo : Array(vgeo)\n\n    S = zeros(Nqk)\n    S1 = zeros(Nqk)\n    K = zeros(Nqk)\n\n    for e in 1:nrealelem\n        for k in 1:Nqk\n            for j in 1:Nq\n                for i in 1:Nq\n                    ijk = i + Nq * ((j - 1) + Nq * (k - 1))\n                    S[k] +=\n                        localvgeo[ijk, grid.x1id, e] *\n                        localvgeo[ijk, grid.MHid, e]\n                    S1[k] += localvgeo[ijk, grid.MHid, e]\n                    K[k] = localvgeo[ijk, grid.x3id, e]\n                end\n            end\n        end\n    end\n\n    Stot = zeros(Nqk)\n    S1tot = zeros(Nqk)\n    Err = 0.0\n\n    for k in 1:Nqk\n        Stot[k] = MPI.Reduce(S[k], +, 0, mpicomm)\n        S1tot[k] = MPI.Reduce(S1[k], +, 0, mpicomm)\n        Err += (0.5 + sin(2 * π * K[k]) / 16 - Stot[k] / S1tot[k])^2\n    end\n    Err = sqrt(Err / Nqk)\n    @test 2e-15 > Err\nend\n\nfunction run_test3(mpicomm, dim, Ne, N, FT, ArrayType)\n    base_Nhorz = 4\n    base_Nvert = 2\n    Rinner = 1 // 2\n    Router = 1\n    expected_result = [\n        -4.5894269717905445e-8 -1.1473566985387151e-8\n        -2.0621904184281448e-10 -5.155431637149377e-11\n        -8.72191208145523e-13 -2.1715962361668062e-13\n    ]\n    for l in 1:3\n        Nhorz = 2^(l - 1) * base_Nhorz\n        Nvert = 2^(l - 1) * base_Nvert\n        Rrange = grid1d(FT(Rinner), FT(Router); nelem = Nvert)\n        topl = StackedCubedSphereTopology(mpicomm, Nhorz, Rrange)\n        grid = DiscontinuousSpectralElementGrid(\n            topl,\n            FloatType = FT,\n            DeviceArray = ArrayType,\n            polynomialorder = N,\n            meshwarp = Topologies.equiangular_cubed_sphere_warp,\n        )\n\n        nrealelem = length(topl.realelems)\n        Nq = N + 1\n        Nqk = dimensionality(grid) == 2 ? 1 : Nq\n        vgeo = grid.vgeo\n        localvgeo = array_device(vgeo) isa CPU ? vgeo : Array(vgeo)\n\n        topology = grid.topology\n        nvertelem = topology.stacksize\n        nhorzelem = div(nrealelem, nvertelem)\n\n        Surfout = 0\n        Surfin = 0\n\n        for ev in 1:nvertelem\n            for eh in 1:nhorzelem\n                e = ev + (eh - 1) * nvertelem\n                for i in 1:Nq\n                    for j in 1:Nq\n                        for k in 1:Nqk\n                            ijk = i + Nq * ((j - 1) + Nqk * (k - 1))\n                            if (k == Nqk && ev == nvertelem)\n                                Surfout += localvgeo[ijk, grid.MHid, e]\n                            end\n                            if (k == 1 && ev == 1)\n                                Surfin += localvgeo[ijk, grid.MHid, e]\n                            end\n                        end\n                    end\n                end\n            end\n        end\n        Surfouttot = MPI.Reduce(Surfout, +, 0, MPI.COMM_WORLD)\n        Surfintot = MPI.Reduce(Surfin, +, 0, MPI.COMM_WORLD)\n        @test (4 * π * Router^2 - Surfouttot) ≈ expected_result[l, 1] rtol =\n            1e-3 atol = eps(FT) * 4 * π * Router^2\n        @test (4 * π * Rinner^2 - Surfintot) ≈ expected_result[l, 2] rtol = 1e-3 atol =\n            eps(FT) * 4 * π * Rinner^2\n    end\nend\n\n# Test for 2D integral\nfunction run_test4(mpicomm, dim, Ne, N, FT, ArrayType)\n    brickrange = ntuple(j -> range(FT(0); length = Ne + 1, stop = 1), 2)\n    topl = StackedBrickTopology(\n        mpicomm,\n        brickrange,\n        periodicity = ntuple(j -> true, 2),\n        connectivity = :face,\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n    nrealelem = length(topl.realelems)\n    Nq = N + 1\n    vgeo = grid.vgeo\n    localvgeo = array_device(vgeo) isa CPU ? vgeo : Array(vgeo)\n\n    S = zeros(Nq)\n\n    for e in 1:nrealelem\n        for i in 1:Nq\n            for j in 1:Nq\n                ij = i + Nq * (j - 1)\n                S[j] +=\n                    localvgeo[ij, grid.x1id, e] * localvgeo[ij, grid.MHid, e]\n            end\n        end\n    end\n\n    Err = 0\n    for j in 1:Nq\n        Err += (S[j] - 0.5)^2\n    end\n\n    Err = sqrt(Err / Nq)\n    @test Err <= 1e-15\nend\n\nfunction run_test5(mpicomm, dim, Ne, N, FT, ArrayType)\n    warpfun = (ξ1, ξ2, ξ3) -> begin\n        x1 = cos(π * ξ2) / 16 + abs(ξ1)\n        x2 = ξ2\n        x3 = ξ3\n        return (x1, x2, x3)\n    end\n\n    brickrange = ntuple(j -> range(FT(0); length = Ne + 1, stop = 1), 2)\n    topl = StackedBrickTopology(\n        mpicomm,\n        brickrange,\n        periodicity = ntuple(j -> true, 2),\n        connectivity = :face,\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n        meshwarp = warpfun,\n    )\n\n    nrealelem = length(topl.realelems)\n    Nq = N + 1\n    vgeo = grid.vgeo\n    localvgeo = array_device(vgeo) isa CPU ? vgeo : Array(vgeo)\n\n    S = zeros(Nq)\n    J = zeros(Nq)\n\n    for e in 1:nrealelem\n        for i in 1:Nq\n            for j in 1:Nq\n                ij = i + Nq * (j - 1)\n                S[j] +=\n                    localvgeo[ij, grid.x1id, e] * localvgeo[ij, grid.MHid, e]\n                J[j] = localvgeo[ij, grid.x2id, e]\n            end\n        end\n    end\n\n    Err = 0\n    for j in 1:Nq\n        Err += (S[j] - 0.5 - cos(π * J[j]) / 16)^2\n    end\n\n    Err = sqrt(Err / Nq)\n    @test Err < 1e-15\nend\n\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    FT = Float64\n    dim = 3\n    Ne = 1\n    polynomialorder = 4\n\n    @info (ArrayType, FT, dim)\n\n    @testset \"horizontal_integral\" begin\n        run_test1(mpicomm, dim, Ne, polynomialorder, FT, ArrayType)\n        run_test2(mpicomm, dim, Ne, polynomialorder, FT, ArrayType)\n        run_test3(mpicomm, dim, Ne, polynomialorder, FT, ArrayType)\n        run_test4(mpicomm, dim, Ne, polynomialorder, FT, ArrayType)\n        run_test5(mpicomm, dim, Ne, polynomialorder, FT, ArrayType)\n    end\nend\n"
  },
  {
    "path": "test/Numerics/DGMethods/integral_test.jl",
    "content": "using MPI\nusing StaticArrays\nusing ClimateMachine\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing Printf\nusing LinearAlgebra\nusing Logging\n\nusing ClimateMachine.BalanceLaws\n\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    flux_first_order!,\n    flux_second_order!,\n    source!,\n    wavespeed,\n    boundary_state!,\n    nodal_init_state_auxiliary!,\n    init_state_prognostic!,\n    update_auxiliary_state!,\n    indefinite_stack_integral!,\n    reverse_indefinite_stack_integral!,\n    integral_load_auxiliary_state!,\n    integral_set_auxiliary_state!,\n    reverse_integral_load_auxiliary_state!,\n    reverse_integral_set_auxiliary_state!\n\nimport ClimateMachine.DGMethods: init_ode_state\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\n\n\nstruct IntegralTestModel{dim} <: BalanceLaw end\n\nvars_state(::IntegralTestModel, ::DownwardIntegrals, T) = @vars(a::T, b::T)\nvars_state(::IntegralTestModel, ::UpwardIntegrals, T) = @vars(a::T, b::T)\nvars_state(m::IntegralTestModel, ::Auxiliary, T) = @vars(\n    int::vars_state(m, UpwardIntegrals(), T),\n    rev_int::vars_state(m, DownwardIntegrals(), T),\n    coord::SVector{3, T},\n    a::T,\n    b::T,\n    rev_a::T,\n    rev_b::T\n)\n\nvars_state(::IntegralTestModel, ::AbstractStateType, T) = @vars()\n\nflux_first_order!(::IntegralTestModel, _...) = nothing\nflux_second_order!(::IntegralTestModel, _...) = nothing\nsource!(::IntegralTestModel, _...) = nothing\nboundary_state!(_, ::IntegralTestModel, _...) = nothing\ninit_state_prognostic!(::IntegralTestModel, _...) = nothing\nwavespeed(::IntegralTestModel, _...) = 1\n\nfunction nodal_init_state_auxiliary!(\n    ::IntegralTestModel{dim},\n    aux::Vars,\n    tmp::Vars,\n    g::LocalGeometry,\n) where {dim}\n    x, y, z = aux.coord = g.coord\n    if dim == 2\n        aux.a = x * y\n        aux.b = 2 * x * y + sin(x) * y^2 / 2 - (z - 1)^2 * y^3 / 3\n        y_top = 3\n        a_top = x * y_top\n        b_top = 2 * x * y_top + sin(x) * y_top^2 / 2 - (z - 1)^2 * y_top^3 / 3\n        aux.rev_a = a_top - aux.a\n        aux.rev_b = b_top - aux.b\n    else\n        aux.a = x * z + y * z\n        aux.b = 2 * x * z + sin(x) * y * z - (1 + (z - 1)^3) * y^2 / 3\n        zz_top = 3\n        a_top = x * zz_top + y * zz_top\n        b_top =\n            2 * x * zz_top + sin(x) * y * zz_top -\n            (1 + (zz_top - 1)^3) * y^2 / 3\n        aux.rev_a = a_top - aux.a\n        aux.rev_b = b_top - aux.b\n    end\nend\n\nfunction update_auxiliary_state!(\n    dg::DGModel,\n    m::IntegralTestModel,\n    Q::MPIStateArray,\n    t::Real,\n    elems::UnitRange,\n)\n    indefinite_stack_integral!(dg, m, Q, dg.state_auxiliary, t, elems)\n    reverse_indefinite_stack_integral!(dg, m, Q, dg.state_auxiliary, t, elems)\n\n    return true\nend\n\n@inline function integral_load_auxiliary_state!(\n    m::IntegralTestModel{dim},\n    integrand::Vars,\n    state::Vars,\n    aux::Vars,\n) where {dim}\n    x, y, z = aux.coord\n    integrand.a = x + (dim == 3 ? y : 0)\n    integrand.b = 2 * x + sin(x) * y - (z - 1)^2 * y^2\nend\n\n@inline function integral_set_auxiliary_state!(\n    m::IntegralTestModel,\n    aux::Vars,\n    integral::Vars,\n)\n    aux.int.a = integral.a\n    aux.int.b = integral.b\nend\n\n@inline function reverse_integral_load_auxiliary_state!(\n    m::IntegralTestModel,\n    integral::Vars,\n    state::Vars,\n    aux::Vars,\n)\n    integral.a = aux.int.a\n    integral.b = aux.int.b\nend\n\n@inline function reverse_integral_set_auxiliary_state!(\n    m::IntegralTestModel,\n    aux::Vars,\n    integral::Vars,\n)\n    aux.rev_int.a = integral.a\n    aux.rev_int.b = integral.b\nend\n\nusing Test\nfunction test_run(mpicomm, dim, Ne, N, FT, ArrayType)\n    connectivity = dim == 3 ? :full : :face\n    brickrange = ntuple(j -> range(FT(0); length = Ne[j] + 1, stop = 3), dim)\n    topl = StackedBrickTopology(\n        mpicomm,\n        brickrange,\n        periodicity = ntuple(j -> true, dim),\n        connectivity = connectivity,\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n    dg = DGModel(\n        IntegralTestModel{dim}(),\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n    dQdt = similar(Q)\n\n    dg(dQdt, Q, nothing, 0.0)\n\n    # Wrapping in Array ensure both GPU and CPU code use same approx\n    if N[end] > 0\n        # Forward integral a\n        @test Array(dg.state_auxiliary.data[:, 1, :]) ≈\n              Array(dg.state_auxiliary.data[:, 8, :])\n        # Forward integral b\n        @test Array(dg.state_auxiliary.data[:, 2, :]) ≈\n              Array(dg.state_auxiliary.data[:, 9, :])\n        # Reverse integral a\n        @test Array(dg.state_auxiliary.data[:, 3, :]) ≈\n              Array(dg.state_auxiliary.data[:, 10, :])\n        # Reverse integral b\n        @test Array(dg.state_auxiliary.data[:, 4, :]) ≈\n              Array(dg.state_auxiliary.data[:, 11, :])\n    else\n        # For N = 0 we only compare the first integral which is the integral of\n        # a vertical constant function; N = 0 can also integrate linears exactly\n        # since we use the midpoint rule to compute the face value, but the\n        # averaging procedure we use below does not work in this case\n        Nq = polynomialorders(grid) .+ 1\n\n        # Reshape the data array to be (dofs, vertical elem, horizontal elem)\n        A_faces = reshape(\n            Array(dg.state_auxiliary.data[:, 1, :]),\n            prod(Nq),\n            Ne[end],               # Vertical element is fastest on stacked meshes\n            prod(Ne[1:(end - 1)]), # Horiztonal elements\n        )\n        A_center_exact = reshape(\n            Array(dg.state_auxiliary.data[:, 8, :]),\n            prod(Nq),\n            Ne[end],               # Vertical element is fastest on stacked meshes\n            prod(Ne[1:(end - 1)]), # Horiztonal elements\n        )\n        # With N = 0, the integral will return the values in the faces. Namely,\n        # verical element index `eV` will be the value of the integral on the\n        # face ABOVE element `eV`. Namely,\n        #    A_faces[n, eV, eH]\n        # will be degree of freedom `n`, in horizontal element stack `eH`, and\n        # face `eV + 1/2`.\n        #\n        # The exact values stored in `A_center_exact` are actually at the cell\n        # centers because these are computed using the `init_state_auxiliary!`\n        # which evaluates using the cell centers.\n        #\n        # This mismatch means we need to convert from faces to cell centers for\n        # comparison, and we do this using averaging to go from faces to cell\n        # centers.\n\n        # Storage for the averaging\n        A_center = similar(A_faces)\n\n        # Bottom cell value is average of 0 and top face of cell\n        A_center[:, 1, :] .= A_faces[:, 1, :] / 2\n\n        # Remaining cells are average of the two faces\n        A_center[:, 2:end, :, :] .=\n            (A_faces[:, 1:(end - 1), :] + A_faces[:, 2:end, :]) / 2\n\n        # Compare the exact and computed\n        @test A_center ≈ A_center_exact\n\n        # We do the same things for the reverse integral, the only difference is\n        # now the values\n        #    RA_faces[n, eV, eH]\n        # will be degree of freedom `n`, in horizontal element stack `eH`, and\n        # face `eV - 1/2` (e.g., the face below element `eV`\n        # Reshape the data array to be (dofs, vertical elm, horizontal elm)\n        RA_faces = reshape(\n            Array(dg.state_auxiliary.data[:, 3, :]),\n            prod(Nq),\n            Ne[end],               # Vertical element is fastest on stacked meshes\n            prod(Ne[1:(end - 1)]), # Horiztonal elements\n        )\n        RA_center_exact = reshape(\n            Array(dg.state_auxiliary.data[:, 10, :]),\n            prod(Nq),\n            Ne[end],               # Vertical element is fastest on stacked meshes\n            prod(Ne[1:(end - 1)]), # Horiztonal elements\n        )\n\n        # Storage for the averaging\n        RA_center = similar(RA_faces)\n\n        # Top cell value is average of 0 and top face of cell\n        RA_center[:, end, :] .= RA_faces[:, end, :] / 2\n\n        # Remaining cells are average of the two faces\n        RA_center[:, 1:(end - 1), :, :] .=\n            (RA_faces[:, 1:(end - 1), :] + RA_faces[:, 2:end, :]) / 2\n\n        # Compare the exact and computed\n        @test RA_center ≈ RA_center_exact\n    end\nend\n\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    numelem = (5, 6, 7)\n    lvls = 1\n\n    for polynomialorder in ((4, 4), (4, 3), (4, 0))\n        for FT in (Float64,)\n            for dim in 2:3\n                err = zeros(FT, lvls)\n                for l in 1:lvls\n                    @info (ArrayType, FT, dim, polynomialorder)\n                    test_run(\n                        mpicomm,\n                        dim,\n                        ntuple(j -> 2^(l - 1) * numelem[j], dim),\n                        polynomialorder,\n                        FT,\n                        ArrayType,\n                    )\n                end\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/DGMethods/integral_test_sphere.jl",
    "content": "using MPI\nusing StaticArrays\nusing ClimateMachine\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing Printf\nusing LinearAlgebra\nusing Logging\n\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw,\n    Prognostic,\n    Auxiliary,\n    GradientFlux,\n    UpwardIntegrals,\n    DownwardIntegrals\n\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    integral_load_auxiliary_state!,\n    flux_first_order!,\n    flux_second_order!,\n    source!,\n    wavespeed,\n    update_auxiliary_state!,\n    indefinite_stack_integral!,\n    reverse_indefinite_stack_integral!,\n    boundary_conditions,\n    boundary_state!,\n    compute_gradient_argument!,\n    nodal_init_state_auxiliary!,\n    init_state_prognostic!,\n    integral_set_auxiliary_state!,\n    reverse_integral_load_auxiliary_state!,\n    reverse_integral_set_auxiliary_state!\n\nimport ClimateMachine.DGMethods: init_ode_state\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\n\nstruct IntegralTestSphereModel{T} <: BalanceLaw\n    Rinner::T\n    Router::T\nend\n\nfunction update_auxiliary_state!(\n    dg::DGModel,\n    m::IntegralTestSphereModel,\n    Q::MPIStateArray,\n    t::Real,\n    elems::UnitRange,\n)\n    indefinite_stack_integral!(dg, m, Q, dg.state_auxiliary, t, elems)\n    reverse_indefinite_stack_integral!(dg, m, Q, dg.state_auxiliary, t, elems)\n\n    return true\nend\n\nvars_state(::IntegralTestSphereModel, ::UpwardIntegrals, T) = @vars(v::T, r::T)\nvars_state(::IntegralTestSphereModel, ::DownwardIntegrals, T) =\n    @vars(v::T, r::T)\nvars_state(m::IntegralTestSphereModel, ::Auxiliary, T) = @vars(\n    int::vars_state(m, UpwardIntegrals(), T),\n    rev_int::vars_state(m, DownwardIntegrals(), T),\n    r::T,\n    v::T\n)\n\nvars_state(::IntegralTestSphereModel, ::Prognostic, T) = @vars()\nvars_state(::IntegralTestSphereModel, ::GradientFlux, T) = @vars()\n\nflux_first_order!(::IntegralTestSphereModel, _...) = nothing\nflux_second_order!(::IntegralTestSphereModel, _...) = nothing\nsource!(::IntegralTestSphereModel, _...) = nothing\nboundary_conditions(::IntegralTestSphereModel) = (nothing,)\nboundary_state!(_, ::Nothing, ::IntegralTestSphereModel, _...) = nothing\ninit_state_prognostic!(::IntegralTestSphereModel, _...) = nothing\nwavespeed(::IntegralTestSphereModel, _...) = 1\n\nfunction nodal_init_state_auxiliary!(\n    m::IntegralTestSphereModel,\n    aux::Vars,\n    tmp::Vars,\n    g::LocalGeometry,\n)\n    x, y, z = g.coord\n    aux.r = hypot(x, y, z)\n    θ = atan(y, x)\n    ϕ = asin(z / aux.r)\n    # Exact integral\n    aux.v = 1 + cos(ϕ)^2 * sin(θ)^2 + sin(ϕ)^2\n    aux.int.v = exp(-aux.v * aux.r^2) - exp(-aux.v * m.Rinner^2)\n    aux.int.r = aux.r - m.Rinner\n    aux.rev_int.v = exp(-aux.v * m.Router^2) - exp(-aux.v * aux.r^2)\n    aux.rev_int.r = m.Router - aux.r\nend\n\n@inline function integral_load_auxiliary_state!(\n    m::IntegralTestSphereModel,\n    integrand::Vars,\n    state::Vars,\n    aux::Vars,\n)\n    integrand.v = -2 * aux.r * aux.v * exp(-aux.v * aux.r^2)\n    integrand.r = 1\nend\n\n@inline function integral_set_auxiliary_state!(\n    m::IntegralTestSphereModel,\n    aux::Vars,\n    integral::Vars,\n)\n    aux.int.v = integral.v\n    aux.int.r = integral.r\nend\n\n@inline function reverse_integral_load_auxiliary_state!(\n    m::IntegralTestSphereModel,\n    integral::Vars,\n    state::Vars,\n    aux::Vars,\n)\n    integral.v = aux.int.v\n    integral.r = aux.int.r\nend\n\n@inline function reverse_integral_set_auxiliary_state!(\n    m::IntegralTestSphereModel,\n    aux::Vars,\n    integral::Vars,\n)\n    aux.rev_int.v = integral.v\n    aux.rev_int.r = integral.r\nend\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nusing Test\nfunction test_run(mpicomm, topl, ArrayType, N, FT, Rinner, Router)\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n        meshwarp = Topologies.equiangular_cubed_sphere_warp,\n    )\n    dg = DGModel(\n        IntegralTestSphereModel(Rinner, Router),\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n    dQdt = similar(Q)\n\n    exact_aux = copy(dg.state_auxiliary)\n    dg(dQdt, Q, nothing, 0.0)\n    (int_r_ind, rev_int_r_ind) = varsindices(\n        vars_state(dg.balance_law, Auxiliary(), FT),\n        (\"int.r\", \"rev_int.r\"),\n    )\n\n    # Since N = 0 integrals live at the faces we need to average values to the\n    # faces for comparison\n    if N == 0\n\n        nvertelem = topl.stacksize\n        nhorzelem = div(length(topl.elems), nvertelem)\n        naux = size(exact_aux, 2)\n        ndof = size(exact_aux, 1)\n\n        # Reshape the data array to be (dofs, naux, vertical elm, horizontal elm)\n        aux =\n            reshape(dg.state_auxiliary.data, (ndof, naux, nvertelem, nhorzelem))\n\n        # average forward integrals to cell centers\n        for ind in varsindices(\n            vars_state(dg.balance_law, Auxiliary(), FT),\n            (\"int.r\", \"int.v\"),\n        )\n\n            # Store the computed face values\n            A_faces = aux[:, ind, :, :]\n\n            # Bottom cell value is average of 0 and top face of cell\n            aux[:, ind, 1, :] .= A_faces[:, 1, :] / 2\n\n            # Remaining cells are average of the two faces\n            aux[:, ind, 2:end, :] .=\n                (A_faces[:, 1:(end - 1), :] + A_faces[:, 2:end, :]) / 2\n        end\n\n        # average reverse integrals to cell centers\n        for ind in varsindices(\n            vars_state(dg.balance_law, Auxiliary(), FT),\n            (\"rev_int.r\", \"rev_int.v\"),\n        )\n\n            # Store the computed face values\n            RA_faces = aux[:, ind, :, :]\n\n            # Bottom cell value is average of 0 and top face of cell\n            aux[:, ind, end, :] .= RA_faces[:, end, :] / 2\n\n            # Remaining cells are average of the two faces\n            aux[:, ind, 1:(end - 1), :] .=\n                (RA_faces[:, 1:(end - 1), :] + RA_faces[:, 2:end, :]) / 2\n        end\n    end\n    # We should be exact for the integral of ∫_{R_{inner}}^{r} 1\n    @test exact_aux[:, int_r_ind, :] ≈ dg.state_auxiliary[:, int_r_ind, :]\n    @test exact_aux[:, rev_int_r_ind, :] ≈\n          dg.state_auxiliary[:, rev_int_r_ind, :]\n    euclidean_distance(exact_aux, dg.state_auxiliary)\nend\n\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    base_Nhorz = 4\n    base_Nvert = 2\n    Rinner = 1 // 2\n    Router = 1\n\n    expected_result = Dict()\n    expected_result[0] = [\n        2.2259657670562167e-02\n        5.6063943176909315e-03\n        1.4042479532664005e-03\n        3.5122834187695408e-04\n    ]\n    expected_result[1] = [\n        1.5934735012225074e-02\n        4.0030667455285352e-03\n        1.0020652111566574e-03\n        2.5059856392475887e-04\n    ]\n    expected_result[4] = [\n        4.662884229467401e-7,\n        7.218989778540723e-9,\n        1.1258613174916711e-10,\n        1.7587739986848968e-12,\n    ]\n    lvls = integration_testing ? length(expected_result[4]) : 1\n\n    for N in (0, 1, 4)\n        for FT in (Float64,)\n            err = zeros(FT, lvls)\n            for l in 1:lvls\n                @info (ArrayType, FT, \"sphere\", N, l)\n                Nhorz = 2^(l - 1) * base_Nhorz\n                Nvert = 2^(l - 1) * base_Nvert\n                Rrange = grid1d(FT(Rinner), FT(Router); nelem = Nvert)\n                topl = StackedCubedSphereTopology(mpicomm, Nhorz, Rrange)\n                err[l] = test_run(\n                    mpicomm,\n                    topl,\n                    ArrayType,\n                    N,\n                    FT,\n                    FT(Rinner),\n                    FT(Router),\n                )\n                @test expected_result[N][l] ≈ err[l] rtol = 1e-3 atol = eps(FT)\n            end\n            if lvls > 1\n                @info begin\n                    msg = \"polynomialorder order $N\"\n                    for l in 1:(lvls - 1)\n                        rate = log2(err[l]) - log2(err[l + 1])\n                        msg *= @sprintf(\"\\n  rate for level %d = %e\\n\", l, rate)\n                    end\n                    msg\n                end\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/DGMethods/remainder_model.jl",
    "content": "using Test\nusing LinearAlgebra\nusing MPI\nusing Random\nusing StaticArrays\n\nusing ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Orientations\nusing Thermodynamics.TemperatureProfiles\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: planet_radius\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n\"\"\"\n    main()\n\nRun this test problem\n\"\"\"\nfunction main()\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 5\n    numelem_horz = 10\n    numelem_vert = 5\n\n    @testset \"remainder model\" begin\n        for FT in (Float64,)# Float32)\n            result = test_run(\n                mpicomm,\n                polynomialorder,\n                numelem_horz,\n                numelem_vert,\n                ArrayType,\n                FT,\n            )\n        end\n    end\nend\n\nfunction test_run(\n    mpicomm,\n    polynomialorder,\n    numelem_horz,\n    numelem_vert,\n    ArrayType,\n    FT,\n)\n\n    # Structure to pass around to setup the simulation\n    setup = RemainderTestSetup{FT}()\n\n    # Create the cubed sphere mesh\n    _planet_radius::FT = planet_radius(param_set)\n    vert_range = grid1d(\n        _planet_radius,\n        FT(_planet_radius + setup.domain_height),\n        nelem = numelem_vert,\n    )\n    topology = StackedCubedSphereTopology(mpicomm, numelem_horz, vert_range)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorder,\n        meshwarp = equiangular_cubed_sphere_warp,\n    )\n    T_profile = IsothermalProfile(param_set, setup.T_ref)\n\n    # This is the base model which defines all the data (all other DGModels\n    # for substepping components will piggy-back off of this models data)\n    fullphysics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = HydrostaticState(T_profile),\n        turbulence = Vreman(FT(0.23)),\n        moisture = DryModel(),\n    )\n    fullmodel = AtmosModel{FT}(\n        AtmosLESConfigType,\n        fullphysics;\n        orientation = SphericalOrientation(),\n        init_state_prognostic = setup,\n        source = (Gravity(),),\n    )\n    dg = DGModel(\n        fullmodel,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n    Random.seed!(1235)\n    Q = init_ode_state(dg, FT(0); init_on_cpu = true)\n\n    acousticmodel = AtmosAcousticGravityLinearModel(fullmodel)\n\n    acoustic_dg = DGModel(\n        acousticmodel,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient();\n        direction = EveryDirection(),\n        state_auxiliary = dg.state_auxiliary,\n    )\n    vacoustic_dg = DGModel(\n        acousticmodel,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient();\n        direction = VerticalDirection(),\n        state_auxiliary = dg.state_auxiliary,\n    )\n    hacoustic_dg = DGModel(\n        acousticmodel,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient();\n        direction = HorizontalDirection(),\n        state_auxiliary = dg.state_auxiliary,\n    )\n\n    # Create some random data to check the wavespeed function with\n    nM = rand(3)\n    nM /= norm(nM)\n    state_prognostic = Vars{vars_state(dg.balance_law, Prognostic(), FT)}(rand(\n        FT,\n        number_states(dg.balance_law, Prognostic()),\n    ))\n    state_auxiliary = Vars{vars_state(dg.balance_law, Auxiliary(), FT)}(rand(\n        FT,\n        number_states(dg.balance_law, Auxiliary()),\n    ))\n    full_wavespeed = wavespeed(\n        dg.balance_law,\n        nM,\n        state_prognostic,\n        state_auxiliary,\n        FT(0),\n        (EveryDirection(),),\n    )\n    acoustic_wavespeed = wavespeed(\n        acoustic_dg.balance_law,\n        nM,\n        state_prognostic,\n        state_auxiliary,\n        FT(0),\n        (EveryDirection(),),\n    )\n\n    # Evaluate the full tendency\n    full_tendency = similar(Q)\n    dg(full_tendency, Q, nothing, 0; increment = false)\n\n    # Evaluate various splittings\n    split_tendency = similar(Q)\n\n    # Check pulling acoustic model out\n    @testset \"full acoustic\" begin\n        rem_dg = remainder_DGModel(\n            dg,\n            (acoustic_dg,);\n            numerical_flux_first_order = (\n                dg.numerical_flux_first_order,\n                (acoustic_dg.numerical_flux_first_order,),\n            ),\n        )\n        rem_dg(split_tendency, Q, nothing, 0; increment = false)\n        acoustic_dg(split_tendency, Q, nothing, 0; increment = true)\n        A = Array(full_tendency.data)\n        B = Array(split_tendency.data)\n        # Test that we have a fully discrete splitting\n        @test all(isapprox.(\n            A,\n            B,\n            rtol = sqrt(eps(FT)),\n            atol = 10 * sqrt(eps(FT)),\n        ))\n\n        # Test that wavespeeds are split by direction\n        every_wavespeed = full_wavespeed .- acoustic_wavespeed\n        horz_wavespeed = -zero(FT)\n        vert_wavespeed = -zero(FT)\n        @test all(\n            every_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (EveryDirection(),),\n            ),\n        )\n        @test all(\n            horz_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (HorizontalDirection(),),\n            ),\n        )\n        @test all(\n            vert_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (VerticalDirection(),),\n            ),\n        )\n        @test all(\n            every_wavespeed .+ horz_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (EveryDirection(), HorizontalDirection()),\n            ),\n        )\n        @test all(\n            every_wavespeed .+ vert_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (EveryDirection(), VerticalDirection()),\n            ),\n        )\n    end\n\n    # Check pulling acoustic model but as two pieces\n    @testset \"horizontal and vertical acoustic\" begin\n        rem_dg = remainder_DGModel(\n            dg,\n            (hacoustic_dg, vacoustic_dg);\n            numerical_flux_first_order = (\n                dg.numerical_flux_first_order,\n                (\n                    hacoustic_dg.numerical_flux_first_order,\n                    vacoustic_dg.numerical_flux_first_order,\n                ),\n            ),\n        )\n        rem_dg(split_tendency, Q, nothing, 0; increment = false)\n        vacoustic_dg(split_tendency, Q, nothing, 0; increment = true)\n        hacoustic_dg(split_tendency, Q, nothing, 0; increment = true)\n        A = Array(full_tendency.data)\n        B = Array(split_tendency.data)\n        # Test that we have a fully discrete splitting\n        @test all(isapprox.(\n            A,\n            B,\n            rtol = sqrt(eps(FT)),\n            atol = 10 * sqrt(eps(FT)),\n        ))\n\n        # Test that wavespeeds are split by direction\n        every_wavespeed = full_wavespeed\n        horz_wavespeed = -acoustic_wavespeed\n        vert_wavespeed = -acoustic_wavespeed\n        @test all(\n            every_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (EveryDirection(),),\n            ),\n        )\n        @test all(\n            horz_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (HorizontalDirection(),),\n            ),\n        )\n        @test all(\n            vert_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (VerticalDirection(),),\n            ),\n        )\n        @test all(\n            every_wavespeed .+ horz_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (EveryDirection(), HorizontalDirection()),\n            ),\n        )\n        @test all(\n            every_wavespeed .+ vert_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (EveryDirection(), VerticalDirection()),\n            ),\n        )\n    end\n\n    # Check pulling horizontal acoustic model\n    @testset \"horizontal acoustic\" begin\n        rem_dg = remainder_DGModel(\n            dg,\n            (hacoustic_dg,);\n            numerical_flux_first_order = (\n                dg.numerical_flux_first_order,\n                (hacoustic_dg.numerical_flux_first_order,),\n            ),\n        )\n        rem_dg(split_tendency, Q, nothing, 0; increment = false)\n        hacoustic_dg(split_tendency, Q, nothing, 0; increment = true)\n        A = Array(full_tendency.data)\n        B = Array(split_tendency.data)\n        # Test that we have a fully discrete splitting\n        @test all(isapprox.(\n            A,\n            B,\n            rtol = sqrt(eps(FT)),\n            atol = 10 * sqrt(eps(FT)),\n        ))\n\n        # Test that wavespeeds are split by direction\n        every_wavespeed = full_wavespeed\n        horz_wavespeed = -acoustic_wavespeed\n        vert_wavespeed = -zero(eltype(FT))\n        @test all(\n            every_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (EveryDirection(),),\n            ),\n        )\n        @test all(\n            horz_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (HorizontalDirection(),),\n            ),\n        )\n        @test all(\n            vert_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (VerticalDirection(),),\n            ),\n        )\n        @test all(\n            every_wavespeed .+ horz_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (EveryDirection(), HorizontalDirection()),\n            ),\n        )\n        @test all(\n            every_wavespeed .+ vert_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (EveryDirection(), VerticalDirection()),\n            ),\n        )\n    end\n\n    # Check pulling vertical acoustic model\n    @testset \"vertical acoustic\" begin\n        rem_dg = remainder_DGModel(\n            dg,\n            (vacoustic_dg,);\n            numerical_flux_first_order = (\n                dg.numerical_flux_first_order,\n                (vacoustic_dg.numerical_flux_first_order,),\n            ),\n        )\n        rem_dg(split_tendency, Q, nothing, 0; increment = false)\n        vacoustic_dg(split_tendency, Q, nothing, 0; increment = true)\n        A = Array(full_tendency.data)\n        B = Array(split_tendency.data)\n        # Test that we have a fully discrete splitting\n        @test all(isapprox.(\n            A,\n            B,\n            rtol = sqrt(eps(FT)),\n            atol = 10 * sqrt(eps(FT)),\n        ))\n\n        # Test that wavespeeds are split by direction\n        every_wavespeed = full_wavespeed\n        horz_wavespeed = -zero(eltype(FT))\n        vert_wavespeed = -acoustic_wavespeed\n        @test all(\n            every_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (EveryDirection(),),\n            ),\n        )\n        @test all(\n            horz_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (HorizontalDirection(),),\n            ),\n        )\n        @test all(\n            vert_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (VerticalDirection(),),\n            ),\n        )\n        @test all(\n            every_wavespeed .+ horz_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (EveryDirection(), HorizontalDirection()),\n            ),\n        )\n        @test all(\n            every_wavespeed .+ vert_wavespeed .≈ wavespeed(\n                rem_dg.balance_law,\n                nM,\n                state_prognostic,\n                state_auxiliary,\n                FT(0),\n                (EveryDirection(), VerticalDirection()),\n            ),\n        )\n    end\nend\n\nBase.@kwdef struct RemainderTestSetup{FT}\n    domain_height::FT = 10e3\n    T_ref::FT = 300\nend\n\nfunction (setup::RemainderTestSetup)(problem, bl, state, aux, localgeo, t)\n    FT = eltype(state)\n\n    # Vary around the reference state by 10% and a random velocity field\n    state.ρ = (4 + rand(FT)) / 5 + aux.ref_state.ρ\n    state.ρu = 2 * (@SVector rand(FT, 3)) .- 1\n    state.energy.ρe = (4 + rand(FT)) / 5 + aux.ref_state.ρe\n\n    nothing\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/DGMethods/vars_test.jl",
    "content": "using MPI\nusing StaticArrays\nusing ClimateMachine\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing Printf\nusing LinearAlgebra\nusing Logging\n\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, GradientFlux\n\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    flux_first_order!,\n    flux_second_order!,\n    source!,\n    wavespeed,\n    update_auxiliary_state!,\n    boundary_state!,\n    nodal_init_state_auxiliary!,\n    init_state_prognostic!\n\nimport ClimateMachine.DGMethods: init_ode_state\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\n\n\nstruct VarsTestModel{dim} <: BalanceLaw end\n\nvars_state(::VarsTestModel, ::Prognostic, T) = @vars(x::T, coord::SVector{3, T})\nvars_state(m::VarsTestModel, ::Auxiliary, T) =\n    @vars(coord::SVector{3, T}, polynomial::T)\nvars_state(m::VarsTestModel, ::GradientFlux, T) = @vars()\n\nflux_first_order!(::VarsTestModel, _...) = nothing\nflux_second_order!(::VarsTestModel, _...) = nothing\nsource!(::VarsTestModel, _...) = nothing\nboundary_state!(_, ::VarsTestModel, _...) = nothing\nwavespeed(::VarsTestModel, _...) = 1\n\nfunction init_state_prognostic!(\n    m::VarsTestModel,\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t::Real,\n)\n    @inbounds state.x = localgeo.coord[1]\n    state.coord = localgeo.coord\nend\n\nfunction nodal_init_state_auxiliary!(\n    ::VarsTestModel{dim},\n    aux::Vars,\n    tmp::Vars,\n    g::LocalGeometry,\n) where {dim}\n    x, y, z = aux.coord = g.coord\n    aux.polynomial = x * y + x * z + y * z\nend\n\nusing Test\nfunction test_run(mpicomm, dim, Ne, N, FT, ArrayType)\n\n    brickrange = ntuple(j -> range(FT(0); length = Ne[j] + 1, stop = 3), dim)\n    connectivity = dim == 3 ? :full : :face\n    topl = StackedBrickTopology(\n        mpicomm,\n        brickrange,\n        periodicity = ntuple(j -> true, dim),\n        connectivity = connectivity,\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n    dg = DGModel(\n        VarsTestModel{dim}(),\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n    @test Array(Q.x)[:, 1, :] == Array(Q.coord)[:, 1, :]\n    @test Array(dg.state_auxiliary.coord) == Array(Q.coord)\n    x = Array(Q.coord)[:, 1, :]\n    y = Array(Q.coord)[:, 2, :]\n    z = Array(Q.coord)[:, 3, :]\n    @test Array(dg.state_auxiliary.polynomial)[:, 1, :] ≈\n          x .* y + x .* z + y .* z\nend\n\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    numelem = (5, 5, 5)\n    lvls = 1\n\n    polynomialorder = 4\n\n    for FT in (Float64,) #Float32)\n        for dim in 2:3\n            err = zeros(FT, lvls)\n            for l in 1:lvls\n                @info (ArrayType, FT, dim)\n                test_run(\n                    mpicomm,\n                    dim,\n                    ntuple(j -> 2^(l - 1) * numelem[j], dim),\n                    polynomialorder,\n                    FT,\n                    ArrayType,\n                )\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/ESDGMethods/DryAtmos/DryAtmos.jl",
    "content": "using ClimateMachine.VariableTemplates: Vars, Grad, @vars\nusing ClimateMachine.BalanceLaws\nimport ClimateMachine.BalanceLaws:\n    BalanceLaw,\n    vars_state,\n    state_to_entropy_variables!,\n    entropy_variables_to_state!,\n    nodal_init_state_auxiliary!,\n    init_state_prognostic!,\n    state_to_entropy,\n    boundary_conditions,\n    boundary_state!,\n    wavespeed,\n    flux_first_order!,\n    source!\n\nusing StaticArrays\nusing LinearAlgebra: dot, I\nusing ClimateMachine.DGMethods.NumericalFluxes: NumericalFluxFirstOrder\nimport ClimateMachine.DGMethods.NumericalFluxes:\n    EntropyConservative,\n    numerical_volume_conservative_flux_first_order!,\n    numerical_volume_fluctuation_flux_first_order!,\n    ave,\n    logave,\n    numerical_flux_first_order!,\n    numerical_flux_second_order!,\n    numerical_boundary_flux_second_order!\n\nusing ClimateMachine.Orientations:\n    Orientation, FlatOrientation, SphericalOrientation\nusing ClimateMachine.Atmos: NoReferenceState\nusing ClimateMachine.Grids\n\nusing CLIMAParameters: AbstractEarthParameterSet\nusing CLIMAParameters.Planet: grav, R_d, cp_d, cv_d, planet_radius, MSLP, Omega\n\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nconst total_energy = false\nconst fluctuation_gravity = false\n\n@inline gamma(ps::EarthParameterSet) = cp_d(ps) / cv_d(ps)\n\nabstract type AbstractDryAtmosProblem end\n\nstruct DryAtmosModel{D, O, P, RS, S, DS} <: BalanceLaw\n    orientation::O\n    problem::P\n    ref_state::RS\n    sources::S\n    drag_source::DS\nend\nfunction DryAtmosModel{D}(\n    orientation,\n    problem::AbstractDryAtmosProblem;\n    ref_state = NoReferenceState(),\n    sources = (),\n    drag_source = NoDrag(),\n) where {D}\n    O = typeof(orientation)\n    P = typeof(problem)\n    RS = typeof(ref_state)\n    S = typeof(sources)\n    DS = typeof(drag_source)\n    DryAtmosModel{D, O, P, RS, S, DS}(\n        orientation,\n        problem,\n        ref_state,\n        sources,\n        drag_source,\n    )\nend\n\nboundary_conditions(::DryAtmosModel) = (1, 2)\n# XXX: Hack for Impenetrable.\n#      This is NOT entropy stable / conservative!!!!\nfunction boundary_state!(\n    ::NumericalFluxFirstOrder,\n    bctype,\n    ::DryAtmosModel,\n    state⁺,\n    aux⁺,\n    n,\n    state⁻,\n    aux⁻,\n    _...,\n)\n    state⁺.ρ = state⁻.ρ\n    state⁺.ρu -= 2 * dot(state⁻.ρu, n) .* SVector(n)\n    state⁺.ρe = state⁻.ρe\n    aux⁺.Φ = aux⁻.Φ\nend\n\nfunction init_state_prognostic!(m::DryAtmosModel, args...)\n    init_state_prognostic!(m, m.problem, args...)\nend\n\nfunction nodal_init_state_auxiliary!(\n    m::DryAtmosModel,\n    state_auxiliary,\n    tmp,\n    geom,\n)\n    init_state_auxiliary!(m, m.orientation, state_auxiliary, geom)\n    init_state_auxiliary!(m, m.ref_state, state_auxiliary, geom)\n    init_state_auxiliary!(m, m.problem, state_auxiliary, geom)\nend\n\nfunction altitude(::DryAtmosModel{dim}, ::FlatOrientation, geom) where {dim}\n    @inbounds geom.coord[dim]\nend\n\nfunction altitude(::DryAtmosModel, ::SphericalOrientation, geom)\n    FT = eltype(geom)\n    _planet_radius::FT = planet_radius(param_set)\n    norm(geom.coord) - _planet_radius\nend\n\n\"\"\"\n    init_state_auxiliary!(\n        m::DryAtmosModel,\n        aux::Vars,\n        geom::LocalGeometry\n        )\n\nInitialize geopotential for the `DryAtmosModel`.\n\"\"\"\nfunction init_state_auxiliary!(\n    ::DryAtmosModel{dim},\n    ::FlatOrientation,\n    state_auxiliary,\n    geom,\n) where {dim}\n    FT = eltype(state_auxiliary)\n    _grav = FT(grav(param_set))\n    @inbounds r = geom.coord[dim]\n    state_auxiliary.Φ = _grav * r\n    state_auxiliary.∇Φ =\n        dim == 2 ? SVector{3, FT}(0, _grav, 0) : SVector{3, FT}(0, 0, _grav)\nend\nfunction init_state_auxiliary!(\n    ::DryAtmosModel,\n    ::SphericalOrientation,\n    state_auxiliary,\n    geom,\n)\n    FT = eltype(state_auxiliary)\n    _grav = FT(grav(param_set))\n    r = norm(geom.coord)\n    state_auxiliary.Φ = _grav * r\n    state_auxiliary.∇Φ = _grav * geom.coord / r\nend\n\nfunction init_state_auxiliary!(\n    ::DryAtmosModel,\n    ::NoReferenceState,\n    state_auxiliary,\n    geom,\n) end\n\nfunction init_state_auxiliary!(\n    ::DryAtmosModel,\n    ::AbstractDryAtmosProblem,\n    state_auxiliary,\n    geom,\n) end\n\nstruct DryReferenceState{TP}\n    temperature_profile::TP\nend\nvars_state(::DryAtmosModel, ::DryReferenceState, ::Auxiliary, FT) =\n    @vars(T::FT, p::FT, ρ::FT, ρe::FT)\nvars_state(::DryAtmosModel, ::NoReferenceState, ::Auxiliary, FT) = @vars()\n\nfunction init_state_auxiliary!(\n    m::DryAtmosModel,\n    ref_state::DryReferenceState,\n    state_auxiliary,\n    geom,\n)\n    FT = eltype(state_auxiliary)\n    z = altitude(m, m.orientation, geom)\n    T, p = ref_state.temperature_profile(param_set, z)\n\n    _R_d::FT = R_d(param_set)\n    ρ = p / (_R_d * T)\n    Φ = state_auxiliary.Φ\n    ρu = SVector{3, FT}(0, 0, 0)\n\n    state_auxiliary.ref_state.T = T\n    state_auxiliary.ref_state.p = p\n    state_auxiliary.ref_state.ρ = ρ\n    state_auxiliary.ref_state.ρe = totalenergy(ρ, ρu, p, Φ)\nend\n\n@inline function flux_first_order!(\n    m::DryAtmosModel,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n)\n    ρ = state.ρ\n    ρinv = 1 / ρ\n    ρu = state.ρu\n    ρe = state.ρe\n    u = ρinv * ρu\n    Φ = aux.Φ\n\n    p = pressure(ρ, ρu, ρe, Φ)\n\n    flux.ρ = ρ * u\n    flux.ρu = p * I + ρ * u .* u'\n    flux.ρe = u * (state.ρe + p)\nend\n\nfunction wavespeed(\n    ::DryAtmosModel,\n    nM,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n)\n    ρ = state.ρ\n    ρu = state.ρu\n    ρe = state.ρe\n    Φ = aux.Φ\n    p = pressure(ρ, ρu, ρe, Φ)\n\n    u = ρu / ρ\n    uN = abs(dot(nM, u))\n    return uN + soundspeed(ρ, p)\nend\n\n\"\"\"\n    pressure(ρ, ρu, ρe, Φ)\n\nCompute the pressure given density `ρ`, momentum `ρu`, total energy `ρe`, and\ngravitational potential `Φ`.\n\"\"\"\nfunction pressure(ρ, ρu, ρe, Φ)\n    FT = eltype(ρ)\n    γ = FT(gamma(param_set))\n    if total_energy\n        (γ - 1) * (ρe - dot(ρu, ρu) / 2ρ - ρ * Φ)\n    else\n        (γ - 1) * (ρe - dot(ρu, ρu) / 2ρ)\n    end\nend\n\n\"\"\"\n    totalenergy(ρ, ρu, p, Φ)\n\nCompute the total energy given density `ρ`, momentum `ρu`, pressure `p`, and\ngravitational potential `Φ`.\n\"\"\"\nfunction totalenergy(ρ, ρu, p, Φ)\n    FT = eltype(ρ)\n    γ = FT(gamma(param_set))\n    if total_energy\n        return p / (γ - 1) + dot(ρu, ρu) / 2ρ + ρ * Φ\n    else\n        return p / (γ - 1) + dot(ρu, ρu) / 2ρ\n    end\nend\n\n\"\"\"\n    soundspeed(ρ, p)\n\nCompute the speed of sound from the density `ρ` and pressure `p`.\n\"\"\"\nfunction soundspeed(ρ, p)\n    FT = eltype(ρ)\n    γ = FT(gamma(param_set))\n    sqrt(γ * p / ρ)\nend\n\n\"\"\"\n    vars_state(::DryAtmosModel, ::Prognostic, FT)\n\nThe prognostic state variables for the `DryAtmosModel` are density `ρ`, momentum `ρu`,\nand total energy `ρe`\n\"\"\"\nfunction vars_state(::DryAtmosModel, ::Prognostic, FT)\n    @vars begin\n        ρ::FT\n        ρu::SVector{3, FT}\n        ρe::FT\n    end\nend\n\n\"\"\"\n    vars_state(::DryAtmosModel, ::Auxiliary, FT)\n\nThe auxiliary variables for the `DryAtmosModel` is gravitational potential\n`Φ`\n\"\"\"\nfunction vars_state(m::DryAtmosModel, st::Auxiliary, FT)\n    @vars begin\n        Φ::FT\n        ∇Φ::SVector{3, FT} # TODO: only needed for the linear model\n        ref_state::vars_state(m, m.ref_state, st, FT)\n        problem::vars_state(m, m.problem, st, FT)\n    end\nend\nvars_state(::DryAtmosModel, ::AbstractDryAtmosProblem, ::Auxiliary, FT) =\n    @vars()\n\n\"\"\"\n    vars_state(::DryAtmosModel, ::Entropy, FT)\n\nThe entropy variables for the `DryAtmosModel` correspond to the state\nvariables density `ρ`, momentum `ρu`, and total energy `ρe` as well as the\nauxiliary variable gravitational potential `Φ`\n\"\"\"\nfunction vars_state(::DryAtmosModel, ::Entropy, FT)\n    @vars begin\n        ρ::FT\n        ρu::SVector{3, FT}\n        ρe::FT\n        Φ::FT\n    end\nend\n\n\"\"\"\n    state_to_entropy_variables!(\n        ::DryAtmosModel,\n        entropy::Vars,\n        state::Vars,\n        aux::Vars,\n    )\n\nSee [`BalanceLaws.state_to_entropy_variables!`](@ref)\n\"\"\"\nfunction state_to_entropy_variables!(\n    ::DryAtmosModel,\n    entropy::Vars,\n    state::Vars,\n    aux::Vars,\n)\n    ρ, ρu, ρe, Φ = state.ρ, state.ρu, state.ρe, aux.Φ\n\n    FT = eltype(state)\n    γ = FT(gamma(param_set))\n\n    p = pressure(ρ, ρu, ρe, Φ)\n    s = log(p / ρ^γ)\n    b = ρ / 2p\n    u = ρu / ρ\n\n    if total_energy\n        entropy.ρ = (γ - s) / (γ - 1) - (dot(u, u) - 2Φ) * b\n    else\n        entropy.ρ = (γ - s) / (γ - 1) - (dot(u, u)) * b\n    end\n    entropy.ρu = 2b * u\n    entropy.ρe = -2b\n    entropy.Φ = 2ρ * b\nend\n\n\"\"\"\n    entropy_variables_to_state!(\n        ::DryAtmosModel,\n        state::Vars,\n        aux::Vars,\n        entropy::Vars,\n    )\n\nSee [`BalanceLaws.entropy_variables_to_state!`](@ref)\n\"\"\"\nfunction entropy_variables_to_state!(\n    ::DryAtmosModel,\n    state::Vars,\n    aux::Vars,\n    entropy::Vars,\n)\n    FT = eltype(state)\n    β = entropy\n    γ = FT(gamma(param_set))\n\n    b = -β.ρe / 2\n    ρ = β.Φ / (2b)\n    ρu = ρ * β.ρu / (2b)\n\n    p = ρ / (2b)\n    s = log(p / ρ^γ)\n    Φ = dot(ρu, ρu) / (2 * ρ^2) - ((γ - s) / (γ - 1) - β.ρ) / (2b)\n\n    ρe = p / (γ - 1) + dot(ρu, ρu) / (2ρ) + ρ * Φ\n\n    state.ρ = ρ\n    state.ρu = ρu\n    state.ρe = ρe\n    aux.Φ = Φ\nend\n\nfunction state_to_entropy(::DryAtmosModel, state::Vars, aux::Vars)\n    FT = eltype(state)\n    ρ, ρu, ρe, Φ = state.ρ, state.ρu, state.ρe, aux.Φ\n    p = pressure(ρ, ρu, ρe, Φ)\n    γ = FT(gamma(param_set))\n    s = log(p / ρ^γ)\n    η = -ρ * s / (γ - 1)\n    return η\nend\n\nfunction numerical_volume_conservative_flux_first_order!(\n    ::EntropyConservative,\n    ::DryAtmosModel,\n    F::Grad,\n    state_1::Vars,\n    aux_1::Vars,\n    state_2::Vars,\n    aux_2::Vars,\n)\n    FT = eltype(F)\n    ρ_1, ρu_1, ρe_1 = state_1.ρ, state_1.ρu, state_1.ρe\n    ρ_2, ρu_2, ρe_2 = state_2.ρ, state_2.ρu, state_2.ρe\n    Φ_1, Φ_2 = aux_1.Φ, aux_2.Φ\n    u_1 = ρu_1 / ρ_1\n    u_2 = ρu_2 / ρ_2\n    p_1 = pressure(ρ_1, ρu_1, ρe_1, Φ_1)\n    p_2 = pressure(ρ_2, ρu_2, ρe_2, Φ_2)\n    b_1 = ρ_1 / 2p_1\n    b_2 = ρ_2 / 2p_2\n\n    ρ_avg = ave(ρ_1, ρ_2)\n    u_avg = ave(u_1, u_2)\n    b_avg = ave(b_1, b_2)\n    Φ_avg = ave(Φ_1, Φ_2)\n\n    usq_avg = ave(dot(u_1, u_1), dot(u_2, u_2))\n\n    ρ_log = logave(ρ_1, ρ_2)\n    b_log = logave(b_1, b_2)\n    α = b_avg * ρ_log / 2b_1\n\n    γ = FT(gamma(param_set))\n\n    Fρ = u_avg * ρ_log\n    Fρu = u_avg * Fρ' + ρ_avg / 2b_avg * I\n    if total_energy\n        Fρe =\n            (1 / (2 * (γ - 1) * b_log) - usq_avg / 2 + Φ_avg) * Fρ + Fρu * u_avg\n    else\n        Fρe = (1 / (2 * (γ - 1) * b_log) - usq_avg / 2) * Fρ + Fρu * u_avg\n    end\n\n    F.ρ += Fρ\n    F.ρu += Fρu\n    F.ρe += Fρe\nend\n\nfunction numerical_volume_fluctuation_flux_first_order!(\n    ::NumericalFluxFirstOrder,\n    ::DryAtmosModel,\n    D::Grad,\n    state_1::Vars,\n    aux_1::Vars,\n    state_2::Vars,\n    aux_2::Vars,\n)\n    if fluctuation_gravity\n        FT = eltype(D)\n        ρ_1, ρu_1, ρe_1 = state_1.ρ, state_1.ρu, state_1.ρe\n        ρ_2, ρu_2, ρe_2 = state_2.ρ, state_2.ρu, state_2.ρe\n        Φ_1, Φ_2 = aux_1.Φ, aux_2.Φ\n        p_1 = pressure(ρ_1, ρu_1, ρe_1, Φ_1)\n        p_2 = pressure(ρ_2, ρu_2, ρe_2, Φ_2)\n        b_1 = ρ_1 / 2p_1\n        b_2 = ρ_2 / 2p_2\n\n        ρ_log = logave(ρ_1, ρ_2)\n        b_avg = ave(b_1, b_2)\n        α = b_avg * ρ_log / 2b_1\n\n        D.ρu -= α * (Φ_1 - Φ_2) * I\n    end\nend\n\nstruct CentralVolumeFlux <: NumericalFluxFirstOrder end\nfunction numerical_volume_conservative_flux_first_order!(\n    ::CentralVolumeFlux,\n    m::DryAtmosModel,\n    F::Grad,\n    state_1::Vars,\n    aux_1::Vars,\n    state_2::Vars,\n    aux_2::Vars,\n)\n    FT = eltype(F)\n    F_1 = similar(F)\n    flux_first_order!(m, F_1, state_1, aux_1, FT(0), EveryDirection())\n\n    F_2 = similar(F)\n    flux_first_order!(m, F_2, state_2, aux_2, FT(0), EveryDirection())\n\n    parent(F) .= (parent(F_1) .+ parent(F_2)) ./ 2\nend\n\nstruct KGVolumeFlux <: NumericalFluxFirstOrder end\nfunction numerical_volume_conservative_flux_first_order!(\n    ::KGVolumeFlux,\n    m::DryAtmosModel,\n    F::Grad,\n    state_1::Vars,\n    aux_1::Vars,\n    state_2::Vars,\n    aux_2::Vars,\n)\n    Φ_1 = aux_1.Φ\n    ρ_1 = state_1.ρ\n    ρu_1 = state_1.ρu\n    ρe_1 = state_1.ρe\n    u_1 = ρu_1 / ρ_1\n    e_1 = ρe_1 / ρ_1\n    p_1 = pressure(ρ_1, ρu_1, ρe_1, Φ_1)\n\n    Φ_2 = aux_2.Φ\n    ρ_2 = state_2.ρ\n    ρu_2 = state_2.ρu\n    ρe_2 = state_2.ρe\n    u_2 = ρu_2 / ρ_2\n    e_2 = ρe_2 / ρ_2\n    p_2 = pressure(ρ_2, ρu_2, ρe_2, Φ_2)\n\n    ρ_avg = ave(ρ_1, ρ_2)\n    u_avg = ave(u_1, u_2)\n    e_avg = ave(e_1, e_2)\n    p_avg = ave(p_1, p_2)\n\n    F.ρ = ρ_avg * u_avg\n    F.ρu = p_avg * I + ρ_avg * u_avg .* u_avg'\n    F.ρe = ρ_avg * u_avg * e_avg + p_avg * u_avg\nend\n\n\nstruct Coriolis end\nfunction source!(\n    m::DryAtmosModel,\n    ::Coriolis,\n    source,\n    state_prognostic,\n    state_auxiliary,\n)\n    FT = eltype(state_prognostic)\n    _Omega::FT = Omega(param_set)\n    # note: this assumes a SphericalOrientation\n    source.ρu -= SVector(0, 0, 2 * _Omega) × state_prognostic.ρu\nend\n\nfunction source!(m::DryAtmosModel, source, state_prognostic, state_auxiliary)\n    ntuple(Val(length(m.sources))) do s\n        Base.@_inline_meta\n        source!(m, m.sources[s], source, state_prognostic, state_auxiliary)\n    end\nend\n\n\nstruct EntropyConservativeWithPenalty <: NumericalFluxFirstOrder end\nfunction numerical_flux_first_order!(\n    numerical_flux::EntropyConservativeWithPenalty,\n    balance_law::BalanceLaw,\n    fluxᵀn::Vars{S},\n    normal_vector::SVector,\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n    direction,\n) where {S, A}\n\n    FT = eltype(fluxᵀn)\n\n    numerical_flux_first_order!(\n        EntropyConservative(),\n        balance_law,\n        fluxᵀn,\n        normal_vector,\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n        direction,\n    )\n    fluxᵀn = parent(fluxᵀn)\n\n    wavespeed⁻ = wavespeed(\n        balance_law,\n        normal_vector,\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        t,\n        direction,\n    )\n    wavespeed⁺ = wavespeed(\n        balance_law,\n        normal_vector,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n        direction,\n    )\n    max_wavespeed = max.(wavespeed⁻, wavespeed⁺)\n    penalty =\n        max_wavespeed .* (parent(state_prognostic⁻) - parent(state_prognostic⁺))\n\n    fluxᵀn .+= penalty / 2\nend\n\nBase.@kwdef struct MatrixFlux{FT} <: NumericalFluxFirstOrder\n    Mcut::FT = 0\n    low_mach::Bool = false\n    kinetic_energy_preserving::Bool = false\nend\n\nfunction numerical_flux_first_order!(\n    numerical_flux::MatrixFlux,\n    balance_law::BalanceLaw,\n    fluxᵀn::Vars{S},\n    normal_vector::SVector,\n    state_prognostic⁻::Vars{S},\n    state_auxiliary⁻::Vars{A},\n    state_prognostic⁺::Vars{S},\n    state_auxiliary⁺::Vars{A},\n    t,\n    direction,\n) where {S, A}\n\n    FT = eltype(fluxᵀn)\n    numerical_flux_first_order!(\n        EntropyConservative(),\n        balance_law,\n        fluxᵀn,\n        normal_vector,\n        state_prognostic⁻,\n        state_auxiliary⁻,\n        state_prognostic⁺,\n        state_auxiliary⁺,\n        t,\n        direction,\n    )\n    fluxᵀn = parent(fluxᵀn)\n\n    γ = FT(gamma(param_set))\n\n    low_mach = numerical_flux.low_mach\n    Mcut = numerical_flux.Mcut\n    kinetic_energy_preserving = numerical_flux.kinetic_energy_preserving\n\n    ω = FT(π) / 3\n    δ = FT(π) / 5\n    random_unit_vector = SVector(sin(ω) * cos(δ), cos(ω) * cos(δ), sin(δ))\n    # tangent space basis\n    τ1 = random_unit_vector × normal_vector\n    τ2 = τ1 × normal_vector\n\n    ρ⁻ = state_prognostic⁻.ρ\n    ρu⁻ = state_prognostic⁻.ρu\n    ρe⁻ = state_prognostic⁻.ρe\n\n    Φ⁻ = state_auxiliary⁻.Φ\n    u⁻ = ρu⁻ / ρ⁻\n    p⁻ = pressure(ρ⁻, ρu⁻, ρe⁻, Φ⁻)\n    β⁻ = ρ⁻ / 2p⁻\n\n    Φ⁺ = state_auxiliary⁺.Φ\n    ρ⁺ = state_prognostic⁺.ρ\n    ρu⁺ = state_prognostic⁺.ρu\n    ρe⁺ = state_prognostic⁺.ρe\n\n    u⁺ = ρu⁺ / ρ⁺\n    p⁺ = pressure(ρ⁺, ρu⁺, ρe⁺, Φ⁺)\n    β⁺ = ρ⁺ / 2p⁺\n\n    ρ_log = logave(ρ⁻, ρ⁺)\n    β_log = logave(β⁻, β⁺)\n    if total_energy\n        Φ_avg = ave(Φ⁻, Φ⁺)\n    else\n        Φ_avg = 0\n    end\n    u_avg = ave.(u⁻, u⁺)\n    p_avg = ave(ρ⁻, ρ⁺) / 2ave(β⁻, β⁺)\n    u²_bar = 2 * sum(u_avg .^ 2) - sum(ave(u⁻ .^ 2, u⁺ .^ 2))\n\n    h_bar = γ / (2 * β_log * (γ - 1)) + u²_bar / 2 + Φ_avg\n    c_bar = sqrt(γ * p_avg / ρ_log)\n\n    umc = u_avg - c_bar * normal_vector\n    upc = u_avg + c_bar * normal_vector\n    u_avgᵀn = u_avg' * normal_vector\n    R = hcat(\n        SVector(1, umc[1], umc[2], umc[3], h_bar - c_bar * u_avgᵀn),\n        SVector(1, u_avg[1], u_avg[2], u_avg[3], u²_bar / 2 + Φ_avg),\n        SVector(0, τ1[1], τ1[2], τ1[3], τ1' * u_avg),\n        SVector(0, τ2[1], τ2[2], τ2[3], τ2' * u_avg),\n        SVector(1, upc[1], upc[2], upc[3], h_bar + c_bar * u_avgᵀn),\n    )\n\n    if low_mach\n        M = abs(u_avg' * normal_vector) / c_bar\n        c_bar *= max(min(M, FT(1)), Mcut)\n    end\n\n    if kinetic_energy_preserving\n        λl = abs(u_avgᵀn) + c_bar\n        λr = λl\n    else\n        λl = abs(u_avgᵀn - c_bar)\n        λr = abs(u_avgᵀn + c_bar)\n    end\n\n    Λ = SDiagonal(λl, abs(u_avgᵀn), abs(u_avgᵀn), abs(u_avgᵀn), λr)\n    #Ξ = sqrt(abs((p⁺ - p⁻) / (p⁺ + p⁻)))\n    #Λ = Ξ * abs(u_avgᵀn + c_bar) * I + (1 - Ξ) * ΛM\n\n    T = SDiagonal(ρ_log / 2γ, ρ_log * (γ - 1) / γ, p_avg, p_avg, ρ_log / 2γ)\n\n    entropy⁻ = similar(parent(state_prognostic⁻), Size(6))\n    state_to_entropy_variables!(\n        balance_law,\n        Vars{vars_state(balance_law, Entropy(), FT)}(entropy⁻),\n        state_prognostic⁻,\n        state_auxiliary⁻,\n    )\n\n    entropy⁺ = similar(parent(state_prognostic⁺), Size(6))\n    state_to_entropy_variables!(\n        balance_law,\n        Vars{vars_state(balance_law, Entropy(), FT)}(entropy⁺),\n        state_prognostic⁺,\n        state_auxiliary⁺,\n    )\n\n    Δentropy = parent(entropy⁺) - parent(entropy⁻)\n\n    fluxᵀn .-= R * Λ * T * R' * Δentropy[SOneTo(5)] / 2\nend\n\nfunction vertical_unit_vector(m::DryAtmosModel, aux::Vars)\n    FT = eltype(aux)\n    aux.∇Φ / FT(grav(param_set))\nend\n\nnorm_u(state::Vars, k̂::AbstractVector, ::VerticalDirection) =\n    abs(dot(state.ρu, k̂)) / state.ρ\nnorm_u(state::Vars, k̂::AbstractVector, ::HorizontalDirection) =\n    norm((state.ρu .- dot(state.ρu, k̂) * k̂) / state.ρ)\nnorm_u(state::Vars, k̂::AbstractVector, ::Direction) = norm(state.ρu / state.ρ)\n\nfunction advective_courant(\n    m::DryAtmosModel,\n    state::Vars,\n    aux::Vars,\n    diffusive::Vars,\n    Δx,\n    Δt,\n    t,\n    direction,\n)\n    k̂ = vertical_unit_vector(m, aux)\n    normu = norm_u(state, k̂, direction)\n    return Δt * normu / Δx\nend\n\nfunction nondiffusive_courant(\n    m::DryAtmosModel,\n    state::Vars,\n    aux::Vars,\n    diffusive::Vars,\n    Δx,\n    Δt,\n    t,\n    direction,\n)\n    ρ = state.ρ\n    ρu = state.ρu\n    ρe = state.ρe\n    Φ = aux.Φ\n    p = pressure(ρ, ρu, ρe, Φ)\n\n    k̂ = vertical_unit_vector(m, aux)\n    normu = norm_u(state, k̂, direction)\n    ss = soundspeed(ρ, p)\n    return Δt * (normu + ss) / Δx\nend\n\nfunction drag_source!(m::DryAtmosModel, args...)\n    drag_source!(m, m.drag_source, args...)\nend\nstruct NoDrag end\ndrag_source!(m::DryAtmosModel, ::NoDrag, args...) = nothing\n\nstruct Gravity end\nfunction source!(m::DryAtmosModel, ::Gravity, source, state, aux)\n    ∇Φ = aux.∇Φ\n    if !fluctuation_gravity\n        source.ρu -= state.ρ * ∇Φ\n    end\n    if !total_energy\n        source.ρe -= state.ρu' * ∇Φ\n    end\nend\n\n# Numerical Flux \n#=\nnumerical_flux_first_order!(::Nothing, _...) = nothing\nnumerical_boundary_flux_first_order!(::Nothing, _...) = nothing\nnumerical_flux_second_order!(::Nothing, _...) = nothing\nnumerical_boundary_flux_second_order!(::Nothing, _...) = nothing\n=#\nnumerical_flux_first_order!(::Nothing, ::DryAtmosModel, _...) = nothing\nnumerical_flux_second_order!(::Nothing, ::DryAtmosModel, _...) = nothing\nnumerical_boundary_flux_second_order!(::Nothing, a, ::DryAtmosModel, _...) =\n    nothing\n\ninclude(\"linear.jl\")\n\n# Throwing this in for convenience\nfunction cubedshellwarp(a, b, c, R = max(abs(a), abs(b), abs(c)))\n\n    function f(sR, ξ, η)\n        X, Y = tan(π * ξ / 4), tan(π * η / 4)\n        x1 = sR / sqrt(X^2 + Y^2 + 1)\n        x2, x3 = X * x1, Y * x1\n        x1, x2, x3\n    end\n\n    fdim = argmax(abs.((a, b, c)))\n    if fdim == 1 && a < 0\n        # (-R, *, *) : Face I from Ronchi, Iacono, Paolucci (1996)\n        x1, x2, x3 = f(-R, b / a, c / a)\n    elseif fdim == 2 && b < 0\n        # ( *,-R, *) : Face II from Ronchi, Iacono, Paolucci (1996)\n        x2, x1, x3 = f(-R, a / b, c / b)\n    elseif fdim == 1 && a > 0\n        # ( R, *, *) : Face III from Ronchi, Iacono, Paolucci (1996)\n        x1, x2, x3 = f(R, b / a, c / a)\n    elseif fdim == 2 && b > 0\n        # ( *, R, *) : Face IV from Ronchi, Iacono, Paolucci (1996)\n        x2, x1, x3 = f(R, a / b, c / b)\n    elseif fdim == 3 && c > 0\n        # ( *, *, R) : Face V from Ronchi, Iacono, Paolucci (1996)\n        x3, x2, x1 = f(R, b / c, a / c)\n    elseif fdim == 3 && c < 0\n        # ( *, *,-R) : Face VI from Ronchi, Iacono, Paolucci (1996)\n        x3, x2, x1 = f(-R, b / c, a / c)\n    else\n        error(\"invalid case for cubedshellwarp: $a, $b, $c\")\n    end\n\n    return x1, x2, x3\nend\n"
  },
  {
    "path": "test/Numerics/ESDGMethods/DryAtmos/baroclinic_wave.jl",
    "content": "using ClimateMachine\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Mesh.Topologies: StackedCubedSphereTopology, grid1d\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Atmos: AtmosFilterPerturbations\nusing ClimateMachine.DGMethods: ESDGModel, init_ode_state, courant\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.VTK: writevtk, writepvtu\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing Thermodynamics: soundspeed_air\nusing Thermodynamics.TemperatureProfiles\nusing ClimateMachine.VariableTemplates: flattenednames\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: R_d, cv_d, Omega, planet_radius, MSLP\nimport CLIMAParameters\n\nusing MPI, Logging, StaticArrays, LinearAlgebra, Printf, Dates, Test\nusing CUDA\n\nconst output_vtk = false\n\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet();\n#const X = 20\nconst X = 1\nCLIMAParameters.Planet.planet_radius(::EarthParameterSet) = 6.371e6 / X\nCLIMAParameters.Planet.Omega(::EarthParameterSet) = 7.2921159e-5 * X\n\n# No this isn't great but w/e \n\ninclude(\"DryAtmos.jl\")\n\nfunction sphr_to_cart_vec(vec, lat, lon)\n    FT = eltype(vec)\n    slat, clat = sin(lat), cos(lat)\n    slon, clon = sin(lon), cos(lon)\n    u = MVector{3, FT}(\n        -slon * vec[1] - slat * clon * vec[2] + clat * clon * vec[3],\n        clon * vec[1] - slat * slon * vec[2] + clat * slon * vec[3],\n        clat * vec[2] + slat * vec[3],\n    )\n    return u\nend\n\nstruct BaroclinicWave <: AbstractDryAtmosProblem end\n\nfunction init_state_prognostic!(\n    bl::DryAtmosModel,\n    ::BaroclinicWave,\n    state,\n    aux,\n    localgeo,\n    t,\n)\n    coords = localgeo.coord\n    FT = eltype(state)\n\n    # parameters\n    _grav::FT = grav(param_set)\n    _R_d::FT = R_d(param_set)\n    _cv_d::FT = cv_d(param_set)\n    _Ω::FT = Omega(param_set)\n    _a::FT = planet_radius(param_set)\n    _p_0::FT = MSLP(param_set)\n\n    k::FT = 3\n    T_E::FT = 310\n    T_P::FT = 240\n    T_0::FT = 0.5 * (T_E + T_P)\n    Γ::FT = 0.005\n    A::FT = 1 / Γ\n    B::FT = (T_0 - T_P) / T_0 / T_P\n    C::FT = 0.5 * (k + 2) * (T_E - T_P) / T_E / T_P\n    b::FT = 2\n    H::FT = _R_d * T_0 / _grav\n    z_t::FT = 15e3\n    λ_c::FT = π / 9\n    φ_c::FT = 2 * π / 9\n    d_0::FT = _a / 6\n    V_p::FT = 1\n    M_v::FT = 0.608\n    p_w::FT = 34e3             ## Pressure width parameter for specific humidity\n    η_crit::FT = 10 * _p_0 / p_w ## Critical pressure coordinate\n    q_0::FT = 0                ## Maximum specific humidity (default: 0.018)\n    q_t::FT = 1e-12            ## Specific humidity above artificial tropopause\n    φ_w::FT = 2π / 9           ## Specific humidity latitude wind parameter\n\n    # grid\n    λ = @inbounds atan(coords[2], coords[1])\n    φ = @inbounds asin(coords[3] / norm(coords, 2))\n    z = norm(coords) - _a\n\n    r::FT = z + _a\n    γ::FT = 1 # set to 0 for shallow-atmosphere case and to 1 for deep atmosphere case\n\n    # convenience functions for temperature and pressure\n    τ_z_1::FT = exp(Γ * z / T_0)\n    τ_z_2::FT = 1 - 2 * (z / b / H)^2\n    τ_z_3::FT = exp(-(z / b / H)^2)\n    τ_1::FT = 1 / T_0 * τ_z_1 + B * τ_z_2 * τ_z_3\n    τ_2::FT = C * τ_z_2 * τ_z_3\n    τ_int_1::FT = A * (τ_z_1 - 1) + B * z * τ_z_3\n    τ_int_2::FT = C * z * τ_z_3\n    I_T::FT =\n        (cos(φ) * (1 + γ * z / _a))^k -\n        k / (k + 2) * (cos(φ) * (1 + γ * z / _a))^(k + 2)\n\n    # base state virtual temperature, pressure, specific humidity, density\n    T_v::FT = (τ_1 - τ_2 * I_T)^(-1)\n    p::FT = _p_0 * exp(-_grav / _R_d * (τ_int_1 - τ_int_2 * I_T))\n\n    # base state velocity\n    U::FT =\n        _grav * k / _a *\n        τ_int_2 *\n        T_v *\n        (\n            (cos(φ) * (1 + γ * z / _a))^(k - 1) -\n            (cos(φ) * (1 + γ * z / _a))^(k + 1)\n        )\n    u_ref::FT =\n        -_Ω * (_a + γ * z) * cos(φ) +\n        sqrt((_Ω * (_a + γ * z) * cos(φ))^2 + (_a + γ * z) * cos(φ) * U)\n    v_ref::FT = 0\n    w_ref::FT = 0\n\n    # velocity perturbations\n    F_z::FT = 1 - 3 * (z / z_t)^2 + 2 * (z / z_t)^3\n    if z > z_t\n        F_z = FT(0)\n    end\n    d::FT = _a * acos(sin(φ) * sin(φ_c) + cos(φ) * cos(φ_c) * cos(λ - λ_c))\n    c3::FT = cos(π * d / 2 / d_0)^3\n    s1::FT = sin(π * d / 2 / d_0)\n    if 0 < d < d_0 && d != FT(_a * π)\n        u′::FT =\n            -16 * V_p / 3 / sqrt(3) *\n            F_z *\n            c3 *\n            s1 *\n            (-sin(φ_c) * cos(φ) + cos(φ_c) * sin(φ) * cos(λ - λ_c)) /\n            sin(d / _a)\n        v′::FT =\n            16 * V_p / 3 / sqrt(3) * F_z * c3 * s1 * cos(φ_c) * sin(λ - λ_c) /\n            sin(d / _a)\n    else\n        u′ = FT(0)\n        v′ = FT(0)\n    end\n    w′::FT = 0\n    u_sphere = SVector{3, FT}(u_ref + u′, v_ref + v′, w_ref + w′)\n    #u_sphere = SVector{3, FT}(u_ref, v_ref, w_ref)\n    u_cart = sphr_to_cart_vec(u_sphere, φ, λ)\n\n    ## temperature & density\n    T::FT = T_v\n    ρ::FT = p / (_R_d * T)\n    ## potential & kinetic energy\n    e_pot = aux.Φ\n    e_kin::FT = 0.5 * u_cart' * u_cart\n    e_int = _cv_d * T\n\n    ## Assign state variables\n    state.ρ = ρ\n    state.ρu = ρ * u_cart\n    if total_energy\n        state.ρe = ρ * (e_int + e_kin + e_pot)\n    else\n        state.ρe = ρ * (e_int + e_kin)\n    end\n\n    nothing\nend\n\n\nfunction main()\n    ClimateMachine.init(parse_clargs = true)\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 3\n    numelem_horz = 8\n    numelem_vert = 5\n\n    timeend = 10 * 24 * 3600\n    outputtime = 24 * 3600\n\n    FT = Float64\n    result = run(\n        mpicomm,\n        polynomialorder,\n        numelem_horz,\n        numelem_vert,\n        timeend,\n        outputtime,\n        ArrayType,\n        FT,\n    )\nend\n\nfunction run(\n    mpicomm,\n    polynomialorder,\n    numelem_horz,\n    numelem_vert,\n    timeend,\n    outputtime,\n    ArrayType,\n    FT,\n)\n    _planet_radius::FT = planet_radius(param_set)\n    domain_height = FT(30e3)\n    vert_range = grid1d(\n        _planet_radius,\n        FT(_planet_radius + domain_height),\n        nelem = numelem_vert,\n    )\n    topology = StackedCubedSphereTopology(mpicomm, numelem_horz, vert_range)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorder,\n        meshwarp = cubedshellwarp,\n    )\n\n    T_profile =\n        DecayingTemperatureProfile{FT}(param_set, FT(290), FT(220), FT(8e3))\n\n\n    if total_energy\n        sources = (Coriolis(),)\n    else\n        sources = (Coriolis(), Gravity())\n    end\n    problem = BaroclinicWave()\n    model = DryAtmosModel{FT}(\n        SphericalOrientation(),\n        problem,\n        ref_state = DryReferenceState(T_profile),\n        sources = sources,\n    )\n\n    esdg = ESDGModel(\n        model,\n        grid,\n        #volume_numerical_flux_first_order = CentralVolumeFlux(),\n        #volume_numerical_flux_first_order = EntropyConservative(),\n        volume_numerical_flux_first_order = KGVolumeFlux(),\n        #surface_numerical_flux_first_order = MatrixFlux(),\n        surface_numerical_flux_first_order = RusanovNumericalFlux(),\n    )\n\n    linearmodel = DryAtmosAcousticGravityLinearModel(model)\n    lineardg = DGModel(\n        linearmodel,\n        grid,\n        RusanovNumericalFlux(),\n        #CentralNumericalFluxFirstOrder(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient();\n        direction = VerticalDirection(),\n        state_auxiliary = esdg.state_auxiliary,\n    )\n\n    # determine the time step\n    element_size = (domain_height / numelem_vert)\n    acoustic_speed = soundspeed_air(param_set, FT(330))\n\n    dx = min_node_distance(grid)\n    cfl = 3\n    dt = cfl * dx / acoustic_speed\n\n    Q = init_ode_state(esdg, FT(0))\n\n    #odesolver = LSRK144NiegemannDiehlBusch(esdg, Q; dt = dt, t0 = 0)\n\n    linearsolver = ManyColumnLU()\n    odesolver = ARK2GiraldoKellyConstantinescu(\n        esdg,\n        lineardg,\n        LinearBackwardEulerSolver(linearsolver; isadjustable = false),\n        Q;\n        dt = dt,\n        t0 = 0,\n        split_explicit_implicit = false,\n    )\n\n    eng0 = norm(Q)\n    @info @sprintf \"\"\"Starting\n                      ArrayType       = %s\n                      FT              = %s\n                      polynomialorder = %d\n                      numelem_horz    = %d\n                      numelem_vert    = %d\n                      dt              = %.16e\n                      norm(Q₀)        = %.16e\n                      \"\"\" \"$ArrayType\" \"$FT\" polynomialorder numelem_horz numelem_vert dt eng0\n\n    # Set up the information callback\n    starttime = Ref(now())\n    cbinfo = EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q)\n            runtime = Dates.format(\n                convert(DateTime, now() - starttime[]),\n                dateformat\"HH:MM:SS\",\n            )\n            @info @sprintf \"\"\"Update\n                              simtime = %.16e\n                              runtime = %s\n                              norm(Q) = %.16e\n                              \"\"\" gettime(odesolver) runtime energy\n        end\n    end\n    cbcfl = EveryXSimulationSteps(100) do\n        simtime = gettime(odesolver)\n\n        @views begin\n            ρ = Array(Q.data[:, 1, :])\n            ρu = Array(Q.data[:, 2, :])\n            ρv = Array(Q.data[:, 3, :])\n            ρw = Array(Q.data[:, 4, :])\n        end\n\n        u = ρu ./ ρ\n        v = ρv ./ ρ\n        w = ρw ./ ρ\n\n        ue = extrema(u)\n        ve = extrema(v)\n        we = extrema(w)\n\n        @info @sprintf \"\"\"CFL\n                simtime = %.16e\n                u = (%.4e, %.4e)\n                v = (%.4e, %.4e)\n                w = (%.4e, %.4e)\n                \"\"\" simtime ue... ve... we...\n    end\n    callbacks = (cbinfo, cbcfl)\n\n\n    #filterorder = 32\n    #filter = ExponentialFilter(grid, 0, filterorder)\n    #cbfilter = EveryXSimulationSteps(1) do\n    #    Filters.apply!(\n    #        Q,\n    #        #AtmosFilterPerturbations(model),\n    #        :,\n    #        grid,\n    #        filter,\n    #       # state_auxiliary = esdg.state_auxiliary,\n    #    )\n    #    nothing\n    #end\n    #callbacks = (callbacks..., cbfilter)\n\n    if output_vtk\n        # create vtk dir\n        vtkdir =\n            \"vtk_esdg_total_KG_ncg_hires_baroclinic\" *\n            \"_poly$(polynomialorder)_horz$(numelem_horz)_vert$(numelem_vert)\" *\n            \"_$(ArrayType)_$(FT)\"\n        mkpath(vtkdir)\n\n        vtkstep = 0\n        # output initial step\n        do_output(mpicomm, vtkdir, vtkstep, esdg, Q, model)\n\n        # setup the output callback\n        cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n            vtkstep += 1\n            do_output(mpicomm, vtkdir, vtkstep, esdg, Q, model)\n        end\n        callbacks = (callbacks..., cbvtk)\n    end\n\n    ## Create a callback to report state statistics for main MPIStateArrays\n    ## every ntFreq timesteps.\n    nt_freq = floor(Int, 1 // 10 * timeend / dt)\n    cbsc =\n        ClimateMachine.StateCheck.sccreate([(Q, \"state\")], nt_freq; prec = 12)\n    callbacks = (callbacks..., cbsc)\n\n    solve!(\n        Q,\n        odesolver;\n        timeend = timeend,\n        adjustfinalstep = false,\n        callbacks = callbacks,\n    )\n\n    # final statistics\n    engf = norm(Q)\n    @info @sprintf \"\"\"Finished\n    norm(Q)                 = %.16e\n    norm(Q) / norm(Q₀)      = %.16e\n    norm(Q) - norm(Q₀)      = %.16e\n    \"\"\" engf engf / eng0 engf - eng0\n    engf\n\n    ## Check results against reference if present\n    ClimateMachine.StateCheck.scprintref(cbsc)\n    #! format: off\n    refDat = (\n        [\n            [ \"state\",     \"ρ\",   1.26085994139264381125e-02,  1.51502814805562069367e+00,  3.46330071392505767225e-01,  3.42221017037761976454e-01 ],\n            [ \"state\", \"ρu[1]\",  -1.26747868050267172180e+02,  1.22735648852872344605e+02, -6.56484249582622303443e-02,  1.07588365672914481053e+01 ],\n            [ \"state\", \"ρu[2]\",  -1.44635478251794808102e+02,  1.11383888659731638882e+02, -1.53109264073002888581e-03,  1.06376480052955262323e+01 ],\n            [ \"state\", \"ρu[3]\",  -1.50479775987282266669e+02,  1.62284843398145170568e+02,  5.84596035289077428643e-02,  9.73728925076320095400e+00 ],\n            [ \"state\",    \"ρe\",   1.73490214503025617887e+03,  2.70352534694924892392e+05,  6.42401799036320589948e+04,  7.10054852130306971958e+04 ],\n        ],\n        [\n            [ \"state\",     \"ρ\",    12,    12,    12,    12 ],\n            [ \"state\", \"ρu[1]\",    12,    12,    12,    12 ],\n            [ \"state\", \"ρu[2]\",    12,    12,    12,    12 ],\n            [ \"state\", \"ρu[3]\",    12,    12,    12,    12 ],\n            [ \"state\",    \"ρe\",    12,    12,    12,    12 ],\n        ],\n    )\n    #! format: on\n    if length(refDat) > 0\n        @test ClimateMachine.StateCheck.scdocheck(cbsc, refDat)\n    end\nend\n\n\nfunction do_output(\n    mpicomm,\n    vtkdir,\n    vtkstep,\n    dg,\n    Q,\n    model,\n    testname = \"baroclinicwave\",\n)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n    auxnames = flattenednames(vars_state(model, Auxiliary(), eltype(Q)))\n    writevtk(\n        filename,\n        Q,\n        dg,\n        statenames,\n        dg.state_auxiliary,\n        auxnames;\n        number_sample_points = 10,\n    )\n\n    ## Generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(pvtuprefix, prefixes, (statenames..., auxnames...), eltype(Q))\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend\n\n@testset \"$(@__FILE__)\" begin\n    tic = Base.time()\n\n    main()\n\n    toc = Base.time()\n    time = toc - tic\n    println(time)\nend\n"
  },
  {
    "path": "test/Numerics/ESDGMethods/DryAtmos/linear.jl",
    "content": "using ClimateMachine.DGMethods: DGModel\nusing ClimateMachine.MPIStateArrays: MPIStateArray\nusing ClimateMachine.DGMethods.NumericalFluxes: NumericalFluxSecondOrder\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\nusing ClimateMachine.Mesh.Grids: Direction\n\nimport ClimateMachine.BalanceLaws:\n    flux_second_order!,\n    indefinite_stack_integral!,\n    reverse_indefinite_stack_integral!,\n    integral_load_auxiliary_state!,\n    integral_set_auxiliary_state!,\n    reverse_integral_load_auxiliary_state!,\n    reverse_integral_set_auxiliary_state!\n\n@inline function linearized_pressure(ρ, ρe, Φ)\n    FT = eltype(ρ)\n    γ = FT(gamma(param_set))\n    if total_energy\n        (γ - 1) * (ρe - ρ * Φ)\n    else\n        (γ - 1) * ρe\n    end\nend\n\nabstract type DryAtmosLinearModel <: BalanceLaw end\n\nfunction vars_state(lm::DryAtmosLinearModel, ::Prognostic, FT)\n    @vars begin\n        ρ::FT\n        ρu::SVector{3, FT}\n        ρe::FT\n    end\nend\nvars_state(lm::DryAtmosLinearModel, st::Auxiliary, FT) =\n    vars_state(lm.atmos, st, FT)\n\nfunction update_auxiliary_state!(\n    dg::DGModel,\n    lm::DryAtmosLinearModel,\n    Q::MPIStateArray,\n    t::Real,\n    elems::UnitRange,\n)\n    return false\nend\nfunction flux_second_order!(\n    lm::DryAtmosLinearModel,\n    flux::Grad,\n    state::Vars,\n    diffusive::Vars,\n    hyperdiffusive::Vars,\n    aux::Vars,\n    t::Real,\n)\n    nothing\nend\nintegral_load_auxiliary_state!(\n    lm::DryAtmosLinearModel,\n    integ::Vars,\n    state::Vars,\n    aux::Vars,\n) = nothing\nintegral_set_auxiliary_state!(lm::DryAtmosLinearModel, aux::Vars, integ::Vars) =\n    nothing\nreverse_integral_load_auxiliary_state!(\n    lm::DryAtmosLinearModel,\n    integ::Vars,\n    state::Vars,\n    aux::Vars,\n) = nothing\nreverse_integral_set_auxiliary_state!(\n    lm::DryAtmosLinearModel,\n    aux::Vars,\n    integ::Vars,\n) = nothing\nflux_second_order!(\n    lm::DryAtmosLinearModel,\n    flux::Grad,\n    state::Vars,\n    diffusive::Vars,\n    aux::Vars,\n    t::Real,\n) = nothing\nfunction wavespeed(\n    lm::DryAtmosLinearModel,\n    nM,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n)\n    ref = aux.ref_state\n    return soundspeed(ref.ρ, ref.p)\nend\n\nboundary_conditions(lm::DryAtmosLinearModel) = (1, 2)\nfunction boundary_state!(\n    nf::NumericalFluxFirstOrder,\n    bc,\n    lm::DryAtmosLinearModel,\n    args...,\n)\n    boundary_state!(nf, bc, lm.atmos, args...)\nend\nfunction boundary_state!(\n    nf::NumericalFluxSecondOrder,\n    bc,\n    lm::DryAtmosLinearModel,\n    args...,\n)\n    nothing\nend\ninit_state_auxiliary!(lm::DryAtmosLinearModel, aux::Vars, geom::LocalGeometry) =\n    nothing\ninit_state_prognostic!(\n    lm::DryAtmosLinearModel,\n    state::Vars,\n    aux::Vars,\n    coords,\n    t,\n) = nothing\n\nstruct DryAtmosAcousticGravityLinearModel{M} <: DryAtmosLinearModel\n    atmos::M\n    function DryAtmosAcousticGravityLinearModel(atmos::M) where {M}\n        if atmos.ref_state === NoReferenceState()\n            error(\"DryAtmosAcousticGravityLinearModel needs a model with a reference state\")\n        end\n        new{M}(atmos)\n    end\nend\nfunction flux_first_order!(\n    lm::DryAtmosAcousticGravityLinearModel,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    direction,\n)\n    FT = eltype(state)\n    ref = aux.ref_state\n\n    flux.ρ = state.ρu\n    pL = linearized_pressure(state.ρ, state.ρe, aux.Φ)\n    flux.ρu += pL * I\n    flux.ρe = ((ref.ρe + ref.p) / ref.ρ) * state.ρu\n    nothing\nend\nfunction source!(\n    lm::DryAtmosAcousticGravityLinearModel,\n    source::Vars,\n    state::Vars,\n    diffusive::Vars,\n    aux::Vars,\n    t::Real,\n    ::NTuple{1, Dir},\n) where {Dir <: Direction}\n    if Dir === VerticalDirection || Dir === EveryDirection\n        ∇Φ = aux.∇Φ\n        source.ρu -= state.ρ * ∇Φ\n        if !total_energy\n            source.ρe -= state.ρu' * ∇Φ\n        end\n    end\n    nothing\nend\n"
  },
  {
    "path": "test/Numerics/ESDGMethods/DryAtmos/run_tests.jl",
    "content": "using Test\nusing ClimateMachine\nimport ClimateMachine.BalanceLaws\nimport ClimateMachine.BalanceLaws: boundary_state!\nusing ClimateMachine.DGMethods: ESDGModel, init_ode_state\nusing ClimateMachine.DGMethods.NumericalFluxes:\n    numerical_volume_flux_first_order!\nusing ClimateMachine.Mesh.Topologies: BrickTopology\nusing ClimateMachine.Mesh.Grids: DiscontinuousSpectralElementGrid\nusing ClimateMachine.VariableTemplates: varsindex\nusing StaticArrays: MArray, @SVector\nusing KernelAbstractions: wait\nusing Random\nusing DoubleFloats\nusing MPI\nusing ClimateMachine.MPIStateArrays\n\nRandom.seed!(7)\n\ninclude(\"DryAtmos.jl\")\n\nboundary_state!(::Nothing, _...) = nothing\n\nstruct TestProblem <: AbstractDryAtmosProblem end\n\n# Random initialization function\nfunction init_state_prognostic!(\n    ::DryAtmosModel,\n    ::TestProblem,\n    state_prognostic,\n    state_auxiliary,\n    _...,\n) where {dim}\n    FT = eltype(state_prognostic)\n    ρ = state_prognostic.ρ = rand(FT) + 1\n    ρu = state_prognostic.ρu = 2 * (@SVector rand(FT, 3)) .- 1\n    p = rand(FT) + 1\n    Φ = state_auxiliary.Φ\n    state_prognostic.ρe = totalenergy(ρ, ρu, p, Φ)\nend\n\nfunction check_operators(FT, dim, mpicomm, N, ArrayType)\n    # Create a warped mesh so the metrics are not constant\n    Ne = (8, 9, 10)\n    brickrange = (\n        range(FT(-1); length = Ne[1] + 1, stop = 1),\n        range(FT(-1); length = Ne[2] + 1, stop = 1),\n        range(FT(-1); length = Ne[3] + 1, stop = 1),\n    )\n    topl = BrickTopology(\n        mpicomm,\n        ntuple(k -> brickrange[k], dim);\n        periodicity = ntuple(k -> true, dim),\n    )\n    warpfun =\n        (x1, x2, x3) -> begin\n            α = (4 / π) * (1 - x1^2) * (1 - x2^2) * (1 - x3^2)\n            # Rotate by α with x1 and x2\n            x1, x2 = cos(α) * x1 - sin(α) * x2, sin(α) * x1 + cos(α) * x2\n            # Rotate by α with x1 and x3\n            if dim == 3\n                x1, x3 = cos(α) * x1 - sin(α) * x3, sin(α) * x1 + cos(α) * x3\n            end\n            return (x1, x2, x3)\n        end\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n        meshwarp = warpfun,\n    )\n\n    # Orientation does not matter since we will be setting the geopotential to a\n    # random field\n    model = DryAtmosModel{dim}(FlatOrientation(), TestProblem())\n\n    ##################################################################\n    # check that the volume terms lead to only surface contributions #\n    ##################################################################\n    # Create the ES model\n    esdg = ESDGModel(\n        model,\n        grid;\n        volume_numerical_flux_first_order = EntropyConservative(),\n        surface_numerical_flux_first_order = nothing,\n    )\n\n    # Make the Geopotential random\n    esdg.state_auxiliary .= ArrayType(2rand(FT, size(esdg.state_auxiliary)))\n    start_exchange = MPIStateArrays.begin_ghost_exchange!(esdg.state_auxiliary)\n    end_exchange = MPIStateArrays.end_ghost_exchange!(\n        esdg.state_auxiliary,\n        dependencies = start_exchange,\n    )\n    wait(end_exchange)\n\n    # Create a random state\n    state_prognostic = init_ode_state(esdg; init_on_cpu = true)\n\n    # Storage for the tendency\n    volume_tendency = similar(state_prognostic)\n\n    # Compute the tendency function\n    esdg(volume_tendency, state_prognostic, nothing, 0)\n\n    # Check that the volume terms only lead to surface integrals of\n    #    ∑_{j} n_j ψ_j\n    # where Ψ_j = β^T f_j - ζ_j = ρu_j\n    Np, K = (N + 1)^dim, length(esdg.grid.topology.realelems)\n\n    # Get the mass matrix on the host\n    _M = ClimateMachine.Grids._M\n    M = Array(grid.vgeo[:, _M:_M, 1:K])\n\n    # Get the state, tendency, and aux on the host\n    Q = Array(state_prognostic.data[:, :, 1:K])\n    dQ = Array(volume_tendency.data[:, :, 1:K])\n    A = Array(esdg.state_auxiliary.data[:, :, 1:K])\n\n    # Compute the entropy variables\n    β = similar(Q, Np, number_states(model, Entropy()), K)\n    @views for e in 1:K\n        for i in 1:Np\n            state_to_entropy_variables!(\n                model,\n                β[i, :, e],\n                Q[i, :, e],\n                A[i, :, e],\n            )\n        end\n    end\n\n    # Get the unit normals and surface mass matrix\n    sgeo = Array(grid.sgeo)\n    n1 = sgeo[ClimateMachine.Grids._n1, :, :, 1:K]\n    n2 = sgeo[ClimateMachine.Grids._n2, :, :, 1:K]\n    n3 = sgeo[ClimateMachine.Grids._n3, :, :, 1:K]\n    sM = sgeo[ClimateMachine.Grids._sM, :, :, 1:K]\n\n    # Get the Ψs\n    fmask = Array(grid.vmap⁻[:, :, 1])\n    _ρu = varsindex(vars_state(model, Prognostic(), FT), :ρu)\n    Ψ1 = Q[fmask, _ρu[1], 1:K]\n    Ψ2 = Q[fmask, _ρu[2], 1:K]\n    Ψ3 = Q[fmask, _ρu[3], 1:K]\n\n    # Compute the surface integral:\n    #    ∫_Ωf ∑_j n_j * Ψ_j\n    surface = sum(sM .* (n1 .* Ψ1 + n2 .* Ψ2 + n3 .* Ψ3), dims = (1, 2))[:]\n\n    # Compute the volume integral:\n    #   -∫_Ω ∑_j β^T (dq/dt)\n    # (tendency is -dq / dt)\n    num_state = number_states(model, Prognostic())\n    volume = sum(β[:, 1:num_state, :] .* M .* dQ, dims = (1, 2))[:]\n\n    @test all(isapprox.(\n        surface,\n        volume;\n        atol = 10eps(FT),\n        rtol = sqrt(eps(FT)),\n    ))\n\n    ###########################################\n    # check that the volume and surface match #\n    ###########################################\n    esdg = ESDGModel(\n        model,\n        grid;\n        state_auxiliary = esdg.state_auxiliary,\n        volume_numerical_flux_first_order = nothing,\n        surface_numerical_flux_first_order = EntropyConservative(),\n    )\n\n    surface_tendency = similar(state_prognostic)\n\n    # Compute the tendency function\n    esdg(surface_tendency, state_prognostic, nothing, 0)\n\n    # Surface integral should be equal and opposite to the volume integral\n    dQ = Array(surface_tendency.data[:, :, 1:K])\n    volume_integral = MPI.Allreduce(sum(volume), +, mpicomm)\n    surface_integral =\n        MPI.Allreduce(sum(β[:, 1:num_state, :] .* M .* dQ), +, mpicomm)\n    @test volume_integral ≈ -surface_integral\n\n    ########################################################\n    # check that the full tendency is entropy conservative #\n    ########################################################\n    esdg = ESDGModel(\n        model,\n        grid;\n        state_auxiliary = esdg.state_auxiliary,\n        volume_numerical_flux_first_order = EntropyConservative(),\n        surface_numerical_flux_first_order = EntropyConservative(),\n    )\n\n    tendency = similar(state_prognostic)\n\n    # Compute the tendency function\n    esdg(tendency, state_prognostic, nothing, 0)\n\n    # Check for entropy conservation\n    dQ = Array(tendency.data[:, :, 1:K])\n    integral = MPI.Allreduce(sum(β[:, 1:num_state, :] .* M .* dQ), +, mpicomm)\n    @test isapprox(integral, 0, atol = sqrt(eps(sum(volume))))\nend\n\nlet\n    model = DryAtmosModel{3}(FlatOrientation(), TestProblem())\n    num_state = number_states(model, Prognostic())\n    num_aux = number_states(model, Auxiliary())\n    num_entropy = number_states(model, Entropy())\n\n    @testset \"state to entropy variable transforms\" begin\n        for FT in (Float32, Float64)\n            state_in =\n                [1, 2, 2, 2, 1] .* rand(FT, num_state) + [3, -1, -1, -1, 100]\n            aux_in = rand(FT, num_aux)\n            state_out = similar(state_in)\n            aux_out = similar(aux_in)\n            entropy = similar(state_in, num_entropy)\n\n            state_to_entropy_variables!(model, entropy, state_in, aux_in)\n            entropy_variables_to_state!(model, state_out, aux_out, entropy)\n\n            @test all(state_in .≈ state_out)\n            #@test all(aux_in .≈ aux_out)\n        end\n    end\n\n    @testset \"test numerical flux for Tadmor shuffle\" begin\n        for FT in (Float32, Float64)\n            # Create some random states\n            state_1 =\n                [1, 2, 2, 2, 1] .* rand(FT, num_state) + [3, -1, -1, -1, 100]\n            aux_1 = 0 * rand(FT, num_aux)\n\n            state_2 =\n                [1, 2, 2, 2, 1] .* rand(FT, num_state) + [3, -1, -1, -1, 100]\n            aux_2 = 0 * rand(FT, num_aux)\n\n            # Get the entropy variables for the two states\n            entropy_1 = similar(state_1, num_entropy)\n            state_to_entropy_variables!(model, entropy_1, state_1, aux_1)\n\n            entropy_2 = similar(state_1, num_entropy)\n            state_to_entropy_variables!(model, entropy_2, state_2, aux_2)\n\n            # Get the values of Ψ_j = β^T f_j - ζ_j = ρu_j where β is the\n            # entropy variables, f_j is the conservative flux, and ζ_j is the\n            # entropy flux. For conservation laws this is the entropy potential.\n            Ψ_1 = Vars{vars_state(model, Prognostic(), FT)}(state_1).ρu\n            Ψ_2 = Vars{vars_state(model, Prognostic(), FT)}(state_2).ρu\n\n            # Evaluate the flux with both orders of the two states\n            H_12 = fill!(MArray{Tuple{3, num_state}, FT}(undef), -zero(FT))\n            numerical_volume_flux_first_order!(\n                EntropyConservative(),\n                model,\n                H_12,\n                state_1,\n                aux_1,\n                state_2,\n                aux_2,\n            )\n\n            H_21 = fill!(MArray{Tuple{3, num_state}, FT}(undef), -zero(FT))\n            numerical_volume_flux_first_order!(\n                EntropyConservative(),\n                model,\n                H_21,\n                state_2,\n                aux_2,\n                state_1,\n                aux_1,\n            )\n\n            # Check that we satisfy the Tadmor shuffle\n            @test all(\n                H_12 * entropy_1[1:num_state] - H_21 * entropy_2[1:num_state] .≈\n                Ψ_1 - Ψ_2,\n            )\n        end\n    end\n\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n    mpicomm = MPI.COMM_WORLD\n    polynomialorder = 4\n    test_types = (Float32, Float64)\n    for FT in test_types\n        for dim in 2:3\n            @testset \"check ESDGMethods relations for dim = $dim and FT = $FT\" begin\n                check_operators(FT, dim, mpicomm, polynomialorder, ArrayType)\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/ESDGMethods/DryAtmos/run_tests_mpo.jl",
    "content": "using Test\nusing ClimateMachine\nimport ClimateMachine.BalanceLaws\nimport ClimateMachine.BalanceLaws: boundary_state!\nusing ClimateMachine.DGMethods: ESDGModel, init_ode_state\nusing ClimateMachine.DGMethods.NumericalFluxes:\n    numerical_volume_flux_first_order!\nusing ClimateMachine.Mesh.Topologies: BrickTopology\nusing ClimateMachine.Mesh.Grids: DiscontinuousSpectralElementGrid\nusing ClimateMachine.VariableTemplates: varsindex\nusing StaticArrays: MArray, @SVector\nusing KernelAbstractions: wait\nusing Random\nusing DoubleFloats\nusing MPI\nusing ClimateMachine.MPIStateArrays\n\nRandom.seed!(7)\n\ninclude(\"DryAtmos.jl\")\n\nboundary_state!(::Nothing, _...) = nothing\n\nstruct TestProblem <: AbstractDryAtmosProblem end\n\n# Random initialization function\nfunction init_state_prognostic!(\n    ::DryAtmosModel,\n    ::TestProblem,\n    state_prognostic,\n    state_auxiliary,\n    _...,\n) where {dim}\n    FT = eltype(state_prognostic)\n    ρ = state_prognostic.ρ = rand(FT) + 1\n    ρu = state_prognostic.ρu = 2 * (@SVector rand(FT, 3)) .- 1\n    p = rand(FT) + 1\n    Φ = state_auxiliary.Φ\n    state_prognostic.ρe = totalenergy(ρ, ρu, p, Φ)\nend\n\nfunction check_operators(FT, dim, mpicomm, N, ArrayType)\n    # Create a warped mesh so the metrics are not constant\n    Ne = (8, 9, 10)\n    brickrange = (\n        range(FT(-1); length = Ne[1] + 1, stop = 1),\n        range(FT(-1); length = Ne[2] + 1, stop = 1),\n        range(FT(-1); length = Ne[3] + 1, stop = 1),\n    )\n    topl = BrickTopology(\n        mpicomm,\n        ntuple(k -> brickrange[k], dim);\n        periodicity = ntuple(k -> true, dim),\n    )\n    warpfun =\n        (x1, x2, x3) -> begin\n            α = (4 / π) * (1 - x1^2) * (1 - x2^2) * (1 - x3^2)\n            # Rotate by α with x1 and x2\n            x1, x2 = cos(α) * x1 - sin(α) * x2, sin(α) * x1 + cos(α) * x2\n            # Rotate by α with x1 and x3\n            if dim == 3\n                x1, x3 = cos(α) * x1 - sin(α) * x3, sin(α) * x1 + cos(α) * x3\n            end\n            return (x1, x2, x3)\n        end\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n        meshwarp = warpfun,\n    )\n\n    # Orientation does not matter since we will be setting the geopotential to a\n    # random field\n    model = DryAtmosModel{dim}(FlatOrientation(), TestProblem())\n\n    ##################################################################\n    # check that the volume terms lead to only surface contributions #\n    ##################################################################\n    # Create the ES model\n    esdg = ESDGModel(\n        model,\n        grid;\n        volume_numerical_flux_first_order = EntropyConservative(),\n        surface_numerical_flux_first_order = nothing,\n    )\n\n    # Make the Geopotential random\n    esdg.state_auxiliary .= ArrayType(2rand(FT, size(esdg.state_auxiliary)))\n    start_exchange = MPIStateArrays.begin_ghost_exchange!(esdg.state_auxiliary)\n    end_exchange = MPIStateArrays.end_ghost_exchange!(\n        esdg.state_auxiliary,\n        dependencies = start_exchange,\n    )\n    wait(end_exchange)\n\n    # Create a random state\n    state_prognostic = init_ode_state(esdg; init_on_cpu = true)\n\n    # Storage for the tendency\n    volume_tendency = similar(state_prognostic)\n\n    # Compute the tendency function\n    esdg(volume_tendency, state_prognostic, nothing, 0)\n\n    # Check that the volume terms only lead to surface integrals of\n    #    ∑_{j} n_j ψ_j\n    # where Ψ_j = β^T f_j - ζ_j = ρu_j\n    Np, K = prod(N .+ 1), length(esdg.grid.topology.realelems)\n\n    # Get the mass matrix on the host\n    _M = ClimateMachine.Grids._M\n    M = Array(grid.vgeo[:, _M:_M, 1:K])\n\n    # Get the state, tendency, and aux on the host\n    Q = Array(state_prognostic.data[:, :, 1:K])\n    dQ = Array(volume_tendency.data[:, :, 1:K])\n    A = Array(esdg.state_auxiliary.data[:, :, 1:K])\n\n    # Compute the entropy variables\n    β = similar(Q, Np, number_states(model, Entropy()), K)\n    @views for e in 1:K\n        for i in 1:Np\n            state_to_entropy_variables!(\n                model,\n                β[i, :, e],\n                Q[i, :, e],\n                A[i, :, e],\n            )\n        end\n    end\n\n    # Get the unit normals and surface mass matrix\n    sgeo = Array(grid.sgeo)\n    n1 = sgeo[ClimateMachine.Grids._n1, :, :, 1:K]\n    n2 = sgeo[ClimateMachine.Grids._n2, :, :, 1:K]\n    n3 = sgeo[ClimateMachine.Grids._n3, :, :, 1:K]\n    sM = sgeo[ClimateMachine.Grids._sM, :, :, 1:K]\n\n    num_state = number_states(model, Prognostic())\n    volume = sum(β[:, 1:num_state, :] .* M .* dQ, dims = (1, 2))[:]\n\n    ###########################################\n    # check that the volume and surface match #\n    ###########################################\n    esdg = ESDGModel(\n        model,\n        grid;\n        state_auxiliary = esdg.state_auxiliary,\n        volume_numerical_flux_first_order = nothing,\n        surface_numerical_flux_first_order = EntropyConservative(),\n    )\n\n    surface_tendency = similar(state_prognostic)\n\n    # Compute the tendency function\n    esdg(surface_tendency, state_prognostic, nothing, 0)\n\n    # Surface integral should be equal and opposite to the volume integral\n    dQ = Array(surface_tendency.data[:, :, 1:K])\n    volume_integral = MPI.Allreduce(sum(volume), +, mpicomm)\n    surface_integral =\n        MPI.Allreduce(sum(β[:, 1:num_state, :] .* M .* dQ), +, mpicomm)\n    @test volume_integral ≈ -surface_integral\n\n    ########################################################\n    # check that the full tendency is entropy conservative #\n    ########################################################\n    esdg = ESDGModel(\n        model,\n        grid;\n        state_auxiliary = esdg.state_auxiliary,\n        volume_numerical_flux_first_order = EntropyConservative(),\n        surface_numerical_flux_first_order = EntropyConservative(),\n    )\n\n    tendency = similar(state_prognostic)\n\n    # Compute the tendency function\n    esdg(tendency, state_prognostic, nothing, 0)\n\n    # Check for entropy conservation\n    dQ = Array(tendency.data[:, :, 1:K])\n    integral = MPI.Allreduce(sum(β[:, 1:num_state, :] .* M .* dQ), +, mpicomm)\n    @test isapprox(integral, 0, atol = sqrt(eps(sum(volume))))\nend\n\nlet\n    model = DryAtmosModel{3}(FlatOrientation(), TestProblem())\n    num_state = number_states(model, Prognostic())\n    num_aux = number_states(model, Auxiliary())\n    num_entropy = number_states(model, Entropy())\n\n    @testset \"state to entropy variable transforms\" begin\n        for FT in (Float32, Float64)\n            state_in =\n                [1, 2, 2, 2, 1] .* rand(FT, num_state) + [3, -1, -1, -1, 100]\n            aux_in = rand(FT, num_aux)\n            state_out = similar(state_in)\n            aux_out = similar(aux_in)\n            entropy = similar(state_in, num_entropy)\n\n            state_to_entropy_variables!(model, entropy, state_in, aux_in)\n            entropy_variables_to_state!(model, state_out, aux_out, entropy)\n\n            @test all(state_in .≈ state_out)\n            #@test all(aux_in .≈ aux_out)\n        end\n    end\n\n    @testset \"test numerical flux for Tadmor shuffle\" begin\n        for FT in (Float32, Float64)\n            # Create some random states\n            state_1 =\n                [1, 2, 2, 2, 1] .* rand(FT, num_state) + [3, -1, -1, -1, 100]\n            aux_1 = 0 * rand(FT, num_aux)\n\n            state_2 =\n                [1, 2, 2, 2, 1] .* rand(FT, num_state) + [3, -1, -1, -1, 100]\n            aux_2 = 0 * rand(FT, num_aux)\n\n            # Get the entropy variables for the two states\n            entropy_1 = similar(state_1, num_entropy)\n            state_to_entropy_variables!(model, entropy_1, state_1, aux_1)\n\n            entropy_2 = similar(state_1, num_entropy)\n            state_to_entropy_variables!(model, entropy_2, state_2, aux_2)\n\n            # Get the values of Ψ_j = β^T f_j - ζ_j = ρu_j where β is the\n            # entropy variables, f_j is the conservative flux, and ζ_j is the\n            # entropy flux. For conservation laws this is the entropy potential.\n            Ψ_1 = Vars{vars_state(model, Prognostic(), FT)}(state_1).ρu\n            Ψ_2 = Vars{vars_state(model, Prognostic(), FT)}(state_2).ρu\n\n            # Evaluate the flux with both orders of the two states\n            H_12 = fill!(MArray{Tuple{3, num_state}, FT}(undef), -zero(FT))\n            numerical_volume_flux_first_order!(\n                EntropyConservative(),\n                model,\n                H_12,\n                state_1,\n                aux_1,\n                state_2,\n                aux_2,\n            )\n\n            H_21 = fill!(MArray{Tuple{3, num_state}, FT}(undef), -zero(FT))\n            numerical_volume_flux_first_order!(\n                EntropyConservative(),\n                model,\n                H_21,\n                state_2,\n                aux_2,\n                state_1,\n                aux_1,\n            )\n\n            # Check that we satisfy the Tadmor shuffle\n            @test all(\n                H_12 * entropy_1[1:num_state] - H_21 * entropy_2[1:num_state] .≈\n                Ψ_1 - Ψ_2,\n            )\n        end\n    end\n\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n    mpicomm = MPI.COMM_WORLD\n    polynomialorders = ((2, 2, 1), (1, 1, 2))\n    test_types = (Float32, Float64)\n    for FT in test_types\n        for dim in (3,)\n            for polynomialorder in polynomialorders\n                dim = 3\n                @testset \"check ESDGMethods relations for dim = $dim, FT = $FT, and polynomial order $polynomialorder\" begin\n                    check_operators(\n                        FT,\n                        dim,\n                        mpicomm,\n                        polynomialorder,\n                        ArrayType,\n                    )\n                end\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/ESDGMethods/diagnostics.jl",
    "content": "using KernelAbstractions\nusing ClimateMachine.MPIStateArrays: array_device, weightedsum\nusing KernelAbstractions.Extras: @unroll\n\nfunction entropy_integral(dg, entropy, state_prognostic)\n    balance_law = dg.balance_law\n    state_auxiliary = dg.state_auxiliary\n    device = array_device(state_prognostic)\n    grid = dg.grid\n    topology = grid.topology\n    Np = dofs_per_element(grid)\n    dim = dimensionality(grid)\n    # XXX: Needs updating for multiple polynomial orders\n    N = polynomialorders(grid)\n    # Currently only support single polynomial order\n    @assert all(N[1] .== N)\n    N = N[1]\n\n    realelems = topology.realelems\n\n    event = Event(device)\n    event = esdg_compute_entropy!(device, min(Np, 1024))(\n        balance_law,\n        Val(dim),\n        Val(N),\n        entropy.data,\n        state_prognostic.data,\n        state_auxiliary.data,\n        realelems,\n        ndrange = Np * length(realelems),\n        dependencies = event,\n    )\n    wait(event)\n\n    weightedsum(entropy)\nend\n\n@kernel function esdg_compute_entropy!(\n    balance_law::BalanceLaw,\n    ::Val{dim},\n    ::Val{N},\n    entropy,\n    state_prognostic,\n    state_auxiliary,\n    elems,\n) where {dim, N}\n\n    FT = eltype(state_prognostic)\n    num_state_prognostic = number_states(balance_law, Prognostic())\n    num_state_auxiliary = number_states(balance_law, Auxiliary())\n\n    Nq = N + 1\n\n    Nqk = dim == 2 ? 1 : Nq\n\n    Np = Nq * Nq * Nqk\n\n    local_state_prognostic = MArray{Tuple{num_state_prognostic}, FT}(undef)\n    local_state_auxiliary = MArray{Tuple{num_state_auxiliary}, FT}(undef)\n\n    I = @index(Global, Linear)\n    eI = (I - 1) ÷ Np + 1\n    n = (I - 1) % Np + 1\n\n    @inbounds begin\n        e = elems[eI]\n        @unroll for s in 1:num_state_prognostic\n            local_state_prognostic[s] = state_prognostic[n, s, e]\n        end\n\n        @unroll for s in 1:num_state_auxiliary\n            local_state_auxiliary[s] = state_auxiliary[n, s, e]\n        end\n\n        entropy[n, 1, e] = state_to_entropy(\n            balance_law,\n            Vars{vars_state(balance_law, Prognostic(), FT)}(\n                local_state_prognostic,\n            ),\n            Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                local_state_auxiliary,\n            ),\n        )\n    end\nend\n\nfunction entropy_product(dg, entropy, state_prognostic, tendency)\n    balance_law = dg.balance_law\n    state_auxiliary = dg.state_auxiliary\n    device = array_device(state_prognostic)\n    grid = dg.grid\n    topology = grid.topology\n    Np = dofs_per_element(grid)\n    dim = dimensionality(grid)\n    # XXX: Needs updating for multiple polynomial orders\n    N = polynomialorders(grid)\n    # Currently only support single polynomial order\n    @assert all(N[1] .== N)\n    N = N[1]\n\n    realelems = topology.realelems\n\n    event = Event(device)\n    event = esdg_compute_entropy_product!(device, min(Np, 1024))(\n        balance_law,\n        Val(dim),\n        Val(N),\n        entropy.data,\n        state_prognostic.data,\n        tendency.data,\n        state_auxiliary.data,\n        realelems,\n        ndrange = Np * length(realelems),\n        dependencies = event,\n    )\n    wait(event)\n\n    weightedsum(entropy)\nend\n\n@kernel function esdg_compute_entropy_product!(\n    balance_law::BalanceLaw,\n    ::Val{dim},\n    ::Val{N},\n    entropy,\n    state_prognostic,\n    tendency,\n    state_auxiliary,\n    elems,\n) where {dim, N}\n\n    FT = eltype(state_prognostic)\n    num_state_prognostic = number_states(balance_law, Prognostic())\n    num_state_entropy = number_states(balance_law, Entropy())\n    num_state_auxiliary = number_states(balance_law, Auxiliary())\n\n    Nq = N + 1\n\n    Nqk = dim == 2 ? 1 : Nq\n\n    Np = Nq * Nq * Nqk\n\n    local_state_entropy = MArray{Tuple{num_state_entropy}, FT}(undef)\n    local_state_prognostic = MArray{Tuple{num_state_prognostic}, FT}(undef)\n    local_tendency = MArray{Tuple{num_state_prognostic}, FT}(undef)\n    local_state_auxiliary = MArray{Tuple{num_state_auxiliary}, FT}(undef)\n\n    I = @index(Global, Linear)\n    eI = (I - 1) ÷ Np + 1\n    n = (I - 1) % Np + 1\n\n    @inbounds begin\n        e = elems[eI]\n\n        @unroll for s in 1:num_state_prognostic\n            local_state_prognostic[s] = state_prognostic[n, s, e]\n        end\n\n        @unroll for s in 1:num_state_prognostic\n            local_tendency[s] = tendency[n, s, e]\n        end\n\n        @unroll for s in 1:num_state_auxiliary\n            local_state_auxiliary[s] = state_auxiliary[n, s, e]\n        end\n\n        state_to_entropy_variables!(\n            balance_law,\n            Vars{vars_state(balance_law, Entropy(), FT)}(local_state_entropy),\n            Vars{vars_state(balance_law, Prognostic(), FT)}(\n                local_state_prognostic,\n            ),\n            Vars{vars_state(balance_law, Auxiliary(), FT)}(\n                local_state_auxiliary,\n            ),\n        )\n\n        local_product = -zero(FT)\n        # not that tendency related to the last entropy variable is assumed zero\n        @unroll for s in 1:num_state_prognostic\n            local_product += local_state_entropy[s] * local_tendency[s]\n        end\n        entropy[n, 1, e] = local_product\n    end\nend\n"
  },
  {
    "path": "test/Numerics/Mesh/BrickMesh.jl",
    "content": "using ClimateMachine.Mesh.BrickMesh\nusing ClimateMachine.Mesh.Grids: mappings, commmapping\nusing Test\nusing MPI\n\nMPI.Initialized() || MPI.Init()\n\n@testset \"Linear Parition\" begin\n    @test BrickMesh.linearpartition(1, 1, 1) == 1:1\n    @test BrickMesh.linearpartition(20, 1, 1) == 1:20\n    @test BrickMesh.linearpartition(10, 1, 2) == 1:5\n    @test BrickMesh.linearpartition(10, 2, 2) == 6:10\nend\n\n@testset \"Hilbert Code\" begin\n    @test BrickMesh.hilbertcode([0, 0], bits = 1) == [0, 0]\n    @test BrickMesh.hilbertcode([0, 1], bits = 1) == [0, 1]\n    @test BrickMesh.hilbertcode([1, 1], bits = 1) == [1, 0]\n    @test BrickMesh.hilbertcode([1, 0], bits = 1) == [1, 1]\n    @test BrickMesh.hilbertcode([0, 0], bits = 2) == [0, 0]\n    @test BrickMesh.hilbertcode([1, 0], bits = 2) == [0, 1]\n    @test BrickMesh.hilbertcode([1, 1], bits = 2) == [0, 2]\n    @test BrickMesh.hilbertcode([0, 1], bits = 2) == [0, 3]\n    @test BrickMesh.hilbertcode([0, 2], bits = 2) == [1, 0]\n    @test BrickMesh.hilbertcode([0, 3], bits = 2) == [1, 1]\n    @test BrickMesh.hilbertcode([1, 3], bits = 2) == [1, 2]\n    @test BrickMesh.hilbertcode([1, 2], bits = 2) == [1, 3]\n    @test BrickMesh.hilbertcode([2, 2], bits = 2) == [2, 0]\n    @test BrickMesh.hilbertcode([2, 3], bits = 2) == [2, 1]\n    @test BrickMesh.hilbertcode([3, 3], bits = 2) == [2, 2]\n    @test BrickMesh.hilbertcode([3, 2], bits = 2) == [2, 3]\n    @test BrickMesh.hilbertcode([3, 1], bits = 2) == [3, 0]\n    @test BrickMesh.hilbertcode([2, 1], bits = 2) == [3, 1]\n    @test BrickMesh.hilbertcode([2, 0], bits = 2) == [3, 2]\n    @test BrickMesh.hilbertcode([3, 0], bits = 2) == [3, 3]\n\n    @test BrickMesh.hilbertcode(UInt64.([14, 3, 4])) ==\n          UInt64.([0x0, 0x0, 0xe25])\nend\n\n@testset \"Mesh to Hilbert Code\" begin\n    let\n        etc = Array{Float64}(undef, 2, 4, 6)\n        etc[:, :, 1] = [2.0 3.0 2.0 3.0; 4.0 4.0 5.0 5.0]\n        etc[:, :, 2] = [3.0 4.0 3.0 4.0; 4.0 4.0 5.0 5.0]\n        etc[:, :, 3] = [4.0 5.0 4.0 5.0; 4.0 4.0 5.0 5.0]\n        etc[:, :, 4] = [2.0 3.0 2.0 3.0; 5.0 5.0 6.0 6.0]\n        etc[:, :, 5] = [3.0 4.0 3.0 4.0; 5.0 5.0 6.0 6.0]\n        etc[:, :, 6] = [4.0 5.0 4.0 5.0; 5.0 5.0 6.0 6.0]\n\n        code_exect = UInt64[\n            0x0000000000000000 0x1555555555555555 0xffffffffffffffff 0x5555555555555555 0x6aaaaaaaaaaaaaaa 0xaaaaaaaaaaaaaaaa\n            0x0000000000000000 0x5555555555555555 0xffffffffffffffff 0x5555555555555555 0xaaaaaaaaaaaaaaaa 0xaaaaaaaaaaaaaaaa\n        ]\n\n        code = centroidtocode(MPI.COMM_SELF, etc)\n\n        @test code == code_exect\n    end\n\n    let\n        nelem = 1\n        d = 2\n\n        etc = Array{Float64}(undef, d, d^2, nelem)\n        etc[:, :, 1] = [2.0 3.0 2.0 3.0; 4.0 4.0 5.0 5.0]\n        code = centroidtocode(MPI.COMM_SELF, etc)\n\n        @test code == zeros(eltype(code), d, nelem)\n    end\nend\n\n@testset \"Vertex Ordering\" begin\n    @test ((1,), 1) == BrickMesh.vertsortandorder(1)\n\n    @test ((1, 2), 1) == BrickMesh.vertsortandorder(1, 2)\n    @test ((1, 2), 2) == BrickMesh.vertsortandorder(2, 1)\n\n    @test ((1, 2, 3), 1) == BrickMesh.vertsortandorder(1, 2, 3)\n    @test ((1, 2, 3), 2) == BrickMesh.vertsortandorder(3, 1, 2)\n    @test ((1, 2, 3), 3) == BrickMesh.vertsortandorder(2, 3, 1)\n    @test ((1, 2, 3), 4) == BrickMesh.vertsortandorder(2, 1, 3)\n    @test ((1, 2, 3), 5) == BrickMesh.vertsortandorder(3, 2, 1)\n    @test ((1, 2, 3), 6) == BrickMesh.vertsortandorder(1, 3, 2)\n\n    @test_throws ErrorException BrickMesh.vertsortandorder(2, 1, 1)\n\n    @test ((1, 2, 3, 4), 1) == BrickMesh.vertsortandorder(1, 2, 3, 4)\n    @test ((1, 2, 3, 4), 2) == BrickMesh.vertsortandorder(1, 3, 2, 4)\n    @test ((1, 2, 3, 4), 3) == BrickMesh.vertsortandorder(2, 1, 3, 4)\n    @test ((1, 2, 3, 4), 4) == BrickMesh.vertsortandorder(2, 4, 1, 3)\n    @test ((1, 2, 3, 4), 5) == BrickMesh.vertsortandorder(3, 1, 4, 2)\n    @test ((1, 2, 3, 4), 6) == BrickMesh.vertsortandorder(3, 4, 1, 2)\n    @test ((1, 2, 3, 4), 7) == BrickMesh.vertsortandorder(4, 2, 3, 1)\n    @test ((1, 2, 3, 4), 8) == BrickMesh.vertsortandorder(4, 3, 2, 1)\n\n    @test_throws ErrorException BrickMesh.vertsortandorder(1, 3, 3, 1)\nend\n\n@testset \"Mesh\" begin\n    let\n        (etv, etc, etb, fc) = brickmesh((4:7,), (false,))\n        etv_expect = [\n            1 2 3\n            2 3 4\n        ]\n        etb_expect = [\n            1 0 0\n            0 0 1\n        ]\n        fc_expect = Array{Int64, 1}[]\n\n        @test etv == etv_expect\n        @test etb == etb_expect\n        @test fc == fc_expect\n        @test etc[:, :, 1] == [4 5]\n        @test etc[:, :, 2] == [5 6]\n        @test etc[:, :, 3] == [6 7]\n    end\n\n    let\n        (etv, etc, etb, fc) = brickmesh((4:7,), (true,))\n        etv_expect = [\n            1 2 3\n            2 3 4\n        ]\n        etb_expect = [\n            0 0 0\n            0 0 0\n        ]\n        fc_expect = Array{Int64, 1}[[3, 2, 1]]\n\n        @test etv == etv_expect\n        @test etb == etb_expect\n        @test fc == fc_expect\n        @test etc[:, :, 1] == [4 5]\n        @test etc[:, :, 2] == [5 6]\n        @test etc[:, :, 3] == [6 7]\n    end\n\n    let\n        (etv, etc, etb, fc) = brickmesh((2:5, 4:6), (false, true))\n\n        etv_expect = [\n            1 2 5 6\n            2 3 6 7\n            3 4 7 8\n            5 6 9 10\n            6 7 10 11\n            7 8 11 12\n        ]'\n        etb_expect = [\n            1 0 0 1 0 0\n            0 0 1 0 0 1\n            0 0 0 0 0 0\n            0 0 0 0 0 0\n        ]\n        fc_expect = Array{Int64, 1}[[4, 4, 1, 2], [5, 4, 2, 3], [6, 4, 3, 4]]\n\n        @test etv == etv_expect\n        @test etb == etb_expect\n        @test fc == fc_expect\n        @test etc[:, :, 1] == [\n            2 3 2 3\n            4 4 5 5\n        ]\n        @test etc[:, :, 5] == [\n            3 4 3 4\n            5 5 6 6\n        ]\n    end\n\n    let\n        (etv, etc, etb, fc) =\n            brickmesh((-1:2:1, -1:2:1, -1:1:1), (true, true, true))\n        etv_expect = [\n            1 5\n            2 6\n            3 7\n            4 8\n            5 9\n            6 10\n            7 11\n            8 12\n        ]\n        etb_expect = zeros(Int64, 6, 2)\n\n        fc_expect = Array{Int64, 1}[\n            [1, 2, 1, 3, 5, 7],\n            [1, 4, 1, 2, 5, 6],\n            [2, 2, 5, 7, 9, 11],\n            [2, 4, 5, 6, 9, 10],\n            [2, 6, 1, 2, 3, 4],\n        ]\n\n        @test etv == etv_expect\n        @test etb == etb_expect\n        @test fc == fc_expect\n\n        @test etc[:, :, 1] == [\n            -1 1 -1 1 -1 1 -1 1\n            -1 -1 1 1 -1 -1 1 1\n            -1 -1 -1 -1 0 0 0 0\n        ]\n\n        @test etc[:, :, 2] == [\n            -1 1 -1 1 -1 1 -1 1\n            -1 -1 1 1 -1 -1 1 1\n            0 0 0 0 1 1 1 1\n        ]\n    end\n\n    let\n        (etv, etc, etb, fc) = brickmesh(\n            (-1:1, -1:1, -1:1),\n            (false, false, false),\n            boundary = ((11, 12), (13, 14), (15, 16)),\n        )\n\n        @test etb == [\n            11 0 11 0 11 0 11 0\n            0 12 0 12 0 12 0 12\n            13 13 0 0 13 13 0 0\n            0 0 14 14 0 0 14 14\n            15 15 15 15 0 0 0 0\n            0 0 0 0 16 16 16 16\n        ]\n    end\n\n    let\n        x = (1:1000,)\n        p = (false,)\n        b = ((1, 2),)\n\n        (etv, etc, etb, fc) = brickmesh(x, p, boundary = b)\n\n        n = 50\n        (etv_parts, etc_parts, etb_parts, fc_parts) =\n            brickmesh(x, p, boundary = b, part = 1, numparts = n)\n        for j in 2:n\n            (etv_j, etc_j, etb_j, fc_j) =\n                brickmesh(x, p, boundary = b, part = j, numparts = n)\n            etv_parts = cat(etv_parts, etv_j; dims = 2)\n            etc_parts = cat(etc_parts, etc_j; dims = 3)\n            etb_parts = cat(etb_parts, etb_j; dims = 2)\n        end\n\n        @test etv == etv_parts\n        @test etc == etc_parts\n        @test etb == etb_parts\n    end\n\n\n    let\n        x = (-1:2:10, -1:1:1, -4:1:1)\n        p = (true, false, true)\n        b = ((1, 2), (3, 4), (5, 6))\n\n        (etv, etc, etb, fc) = brickmesh(x, p, boundary = b)\n\n        n = 50\n        (etv_parts, etc_parts, etb_parts, fc_parts) =\n            brickmesh(x, p, boundary = b, part = 1, numparts = n)\n        for j in 2:n\n            (etv_j, etc_j, etb_j, fc_j) =\n                brickmesh(x, p, boundary = b, part = j, numparts = n)\n            etv_parts = cat(etv_parts, etv_j; dims = 2)\n            etc_parts = cat(etc_parts, etc_j; dims = 3)\n            etb_parts = cat(etb_parts, etb_j; dims = 2)\n        end\n\n        @test etv == etv_parts\n        @test etc == etc_parts\n        @test etb == etb_parts\n    end\nend\n\n@testset \"Connect\" begin\n    let\n        comm = MPI.COMM_SELF\n\n        mesh = connectmesh(\n            comm,\n            partition(comm, brickmesh((0:10,), (true,))...)[1:4]...,\n        )\n\n        nelem = 10\n\n        @test mesh[:elemtocoord][:, :, 1] == [0 1]\n        @test mesh[:elemtocoord][:, :, 2] == [1 2]\n        @test mesh[:elemtocoord][:, :, 3] == [2 3]\n        @test mesh[:elemtocoord][:, :, 4] == [3 4]\n        @test mesh[:elemtocoord][:, :, 5] == [4 5]\n        @test mesh[:elemtocoord][:, :, 6] == [5 6]\n        @test mesh[:elemtocoord][:, :, 7] == [6 7]\n        @test mesh[:elemtocoord][:, :, 8] == [7 8]\n        @test mesh[:elemtocoord][:, :, 9] == [8 9]\n        @test mesh[:elemtocoord][:, :, 10] == [9 10]\n\n        @test mesh[:elemtoelem] == [\n            10 1 2 3 4 5 6 7 8 9\n            2 3 4 5 6 7 8 9 10 1\n        ]\n\n        @test mesh[:elemtoface] == [\n            2 2 2 2 2 2 2 2 2 2\n            1 1 1 1 1 1 1 1 1 1\n        ]\n\n        @test mesh[:elemtoordr] == ones(Int, size(mesh[:elemtoordr]))\n        @test mesh[:elemtobndy] == zeros(Int, size(mesh[:elemtoordr]))\n\n        @test mesh[:elems] == 1:nelem\n        @test mesh[:realelems] == 1:nelem\n        @test mesh[:ghostelems] == nelem .+ (1:0)\n\n        @test length(mesh[:sendelems]) == 0\n\n        @test mesh[:nabrtorank] == Int[]\n        @test mesh[:nabrtorecv] == UnitRange{Int}[]\n        @test mesh[:nabrtosend] == UnitRange{Int}[]\n    end\n\n    let\n        comm = MPI.COMM_SELF\n        mesh = connectmesh(\n            comm,\n            partition(comm, brickmesh((0:4, 5:9), (false, true))...)[1:4]...,\n        )\n\n        nelem = 16\n\n        @test mesh[:elemtocoord][:, :, 1] == [0 1 0 1; 5 5 6 6]\n        @test mesh[:elemtocoord][:, :, 2] == [1 2 1 2; 5 5 6 6]\n        @test mesh[:elemtocoord][:, :, 3] == [1 2 1 2; 6 6 7 7]\n        @test mesh[:elemtocoord][:, :, 4] == [0 1 0 1; 6 6 7 7]\n        @test mesh[:elemtocoord][:, :, 5] == [0 1 0 1; 7 7 8 8]\n        @test mesh[:elemtocoord][:, :, 6] == [0 1 0 1; 8 8 9 9]\n        @test mesh[:elemtocoord][:, :, 7] == [1 2 1 2; 8 8 9 9]\n        @test mesh[:elemtocoord][:, :, 8] == [1 2 1 2; 7 7 8 8]\n        @test mesh[:elemtocoord][:, :, 9] == [2 3 2 3; 7 7 8 8]\n        @test mesh[:elemtocoord][:, :, 10] == [2 3 2 3; 8 8 9 9]\n        @test mesh[:elemtocoord][:, :, 11] == [3 4 3 4; 8 8 9 9]\n        @test mesh[:elemtocoord][:, :, 12] == [3 4 3 4; 7 7 8 8]\n        @test mesh[:elemtocoord][:, :, 13] == [3 4 3 4; 6 6 7 7]\n        @test mesh[:elemtocoord][:, :, 14] == [2 3 2 3; 6 6 7 7]\n        @test mesh[:elemtocoord][:, :, 15] == [2 3 2 3; 5 5 6 6]\n        @test mesh[:elemtocoord][:, :, 16] == [3 4 3 4; 5 5 6 6]\n\n        @test mesh[:elemtoelem] == [\n            1 1 4 4 5 6 6 5 8 7 10 9 14 3 2 15\n            2 15 14 3 8 7 10 9 12 11 11 12 13 13 16 16\n            6 7 2 1 4 5 8 3 14 9 12 13 16 15 10 11\n            4 3 8 5 6 1 2 7 10 15 16 11 12 9 14 13\n        ]\n\n        @test mesh[:elemtoface] == [\n            1 2 2 1 1 1 2 2 2 2 2 2 2 2 2 2\n            1 1 1 1 1 1 1 1 1 1 2 2 2 1 1 2\n            4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4\n            3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3\n        ]\n\n        @test mesh[:elemtoordr] == ones(Int, size(mesh[:elemtoordr]))\n\n        @test mesh[:elems] == 1:nelem\n        @test mesh[:realelems] == 1:nelem\n        @test mesh[:ghostelems] == nelem .+ (1:0)\n\n        @test length(mesh[:sendelems]) == 0\n\n        @test mesh[:nabrtorank] == Int[]\n        @test mesh[:nabrtorecv] == UnitRange{Int}[]\n        @test mesh[:nabrtosend] == UnitRange{Int}[]\n    end\n\n    let\n        comm = MPI.COMM_SELF\n        mesh = connectmeshfull(\n            comm,\n            partition(comm, brickmesh((0:4, 5:9), (false, true))...)[1:4]...,\n        )\n\n        nelem = 16\n\n        @test mesh[:elemtocoord][:, :, 1] == [0 1 0 1; 5 5 6 6]\n        @test mesh[:elemtocoord][:, :, 2] == [1 2 1 2; 5 5 6 6]\n        @test mesh[:elemtocoord][:, :, 3] == [1 2 1 2; 6 6 7 7]\n        @test mesh[:elemtocoord][:, :, 4] == [0 1 0 1; 6 6 7 7]\n        @test mesh[:elemtocoord][:, :, 5] == [0 1 0 1; 7 7 8 8]\n        @test mesh[:elemtocoord][:, :, 6] == [0 1 0 1; 8 8 9 9]\n        @test mesh[:elemtocoord][:, :, 7] == [1 2 1 2; 8 8 9 9]\n        @test mesh[:elemtocoord][:, :, 8] == [1 2 1 2; 7 7 8 8]\n        @test mesh[:elemtocoord][:, :, 9] == [2 3 2 3; 7 7 8 8]\n        @test mesh[:elemtocoord][:, :, 10] == [2 3 2 3; 8 8 9 9]\n        @test mesh[:elemtocoord][:, :, 11] == [3 4 3 4; 8 8 9 9]\n        @test mesh[:elemtocoord][:, :, 12] == [3 4 3 4; 7 7 8 8]\n        @test mesh[:elemtocoord][:, :, 13] == [3 4 3 4; 6 6 7 7]\n        @test mesh[:elemtocoord][:, :, 14] == [2 3 2 3; 6 6 7 7]\n        @test mesh[:elemtocoord][:, :, 15] == [2 3 2 3; 5 5 6 6]\n        @test mesh[:elemtocoord][:, :, 16] == [3 4 3 4; 5 5 6 6]\n\n        @test mesh[:elemtoelem] == [\n            1 1 4 4 5 6 6 5 8 7 10 9 14 3 2 15\n            2 15 14 3 8 7 10 9 12 11 11 12 13 13 16 16\n            6 7 2 1 4 5 8 3 14 9 12 13 16 15 10 11\n            4 3 8 5 6 1 2 7 10 15 16 11 12 9 14 13\n        ]\n\n        @test mesh[:elemtoface] == [\n            1 2 2 1 1 1 2 2 2 2 2 2 2 2 2 2\n            1 1 1 1 1 1 1 1 1 1 2 2 2 1 1 2\n            4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4\n            3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3\n        ]\n\n        @test mesh[:elemtoordr] == ones(Int, size(mesh[:elemtoordr]))\n\n        @test mesh[:elems] == 1:nelem\n        @test mesh[:realelems] == 1:nelem\n        @test mesh[:ghostelems] == nelem .+ (1:0)\n\n        @test length(mesh[:sendelems]) == 0\n\n        @test mesh[:nabrtorank] == Int[]\n        @test mesh[:nabrtorecv] == UnitRange{Int}[]\n        @test mesh[:nabrtosend] == UnitRange{Int}[]\n    end\nend\n\n@testset \"Mappings\" begin\n    let\n        comm = MPI.COMM_SELF\n        x = (0:4,)\n        mesh =\n            connectmesh(comm, partition(comm, brickmesh(x, (true,))...)[1:4]...)\n\n        N = 3\n        d = length(x)\n        nelem = prod(length.(x) .- 1)\n        nface = 2d\n        Nfp = (N + 1)^(d - 1)\n\n        vmap⁻, vmap⁺ = mappings(\n            ntuple(j -> N, d),\n            mesh[:elemtoelem],\n            mesh[:elemtoface],\n            mesh[:elemtoordr],\n        )\n\n        @test vmap⁻ == reshape([1, 4, 5, 8, 9, 12, 13, 16], Nfp, nface, nelem)\n        @test vmap⁺ == reshape([16, 5, 4, 9, 8, 13, 12, 1], Nfp, nface, nelem)\n    end\n\n    # Single polynomial order\n    let\n        comm = MPI.COMM_SELF\n        x = (-1:1, 0:1)\n        p = (false, true)\n        mesh = connectmesh(comm, partition(comm, brickmesh(x, p)...)[1:4]...)\n\n        N = 2\n        d = length(x)\n        nelem = prod(length.(x) .- 1)\n        nface = 2d\n        Nfp = (N + 1)^(d - 1)\n\n        vmap⁻, vmap⁺ = mappings(\n            ntuple(j -> N, d),\n            mesh[:elemtoelem],\n            mesh[:elemtoface],\n            mesh[:elemtoordr],\n        )\n\n        #! format: off\n        @test vmap⁻ == reshape(\n            [\n                 1,  4,  7,  # f=1 e=1\n                 3,  6,  9,  # f=2 e=1\n                 1,  2,  3,  # f=3 e=1\n                 7,  8,  9,  # f=4 e=1\n                10, 13, 16,  # f=1 e=2\n                12, 15, 18,  # f=2 e=2\n                10, 11, 12,  # f=3 e=2\n                16, 17, 18,  # f=4 e=2\n            ],\n            Nfp,\n            nface,\n            nelem,\n        )\n\n        @test vmap⁺ == reshape(\n            [\n                 1,  4,  7,  # f=1 e=1\n                10, 13, 16,  # f=1 e=2\n                 7,  8,  9,  # f=4 e=1\n                 1,  2,  3,  # f=3 e=1\n                 3,  6,  9,  # f=2 e=1\n                12, 15, 18,  # f=2 e=2\n                16, 17, 18,  # f=4 e=2\n                10, 11, 12,  # f=3 e=2\n            ],\n            Nfp,\n            nface,\n            nelem,\n        )\n        #! format: on\n    end\n\n    # Two polynomial orders\n    let\n        comm = MPI.COMM_SELF\n        x = (-1:1, 0:1)\n        p = (false, true)\n        mesh = connectmesh(comm, partition(comm, brickmesh(x, p)...)[1:4]...)\n\n        N = (2, 3)\n        d = length(x)\n        nelem = prod(length.(x) .- 1)\n        nface = 2d\n        Nfp = div.(prod(N .+ 1), ((N .+ 1) .^ (d - 1)))\n\n        vmap⁻, vmap⁺ =\n            mappings(N, mesh[:elemtoelem], mesh[:elemtoface], mesh[:elemtoordr])\n\n        #! format: off\n        @test vmap⁻ == reshape(\n            [\n                 1,  4,  7, 10,  # f=1 e=1\n                 3,  6,  9, 12,  # f=2 e=1\n                 1,  2,  3,  0,  # f=3 e=1\n                10, 11, 12,  0,  # f=4 e=1\n                13, 16, 19, 22,  # f=1 e=2\n                15, 18, 21, 24,  # f=2 e=2\n                13, 14, 15,  0,  # f=3 e=2\n                22, 23, 24,  0,  # f=4 e=2\n            ],\n            maximum(Nfp),\n            nface,\n            nelem,\n        )\n\n        @test vmap⁺ == reshape(\n            [\n                 1,  4,  7, 10,  # f=1 e=1\n                13, 16, 19, 22,  # f=2 e=1\n                10, 11, 12,  0,  # f=3 e=1\n                 1,  2,  3,  0,  # f=4 e=1\n                 3,  6,  9, 12,  # f=1 e=2\n                15, 18, 21, 24,  # f=2 e=2\n                22, 23, 24,  0,  # f=3 e=2\n                13, 14, 15,  0,  # f=4 e=2\n            ],\n            maximum(Nfp),\n            nface,\n            nelem,\n        )\n        #! format: on\n    end\n\n    # Single polynomial order\n    let\n        comm = MPI.COMM_SELF\n        x = (0:1, 0:1, -1:1)\n        p = (false, true, false)\n        mesh = connectmesh(comm, partition(comm, brickmesh(x, p)...)[1:4]...)\n\n        N = 2\n        d = length(x)\n        nelem = prod(length.(x) .- 1)\n        nface = 2d\n        Np = (N + 1)^d\n        Nfp = (N + 1)^(d - 1)\n\n        vmap⁻, vmap⁺ = mappings(\n            ntuple(j -> N, d),\n            mesh[:elemtoelem],\n            mesh[:elemtoface],\n            mesh[:elemtoordr],\n        )\n\n        #! format: off\n        fmask = [\n             1  3  1  7 1 19\n             4  6  2  8 2 20\n             7  9  3  9 3 21\n            10 12 10 16 4 22\n            13 15 11 17 5 23\n            16 18 12 18 6 24\n            19 21 19 25 7 25\n            22 24 20 26 8 26\n            25 27 21 27 9 27\n        ]\n        #! format: on\n\n        @test vmap⁻ == reshape([fmask[:]; fmask[:] .+ Np], Nfp, nface, nelem)\n\n        @test vmap⁺ == reshape(\n            [\n                fmask[:, 1]\n                fmask[:, 2]\n                fmask[:, 4]\n                fmask[:, 3]\n                fmask[:, 5]\n                fmask[:, 5] .+ Np\n                fmask[:, 1] .+ Np\n                fmask[:, 2] .+ Np\n                fmask[:, 4] .+ Np\n                fmask[:, 3] .+ Np\n                fmask[:, 6]\n                fmask[:, 6] .+ Np\n            ],\n            Nfp,\n            nface,\n            nelem,\n        )\n    end\n\n    # Multiple polynomial orders\n    let\n        comm = MPI.COMM_SELF\n        x = (0:1, 0:1, -1:1)\n        p = (false, true, false)\n        mesh = connectmesh(comm, partition(comm, brickmesh(x, p)...)[1:4]...)\n\n        N = (1, 2, 3)\n        d = length(x)\n        nelem = prod(length.(x) .- 1)\n        nface = 2d\n        Np = prod(N .+ 1)\n        Nfp = div.(Np, N .+ 1)\n\n        vmap⁻, vmap⁺ =\n            mappings(N, mesh[:elemtoelem], mesh[:elemtoface], mesh[:elemtoordr])\n\n        #! format: off\n        fmask =\n        (\n         [ 1  3  5  7  9 11 13 15 17 19 21 23],\n         [ 2  4  6  8 10 12 14 16 18 20 22 24],\n         [ 1  2  7  8 13 14 19 20  0  0  0  0],\n         [ 5  6 11 12 17 18 23 24  0  0  0  0],\n         [ 1  2  3  4  5  6  0  0  0  0  0  0],\n         [19 20 21 22 23 24  0  0  0  0  0  0],\n        )\n        #! format: on\n\n        @test vmap⁻ == reshape(\n            [\n                fmask[1]\n                fmask[2]\n                fmask[3]\n                fmask[4]\n                fmask[5]\n                fmask[6]\n                fmask[1] .+ Np .* (fmask[1] .> 0)\n                fmask[2] .+ Np .* (fmask[2] .> 0)\n                fmask[3] .+ Np .* (fmask[3] .> 0)\n                fmask[4] .+ Np .* (fmask[4] .> 0)\n                fmask[5] .+ Np .* (fmask[5] .> 0)\n                fmask[6] .+ Np .* (fmask[6] .> 0)\n            ]',\n            maximum(Nfp),\n            nface,\n            nelem,\n        )\n\n        @test vmap⁺ == reshape(\n            [\n                vmap⁻[:, 1, 1]\n                vmap⁻[:, 2, 1]\n                vmap⁻[:, 4, 1]\n                vmap⁻[:, 3, 1]\n                vmap⁻[:, 5, 1]\n                vmap⁻[:, 5, 2]\n                vmap⁻[:, 1, 2]\n                vmap⁻[:, 2, 2]\n                vmap⁻[:, 4, 2]\n                vmap⁻[:, 3, 2]\n                vmap⁻[:, 6, 1]\n                vmap⁻[:, 6, 2]\n            ],\n            maximum(Nfp),\n            nface,\n            nelem,\n        )\n    end\n\n    # Test polynomial order 0\n    let\n        comm = MPI.COMM_SELF\n        x = (0:1, -1:1)\n        p = (false, true)\n        mesh = connectmesh(comm, partition(comm, brickmesh(x, p)...)[1:4]...)\n\n        N = (5, 0)\n        Nq = N .+ 1\n        d = length(x)\n        nelem = prod(length.(x) .- 1)\n        nface = 2d\n        Np = prod(Nq)\n        Nfp = div.(Np, Nq)\n\n        vmap⁻, vmap⁺ =\n            mappings(N, mesh[:elemtoelem], mesh[:elemtoface], mesh[:elemtoordr])\n\n        #! format: off\n        p = reshape(1:Np, Nq)\n        fmask = ntuple(j -> zeros(Int, maximum(Nfp)), 2d)\n        fmask[1][1:Nfp[1]] .= p[1, :][:]\n        fmask[2][1:Nfp[1]] .= p[end, :][:]\n        fmask[3][1:Nfp[2]] .= p[:, 1][:]\n        fmask[4][1:Nfp[2]] .= p[:, end][:]\n        #! format: on\n\n        @test vmap⁻ == reshape(\n            [\n                fmask[1]\n                fmask[2]\n                fmask[3]\n                fmask[4]\n                fmask[1] .+ Np .* (fmask[1] .> 0)\n                fmask[2] .+ Np .* (fmask[2] .> 0)\n                fmask[3] .+ Np .* (fmask[3] .> 0)\n                fmask[4] .+ Np .* (fmask[4] .> 0)\n            ]',\n            maximum(Nfp),\n            nface,\n            nelem,\n        )\n\n        @test vmap⁺ == reshape(\n            [\n                vmap⁻[:, 1, 1]\n                vmap⁻[:, 2, 1]\n                vmap⁻[:, 4, 2]\n                vmap⁻[:, 3, 2]\n                vmap⁻[:, 1, 2]\n                vmap⁻[:, 2, 2]\n                vmap⁻[:, 4, 1]\n                vmap⁻[:, 3, 1]\n            ],\n            maximum(Nfp),\n            nface,\n            nelem,\n        )\n    end\n\n    # Test polynomial order 0\n    let\n        comm = MPI.COMM_SELF\n        x = (0:1, 0:1, -1:1)\n        p = (false, true, false)\n        mesh = connectmesh(comm, partition(comm, brickmesh(x, p)...)[1:4]...)\n\n        N = (7, 2, 0)\n        Nq = N .+ 1\n        d = length(x)\n        nelem = prod(length.(x) .- 1)\n        nface = 2d\n        Np = prod(Nq)\n        Nfp = div.(Np, Nq)\n\n        vmap⁻, vmap⁺ =\n            mappings(N, mesh[:elemtoelem], mesh[:elemtoface], mesh[:elemtoordr])\n\n        #! format: off\n        p = reshape(1:Np, Nq)\n        fmask = ntuple(j -> zeros(Int, maximum(Nfp)), 2d)\n        fmask[1][1:Nfp[1]] .= p[1, :, :][:]\n        fmask[2][1:Nfp[1]] .= p[end, :, :][:]\n        fmask[3][1:Nfp[2]] .= p[:, 1,  :][:]\n        fmask[4][1:Nfp[2]] .= p[:, end,  :][:]\n        fmask[5][1:Nfp[3]] .= p[:, :, 1][:]\n        fmask[6][1:Nfp[3]] .= p[:, :, end][:]\n        #! format: on\n\n        @test vmap⁻ == reshape(\n            [\n                fmask[1]\n                fmask[2]\n                fmask[3]\n                fmask[4]\n                fmask[5]\n                fmask[6]\n                fmask[1] .+ Np .* (fmask[1] .> 0)\n                fmask[2] .+ Np .* (fmask[2] .> 0)\n                fmask[3] .+ Np .* (fmask[3] .> 0)\n                fmask[4] .+ Np .* (fmask[4] .> 0)\n                fmask[5] .+ Np .* (fmask[5] .> 0)\n                fmask[6] .+ Np .* (fmask[6] .> 0)\n            ]',\n            maximum(Nfp),\n            nface,\n            nelem,\n        )\n\n        @test vmap⁺ == reshape(\n            [\n                vmap⁻[:, 1, 1]\n                vmap⁻[:, 2, 1]\n                vmap⁻[:, 4, 1]\n                vmap⁻[:, 3, 1]\n                vmap⁻[:, 5, 1]\n                vmap⁻[:, 5, 2]\n                vmap⁻[:, 1, 2]\n                vmap⁻[:, 2, 2]\n                vmap⁻[:, 4, 2]\n                vmap⁻[:, 3, 2]\n                vmap⁻[:, 6, 1]\n                vmap⁻[:, 6, 2]\n            ],\n            maximum(Nfp),\n            nface,\n            nelem,\n        )\n    end\nend\n\n@testset \"Get Partition\" begin\n    let\n        Nelem = 150\n        (so, ss, rs) = BrickMesh.getpartition(MPI.COMM_SELF, Nelem:-1:1)\n        @test so == Nelem:-1:1\n        @test ss == [1, Nelem + 1]\n        @test rs == [1, Nelem + 1]\n    end\n\n    let\n        Nelem = 111\n        code = [ones(1, Nelem); collect(Nelem:-1:1)']\n        (so, ss, rs) = BrickMesh.getpartition(MPI.COMM_SELF, Nelem:-1:1)\n        @test so == Nelem:-1:1\n        @test ss == [1, Nelem + 1]\n        @test rs == [1, Nelem + 1]\n    end\nend\n\n@testset \"Partition\" begin\n    (etv, etc, etb, fc) =\n        brickmesh((-1:2:1, -1:2:1, -2:1:2), (true, true, true))\n    (netv, netc, netb, nfc) = partition(MPI.COMM_SELF, etv, etc, etb, fc)[1:4]\n    @test etv == netv\n    @test etc == netc\n    @test etb == netb\n    @test fc == nfc\nend\n\n@testset \"Comm Mappings\" begin\n    let\n        N = 1\n        d = 2\n        nface = 2d\n\n        commelems = [1, 2, 5]\n        commfaces = BitArray(undef, nface, length(commelems))\n        commfaces .= false\n        nabrtocomm = [1:2, 3:3]\n\n        vmapC, nabrtovmapC =\n            commmapping(ntuple(j -> N, d), commelems, commfaces, nabrtocomm)\n\n        @test vmapC == Int[]\n        @test nabrtovmapC == UnitRange{Int64}[1:0, 1:0]\n    end\n\n    let\n        N = 1\n        d = 2\n        nface = 2d\n\n        commelems = [1, 2, 5]\n        commfaces = BitArray([\n            false false false\n            false true false\n            false true false\n            false false true\n        ])\n        nabrtocomm = [1:2, 3:3]\n\n        vmapC, nabrtovmapC =\n            commmapping(ntuple(j -> N, d), commelems, commfaces, nabrtocomm)\n\n        @test vmapC == [5, 6, 8, 19, 20]\n        @test nabrtovmapC == UnitRange{Int64}[1:3, 4:5]\n    end\n\n    # 2D, single polynomial order\n    let\n        N = 2\n        d = 2\n        nface = 2d\n\n        commelems = [2, 4, 5]\n        commfaces = BitArray([\n            true true false\n            false false false\n            false true true\n            false false true\n        ])\n        nabrtocomm = [1:1, 2:3]\n\n        vmapC, nabrtovmapC =\n            commmapping(ntuple(j -> N, d), commelems, commfaces, nabrtocomm)\n\n        @test vmapC == [10, 13, 16, 28, 29, 30, 31, 34, 37, 38, 39, 43, 44, 45]\n        @test nabrtovmapC == UnitRange{Int64}[1:3, 4:14]\n    end\n\n    # 2D, multiple polynomial orders\n    let\n        N = (2, 3)\n        Np = prod(N .+ 1)\n        d = length(N)\n        nface = 2d\n\n        commelems = [2, 4, 5]\n        #! format: off\n        commfaces = BitArray([\n             true  true false false # faces of commelems[1] to send\n            false false false  true # faces of commelems[2] to send\n             true false false  true # faces of commelems[3] to send\n        ]')\n        #! format: on\n        nabrtocomm = [1:1, 2:3]\n        vmapC, nabrtovmapC = commmapping(N, commelems, commfaces, nabrtocomm)\n\n        @test vmapC == [\n            (commelems[1] - 1) * Np + 1\n            (commelems[1] - 1) * Np + 3\n            (commelems[1] - 1) * Np + 4\n            (commelems[1] - 1) * Np + 6\n            (commelems[1] - 1) * Np + 7\n            (commelems[1] - 1) * Np + 9\n            (commelems[1] - 1) * Np + 10\n            (commelems[1] - 1) * Np + 12\n            (commelems[2] - 1) * Np + 10\n            (commelems[2] - 1) * Np + 11\n            (commelems[2] - 1) * Np + 12\n            (commelems[3] - 1) * Np + 1\n            (commelems[3] - 1) * Np + 4\n            (commelems[3] - 1) * Np + 7\n            (commelems[3] - 1) * Np + 10\n            (commelems[3] - 1) * Np + 11\n            (commelems[3] - 1) * Np + 12\n        ]\n\n        @test nabrtovmapC == UnitRange{Int64}[1:8, 9:17]\n    end\n\n    # 3D, single polynomial order\n    let\n        N = 2\n        d = 3\n        nface = 2d\n\n        commelems = [3, 4, 7, 9]\n        commfaces = BitArray([\n            true true true false\n            false true false false\n            false true false false\n            false true false false\n            false true true true\n            false true false true\n        ])\n        nabrtocomm = [1:1, 2:4]\n\n        vmapC, nabrtovmapC =\n            commmapping(ntuple(j -> N, d), commelems, commfaces, nabrtocomm)\n\n        #! format: off\n        @test vmapC == [\n            55, 58, 61, 64, 67, 70, 73, 76, 79, 82, 83, 84, 85, 86, 87, 88, 89,\n            90, 91, 92, 93, 94, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105,\n            106, 107, 108, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172,\n            175, 178, 181, 184, 187, 217, 218, 219, 220, 221, 222, 223, 224,\n            225, 235, 236, 237, 238, 239, 240, 241, 242, 243,\n        ]\n        #! format: on\n        @test nabrtovmapC == UnitRange{Int64}[1:9, 10:68]\n    end\n\n    # 3D, multiple polynomial order\n    let\n        N = (2, 3, 4)\n        Nq = N .+ 1\n        Np = prod(Nq)\n        d = length(N)\n        nface = 2d\n\n        commelems = [3, 4, 7, 9]\n        commfaces = BitArray([\n            true true true false\n            false true false false\n            false true false false\n            false true false false\n            false true true true\n            false true false true\n        ])\n\n        p = reshape(1:Np, Nq)\n        fmask = (\n            p[1, :, :][:],     # Face 1\n            p[Nq[1], :, :][:], # Face 2\n            p[:, 1, :][:],     # Face 3\n            p[:, Nq[2], :][:], # Face 4\n            p[:, :, 1][:],     # Face 5\n            p[:, :, Nq[3]][:], # Face 6\n        )\n\n        nabrtocomm = [1:1, 2:4]\n\n        vmapC, nabrtovmapC = commmapping(N, commelems, commfaces, nabrtocomm)\n\n        #! format: off\n        @test vmapC ==\n        [\n         sort(unique(vcat(fmask[commfaces[:, 1]]...))) .+ (commelems[1] - 1) *Np\n         sort(unique(vcat(fmask[commfaces[:, 2]]...))) .+ (commelems[2] - 1) *Np\n         sort(unique(vcat(fmask[commfaces[:, 3]]...))) .+ (commelems[3] - 1) *Np\n         sort(unique(vcat(fmask[commfaces[:, 4]]...))) .+ (commelems[4] - 1) *Np\n        ]\n        #! format: on\n\n        @test nabrtovmapC == UnitRange{Int64}[1:20, 21:126]\n    end\n\n    # Test mappings with polyorder 0 in one dimension\n    let\n        N = (0, 1, 2)\n        Nq = N .+ 1\n        Np = prod(Nq)\n        d = length(N)\n        nface = 2d\n\n        commelems = [3, 4, 7, 9]\n        commfaces = BitArray([\n            true true true false\n            false true false false\n            false true false false\n            false true false false\n            false true true true\n            false true false true\n        ])\n\n        p = reshape(1:Np, Nq)\n        fmask = (\n            p[1, :, :][:],     # Face 1\n            p[Nq[1], :, :][:], # Face 2\n            p[:, 1, :][:],     # Face 3\n            p[:, Nq[2], :][:], # Face 4\n            p[:, :, 1][:],     # Face 5\n            p[:, :, Nq[3]][:], # Face 6\n        )\n\n        nabrtocomm = [1:1, 2:4]\n\n        vmapC, nabrtovmapC = commmapping(N, commelems, commfaces, nabrtocomm)\n\n        #! format: off\n        @test vmapC ==\n        [\n         sort(unique(vcat(fmask[commfaces[:, 1]]...))) .+ (commelems[1] - 1) *Np\n         sort(unique(vcat(fmask[commfaces[:, 2]]...))) .+ (commelems[2] - 1) *Np\n         sort(unique(vcat(fmask[commfaces[:, 3]]...))) .+ (commelems[3] - 1) *Np\n         sort(unique(vcat(fmask[commfaces[:, 4]]...))) .+ (commelems[4] - 1) *Np\n        ]\n        #! format: on\n\n        @test nabrtovmapC == UnitRange{Int64}[1:6, 7:22]\n    end\n\n    let\n        N = (3, 2, 0)\n        Nq = N .+ 1\n        Np = prod(Nq)\n        d = length(N)\n        nface = 2d\n\n        commelems = [3, 4, 7, 9, 19]\n        commfaces = BitArray([\n            true true true false true\n            false true false false false\n            false true false false true\n            false true false false false\n            false true true true false\n            false true false true false\n        ])\n\n        p = reshape(1:Np, Nq)\n        fmask = (\n            p[1, :, :][:],     # Face 1\n            p[Nq[1], :, :][:], # Face 2\n            p[:, 1, :][:],     # Face 3\n            p[:, Nq[2], :][:], # Face 4\n            p[:, :, 1][:],     # Face 5\n            p[:, :, Nq[3]][:], # Face 6\n        )\n\n        nabrtocomm = [1:1, 2:5]\n\n        vmapC, nabrtovmapC = commmapping(N, commelems, commfaces, nabrtocomm)\n\n        #! format: off\n        @test vmapC ==\n        [\n         sort(unique(vcat(fmask[commfaces[:, 1]]...))) .+ (commelems[1] - 1) *Np\n         sort(unique(vcat(fmask[commfaces[:, 2]]...))) .+ (commelems[2] - 1) *Np\n         sort(unique(vcat(fmask[commfaces[:, 3]]...))) .+ (commelems[3] - 1) *Np\n         sort(unique(vcat(fmask[commfaces[:, 4]]...))) .+ (commelems[4] - 1) *Np\n         sort(unique(vcat(fmask[commfaces[:, 5]]...))) .+ (commelems[5] - 1) *Np\n        ]\n        #! format: on\n\n        @test nabrtovmapC == UnitRange{Int64}[1:3, 4:45]\n    end\n\n    let\n        N = (0, 2)\n        Nq = N .+ 1\n        Np = prod(Nq)\n        d = length(N)\n        nface = 2d\n\n        commelems = [3, 4, 7, 9]\n        commfaces = BitArray([\n            true true true false\n            false true false true\n            false true false false\n            false true false true\n        ])\n\n        p = reshape(1:Np, Nq)\n        fmask = (\n            p[1, :][:],     # Face 1\n            p[Nq[1], :][:], # Face 2\n            p[:, 1][:],     # Face 3\n            p[:, Nq[2]][:], # Face 4\n        )\n\n        nabrtocomm = [1:1, 2:4]\n\n        vmapC, nabrtovmapC = commmapping(N, commelems, commfaces, nabrtocomm)\n\n        #! format: off\n        @test vmapC ==\n        [\n         sort(unique(vcat(fmask[commfaces[:, 1]]...))) .+ (commelems[1] - 1) *Np\n         sort(unique(vcat(fmask[commfaces[:, 2]]...))) .+ (commelems[2] - 1) *Np\n         sort(unique(vcat(fmask[commfaces[:, 3]]...))) .+ (commelems[3] - 1) *Np\n         sort(unique(vcat(fmask[commfaces[:, 4]]...))) .+ (commelems[4] - 1) *Np\n        ]\n        #! format: on\n\n        @test nabrtovmapC == UnitRange{Int64}[1:3, 4:12]\n    end\n\n    let\n        N = (3, 0)\n        Nq = N .+ 1\n        Np = prod(Nq)\n        d = length(N)\n        nface = 2d\n\n        commelems = [3, 4, 7, 9, 19]\n        commfaces = BitArray([\n            true true true false true\n            false true false false true\n            false true false true false\n            false true false true false\n        ])\n\n        p = reshape(1:Np, Nq)\n        fmask = (\n            p[1, :][:],     # Face 1\n            p[Nq[1], :][:], # Face 2\n            p[:, 1][:],     # Face 3\n            p[:, Nq[2]][:], # Face 4\n        )\n\n        nabrtocomm = [1:1, 2:5]\n\n        vmapC, nabrtovmapC = commmapping(N, commelems, commfaces, nabrtocomm)\n\n        #! format: off\n        @test vmapC ==\n        [\n         sort(unique(vcat(fmask[commfaces[:, 1]]...))) .+ (commelems[1] - 1) *Np\n         sort(unique(vcat(fmask[commfaces[:, 2]]...))) .+ (commelems[2] - 1) *Np\n         sort(unique(vcat(fmask[commfaces[:, 3]]...))) .+ (commelems[3] - 1) *Np\n         sort(unique(vcat(fmask[commfaces[:, 4]]...))) .+ (commelems[4] - 1) *Np\n         sort(unique(vcat(fmask[commfaces[:, 5]]...))) .+ (commelems[5] - 1) *Np\n        ]\n        #! format: on\n\n        @test nabrtovmapC == UnitRange{Int64}[1:1, 2:12]\n    end\n\nend\n"
  },
  {
    "path": "test/Numerics/Mesh/DSS.jl",
    "content": "using Test\nusing MPI\nusing ClimateMachine\nClimateMachine.init()\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.Mesh.DSS\nusing KernelAbstractions\n\nDA = ClimateMachine.array_type()\n\nfunction test_dss()\n    FT = Float64\n    comm = MPI.COMM_WORLD\n    crank = MPI.Comm_rank(comm)\n    csize = MPI.Comm_size(comm)\n\n    @assert csize == 1\n\n    N = (4, 4, 5)\n    brickrange = (0:2, 5:6, 0:1)\n    periodicity = (false, false, false)\n    nvars = 1\n\n    topl = StackedBrickTopology(\n        comm,\n        brickrange,\n        periodicity = periodicity,\n        boundary = ((1, 2), (3, 4), (5, 6)),\n        connectivity = :full,\n    )\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = DA,\n        polynomialorder = N,\n    )\n\n    Nq = N .+ 1\n    Np = prod(Nq)\n    Q = MPIStateArray{FT}(\n        comm,\n        DA,\n        Np,\n        nvars,\n        length(topl.elems),\n        realelems = topl.realelems,\n        ghostelems = topl.ghostelems,\n        vmaprecv = grid.vmaprecv,\n        vmapsend = grid.vmapsend,\n        nabrtorank = topl.nabrtorank,\n        nabrtovmaprecv = grid.nabrtovmaprecv,\n        nabrtovmapsend = grid.nabrtovmapsend,\n    )\n    realelems = Q.realelems\n    ghostelems = Q.ghostelems\n\n    ldof = DA{FT, 1}(reshape(1:Np, Np))\n\n    for ivar in 1:nvars\n        for ielem in realelems\n            Q.data[:, ivar, ielem] .= ldof .+ FT((ielem - 1) * Np)\n        end\n        Q.data[:, ivar, ghostelems] .= FT(0)\n    end\n\n    pre_dss = Array(Q.data)\n\n    dss!(Q, grid)\n    #---------Tests-------------------------\n    nodes = reshape(1:Np, Nq)\n    interior = nodes[2:(Nq[1] - 1), 2:(Nq[2] - 1), 2:(Nq[3] - 1)][:]\n    vertmap = Array(grid.vertmap)\n    edgemap = Array(grid.edgemap)\n    facemap = Array(grid.facemap)\n    post_dss = Array(Q.data)\n\n    ne1r = 1:max(Nq[1] - 2, 0)\n    ne2r = 1:max(Nq[2] - 2, 0)\n    ne3r = 1:max(Nq[3] - 2, 0)\n\n    nf1r = 1:max((Nq[2] - 2) * (Nq[3] - 2), 0)\n    nf2r = 1:max((Nq[1] - 2) * (Nq[3] - 2), 0)\n    nf3r = 1:max((Nq[1] - 2) * (Nq[2] - 2), 0)\n\n    compare(el1, i1, el2, i2, efmap, rng, post, pre) =\n        post[efmap[rng, i1, 1], 1, el1] == pre[efmap[rng, i2, 1], 1, el2]\n\n    compare(el1, i1, el2, i2, el3, i3, efmap, rng, post, pre) =\n        post[efmap[rng, i1, 1], 1, el1] ==\n        pre[efmap[rng, i2, 1], 1, el2] .+ pre[efmap[rng, i3, 1], 1, el3]\n\n    el1, el2 = 1, 2\n    # Element # 1 --------------------------------------------------------------------------\n    # interior dof should not be affected\n    @test pre_dss[interior, 1, 1] == post_dss[interior, 1, 1]\n    # vertex check\n    @test post_dss[vertmap, 1, 1] == [\n        pre_dss[vertmap[1], 1, 1],\n        pre_dss[vertmap[2], 1, 1] + pre_dss[vertmap[1], 1, 2],\n        pre_dss[vertmap[3], 1, 1],\n        pre_dss[vertmap[4], 1, 1] + pre_dss[vertmap[3], 1, 2],\n        pre_dss[vertmap[5], 1, 1],\n        pre_dss[vertmap[6], 1, 1] + pre_dss[vertmap[5], 1, 2],\n        pre_dss[vertmap[7], 1, 1],\n        pre_dss[vertmap[8], 1, 1] + pre_dss[vertmap[7], 1, 2],\n    ]\n    # edge check\n    @test compare(el1, 1, el1, 1, edgemap, ne1r, post_dss, pre_dss)          # edge  1 (unaffected)\n    @test compare(el1, 2, el1, 2, edgemap, ne1r, post_dss, pre_dss)          # edge  2 (unaffected)\n    @test compare(el1, 3, el1, 3, edgemap, ne1r, post_dss, pre_dss)          # edge  3 (unaffected)\n    @test compare(el1, 4, el1, 4, edgemap, ne1r, post_dss, pre_dss)          # edge  4 (unaffected)\n\n    @test compare(el1, 5, el1, 5, edgemap, ne2r, post_dss, pre_dss)          # edge  5 (unaffected)\n    @test compare(el1, 6, el1, 6, el2, 5, edgemap, ne2r, post_dss, pre_dss) # edge  6 (shared edge)\n    @test compare(el1, 7, el1, 7, edgemap, ne2r, post_dss, pre_dss)          # edge  7 (unaffected)\n    @test compare(el1, 8, el1, 8, el2, 7, edgemap, ne2r, post_dss, pre_dss) # edge  8 (shared edge)\n\n    @test compare(el1, 9, el1, 9, edgemap, ne3r, post_dss, pre_dss)          # edge  9 (unaffected)\n    @test compare(el1, 10, el1, 10, el2, 9, edgemap, ne3r, post_dss, pre_dss) # edge 10 (shared edge)\n    @test compare(el1, 11, el1, 11, edgemap, ne3r, post_dss, pre_dss)          # edge 11 (unaffected)\n    @test compare(el1, 12, el1, 12, el2, 11, edgemap, ne3r, post_dss, pre_dss) # edge 12 (shared edge)\n\n    # face check\n    @test compare(el1, 1, el1, 1, facemap, nf1r, post_dss, pre_dss)         # face 1 (unaffected)\n    @test compare(el1, 2, el1, 2, el2, 1, facemap, nf1r, post_dss, pre_dss) # face 2 (shared face)\n\n    @test compare(el1, 3, el1, 3, facemap, nf2r, post_dss, pre_dss)         # face 3 (unaffected)\n    @test compare(el1, 4, el1, 4, facemap, nf2r, post_dss, pre_dss)         # face 4 (unaffected)\n\n    @test compare(el1, 5, el1, 5, facemap, nf3r, post_dss, pre_dss)         # face 5 (unaffected)\n    @test compare(el1, 6, el1, 6, facemap, nf3r, post_dss, pre_dss)         # face 6 (unaffected)\n\n    # Element # 2 --------------------------------------------------------------------------\n    # interior dof should not be affected\n    @test pre_dss[interior, 1, 2] == post_dss[interior, 1, 2]\n    # vertex check\n    @test post_dss[vertmap, 1, 2] == [\n        pre_dss[vertmap[1], 1, 2] + pre_dss[vertmap[2], 1, 1],\n        pre_dss[vertmap[2], 1, 2],\n        pre_dss[vertmap[3], 1, 2] + pre_dss[vertmap[4], 1, 1],\n        pre_dss[vertmap[4], 1, 2],\n        pre_dss[vertmap[5], 1, 2] + pre_dss[vertmap[6], 1, 1],\n        pre_dss[vertmap[6], 1, 2],\n        pre_dss[vertmap[7], 1, 2] + pre_dss[vertmap[8], 1, 1],\n        pre_dss[vertmap[8], 1, 2],\n    ]\n    # edge check\n    @test compare(el2, 1, el2, 1, edgemap, ne1r, post_dss, pre_dss)          # edge  1 (unaffected)\n    @test compare(el2, 2, el2, 2, edgemap, ne1r, post_dss, pre_dss)          # edge  2 (unaffected)\n    @test compare(el2, 3, el2, 3, edgemap, ne1r, post_dss, pre_dss)          # edge  3 (unaffected)\n    @test compare(el2, 4, el2, 4, edgemap, ne1r, post_dss, pre_dss)          # edge  4 (unaffected)\n\n    @test compare(el2, 5, el2, 5, el1, 6, edgemap, ne2r, post_dss, pre_dss) # edge  5 (shared edge)\n    @test compare(el2, 6, el2, 6, edgemap, ne2r, post_dss, pre_dss)          # edge  6 (unaffected)\n    @test compare(el2, 7, el2, 7, el1, 8, edgemap, ne2r, post_dss, pre_dss) # edge  7 (shared edge)\n    @test compare(el2, 8, el2, 8, edgemap, ne2r, post_dss, pre_dss)          # edge  8 (unaffected)\n\n    @test compare(el2, 9, el2, 9, el1, 10, edgemap, ne3r, post_dss, pre_dss) # edge  9 (shared edge)\n    @test compare(el2, 10, el2, 10, edgemap, ne3r, post_dss, pre_dss)          # edge 10 (unaffected)\n    @test compare(el2, 11, el2, 11, el1, 12, edgemap, ne3r, post_dss, pre_dss) # edge 11 (shared edge)\n    @test compare(el2, 12, el2, 12, edgemap, ne3r, post_dss, pre_dss)          # edge 12 (unaffected)\n\n    # face check\n    @test compare(el2, 1, el2, 1, el1, 2, facemap, nf1r, post_dss, pre_dss) # face 1 (shared face)\n    @test compare(el2, 2, el2, 2, facemap, nf1r, post_dss, pre_dss)         # face 2 (unaffected)\n\n    @test compare(el2, 3, el2, 3, facemap, nf2r, post_dss, pre_dss)         # face 3 (unaffected)\n    @test compare(el2, 4, el2, 4, facemap, nf2r, post_dss, pre_dss)         # face 4 (unaffected)\n\n    @test compare(el2, 5, el2, 5, facemap, nf3r, post_dss, pre_dss)         # face 5 (unaffected)\n    @test compare(el2, 6, el2, 6, facemap, nf3r, post_dss, pre_dss)         # face 6 (unaffected)\n\nend\n\ntest_dss()\n"
  },
  {
    "path": "test/Numerics/Mesh/DSS_mpi.jl",
    "content": "using Test\nusing MPI\nusing ClimateMachine\nClimateMachine.init()\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.Mesh.DSS\nusing KernelAbstractions\n\nDA = ClimateMachine.array_type()\n\nfunction test_dss_stacked_3d()\n    FT = Float64\n    comm = MPI.COMM_WORLD\n    crank = MPI.Comm_rank(comm)\n    csize = MPI.Comm_size(comm)\n\n    @assert csize == 3\n\n    N = (4, 4, 5)\n    brickrange = (0:4, 5:9, 0:3)\n    periodicity = (false, false, false)\n    nvars = 3\n\n    topl = StackedBrickTopology(\n        comm,\n        brickrange,\n        periodicity = periodicity,\n        boundary = ((1, 2), (3, 4), (5, 6)),\n        connectivity = :full,\n    )\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = DA,\n        polynomialorder = N,\n    )\n\n    Nq = N .+ 1\n    Np = prod(Nq)\n    Q = MPIStateArray{FT}(\n        comm,\n        DA,\n        Np,\n        nvars,\n        length(topl.elems),\n        realelems = topl.realelems,\n        ghostelems = topl.ghostelems,\n        vmaprecv = grid.vmaprecv,\n        vmapsend = grid.vmapsend,\n        nabrtorank = topl.nabrtorank,\n        nabrtovmaprecv = grid.nabrtovmaprecv,\n        nabrtovmapsend = grid.nabrtovmapsend,\n    )\n    realelems = Q.realelems\n    ghostelems = Q.ghostelems\n\n    Q.data[:, :, realelems] .= FT(1)\n    Q.data[:, :, ghostelems] .= FT(0)\n\n    dss!(Q, grid)\n    #---------Tests-------------------------\n    nodes = reshape(1:Np, Nq)\n    interior = nodes[2:(Nq[1] - 1), 2:(Nq[2] - 1), 2:(Nq[3] - 1)][:]\n    vertmap = Array(grid.vertmap)\n    edgemap = Array(grid.edgemap)\n    facemap = Array(grid.facemap)\n    data = Array(Q.data)\n    compare(data, ivar, iel, efmap) =\n        unique(data[setdiff(efmap, [-1]), ivar, lel])\n    lel = 1 # local element number for each process\n\n    if crank == 0\n        for ivar in 1:nvars\n            # local element #1, global elememt # 1\n            # interior dof should not be affected\n            idof = unique(data[interior, ivar, lel])\n            @test idof == [FT(1)]\n            # vertex dof check\n            @test data[vertmap, ivar, lel] == FT.([1, 2, 2, 4, 2, 4, 4, 8])\n            # edge dof check\n            @test compare(data, ivar, lel, edgemap[:, 1, 1]) == [FT(1)] # edge #  1\n            @test compare(data, ivar, lel, edgemap[:, 2, 1]) == [FT(2)] # edge #  2\n            @test compare(data, ivar, lel, edgemap[:, 3, 1]) == [FT(2)] # edge #  3\n            @test compare(data, ivar, lel, edgemap[:, 4, 1]) == [FT(4)] # edge #  4\n            @test compare(data, ivar, lel, edgemap[:, 5, 1]) == [FT(1)] # edge #  5\n            @test compare(data, ivar, lel, edgemap[:, 6, 1]) == [FT(2)] # edge #  6\n            @test compare(data, ivar, lel, edgemap[:, 7, 1]) == [FT(2)] # edge #  7\n            @test compare(data, ivar, lel, edgemap[:, 8, 1]) == [FT(4)] # edge #  8\n            @test compare(data, ivar, lel, edgemap[:, 9, 1]) == [FT(1)] # edge #  9\n            @test compare(data, ivar, lel, edgemap[:, 10, 1]) == [FT(2)] # edge # 10\n            @test compare(data, ivar, lel, edgemap[:, 11, 1]) == [FT(2)] # edge # 11\n            @test compare(data, ivar, lel, edgemap[:, 12, 1]) == [FT(4)] # edge # 12\n\n            # face dof check\n            @test compare(data, ivar, lel, facemap[:, 1, 1]) == [FT(1)] # face # 1\n            @test compare(data, ivar, lel, facemap[:, 2, 1]) == [FT(2)] # face # 2\n            @test compare(data, ivar, lel, facemap[:, 3, 1]) == [FT(1)] # face # 3\n            @test compare(data, ivar, lel, facemap[:, 4, 1]) == [FT(2)] # face # 4\n            @test compare(data, ivar, lel, facemap[:, 5, 1]) == [FT(1)] # face # 5\n            @test compare(data, ivar, lel, facemap[:, 6, 1]) == [FT(2)] # face # 6\n        end\n\n    elseif crank == 1\n        for ivar in 1:nvars\n            # local element #1, global elememt # 6 (in 2D)\n            # interior dof should not be affected\n            idof = unique(data[interior, ivar, lel])\n            @test idof == [FT(1)]\n            # vertex dof check\n            @test data[vertmap, ivar, lel] == FT.([2, 4, 1, 2, 4, 8, 2, 4])\n            # edge dof check\n            @test compare(data, ivar, lel, edgemap[:, 1, 1]) == [FT(2)] # edge #  1\n            @test compare(data, ivar, lel, edgemap[:, 2, 1]) == [FT(1)] # edge #  2\n            @test compare(data, ivar, lel, edgemap[:, 3, 1]) == [FT(4)] # edge #  3\n            @test compare(data, ivar, lel, edgemap[:, 4, 1]) == [FT(2)] # edge #  4\n            @test compare(data, ivar, lel, edgemap[:, 5, 1]) == [FT(1)] # edge #  5\n            @test compare(data, ivar, lel, edgemap[:, 6, 1]) == [FT(2)] # edge #  6\n            @test compare(data, ivar, lel, edgemap[:, 7, 1]) == [FT(2)] # edge #  7\n            @test compare(data, ivar, lel, edgemap[:, 8, 1]) == [FT(4)] # edge #  8\n            @test compare(data, ivar, lel, edgemap[:, 9, 1]) == [FT(2)] # edge #  9\n            @test compare(data, ivar, lel, edgemap[:, 10, 1]) == [FT(4)] # edge # 10\n            @test compare(data, ivar, lel, edgemap[:, 11, 1]) == [FT(1)] # edge # 11\n            @test compare(data, ivar, lel, edgemap[:, 12, 1]) == [FT(2)] # edge # 12\n\n\n            # face dof check\n            @test compare(data, ivar, lel, facemap[:, 1, 1]) == [FT(1)] # face # 1\n            @test compare(data, ivar, lel, facemap[:, 2, 1]) == [FT(2)] # face # 2\n            @test compare(data, ivar, lel, facemap[:, 3, 1]) == [FT(2)] # face # 3\n            @test compare(data, ivar, lel, facemap[:, 4, 1]) == [FT(1)] # face # 4\n            @test compare(data, ivar, lel, facemap[:, 5, 1]) == [FT(1)] # face # 5\n            @test compare(data, ivar, lel, facemap[:, 6, 1]) == [FT(2)] # face # 6\n        end\n    else # crank == 2\n        for ivar in 1:nvars\n            # local element #1, global elememt # 11 (in 2D)\n            # interior dof should not be affected\n            idof = unique(data[interior, ivar, lel])\n            @test idof == [FT(1)]\n            # vertex dof check\n            @test data[vertmap, ivar, lel] == FT.([4, 2, 2, 1, 8, 4, 4, 2])\n            # edge dof check\n            @test compare(data, ivar, lel, edgemap[:, 1, 1]) == [FT(2)] # edge #  1\n            @test compare(data, ivar, lel, edgemap[:, 2, 1]) == [FT(1)] # edge #  2\n            @test compare(data, ivar, lel, edgemap[:, 3, 1]) == [FT(4)] # edge #  3\n            @test compare(data, ivar, lel, edgemap[:, 4, 1]) == [FT(2)] # edge #  4\n            @test compare(data, ivar, lel, edgemap[:, 5, 1]) == [FT(2)] # edge #  5\n            @test compare(data, ivar, lel, edgemap[:, 6, 1]) == [FT(1)] # edge #  6\n            @test compare(data, ivar, lel, edgemap[:, 7, 1]) == [FT(4)] # edge #  7\n            @test compare(data, ivar, lel, edgemap[:, 8, 1]) == [FT(2)] # edge #  8\n            @test compare(data, ivar, lel, edgemap[:, 9, 1]) == [FT(4)] # edge #  9\n            @test compare(data, ivar, lel, edgemap[:, 10, 1]) == [FT(2)] # edge # 10\n            @test compare(data, ivar, lel, edgemap[:, 11, 1]) == [FT(2)] # edge # 11\n            @test compare(data, ivar, lel, edgemap[:, 12, 1]) == [FT(1)] # edge # 12\n            # face dof check\n            @test compare(data, ivar, lel, facemap[:, 1, 1]) == [FT(2)] # face # 1\n            @test compare(data, ivar, lel, facemap[:, 2, 1]) == [FT(1)] # face # 2\n            @test compare(data, ivar, lel, facemap[:, 3, 1]) == [FT(2)] # face # 3\n            @test compare(data, ivar, lel, facemap[:, 4, 1]) == [FT(1)] # face # 4\n            @test compare(data, ivar, lel, facemap[:, 5, 1]) == [FT(1)] # face # 5\n            @test compare(data, ivar, lel, facemap[:, 6, 1]) == [FT(2)] # face # 6\n        end\n    end\nend\n\ntest_dss_stacked_3d()\n"
  },
  {
    "path": "test/Numerics/Mesh/Elements.jl",
    "content": "using ClimateMachine.Mesh.Elements\nusing GaussQuadrature\nusing LinearAlgebra\nusing Test\n\n@testset \"GaussQuadrature\" begin\n    for T in (Float32, Float64, BigFloat)\n        let\n            x, w = GaussQuadrature.legendre(T, 1)\n            @test iszero(x)\n            @test w ≈ [2 * one(T)]\n        end\n\n        let\n            endpt = GaussQuadrature.left\n            x, w = GaussQuadrature.legendre(T, 1, endpt)\n            @test x ≈ [-one(T)]\n            @test w ≈ [2 * one(T)]\n        end\n\n        let\n            endpt = GaussQuadrature.right\n            x, w = GaussQuadrature.legendre(T, 1, endpt)\n            @test x ≈ [one(T)]\n            @test w ≈ [2 * one(T)]\n        end\n\n        let\n            endpt = GaussQuadrature.left\n            x, w = GaussQuadrature.legendre(T, 2, endpt)\n            @test x ≈ [-one(T); T(1 // 3)]\n            @test w ≈ [T(1 // 2); T(3 // 2)]\n        end\n\n        let\n            endpt = GaussQuadrature.right\n            x, w = GaussQuadrature.legendre(T, 2, endpt)\n            @test x ≈ [T(-1 // 3); one(T)]\n            @test w ≈ [T(3 // 2); T(1 // 2)]\n        end\n    end\n\n    let\n        err = ErrorException(\"Must have at least two points for both ends.\")\n        endpt = GaussQuadrature.both\n        @test_throws err GaussQuadrature.legendre(1, endpt)\n    end\n\n    let\n        T = Float64\n        n = 100\n        endpt = GaussQuadrature.both\n\n        a, b = GaussQuadrature.legendre_coefs(T, n)\n\n        err = ErrorException(\n            \"No convergence after 1 iterations \" * \"(try increasing maxits)\",\n        )\n\n        @test_throws err GaussQuadrature.custom_gauss_rule(\n            -one(T),\n            one(T),\n            a,\n            b,\n            endpt,\n            1,\n        )\n    end\nend\n\n@testset \"Operators\" begin\n    P5(r::AbstractVector{T}) where {T} =\n        T(1) / T(8) * (T(15) * r - T(70) * r .^ 3 + T(63) * r .^ 5)\n\n    P6(r::AbstractVector{T}) where {T} =\n        T(1) / T(16) *\n        (-T(5) .+ T(105) * r .^ 2 - T(315) * r .^ 4 + T(231) * r .^ 6)\n    DP6(r::AbstractVector{T}) where {T} =\n        T(1) / T(16) *\n        (T(2 * 105) * r - T(4 * 315) * r .^ 3 + T(6 * 231) * r .^ 5)\n\n    IPN(::Type{T}, N) where {T} = T(2) / T(2 * N + 1)\n\n    N = 6\n    for test_type in (Float32, Float64, BigFloat)\n        r, w = Elements.lglpoints(test_type, N)\n        D = Elements.spectralderivative(r)\n        x = LinRange{test_type}(-1, 1, 101)\n        I = Elements.interpolationmatrix(r, x)\n\n        @test sum(P5(r) .^ 2 .* w) ≈ IPN(test_type, 5)\n        @test D * P6(r) ≈ DP6(r)\n        @test I * P6(r) ≈ P6(x)\n    end\n\n    for test_type in (Float32, Float64, BigFloat)\n        r, w = Elements.glpoints(test_type, N)\n        D = Elements.spectralderivative(r)\n\n        @test sum(P5(r) .^ 2 .* w) ≈ IPN(test_type, 5)\n        @test sum(P6(r) .^ 2 .* w) ≈ IPN(test_type, 6)\n        @test D * P6(r) ≈ DP6(r)\n    end\nend\n\n@testset \"Jacobip\" begin\n    for T in (Float32, Float64, BigFloat)\n        let\n            α, β, N = T(0), T(0), 3 # α, β (for Legendre polynomials) & polynomial order\n            x, wt = Elements.lglpoints(T, N + 1) # lgl points for polynomial order N\n            V = Elements.jacobip(α, β, N, x)\n            # compare with orthonormalized exact solution\n            # https://en.wikipedia.org/wiki/Legendre_polynomials\n            V_exact = similar(V)\n            V_exact[:, 1] .= 1\n            V_exact[:, 2] .= x\n            V_exact[:, 3] .= (3 * x .^ 2 .- 1) / 2\n            V_exact[:, 4] .= (5 * x .^ 3 .- 3 * x) / 2\n            scale = 1 ./ sqrt.(diag(V_exact' * Diagonal(wt) * V_exact))\n            V_exact = V_exact * Diagonal(scale)\n            @test V ≈ V_exact\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/Mesh/Geometry.jl",
    "content": "using Test, MPI\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Geometry\nusing StaticArrays\n\nMPI.Initialized() || MPI.Init()\n\n@testset \"LocalGeometry\" begin\n    FT = Float64\n    ArrayType = Array\n\n    xmin = 0\n    ymin = 0\n    zmin = 0\n    xmax = 2000\n    ymax = 400\n    zmax = 2000\n\n    Ne = (20, 2, 20)\n\n    polynomialorder = 4\n\n    brickrange = (\n        range(FT(xmin); length = Ne[1] + 1, stop = xmax),\n        range(FT(ymin); length = Ne[2] + 1, stop = ymax),\n        range(FT(zmin); length = Ne[3] + 1, stop = zmax),\n    )\n    topl = StackedBrickTopology(\n        MPI.COMM_SELF,\n        brickrange,\n        periodicity = (true, true, false),\n    )\n\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = polynomialorder,\n    )\n\n    S = (\n        (xmax - xmin) / (polynomialorder * Ne[1]),\n        (ymax - ymin) / (polynomialorder * Ne[2]),\n        (zmax - zmin) / (polynomialorder * Ne[3]),\n    )\n    Savg = cbrt(prod(S))\n    Shoriavg = (S[1] + S[2]) / 2\n    M = SDiagonal(S .^ -2)\n\n    N = (polynomialorder, polynomialorder)\n    Np = (polynomialorder + 1)^3\n    for e in 1:size(grid.vgeo, 3)\n        for n in 1:size(grid.vgeo, 1)\n            g = LocalGeometry{Np, N}(grid.vgeo, n, e)\n            @test lengthscale(g) ≈ Savg\n            @test lengthscale_horizontal(g) ≈ Shoriavg\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/Mesh/Grids.jl",
    "content": "using ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Topologies: BrickTopology\nusing Test\nusing MPI\n\nMPI.Initialized() || MPI.Init()\n\n@testset \"2-D Mass matrix\" begin\n    topology = BrickTopology(MPI.COMM_WORLD, ntuple(_ -> (-1, 1), 2);)\n\n    @testset for N in ((2, 2), (2, 3), (3, 2))\n        Nq = N .+ 1\n        Np = prod(Nq)\n        Nfp = div.(Np, Nq)\n\n        grid = DiscontinuousSpectralElementGrid(\n            topology,\n            FloatType = Float64,\n            DeviceArray = Array,\n            polynomialorder = N,\n        )\n\n        @views begin\n            ω = grid.ω\n            M = grid.vgeo[:, Grids._M, 1]\n            MH = grid.vgeo[:, Grids._MH, 1]\n            sM = grid.sgeo[Grids._sM, :, :, 1]\n\n            M = reshape(M, Nq[1], Nq[2])\n            @test M ≈ [ω[1][i] * ω[2][j] for i in 1:Nq[1], j in 1:Nq[2]]\n\n            MH = reshape(MH, Nq[1], Nq[2])\n            @test MH ≈ [ω[1][i] for i in 1:Nq[1], j in 1:Nq[2]]\n\n            sM12 = sM[1:Nfp[1], 1:2]\n            @test sM12[:, 1] ≈ [ω[2][j] for j in 1:Nq[2]]\n            @test sM12[:, 2] ≈ [ω[2][j] for j in 1:Nq[2]]\n\n            sM34 = sM[1:Nfp[2], 3:4]\n            @test sM34[:, 1] ≈ [ω[1][j] for j in 1:Nq[1]]\n            @test sM34[:, 2] ≈ [ω[1][j] for j in 1:Nq[1]]\n        end\n    end\nend\n\n@testset \"3-D Mass matrix\" begin\n    topology = BrickTopology(MPI.COMM_WORLD, ntuple(_ -> (-1, 1), 3);)\n\n    @testset for N in ((2, 2, 2), (2, 3, 4), (4, 3, 2), (2, 4, 3))\n        Nq = N .+ 1\n        Np = prod(Nq)\n        Nfp = div.(Np, Nq)\n\n        grid = DiscontinuousSpectralElementGrid(\n            topology,\n            FloatType = Float64,\n            DeviceArray = Array,\n            polynomialorder = N,\n        )\n\n        @views begin\n            ω = grid.ω\n            M = grid.vgeo[:, Grids._M, 1]\n            MH = grid.vgeo[:, Grids._MH, 1]\n            sM = grid.sgeo[Grids._sM, :, :, 1]\n\n            M = reshape(M, Nq[1], Nq[2], Nq[3])\n            @test M ≈ [\n                ω[1][i] * ω[2][j] * ω[3][k]\n                for i in 1:Nq[1], j in 1:Nq[2], k in 1:Nq[3]\n            ]\n\n            MH = reshape(MH, Nq[1], Nq[2], Nq[3])\n            @test MH ≈ [\n                ω[1][i] * ω[2][j] for i in 1:Nq[1], j in 1:Nq[2], k in 1:Nq[3]\n            ]\n\n            sM12 = reshape(sM[1:Nfp[1], 1:2], Nq[2], Nq[3], 2)\n            @test sM12[:, :, 1] ≈\n                  [ω[2][j] * ω[3][k] for j in 1:Nq[2], k in 1:Nq[3]]\n            @test sM12[:, :, 2] ≈\n                  [ω[2][j] * ω[3][k] for j in 1:Nq[2], k in 1:Nq[3]]\n\n            sM34 = reshape(sM[1:Nfp[2], 3:4], Nq[1], Nq[3], 2)\n            @test sM34[:, :, 1] ≈\n                  [ω[1][i] * ω[3][k] for i in 1:Nq[1], k in 1:Nq[3]]\n            @test sM34[:, :, 2] ≈\n                  [ω[1][i] * ω[3][k] for i in 1:Nq[1], k in 1:Nq[3]]\n\n            sM56 = reshape(sM[1:Nfp[3], 5:6], Nq[1], Nq[2], 2)\n            @test sM56[:, :, 1] ≈\n                  [ω[1][i] * ω[2][j] for i in 1:Nq[1], j in 1:Nq[2]]\n            @test sM56[:, :, 2] ≈\n                  [ω[1][i] * ω[2][j] for i in 1:Nq[1], j in 1:Nq[2]]\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/Mesh/Metrics.jl",
    "content": "using ClimateMachine.Mesh.Elements\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.GeometricFactors\nusing ClimateMachine.Mesh.Metrics\nusing LinearAlgebra: I\nusing Test\nusing Random: MersenneTwister\n\nconst _ξ1x1, _ξ2x1, _ξ3x1 = Grids._ξ1x1, Grids._ξ2x1, Grids._ξ3x1\nconst _ξ1x2, _ξ2x2, _ξ3x2 = Grids._ξ1x2, Grids._ξ2x2, Grids._ξ3x2\nconst _ξ1x3, _ξ2x3, _ξ3x3 = Grids._ξ1x3, Grids._ξ2x3, Grids._ξ3x3\nconst _M, _MI = Grids._M, Grids._MI\nconst _x1, _x2, _x3 = Grids._x1, Grids._x2, Grids._x3\nconst _JcV = Grids._JcV\nconst _nvgeo = Grids._nvgeo\n\nconst _n1, _n2, _n3 = Grids._n1, Grids._n2, Grids._n3\nconst _sM, _vMI = Grids._sM, Grids._vMI\nconst _nsgeo = Grids._nsgeo\n\n@testset \"1-D Metric terms\" begin\n    for FT in (Float32, Float64)\n        #{{{\n        let\n            N = (4,)\n            Nq = N .+ 1\n            Np = prod(Nq)\n\n            dim = length(N)\n            nface = 2dim\n\n            # Create element operators for each polynomial order\n            ξω = ntuple(j -> Elements.lglpoints(FT, N[j]), dim)\n            ξ, ω = ntuple(j -> map(x -> x[j], ξω), 2)\n            D = ntuple(j -> Elements.spectralderivative(ξ[j]), dim)\n\n            dim = 1\n            e2c = Array{FT, 3}(undef, 1, 2, 2)\n            e2c[:, :, 1] = [-1 0]\n            e2c[:, :, 2] = [0 10]\n            nelem = size(e2c, 3)\n\n            (vgeo, sgeo, _) =\n                Grids.computegeometry(e2c, D, ξ, ω, (x...) -> identity(x))\n\n            vgeo = reshape(\n                vgeo.array,\n                Nq...,\n                # - 1 after fieldcount is to remove the `array` field from the array allocation\n                fieldcount(GeometricFactors.VolumeGeometry) - 1,\n                nelem,\n            )\n            @test vgeo[:, _x1, 1] ≈ (ξ[1] .- 1) / 2\n            @test vgeo[:, _x1, 2] ≈ 5 * (ξ[1] .+ 1)\n\n            @test vgeo[:, _M, 1] ≈ ω[1] .* ones(FT, Nq) / 2\n            @test vgeo[:, _M, 2] ≈ 5 * ω[1] .* ones(FT, Nq)\n            @test vgeo[:, _ξ1x1, 1] ≈ 2 * ones(FT, Nq)\n            @test vgeo[:, _ξ1x1, 2] ≈ ones(FT, Nq) / 5\n            @test sgeo.n1[1, 1, :] ≈ -ones(FT, nelem)\n            @test sgeo.n1[1, 2, :] ≈ ones(FT, nelem)\n            @test sgeo.sωJ[1, 1, :] ≈ ones(FT, nelem)\n            @test sgeo.sωJ[1, 2, :] ≈ ones(FT, nelem)\n        end\n        #}}}\n    end\n\n    # N = 0 test\n    for FT in (Float32, Float64)\n        #{{{\n        let\n            N = (0,)\n            Nq = N .+ 1\n            Np = prod(Nq)\n\n            dim = length(N)\n            nface = 2dim\n\n            # Create element operators for each polynomial order\n            ξω = ntuple(j -> Elements.glpoints(FT, N[j]), dim)\n            ξ, ω = ntuple(j -> map(x -> x[j], ξω), 2)\n\n            D = ntuple(j -> Elements.spectralderivative(ξ[j]), dim)\n\n            dim = 1\n            e2c = Array{FT, 3}(undef, 1, 2, 2)\n            e2c[:, :, 1] = [-1 0]\n            e2c[:, :, 2] = [0 10]\n            nelem = size(e2c, 3)\n\n            (vgeo, sgeo, _) =\n                Grids.computegeometry(e2c, D, ξ, ω, (x...) -> identity(x))\n            vgeo = reshape(\n                vgeo.array,\n                Nq...,\n                # - 1 after fieldcount is to remove the `array` field from the array allocation\n                fieldcount(GeometricFactors.VolumeGeometry) - 1,\n                nelem,\n            )\n            @test vgeo[1, _x1, 1] ≈ sum(e2c[:, :, 1]) / 2\n            @test vgeo[1, _x1, 2] ≈ sum(e2c[:, :, 2]) / 2\n\n            @test vgeo[:, _M, 1] ≈ ω[1] .* ones(FT, Nq) / 2\n            @test vgeo[:, _M, 2] ≈ 5 * ω[1] .* ones(FT, Nq)\n            @test vgeo[:, _ξ1x1, 1] ≈ 2 * ones(FT, Nq)\n            @test vgeo[:, _ξ1x1, 2] ≈ ones(FT, Nq) / 5\n\n            @test sgeo.n1[1, 1, :] ≈ -ones(FT, nelem)\n            @test sgeo.n1[1, 2, :] ≈ ones(FT, nelem)\n            @test sgeo.sωJ[1, 1, :] ≈ ones(FT, nelem)\n            @test sgeo.sωJ[1, 2, :] ≈ ones(FT, nelem)\n        end\n        #}}}\n    end\nend\n\n@testset \"2-D Metric terms\" begin\n    for FT in (Float32, Float64), N in ((4, 4), (4, 6), (6, 4))\n        Nq = N .+ 1\n        Np = prod(Nq)\n        Nfp = div.(Np, Nq)\n\n        dim = length(N)\n        nface = 2dim\n\n        # Create element operators for each polynomial order\n        ξω = ntuple(j -> Elements.lglpoints(FT, N[j]), dim)\n        ξ, ω = ntuple(j -> map(x -> x[j], ξω), 2)\n        D = ntuple(j -> Elements.spectralderivative(ξ[j]), dim)\n\n        # linear and rotation test\n        #{{{\n        let\n            e2c = Array{FT, 3}(undef, 2, 4, 4)\n            e2c[:, :, 1] = [\n                0 2 0 2\n                0 0 2 2\n            ]\n            e2c[:, :, 2] = [\n                2 2 0 0\n                0 2 0 2\n            ]\n            e2c[:, :, 3] = [\n                2 0 2 0\n                2 2 0 0\n            ]\n            e2c[:, :, 4] = [\n                0 0 2 2\n                2 0 2 0\n            ]\n            nelem = size(e2c, 3)\n\n            x_exact = Array{FT, 3}(undef, Nq..., nelem)\n            x_exact[:, :, 1] .= 1 .+ ξ[1]\n            x_exact[:, :, 2] .= 1 .- ξ[2]'\n            x_exact[:, :, 3] .= 1 .- ξ[1]\n            x_exact[:, :, 4] .= 1 .+ ξ[2]'\n\n            y_exact = Array{FT, 3}(undef, Nq..., nelem)\n            y_exact[:, :, 1] .= 1 .+ ξ[2]'\n            y_exact[:, :, 2] .= 1 .+ ξ[1]\n            y_exact[:, :, 3] .= 1 .- ξ[2]'\n            y_exact[:, :, 4] .= 1 .- ξ[1]\n\n            M_exact =\n                ones(FT, Nq..., nelem) .* reshape(kron(reverse(ω)...), Nq..., 1)\n\n            ξ1x1_exact = zeros(FT, Nq..., nelem)\n            ξ1x1_exact[:, :, 1] .= 1\n            ξ1x1_exact[:, :, 3] .= -1\n\n            ξ1x2_exact = zeros(FT, Nq..., nelem)\n            ξ1x2_exact[:, :, 2] .= 1\n            ξ1x2_exact[:, :, 4] .= -1\n\n            ξ2x1_exact = zeros(FT, Nq..., nelem)\n            ξ2x1_exact[:, :, 2] .= -1\n            ξ2x1_exact[:, :, 4] .= 1\n\n            ξ2x2_exact = zeros(FT, Nq..., nelem)\n            ξ2x2_exact[:, :, 1] .= 1\n            ξ2x2_exact[:, :, 3] .= -1\n\n            sM_exact = fill(FT(NaN), maximum(Nfp), nface, nelem)\n            sM_exact[1:Nfp[1], 1, :] .= 1 .* ω[2]\n            sM_exact[1:Nfp[1], 2, :] .= 1 .* ω[2]\n            sM_exact[1:Nfp[2], 3, :] .= 1 .* ω[1]\n            sM_exact[1:Nfp[2], 4, :] .= 1 .* ω[1]\n\n            nx_exact = fill(FT(NaN), maximum(Nfp), nface, nelem)\n            nx_exact[1:Nfp[1], 1:2, :] .= 0\n            nx_exact[1:Nfp[2], 3:4, :] .= 0\n\n            nx_exact[1:Nfp[1], 1, 1] .= -1\n            nx_exact[1:Nfp[1], 2, 1] .= 1\n            nx_exact[1:Nfp[2], 3, 2] .= 1\n            nx_exact[1:Nfp[2], 4, 2] .= -1\n            nx_exact[1:Nfp[1], 1, 3] .= 1\n            nx_exact[1:Nfp[1], 2, 3] .= -1\n            nx_exact[1:Nfp[2], 3, 4] .= -1\n            nx_exact[1:Nfp[2], 4, 4] .= 1\n\n            ny_exact = fill(FT(NaN), maximum(Nfp), nface, nelem)\n            ny_exact[1:Nfp[1], 1:2, :] .= 0\n            ny_exact[1:Nfp[2], 3:4, :] .= 0\n\n            ny_exact[1:Nfp[2], 3, 1] .= -1\n            ny_exact[1:Nfp[2], 4, 1] .= 1\n            ny_exact[1:Nfp[1], 1, 2] .= -1\n            ny_exact[1:Nfp[1], 2, 2] .= 1\n            ny_exact[1:Nfp[2], 3, 3] .= 1\n            ny_exact[1:Nfp[2], 4, 3] .= -1\n            ny_exact[1:Nfp[1], 1, 4] .= 1\n            ny_exact[1:Nfp[1], 2, 4] .= -1\n\n            (vgeo, sgeo, _) =\n                Grids.computegeometry(e2c, D, ξ, ω, (x...) -> identity(x))\n\n            vgeo = reshape(\n                vgeo.array,\n                Nq...,\n                # - 1 after fieldcount is to remove the `array` field from the array allocation\n                fieldcount(GeometricFactors.VolumeGeometry) - 1,\n                nelem,\n            )\n\n            @test (@view vgeo[:, :, _x1, :]) ≈ x_exact\n            @test (@view vgeo[:, :, _x2, :]) ≈ y_exact\n            @test (@view vgeo[:, :, _M, :]) ≈ M_exact\n            @test (@view vgeo[:, :, _ξ1x1, :]) ≈ ξ1x1_exact\n            @test (@view vgeo[:, :, _ξ1x2, :]) ≈ ξ1x2_exact\n            @test (@view vgeo[:, :, _ξ2x1, :]) ≈ ξ2x1_exact\n            @test (@view vgeo[:, :, _ξ2x2, :]) ≈ ξ2x2_exact\n            msk = isfinite.(sM_exact)\n            @test sgeo.sωJ[:, :, :][msk] ≈ sM_exact[msk]\n            @test sgeo.n1[:, :, :][msk] ≈ nx_exact[msk]\n            @test sgeo.n2[:, :, :][msk] ≈ ny_exact[msk]\n\n            nothing\n        end\n        #}}}\n\n        # Polynomial 2-D test\n        #{{{\n        let\n            f(ξ1, ξ2) = (\n                9 .* ξ1 - (1 .+ ξ1) .* ξ2 .^ 2 +\n                (ξ1 .- 1) .^ 2 .* (1 .- ξ2 .^ 2 .+ ξ2 .^ 3),\n                10 .* ξ2 .+ ξ1 .^ 4 .* (1 .- ξ2) .+ ξ1 .^ 2 .* ξ2 .* (1 .+ ξ2),\n            )\n            fx1ξ1(ξ1, ξ2) =\n                7 .+ ξ2 .^ 2 .- 2 .* ξ2 .^ 3 .+\n                2 .* ξ1 .* (1 .- ξ2 .^ 2 .+ ξ2 .^ 3)\n            fx1ξ2(ξ1, ξ2) =\n                -2 .* (1 .+ ξ1) .* ξ2 .+\n                (-1 .+ ξ1) .^ 2 .* ξ2 .* (-2 .+ 3 .* ξ2)\n            fx2ξ1(ξ1, ξ2) =\n                -4 .* ξ1 .^ 3 .* (-1 .+ ξ2) .+ 2 .* ξ1 .* ξ2 .* (1 .+ ξ2)\n            fx2ξ2(ξ1, ξ2) = 10 .- ξ1 .^ 4 .+ ξ1 .^ 2 .* (1 .+ 2 .* ξ2)\n\n            e2c = Array{FT, 3}(undef, 2, 4, 1)\n            e2c[:, :, 1] = [-1 1 -1 1; -1 -1 1 1]\n            nelem = size(e2c, 3)\n\n            # Create the metrics\n            (x1ξ1, x1ξ2, x2ξ1, x2ξ2) = let\n                (vgeo, _) = Grids.computegeometry(\n                    e2c,\n                    D,\n                    ξ,\n                    ω,\n                    (x...) -> identity(x),\n                )\n                vgeo = reshape(\n                    vgeo.array,\n                    Nq...,\n                    # - 1 after fieldcount is to remove the `array` field from the array allocation\n                    fieldcount(GeometricFactors.VolumeGeometry) - 1,\n                    nelem,\n                )\n                ξ1, ξ2 = vgeo[:, :, _x1, :], vgeo[:, :, _x2, :]\n                (fx1ξ1(ξ1, ξ2), fx1ξ2(ξ1, ξ2), fx2ξ1(ξ1, ξ2), fx2ξ2(ξ1, ξ2))\n            end\n            J = (x1ξ1 .* x2ξ2 - x1ξ2 .* x2ξ1)\n            M = J .* reshape(kron(reverse(ω)...), Nq..., 1)\n\n            meshwarp(ξ1, ξ2, _) = (f(ξ1, ξ2)..., 0)\n            (vgeo, sgeo, _) = Grids.computegeometry(e2c, D, ξ, ω, meshwarp)\n            vgeo = reshape(\n                vgeo.array,\n                Nq...,\n                # - 1 after fieldcount is to remove the `array` field from the array allocation\n                fieldcount(GeometricFactors.VolumeGeometry) - 1,\n                nelem,\n            )\n            x1 = @view vgeo[:, :, _x1, :]\n            x2 = @view vgeo[:, :, _x2, :]\n\n            @test M ≈ (@view vgeo[:, :, _M, :])\n            @test (@view vgeo[:, :, _ξ1x1, :]) ≈ x2ξ2 ./ J\n            @test (@view vgeo[:, :, _ξ2x1, :]) ≈ -x2ξ1 ./ J\n            @test (@view vgeo[:, :, _ξ1x2, :]) ≈ -x1ξ2 ./ J\n            @test (@view vgeo[:, :, _ξ2x2, :]) ≈ x1ξ1 ./ J\n\n            # check the normals?\n            sM = @view sgeo.sωJ[:, :, :]\n            n1 = @view sgeo.n1[:, :, :]\n            n2 = @view sgeo.n2[:, :, :]\n            @test all(hypot.(n1[1:Nfp[1], 1:2, :], n2[1:Nfp[1], 1:2, :]) .≈ 1)\n            @test all(hypot.(n1[1:Nfp[2], 3:4, :], n2[1:Nfp[2], 3:4, :]) .≈ 1)\n            @test sM[1:Nfp[1], 1, :] .* n1[1:Nfp[1], 1, :] ≈\n                  -x2ξ2[1, :, :] .* ω[2]\n            @test sM[1:Nfp[1], 1, :] .* n2[1:Nfp[1], 1, :] ≈\n                  x1ξ2[1, :, :] .* ω[2]\n            @test sM[1:Nfp[1], 2, :] .* n1[1:Nfp[1], 2, :] ≈\n                  x2ξ2[Nq[1], :, :] .* ω[2]\n            @test sM[1:Nfp[1], 2, :] .* n2[1:Nfp[1], 2, :] ≈\n                  -x1ξ2[Nq[1], :, :] .* ω[2]\n            @test sM[1:Nfp[2], 3, :] .* n1[1:Nfp[2], 3, :] ≈\n                  x2ξ1[:, 1, :] .* ω[1]\n            @test sM[1:Nfp[2], 3, :] .* n2[1:Nfp[2], 3, :] ≈\n                  -x1ξ1[:, 1, :] .* ω[1]\n            @test sM[1:Nfp[2], 4, :] .* n1[1:Nfp[2], 4, :] ≈\n                  -x2ξ1[:, Nq[2], :] .* ω[1]\n            @test sM[1:Nfp[2], 4, :] .* n2[1:Nfp[2], 4, :] ≈\n                  x1ξ1[:, Nq[2], :] .* ω[1]\n        end\n        #}}}\n\n        # Constant preserving test\n        #{{{\n        let\n            rng = MersenneTwister(777)\n            f(ξ1, ξ2) = (\n                ξ1 + (ξ1 * ξ2 * rand(rng) + rand(rng)) / 10,\n                ξ2 + (ξ1 * ξ2 * rand(rng) + rand(rng)) / 10,\n            )\n\n            e2c = Array{FT, 3}(undef, 2, 4, 1)\n            e2c[:, :, 1] = [-1 1 -1 1; -1 -1 1 1]\n            nelem = size(e2c, 3)\n\n            meshwarp(ξ1, ξ2, _) = (f(ξ1, ξ2)..., 0)\n            (vgeo, sgeo, _) = Grids.computegeometry(e2c, D, ξ, ω, meshwarp)\n            vgeo = reshape(\n                vgeo.array,\n                Nq...,\n                # - 1 after fieldcount is to remove the `array` field from the array allocation\n                fieldcount(GeometricFactors.VolumeGeometry) - 1,\n                nelem,\n            )\n            x1 = @view vgeo[:, :, _x1, :]\n            x2 = @view vgeo[:, :, _x2, :]\n\n            (Cx1, Cx2) = (zeros(FT, Nq...), zeros(FT, Nq...))\n\n            J = vgeo[:, :, _M, :] ./ reshape(kron(reverse(ω)...), Nq..., 1)\n            ξ1x1 = @view vgeo[:, :, _ξ1x1, :]\n            ξ1x2 = @view vgeo[:, :, _ξ1x2, :]\n            ξ2x1 = @view vgeo[:, :, _ξ2x1, :]\n            ξ2x2 = @view vgeo[:, :, _ξ2x2, :]\n\n            e = 1\n            for n in 1:Nq[2]\n                Cx1[:, n] += D[1] * (J[:, n, e] .* ξ1x1[:, n, e])\n                Cx2[:, n] += D[1] * (J[:, n, e] .* ξ1x2[:, n, e])\n            end\n            for n in 1:Nq[1]\n                Cx1[n, :] += D[2] * (J[n, :, e] .* ξ2x1[n, :, e])\n                Cx2[n, :] += D[2] * (J[n, :, e] .* ξ2x2[n, :, e])\n            end\n            @test maximum(abs.(Cx1)) ≤ 100 * eps(FT)\n            @test maximum(abs.(Cx2)) ≤ 100 * eps(FT)\n        end\n        #}}}\n    end\n\n    #N = 0 test\n    #{{{\n    let\n        for FT in (Float32, Float64)\n            N = (2, 0)\n            Nq = N .+ 1\n            Np = prod(Nq)\n            Nfp = div.(Np, Nq)\n\n            dim = length(N)\n            nface = 2dim\n\n            # Create element operators for each polynomial order\n            ξω = ntuple(\n                j ->\n                    Nq[j] == 1 ? Elements.glpoints(FT, N[j]) :\n                    Elements.lglpoints(FT, N[j]),\n                dim,\n            )\n            ξ, ω = ntuple(j -> map(x -> x[j], ξω), 2)\n            D = ntuple(j -> Elements.spectralderivative(ξ[j]), dim)\n\n            fx1(ξ1, ξ2) = ξ1 + (1 + ξ1)^2 * ξ2 / 10\n            fx1ξ1(ξ1, ξ2) = 1 + 2 * (1 + ξ1) * ξ2 / 10\n            fx1ξ2(ξ1, ξ2) = (1 + ξ1)^2 / 10\n            fx2(ξ1, ξ2) = ξ2 - (1 + ξ1)^2\n            fx2ξ1(ξ1, ξ2) = -2 * (1 + ξ1)\n            fx2ξ2(ξ1, ξ2) = 1\n\n            e2c = Array{FT, 3}(undef, 2, 4, 1)\n            e2c[:, :, 1] = [-1 1 -1 1; -1 -1 1 1]\n            nelem = size(e2c, 3)\n\n            # Create the metrics\n            (x1, x2, x1ξ1, x1ξ2, x2ξ1, x2ξ2) = let\n                vgeo = VolumeGeometry(FT, Nq, nelem)\n                Metrics.creategrid!(vgeo, e2c, ξ)\n                (\n                    fx1.(vgeo.x1, vgeo.x2),\n                    fx2.(vgeo.x1, vgeo.x2),\n                    fx1ξ1.(vgeo.x1, vgeo.x2),\n                    fx1ξ2.(vgeo.x1, vgeo.x2),\n                    fx2ξ1.(vgeo.x1, vgeo.x2),\n                    fx2ξ2.(vgeo.x1, vgeo.x2),\n                )\n            end\n            J = (x1ξ1 .* x2ξ2 - x1ξ2 .* x2ξ1)\n\n            M = J .* reshape(kron(reverse(ω)...), Nq..., 1)\n\n            meshwarp(ξ1, ξ2, _) = (fx1(ξ1, ξ2), fx2(ξ1, ξ2), 0)\n            (vgeo, sgeo, _) = Grids.computegeometry(e2c, D, ξ, ω, meshwarp)\n            vgeo = reshape(\n                vgeo.array,\n                Nq...,\n                # - 1 after fieldcount is to remove the `array` field from the array allocation\n                fieldcount(GeometricFactors.VolumeGeometry) - 1,\n                nelem,\n            )\n            @test x1 ≈ vgeo[:, :, _x1, :]\n            @test x2 ≈ vgeo[:, :, _x2, :]\n\n            @test M ≈ vgeo[:, :, _M, :]\n            @test (@view vgeo[:, :, _ξ2x1, :]) .* J ≈ -x2ξ1\n            @test (@view vgeo[:, :, _ξ2x2, :]) .* J ≈ x1ξ1\n            @test (@view vgeo[:, :, _ξ1x1, :]) .* J ≈ x2ξ2\n            @test (@view vgeo[:, :, _ξ1x2, :]) .* J ≈ -x1ξ2\n\n            # check the normals?\n            sM = @view sgeo.sωJ[:, :, :]\n            n1 = @view sgeo.n1[:, :, :]\n            n2 = @view sgeo.n2[:, :, :]\n            @test all(hypot.(n1[1:Nfp[1], 1:2, :], n2[1:Nfp[1], 1:2, :]) .≈ 1)\n            @test all(hypot.(n1[1:Nfp[2], 3:4, :], n2[1:Nfp[2], 3:4, :]) .≈ 1)\n            @test sM[1:Nfp[1], 1, :] .* n1[1:Nfp[1], 1, :] ≈\n                  -x2ξ2[1, :, :] .* ω[2]\n            @test sM[1:Nfp[1], 1, :] .* n2[1:Nfp[1], 1, :] ≈\n                  x1ξ2[1, :, :] .* ω[2]\n            @test sM[1:Nfp[1], 2, :] .* n1[1:Nfp[1], 2, :] ≈\n                  x2ξ2[Nq[1], :, :] .* ω[2]\n            @test sM[1:Nfp[1], 2, :] .* n2[1:Nfp[1], 2, :] ≈\n                  -x1ξ2[Nq[1], :, :] .* ω[2]\n\n            # for these faces we need the N = 1 metrics\n            (x1ξ1, x2ξ1) = let\n                @assert Nq[2] == 1 && Nq[1] != 1\n                Nq_N1 = max.(2, Nq)\n                vgeo_N1 = VolumeGeometry(FT, Nq_N1, nelem)\n                Metrics.creategrid!(\n                    vgeo_N1,\n                    e2c,\n                    (ξ[1], Elements.lglpoints(FT, 1)[1]),\n                )\n                x1 = reshape(vgeo_N1.x1, (Nq_N1..., nelem))\n                x2 = reshape(vgeo_N1.x2, (Nq_N1..., nelem))\n                (fx1ξ1.(x1, x2), fx2ξ1.(x1, x2))\n            end\n\n            @test sM[1:Nfp[2], 3, :] .* n1[1:Nfp[2], 3, :] ≈\n                  x2ξ1[:, 1, :] .* ω[1]\n            @test sM[1:Nfp[2], 3, :] .* n2[1:Nfp[2], 3, :] ≈\n                  -x1ξ1[:, 1, :] .* ω[1]\n            @test sM[1:Nfp[2], 4, :] .* n1[1:Nfp[2], 4, :] ≈\n                  -x2ξ1[:, 2, :] .* ω[1]\n            @test sM[1:Nfp[2], 4, :] .* n2[1:Nfp[2], 4, :] ≈\n                  x1ξ1[:, 2, :] .* ω[1]\n        end\n    end\n    #}}}\n\n    # Constant preserving test for N = 0\n    #{{{\n    let\n        for FT in (Float32, Float64), N in ((4, 0), (0, 4))\n            Nq = N .+ 1\n            Np = prod(Nq)\n            Nfp = div.(Np, Nq)\n\n            dim = length(N)\n            nface = 2dim\n\n            # Create element operators for each polynomial order\n            ξω = ntuple(\n                j ->\n                    Nq[j] == 1 ? Elements.glpoints(FT, N[j]) :\n                    Elements.lglpoints(FT, N[j]),\n                dim,\n            )\n            ξ, ω = ntuple(j -> map(x -> x[j], ξω), 2)\n            D = ntuple(j -> Elements.spectralderivative(ξ[j]), dim)\n\n            rng = MersenneTwister(777)\n            fx1(ξ1, ξ2) = ξ1 + (ξ1 * ξ2 * rand(rng) + rand(rng)) / 10\n            fx2(ξ1, ξ2) = ξ2 + (ξ1 * ξ2 * rand(rng) + rand(rng)) / 10\n\n            e2c = Array{FT, 3}(undef, 2, 4, 1)\n            e2c[:, :, 1] = [-1 1 -1 1; -1 -1 1 1]\n            nelem = size(e2c, 3)\n\n            meshwarp(ξ1, ξ2, _) = (fx1(ξ1, ξ2), fx2(ξ1, ξ2), 0)\n            (vgeo, sgeo, _) = Grids.computegeometry(e2c, D, ξ, ω, meshwarp)\n\n            vgeo = reshape(\n                vgeo.array,\n                prod(Nq),\n                # - 1 after fieldcount is to remove the `array` field from the array allocation\n                fieldcount(GeometricFactors.VolumeGeometry) - 1,\n                nelem,\n            )\n            M = vgeo[:, _M, :]\n            ξ1x1 = vgeo[:, _ξ1x1, :]\n            ξ2x1 = vgeo[:, _ξ2x1, :]\n            ξ1x2 = vgeo[:, _ξ1x2, :]\n            ξ2x2 = vgeo[:, _ξ2x2, :]\n\n            I1 = Matrix(I, Nq[1], Nq[1])\n            I2 = Matrix(I, Nq[2], Nq[2])\n            D1 = kron(I2, D[1])\n            D2 = kron(D[2], I1)\n\n            # Face interpolation operators\n            L = (\n                kron(I2, I1[1, :]'),\n                kron(I2, I1[Nq[1], :]'),\n                kron(I2[1, :]', I1),\n                kron(I2[Nq[2], :]', I1),\n            )\n            sM = ntuple(f -> sgeo.sωJ[1:Nfp[cld(f, 2)], f, :], nface)\n            n1 = ntuple(f -> sgeo.n1[1:Nfp[cld(f, 2)], f, :], nface)\n            n2 = ntuple(f -> sgeo.n2[1:Nfp[cld(f, 2)], f, :], nface)\n\n            # If constant preserving then:\n            #   \\sum_{j} = D' * M * ξjxk = \\sum_{f} L_f' * sM_f * n1_f\n            @test D1' * (M .* ξ1x1) + D2' * (M .* ξ2x1) ≈\n                  mapreduce((L, sM, n1) -> L' * (sM .* n1), +, L, sM, n1)\n            @test D1' * (M .* ξ1x2) + D2' * (M .* ξ2x2) ≈\n                  mapreduce((L, sM, n2) -> L' * (sM .* n2), +, L, sM, n2)\n        end\n    end\n    #}}}\nend\n\n@testset \"3-D Metric terms\" begin\n    # linear test\n    #{{{\n    for FT in (Float32, Float64), N in ((2, 2, 2), (2, 3, 4), (4, 3, 2))\n        Nq = N .+ 1\n        Np = prod(Nq)\n        Nfp = div.(Np, Nq)\n\n        dim = length(N)\n        nface = 2dim\n\n        # Create element operators for each polynomial order\n        ξω = ntuple(j -> Elements.lglpoints(FT, N[j]), dim)\n        ξ, ω = ntuple(j -> map(x -> x[j], ξω), 2)\n        D = ntuple(j -> Elements.spectralderivative(ξ[j]), dim)\n\n        e2c = Array{FT, 3}(undef, dim, 8, 2)\n        e2c[:, :, 1] = [\n            0 2 0 2 0 2 0 2\n            0 0 2 2 0 0 2 2\n            0 0 0 0 2 2 2 2\n        ]\n        e2c[:, :, 2] = [\n            2 2 0 0 2 2 0 0\n            0 2 0 2 0 2 0 2\n            0 0 0 0 2 2 2 2\n        ]\n\n        nelem = size(e2c, 3)\n\n        x_exact = Array{FT, 4}(undef, Nq..., nelem)\n        x_exact[:, :, :, 1] .= 1 .+ ξ[1]\n        x_exact[:, :, :, 2] .= 1 .- ξ[2]'\n\n        ξ1x1_exact = zeros(Int, Nq..., nelem)\n        ξ1x1_exact[:, :, :, 1] .= 1\n\n        ξ1x2_exact = zeros(Int, Nq..., nelem)\n        ξ1x2_exact[:, :, :, 2] .= 1\n\n        ξ2x1_exact = zeros(Int, Nq..., nelem)\n        ξ2x1_exact[:, :, :, 2] .= -1\n\n        ξ2x2_exact = zeros(Int, Nq..., nelem)\n        ξ2x2_exact[:, :, :, 1] .= 1\n\n        ξ3x3_exact = ones(Int, Nq..., nelem)\n\n        y_exact = Array{FT, 4}(undef, Nq..., nelem)\n        y_exact[:, :, :, 1] .= 1 .+ ξ[2]'\n        y_exact[:, :, :, 2] .= 1 .+ ξ[1]\n\n        z_exact = Array{FT, 4}(undef, Nq..., nelem)\n        z_exact[:, :, :, :] .= reshape(1 .+ ξ[3], 1, 1, Nq[3])\n\n        M_exact =\n            ones(Int, Nq..., nelem) .* reshape(kron(reverse(ω)...), Nq..., 1)\n\n        sJ_exact = ones(Int, maximum(Nfp), nface, nelem)\n\n        nx_exact = zeros(Int, maximum(Nfp), nface, nelem)\n        nx_exact[:, 1, 1] .= -1\n        nx_exact[:, 2, 1] .= 1\n        nx_exact[:, 3, 2] .= 1\n        nx_exact[:, 4, 2] .= -1\n\n        ny_exact = zeros(Int, maximum(Nfp), nface, nelem)\n        ny_exact[:, 3, 1] .= -1\n        ny_exact[:, 4, 1] .= 1\n        ny_exact[:, 1, 2] .= -1\n        ny_exact[:, 2, 2] .= 1\n\n        nz_exact = zeros(Int, maximum(Nfp), nface, nelem)\n        nz_exact[:, 5, 1:2] .= -1\n        nz_exact[:, 6, 1:2] .= 1\n\n        (vgeo, sgeo, _) =\n            Grids.computegeometry(e2c, D, ξ, ω, (x...) -> identity(x))\n\n        vgeo = reshape(\n            vgeo.array,\n            Nq...,\n            # - 1 after fieldcount is to remove the `array` field from the array allocation\n            fieldcount(GeometricFactors.VolumeGeometry) - 1,\n            nelem,\n        )\n\n        @test (@view vgeo[:, :, :, _x1, :]) ≈ x_exact\n        @test (@view vgeo[:, :, :, _x2, :]) ≈ y_exact\n        @test (@view vgeo[:, :, :, _x3, :]) ≈ z_exact\n        @test (@view vgeo[:, :, :, _M, :]) ≈ M_exact\n        @test (@view vgeo[:, :, :, _ξ1x1, :]) ≈ ξ1x1_exact\n        @test (@view vgeo[:, :, :, _ξ1x2, :]) ≈ ξ1x2_exact\n        @test maximum(abs.(@view vgeo[:, :, :, _ξ1x3, :])) ≤ 100 * eps(FT)\n        @test (@view vgeo[:, :, :, _ξ2x1, :]) ≈ ξ2x1_exact\n        @test (@view vgeo[:, :, :, _ξ2x2, :]) ≈ ξ2x2_exact\n        @test maximum(abs.(@view vgeo[:, :, :, _ξ2x3, :])) ≤ 100 * eps(FT)\n        @test maximum(abs.(@view vgeo[:, :, :, _ξ3x1, :])) ≤ 100 * eps(FT)\n        @test maximum(abs.(@view vgeo[:, :, :, _ξ3x2, :])) ≤ 100 * eps(FT)\n        @test (@view vgeo[:, :, :, _ξ3x3, :]) ≈ ξ3x3_exact\n        for d in 1:dim\n            for f in (2d - 1):(2d)\n                ωf = ntuple(j -> ω[mod1(d + j, dim)], dim - 1)\n                if !(dim == 3 && d == 2)\n                    ωf = reverse(ωf)\n                end\n                Mf = kron(1, ωf...)\n                @test isapprox(\n                    (@view sgeo.sωJ[1:Nfp[d], f, :]),\n                    sJ_exact[1:Nfp[d], f, :] .* Mf,\n                    atol = √eps(FT),\n                    rtol = √eps(FT),\n                )\n                @test isapprox(\n                    (@view sgeo.n1[1:Nfp[d], f, :]),\n                    nx_exact[1:Nfp[d], f, :];\n                    atol = √eps(FT),\n                    rtol = √eps(FT),\n                )\n                @test isapprox(\n                    (@view sgeo.n2[1:Nfp[d], f, :]),\n                    ny_exact[1:Nfp[d], f, :];\n                    atol = √eps(FT),\n                    rtol = √eps(FT),\n                )\n                @test isapprox(\n                    (@view sgeo.n3[1:Nfp[d], f, :]),\n                    nz_exact[1:Nfp[d], f, :];\n                    atol = √eps(FT),\n                    rtol = √eps(FT),\n                )\n            end\n        end\n    end\n    #}}}\n\n    # Polynomial 3-D test\n    #{{{\n    for FT in (Float32, Float64), N in ((9, 9, 9), (9, 9, 10), (10, 9, 11))\n        Nq = N .+ 1\n        Np = prod(Nq)\n        Nfp = div.(Np, Nq)\n\n        dim = length(N)\n        nface = 2dim\n\n        # Create element operators for each polynomial order\n        ξω = ntuple(j -> Elements.lglpoints(FT, N[j]), dim)\n        ξ, ω = ntuple(j -> map(x -> x[j], ξω), 2)\n        D = ntuple(j -> Elements.spectralderivative(ξ[j]), dim)\n\n        f(ξ1, ξ2, ξ3) = @.( (\n            ξ2 + ξ1 * ξ3 - (ξ1^2 * ξ2^2 * ξ3^2) / 4,\n            ξ3 - ((ξ1 * ξ2 * ξ3 + 1) / 2)^3 + 1,\n            ξ1 + 2 * ((ξ1 + 1) / 2)^6 * ((ξ2 + 1) / 2)^6 * ((ξ3 + 1) / 2)^6,\n        ))\n\n        fx1ξ1(ξ1, ξ2, ξ3) = @.(ξ3 - (ξ1 * ξ2^2 * ξ3^2) / 2)\n        fx1ξ2(ξ1, ξ2, ξ3) = @.(1 - (ξ1^2 * ξ2 * ξ3^2) / 2)\n        fx1ξ3(ξ1, ξ2, ξ3) = @.(ξ1 - (ξ1^2 * ξ2^2 * ξ3) / 2)\n        fx2ξ1(ξ1, ξ2, ξ3) = @.(-(3 * ξ2 * ξ3 * ((ξ1 * ξ2 * ξ3 + 1) / 2)^2) / 2)\n        fx2ξ2(ξ1, ξ2, ξ3) = @.(-(3 * ξ1 * ξ3 * ((ξ1 * ξ2 * ξ3 + 1) / 2)^2) / 2)\n        fx2ξ3(ξ1, ξ2, ξ3) =\n            @.(1 - (3 * ξ1 * ξ2 * ((ξ1 * ξ2 * ξ3 + 1) / 2)^2) / 2)\n        fx3ξ1(ξ1, ξ2, ξ3) =\n            @.(6 * ((ξ1 + 1) / 2)^5 * ((ξ2 + 1) / 2)^6 * ((ξ3 + 1) / 2)^6 + 1)\n        fx3ξ2(ξ1, ξ2, ξ3) =\n            @.(6 * ((ξ1 + 1) / 2)^6 * ((ξ2 + 1) / 2)^5 * ((ξ3 + 1) / 2)^6)\n        fx3ξ3(ξ1, ξ2, ξ3) =\n            @.(6 * ((ξ1 + 1) / 2)^6 * ((ξ2 + 1) / 2)^6 * ((ξ3 + 1) / 2)^5)\n\n        e2c = Array{FT, 3}(undef, dim, 8, 1)\n        e2c[:, :, 1] = [\n            -1 1 -1 1 -1 1 -1 1\n            -1 -1 1 1 -1 -1 1 1\n            -1 -1 -1 -1 1 1 1 1\n        ]\n\n        nelem = size(e2c, 3)\n\n        # Compute exact metrics\n        (x1ξ1, x1ξ2, x1ξ3, x2ξ1, x2ξ2, x2ξ3, x3ξ1, x3ξ2, x3ξ3) = let\n            (vgeo, _) =\n                Grids.computegeometry(e2c, D, ξ, ω, (x...) -> identity(x))\n            vgeo = reshape(\n                vgeo.array,\n                Nq...,\n                # - 1 after fieldcount is to remove the `array` field from the array allocation\n                fieldcount(GeometricFactors.VolumeGeometry) - 1,\n                nelem,\n            )\n            ξ1 = vgeo[:, :, :, _x1, :]\n            ξ2 = vgeo[:, :, :, _x2, :]\n            ξ3 = vgeo[:, :, :, _x3, :]\n            (\n                fx1ξ1(ξ1, ξ2, ξ3),\n                fx1ξ2(ξ1, ξ2, ξ3),\n                fx1ξ3(ξ1, ξ2, ξ3),\n                fx2ξ1(ξ1, ξ2, ξ3),\n                fx2ξ2(ξ1, ξ2, ξ3),\n                fx2ξ3(ξ1, ξ2, ξ3),\n                fx3ξ1(ξ1, ξ2, ξ3),\n                fx3ξ2(ξ1, ξ2, ξ3),\n                fx3ξ3(ξ1, ξ2, ξ3),\n            )\n        end\n        J = (\n            x1ξ1 .* (x2ξ2 .* x3ξ3 - x2ξ3 .* x3ξ2) +\n            x2ξ1 .* (x3ξ2 .* x1ξ3 - x3ξ3 .* x1ξ2) +\n            x3ξ1 .* (x1ξ2 .* x2ξ3 - x1ξ3 .* x2ξ2)\n        )\n\n        ξ1x1 = (x2ξ2 .* x3ξ3 - x2ξ3 .* x3ξ2) ./ J\n        ξ1x2 = (x3ξ2 .* x1ξ3 - x3ξ3 .* x1ξ2) ./ J\n        ξ1x3 = (x1ξ2 .* x2ξ3 - x1ξ3 .* x2ξ2) ./ J\n        ξ2x1 = (x2ξ3 .* x3ξ1 - x2ξ1 .* x3ξ3) ./ J\n        ξ2x2 = (x3ξ3 .* x1ξ1 - x3ξ1 .* x1ξ3) ./ J\n        ξ2x3 = (x1ξ3 .* x2ξ1 - x1ξ1 .* x2ξ3) ./ J\n        ξ3x1 = (x2ξ1 .* x3ξ2 - x2ξ2 .* x3ξ1) ./ J\n        ξ3x2 = (x3ξ1 .* x1ξ2 - x3ξ2 .* x1ξ1) ./ J\n        ξ3x3 = (x1ξ1 .* x2ξ2 - x1ξ2 .* x2ξ1) ./ J\n\n        (vgeo, sgeo, _) = Grids.computegeometry(e2c, D, ξ, ω, f)\n        vgeo = reshape(\n            vgeo.array,\n            Nq...,\n            # - 1 after fieldcount is to remove the `array` field from the array allocation\n            fieldcount(GeometricFactors.VolumeGeometry) - 1,\n            nelem,\n        )\n\n        @test (@view vgeo[:, :, :, _M, :]) ≈\n              J .* reshape(kron(reverse(ω)...), Nq..., 1)\n        @test (@view vgeo[:, :, :, _ξ1x1, :]) ≈ ξ1x1\n        @test (@view vgeo[:, :, :, _ξ1x2, :]) ≈ ξ1x2\n        @test (@view vgeo[:, :, :, _ξ1x3, :]) ≈ ξ1x3\n        @test (@view vgeo[:, :, :, _ξ2x1, :]) ≈ ξ2x1\n        @test (@view vgeo[:, :, :, _ξ2x2, :]) ≈ ξ2x2\n        @test (@view vgeo[:, :, :, _ξ2x3, :]) ≈ ξ2x3\n        @test (@view vgeo[:, :, :, _ξ3x1, :]) ≈ ξ3x1\n        @test (@view vgeo[:, :, :, _ξ3x2, :]) ≈ ξ3x2\n        @test (@view vgeo[:, :, :, _ξ3x3, :]) ≈ ξ3x3\n        n1 = @view sgeo.n1[:, :, :]\n        n2 = @view sgeo.n2[:, :, :]\n        n3 = @view sgeo.n3[:, :, :]\n        sM = @view sgeo.sωJ[:, :, :]\n        for d in 1:dim\n            for f in (2d - 1):(2d)\n                @test all(\n                    hypot.(\n                        n1[1:Nfp[d], f, :],\n                        n2[1:Nfp[d], f, :],\n                        n3[1:Nfp[d], f, :],\n                    ) .≈ 1,\n                )\n            end\n        end\n        d, f = 1, 1\n        Mf = kron(1, ω[3], ω[2])\n        @test [\n            (sM[1:Nfp[d], f, :] .* n1[1:Nfp[d], f, :])[:],\n            (sM[1:Nfp[d], f, :] .* n2[1:Nfp[d], f, :])[:],\n            (sM[1:Nfp[d], f, :] .* n3[1:Nfp[d], f, :])[:],\n        ] ≈ [\n            (-J[1, :, :, :] .* ξ1x1[1, :, :, :])[:] .* Mf,\n            (-J[1, :, :, :] .* ξ1x2[1, :, :, :])[:] .* Mf,\n            (-J[1, :, :, :] .* ξ1x3[1, :, :, :])[:] .* Mf,\n        ]\n        d, f = 1, 2\n        Mf = kron(1, ω[3], ω[2])\n        @test [\n            (sM[1:Nfp[d], f, :] .* n1[1:Nfp[d], f, :])[:],\n            (sM[1:Nfp[d], f, :] .* n2[1:Nfp[d], f, :])[:],\n            (sM[1:Nfp[d], f, :] .* n3[1:Nfp[d], f, :])[:],\n        ] ≈ [\n            (J[Nq[d], :, :, :] .* ξ1x1[Nq[d], :, :, :])[:] .* Mf,\n            (J[Nq[d], :, :, :] .* ξ1x2[Nq[d], :, :, :])[:] .* Mf,\n            (J[Nq[d], :, :, :] .* ξ1x3[Nq[d], :, :, :])[:] .* Mf,\n        ]\n        d, f = 2, 3\n        Mf = kron(1, ω[3], ω[1])\n        @test [\n            (sM[1:Nfp[d], f, :] .* n1[1:Nfp[d], f, :])[:],\n            (sM[1:Nfp[d], f, :] .* n2[1:Nfp[d], f, :])[:],\n            (sM[1:Nfp[d], f, :] .* n3[1:Nfp[d], f, :])[:],\n        ] ≈ [\n            (-J[:, 1, :, :] .* ξ2x1[:, 1, :, :])[:] .* Mf,\n            (-J[:, 1, :, :] .* ξ2x2[:, 1, :, :])[:] .* Mf,\n            (-J[:, 1, :, :] .* ξ2x3[:, 1, :, :])[:] .* Mf,\n        ]\n        d, f = 2, 4\n        Mf = kron(1, ω[3], ω[1])\n        @test [\n            (sM[1:Nfp[d], f, :] .* n1[1:Nfp[d], f, :])[:],\n            (sM[1:Nfp[d], f, :] .* n2[1:Nfp[d], f, :])[:],\n            (sM[1:Nfp[d], f, :] .* n3[1:Nfp[d], f, :])[:],\n        ] ≈ [\n            (J[:, Nq[d], :, :] .* ξ2x1[:, Nq[d], :, :])[:] .* Mf,\n            (J[:, Nq[d], :, :] .* ξ2x2[:, Nq[d], :, :])[:] .* Mf,\n            (J[:, Nq[d], :, :] .* ξ2x3[:, Nq[d], :, :])[:] .* Mf,\n        ]\n        d, f = 3, 5\n        Mf = kron(1, ω[2], ω[1])\n        @test [\n            (sM[1:Nfp[d], f, :] .* n1[1:Nfp[d], f, :])[:],\n            (sM[1:Nfp[d], f, :] .* n2[1:Nfp[d], f, :])[:],\n            (sM[1:Nfp[d], f, :] .* n3[1:Nfp[d], f, :])[:],\n        ] ≈ [\n            (-J[:, :, 1, :] .* ξ3x1[:, :, 1, :])[:] .* Mf,\n            (-J[:, :, 1, :] .* ξ3x2[:, :, 1, :])[:] .* Mf,\n            (-J[:, :, 1, :] .* ξ3x3[:, :, 1, :])[:] .* Mf,\n        ]\n        d, f = 3, 6\n        Mf = kron(1, ω[2], ω[1])\n        @test [\n            (sM[1:Nfp[d], f, :] .* n1[1:Nfp[d], f, :])[:],\n            (sM[1:Nfp[d], f, :] .* n2[1:Nfp[d], f, :])[:],\n            (sM[1:Nfp[d], f, :] .* n3[1:Nfp[d], f, :])[:],\n        ] ≈ [\n            (J[:, :, Nq[d], :] .* ξ3x1[:, :, Nq[d], :])[:] .* Mf,\n            (J[:, :, Nq[d], :] .* ξ3x2[:, :, Nq[d], :])[:] .* Mf,\n            (J[:, :, Nq[d], :] .* ξ3x3[:, :, Nq[d], :])[:] .* Mf,\n        ]\n    end\n    #}}}\n\n    # Constant preserving test\n    #{{{\n    for FT in (Float32, Float64), N in ((5, 5, 5), (3, 4, 5), (4, 4, 5))\n        Nq = N .+ 1\n        Np = prod(Nq)\n        Nfp = div.(Np, Nq)\n\n        dim = length(N)\n        nface = 2dim\n\n        # Create element operators for each polynomial order\n        ξω = ntuple(j -> Elements.lglpoints(FT, N[j]), dim)\n        ξ, ω = ntuple(j -> map(x -> x[j], ξω), 2)\n        D = ntuple(j -> Elements.spectralderivative(ξ[j]), dim)\n\n        f(ξ1, ξ2, ξ3) = @.( (\n            ξ2 + ξ1 * ξ3 - (ξ1^2 * ξ2^2 * ξ3^2) / 4,\n            ξ3 - ((ξ1 * ξ2 * ξ3 + 1) / 2)^3 + 1,\n            ξ1 + ((ξ1 + 1) / 2)^6 * ((ξ2 + 1) / 2)^6 * ((ξ3 + 1) / 2)^6,\n        ))\n\n        e2c = Array{FT, 3}(undef, dim, 8, 1)\n        e2c[:, :, 1] = [\n            -1 1 -1 1 -1 1 -1 1\n            -1 -1 1 1 -1 -1 1 1\n            -1 -1 -1 -1 1 1 1 1\n        ]\n\n        nelem = size(e2c, 3)\n\n        (vgeo, sgeo, _) = Grids.computegeometry(e2c, D, ξ, ω, f)\n        vgeo = reshape(\n            vgeo.array,\n            Nq...,\n            # - 1 after fieldcount is to remove the `array` field from the array allocation\n            fieldcount(GeometricFactors.VolumeGeometry) - 1,\n            nelem,\n        )\n\n        (Cx1, Cx2, Cx3) = (zeros(FT, Nq...), zeros(FT, Nq...), zeros(FT, Nq...))\n\n        J =\n            (@view vgeo[:, :, :, _M, :]) ./\n            reshape(kron(reverse(ω)...), Nq..., 1)\n        ξ1x1 = @view vgeo[:, :, :, _ξ1x1, :]\n        ξ1x2 = @view vgeo[:, :, :, _ξ1x2, :]\n        ξ1x3 = @view vgeo[:, :, :, _ξ1x3, :]\n        ξ2x1 = @view vgeo[:, :, :, _ξ2x1, :]\n        ξ2x2 = @view vgeo[:, :, :, _ξ2x2, :]\n        ξ2x3 = @view vgeo[:, :, :, _ξ2x3, :]\n        ξ3x1 = @view vgeo[:, :, :, _ξ3x1, :]\n        ξ3x2 = @view vgeo[:, :, :, _ξ3x2, :]\n        ξ3x3 = @view vgeo[:, :, :, _ξ3x3, :]\n\n        e = 1\n        for k in 1:Nq[3]\n            for j in 1:Nq[2]\n                Cx1[:, j, k] += D[1] * (J[:, j, k, e] .* ξ1x1[:, j, k, e])\n                Cx2[:, j, k] += D[1] * (J[:, j, k, e] .* ξ1x2[:, j, k, e])\n                Cx3[:, j, k] += D[1] * (J[:, j, k, e] .* ξ1x3[:, j, k, e])\n            end\n        end\n\n        for k in 1:Nq[3]\n            for i in 1:Nq[1]\n                Cx1[i, :, k] += D[2] * (J[i, :, k, e] .* ξ2x1[i, :, k, e])\n                Cx2[i, :, k] += D[2] * (J[i, :, k, e] .* ξ2x2[i, :, k, e])\n                Cx3[i, :, k] += D[2] * (J[i, :, k, e] .* ξ2x3[i, :, k, e])\n            end\n        end\n\n\n        for j in 1:Nq[2]\n            for i in 1:Nq[1]\n                Cx1[i, j, :] += D[3] * (J[i, j, :, e] .* ξ3x1[i, j, :, e])\n                Cx2[i, j, :] += D[3] * (J[i, j, :, e] .* ξ3x2[i, j, :, e])\n                Cx3[i, j, :] += D[3] * (J[i, j, :, e] .* ξ3x3[i, j, :, e])\n            end\n        end\n        @test maximum(abs.(Cx1)) ≤ 300 * eps(FT)\n        @test maximum(abs.(Cx2)) ≤ 300 * eps(FT)\n        @test maximum(abs.(Cx3)) ≤ 300 * eps(FT)\n    end\n    #}}}\n\n    #N = 0 test\n    #{{{\n    let\n        for FT in (Float32, Float64)\n            N = (4, 4, 0)\n            Nq = N .+ 1\n            Np = prod(Nq)\n            Nfp = div.(Np, Nq)\n\n            dim = length(N)\n            nface = 2dim\n\n            # Create element operators for each polynomial order\n            ξω = ntuple(\n                j ->\n                    Nq[j] == 1 ? Elements.glpoints(FT, N[j]) :\n                    Elements.lglpoints(FT, N[j]),\n                dim,\n            )\n            ξ, ω = ntuple(j -> map(x -> x[j], ξω), 2)\n            D = ntuple(j -> Elements.spectralderivative(ξ[j]), dim)\n\n            fx1(ξ1, ξ2, ξ3) = ξ1 + (1 + ξ1)^2 * (1 + ξ2)^2 + ξ3 / 10\n            fx1ξ1(ξ1, ξ2, ξ3) = 1 + 2 * (1 + ξ1) * (1 + ξ2)^2\n            fx1ξ2(ξ1, ξ2, ξ3) = (1 + ξ1)^2 * 2 * (1 + ξ2)\n            fx1ξ3(ξ1, ξ2, ξ3) = 1 / 10\n\n            fx2(ξ1, ξ2, ξ3) = ξ2 - (1 + ξ1)^2 + (2 + ξ3) / 2\n            fx2ξ1(ξ1, ξ2, ξ3) = -2 * (1 + ξ1)\n            fx2ξ2(ξ1, ξ2, ξ3) = 1\n            fx2ξ3(ξ1, ξ2, ξ3) = 1 / 2\n\n            fx3(ξ1, ξ2, ξ3) = ξ3 + (1 + ξ1)^2 * (1 + ξ2)^2 / 10\n            fx3ξ1(ξ1, ξ2, ξ3) = 2 * (1 + ξ1) * (1 + ξ2)^2 / 10\n            fx3ξ2(ξ1, ξ2, ξ3) = (1 + ξ1)^2 * 2 * (1 + ξ2) / 10\n            fx3ξ3(ξ1, ξ2, ξ3) = 1\n\n            e2c = Array{FT, 3}(undef, 3, 8, 1)\n            e2c[:, :, 1] = [\n                -1 +1 -1 +1 -1 +1 -1 +1\n                -1 -1 +1 +1 -1 -1 +1 +1\n                -1 -1 -1 -1 +1 +1 +1 +1\n            ]\n            nelem = size(e2c, 3)\n\n            # Create the metrics\n            (x1, x2, x3, x1ξ1, x1ξ2, x1ξ3, x2ξ1, x2ξ2, x2ξ3, x3ξ1, x3ξ2, x3ξ3) =\n                let\n                    vgeo = VolumeGeometry(FT, Nq, nelem)\n                    Metrics.creategrid!(vgeo, e2c, ξ)\n                    x1 = reshape(vgeo.x1, (Nq..., nelem))\n                    x2 = reshape(vgeo.x2, (Nq..., nelem))\n                    x3 = reshape(vgeo.x3, (Nq..., nelem))\n                    (\n                        fx1.(x1, x2, x3),\n                        fx2.(x1, x2, x3),\n                        fx3.(x1, x2, x3),\n                        fx1ξ1.(x1, x2, x3),\n                        fx1ξ2.(x1, x2, x3),\n                        fx1ξ3.(x1, x2, x3),\n                        fx2ξ1.(x1, x2, x3),\n                        fx2ξ2.(x1, x2, x3),\n                        fx2ξ3.(x1, x2, x3),\n                        fx3ξ1.(x1, x2, x3),\n                        fx3ξ2.(x1, x2, x3),\n                        fx3ξ3.(x1, x2, x3),\n                    )\n                end\n\n            J = @.(\n                x1ξ1 * (x2ξ2 * x3ξ3 - x3ξ2 * x2ξ3) +\n                x2ξ1 * (x3ξ2 * x1ξ3 - x1ξ2 * x3ξ3) +\n                x3ξ1 * (x1ξ2 * x2ξ3 - x2ξ2 * x1ξ3)\n            )\n\n            ξ1x1 = (x2ξ2 .* x3ξ3 - x2ξ3 .* x3ξ2) ./ J\n            ξ1x2 = (x3ξ2 .* x1ξ3 - x3ξ3 .* x1ξ2) ./ J\n            ξ1x3 = (x1ξ2 .* x2ξ3 - x1ξ3 .* x2ξ2) ./ J\n            ξ2x1 = (x2ξ3 .* x3ξ1 - x2ξ1 .* x3ξ3) ./ J\n            ξ2x2 = (x3ξ3 .* x1ξ1 - x3ξ1 .* x1ξ3) ./ J\n            ξ2x3 = (x1ξ3 .* x2ξ1 - x1ξ1 .* x2ξ3) ./ J\n            ξ3x1 = (x2ξ1 .* x3ξ2 - x2ξ2 .* x3ξ1) ./ J\n            ξ3x2 = (x3ξ1 .* x1ξ2 - x3ξ2 .* x1ξ1) ./ J\n            ξ3x3 = (x1ξ1 .* x2ξ2 - x1ξ2 .* x2ξ1) ./ J\n\n            M = J .* reshape(kron(reverse(ω)...), Nq..., 1)\n\n            meshwarp(ξ1, ξ2, ξ3) =\n                (fx1(ξ1, ξ2, ξ3), fx2(ξ1, ξ2, ξ3), fx3(ξ1, ξ2, ξ3))\n            (vgeo, sgeo, _) = Grids.computegeometry(e2c, D, ξ, ω, meshwarp)\n            vgeo = reshape(\n                vgeo.array,\n                Nq...,\n                # - 1 after fieldcount is to remove the `array` field from the array allocation\n                fieldcount(GeometricFactors.VolumeGeometry) - 1,\n                nelem,\n            )\n\n            @test x1 ≈ vgeo[:, :, :, _x1, :]\n            @test x2 ≈ vgeo[:, :, :, _x2, :]\n            @test x3 ≈ vgeo[:, :, :, _x3, :]\n\n            @test M ≈ vgeo[:, :, :, _M, :]\n\n            @test (@view vgeo[:, :, :, _ξ1x1, :]) ≈ ξ1x1\n            @test (@view vgeo[:, :, :, _ξ1x2, :]) ≈ ξ1x2\n            @test (@view vgeo[:, :, :, _ξ1x3, :]) ≈ ξ1x3\n\n            @test (@view vgeo[:, :, :, _ξ2x1, :]) ≈ ξ2x1\n            @test (@view vgeo[:, :, :, _ξ2x2, :]) ≈ ξ2x2\n            @test (@view vgeo[:, :, :, _ξ2x3, :]) ≈ ξ2x3\n\n            @test (@view vgeo[:, :, :, _ξ3x1, :]) ≈ ξ3x1\n            @test (@view vgeo[:, :, :, _ξ3x2, :]) ≈ ξ3x2\n            @test (@view vgeo[:, :, :, _ξ3x3, :]) ≈ ξ3x3\n\n            # check the normals?\n            sM = @view sgeo.sωJ[:, :, :]\n            n1 = @view sgeo.n1[:, :, :]\n            n2 = @view sgeo.n2[:, :, :]\n            n3 = @view sgeo.n3[:, :, :]\n            @test all(\n                hypot.(\n                    n1[1:Nfp[1], 1:2, :],\n                    n2[1:Nfp[1], 1:2, :],\n                    n3[1:Nfp[1], 1:2, :],\n                ) .≈ 1,\n            )\n            @test all(\n                hypot.(\n                    n1[1:Nfp[2], 3:4, :],\n                    n2[1:Nfp[2], 3:4, :],\n                    n3[1:Nfp[2], 3:4, :],\n                ) .≈ 1,\n            )\n            @test all(\n                hypot.(\n                    n1[1:Nfp[3], 5:6, :],\n                    n2[1:Nfp[3], 5:6, :],\n                    n3[1:Nfp[3], 5:6, :],\n                ) .≈ 1,\n            )\n\n            d, f = 1, 1\n            Mf = kron(1, ω[3], ω[2])\n            @test [\n                (sM[1:Nfp[d], f, :] .* n1[1:Nfp[d], f, :])[:],\n                (sM[1:Nfp[d], f, :] .* n2[1:Nfp[d], f, :])[:],\n                (sM[1:Nfp[d], f, :] .* n3[1:Nfp[d], f, :])[:],\n            ] ≈ [\n                (-J[1, :, :, :] .* ξ1x1[1, :, :, :])[:] .* Mf,\n                (-J[1, :, :, :] .* ξ1x2[1, :, :, :])[:] .* Mf,\n                (-J[1, :, :, :] .* ξ1x3[1, :, :, :])[:] .* Mf,\n            ]\n            d, f = 1, 2\n            Mf = kron(1, ω[3], ω[2])\n            @test [\n                (sM[1:Nfp[d], f, :] .* n1[1:Nfp[d], f, :])[:],\n                (sM[1:Nfp[d], f, :] .* n2[1:Nfp[d], f, :])[:],\n                (sM[1:Nfp[d], f, :] .* n3[1:Nfp[d], f, :])[:],\n            ] ≈ [\n                (J[Nq[d], :, :, :] .* ξ1x1[Nq[d], :, :, :])[:] .* Mf,\n                (J[Nq[d], :, :, :] .* ξ1x2[Nq[d], :, :, :])[:] .* Mf,\n                (J[Nq[d], :, :, :] .* ξ1x3[Nq[d], :, :, :])[:] .* Mf,\n            ]\n            d, f = 2, 3\n            Mf = kron(1, ω[3], ω[1])\n            @test [\n                (sM[1:Nfp[d], f, :] .* n1[1:Nfp[d], f, :])[:],\n                (sM[1:Nfp[d], f, :] .* n2[1:Nfp[d], f, :])[:],\n                (sM[1:Nfp[d], f, :] .* n3[1:Nfp[d], f, :])[:],\n            ] ≈ [\n                (-J[:, 1, :, :] .* ξ2x1[:, 1, :, :])[:] .* Mf,\n                (-J[:, 1, :, :] .* ξ2x2[:, 1, :, :])[:] .* Mf,\n                (-J[:, 1, :, :] .* ξ2x3[:, 1, :, :])[:] .* Mf,\n            ]\n            d, f = 2, 4\n            Mf = kron(1, ω[3], ω[1])\n            @test [\n                (sM[1:Nfp[d], f, :] .* n1[1:Nfp[d], f, :])[:],\n                (sM[1:Nfp[d], f, :] .* n2[1:Nfp[d], f, :])[:],\n                (sM[1:Nfp[d], f, :] .* n3[1:Nfp[d], f, :])[:],\n            ] ≈ [\n                (J[:, Nq[d], :, :] .* ξ2x1[:, Nq[d], :, :])[:] .* Mf,\n                (J[:, Nq[d], :, :] .* ξ2x2[:, Nq[d], :, :])[:] .* Mf,\n                (J[:, Nq[d], :, :] .* ξ2x3[:, Nq[d], :, :])[:] .* Mf,\n            ]\n\n            # for these faces we need the N = 1 metrics\n            (x1ξ1, x1ξ2, x1ξ3, x2ξ1, x2ξ2, x2ξ3, x3ξ1, x3ξ2, x3ξ3) = let\n                @assert Nq[1] != 1 && Nq[2] != 1 && Nq[3] == 1\n                Nq_N1 = max.(2, Nq)\n                vgeo_N1 = VolumeGeometry(FT, Nq_N1, nelem)\n                Metrics.creategrid!(\n                    vgeo_N1,\n                    e2c,\n                    (ξ[1], ξ[2], Elements.lglpoints(FT, 1)[1]),\n                )\n                x1 = reshape(vgeo_N1.x1, (Nq_N1..., nelem))\n                x2 = reshape(vgeo_N1.x2, (Nq_N1..., nelem))\n                x3 = reshape(vgeo_N1.x3, (Nq_N1..., nelem))\n                (\n                    fx1ξ1.(x1, x2, x3),\n                    fx1ξ2.(x1, x2, x3),\n                    fx1ξ3.(x1, x2, x3),\n                    fx2ξ1.(x1, x2, x3),\n                    fx2ξ2.(x1, x2, x3),\n                    fx2ξ3.(x1, x2, x3),\n                    fx3ξ1.(x1, x2, x3),\n                    fx3ξ2.(x1, x2, x3),\n                    fx3ξ3.(x1, x2, x3),\n                )\n            end\n            J = @.(\n                x1ξ1 * (x2ξ2 * x3ξ3 - x3ξ2 * x2ξ3) +\n                x2ξ1 * (x3ξ2 * x1ξ3 - x1ξ2 * x3ξ3) +\n                x3ξ1 * (x1ξ2 * x2ξ3 - x2ξ2 * x1ξ3)\n            )\n            ξ3x1 = (x2ξ1 .* x3ξ2 - x2ξ2 .* x3ξ1) ./ J\n            ξ3x2 = (x3ξ1 .* x1ξ2 - x3ξ2 .* x1ξ1) ./ J\n            ξ3x3 = (x1ξ1 .* x2ξ2 - x1ξ2 .* x2ξ1) ./ J\n\n            d, f = 3, 5\n            Mf = kron(1, ω[2], ω[1])\n            @test [\n                (sM[1:Nfp[d], f, :] .* n1[1:Nfp[d], f, :])[:],\n                (sM[1:Nfp[d], f, :] .* n2[1:Nfp[d], f, :])[:],\n                (sM[1:Nfp[d], f, :] .* n3[1:Nfp[d], f, :])[:],\n            ] ≈ [\n                (-J[:, :, 1, :] .* ξ3x1[:, :, 1, :])[:] .* Mf,\n                (-J[:, :, 1, :] .* ξ3x2[:, :, 1, :])[:] .* Mf,\n                (-J[:, :, 1, :] .* ξ3x3[:, :, 1, :])[:] .* Mf,\n            ]\n\n            d, f = 3, 6\n            Mf = kron(1, ω[2], ω[1])\n            @test [\n                (sM[1:Nfp[d], f, :] .* n1[1:Nfp[d], f, :])[:],\n                (sM[1:Nfp[d], f, :] .* n2[1:Nfp[d], f, :])[:],\n                (sM[1:Nfp[d], f, :] .* n3[1:Nfp[d], f, :])[:],\n            ] ≈ [\n                (J[:, :, 2, :] .* ξ3x1[:, :, 2, :])[:] .* Mf,\n                (J[:, :, 2, :] .* ξ3x2[:, :, 2, :])[:] .* Mf,\n                (J[:, :, 2, :] .* ξ3x3[:, :, 2, :])[:] .* Mf,\n            ]\n        end\n    end\n    #}}}\n\n    # Constant preserving test for N = 0\n    #{{{\n    let\n        for FT in (Float64, Float32),\n            N in ((4, 4, 0), (0, 0, 2), (0, 3, 4), (2, 0, 3))\n\n            Nq = N .+ 1\n\n            Np = prod(Nq)\n            Nfp = div.(Np, Nq)\n\n            dim = length(N)\n            nface = 2dim\n\n            # Create element operators for each polynomial order\n            ξω = ntuple(\n                j ->\n                    Nq[j] == 1 ? Elements.glpoints(FT, N[j]) :\n                    Elements.lglpoints(FT, N[j]),\n                dim,\n            )\n            ξ, ω = ntuple(j -> map(x -> x[j], ξω), 2)\n            D = ntuple(j -> Elements.spectralderivative(ξ[j]), dim)\n\n            rng = MersenneTwister(777)\n            fx1(ξ1, ξ2, ξ3) = ξ1 + (ξ1 * ξ2 * ξ3 * rand(rng) + rand(rng)) / 10\n            fx2(ξ1, ξ2, ξ3) = ξ2 + (ξ1 * ξ2 * ξ3 * rand(rng) + rand(rng)) / 10\n            fx3(ξ1, ξ2, ξ3) = ξ3 + (ξ1 * ξ2 * ξ3 * rand(rng) + rand(rng)) / 10\n\n            e2c = Array{FT, 3}(undef, 3, 8, 1)\n            e2c[:, :, 1] = [\n                -1 +1 -1 +1 -1 +1 -1 +1\n                -1 -1 +1 +1 -1 -1 +1 +1\n                -1 -1 -1 -1 +1 +1 +1 +1\n            ]\n            nelem = size(e2c, 3)\n\n            meshwarp(ξ1, ξ2, ξ3) =\n                (fx1(ξ1, ξ2, ξ3), fx2(ξ1, ξ2, ξ3), fx2(ξ1, ξ2, ξ3))\n            (vgeo, sgeo, _) = Grids.computegeometry(e2c, D, ξ, ω, meshwarp)\n\n            vgeo = reshape(\n                vgeo.array,\n                prod(Nq),\n                # - 1 after fieldcount is to remove the `array` field from the array allocation\n                fieldcount(GeometricFactors.VolumeGeometry) - 1,\n                nelem,\n            )\n\n            M = vgeo[:, _M, :]\n            ξ1x1 = vgeo[:, _ξ1x1, :]\n            ξ2x1 = vgeo[:, _ξ2x1, :]\n            ξ3x1 = vgeo[:, _ξ3x1, :]\n            ξ1x2 = vgeo[:, _ξ1x2, :]\n            ξ2x2 = vgeo[:, _ξ2x2, :]\n            ξ3x2 = vgeo[:, _ξ3x2, :]\n            ξ1x3 = vgeo[:, _ξ1x3, :]\n            ξ2x3 = vgeo[:, _ξ2x3, :]\n            ξ3x3 = vgeo[:, _ξ3x3, :]\n\n            M = vgeo[:, _M, :]\n            ξ1x1 = vgeo[:, _ξ1x1, :]\n            ξ2x1 = vgeo[:, _ξ2x1, :]\n            ξ1x2 = vgeo[:, _ξ1x2, :]\n            ξ2x2 = vgeo[:, _ξ2x2, :]\n\n            I1 = Matrix(I, Nq[1], Nq[1])\n            I2 = Matrix(I, Nq[2], Nq[2])\n            I3 = Matrix(I, Nq[3], Nq[3])\n            D1 = kron(I3, I2, D[1])\n            D2 = kron(I3, D[2], I1)\n            D3 = kron(D[3], I2, I1)\n\n            # Face interpolation operators\n            L = (\n                kron(I3, I2, I1[1, :]'),\n                kron(I3, I2, I1[Nq[1], :]'),\n                kron(I3, I2[1, :]', I1),\n                kron(I3, I2[Nq[2], :]', I1),\n                kron(I3[1, :]', I2, I1),\n                kron(I3[Nq[3], :]', I2, I1),\n            )\n            sM = ntuple(f -> sgeo.sωJ[1:Nfp[cld(f, 2)], f, :], nface)\n            n1 = ntuple(f -> sgeo.n1[1:Nfp[cld(f, 2)], f, :], nface)\n            n2 = ntuple(f -> sgeo.n2[1:Nfp[cld(f, 2)], f, :], nface)\n            n3 = ntuple(f -> sgeo.n3[1:Nfp[cld(f, 2)], f, :], nface)\n\n            # If constant preserving then:\n            #   \\sum_{j} = D' * M * ξjxk = \\sum_{f} L_f' * sM_f * n1_f\n            @test D1' * (M .* ξ1x1) + D2' * (M .* ξ2x1) + D3' * (M .* ξ3x1) ≈\n                  mapreduce((L, sM, n1) -> L' * (sM .* n1), +, L, sM, n1)\n            @test D1' * (M .* ξ1x2) + D2' * (M .* ξ2x2) + D3' * (M .* ξ3x2) ≈\n                  mapreduce((L, sM, n2) -> L' * (sM .* n2), +, L, sM, n2)\n            @test D1' * (M .* ξ1x3) + D2' * (M .* ξ2x3) + D3' * (M .* ξ3x3) ≈\n                  mapreduce((L, sM, n3) -> L' * (sM .* n3), +, L, sM, n3)\n        end\n    end\n    #}}}\n\n    # Constant preserving test with all N = 0\n    #{{{\n    let\n        for FT in (Float64, Float32)\n            N = (0, 0, 0)\n            Nq = N .+ 1\n\n            Np = prod(Nq)\n            Nfp = div.(Np, Nq)\n\n            dim = length(N)\n            nface = 2dim\n\n            # Create element operators for each polynomial order\n            ξω = ntuple(\n                j ->\n                    Nq[j] == 1 ? Elements.glpoints(FT, N[j]) :\n                    Elements.lglpoints(FT, N[j]),\n                dim,\n            )\n            ξ, ω = ntuple(j -> map(x -> x[j], ξω), 2)\n            D = ntuple(j -> Elements.spectralderivative(ξ[j]), dim)\n\n            rng = MersenneTwister(777)\n            fx1(ξ1, ξ2, ξ3) = ξ1 + (ξ1 * ξ2 * ξ3 * rand(rng) + rand(rng)) / 10\n            fx2(ξ1, ξ2, ξ3) = ξ2 + (ξ1 * ξ2 * ξ3 * rand(rng) + rand(rng)) / 10\n            fx3(ξ1, ξ2, ξ3) = ξ3 + (ξ1 * ξ2 * ξ3 * rand(rng) + rand(rng)) / 10\n\n            e2c = Array{FT, 3}(undef, 3, 8, 1)\n            e2c[:, :, 1] = [\n                -1 +1 -1 +1 -1 +1 -1 +1\n                -1 -1 +1 +1 -1 -1 +1 +1\n                -1 -1 -1 -1 +1 +1 +1 +1\n            ]\n            nelem = size(e2c, 3)\n\n            meshwarp(ξ1, ξ2, ξ3) =\n                (fx1(ξ1, ξ2, ξ3), fx2(ξ1, ξ2, ξ3), fx2(ξ1, ξ2, ξ3))\n            (vgeo, sgeo, _) = Grids.computegeometry(e2c, D, ξ, ω, meshwarp)\n\n            vgeo = reshape(\n                vgeo.array,\n                prod(Nq),\n                # - 1 after fieldcount is to remove the `array` field from the array allocation\n                fieldcount(GeometricFactors.VolumeGeometry) - 1,\n                nelem,\n            )\n\n            M = vgeo[:, _M, :]\n            ξ1x1 = vgeo[:, _ξ1x1, :]\n            ξ2x1 = vgeo[:, _ξ2x1, :]\n            ξ3x1 = vgeo[:, _ξ3x1, :]\n            ξ1x2 = vgeo[:, _ξ1x2, :]\n            ξ2x2 = vgeo[:, _ξ2x2, :]\n            ξ3x2 = vgeo[:, _ξ3x2, :]\n            ξ1x3 = vgeo[:, _ξ1x3, :]\n            ξ2x3 = vgeo[:, _ξ2x3, :]\n            ξ3x3 = vgeo[:, _ξ3x3, :]\n\n            M = vgeo[:, _M, :]\n            ξ1x1 = vgeo[:, _ξ1x1, :]\n            ξ2x1 = vgeo[:, _ξ2x1, :]\n            ξ1x2 = vgeo[:, _ξ1x2, :]\n            ξ2x2 = vgeo[:, _ξ2x2, :]\n\n            I1 = Matrix(I, Nq[1], Nq[1])\n            I2 = Matrix(I, Nq[2], Nq[2])\n            I3 = Matrix(I, Nq[3], Nq[3])\n            D1 = kron(I3, I2, D[1])\n            D2 = kron(I3, D[2], I1)\n            D3 = kron(D[3], I2, I1)\n\n            # Face interpolation operators\n            L = (\n                kron(I3, I2, I1[1, :]'),\n                kron(I3, I2, I1[Nq[1], :]'),\n                kron(I3, I2[1, :]', I1),\n                kron(I3, I2[Nq[2], :]', I1),\n                kron(I3[1, :]', I2, I1),\n                kron(I3[Nq[3], :]', I2, I1),\n            )\n            sM = ntuple(f -> sgeo.sωJ[1:Nfp[cld(f, 2)], f, :], nface)\n            n1 = ntuple(f -> sgeo.n1[1:Nfp[cld(f, 2)], f, :], nface)\n            n2 = ntuple(f -> sgeo.n2[1:Nfp[cld(f, 2)], f, :], nface)\n            n3 = ntuple(f -> sgeo.n3[1:Nfp[cld(f, 2)], f, :], nface)\n\n            # If constant preserving then \\sum_{f} L_f' * sM_f * n1_f ≈ 0\n            @test abs(mapreduce(\n                (L, sM, n1) -> L' * (sM .* n1),\n                +,\n                L,\n                sM,\n                n1,\n            )[1]) < 10 * eps(FT)\n            @test abs(mapreduce(\n                (L, sM, n2) -> L' * (sM .* n2),\n                +,\n                L,\n                sM,\n                n2,\n            )[1]) < 10 * eps(FT)\n            @test abs(mapreduce(\n                (L, sM, n3) -> L' * (sM .* n3),\n                +,\n                L,\n                sM,\n                n3,\n            )[1]) < 10 * eps(FT)\n        end\n    end\n    #}}}\nend\n"
  },
  {
    "path": "test/Numerics/Mesh/filter.jl",
    "content": "using Test\nimport ClimateMachine\nusing ClimateMachine.VariableTemplates: @vars, Vars\nusing ClimateMachine.Mesh.Grids:\n    EveryDirection, HorizontalDirection, VerticalDirection\nusing ClimateMachine.MPIStateArrays: weightedsum\nusing ClimateMachine.BalanceLaws\n\nimport GaussQuadrature\nusing MPI\nusing LinearAlgebra\n\nClimateMachine.init()\n\n@testset \"Exponential and Cutoff filter matrix\" begin\n    let\n        # Values computed with:\n        #   https://github.com/tcew/nodal-dg/blob/master/Codes1.1/Codes1D/Filter1D.m\n        #! format: off\n        W = [0x3fe98f3cd0d725e8  0x3fddfd863c6c9a44  0xbfe111110d0fd334  0x3fddbe357bce0b5c  0xbfc970267f929618\n             0x3fb608a150f6f927  0x3fe99528b1a1cd8d  0x3fcd41d41f8bae45  0xbfc987d5fabab8d5  0x3fb5da1cd858af87\n             0xbfb333332eb1cd92  0x3fc666666826f178  0x3fe999999798faaa  0x3fc666666826f176  0xbfb333332eb1cd94\n             0x3fb5da1cd858af84  0xbfc987d5fabab8d4  0x3fcd41d41f8bae46  0x3fe99528b1a1cd8e  0x3fb608a150f6f924\n             0xbfc970267f929618  0x3fddbe357bce0b5c  0xbfe111110d0fd333  0x3fddfd863c6c9a44  0x3fe98f3cd0d725e8]\n        #! format: on\n        W = reinterpret.(Float64, W)\n\n        N = size(W, 1) - 1\n\n        topology = ClimateMachine.Mesh.Topologies.BrickTopology(\n            MPI.COMM_SELF,\n            -1.0:2.0:1.0,\n        )\n\n        grid = ClimateMachine.Mesh.Grids.DiscontinuousSpectralElementGrid(\n            topology;\n            polynomialorder = N,\n            FloatType = Float64,\n            DeviceArray = Array,\n        )\n\n        filter = ClimateMachine.Mesh.Filters.ExponentialFilter(grid, 0, 32)\n        nf = length(filter.filter_matrices)\n        @test all(ntuple(i -> filter.filter_matrices[i] ≈ W, nf))\n    end\n\n    let\n        # Values computed with:\n        #   https://github.com/tcew/nodal-dg/blob/master/Codes1.1/Codes1D/Filter1D.m\n        #! format: off\n        W = [0x3fd822e5f54ecb62   0x3fedd204a0f08ef8   0xbfc7d3aa58fd6968   0xbfbf74682ac4d276\n             0x3fc7db36e726d8c1   0x3fe59d16feee478b   0x3fc6745bfbb91e20   0xbfa30fbb7a645448\n             0xbfa30fbb7a645455   0x3fc6745bfbb91e26   0x3fe59d16feee478a   0x3fc7db36e726d8c4\n             0xbfbf74682ac4d280   0xbfc7d3aa58fd6962   0x3fedd204a0f08ef7   0x3fd822e5f54ecb62]\n        #! format: on\n        W = reinterpret.(Float64, W)\n\n        N = size(W, 1) - 1\n\n        topology = ClimateMachine.Mesh.Topologies.BrickTopology(\n            MPI.COMM_SELF,\n            -1.0:2.0:1.0,\n        )\n        grid = ClimateMachine.Mesh.Grids.DiscontinuousSpectralElementGrid(\n            topology;\n            polynomialorder = N,\n            FloatType = Float64,\n            DeviceArray = Array,\n        )\n\n        filter = ClimateMachine.Mesh.Filters.ExponentialFilter(grid, 1, 4)\n        nf = length(filter.filter_matrices)\n        @test all(ntuple(i -> filter.filter_matrices[i] ≈ W, nf))\n    end\n\n    let\n        T = Float64\n        N = (5, 3)\n        Nc = (4, 2)\n\n        topology = ClimateMachine.Mesh.Topologies.BrickTopology(\n            MPI.COMM_SELF,\n            -1.0:2.0:1.0,\n        )\n        grid = ClimateMachine.Mesh.Grids.DiscontinuousSpectralElementGrid(\n            topology;\n            polynomialorder = N,\n            FloatType = T,\n            DeviceArray = Array,\n        )\n\n        ξ = ClimateMachine.Mesh.Grids.referencepoints(grid)\n        ξ1 = ξ[1]\n        ξ2 = ξ[2]\n        a1, b1 = GaussQuadrature.legendre_coefs(T, N[1])\n        a2, b2 = GaussQuadrature.legendre_coefs(T, N[2])\n        V1 = GaussQuadrature.orthonormal_poly(ξ1, a1, b1)\n        V2 = GaussQuadrature.orthonormal_poly(ξ2, a2, b2)\n\n        Σ1 = ones(T, N[1] + 1)\n        Σ2 = ones(T, N[2] + 1)\n        Σ1[(Nc[1]:N[1]) .+ 1] .= 0\n        Σ2[(Nc[2]:N[2]) .+ 1] .= 0\n\n        W1 = V1 * Diagonal(Σ1) / V1\n        W2 = V2 * Diagonal(Σ2) / V2\n\n        filter = ClimateMachine.Mesh.Filters.CutoffFilter(grid, Nc)\n        @test filter.filter_matrices[1] ≈ W1\n        @test filter.filter_matrices[2] ≈ W2\n    end\n\n    let\n        T = Float64\n        N = (5, 3)\n        Nc = (4, 2)\n\n        topology = ClimateMachine.Mesh.Topologies.BrickTopology(\n            MPI.COMM_SELF,\n            -1.0:2.0:1.0,\n        )\n        grid = ClimateMachine.Mesh.Grids.DiscontinuousSpectralElementGrid(\n            topology;\n            polynomialorder = N,\n            FloatType = T,\n            DeviceArray = Array,\n        )\n\n        ξ = ClimateMachine.Mesh.Grids.referencepoints(grid)\n        ξ1 = ξ[1]\n        ξ2 = ξ[2]\n        a1, b1 = GaussQuadrature.legendre_coefs(T, N[1])\n        a2, b2 = GaussQuadrature.legendre_coefs(T, N[2])\n        V1 = GaussQuadrature.orthonormal_poly(ξ1, a1, b1)\n        V2 = GaussQuadrature.orthonormal_poly(ξ2, a2, b2)\n\n        Σ1 = ones(T, N[1] + 1)\n        Σ2 = ones(T, N[2] + 1)\n        Σ1[(Nc[1]:N[1]) .+ 1] .= 0\n        Σ2[(Nc[2]:N[2]) .+ 1] .= 0\n\n        W1 = V1 * Diagonal(Σ1) / V1\n        W2 = V2 * Diagonal(Σ2) / V2\n\n        filter =\n            ClimateMachine.Mesh.Filters.MassPreservingCutoffFilter(grid, Nc)\n        @test filter.filter_matrices[1] ≈ W1\n        @test filter.filter_matrices[2] ≈ W2\n    end\n\nend\n\nstruct FilterTestModel{N} <: ClimateMachine.BalanceLaws.BalanceLaw end\nClimateMachine.BalanceLaws.vars_state(::FilterTestModel, ::Auxiliary, FT) =\n    @vars()\nClimateMachine.BalanceLaws.init_state_auxiliary!(::FilterTestModel, _...) =\n    nothing\n\n# Legendre Polynomials\nl0(r) = 1\nl1(r) = r\nl2(r) = (3 * r^2 - 1) / 2\nl3(r) = (5 * r^3 - 3r) / 2\n\nlow(x, y, z) = l0(x) * l0(y) + 4 * l1(x) * l1(y) + 5 * l1(z) + 6 * l1(z) * l1(x)\n\nhigh(x, y, z) = l2(x) * l3(y) + l3(x) + l2(y) + l3(z) * l1(y)\n\nfiltered(::EveryDirection, dim, x, y, z) = high(x, y, z)\nfiltered(::VerticalDirection, dim, x, y, z) =\n    (dim == 2) ? l2(x) * l3(y) + l2(y) : l3(z) * l1(y)\nfiltered(::HorizontalDirection, dim, x, y, z) =\n    (dim == 2) ? l2(x) * l3(y) + l3(x) : l2(x) * l3(y) + l3(x) + l2(y)\n\nClimateMachine.BalanceLaws.vars_state(::FilterTestModel{4}, ::Prognostic, FT) =\n    @vars(q1::FT, q2::FT, q3::FT, q4::FT)\nfunction ClimateMachine.BalanceLaws.init_state_prognostic!(\n    ::FilterTestModel{4},\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    filter_direction,\n    dim,\n)\n    (x, y, z) = localgeo.coord\n    state.q1 = low(x, y, z) + high(x, y, z)\n    state.q2 = low(x, y, z) + high(x, y, z)\n    state.q3 = low(x, y, z) + high(x, y, z)\n    state.q4 = low(x, y, z) + high(x, y, z)\n\n    if !isnothing(filter_direction)\n        state.q1 -= filtered(filter_direction, dim, x, y, z)\n        state.q3 -= filtered(filter_direction, dim, x, y, z)\n    end\nend\n\n@testset \"Exponential and Cutoff filter application\" begin\n    N = 3\n    Ne = (1, 1, 1)\n\n    @testset for FT in (Float64, Float32)\n        @testset for dim in 2:3\n            @testset for direction in (\n                EveryDirection,\n                HorizontalDirection,\n                VerticalDirection,\n            )\n                brickrange = ntuple(\n                    j -> range(FT(-1); length = Ne[j] + 1, stop = 1),\n                    dim,\n                )\n                topl = ClimateMachine.Mesh.Topologies.BrickTopology(\n                    MPI.COMM_WORLD,\n                    brickrange,\n                    periodicity = ntuple(j -> true, dim),\n                )\n\n                grid =\n                    ClimateMachine.Mesh.Grids.DiscontinuousSpectralElementGrid(\n                        topl,\n                        FloatType = FT,\n                        DeviceArray = ClimateMachine.array_type(),\n                        polynomialorder = N,\n                    )\n\n                filter = ClimateMachine.Mesh.Filters.CutoffFilter(grid, 2)\n\n                model = FilterTestModel{4}()\n                dg = ClimateMachine.DGMethods.DGModel(\n                    model,\n                    grid,\n                    nothing,\n                    nothing,\n                    nothing;\n                    state_gradient_flux = nothing,\n                )\n\n                @testset for target in ((1, 3), (:q1, :q3))\n                    Q = ClimateMachine.DGMethods.init_ode_state(\n                        dg,\n                        nothing,\n                        dim,\n                    )\n                    ClimateMachine.Mesh.Filters.apply!(\n                        Q,\n                        target,\n                        grid,\n                        filter,\n                        direction = direction(),\n                    )\n                    P = ClimateMachine.DGMethods.init_ode_state(\n                        dg,\n                        direction(),\n                        dim,\n                    )\n                    @test Array(Q.data) ≈ Array(P.data)\n                end\n            end\n        end\n    end\nend\n\n@testset \"Mass Preserving Cutoff filter application\" begin\n    N = 3\n    Ne = (1, 1, 1)\n\n    @testset for FT in (Float64, Float32)\n        @testset for dim in 2:3\n            @testset for direction in (\n                EveryDirection,\n                HorizontalDirection,\n                VerticalDirection,\n            )\n                brickrange = ntuple(\n                    j -> range(FT(-1); length = Ne[j] + 1, stop = 1),\n                    dim,\n                )\n                topl = ClimateMachine.Mesh.Topologies.BrickTopology(\n                    MPI.COMM_WORLD,\n                    brickrange,\n                    periodicity = ntuple(j -> true, dim),\n                )\n\n                grid =\n                    ClimateMachine.Mesh.Grids.DiscontinuousSpectralElementGrid(\n                        topl,\n                        FloatType = FT,\n                        DeviceArray = ClimateMachine.array_type(),\n                        polynomialorder = N,\n                    )\n\n                filter = ClimateMachine.Mesh.Filters.MassPreservingCutoffFilter(\n                    grid,\n                    2,\n                )\n\n                model = FilterTestModel{4}()\n                dg = ClimateMachine.DGMethods.DGModel(\n                    model,\n                    grid,\n                    nothing,\n                    nothing,\n                    nothing;\n                    state_gradient_flux = nothing,\n                )\n\n                @testset for target in ((1, 3), (:q1, :q3))\n                    Q = ClimateMachine.DGMethods.init_ode_state(\n                        dg,\n                        nothing,\n                        dim,\n                    )\n                    ClimateMachine.Mesh.Filters.apply!(\n                        Q,\n                        target,\n                        grid,\n                        filter,\n                        direction = direction(),\n                    )\n                    P = ClimateMachine.DGMethods.init_ode_state(\n                        dg,\n                        direction(),\n                        dim,\n                    )\n                    @test Array(Q.data) ≈ Array(P.data)\n                end\n            end\n        end\n    end\nend\n\nClimateMachine.BalanceLaws.vars_state(\n    ::FilterTestModel{1},\n    ::Prognostic,\n    FT,\n) where {N} = @vars(q::FT)\nfunction ClimateMachine.BalanceLaws.init_state_prognostic!(\n    ::FilterTestModel{1},\n    state::Vars,\n    aux::Vars,\n    localgeo,\n)\n    (x, y, z) = localgeo.coord\n    state.q = abs(x) - 0.1\nend\n\n@testset \"TMAR filter application\" begin\n\n    N = 4\n    Ne = (2, 2, 2)\n\n    @testset for FT in (Float64, Float32)\n        @testset for dim in 2:3\n            brickrange =\n                ntuple(j -> range(FT(-1); length = Ne[j] + 1, stop = 1), dim)\n            topl = ClimateMachine.Mesh.Topologies.BrickTopology(\n                MPI.COMM_WORLD,\n                brickrange,\n                periodicity = ntuple(j -> true, dim),\n            )\n\n            grid = ClimateMachine.Mesh.Grids.DiscontinuousSpectralElementGrid(\n                topl,\n                FloatType = FT,\n                DeviceArray = ClimateMachine.array_type(),\n                polynomialorder = N,\n            )\n\n            model = FilterTestModel{1}()\n            dg = ClimateMachine.DGMethods.DGModel(\n                model,\n                grid,\n                nothing,\n                nothing,\n                nothing;\n                state_gradient_flux = nothing,\n            )\n\n            @testset for target in ((1,), (:q,), :)\n                Q = ClimateMachine.DGMethods.init_ode_state(dg)\n\n                initialsumQ = weightedsum(Q)\n                @test minimum(Q.realdata) < 0\n\n                ClimateMachine.Mesh.Filters.apply!(\n                    Q,\n                    target,\n                    grid,\n                    ClimateMachine.Mesh.Filters.TMARFilter(),\n                )\n\n                sumQ = weightedsum(Q)\n\n                @test minimum(Q.realdata) >= 0\n                @test isapprox(initialsumQ, sumQ; rtol = 10 * eps(FT))\n            end\n        end\n    end\nend\n\nfunction cubedshellwarp(a, b, c, R = max(abs(a), abs(b), abs(c)))\n\n    function f(sR, ξ, η)\n        X, Y = tan(π * ξ / 4), tan(π * η / 4)\n        x1 = sR / sqrt(X^2 + Y^2 + 1)\n        x2, x3 = X * x1, Y * x1\n        x1, x2, x3\n    end\n\n    fdim = argmax(abs.((a, b, c)))\n    if fdim == 1 && a < 0\n        # (-R, *, *) : Face I from Ronchi, Iacono, Paolucci (1996)\n        x1, x2, x3 = f(-R, b / a, c / a)\n    elseif fdim == 2 && b < 0\n        # ( *,-R, *) : Face II from Ronchi, Iacono, Paolucci (1996)\n        x2, x1, x3 = f(-R, a / b, c / b)\n    elseif fdim == 1 && a > 0\n        # ( R, *, *) : Face III from Ronchi, Iacono, Paolucci (1996)\n        x1, x2, x3 = f(R, b / a, c / a)\n    elseif fdim == 2 && b > 0\n        # ( *, R, *) : Face IV from Ronchi, Iacono, Paolucci (1996)\n        x2, x1, x3 = f(R, a / b, c / b)\n    elseif fdim == 3 && c > 0\n        # ( *, *, R) : Face V from Ronchi, Iacono, Paolucci (1996)\n        x3, x2, x1 = f(R, b / c, a / c)\n    elseif fdim == 3 && c < 0\n        # ( *, *,-R) : Face VI from Ronchi, Iacono, Paolucci (1996)\n        x3, x2, x1 = f(-R, b / c, a / c)\n    else\n        error(\"invalid case for cubedshellwarp: $a, $b, $c\")\n    end\n\n    return x1, x2, x3\nend\n\n@testset \"Mass Preserving Cutoff Filter Conservation Test\" begin\n    N = 3\n    Ne = (1, 1, 1)\n    dim = 3\n    # dim, direction\n    @testset for FT in (Float64,)\n        Rrange = [FT(1.0), FT(1.2)]\n\n        topl = ClimateMachine.Mesh.Grids.StackedCubedSphereTopology(\n            MPI.COMM_WORLD,\n            1,\n            Rrange,\n            boundary = (5, 6),\n        )\n\n        grid = ClimateMachine.Mesh.Grids.DiscontinuousSpectralElementGrid(\n            topl,\n            FloatType = FT,\n            DeviceArray = ClimateMachine.array_type(),\n            polynomialorder = (N, N),\n            meshwarp = cubedshellwarp,\n        )\n\n\n        mp_filter =\n            ClimateMachine.Mesh.Filters.MassPreservingCutoffFilter(grid, 2)\n\n        reg_filter = ClimateMachine.Mesh.Filters.CutoffFilter(grid, 2)\n\n        model = FilterTestModel{4}()\n        dg = ClimateMachine.DGMethods.DGModel(\n            model,\n            grid,\n            nothing,\n            nothing,\n            nothing;\n            state_gradient_flux = nothing,\n        )\n\n        # test mp filter\n        filter = mp_filter\n        Q = ClimateMachine.DGMethods.init_ode_state(dg, nothing, dim)\n        sum_before_1 = weightedsum(Q, 1)\n        sum_before_2 = weightedsum(Q, 2)\n        sum_before_3 = weightedsum(Q, 3)\n        target = 1:3\n        ClimateMachine.Mesh.Filters.apply!(Q, target, grid, filter)\n        sum_after_1 = weightedsum(Q, 1)\n        sum_after_2 = weightedsum(Q, 2)\n        sum_after_3 = weightedsum(Q, 3)\n\n        @test sum_before_1 ≈ sum_after_1\n        @test sum_before_2 ≈ sum_after_2\n        @test sum_before_3 ≈ sum_after_3\n\n\n        # test regular filter\n        filter = reg_filter\n        Q = ClimateMachine.DGMethods.init_ode_state(dg, nothing, dim)\n        sum_before_1 = weightedsum(Q, 1)\n        sum_before_2 = weightedsum(Q, 2)\n        sum_before_3 = weightedsum(Q, 3)\n        target = 1:3\n        ClimateMachine.Mesh.Filters.apply!(Q, target, grid, filter)\n        sum_after_1 = weightedsum(Q, 1)\n        sum_after_2 = weightedsum(Q, 2)\n        sum_after_3 = weightedsum(Q, 3)\n\n        @test !(sum_before_1 ≈ sum_after_1)\n        @test !(sum_before_2 ≈ sum_after_2)\n        @test !(sum_before_3 ≈ sum_after_3)\n\n    end\nend\n"
  },
  {
    "path": "test/Numerics/Mesh/filter_TMAR.jl",
    "content": "# This tutorial uses the TMAR Filter from [Light2016](@cite)\n#\n# to reproduce the tutorial in section 4b.  It is a shear swirling\n# flow deformation of a transported quantity from LeVeque 1996.  The exact\n# solution at the final time is the same as the initial condition.\n\nusing MPI\nusing Test\nusing ClimateMachine\nClimateMachine.init()\nusing Logging\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing LinearAlgebra\nusing Printf\nusing Dates\nusing ClimateMachine.GenericCallbacks:\n    EveryXWallTimeSeconds, EveryXSimulationSteps\nusing ClimateMachine.VTK: writevtk, writepvtu\n\nusing ClimateMachine\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\ninclude(joinpath(\n    clima_dir,\n    \"test\",\n    \"Numerics\",\n    \"DGMethods\",\n    \"advection_diffusion\",\n    \"advection_diffusion_model.jl\",\n))\n\nBase.@kwdef struct SwirlingFlow{FT} <: AdvectionDiffusionProblem\n    period::FT = 5\nend\n\ninit_velocity_diffusion!(::SwirlingFlow, aux::Vars, geom::LocalGeometry) =\n    nothing\n\ncosbell(τ, q) = τ ≤ 1 ? ((1 + cospi(τ)) / 2)^q : zero(τ)\n\nfunction initial_condition!(::SwirlingFlow, state, aux, localgeo, t)\n    FT = eltype(state)\n    x, y, _ = aux.coord\n    x0, y0 = FT(1 // 4), FT(1 // 4)\n    τ = 4 * hypot(x - x0, y - y0)\n    state.ρ = cosbell(τ, 3)\nend;\n\nhas_variable_coefficients(::SwirlingFlow) = true\nfunction update_velocity_diffusion!(\n    problem::SwirlingFlow,\n    ::AdvectionDiffusion,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    x, y, _ = aux.coord\n    sx, cx = sinpi(x), cospi(x)\n    sy, cy = sinpi(y), cospi(y)\n    ct = cospi(t / problem.period)\n\n    u = 2 * sx^2 * sy * cy * ct\n    v = -2 * sy^2 * sx * cx * ct\n    aux.advection.u = SVector(u, v, 0)\nend;\n\nfunction do_output(mpicomm, vtkdir, vtkstep, dg, Q, model, testname)\n    ## name of the file that this MPI rank will write\n    filename = @sprintf(\n        \"%s/%s_mpirank%04d_step%04d\",\n        vtkdir,\n        testname,\n        MPI.Comm_rank(mpicomm),\n        vtkstep\n    )\n\n    statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n\n    writevtk(filename, Q, dg, statenames)\n\n    ## generate the pvtu file for these vtk files\n    if MPI.Comm_rank(mpicomm) == 0\n        ## name of the pvtu file\n        pvtuprefix = @sprintf(\"%s/%s_step%04d\", vtkdir, testname, vtkstep)\n\n        ## name of each of the ranks vtk files\n        prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n            @sprintf(\"%s_mpirank%04d_step%04d\", testname, i - 1, vtkstep)\n        end\n\n        writepvtu(pvtuprefix, prefixes, statenames, eltype(Q))\n\n        @info \"Done writing VTK: $pvtuprefix\"\n    end\nend;\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    topl,\n    problem,\n    dt,\n    N,\n    timeend,\n    FT,\n    vtkdir,\n    outputtime,\n)\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    bcs = (HomogeneousBC{0}(),)\n    model = AdvectionDiffusion{2}(problem, bcs, diffusion = false)\n\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n\n    initialsumQ = weightedsum(Q)\n\n    ## We integrate so that the final solution is equal to the initial solution\n    Qe = copy(Q)\n\n    rhs! = function (dQdt, Q, ::Nothing, t; increment = false)\n        Filters.apply!(Q, :, grid, TMARFilter())\n        dg(dQdt, Q, nothing, t; increment = false)\n    end\n\n    odesolver = SSPRK33ShuOsher(rhs!, Q; dt = dt, t0 = 0)\n\n    cbTMAR = EveryXSimulationSteps(1) do\n        Filters.apply!(Q, :, grid, TMARFilter())\n    end\n\n    ## create output directory on first rank of communicator\n    if MPI.Comm_rank(mpicomm) == 0\n        mkpath(vtkdir)\n    end\n    MPI.Barrier(mpicomm)\n\n    vtkstep = 0\n    ## output initial step\n    do_output(mpicomm, vtkdir, vtkstep, dg, Q, model, \"nonnegative\")\n    ## setup the output callback\n    cbvtk = EveryXSimulationSteps(floor(outputtime / dt)) do\n        vtkstep += 1\n        minQ, maxQ = minimum(Q), maximum(Q)\n        sumQ = weightedsum(Q)\n\n        sumerror = (initialsumQ - sumQ) / initialsumQ\n\n        @info @sprintf \"\"\"Step  = %d\n          minimum(Q)  = %.16e\n          maximum(Q)  = %.16e\n          sum error   = %.16e\n          \"\"\" vtkstep minQ maxQ sumerror\n\n        do_output(mpicomm, vtkdir, vtkstep, dg, Q, model, \"nonnegative\")\n    end\n\n    callbacks = (cbTMAR, cbvtk)\n    solve!(Q, odesolver; timeend = timeend, callbacks = callbacks)\n\n    minQ, maxQ = minimum(Q), maximum(Q)\n    finalsumQ = weightedsum(Q)\n    sumerror = (initialsumQ - finalsumQ) / initialsumQ\n    error = euclidean_distance(Q, Qe)\n\n    @test minQ ≥ 0\n\n    @info @sprintf \"\"\"Finished\n    minimum(Q) = %.16e\n    maximum(Q) = %.16e\n    L2 error   = %.16e\n    sum error  = %.16e\n    \"\"\" minQ maxQ error sumerror\nend;\n\nlet\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    FT = Float64\n    dim = 2\n    Ne = 20\n    polynomialorder = 4\n\n    problem = SwirlingFlow()\n\n    brickrange = (\n        range(FT(0); length = Ne + 1, stop = 1),\n        range(FT(0); length = Ne + 1, stop = 1),\n        range(FT(0); length = Ne + 1, stop = 1),\n    )\n\n    topology = BrickTopology(\n        mpicomm,\n        brickrange[1:dim],\n        boundary = ntuple(d -> (1, 1), dim),\n    )\n\n    maxvelocity = 2\n    elementsize = 1 / Ne\n    dx = elementsize / polynomialorder^2\n    CFL = 1\n    dt = CFL * dx / maxvelocity\n\n    vtkdir =\n        abspath(joinpath(ClimateMachine.Settings.output_dir, \"vtk_nonnegative\"))\n    outputtime = 0.0625\n    dt = outputtime / ceil(Int64, outputtime / dt)\n\n    timeend = problem.period\n\n    @info @sprintf \"\"\"Starting\n    FT               = %s\n    dim              = %d\n    Ne               = %d\n    polynomial order = %d\n    final time       = %.16e\n    time step        = %.16e\n    \"\"\" FT dim Ne polynomialorder timeend dt\n\n    test_run(\n        mpicomm,\n        ArrayType,\n        topology,\n        problem,\n        dt,\n        polynomialorder,\n        timeend,\n        FT,\n        vtkdir,\n        outputtime,\n    )\nend;\n"
  },
  {
    "path": "test/Numerics/Mesh/grid_integral.jl",
    "content": "using Test\nusing ClimateMachine\n\nlet\n    for N in 1:10\n        for FloatType in (Float64, Float32)\n            (ξ, ω) = ClimateMachine.Mesh.Elements.lglpoints(FloatType, N)\n            I∫ =\n                ClimateMachine.Mesh.Grids.indefinite_integral_interpolation_matrix(\n                    ξ,\n                    ω,\n                )\n            for n in 1:N\n                if N == 1\n                    @test sum(abs.(I∫ * ξ)) < 10 * eps(FloatType)\n                else\n                    @test I∫ * ξ .^ n ≈\n                          (ξ .^ (n + 1) .- (-1) .^ (n + 1)) / (n + 1)\n                end\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/Mesh/interpolation.jl",
    "content": "using Dates\nusing LinearAlgebra\nusing Logging\nusing MPI\nusing Printf\nusing StaticArrays\nusing Statistics\nusing Test\nimport GaussQuadrature\nusing KernelAbstractions\n\nusing ClimateMachine\nClimateMachine.init()\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Atmos\nusing ClimateMachine.Atmos: vars_state\nusing ClimateMachine.Orientations\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Geometry\nusing ClimateMachine.Mesh.Interpolation\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Writers\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: R_d, planet_radius, grav, MSLP\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n#-------------------------------------\nfcn(x, y, z) = sin(x) * cos(y) * cos(z) # sample function\n#-------------------------------------\nfunction Initialize_Brick_Interpolation_Test!(\n    problem,\n    bl,\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t,\n)\n    FT = eltype(state)\n    # Dummy variables for initial condition function\n    state.ρ = FT(0)\n    state.ρu = SVector{3, FT}(0, 0, 0)\n    state.energy.ρe = FT(0)\n    state.moisture.ρq_tot = FT(0)\nend\n#------------------------------------------------\nstruct TestSphereSetup{DT}\n    p_ground::DT\n    T_initial::DT\n    domain_height::DT\n\n    function TestSphereSetup(\n        p_ground::DT,\n        T_initial::DT,\n        domain_height::DT,\n    ) where {DT <: AbstractFloat}\n        return new{DT}(p_ground, T_initial, domain_height)\n    end\nend\n#----------------------------------------------------------------------------\nfunction (setup::TestSphereSetup)(problem, bl, state, aux, coords, t)\n    # callable to set initial conditions\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n    _grav::FT = grav(param_set)\n    _R_d::FT = R_d(param_set)\n\n    z = altitude(bl, aux)\n\n    scale_height::FT = _R_d * setup.T_initial / _grav\n    p::FT = setup.p_ground * exp(-z / scale_height)\n    e_int = internal_energy(param_set, setup.T_initial)\n    e_pot = gravitational_potential(bl.orientation, aux)\n\n    # TODO: Fix type instability: typeof(setup.T_initial) == typeof(p) fails\n    state.ρ = air_density(param_set, FT(setup.T_initial), p)\n    state.ρu = SVector{3, FT}(0, 0, 0)\n    state.energy.ρe = state.ρ * (e_int + e_pot)\n    return nothing\nend\n#----------------------------------------------------------------------------\nfunction run_brick_interpolation_test(\n    ::Type{DA},\n    ::Type{FT},\n    polynomialorders,\n    toler::FT,\n) where {DA, FT <: AbstractFloat}\n    mpicomm = MPI.COMM_WORLD\n    root = 0\n    pid = MPI.Comm_rank(mpicomm)\n    npr = MPI.Comm_size(mpicomm)\n\n    xmin, ymin, zmin = FT(0), FT(0), FT(0)         # defining domain extent\n    xmax, ymax, zmax = FT(2000), FT(400), FT(2000)\n    xres = [FT(10), FT(10), FT(10)] # resolution of interpolation grid\n\n    Ne = (20, 4, 20)\n    #-------------------------\n    _x, _y, _z = ClimateMachine.Mesh.Grids.vgeoid.x1id,\n    ClimateMachine.Mesh.Grids.vgeoid.x2id,\n    ClimateMachine.Mesh.Grids.vgeoid.x3id\n    #-------------------------\n    brickrange = (\n        range(FT(xmin); length = Ne[1] + 1, stop = xmax),\n        range(FT(ymin); length = Ne[2] + 1, stop = ymax),\n        range(FT(zmin); length = Ne[3] + 1, stop = zmax),\n    )\n    topl = StackedBrickTopology(\n        mpicomm,\n        brickrange,\n        periodicity = (true, true, false),\n    )\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = DA,\n        polynomialorder = polynomialorders,\n    )\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = NoReferenceState(),\n        turbulence = ConstantDynamicViscosity(FT(0)),\n    )\n\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        orientation = SphericalOrientation(),\n        init_state_prognostic = Initialize_Brick_Interpolation_Test!,\n        source = (Gravity(),),\n    )\n\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n\n    Q = init_ode_state(dg, FT(0))\n\n    #------------------------------\n    x1 = @view grid.vgeo[:, _x:_x, :]\n    x2 = @view grid.vgeo[:, _y:_y, :]\n    x3 = @view grid.vgeo[:, _z:_z, :]\n    #----calling interpolation function on state variable # st_idx--------------------------\n    nvars = size(Q.data, 2)\n    Q.data .= sin.(x1 ./ xmax) .* cos.(x2 ./ ymax) .* cos.(x3 ./ zmax)\n\n    xbnd = Array{FT}(undef, 2, 3)\n\n    xbnd[1, 1] = FT(xmin)\n    xbnd[2, 1] = FT(xmax)\n    xbnd[1, 2] = FT(ymin)\n    xbnd[2, 2] = FT(ymax)\n    xbnd[1, 3] = FT(zmin)\n    xbnd[2, 3] = FT(zmax)\n    #----------------------------------------------------------\n    x1g = collect(range(xbnd[1, 1], xbnd[2, 1], step = xres[1]))\n    nx1 = length(x1g)\n    x2g = collect(range(xbnd[1, 2], xbnd[2, 2], step = xres[2]))\n    nx2 = length(x2g)\n    x3g = collect(range(xbnd[1, 3], xbnd[2, 3], step = xres[3]))\n    nx3 = length(x3g)\n\n    filename = \"test.nc\"\n    varnames = (\"ρ\", \"ρu\", \"ρv\", \"ρw\", \"e\", \"other\")\n\n    intrp_brck = InterpolationBrick(grid, xbnd, x1g, x2g, x3g)        # sets up the interpolation structure\n    iv = DA(Array{FT}(undef, intrp_brck.Npl, nvars))                  # allocating space for the interpolation variable\n    if pid == 0\n        fiv = DA(Array{FT}(undef, nx1, nx2, nx3, nvars))    # allocating space for the full interpolation variables accumulated on proc# 0\n    else\n        fiv = DA(Array{FT}(undef, 0, 0, 0, 0))\n    end\n    interpolate_local!(intrp_brck, Q.data, iv)                    # interpolation\n\n    accumulate_interpolated_data!(intrp_brck, iv, fiv)      # write interpolation data to file\n    #------------------------------\n    err_inf_dom = zeros(FT, nvars)\n\n    x1g = intrp_brck.x1g\n    x2g = intrp_brck.x2g\n    x3g = intrp_brck.x3g\n\n    if pid == 0\n        nx1 = length(x1g)\n        nx2 = length(x2g)\n        nx3 = length(x3g)\n        x1 = Array{FT}(undef, nx1, nx2, nx3)\n        x2 = similar(x1)\n        x3 = similar(x1)\n\n        fiv_cpu = Array(fiv)\n\n        for k in 1:nx3, j in 1:nx2, i in 1:nx1\n            x1[i, j, k] = x1g[i]\n            x2[i, j, k] = x2g[j]\n            x3[i, j, k] = x3g[k]\n        end\n\n        fex = sin.(x1 ./ xmax) .* cos.(x2 ./ ymax) .* cos.(x3 ./ zmax)\n\n        for vari in 1:nvars\n            err_inf_dom[vari] =\n                maximum(abs.(fiv_cpu[:, :, :, vari] .- fex[:, :, :]))\n        end\n    end\n\n    MPI.Bcast!(err_inf_dom, root, mpicomm)\n\n    if maximum(err_inf_dom) > toler\n        if pid == 0\n            println(\"err_inf_domain = $(maximum(err_inf_dom)) is larger than prescribed tolerance of $toler\")\n        end\n        MPI.Barrier(mpicomm)\n    end\n    @test maximum(err_inf_dom) < toler\n    return nothing\n    #----------------\nend #function run_brick_interpolation_test\n#----------------------------------------------------------------------------\n\n#----------------------------------------------------------------------------\n# Cubed sphere, lat/long interpolation test\n#----------------------------------------------------------------------------\nfunction run_cubed_sphere_interpolation_test(\n    ::Type{DA},\n    ::Type{FT},\n    polynomialorders,\n    toler::FT,\n) where {DA, FT <: AbstractFloat}\n    mpicomm = MPI.COMM_WORLD\n    root = 0\n    pid = MPI.Comm_rank(mpicomm)\n    npr = MPI.Comm_size(mpicomm)\n\n    domain_height = FT(30e3)\n    numelem_horz = 6\n    numelem_vert = 4\n    #-------------------------\n    _x, _y, _z = ClimateMachine.Mesh.Grids.vgeoid.x1id,\n    ClimateMachine.Mesh.Grids.vgeoid.x2id,\n    ClimateMachine.Mesh.Grids.vgeoid.x3id\n    _ρ, _ρu, _ρv, _ρw = 1, 2, 3, 4\n    #-------------------------\n    _planet_radius::FT = planet_radius(param_set)\n\n    vert_range = grid1d(\n        _planet_radius,\n        FT(_planet_radius + domain_height),\n        nelem = numelem_vert,\n    )\n\n    lat_res = FT(1) # 1 degree resolution\n    long_res = FT(1) # 1 degree resolution\n    nel_vert_grd = 20 #100 #50 #10#50\n    rad_res = FT((vert_range[end] - vert_range[1]) / FT(nel_vert_grd)) # 1000 m vertical resolution\n    #----------------------------------------------------------\n    _MSLP::FT = MSLP(param_set)\n    setup = TestSphereSetup(_MSLP, FT(255), FT(30e3))\n\n    topology = StackedCubedSphereTopology(mpicomm, numelem_horz, vert_range)\n\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        FloatType = FT,\n        DeviceArray = DA,\n        polynomialorder = polynomialorders,\n        meshwarp = ClimateMachine.Mesh.Topologies.equiangular_cubed_sphere_warp,\n    )\n\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = NoReferenceState(),\n        turbulence = ConstantDynamicViscosity(FT(0)),\n        moisture = DryModel(),\n    )\n\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        init_state_prognostic = setup,\n        source = (),\n    )\n\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n\n    #------------------------------\n    x1 = @view grid.vgeo[:, _x:_x, :]\n    x2 = @view grid.vgeo[:, _y:_y, :]\n    x3 = @view grid.vgeo[:, _z:_z, :]\n\n    xmax = _planet_radius\n    ymax = _planet_radius\n    zmax = _planet_radius\n\n    nvars = size(Q.data, 2)\n\n    Q.data .= sin.(x1 ./ xmax) .* cos.(x2 ./ ymax) .* cos.(x3 ./ zmax)\n    #for ivar in 1:nvars\n    #    Q.data[:, ivar, :] .=\n    #        sin.(x1[:, 1, :] ./ xmax) .* cos.(x2[:, 1, :] ./ ymax) .*\n    #        cos.(x3[:, 1, :] ./ zmax)\n    #end\n    #------------------------------\n    lat_min, lat_max = FT(-90.0), FT(90.0)            # inclination/zeinth angle range\n    long_min, long_max = FT(-180.0), FT(180.0)     # azimuthal angle range\n    rad_min, rad_max = vert_range[1], vert_range[end] # radius range\n\n\n    lat_grd = collect(range(lat_min, lat_max, step = lat_res))\n    n_lat = length(lat_grd)\n    long_grd = collect(range(long_min, long_max, step = long_res))\n    n_long = length(long_grd)\n    rad_grd = collect(range(rad_min, rad_max, step = rad_res))\n    n_rad = length(rad_grd)\n\n    _ρu, _ρv, _ρw = 2, 3, 4\n\n    filename = \"test.nc\"\n    varnames = (\"ρ\", \"ρu\", \"ρv\", \"ρw\", \"e\")\n    projectv = true\n\n    intrp_cs = InterpolationCubedSphere(\n        grid,\n        collect(vert_range),\n        numelem_horz,\n        lat_grd,\n        long_grd,\n        rad_grd,\n    ) # sets up the interpolation structure\n    iv = DA(Array{FT}(undef, intrp_cs.Npl, nvars))             # allocating space for the interpolation variable\n    if pid == 0\n        fiv = DA(Array{FT}(undef, n_long, n_lat, n_rad, nvars))    # allocating space for the full interpolation variables accumulated on proc# 0\n    else\n        fiv = DA(Array{FT}(undef, 0, 0, 0, 0))\n    end\n\n    interpolate_local!(intrp_cs, Q.data, iv)                   # interpolation\n    project_cubed_sphere!(intrp_cs, iv, (_ρu, _ρv, _ρw))         # project velocity onto unit vectors along rad, lat & long\n    accumulate_interpolated_data!(intrp_cs, iv, fiv)           # accumulate interpolated data on to proc# 0\n    #----------------------------------------------------------\n    # Testing\n    err_inf_dom = zeros(FT, nvars)\n    rad = Array(intrp_cs.rad_grd)\n    lat = Array(intrp_cs.lat_grd)\n    long = Array(intrp_cs.long_grd)\n    fiv_cpu = Array(fiv)\n    if pid == 0\n        nrad = length(rad)\n        nlat = length(lat)\n        nlong = length(long)\n        x1g = Array{FT}(undef, nrad, nlat, nlong)\n        x2g = similar(x1g)\n        x3g = similar(x1g)\n\n        fex = zeros(FT, nlong, nlat, nrad, nvars)\n\n        for vari in 1:nvars\n            for i in 1:nlong, j in 1:nlat, k in 1:nrad\n                x1g_ijk = rad[k] * cosd(lat[j]) * cosd(long[i]) # inclination -> latitude; azimuthal -> longitude.\n                x2g_ijk = rad[k] * cosd(lat[j]) * sind(long[i]) # inclination -> latitude; azimuthal -> longitude.\n                x3g_ijk = rad[k] * sind(lat[j])\n\n                fex[i, j, k, vari] =\n                    fcn(x1g_ijk / xmax, x2g_ijk / ymax, x3g_ijk / zmax)\n            end\n        end\n\n        if projectv\n            for i in 1:nlong, j in 1:nlat, k in 1:nrad\n                fex[i, j, k, _ρu] =\n                    -fex[i, j, k, _ρ] * sind(long[i]) +\n                    fex[i, j, k, _ρ] * cosd(long[i])\n\n                fex[i, j, k, _ρv] =\n                    -fex[i, j, k, _ρ] * sind(lat[j]) * cosd(long[i]) -\n                    fex[i, j, k, _ρ] * sind(lat[j]) * sind(long[i]) +\n                    fex[i, j, k, _ρ] * cosd(lat[j])\n\n                fex[i, j, k, _ρw] =\n                    fex[i, j, k, _ρ] * cosd(lat[j]) * cosd(long[i]) +\n                    fex[i, j, k, _ρ] * cosd(lat[j]) * sind(long[i]) +\n                    fex[i, j, k, _ρ] * sind(lat[j])\n            end\n        end\n\n        for vari in 1:nvars\n            err_inf_dom[vari] =\n                maximum(abs.(fiv_cpu[:, :, :, vari] .- fex[:, :, :, vari]))\n        end\n    end\n\n    MPI.Bcast!(err_inf_dom, root, mpicomm)\n\n    if maximum(err_inf_dom) > toler\n        if pid == 0\n            println(\"err_inf_domain = $(maximum(err_inf_dom)) is larger than prescribed tolerance of $toler\")\n        end\n        MPI.Barrier(mpicomm)\n    end\n    @test maximum(err_inf_dom) < toler\n    return nothing\nend\n#----------------------------------------------------------------------------\n@testset \"Interpolation tests\" begin\n    DA = ClimateMachine.array_type()\n\n    run_brick_interpolation_test(DA, Float32, (0), Float32(1E-1))\n    run_brick_interpolation_test(DA, Float64, (0), Float64(1E-1))\n\n    run_brick_interpolation_test(DA, Float32, (5), Float32(1E-6))\n    run_brick_interpolation_test(DA, Float64, (5), Float64(1E-9))\n\n    run_brick_interpolation_test(DA, Float32, (5, 6), Float32(1E-6))\n    run_brick_interpolation_test(DA, Float64, (5, 6), Float64(1E-9))\n\n    run_cubed_sphere_interpolation_test(DA, Float32, (0), Float32(2e-1))\n    run_cubed_sphere_interpolation_test(DA, Float64, (0), Float64(2e-1))\n\n    run_cubed_sphere_interpolation_test(DA, Float32, (5), Float32(2e-6))\n    run_cubed_sphere_interpolation_test(DA, Float64, (5), Float64(2e-7))\n\n    run_cubed_sphere_interpolation_test(DA, Float32, (5, 6), Float32(2e-6))\n    run_cubed_sphere_interpolation_test(DA, Float64, (5, 6), Float64(2e-7))\nend\n#------------------------------------------------\n"
  },
  {
    "path": "test/Numerics/Mesh/min_node_distance.jl",
    "content": "using Test\nusing MPI\nusing ClimateMachine\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.VTK\nusing Logging\nusing Printf\nusing LinearAlgebra\n\nlet\n    # boiler plate MPI stuff\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    # Mesh generation parameters\n    Neh = 10\n    Nev = 4\n\n    Ns = ((4, (4, 3), (2, 3)), (4, (2, 3, 4), (4, 2, 3), (4, 2, 3)))\n    @testset \"$(@__FILE__) DGModel matrix\" begin\n        for FT in (Float64, Float32)\n            for dim in (2, 3)\n                for N in Ns[dim - 1]\n                    if dim == 2\n                        brickrange = (\n                            range(FT(0); length = Neh + 1, stop = 1),\n                            range(FT(1); length = Nev + 1, stop = 2),\n                        )\n                    elseif dim == 3\n                        brickrange = (\n                            range(FT(0); length = Neh + 1, stop = 1),\n                            range(FT(0); length = Neh + 1, stop = 1),\n                            range(FT(1); length = Nev + 1, stop = 2),\n                        )\n                    end\n\n                    topl = StackedBrickTopology(mpicomm, brickrange)\n\n                    function warpfun(ξ1, ξ2, ξ3)\n                        FT = eltype(ξ1)\n\n                        ξ1 ≥ FT(1 // 2) &&\n                            (ξ1 = FT(1 // 2) + 2 * (ξ1 - FT(1 // 2)))\n                        if dim == 2\n                            ξ2 ≥ FT(3 // 2) &&\n                                (ξ2 = FT(3 // 2) + 2 * (ξ2 - FT(3 // 2)))\n                        elseif dim == 3\n                            ξ2 ≥ FT(1 // 2) &&\n                                (ξ2 = FT(1 // 2) + 2 * (ξ2 - FT(1 // 2)))\n                            ξ3 ≥ FT(3 // 2) &&\n                                (ξ3 = FT(3 // 2) + 2 * (ξ3 - FT(3 // 2)))\n                        end\n                        (ξ1, ξ2, ξ3)\n                    end\n\n                    grid = DiscontinuousSpectralElementGrid(\n                        topl,\n                        FloatType = FT,\n                        DeviceArray = ArrayType,\n                        polynomialorder = N,\n                        meshwarp = warpfun,\n                    )\n\n                    # testname = \"grid_poly$(N)_dim$(dim)_$(ArrayType)_$(FT)\"\n                    # filename(rank) = @sprintf(\"%s_mpirank%04d\", testname, rank)\n                    # writevtk(filename(MPI.Comm_rank(mpicomm)), grid)\n                    # if MPI.Comm_rank(mpicomm) == 0\n                    #   writepvtu(testname, filename.(0:MPI.Comm_size(mpicomm)-1), (), FT)\n                    # end\n\n                    ξ = referencepoints(grid)\n                    Δξ = ntuple(d -> ξ[d][2] - ξ[d][1], dim)\n\n                    hmnd = minimum(Δξ[1:(dim - 1)]) / (2Neh)\n                    vmnd = Δξ[end] / (2Nev)\n\n                    @test hmnd ≈ min_node_distance(grid, EveryDirection())\n                    @test vmnd ≈ min_node_distance(grid, VerticalDirection())\n                    @test hmnd ≈ min_node_distance(grid, HorizontalDirection())\n                end\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/Mesh/mpi_centroid.jl",
    "content": "using Test\nusing MPI\nusing ClimateMachine.Mesh.BrickMesh\n\nfunction main()\n    MPI.Init()\n    comm = MPI.COMM_WORLD\n\n    (elemtovert, elemtocorner, facecode) = brickmesh(\n        (2.0:5.0, 4.0:6.0),\n        (false, true);\n        part = MPI.Comm_rank(comm) + 1,\n        numparts = MPI.Comm_size(comm),\n    )\n\n    code = centroidtocode(comm, elemtocorner)\n    (d, nelem) = size(code)\n\n    root = 0\n    counts = MPI.Gather(Cint(length(code)), 0, comm)\n    code_all = MPI.Gatherv!(\n        code,\n        MPI.Comm_rank(comm) == root ?\n        VBuffer(similar(code, sum(counts)), counts) : nothing,\n        root,\n        comm,\n    )\n\n    if MPI.Comm_rank(comm) == root\n\n        code_all = reshape(code_all, d, div(sum(counts), d))\n\n        code_expect = UInt64[\n            0x0000000000000000 0x1555555555555555 0xffffffffffffffff 0x5555555555555555 0x6aaaaaaaaaaaaaaa 0xaaaaaaaaaaaaaaaa\n            0x0000000000000000 0x5555555555555555 0xffffffffffffffff 0x5555555555555555 0xaaaaaaaaaaaaaaaa 0xaaaaaaaaaaaaaaaa\n        ]\n        @test code_all == code_expect\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/Mesh/mpi_connect.jl",
    "content": "using Test\nusing MPI\nusing ClimateMachine.Mesh.Topologies\n\nfunction main()\n    MPI.Init()\n    comm = MPI.COMM_WORLD\n    crank = MPI.Comm_rank(comm)\n    csize = MPI.Comm_size(comm)\n\n    @assert csize == 3\n\n    topology = BrickTopology(\n        comm,\n        (0:4, 5:9);\n        boundary = ((1, 2), (3, 4)),\n        periodicity = (false, true),\n        connectivity = :face,\n    )\n\n    elems = topology.elems\n    realelems = topology.realelems\n    ghostelems = topology.ghostelems\n    sendelems = topology.sendelems\n    elemtocoord = topology.elemtocoord\n    elemtoelem = topology.elemtoelem\n    elemtoface = topology.elemtoface\n    elemtoordr = topology.elemtoordr\n    elemtobndy = topology.elemtobndy\n    nabrtorank = topology.nabrtorank\n    nabrtorecv = topology.nabrtorecv\n    nabrtosend = topology.nabrtosend\n\n    globalelemtoface = [\n        1 2 2 1 1 1 2 2 2 2 2 2 2 2 2 2\n        1 1 1 1 1 1 1 1 1 1 2 2 2 1 1 2\n        4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4\n        3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3\n    ]\n\n    globalelemtoordr = ones(Int, size(globalelemtoface))\n\n    globalelemtocoord = Array{Int}(undef, 2, 4, 16)\n    globalelemtocoord[:, :, 1] = [0 1 0 1; 5 5 6 6]\n    globalelemtocoord[:, :, 2] = [1 2 1 2; 5 5 6 6]\n    globalelemtocoord[:, :, 3] = [1 2 1 2; 6 6 7 7]\n    globalelemtocoord[:, :, 4] = [0 1 0 1; 6 6 7 7]\n    globalelemtocoord[:, :, 5] = [0 1 0 1; 7 7 8 8]\n    globalelemtocoord[:, :, 6] = [0 1 0 1; 8 8 9 9]\n    globalelemtocoord[:, :, 7] = [1 2 1 2; 8 8 9 9]\n    globalelemtocoord[:, :, 8] = [1 2 1 2; 7 7 8 8]\n    globalelemtocoord[:, :, 9] = [2 3 2 3; 7 7 8 8]\n    globalelemtocoord[:, :, 10] = [2 3 2 3; 8 8 9 9]\n    globalelemtocoord[:, :, 11] = [3 4 3 4; 8 8 9 9]\n    globalelemtocoord[:, :, 12] = [3 4 3 4; 7 7 8 8]\n    globalelemtocoord[:, :, 13] = [3 4 3 4; 6 6 7 7]\n    globalelemtocoord[:, :, 14] = [2 3 2 3; 6 6 7 7]\n    globalelemtocoord[:, :, 15] = [2 3 2 3; 5 5 6 6]\n    globalelemtocoord[:, :, 16] = [3 4 3 4; 5 5 6 6]\n\n    globalelemtobndy = [\n        1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0\n        0 0 0 0 0 0 0 0 0 0 2 2 2 0 0 2\n        0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n        0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n    ]\n\n    if crank == 0\n        nrealelem = 5\n        globalelems = [1, 2, 3, 4, 5, 6, 7, 8, 14, 15]\n        elemtoelem_expect = [\n            1 1 4 2 3 4 7 8 9 10\n            2 10 9 3 8 6 7 8 9 10\n            6 7 2 1 4 6 7 8 9 10\n            4 3 8 5 6 6 7 8 9 10\n        ]\n        nabrtorank_expect = [1, 2]\n        nabrtorecv_expect = UnitRange{Int}[1:3, 4:5]\n        nabrtosend_expect = UnitRange{Int}[1:4, 5:6]\n    elseif crank == 1\n        nrealelem = 5\n        globalelems = [6, 7, 8, 9, 10, 1, 2, 3, 5, 11, 12, 14, 15]\n        elemtoelem_expect = [\n            1 1 9 3 2 2 7 8 3 10 11 12 13\n            2 5 4 11 10 6 7 8 9 1 2 12 13\n            9 3 8 12 4 6 7 8 9 10 11 12 13\n            6 7 2 5 13 6 7 8 9 10 11 12 13\n        ]\n        nabrtorank_expect = [0, 2]\n        nabrtorecv_expect = UnitRange{Int}[1:4, 5:8]\n        nabrtosend_expect = UnitRange{Int}[1:3, 4:5]\n    elseif crank == 2\n        nrealelem = 6\n        globalelems = [11, 12, 13, 14, 15, 16, 2, 3, 9, 10]\n        elemtoelem_expect = [\n            10 9 4 8 7 5 7 8 9 10\n            1 2 3 3 6 4 7 8 9 10\n            2 3 6 5 10 1 7 8 9 10\n            6 1 2 9 4 3 7 8 9 10\n        ]\n        nabrtorank_expect = [0, 1]\n        nabrtorecv_expect = UnitRange{Int}[1:2, 3:4]\n        nabrtosend_expect = UnitRange{Int}[1:2, 3:6]\n    end\n\n    @test elems == 1:length(globalelems)\n    @test realelems == 1:nrealelem\n    @test ghostelems == (nrealelem + 1):length(globalelems)\n    @test elemtocoord == globalelemtocoord[:, :, globalelems]\n    @test elemtoface[:, realelems] ==\n          globalelemtoface[:, globalelems[realelems]]\n    @test elemtoelem == elemtoelem_expect\n    @test elemtobndy == globalelemtobndy[:, globalelems]\n    @test elemtoordr == ones(eltype(elemtoordr), size(elemtoordr))\n    @test nabrtorank == nabrtorank_expect\n    @test nabrtorecv == nabrtorecv_expect\n    @test nabrtosend == nabrtosend_expect\n\n    @test collect(realelems) ==\n          sort(union(topology.exteriorelems, topology.interiorelems))\n    @test unique(sort(sendelems)) == topology.exteriorelems\n    @test length(intersect(topology.exteriorelems, topology.interiorelems)) == 0\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/Mesh/mpi_connect_1d.jl",
    "content": "using Test\nusing MPI\nusing ClimateMachine.Mesh.Topologies\n\nfunction main()\n    MPI.Init()\n    comm = MPI.COMM_WORLD\n    crank = MPI.Comm_rank(comm)\n    csize = MPI.Comm_size(comm)\n\n    @assert csize == 5\n\n    topology = BrickTopology(\n        comm,\n        (0:10,);\n        boundary = ((1, 2),),\n        periodicity = (true,),\n    )\n\n    elems = topology.elems\n    realelems = topology.realelems\n    ghostelems = topology.ghostelems\n    sendelems = topology.sendelems\n    elemtocoord = topology.elemtocoord\n    elemtoelem = topology.elemtoelem\n    elemtoface = topology.elemtoface\n    elemtoordr = topology.elemtoordr\n    elemtobndy = topology.elemtobndy\n    nabrtorank = topology.nabrtorank\n    nabrtorecv = topology.nabrtorecv\n    nabrtosend = topology.nabrtosend\n\n    globalelemtoelem = [\n        10 1 2 3 4 5 6 7 8 9\n        2 3 4 5 6 7 8 9 10 1\n    ]\n\n    globalelemtoface = [\n        2 2 2 2 2 2 2 2 2 2\n        1 1 1 1 1 1 1 1 1 1\n    ]\n\n    globalelemtoordr = ones(Int, size(globalelemtoface))\n    globalelemtobndy = zeros(Int, size(globalelemtoface))\n\n    globalelemtocoord = Array{Int}(undef, 1, 2, 10)\n    globalelemtocoord[:, :, 1] = [0 1]\n    globalelemtocoord[:, :, 2] = [1 2]\n    globalelemtocoord[:, :, 3] = [2 3]\n    globalelemtocoord[:, :, 4] = [3 4]\n    globalelemtocoord[:, :, 5] = [4 5]\n    globalelemtocoord[:, :, 6] = [5 6]\n    globalelemtocoord[:, :, 7] = [6 7]\n    globalelemtocoord[:, :, 8] = [7 8]\n    globalelemtocoord[:, :, 9] = [8 9]\n    globalelemtocoord[:, :, 10] = [9 10]\n\n    @assert csize == 5\n    nrealelem = 2\n    if crank == 0\n        globalelems = [1, 2, 3, 10]\n        elemtoelem_expect = [4 1 3 4; 2 3 3 4]\n        nabrtorank_expect = [1, 4]\n    elseif crank == 1\n        globalelems = [3, 4, 2, 5]\n        elemtoelem_expect = [3 1 3 4; 2 4 3 4]\n        nabrtorank_expect = [0, 2]\n    elseif crank == 2\n        globalelems = [5, 6, 4, 7]\n        elemtoelem_expect = [3 1 3 4; 2 4 3 4]\n        nabrtorank_expect = [1, 3]\n    elseif crank == 3\n        globalelems = [7, 8, 6, 9]\n        elemtoelem_expect = [3 1 3 4; 2 4 3 4]\n        nabrtorank_expect = [2, 4]\n    elseif crank == 4\n        globalelems = [9, 10, 1, 8]\n        elemtoelem_expect = [4 1 3 4; 2 3 3 4]\n        nabrtorank_expect = [0, 3]\n    end\n    nabrtorecv_expect = UnitRange{Int}[1:1, 2:2]\n    nabrtosend_expect = UnitRange{Int}[1:1, 2:2]\n\n    @test elems == 1:length(globalelems)\n    @test realelems == 1:nrealelem\n    @test ghostelems == (nrealelem + 1):length(globalelems)\n    @test elemtocoord == globalelemtocoord[:, :, globalelems]\n    @test elemtoface[:, realelems] ==\n          globalelemtoface[:, globalelems[realelems]]\n    @test elemtoelem == elemtoelem_expect\n    @test elemtobndy == globalelemtobndy[:, globalelems]\n    @test elemtoordr == globalelemtoordr[:, globalelems]\n    @test nabrtorank == nabrtorank_expect\n    @test nabrtorecv == nabrtorecv_expect\n    @test nabrtosend == nabrtosend_expect\n\n    @test collect(realelems) ==\n          sort(union(topology.exteriorelems, topology.interiorelems))\n    @test unique(sort(sendelems)) == topology.exteriorelems\n    @test length(intersect(topology.exteriorelems, topology.interiorelems)) == 0\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/Mesh/mpi_connect_ell.jl",
    "content": "using Test\nusing MPI\nusing ClimateMachine.Mesh.Topologies\n\nfunction main()\n    MPI.Init()\n    comm = MPI.COMM_WORLD\n    crank = MPI.Comm_rank(comm)\n    csize = MPI.Comm_size(comm)\n\n    @assert csize == 2\n\n    FT = Float64\n    Nx = 3\n    Ny = 2\n    x = range(FT(0); length = Nx + 1, stop = 1)\n    y = range(FT(0); length = Ny + 1, stop = 1)\n\n    topology = BrickTopology(\n        comm,\n        (x, y);\n        boundary = ((1, 2), (3, 4)),\n        periodicity = (true, true),\n        connectivity = :face,\n    )\n\n    elems = topology.elems\n    realelems = topology.realelems\n    ghostelems = topology.ghostelems\n    sendelems = topology.sendelems\n    elemtocoord = topology.elemtocoord\n    elemtoelem = topology.elemtoelem\n    elemtoface = topology.elemtoface\n    elemtoordr = topology.elemtoordr\n    elemtobndy = topology.elemtobndy\n    nabrtorank = topology.nabrtorank\n    nabrtorecv = topology.nabrtorecv\n    nabrtosend = topology.nabrtosend\n\n    globalelemtoface = [\n        2 2 2 2 2 2\n        1 1 1 1 1 1\n        4 4 4 4 4 4\n        3 3 3 3 3 3\n    ]\n\n    globalelemtoordr = ones(Int, size(globalelemtoface))\n    globalelemtobndy = zeros(Int, size(globalelemtoface))\n\n    if crank == 0\n        nrealelem = 3\n        globalelems = [1, 2, 3, 4, 5, 6]\n        elemtoelem_expect = [\n            6 4 2 4 5 6\n            5 3 4 4 5 6\n            2 1 5 4 5 6\n            2 1 5 4 5 6\n        ]\n        nabrtorank_expect = [1]\n        nabrtorecv_expect = UnitRange{Int}[1:3]\n        nabrtosend_expect = UnitRange{Int}[1:3]\n    elseif crank == 1\n        nrealelem = 3\n        globalelems = [4, 5, 6, 1, 2, 3]\n        elemtoelem_expect = [\n            6 4 2 4 5 6\n            5 3 4 4 5 6\n            3 6 1 4 5 6\n            3 6 1 4 5 6\n        ]\n        nabrtorank_expect = [0]\n        nabrtorecv_expect = UnitRange{Int}[1:3]\n        nabrtosend_expect = UnitRange{Int}[1:3]\n    end\n\n    @test elems == 1:length(globalelems)\n    @test realelems == 1:nrealelem\n    @test ghostelems == (nrealelem + 1):length(globalelems)\n    @test elemtoface[:, realelems] ==\n          globalelemtoface[:, globalelems[realelems]]\n    @test elemtoelem == elemtoelem_expect\n    @test elemtobndy == globalelemtobndy[:, globalelems]\n    @test elemtoordr == ones(eltype(elemtoordr), size(elemtoordr))\n    @test nabrtorank == nabrtorank_expect\n    @test nabrtorecv == nabrtorecv_expect\n    @test nabrtosend == nabrtosend_expect\n\n    @test collect(realelems) ==\n          sort(union(topology.exteriorelems, topology.interiorelems))\n    @test unique(sort(sendelems)) == topology.exteriorelems\n    @test length(intersect(topology.exteriorelems, topology.interiorelems)) == 0\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/Mesh/mpi_connect_sphere.jl",
    "content": "using Test\nusing MPI\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.MPIStateArrays\nusing KernelAbstractions\n\nfunction main()\n    FT = Float64\n    Nhorz = 3\n    Nstack = 5\n    N = 4\n    DA = Array\n\n    MPI.Initialized() || MPI.Init()\n\n    comm = MPI.COMM_WORLD\n    crank = MPI.Comm_rank(comm)\n    csize = MPI.Comm_size(comm)\n\n    Rrange = FT.(accumulate(+, 1:(Nstack + 1)))\n    topology = StackedCubedSphereTopology(\n        MPI.COMM_SELF,\n        Nhorz,\n        Rrange;\n        boundary = (1, 2),\n        connectivity = :face,\n    )\n    grid = DiscontinuousSpectralElementGrid(\n        topology;\n        FloatType = FT,\n        DeviceArray = DA,\n        polynomialorder = N,\n        meshwarp = Topologies.equiangular_cubed_sphere_warp,\n    )\n\n    let\n        Np = (N + 1)^3\n        activedofs = Array(grid.activedofs)\n        vmaprecv = Array(grid.vmaprecv)\n        active = union(1:(length(topology.realelems) * Np), vmaprecv)\n        inactive = setdiff(1:(length(topology.elems) * Np), active)\n\n        @test all(activedofs[active] .== true)\n        @test all(activedofs[inactive] .== false)\n    end\n\n    #=\n    @show elems       = topology.elems\n    @show realelems   = topology.realelems\n    @show ghostelems  = topology.ghostelems\n    @show sendelems   = topology.sendelems\n    @show elemtocoord = topology.elemtocoord\n    @show elemtoelem  = topology.elemtoelem\n    @show elemtoface  = topology.elemtoface\n    @show elemtoordr  = topology.elemtoordr\n    @show elemtobndy  = topology.elemtobndy\n    @show nabrtorank  = topology.nabrtorank\n    @show nabrtorecv  = topology.nabrtorecv\n    @show nabrtosend  = topology.nabrtosend\n    =#\n\n    # Check x1x2x3 matches before comm\n    x1 = @view grid.vgeo[:, Grids._x1, :]\n    x2 = @view grid.vgeo[:, Grids._x2, :]\n    x3 = @view grid.vgeo[:, Grids._x3, :]\n\n    interior_faces = vec(grid.elemtobndy .== 0)\n    interior_vmap⁻ =\n        reshape(grid.vmap⁻, (size(grid.vmap⁻, 1), :))[:, interior_faces]\n    interior_vmap⁺ =\n        reshape(grid.vmap⁺, (size(grid.vmap⁺, 1), :))[:, interior_faces]\n\n    @test x1[interior_vmap⁻] ≈ x1[interior_vmap⁺]\n    @test x2[interior_vmap⁻] ≈ x2[interior_vmap⁺]\n    @test x3[interior_vmap⁻] ≈ x3[interior_vmap⁺]\n\n    Np = (N + 1)^3\n    x1x2x3 = MPIStateArray{FT}(\n        topology.mpicomm,\n        DA,\n        Np,\n        3,\n        length(topology.elems),\n        realelems = topology.realelems,\n        ghostelems = topology.ghostelems,\n        vmaprecv = grid.vmaprecv,\n        vmapsend = grid.vmapsend,\n        nabrtorank = topology.nabrtorank,\n        nabrtovmaprecv = grid.nabrtovmaprecv,\n        nabrtovmapsend = grid.nabrtovmapsend,\n    )\n    x1x2x3.data[:, :, topology.realelems] .= @view grid.vgeo[\n        :,\n        [Grids._x1, Grids._x2, Grids._x3],\n        topology.realelems,\n    ]\n\n    event = Event(array_device(x1x2x3))\n    event = MPIStateArrays.begin_ghost_exchange!(x1x2x3, dependencies = event)\n    event = MPIStateArrays.end_ghost_exchange!(x1x2x3, dependencies = event)\n    wait(array_device(x1x2x3), event)\n\n    # Check x1x2x3 matches after\n    x1 = @view x1x2x3.data[:, 1, :]\n    x2 = @view x1x2x3.data[:, 2, :]\n    x3 = @view x1x2x3.data[:, 3, :]\n\n    @test x1[interior_vmap⁻] ≈ x1[interior_vmap⁺]\n    @test x2[interior_vmap⁻] ≈ x2[interior_vmap⁺]\n    @test x3[interior_vmap⁻] ≈ x3[interior_vmap⁺]\n\n    nothing\nend\nisinteractive() || main()\n"
  },
  {
    "path": "test/Numerics/Mesh/mpi_connect_stacked.jl",
    "content": "using Test\nusing MPI\nusing ClimateMachine.Mesh.Topologies\n\nfunction main()\n    MPI.Init()\n    comm = MPI.COMM_WORLD\n    crank = MPI.Comm_rank(comm)\n    csize = MPI.Comm_size(comm)\n\n    @assert csize == 3\n\n    topology = StackedBrickTopology(\n        comm,\n        (2:5, 4:6),\n        periodicity = (false, true),\n        boundary = ((1, 2), (3, 4)),\n        connectivity = :face,\n    )\n\n\n    elems = topology.elems\n    realelems = topology.realelems\n    ghostelems = topology.ghostelems\n    sendelems = topology.sendelems\n    elemtocoord = topology.elemtocoord\n    elemtoelem = topology.elemtoelem\n    elemtoface = topology.elemtoface\n    elemtoordr = topology.elemtoordr\n    elemtobndy = topology.elemtobndy\n    nabrtorank = topology.nabrtorank\n    nabrtorecv = topology.nabrtorecv\n    nabrtosend = topology.nabrtosend\n\n    globalelemtoface = [\n        1 1 2 2 2 2\n        1 1 1 1 2 2\n        4 4 4 4 4 4\n        3 3 3 3 3 3\n    ]\n\n    globalelemtoordr = ones(Int, size(globalelemtoface))\n\n    globalelemtocoord = Array{Int}(undef, 2, 4, 6)\n    globalelemtocoord[:, :, 1] = [2 3 2 3; 4 4 5 5]\n    globalelemtocoord[:, :, 2] = [2 3 2 3; 5 5 6 6]\n    globalelemtocoord[:, :, 3] = [3 4 3 4; 4 4 5 5]\n    globalelemtocoord[:, :, 4] = [3 4 3 4; 5 5 6 6]\n    globalelemtocoord[:, :, 5] = [4 5 4 5; 4 4 5 5]\n    globalelemtocoord[:, :, 6] = [4 5 4 5; 5 5 6 6]\n\n\n    globalelemtobndy = [\n        1 1 0 0 0 0\n        0 0 0 0 2 2\n        0 0 0 0 0 0\n        0 0 0 0 0 0\n    ]\n\n    if crank == 0\n        nrealelem = 2\n        globalelems = [1, 2, 3, 4]\n        elemtoelem_expect = [\n            1 2 3 4\n            3 4 3 4\n            2 1 3 4\n            2 1 3 4\n        ]\n        nabrtorank_expect = [1]\n        nabrtorecv_expect = UnitRange{Int}[1:2]\n        nabrtosend_expect = UnitRange{Int}[1:2]\n    elseif crank == 1\n        nrealelem = 2\n        globalelems = [3, 4, 1, 2, 5, 6]\n        elemtoelem_expect = [\n            3 4 1 2 5 6\n            5 6 3 4 1 2\n            2 1 3 4 5 6\n            2 1 3 4 5 6\n        ]\n        nabrtorank_expect = [0, 2]\n        nabrtorecv_expect = UnitRange{Int}[1:2, 3:4]\n        nabrtosend_expect = UnitRange{Int}[1:2, 3:4]\n    elseif crank == 2\n        nrealelem = 2\n        globalelems = [5, 6, 3, 4]\n        elemtoelem_expect = [\n            3 4 3 4\n            1 2 3 4\n            2 1 3 4\n            2 1 3 4\n        ]\n        nabrtorank_expect = [1]\n        nabrtorecv_expect = UnitRange{Int}[1:2]\n        nabrtosend_expect = UnitRange{Int}[1:2]\n    end\n\n    @test elems == 1:length(globalelems)\n    @test realelems == 1:nrealelem\n    @test ghostelems == (nrealelem + 1):length(globalelems)\n    @test elemtocoord == globalelemtocoord[:, :, globalelems]\n    @test elemtoface[:, realelems] ==\n          globalelemtoface[:, globalelems[realelems]]\n    @test elemtoelem == elemtoelem_expect\n    @test elemtobndy == globalelemtobndy[:, globalelems]\n    @test elemtoordr == ones(eltype(elemtoordr), size(elemtoordr))\n    @test nabrtorank == nabrtorank_expect\n    @test nabrtorecv == nabrtorecv_expect\n    @test nabrtosend == nabrtosend_expect\n\n    @test collect(realelems) ==\n          sort(union(topology.exteriorelems, topology.interiorelems))\n    @test unique(sort(sendelems)) == topology.exteriorelems\n    @test length(intersect(topology.exteriorelems, topology.interiorelems)) == 0\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/Mesh/mpi_connect_stacked_3d.jl",
    "content": "using Test\nusing MPI\nusing ClimateMachine.Mesh.Topologies\n\nfunction main()\n    MPI.Init()\n\n    comm = MPI.COMM_WORLD\n    crank = MPI.Comm_rank(comm)\n    csize = MPI.Comm_size(comm)\n\n    @assert csize == 2\n\n    topology = StackedBrickTopology(\n        comm,\n        (1:4, 5:8, 9:12),\n        periodicity = (false, true, false),\n        boundary = ((1, 2), (3, 4), (5, 6)),\n        connectivity = :face,\n    )\n\n    elems = topology.elems\n    realelems = topology.realelems\n    ghostelems = topology.ghostelems\n    sendelems = topology.sendelems\n    elemtocoord = topology.elemtocoord\n    elemtoelem = topology.elemtoelem\n    elemtoface = topology.elemtoface\n    elemtoordr = topology.elemtoordr\n    elemtobndy = topology.elemtobndy\n    nabrtorank = topology.nabrtorank\n    nabrtorecv = topology.nabrtorecv\n    nabrtosend = topology.nabrtosend\n\n    globalelemtoface = [\n        1 1 1 2 2 2 2 2 2 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2\n        1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2\n        4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4\n        3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3\n        5 6 6 5 6 6 5 6 6 5 6 6 5 6 6 5 6 6 5 6 6 5 6 6 5 6 6\n        5 5 6 5 5 6 5 5 6 5 5 6 5 5 6 5 5 6 5 5 6 5 5 6 5 5 6\n    ]\n\n    globalelemtoordr = ones(Int, size(globalelemtoface))\n\n    globalelemtocoord = Array{Int}(undef, 3, 8, 27)\n\n    globalelemtocoord[:, :, 1] =\n        [1 2 1 2 1 2 1 2; 5 5 6 6 5 5 6 6; 9 9 9 9 10 10 10 10]\n    globalelemtocoord[:, :, 2] =\n        [1 2 1 2 1 2 1 2; 5 5 6 6 5 5 6 6; 10 10 10 10 11 11 11 11]\n    globalelemtocoord[:, :, 3] =\n        [1 2 1 2 1 2 1 2; 5 5 6 6 5 5 6 6; 11 11 11 11 12 12 12 12]\n    globalelemtocoord[:, :, 4] =\n        [2 3 2 3 2 3 2 3; 5 5 6 6 5 5 6 6; 9 9 9 9 10 10 10 10]\n    globalelemtocoord[:, :, 5] =\n        [2 3 2 3 2 3 2 3; 5 5 6 6 5 5 6 6; 10 10 10 10 11 11 11 11]\n    globalelemtocoord[:, :, 6] =\n        [2 3 2 3 2 3 2 3; 5 5 6 6 5 5 6 6; 11 11 11 11 12 12 12 12]\n    globalelemtocoord[:, :, 7] =\n        [2 3 2 3 2 3 2 3; 6 6 7 7 6 6 7 7; 9 9 9 9 10 10 10 10]\n    globalelemtocoord[:, :, 8] =\n        [2 3 2 3 2 3 2 3; 6 6 7 7 6 6 7 7; 10 10 10 10 11 11 11 11]\n    globalelemtocoord[:, :, 9] =\n        [2 3 2 3 2 3 2 3; 6 6 7 7 6 6 7 7; 11 11 11 11 12 12 12 12]\n    globalelemtocoord[:, :, 10] =\n        [1 2 1 2 1 2 1 2; 6 6 7 7 6 6 7 7; 9 9 9 9 10 10 10 10]\n    globalelemtocoord[:, :, 11] =\n        [1 2 1 2 1 2 1 2; 6 6 7 7 6 6 7 7; 10 10 10 10 11 11 11 11]\n    globalelemtocoord[:, :, 12] =\n        [1 2 1 2 1 2 1 2; 6 6 7 7 6 6 7 7; 11 11 11 11 12 12 12 12]\n    globalelemtocoord[:, :, 13] =\n        [1 2 1 2 1 2 1 2; 7 7 8 8 7 7 8 8; 9 9 9 9 10 10 10 10]\n    globalelemtocoord[:, :, 14] =\n        [1 2 1 2 1 2 1 2; 7 7 8 8 7 7 8 8; 10 10 10 10 11 11 11 11]\n    globalelemtocoord[:, :, 15] =\n        [1 2 1 2 1 2 1 2; 7 7 8 8 7 7 8 8; 11 11 11 11 12 12 12 12]\n    globalelemtocoord[:, :, 16] =\n        [2 3 2 3 2 3 2 3; 7 7 8 8 7 7 8 8; 9 9 9 9 10 10 10 10]\n    globalelemtocoord[:, :, 17] =\n        [2 3 2 3 2 3 2 3; 7 7 8 8 7 7 8 8; 10 10 10 10 11 11 11 11]\n    globalelemtocoord[:, :, 18] =\n        [2 3 2 3 2 3 2 3; 7 7 8 8 7 7 8 8; 11 11 11 11 12 12 12 12]\n    globalelemtocoord[:, :, 19] =\n        [3 4 3 4 3 4 3 4; 7 7 8 8 7 7 8 8; 9 9 9 9 10 10 10 10]\n    globalelemtocoord[:, :, 20] =\n        [3 4 3 4 3 4 3 4; 7 7 8 8 7 7 8 8; 10 10 10 10 11 11 11 11]\n    globalelemtocoord[:, :, 21] =\n        [3 4 3 4 3 4 3 4; 7 7 8 8 7 7 8 8; 11 11 11 11 12 12 12 12]\n    globalelemtocoord[:, :, 22] =\n        [3 4 3 4 3 4 3 4; 6 6 7 7 6 6 7 7; 9 9 9 9 10 10 10 10]\n    globalelemtocoord[:, :, 23] =\n        [3 4 3 4 3 4 3 4; 6 6 7 7 6 6 7 7; 10 10 10 10 11 11 11 11]\n    globalelemtocoord[:, :, 24] =\n        [3 4 3 4 3 4 3 4; 6 6 7 7 6 6 7 7; 11 11 11 11 12 12 12 12]\n    globalelemtocoord[:, :, 25] =\n        [3 4 3 4 3 4 3 4; 5 5 6 6 5 5 6 6; 9 9 9 9 10 10 10 10]\n    globalelemtocoord[:, :, 26] =\n        [3 4 3 4 3 4 3 4; 5 5 6 6 5 5 6 6; 10 10 10 10 11 11 11 11]\n    globalelemtocoord[:, :, 27] =\n        [3 4 3 4 3 4 3 4; 5 5 6 6 5 5 6 6; 11 11 11 11 12 12 12 12]\n\n\n    globalelemtobndy = [\n        1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0\n        0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 2 2 2 2 2 2 2 2\n        0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n        0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n        5 0 0 5 0 0 5 0 0 5 0 0 5 0 0 5 0 0 5 0 0 5 0 0 5 0 0\n        0 0 6 0 0 6 0 0 6 0 0 6 0 0 6 0 0 6 0 0 6 0 0 6 0 0 6\n    ]\n\n    if crank == 0\n        nrealelem = 12\n        globalelems = [\n            1,\n            2,\n            3, # 1\n            4,\n            5,\n            6, # 2\n            7,\n            8,\n            9, # 3\n            10,\n            11,\n            12, # 4\n            13,\n            14,\n            15, # 5\n            16,\n            17,\n            18, # 6\n            22,\n            23,\n            24, # 8\n            25,\n            26,\n            27,\n        ] # 9\n\n        elemtoelem_expect = [\n            1 2 3 1 2 3 10 11 12 4 5 6 7 8 9 16 17 18 19 20 21 22 23 24\n            4 5 6 22 23 24 19 20 21 7 8 9 13 14 15 16 17 18 1 2 3 4 5 6\n            13 14 15 16 17 18 4 5 6 1 2 3 13 14 15 16 17 18 19 20 21 22 23 24\n            10 11 12 7 8 9 16 17 18 13 14 15 13 14 15 16 17 18 19 20 21 22 23 24\n            1 1 2 2 4 5 3 7 8 4 10 11 5 14 15 6 17 18 7 20 21 8 23 24\n            2 3 1 5 6 2 8 9 3 11 12 4 13 14 5 16 17 6 19 20 7 22 23 8\n        ]\n\n        nabrtorank_expect = [1]\n        nabrtorecv_expect = UnitRange{Int}[1:12]\n        nabrtosend_expect = UnitRange{Int}[1:12]\n    elseif crank == 1\n        nrealelem = 15\n\n        globalelems = [\n            13,\n            14,\n            15, # 5\n            16,\n            17,\n            18, # 6\n            19,\n            20,\n            21, # 7\n            22,\n            23,\n            24, # 8\n            25,\n            26,\n            27, # 9\n            1,\n            2,\n            3, # 1\n            4,\n            5,\n            6, # 2\n            7,\n            8,\n            9, # 3\n            10,\n            11,\n            12,\n        ] # 4\n\n        elemtoelem_expect = [\n            1 2 3 1 2 3 4 5 6 22 23 24 19 20 21 4 5 6 19 20 21 22 23 24 7 8 9\n            4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 16 17 18 19 20 21 22 23 24 25 26 27\n            25 26 27 22 23 24 10 11 12 13 14 15 7 8 9 16 17 18 19 20 21 22 23 24 25 26 27\n            16 17 18 19 20 21 13 14 15 7 8 9 10 11 12 16 17 18 19 20 21 22 23 24 25 26 27\n            1 1 2 2 4 5 3 7 8 4 10 11 5 13 14 6 17 18 7 20 21 8 23 24 9 26 27\n            2 3 1 5 6 2 8 9 3 11 12 4 14 15 5 16 17 6 19 20 7 22 23 8 25 26 9\n        ]\n\n        nabrtorank_expect = [0]\n        nabrtorecv_expect = UnitRange{Int}[1:12]\n        nabrtosend_expect = UnitRange{Int}[1:12]\n    end\n\n    @test elems == 1:length(globalelems)\n    @test realelems == 1:nrealelem\n    @test ghostelems == (nrealelem + 1):length(globalelems)\n\n    @test elemtocoord == globalelemtocoord[:, :, globalelems]\n    @test elemtoface[:, realelems] ==\n          globalelemtoface[:, globalelems[realelems]]\n    @test elemtoelem == elemtoelem_expect\n    @test elemtobndy == globalelemtobndy[:, globalelems]\n    @test elemtoordr == ones(eltype(elemtoordr), size(elemtoordr))\n    @test nabrtorank == nabrtorank_expect\n    @test nabrtorecv == nabrtorecv_expect\n    @test nabrtosend == nabrtosend_expect\n\n    @test collect(realelems) ==\n          sort(union(topology.exteriorelems, topology.interiorelems))\n    @test unique(sort(sendelems)) == topology.exteriorelems\n    @test length(intersect(topology.exteriorelems, topology.interiorelems)) == 0\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/Mesh/mpi_connectfull.jl",
    "content": "using Test\nusing MPI\nusing ClimateMachine.Mesh.Topologies\n\nfunction test_connectmeshfull()\n    MPI.Init()\n    comm = MPI.COMM_WORLD\n    crank = MPI.Comm_rank(comm)\n    csize = MPI.Comm_size(comm)\n\n    @assert csize == 3\n\n    topology = BrickTopology(\n        comm,\n        (0:4, 5:9);\n        boundary = ((1, 2), (3, 4)),\n        periodicity = (false, true),\n        connectivity = :full,\n    )\n\n    elems = topology.elems\n    realelems = topology.realelems\n    ghostelems = topology.ghostelems\n    sendelems = topology.sendelems\n    elemtocoord = topology.elemtocoord\n    elemtoelem = topology.elemtoelem\n    elemtoface = topology.elemtoface\n    elemtoordr = topology.elemtoordr\n    elemtobndy = topology.elemtobndy\n    nabrtorank = topology.nabrtorank\n    nabrtorecv = topology.nabrtorecv\n    nabrtosend = topology.nabrtosend\n\n    globalelemtoface = [\n        1 2 2 1 1 1 2 2 2 2 2 2 2 2 2 2\n        1 1 1 1 1 1 1 1 1 1 2 2 2 1 1 2\n        4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4\n        3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3\n    ]\n\n    globalelemtoordr = ones(Int, size(globalelemtoface))\n\n    globalelemtocoord = Array{Int}(undef, 2, 4, 16)\n    globalelemtocoord[:, :, 1] = [0 1 0 1; 5 5 6 6]\n    globalelemtocoord[:, :, 2] = [1 2 1 2; 5 5 6 6]\n    globalelemtocoord[:, :, 3] = [1 2 1 2; 6 6 7 7]\n    globalelemtocoord[:, :, 4] = [0 1 0 1; 6 6 7 7]\n    globalelemtocoord[:, :, 5] = [0 1 0 1; 7 7 8 8]\n    globalelemtocoord[:, :, 6] = [0 1 0 1; 8 8 9 9]\n    globalelemtocoord[:, :, 7] = [1 2 1 2; 8 8 9 9]\n    globalelemtocoord[:, :, 8] = [1 2 1 2; 7 7 8 8]\n    globalelemtocoord[:, :, 9] = [2 3 2 3; 7 7 8 8]\n    globalelemtocoord[:, :, 10] = [2 3 2 3; 8 8 9 9]\n    globalelemtocoord[:, :, 11] = [3 4 3 4; 8 8 9 9]\n    globalelemtocoord[:, :, 12] = [3 4 3 4; 7 7 8 8]\n    globalelemtocoord[:, :, 13] = [3 4 3 4; 6 6 7 7]\n    globalelemtocoord[:, :, 14] = [2 3 2 3; 6 6 7 7]\n    globalelemtocoord[:, :, 15] = [2 3 2 3; 5 5 6 6]\n    globalelemtocoord[:, :, 16] = [3 4 3 4; 5 5 6 6]\n\n    globalelemtobndy = [\n        1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0\n        0 0 0 0 0 0 0 0 0 0 2 2 2 0 0 2\n        0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n        0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n    ]\n\n    if crank == 0\n        nrealelem = 5\n        globalelems = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 14, 15]\n        elemtoelem_expect = [\n            1 1 4 2 3 4 6 5 8 7 3 2\n            2 12 11 3 8 7 10 9 9 10 11 12\n            6 7 2 1 4 5 8 3 11 9 12 10\n            4 3 8 5 6 1 2 7 10 12 9 11\n        ]\n\n        elemtoface_expect = [\n            1 2 2 1 1 1 2 2 2 2 2 2\n            1 1 1 1 1 1 1 1 2 2 2 2\n            4 4 4 4 4 4 4 4 4 4 4 4\n            3 3 3 3 3 3 3 3 3 3 3 3\n        ]\n\n        nabrtorank_expect = [1, 2]\n        nabrtorecv_expect = UnitRange{Int}[1:5, 6:7]\n        nabrtosend_expect = UnitRange{Int}[1:5, 6:7]\n\n    elseif crank == 1\n        nrealelem = 5\n        globalelems = [6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 11, 12, 13, 14, 15, 16]\n        elemtoelem_expect = [\n            1 1 10 3 2 2 6 9 3 4 5 4 14 8 7 15\n            2 5 4 12 11 7 15 14 8 3 1 2 3 13 16 4\n            10 3 8 14 4 1 2 7 6 9 12 13 16 15 5 11\n            6 7 2 5 15 9 8 3 10 1 16 11 12 4 14 13\n        ]\n        elemtoface_expect = [\n            1 2 2 2 2 1 2 2 1 1 2 2 2 2 2 2\n            1 1 1 1 1 1 1 1 1 1 2 2 2 1 1 2\n            4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4\n            3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3\n        ]\n\n        nabrtorank_expect = [0, 2]\n        nabrtorecv_expect = UnitRange{Int}[1:5, 6:11]\n        nabrtosend_expect = UnitRange{Int}[1:5, 6:9]\n\n    elseif crank == 2\n        nrealelem = 6\n        globalelems = [11, 12, 13, 14, 15, 16, 2, 3, 7, 8, 9, 10]\n        elemtoelem_expect = [\n            12 11 4 8 7 5 7 8 9 10 10 9\n            1 2 3 3 6 4 5 4 12 11 2 1\n            2 3 6 5 12 1 9 7 10 8 4 11\n            6 1 2 11 4 3 8 10 7 9 12 5\n        ]\n        elemtoface_expect = [\n            2 2 2 2 2 2 1 1 1 1 2 2\n            2 2 2 1 1 2 1 1 1 1 1 1\n            4 4 4 4 4 4 4 4 4 4 4 4\n            3 3 3 3 3 3 3 3 3 3 3 3\n        ]\n\n        nabrtorank_expect = [0, 1]\n        nabrtorecv_expect = UnitRange{Int}[1:2, 3:6]\n        nabrtosend_expect = UnitRange{Int}[1:2, 3:8]\n    end\n\n    @test elems == 1:length(globalelems)\n    @test realelems == 1:nrealelem\n    @test ghostelems == (nrealelem + 1):length(globalelems)\n    @test elemtocoord == globalelemtocoord[:, :, globalelems]\n    @test elemtoface[:, realelems] ==\n          globalelemtoface[:, globalelems[realelems]]\n    @test elemtoelem == elemtoelem_expect\n    @test elemtobndy == globalelemtobndy[:, globalelems]\n    @test elemtoordr == ones(eltype(elemtoordr), size(elemtoordr))\n    @test nabrtorank == nabrtorank_expect\n    @test nabrtorecv == nabrtorecv_expect\n    @test nabrtosend == nabrtosend_expect\n\n    @test collect(realelems) ==\n          sort(union(topology.exteriorelems, topology.interiorelems))\n    @test unique(sort(sendelems)) == topology.exteriorelems\n    @test length(intersect(topology.exteriorelems, topology.interiorelems)) == 0\nend\n\ntest_connectmeshfull()\n"
  },
  {
    "path": "test/Numerics/Mesh/mpi_getpartition.jl",
    "content": "using Test\nusing MPI\nusing ClimateMachine.Mesh.BrickMesh\nusing Random\n\nfunction main()\n    MPI.Init()\n    comm = MPI.COMM_WORLD\n    crank = MPI.Comm_rank(comm)\n    csize = MPI.Comm_size(comm)\n\n    Nelemtotal = 113\n    Random.seed!(1234)\n    globalcode = randperm(Nelemtotal)\n\n    @assert csize > 1\n\n    bs = [\n        (i == 1) ? (1:0) :\n        BrickMesh.linearpartition(Nelemtotal, i - 1, csize - 1)\n        for i in 1:csize\n    ]\n    as = [BrickMesh.linearpartition(Nelemtotal, i, csize) for i in 1:csize]\n\n    codeb = globalcode[bs[crank + 1]]\n\n    (so, ss, rs) = BrickMesh.getpartition(comm, codeb)\n\n    codeb = codeb[so]\n\n\n    codec = []\n    for r in 0:(csize - 1)\n        sendrange = ss[r + 1]:(ss[r + 2] - 1)\n        rcounts = MPI.Gather(Cint(length(sendrange)), r, comm)\n        c = MPI.Gatherv!(\n            view(codeb, sendrange),\n            crank == r ? VBuffer(similar(codeb, sum(rcounts)), rcounts) :\n            nothing,\n            r,\n            comm,\n        )\n        if r == crank\n            codec = c\n        end\n    end\n\n    codea = (1:Nelemtotal)[as[crank + 1]]\n\n    @test sort(codec) == codea\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/Mesh/mpi_partition.jl",
    "content": "using Test\nusing MPI\nusing ClimateMachine.Mesh.BrickMesh\n\nfunction main()\n    MPI.Init()\n    comm = MPI.COMM_WORLD\n    crank = MPI.Comm_rank(comm)\n    csize = MPI.Comm_size(comm)\n\n    @assert csize == 3\n\n    (etv, etc, etb, fc) = brickmesh(\n        (0:4, 5:9),\n        (false, true),\n        boundary = ((1, 2), (3, 4), (5, 6)),\n        part = crank + 1,\n        numparts = csize,\n    )\n    (etv, etc, etb, fc) = partition(comm, etv, etc, etb, fc)\n\n    if crank == 0\n        etv_expect = [\n            1 2 7 6 11\n            2 3 8 7 12\n            6 7 12 11 16\n            7 8 13 12 17\n        ]\n        @test etv == etv_expect\n        @test etc[:, :, 1] == [0 1 0 1; 5 5 6 6]\n        @test etc[:, :, 2] == [1 2 1 2; 5 5 6 6]\n        @test etc[:, :, 3] == [1 2 1 2; 6 6 7 7]\n        @test etc[:, :, 4] == [0 1 0 1; 6 6 7 7]\n        @test etc[:, :, 5] == [0 1 0 1; 7 7 8 8]\n        etb_expect = [\n            1 0 0 1 1\n            0 0 0 0 0\n            0 0 0 0 0\n            0 0 0 0 0\n        ]\n        @test etb == etb_expect\n        fc_expect = Array{Int64, 1}[]\n        @test fc == fc_expect\n    elseif crank == 1\n        etv_expect = [\n            16 17 12 13 18\n            17 18 13 14 19\n            21 22 17 18 23\n            22 23 18 19 24\n        ]\n        @test etv == etv_expect\n        @test etc[:, :, 1] == [0 1 0 1; 8 8 9 9]\n        @test etc[:, :, 2] == [1 2 1 2; 8 8 9 9]\n        @test etc[:, :, 3] == [1 2 1 2; 7 7 8 8]\n        @test etc[:, :, 4] == [2 3 2 3; 7 7 8 8]\n        @test etc[:, :, 5] == [2 3 2 3; 8 8 9 9]\n        etb_expect = [\n            1 0 0 0 0\n            0 0 0 0 0\n            0 0 0 0 0\n            0 0 0 0 0\n        ]\n        @test etb == etb_expect\n        fc_expect = Array{Int64, 1}[[1, 4, 1, 2], [2, 4, 2, 3], [5, 4, 3, 4]]\n        @test fc == fc_expect\n    elseif crank == 2\n        etv_expect = [\n            19 14 9 8 3 4\n            20 15 10 9 4 5\n            24 19 14 13 8 9\n            25 20 15 14 9 10\n        ]\n        @test etv == etv_expect\n        @test etc[:, :, 1] == [3 4 3 4; 8 8 9 9]\n        @test etc[:, :, 2] == [3 4 3 4; 7 7 8 8]\n        @test etc[:, :, 3] == [3 4 3 4; 6 6 7 7]\n        @test etc[:, :, 4] == [2 3 2 3; 6 6 7 7]\n        @test etc[:, :, 5] == [2 3 2 3; 5 5 6 6]\n        @test etc[:, :, 6] == [3 4 3 4; 5 5 6 6]\n        etb_expect = [\n            0 0 0 0 0 0\n            2 2 2 0 0 2\n            0 0 0 0 0 0\n            0 0 0 0 0 0\n        ]\n        @test etb == etb_expect\n        fc_expect = Array{Int64, 1}[[1, 4, 4, 5]]\n        @test fc == fc_expect\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/Mesh/mpi_sortcolumns.jl",
    "content": "using Random\nusing Test\nusing MPI\nusing ClimateMachine.Mesh.BrickMesh\n\nfunction main()\n    MPI.Init()\n    comm = MPI.COMM_WORLD\n    rank = MPI.Comm_rank(comm)\n\n    Random.seed!(1234)\n    d = 4\n    A = rand(1:10, d, 3rank)\n    B = BrickMesh.parallelsortcolumns(comm, A, rev = true)\n\n    root = 0\n    Acounts = MPI.Gather(Cint(length(A)), root, comm)\n    A_all = MPI.Gatherv!(\n        A,\n        MPI.Comm_rank(comm) == root ?\n        VBuffer(similar(A, sum(Acounts)), Acounts) : nothing,\n        root,\n        comm,\n    )\n\n    Bcounts = MPI.Gather(Cint(length(B)), root, comm)\n    B_all = MPI.Gatherv!(\n        B,\n        MPI.Comm_rank(comm) == root ?\n        VBuffer(similar(B, sum(Bcounts)), Bcounts) : nothing,\n        root,\n        comm,\n    )\n\n    if MPI.Comm_rank(comm) == root\n        A_all = reshape(A_all, d, div(length(A_all), d))\n        B_all = reshape(B_all, d, div(length(B_all), d))\n\n        A_all = sortslices(A_all, dims = 2, rev = true)\n\n        @test A_all == B_all\n    end\nend\n\nmain()\n"
  },
  {
    "path": "test/Numerics/Mesh/topology.jl",
    "content": "using Test\nusing ClimateMachine.Mesh.Topologies\nusing Combinatorics, MPI\n\nMPI.Initialized() || MPI.Init()\n\n@testset \"Equiangular cubed_sphere_warp tests\" begin\n    import ClimateMachine.Mesh.Topologies: equiangular_cubed_sphere_warp\n\n    # Create function alias for shorter formatting\n    eacsw = equiangular_cubed_sphere_warp\n\n    @testset \"check radius\" begin\n        @test hypot(eacsw(3.0, -2.2, 1.3)...) ≈ 3.0 rtol = eps()\n        @test hypot(eacsw(-3.0, -2.2, 1.3)...) ≈ 3.0 rtol = eps()\n        @test hypot(eacsw(1.1, -2.2, 3.0)...) ≈ 3.0 rtol = eps()\n        @test hypot(eacsw(1.1, -2.2, -3.0)...) ≈ 3.0 rtol = eps()\n        @test hypot(eacsw(1.1, 3.0, 0.0)...) ≈ 3.0 rtol = eps()\n        @test hypot(eacsw(1.1, -3.0, 0.0)...) ≈ 3.0 rtol = eps()\n    end\n\n    @testset \"check sign\" begin\n        @test sign.(eacsw(3.0, -2.2, 1.3)) == sign.((3.0, -2.2, 1.3))\n        @test sign.(eacsw(-3.0, -2.2, 1.3)) == sign.((-3.0, -2.2, 1.3))\n        @test sign.(eacsw(1.1, -2.2, 3.0)) == sign.((1.1, -2.2, 3.0))\n        @test sign.(eacsw(1.1, -2.2, -3.0)) == sign.((1.1, -2.2, -3.0))\n        @test sign.(eacsw(1.1, 3.0, 0.0)) == sign.((1.1, 3.0, 0.0))\n        @test sign.(eacsw(1.1, -3.0, 0.0)) == sign.((1.1, -3.0, 0.0))\n    end\n\n    @testset \"check continuity\" begin\n        for (u, v) in zip(\n            permutations([3.0, 2.999999999, 1.3]),\n            permutations([2.999999999, 3.0, 1.3]),\n        )\n            @test all(eacsw(u...) .≈ eacsw(v...))\n        end\n        for (u, v) in zip(\n            permutations([3.0, -2.999999999, 1.3]),\n            permutations([2.999999999, -3.0, 1.3]),\n        )\n            @test all(eacsw(u...) .≈ eacsw(v...))\n        end\n        for (u, v) in zip(\n            permutations([-3.0, 2.999999999, 1.3]),\n            permutations([-2.999999999, 3.0, 1.3]),\n        )\n            @test all(eacsw(u...) .≈ eacsw(v...))\n        end\n        for (u, v) in zip(\n            permutations([-3.0, -2.999999999, 1.3]),\n            permutations([-2.999999999, -3.0, 1.3]),\n        )\n            @test all(eacsw(u...) .≈ eacsw(v...))\n        end\n    end\nend\n\n@testset \"Equiangular cubed_sphere_unwarp tests\" begin\n    import ClimateMachine.Mesh.Topologies:\n        cubed_sphere_warp, equiangular_cubed_sphere_unwarp\n\n    # Create function aliases for shorter formatting\n    eacsw = equiangular_cubed_sphere_warp\n    eacsu = equiangular_cubed_sphere_unwarp\n\n    for u in permutations([3.0, 2.999999999, 1.3])\n        @test all(eacsu(eacsw(u...)...) .≈ u)\n    end\n    for u in permutations([3.0, -2.999999999, 1.3])\n        @test all(eacsu(eacsw(u...)...) .≈ u)\n    end\n    for u in permutations([-3.0, 2.999999999, 1.3])\n        @test all(eacsu(eacsw(u...)...) .≈ u)\n    end\n    for u in permutations([-3.0, -2.999999999, 1.3])\n        @test all(eacsu(eacsw(u...)...) .≈ u)\n    end\nend\n\n@testset \"Equidistant cubed_sphere_warp tests\" begin\n    import ClimateMachine.Mesh.Topologies: equidistant_cubed_sphere_warp\n\n    # Create function alias for shorter formatting\n    edcsw = equidistant_cubed_sphere_warp\n\n    @testset \"check radius\" begin\n        @test hypot(edcsw(3.0, -2.2, 1.3)...) ≈ 3.0 rtol = eps()\n        @test hypot(edcsw(-3.0, -2.2, 1.3)...) ≈ 3.0 rtol = eps()\n        @test hypot(edcsw(1.1, -2.2, 3.0)...) ≈ 3.0 rtol = eps()\n        @test hypot(edcsw(1.1, -2.2, -3.0)...) ≈ 3.0 rtol = eps()\n        @test hypot(edcsw(1.1, 3.0, 0.0)...) ≈ 3.0 rtol = eps()\n        @test hypot(edcsw(1.1, -3.0, 0.0)...) ≈ 3.0 rtol = eps()\n    end\n\n    @testset \"check sign\" begin\n        @test sign.(edcsw(3.0, -2.2, 1.3)) == sign.((3.0, -2.2, 1.3))\n        @test sign.(edcsw(-3.0, -2.2, 1.3)) == sign.((-3.0, -2.2, 1.3))\n        @test sign.(edcsw(1.1, -2.2, 3.0)) == sign.((1.1, -2.2, 3.0))\n        @test sign.(edcsw(1.1, -2.2, -3.0)) == sign.((1.1, -2.2, -3.0))\n        @test sign.(edcsw(1.1, 3.0, 0.0)) == sign.((1.1, 3.0, 0.0))\n        @test sign.(edcsw(1.1, -3.0, 0.0)) == sign.((1.1, -3.0, 0.0))\n    end\n\n    @testset \"check continuity\" begin\n        for (u, v) in zip(\n            permutations([3.0, 2.999999999, 1.3]),\n            permutations([2.999999999, 3.0, 1.3]),\n        )\n            @test all(edcsw(u...) .≈ edcsw(v...))\n        end\n        for (u, v) in zip(\n            permutations([3.0, -2.999999999, 1.3]),\n            permutations([2.999999999, -3.0, 1.3]),\n        )\n            @test all(edcsw(u...) .≈ edcsw(v...))\n        end\n        for (u, v) in zip(\n            permutations([-3.0, 2.999999999, 1.3]),\n            permutations([-2.999999999, 3.0, 1.3]),\n        )\n            @test all(edcsw(u...) .≈ edcsw(v...))\n        end\n        for (u, v) in zip(\n            permutations([-3.0, -2.999999999, 1.3]),\n            permutations([-2.999999999, -3.0, 1.3]),\n        )\n            @test all(edcsw(u...) .≈ edcsw(v...))\n        end\n    end\nend\n\n@testset \"Equidistant cubed_sphere_unwarp tests\" begin\n    import ClimateMachine.Mesh.Topologies:\n        equidistant_cubed_sphere_warp, equidistant_cubed_sphere_unwarp\n\n    # Create function aliases for shorter formatting\n    edcsw = equidistant_cubed_sphere_warp\n    edcsu = equidistant_cubed_sphere_unwarp\n\n    for u in permutations([3.0, 2.999999999, 1.3])\n        @test all(edcsu(edcsw(u...)...) .≈ u)\n    end\n    for u in permutations([3.0, -2.999999999, 1.3])\n        @test all(edcsu(edcsw(u...)...) .≈ u)\n    end\n    for u in permutations([-3.0, 2.999999999, 1.3])\n        @test all(edcsu(edcsw(u...)...) .≈ u)\n    end\n    for u in permutations([-3.0, -2.999999999, 1.3])\n        @test all(edcsu(edcsw(u...)...) .≈ u)\n    end\nend\n\n@testset \"Conformal cubed_sphere_warp tests\" begin\n    import ClimateMachine.Mesh.Topologies: conformal_cubed_sphere_warp\n\n    # Create function alias for shorter formatting\n    ccsw = conformal_cubed_sphere_warp\n\n    @testset \"check radius\" begin\n        @test hypot(ccsw(3.0, -2.2, 1.3)...) ≈ 3.0 rtol = eps()\n        @test hypot(ccsw(-3.0, -2.2, 1.3)...) ≈ 3.0 rtol = eps()\n        @test hypot(ccsw(1.1, -2.2, 3.0)...) ≈ 3.0 rtol = eps()\n        @test hypot(ccsw(1.1, -2.2, -3.0)...) ≈ 3.0 rtol = eps()\n        @test hypot(ccsw(1.1, 3.0, 0.0)...) ≈ 3.0 rtol = eps()\n        @test hypot(ccsw(1.1, -3.0, 0.0)...) ≈ 3.0 rtol = eps()\n    end\n\n    @testset \"check sign\" begin\n        @test sign.(ccsw(3.0, -2.2, 1.3)) == sign.((3.0, -2.2, 1.3))\n        @test sign.(ccsw(-3.0, -2.2, 1.3)) == sign.((-3.0, -2.2, 1.3))\n        @test sign.(ccsw(1.1, -2.2, 3.0)) == sign.((1.1, -2.2, 3.0))\n        @test sign.(ccsw(1.1, -2.2, -3.0)) == sign.((1.1, -2.2, -3.0))\n        @test sign.(ccsw(1.1, 3.0, -2.2)) == sign.((1.1, 3.0, -2.2))\n        @test sign.(ccsw(1.1, -3.0, -2.2)) == sign.((1.1, -3.0, -2.2))\n    end\n\n    @testset \"check continuity\" begin\n        for (u, v) in zip(\n            permutations([3.0, 2.999999999, 1.3]),\n            permutations([2.999999999, 3.0, 1.3]),\n        )\n            @test all(ccsw(u...) .≈ ccsw(v...))\n        end\n        for (u, v) in zip(\n            permutations([3.0, -2.999999999, 1.3]),\n            permutations([2.999999999, -3.0, 1.3]),\n        )\n            @test all(ccsw(u...) .≈ ccsw(v...))\n        end\n        for (u, v) in zip(\n            permutations([-3.0, 2.999999999, 1.3]),\n            permutations([-2.999999999, 3.0, 1.3]),\n        )\n            @test all(ccsw(u...) .≈ ccsw(v...))\n        end\n        for (u, v) in zip(\n            permutations([-3.0, -2.999999999, 1.3]),\n            permutations([-2.999999999, -3.0, 1.3]),\n        )\n            @test all(ccsw(u...) .≈ ccsw(v...))\n        end\n    end\nend\n\n@testset \"Conformal cubed_sphere_unwarp tests\" begin\n    import ClimateMachine.Mesh.Topologies:\n        conformal_cubed_sphere_warp, conformal_cubed_sphere_unwarp\n\n    # Create function aliases for shorter formatting\n    ccsw = conformal_cubed_sphere_warp\n    ccsu = conformal_cubed_sphere_unwarp\n\n    for u in permutations([3.0, 2.999999999, 1.3])\n        @test all(ccsu(ccsw(u...)...) .≈ u)\n    end\n    for u in permutations([3.0, -2.999999999, 1.3])\n        @test all(ccsu(ccsw(u...)...) .≈ u)\n    end\n    for u in permutations([-3.0, 2.999999999, 1.3])\n        @test all(ccsu(ccsw(u...)...) .≈ u)\n    end\n    for u in permutations([-3.0, -2.999999999, 1.3])\n        @test all(ccsu(ccsw(u...)...) .≈ u)\n    end\nend\n\n@testset \"grid1d\" begin\n    g = grid1d(0, 10, nelem = 10)\n    @test eltype(g) == Float64\n    @test length(g) == 11\n    @test g[1] == 0\n    @test g[end] == 10\n\n    g = grid1d(10.0f0, 20.0f0, elemsize = 0.1)\n    @test eltype(g) == Float32\n    @test length(g) == 101\n    @test g[1] == 10\n    @test g[end] == 20\n\n    g = grid1d(10.0f0, 20.0f0, InteriorStretching(0), elemsize = 0.1)\n    @test eltype(g) == Float32\n    @test length(g) == 101\n    @test g[1] == 10\n    @test g[end] == 20\n\n    g = grid1d(\n        10.0f0,\n        20.0f0,\n        SingleExponentialStretching(2.5f0),\n        elemsize = 0.1,\n    )\n    @test eltype(g) == Float32\n    @test length(g) == 101\n    @test g[1] == 10\n    @test g[end] == 20\nend\n\n@testset \"BrickTopology tests\" begin\n\n    let\n        comm = MPI.COMM_SELF\n\n\n        elemrange = (0:10,)\n        periodicity = (true,)\n\n        topology = BrickTopology(\n            comm,\n            elemrange,\n            periodicity = periodicity,\n            connectivity = :face,\n        )\n\n        nelem = length(elemrange[1]) - 1\n\n        for e in 1:nelem\n            @test topology.elemtocoord[:, :, e] == [e - 1 e]\n        end\n\n        @test topology.elemtoelem ==\n              [nelem collect(1:(nelem - 1))'; collect(2:nelem)' 1]\n        @test topology.elemtoface == repeat(2:-1:1, outer = (1, nelem))\n\n        @test topology.elemtoordr == ones(Int, size(topology.elemtoordr))\n        @test topology.elemtobndy == zeros(Int, size(topology.elemtoordr))\n\n        @test topology.elems == 1:nelem\n        @test topology.realelems == 1:nelem\n        @test topology.ghostelems == nelem .+ (1:0)\n\n        @test length(topology.sendelems) == 0\n        @test length(topology.exteriorelems) == 0\n        @test collect(topology.realelems) == topology.interiorelems\n\n        @test topology.nabrtorank == Int[]\n        @test topology.nabrtorecv == UnitRange{Int}[]\n        @test topology.nabrtosend == UnitRange{Int}[]\n    end\n\n    let\n        comm = MPI.COMM_SELF\n        topology = BrickTopology(\n            comm,\n            (0:4, 5:9),\n            periodicity = (false, true),\n            connectivity = :face,\n        )\n\n        nelem = 16\n\n        @test topology.elemtocoord[:, :, 1] == [0 1 0 1; 5 5 6 6]\n        @test topology.elemtocoord[:, :, 2] == [1 2 1 2; 5 5 6 6]\n        @test topology.elemtocoord[:, :, 3] == [1 2 1 2; 6 6 7 7]\n        @test topology.elemtocoord[:, :, 4] == [0 1 0 1; 6 6 7 7]\n        @test topology.elemtocoord[:, :, 5] == [0 1 0 1; 7 7 8 8]\n        @test topology.elemtocoord[:, :, 6] == [0 1 0 1; 8 8 9 9]\n        @test topology.elemtocoord[:, :, 7] == [1 2 1 2; 8 8 9 9]\n        @test topology.elemtocoord[:, :, 8] == [1 2 1 2; 7 7 8 8]\n        @test topology.elemtocoord[:, :, 9] == [2 3 2 3; 7 7 8 8]\n        @test topology.elemtocoord[:, :, 10] == [2 3 2 3; 8 8 9 9]\n        @test topology.elemtocoord[:, :, 11] == [3 4 3 4; 8 8 9 9]\n        @test topology.elemtocoord[:, :, 12] == [3 4 3 4; 7 7 8 8]\n        @test topology.elemtocoord[:, :, 13] == [3 4 3 4; 6 6 7 7]\n        @test topology.elemtocoord[:, :, 14] == [2 3 2 3; 6 6 7 7]\n        @test topology.elemtocoord[:, :, 15] == [2 3 2 3; 5 5 6 6]\n        @test topology.elemtocoord[:, :, 16] == [3 4 3 4; 5 5 6 6]\n\n        @test topology.elemtoelem == [\n            1 1 4 2 3 4 6 5 8 7 10 9 14 3 2 15\n            2 15 14 3 8 7 10 9 12 11 5 6 7 13 16 8\n            6 7 2 1 4 5 8 3 14 9 12 13 16 15 10 11\n            4 3 8 5 6 1 2 7 10 15 16 11 12 9 14 13\n        ]\n\n        @test topology.elemtoface == [\n            1 2 2 1 1 1 2 2 2 2 2 2 2 2 2 2\n            1 1 1 1 1 1 1 1 1 1 2 2 2 1 1 2\n            4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4\n            3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3\n        ]\n\n        @test topology.elemtoordr == ones(Int, size(topology.elemtoordr))\n\n        @test topology.elemtoelem[topology.elemtobndy .== 1] == 1:8\n\n        @test topology.elems == 1:nelem\n        @test topology.realelems == 1:nelem\n        @test topology.ghostelems == nelem .+ (1:0)\n\n        @test length(topology.sendelems) == 0\n        @test length(topology.exteriorelems) == 0\n        @test collect(topology.realelems) == topology.interiorelems\n\n        @test topology.nabrtorank == Int[]\n        @test topology.nabrtorecv == UnitRange{Int}[]\n        @test topology.nabrtosend == UnitRange{Int}[]\n    end\n\n    let\n        comm = MPI.COMM_SELF\n        for px in (true, false)\n            topology = BrickTopology(\n                comm,\n                (0:10,),\n                periodicity = (px,),\n                connectivity = :face,\n            )\n            @test Topologies.hasboundary(topology) == !px\n            if px\n                @test topology.bndytoelem == ()\n                @test topology.bndytoface == ()\n            else\n                @test topology.bndytoelem == ([1, 10],)\n                @test topology.bndytoface == ([1, 2],)\n            end\n        end\n        for py in (true, false), px in (true, false)\n            topology = BrickTopology(\n                comm,\n                (0:10, 0:3),\n                periodicity = (px, py),\n                connectivity = :face,\n            )\n            @test Topologies.hasboundary(topology) == !(px && py)\n            if px && py\n                @test topology.bndytoelem == ()\n                @test topology.bndytoface == ()\n            else\n                @test sort(unique(topology.bndytoface[1])) == vcat(\n                    px ? Int64[] : Int64[1, 2],\n                    py ? Int64[] : Int64[3, 4],\n                )\n            end\n        end\n        for pz in (true, false), py in (true, false), px in (true, false)\n            topology = BrickTopology(\n                comm,\n                (0:10, 0:3, -1:3),\n                periodicity = (px, py, pz),\n                connectivity = :face,\n            )\n            @test Topologies.hasboundary(topology) == !(px && py && pz)\n            if px && py && pz\n                @test topology.bndytoelem == ()\n                @test topology.bndytoface == ()\n            else\n                @test sort(unique(topology.bndytoface[1])) == vcat(\n                    px ? Int64[] : Int64[1, 2],\n                    py ? Int64[] : Int64[3, 4],\n                    pz ? Int64[] : Int64[5, 6],\n                )\n            end\n        end\n    end\nend\n\n@testset \"StackedBrickTopology tests\" begin\n    let\n        comm = MPI.COMM_SELF\n        topology = StackedBrickTopology(\n            comm,\n            (2:5, 4:6),\n            periodicity = (false, true),\n            boundary = ((1, 2), (3, 4)),\n            connectivity = :face,\n        )\n\n        nelem = 6\n\n        @test topology.elemtocoord[:, :, 1] == [2 3 2 3; 4 4 5 5]\n        @test topology.elemtocoord[:, :, 2] == [2 3 2 3; 5 5 6 6]\n        @test topology.elemtocoord[:, :, 3] == [3 4 3 4; 4 4 5 5]\n        @test topology.elemtocoord[:, :, 4] == [3 4 3 4; 5 5 6 6]\n        @test topology.elemtocoord[:, :, 5] == [4 5 4 5; 4 4 5 5]\n        @test topology.elemtocoord[:, :, 6] == [4 5 4 5; 5 5 6 6]\n\n        @test topology.elemtoelem == [\n            1 2 1 2 3 4\n            3 4 5 6 1 2\n            2 1 4 3 6 5\n            2 1 4 3 6 5\n        ]\n\n        @test topology.elemtoface == [\n            1 1 2 2 2 2\n            1 1 1 1 2 2\n            4 4 4 4 4 4\n            3 3 3 3 3 3\n        ]\n\n        @test topology.elemtoordr == ones(Int, size(topology.elemtoordr))\n\n        @test topology.elemtobndy == [\n            1 1 0 0 0 0\n            0 0 0 0 2 2\n            0 0 0 0 0 0\n            0 0 0 0 0 0\n        ]\n\n        @test topology.elemtoelem[topology.elemtobndy .== 1] == 1:2\n        @test topology.elemtoelem[topology.elemtobndy .== 2] == 1:2\n\n        @test topology.elems == 1:nelem\n        @test topology.realelems == 1:nelem\n        @test topology.ghostelems == nelem .+ (1:0)\n\n        @test length(topology.sendelems) == 0\n        @test length(topology.exteriorelems) == 0\n        @test collect(topology.realelems) == topology.interiorelems\n\n        @test topology.nabrtorank == Int[]\n        @test topology.nabrtorecv == UnitRange{Int}[]\n        @test topology.nabrtosend == UnitRange{Int}[]\n\n        @test topology.bndytoelem == ([1, 2], [5, 6])\n        @test topology.bndytoface == ([1, 1], [2, 2])\n    end\n    let\n        comm = MPI.COMM_SELF\n        for py in (true, false), px in (true, false)\n            topology = StackedBrickTopology(\n                comm,\n                (0:10, 0:3),\n                periodicity = (px, py),\n                connectivity = :face,\n            )\n            @test Topologies.hasboundary(topology) == !(px && py)\n            if px && py\n                @test topology.bndytoelem == ()\n                @test topology.bndytoface == ()\n            else\n                @test sort(unique(topology.bndytoface[1])) == vcat(\n                    px ? Int64[] : Int64[1, 2],\n                    py ? Int64[] : Int64[3, 4],\n                )\n            end\n        end\n        for pz in (true, false), py in (true, false), px in (true, false)\n            topology = StackedBrickTopology(\n                comm,\n                (0:10, 0:3, -1:3),\n                periodicity = (px, py, pz),\n                connectivity = :face,\n            )\n            @test Topologies.hasboundary(topology) == !(px && py && pz)\n            if px && py && pz\n                @test topology.bndytoelem == ()\n                @test topology.bndytoface == ()\n            else\n                @test sort(unique(topology.bndytoface[1])) == vcat(\n                    px ? Int64[] : Int64[1, 2],\n                    py ? Int64[] : Int64[3, 4],\n                    pz ? Int64[] : Int64[5, 6],\n                )\n            end\n        end\n    end\nend\n\n@testset \"StackedCubedSphereTopology tests\" begin\n    topology = StackedCubedSphereTopology(\n        MPI.COMM_SELF,\n        3,\n        1.0:3.0,\n        boundary = (2, 1),\n        connectivity = :face,\n    )\n    @test Topologies.hasboundary(topology)\n    @test map(unique, topology.bndytoface) == ([6], [5])\nend\n\n@testset \"CubedShellTopology tests\" begin\n    topology =\n        CubedShellTopology(MPI.COMM_SELF, 3, Float64, connectivity = :face)\n    @test !Topologies.hasboundary(topology)\nend\n"
  },
  {
    "path": "test/Numerics/ODESolvers/callbacks.jl",
    "content": "using MPI\nusing Test\nusing ClimateMachine.ODESolvers: AbstractODESolver\nusing ClimateMachine.GenericCallbacks\n\nmutable struct PseudoSolver <: AbstractODESolver\n    t::Float64\n    steps::Int\n    PseudoSolver() = new(42.0, 0)\nend\ngettime(ps::PseudoSolver) = ps.t\n\n\nmutable struct MyCallback\n    initialized::Bool\n    calls::Int\n    finished::Bool\nend\nMyCallback() = MyCallback(false, 0, false)\n\nGenericCallbacks.init!(cb::MyCallback, _...) = cb.initialized = true\nGenericCallbacks.call!(cb::MyCallback, _...) = (cb.calls += 1; nothing)\nGenericCallbacks.fini!(cb::MyCallback, _...) = cb.finished = true\n\nMPI.Init()\nmpicomm = MPI.COMM_WORLD\nps = PseudoSolver()\n\nwtcb = GenericCallbacks.EveryXWallTimeSeconds(MyCallback(), 2, mpicomm)\nstcb = GenericCallbacks.EveryXSimulationTime(MyCallback(), 0.5)\nsscb = GenericCallbacks.EveryXSimulationSteps(MyCallback(), 10)\n\nfn_calls = 0\nfncb = () -> (global fn_calls += 1)\n\nwtfn_calls = 0\nwtfncb = GenericCallbacks.EveryXWallTimeSeconds(\n    AtInit(() -> global wtfn_calls += 1),\n    2,\n    mpicomm,\n)\n\nstfn_calls = 0\nstfncb = GenericCallbacks.EveryXSimulationTime(\n    AtInitAndFini(() -> global stfn_calls += 1),\n    0.5,\n)\n\nssfn_calls = 0\nssfncb = GenericCallbacks.EveryXSimulationSteps(\n    AtInit(() -> global ssfn_calls += 1),\n    5,\n)\n\ncallbacks = ((wtcb, stcb, sscb), fncb, (wtfncb, stfncb, ssfncb))\n\n@testset \"GenericCallbacks\" begin\n    GenericCallbacks.init!(callbacks, ps, nothing, nothing, ps.t)\n\n    @test wtcb.callback.initialized\n    @test stcb.callback.initialized\n    @test sscb.callback.initialized\n\n    @test wtcb.callback.calls == 0\n    @test stcb.callback.calls == 0\n    @test sscb.callback.calls == 0\n    @test fn_calls == 0\n    @test wtfn_calls >= 1\n    @test stfn_calls == 1\n    @test ssfn_calls == 1\n\n    @test GenericCallbacks.call!(callbacks, ps, nothing, nothing, ps.t) in\n          (0, nothing)\n\n    @test wtcb.callback.calls >= 0\n    @test stcb.callback.calls == 0\n    @test sscb.callback.calls == 0\n    @test fn_calls == 1\n    @test wtfn_calls >= 1\n    @test stfn_calls == 1\n    @test ssfn_calls == 1\n\n    ps.t += 0.5\n    @test GenericCallbacks.call!(callbacks, ps, nothing, nothing, ps.t) in\n          (0, nothing)\n\n    @test wtcb.callback.calls >= 0\n    @test stcb.callback.calls == 1\n    @test sscb.callback.calls == 0\n    @test fn_calls == 2\n    @test wtfn_calls >= 1\n    @test stfn_calls == 2\n    @test ssfn_calls == 1\n\n    sleep(max((2.1 + wtcb.lastcbtime_ns / 1e9 - time_ns() / 1e9), 0.0))\n    ps.t += 0.5\n    @test GenericCallbacks.call!(callbacks, ps, nothing, nothing, ps.t) in\n          (0, nothing)\n\n    @test wtcb.callback.calls >= 1\n    @test stcb.callback.calls == 2\n    @test sscb.callback.calls == 0\n    @test fn_calls == 3\n    @test wtfn_calls >= 2\n    @test stfn_calls == 3\n    @test ssfn_calls == 1\n\n    for i in 1:7\n        @test GenericCallbacks.call!(callbacks, ps, nothing, nothing, ps.t) in\n              (0, nothing)\n    end\n\n    @test wtcb.callback.calls >= 1\n    @test stcb.callback.calls == 2\n    @test sscb.callback.calls == 1\n    @test fn_calls == 10\n    @test wtfn_calls >= 2\n    @test stfn_calls == 3\n    @test ssfn_calls == 3\n\n    GenericCallbacks.fini!(callbacks, ps, nothing, nothing, ps.t)\n    @test wtcb.callback.finished\n    @test stcb.callback.finished\n    @test sscb.callback.finished\n    @test wtfn_calls >= 2\n    @test stfn_calls == 4\n    @test ssfn_calls == 3\nend\n"
  },
  {
    "path": "test/Numerics/ODESolvers/ode_tests_basic.jl",
    "content": "using Test\nusing ClimateMachine\nusing LinearAlgebra\nimport OrdinaryDiffEq: SSPRK73\n\ninclude(\"ode_tests_common.jl\")\n\nClimateMachine.init()\nconst ArrayType = ClimateMachine.array_type()\n\na = 100\nb = 1\nc = 1 / 100\nΔ = sqrt(4 * a * c - b^2)\n\nα1, α2 = 1 / 4, 3 / 4\nβ1, β2, β3 = 1 / 3, 3 / 6, 1 / 6\n\nfunction rhs!(dQ, Q, ::Nothing, t; increment = false)\n    if increment\n        @. dQ += $cos(t) * (a + b * Q + c * Q^2)\n    else\n        @. dQ = $cos(t) * (a + b * Q + c * Q^2)\n    end\nend\nfunction rhs_linear!(dQ, Q, ::Nothing, t; increment = false)\n    if increment\n        @. dQ += $cos(t) * b * Q\n    else\n        @. dQ = $cos(t) * b * Q\n    end\nend\nstruct ODETestBasicLinBE <: AbstractBackwardEulerSolver end\nODESolvers.Δt_is_adjustable(::ODETestBasicLinBE) = true\n(::ODETestBasicLinBE)(Q, Qhat, α, p, t) = @. Q = Qhat / (1 - α * $cos(t) * b)\nfunction rhs_nonlinear!(dQ, Q, ::Nothing, t; increment = false)\n    if increment\n        @. dQ += $cos(t) * (a + c * Q^2)\n    else\n        @. dQ = $cos(t) * (a + c * Q^2)\n    end\nend\nfunction rhs_fast!(dQ, Q, ::Any, t; increment = false)\n    if increment\n        @. dQ += α1 * $cos(t) * (a + b * Q + c * Q^2)\n    else\n        @. dQ = α1 * $cos(t) * (a + b * Q + c * Q^2)\n    end\nend\nfunction rhs_fast_linear!(dQ, Q, ::Nothing, t; increment = false)\n    if increment\n        @. dQ += α1 * $cos(t) * Q\n    else\n        @. dQ = α1 * $cos(t) * Q\n    end\nend\nfunction rhs_slow!(dQ, Q, ::Nothing, t; increment = false)\n    if increment\n        @. dQ += α2 * $cos(t) * (a + b * Q + c * Q^2)\n    else\n        @. dQ = α2 * $cos(t) * (a + b * Q + c * Q^2)\n    end\nend\nfunction rhs1!(dQ, Q, ::Any, t; increment = false)\n    if increment\n        @. dQ += β1 * $cos(t) * (a + b * Q + c * Q^2)\n    else\n        @. dQ = β1 * $cos(t) * (a + b * Q + c * Q^2)\n    end\nend\nfunction rhs2!(dQ, Q, ::Any, t; increment = false)\n    if increment\n        @. dQ += β2 * $cos(t) * (a + b * Q + c * Q^2)\n    else\n        @. dQ = β2 * $cos(t) * (a + b * Q + c * Q^2)\n    end\nend\nfunction rhs3!(dQ, Q, ::Nothing, t; increment = false)\n    if increment\n        @. dQ += β3 * $cos(t) * (a + b * Q + c * Q^2)\n    else\n        @. dQ = β3 * $cos(t) * (a + b * Q + c * Q^2)\n    end\nend\n\nfunction exactsolution(t, q0, t0)\n    k = @. 2 * atan((2 * c * q0 + b) / Δ) / Δ - sin(t0)\n    solution = @. (Δ * tan((k + sin(t)) * Δ / 2) - b) / (2 * c)\n    return ArrayType(solution)\nend\n\nq0 = ArrayType === Array ? [1.0] : range(-1.0, 1.0, length = 303)\nt0 = 0.1\nfinaltime = 1.2\n\nQinit = exactsolution(t0, q0, t0)\nQ = similar(Qinit)\nQexact = exactsolution(finaltime, q0, t0)\n\n@testset \"Convergence/limited\" begin\n    @testset \"Explicit methods\" begin\n        dts = [2.0^(-k) for k in 3:4]\n        errors = similar(dts)\n        for (method, expected_order) in explicit_methods\n            for (n, dt) in enumerate(dts)\n                Q .= Qinit\n                solver = method(rhs!, Q; dt = dt, t0 = t0)\n                solve!(Q, solver; timeend = finaltime)\n                errors[n] = norm(Q - Qexact)\n            end\n            rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n            @test isapprox(rates[end], expected_order; atol = 0.7)\n        end\n    end\n\n    @testset \"IMEX methods (LowStorageVariant)\" begin\n        dts = [2.0^(-k) for k in 4:5]\n        errors = similar(dts)\n        for (method, order) in imex_methods_lowstorage_compatible\n            for split_explicit_implicit in (false, true)\n                for (n, dt) in enumerate(dts)\n                    Q .= Qinit\n                    rhs_arg! = split_explicit_implicit ? rhs_nonlinear! : rhs!\n                    solver = method(\n                        rhs_arg!,\n                        rhs_linear!,\n                        LinearBackwardEulerSolver(\n                            DivideLinearSolver();\n                            isadjustable = true,\n                        ),\n                        Q;\n                        dt = dt,\n                        t0 = t0,\n                        split_explicit_implicit = split_explicit_implicit,\n                        variant = LowStorageVariant(),\n                    )\n                    solve!(Q, solver; timeend = finaltime)\n                    errors[n] = norm(Q - Qexact)\n                end\n                rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n                if split_explicit_implicit\n                    if method === ARK1ForwardBackwardEuler\n                        expected_order = 1\n                    else\n                        expected_order = 2\n                    end\n                else\n                    expected_order = order\n                end\n                @test isapprox(rates[end], expected_order; rtol = 0.3)\n            end\n        end\n    end\n\n    @testset \"IMEX methods (NaiveVariant)\" begin\n        dts = [2.0^(-k) for k in 4:5]\n        errors = similar(dts)\n        for (method, expected_order) in imex_methods_naivestorage_compatible\n            for split_explicit_implicit in (false, true)\n                for (n, dt) in enumerate(dts)\n                    Q .= Qinit\n                    rhs_arg! = split_explicit_implicit ? rhs_nonlinear! : rhs!\n                    solver = method(\n                        rhs_arg!,\n                        rhs_linear!,\n                        LinearBackwardEulerSolver(\n                            DivideLinearSolver();\n                            isadjustable = true,\n                        ),\n                        Q;\n                        dt = dt,\n                        t0 = t0,\n                        split_explicit_implicit = split_explicit_implicit,\n                        variant = NaiveVariant(),\n                    )\n                    solve!(Q, solver; timeend = finaltime)\n                    errors[n] = norm(Q - Qexact)\n                end\n                rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n                @test isapprox(rates[end], expected_order; rtol = 0.3)\n            end\n        end\n    end\n\n    @testset \"IMEX methods with direct solver\" begin\n        dts = [2.0^(-k) for k in 4:5]\n        errors = similar(dts)\n        for (method, order) in imex_methods_naivestorage_compatible\n            for split_explicit_implicit in (false, true)\n                for (n, dt) in enumerate(dts)\n                    Q .= Qinit\n                    rhs_arg! = split_explicit_implicit ? rhs_nonlinear! : rhs!\n                    solver = method(\n                        rhs_arg!,\n                        rhs_linear!,\n                        ODETestBasicLinBE(),\n                        Q;\n                        dt = dt,\n                        t0 = t0,\n                        split_explicit_implicit = split_explicit_implicit,\n                        variant = NaiveVariant(),\n                    )\n                    solve!(Q, solver; timeend = finaltime)\n                    errors[n] = norm(Q - Qexact)\n                end\n                rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n                expected_order = order\n                @test isapprox(rates[end], expected_order; rtol = 0.3)\n            end\n        end\n    end\n\n    @testset \"MRRK methods with 2 rates\" begin\n        dts = [2.0^(-k) for k in 3:4]\n        errors = similar(dts)\n        for (slow_method, slow_expected_order) in slow_mrrk_methods\n            for (fast_method, fast_expected_order) in fast_mrrk_methods\n                for nsubsteps in (1, 3)\n                    for (n, dt) in enumerate(dts)\n                        Q .= Qinit\n                        solver = MultirateRungeKutta(\n                            (\n                                slow_method(rhs_slow!, Q; dt = dt),\n                                fast_method(rhs_fast!, Q; dt = dt / nsubsteps),\n                            );\n                            t0 = t0,\n                        )\n                        solve!(Q, solver; timeend = finaltime)\n                        errors[n] = norm(Q - Qexact)\n                    end\n\n                    rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n                    min_order = min(slow_expected_order, fast_expected_order)\n                    max_order = max(slow_expected_order, fast_expected_order)\n                    @test (\n                        isapprox(rates[end], min_order; atol = 0.5) ||\n                        isapprox(rates[end], max_order; atol = 0.5) ||\n                        min_order <= rates[end] <= max_order\n                    )\n                end\n            end\n        end\n    end\n\n    @testset \"MRRK methods with IMEX\" begin\n        dts = [2.0^(-k) for k in 3:4]\n        errors = similar(dts)\n        for (slow_method, slow_expected_order) in slow_mrrk_methods\n            for (fast_method, fast_expected_order) in\n                imex_methods_lowstorage_compatible\n                for (n, dt) in enumerate(dts)\n                    Q .= Qinit\n                    solver = MultirateRungeKutta(\n                        (\n                            slow_method(rhs_slow!, Q; dt = dt),\n                            fast_method(\n                                rhs_fast!,\n                                rhs_fast_linear!,\n                                LinearBackwardEulerSolver(\n                                    DivideLinearSolver();\n                                    isadjustable = true,\n                                ),\n                                Q;\n                                dt = dt,\n                                split_explicit_implicit = false,\n                                variant = LowStorageVariant(),\n                            ),\n                        ),\n                        t0 = t0,\n                    )\n                    solve!(Q, solver; timeend = finaltime)\n                    errors[n] = norm(Q - Qexact)\n                end\n                rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n                min_order = min(slow_expected_order, fast_expected_order)\n                max_order = max(slow_expected_order, fast_expected_order)\n                @test (\n                    isapprox(rates[end], min_order; atol = 0.5) ||\n                    isapprox(rates[end], max_order; atol = 0.5) ||\n                    min_order <= rates[end] <= max_order\n                )\n            end\n        end\n    end\n\n    @testset \"MRRK methods with 3 rates\" begin\n        dts = [2.0^(-k) for k in 3:4]\n        errors = similar(dts)\n        for (rate3_method, rate3_order) in slow_mrrk_methods\n            for (rate2_method, rate2_order) in slow_mrrk_methods\n                for (rate1_method, rate1_order) in fast_mrrk_methods\n                    for nsubsteps in (1, 2)\n                        for (n, dt) in enumerate(dts)\n                            Q .= Qinit\n                            solver = MultirateRungeKutta(\n                                (\n                                    rate3_method(rhs3!, Q, dt = dt),\n                                    rate2_method(rhs2!, Q, dt = dt / nsubsteps),\n                                    rate1_method(\n                                        rhs1!,\n                                        Q;\n                                        dt = dt / nsubsteps^2,\n                                    ),\n                                );\n                                dt = dt,\n                                t0 = t0,\n                            )\n                            solve!(Q, solver; timeend = finaltime)\n                            errors[n] = norm(Q - Qexact)\n                        end\n                        rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n                        @test 3.8 <= rates[end]\n                    end\n                end\n            end\n        end\n    end\n\n    @testset \"MIS methods\" begin\n        dts = [2.0^(-k) for k in 3:4]\n        errors = similar(dts)\n        for (method, expected_order) in mis_methods\n            for fast_method in (LSRK54CarpenterKennedy,)\n                for (n, dt) in enumerate(dts)\n                    Q .= Qinit\n                    solver = method(\n                        rhs_slow!,\n                        rhs_fast!,\n                        fast_method,\n                        4,\n                        Q;\n                        dt = dt,\n                        t0 = t0,\n                    )\n                    solve!(Q, solver; timeend = finaltime)\n                    errors[n] = norm(Q - Qexact)\n                end\n                rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n                @test isapprox(rates[end], expected_order; atol = 0.6)\n            end\n        end\n    end\n\n    @testset \"MRI GARK methods with 2 rates\" begin\n        dts = [2.0^(-k) for k in 3:4]\n        errors = similar(dts)\n        for (slow_method, expected_order) in mrigark_erk_methods\n            for (fast_method, _) in fast_mrigark_methods\n                for nsubsteps in (1, 3)\n                    for (n, dt) in enumerate(dts)\n                        dt /= 4 # Need a smaller dt to get convergence rate\n                        Q .= Qinit\n                        fastsolver =\n                            fast_method(rhs_fast!, Q; dt = dt / nsubsteps)\n                        solver = slow_method(\n                            rhs_slow!,\n                            fastsolver,\n                            Q;\n                            dt = dt,\n                            t0 = t0,\n                        )\n                        solve!(Q, solver; timeend = finaltime)\n\n                        errors[n] = norm(Q - Qexact)\n                    end\n\n                    rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n                    @test isapprox(rates[end], expected_order; atol = 0.5)\n                end\n            end\n        end\n    end\n\n    @testset \"MRI GARK methods with 3 rates\" begin\n        dts = [2.0^(-k) for k in 3:4]\n        errors = similar(dts)\n        for (rate3_method, rate3_order) in mrigark_erk_methods\n            for (rate2_method, rate2_order) in mrigark_erk_methods\n                for (rate1_method, _) in fast_mrigark_methods\n                    for nsubsteps in (1, 2)\n                        for (n, dt) in enumerate(dts)\n                            dt /= 4 # Need a smaller dt to get convergence rate\n                            Q .= Qinit\n                            solver1 =\n                                rate1_method(rhs1!, Q; dt = dt / nsubsteps^2)\n                            solver2 = rate2_method(\n                                rhs2!,\n                                solver1,\n                                Q;\n                                dt = dt / nsubsteps,\n                            )\n                            solver3 = rate3_method(\n                                rhs3!,\n                                solver2,\n                                Q;\n                                dt = dt,\n                                t0 = t0,\n                            )\n                            solve!(Q, solver3; timeend = finaltime)\n                            errors[n] = norm(Q - Qexact)\n                        end\n                        rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n                        expected_order = min(rate3_order, rate2_order)\n                        @test isapprox(rates[end], expected_order; atol = 0.5)\n                    end\n                end\n            end\n        end\n    end\n\n    @testset \"MRI GARK implicit methods with 2 rates and linear solver\" begin\n        dts = [2.0^(-k) for k in 3:4]\n        errors = similar(dts)\n        for (slow_method, expected_order) in mrigark_irk_methods\n            for (fast_method, _) in fast_mrigark_methods\n                for nsubsteps in (1, 3)\n                    for (n, dt) in enumerate(dts)\n                        dt /= 4\n                        Q .= Qinit\n                        nsteps = ceil(Int, (finaltime - t0) / dt)\n                        dt = (finaltime - t0) / nsteps\n                        fastsolver =\n                            fast_method(rhs_nonlinear!, Q; dt = dt / nsubsteps)\n                        solver = slow_method(\n                            rhs_linear!,\n                            LinearBackwardEulerSolver(\n                                DivideLinearSolver();\n                                isadjustable = true,\n                            ),\n                            fastsolver,\n                            Q;\n                            dt = dt,\n                            t0 = t0,\n                        )\n                        solve!(Q, solver; timeend = finaltime)\n\n                        errors[n] = norm(Q - Qexact)\n                    end\n\n                    rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n                    @test isapprox(rates[end], expected_order; atol = 0.5)\n                end\n            end\n        end\n    end\n\n    @testset \"MRI GARK implicit methods with 2 rates and custom solver\" begin\n        dts = [2.0^(-k) for k in 3:4]\n        errors = similar(dts)\n        for (slow_method, expected_order) in mrigark_irk_methods\n            for (fast_method, _) in fast_mrigark_methods\n                for nsubsteps in (1, 3)\n                    for (n, dt) in enumerate(dts)\n                        dt /= 4\n                        Q .= Qinit\n                        nsteps = ceil(Int, (finaltime - t0) / dt)\n                        dt = (finaltime - t0) / nsteps\n                        fastsolver =\n                            fast_method(rhs_nonlinear!, Q; dt = dt / nsubsteps)\n                        solver = slow_method(\n                            rhs_linear!,\n                            ODETestBasicLinBE(),\n                            fastsolver,\n                            Q;\n                            dt = dt,\n                            t0 = t0,\n                        )\n                        solve!(Q, solver; timeend = finaltime)\n\n                        errors[n] = norm(Q - Qexact)\n                    end\n\n                    rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n                    @test isapprox(rates[end], expected_order; atol = 0.5)\n                end\n            end\n        end\n    end\nend\n\n@testset \"Explicit methods composition of solve!\" begin\n    halftime = 1.0\n    finaltime = 2.0\n    dt = 0.075\n    for (method, _) in explicit_methods\n        Q .= Qinit\n        solver1 = method(rhs!, Q; dt = dt, t0 = t0)\n        solve!(Q, solver1; timeend = finaltime)\n\n        Q2 = similar(Q)\n        Q2 .= Qinit\n        solver2 = method(rhs!, Q2; dt = dt, t0 = t0)\n        solve!(Q2, solver2; timeend = halftime, adjustfinalstep = false)\n        solve!(Q2, solver2; timeend = finaltime)\n\n        @test Q2 == Q\n    end\nend\n\n@testset \"DiffEq methods\" begin\n    alg = SSPRK73()\n    expected_order = 3\n    dts = [2.0^(-k) for k in 3:4]\n    errors = similar(dts)\n    for (n, dt) in enumerate(dts)\n        Q .= Qinit\n        solver = DiffEqJLSolver(rhs!, alg, Q; dt = dt, t0 = t0)\n        solve!(Q, solver; timeend = finaltime)\n        errors[n] = norm(Q - Qexact)\n    end\n    rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n    @test isapprox(rates[end], expected_order; atol = 0.7)\nend\n"
  },
  {
    "path": "test/Numerics/ODESolvers/ode_tests_common.jl",
    "content": "using ClimateMachine.ODESolvers\nusing ClimateMachine.SystemSolvers\n\nconst slow_mrrk_methods =\n    ((LSRK54CarpenterKennedy, 4), (LSRK144NiegemannDiehlBusch, 4))\nconst fast_mrrk_methods = (\n    (LSRK54CarpenterKennedy, 4),\n    (LSRK144NiegemannDiehlBusch, 4),\n    (SSPRK33ShuOsher, 3),\n    (SSPRK34SpiteriRuuth, 3),\n)\nconst explicit_methods = (\n    (LSRK54CarpenterKennedy, 4),\n    (LSRK144NiegemannDiehlBusch, 4),\n    (LS3NRK44Classic, 4),\n    (LS3NRK33Heuns, 3),\n    (SSPRK22Heuns, 2),\n    (SSPRK22Ralstons, 2),\n    (SSPRK33ShuOsher, 3),\n    (SSPRK34SpiteriRuuth, 3),\n    (LSRKEulerMethod, 1),\n)\n\nconst imex_methods_lowstorage_compatible = (\n    # Low-storage variant methods have an assumption that the\n    # explicit and implicit rhs/time-scaling coefficients (B/C vectors)\n    # in the Butcher tables are the same.\n    (ARK1ForwardBackwardEuler, 1),\n    (ARK2ImplicitExplicitMidpoint, 2),\n    (ARK2GiraldoKellyConstantinescu, 2),\n    (ARK437L2SA1KennedyCarpenter, 4),\n    (ARK548L2SA2KennedyCarpenter, 5),\n    (DBM453VoglEtAl, 3),\n)\nconst imex_methods_naivestorage_compatible = (\n    imex_methods_lowstorage_compatible...,\n    # Some methods can only be used with the `NaiveVariant` storage\n    # scheme since, in general, ARK methods can have different time-scaling/rhs-scaling\n    # coefficients (C/B vectors in the Butcher tables). For future reference,\n    # any other ARK-type methods that have more general Butcher tables\n    # (but with same number of stages) should be tested here:\n    (Trap2LockWoodWeller, 2),\n)\n\nconst mis_methods =\n    ((MIS2, 2), (MIS3C, 2), (MIS4, 3), (MIS4a, 3), (TVDMISA, 2), (TVDMISB, 2))\n\n\nconst mrigark_erk_methods = ((MRIGARKERK33aSandu, 3), (MRIGARKERK45aSandu, 4))\n\nconst mrigark_irk_methods = (\n    (MRIGARKESDIRK24LSA, 2),\n    (MRIGARKESDIRK23LSA, 2),\n    (MRIGARKIRK21aSandu, 2),\n    (MRIGARKESDIRK34aSandu, 3),\n    (MRIGARKESDIRK46aSandu, 4),\n)\n\nconst fast_mrigark_methods =\n    ((LSRK54CarpenterKennedy, 4), (LSRK144NiegemannDiehlBusch, 4))\n\nstruct DivideLinearSolver <: AbstractSystemSolver end\nfunction SystemSolvers.prefactorize(\n    linearoperator!,\n    ::DivideLinearSolver,\n    args...,\n)\n    linearoperator!\nend\nfunction SystemSolvers.linearsolve!(\n    linearoperator!,\n    preconditioner,\n    ::DivideLinearSolver,\n    Qtt,\n    Qhat,\n    args...,\n)\n    @. Qhat = 1 / Qhat\n    linearoperator!(Qtt, Qhat, args...)\n    @. Qtt = 1 / Qtt\nend\n"
  },
  {
    "path": "test/Numerics/ODESolvers/ode_tests_convergence.jl",
    "content": "using Test\nusing ClimateMachine\nusing StaticArrays\nusing LinearAlgebra\nusing KernelAbstractions\nusing ClimateMachine.MPIStateArrays: array_device\n\ninclude(\"ode_tests_common.jl\")\n\nClimateMachine.init()\nconst ArrayType = ClimateMachine.array_type()\n\n@testset \"ODE Solvers\" begin\n    @testset \"Convergence/extensive\" begin\n        @testset \"1-rate ODE\" begin\n            function rhs!(dQ, Q, ::Nothing, time; increment)\n                if increment\n                    dQ .+= Q * cos(time)\n                else\n                    dQ .= Q * cos(time)\n                end\n            end\n            exactsolution(q0, time) = q0 * exp(sin(time))\n\n            @testset \"Explicit methods\" begin\n                finaltime = 20.0\n                dts = [2.0^(-k) for k in 6:7]\n                errors = similar(dts)\n                q0 =\n                    ArrayType === Array ? [1.0] : range(-1.0, 1.0, length = 303)\n                for (method, expected_order) in explicit_methods\n                    for (n, dt) in enumerate(dts)\n                        Q = ArrayType(q0)\n                        solver = method(rhs!, Q; dt = dt, t0 = 0.0)\n                        solve!(Q, solver; timeend = finaltime)\n                        Q = Array(Q)\n                        errors[n] =\n                            maximum(@. abs(Q - exactsolution(q0, finaltime)))\n                    end\n                    rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n                    @test isapprox(rates[end], expected_order; atol = 0.17)\n                end\n            end\n        end\n\n        @testset \"Two-rate ODE with a linear stiff part\" begin\n            c = 100.0\n            function rhs_full!(dQ, Q, ::Nothing, time; increment)\n                if increment\n                    dQ .+= im * c * Q .+ exp(im * time)\n                else\n                    dQ .= im * c * Q .+ exp(im * time)\n                end\n            end\n\n            function rhs_nonlinear!(dQ, Q, ::Nothing, time; increment)\n                if increment\n                    dQ .+= exp(im * time)\n                else\n                    dQ .= exp(im * time)\n                end\n            end\n            rhs_slow! = rhs_nonlinear!\n\n            function rhs_linear!(dQ, Q, ::Nothing, time; increment)\n                if increment\n                    dQ .+= im * c * Q\n                else\n                    dQ .= im * c * Q\n                end\n            end\n            rhs_fast! = rhs_linear!\n\n            function exactsolution(q0, time)\n                q0 * exp(im * c * time) +\n                (exp(im * time) - exp(im * c * time)) / (im * (1 - c))\n            end\n\n            @testset \"IMEX methods (LowStorageVariant)\" begin\n                finaltime = pi / 2\n                dts = [2.0^(-k) for k in 13:14]\n                errors = similar(dts)\n\n                q0 = ArrayType <: Array ? [1.0] : range(-1.0, 1.0, length = 303)\n                for (method, expected_order) in\n                    imex_methods_lowstorage_compatible\n                    for split_explicit_implicit in (false, true)\n                        for (n, dt) in enumerate(dts)\n                            Q = ArrayType{ComplexF64}(q0)\n                            rhs! =\n                                split_explicit_implicit ? rhs_nonlinear! :\n                                rhs_full!\n                            solver = method(\n                                rhs!,\n                                rhs_linear!,\n                                LinearBackwardEulerSolver(\n                                    DivideLinearSolver(),\n                                    isadjustable = true,\n                                ),\n                                Q;\n                                dt = dt,\n                                t0 = 0.0,\n                                split_explicit_implicit = split_explicit_implicit,\n                                variant = LowStorageVariant(),\n                            )\n                            solve!(Q, solver; timeend = finaltime)\n                            Q = Array(Q)\n                            errors[n] = maximum(@. abs(\n                                Q - exactsolution(q0, finaltime),\n                            ))\n                        end\n\n                        rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n                        @test errors[1] < 2.0\n                        @test isapprox(rates[end], expected_order; atol = 0.35)\n                    end\n                end\n            end\n\n            @testset \"IMEX methods (NaiveVariant)\" begin\n                finaltime = pi / 2\n                dts = [2.0^(-k) for k in 13:14]\n                errors = similar(dts)\n\n                q0 = ArrayType <: Array ? [1.0] : range(-1.0, 1.0, length = 303)\n                for (method, expected_order) in\n                    imex_methods_naivestorage_compatible\n                    for split_explicit_implicit in (false, true)\n                        for (n, dt) in enumerate(dts)\n                            Q = ArrayType{ComplexF64}(q0)\n                            rhs! =\n                                split_explicit_implicit ? rhs_nonlinear! :\n                                rhs_full!\n                            solver = method(\n                                rhs!,\n                                rhs_linear!,\n                                LinearBackwardEulerSolver(\n                                    DivideLinearSolver(),\n                                    isadjustable = true,\n                                ),\n                                Q;\n                                dt = dt,\n                                t0 = 0.0,\n                                split_explicit_implicit = split_explicit_implicit,\n                                variant = NaiveVariant(),\n                            )\n                            solve!(Q, solver; timeend = finaltime)\n                            Q = Array(Q)\n                            errors[n] = maximum(@. abs(\n                                Q - exactsolution(q0, finaltime),\n                            ))\n                        end\n\n                        rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n                        @test errors[1] < 2.0\n                        @test isapprox(rates[end], expected_order; atol = 0.35)\n                    end\n                end\n            end\n\n            @testset \"MRRK methods (no substeps)\" begin\n                finaltime = pi / 2\n                dts = [2.0^(-k) for k in 10:11]\n                errors = similar(dts)\n                for (slow_method, slow_expected_order) in slow_mrrk_methods\n                    for (fast_method, fast_expected_order) in fast_mrrk_methods\n                        q0 =\n                            ArrayType === Array ? [1.0] :\n                            range(-1.0, 1.0, length = 303)\n                        for (n, dt) in enumerate(dts)\n                            Q = ArrayType{ComplexF64}(q0)\n                            solver = MultirateRungeKutta(\n                                (\n                                    slow_method(rhs_slow!, Q),\n                                    fast_method(rhs_fast!, Q),\n                                );\n                                dt = dt,\n                                t0 = 0.0,\n                            )\n                            solve!(Q, solver; timeend = finaltime)\n                            Q = Array(Q)\n                            errors[n] = maximum(@. abs(\n                                Q - exactsolution(q0, finaltime),\n                            ))\n                        end\n\n                        rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n                        min_order =\n                            min(slow_expected_order, fast_expected_order)\n                        max_order =\n                            max(slow_expected_order, fast_expected_order)\n                        @test (\n                            isapprox(rates[end], min_order; atol = 0.1) ||\n                            isapprox(rates[end], max_order; atol = 0.1) ||\n                            min_order <= rates[end] <= max_order\n                        )\n                    end\n                end\n            end\n\n            @testset \"MRRK methods (with substeps)\" begin\n                finaltime = pi / 2\n                dts = [2.0^(-k) for k in 14:15]\n                errors = similar(dts)\n                for (slow_method, slow_expected_order) in slow_mrrk_methods\n                    for (fast_method, fast_expected_order) in fast_mrrk_methods\n                        q0 =\n                            ArrayType === Array ? [1.0] :\n                            range(-1.0, 1.0, length = 303)\n                        for (n, fast_dt) in enumerate(dts)\n                            slow_dt = c * fast_dt\n                            Q = ArrayType{ComplexF64}(q0)\n                            solver = MultirateRungeKutta((\n                                slow_method(rhs_slow!, Q; dt = slow_dt),\n                                fast_method(rhs_fast!, Q; dt = fast_dt),\n                            ))\n                            solve!(Q, solver; timeend = finaltime)\n                            Q = Array(Q)\n                            errors[n] = maximum(@. abs(\n                                Q - exactsolution(q0, finaltime),\n                            ))\n                        end\n\n                        rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n                        min_order =\n                            min(slow_expected_order, fast_expected_order)\n                        max_order =\n                            max(slow_expected_order, fast_expected_order)\n                        @test (\n                            isapprox(rates[end], min_order; atol = 0.1) ||\n                            isapprox(rates[end], max_order; atol = 0.1) ||\n                            min_order <= rates[end] <= max_order\n                        )\n                    end\n                end\n            end\n\n            @testset \"MIS methods (with substeps)\" begin\n                finaltime = pi / 2\n                dts = [2.0^(-k) for k in 8:9]\n                errors = similar(dts)\n                for (mis_method, mis_expected_order) in mis_methods\n                    for fast_method in (LSRK54CarpenterKennedy,)\n                        q0 =\n                            ArrayType === Array ? [1.0] :\n                            range(-1.0, 1.0, length = 303)\n                        for (n, dt) in enumerate(dts)\n                            Q = ArrayType{ComplexF64}(q0)\n                            solver = mis_method(\n                                rhs_slow!,\n                                rhs_fast!,\n                                fast_method,\n                                4,\n                                Q;\n                                dt = dt,\n                                t0 = 0.0,\n                            )\n                            solve!(Q, solver; timeend = finaltime)\n                            Q = Array(Q)\n                            errors[n] = maximum(@. abs(\n                                Q - exactsolution(q0, finaltime),\n                            ))\n                        end\n                        rates = log2.(errors[1:(end - 1)] ./ errors[2:end])\n                        @test isapprox(\n                            rates[end],\n                            mis_expected_order;\n                            atol = 0.1,\n                        )\n                    end\n                end\n            end\n        end\n\n        #=\n        Test problem (4.2) from [Roberts2018](@cite)\n\n        Note: The actual rates are all over the place with this test and passing largely\n              depends on final dt size\n        =#\n        @testset \"2-rate ODE from Roberts2018\" begin\n            ω = 100\n            λf = -10\n            λs = -1\n            ξ = 1 // 10\n            α = 1\n            ηfs = ((1 - ξ) / α) * (λf - λs)\n            ηsf = -ξ * α * (λf - λs)\n            Ω = @SMatrix [\n                λf ηfs\n                ηsf λs\n            ]\n\n            function rhs_fast!(dQ, Q, param, t; increment)\n                @inbounds begin\n                    increment || (dQ .= 0)\n                    yf = Q[1]\n                    ys = Q[2]\n                    gf = (-3 + yf^2 - cos(ω * t)) / 2yf\n                    gs = (-2 + ys^2 - cos(t)) / 2ys\n                    dQ[1] += Ω[1, 1] * gf + Ω[1, 2] * gs - ω * sin(ω * t) / 2yf\n                end\n            end\n\n            function rhs_slow!(dQ, Q, param, t; increment)\n                @inbounds begin\n                    increment || (dQ .= 0)\n                    yf = Q[1]\n                    ys = Q[2]\n                    gf = (-3 + yf^2 - cos(ω * t)) / 2yf\n                    gs = (-2 + ys^2 - cos(t)) / 2ys\n                    dQ[2] += Ω[2, 1] * gf + Ω[2, 2] * gs - sin(t) / 2ys\n                end\n            end\n\n            exactsolution(t) = [sqrt(3 + cos(ω * t)); sqrt(2 + cos(t))]\n\n            @testset \"MRRK methods (no substeps)\" begin\n                finaltime = 5π / 2\n                dts = [2.0^(-k) for k in 7:8]\n                error = similar(dts)\n                for (slow_method, slow_expected_order) in slow_mrrk_methods\n                    for (fast_method, fast_expected_order) in fast_mrrk_methods\n                        for (n, dt) in enumerate(dts)\n                            Q = exactsolution(0)\n                            solver = MultirateRungeKutta(\n                                (\n                                    slow_method(rhs_slow!, Q),\n                                    fast_method(rhs_fast!, Q),\n                                );\n                                dt = dt,\n                                t0 = 0.0,\n                            )\n                            solve!(Q, solver; timeend = finaltime)\n                            error[n] = norm(Q - exactsolution(finaltime))\n                        end\n\n                        rate = log2.(error[1:(end - 1)] ./ error[2:end])\n                        min_order =\n                            min(slow_expected_order, fast_expected_order)\n                        max_order =\n                            max(slow_expected_order, fast_expected_order)\n                        @test (\n                            isapprox(rate[end], min_order; atol = 0.3) ||\n                            isapprox(rate[end], max_order; atol = 0.3) ||\n                            min_order <= rate[end] <= max_order\n                        )\n                    end\n                end\n            end\n\n            @testset \"MRRK methods (with substeps)\" begin\n                finaltime = 5π / 2\n                dts = [2.0^(-k) for k in 8:9]\n                error = similar(dts)\n                for (slow_method, slow_expected_order) in slow_mrrk_methods\n                    for (fast_method, fast_expected_order) in fast_mrrk_methods\n                        for (n, fast_dt) in enumerate(dts)\n                            Q = exactsolution(0)\n                            slow_dt = ω * fast_dt\n                            solver = MultirateRungeKutta((\n                                slow_method(rhs_slow!, Q; dt = slow_dt),\n                                fast_method(rhs_fast!, Q; dt = fast_dt),\n                            ))\n                            solve!(Q, solver; timeend = finaltime)\n                            error[n] = norm(Q - exactsolution(finaltime))\n                        end\n\n                        rate = log2.(error[1:(end - 1)] ./ error[2:end])\n                        min_order =\n                            min(slow_expected_order, fast_expected_order)\n                        max_order =\n                            max(slow_expected_order, fast_expected_order)\n                        @test (\n                            isapprox(rate[end], min_order; atol = 0.3) ||\n                            isapprox(rate[end], max_order; atol = 0.3) ||\n                            min_order <= rate[end] <= max_order\n                        )\n                    end\n                end\n            end\n\n            @testset \"MRRK methods with IMEX fast solver\" begin\n                function rhs_zero!(dQ, Q, param, t; increment)\n                    if !increment\n                        dQ .= 0\n                    end\n                end\n\n                finaltime = 5π / 2\n                dts = [2.0^(-k) for k in 8:9]\n                error = similar(dts)\n                for (slow_method, slow_expected_order) in slow_mrrk_methods\n                    for (fast_method, fast_expected_order) in\n                        imex_methods_lowstorage_compatible\n                        for (n, fast_dt) in enumerate(dts)\n                            Q = exactsolution(0)\n                            slow_dt = ω * fast_dt\n                            solver = MultirateRungeKutta((\n                                slow_method(rhs_slow!, Q; dt = slow_dt),\n                                fast_method(\n                                    rhs_fast!,\n                                    rhs_zero!,\n                                    LinearBackwardEulerSolver(\n                                        DivideLinearSolver(),\n                                        isadjustable = true,\n                                    ),\n                                    Q;\n                                    dt = fast_dt,\n                                    split_explicit_implicit = false,\n                                    variant = LowStorageVariant(),\n                                ),\n                            ))\n                            solve!(Q, solver; timeend = finaltime)\n                            error[n] = norm(Q - exactsolution(finaltime))\n                        end\n\n                        rate = log2.(error[1:(end - 1)] ./ error[2:end])\n                        min_order =\n                            min(slow_expected_order, fast_expected_order)\n                        max_order =\n                            max(slow_expected_order, fast_expected_order)\n                        atol =\n                            fast_method == ARK2GiraldoKellyConstantinescu ?\n                            0.5 : 0.37\n                        @test (\n                            isapprox(rate[end], min_order; atol = atol) ||\n                            isapprox(rate[end], max_order; atol = atol) ||\n                            min_order <= rate[end] <= max_order\n                        )\n                    end\n                end\n            end\n\n            @testset \"MIS methods (with substeps)\" begin\n                finaltime = 5π / 2\n                dts = [2.0^(-k) for k in 9:10]\n                error = similar(dts)\n                for (mis_method, mis_expected_order) in mis_methods\n                    for fast_method in (LSRK54CarpenterKennedy,)\n                        for (n, dt) in enumerate(dts)\n                            Q = exactsolution(0)\n                            solver = mis_method(\n                                rhs_slow!,\n                                rhs_fast!,\n                                fast_method,\n                                4,\n                                Q;\n                                dt = dt,\n                                t0 = 0.0,\n                            )\n                            solve!(Q, solver; timeend = finaltime)\n                            error[n] = norm(Q - exactsolution(finaltime))\n                        end\n\n                        rate = log2.(error[1:(end - 1)] ./ error[2:end])\n                        @test isapprox(\n                            rate[end],\n                            mis_expected_order;\n                            atol = 0.1,\n                        )\n                    end\n                end\n            end\n        end\n\n        # Simple 3-rate problem based on test of [Roberts2018](@cite)\n        #\n        # NOTE: Since we have no theory to say this ODE solver is accurate, the rates\n        #      suggest that things are really only 2nd order.\n        #\n        # TODO: This is not great, but no theory to say we should be accurate!\n        @testset \"3-rate ODE\" begin\n            ω1, ω2, ω3 = 10000, 100, 1\n            λ1, λ2, λ3 = -100, -10, -1\n            β1, β2, β3 = 2, 3, 4\n\n            ξ12 = λ2 / λ1\n            ξ13 = λ3 / λ1\n            ξ23 = λ3 / λ2\n\n            α12, α13, α23 = 1, 1, 1\n\n            η12 = ((1 - ξ12) / α12) * (λ1 - λ2)\n            η13 = ((1 - ξ13) / α13) * (λ1 - λ3)\n            η23 = ((1 - ξ23) / α23) * (λ2 - λ3)\n\n            η21 = ξ12 * α12 * (λ2 - λ1)\n            η31 = ξ13 * α13 * (λ3 - λ1)\n            η32 = ξ23 * α23 * (λ3 - λ2)\n\n            Ω = @SMatrix [\n                λ1 η12 η13\n                η21 λ2 η23\n                η31 η32 λ3\n            ]\n\n            function rhs1!(dQ, Q, param, t; increment)\n                @inbounds begin\n                    increment || (dQ .= 0)\n                    y1, y2, y3 = Q[1], Q[2], Q[3]\n                    g = @SVector [\n                        (-β1 + y1^2 - cos(ω1 * t)) / 2y1,\n                        (-β2 + y2^2 - cos(ω2 * t)) / 2y2,\n                        (-β3 + y3^2 - cos(ω3 * t)) / 2y3,\n                    ]\n                    dQ[1] += Ω[1, :]' * g - ω1 * sin(ω1 * t) / 2y1\n                end\n            end\n            function rhs2!(dQ, Q, param, t; increment)\n                @inbounds begin\n                    increment || (dQ .= 0)\n                    y1, y2, y3 = Q[1], Q[2], Q[3]\n                    g = @SVector [\n                        (-β1 + y1^2 - cos(ω1 * t)) / 2y1,\n                        (-β2 + y2^2 - cos(ω2 * t)) / 2y2,\n                        (-β3 + y3^2 - cos(ω3 * t)) / 2y3,\n                    ]\n                    dQ[2] += Ω[2, :]' * g - ω2 * sin(ω2 * t) / 2y2\n                end\n            end\n            function rhs3!(dQ, Q, param, t; increment)\n                @inbounds begin\n                    increment || (dQ .= 0)\n                    y1, y2, y3 = Q[1], Q[2], Q[3]\n                    g = @SVector [\n                        (-β1 + y1^2 - cos(ω1 * t)) / 2y1,\n                        (-β2 + y2^2 - cos(ω2 * t)) / 2y2,\n                        (-β3 + y3^2 - cos(ω3 * t)) / 2y3,\n                    ]\n                    dQ[3] += Ω[3, :]' * g - ω3 * sin(ω3 * t) / 2y3\n                end\n            end\n            function rhs12!(dQ, Q, param, t; increment)\n                rhs1!(dQ, Q, param, t; increment = increment)\n                rhs2!(dQ, Q, param, t; increment = true)\n            end\n\n            exactsolution(t) = [\n                sqrt(β1 + cos(ω1 * t)),\n                sqrt(β2 + cos(ω2 * t)),\n                sqrt(β3 + cos(ω3 * t)),\n            ]\n\n            @testset \"MRRK methods (no substeps)\" begin\n                finaltime = π / 2\n                dts = [2.0^(-k) for k in 9:10]\n                error = similar(dts)\n                for (rate3_method, rate3_order) in slow_mrrk_methods\n                    for (rate2_method, rate2_order) in slow_mrrk_methods\n                        for (rate1_method, rate1_order) in fast_mrrk_methods\n                            for (n, dt) in enumerate(dts)\n                                Q = exactsolution(0)\n                                solver = MultirateRungeKutta(\n                                    (\n                                        rate3_method(rhs3!, Q),\n                                        rate2_method(rhs2!, Q),\n                                        rate1_method(rhs1!, Q),\n                                    );\n                                    dt = dt,\n                                    t0 = 0.0,\n                                )\n                                solve!(Q, solver; timeend = finaltime)\n                                error[n] = norm(Q - exactsolution(finaltime))\n                            end\n\n                            rate = log2.(error[1:(end - 1)] ./ error[2:end])\n                            min_order =\n                                min(rate3_order, rate2_order, rate1_order)\n                            max_order =\n                                max(rate3_order, rate2_order, rate1_order)\n                            @test 2 <= rate[end]\n                        end\n                    end\n                end\n            end\n\n            @testset \"MRRK methods (with substeps)\" begin\n                finaltime = π / 2\n                dts = [2.0^(-k) for k in 16:17]\n                error = similar(dts)\n                for (rate3_method, rate3_order) in slow_mrrk_methods\n                    for (rate2_method, rate2_order) in slow_mrrk_methods\n                        for (rate1_method, rate1_order) in fast_mrrk_methods\n                            for (n, dt1) in enumerate(dts)\n                                Q = exactsolution(0)\n                                dt2 = (ω1 / ω2) * dt1\n                                dt3 = (ω2 / ω3) * dt2\n                                solver = MultirateRungeKutta((\n                                    rate3_method(rhs3!, Q; dt = dt3),\n                                    rate2_method(rhs2!, Q; dt = dt2),\n                                    rate1_method(rhs1!, Q; dt = dt1),\n                                ))\n                                solve!(Q, solver; timeend = finaltime)\n                                error[n] = norm(Q - exactsolution(finaltime))\n                            end\n\n                            rate = log2.(error[1:(end - 1)] ./ error[2:end])\n                            min_order =\n                                min(rate3_order, rate2_order, rate1_order)\n                            max_order =\n                                max(rate3_order, rate2_order, rate1_order)\n\n                            @test 2 <= rate[end]\n                        end\n                    end\n                end\n            end\n        end\n\n        #=\n        Test problem (8.2) from [Sandu2019](@cite) for MRI-GARK Schemes\n        =#\n        @testset \"2-rate problem\" begin\n            ω = 20\n            λf = -10\n            λs = -1\n            ξ = 1 // 10\n            α = 1\n            ηfs = ((1 - ξ) / α) * (λf - λs)\n            ηsf = -ξ * α * (λf - λs)\n            Ω = @SMatrix [\n                λf ηfs\n                ηsf λs\n            ]\n\n            function rhs_fast!(dQ, Q, param, t; increment)\n                @kernel function knl!(dQ, Q, t, increment)\n                    @inbounds begin\n                        increment || (dQ .= 0)\n                        yf = Q[1]\n                        ys = Q[2]\n                        gf = (-3 + yf^2 - cos(ω * t)) / 2yf\n                        gs = (-2 + ys^2 - cos(t)) / 2ys\n                        dQ[1] +=\n                            Ω[1, 1] * gf + Ω[1, 2] * gs - ω * sin(ω * t) / 2yf\n                    end\n                end\n                event = Event(array_device(Q))\n                event = knl!(array_device(Q), 1)(\n                    dQ,\n                    Q,\n                    t,\n                    increment;\n                    ndrange = 1,\n                    dependencies = (event,),\n                )\n                wait(array_device(Q), event)\n            end\n\n\n            function rhs_slow!(dQ, Q, param, t; increment)\n                @kernel function knl!(dQ, Q, t, increment)\n                    @inbounds begin\n                        increment || (dQ .= 0)\n                        yf = Q[1]\n                        ys = Q[2]\n                        gf = (-3 + yf^2 - cos(ω * t)) / 2yf\n                        gs = (-2 + ys^2 - cos(t)) / 2ys\n                        dQ[2] += Ω[2, 1] * gf + Ω[2, 2] * gs - sin(t) / 2ys\n                    end\n                end\n                event = Event(array_device(Q))\n                event = knl!(array_device(Q), 1)(\n                    dQ,\n                    Q,\n                    t,\n                    increment;\n                    ndrange = 1,\n                    dependencies = (event,),\n                )\n                wait(array_device(Q), event)\n            end\n\n            struct ODETestConvNonLinBE <: AbstractBackwardEulerSolver end\n            ODESolvers.Δt_is_adjustable(::ODETestConvNonLinBE) = true\n            function (::ODETestConvNonLinBE)(Q, Qhat, α, p, t)\n                @kernel function knl!(Q, Qhat, α, p, t)\n                    @inbounds begin\n                        # Slow RHS has zero tendency of yf so just copy Qhat\n                        Q[1] = yf = Qhat[1]\n\n                        gf = (-3 + yf^2 - cos(ω * t)) / 2yf\n\n                        # solves: Q = Qhat[2] + α * rhs_slow(Q, t)\n                        # (simplifies to a quadratic equation)\n                        a = 2 - α * Ω[2, 2]\n                        b = -2 * (Qhat[2] + α * Ω[2, 1] * gf)\n                        c = α * (Ω[2, 2] * (2 + cos(t)) + sin(t))\n                        Q[2] = (-b + sqrt(b^2 - 4 * a * c)) / (2a)\n                    end\n                end\n                event = Event(array_device(Q))\n                event = knl!(array_device(Q), 1)(\n                    Q,\n                    Qhat,\n                    α,\n                    p,\n                    t;\n                    ndrange = 1,\n                    dependencies = (event,),\n                )\n                wait(array_device(Q), event)\n            end\n\n            exactsolution(t) =\n                ArrayType([sqrt(3 + cos(ω * t)); sqrt(2 + cos(t))])\n\n            finaltime = 1\n            dts = [2.0^(-k) for k in 7:9]\n            error = similar(dts)\n            @testset \"Explicit\" begin\n                for (mri_method, mri_expected_order) in mrigark_erk_methods\n                    for (fast_method, fast_expected_order) in\n                        fast_mrigark_methods\n                        for (n, slow_dt) in enumerate(dts)\n                            Q = exactsolution(0)\n                            fast_dt = slow_dt / ω\n                            fastsolver = fast_method(rhs_fast!, Q; dt = fast_dt)\n                            solver = mri_method(\n                                rhs_slow!,\n                                fastsolver,\n                                Q,\n                                dt = slow_dt,\n                            )\n                            solve!(Q, solver; timeend = finaltime)\n                            error[n] = norm(Q - exactsolution(finaltime))\n                        end\n\n                        rate = log2.(error[1:(end - 1)] ./ error[2:end])\n                        order = mri_expected_order\n                        @test isapprox(\n                            rate[end],\n                            mri_expected_order;\n                            atol = 0.3,\n                        )\n                    end\n                end\n            end\n\n            @testset \"Implicit\" begin\n                for (mri_method, mri_expected_order) in mrigark_irk_methods\n                    for (fast_method, fast_expected_order) in\n                        fast_mrigark_methods\n                        for (n, slow_dt) in enumerate(dts)\n                            Q = exactsolution(0)\n                            fast_dt = slow_dt / ω\n                            fastsolver = fast_method(rhs_fast!, Q; dt = fast_dt)\n                            solver = mri_method(\n                                rhs_slow!,\n                                ODETestConvNonLinBE(),\n                                fastsolver,\n                                Q,\n                                dt = slow_dt,\n                            )\n                            solve!(Q, solver; timeend = finaltime)\n                            error[n] = norm(Q - exactsolution(finaltime))\n                        end\n\n                        rate = log2.(error[1:(end - 1)] ./ error[2:end])\n                        order = mri_expected_order\n                        @test isapprox(\n                            rate[end],\n                            mri_expected_order;\n                            atol = 0.3,\n                        )\n                    end\n                end\n            end\n\n            @testset \"IMEX\" begin\n                for (method, expected_order) in\n                    imex_methods_naivestorage_compatible\n                    for (n, dt) in enumerate(dts)\n                        Q = exactsolution(0)\n                        solver = method(\n                            rhs_fast!,\n                            rhs_slow!,\n                            ODETestConvNonLinBE(),\n                            Q;\n                            dt = dt,\n                            t0 = 0.0,\n                            split_explicit_implicit = true,\n                            variant = NaiveVariant(),\n                        )\n                        solve!(Q, solver; timeend = finaltime)\n                        error[n] = norm(Q - exactsolution(finaltime))\n                    end\n                    rate = log2.(error[1:(end - 1)] ./ error[2:end])\n                    order = expected_order\n                    @test isapprox(rate[end], expected_order; atol = 0.3)\n                end\n            end\n        end\n\n        # 3-rate modification of the above test problem\n        @testset \"3-rate problem\" begin\n            ω1, ω2, ω3 = 20, 5, 1\n            λ1, λ2, λ3 = -20, -5, -1\n            β1, β2, β3 = 2, 2, 2\n\n            ξ12 = λ2 / (λ1 + λ2)\n            ξ13 = λ3 / (λ1 + λ3)\n            ξ23 = λ3 / (λ2 + λ3)\n\n            α12, α13, α23 = 1, 1, 1\n\n            η12 = ((1 - ξ12) / α12) * (λ1 - λ2)\n            η13 = 0#((1 - ξ13) / α13) * (λ1 - λ3)\n            η23 = ((1 - ξ23) / α23) * (λ2 - λ3)\n\n            η21 = ξ12 * α12 * (λ2 - λ1)\n            η31 = 0#ξ13 * α13 * (λ3 - λ1)\n            η32 = ξ23 * α23 * (λ3 - λ2)\n\n            Ω = @SMatrix [\n                λ1 η12 η13\n                η21 λ2 η23\n                η31 η32 λ3\n            ]\n\n            function rhs1!(dQ, Q, param, t; increment)\n                @kernel function knl!(dQ, Q, t, increment)\n                    @inbounds begin\n                        increment || (dQ .= 0)\n                        y1, y2, y3 = Q[1], Q[2], Q[3]\n                        g = @SVector [\n                            (-β1 + y1^2 - cos(ω1 * t)) / 2y1,\n                            (-β2 + y2^2 - cos(ω2 * t)) / 2y2,\n                            (-β3 + y3^2 - cos(ω3 * t)) / 2y3,\n                        ]\n                        dQ[1] += Ω[1, :]' * g - ω1 * sin(ω1 * t) / 2y1\n                    end\n                end\n                event = Event(array_device(Q))\n                event = knl!(array_device(Q), 1)(\n                    dQ,\n                    Q,\n                    t,\n                    increment;\n                    ndrange = 1,\n                    dependencies = (event,),\n                )\n                wait(array_device(Q), event)\n            end\n            function rhs2!(dQ, Q, param, t; increment)\n                @kernel function knl!(dQ, Q, t, increment)\n                    @inbounds begin\n                        increment || (dQ .= 0)\n                        y1, y2, y3 = Q[1], Q[2], Q[3]\n                        g = @SVector [\n                            (-β1 + y1^2 - cos(ω1 * t)) / 2y1,\n                            (-β2 + y2^2 - cos(ω2 * t)) / 2y2,\n                            (-β3 + y3^2 - cos(ω3 * t)) / 2y3,\n                        ]\n                        dQ[2] += Ω[2, :]' * g - ω2 * sin(ω2 * t) / 2y2\n                    end\n                end\n                event = Event(array_device(Q))\n                event = knl!(array_device(Q), 1)(\n                    dQ,\n                    Q,\n                    t,\n                    increment;\n                    ndrange = 1,\n                    dependencies = (event,),\n                )\n                wait(array_device(Q), event)\n            end\n            function rhs3!(dQ, Q, param, t; increment)\n                @kernel function knl!(dQ, Q, t, increment)\n                    @inbounds begin\n                        increment || (dQ .= 0)\n                        y1, y2, y3 = Q[1], Q[2], Q[3]\n                        g = @SVector [\n                            (-β1 + y1^2 - cos(ω1 * t)) / 2y1,\n                            (-β2 + y2^2 - cos(ω2 * t)) / 2y2,\n                            (-β3 + y3^2 - cos(ω3 * t)) / 2y3,\n                        ]\n                        dQ[3] += Ω[3, :]' * g - ω3 * sin(ω3 * t) / 2y3\n                    end\n                end\n                event = Event(array_device(Q))\n                event = knl!(array_device(Q), 1)(\n                    dQ,\n                    Q,\n                    t,\n                    increment;\n                    ndrange = 1,\n                    dependencies = (event,),\n                )\n                wait(array_device(Q), event)\n            end\n            struct ODETestConvNonLinBE3Rate <: AbstractBackwardEulerSolver end\n            ODESolvers.Δt_is_adjustable(::ODETestConvNonLinBE3Rate) = true\n            function (::ODETestConvNonLinBE3Rate)(Q, Qhat, α, p, t)\n                @kernel function knl!(Q, Qhat, α, p, t)\n                    @inbounds begin\n                        # Slower RHS has zero tendency of yf so just copy Qhat\n                        Q[1] = y1 = Qhat[1]\n                        Q[2] = y2 = Qhat[2]\n\n                        g = @SVector [\n                            (-β1 + y1^2 - cos(ω1 * t)) / 2y1,\n                            (-β2 + y2^2 - cos(ω2 * t)) / 2y2,\n                        ]\n\n                        # solves: Q = Qhat + α * rhs_slow(Q, t)\n                        # (simplifies to a quadratic equation)\n                        a = 2 - α * Ω[3, 3]\n                        b =\n                            -2 *\n                            (Qhat[3] + α * Ω[3, 1] * g[1] + α * Ω[3, 2] * g[2])\n                        c = α * (Ω[3, 3] * (β3 + cos(t)) + sin(t))\n                        Q[3] = (-b + sqrt(b^2 - 4 * a * c)) / (2a)\n                    end\n                end\n                event = Event(array_device(Q))\n                event = knl!(array_device(Q), 1)(\n                    Q,\n                    Qhat,\n                    α,\n                    p,\n                    t;\n                    ndrange = 1,\n                    dependencies = (event,),\n                )\n                wait(array_device(Q), event)\n            end\n\n            exactsolution(t) = ArrayType([\n                sqrt(β1 + cos(ω1 * t)),\n                sqrt(β2 + cos(ω2 * t)),\n                sqrt(β3 + cos(ω3 * t)),\n            ])\n\n            finaltime = 1\n            dts = [2.0^(-k) for k in 6:7]\n            error = similar(dts)\n            @testset \"Explicit\" begin\n                for (slow_method, slow_order) in mrigark_erk_methods\n                    for (mid_method, mid_order) in mrigark_erk_methods\n                        for (fast_method, fast_order) in fast_mrigark_methods\n                            for (n, slow_dt) in enumerate(dts)\n                                Q = exactsolution(0)\n                                mid_dt = slow_dt / ω2\n                                fast_dt = slow_dt / ω1\n                                fastsolver = fast_method(rhs1!, Q; dt = fast_dt)\n                                midsolver = mid_method(\n                                    rhs2!,\n                                    fastsolver,\n                                    Q,\n                                    dt = mid_dt,\n                                )\n                                slowsolver = slow_method(\n                                    rhs3!,\n                                    midsolver,\n                                    Q,\n                                    dt = slow_dt,\n                                )\n                                solve!(Q, slowsolver; timeend = finaltime)\n                                error[n] = norm(Q - exactsolution(finaltime))\n                            end\n\n                            rate = log2.(error[1:(end - 1)] ./ error[2:end])\n                            expected_order =\n                                min(slow_order, mid_order, fast_order)\n                            @test isapprox(\n                                rate[end],\n                                expected_order;\n                                atol = 0.3,\n                            )\n                        end\n                    end\n                end\n            end\n            @testset \"Implicit-Explicit\" begin\n                for (slow_method, slow_order) in mrigark_irk_methods\n                    for (mid_method, mid_order) in mrigark_erk_methods\n                        for (fast_method, fast_order) in fast_mrigark_methods\n                            for (n, slow_dt) in enumerate(dts)\n                                Q = exactsolution(0)\n                                mid_dt = slow_dt / ω2\n                                fast_dt = slow_dt / ω1\n                                fastsolver = fast_method(rhs1!, Q; dt = fast_dt)\n                                midsolver = mid_method(\n                                    rhs2!,\n                                    fastsolver,\n                                    Q,\n                                    dt = mid_dt,\n                                )\n                                slowsolver = slow_method(\n                                    rhs3!,\n                                    ODETestConvNonLinBE3Rate(),\n                                    midsolver,\n                                    Q,\n                                    dt = slow_dt,\n                                )\n                                solve!(Q, slowsolver; timeend = finaltime)\n                                error[n] = norm(Q - exactsolution(finaltime))\n                            end\n\n                            rate = log2.(error[1:(end - 1)] ./ error[2:end])\n                            expected_order =\n                                min(slow_order, mid_order, fast_order)\n                            @test isapprox(\n                                rate[end],\n                                expected_order;\n                                atol = 0.4,\n                            )\n                        end\n                    end\n                end\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/ODESolvers/runtests.jl",
    "content": "using Test, MPI\ninclude(joinpath(\"..\", \"..\", \"testhelpers.jl\"))\n\n@testset \"ODE Solvers\" begin\n    runmpi(joinpath(@__DIR__, \"callbacks.jl\"))\nend\n"
  },
  {
    "path": "test/Numerics/SystemSolvers/bandedsystem.jl",
    "content": "using ClimateMachine\nusing MPI\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing Logging\nusing LinearAlgebra\nusing Random\nusing StaticArrays\nusing ClimateMachine.BalanceLaws\nimport ClimateMachine.BalanceLaws: vars_state, number_states\nusing ClimateMachine.DGMethods: DGModel, DGFVModel, init_ode_state, create_state\nusing ClimateMachine.SystemSolvers: banded_matrix, banded_matrix_vector_product!\nusing ClimateMachine.DGMethods.FVReconstructions: FVConstant, FVLinear\nusing ClimateMachine.DGMethods.NumericalFluxes:\n    RusanovNumericalFlux,\n    CentralNumericalFluxSecondOrder,\n    CentralNumericalFluxGradient\nusing ClimateMachine.MPIStateArrays: MPIStateArray, euclidean_distance\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.SystemSolvers: band_lu!, band_forward!, band_back!\n\nusing Test\n\ninclude(\"../DGMethods/advection_diffusion/advection_diffusion_model.jl\")\n\nstruct Pseudo1D{n, α, β, μ, δ} <: AdvectionDiffusionProblem end\n\nfunction init_velocity_diffusion!(\n    ::Pseudo1D{n, α, β},\n    aux::Vars,\n    geom::LocalGeometry,\n) where {n, α, β}\n    # Direction of flow is n with magnitude α\n    aux.advection.u = α * n\n\n    # diffusion of strength β in the n direction\n    aux.diffusion.D = β * n * n'\nend\n\nstruct BigAdvectionDiffusion <: BalanceLaw end\nfunction vars_state(::BigAdvectionDiffusion, ::Prognostic, FT)\n    @vars begin\n        ρ::FT\n        X::SVector{3, FT}\n    end\nend\n\nfunction initial_condition!(\n    ::Pseudo1D{n, α, β, μ, δ},\n    state,\n    aux,\n    localgeo,\n    t,\n) where {n, α, β, μ, δ}\n    ξn = dot(n, localgeo.coord)\n    # ξT = SVector(x) - ξn * n\n    state.ρ = exp(-(ξn - μ - α * t)^2 / (4 * β * (δ + t))) / sqrt(1 + t / δ)\nend\n\nlet\n    # boiler plate MPI stuff\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n    Random.seed!(777 + MPI.Comm_rank(mpicomm))\n\n    # Mesh generation parameters\n    Neh = 10\n    Nev = 4\n\n    @testset \"$(@__FILE__) DGModel matrix\" begin\n        for FT in (Float64, Float32)\n            for dim in (2, 3)\n                connectivity = dim == 3 ? :full : :face\n                for single_column in (false, true)\n                    # Setup the topology\n                    if dim == 2\n                        brickrange = (\n                            range(FT(0); length = Neh + 1, stop = 1),\n                            range(FT(1); length = Nev + 1, stop = 2),\n                        )\n                    elseif dim == 3\n                        brickrange = (\n                            range(FT(0); length = Neh + 1, stop = 1),\n                            range(FT(0); length = Neh + 1, stop = 1),\n                            range(FT(1); length = Nev + 1, stop = 2),\n                        )\n                    end\n                    topl = StackedBrickTopology(\n                        mpicomm,\n                        brickrange,\n                        connectivity = connectivity,\n                    )\n\n                    # Warp mesh\n                    function warpfun(ξ1, ξ2, ξ3)\n                        # single column currently requires no geometry warping\n\n                        # Even if the warping is in only the horizontal, the way we\n                        # compute metrics causes problems for the single column approach\n                        # (possibly need to not use curl-invariant computation)\n                        if !single_column\n                            ξ1 = ξ1 + sin(2 * FT(π) * ξ1 * ξ2) / 10\n                            ξ2 = ξ2 + sin(2 * FT(π) * ξ1) / 5\n                            if dim == 3\n                                ξ3 = ξ3 + sin(8 * FT(π) * ξ1 * ξ2) / 10\n                            end\n                        end\n                        (ξ1, ξ2, ξ3)\n                    end\n\n                    d = dim == 2 ? FT[1, 10, 0] : FT[1, 1, 10]\n                    n = SVector{3, FT}(d ./ norm(d))\n\n                    α = FT(1)\n                    β = FT(1 // 100)\n                    μ = FT(-1 // 2)\n                    δ = FT(1 // 10)\n                    bcs = (HomogeneousBC{0}(),)\n                    model =\n                        AdvectionDiffusion{dim}(Pseudo1D{n, α, β, μ, δ}(), bcs)\n\n                    for (N, fvmethod) in (\n                        ((4, 4), nothing),\n                        ((4, 0), FVConstant()),\n                        ((4, 0), FVLinear()),\n                    )\n\n                        # create the actual grid\n                        grid = DiscontinuousSpectralElementGrid(\n                            topl,\n                            FloatType = FT,\n                            DeviceArray = ArrayType,\n                            polynomialorder = N,\n                            meshwarp = warpfun,\n                        )\n\n\n                        # the nonlinear model is needed so we can grab the state_auxiliary below\n                        dg =\n                            (fvmethod === nothing) ?\n                            DGModel(\n                                model,\n                                grid,\n                                RusanovNumericalFlux(),\n                                CentralNumericalFluxSecondOrder(),\n                                CentralNumericalFluxGradient(),\n                            ) :\n                            DGFVModel(\n                                model,\n                                grid,\n                                fvmethod,\n                                RusanovNumericalFlux(),\n                                CentralNumericalFluxSecondOrder(),\n                                CentralNumericalFluxGradient(),\n                            )\n\n                        vdg =\n                            (fvmethod === nothing) ?\n                            DGModel(\n                                model,\n                                grid,\n                                RusanovNumericalFlux(),\n                                CentralNumericalFluxSecondOrder(),\n                                CentralNumericalFluxGradient();\n                                direction = VerticalDirection(),\n                                state_auxiliary = dg.state_auxiliary,\n                            ) :\n                            DGFVModel(\n                                model,\n                                grid,\n                                fvmethod,\n                                RusanovNumericalFlux(),\n                                CentralNumericalFluxSecondOrder(),\n                                CentralNumericalFluxGradient();\n                                direction = VerticalDirection(),\n                                state_auxiliary = dg.state_auxiliary,\n                            )\n\n                        A_banded = banded_matrix(\n                            vdg,\n                            MPIStateArray(dg),\n                            MPIStateArray(dg);\n                            single_column = single_column,\n                        )\n\n                        Q = init_ode_state(dg, FT(0))\n                        dQ1 = MPIStateArray(dg)\n                        dQ2 = MPIStateArray(dg)\n\n                        vdg(dQ1, Q, nothing, 0; increment = false)\n                        Q.data .= dQ1.data\n\n                        vdg(dQ1, Q, nothing, 0; increment = false)\n                        banded_matrix_vector_product!(A_banded, dQ2, Q)\n                        @test all(isapprox.(\n                            Array(dQ1.realdata),\n                            Array(dQ2.realdata),\n                            atol = 100 * eps(FT),\n                        ))\n\n                        big_Q = create_state(\n                            BigAdvectionDiffusion(),\n                            grid,\n                            Prognostic(),\n                        )\n                        big_dQ = create_state(\n                            BigAdvectionDiffusion(),\n                            grid,\n                            Prognostic(),\n                        )\n\n                        big_Q .= NaN\n                        big_dQ .= NaN\n\n                        big_Q.data[:, 1:size(Q, 2), :] .= Q.data\n\n                        vdg(big_dQ, big_Q, nothing, 0; increment = false)\n\n                        @test all(isapprox.(\n                            Array(big_dQ.realdata[:, 1:size(Q, 2), :]),\n                            Array(dQ1.realdata),\n                            atol = 100 * eps(FT),\n                        ))\n\n                        @test all(big_dQ[:, (size(Q, 2) + 1):end, :] .== 0)\n\n                        big_dQ.data[:, (size(Q, 2) + 1):end, :] .= -7\n\n                        vdg(big_dQ, big_Q, nothing, 0; increment = true)\n\n                        @test all(big_dQ[:, (size(Q, 2) + 1):end, :] .== -7)\n\n                        @test all(isapprox.(\n                            Array(big_dQ.realdata[:, 1:size(Q, 2), :]),\n                            Array(dQ1.realdata),\n                            atol = 100 * eps(FT),\n                        ))\n\n                        big_A = banded_matrix(\n                            vdg,\n                            similar(big_Q),\n                            similar(big_dQ);\n                            single_column = single_column,\n                        )\n                        @test all(isapprox.(\n                            Array(big_A.data),\n                            Array(A_banded.data),\n                            atol = 100 * eps(FT),\n                        ))\n\n                        α = FT(1 // 10)\n                        function op!(LQ, Q)\n                            vdg(LQ, Q, nothing, 0; increment = false)\n                            @. LQ = Q + α * LQ\n                        end\n\n                        A_banded = banded_matrix(\n                            op!,\n                            vdg,\n                            MPIStateArray(dg),\n                            MPIStateArray(dg);\n                            single_column = single_column,\n                        )\n\n                        Q = init_ode_state(dg, FT(0))\n                        dQ1 = MPIStateArray(vdg)\n                        dQ2 = MPIStateArray(vdg)\n\n                        op!(dQ1, Q)\n                        Q.data .= dQ1.data\n\n                        op!(dQ1, Q)\n                        banded_matrix_vector_product!(A_banded, dQ2, Q)\n                        @test all(isapprox.(\n                            Array(dQ1.realdata),\n                            Array(dQ2.realdata),\n                            atol = 100 * eps(FT),\n                        ))\n\n                        big_Q .= NaN\n                        big_Q.data[:, 1:size(Q, 2), :] .= Q.data\n\n                        big_dQ .= NaN\n                        op!(big_dQ, big_Q)\n                        big_dQ.data[:, (size(Q, 2) + 1):end, :] .= -7\n\n                        big_A = banded_matrix(\n                            op!,\n                            vdg,\n                            similar(big_Q),\n                            similar(big_dQ);\n                            single_column = single_column,\n                        )\n\n                        @test all(isapprox.(\n                            Array(big_A.data),\n                            Array(A_banded.data),\n                            atol = 100 * eps(FT),\n                        ))\n\n                        band_lu!(big_A)\n                        band_forward!(big_dQ, big_A)\n                        band_back!(big_dQ, big_A)\n\n                        @test all(isapprox.(\n                            Array(big_dQ.realdata[:, 1:size(Q, 2), :]),\n                            Array(Q.realdata),\n                            atol = 100 * eps(FT),\n                        ))\n                        @test all(big_dQ[:, (size(Q, 2) + 1):end, :] .== -7)\n                    end\n                end\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/SystemSolvers/bgmres.jl",
    "content": "using MPI\nusing Test\nusing LinearAlgebra\nusing Random\nusing StaticArrays\nusing ClimateMachine\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.MPIStateArrays\nusing CUDA\nusing Random\nusing KernelAbstractions\n\nimport ClimateMachine.MPIStateArrays: array_device\n\nClimateMachine.init(; fix_rng_seed = true)\n\n@kernel function multiply_by_A!(x, A, y, n1, n2)\n    I = @index(Global)\n    for i in 1:n1\n        tmp = zero(eltype(x))\n        for j in 1:n2\n            tmp += A[i, j, I] * y[j, I]\n        end\n        x[i, I] = tmp\n    end\nend\n\nlet\n    if CUDA.has_cuda_gpu()\n        Arrays = [Array, CuArray]\n    else\n        Arrays = [Array]\n    end\n\n    for ArrayType in Arrays\n        for T in [Float32, Float64]\n            ϵ = eps(T)\n\n            @testset \"($ArrayType, $T) Basic Test\" begin\n                Random.seed!(42)\n\n                # Test 1: Basic Functionality\n                n = 100   # size of local (batch) matrix\n                ni = 100  # batch size\n\n                b = ArrayType(randn(n, ni))  # rhs\n                x = ArrayType(randn(n, ni))  # initial guess\n                x_ref = similar(x)\n\n                A = ArrayType(randn((n, n, ni)) ./ sqrt(n))\n                for i in 1:n\n                    A[i, i, :] .+= 10i\n                end\n\n                ss = size(b)[1]\n                bgmres = BatchedGeneralizedMinimalResidual(\n                    b,\n                    n,\n                    ni,\n                    M = ss,\n                    atol = ϵ,\n                    rtol = ϵ,\n                )\n\n                # Define the linear operator\n                function closure_linear_operator_multi!(A, n1, n2, n3)\n                    function linear_operator!(x, y)\n                        device = array_device(x)\n                        if isa(device, CPU)\n                            groupsize = Threads.nthreads()\n                        else # isa(device, CUDADevice)\n                            groupsize = 256\n                        end\n                        event = Event(device)\n                        event = multiply_by_A!(device, groupsize)(\n                            x,\n                            A,\n                            y,\n                            n1,\n                            n2,\n                            ndrange = n3,\n                            dependencies = (event,),\n                        )\n                        wait(device, event)\n                        nothing\n                    end\n                end\n                linear_operator! = closure_linear_operator_multi!(A, size(A)...)\n\n                # Now solve\n                linearsolve!(\n                    linear_operator!,\n                    nothing,\n                    bgmres,\n                    x,\n                    b;\n                    max_iters = Inf,\n                )\n\n                # reference solution\n                for i in 1:ni\n                    x_ref[:, i] = A[:, :, i] \\ b[:, i]\n                end\n\n                @test norm(x - x_ref) < 1000ϵ\n            end\n\n            ###\n            # Test 2: MPI State Array\n            ###\n            @testset \"($ArrayType, $T) MPIStateArray Test\" begin\n                Random.seed!(43)\n\n                n1 = 8\n                n2 = 3\n                n3 = 10\n                mpicomm = MPI.COMM_WORLD\n                mpi_b = MPIStateArray{T}(mpicomm, ArrayType, n1, n2, n3)\n                mpi_x = MPIStateArray{T}(mpicomm, ArrayType, n1, n2, n3)\n                mpi_A = ArrayType(randn(n1 * n2, n1 * n2, n3))\n\n                mpi_b.data[:] .= ArrayType(randn(n1 * n2 * n3))\n                mpi_x.data[:] .= ArrayType(randn(n1 * n2 * n3))\n\n                bgmres = BatchedGeneralizedMinimalResidual(\n                    mpi_b,\n                    n1 * n2,\n                    n3,\n                    M = n1 * n2,\n                    atol = ϵ,\n                    rtol = ϵ,\n                )\n\n                # Now define the linear operator\n                function closure_linear_operator_mpi!(A, n1, n2, n3)\n                    function linear_operator!(x, y)\n                        alias_x = reshape(x.data, (n1, n3))\n                        alias_y = reshape(y.data, (n1, n3))\n                        device = array_device(x)\n                        if isa(device, CPU)\n                            groupsize = Threads.nthreads()\n                        else # isa(device, CUDADevice)\n                            groupsize = 256\n                        end\n                        event = Event(device)\n                        event = multiply_by_A!(device, groupsize)(\n                            alias_x,\n                            A,\n                            alias_y,\n                            n1,\n                            n2,\n                            ndrange = n3,\n                            dependencies = (event,),\n                        )\n                        wait(device, event)\n                        nothing\n                    end\n                end\n                linear_operator! =\n                    closure_linear_operator_mpi!(mpi_A, size(mpi_A)...)\n\n                # Now solve\n                linearsolve!(\n                    linear_operator!,\n                    nothing,\n                    bgmres,\n                    mpi_x,\n                    mpi_b;\n                    max_iters = Inf,\n                )\n\n                # check all solutions\n                norms = -zeros(n3)\n                for cidx in 1:n3\n                    sol =\n                        Array(mpi_A[:, :, cidx]) \\\n                        Array(mpi_b.data[:, :, cidx])[:]\n                    norms[cidx] = norm(sol - Array(mpi_x.data[:, :, cidx])[:])\n                end\n                @test maximum(norms) < 8000ϵ\n            end\n\n            ###\n            # Test 3: Columnwise test\n            ###\n            @testset \"(Array, $T) Columnwise Test\" begin\n\n                Random.seed!(2424)\n                function closure_linear_operator_columwise!(A, tup)\n                    function linear_operator!(y, x)\n                        alias_x = reshape(x, tup)\n                        alias_y = reshape(y, tup)\n                        for i6 in 1:tup[6]\n                            for i4 in 1:tup[4]\n                                for i2 in 1:tup[2]\n                                    for i1 in 1:tup[1]\n                                        tmp = alias_x[i1, i2, :, i4, :, i6][:]\n                                        tmp2 = A[i1, i2, i4, i6] * tmp\n                                        alias_y[i1, i2, :, i4, :, i6] .=\n                                            reshape(tmp2, (tup[3], tup[5]))\n                                    end\n                                end\n                            end\n                        end\n                    end\n                end\n\n                tup = (3, 4, 7, 6, 5, 2)\n                B = [\n                    randn(tup[3] * tup[5], tup[3] * tup[5])\n                    for\n                    i1 in 1:tup[1],\n                    i2 in 1:tup[2], i4 in 1:tup[4], i6 in 1:tup[6]\n                ]\n                columnwise_A = [\n                    B[i1, i2, i4, i6] + 10I\n                    for\n                    i1 in 1:tup[1],\n                    i2 in 1:tup[2], i4 in 1:tup[4], i6 in 1:tup[6]\n                ]\n                # taking the inverse of A isn't great, but it is convenient\n                columnwise_inv_A = [\n                    inv(columnwise_A[i1, i2, i4, i6])\n                    for\n                    i1 in 1:tup[1],\n                    i2 in 1:tup[2], i4 in 1:tup[4], i6 in 1:tup[6]\n                ]\n                columnwise_linear_operator! =\n                    closure_linear_operator_columwise!(columnwise_A, tup)\n                columnwise_inverse_linear_operator! =\n                    closure_linear_operator_columwise!(columnwise_inv_A, tup)\n\n                mpi_tup = (tup[1] * tup[2] * tup[3], tup[4], tup[5] * tup[6])\n                b = randn(mpi_tup)\n                x = copy(b)\n                columnwise_inverse_linear_operator!(x, b)\n                x +=\n                    randn((tup[1] * tup[2] * tup[3], tup[4], tup[5] * tup[6])) *\n                    0.1\n\n                reshape_tuple_f = tup\n                permute_tuple = (5, 3, 1, 4, 2, 6)\n\n                bgmres = BatchedGeneralizedMinimalResidual(\n                    b,\n                    tup[3] * tup[5],\n                    tup[1] * tup[2] * tup[4] * tup[6];\n                    M = tup[3] * tup[5],\n                    forward_reshape = tup,\n                    forward_permute = permute_tuple,\n                    atol = 10ϵ,\n                    rtol = 10ϵ,\n                )\n\n                x_exact = copy(x)\n                linearsolve!(\n                    columnwise_linear_operator!,\n                    nothing,\n                    bgmres,\n                    x,\n                    b,\n                    max_iters = tup[3] * tup[5],\n                )\n\n                columnwise_inverse_linear_operator!(x_exact, b)\n                @test norm(x - x_exact) / norm(x_exact) < 1000ϵ\n                columnwise_linear_operator!(x_exact, x)\n                @test norm(x_exact - b) / norm(b) < 1000ϵ\n            end\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/SystemSolvers/cg.jl",
    "content": "using CUDA\nusing MPI\nusing Test\nusing LinearAlgebra\nusing Random\nusing StaticArrays\nusing KernelAbstractions: CPU, CUDADevice\nusing ClimateMachine\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.MPIStateArrays\nusing Random\n\nRandom.seed!(1235)\n\nlet\n    ClimateMachine.init()\n    mpicomm = MPI.COMM_WORLD\n    ArrayType = ClimateMachine.array_type()\n    n = 100\n    T = Float64\n    A = rand(n, n)\n    scale = 2.0\n    ϵ = 0.1\n    # Matrix 1\n    A = A' * A .* ϵ + scale * I\n\n    err_thresh = sqrt(eps(T))\n\n    # Matrix 2\n    # A = Diagonal(collect(1:n) * 1.0)\n    positive_definite = minimum(eigvals(A)) > eps(1.0)\n    @test positive_definite\n\n    b = ones(n) * 1.0\n    mulbyA!(y, x) = (y .= A * x)\n\n    tol = sqrt(eps(T))\n    method(b, tol) = ConjugateGradient(b, max_iter = n)\n    linearsolver = method(b, tol)\n\n    x = ones(n) * 1.0\n    x0 = copy(x)\n    iters = linearsolve!(mulbyA!, nothing, linearsolver, x, b; max_iters = Inf)\n    exact = A \\ b\n    x0 = copy(x)\n\n    @testset \"Array test\" begin\n        @test norm(x - exact) / norm(exact) < err_thresh\n        @test norm(A * x - b) / norm(b) < err_thresh\n    end\n\n    # Testing for CUDA CuArrays\n    if CUDA.has_cuda_gpu()\n        at_A = ArrayType(A)\n        at_b = ArrayType(b)\n        at_x = ArrayType(ones(n) * 1.0)\n        mulbyat_A!(y, x) = (y .= at_A * x)\n        at_method(b, tol) = ConjugateGradient(b, max_iter = n)\n        linearsolver = at_method(at_b, tol)\n        iters = linearsolve!(\n            mulbyat_A!,\n            nothing,\n            linearsolver,\n            at_x,\n            at_b;\n            max_iters = n,\n        )\n        exact = at_A \\ at_b\n\n        @testset \"CuArray test\" begin\n            @test norm(at_x - exact) / norm(exact) < err_thresh\n            @test norm(at_A * at_x - at_b) / norm(at_b) < err_thresh\n        end\n    end\n\n    mpi_b = MPIStateArray{T}(mpicomm, ArrayType, 4, 4, 4)\n    mpi_x = MPIStateArray{T}(mpicomm, ArrayType, 4, 4, 4)\n    mpi_A = ArrayType(randn(4^3, 4^3))\n    mpi_A .= mpi_A' * mpi_A\n\n    function mpi_mulby!(x, y)\n        fy = y.data[:]\n        fx = mpi_A * fy\n        x.data[:] .= fx[:]\n        return nothing\n    end\n\n    mpi_b.data[:] .= ArrayType(randn(4^3))\n    mpi_x.data[:] .= ArrayType(randn(4^3))\n\n    mpi_method(mpi_b, tol) = ConjugateGradient(mpi_b, max_iter = n)\n    linearsolver = mpi_method(mpi_b, tol)\n    iters = linearsolve!(\n        mpi_mulby!,\n        nothing,\n        linearsolver,\n        mpi_x,\n        mpi_b;\n        max_iters = n,\n    )\n\n    exact = mpi_A \\ mpi_b[:]\n\n    @testset \"MPIStateArray test\" begin\n        @test norm(mpi_x.data[:] - exact) / norm(exact) < err_thresh\n        mpi_Ax = MPIStateArray{T}(mpicomm, ArrayType, 4, 4, 4)\n        mpi_mulby!(mpi_Ax, mpi_x)\n        @test norm(mpi_Ax - mpi_b) / norm(mpi_b) < err_thresh\n    end\n\n    # ## More Complex Example\n    function closure_linear_operator!(A, tup)\n        function linear_operator!(y, x)\n            alias_x = reshape(x, tup)\n            alias_y = reshape(y, tup)\n            for i6 in 1:tup[6]\n                for i4 in 1:tup[4]\n                    for i2 in 1:tup[2]\n                        for i1 in 1:tup[1]\n                            tmp = alias_x[i1, i2, :, i4, :, i6][:]\n                            tmp2 = A[i1, i2, i4, i6] * tmp\n                            alias_y[i1, i2, :, i4, :, i6] .=\n                                reshape(tmp2, (tup[3], tup[5]))\n                        end\n                    end\n                end\n            end\n        end\n    end\n\n    tup = (3, 4, 7, 2, 20, 2)\n\n    B = [\n        randn(tup[3] * tup[5], tup[3] * tup[5])\n        for i1 in 1:tup[1], i2 in 1:tup[2], i4 in 1:tup[4], i6 in 1:tup[6]\n    ]\n    columnwise_A = [\n        B[i1, i2, i4, i6] * B[i1, i2, i4, i6]' + 10I\n        for i1 in 1:tup[1], i2 in 1:tup[2], i4 in 1:tup[4], i6 in 1:tup[6]\n    ]\n    columnwise_inv_A = [\n        inv(columnwise_A[i1, i2, i4, i6])\n        for i1 in 1:tup[1], i2 in 1:tup[2], i4 in 1:tup[4], i6 in 1:tup[6]\n    ]\n    columnwise_linear_operator! = closure_linear_operator!(columnwise_A, tup)\n    columnwise_inverse_linear_operator! =\n        closure_linear_operator!(columnwise_inv_A, tup)\n\n    mpi_tup = (tup[1] * tup[2] * tup[3], tup[4], tup[5] * tup[6])\n    b = randn(mpi_tup)\n    x = randn(mpi_tup)\n\n    linearsolver = ConjugateGradient(\n        x,\n        max_iter = tup[3] * tup[5],\n        dims = (3, 5),\n        reshape_tuple = tup,\n    )\n\n    iters =\n        linearsolve!(columnwise_linear_operator!, nothing, linearsolver, x, b)\n    x_exact = copy(x)\n    columnwise_inverse_linear_operator!(x_exact, b)\n\n    @testset \"Columnwise test\" begin\n        @test norm(x - x_exact) / norm(x_exact) < err_thresh\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/SystemSolvers/columnwiselu.jl",
    "content": "using MPI\nusing Test\nusing LinearAlgebra\nusing Random\nusing KernelAbstractions, StaticArrays\n\nusing ClimateMachine\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.SystemSolvers:\n    band_lu_kernel!,\n    band_forward_kernel!,\n    band_back_kernel!,\n    DGColumnBandedMatrix,\n    lower_bandwidth,\n    upper_bandwidth\n\nimport ClimateMachine.MPIStateArrays: array_device\n\nClimateMachine.init()\nconst ArrayType = ClimateMachine.array_type()\n\nfunction band_to_full(B, p, q)\n    _, n = size(B)\n\n    A = similar(B, n, n) # assume square\n    fill!(A, 0)\n    for j in 1:n, i in max(1, j - q):min(j + p, n)\n        A[i, j] = B[q + i - j + 1, j]\n    end\n    A\nend\n\nfunction run_columnwiselu_test(FT, N)\n\n    Nq = N .+ 1\n    Nq1 = Nq[1]\n    Nq2 = Nq[2]\n    Nqv = Nq[3]\n    nstate = 3\n    nvertelem = 5\n    nhorzelem = 4\n    eband = 2\n\n    m = n = Nqv * nstate * nvertelem\n    p = lower_bandwidth(N[end], nstate, eband)\n    q = upper_bandwidth(N[end], nstate, eband)\n\n    Random.seed!(1234)\n    AB = rand(FT, Nq1, Nq2, p + q + 1, n, nhorzelem)\n\n    AB[:, :, q + 1, :, :] .+= 10 # Make A's diagonally dominate\n\n    Random.seed!(5678)\n    b = rand(FT, Nq1, Nq2, Nqv, nstate, nvertelem, nhorzelem)\n    x = similar(b)\n\n    perm = (4, 3, 5, 1, 2, 6)\n    bp = reshape(PermutedDimsArray(b, perm), n, Nq1, Nq2, nhorzelem)\n    xp = reshape(PermutedDimsArray(x, perm), n, Nq1, Nq2, nhorzelem)\n\n    d_F = ArrayType(AB)\n    d_F = DGColumnBandedMatrix{\n        3,\n        N,\n        nstate,\n        nhorzelem,\n        nvertelem,\n        eband,\n        false,\n        typeof(d_F),\n    }(\n        d_F,\n    )\n\n    groupsize = (Nq1, Nq2)\n    ndrange = (Nq1, Nq2, nhorzelem)\n\n    event = Event(array_device(d_F.data))\n    event = band_lu_kernel!(array_device(d_F.data), groupsize, ndrange)(\n        d_F,\n        dependencies = (event,),\n    )\n    wait(array_device(d_F.data), event)\n\n    F = Array(d_F.data)\n\n    for h in 1:nhorzelem, j in 1:Nq2, i in 1:Nq1\n        B = AB[i, j, :, :, h]\n        G = band_to_full(B, p, q)\n        GLU = lu!(G, Val(false))\n\n        H = band_to_full(F[i, j, :, :, h], p, q)\n\n        @assert H ≈ G\n\n        xp[:, i, j, h] .= GLU \\ bp[:, i, j, h]\n    end\n\n    b = reshape(b, Nq1 * Nq2 * Nqv, nstate, nvertelem * nhorzelem)\n    x = reshape(x, Nq1 * Nq2 * Nqv, nstate, nvertelem * nhorzelem)\n\n    d_x = ArrayType(b)\n\n    event = Event(array_device(d_x))\n    event = band_forward_kernel!(array_device(d_x), groupsize, ndrange)(\n        d_x,\n        d_F,\n        dependencies = (event,),\n    )\n\n    event = band_back_kernel!(array_device(d_x), groupsize, ndrange)(\n        d_x,\n        d_F,\n        dependencies = (event,),\n    )\n    wait(array_device(d_x), event)\n\n    result = x ≈ Array(d_x)\n    return result\nend\n\n@testset \"Columnwise LU test\" begin\n    for FT in (Float64, Float32)\n        for N in ((1, 1, 1), (1, 1, 2), (2, 2, 1), (2, 2, 0))\n            result = run_columnwiselu_test(FT, N)\n            @test result\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/SystemSolvers/iterativesolvers.jl",
    "content": "using Test\nusing ClimateMachine\nusing ClimateMachine.SystemSolvers\n\nusing StaticArrays, LinearAlgebra, Random\n\n# this test setup is partly based on IterativeSolvers.jl, see e.g\n# https://github.com/JuliaMath/IterativeSolvers.jl/blob/master/test/cg.jl\n@testset \"SystemSolvers small full system\" begin\n    n = 10\n\n    methods = (\n        (b, tol) -> GeneralizedConjugateResidual(2, b, rtol = tol),\n        (b, tol) -> GeneralizedMinimalResidual(b, M = 3, rtol = tol),\n        (b, tol) -> GeneralizedMinimalResidual(b, M = n, rtol = tol),\n    )\n\n    expected_iters = (\n        Dict(Float32 => 7, Float64 => 11),\n        Dict(Float32 => 5, Float64 => 17),\n        Dict(Float32 => 4, Float64 => 10),\n    )\n\n    for (m, method) in enumerate(methods), T in [Float32, Float64]\n        Random.seed!(44)\n\n        A = @MMatrix rand(T, n, n)\n        A = A' * A + I\n        b = @MVector rand(T, n)\n\n        mulbyA!(y, x) = (y .= A * x)\n\n        tol = sqrt(eps(T))\n        linearsolver = method(b, tol)\n\n        x = @MVector rand(T, n)\n        x0 = copy(x)\n        iters =\n            linearsolve!(mulbyA!, nothing, linearsolver, x, b; max_iters = Inf)\n\n        @test iters == expected_iters[m][T]\n        @test norm(A * x - b) / norm(A * x0 - b) <= tol\n\n        # test for convergence in 0 iterations by\n        # initializing with the exact solution\n        x = A \\ b\n\n        iters =\n            linearsolve!(mulbyA!, nothing, linearsolver, x, b; max_iters = Inf)\n        @test iters == 0\n        @test norm(A * x - b) <= 100 * eps(T)\n\n\n        newtol = 1000tol\n        settolerance!(linearsolver, newtol)\n\n        x = @MVector rand(T, n)\n        x0 = copy(x)\n        linearsolve!(mulbyA!, nothing, linearsolver, x, b; max_iters = Inf)\n\n        @test norm(A * x - b) / norm(A * x0 - b) <= newtol\n        @test norm(A * x - b) / norm(A * x0 - b) >= tol\n\n    end\nend\n\n@testset \"SystemSolvers large full system\" begin\n    n = 1000\n\n    methods = (\n        (b, tol) -> GeneralizedMinimalResidual(b, M = 15, rtol = tol),\n        (b, tol) -> GeneralizedMinimalResidual(b, M = 20, rtol = tol),\n    )\n\n    expected_iters = (\n        Dict(Float32 => (3, 3), Float64 => (9, 8)),\n        Dict(Float32 => (3, 3), Float64 => (9, 8)),\n    )\n\n    for (m, method) in enumerate(methods), T in [Float32, Float64]\n        for (i, α) in enumerate(T[1e-2, 5e-3])\n            Random.seed!(44)\n            A = rand(T, 200, 1000)\n            A = α * A' * A + I\n            b = rand(T, n)\n\n            mulbyA!(y, x) = (y .= A * x)\n\n            tol = sqrt(eps(T))\n            linearsolver = method(b, tol)\n\n            x = rand(T, n)\n            x0 = copy(x)\n            iters = linearsolve!(\n                mulbyA!,\n                nothing,\n                linearsolver,\n                x,\n                b;\n                max_iters = Inf,\n            )\n\n            @test iters == expected_iters[m][T][i]\n            @test norm(A * x - b) / norm(A * x0 - b) <= tol\n\n            newtol = 1000tol\n            settolerance!(linearsolver, newtol)\n\n            x = rand(T, n)\n            x0 = copy(x)\n            linearsolve!(mulbyA!, nothing, linearsolver, x, b; max_iters = Inf)\n\n            @test norm(A * x - b) / norm(A * x0 - b) <= newtol\n        end\n    end\nend\n"
  },
  {
    "path": "test/Numerics/SystemSolvers/poisson.jl",
    "content": "using MPI\nusing Test\nusing StaticArrays\nusing Logging, Printf\n\nusing ClimateMachine\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.DGMethods\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux\n\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    flux_first_order!,\n    flux_second_order!,\n    source!,\n    boundary_conditions,\n    boundary_state!,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    nodal_init_state_auxiliary!,\n    init_state_prognostic!\n\nimport ClimateMachine.DGMethods: numerical_boundary_flux_second_order!\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\n\nimport ClimateMachine.DGMethods.NumericalFluxes:\n    NumericalFluxSecondOrder, numerical_flux_second_order!\n\nif !@isdefined integration_testing\n    const integration_testing = parse(\n        Bool,\n        lowercase(get(ENV, \"JULIA_CLIMA_INTEGRATION_TESTING\", \"false\")),\n    )\nend\n\nstruct PoissonModel{dim} <: BalanceLaw end\n\nvars_state(::PoissonModel, ::Auxiliary, T) = @vars(rhs_ϕ::T)\nvars_state(::PoissonModel, ::Prognostic, T) = @vars(ϕ::T)\nvars_state(::PoissonModel, ::Gradient, T) = @vars(ϕ::T)\nvars_state(::PoissonModel, ::GradientFlux, T) = @vars(∇ϕ::SVector{3, T})\n\nboundary_conditions(::PoissonModel) = ()\nboundary_state!(nf, bc, bl::PoissonModel, _...) = nothing\n\nfunction flux_first_order!(::PoissonModel, _...) end\n\nfunction flux_second_order!(\n    ::PoissonModel,\n    flux::Grad,\n    state::Vars,\n    diffusive::Vars,\n    hyperdiffusive::Vars,\n    state_auxiliary::Vars,\n    t::Real,\n)\n    flux.ϕ = diffusive.∇ϕ\nend\n\nstruct PenaltyNumFluxDiffusive <: NumericalFluxSecondOrder end\n\n# There is no boundary since we are periodic\nnumerical_boundary_flux_second_order!(nf::PenaltyNumFluxDiffusive, _...) =\n    nothing\n\nfunction numerical_flux_second_order!(\n    ::PenaltyNumFluxDiffusive,\n    bl::PoissonModel,\n    fluxᵀn::Vars{S},\n    n::SVector,\n    state⁻::Vars{S},\n    diff⁻::Vars{D},\n    hyperdiff⁻::Vars{HD},\n    aux⁻::Vars{A},\n    state⁺::Vars{S},\n    diff⁺::Vars{D},\n    hyperdiff⁺::Vars{HD},\n    aux⁺::Vars{A},\n    t,\n) where {S, HD, D, A}\n\n    numerical_flux_second_order!(\n        CentralNumericalFluxSecondOrder(),\n        bl,\n        fluxᵀn,\n        n,\n        state⁻,\n        diff⁻,\n        hyperdiff⁻,\n        aux⁻,\n        state⁺,\n        diff⁺,\n        hyperdiff⁺,\n        aux⁺,\n        t,\n    )\n\n    Fᵀn = parent(fluxᵀn)\n    FT = eltype(Fᵀn)\n    tau = FT(1)\n    Fᵀn .-= tau * (parent(state⁻) - parent(state⁺))\nend\n\nfunction compute_gradient_argument!(\n    ::PoissonModel,\n    transformstate::Vars,\n    state::Vars,\n    state_auxiliary::Vars,\n    t::Real,\n)\n    transformstate.ϕ = state.ϕ\nend\n\nfunction compute_gradient_flux!(\n    ::PoissonModel,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    state_auxiliary::Vars,\n    t::Real,\n)\n    diffusive.∇ϕ = ∇transform.ϕ\nend\n\nsource!(::PoissonModel, _...) = nothing\n\n# note, that the code assumes solutions with zero mean\nsol1d(x) = sin(2pi * x)^4 - 3 / 8\ndxx_sol1d(x) =\n    -16 * pi^2 * sin(2pi * x)^2 * (sin(2pi * x)^2 - 3 * cos(2pi * x)^2)\n\nfunction nodal_init_state_auxiliary!(\n    ::PoissonModel{dim},\n    aux::Vars,\n    tmp::Vars,\n    g::LocalGeometry,\n) where {dim}\n    aux.rhs_ϕ = 0\n    @inbounds for d in 1:dim\n        x1 = g.coord[d]\n        x2 = g.coord[1 + mod(d, dim)]\n        x3 = g.coord[1 + mod(d + 1, dim)]\n        x23 = SVector(x2, x3)\n        aux.rhs_ϕ -= dxx_sol1d(x1) * prod(sol1d, view(x23, 1:(dim - 1)))\n    end\nend\n\nfunction init_state_prognostic!(\n    ::PoissonModel{dim},\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t,\n) where {dim}\n    coords = localgeo.coord\n    state.ϕ = prod(sol1d, view(coords, 1:dim))\nend\n\nfunction test_run(\n    mpicomm,\n    ArrayType,\n    ::Type{FT},\n    dim,\n    polynomialorder,\n    brickrange,\n    periodicity,\n    linmethod,\n) where {FT}\n\n    topology = BrickTopology(mpicomm, brickrange, periodicity = periodicity)\n    grid = DiscontinuousSpectralElementGrid(\n        topology,\n        polynomialorder = polynomialorder,\n        DeviceArray = ArrayType,\n        FloatType = FT,\n    )\n\n    dg = DGModel(\n        PoissonModel{dim}(),\n        grid,\n        CentralNumericalFluxFirstOrder(),\n        PenaltyNumFluxDiffusive(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n    Qrhs = dg.state_auxiliary\n    Qexact = init_ode_state(dg, FT(0))\n\n    linearoperator!(y, x) = dg(y, x, nothing, 0; increment = false)\n\n    linearsolver = linmethod(Q)\n\n    iters = linearsolve!(linearoperator!, nothing, linearsolver, Q, Qrhs)\n\n    error = euclidean_distance(Q, Qexact)\n\n    @info @sprintf \"\"\"Finished\n    error = %.16e\n    iters = %d\n    \"\"\" error iters\n    error, iters\nend\n\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n\n    mpicomm = MPI.COMM_WORLD\n\n    polynomialorder = 4\n    base_num_elem = 4\n    tol = 1e-9\n\n    linmethods = (\n        b -> GeneralizedConjugateResidual(3, b, rtol = tol),\n        b -> GeneralizedMinimalResidual(b, M = 7, rtol = tol),\n    )\n\n    expected_result = Array{Float64}(undef, 2, 2, 3) # method, dim-1, lvl\n\n    # GeneralizedConjugateResidual\n    expected_result[1, 1, 1] = 5.0540243616448058e-02\n    expected_result[1, 1, 2] = 1.4802275366044011e-03\n    expected_result[1, 1, 3] = 3.3852821775121401e-05\n    expected_result[1, 2, 1] = 1.4957957657736219e-02\n    expected_result[1, 2, 2] = 4.7282369781541172e-04\n    expected_result[1, 2, 3] = 1.4697449643351771e-05\n\n    # GeneralizedMinimalResidual\n    expected_result[2, 1, 1] = 5.0540243587512981e-02\n    expected_result[2, 1, 2] = 1.4802275409186211e-03\n    expected_result[2, 1, 3] = 3.3852820667079927e-05\n    expected_result[2, 2, 1] = 1.4957957659220951e-02\n    expected_result[2, 2, 2] = 4.7282369895963614e-04\n    expected_result[2, 2, 3] = 1.4697449516628483e-05\n\n    lvls = integration_testing ? size(expected_result)[end] : 1\n\n    for (m, linmethod) in enumerate(linmethods), FT in (Float64,)\n        result = Array{Tuple{FT, Int}}(undef, lvls)\n        for dim in 2:3\n            for l in 1:lvls\n                Ne = ntuple(d -> 2^(l - 1) * base_num_elem, dim)\n                brickrange =\n                    ntuple(d -> range(FT(0), length = Ne[d], stop = 1), dim)\n                periodicity = ntuple(d -> true, dim)\n\n                @info (ArrayType, FT, m, dim)\n                result[l] = test_run(\n                    mpicomm,\n                    ArrayType,\n                    FT,\n                    dim,\n                    polynomialorder,\n                    brickrange,\n                    periodicity,\n                    linmethod,\n                )\n\n                @test isapprox(\n                    result[l][1],\n                    FT(expected_result[m, dim - 1, l]),\n                    rtol = sqrt(tol),\n                )\n            end\n\n            if integration_testing\n                @info begin\n                    msg = \"\"\n                    for l in 1:(lvls - 1)\n                        rate = log2(result[l][1]) - log2(result[l + 1][1])\n                        msg *= @sprintf(\"\\n  rate for level %d = %e\\n\", l, rate)\n                    end\n                    msg\n                end\n            end\n        end\n    end\nend\n\nnothing\n"
  },
  {
    "path": "test/Numerics/SystemSolvers/runtests.jl",
    "content": "using Test, MPI\ninclude(joinpath(\"..\", \"..\", \"testhelpers.jl\"))\n\ninclude(\"iterativesolvers.jl\")\n"
  },
  {
    "path": "test/Numerics/runtests.jl",
    "content": "using Test, Pkg\n\n@testset \"Numerics\" begin\n    all_tests = isempty(ARGS) || \"all\" in ARGS ? true : false\n    for submodule in [\"SystemSolvers\", \"ODESolvers\"]\n        if all_tests ||\n           \"$submodule\" in ARGS ||\n           \"Numerics/$submodule\" in ARGS ||\n           \"Numerics\" in ARGS\n            include_test(submodule)\n        end\n    end\nend\n"
  },
  {
    "path": "test/Ocean/HydrostaticBoussinesq/test_3D_spindown.jl",
    "content": "#!/usr/bin/env julia --project\nusing ClimateMachine\nClimateMachine.init(parse_clargs = true)\n\nusing ClimateMachine.MPIStateArrays: euclidean_distance, norm\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.BalanceLaws: parameter_set\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Mesh.Grids: polynomialorders\nusing ClimateMachine.Ocean.HydrostaticBoussinesq\nusing ClimateMachine.Ocean.OceanProblems\n\nusing Test\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nfunction config_simple_box(\n    ::Type{FT},\n    N,\n    resolution,\n    dimensions;\n    BC = nothing,\n) where {FT}\n    if BC == nothing\n        problem = SimpleBox{FT}(dimensions...)\n    else\n        problem = SimpleBox{FT}(dimensions...; BC = BC)\n    end\n\n    model = HydrostaticBoussinesqModel{FT}(\n        param_set,\n        problem;\n        cʰ = FT(1),\n        αᵀ = FT(0),\n        κʰ = FT(0),\n        κᶻ = FT(0),\n        fₒ = FT(0),\n        β = FT(0),\n    )\n    solver_type = ExplicitSolverType(solver_method = LSRK144NiegemannDiehlBusch)\n    config = ClimateMachine.OceanBoxGCMConfiguration(\n        \"hydrostatic_spindown\",\n        N,\n        resolution,\n        param_set,\n        model;\n        periodicity = (true, true, false),\n        boundary = ((0, 0), (0, 0), (1, 2)),\n    )\n\n    return config, solver_type\nend\n\nfunction run_hydrostatic_test(; imex::Bool = false, BC = nothing, refDat = ())\n    FT = Float64\n\n    # DG polynomial order\n    N = Int(4)\n\n    # Domain resolution and size\n    Nˣ = Int(5)\n    Nʸ = Int(5)\n    Nᶻ = Int(8)\n    resolution = (Nˣ, Nʸ, Nᶻ)\n\n    Lˣ = 1e6    # m\n    Lʸ = 1e6    # m\n    H = 400   # m\n    dimensions = (Lˣ, Lʸ, H)\n\n    timestart = FT(0)   # s\n    timeout = FT(6 * 3600)  # s\n    timeend = FT(86400) # s\n    dt = FT(120)    # s\n\n    if imex\n        solver_type =\n            ClimateMachine.IMEXSolverType(implicit_model = LinearHBModel)\n    else\n        solver_type = ClimateMachine.ExplicitSolverType(\n            solver_method = LSRK144NiegemannDiehlBusch,\n        )\n    end\n\n    driver, solver_type_driver_config =\n        config_simple_box(FT, N, resolution, dimensions; BC = BC)\n\n    grid = driver.grid\n    N = polynomialorders(grid)\n    Nvert = N[end]\n    vert_filter = CutoffFilter(grid, Nvert - 1)\n    exp_filter = ExponentialFilter(grid, 1, 8)\n    modeldata = (vert_filter = vert_filter, exp_filter = exp_filter)\n\n    solver = ClimateMachine.SolverConfiguration(\n        timestart,\n        timeend,\n        driver,\n        init_on_cpu = true,\n        ode_dt = dt,\n        ode_solver_type = solver_type,\n        modeldata = modeldata,\n    )\n\n    output_interval = ceil(Int64, timeout / solver.dt)\n\n    ClimateMachine.Settings.vtk = \"never\"\n    # ClimateMachine.Settings.vtk = \"$(output_interval)steps\"\n\n    ClimateMachine.Settings.diagnostics = \"never\"\n    # ClimateMachine.Settings.diagnostics = \"$(output_interval)steps\"\n\n    cb = ClimateMachine.StateCheck.sccreate(\n        [(solver.Q, \"state\"), (solver.dg.state_auxiliary, \"aux\")],\n        output_interval;\n        prec = 12,\n    )\n\n    result = ClimateMachine.invoke!(solver; user_callbacks = [cb])\n\n    Q_exact = ClimateMachine.DGMethods.init_ode_state(\n        solver.dg,\n        timeend,\n        solver.init_args...;\n        init_on_cpu = solver.init_on_cpu,\n    )\n\n    error = euclidean_distance(solver.Q, Q_exact) / norm(Q_exact)\n\n    println(\"error = \", error)\n    @test isapprox(error, FT(0.0); atol = 0.005)\n\n    ## Check results against reference\n    ClimateMachine.StateCheck.scprintref(cb)\n    if length(refDat) > 0\n        @test ClimateMachine.StateCheck.scdocheck(cb, refDat)\n    end\nend\n\n@testset \"$(@__FILE__)\" begin\n\n    include(\"../refvals/3D_hydrostatic_spindown_refvals.jl\")\n\n    run_hydrostatic_test(imex = false, refDat = refVals.explicit) # error = 0.0011289879366523504\nend\n"
  },
  {
    "path": "test/Ocean/HydrostaticBoussinesq/test_initial_value_problem.jl",
    "content": "using ClimateMachine\n\nClimateMachine.init()\n\nusing ClimateMachine.Ocean.OceanProblems: InitialConditions, InitialValueProblem\n\n@testset \"$(@__FILE__)\" begin\n    U = 0.1\n    L = 0.2\n    a = 0.3\n    U = 0.4\n\n    Ψ(x, L) = exp(-x^2 / 2 * L^2) # a Gaussian\n\n    uᵢ(x, y, z) = +U * y / L * Ψ(x, L)\n    vᵢ(x, y, z) = -U * x / L * Ψ(x, L)\n    ηᵢ(x, y, z) = a * Ψ(x, L)\n    θᵢ(x, y, z) = 20.0 + 1e-3 * z\n\n    initial_conditions = InitialConditions(u = uᵢ, v = vᵢ, η = ηᵢ, θ = θᵢ)\n\n    problem = InitialValueProblem{Float64}(\n        dimensions = (π, 42, 1.1),\n        initial_conditions = initial_conditions,\n    )\n\n    @test problem.Lˣ == Float64(π)\n    @test problem.Lʸ == 42.0\n    @test problem.H == 1.1\n\n    @test problem.initial_conditions.u === uᵢ\n    @test problem.initial_conditions.v === vᵢ\n    @test problem.initial_conditions.η === ηᵢ\n    @test problem.initial_conditions.θ === θᵢ\nend\n"
  },
  {
    "path": "test/Ocean/HydrostaticBoussinesq/test_ocean_gyre_long.jl",
    "content": "#!/usr/bin/env julia --project\n\ninclude(\"../../../experiments/OceanBoxGCM/simple_box.jl\")\nClimateMachine.init()\n\nconst FT = Float64\n\n#################\n# RUN THE TESTS #\n#################\n@testset \"$(@__FILE__)\" begin\n\n    include(\"../refvals/test_ocean_gyre_refvals.jl\")\n\n    # simulation time\n    timestart = FT(0)    # s\n    timeend = FT(86400)  # s\n    timespan = (timestart, timeend)\n\n    # DG polynomial order\n    N = Int(4)\n\n    # Domain resolution\n    Nˣ = Int(20)\n    Nʸ = Int(20)\n    Nᶻ = Int(20)\n    resolution = (N, Nˣ, Nʸ, Nᶻ)\n\n    # Domain size\n    Lˣ = 4e6    # m\n    Lʸ = 4e6    # m\n    H = 1000   # m\n    dimensions = (Lˣ, Lʸ, H)\n\n    BC = (\n        OceanBC(Impenetrable(NoSlip()), Insulating()),\n        OceanBC(Impenetrable(NoSlip()), Insulating()),\n        OceanBC(Penetrable(KinematicStress()), TemperatureFlux()),\n    )\n\n    run_simple_box(\n        \"ocean_gyre_long\",\n        resolution,\n        dimensions,\n        timespan,\n        OceanGyre,\n        imex = false,\n        BC = BC,\n        refDat = refVals.long,\n    )\nend\n"
  },
  {
    "path": "test/Ocean/HydrostaticBoussinesq/test_ocean_gyre_short.jl",
    "content": "#!/usr/bin/env julia --project\n\ninclude(\"../../../experiments/OceanBoxGCM/simple_box.jl\")\nClimateMachine.init()\n\nconst FT = Float64\n\n#################\n# RUN THE TESTS #\n#################\n@testset \"$(@__FILE__)\" begin\n\n    include(\"../refvals/test_ocean_gyre_refvals.jl\")\n\n    # simulation time\n    timestart = FT(0)    # s\n    timeend = FT(3600)  # s\n    timespan = (timestart, timeend)\n\n    # DG polynomial order\n    N = Int(4)\n\n    # Domain resolution\n    Nˣ = Int(5)\n    Nʸ = Int(5)\n    Nᶻ = Int(5)\n    resolution = (N, Nˣ, Nʸ, Nᶻ)\n\n    # Domain size\n    Lˣ = 1e6    # m\n    Lʸ = 1e6    # m\n    H = 1000   # m\n    dimensions = (Lˣ, Lʸ, H)\n\n    BC = (\n        OceanBC(Impenetrable(NoSlip()), Insulating()),\n        OceanBC(Impenetrable(NoSlip()), Insulating()),\n        OceanBC(Penetrable(KinematicStress()), TemperatureFlux()),\n    )\n\n    run_simple_box(\n        \"ocean_gyre_short\",\n        resolution,\n        dimensions,\n        timespan,\n        OceanGyre,\n        imex = false,\n        BC = BC,\n        Δt = 120,\n        refDat = refVals.short,\n    )\nend\n"
  },
  {
    "path": "test/Ocean/HydrostaticBoussinesq/test_windstress_long.jl",
    "content": "#!/usr/bin/env julia --project\n\ninclude(\"../../../experiments/OceanBoxGCM/simple_box.jl\")\nClimateMachine.init()\n\nconst FT = Float64\n\n#################\n# RUN THE TESTS #\n#################\n@testset \"$(@__FILE__)\" begin\n\n    include(\"../refvals/test_windstress_refvals.jl\")\n\n    # simulation time\n    timestart = FT(0)      # s\n    timeend = FT(86400) # s\n    timespan = (timestart, timeend)\n\n    # DG polynomial order\n    N = Int(4)\n\n    # Domain resolution\n    Nˣ = Int(20)\n    Nʸ = Int(20)\n    Nᶻ = Int(50)\n    resolution = (N, Nˣ, Nʸ, Nᶻ)\n\n    # Domain size\n    Lˣ = 4e6    # m\n    Lʸ = 4e6    # m\n    H = 400   # m\n    dimensions = (Lˣ, Lʸ, H)\n\n    BC = (\n        OceanBC(Impenetrable(NoSlip()), Insulating()),\n        OceanBC(Impenetrable(FreeSlip()), Insulating()),\n        OceanBC(Penetrable(KinematicStress()), Insulating()),\n    )\n\n    run_simple_box(\n        \"test_windstress_long_imex\",\n        resolution,\n        dimensions,\n        timespan,\n        HomogeneousBox,\n        imex = true,\n        BC = BC,\n        Δt = 55,\n        refDat = refVals.long,\n    )\nend\n"
  },
  {
    "path": "test/Ocean/HydrostaticBoussinesq/test_windstress_short.jl",
    "content": "#!/usr/bin/env julia --project\n\ninclude(\"../../../experiments/OceanBoxGCM/simple_box.jl\")\nClimateMachine.init()\n\nconst FT = Float64\n\n#################\n# RUN THE TESTS #\n#################\n@testset \"$(@__FILE__)\" begin\n\n    include(\"../refvals/test_windstress_refvals.jl\")\n\n    # simulation time\n    timestart = FT(0)    # s\n    timeend = FT(3600)  # s\n    timespan = (timestart, timeend)\n\n    # DG polynomial order\n    N = Int(4)\n\n    # Domain resolution\n    Nˣ = Int(5)\n    Nʸ = Int(5)\n    Nᶻ = Int(5)\n    resolution = (N, Nˣ, Nʸ, Nᶻ)\n\n    # Domain size\n    Lˣ = 1e6    # m\n    Lʸ = 1e6    # m\n    H = 400   # m\n    dimensions = (Lˣ, Lʸ, H)\n\n    BC = (\n        OceanBC(Impenetrable(NoSlip()), Insulating()),\n        OceanBC(Impenetrable(FreeSlip()), Insulating()),\n        OceanBC(Penetrable(KinematicStress()), Insulating()),\n    )\n\n    run_simple_box(\n        \"test_windstress_short_imex\",\n        resolution,\n        dimensions,\n        timespan,\n        HomogeneousBox,\n        imex = true,\n        BC = BC,\n        Δt = 60,\n        refDat = refVals.imex,\n    )\n\n    run_simple_box(\n        \"test_windstress_short_explicit\",\n        resolution,\n        dimensions,\n        timespan,\n        HomogeneousBox,\n        imex = false,\n        BC = BC,\n        Δt = 180,\n        refDat = ClimateMachine.array_type() == Array ? refVals.explicit_cpu :\n                 refVals.explicit_gpu,\n    )\nend\n"
  },
  {
    "path": "test/Ocean/HydrostaticBoussinesqModel/test_hydrostatic_boussinesq_model.jl",
    "content": "using ClimateMachine\n\nClimateMachine.init()\n\nusing ClimateMachine.CartesianDomains\n\nusing CLIMAParameters: AbstractEarthParameterSet\nstruct DefaultParameters <: AbstractEarthParameterSet end\n\nusing ClimateMachine.Ocean:\n    HydrostaticBoussinesqSuperModel, current_time, current_step, Δt\n\n@testset \"$(@__FILE__)\" begin\n\n    domain = RectangularDomain(\n        Ne = (1, 1, 1),\n        Np = 4,\n        x = (-1, 1),\n        y = (-1, 1),\n        z = (-1, 0),\n    )\n\n    model = HydrostaticBoussinesqSuperModel(\n        domain = domain,\n        time_step = 0.1,\n        parameters = DefaultParameters(),\n    )\n\n    @test model isa HydrostaticBoussinesqSuperModel\n    @test Δt(model) == 0.1\n    @test current_step(model) == 0\n    @test current_time(model) == 0.0\nend\n"
  },
  {
    "path": "test/Ocean/OceanProblems/test_initial_value_problem.jl",
    "content": "using ClimateMachine\n\nClimateMachine.init()\n\nusing ClimateMachine.Ocean.OceanProblems: InitialConditions, InitialValueProblem\n\n@testset \"$(@__FILE__)\" begin\n    U = 0.1\n    L = 0.2\n    a = 0.3\n    U = 0.4\n\n    Ψ(x, L) = exp(-x^2 / 2 * L^2) # a Gaussian\n\n    uᵢ(x, y, z) = +U * y / L * Ψ(x, L)\n    vᵢ(x, y, z) = -U * x / L * Ψ(x, L)\n    ηᵢ(x, y, z) = a * Ψ(x, L)\n    θᵢ(x, y, z) = 20.0 + 1e-3 * z\n\n    initial_conditions = InitialConditions(u = uᵢ, v = vᵢ, η = ηᵢ, θ = θᵢ)\n\n    problem = InitialValueProblem{Float64}(\n        dimensions = (π, 42, 1.1),\n        initial_conditions = initial_conditions,\n    )\n\n    @test problem.Lˣ == Float64(π)\n    @test problem.Lʸ == 42.0\n    @test problem.H == 1.1\n\n    @test problem.initial_conditions.u === uᵢ\n    @test problem.initial_conditions.v === vᵢ\n    @test problem.initial_conditions.η === ηᵢ\n    @test problem.initial_conditions.θ === θᵢ\nend\n"
  },
  {
    "path": "test/Ocean/ShallowWater/GyreDriver.jl",
    "content": "using MPI\nusing Test\nusing ClimateMachine\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.VariableTemplates: flattenednames\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.Ocean.ShallowWater\nusing ClimateMachine.Ocean.ShallowWater:\n    TurbulenceClosure, LinearDrag, ConstantViscosity\nusing ClimateMachine.Ocean\nusing ClimateMachine.Ocean.OceanProblems\n\nusing LinearAlgebra\nusing StaticArrays\nusing Logging, Printf, Dates\nusing ClimateMachine.VTK\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nif !isempty(ARGS)\n    stommel = Bool(parse(Int, ARGS[1]))\n    linear = Bool(parse(Int, ARGS[2]))\n    test = parse(Int, ARGS[3])\nelse\n    stommel = true\n    linear = true\n    test = 1\nend\n\n###################\n# PARAM SELECTION #\n###################\nconst FT = Float64\n\nconst τₒ = 2e-4 # value includes τₒ, g, and ρ\nconst fₒ = 1e-4\nconst β = 1e-11\nconst λ = 1e-6\nconst ν = 1e4\n\nconst Lˣ = 1e6\nconst Lʸ = 1e6\nconst timeend = 100 * 24 * 60 * 60\nconst H = 1000\nconst c = sqrt(grav(param_set) * H)\n\nif stommel\n    gyre = \"stommel\"\nelse\n    gyre = \"munk\"\nend\n\nif linear\n    advec = \"linear\"\nelse\n    advec = \"nonlinear\"\nend\n\noutname = \"vtk_new_dt_\" * gyre * \"_\" * advec\n\nfunction setup_model(\n    ::Type{FT},\n    stommel,\n    linear,\n    τₒ,\n    fₒ,\n    β,\n    γ,\n    ν,\n    Lˣ,\n    Lʸ,\n    H,\n) where {FT}\n    problem = HomogeneousBox{FT}(\n        Lˣ,\n        Lʸ,\n        H,\n        τₒ = τₒ,\n        BC = (OceanBC(Impenetrable(FreeSlip()), Insulating()),),\n    )\n\n\n    if stommel\n        turbulence = LinearDrag{FT}(λ)\n    else\n        turbulence = ConstantViscosity{FT}(ν)\n    end\n\n    if linear\n        advection = nothing\n    else\n        advection = NonLinearAdvectionTerm()\n    end\n\n    model = ShallowWaterModel{FT}(\n        param_set,\n        problem,\n        turbulence,\n        advection,\n        c = c,\n        fₒ = fₒ,\n        β = β,\n    )\nend\n\n#########################\n# Timestepping function #\n#########################\n\nfunction test_run(\n    mpicomm,\n    topl,\n    ArrayType,\n    N,\n    dt,\n    ::Type{FT},\n    model,\n    test,\n) where {FT}\n    grid = DiscontinuousSpectralElementGrid(\n        topl,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    dg = DGModel(\n        model,\n        grid,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q = init_ode_state(dg, FT(0))\n    Qe = init_ode_state(dg, FT(timeend))\n\n    lsrk = LSRK144NiegemannDiehlBusch(dg, Q; dt = dt, t0 = 0)\n\n    nt_freq = floor(Int, 1 // 10 * timeend / dt)\n\n    cbcs_dg = ClimateMachine.StateCheck.sccreate(\n        [(Q, \"2D state\")],\n        nt_freq;\n        prec = 12,\n    )\n\n    cb = (cbcs_dg,)\n\n    if test > 2\n        outprefix = @sprintf(\"ic_mpirank%04d_ic\", MPI.Comm_rank(mpicomm))\n        statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n        auxnames = flattenednames(vars_state(model, Auxiliary(), eltype(Q)))\n        writevtk(outprefix, Q, dg, statenames, dg.state_auxiliary, auxnames)\n\n        outprefix = @sprintf(\"exact_mpirank%04d\", MPI.Comm_rank(mpicomm))\n        statenames = flattenednames(vars_state(model, Prognostic(), eltype(Qe)))\n        auxnames = flattenednames(vars_state(model, Auxiliary(), eltype(Qe)))\n        writevtk(outprefix, Qe, dg, statenames, dg.state_auxiliary, auxnames)\n\n        vtkstep = [0]\n        vtkpath = outname\n        mkpath(vtkpath)\n        cbvtk = GenericCallbacks.EveryXSimulationSteps(1000) do\n            outprefix = @sprintf(\n                \"%s/mpirank%04d_step%04d\",\n                vtkpath,\n                MPI.Comm_rank(mpicomm),\n                vtkstep[1]\n            )\n            @debug \"doing VTK output\" outprefix\n            statenames =\n                flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n            auxiliarynames =\n                flattenednames(vars_state(model, Auxiliary(), eltype(Q)))\n            writevtk(\n                outprefix,\n                Q,\n                dg,\n                statenames,\n                dg.state_auxiliary,\n                auxiliarynames,\n            )\n            vtkstep[1] += 1\n            nothing\n        end\n        cb = (cb..., cbvtk)\n    end\n\n    solve!(Q, lsrk; timeend = timeend, callbacks = cb)\n\n    error = euclidean_distance(Q, Qe) / norm(Qe)\n    @info @sprintf(\n        \"\"\"Finished\n        error = %.16e\n        \"\"\",\n        error\n    )\n\n    return error\nend\n\n\n################\n# Start Driver #\n################\n\nlet\n    ClimateMachine.init()\n    ArrayType = ClimateMachine.array_type()\n    mpicomm = MPI.COMM_WORLD\n\n    model = setup_model(FT, stommel, linear, τₒ, fₒ, β, λ, ν, Lˣ, Lʸ, H)\n\n    if test == 1\n        cellsrange = 10:10\n        orderrange = 4:4\n        testval = 1.6068814534535144e-03\n    elseif test == 2\n        cellsrange = 5:5:10\n        orderrange = 3:4\n    elseif test > 2\n        cellsrange = 5:5:25\n        orderrange = 6:10\n    end\n\n    errors = zeros(FT, length(cellsrange), length(orderrange))\n    for (i, Ne) in enumerate(cellsrange)\n        brickrange = (\n            range(FT(0); length = Ne + 1, stop = Lˣ),\n            range(FT(0); length = Ne + 1, stop = Lʸ),\n        )\n        topl = BrickTopology(\n            mpicomm,\n            brickrange,\n            periodicity = (false, false),\n            boundary = ((1, 1), (1, 1)),\n        )\n\n        for (j, N) in enumerate(orderrange)\n            @info \"running Ne $Ne and N $N with\"\n            dt = (Lˣ / c) / Ne / N^2\n            @info @sprintf(\"\\n dt = %f\", dt)\n            errors[i, j] =\n                test_run(mpicomm, topl, ArrayType, N, dt, FT, model, test)\n        end\n    end\n\n    @test errors[end, end] ≈ testval\n\n    #=\n    msg = \"\"\n    for i in length(cellsrange)-1\n      rate = log2(errors[i, end] - log2(errors[i+1, end]))\n      msg *= @sprintf(\"\\n rate for Ne %d = %e\", cellsrange[i], rate)\n    end\n    @info msg\n\n    msg = \"\"\n    for j in length(orderrange)-1\n      rate = log2(errors[end, j] - log2(errors[end, j+1]))\n      msg *= @sprintf(\"\\n rate for N  %d = %e\", orderrange[j], rate)\n    end\n    @info msg\n    =#\n\nend\n"
  },
  {
    "path": "test/Ocean/ShallowWater/test_2D_spindown.jl",
    "content": "#!/usr/bin/env julia --project\nusing Test\nusing ClimateMachine\nClimateMachine.init()\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Mesh.Grids: polynomialorders\nusing ClimateMachine.Ocean.ShallowWater\nusing ClimateMachine.Ocean.OceanProblems\n\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.BalanceLaws: vars_state, Prognostic, Auxiliary\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.VTK\n\nusing MPI\nusing LinearAlgebra\nusing StaticArrays\nusing Logging, Printf, Dates\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nfunction run_hydrostatic_spindown(; refDat = ())\n    mpicomm = MPI.COMM_WORLD\n    ArrayType = ClimateMachine.array_type()\n\n    ll = uppercase(get(ENV, \"JULIA_LOG_LEVEL\", \"INFO\"))\n    loglevel =\n        ll == \"DEBUG\" ? Logging.Debug :\n        ll == \"WARN\" ? Logging.Warn :\n        ll == \"ERROR\" ? Logging.Error : Logging.Info\n    logger_stream = MPI.Comm_rank(mpicomm) == 0 ? stderr : devnull\n    global_logger(ConsoleLogger(logger_stream, loglevel))\n\n    brickrange_2D = (xrange, yrange)\n    topl_2D = BrickTopology(\n        mpicomm,\n        brickrange_2D,\n        periodicity = (true, true),\n        boundary = ((0, 0), (0, 0)),\n    )\n    grid_2D = DiscontinuousSpectralElementGrid(\n        topl_2D,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    problem = SimpleBox{FT}(Lˣ, Lʸ, H)\n\n    model_2D = ShallowWaterModel{FT}(\n        param_set,\n        problem,\n        ShallowWater.ConstantViscosity{FT}(5e3),\n        nothing;\n        c = FT(1),\n        fₒ = FT(0),\n        β = FT(0),\n    )\n\n    dt_fast = 300\n    nout = ceil(Int64, tout / dt_fast)\n    dt_fast = tout / nout\n\n    dg_2D = DGModel(\n        model_2D,\n        grid_2D,\n        CentralNumericalFluxFirstOrder(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q_2D = init_ode_state(dg_2D, FT(0); init_on_cpu = true)\n\n    lsrk_2D = LSRK54CarpenterKennedy(dg_2D, Q_2D, dt = dt_fast, t0 = 0)\n\n    odesolver = lsrk_2D\n\n    vtkstep = [0, 0]\n    cbvector = make_callbacks(\n        vtkpath,\n        vtkstep,\n        nout,\n        mpicomm,\n        odesolver,\n        dg_2D,\n        model_2D,\n        Q_2D,\n    )\n\n    eng0 = norm(Q_2D)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\n    ArrayType = %s\"\"\" eng0 ArrayType\n\n    solve!(Q_2D, odesolver; timeend = timeend, callbacks = cbvector)\n\n    Qe_2D = init_ode_state(dg_2D, timeend, init_on_cpu = true)\n\n    error_2D = euclidean_distance(Q_2D, Qe_2D) / norm(Qe_2D)\n\n    println(\"2D error = \", error_2D)\n    @test isapprox(error_2D, FT(0.0); atol = 0.005)\n\n    ## Check results against reference\n    ClimateMachine.StateCheck.scprintref(cbvector[end])\n    if length(refDat) > 0\n        @test ClimateMachine.StateCheck.scdocheck(cbvector[end], refDat)\n    end\n\n    return nothing\nend\n\nfunction make_callbacks(\n    vtkpath,\n    vtkstep,\n    nout,\n    mpicomm,\n    odesolver,\n    dg_fast,\n    model_fast,\n    Q_fast,\n)\n    if isdir(vtkpath)\n        rm(vtkpath, recursive = true)\n    end\n    mkpath(vtkpath)\n    mkpath(vtkpath * \"/fast\")\n\n    function do_output(span, vtkstep, model, dg, Q)\n        outprefix = @sprintf(\n            \"%s/%s/mpirank%04d_step%04d\",\n            vtkpath,\n            span,\n            MPI.Comm_rank(mpicomm),\n            vtkstep\n        )\n        @info \"doing VTK output\" outprefix\n        statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n        auxnames = flattenednames(vars_state(model, Auxiliary(), eltype(Q)))\n        writevtk(outprefix, Q, dg, statenames, dg.state_auxiliary, auxnames)\n    end\n\n    do_output(\"fast\", vtkstep[2], model_fast, dg_fast, Q_fast)\n    cbvtk_fast = GenericCallbacks.EveryXSimulationSteps(nout) do (init = false)\n        do_output(\"fast\", vtkstep[2], model_fast, dg_fast, Q_fast)\n        vtkstep[2] += 1\n        nothing\n    end\n\n    starttime = Ref(now())\n    cbinfo = GenericCallbacks.EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q_fast)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %8.2f / %8.2f\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                ODESolvers.gettime(odesolver),\n                timeend,\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n\n    cbcs_dg = ClimateMachine.StateCheck.sccreate(\n        [(Q_fast, \"2D state\")],\n        nout;\n        prec = 12,\n    )\n\n    # don't write vtk during CI testing\n    # return (cbvtk_fast, cbinfo, cbcs_dg)\n    return (cbinfo, cbcs_dg)\nend\n\n#################\n# RUN THE TESTS #\n#################\nFT = Float64\nvtkpath = abspath(joinpath(\n    ClimateMachine.Settings.output_dir,\n    \"vtk_shallow_spindown\",\n))\n\nconst timeend = FT(24 * 3600) # s\nconst tout = FT(2 * 3600) # s\n# const timeend = 1200 # s\n# const tout = 600 # s\n\nconst N = 4\nconst Nˣ = 5\nconst Nʸ = 5\nconst Lˣ = 1e6  # m\nconst Lʸ = 1e6  # m\nconst H = 400  # m\n\nxrange = range(FT(0); length = Nˣ + 1, stop = Lˣ)\nyrange = range(FT(0); length = Nʸ + 1, stop = Lʸ)\n\n@testset \"$(@__FILE__)\" begin\n    include(\"../refvals/2D_hydrostatic_spindown_refvals.jl\")\n\n    run_hydrostatic_spindown(refDat = refVals.explicit) # error = 0.00011327920483879001\nend\n"
  },
  {
    "path": "test/Ocean/SplitExplicit/hydrostatic_spindown.jl",
    "content": "include(\"split_explicit.jl\")\n\nfunction SplitConfig(\n    name,\n    resolution,\n    dimensions,\n    coupling,\n    rotation = Fixed();\n    boundary_conditions = (\n        OceanBC(Impenetrable(FreeSlip()), Insulating()),\n        OceanBC(Penetrable(FreeSlip()), Insulating()),\n    ),\n    solver = SplitExplicitSolver,\n    dt_slow = 90 * 60,\n)\n    mpicomm = MPI.COMM_WORLD\n    ArrayType = ClimateMachine.array_type()\n\n    N, Nˣ, Nʸ, Nᶻ = resolution\n    Lˣ, Lʸ, H = dimensions\n\n    xrange = range(FT(0); length = Nˣ + 1, stop = Lˣ)\n    yrange = range(FT(0); length = Nʸ + 1, stop = Lʸ)\n    zrange = range(FT(-H); length = Nᶻ + 1, stop = 0)\n\n    brickrange_2D = (xrange, yrange)\n    topl_2D = BrickTopology(\n        mpicomm,\n        brickrange_2D,\n        periodicity = (true, true),\n        boundary = ((0, 0), (0, 0)),\n    )\n    grid_2D = DiscontinuousSpectralElementGrid(\n        topl_2D,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    brickrange_3D = (xrange, yrange, zrange)\n    topl_3D = StackedBrickTopology(\n        mpicomm,\n        brickrange_3D;\n        periodicity = (true, true, false),\n        boundary = ((0, 0), (0, 0), (1, 2)),\n    )\n    grid_3D = DiscontinuousSpectralElementGrid(\n        topl_3D,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    problem = SimpleBox{FT}(\n        dimensions...;\n        BC = boundary_conditions,\n        rotation = rotation,\n    )\n\n    dg_3D, dg_2D = setup_models(\n        solver,\n        problem,\n        grid_3D,\n        grid_2D,\n        param_set,\n        coupling,\n        dt_slow,\n    )\n\n    return SplitConfig(name, dg_3D, dg_2D, solver, mpicomm, ArrayType)\nend\n\nfunction setup_models(\n    ::Type{SplitExplicitSolver},\n    problem,\n    grid_3D,\n    grid_2D,\n    param_set,\n    coupling,\n    _,\n)\n\n    model_3D = HydrostaticBoussinesqModel{FT}(\n        param_set,\n        problem;\n        coupling = coupling,\n        cʰ = FT(1),\n        αᵀ = FT(0),\n        κʰ = FT(0),\n        κᶻ = FT(0),\n    )\n\n    model_2D = ShallowWaterModel{FT}(\n        param_set,\n        problem,\n        ShallowWater.ConstantViscosity{FT}(model_3D.νʰ),\n        nothing;\n        coupling = coupling,\n        c = FT(1),\n    )\n\n    N = polynomialorders(grid_3D)\n    Nvert = N[end]\n    vert_filter = CutoffFilter(grid_3D, Nvert - 1)\n    exp_filter = ExponentialFilter(grid_3D, 1, 8)\n\n    integral_model = DGModel(\n        VerticalIntegralModel(model_3D),\n        grid_3D,\n        CentralNumericalFluxFirstOrder(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    dg_2D = DGModel(\n        model_2D,\n        grid_2D,\n        CentralNumericalFluxFirstOrder(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q_2D = init_ode_state(dg_2D, FT(0); init_on_cpu = true)\n\n    modeldata = (\n        dg_2D = dg_2D,\n        Q_2D = Q_2D,\n        vert_filter = vert_filter,\n        exp_filter = exp_filter,\n        integral_model = integral_model,\n    )\n\n    dg_3D = DGModel(\n        model_3D,\n        grid_3D,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient();\n        modeldata = modeldata,\n    )\n\n    return dg_3D, dg_2D\n\nend\n\nfunction setup_models(\n    ::Type{SplitExplicitLSRK2nSolver},\n    problem,\n    grid_3D,\n    grid_2D,\n    param_set,\n    _,\n    dt_slow,\n)\n    add_fast_substeps = 2\n    numImplSteps = 5\n    numImplSteps > 0 ? ivdc_dt = dt_slow / FT(numImplSteps) : ivdc_dt = dt_slow\n    model_3D = OceanModel{FT}(\n        param_set,\n        problem,\n        cʰ = FT(1),\n        αᵀ = FT(0),\n        κʰ = FT(0),\n        κᶻ = FT(0),\n        add_fast_substeps = add_fast_substeps,\n        numImplSteps = numImplSteps,\n        ivdc_dt = ivdc_dt,\n    )\n\n    model_2D = BarotropicModel(model_3D)\n\n    dg_2D = DGModel(\n        model_2D,\n        grid_2D,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q_2D = init_ode_state(dg_2D, FT(0); init_on_cpu = true)\n\n    dg_3D = OceanDGModel(\n        model_3D,\n        grid_3D,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient();\n        modeldata = (dg_2D, Q_2D),\n    )\n\n    return dg_3D, dg_2D\n\nend\n"
  },
  {
    "path": "test/Ocean/SplitExplicit/simple_box_2dt.jl",
    "content": "#!/usr/bin/env julia --project\nusing ClimateMachine\nClimateMachine.init(parse_clargs = true)\n\nusing ClimateMachine.BalanceLaws: vars_state, Prognostic, Auxiliary\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates: flattenednames\nusing ClimateMachine.Ocean.SplitExplicit01\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.VTK\nusing ClimateMachine.Checkpoint\n\nusing Test\nusing MPI\nusing LinearAlgebra\nusing StaticArrays\nusing Logging, Printf, Dates\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nimport ClimateMachine.Ocean.SplitExplicit01:\n    ocean_init_aux!,\n    ocean_init_state!,\n    ocean_boundary_state!,\n    CoastlineFreeSlip,\n    CoastlineNoSlip,\n    OceanFloorFreeSlip,\n    OceanFloorNoSlip,\n    OceanSurfaceNoStressNoForcing,\n    OceanSurfaceStressNoForcing,\n    OceanSurfaceNoStressForcing,\n    OceanSurfaceStressForcing\nimport ClimateMachine.DGMethods:\n    update_auxiliary_state!, update_auxiliary_state_gradient!, VerticalDirection\n# using GPUifyLoops\n\nconst ArrayType = ClimateMachine.array_type()\n\nstruct SimpleBox{T, BC} <: AbstractOceanProblem\n    Lˣ::T\n    Lʸ::T\n    H::T\n    τₒ::T\n    λʳ::T\n    θᴱ::T\n    boundary_conditions::BC\nend\n\nfunction ocean_init_state!(p::SimpleBox, Q, A, localgeo, t)\n    coords = localgeo.coord\n    @inbounds y = coords[2]\n    @inbounds z = coords[3]\n    @inbounds H = p.H\n\n    Q.u = @SVector [-0, -0]\n    Q.η = -0\n    Q.θ = (5 + 4 * cos(y * π / p.Lʸ)) * (1 + z / H)\n\n    return nothing\nend\n\nfunction ocean_init_aux!(m::OceanModel, p::SimpleBox, A, geom)\n    FT = eltype(A)\n    @inbounds A.y = geom.coord[2]\n\n    # not sure if this is needed but getting weird intialization stuff\n    A.w = -0\n    A.pkin = -0\n    A.wz0 = -0\n    A.u_d = @SVector [-0, -0]\n    A.ΔGu = @SVector [-0, -0]\n\n    return nothing\nend\n\n# A is Filled afer the state\nfunction ocean_init_aux!(m::BarotropicModel, P::SimpleBox, A, geom)\n    @inbounds A.y = geom.coord[2]\n\n    A.Gᵁ = @SVector [-0, -0]\n    A.U_c = @SVector [-0, -0]\n    A.η_c = -0\n    A.U_s = @SVector [-0, -0]\n    A.η_s = -0\n    A.Δu = @SVector [-0, -0]\n    A.η_diag = -0\n    A.Δη = -0\n\n    return nothing\nend\n\nfunction main(; restart = 0)\n    mpicomm = MPI.COMM_WORLD\n\n    ll = uppercase(get(ENV, \"JULIA_LOG_LEVEL\", \"INFO\"))\n    loglevel =\n        ll == \"DEBUG\" ? Logging.Debug :\n        ll == \"WARN\" ? Logging.Warn :\n        ll == \"ERROR\" ? Logging.Error : Logging.Info\n    logger_stream = MPI.Comm_rank(mpicomm) == 0 ? stderr : devnull\n    global_logger(ConsoleLogger(logger_stream, loglevel))\n\n    if restart == 0 && MPI.Comm_rank(mpicomm) == 0 && isdir(vtkpath)\n        @info @sprintf(\"\"\"Remove old dir: %s and make new one\"\"\", vtkpath)\n        rm(vtkpath, recursive = true)\n    end\n\n    brickrange_2D = (xrange, yrange)\n    topl_2D =\n        BrickTopology(mpicomm, brickrange_2D, periodicity = (false, false))\n    grid_2D = DiscontinuousSpectralElementGrid(\n        topl_2D,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    brickrange_3D = (xrange, yrange, zrange)\n    topl_3D = StackedBrickTopology(\n        mpicomm,\n        brickrange_3D;\n        periodicity = (false, false, false),\n        boundary = ((1, 1), (1, 1), (2, 3)),\n    )\n    grid_3D = DiscontinuousSpectralElementGrid(\n        topl_3D,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    BC = (\n        ClimateMachine.Ocean.SplitExplicit01.CoastlineNoSlip(),\n        ClimateMachine.Ocean.SplitExplicit01.OceanFloorNoSlip(),\n        ClimateMachine.Ocean.SplitExplicit01.OceanSurfaceStressForcing(),\n    )\n    prob = SimpleBox{FT, typeof(BC)}(Lˣ, Lʸ, H, τₒ, λʳ, θᴱ, BC)\n    gravity::FT = grav(param_set)\n\n    #- set model time-step:\n    dt_fast = 240\n    dt_slow = 5400\n    # dt_fast = 300\n    # dt_slow = 300\n    if t_chkp > 0\n        n_chkp = ceil(Int64, t_chkp / dt_slow)\n        dt_slow = t_chkp / n_chkp\n    else\n        n_chkp = ceil(Int64, runTime / dt_slow)\n        dt_slow = runTime / n_chkp\n        n_chkp = 0\n    end\n    n_outp =\n        t_outp > 0 ? floor(Int64, t_outp / dt_slow) :\n        ceil(Int64, runTime / dt_slow)\n    ivdc_dt = numImplSteps > 0 ? dt_slow / FT(numImplSteps) : dt_slow\n\n    model = OceanModel{FT}(\n        prob,\n        grav = gravity,\n        cʰ = cʰ,\n        add_fast_substeps = add_fast_substeps,\n    )\n    # model = OceanModel{FT}(prob, cʰ = cʰ, fₒ = FT(0), β = FT(0) )\n    # model = OceanModel{FT}(prob, cʰ = cʰ, νʰ = FT(1e3), νᶻ = FT(1e-3) )\n    # model = OceanModel{FT}(prob, cʰ = cʰ, νʰ = FT(0), fₒ = FT(0), β = FT(0) )\n\n    barotropicmodel = BarotropicModel(model)\n\n    minΔx = min_node_distance(grid_3D, HorizontalDirection())\n    minΔz = min_node_distance(grid_3D, VerticalDirection())\n    #- 2 horiz directions\n    gravity_max_dT = 1 / (2 * sqrt(gravity * H) / minΔx)\n    # dt_fast = minimum([gravity_max_dT])\n\n    #- 2 horiz directions + harmonic visc or diffusion: 2^2 factor in CFL:\n    viscous_max_dT = 1 / (2 * model.νʰ / minΔx^2 + model.νᶻ / minΔz^2) / 4\n    diffusive_max_dT = 1 / (2 * model.κʰ / minΔx^2 + model.κᶻ / minΔz^2) / 4\n    # dt_slow = minimum([diffusive_max_dT, viscous_max_dT])\n\n    @info @sprintf(\n        \"\"\"Update\n           Gravity Max-dT = %.1f\n           Timestep       = %.1f\"\"\",\n        gravity_max_dT,\n        dt_fast\n    )\n\n    @info @sprintf(\n        \"\"\"Update\n       Viscous   Max-dT = %.1f\n       Diffusive Max-dT = %.1f\n       Timestep      = %.1f\"\"\",\n        viscous_max_dT,\n        diffusive_max_dT,\n        dt_slow\n    )\n\n    if restart > 0\n        direction = EveryDirection()\n        Q_3D, _, t0 =\n            read_checkpoint(vtkpath, \"baroclinic\", ArrayType, mpicomm, restart)\n        Q_2D, _, _ =\n            read_checkpoint(vtkpath, \"barotropic\", ArrayType, mpicomm, restart)\n\n        dg = OceanDGModel(\n            model,\n            grid_3D,\n            RusanovNumericalFlux(),\n            CentralNumericalFluxSecondOrder(),\n            CentralNumericalFluxGradient(),\n        )\n        barotropic_dg = DGModel(\n            barotropicmodel,\n            grid_2D,\n            RusanovNumericalFlux(),\n            CentralNumericalFluxSecondOrder(),\n            CentralNumericalFluxGradient(),\n        )\n\n        Q_3D = restart_ode_state(dg, Q_3D; init_on_cpu = true)\n        Q_2D = restart_ode_state(barotropic_dg, Q_2D; init_on_cpu = true)\n\n    else\n        t0 = 0\n        dg = OceanDGModel(\n            model,\n            grid_3D,\n            # CentralNumericalFluxFirstOrder(),\n            RusanovNumericalFlux(),\n            CentralNumericalFluxSecondOrder(),\n            CentralNumericalFluxGradient(),\n        )\n        barotropic_dg = DGModel(\n            barotropicmodel,\n            grid_2D,\n            # CentralNumericalFluxFirstOrder(),\n            RusanovNumericalFlux(),\n            CentralNumericalFluxSecondOrder(),\n            CentralNumericalFluxGradient(),\n        )\n\n        Q_3D = init_ode_state(dg, FT(0); init_on_cpu = true)\n        Q_2D = init_ode_state(barotropic_dg, FT(0); init_on_cpu = true)\n\n    end\n    timeend = runTime + t0\n\n    lsrk_ocean = LSRK54CarpenterKennedy(dg, Q_3D, dt = dt_slow, t0 = t0)\n    lsrk_barotropic =\n        LSRK54CarpenterKennedy(barotropic_dg, Q_2D, dt = dt_fast, t0 = t0)\n\n    odesolver = SplitExplicitLSRK2nSolver(lsrk_ocean, lsrk_barotropic)\n\n    #-- Set up State Check call back for config state arrays, called every ntFrq_SC time steps\n    cbcs_dg = ClimateMachine.StateCheck.sccreate(\n        [\n            (Q_3D, \"oce Q_3D\"),\n            (dg.state_auxiliary, \"oce aux\"),\n            # (dg.diffstate,\"oce diff\",),\n            # (lsrk_ocean.dQ,\"oce_dQ\",),\n            # (dg.modeldata.tendency_dg.state_auxiliary,\"tend Int aux\",),\n            # (dg.modeldata.conti3d_Q,\"conti3d_Q\",),\n            (Q_2D, \"baro Q_2D\"),\n            (barotropic_dg.state_auxiliary, \"baro aux\"),\n        ],\n        ntFrq_SC;\n        prec = 12,\n    )\n    # (barotropic_dg.diffstate,\"baro diff\",),\n    # (lsrk_barotropic.dQ,\"baro_dQ\",)\n    #--\n\n    cb_ntFrq = [n_outp, n_chkp]\n    outp_nb = round(Int64, restart * n_chkp / n_outp)\n    step = [outp_nb, outp_nb, restart + 1]\n    cbvector = make_callbacks(\n        vtkpath,\n        step,\n        cb_ntFrq,\n        timeend,\n        mpicomm,\n        odesolver,\n        dg,\n        model,\n        Q_3D,\n        barotropic_dg,\n        barotropicmodel,\n        Q_2D,\n    )\n\n    eng0 = norm(Q_3D)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\n    ArrayType = %s\"\"\" eng0 ArrayType\n\n    # slow fast state tuple\n    Qvec = (slow = Q_3D, fast = Q_2D)\n    # solve!(Qvec, odesolver; timeend = timeend, callbacks = cbvector)\n    cbv = (cbvector..., cbcs_dg)\n    solve!(Qvec, odesolver; timeend = timeend, callbacks = cbv)\n\n    ## Enable the code block below to print table for use in reference value code\n    ## reference value code sits in a file named $(@__FILE__)_refvals.jl. It is hand\n    ## edited using code generated by block below when reference values are updated.\n    regenRefVals = false\n    if regenRefVals\n        ## Print state statistics in format for use as reference values\n        println(\n            \"# SC ========== Test number \",\n            1,\n            \" reference values and precision match template. =======\",\n        )\n        println(\"# SC ========== $(@__FILE__) test reference values ======================================\")\n        ClimateMachine.StateCheck.scprintref(cbcs_dg)\n        println(\"# SC ====================================================================================\")\n    end\n\n    ## Check results against reference if present\n    checkRefVals = true\n    if checkRefVals\n        include(\"../refvals/simple_box_2dt_refvals.jl\")\n        refDat = (refVals[1], refPrecs[1])\n        checkPass = ClimateMachine.StateCheck.scdocheck(cbcs_dg, refDat)\n        checkPass ? checkRep = \"Pass\" : checkRep = \"Fail\"\n        @info @sprintf(\"\"\"Compare vs RefVals: %s\"\"\", checkRep)\n        @test checkPass\n    end\n\n    return nothing\nend\n\nfunction make_callbacks(\n    vtkpath,\n    step,\n    ntFrq,\n    timeend,\n    mpicomm,\n    odesolver,\n    dg_slow,\n    model_slow,\n    Q_slow,\n    dg_fast,\n    model_fast,\n    Q_fast,\n)\n    n_outp = ntFrq[1]\n    n_chkp = ntFrq[2]\n    mkpath(vtkpath)\n    mkpath(vtkpath * \"/slow\")\n    mkpath(vtkpath * \"/fast\")\n\n    function do_output(span, step, model, dg, Q)\n        outprefix = @sprintf(\n            \"%s/%s/mpirank%04d_step%04d\",\n            vtkpath,\n            span,\n            MPI.Comm_rank(mpicomm),\n            step\n        )\n        @info \"doing VTK output\" outprefix\n        statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n        auxnames = flattenednames(vars_state(model, Auxiliary(), eltype(Q)))\n        writevtk(outprefix, Q, dg, statenames, dg.state_auxiliary, auxnames)\n\n        mycomm = Q.mpicomm\n        ## Generate the pvtu file for these vtk files\n        if MPI.Comm_rank(mpicomm) == 0 && MPI.Comm_size(mpicomm) > 1\n            ## name of the pvtu file\n            pvtuprefix = @sprintf(\"%s/%s/step%04d\", vtkpath, span, step)\n            ## name of each of the ranks vtk files\n            prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n                @sprintf(\"mpirank%04d_step%04d\", i - 1, step)\n            end\n            writepvtu(\n                pvtuprefix,\n                prefixes,\n                (statenames..., auxnames...),\n                eltype(Q),\n            )\n            @info \"Done writing VTK: $pvtuprefix\"\n        end\n\n    end\n\n    do_output(\"slow\", step[1], model_slow, dg_slow, Q_slow)\n    step[1] += 1\n    cbvtk_slow =\n        GenericCallbacks.EveryXSimulationSteps(n_outp) do (init = false)\n            do_output(\"slow\", step[1], model_slow, dg_slow, Q_slow)\n            step[1] += 1\n            nothing\n        end\n\n    do_output(\"fast\", step[2], model_fast, dg_fast, Q_fast)\n    step[2] += 1\n    cbvtk_fast =\n        GenericCallbacks.EveryXSimulationSteps(n_outp) do (init = false)\n            do_output(\"fast\", step[2], model_fast, dg_fast, Q_fast)\n            step[2] += 1\n            nothing\n        end\n\n    starttime = Ref(now())\n    cbinfo = GenericCallbacks.EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q_slow)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %8.2f / %8.2f\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                ODESolvers.gettime(odesolver),\n                timeend,\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n\n    if n_chkp > 0\n        # Note: write zeros instead of Aux vars (not needed to restart); would be\n        # better just to write state vars (once write_checkpoint() can handle it)\n        cb_checkpoint = GenericCallbacks.EveryXSimulationSteps(n_chkp) do\n            write_checkpoint(\n                Q_slow,\n                zero(Q_slow),\n                odesolver,\n                vtkpath,\n                \"baroclinic\",\n                mpicomm,\n                step[3],\n            )\n\n            write_checkpoint(\n                Q_fast,\n                zero(Q_fast),\n                odesolver,\n                vtkpath,\n                \"barotropic\",\n                mpicomm,\n                step[3],\n            )\n\n            step[3] += 1\n            nothing\n        end\n        return (cbvtk_slow, cbvtk_fast, cbinfo, cb_checkpoint)\n    else\n        return (cbvtk_slow, cbvtk_fast, cbinfo)\n    end\n\nend\n\n#################\n# RUN THE TESTS #\n#################\nFT = Float64\nvtkpath = \"vtk_split\"\n\nconst runTime = 5 * 24 * 3600 # s\nconst t_outp = 24 * 3600 # s\nconst t_chkp = runTime  # s\n#const runTime = 6 * 3600 # s\n#const t_outp = 6 * 3600 # s\n#const t_chkp = 0\nconst ntFrq_SC = 1 # frequency (in time-step) for State-Check output\n\nconst N = 4\nconst Nˣ = 20\nconst Nʸ = 20\nconst Nᶻ = 20\nconst Lˣ = 4e6  # m\nconst Lʸ = 4e6  # m\nconst H = 1000  # m\n\nxrange = range(FT(0); length = Nˣ + 1, stop = Lˣ)\nyrange = range(FT(0); length = Nʸ + 1, stop = Lʸ)\nzrange = range(FT(-H); length = Nᶻ + 1, stop = 0)\n\n#const cʰ = sqrt(gravity * H)\nconst cʰ = 1  # typical of ocean internal-wave speed\nconst cᶻ = 0\n\n#- inverse ratio of additional fast time steps (for weighted average)\n#  --> do 1/add more time-steps and average from: 1 - 1/add up to: 1 + 1/add\n# e.g., = 1 --> 100% more ; = 2 --> 50% more ; = 3 --> 33% more ...\nadd_fast_substeps = 2\n\n#- number of Implicit vertical-diffusion sub-time-steps within one model full time-step\n# default = 0 : disable implicit vertical diffusion\nnumImplSteps = 0\n\nconst τₒ = 2e-1  # (Pa = N/m^2)\nconst λʳ = 20 // 86400 # m/s\n#- since we are using old BC (with factor of 2), take only half:\n#const τₒ = 1e-1\n#const λʳ = 10 // 86400\nconst θᴱ = 10    # deg.C\n\n@testset \"$(@__FILE__)\" begin\n    main(restart = 0)\nend\n"
  },
  {
    "path": "test/Ocean/SplitExplicit/simple_box_ivd.jl",
    "content": "#!/usr/bin/env julia --project\nusing ClimateMachine\nClimateMachine.init(parse_clargs = true)\n\nusing ClimateMachine.BalanceLaws: vars_state, Prognostic, Auxiliary\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates: flattenednames\nusing ClimateMachine.Ocean.SplitExplicit01\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.VTK\nusing ClimateMachine.Checkpoint\n\nusing Test\nusing MPI\nusing LinearAlgebra\nusing StaticArrays\nusing Logging, Printf, Dates\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nimport ClimateMachine.Ocean.SplitExplicit01:\n    ocean_init_aux!,\n    ocean_init_state!,\n    ocean_boundary_state!,\n    CoastlineFreeSlip,\n    CoastlineNoSlip,\n    OceanFloorFreeSlip,\n    OceanFloorNoSlip,\n    OceanSurfaceNoStressNoForcing,\n    OceanSurfaceStressNoForcing,\n    OceanSurfaceNoStressForcing,\n    OceanSurfaceStressForcing\nimport ClimateMachine.DGMethods:\n    update_auxiliary_state!, update_auxiliary_state_gradient!, VerticalDirection\n# using GPUifyLoops\n\nconst ArrayType = ClimateMachine.array_type()\n\nstruct SimpleBox{T, BC} <: AbstractOceanProblem\n    Lˣ::T\n    Lʸ::T\n    H::T\n    τₒ::T\n    λʳ::T\n    θᴱ::T\n    boundary_conditions::BC\nend\n\nfunction ocean_init_state!(p::SimpleBox, Q, A, localgeo, t)\n    coords = localgeo.coord\n    @inbounds y = coords[2]\n    @inbounds z = coords[3]\n    @inbounds H = p.H\n\n    Q.u = @SVector [-0, -0]\n    Q.η = -0\n    Q.θ = (5 + 4 * cos(y * π / p.Lʸ)) * (1 + z / H)\n\n    return nothing\nend\n\nfunction ocean_init_aux!(m::OceanModel, p::SimpleBox, A, geom)\n    FT = eltype(A)\n    @inbounds A.y = geom.coord[2]\n\n    # not sure if this is needed but getting weird intialization stuff\n    A.w = -0\n    A.pkin = -0\n    A.wz0 = -0\n    A.u_d = @SVector [-0, -0]\n    A.ΔGu = @SVector [-0, -0]\n\n    return nothing\nend\n\n# A is Filled afer the state\nfunction ocean_init_aux!(m::BarotropicModel, P::SimpleBox, A, geom)\n    @inbounds A.y = geom.coord[2]\n\n    A.Gᵁ = @SVector [-0, -0]\n    A.U_c = @SVector [-0, -0]\n    A.η_c = -0\n    A.U_s = @SVector [-0, -0]\n    A.η_s = -0\n    A.Δu = @SVector [-0, -0]\n    A.η_diag = -0\n    A.Δη = -0\n\n    return nothing\nend\n\nfunction main(; restart = 0)\n    mpicomm = MPI.COMM_WORLD\n\n    ll = uppercase(get(ENV, \"JULIA_LOG_LEVEL\", \"INFO\"))\n    loglevel =\n        ll == \"DEBUG\" ? Logging.Debug :\n        ll == \"WARN\" ? Logging.Warn :\n        ll == \"ERROR\" ? Logging.Error : Logging.Info\n    logger_stream = MPI.Comm_rank(mpicomm) == 0 ? stderr : devnull\n    global_logger(ConsoleLogger(logger_stream, loglevel))\n\n    if restart == 0 && MPI.Comm_rank(mpicomm) == 0 && isdir(vtkpath)\n        @info @sprintf(\"\"\"Remove old dir: %s and make new one\"\"\", vtkpath)\n        rm(vtkpath, recursive = true)\n    end\n\n    brickrange_2D = (xrange, yrange)\n    topl_2D =\n        BrickTopology(mpicomm, brickrange_2D, periodicity = (false, false))\n    grid_2D = DiscontinuousSpectralElementGrid(\n        topl_2D,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    brickrange_3D = (xrange, yrange, zrange)\n    topl_3D = StackedBrickTopology(\n        mpicomm,\n        brickrange_3D;\n        periodicity = (false, false, false),\n        boundary = ((1, 1), (1, 1), (2, 3)),\n    )\n    grid_3D = DiscontinuousSpectralElementGrid(\n        topl_3D,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    BC = (\n        ClimateMachine.Ocean.SplitExplicit01.CoastlineNoSlip(),\n        ClimateMachine.Ocean.SplitExplicit01.OceanFloorNoSlip(),\n        ClimateMachine.Ocean.SplitExplicit01.OceanSurfaceStressForcing(),\n    )\n    prob = SimpleBox{FT, typeof(BC)}(Lˣ, Lʸ, H, τₒ, λʳ, θᴱ, BC)\n    gravity::FT = grav(param_set)\n\n    #- set model time-step:\n    dt_fast = 240\n    dt_slow = 5400\n    # dt_fast = 300\n    # dt_slow = 300\n    if t_chkp > 0\n        n_chkp = ceil(Int64, t_chkp / dt_slow)\n        dt_slow = t_chkp / n_chkp\n    else\n        n_chkp = ceil(Int64, runTime / dt_slow)\n        dt_slow = runTime / n_chkp\n        n_chkp = 0\n    end\n    n_outp =\n        t_outp > 0 ? floor(Int64, t_outp / dt_slow) :\n        ceil(Int64, runTime / dt_slow)\n    ivdc_dt = numImplSteps > 0 ? dt_slow / FT(numImplSteps) : dt_slow\n\n    model = OceanModel{FT}(\n        prob,\n        grav = gravity,\n        cʰ = cʰ,\n        add_fast_substeps = add_fast_substeps,\n        numImplSteps = numImplSteps,\n        ivdc_dt = ivdc_dt,\n        κᶜ = FT(0.1),\n    )\n    # model = OceanModel{FT}(prob, cʰ = cʰ, fₒ = FT(0), β = FT(0) )\n    # model = OceanModel{FT}(prob, cʰ = cʰ, νʰ = FT(1e3), νᶻ = FT(1e-3) )\n    # model = OceanModel{FT}(prob, cʰ = cʰ, νʰ = FT(0), fₒ = FT(0), β = FT(0) )\n\n    barotropicmodel = BarotropicModel(model)\n\n    minΔx = min_node_distance(grid_3D, HorizontalDirection())\n    minΔz = min_node_distance(grid_3D, VerticalDirection())\n    #- 2 horiz directions\n    gravity_max_dT = 1 / (2 * sqrt(gravity * H) / minΔx)\n    # dt_fast = minimum([gravity_max_dT])\n\n    #- 2 horiz directions + harmonic visc or diffusion: 2^2 factor in CFL:\n    viscous_max_dT = 1 / (2 * model.νʰ / minΔx^2 + model.νᶻ / minΔz^2) / 4\n    diffusive_max_dT = 1 / (2 * model.κʰ / minΔx^2 + model.κᶻ / minΔz^2) / 4\n    # dt_slow = minimum([diffusive_max_dT, viscous_max_dT])\n\n    @info @sprintf(\n        \"\"\"Update\n           Gravity Max-dT = %.1f\n           Timestep       = %.1f\"\"\",\n        gravity_max_dT,\n        dt_fast\n    )\n\n    @info @sprintf(\n        \"\"\"Update\n       Viscous   Max-dT = %.1f\n       Diffusive Max-dT = %.1f\n       Timestep      = %.1f\"\"\",\n        viscous_max_dT,\n        diffusive_max_dT,\n        dt_slow\n    )\n\n    if restart > 0\n        direction = EveryDirection()\n        Q_3D, _, t0 =\n            read_checkpoint(vtkpath, \"baroclinic\", ArrayType, mpicomm, restart)\n        Q_2D, _, _ =\n            read_checkpoint(vtkpath, \"barotropic\", ArrayType, mpicomm, restart)\n\n        dg = OceanDGModel(\n            model,\n            grid_3D,\n            RusanovNumericalFlux(),\n            CentralNumericalFluxSecondOrder(),\n            CentralNumericalFluxGradient(),\n        )\n        barotropic_dg = DGModel(\n            barotropicmodel,\n            grid_2D,\n            RusanovNumericalFlux(),\n            CentralNumericalFluxSecondOrder(),\n            CentralNumericalFluxGradient(),\n        )\n\n        Q_3D = restart_ode_state(dg, Q_3D; init_on_cpu = true)\n        Q_2D = restart_ode_state(barotropic_dg, Q_2D; init_on_cpu = true)\n\n    else\n        t0 = 0\n        dg = OceanDGModel(\n            model,\n            grid_3D,\n            # CentralNumericalFluxFirstOrder(),\n            RusanovNumericalFlux(),\n            CentralNumericalFluxSecondOrder(),\n            CentralNumericalFluxGradient(),\n        )\n        barotropic_dg = DGModel(\n            barotropicmodel,\n            grid_2D,\n            # CentralNumericalFluxFirstOrder(),\n            RusanovNumericalFlux(),\n            CentralNumericalFluxSecondOrder(),\n            CentralNumericalFluxGradient(),\n        )\n\n        Q_3D = init_ode_state(dg, FT(0); init_on_cpu = true)\n        Q_2D = init_ode_state(barotropic_dg, FT(0); init_on_cpu = true)\n\n    end\n    timeend = runTime + t0\n\n    lsrk_ocean = LSRK54CarpenterKennedy(dg, Q_3D, dt = dt_slow, t0 = t0)\n    lsrk_barotropic =\n        LSRK54CarpenterKennedy(barotropic_dg, Q_2D, dt = dt_fast, t0 = t0)\n\n    odesolver = SplitExplicitLSRK2nSolver(lsrk_ocean, lsrk_barotropic)\n\n    #-- Set up State Check call back for config state arrays, called every ntFrq_SC time steps\n    cbcs_dg = ClimateMachine.StateCheck.sccreate(\n        [\n            (Q_3D, \"oce Q_3D\"),\n            (dg.state_auxiliary, \"oce aux\"),\n            # (dg.diffstate,\"oce diff\",),\n            # (lsrk_ocean.dQ,\"oce_dQ\",),\n            # (dg.modeldata.tendency_dg.state_auxiliary,\"tend Int aux\",),\n            # (dg.modeldata.conti3d_Q,\"conti3d_Q\",),\n            (Q_2D, \"baro Q_2D\"),\n            (barotropic_dg.state_auxiliary, \"baro aux\"),\n        ],\n        ntFrq_SC;\n        prec = 12,\n    )\n    # (barotropic_dg.diffstate,\"baro diff\",),\n    # (lsrk_barotropic.dQ,\"baro_dQ\",)\n    #--\n\n    cb_ntFrq = [n_outp, n_chkp]\n    outp_nb = round(Int64, restart * n_chkp / n_outp)\n    step = [outp_nb, outp_nb, restart + 1]\n    cbvector = make_callbacks(\n        vtkpath,\n        step,\n        cb_ntFrq,\n        timeend,\n        mpicomm,\n        odesolver,\n        dg,\n        model,\n        Q_3D,\n        barotropic_dg,\n        barotropicmodel,\n        Q_2D,\n    )\n\n    eng0 = norm(Q_3D)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\n    ArrayType = %s\"\"\" eng0 ArrayType\n\n    # slow fast state tuple\n    Qvec = (slow = Q_3D, fast = Q_2D)\n    # solve!(Qvec, odesolver; timeend = timeend, callbacks = cbvector)\n    cbv = (cbvector..., cbcs_dg)\n    solve!(Qvec, odesolver; timeend = timeend, callbacks = cbv)\n\n    ## Enable the code block below to print table for use in reference value code\n    ## reference value code sits in a file named $(@__FILE__)_refvals.jl. It is hand\n    ## edited using code generated by block below when reference values are updated.\n    regenRefVals = false\n    if regenRefVals\n        ## Print state statistics in format for use as reference values\n        println(\n            \"# SC ========== Test number \",\n            1,\n            \" reference values and precision match template. =======\",\n        )\n        println(\"# SC ========== $(@__FILE__) test reference values ======================================\")\n        ClimateMachine.StateCheck.scprintref(cbcs_dg)\n        println(\"# SC ====================================================================================\")\n    end\n\n    ## Check results against reference if present\n    checkRefVals = true\n    if checkRefVals\n        include(\"../refvals/simple_box_ivd_refvals.jl\")\n        refDat = (refVals[1], refPrecs[1])\n        checkPass = ClimateMachine.StateCheck.scdocheck(cbcs_dg, refDat)\n        checkPass ? checkRep = \"Pass\" : checkRep = \"Fail\"\n        @info @sprintf(\"\"\"Compare vs RefVals: %s\"\"\", checkRep)\n        @test checkPass\n    end\n\n    return nothing\nend\n\nfunction make_callbacks(\n    vtkpath,\n    step,\n    ntFrq,\n    timeend,\n    mpicomm,\n    odesolver,\n    dg_slow,\n    model_slow,\n    Q_slow,\n    dg_fast,\n    model_fast,\n    Q_fast,\n)\n    n_outp = ntFrq[1]\n    n_chkp = ntFrq[2]\n    mkpath(vtkpath)\n    mkpath(vtkpath * \"/slow\")\n    mkpath(vtkpath * \"/fast\")\n\n    function do_output(span, step, model, dg, Q)\n        outprefix = @sprintf(\n            \"%s/%s/mpirank%04d_step%04d\",\n            vtkpath,\n            span,\n            MPI.Comm_rank(mpicomm),\n            step\n        )\n        @info \"doing VTK output\" outprefix\n        statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n        auxnames = flattenednames(vars_state(model, Auxiliary(), eltype(Q)))\n        writevtk(outprefix, Q, dg, statenames, dg.state_auxiliary, auxnames)\n\n        mycomm = Q.mpicomm\n        ## Generate the pvtu file for these vtk files\n        if MPI.Comm_rank(mpicomm) == 0 && MPI.Comm_size(mpicomm) > 1\n            ## name of the pvtu file\n            pvtuprefix = @sprintf(\"%s/%s/step%04d\", vtkpath, span, step)\n            ## name of each of the ranks vtk files\n            prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n                @sprintf(\"mpirank%04d_step%04d\", i - 1, step)\n            end\n            writepvtu(\n                pvtuprefix,\n                prefixes,\n                (statenames..., auxnames...),\n                eltype(Q),\n            )\n            @info \"Done writing VTK: $pvtuprefix\"\n        end\n\n    end\n\n    do_output(\"slow\", step[1], model_slow, dg_slow, Q_slow)\n    step[1] += 1\n    cbvtk_slow =\n        GenericCallbacks.EveryXSimulationSteps(n_outp) do (init = false)\n            do_output(\"slow\", step[1], model_slow, dg_slow, Q_slow)\n            step[1] += 1\n            nothing\n        end\n\n    do_output(\"fast\", step[2], model_fast, dg_fast, Q_fast)\n    step[2] += 1\n    cbvtk_fast =\n        GenericCallbacks.EveryXSimulationSteps(n_outp) do (init = false)\n            do_output(\"fast\", step[2], model_fast, dg_fast, Q_fast)\n            step[2] += 1\n            nothing\n        end\n\n    starttime = Ref(now())\n    cbinfo = GenericCallbacks.EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q_slow)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %8.2f / %8.2f\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                ODESolvers.gettime(odesolver),\n                timeend,\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n\n    if n_chkp > 0\n        # Note: write zeros instead of Aux vars (not needed to restart); would be\n        # better just to write state vars (once write_checkpoint() can handle it)\n        cb_checkpoint = GenericCallbacks.EveryXSimulationSteps(n_chkp) do\n            write_checkpoint(\n                Q_slow,\n                zero(Q_slow),\n                odesolver,\n                vtkpath,\n                \"baroclinic\",\n                mpicomm,\n                step[3],\n            )\n\n            write_checkpoint(\n                Q_fast,\n                zero(Q_fast),\n                odesolver,\n                vtkpath,\n                \"barotropic\",\n                mpicomm,\n                step[3],\n            )\n\n            step[3] += 1\n            nothing\n        end\n        return (cbvtk_slow, cbvtk_fast, cbinfo, cb_checkpoint)\n        # return (cbinfo, cb_checkpoint)\n    else\n        return (cbvtk_slow, cbvtk_fast, cbinfo)\n        # return (cbinfo)\n    end\n\nend\n\n#################\n# RUN THE TESTS #\n#################\nFT = Float64\nvtkpath = \"vtk_split\"\n\nconst runTime = 5 * 24 * 3600 # s\nconst t_outp = 24 * 3600 # s\nconst t_chkp = runTime  # s\n#const runTime = 6 * 3600 # s\n#const t_outp = 6 * 3600 # s\n#const t_chkp = 0\nconst ntFrq_SC = 1 # frequency (in time-step) for State-Check output\n\nconst N = 4\nconst Nˣ = 20\nconst Nʸ = 20\nconst Nᶻ = 20\nconst Lˣ = 4e6  # m\nconst Lʸ = 4e6  # m\nconst H = 1000  # m\n\nxrange = range(FT(0); length = Nˣ + 1, stop = Lˣ)\nyrange = range(FT(0); length = Nʸ + 1, stop = Lʸ)\nzrange = range(FT(-H); length = Nᶻ + 1, stop = 0)\n\n#const cʰ = sqrt(gravity * H)\nconst cʰ = 1  # typical of ocean internal-wave speed\nconst cᶻ = 0\n\n#- inverse ratio of additional fast time steps (for weighted average)\n#  --> do 1/add more time-steps and average from: 1 - 1/add up to: 1 + 1/add\n# e.g., = 1 --> 100% more ; = 2 --> 50% more ; = 3 --> 33% more ...\nadd_fast_substeps = 2\n\n#- number of Implicit vertical-diffusion sub-time-steps within one model full time-step\n# default = 0 : disable implicit vertical diffusion\nnumImplSteps = 5\n\nconst τₒ = 2e-1  # (Pa = N/m^2)\nconst λʳ = 20 // 86400 # m/s\n#- since we are using old BC (with factor of 2), take only half:\n#const τₒ = 1e-1\n#const λʳ = 10 // 86400\nconst θᴱ = 10    # deg.C\n\n@testset \"$(@__FILE__)\" begin\n    main(restart = 0)\nend\n"
  },
  {
    "path": "test/Ocean/SplitExplicit/simple_box_rk3.jl",
    "content": "#!/usr/bin/env julia --project\nusing ClimateMachine\nClimateMachine.init(parse_clargs = true)\n\nusing ClimateMachine.BalanceLaws: vars_state, Prognostic, Auxiliary\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates: flattenednames\nusing ClimateMachine.Ocean.SplitExplicit01\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.VTK\nusing ClimateMachine.Checkpoint\n\nusing Test\nusing MPI\nusing LinearAlgebra\nusing StaticArrays\nusing Logging, Printf, Dates\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nimport ClimateMachine.Ocean.SplitExplicit01:\n    ocean_init_aux!,\n    ocean_init_state!,\n    ocean_boundary_state!,\n    CoastlineFreeSlip,\n    CoastlineNoSlip,\n    OceanFloorFreeSlip,\n    OceanFloorNoSlip,\n    OceanSurfaceNoStressNoForcing,\n    OceanSurfaceStressNoForcing,\n    OceanSurfaceNoStressForcing,\n    OceanSurfaceStressForcing\nimport ClimateMachine.DGMethods:\n    update_auxiliary_state!, update_auxiliary_state_gradient!, VerticalDirection\n# using GPUifyLoops\n\nconst ArrayType = ClimateMachine.array_type()\n\nstruct SimpleBox{T, BC} <: AbstractOceanProblem\n    Lˣ::T\n    Lʸ::T\n    H::T\n    τₒ::T\n    λʳ::T\n    θᴱ::T\n    boundary_conditions::BC\nend\n\nfunction ocean_init_state!(p::SimpleBox, Q, A, localgeo, t)\n    coords = localgeo.coord\n    @inbounds y = coords[2]\n    @inbounds z = coords[3]\n    @inbounds H = p.H\n\n    Q.u = @SVector [-0, -0]\n    Q.η = -0\n    Q.θ = (5 + 4 * cos(y * π / p.Lʸ)) * (1 + z / H)\n\n    return nothing\nend\n\nfunction ocean_init_aux!(m::OceanModel, p::SimpleBox, A, geom)\n    FT = eltype(A)\n    @inbounds A.y = geom.coord[2]\n\n    # not sure if this is needed but getting weird intialization stuff\n    A.w = -0\n    A.pkin = -0\n    A.wz0 = -0\n    A.u_d = @SVector [-0, -0]\n    A.ΔGu = @SVector [-0, -0]\n\n    return nothing\nend\n\n# A is Filled afer the state\nfunction ocean_init_aux!(m::BarotropicModel, P::SimpleBox, A, geom)\n    @inbounds A.y = geom.coord[2]\n\n    A.Gᵁ = @SVector [-0, -0]\n    A.U_c = @SVector [-0, -0]\n    A.η_c = -0\n    A.U_s = @SVector [-0, -0]\n    A.η_s = -0\n    A.Δu = @SVector [-0, -0]\n    A.η_diag = -0\n    A.Δη = -0\n\n    return nothing\nend\n\nfunction main(; restart = 0)\n    mpicomm = MPI.COMM_WORLD\n\n    ll = uppercase(get(ENV, \"JULIA_LOG_LEVEL\", \"INFO\"))\n    loglevel =\n        ll == \"DEBUG\" ? Logging.Debug :\n        ll == \"WARN\" ? Logging.Warn :\n        ll == \"ERROR\" ? Logging.Error : Logging.Info\n    logger_stream = MPI.Comm_rank(mpicomm) == 0 ? stderr : devnull\n    global_logger(ConsoleLogger(logger_stream, loglevel))\n\n    if restart == 0 && MPI.Comm_rank(mpicomm) == 0 && isdir(vtkpath)\n        @info @sprintf(\"\"\"Remove old dir: %s and make new one\"\"\", vtkpath)\n        rm(vtkpath, recursive = true)\n    end\n\n    brickrange_2D = (xrange, yrange)\n    topl_2D =\n        BrickTopology(mpicomm, brickrange_2D, periodicity = (false, false))\n    grid_2D = DiscontinuousSpectralElementGrid(\n        topl_2D,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    brickrange_3D = (xrange, yrange, zrange)\n    topl_3D = StackedBrickTopology(\n        mpicomm,\n        brickrange_3D;\n        periodicity = (false, false, false),\n        boundary = ((1, 1), (1, 1), (2, 3)),\n    )\n    grid_3D = DiscontinuousSpectralElementGrid(\n        topl_3D,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    BC = (\n        ClimateMachine.Ocean.SplitExplicit01.CoastlineNoSlip(),\n        ClimateMachine.Ocean.SplitExplicit01.OceanFloorNoSlip(),\n        ClimateMachine.Ocean.SplitExplicit01.OceanSurfaceStressForcing(),\n    )\n    prob = SimpleBox{FT, typeof(BC)}(Lˣ, Lʸ, H, τₒ, λʳ, θᴱ, BC)\n    gravity::FT = grav(param_set)\n\n    #- set model time-step:\n    dt_fast = 120\n    dt_slow = 2400\n    # dt_fast = 240\n    # dt_slow = 5400\n    if t_chkp > 0\n        n_chkp = ceil(Int64, t_chkp / dt_slow)\n        dt_slow = t_chkp / n_chkp\n    else\n        n_chkp = ceil(Int64, runTime / dt_slow)\n        dt_slow = runTime / n_chkp\n        n_chkp = 0\n    end\n    n_outp =\n        t_outp > 0 ? floor(Int64, t_outp / dt_slow) :\n        ceil(Int64, runTime / dt_slow)\n    ivdc_dt = numImplSteps > 0 ? dt_slow / FT(numImplSteps) : dt_slow\n\n    model = OceanModel{FT}(\n        prob,\n        grav = gravity,\n        cʰ = cʰ,\n        add_fast_substeps = add_fast_substeps,\n        numImplSteps = numImplSteps,\n        ivdc_dt = ivdc_dt,\n        κᶜ = FT(0.1),\n    )\n    # model = OceanModel{FT}(prob, cʰ = cʰ, fₒ = FT(0), β = FT(0) )\n    # model = OceanModel{FT}(prob, cʰ = cʰ, νʰ = FT(1e3), νᶻ = FT(1e-3) )\n    # model = OceanModel{FT}(prob, cʰ = cʰ, νʰ = FT(0), fₒ = FT(0), β = FT(0) )\n\n    barotropicmodel = BarotropicModel(model)\n\n    minΔx = min_node_distance(grid_3D, HorizontalDirection())\n    minΔz = min_node_distance(grid_3D, VerticalDirection())\n    #- 2 horiz directions\n    gravity_max_dT = 1 / (2 * sqrt(gravity * H) / minΔx)\n    # dt_fast = minimum([gravity_max_dT])\n\n    #- 2 horiz directions + harmonic visc or diffusion: 2^2 factor in CFL:\n    viscous_max_dT = 1 / (2 * model.νʰ / minΔx^2 + model.νᶻ / minΔz^2) / 4\n    diffusive_max_dT = 1 / (2 * model.κʰ / minΔx^2 + model.κᶻ / minΔz^2) / 4\n    # dt_slow = minimum([diffusive_max_dT, viscous_max_dT])\n\n    @info @sprintf(\n        \"\"\"Update\n           Gravity Max-dT = %.1f\n           Timestep       = %.1f\"\"\",\n        gravity_max_dT,\n        dt_fast\n    )\n\n    @info @sprintf(\n        \"\"\"Update\n       Viscous   Max-dT = %.1f\n       Diffusive Max-dT = %.1f\n       Timestep      = %.1f\"\"\",\n        viscous_max_dT,\n        diffusive_max_dT,\n        dt_slow\n    )\n\n    if restart > 0\n        direction = EveryDirection()\n        Q_3D, _, t0 =\n            read_checkpoint(vtkpath, \"baroclinic\", ArrayType, mpicomm, restart)\n        Q_2D, _, _ =\n            read_checkpoint(vtkpath, \"barotropic\", ArrayType, mpicomm, restart)\n\n        dg = OceanDGModel(\n            model,\n            grid_3D,\n            RusanovNumericalFlux(),\n            CentralNumericalFluxSecondOrder(),\n            CentralNumericalFluxGradient(),\n        )\n        barotropic_dg = DGModel(\n            barotropicmodel,\n            grid_2D,\n            RusanovNumericalFlux(),\n            CentralNumericalFluxSecondOrder(),\n            CentralNumericalFluxGradient(),\n        )\n\n        Q_3D = restart_ode_state(dg, Q_3D; init_on_cpu = true)\n        Q_2D = restart_ode_state(barotropic_dg, Q_2D; init_on_cpu = true)\n\n    else\n        t0 = 0\n        dg = OceanDGModel(\n            model,\n            grid_3D,\n            # CentralNumericalFluxFirstOrder(),\n            RusanovNumericalFlux(),\n            CentralNumericalFluxSecondOrder(),\n            CentralNumericalFluxGradient(),\n        )\n        barotropic_dg = DGModel(\n            barotropicmodel,\n            grid_2D,\n            # CentralNumericalFluxFirstOrder(),\n            RusanovNumericalFlux(),\n            CentralNumericalFluxSecondOrder(),\n            CentralNumericalFluxGradient(),\n        )\n\n        Q_3D = init_ode_state(dg, FT(0); init_on_cpu = true)\n        Q_2D = init_ode_state(barotropic_dg, FT(0); init_on_cpu = true)\n\n    end\n    timeend = runTime + t0\n\n    lsrk_ocean = LS3NRK33Heuns(dg, Q_3D, dt = dt_slow, t0 = t0)\n    lsrk_barotropic = LS3NRK33Heuns(barotropic_dg, Q_2D, dt = dt_fast, t0 = t0)\n\n    odesolver = SplitExplicitLSRK3nSolver(lsrk_ocean, lsrk_barotropic)\n\n    #-- Set up State Check call back for config state arrays, called every ntFrq_SC time steps\n    cbcs_dg = ClimateMachine.StateCheck.sccreate(\n        [\n            (Q_3D, \"oce Q_3D\"),\n            (dg.state_auxiliary, \"oce aux\"),\n            # (dg.diffstate,\"oce diff\",),\n            # (lsrk_ocean.dQ,\"oce_dQ\",),\n            # (dg.modeldata.tendency_dg.state_auxiliary,\"tend Int aux\",),\n            # (dg.modeldata.conti3d_Q,\"conti3d_Q\",),\n            (Q_2D, \"baro Q_2D\"),\n            (barotropic_dg.state_auxiliary, \"baro aux\"),\n        ],\n        ntFrq_SC;\n        prec = 12,\n    )\n    # (barotropic_dg.diffstate,\"baro diff\",),\n    # (lsrk_barotropic.dQ,\"baro_dQ\",)\n    #--\n\n    cb_ntFrq = [n_outp, n_chkp]\n    outp_nb = round(Int64, restart * n_chkp / n_outp)\n    step = [outp_nb, outp_nb, restart + 1]\n    cbvector = make_callbacks(\n        vtkpath,\n        step,\n        cb_ntFrq,\n        timeend,\n        mpicomm,\n        odesolver,\n        dg,\n        model,\n        Q_3D,\n        barotropic_dg,\n        barotropicmodel,\n        Q_2D,\n    )\n\n    eng0 = norm(Q_3D)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\n    ArrayType = %s\"\"\" eng0 ArrayType\n\n    # slow fast state tuple\n    Qvec = (slow = Q_3D, fast = Q_2D)\n    # solve!(Qvec, odesolver; timeend = timeend, callbacks = cbvector)\n    cbv = (cbvector..., cbcs_dg)\n    solve!(Qvec, odesolver; timeend = timeend, callbacks = cbv)\n\n    ## Enable the code block below to print table for use in reference value code\n    ## reference value code sits in a file named $(@__FILE__)_refvals.jl. It is hand\n    ## edited using code generated by block below when reference values are updated.\n    regenRefVals = false\n    if regenRefVals\n        ## Print state statistics in format for use as reference values\n        println(\n            \"# SC ========== Test number \",\n            1,\n            \" reference values and precision match template. =======\",\n        )\n        println(\"# SC ========== $(@__FILE__) test reference values ======================================\")\n        ClimateMachine.StateCheck.scprintref(cbcs_dg)\n        println(\"# SC ====================================================================================\")\n    end\n\n    ## Check results against reference if present\n    checkRefVals = true\n    if checkRefVals\n        include(\"../refvals/simple_box_rk3_refvals.jl\")\n        refDat = (refVals[1], refPrecs[1])\n        checkPass = ClimateMachine.StateCheck.scdocheck(cbcs_dg, refDat)\n        checkPass ? checkRep = \"Pass\" : checkRep = \"Fail\"\n        @info @sprintf(\"\"\"Compare vs RefVals: %s\"\"\", checkRep)\n        @test checkPass\n    end\n\n    return nothing\nend\n\nfunction make_callbacks(\n    vtkpath,\n    step,\n    ntFrq,\n    timeend,\n    mpicomm,\n    odesolver,\n    dg_slow,\n    model_slow,\n    Q_slow,\n    dg_fast,\n    model_fast,\n    Q_fast,\n)\n    n_outp = ntFrq[1]\n    n_chkp = ntFrq[2]\n    mkpath(vtkpath)\n    mkpath(vtkpath * \"/slow\")\n    mkpath(vtkpath * \"/fast\")\n\n    function do_output(span, step, model, dg, Q)\n        outprefix = @sprintf(\n            \"%s/%s/mpirank%04d_step%04d\",\n            vtkpath,\n            span,\n            MPI.Comm_rank(mpicomm),\n            step\n        )\n        @info \"doing VTK output\" outprefix\n        statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n        auxnames = flattenednames(vars_state(model, Auxiliary(), eltype(Q)))\n        writevtk(outprefix, Q, dg, statenames, dg.state_auxiliary, auxnames)\n\n        mycomm = Q.mpicomm\n        ## Generate the pvtu file for these vtk files\n        if MPI.Comm_rank(mpicomm) == 0 && MPI.Comm_size(mpicomm) > 1\n            ## name of the pvtu file\n            pvtuprefix = @sprintf(\"%s/%s/step%04d\", vtkpath, span, step)\n            ## name of each of the ranks vtk files\n            prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n                @sprintf(\"mpirank%04d_step%04d\", i - 1, step)\n            end\n            writepvtu(\n                pvtuprefix,\n                prefixes,\n                (statenames..., auxnames...),\n                eltype(Q),\n            )\n            @info \"Done writing VTK: $pvtuprefix\"\n        end\n\n    end\n\n    do_output(\"slow\", step[1], model_slow, dg_slow, Q_slow)\n    step[1] += 1\n    cbvtk_slow =\n        GenericCallbacks.EveryXSimulationSteps(n_outp) do (init = false)\n            do_output(\"slow\", step[1], model_slow, dg_slow, Q_slow)\n            step[1] += 1\n            nothing\n        end\n\n    do_output(\"fast\", step[2], model_fast, dg_fast, Q_fast)\n    step[2] += 1\n    cbvtk_fast =\n        GenericCallbacks.EveryXSimulationSteps(n_outp) do (init = false)\n            do_output(\"fast\", step[2], model_fast, dg_fast, Q_fast)\n            step[2] += 1\n            nothing\n        end\n\n    starttime = Ref(now())\n    cbinfo = GenericCallbacks.EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q_slow)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %8.2f / %8.2f\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                ODESolvers.gettime(odesolver),\n                timeend,\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n\n    if n_chkp > 0\n        # Note: write zeros instead of Aux vars (not needed to restart); would be\n        # better just to write state vars (once write_checkpoint() can handle it)\n        cb_checkpoint = GenericCallbacks.EveryXSimulationSteps(n_chkp) do\n            write_checkpoint(\n                Q_slow,\n                zero(Q_slow),\n                odesolver,\n                vtkpath,\n                \"baroclinic\",\n                mpicomm,\n                step[3],\n            )\n\n            write_checkpoint(\n                Q_fast,\n                zero(Q_fast),\n                odesolver,\n                vtkpath,\n                \"barotropic\",\n                mpicomm,\n                step[3],\n            )\n\n            step[3] += 1\n            nothing\n        end\n        return (cbvtk_slow, cbvtk_fast, cbinfo, cb_checkpoint)\n    else\n        return (cbvtk_slow, cbvtk_fast, cbinfo)\n    end\n\nend\n\n#################\n# RUN THE TESTS #\n#################\nFT = Float64\nvtkpath = \"vtk_split\"\n\nconst runTime = 3 * 24 * 3600 # s\nconst t_outp = 24 * 3600 # s\nconst t_chkp = runTime  # s\n#const runTime = 6 * 3600 # s\n#const t_outp = 6 * 3600 # s\n#const t_chkp = 0\nconst ntFrq_SC = 1 # frequency (in time-step) for State-Check output\n\nconst N = 4\nconst Nˣ = 20\nconst Nʸ = 20\nconst Nᶻ = 20\nconst Lˣ = 4e6  # m\nconst Lʸ = 4e6  # m\nconst H = 1000  # m\n\nxrange = range(FT(0); length = Nˣ + 1, stop = Lˣ)\nyrange = range(FT(0); length = Nʸ + 1, stop = Lʸ)\nzrange = range(FT(-H); length = Nᶻ + 1, stop = 0)\n\n#const cʰ = sqrt(gravity * H)\nconst cʰ = 1  # typical of ocean internal-wave speed\nconst cᶻ = 0\n\n#- inverse ratio of additional fast time steps (for weighted average)\n#  --> do 1/add more time-steps and average from: 1 - 1/add up to: 1 + 1/add\n# e.g., = 1 --> 100% more ; = 2 --> 50% more ; = 3 --> 33% more ...\nadd_fast_substeps = 3\n\n#- number of Implicit vertical-diffusion sub-time-steps within one model full time-step\n# default = 0 : disable implicit vertical diffusion\nnumImplSteps = 5\n\nconst τₒ = 2e-1  # (Pa = N/m^2)\nconst λʳ = 20 // 86400 # m/s\n#- since we are using old BC (with factor of 2), take only half:\n#const τₒ = 1e-1\n#const λʳ = 10 // 86400\nconst θᴱ = 10    # deg.C\n\n@testset \"$(@__FILE__)\" begin\n    main(restart = 0)\nend\n"
  },
  {
    "path": "test/Ocean/SplitExplicit/simple_dbl_gyre.jl",
    "content": "#!/usr/bin/env julia --project\nusing ClimateMachine\nClimateMachine.init(parse_clargs = true)\n\nusing ClimateMachine.BalanceLaws: vars_state, Prognostic, Auxiliary\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates: flattenednames\nusing ClimateMachine.Ocean.SplitExplicit01\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.VTK\nusing ClimateMachine.Checkpoint\n\nusing Test\nusing MPI\nusing LinearAlgebra\nusing StaticArrays\nusing Logging, Printf, Dates\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nimport ClimateMachine.Ocean.SplitExplicit01:\n    ocean_init_aux!,\n    ocean_init_state!,\n    ocean_boundary_state!,\n    CoastlineFreeSlip,\n    CoastlineNoSlip,\n    OceanFloorFreeSlip,\n    OceanFloorNoSlip,\n    OceanSurfaceNoStressNoForcing,\n    OceanSurfaceStressNoForcing,\n    OceanSurfaceNoStressForcing,\n    OceanSurfaceStressForcing,\n    velocity_flux,\n    temperature_flux\nimport ClimateMachine.DGMethods:\n    update_auxiliary_state!, update_auxiliary_state_gradient!, VerticalDirection\n# using GPUifyLoops\n\nconst ArrayType = ClimateMachine.array_type()\n\nstruct DoubleGyreBox{T, BC} <: AbstractOceanProblem\n    Lˣ::T\n    Lʸ::T\n    H::T\n    τₒ::T\n    λʳ::T\n    θᴱ::T\n    boundary_conditions::BC\nend\n\n@inline velocity_flux(p::DoubleGyreBox, y, ρ) =\n    -(p.τₒ / ρ) * cos(2 * π * y / p.Lʸ)\n\n@inline function temperature_flux(p::DoubleGyreBox, y, θ)\n    θʳ = p.θᴱ * (1 - y / p.Lʸ)\n    return p.λʳ * (θʳ - θ)\nend\n\nfunction ocean_init_state!(p::DoubleGyreBox, Q, A, localgeo, t)\n    coords = localgeo.coord\n    @inbounds y = coords[2]\n    @inbounds z = coords[3]\n    @inbounds H = p.H\n\n    Q.u = @SVector [-0, -0]\n    Q.η = -0\n    Q.θ = (12 + 10 * cos(π * y / p.Lʸ)) * (1 + z / H)\n\n    return nothing\nend\n\nfunction ocean_init_aux!(m::OceanModel, p::DoubleGyreBox, A, geom)\n    FT = eltype(A)\n    @inbounds A.y = geom.coord[2]\n\n    # not sure if this is needed but getting weird intialization stuff\n    A.w = -0\n    A.pkin = -0\n    A.wz0 = -0\n    A.u_d = @SVector [-0, -0]\n    A.ΔGu = @SVector [-0, -0]\n\n    return nothing\nend\n\n# A is Filled afer the state\nfunction ocean_init_aux!(m::BarotropicModel, P::DoubleGyreBox, A, geom)\n    @inbounds A.y = geom.coord[2]\n\n    A.Gᵁ = @SVector [-0, -0]\n    A.U_c = @SVector [-0, -0]\n    A.η_c = -0\n    A.U_s = @SVector [-0, -0]\n    A.η_s = -0\n    A.Δu = @SVector [-0, -0]\n    A.η_diag = -0\n    A.Δη = -0\n\n    return nothing\nend\n\nfunction main(; restart = 0)\n    mpicomm = MPI.COMM_WORLD\n\n    ll = uppercase(get(ENV, \"JULIA_LOG_LEVEL\", \"INFO\"))\n    loglevel =\n        ll == \"DEBUG\" ? Logging.Debug :\n        ll == \"WARN\" ? Logging.Warn :\n        ll == \"ERROR\" ? Logging.Error : Logging.Info\n    logger_stream = MPI.Comm_rank(mpicomm) == 0 ? stderr : devnull\n    global_logger(ConsoleLogger(logger_stream, loglevel))\n\n    if restart == 0 && MPI.Comm_rank(mpicomm) == 0 && isdir(vtkpath)\n        @info @sprintf(\"\"\"Remove old dir: %s and make new one\"\"\", vtkpath)\n        rm(vtkpath, recursive = true)\n    end\n\n    brickrange_2D = (xrange, yrange)\n    topl_2D =\n        BrickTopology(mpicomm, brickrange_2D, periodicity = (false, false))\n    grid_2D = DiscontinuousSpectralElementGrid(\n        topl_2D,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    brickrange_3D = (xrange, yrange, zrange)\n    topl_3D = StackedBrickTopology(\n        mpicomm,\n        brickrange_3D;\n        periodicity = (false, false, false),\n        boundary = ((1, 1), (1, 1), (2, 3)),\n    )\n    grid_3D = DiscontinuousSpectralElementGrid(\n        topl_3D,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    BC = (\n        ClimateMachine.Ocean.SplitExplicit01.CoastlineNoSlip(),\n        ClimateMachine.Ocean.SplitExplicit01.OceanFloorNoSlip(),\n        ClimateMachine.Ocean.SplitExplicit01.OceanSurfaceStressForcing(),\n    )\n    prob = DoubleGyreBox{FT, typeof(BC)}(Lˣ, Lʸ, H, τₒ, λʳ, θᴱ, BC)\n    gravity::FT = grav(param_set)\n\n    #- set model time-step:\n    dt_fast = 96\n    dt_slow = 3456\n    if t_chkp > 0\n        n_chkp = ceil(Int64, t_chkp / dt_slow)\n        dt_slow = t_chkp / n_chkp\n    else\n        n_chkp = ceil(Int64, runTime / dt_slow)\n        dt_slow = runTime / n_chkp\n        n_chkp = 0\n    end\n    n_outp =\n        t_outp > 0 ? floor(Int64, t_outp / dt_slow) :\n        ceil(Int64, runTime / dt_slow)\n    ivdc_dt = numImplSteps > 0 ? dt_slow / FT(numImplSteps) : dt_slow\n\n    model = OceanModel{FT}(\n        prob,\n        grav = gravity,\n        cʰ = cʰ,\n        add_fast_substeps = add_fast_substeps,\n        numImplSteps = numImplSteps,\n        ivdc_dt = ivdc_dt,\n        νʰ = FT(15e3),\n        νᶻ = FT(5e-3),\n        κᶜ = FT(1.0),\n        fₒ = FT(3.8e-5),\n        β = FT(1.7e-11),\n    )\n    # model = OceanModel{FT}(prob, cʰ = cʰ, fₒ = FT(0), β = FT(0) )\n    # model = OceanModel{FT}(prob, cʰ = cʰ, νʰ = FT(1e3), νᶻ = FT(1e-3) )\n    # model = OceanModel{FT}(prob, cʰ = cʰ, νʰ = FT(0), fₒ = FT(0), β = FT(0) )\n\n    barotropicmodel = BarotropicModel(model)\n\n    minΔx = min_node_distance(grid_3D, HorizontalDirection())\n    minΔz = min_node_distance(grid_3D, VerticalDirection())\n    #- 2 horiz directions\n    gravity_max_dT = 1 / (2 * sqrt(gravity * H) / minΔx)\n    # dt_fast = minimum([gravity_max_dT])\n\n    #- 2 horiz directions + harmonic visc or diffusion: 2^2 factor in CFL:\n    viscous_max_dT = 1 / (2 * model.νʰ / minΔx^2 + model.νᶻ / minΔz^2) / 4\n    diffusive_max_dT = 1 / (2 * model.κʰ / minΔx^2 + model.κᶻ / minΔz^2) / 4\n    # dt_slow = minimum([diffusive_max_dT, viscous_max_dT])\n\n    @info @sprintf(\n        \"\"\"Update\n           Gravity Max-dT = %.1f\n           Timestep       = %.1f\"\"\",\n        gravity_max_dT,\n        dt_fast\n    )\n\n    @info @sprintf(\n        \"\"\"Update\n       Viscous   Max-dT = %.1f\n       Diffusive Max-dT = %.1f\n       Timestep      = %.1f\"\"\",\n        viscous_max_dT,\n        diffusive_max_dT,\n        dt_slow\n    )\n\n    if restart > 0\n        direction = EveryDirection()\n        Q_3D, _, t0 =\n            read_checkpoint(vtkpath, \"baroclinic\", ArrayType, mpicomm, restart)\n        Q_2D, _, _ =\n            read_checkpoint(vtkpath, \"barotropic\", ArrayType, mpicomm, restart)\n\n        dg = OceanDGModel(\n            model,\n            grid_3D,\n            RusanovNumericalFlux(),\n            CentralNumericalFluxSecondOrder(),\n            CentralNumericalFluxGradient(),\n        )\n        barotropic_dg = DGModel(\n            barotropicmodel,\n            grid_2D,\n            RusanovNumericalFlux(),\n            CentralNumericalFluxSecondOrder(),\n            CentralNumericalFluxGradient(),\n        )\n\n        Q_3D = restart_ode_state(dg, Q_3D; init_on_cpu = true)\n        Q_2D = restart_ode_state(barotropic_dg, Q_2D; init_on_cpu = true)\n\n    else\n        t0 = 0\n        dg = OceanDGModel(\n            model,\n            grid_3D,\n            # CentralNumericalFluxFirstOrder(),\n            RusanovNumericalFlux(),\n            CentralNumericalFluxSecondOrder(),\n            CentralNumericalFluxGradient(),\n        )\n        barotropic_dg = DGModel(\n            barotropicmodel,\n            grid_2D,\n            # CentralNumericalFluxFirstOrder(),\n            RusanovNumericalFlux(),\n            CentralNumericalFluxSecondOrder(),\n            CentralNumericalFluxGradient(),\n        )\n\n        Q_3D = init_ode_state(dg, FT(0); init_on_cpu = true)\n        Q_2D = init_ode_state(barotropic_dg, FT(0); init_on_cpu = true)\n\n    end\n    timeend = runTime + t0\n\n    lsrk_ocean = LS3NRK33Heuns(dg, Q_3D, dt = dt_slow, t0 = t0)\n    lsrk_barotropic = LS3NRK33Heuns(barotropic_dg, Q_2D, dt = dt_fast, t0 = t0)\n\n    odesolver = SplitExplicitLSRK3nSolver(lsrk_ocean, lsrk_barotropic)\n\n    #-- Set up State Check call back for config state arrays, called every ntFrq_SC time steps\n    cbcs_dg = ClimateMachine.StateCheck.sccreate(\n        [\n            (Q_3D, \"oce Q_3D\"),\n            (dg.state_auxiliary, \"oce aux\"),\n            # (dg.diffstate,\"oce diff\",),\n            # (lsrk_ocean.dQ,\"oce_dQ\",),\n            # (dg.modeldata.tendency_dg.state_auxiliary,\"tend Int aux\",),\n            # (dg.modeldata.conti3d_Q,\"conti3d_Q\",),\n            (Q_2D, \"baro Q_2D\"),\n            (barotropic_dg.state_auxiliary, \"baro aux\"),\n        ],\n        ntFrq_SC;\n        prec = 12,\n    )\n    # (barotropic_dg.diffstate,\"baro diff\",),\n    # (lsrk_barotropic.dQ,\"baro_dQ\",)\n    #--\n\n    cb_ntFrq = [n_outp, n_chkp]\n    outp_nb = round(Int64, restart * n_chkp / n_outp)\n    step = [outp_nb, outp_nb, restart + 1]\n    cbvector = make_callbacks(\n        vtkpath,\n        step,\n        cb_ntFrq,\n        timeend,\n        mpicomm,\n        odesolver,\n        dg,\n        model,\n        Q_3D,\n        barotropic_dg,\n        barotropicmodel,\n        Q_2D,\n    )\n\n    eng0 = norm(Q_3D)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\n    ArrayType = %s\"\"\" eng0 ArrayType\n\n    # slow fast state tuple\n    Qvec = (slow = Q_3D, fast = Q_2D)\n    # solve!(Qvec, odesolver; timeend = timeend, callbacks = cbvector)\n    cbv = (cbvector..., cbcs_dg)\n    solve!(Qvec, odesolver; timeend = timeend, callbacks = cbv)\n\n    ## Enable the code block below to print table for use in reference value code\n    ## reference value code sits in a file named $(@__FILE__)_refvals.jl. It is hand\n    ## edited using code generated by block below when reference values are updated.\n    regenRefVals = false\n    if regenRefVals\n        ## Print state statistics in format for use as reference values\n        println(\n            \"# SC ========== Test number \",\n            1,\n            \" reference values and precision match template. =======\",\n        )\n        println(\"# SC ========== $(@__FILE__) test reference values ======================================\")\n        ClimateMachine.StateCheck.scprintref(cbcs_dg)\n        println(\"# SC ====================================================================================\")\n    end\n\n    ## Check results against reference if present\n    checkRefVals = true\n    if checkRefVals\n        include(\"../refvals/simple_dbl_gyre_refvals.jl\")\n        refDat = (refVals[1], refPrecs[1])\n        checkPass = ClimateMachine.StateCheck.scdocheck(cbcs_dg, refDat)\n        checkPass ? checkRep = \"Pass\" : checkRep = \"Fail\"\n        @info @sprintf(\"\"\"Compare vs RefVals: %s\"\"\", checkRep)\n        @test checkPass\n    end\n\n    return nothing\nend\n\nfunction make_callbacks(\n    vtkpath,\n    step,\n    ntFrq,\n    timeend,\n    mpicomm,\n    odesolver,\n    dg_slow,\n    model_slow,\n    Q_slow,\n    dg_fast,\n    model_fast,\n    Q_fast,\n)\n    n_outp = ntFrq[1]\n    n_chkp = ntFrq[2]\n    mkpath(vtkpath)\n    mkpath(vtkpath * \"/slow\")\n    mkpath(vtkpath * \"/fast\")\n\n    function do_output(span, step, model, dg, Q)\n        outprefix = @sprintf(\n            \"%s/%s/mpirank%04d_step%04d\",\n            vtkpath,\n            span,\n            MPI.Comm_rank(mpicomm),\n            step\n        )\n        @info \"doing VTK output\" outprefix\n        statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n        auxnames = flattenednames(vars_state(model, Auxiliary(), eltype(Q)))\n        writevtk(outprefix, Q, dg, statenames, dg.state_auxiliary, auxnames)\n\n        mycomm = Q.mpicomm\n        ## Generate the pvtu file for these vtk files\n        if MPI.Comm_rank(mpicomm) == 0 && MPI.Comm_size(mpicomm) > 1\n            ## name of the pvtu file\n            pvtuprefix = @sprintf(\"%s/%s/step%04d\", vtkpath, span, step)\n            ## name of each of the ranks vtk files\n            prefixes = ntuple(MPI.Comm_size(mpicomm)) do i\n                @sprintf(\"mpirank%04d_step%04d\", i - 1, step)\n            end\n            writepvtu(\n                pvtuprefix,\n                prefixes,\n                (statenames..., auxnames...),\n                eltype(Q),\n            )\n            @info \"Done writing VTK: $pvtuprefix\"\n        end\n\n    end\n\n    do_output(\"slow\", step[1], model_slow, dg_slow, Q_slow)\n    step[1] += 1\n    cbvtk_slow =\n        GenericCallbacks.EveryXSimulationSteps(n_outp) do (init = false)\n            do_output(\"slow\", step[1], model_slow, dg_slow, Q_slow)\n            step[1] += 1\n            nothing\n        end\n\n    do_output(\"fast\", step[2], model_fast, dg_fast, Q_fast)\n    step[2] += 1\n    cbvtk_fast =\n        GenericCallbacks.EveryXSimulationSteps(n_outp) do (init = false)\n            do_output(\"fast\", step[2], model_fast, dg_fast, Q_fast)\n            step[2] += 1\n            nothing\n        end\n\n    starttime = Ref(now())\n    cbinfo = GenericCallbacks.EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q_slow)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %8.2f / %8.2f\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                ODESolvers.gettime(odesolver),\n                timeend,\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n\n    if n_chkp > 0\n        # Note: write zeros instead of Aux vars (not needed to restart); would be\n        # better just to write state vars (once write_checkpoint() can handle it)\n        cb_checkpoint = GenericCallbacks.EveryXSimulationSteps(n_chkp) do\n            write_checkpoint(\n                Q_slow,\n                zero(Q_slow),\n                odesolver,\n                vtkpath,\n                \"baroclinic\",\n                mpicomm,\n                step[3],\n            )\n\n            write_checkpoint(\n                Q_fast,\n                zero(Q_fast),\n                odesolver,\n                vtkpath,\n                \"barotropic\",\n                mpicomm,\n                step[3],\n            )\n\n            step[3] += 1\n            nothing\n        end\n        return (cbvtk_slow, cbvtk_fast, cbinfo, cb_checkpoint)\n    else\n        return (cbvtk_slow, cbvtk_fast, cbinfo)\n    end\n\nend\n\n#################\n# RUN THE TESTS #\n#################\nFT = Float64\nvtkpath = \"vtk_split\"\n\nconst runTime = 3 * 24 * 3600 # s\nconst t_outp = 24 * 3600 # s\nconst t_chkp = runTime  # s\n#const runTime = 6 * 3600 # s\n#const t_outp = 6 * 3600 # s\n#const t_chkp = 0\nconst ntFrq_SC = 1 # frequency (in time-step) for State-Check output\n\nconst N = 4\nconst Nˣ = 20\nconst Nʸ = 30\nconst Nᶻ = 15\nconst Lˣ = 4e6  # m\nconst Lʸ = 6e6  # m\nconst H = 3000  # m\n\nxrange = range(FT(0); length = Nˣ + 1, stop = Lˣ)\nyrange = range(FT(0); length = Nʸ + 1, stop = Lʸ)\nzrange = range(FT(-H); length = Nᶻ + 1, stop = 0)\n# dz = [576, 540, 433, 339, 266, 208, 162, 128, 99, 75, 58, 43, 31, 22, 20]\n# zrange = zeros(FT, Nᶻ + 1)\n# zrange[2:(Nᶻ + 1)] .= cumsum(dz)\n# zrange .-= H\n\n#const cʰ = sqrt(gravity * H)\nconst cʰ = 1  # typical of ocean internal-wave speed\nconst cᶻ = 0\n\n#- inverse ratio of additional fast time steps (for weighted average)\n#  --> do 1/add more time-steps and average from: 1 - 1/add up to: 1 + 1/add\n# e.g., = 1 --> 100% more ; = 2 --> 50% more ; = 3 --> 33% more ...\nadd_fast_substeps = 3\n\n#- number of Implicit vertical-diffusion sub-time-steps within one model full time-step\n# default = 0 : disable implicit vertical diffusion\nnumImplSteps = 5\n\nconst τₒ = 1e-1  # (Pa = N/m^2)\nconst λʳ = 20 // 86400 # m/s\n#- since we are using old BC (with factor of 2), take only half:\n#const τₒ = 5e-2\n#const λʳ = 10 // 86400\nconst θᴱ = 25    # deg.C\n\n@testset \"$(@__FILE__)\" begin\n    main(restart = 0)\nend\n"
  },
  {
    "path": "test/Ocean/SplitExplicit/split_explicit.jl",
    "content": "#!/usr/bin/env julia --project\nusing Test\nusing ClimateMachine\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Mesh.Grids: polynomialorders\nusing ClimateMachine.Ocean\nusing ClimateMachine.Ocean.HydrostaticBoussinesq\nusing ClimateMachine.Ocean.ShallowWater\nusing ClimateMachine.Ocean.SplitExplicit: VerticalIntegralModel\nusing ClimateMachine.Ocean.SplitExplicit01\nusing ClimateMachine.Ocean.OceanProblems\n\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.BalanceLaws: vars_state, Prognostic, Auxiliary\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.VTK\nusing ClimateMachine.Checkpoint\nusing ClimateMachine.SystemSolvers\n\nusing MPI\nusing LinearAlgebra\nusing StaticArrays\nusing Logging, Printf, Dates\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nstruct SplitConfig{N, D3, D2, S, M, AT}\n    name::N\n    dg_3D::D3\n    dg_2D::D2\n    solver::S\n    mpicomm::M\n    ArrayType::AT\nend\n\nfunction run_split_explicit(\n    config::SplitConfig,\n    timespan;\n    dt_fast = 300,\n    dt_slow = 300,\n    refDat = (),\n    restart = 0,\n    analytic_solution = false,\n)\n    Q_3D, Q_2D, t0 = init_states(config, Val(restart))\n\n    tout, timeend = timespan\n\n    # @show dt_fast = floor(Int, 1 / (2 * sqrt(gravity * H) / minΔx)) # / 4\n    # @show dt_slow = floor(Int, minΔx / 15) # / 4\n\n    nout = ceil(Int64, tout / dt_slow)\n    dt_slow = tout / nout\n\n    timeendlocal = timeend + t0\n\n    lsrk_3D = LSRK54CarpenterKennedy(config.dg_3D, Q_3D, dt = dt_slow, t0 = t0)\n    lsrk_2D = LSRK54CarpenterKennedy(config.dg_2D, Q_2D, dt = dt_fast, t0 = t0)\n\n    odesolver = config.solver(lsrk_3D, lsrk_2D;)\n\n    vtkstep = [restart, restart, restart + 1, restart + 1]\n    cbvector = make_callbacks(\n        abspath(joinpath(ClimateMachine.Settings.output_dir, config.name)),\n        vtkstep,\n        nout,\n        config.mpicomm,\n        odesolver,\n        config.dg_3D,\n        config.dg_3D.balance_law,\n        Q_3D,\n        config.dg_2D,\n        config.dg_2D.balance_law,\n        Q_2D,\n        timeendlocal,\n    )\n\n    eng0 = norm(Q_3D)\n    @info @sprintf \"\"\"Starting\n    norm(Q₀) = %.16e\n    ArrayType = %s\"\"\" eng0 config.ArrayType\n\n    # slow fast state tuple\n    Qvec = (slow = Q_3D, fast = Q_2D)\n    solve!(Q_3D, odesolver; timeend = timeendlocal, callbacks = cbvector)\n\n    if analytic_solution\n        Qe_3D = init_ode_state(config.dg_3D, timeendlocal, init_on_cpu = true)\n        Qe_2D = init_ode_state(config.dg_2D, timeendlocal, init_on_cpu = true)\n\n        error_3D = euclidean_distance(Q_3D, Qe_3D) / norm(Qe_3D)\n        error_2D = euclidean_distance(Q_2D, Qe_2D) / norm(Qe_2D)\n\n        println(\"3D error = \", error_3D)\n        println(\"2D error = \", error_2D)\n        @test isapprox(error_3D, FT(0.0); atol = 0.005)\n        @test isapprox(error_2D, FT(0.0); atol = 0.005)\n    end\n\n    ## Check results against reference\n    ClimateMachine.StateCheck.scprintref(cbvector[end])\n    if length(refDat) > 0\n        @test ClimateMachine.StateCheck.scdocheck(cbvector[end], refDat)\n    end\n\n    return nothing\nend\n\nfunction init_states(config, ::Val{0})\n    Q_3D = init_ode_state(config.dg_3D, FT(0); init_on_cpu = true)\n    Q_2D = config.dg_3D.modeldata.Q_2D\n\n    return Q_3D, Q_2D, 0\nend\n\nfunction init_states(config, ::Val{restart}) where {restart}\n    Q_3D, A_3D, t0 = read_checkpoint(\n        abspath(joinpath(ClimateMachine.Settings.output_dir, config.name)),\n        \"baroclinic\",\n        config.ArrayType,\n        config.mpicomm,\n        restart,\n    )\n    Q_2D_restart, A_2D, _ = read_checkpoint(\n        abspath(joinpath(ClimateMachine.Settings.output_dir, config.name)),\n        \"barotropic\",\n        config.ArrayType,\n        config.mpicomm,\n        restart,\n    )\n\n    direction = EveryDirection()\n    A_3D = restart_auxiliary_state(\n        config.dg_3D.balance_law,\n        config.dg_3D.grid,\n        A_3D,\n        direction,\n    )\n    A_2D = restart_auxiliary_state(\n        config.dg_2D.balance_law,\n        config.dg_2D.grid,\n        A_2D,\n        direction,\n    )\n\n    config.dg_3D.state_auxiliary .= A_3D\n    config.dg_2D.state_auxiliary .= A_2D\n\n    Q_3D = restart_ode_state(config.dg_3D, Q_3D; init_on_cpu = true)\n    Q_2D = config.dg_3D.modeldata.Q_2D\n    Q_2D .= Q_2D_restart\n\n    return Q_3D, Q_2D, t0\nend\n\nfunction make_callbacks(\n    vtkpath,\n    vtkstep,\n    nout,\n    mpicomm,\n    odesolver,\n    dg_slow,\n    model_slow,\n    Q_slow,\n    dg_fast,\n    model_fast,\n    Q_fast,\n    timeend,\n)\n    mkpath(vtkpath)\n    mkpath(vtkpath * \"/slow\")\n    mkpath(vtkpath * \"/fast\")\n\n    A_slow = dg_slow.state_auxiliary\n    A_fast = dg_fast.state_auxiliary\n\n    function do_output(span, vtkstep, model, dg, Q, A)\n        outprefix = @sprintf(\n            \"%s/%s/mpirank%04d_step%04d\",\n            vtkpath,\n            span,\n            MPI.Comm_rank(mpicomm),\n            vtkstep\n        )\n        @info \"doing VTK output\" outprefix\n        statenames = flattenednames(vars_state(model, Prognostic(), eltype(Q)))\n        auxnames = flattenednames(vars_state(model, Auxiliary(), eltype(Q)))\n        writevtk(outprefix, Q, dg, statenames, A, auxnames)\n    end\n\n    do_output(\"slow\", vtkstep[1], model_slow, dg_slow, Q_slow, A_slow)\n    vtkstep[1] += 1\n    cbvtk_slow = GenericCallbacks.EveryXSimulationSteps(nout) do (init = false)\n        do_output(\"slow\", vtkstep[1], model_slow, dg_slow, Q_slow, A_slow)\n        vtkstep[1] += 1\n        nothing\n    end\n\n    do_output(\"fast\", vtkstep[2], model_fast, dg_fast, Q_fast, A_fast)\n    vtkstep[2] += 1\n    cbvtk_fast = GenericCallbacks.EveryXSimulationSteps(nout) do (init = false)\n        do_output(\"fast\", vtkstep[2], model_fast, dg_fast, Q_fast, A_fast)\n        vtkstep[2] += 1\n        nothing\n    end\n\n    starttime = Ref(now())\n    cbinfo = GenericCallbacks.EveryXWallTimeSeconds(60, mpicomm) do (s = false)\n        if s\n            starttime[] = now()\n        else\n            energy = norm(Q_slow)\n            @info @sprintf(\n                \"\"\"Update\n                simtime = %8.2f / %8.2f\n                runtime = %s\n                norm(Q) = %.16e\"\"\",\n                ODESolvers.gettime(odesolver),\n                timeend,\n                Dates.format(\n                    convert(Dates.DateTime, Dates.now() - starttime[]),\n                    Dates.dateformat\"HH:MM:SS\",\n                ),\n                energy\n            )\n        end\n    end\n\n    cbcs_dg = ClimateMachine.StateCheck.sccreate(\n        [\n            (Q_slow, \"3D state\"),\n            (A_slow, \"3D aux\"),\n            (Q_fast, \"2D state\"),\n            (A_fast, \"2D aux\"),\n        ],\n        nout;\n        prec = 12,\n    )\n\n    cb_checkpoint = GenericCallbacks.EveryXSimulationSteps(nout) do\n        write_checkpoint(\n            Q_slow,\n            A_slow,\n            odesolver,\n            vtkpath,\n            \"baroclinic\",\n            mpicomm,\n            vtkstep[3],\n        )\n\n        write_checkpoint(\n            Q_fast,\n            A_fast,\n            odesolver,\n            vtkpath,\n            \"barotropic\",\n            mpicomm,\n            vtkstep[4],\n        )\n\n        rm_checkpoint(vtkpath, \"baroclinic\", mpicomm, vtkstep[3] - 1)\n\n        rm_checkpoint(vtkpath, \"barotropic\", mpicomm, vtkstep[4] - 1)\n\n        vtkstep[3] += 1\n        vtkstep[4] += 1\n        nothing\n    end\n\n    return (cbvtk_slow, cbvtk_fast, cbinfo, cb_checkpoint, cbcs_dg)\nend\n"
  },
  {
    "path": "test/Ocean/SplitExplicit/test_coriolis.jl",
    "content": "#!/usr/bin/env julia --project\nusing Test\n\ninclude(\"hydrostatic_spindown.jl\")\nClimateMachine.init()\n\nconst FT = Float64\n\n#################\n# RUN THE TESTS #\n#################\n@testset \"$(@__FILE__)\" begin\n\n    include(\"../refvals/hydrostatic_spindown_refvals.jl\")\n\n    # simulation time\n    timeend = FT(15 * 24 * 3600) # s\n    tout = FT(24 * 3600) # s\n    timespan = (tout, timeend)\n\n    # DG polynomial order\n    N = Int(4)\n\n    # Domain resolution\n    Nˣ = Int(5)\n    Nʸ = Int(5)\n    Nᶻ = Int(8)\n    resolution = (N, Nˣ, Nʸ, Nᶻ)\n\n    # Domain size\n    Lˣ = 1e6  # m\n    Lʸ = 1e6  # m\n    H = 400  # m\n    dimensions = (Lˣ, Lʸ, H)\n\n    BC = (\n        OceanBC(Impenetrable(FreeSlip()), Insulating()),\n        OceanBC(Penetrable(FreeSlip()), Insulating()),\n    )\n    config = SplitConfig(\n        \"rotating_bla\",\n        resolution,\n        dimensions,\n        Coupled(),\n        Rotating();\n        solver = SplitExplicitSolver,\n        boundary_conditions = BC,\n    )\n\n    #=\n    BC = (\n        ClimateMachine.Ocean.SplitExplicit01.OceanFloorFreeSlip(),\n        ClimateMachine.Ocean.SplitExplicit01.OceanSurfaceNoStressNoForcing(),\n    )\n\n    config = SplitConfig(\n        \"rotating_jmc\",\n        resolution,\n        dimensions,\n        Coupled(),\n        Rotating();\n        solver = SplitExplicitLSRK2nSolver,\n        boundary_conditions = BC,\n    )\n    =#\n\n    run_split_explicit(\n        config,\n        timespan,\n        dt_fast = 300,\n        dt_slow = 300, # 90 * 60,\n        # refDat = refVals.ninety_minutes,\n        analytic_solution = true,\n    )\nend\n"
  },
  {
    "path": "test/Ocean/SplitExplicit/test_restart.jl",
    "content": "#!/usr/bin/env julia --project\nusing Test\n\ninclude(\"hydrostatic_spindown.jl\")\nClimateMachine.init()\n\nconst FT = Float64\n\n#################\n# RUN THE TESTS #\n#################\n@testset \"$(@__FILE__)\" begin\n\n    include(\"../refvals/hydrostatic_spindown_refvals.jl\")\n\n    # simulation time\n    timeend = FT(24 * 3600) # s\n    tout = FT(3 * 3600) # s\n    timespan = (tout, timeend)\n\n    # DG polynomial order\n    N = Int(4)\n\n    # Domain resolution\n    Nˣ = Int(5)\n    Nʸ = Int(5)\n    Nᶻ = Int(8)\n    resolution = (N, Nˣ, Nʸ, Nᶻ)\n\n    # Domain size\n    Lˣ = 1e6  # m\n    Lʸ = 1e6  # m\n    H = 400  # m\n    dimensions = (Lˣ, Lʸ, H)\n\n    config = SplitConfig(\"test_restart\", resolution, dimensions, Coupled())\n\n    midpoint = timeend / 2\n    timespan = (tout, midpoint)\n\n    run_split_explicit(config, timespan; dt_slow = 90 * 60)\n\n    run_split_explicit(\n        config,\n        timespan;\n        dt_slow = 90 * 60,\n        refDat = refVals.ninety_minutes,\n        analytic_solution = true,\n        restart = Int(midpoint / tout),\n    )\nend\n"
  },
  {
    "path": "test/Ocean/SplitExplicit/test_simple_box.jl",
    "content": "include(\"../../../experiments/OceanSplitExplicit/simple_box.jl\")\n\nClimateMachine.init()\n\n# Float type\nconst FT = Float64\n\n#################\n# RUN THE TESTS #\n#################\n@testset \"$(@__FILE__)\" begin\n\n    include(\"../refvals/simple_box_ivd_refvals.jl\")\n    refDat = (refVals[1], refPrecs[1])\n\n    # simulation time\n    timestart = FT(0)      # s\n    timeend = FT(5 * 86400) # s\n    timespan = (timestart, timeend)\n\n    # DG polynomial order\n    N = Int(4)\n\n    # Domain resolution\n    Nˣ = Int(20)\n    Nʸ = Int(20)\n    Nᶻ = Int(20)\n    resolution = (N, Nˣ, Nʸ, Nᶻ)\n\n    # Domain size\n    Lˣ = 4e6    # m\n    Lʸ = 4e6    # m\n    H = 1000   # m\n    dimensions = (Lˣ, Lʸ, H)\n\n    BC = (\n        ClimateMachine.Ocean.SplitExplicit01.CoastlineNoSlip(),\n        ClimateMachine.Ocean.SplitExplicit01.OceanFloorNoSlip(),\n        ClimateMachine.Ocean.SplitExplicit01.OceanSurfaceStressForcing(),\n    )\n\n    config, solver_type = config_simple_box(\n        \"test_simple_box\",\n        resolution,\n        dimensions,\n        BC;\n        dt_slow = FT(90 * 60),\n        dt_fast = FT(240),\n    )\n\n    run_simple_box(config, timespan, solver_type.dt_slow; refDat = refDat)\n\nend\n"
  },
  {
    "path": "test/Ocean/SplitExplicit/test_spindown_long.jl",
    "content": "#!/usr/bin/env julia --project\nusing Test\n\ninclude(\"hydrostatic_spindown.jl\")\nClimateMachine.init()\n\nconst FT = Float64\n\n#################\n# RUN THE TESTS #\n#################\n@testset \"$(@__FILE__)\" begin\n\n    include(\"../refvals/hydrostatic_spindown_refvals.jl\")\n\n    # simulation time\n    timeend = FT(24 * 3600) # s\n    tout = FT(3 * 3600) # s\n    timespan = (tout, timeend)\n\n    # DG polynomial order\n    N = Int(4)\n\n    # Domain resolution\n    Nˣ = Int(5)\n    Nʸ = Int(5)\n    Nᶻ = Int(8)\n    resolution = (N, Nˣ, Nʸ, Nᶻ)\n\n    # Domain size\n    Lˣ = 1e6  # m\n    Lʸ = 1e6  # m\n    H = 400  # m\n    dimensions = (Lˣ, Lʸ, H)\n\n    @testset \"Single-Rate\" begin\n        @testset \"Not Coupled\" begin\n            config =\n                SplitConfig(\"uncoupled\", resolution, dimensions, Uncoupled())\n\n            run_split_explicit(\n                config,\n                timespan,\n                refDat = refVals.uncoupled,\n                analytic_solution = true,\n            )\n        end\n\n        @testset \"Fully Coupled\" begin\n            config = SplitConfig(\"coupled\", resolution, dimensions, Coupled())\n\n            run_split_explicit(\n                config,\n                timespan,\n                refDat = refVals.coupled,\n                analytic_solution = true,\n            )\n        end\n    end\n\n    @testset \"Multi-rate\" begin\n        @testset \"Δt = 30 mins\" begin\n            config = SplitConfig(\"multirate\", resolution, dimensions, Coupled())\n\n            run_split_explicit(\n                config,\n                timespan,\n                dt_slow = 30 * 60,\n                refDat = refVals.thirty_minutes,\n                analytic_solution = true,\n            )\n        end\n\n        @testset \"Δt = 60 mins\" begin\n            config = SplitConfig(\"multirate\", resolution, dimensions, Coupled())\n\n            run_split_explicit(\n                config,\n                timespan,\n                dt_slow = 60 * 60,\n                refDat = refVals.sixty_minutes,\n                analytic_solution = true,\n            )\n        end\n\n        @testset \"Δt = 90 mins\" begin\n            config = SplitConfig(\"multirate\", resolution, dimensions, Coupled())\n\n            run_split_explicit(\n                config,\n                timespan,\n                dt_slow = 90 * 60,\n                refDat = refVals.ninety_minutes,\n                analytic_solution = true,\n            )\n        end\n    end\nend\n"
  },
  {
    "path": "test/Ocean/SplitExplicit/test_spindown_short.jl",
    "content": "#!/usr/bin/env julia --project\n\ninclude(\"hydrostatic_spindown.jl\")\nClimateMachine.init()\n\nconst FT = Float64\n\n#################\n# RUN THE TESTS #\n#################\n@testset \"$(@__FILE__)\" begin\n\n    include(\"../refvals/hydrostatic_spindown_refvals.jl\")\n\n    # simulation time\n    timeend = FT(24 * 3600) # s\n    tout = FT(1.5 * 3600) # s\n    timespan = (tout, timeend)\n\n    # DG polynomial order\n    N = Int(4)\n\n    # Domain resolution\n    Nˣ = Int(5)\n    Nʸ = Int(5)\n    Nᶻ = Int(8)\n    resolution = (N, Nˣ, Nʸ, Nᶻ)\n\n    # Domain size\n    Lˣ = 1e6  # m\n    Lʸ = 1e6  # m\n    H = 400  # m\n    dimensions = (Lˣ, Lʸ, H)\n\n    BC = (\n        OceanBC(Impenetrable(FreeSlip()), Insulating()),\n        OceanBC(Penetrable(FreeSlip()), Insulating()),\n    )\n    config = SplitConfig(\n        \"spindown_bla\",\n        resolution,\n        dimensions,\n        Coupled();\n        solver = SplitExplicitSolver,\n        boundary_conditions = BC,\n    )\n\n    #=\n    BC = (\n        ClimateMachine.Ocean.SplitExplicit01.OceanFloorFreeSlip(),\n        ClimateMachine.Ocean.SplitExplicit01.OceanSurfaceNoStressNoForcing(),\n    )\n\n    config = SplitConfig(\n        \"spindown_jmc\",\n        resolution,\n        dimensions,\n        Coupled();\n        solver = SplitExplicitLSRK2nSolver,\n        boundary_conditions = BC,\n    )\n    =#\n\n    run_split_explicit(\n        config,\n        timespan;\n        dt_fast = 300, # seconds\n        dt_slow = 90 * 60, # seconds\n        # refDat = refVals.ninety_minutes,\n        analytic_solution = true,\n    )\nend\n"
  },
  {
    "path": "test/Ocean/SplitExplicit/test_vertical_integral_model.jl",
    "content": "#!/usr/bin/env julia --project\nusing Test\nusing ClimateMachine\nClimateMachine.init()\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Mesh.Grids: polynomialorders\nusing ClimateMachine.Ocean.HydrostaticBoussinesq\nusing ClimateMachine.Ocean.ShallowWater\nusing ClimateMachine.Ocean.SplitExplicit: VerticalIntegralModel\nusing ClimateMachine.Ocean.OceanProblems\n\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.VTK\n\nusing MPI\nusing LinearAlgebra\nusing StaticArrays\nusing Logging, Printf, Dates\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nfunction test_vertical_integral_model(::Type{FT}, time; refDat = ()) where {FT}\n    mpicomm = MPI.COMM_WORLD\n    ArrayType = ClimateMachine.array_type()\n\n\n    brickrange_2D = (xrange, yrange)\n    topl_2D = BrickTopology(\n        mpicomm,\n        brickrange_2D,\n        periodicity = (true, true),\n        boundary = ((0, 0), (0, 0)),\n    )\n    grid_2D = DiscontinuousSpectralElementGrid(\n        topl_2D,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    brickrange_3D = (xrange, yrange, zrange)\n    topl_3D = StackedBrickTopology(\n        mpicomm,\n        brickrange_3D;\n        periodicity = (true, true, false),\n        boundary = ((0, 0), (0, 0), (1, 2)),\n    )\n    grid_3D = DiscontinuousSpectralElementGrid(\n        topl_3D,\n        FloatType = FT,\n        DeviceArray = ArrayType,\n        polynomialorder = N,\n    )\n\n    problem = SimpleBox{FT}(Lˣ, Lʸ, H)\n\n    model_3D = HydrostaticBoussinesqModel{FT}(\n        param_set,\n        problem;\n        cʰ = FT(1),\n        αᵀ = FT(0),\n        κʰ = FT(0),\n        κᶻ = FT(0),\n        fₒ = FT(0),\n        β = FT(0),\n    )\n\n    model_2D = ShallowWaterModel{FT}(\n        param_set,\n        problem,\n        ShallowWater.ConstantViscosity{FT}(model_3D.νʰ),\n        nothing;\n        c = FT(1),\n        fₒ = FT(0),\n        β = FT(0),\n    )\n\n    integral_bl = VerticalIntegralModel(model_3D)\n\n    integral_model = DGModel(\n        integral_bl,\n        grid_3D,\n        CentralNumericalFluxFirstOrder(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    dg_3D = DGModel(\n        model_3D,\n        grid_3D,\n        RusanovNumericalFlux(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    dg_2D = DGModel(\n        model_2D,\n        grid_2D,\n        CentralNumericalFluxFirstOrder(),\n        CentralNumericalFluxSecondOrder(),\n        CentralNumericalFluxGradient(),\n    )\n\n    Q_3D = init_ode_state(dg_3D, FT(time); init_on_cpu = true)\n    Q_2D = init_ode_state(dg_2D, FT(time); init_on_cpu = true)\n    Q_int = integral_model.state_auxiliary\n\n    state_check = ClimateMachine.StateCheck.sccreate(\n        [\n            (Q_int, \"∫u\")\n            (Q_2D, \"U\")\n        ],\n        1;\n        prec = 12,\n    )\n\n    update_auxiliary_state!(integral_model, integral_bl, Q_3D, time)\n    GenericCallbacks.call!(state_check, nothing, nothing, nothing, nothing)\n\n    ClimateMachine.StateCheck.scprintref(state_check)\n    if length(refDat) > 0\n        @test ClimateMachine.StateCheck.scdocheck(state_check, refDat)\n    end\n\n    return nothing\nend\n\n#################\n# RUN THE TESTS #\n#################\nconst FT = Float64\n\nconst N = 4\nconst Nˣ = 5\nconst Nʸ = 5\nconst Nᶻ = 8\nconst Lˣ = 1e6  # m\nconst Lʸ = 1e6  # m\nconst H = 400  # m\n\nxrange = range(FT(0); length = Nˣ + 1, stop = Lˣ)\nyrange = range(FT(0); length = Nʸ + 1, stop = Lʸ)\nzrange = range(FT(-H); length = Nᶻ + 1, stop = 0)\n\nconst cʰ = 1  # typical of ocean internal-wave speed\nconst cᶻ = 0\n\n@testset \"$(@__FILE__)\" begin\n    include(\"../refvals/test_vertical_integral_model_refvals.jl\")\n\n    times =\n        [0, 86400, 30 * 86400, 365 * 86400, 10 * 365 * 86400, 100 * 365 * 86400]\n    for (index, time) in enumerate(times)\n        @testset \"$(time)\" begin\n            test_vertical_integral_model(FT, time, refDat = refVals[index])\n        end\n    end\nend\n"
  },
  {
    "path": "test/Ocean/refvals/2D_hydrostatic_spindown_refvals.jl",
    "content": "# [\n#  [ MPIStateArray Name, Field Name, Maximum, Minimum, Mean, Standard Deviation ],\n#  [         :                :          :        :      :          :           ],\n# ]\n\nparr = [\n    [\"2D state\", \"η\", 12, 12, 0, 12],\n    [\"2D state\", \"U[1]\", 12, 12, 0, 12],\n    [\"2D state\", \"U[2]\", 0, 0, 0, 0],\n]\n\nexplicit = [\n    [\n        \"2D state\",\n        \"η\",\n        -8.52722969951589915283e-01,\n        8.52846676313531282254e-01,\n        -2.49578135935735214742e-16,\n        6.03454239990563690021e-01,\n    ],\n    [\n        \"2D state\",\n        \"U[1]\",\n        -3.15431401945821825450e+01,\n        3.15431401945818628008e+01,\n        6.11504145930918957291e-15,\n        2.24273815174625497093e+01,\n    ],\n    [\n        \"2D state\",\n        \"U[2]\",\n        -7.62224398365580242501e-13,\n        9.72156930292624284356e-13,\n        1.39269607441935025982e-14,\n        1.95606703846656748360e-13,\n    ],\n]\n\nrefVals = (explicit = (explicit, parr),)\n"
  },
  {
    "path": "test/Ocean/refvals/3D_hydrostatic_spindown_refvals.jl",
    "content": "# [\n#  [ MPIStateArray Name, Field Name, Maximum, Minimum, Mean, Standard Deviation ],\n#  [         :                :          :        :      :          :           ],\n# ]\nparr = [\n    [\"Q\", \"u[1]\", 12, 12, 0, 12],\n    [\"Q\", \"u[2]\", 0, 0, 0, 0],\n    [\"Q\", \"η\", 12, 12, 0, 12],\n    [\"Q\", \"θ\", 15, 15, 15, 15],\n    [\"s_aux\", \"y\", 15, 15, 15, 15],\n    [\"s_aux\", \"w\", 12, 12, 0, 12],\n    [\"s_aux\", \"pkin\", 15, 15, 15, 15],\n    [\"s_aux\", \"wz0\", 12, 12, 0, 12],\n    [\"aux\", \"uᵈ[1]\", 15, 15, 15, 15],\n    [\"aux\", \"uᵈ[2]\", 15, 15, 15, 15],\n    [\"aux\", \"ΔGᵘ[1]\", 15, 15, 15, 15],\n    [\"aux\", \"ΔGᵘ[2]\", 15, 15, 15, 15],\n]\n\n### fully explicit\nexplicit = [\n    [\n        \"state\",\n        \"u[1]\",\n        -9.58544066049463849843e-01,\n        9.58544066049465071089e-01,\n        -6.13908923696726568442e-17,\n        4.45400263687296238402e-01,\n    ],\n    [\n        \"state\",\n        \"u[2]\",\n        -4.33260855197780914020e-14,\n        1.99397359064900580713e-14,\n        -1.30343101521973575132e-16,\n        2.95525689323845820606e-15,\n    ],\n    [\n        \"state\",\n        \"η\",\n        -8.52732886154656810618e-01,\n        8.52845586939211197652e-01,\n        2.20052243093959998331e-14,\n        6.02992088522925295813e-01,\n    ],\n    [\n        \"state\",\n        \"θ\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"aux\",\n        \"y\",\n        0.00000000000000000000e+00,\n        1.00000000000000011642e+06,\n        5.00000000000000000000e+05,\n        2.92775877460665535182e+05,\n    ],\n    [\n        \"aux\",\n        \"w\",\n        -4.04553460063758398447e-04,\n        4.04714358463272711169e-04,\n        4.75730566051879549438e-19,\n        1.63958655681888576441e-04,\n    ],\n    [\n        \"aux\",\n        \"pkin\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"aux\",\n        \"wz0\",\n        -2.01164684799271339293e-04,\n        2.01041968159484089494e-04,\n        -2.10942374678779754294e-20,\n        1.42228420244455277133e-04,\n    ],\n    [\n        \"aux\",\n        \"uᵈ[1]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"aux\",\n        \"uᵈ[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"aux\",\n        \"ΔGᵘ[1]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"aux\",\n        \"ΔGᵘ[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n]\n\nimex = [\n    [\n        \"state\",\n        \"u[1]\",\n        -9.57705359685807500192e-01,\n        9.57705359685806500991e-01,\n        4.54747350886464140750e-17,\n        4.45323727947873004851e-01,\n    ],\n    [\n        \"state\",\n        \"u[2]\",\n        -4.07596731389788922865e-14,\n        2.53992382675900717845e-14,\n        9.88087105439151860723e-18,\n        2.63742927063789745855e-15,\n    ],\n    [\n        \"state\",\n        \"η\",\n        -8.55941716778505390373e-01,\n        8.56014908798837681481e-01,\n        2.16732587432488803528e-14,\n        6.05260814296588400829e-01,\n    ],\n    [\n        \"state\",\n        \"θ\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"aux\",\n        \"y\",\n        0.00000000000000000000e+00,\n        1.00000000000000011642e+06,\n        5.00000000000000000000e+05,\n        2.92775877460665535182e+05,\n    ],\n    [\n        \"aux\",\n        \"w\",\n        -4.03419301824637895407e-04,\n        4.03626959596159180614e-04,\n        -3.33066907387546970565e-21,\n        1.63816237485524198066e-04,\n    ],\n    [\n        \"aux\",\n        \"pkin\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"aux\",\n        \"wz0\",\n        -1.97016630922629948884e-04,\n        1.97066086934914155761e-04,\n        -7.49400541621980656312e-19,\n        1.39393318574472925390e-04,\n    ],\n    [\n        \"aux\",\n        \"uᵈ[1]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"aux\",\n        \"uᵈ[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"aux\",\n        \"ΔGᵘ[1]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"aux\",\n        \"ΔGᵘ[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n]\n\nrefVals = (explicit = (explicit, parr), imex = (imex, parr))\n"
  },
  {
    "path": "test/Ocean/refvals/hydrostatic_spindown_refvals.jl",
    "content": "# [\n#  [ MPIStateArray Name, Field Name, Maximum, Minimum, Mean, Standard Deviation ],\n#  [         :                :          :        :      :          :           ],\n# ]\n\nparr = [\n    [\"3D state\", \"u[1]\", 12, 12, 0, 12],\n    [\"3D state\", \"u[2]\", 0, 0, 0, 0],\n    [\"3D state\", \"η\", 12, 12, 0, 12],\n    [\"3D state\", \"θ\", 15, 15, 15, 15],\n    [\"3D aux\", \"y\", 15, 15, 15, 15],\n    [\"3D aux\", \"w\", 12, 12, 0, 12],\n    [\"3D aux\", \"pkin\", 15, 15, 15, 15],\n    [\"3D aux\", \"wz0\", 12, 12, 0, 12],\n    [\"3D aux\", \"uᵈ[1]\", 12, 12, 0, 12],\n    [\"3D aux\", \"uᵈ[2]\", 0, 0, 0, 0],\n    [\"3D aux\", \"ΔGᵘ[1]\", 11, 11, 0, 12],\n    [\"3D aux\", \"ΔGᵘ[2]\", 0, 0, 0, 0],\n    [\"2D state\", \"η\", 12, 12, 0, 12],\n    [\"2D state\", \"U[1]\", 12, 12, 0, 12],\n    [\"2D state\", \"U[2]\", 0, 0, 0, 0],\n    [\"2D aux\", \"y\", 15, 15, 15, 2],\n    [\"2D aux\", \"Gᵁ[1]\", 11, 11, 0, 12],\n    [\"2D aux\", \"Gᵁ[2]\", 0, 0, 0, 0],\n    [\"2D aux\", \"Δu[1]\", 12, 12, 0, 12],\n    [\"2D aux\", \"Δu[2]\", 0, 0, 0, 0],\n]\n\nuncoupled = [\n    [\n        \"3D state\",\n        \"u[1]\",\n        -9.58547428246830479637e-01,\n        9.58547428246829258391e-01,\n        -6.82121026329696195717e-18,\n        4.45400655325317695876e-01,\n    ],\n    [\n        \"3D state\",\n        \"u[2]\",\n        -4.13574641907576004779e-14,\n        1.84325494853789287544e-14,\n        1.94567057477529826177e-17,\n        2.12815907156924710074e-15,\n    ],\n    [\n        \"3D state\",\n        \"η\",\n        -8.52721734395889496838e-01,\n        8.52834259382379111791e-01,\n        2.18869899981655176688e-14,\n        6.02983573168680453414e-01,\n    ],\n    [\n        \"3D state\",\n        \"θ\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"3D aux\",\n        \"y\",\n        0.00000000000000000000e+00,\n        1.00000000000000011642e+06,\n        5.00000000000000000000e+05,\n        2.92775877460665535182e+05,\n    ],\n    [\n        \"3D aux\",\n        \"w\",\n        -4.04489222828922911912e-04,\n        4.04649979048336849198e-04,\n        7.79376563286859913379e-19,\n        1.63949585575251901345e-04,\n    ],\n    [\n        \"3D aux\",\n        \"pkin\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"3D aux\",\n        \"wz0\",\n        -2.00933099660541601228e-04,\n        2.00809859469744893647e-04,\n        2.62012633811536956698e-19,\n        1.42064682697776552755e-04,\n    ],\n    [\n        \"3D aux\",\n        \"uᵈ[1]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"3D aux\",\n        \"uᵈ[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"3D aux\",\n        \"ΔGᵘ[1]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"3D aux\",\n        \"ΔGᵘ[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"2D state\",\n        \"η\",\n        -8.52715894965927923010e-01,\n        8.52861218874714666072e-01,\n        -7.70938868299708696148e-16,\n        6.03458382295490314284e-01,\n    ],\n    [\n        \"2D state\",\n        \"U[1]\",\n        -3.15423945317149438949e+01,\n        3.15423945317147342848e+01,\n        1.42724412149908289268e-14,\n        2.24262219407601044452e+01,\n    ],\n    [\n        \"2D state\",\n        \"U[2]\",\n        -9.38673083374999972374e-13,\n        1.16534781393478256382e-12,\n        1.93385973223830695204e-14,\n        2.16634164433978071172e-13,\n    ],\n    [\n        \"2D aux\",\n        \"y\",\n        0.00000000000000000000e+00,\n        1.00000000000000011642e+06,\n        5.00000000000000000000e+05,\n        2.92775877460665535182e+05,\n    ],\n    [\n        \"2D aux\",\n        \"Gᵁ[1]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"2D aux\",\n        \"Gᵁ[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"2D aux\",\n        \"Δu[1]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"2D aux\",\n        \"Δu[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n]\n\ncoupled = [\n    [\n        \"3D state\",\n        \"u[1]\",\n        -9.58544854429326798062e-01,\n        9.58544854429326798062e-01,\n        -4.09272615797817732838e-17,\n        4.45400401208546903309e-01,\n    ],\n    [\n        \"3D state\",\n        \"u[2]\",\n        -4.09119364541065205420e-15,\n        3.41954410407034240802e-15,\n        1.57855184364031418231e-19,\n        7.69982545727116047928e-16,\n    ],\n    [\n        \"3D state\",\n        \"η\",\n        -8.52733075123627615177e-01,\n        8.52843573070954374948e-01,\n        7.27595761418342649852e-17,\n        6.02992194663175995473e-01,\n    ],\n    [\n        \"3D state\",\n        \"θ\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"3D aux\",\n        \"y\",\n        0.00000000000000000000e+00,\n        1.00000000000000011642e+06,\n        5.00000000000000000000e+05,\n        2.92775877460665535182e+05,\n    ],\n    [\n        \"3D aux\",\n        \"w\",\n        -4.04456725919283483547e-04,\n        4.04616610112968017130e-04,\n        1.33892896769793873666e-18,\n        1.63945200913093809460e-04,\n    ],\n    [\n        \"3D aux\",\n        \"pkin\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"3D aux\",\n        \"wz0\",\n        -2.00813442787548966564e-04,\n        2.00686991710668984502e-04,\n        1.68642877440561282675e-18,\n        1.41979510156587984725e-04,\n    ],\n    [\n        \"3D aux\",\n        \"uᵈ[1]\",\n        -8.79713597641291755735e-01,\n        8.79713597641291200624e-01,\n        -9.26547727431170607429e-17,\n        4.41871673548253185437e-01,\n    ],\n    [\n        \"3D aux\",\n        \"uᵈ[2]\",\n        -5.88884665747485312740e-28,\n        6.47654803186450692243e-28,\n        1.40221555861782666734e-30,\n        4.22950073030020620100e-29,\n    ],\n    [\n        \"3D aux\",\n        \"ΔGᵘ[1]\",\n        -1.50292385646252865781e-09,\n        1.50292385640701573972e-09,\n        -3.03178502810731714103e-21,\n        6.72141424487058536370e-10,\n    ],\n    [\n        \"3D aux\",\n        \"ΔGᵘ[2]\",\n        -2.50330922252795548175e-19,\n        2.79421319642066487612e-19,\n        8.87468518373638336981e-36,\n        5.19465794381941235415e-20,\n    ],\n    [\n        \"2D state\",\n        \"η\",\n        -8.52733075123627615177e-01,\n        8.52843573070954374948e-01,\n        1.61115565333602722195e-16,\n        6.03463098440266798583e-01,\n    ],\n    [\n        \"2D state\",\n        \"U[1]\",\n        -3.15402749945522060671e+01,\n        3.15402749945527389741e+01,\n        2.12974776703234167348e-14,\n        2.24262621677375086904e+01,\n    ],\n    [\n        \"2D state\",\n        \"U[2]\",\n        -1.63647745816416607002e-12,\n        1.36781764162808076476e-12,\n        6.31420737450488849608e-17,\n        3.08233543917742288569e-13,\n    ],\n    [\n        \"2D aux\",\n        \"y\",\n        0.00000000000000000000e+00,\n        1.00000000000000011642e+06,\n        5.00000000000000000000e+05,\n        2.92775877460665535182e+05,\n    ],\n    [\n        \"2D aux\",\n        \"Gᵁ[1]\",\n        -6.01169542562806285963e-07,\n        6.01169542585011466433e-07,\n        1.21270571032004377760e-18,\n        2.69066532005499711718e-07,\n    ],\n    [\n        \"2D aux\",\n        \"Gᵁ[2]\",\n        -1.11768527856826595815e-16,\n        1.00132368901118217729e-16,\n        -3.50057026691823957451e-33,\n        2.07948587451660775232e-17,\n    ],\n    [\n        \"2D aux\",\n        \"Δu[1]\",\n        -6.53936429129693404076e-04,\n        6.53936429129569046087e-04,\n        -2.70378337774435083119e-18,\n        4.64975945389895016328e-04,\n    ],\n    [\n        \"2D aux\",\n        \"Δu[2]\",\n        -3.76049176138251460297e-17,\n        3.57641761985933683411e-17,\n        -4.12443309266433447246e-19,\n        7.09162842149286857838e-18,\n    ],\n]\n\nthirty_minutes = [\n    [\n        \"3D state\",\n        \"u[1]\",\n        -9.58527737426365766815e-01,\n        9.58527737426364989659e-01,\n        -2.95585778076201651428e-17,\n        4.45397585011924779241e-01,\n    ],\n    [\n        \"3D state\",\n        \"u[2]\",\n        -3.91776258115697290002e-15,\n        3.71573344368902785991e-15,\n        9.99453214450878842160e-18,\n        7.55533641450465537744e-16,\n    ],\n    [\n        \"3D state\",\n        \"η\",\n        -8.52729582431237531637e-01,\n        8.52830944991449846349e-01,\n        -2.54658516496419884307e-16,\n        6.02990141900936249542e-01,\n    ],\n    [\n        \"3D state\",\n        \"θ\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"3D aux\",\n        \"y\",\n        0.00000000000000000000e+00,\n        1.00000000000000011642e+06,\n        5.00000000000000000000e+05,\n        2.92775877460665535182e+05,\n    ],\n    [\n        \"3D aux\",\n        \"w\",\n        -4.06712181304362465229e-04,\n        4.06869755985816939844e-04,\n        9.07052211118752913371e-19,\n        1.64290385445136678539e-04,\n    ],\n    [\n        \"3D aux\",\n        \"pkin\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"3D aux\",\n        \"wz0\",\n        -2.09003099782708142351e-04,\n        2.08868143992394606845e-04,\n        1.33781874467331362536e-18,\n        1.47768608464728741676e-04,\n    ],\n    [\n        \"3D aux\",\n        \"uᵈ[1]\",\n        -8.79792191807299950312e-01,\n        8.79792191807298729067e-01,\n        -9.54969436861574689412e-17,\n        4.41911149114936674387e-01,\n    ],\n    [\n        \"3D aux\",\n        \"uᵈ[2]\",\n        -4.49650715975976729085e-28,\n        3.22939933074851707840e-28,\n        1.28559371735991545201e-30,\n        3.12162656228539824003e-29,\n    ],\n    [\n        \"3D aux\",\n        \"ΔGᵘ[1]\",\n        -1.52356619979685413105e-09,\n        1.52356619973800994984e-09,\n        -3.00415481336788178489e-21,\n        6.81373145706173027634e-10,\n    ],\n    [\n        \"3D aux\",\n        \"ΔGᵘ[2]\",\n        -2.17115289587171316651e-19,\n        2.99271337844227832879e-19,\n        3.57452597678270976423e-36,\n        5.23982958192948877053e-20,\n    ],\n    [\n        \"2D state\",\n        \"η\",\n        -8.52729582431237531637e-01,\n        8.52830944991449846349e-01,\n        -3.20454773827805192348e-16,\n        6.03461044074932395631e-01,\n    ],\n    [\n        \"2D state\",\n        \"U[1]\",\n        -3.15407643291233448224e+01,\n        3.15407643291237640426e+01,\n        2.30678864898692384736e-14,\n        2.24265574817805202201e+01,\n    ],\n    [\n        \"2D state\",\n        \"U[2]\",\n        -1.56710503246274425174e-12,\n        1.48629337747559491236e-12,\n        3.99781285780299048032e-15,\n        3.02449468686897625244e-13,\n    ],\n    [\n        \"2D aux\",\n        \"y\",\n        0.00000000000000000000e+00,\n        1.00000000000000011642e+06,\n        5.00000000000000000000e+05,\n        2.92775877460665535182e+05,\n    ],\n    [\n        \"2D aux\",\n        \"Gᵁ[1]\",\n        -6.09426479895203986555e-07,\n        6.09426479918741655731e-07,\n        1.20165633492970096897e-18,\n        2.72762104279987029506e-07,\n    ],\n    [\n        \"2D aux\",\n        \"Gᵁ[2]\",\n        -1.19708535137691129300e-16,\n        8.68461158348685235787e-17,\n        -1.50869648123518501517e-33,\n        2.09756864038773538444e-17,\n    ],\n    [\n        \"2D aux\",\n        \"Δu[1]\",\n        -3.89449877638695009935e-03,\n        3.89449877638627485824e-03,\n        -2.02396849009311295664e-17,\n        2.76903473119681107703e-03,\n    ],\n    [\n        \"2D aux\",\n        \"Δu[2]\",\n        -2.13991366909841745553e-16,\n        2.02991354131742782465e-16,\n        -1.26781868574011981865e-18,\n        4.34606820130057938426e-17,\n    ],\n]\n\nsixty_minutes = [\n    [\n        \"3D state\",\n        \"u[1]\",\n        -9.58507705628042327994e-01,\n        9.58507705628042550039e-01,\n        -5.22959453519433752618e-17,\n        4.45394100308371843067e-01,\n    ],\n    [\n        \"3D state\",\n        \"u[2]\",\n        -4.09893498131451878178e-15,\n        3.66595063838075513859e-15,\n        3.52058149555652693147e-18,\n        7.65972478919525466697e-16,\n    ],\n    [\n        \"3D state\",\n        \"η\",\n        -8.52715753705294066123e-01,\n        8.52819886810596838878e-01,\n        0.00000000000000000000e+00,\n        6.02986351734545844572e-01,\n    ],\n    [\n        \"3D state\",\n        \"θ\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"3D aux\",\n        \"y\",\n        0.00000000000000000000e+00,\n        1.00000000000000011642e+06,\n        5.00000000000000000000e+05,\n        2.92775877460665535182e+05,\n    ],\n    [\n        \"3D aux\",\n        \"w\",\n        -4.09355343664679748733e-04,\n        4.09526445665209497954e-04,\n        8.01581023779363006131e-19,\n        1.64782726593626366188e-04,\n    ],\n    [\n        \"3D aux\",\n        \"pkin\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"3D aux\",\n        \"wz0\",\n        -2.18597874938533713813e-04,\n        2.18512725277962983460e-04,\n        1.16684439888103955116e-18,\n        1.54581525183602542031e-04,\n    ],\n    [\n        \"3D aux\",\n        \"uᵈ[1]\",\n        -8.79886113674857694988e-01,\n        8.79886113674856806810e-01,\n        -8.81072992342524199517e-17,\n        4.41958323665245178535e-01,\n    ],\n    [\n        \"3D aux\",\n        \"uᵈ[2]\",\n        -5.04476548888837049561e-28,\n        4.09418809809705127009e-28,\n        1.53142189509630563206e-30,\n        3.27579623353337388378e-29,\n    ],\n    [\n        \"3D aux\",\n        \"ΔGᵘ[1]\",\n        -1.12461707299293835875e-09,\n        1.12461707291060059378e-09,\n        -2.89803852573586336546e-21,\n        5.02954103857782471789e-10,\n    ],\n    [\n        \"3D aux\",\n        \"ΔGᵘ[2]\",\n        -2.29220407034248886795e-19,\n        3.23795504633739099693e-19,\n        4.19082355898662551731e-36,\n        5.50021671320874203932e-20,\n    ],\n    [\n        \"2D state\",\n        \"η\",\n        -8.52715753705294066123e-01,\n        8.52819886810596838878e-01,\n        -2.04281036531028799987e-17,\n        6.03457250948630341547e-01,\n    ],\n    [\n        \"2D state\",\n        \"U[1]\",\n        -3.15415180428826182890e+01,\n        3.15415180428829948767e+01,\n        1.77902803599749859788e-14,\n        2.24265293235027023400e+01,\n    ],\n    [\n        \"2D state\",\n        \"U[2]\",\n        -1.63957399252576030728e-12,\n        1.46638025535227397199e-12,\n        1.40823259822193190848e-15,\n        3.06628264537953242560e-13,\n    ],\n    [\n        \"2D aux\",\n        \"y\",\n        0.00000000000000000000e+00,\n        1.00000000000000011642e+06,\n        5.00000000000000000000e+05,\n        2.92775877460665535182e+05,\n    ],\n    [\n        \"2D aux\",\n        \"Gᵁ[1]\",\n        -4.49846829164240224277e-07,\n        4.49846829197175353425e-07,\n        1.15921837490966064206e-18,\n        2.01338753352722549629e-07,\n    ],\n    [\n        \"2D aux\",\n        \"Gᵁ[2]\",\n        -1.29518201853495637566e-16,\n        9.16881628136995516365e-17,\n        -2.08062063752041870574e-33,\n        2.20180483211723137251e-17,\n    ],\n    [\n        \"2D aux\",\n        \"Δu[1]\",\n        -7.71677156339491132631e-03,\n        7.71677156339359640591e-03,\n        -2.97775205101297201914e-17,\n        5.48673197909435757247e-03,\n    ],\n    [\n        \"2D aux\",\n        \"Δu[2]\",\n        -5.01794629524824969143e-16,\n        5.51655281127317078031e-16,\n        3.20748218455203547025e-18,\n        9.36761976343254920594e-17,\n    ],\n]\n\nninety_minutes = [\n    [\n        \"3D state\",\n        \"u[1]\",\n        -9.58488051495743675900e-01,\n        9.58488051495742121588e-01,\n        2.50111042987555274331e-17,\n        4.45390687454859546257e-01,\n    ],\n    [\n        \"3D state\",\n        \"u[2]\",\n        -1.23989905776825655281e-15,\n        1.61970487656192169626e-15,\n        3.83419479315953585092e-17,\n        3.55219724018798369983e-16,\n    ],\n    [\n        \"3D state\",\n        \"η\",\n        -8.52731405452109680887e-01,\n        8.52809353848989926128e-01,\n        -2.45563569478690627377e-16,\n        6.02988963894262375298e-01,\n    ],\n    [\n        \"3D state\",\n        \"θ\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"3D aux\",\n        \"y\",\n        0.00000000000000000000e+00,\n        1.00000000000000011642e+06,\n        5.00000000000000000000e+05,\n        2.92775877460665535182e+05,\n    ],\n    [\n        \"3D aux\",\n        \"w\",\n        -4.11959288170071617242e-04,\n        4.12126623290091649143e-04,\n        -2.00395255944840751533e-19,\n        1.65355949740609838115e-04,\n    ],\n    [\n        \"3D aux\",\n        \"pkin\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"3D aux\",\n        \"wz0\",\n        -2.28048874139989487609e-04,\n        2.27949849600796259491e-04,\n        -3.35287353436797287965e-19,\n        1.61271245461141072295e-04,\n    ],\n    [\n        \"3D aux\",\n        \"uᵈ[1]\",\n        -8.79979607451056078382e-01,\n        8.79979607451055745315e-01,\n        -2.84217094304040087969e-18,\n        4.42005283454923181274e-01,\n    ],\n    [\n        \"3D aux\",\n        \"uᵈ[2]\",\n        -2.89610559829263959062e-28,\n        2.76495747279964637797e-28,\n        -2.23087062731398024379e-31,\n        2.03879747098428489165e-29,\n    ],\n    [\n        \"3D aux\",\n        \"ΔGᵘ[1]\",\n        -8.86991425991573654091e-10,\n        8.86991425890194811807e-10,\n        -2.95809316169619294608e-22,\n        3.96682558403758137461e-10,\n    ],\n    [\n        \"3D aux\",\n        \"ΔGᵘ[2]\",\n        -1.46453678699776496163e-19,\n        1.78310815453102811486e-19,\n        3.45126646034192634633e-36,\n        3.36942772885638802169e-20,\n    ],\n    [\n        \"2D state\",\n        \"η\",\n        -8.52731405452109680887e-01,\n        8.52809353848989926128e-01,\n        -2.24886775868071691631e-16,\n        6.03459865148300189652e-01,\n    ],\n    [\n        \"2D state\",\n        \"U[1]\",\n        -3.15423820746574818941e+01,\n        3.15423820746572900475e+01,\n        1.25987324739451618828e-14,\n        2.24266774102466719398e+01,\n    ],\n    [\n        \"2D state\",\n        \"U[2]\",\n        -4.95959623107294663097e-13,\n        6.47881950624733081946e-13,\n        1.53367791726381670695e-14,\n        1.42198852443335733848e-13,\n    ],\n    [\n        \"2D aux\",\n        \"y\",\n        0.00000000000000000000e+00,\n        1.00000000000000011642e+06,\n        5.00000000000000000000e+05,\n        2.93004519336559635121e+05,\n    ],\n    [\n        \"2D aux\",\n        \"Gᵁ[1]\",\n        -3.54796570356077906525e-07,\n        3.54796570396629450056e-07,\n        1.18322201808542668922e-19,\n        1.58796938275634264439e-07,\n    ],\n    [\n        \"2D aux\",\n        \"Gᵁ[2]\",\n        -7.13243261812411242092e-17,\n        5.85814714799106030876e-17,\n        -1.27696859032651283100e-33,\n        1.34882362672174716756e-17,\n    ],\n    [\n        \"2D aux\",\n        \"Δu[1]\",\n        -1.14629123365972158261e-02,\n        1.14629123365972678678e-02,\n        2.41175081207994926868e-18,\n        8.15149713200666835300e-03,\n    ],\n    [\n        \"2D aux\",\n        \"Δu[2]\",\n        -7.92579521344275918936e-16,\n        6.08374854985514025679e-16,\n        -3.41928350044524220733e-18,\n        1.39041335489639128848e-16,\n    ],\n]\n\nrefVals = (\n    uncoupled = (uncoupled, parr),\n    coupled = (coupled, parr),\n    thirty_minutes = (thirty_minutes, parr),\n    sixty_minutes = (sixty_minutes, parr),\n    ninety_minutes = (ninety_minutes, parr),\n)\n"
  },
  {
    "path": "test/Ocean/refvals/simple_box_2dt_refvals.jl",
    "content": "refVals = []\nrefPrecs = []\n\n#! format: off\n# SC ========== Test number 1 reference values and precision match template. =======\n# SC ========== /home/jmc/cliMa/cliMa_update/test/Ocean/SplitExplicit/simple_box_2dt.jl test reference values ======================================\n# BEGIN SCPRINT\n# varr - reference values (from reference run)\n# parr - digits match precision (hand edit as needed)\n#\n# [\n#  [ MPIStateArray Name, Field Name, Maximum, Minimum, Mean, Standard Deviation ],\n#  [         :                :          :        :      :          :           ],\n# ]\nvarr = [\n [  \"oce Q_3D\",   \"u[1]\", -1.65970172440775332046e-01,  1.61603740424529351838e-01,  4.57235954923597420763e-03,  2.21382850057524928344e-02 ],\n [  \"oce Q_3D\",   \"u[2]\", -2.16238066786232502325e-01,  2.44156974607258908661e-01, -2.57322116506966246802e-03,  2.49380331699317753236e-02 ],\n [  \"oce Q_3D\",      \"η\", -7.98711302045854054654e-01,  3.47810107484795683064e-01, -2.92595480887335743312e-04,  2.91360229800833869795e-01 ],\n [  \"oce Q_3D\",      \"θ\",  3.55385987992295690474e-03,  9.91576780951165659417e+00,  2.49883145933055006438e+00,  2.17846939512263038097e+00 ],\n [   \"oce aux\",      \"w\", -1.66960462888799841472e-04,  1.65914321067276468273e-04,  5.22671200400887584530e-07,  1.42481192436579197488e-05 ],\n [   \"oce aux\",   \"pkin\", -8.84676906676938301644e+00,  0.00000000000000000000e+00, -3.26734804711110893294e+00,  2.49685702267286213640e+00 ],\n [   \"oce aux\",    \"wz0\", -2.12828008736777837579e-05,  3.08227212254399767725e-05, -1.55080158053877423592e-10,  7.82426303256959697840e-06 ],\n [   \"oce aux\", \"u_d[1]\", -1.56747674354107358052e-01,  1.23337664405837849069e-01, -7.85428994855595028748e-05,  1.15205292893568473495e-02 ],\n [   \"oce aux\", \"u_d[2]\", -2.12928714468491042666e-01,  2.30595749843429731474e-01,  2.75559843466661209680e-05,  1.75135782183724539318e-02 ],\n [   \"oce aux\", \"ΔGu[1]\", -2.33711927426000212944e-06,  3.31423592619396043514e-06, -4.04240250953691765080e-08,  3.37695011150041160627e-07 ],\n [   \"oce aux\", \"ΔGu[2]\", -2.38751859804088027546e-06,  2.23171788862484417825e-06,  1.26953194368922374152e-06,  7.64696321260357341382e-07 ],\n [   \"oce aux\",      \"y\",  0.00000000000000000000e+00,  4.00000000000000046566e+06,  2.00000000000000000000e+06,  1.15573163901915703900e+06 ],\n [ \"baro Q_2D\",   \"U[1]\", -1.77749182159153633620e+01,  6.89466489062268976795e+01,  4.64951906527867731000e+00,  1.81233032109943827948e+01 ],\n [ \"baro Q_2D\",   \"U[2]\", -3.48869693974309527107e+01,  8.56944551257798678989e+01, -2.60002834745782607229e+00,  1.80715674809898771969e+01 ],\n [ \"baro Q_2D\",      \"η\", -7.98718706462308802863e-01,  3.47811046235604659493e-01, -2.92587313874264492736e-04,  2.91375274868600042666e-01 ],\n [  \"baro aux\",  \"Gᵁ[1]\", -3.31423592619396051306e-03,  2.33711927426000221075e-03,  4.04240250953691941898e-05,  3.37711728311069743838e-04 ],\n [  \"baro aux\",  \"Gᵁ[2]\", -2.23171788862484409693e-03,  2.38751859804088020431e-03, -1.26953194368922394480e-03,  7.64734176576895561574e-04 ],\n [  \"baro aux\", \"U_c[1]\", -1.77747948208394639380e+01,  6.89456621278419703458e+01,  4.64949958798823903550e+00,  1.81231282979409442646e+01 ],\n [  \"baro aux\", \"U_c[2]\", -3.48859937354993405734e+01,  8.56944690523459655651e+01, -2.59996531383233087098e+00,  1.80715328159406638520e+01 ],\n [  \"baro aux\",    \"η_c\", -7.98711302045854054654e-01,  3.47810107484795683064e-01, -2.92595480887348482688e-04,  2.91374653217579937525e-01 ],\n [  \"baro aux\", \"U_s[1]\", -1.77749182159153633620e+01,  6.89466489062268976795e+01,  4.64951906527867731000e+00,  1.81233032109943827948e+01 ],\n [  \"baro aux\", \"U_s[2]\", -3.48869693974309527107e+01,  8.56944551257798678989e+01, -2.60002834745782607229e+00,  1.80715674809898771969e+01 ],\n [  \"baro aux\",    \"η_s\", -7.98718706462308802863e-01,  3.47811046235604659493e-01, -2.92587313874264492736e-04,  2.91375274868600042666e-01 ],\n [  \"baro aux\",  \"Δu[1]\", -4.52063405565574774267e-04,  3.99219316002806577735e-04, -1.09219330063404869891e-05,  1.02341200736279461128e-04 ],\n [  \"baro aux\",  \"Δu[2]\", -2.42431689759263984492e-04,  3.83040345117153786629e-04,  5.18877254212526286387e-05,  7.47027376508314802477e-05 ],\n [  \"baro aux\", \"η_diag\", -7.98659935505348972384e-01,  3.47918275378131136577e-01, -2.91845106891554467807e-04,  2.91369719089321743688e-01 ],\n [  \"baro aux\",     \"Δη\", -5.74411984127720653959e-04,  7.42725686140199847785e-04, -7.50373995767764799686e-07,  1.62142594044870064609e-04 ],\n [  \"baro aux\",      \"y\",  0.00000000000000000000e+00,  4.00000000000000046566e+06,  2.00000000000000000000e+06,  1.15578885204060329124e+06 ],\n]\nparr = [\n [  \"oce Q_3D\",   \"u[1]\",    12,    12,    12,    12 ],\n [  \"oce Q_3D\",   \"u[2]\",    12,    12,    12,    12 ],\n [  \"oce Q_3D\",      \"η\",    12,    12,     8,    12 ],\n [  \"oce Q_3D\",      \"θ\",    12,    12,    12,    12 ],\n [   \"oce aux\",      \"w\",    12,    12,     8,    12 ],\n [   \"oce aux\",   \"pkin\",    12,    12,    12,    12 ],\n [   \"oce aux\",    \"wz0\",    12,    12,     8,    12 ],\n [   \"oce aux\", \"u_d[1]\",    12,    12,    12,    12 ],\n [   \"oce aux\", \"u_d[2]\",    12,    12,    12,    12 ],\n [   \"oce aux\", \"ΔGu[1]\",    12,    12,    12,    12 ],\n [   \"oce aux\", \"ΔGu[2]\",    12,    12,    12,    12 ],\n [   \"oce aux\",      \"y\",    12,    12,    12,    12 ],\n [ \"baro Q_2D\",   \"U[1]\",    12,    12,    12,    12 ],\n [ \"baro Q_2D\",   \"U[2]\",    12,    12,    12,    12 ],\n [ \"baro Q_2D\",      \"η\",    12,    12,     8,    12 ],\n [  \"baro aux\",  \"Gᵁ[1]\",    12,    12,    12,    12 ],\n [  \"baro aux\",  \"Gᵁ[2]\",    12,    12,    12,    12 ],\n [  \"baro aux\", \"U_c[1]\",    12,    12,    12,    12 ],\n [  \"baro aux\", \"U_c[2]\",    12,    12,    12,    12 ],\n [  \"baro aux\",    \"η_c\",    12,    12,     8,    12 ],\n [  \"baro aux\", \"U_s[1]\",    12,    12,    12,    12 ],\n [  \"baro aux\", \"U_s[2]\",    12,    12,    12,    12 ],\n [  \"baro aux\",    \"η_s\",    12,    12,     8,    12 ],\n [  \"baro aux\",  \"Δu[1]\",    12,    12,    12,    12 ],\n [  \"baro aux\",  \"Δu[2]\",    12,    12,    12,    12 ],\n [  \"baro aux\", \"η_diag\",    12,    12,     8,    12 ],\n [  \"baro aux\",     \"Δη\",     9,     9,     6,    10 ],\n [  \"baro aux\",      \"y\",    12,    12,    12,    12 ],\n]\n# END SCPRINT\n# SC ====================================================================================\n\n    append!(refVals ,[ varr ] )\n    append!(refPrecs,[ parr ] )\n\n#! format: on\n"
  },
  {
    "path": "test/Ocean/refvals/simple_box_ivd_refvals.jl",
    "content": "refVals = []\nrefPrecs = []\n\n#! format: off\n# SC ========== Test number 1 reference values and precision match template. =======\n# SC ========== /home/jmc/cliMa/cliMa_update/test/Ocean/SplitExplicit/simple_box_ivd.jl test reference values ======================================\n# BEGIN SCPRINT\n# varr - reference values (from reference run)\n# parr - digits match precision (hand edit as needed)\n#\n# [\n#  [ MPIStateArray Name, Field Name, Maximum, Minimum, Mean, Standard Deviation ],\n#  [         :                :          :        :      :          :           ],\n# ]\nvarr = [\n [  \"oce Q_3D\",   \"u[1]\", -1.66327525125104291881e-01,  1.63223287218068308091e-01,  4.57054199141500999692e-03,  2.21420008866952226778e-02 ],\n [  \"oce Q_3D\",   \"u[2]\", -2.16296814964942768489e-01,  2.44105976363460708267e-01, -2.57418981108877616831e-03,  2.49256704872522875938e-02 ],\n [  \"oce Q_3D\",      \"η\", -8.06721661019456748321e-01,  3.47844179114868090608e-01, -3.03380420083551127861e-04,  2.92158779168222249023e-01 ],\n [  \"oce Q_3D\",      \"θ\",  3.58991077492186536763e-03,  9.91901429055192807027e+00,  2.49592172215209329167e+00,  2.17949780843674956188e+00 ],\n [   \"oce aux\",      \"w\", -1.66963788550371410252e-04,  1.64989216196191281925e-04,  5.43118005323253843601e-07,  1.44011197008218777164e-05 ],\n [   \"oce aux\",   \"pkin\", -8.84700789178211977060e+00,  0.00000000000000000000e+00, -3.26178716377690980366e+00,  2.50117471654691314598e+00 ],\n [   \"oce aux\",    \"wz0\", -2.12922215345145814233e-05,  3.08186566412866553883e-05, -1.53993475195290930982e-10,  7.82327443240865498639e-06 ],\n [   \"oce aux\", \"u_d[1]\", -1.57099271619415420398e-01,  1.22268940698781983234e-01, -7.84250724128123411372e-05,  1.15452721709468873745e-02 ],\n [   \"oce aux\", \"u_d[2]\", -2.12964763257990491452e-01,  2.30578860790304179806e-01,  2.76337873040526178152e-05,  1.75119371638010334902e-02 ],\n [   \"oce aux\", \"ΔGu[1]\", -2.33686060871737704252e-06,  3.34281015324516805469e-06, -4.07662146058456868485e-08,  3.37377005914367152159e-07 ],\n [   \"oce aux\", \"ΔGu[2]\", -2.46095864522509354306e-06,  2.23139964389116549296e-06,  1.29016375934756163675e-06,  7.46140899703298511983e-07 ],\n [   \"oce aux\",      \"y\",  0.00000000000000000000e+00,  4.00000000000000046566e+06,  2.00000000000000000000e+06,  1.15573163901915703900e+06 ],\n [ \"baro Q_2D\",   \"U[1]\", -1.77642579751613922667e+01,  6.90024701711891168543e+01,  4.64758402961474637038e+00,  1.81125164737756669808e+01 ],\n [ \"baro Q_2D\",   \"U[2]\", -3.48668457221064898022e+01,  8.56043954389619301537e+01, -2.60107324488959568143e+00,  1.80557567005594421516e+01 ],\n [ \"baro Q_2D\",      \"η\", -8.06729084084832237522e-01,  3.47845108442749906263e-01, -3.03371430044830942430e-04,  2.92173862421732766226e-01 ],\n [  \"baro aux\",  \"Gᵁ[1]\", -3.34281015324516816989e-03,  2.33686060871737691716e-03,  4.07662146058456834074e-05,  3.37393707332951826861e-04 ],\n [  \"baro aux\",  \"Gᵁ[2]\", -2.23139964389116544213e-03,  2.46095864522509347530e-03, -1.29016375934756159609e-03,  7.46177836457347174598e-04 ],\n [  \"baro aux\", \"U_c[1]\", -1.77641349600227265171e+01,  6.90014815427443437557e+01,  4.64756464081321674087e+00,  1.81123414932683921563e+01 ],\n [  \"baro aux\", \"U_c[2]\", -3.48658691554727866446e+01,  8.56044062434442594167e+01, -2.60101037971774573521e+00,  1.80557220201615180599e+01 ],\n [  \"baro aux\",    \"η_c\", -8.06721661019456748321e-01,  3.47844179114868090608e-01, -3.03380420083561807253e-04,  2.92173242116136766544e-01 ],\n [  \"baro aux\", \"U_s[1]\", -1.77642579751613922667e+01,  6.90024701711891168543e+01,  4.64758402961474637038e+00,  1.81125164737756669808e+01 ],\n [  \"baro aux\", \"U_s[2]\", -3.48668457221064898022e+01,  8.56043954389619301537e+01, -2.60107324488959568143e+00,  1.80557567005594421516e+01 ],\n [  \"baro aux\",    \"η_s\", -8.06729084084832237522e-01,  3.47845108442749906263e-01, -3.03371430044830942430e-04,  2.92173862421732766226e-01 ],\n [  \"baro aux\",  \"Δu[1]\", -4.51957131810988945505e-04,  3.99130692959488437947e-04, -1.09363921539353876238e-05,  1.02327771113594037156e-04 ],\n [  \"baro aux\",  \"Δu[2]\", -2.42568998424025800446e-04,  3.82991529986384212410e-04,  5.18845385039475763891e-05,  7.47269801857268440920e-05 ],\n [  \"baro aux\", \"η_diag\", -8.06666755113148337131e-01,  3.47953196151883525911e-01, -3.02575448959868274160e-04,  2.92168272060050748795e-01 ],\n [  \"baro aux\",     \"Δη\", -5.84450730272745300198e-04,  7.45283859099332701703e-04, -8.04971123704421912570e-07,  1.64194305918131369573e-04 ],\n [  \"baro aux\",      \"y\",  0.00000000000000000000e+00,  4.00000000000000046566e+06,  2.00000000000000000000e+06,  1.15578885204060329124e+06 ],\n]\nparr = [\n [  \"oce Q_3D\",   \"u[1]\",    12,    12,    12,    12 ],\n [  \"oce Q_3D\",   \"u[2]\",    12,    12,    12,    12 ],\n [  \"oce Q_3D\",      \"η\",    12,    12,     8,    12 ],\n [  \"oce Q_3D\",      \"θ\",    12,    12,    12,    12 ],\n [   \"oce aux\",      \"w\",    12,    12,     8,    12 ],\n [   \"oce aux\",   \"pkin\",    12,    12,    12,    12 ],\n [   \"oce aux\",    \"wz0\",    12,    12,     8,    12 ],\n [   \"oce aux\", \"u_d[1]\",    12,    12,    12,    12 ],\n [   \"oce aux\", \"u_d[2]\",    12,    12,    12,    12 ],\n [   \"oce aux\", \"ΔGu[1]\",    12,    12,    12,    12 ],\n [   \"oce aux\", \"ΔGu[2]\",    12,    12,    12,    12 ],\n [   \"oce aux\",      \"y\",    12,    12,    12,    12 ],\n [ \"baro Q_2D\",   \"U[1]\",    12,    12,    12,    12 ],\n [ \"baro Q_2D\",   \"U[2]\",    12,    12,    12,    12 ],\n [ \"baro Q_2D\",      \"η\",    12,    12,     8,    12 ],\n [  \"baro aux\",  \"Gᵁ[1]\",    12,    12,    12,    12 ],\n [  \"baro aux\",  \"Gᵁ[2]\",    12,    12,    12,    12 ],\n [  \"baro aux\", \"U_c[1]\",    12,    12,    12,    12 ],\n [  \"baro aux\", \"U_c[2]\",    12,    12,    12,    12 ],\n [  \"baro aux\",    \"η_c\",    12,    12,     8,    12 ],\n [  \"baro aux\", \"U_s[1]\",    12,    12,    12,    12 ],\n [  \"baro aux\", \"U_s[2]\",    12,    12,    12,    12 ],\n [  \"baro aux\",    \"η_s\",    12,    12,     8,    12 ],\n [  \"baro aux\",  \"Δu[1]\",    12,    12,    12,    12 ],\n [  \"baro aux\",  \"Δu[2]\",    12,    12,    12,    12 ],\n [  \"baro aux\", \"η_diag\",    12,    12,     8,    12 ],\n [  \"baro aux\",     \"Δη\",     9,     9,     6,    10 ],\n [  \"baro aux\",      \"y\",    12,    12,    12,    12 ],\n]\n# END SCPRINT\n# SC ====================================================================================\n\n    append!(refVals ,[ varr ] )\n    append!(refPrecs,[ parr ] )\n\n#! format: on\n"
  },
  {
    "path": "test/Ocean/refvals/simple_box_rk3_refvals.jl",
    "content": "refVals = []\nrefPrecs = []\n\n#! format: off\n# SC ========== Test number 1 reference values and precision match template. =======\n# SC ========== /home/jmc/cliMa/cliMa_new_jmc/test/Ocean/SplitExplicit/simple_box_rk3.jl test reference values ======================================\n# BEGIN SCPRINT\n# varr - reference values (from reference run)\n# parr - digits match precision (hand edit as needed)\n#\n# [\n#  [ MPIStateArray Name, Field Name, Maximum, Minimum, Mean, Standard Deviation ],\n#  [         :                :          :        :      :          :           ],\n# ]\nvarr = [\n [  \"oce Q_3D\",   \"u[1]\", -2.16721345244414054232e-01,  1.57840557320998220447e-01, -5.59921465916523013878e-03,  1.73941108901450175450e-02 ],\n [  \"oce Q_3D\",   \"u[2]\", -2.19220347308738905401e-01,  2.31784478500299123693e-01, -2.15429258177158092225e-03,  2.12517725248480282563e-02 ],\n [  \"oce Q_3D\",      \"η\", -4.69394735091986536890e-01,  2.06288744860751438459e-01, -2.16765734563377927271e-04,  1.47004018586567947180e-01 ],\n [  \"oce Q_3D\",      \"θ\",  2.38571451036062890869e-03,  9.91209376647090323331e+00,  2.49760287312251527680e+00,  2.18031198350304133982e+00 ],\n [   \"oce aux\",      \"w\", -1.61734977485578068895e-04,  1.50833500332270227301e-04,  5.43794052142437598582e-07,  1.38648560985387203702e-05 ],\n [   \"oce aux\",   \"pkin\", -8.85948574009684719499e+00,  0.00000000000000000000e+00, -3.26449934601666758027e+00,  2.50111590039020725840e+00 ],\n [   \"oce aux\",    \"wz0\", -3.66555541768163618680e-05,  1.94813350674614766593e-05, -4.61824895762852330113e-10,  9.37524239840742993843e-06 ],\n [   \"oce aux\", \"u_d[1]\", -1.56194066602061892857e-01,  1.21967899279333547025e-01, -2.05350727093651255306e-05,  1.15672828345751727702e-02 ],\n [   \"oce aux\", \"u_d[2]\", -2.09729915133787636616e-01,  2.37375904307950719163e-01,  1.58965003169083721666e-06,  1.81625473749615351515e-02 ],\n [   \"oce aux\", \"ΔGu[1]\", -2.09811375971404995481e-06,  3.23018047166822386688e-06, -4.22417846605267206689e-08,  2.80615396594168210400e-07 ],\n [   \"oce aux\", \"ΔGu[2]\", -2.18036232525426524906e-06,  2.31188192991968871615e-06,  1.27801756611837186519e-06,  7.19811396028193828918e-07 ],\n [   \"oce aux\",      \"y\",  0.00000000000000000000e+00,  4.00000000000000046566e+06,  2.00000000000000000000e+06,  1.15573163901915703900e+06 ],\n [ \"baro Q_2D\",   \"U[1]\", -3.61200008824836729104e+01,  2.32336820840376567787e+01, -5.58234606367367547364e+00,  1.11708003103345792084e+01 ],\n [ \"baro Q_2D\",   \"U[2]\", -4.02361402227773581330e+01,  4.41897774443185085147e+01, -2.15561232944511127485e+00,  1.17915802114324606009e+01 ],\n [ \"baro Q_2D\",      \"η\", -4.69794642977587939559e-01,  2.06346018349864157582e-01, -2.16567930962150958151e-04,  1.47057680237089760666e-01 ],\n [  \"baro aux\",  \"Gᵁ[1]\", -3.23018047166822403968e-03,  2.09811375971404989044e-03,  4.22417846605267028812e-05,  2.80629288101644008488e-04 ],\n [  \"baro aux\",  \"Gᵁ[2]\", -2.31188192991968856707e-03,  2.18036232525426528633e-03, -1.27801756611837201427e-03,  7.19847029373728297674e-04 ],\n [  \"baro aux\", \"U_c[1]\", -3.60967490653019851266e+01,  2.32266714099544557826e+01, -5.58248511658658941315e+00,  1.11690207251244704167e+01 ],\n [  \"baro aux\", \"U_c[2]\", -4.02002845377460147347e+01,  4.41870183291104439149e+01, -2.15756908928005142201e+00,  1.17881994981494511165e+01 ],\n [  \"baro aux\",    \"η_c\", -4.69394735091986536890e-01,  2.06288744860751438459e-01, -2.16765734563369958385e-04,  1.47011295833105265496e-01 ],\n [  \"baro aux\", \"U_s[1]\", -3.61200008824836729104e+01,  2.32336820840376567787e+01, -5.58234606367367547364e+00,  1.11708003103345792084e+01 ],\n [  \"baro aux\", \"U_s[2]\", -4.02361402227773581330e+01,  4.41897774443185085147e+01, -2.15561232944511127485e+00,  1.17915802114324606009e+01 ],\n [  \"baro aux\",    \"η_s\", -4.69794642977587939559e-01,  2.06346018349864157582e-01, -2.16567930962150958151e-04,  1.47057680237089760666e-01 ],\n [  \"baro aux\",  \"Δu[1]\", -1.12410191785971484528e-03,  1.98901388437166311979e-03,  1.77992355967350647386e-04,  4.24716110102865956194e-04 ],\n [  \"baro aux\",  \"Δu[2]\", -1.40780586763535704373e-03,  1.04939722938436467460e-03, -2.11977336567820129603e-04,  2.73797449735710581031e-04 ],\n [  \"baro aux\", \"η_diag\", -4.69273762873680611030e-01,  2.06327225515284040647e-01, -2.15810591775846216884e-04,  1.47008240438936316208e-01 ],\n [  \"baro aux\",     \"Δη\", -2.62922736242607313351e-04,  2.37963172744791451318e-04, -9.55142787533847028187e-07,  6.94341553655788138637e-05 ],\n [  \"baro aux\",      \"y\",  0.00000000000000000000e+00,  4.00000000000000046566e+06,  2.00000000000000000000e+06,  1.15578885204060329124e+06 ],\n]\nparr = [\n [  \"oce Q_3D\",   \"u[1]\",    12,    12,    12,    12 ],\n [  \"oce Q_3D\",   \"u[2]\",    12,    12,    12,    12 ],\n [  \"oce Q_3D\",      \"η\",    12,    12,     8,    12 ],\n [  \"oce Q_3D\",      \"θ\",    12,    12,    12,    12 ],\n [   \"oce aux\",      \"w\",    12,    12,     8,    12 ],\n [   \"oce aux\",   \"pkin\",    12,    12,    12,    12 ],\n [   \"oce aux\",    \"wz0\",    12,    12,     8,    12 ],\n [   \"oce aux\", \"u_d[1]\",    12,    12,    12,    12 ],\n [   \"oce aux\", \"u_d[2]\",    12,    12,    12,    12 ],\n [   \"oce aux\", \"ΔGu[1]\",    12,    12,    12,    12 ],\n [   \"oce aux\", \"ΔGu[2]\",    12,    12,    12,    12 ],\n [   \"oce aux\",      \"y\",    12,    12,    12,    12 ],\n [ \"baro Q_2D\",   \"U[1]\",    12,    12,    12,    12 ],\n [ \"baro Q_2D\",   \"U[2]\",    12,    12,    12,    12 ],\n [ \"baro Q_2D\",      \"η\",    12,    12,     8,    12 ],\n [  \"baro aux\",  \"Gᵁ[1]\",    12,    12,    12,    12 ],\n [  \"baro aux\",  \"Gᵁ[2]\",    12,    12,    12,    12 ],\n [  \"baro aux\", \"U_c[1]\",    12,    12,    12,    12 ],\n [  \"baro aux\", \"U_c[2]\",    12,    12,    12,    12 ],\n [  \"baro aux\",    \"η_c\",    12,    12,     8,    12 ],\n [  \"baro aux\", \"U_s[1]\",    12,    12,    12,    12 ],\n [  \"baro aux\", \"U_s[2]\",    12,    12,    12,    12 ],\n [  \"baro aux\",    \"η_s\",    12,    12,     8,    12 ],\n [  \"baro aux\",  \"Δu[1]\",    12,    12,    12,    12 ],\n [  \"baro aux\",  \"Δu[2]\",    12,    12,    12,    12 ],\n [  \"baro aux\", \"η_diag\",    12,    12,     8,    12 ],\n [  \"baro aux\",     \"Δη\",     9,     9,     6,    10 ],\n [  \"baro aux\",      \"y\",    12,    12,    12,    12 ],\n]\n# END SCPRINT\n# SC ====================================================================================\n\n    append!(refVals ,[ varr ] )\n    append!(refPrecs,[ parr ] )\n\n#! format: on\n"
  },
  {
    "path": "test/Ocean/refvals/simple_dbl_gyre_refvals.jl",
    "content": "refVals = []\nrefPrecs = []\n\n#! format: off\n# SC ========== Test number 1 reference values and precision match template. =======\n# SC ========== /home/jmc/cliMa/cliMa_new_jmc/test/Ocean/SplitExplicit/simple_dbl_gyre.jl test reference values ======================================\n# BEGIN SCPRINT\n# varr - reference values (from reference run)\n# parr - digits match precision (hand edit as needed)\n#\n# [\n#  [ MPIStateArray Name, Field Name, Maximum, Minimum, Mean, Standard Deviation ],\n#  [         :                :          :        :      :          :           ],\n# ]\nvarr = [\n [  \"oce Q_3D\",   \"u[1]\", -1.41235234130601461366e-01,  2.44137911410471170059e-01, -1.17638792697003554538e-02,  4.76422973396204429974e-02 ],\n [  \"oce Q_3D\",   \"u[2]\", -3.71526973402696303328e-01,  2.63129391123315958811e-01, -3.84758873236049556144e-02,  4.34631279346227028526e-02 ],\n [  \"oce Q_3D\",      \"η\", -3.62315954238604787108e+00,  3.25374835544518203889e+00, -1.15530630772209001104e-04,  1.69499420692026547819e+00 ],\n [  \"oce Q_3D\",      \"θ\", -1.95989738813091111946e-02,  2.41980669630830433903e+01,  6.00291945213983790808e+00,  5.36502088796993170661e+00 ],\n [   \"oce aux\",      \"w\", -1.42436775039925483770e-03,  1.17093883909037276177e-03, -4.27143547438785408186e-07,  1.58700821328606480297e-04 ],\n [   \"oce aux\",   \"pkin\", -6.47863971232045656734e+01,  0.00000000000000000000e+00, -2.35470923626465094003e+01,  1.84884052655973327717e+01 ],\n [   \"oce aux\",    \"wz0\", -2.03056757204373681007e-04,  2.51217746755789830913e-04, -1.04288746719621225719e-08,  9.09212328801199519022e-05 ],\n [   \"oce aux\", \"u_d[1]\", -1.29380048170506856131e-01,  2.41847160349357936937e-01,  1.50957373152410783811e-04,  4.55063940872466113352e-02 ],\n [   \"oce aux\", \"u_d[2]\", -2.31151064740037659462e-01,  3.17638906635839823878e-01,  7.38946153332456550028e-05,  3.35035482074258969543e-02 ],\n [   \"oce aux\", \"ΔGu[1]\", -2.28025960163159889874e-05,  3.46723882659755934413e-06, -5.56600796147506152338e-07,  2.61685583178905381934e-06 ],\n [   \"oce aux\", \"ΔGu[2]\", -3.00923384704360015996e-06,  1.24960695834485056166e-05,  6.54681130814259427502e-06,  3.13708534178777629812e-06 ],\n [   \"oce aux\",      \"y\",  0.00000000000000000000e+00,  6.00000000000000093132e+06,  3.00000000000000000000e+06,  1.73273876310492726043e+06 ],\n [ \"baro Q_2D\",   \"U[1]\", -1.37905164694259781299e+02,  1.09296887114357033965e+02, -3.59834173192416955089e+01,  4.24653520384661575804e+01 ],\n [ \"baro Q_2D\",   \"U[2]\", -4.93706839547863808093e+02,  5.71157285218092027890e+01, -1.15735478409127139798e+02,  8.23847078816430951065e+01 ],\n [ \"baro Q_2D\",      \"η\", -3.62624136658312767878e+00,  3.25919860855558907176e+00, -1.15147341274860083972e-04,  1.69658109382101818241e+00 ],\n [  \"baro aux\",  \"Gᵁ[1]\", -1.04017164797926778969e-02,  6.84077880489479678294e-02,  1.66980238844251878058e-03,  7.85082570477715034618e-03 ],\n [  \"baro aux\",  \"Gᵁ[2]\", -3.74882087503455169175e-02,  9.02770154113080071367e-03, -1.96404339244277831300e-02,  9.41156556666298931002e-03 ],\n [  \"baro aux\", \"U_c[1]\", -1.36958883839043295438e+02,  1.09074110263089167461e+02, -3.57794851972332565992e+01,  4.23235946471143051895e+01 ],\n [  \"baro aux\", \"U_c[2]\", -4.92966277666792962009e+02,  5.72050394052077919582e+01, -1.15636183420627688179e+02,  8.22648167942200103653e+01 ],\n [  \"baro aux\",    \"η_c\", -3.62315954238604787108e+00,  3.25374835544518203889e+00, -1.15530630772203336148e-04,  1.69504995619627174541e+00 ],\n [  \"baro aux\", \"U_s[1]\", -1.37905164694259781299e+02,  1.09296887114357033965e+02, -3.59834173192416955089e+01,  4.24653520384661575804e+01 ],\n [  \"baro aux\", \"U_s[2]\", -4.93706839547863808093e+02,  5.71157285218092027890e+01, -1.15735478409127139798e+02,  8.23847078816430951065e+01 ],\n [  \"baro aux\",    \"η_s\", -3.62624136658312767878e+00,  3.25919860855558907176e+00, -1.15147341274860083972e-04,  1.69658109382101818241e+00 ],\n [  \"baro aux\",  \"Δu[1]\", -4.66680666769807577128e-04,  1.05054947340137826844e-02,  3.56177517668052924515e-03,  2.34394235403509801352e-03 ],\n [  \"baro aux\",  \"Δu[2]\", -2.98072019929583121103e-03,  4.75363215770287662193e-03,  1.22307915357422513150e-03,  1.31189027778619380499e-03 ],\n [  \"baro aux\", \"η_diag\", -3.62078008618114477457e+00,  3.25151939956774960194e+00, -1.15589924206263583179e-04,  1.69498993030730304987e+00 ],\n [  \"baro aux\",     \"Δη\", -8.83137470915729139165e-03,  5.97876216292370088468e-03,  5.92934342415881362251e-08,  2.08693123903101827171e-03 ],\n [  \"baro aux\",      \"y\",  0.00000000000000000000e+00,  6.00000000000000093132e+06,  3.00000000000000000000e+06,  1.73279575381979602389e+06 ],\n]\nparr = [\n [  \"oce Q_3D\",   \"u[1]\",    12,    12,    12,    12 ],\n [  \"oce Q_3D\",   \"u[2]\",    12,    12,    12,    12 ],\n [  \"oce Q_3D\",      \"η\",    12,    12,     8,    12 ],\n [  \"oce Q_3D\",      \"θ\",    12,    12,    12,    12 ],\n [   \"oce aux\",      \"w\",    12,    12,     8,    12 ],\n [   \"oce aux\",   \"pkin\",    12,    12,    12,    12 ],\n [   \"oce aux\",    \"wz0\",    12,    12,     8,    12 ],\n [   \"oce aux\", \"u_d[1]\",    12,    12,    12,    12 ],\n [   \"oce aux\", \"u_d[2]\",    12,    12,    12,    12 ],\n [   \"oce aux\", \"ΔGu[1]\",    12,    12,    12,    12 ],\n [   \"oce aux\", \"ΔGu[2]\",    12,    12,    12,    12 ],\n [   \"oce aux\",      \"y\",    12,    12,    12,    12 ],\n [ \"baro Q_2D\",   \"U[1]\",    12,    12,    12,    12 ],\n [ \"baro Q_2D\",   \"U[2]\",    12,    12,    12,    12 ],\n [ \"baro Q_2D\",      \"η\",    12,    12,     8,    12 ],\n [  \"baro aux\",  \"Gᵁ[1]\",    12,    12,    12,    12 ],\n [  \"baro aux\",  \"Gᵁ[2]\",    12,    12,    12,    12 ],\n [  \"baro aux\", \"U_c[1]\",    12,    12,    12,    12 ],\n [  \"baro aux\", \"U_c[2]\",    12,    12,    12,    12 ],\n [  \"baro aux\",    \"η_c\",    12,    12,     8,    12 ],\n [  \"baro aux\", \"U_s[1]\",    12,    12,    12,    12 ],\n [  \"baro aux\", \"U_s[2]\",    12,    12,    12,    12 ],\n [  \"baro aux\",    \"η_s\",    12,    12,     8,    12 ],\n [  \"baro aux\",  \"Δu[1]\",    12,    12,    12,    12 ],\n [  \"baro aux\",  \"Δu[2]\",    12,    12,    12,    12 ],\n [  \"baro aux\", \"η_diag\",    12,    12,     8,    12 ],\n [  \"baro aux\",     \"Δη\",     9,     9,     6,    10 ],\n [  \"baro aux\",      \"y\",    12,    12,    12,    12 ],\n]\n# END SCPRINT\n# SC ====================================================================================\n\n    append!(refVals ,[ varr ] )\n    append!(refPrecs,[ parr ] )\n\n#! format: on\n"
  },
  {
    "path": "test/Ocean/refvals/test_ocean_gyre_refvals.jl",
    "content": "parr = [\n    [\"Q\", \"u[1]\", 12, 12, 12, 12],\n    [\"Q\", \"u[2]\", 12, 12, 12, 12],\n    [\"Q\", \"η\", 12, 12, 11, 12],\n    [\"Q\", \"θ\", 12, 12, 12, 12],\n    [\"s_aux\", \"y\", 12, 12, 12, 12],\n    [\"s_aux\", \"w\", 12, 12, 11, 12],\n    [\"s_aux\", \"pkin\", 12, 12, 12, 12],\n    [\"s_aux\", \"wz0\", 12, 11, 10, 12],\n    [\"s_aux\", \"uᵈ[1]\", 12, 12, 12, 12],\n    [\"s_aux\", \"uᵈ[2]\", 12, 12, 12, 12],\n    [\"s_aux\", \"ΔGᵘ[1]\", 12, 12, 12, 12],\n    [\"s_aux\", \"ΔGᵘ[2]\", 12, 12, 12, 12],\n]\n\n#! format: off\n\nshort = [\n [     \"Q\",   \"u[1]\", -1.56752732465427965791e-02,  1.68514505893861757380e-02, -2.29247099512640793717e-03,  3.25160701902235671490e-03 ],\n [     \"Q\",   \"u[2]\", -3.79775189267773510826e-02,  6.43003815969073189152e-03, -1.34097283250433057383e-02,  1.20337368194613283934e-02 ],\n [     \"Q\",      \"η\", -1.53249855976142324021e-01,  1.57769367725846931805e-01, -6.47997524778634250456e-06,  1.03985821375781786746e-01 ],\n [     \"Q\",      \"θ\",  1.07842251824037485168e-05,  9.00370181779731204585e+00,  2.49971752106072075961e+00,  2.19711465681760964586e+00 ],\n [ \"s_aux\",      \"y\",  0.00000000000000000000e+00,  1.00000000000000011642e+06,  5.00000000000000000000e+05,  2.92779390974978974555e+05 ],\n [ \"s_aux\",      \"w\", -9.71167446044889304474e-05,  8.57892392958965760040e-05,  7.22305481630802806057e-08,  3.80815409312154189983e-05 ],\n [ \"s_aux\",   \"pkin\", -8.99913049767501638243e-01,  0.00000000000000000000e+00, -3.32109083459310172604e-01,  2.56226532893150116266e-01 ],\n [ \"s_aux\",    \"wz0\", -8.17753668537623428815e-05,  8.25631396299614581233e-05, -1.10719884491561329431e-09,  5.14580958965247714965e-05 ],\n [ \"s_aux\",  \"uᵈ[1]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n [ \"s_aux\",  \"uᵈ[2]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n [ \"s_aux\", \"ΔGᵘ[1]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n [ \"s_aux\", \"ΔGᵘ[2]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n]\n\nlong = [\n [     \"Q\",   \"u[1]\", -7.88803234115370982549e-02,  6.54328011157656042052e-02,  2.01451525264662988784e-03,  1.24588716783014998718e-02 ],\n [     \"Q\",   \"u[2]\", -8.71605274310923855419e-02,  1.47505267452481658719e-01,  5.63158201082450144553e-03,  1.28959719954838351180e-02 ],\n [     \"Q\",      \"η\", -4.73491408442001826540e-01,  4.02693687285959778244e-01, -6.49059970677390036392e-05,  2.21689928090663096460e-01 ],\n [     \"Q\",      \"θ\",  4.24292935192232840286e-04,  9.24539353455579338004e+00,  2.49938206627401893201e+00,  2.17986626392490689952e+00 ],\n [ \"s_aux\",      \"y\",  0.00000000000000000000e+00,  4.00000000000000046566e+06,  2.00000000000000000000e+06,  1.15573163901915703900e+06 ],\n [ \"s_aux\",      \"w\", -2.22086406767006932887e-04,  2.00575090959222558738e-04,  2.53168866096380013512e-07,  1.66257132341935760973e-05 ],\n [ \"s_aux\",   \"pkin\", -9.00869877619916104017e-01,  0.00000000000000000000e+00, -3.33171488779369751043e-01,  2.54740287894525019308e-01 ],\n [ \"s_aux\",    \"wz0\", -2.96608015795817572195e-05,  3.66759042312928121082e-05,  3.78116102337067175813e-10,  1.07073256826410757734e-05 ],\n [ \"s_aux\",  \"uᵈ[1]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n [ \"s_aux\",  \"uᵈ[2]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n [ \"s_aux\", \"ΔGᵘ[1]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n [ \"s_aux\", \"ΔGᵘ[2]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n]\n\n#! format: on\n\nrefVals = (short = (short, parr), long = (long, parr))\n"
  },
  {
    "path": "test/Ocean/refvals/test_vertical_integral_model_refvals.jl",
    "content": "# [\n#  [ MPIStateArray Name, Field Name, Maximum, Minimum, Mean, Standard Deviation ],\n#  [         :                :          :        :      :          :           ],\n# ]\n\nparr = [\n    [\"∫u\", \"∫x[1]\", 12, 12, 0, 12],\n    [\"∫u\", \"∫x[2]\", 15, 15, 15, 15],\n    [\"U\", \"η\", 12, 12, 0, 12],\n    [\"U\", \"U[1]\", 12, 12, 0, 12],\n    [\"U\", \"U[2]\", 15, 15, 15, 15],\n]\n\ninitial = [\n    [\n        \"∫u\",\n        \"∫x[1]\",\n        -6.36104748927323058183e+01,\n        6.36104748927324763486e+01,\n        3.59432306140661235747e-14,\n        3.16776758596447187699e+01,\n    ],\n    [\n        \"∫u\",\n        \"∫x[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"U\",\n        \"η\",\n        -1.00000000000000000000e+00,\n        1.00000000000000000000e+00,\n        3.73034936274052574533e-18,\n        7.07673146340372483110e-01,\n    ],\n    [\n        \"U\",\n        \"U[1]\",\n        -9.95282537582876658533e-01,\n        9.95282537582876547511e-01,\n        -8.82958196287834196438e-18,\n        7.07673146340372150043e-01,\n    ],\n    [\n        \"U\",\n        \"U[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n]\n\nday = [\n    [\n        \"∫u\",\n        \"∫x[1]\",\n        -6.40437964821475844701e+01,\n        6.40437964821476981570e+01,\n        2.21189111471176159638e-14,\n        2.60887556680598677872e+01,\n    ],\n    [\n        \"∫u\",\n        \"∫x[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"U\",\n        \"η\",\n        -8.52761706341222169847e-01,\n        8.52761706341222169847e-01,\n        -1.86517468137026310378e-17,\n        6.03476559805077306109e-01,\n    ],\n    [\n        \"U\",\n        \"U[1]\",\n        -3.15397076561132330141e+01,\n        3.15397076561132294614e+01,\n        -2.82332564752296143530e-15,\n        2.24255960582435314166e+01,\n    ],\n    [\n        \"U\",\n        \"U[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n]\n\nmonth = [\n    [\n        \"∫u\",\n        \"∫x[1]\",\n        -3.51564130159214158766e+01,\n        3.51564130159213874549e+01,\n        -1.16415321826934820032e-14,\n        1.41614248818105643579e+01,\n    ],\n    [\n        \"∫u\",\n        \"∫x[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"U\",\n        \"η\",\n        -5.30601964901526002016e-01,\n        5.30601964901526002016e-01,\n        -1.24344978758017535116e-17,\n        3.75492761956246756672e-01,\n    ],\n    [\n        \"U\",\n        \"U[1]\",\n        -3.51564130159208829696e+01,\n        3.51564130159208758641e+01,\n        -2.47942593560761348802e-15,\n        2.49971726354604832920e+01,\n    ],\n    [\n        \"U\",\n        \"U[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n]\n\nyear = [\n    [\n        \"∫u\",\n        \"∫x[1]\",\n        -4.25110151351051179791e-01,\n        4.25110151351050846724e-01,\n        -1.36424205265939223736e-16,\n        1.74619680058462317662e-01,\n    ],\n    [\n        \"∫u\",\n        \"∫x[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"U\",\n        \"η\",\n        -4.39687965160425187072e-02,\n        4.39687965160425187072e-02,\n        -6.10622663543836061796e-19,\n        3.11155365713074068268e-02,\n    ],\n    [\n        \"U\",\n        \"U[1]\",\n        -4.25110151351044573964e-01,\n        4.25110151351044518453e-01,\n        -1.99959679826973105365e-17,\n        3.02264961945818255717e-01,\n    ],\n    [\n        \"U\",\n        \"U[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n]\n\ndecade = [\n    [\n        \"∫u\",\n        \"∫x[1]\",\n        -1.88298126764002322427e-12,\n        1.88298126764002483985e-12,\n        5.62482816536058859320e-28,\n        7.73459738532225749582e-13,\n    ],\n    [\n        \"∫u\",\n        \"∫x[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"U\",\n        \"η\",\n        -3.38352596570510824820e-15,\n        3.38352596570510824820e-15,\n        6.31088724176809474572e-34,\n        2.39443046587488032416e-15,\n    ],\n    [\n        \"U\",\n        \"U[1]\",\n        -1.88298126763999575929e-12,\n        1.88298126763999575929e-12,\n        3.78999175038214983828e-29,\n        1.33885125866565214937e-12,\n    ],\n    [\n        \"U\",\n        \"U[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n]\n\ncentury = [\n    [\n        \"∫u\",\n        \"∫x[1]\",\n        -3.97994692621354800600e-134,\n        3.97994692621354409568e-134,\n        -1.20124985710986044942e-149,\n        1.63481642745144852479e-134,\n    ],\n    [\n        \"∫u\",\n        \"∫x[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n    [\n        \"U\",\n        \"η\",\n        -2.07116984864437321907e-136,\n        2.07116984864437321907e-136,\n        -5.62108290883927252001e-153,\n        1.46571128339547670108e-136,\n    ],\n    [\n        \"U\",\n        \"U[1]\",\n        -3.97994692621348592969e-134,\n        3.97994692621348544091e-134,\n        -4.15669815960076969677e-150,\n        2.82985128060348631090e-134,\n    ],\n    [\n        \"U\",\n        \"U[2]\",\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n        0.00000000000000000000e+00,\n    ],\n]\n\nrefVals = (\n    intitial = (initial, parr),\n    day = (day, parr),\n    month = (month, parr),\n    year = (year, parr),\n    decade = (decade, parr),\n    century = (century, parr),\n)\n"
  },
  {
    "path": "test/Ocean/refvals/test_windstress_refvals.jl",
    "content": "parr = [\n    [\"Q\", \"u[1]\", 12, 12, 12, 12],\n    [\"Q\", \"u[2]\", 12, 12, 12, 12],\n    [\"Q\", \"η\", 12, 12, 11, 12],\n    [\"Q\", \"θ\", 12, 12, 12, 0],\n    [\"s_aux\", \"y\", 12, 12, 12, 12],\n    [\"s_aux\", \"w\", 12, 12, 12, 12],\n    [\"s_aux\", \"pkin\", 12, 12, 12, 12],\n    [\"s_aux\", \"wz0\", 12, 12, 10, 12],\n    [\"s_aux\", \"uᵈ[1]\", 12, 12, 12, 12],\n    [\"s_aux\", \"uᵈ[2]\", 12, 12, 12, 12],\n    [\"s_aux\", \"ΔGᵘ[1]\", 12, 12, 12, 12],\n    [\"s_aux\", \"ΔGᵘ[2]\", 12, 12, 12, 12],\n]\n\n#! format: off\nimex = [\n [     \"Q\",   \"u[1]\", -3.79026877309730850230e-02,  3.77520487392776424307e-02, -1.40391503429245286812e-06,  5.33572816364154614566e-03 ],\n [     \"Q\",   \"u[2]\", -7.13128520899746973227e-03,  6.54348134460207026680e-03, -5.44654209456798120995e-06,  9.83670484102493409076e-04 ],\n [     \"Q\",      \"η\", -5.75156897888350494147e-03,  5.06823909948787842961e-03, -1.61345029906403004787e-05,  1.58599716799621352596e-03 ],\n [     \"Q\",      \"θ\",  1.99999999999999928946e+01,  2.00000000000002948752e+01,  2.00000000000000426326e+01,  6.30374859517183302003e-14 ],\n [ \"s_aux\",      \"y\",  0.00000000000000000000e+00,  1.00000000000000011642e+06,  5.00000000000000000000e+05,  2.92779390974978974555e+05 ],\n [ \"s_aux\",      \"w\", -1.93125254703910637023e-05,  1.89413928194037212366e-05,  6.96287201311761918867e-08,  1.82962329428446588302e-06 ],\n [ \"s_aux\",   \"pkin\", -1.60000000000000408562e+00,  0.00000000000000000000e+00, -8.00000000000002042810e-01,  4.68447025559967145103e-01 ],\n [ \"s_aux\",    \"wz0\", -1.78460606500689411704e-06,  1.42096532423882541587e-06, -3.15575167929213483841e-09,  7.00511144160156445126e-07 ],\n [ \"s_aux\",  \"uᵈ[1]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n [ \"s_aux\",  \"uᵈ[2]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n [ \"s_aux\", \"ΔGᵘ[1]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n [ \"s_aux\", \"ΔGᵘ[2]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n]\n\nexplicit_cpu = [\n [     \"Q\",   \"u[1]\", -3.74270752639261211625e-02,  3.72763215301363109999e-02, -1.40392694287316997219e-06,  5.29521849931491837837e-03 ],\n [     \"Q\",   \"u[2]\", -7.13776376792300999707e-03,  6.54949226335132476257e-03, -5.45004642311143043000e-06,  9.84283148803805074678e-04 ],\n [     \"Q\",      \"η\", -5.75146380759523553894e-03,  5.06819905742867966164e-03, -1.61399463692112299070e-05,  1.58594255118538803723e-03 ],\n [     \"Q\",      \"θ\",  1.99999999999996518341e+01,  2.00000000000013891110e+01,  2.00000000000004192202e+01,  2.61373866244148208458e-13 ],\n [ \"s_aux\",      \"y\",  0.00000000000000000000e+00,  1.00000000000000011642e+06,  5.00000000000000000000e+05,  2.92779390974978974555e+05 ],\n [ \"s_aux\",      \"w\", -1.91903846873268650749e-05,  1.88201368003043060118e-05,  6.88331165208082022114e-08,  1.81657738735220659856e-06 ],\n [ \"s_aux\",   \"pkin\", -1.60000000000003339551e+00,  0.00000000000000000000e+00, -8.00000000000017919000e-01,  4.68447025559975749331e-01 ],\n [ \"s_aux\",    \"wz0\", -1.79359066322475932405e-06,  1.42978124248321576704e-06, -3.01455602004428728418e-09,  6.97246485535118604352e-07 ],\n [ \"s_aux\",  \"uᵈ[1]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n [ \"s_aux\",  \"uᵈ[2]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n [ \"s_aux\", \"ΔGᵘ[1]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n [ \"s_aux\", \"ΔGᵘ[2]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n]\n\nexplicit_gpu = deepcopy(explicit_cpu)\nexplicit_gpu[3][5] = -1.61399463694379806270e-05 # CPU value: -1.61399463692112299070e-05 -> Δ =  2.267507199799762e-16\nexplicit_gpu[6][5] =  6.88331165196929377526e-08 # CPU value:  6.88331165208082022114e-08 -> Δ = -1.1152644588480957e-18\n\nlong = [\n [     \"Q\",   \"u[1]\", -1.26047572708648997208e-01,  8.73949752192989676169e-02,  4.77293205335180186562e-05,  7.01620954203814161526e-03 ],\n [     \"Q\",   \"u[2]\", -7.74039642996040555545e-02,  1.17079049312627345159e-01, -1.81789027475552530900e-04,  1.05959299008928087282e-02 ],\n [     \"Q\",      \"η\", -7.88344390091704622092e-02,  3.98295576513588017731e-02, -9.41579560194082235179e-05,  2.80408708312616314351e-02 ],\n [     \"Q\",      \"θ\",  1.99999999999542694695e+01,  2.00000000000501145792e+01,  2.00000000000023661073e+01,  1.20202839114602287074e-12 ],\n [ \"s_aux\",      \"y\",  0.00000000000000000000e+00,  4.00000000000000046566e+06,  2.00000000000000000000e+06,  1.15573129229947482236e+06 ],\n [ \"s_aux\",      \"w\", -2.39010107630067514077e-05,  6.21800814928817288142e-05,  2.57411032722755278831e-07,  4.26276984128505141513e-06 ],\n [ \"s_aux\",   \"pkin\", -1.60000000000220565788e+00,  0.00000000000000000000e+00, -8.00000000000101851860e-01,  4.61946285916540189120e-01 ],\n [ \"s_aux\",    \"wz0\", -2.51713525692209742640e-06,  9.22709404863107399641e-07, -8.84750036743458214697e-10,  5.52781925119928323386e-07 ],\n [ \"s_aux\",  \"uᵈ[1]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n [ \"s_aux\",  \"uᵈ[2]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n [ \"s_aux\", \"ΔGᵘ[1]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n [ \"s_aux\", \"ΔGᵘ[2]\",  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00,  0.00000000000000000000e+00 ],\n]\n\n#! format: on\n\nrefVals = (\n    explicit_cpu = (explicit_cpu, parr),\n    explicit_gpu = (explicit_gpu, parr),\n    imex = (imex, parr),\n    long = (long, parr),\n)\n"
  },
  {
    "path": "test/Ocean/runtests.jl",
    "content": "using MPI, Test\n\ninclude(\"../testhelpers.jl\")\n\n@testset \"Ocean\" begin\n    runmpi(joinpath(@__DIR__, \"HydrostaticBoussinesq/test_ocean_gyre_short.jl\"))\n    runmpi(joinpath(@__DIR__, \"SplitExplicit/test_spindown_short.jl\"))\n    include(joinpath(\"OceanProblems\", \"test_initial_value_problem.jl\"))\n    include(joinpath(\n        \"HydrostaticBoussinesqModel\",\n        \"test_hydrostatic_boussinesq_model.jl\",\n    ))\nend\n"
  },
  {
    "path": "test/Utilities/SingleStackUtils/runtests.jl",
    "content": "module TestSingleStackUtils\n\nusing MPI, Test\ninclude(joinpath(\"..\", \"..\", \"testhelpers.jl\"))\n\n@testset \"SingleStackUtils\" begin\n    runmpi(joinpath(@__DIR__, \"ssu_tests.jl\"))\nend\n\nend\n"
  },
  {
    "path": "test/Utilities/SingleStackUtils/ssu_tests.jl",
    "content": "using OrderedCollections\nusing StaticArrays\nusing Test\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing ClimateMachine\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.Grids\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.DGMethods: LocalGeometry\n\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Auxiliary, Prognostic, Gradient, GradientFlux\nimport ClimateMachine.BalanceLaws:\n    vars_state, nodal_init_state_auxiliary!, init_state_prognostic!\n\nstruct EmptyBalLaw{FT, PS} <: BalanceLaw\n    \"Parameters\"\n    param_set::PS\n    \"Domain height\"\n    zmax::FT\n\nend\nEmptyBalLaw(param_set, zmax) =\n    EmptyBalLaw{typeof(zmax), typeof(param_set)}(param_set, zmax)\n\nvars_state(::EmptyBalLaw, ::Auxiliary, FT) = @vars(x::FT, y::FT, z::FT)\nvars_state(::EmptyBalLaw, ::Prognostic, FT) = @vars(ρ::FT)\nvars_state(::EmptyBalLaw, ::Gradient, FT) = @vars()\nvars_state(::EmptyBalLaw, ::GradientFlux, FT) = @vars()\n\nfunction nodal_init_state_auxiliary!(\n    m::EmptyBalLaw,\n    aux::Vars,\n    tmp::Vars,\n    geom::LocalGeometry,\n)\n    aux.x = geom.coord[1]\n    aux.y = geom.coord[2]\n    aux.z = geom.coord[3]\nend\n\nfunction init_state_prognostic!(\n    m::EmptyBalLaw,\n    state::Vars,\n    aux::Vars,\n    coords,\n    t::Real,\n)\n    z = aux.z\n    x = aux.x\n    y = aux.y\n    state.ρ = (1 - 4 * (z - m.zmax / 2)^2) * (2 - x - y)\nend\n\nfunction test_hmean(\n    grid::DiscontinuousSpectralElementGrid{T, dim, Ns},\n    Q::MPIStateArray,\n    vars,\n) where {T, dim, Ns}\n    state_vars_avg = get_horizontal_mean(grid, Q, vars)\n    target = target_meanprof(grid)\n    @test state_vars_avg[\"ρ\"] ≈ target\nend\n\nfunction test_hvar(\n    grid::DiscontinuousSpectralElementGrid{T, dim, Ns},\n    Q::MPIStateArray,\n    vars,\n) where {T, dim, Ns}\n    state_vars_var = get_horizontal_variance(grid, Q, vars)\n    target = target_varprof(grid)\n    @test state_vars_var[\"ρ\"] ≈ target\nend\n\nfunction test_horizontally_ave(\n    grid::DiscontinuousSpectralElementGrid{T, dim, Ns},\n    Q_in::MPIStateArray,\n    vars,\n) where {T, dim, Ns}\n    Q = deepcopy(Q_in)\n    state_vars_var = get_horizontal_variance(grid, Q, vars)\n    i_vars = varsindex(vars, :ρ)\n    horizontally_average!(grid, Q, i_vars)\n    FT = eltype(Q)\n    state_vars_var = get_horizontal_variance(grid, Q, vars)\n    @test all(isapprox.(state_vars_var[\"ρ\"], 0, atol = 10 * eps(FT)))\nend\n\nfunction target_meanprof(\n    grid::DiscontinuousSpectralElementGrid{T, dim, Ns},\n) where {T, dim, Ns}\n    Nqs = Ns .+ 1\n    Nq_v = Nqs[end]\n    Ntot = Nq_v * grid.topology.stacksize\n    z = Array(get_z(grid))\n    target =\n        SVector{Ntot, T}([1.0 - 4.0 * (z_i - z[Ntot] / 2.0)^2 for z_i in z])\n    return target\nend\n\nfunction target_varprof(\n    grid::DiscontinuousSpectralElementGrid{T, dim, Ns},\n) where {T, dim, Ns}\n    Nqs = Ns .+ 1\n    Nq_v = Nqs[end]\n    nvertelem = grid.topology.stacksize\n    Ntot = Nq_v * nvertelem\n    z = Array(get_z(grid))\n    x = z[1:Nq_v] * nvertelem\n    scaled_var = 0.0\n    for i in 1:Nq_v\n        for j in 1:Nq_v\n            scaled_var = scaled_var + (2 - x[i] - x[j]) * (2 - x[i] - x[j])\n        end\n    end\n    target = SVector{Ntot, Float64}([\n        (1.0 - 4.0 * (z_i - z[Ntot] / 2.0)^2) *\n        (1.0 - 4.0 * (z_i - z[Ntot] / 2.0)^2) *\n        (scaled_var / Nq_v / Nq_v - 1) for z_i in z\n    ])\n    return target\nend\n\nfunction main()\n    FT = Float64\n    ClimateMachine.init()\n\n    m = EmptyBalLaw(param_set, FT(1))\n\n    # Prescribe polynomial order of basis functions in finite elements\n    N_poly = 5\n    # Specify the number of vertical elements\n    nelem_vert = 20\n    # Specify the domain height\n    zmax = m.zmax\n    # Initial and final times\n    t0 = 0.0\n    timeend = 1.0\n    dt = 0.1\n    # Establish a `ClimateMachine` single stack configuration\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"SingleStackUtilsTest\",\n        N_poly,\n        nelem_vert,\n        zmax,\n        param_set,\n        m,\n    )\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n\n    # tests\n    test_hmean(\n        driver_config.grid,\n        solver_config.Q,\n        vars_state(m, Prognostic(), FT),\n    )\n    test_hvar(\n        driver_config.grid,\n        solver_config.Q,\n        vars_state(m, Prognostic(), FT),\n    )\n\n    r1, z1 = reduce_nodal_stack(\n        max,\n        solver_config.dg.grid,\n        solver_config.Q,\n        vars_state(m, Prognostic(), FT),\n        \"ρ\",\n        i = 6,\n        j = 6,\n    )\n    @test r1 ≈ 8.880558532968455e-16 && z1 == 10\n    r2, z2 = reduce_nodal_stack(\n        +,\n        solver_config.dg.grid,\n        solver_config.Q,\n        vars_state(m, Prognostic(), FT),\n        \"ρ\",\n        i = 3,\n        j = 3,\n    )\n    @test r2 ≈ 102.73283921735293 && z2 == 20\n    ns = reduce_element_stack(\n        +,\n        solver_config.dg.grid,\n        solver_config.Q,\n        vars_state(m, Prognostic(), FT),\n        \"ρ\",\n    )\n    (r3, z3) = let\n        f(a, b) = (a[1] + b[1], b[2])\n        reduce(f, ns)\n    end\n    @test r3 ≈ FT(2877.6) && z3 == 20\n\n    test_horizontally_ave(\n        driver_config.grid,\n        solver_config.Q,\n        vars_state(m, Prognostic(), FT),\n    )\n\n    # Test NodalStack iterator (without interpolation)\n    nodal_stack = NodalStack(solver_config; interp = false)\n    ρ_iter = [local_states.prog.ρ for local_states in nodal_stack]\n    dons = get_vars_from_nodal_stack(\n        solver_config.dg.grid,\n        solver_config.Q,\n        vars_state(m, Prognostic(), FT);\n        interp = false,\n    )\n    ρ_vfns = dons[\"ρ\"]\n    @test all(ρ_iter .≈ ρ_vfns)\n\n    # Test NodalStack iterator (with interpolation)\n    nodal_stack = NodalStack(solver_config; interp = true)\n    ρ_iter = [local_states.prog.ρ for local_states in nodal_stack]\n    dons = get_vars_from_nodal_stack(\n        solver_config.dg.grid,\n        solver_config.Q,\n        vars_state(m, Prognostic(), FT);\n        interp = true,\n    )\n    ρ_vfns_interp = dons[\"ρ\"]\n    @test all(ρ_iter .≈ ρ_vfns_interp)\n    return nothing\nend\n\n@testset \"Single Stack Utils\" begin\n    main()\nend\n"
  },
  {
    "path": "test/Utilities/TicToc/runtests.jl",
    "content": "module TestTicToc\n\nusing Test\nusing ClimateMachine.TicToc\n\nfunction foo()\n    @tic foo\n    sleep(0.25)\n    @toc foo\nend\n\nfunction bar()\n    @tic bar\n    sleep(1)\n    @toc bar\nend\n\n\nif TicToc.tictoc_enabled\n    @testset \"TicToc\" begin\n        @test tictoc() >= 2\n        foo_i = findfirst(s -> s == :tictoc__foo, TicToc.timing_info_names)\n        bar_i = findfirst(s -> s == :tictoc__bar, TicToc.timing_info_names)\n        @test foo_i != nothing\n        @test bar_i != nothing\n        foo()\n        foo()\n        @test TicToc.timing_infos[foo_i].ncalls == 2\n        @test TicToc.timing_infos[bar_i].ncalls == 0\n        @test TicToc.timing_infos[foo_i].time >= 5e8\n        bar()\n        @test TicToc.timing_infos[bar_i].ncalls == 1\n        @test TicToc.timing_infos[bar_i].time >= 1e9\n        buf = IOBuffer()\n        old_stdout = stdout\n        try\n            rd, = redirect_stdout()\n            TicToc.print_timing_info()\n            Libc.flush_cstdio()\n            flush(stdout)\n            write(buf, readavailable(rd))\n        finally\n            redirect_stdout(old_stdout)\n        end\n        str = String(take!(buf))\n        @test findnext(\"tictoc__foo\", str, 1) != nothing\n        @test findnext(\"tictoc__bar\", str, 1) != nothing\n    end\nend\n\nend # module TestTicToc\n"
  },
  {
    "path": "test/Utilities/VariableTemplates/complex_models.jl",
    "content": "using Test\nusing StaticArrays\n\nabstract type AbstractModel end\nabstract type OneLayerModel <: AbstractModel end\n\nstruct EmptyModel <: OneLayerModel end\nstate(m::EmptyModel, T) = @vars()\n\nstruct ScalarModel <: OneLayerModel end\nstate(m::ScalarModel, T) = @vars(x::T)\n\nstruct VectorModel{N} <: OneLayerModel end\nstate(m::VectorModel{N}, T) where {N} = @vars(x::SVector{N, T})\n\nstruct MatrixModel{N, M} <: OneLayerModel end\nstate(m::MatrixModel{N, M}, T) where {N, M} =\n    @vars(x::SHermitianCompact{N, T, M})\n\nabstract type TwoLayerModel <: AbstractModel end\n\nstruct CompositModel{EM, SM, VM, MM} <: TwoLayerModel\n    empty_model::EM\n    scalar_model::SM\n    vector_model::VM\n    matrix_model::MM\nend\nfunction CompositModel(\n    Nv,\n    N,\n    M;\n    empty_model = EmptyModel(),\n    scalar_model = ScalarModel(),\n    vector_model = VectorModel{Nv}(),\n    matrix_model = MatrixModel{N, M}(),\n)\n    args = (empty_model, scalar_model, vector_model, matrix_model)\n    return CompositModel{typeof.(args)...}(args...)\nend\n\n\nfunction state(m::CompositModel, T)\n    @vars begin\n        empty_model::state(m.empty_model, T)\n        scalar_model::state(m.scalar_model, T)\n        vector_model::state(m.vector_model, T)\n        matrix_model::state(m.matrix_model, T)\n    end\nend\n\nBase.@kwdef struct NTupleModel{S, V, Nv} <: OneLayerModel\n    scalar_model::S = ScalarModel()\n    vector_model::V = VectorModel{Nv}()\nend\n\nfunction NTupleModel(\n    Nv;\n    scalar_model::S = ScalarModel(),\n    vector_model::V = VectorModel{Nv}(),\n) where {S, V}\n    return NTupleModel{S, V, Nv}(scalar_model, vector_model)\nend\n\nfunction state(m::NTupleModel, T)\n    @vars begin\n        scalar_model::state(m.scalar_model, T)\n        vector_model::state(m.vector_model, T)\n    end\nend\n\nstate(m::NTuple{N, NTupleModel}, FT) where {N} =\n    Tuple{ntuple(i -> state(m[i], FT), N)...}\n\nstruct NTupleContainingModel{N, NTM, VM, SM} <: TwoLayerModel\n    ntuple_model::NTM\n    vector_model::VM\n    scalar_model::SM\nend\nfunction NTupleContainingModel(\n    N,\n    Nv;\n    ntuple_model = ntuple(i -> NTupleModel(Nv), N),\n    vector_model = VectorModel{Nv}(),\n    scalar_model = ScalarModel(),\n)\n    args = (ntuple_model, vector_model, scalar_model)\n    return NTupleContainingModel{N, typeof.(args)...}(args...)\nend\n\nfunction state(m::NTupleContainingModel, T)\n    @vars begin\n        ntuple_model::state(m.ntuple_model, T)\n        vector_model::state(m.vector_model, T)\n        scalar_model::state(m.scalar_model, T)\n    end\nend\n"
  },
  {
    "path": "test/Utilities/VariableTemplates/runtests.jl",
    "content": "module TestVariableTemplates\n\nusing Test\nusing StaticArrays\nusing ClimateMachine.VariableTemplates\n\n@testset \"VariableTemplates\" begin\n    include(\"test_base_functionality.jl\")\n    include(\"varsindex.jl\")\n    include(\"test_complex_models.jl\")\nend\n\nend\n"
  },
  {
    "path": "test/Utilities/VariableTemplates/runtests_gpu.jl",
    "content": "module TestVariableTemplatesGPU\n\nusing Test\nusing StaticArrays\nusing ClimateMachine.VariableTemplates\n\n@testset \"VariableTemplates - GPU\" begin\n    include(\"test_complex_models_gpu.jl\")\nend\n\nend\n"
  },
  {
    "path": "test/Utilities/VariableTemplates/test_base_functionality.jl",
    "content": "using Test\nusing StaticArrays\nusing ClimateMachine.VariableTemplates\n\nstruct TestModel{A, B, C}\n    a::A\n    b::B\n    c::C\nend\n\nstruct SubModelA end\nstruct SubModelB end\nstruct SubModelC{N} end\n\nfunction state(m::TestModel, T)\n    @vars begin\n        ρ::T\n        ρu::SVector{3, T}\n        ρe::T\n        a::state(m.a, T)\n        b::state(m.b, T)\n        c::state(m.c, T)\n        S::SHermitianCompact{3, T, 6}\n    end\nend\n\nstate(m::SubModelA, T) = @vars()\nstate(m::SubModelB, T) = @vars(ρqt::T)\nstate(m::SubModelC{N}, T) where {N} = @vars(ρk::SVector{N, T})\n\nmodel = TestModel(SubModelA(), SubModelB(), SubModelC{5}())\n\nst = state(model, Float64)\n\n@test varsize(st) == 17\n@test varsize(typeof(())) == 0\n\nv = Vars{st}(zeros(MVector{varsize(st), Float64}))\ng = Grad{st}(zeros(MMatrix{3, varsize(st), Float64}))\n\n@test v.ρ === 0.0\n@test v.ρu === SVector(0.0, 0.0, 0.0)\nv.ρu = SVector(1, 2, 3)\n@test v.ρu === SVector(1.0, 2.0, 3.0)\n\n@test v.b.ρqt === 0.0\nv.b.ρqt = 12.0\n@test v.b.ρqt === 12.0\n\n@test v.S === zeros(SHermitianCompact{3, Float64, 6})\nv.S = SHermitianCompact{3, Float64, 6}(1, 2, 3, 2, 3, 4, 3, 4, 5)\n@test v.S[1, 1] === 1.0\n@test v.S[1, 3] === 3.0\n@test v.S[3, 1] === 3.0\n@test v.S[3, 3] === 5.0\n\nv.S = ones(SMatrix{3, 3, Int64})\n@test v.S[1, 1] === 1.0\n@test v.S[1, 3] === 1.0\n@test v.S[3, 1] === 1.0\n@test v.S[3, 3] === 1.0\n\n@test propertynames(v.a) == ()\n@test propertynames(g.a) == ()\n\n@test g.ρu == zeros(SMatrix{3, 3, Float64})\ng.ρu = SMatrix{3, 3}(1:9)\n@test g.ρu == SMatrix{3, 3, Float64}(1:9)\n\n@test size(v.c.ρk) == (5,)\n@test size(g.c.ρk) == (3, 5)\n\nsv = similar(v)\n@test typeof(sv) == typeof(v)\n@test size(parent(sv)) == size(parent(v))\n\nsg = similar(g)\n@test typeof(sg) == typeof(g)\n@test size(parent(sg)) == size(parent(g))\n\n@test flattenednames(st) == [\n    \"ρ\",\n    \"ρu[1]\",\n    \"ρu[2]\",\n    \"ρu[3]\",\n    \"ρe\",\n    \"b.ρqt\",\n    \"c.ρk[1]\",\n    \"c.ρk[2]\",\n    \"c.ρk[3]\",\n    \"c.ρk[4]\",\n    \"c.ρk[5]\",\n    \"S[1,1]\",\n    \"S[2,1]\",\n    \"S[3,1]\",\n    \"S[2,2]\",\n    \"S[3,2]\",\n    \"S[3,3]\",\n]\n"
  },
  {
    "path": "test/Utilities/VariableTemplates/test_complex_models.jl",
    "content": "using Test\nusing StaticArrays\nusing Printf\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.VariableTemplates: wrap_val\nimport ClimateMachine.VariableTemplates\nVT = VariableTemplates\n\n@testset \"Test complex models\" begin\n    include(\"complex_models.jl\")\n\n    FT = Float32\n\n    # test getproperty\n    m = ScalarModel()\n    st = state(m, FT)\n    vs = varsize(st)\n    a_global = collect(1:vs)\n    v = Vars{st}(a_global)\n    @test v.x == FT(1)\n\n    Nv = 4\n    m = VectorModel{Nv}()\n    st = state(m, FT)\n    vs = varsize(st)\n    a_global = collect(1:vs)\n    v = Vars{st}(a_global)\n    @test v.x == SVector{Nv, FT}(1:Nv)\n\n    N = 3\n    M = 6\n    m = MatrixModel{N, M}()\n    st = state(m, FT)\n    vs = varsize(st)\n    a_global = collect(1:vs)\n    v = Vars{st}(a_global)\n    @test v.x == SHermitianCompact{N, FT, M}(collect(1:(1 + M - 1)))\n\n    Nv = 3\n    N = 3\n    M = 6\n    m = CompositModel(Nv, N, M)\n    st = state(m, FT)\n    vs = varsize(st)\n    a_global = collect(1:vs)\n    v = Vars{st}(a_global)\n\n    scalar_model = v.scalar_model\n    @test v.scalar_model.x == FT(1)\n\n    vector_model = v.vector_model\n    @test v.vector_model.x == SVector{Nv, FT}([2, 3, 4])\n\n    matrix_model = v.matrix_model\n    @test v.matrix_model.x ==\n          SHermitianCompact{N, FT, M}(collect(5:(5 + M - 1)))\n\n    Nv = 3\n    N = 5\n    m = NTupleContainingModel(N, Nv)\n    st = state(m, FT)\n    vs = varsize(st)\n    a_global = collect(1:vs)\n    v = Vars{st}(a_global)\n\n    offset = (Nv + 1) * N\n    @test v.vector_model.x == SVector{Nv, FT}(collect(1:Nv) .+ offset)\n    @test v.scalar_model.x == FT(1 + Nv) + offset\n\n    # Make sure we bounds error for NTupleModel's:\n    @test_throws BoundsError m.ntuple_model[0]\n    @test_throws BoundsError m.ntuple_model[N + 1]\n\n    unval(::Val{i}) where {i} = i\n    @unroll_map(N) do i\n        @test m.ntuple_model[i] isa NTupleModel\n        @test m.ntuple_model[i].scalar_model isa ScalarModel\n        @test v.ntuple_model[i].scalar_model.x ==\n              FT(unval(i)) + (Nv) * (unval(i) - 1)\n        @test v.vector_model.x == SVector{Nv, FT}(1:Nv) .+ offset\n        @test v.scalar_model.x == FT(Nv + 1) + offset\n    end\n\n    @test vuntuple(x -> x, 5) == ntuple(i -> Val(i), Val(5))\n\n    # test flattenednames\n    fn = flattenednames(st)\n    j = 1\n    for i in 1:N\n        @test fn[j] === \"ntuple_model[$i].scalar_model.x\"\n        j += 1\n        for k in 1:Nv\n            @test fn[j] === \"ntuple_model[$i].vector_model.x[$k]\"\n            j += 1\n        end\n    end\n    for k in 1:Nv\n        @test fn[j] === \"vector_model.x[$k]\"\n        j += 1\n    end\n    @test fn[j] === \"scalar_model.x\"\n\n    # flattened_tup_chain - empty/generic cases\n    struct Foo end\n    @test flattened_tup_chain(NamedTuple{(), Tuple{}}) == ()\n    @test flattened_tup_chain(Foo, RetainArr()) == ((Symbol(),),)\n    @test flattened_tup_chain(Foo, FlattenArr()) == ((Symbol(),),)\n\n    # flattened_tup_chain - SHermitianCompact\n    Nv, M = 3, 6\n    A = SHermitianCompact{Nv, FT, M}(collect(1:(1 + M - 1)))\n\n    ftc = flattened_tup_chain(typeof(A), FlattenArr())\n    @test ftc == ntuple(i -> (Symbol(), i), M)\n\n    ftc = flattened_tup_chain(typeof(A), RetainArr())\n    @test ftc == ((Symbol(),),)\n\n    # flattened_tup_chain - Retain arrays\n\n    ftc = flattened_tup_chain(st, RetainArr())\n    j = 1\n    for i in 1:N\n        @test ftc[j] === (:ntuple_model, i, :scalar_model, :x)\n        j += 1\n        @test ftc[j] === (:ntuple_model, i, :vector_model, :x)\n        j += 1\n    end\n    @test ftc[j] === (:vector_model, :x)\n    j += 1\n    @test ftc[j] === (:scalar_model, :x)\n\n    # test varsindex\n    ntuple(N) do i\n        i_val = Val(i)\n        i_sm = varsindex(st, :ntuple_model, i_val, :scalar_model, :x)\n        i_vm = varsindex(st, :ntuple_model, i_val, :vector_model, :x)\n        nt_offset = (Nv + 1) - 1\n\n        i_sm_correct = (i + nt_offset * (i - 1)):(i + nt_offset * (i - 1))\n        @test i_sm == i_sm_correct\n\n        offset = 1\n        i_start = i + nt_offset * (i - 1) + offset\n        i_vm_correct = (i_start):(i_start + Nv - 1)\n        @test i_vm == i_vm_correct\n    end\n\n    # test that getproperty matches varsindex\n    ntuple(N) do i\n        i_ϕ = varsindex(st, wrap_val.(ftc[i])...)\n        ϕ = getproperty(v, wrap_val.(ftc[i]))\n        @test all(parent(v)[i_ϕ] .≈ ϕ)\n    end\n\n    # test getproperty with tup-chain\n    @unroll_map(N) do i\n        @test v.scalar_model.x == getproperty(v, (:scalar_model, :x))\n        @test v.vector_model.x == getproperty(v, (:vector_model, :x))\n        @test v.ntuple_model[i] == getproperty(v, (:ntuple_model, i))\n        @test v.ntuple_model[i].scalar_model ==\n              getproperty(v, (:ntuple_model, i, :scalar_model))\n        @test v.ntuple_model[i].scalar_model.x ==\n              getproperty(v, (:ntuple_model, i, :scalar_model, :x))\n    end\n\n    # Test converting to flattened NamedTuple\n    fnt = flattened_named_tuple(v, RetainArr())\n    @test fnt.ntuple_model_1_scalar_model_x == 1.0f0\n    @test fnt.ntuple_model_1_vector_model_x == Float32[2.0, 3.0, 4.0]\n    @test fnt.ntuple_model_2_scalar_model_x == 5.0f0\n    @test fnt.ntuple_model_2_vector_model_x == Float32[6.0, 7.0, 8.0]\n    @test fnt.ntuple_model_3_scalar_model_x == 9.0f0\n    @test fnt.ntuple_model_3_vector_model_x == Float32[10.0, 11.0, 12.0]\n    @test fnt.ntuple_model_4_scalar_model_x == 13.0f0\n    @test fnt.ntuple_model_4_vector_model_x == Float32[14.0, 15.0, 16.0]\n    @test fnt.ntuple_model_5_scalar_model_x == 17.0f0\n    @test fnt.ntuple_model_5_vector_model_x == Float32[18.0, 19.0, 20.0]\n    @test fnt.vector_model_x == Float32[21.0, 22.0, 23.0]\n    @test fnt.scalar_model_x == 24.0f0\n\n    # flattened_tup_chain - Flatten arrays\n\n    ftc = flattened_tup_chain(st, FlattenArr())\n    j = 1\n    for i in 1:N\n        @test ftc[j] === (:ntuple_model, i, :scalar_model, :x)\n        j += 1\n        for k in 1:Nv\n            @test ftc[j] === (:ntuple_model, i, :vector_model, :x, k)\n            j += 1\n        end\n    end\n    for i in 1:Nv\n        @test ftc[j] === (:vector_model, :x, i)\n        j += 1\n    end\n    @test ftc[j] === (:scalar_model, :x)\n\n    # test varsindex (flatten arrays)\n    ntuple(N) do i\n        i_val = Val(i)\n        i_sm = varsindex(st, :ntuple_model, i_val, :scalar_model, :x)\n        nt_offset = (Nv + 1) - 1\n\n        i_sm_correct = (i + nt_offset * (i - 1)):(i + nt_offset * (i - 1))\n        @test i_sm == i_sm_correct\n\n        for j in 1:Nv\n            i_vm =\n                varsindex(st, :ntuple_model, i_val, :vector_model, :x, Val(j))\n            offset = 1\n            i_start = i + nt_offset * (i - 1) + offset\n            i_vm_correct = i_start + j - 1\n            @test i_vm == i_vm_correct:i_vm_correct\n        end\n    end\n\n    # test that getproperty matches varsindex\n    ntuple(N) do i\n        i_ϕ = varsindex(st, wrap_val.(ftc[i])...)\n        ϕ = getproperty(v, wrap_val.(ftc[i]))\n        @test all(parent(v)[i_ϕ] .≈ ϕ)\n    end\n\n    # test getproperty with tup-chain\n    for k in 1:Nv\n        @test v.vector_model.x[k] == getproperty(v, (:vector_model, :x, Val(k)))\n    end\n\n    # test getindex with Val\n    @test getindex((1, 2), Val(1)) == 1\n    @test getindex((1, 2), Val(2)) == 2\n    @test getindex(SVector(1, 2), Val(1)) == 1\n    @test getindex(SVector(1, 2), Val(2)) == 2\n\n    nt = (; a = ((; x = 1), (; x = 2)))\n    fnt = VT.flattened_tuple(FlattenArr(), nt)\n    vg = Grad{typeof(nt)}(zeros(MMatrix{3, length(fnt), FT}))\n    parent(vg)[1, :] .= fnt\n    parent(vg)[2, :] .= fnt\n    parent(vg)[3, :] .= fnt\n    for i in 1:2\n        @test getindex(vg.a, Val(i)).x[1] == i\n        @test getindex(vg.a, Val(i)).x[2] == i\n        @test getindex(vg.a, Val(i)).x[3] == i\n    end\n\n    # getpropertyorindex\n    @test VT.getpropertyorindex((1, 2), Val(1)) == 1\n    @test VT.getpropertyorindex((1, 2), Val(2)) == 2\n    @test VT.getpropertyorindex([1, 2], Val(1)) == 1\n    @test VT.getpropertyorindex([1, 2], Val(2)) == 2\n    @test VT.getpropertyorindex(v, :scalar_model) == v.scalar_model\n    for i in 1:N\n        @test VT.getpropertyorindex(v.ntuple_model, Val(i)) ==\n              v.ntuple_model[Val(i)]\n        @test VT.getpropertyorindex(v.ntuple_model, (Val(i),)) ==\n              v.ntuple_model[Val(i)]\n        @test getindex(v.ntuple_model, (Val(i),)) ==\n              VT.getpropertyorindex(v.ntuple_model, (Val(i),))\n    end\n\n    # Test converting to flattened NamedTuple\n    fnt = flattened_named_tuple(v, FlattenArr())\n    @test fnt.ntuple_model_1_scalar_model_x == 1.0f0\n    @test fnt.ntuple_model_1_vector_model_x_1 == 2.0\n    @test fnt.ntuple_model_1_vector_model_x_2 == 3.0\n    @test fnt.ntuple_model_1_vector_model_x_3 == 4.0\n    @test fnt.ntuple_model_2_scalar_model_x == 5.0f0\n    @test fnt.ntuple_model_2_vector_model_x_1 == 6.0\n    @test fnt.ntuple_model_2_vector_model_x_2 == 7.0\n    @test fnt.ntuple_model_2_vector_model_x_3 == 8.0\n    @test fnt.ntuple_model_3_scalar_model_x == 9.0f0\n    @test fnt.ntuple_model_3_vector_model_x_1 == 10.0\n    @test fnt.ntuple_model_3_vector_model_x_2 == 11.0\n    @test fnt.ntuple_model_3_vector_model_x_3 == 12.0\n    @test fnt.ntuple_model_4_scalar_model_x == 13.0f0\n    @test fnt.ntuple_model_4_vector_model_x_1 == 14.0\n    @test fnt.ntuple_model_4_vector_model_x_2 == 15.0\n    @test fnt.ntuple_model_4_vector_model_x_3 == 16.0\n    @test fnt.ntuple_model_5_scalar_model_x == 17.0f0\n    @test fnt.ntuple_model_5_vector_model_x_1 == 18.0\n    @test fnt.ntuple_model_5_vector_model_x_2 == 19.0\n    @test fnt.ntuple_model_5_vector_model_x_3 == 20.0\n    @test fnt.vector_model_x_1 == 21.0\n    @test fnt.vector_model_x_2 == 22.0\n    @test fnt.vector_model_x_3 == 23.0\n    @test fnt.scalar_model_x == 24.0f0\n\n    struct Foo end\n    nt = (;\n        nest = (;\n            v = SVector(1, 2, 3),\n            nt = (;\n                shc = SHermitianCompact{3, FT, 6}(collect(1:6)),\n                f = FT(1.0),\n            ),\n            d = SDiagonal(collect(1:3)...),\n            tt = (Foo(), Foo()),\n            t = Foo(),\n        ),\n    )\n    # Test flattened_tuple:\n\n    @test VT.flattened_tuple(RetainArr(), NamedTuple()) == ()\n    @test VT.flattened_tuple(FlattenArr(), NamedTuple()) == ()\n    @test VT.flattened_tuple(RetainArr(), Tuple(NamedTuple())) == ()\n    @test VT.flattened_tuple(FlattenArr(), Tuple(NamedTuple())) == ()\n\n    ft = FlattenArr()\n    @test VT.flattened_tuple(ft, nt.nest.nt.f) == (1.0f0,)\n    @test VT.flattened_tuple(ft, nt.nest.nt) ==\n          (1.0f0, 2.0f0, 3.0f0, 4.0f0, 5.0f0, 6.0f0, 1.0f0)\n    @test VT.flattened_tuple(ft, nt.nest.d) == (1, 2, 3)\n    @test VT.flattened_tuple(ft, nt.nest.t) == (Foo(),)\n    @test VT.flattened_tuple(ft, nt.nest.tt) == (Foo(), Foo())\n\n    ft = RetainArr()\n    @test VT.flattened_tuple(ft, nt.nest.nt.f) == (1.0f0,)\n    @test VT.flattened_tuple(ft, nt.nest.nt)[1] == nt.nest.nt.shc.lowertriangle\n    @test VT.flattened_tuple(ft, nt.nest.nt)[2] == 1.0f0\n    @test VT.flattened_tuple(ft, nt.nest.d) == (nt.nest.d.diag,)\n    @test VT.flattened_tuple(ft, nt.nest.t) == (Foo(),)\n    @test VT.flattened_tuple(ft, nt.nest.tt) == (Foo(), Foo())\n\n    # Test flattened_named_tuple for NamedTuples\n    fnt = flattened_named_tuple(nt, FlattenArr())\n    @test fnt.nest_v_1 == 1\n    @test fnt.nest_v_2 == 2\n    @test fnt.nest_v_3 == 3\n    @test fnt.nest_nt_shc_1 == 1.0\n    @test fnt.nest_nt_shc_2 == 2.0\n    @test fnt.nest_nt_shc_3 == 3.0\n    @test fnt.nest_nt_shc_4 == 4.0\n    @test fnt.nest_nt_shc_5 == 5.0\n    @test fnt.nest_nt_shc_6 == 6.0\n    @test fnt.nest_nt_f == 1.0\n    @test fnt.nest_tt_1 == Foo()\n    @test fnt.nest_tt_2 == Foo()\n    @test fnt.nest_t == Foo()\n\n    fnt = flattened_named_tuple(nt, RetainArr())\n    @test fnt.nest_v == SVector(1, 2, 3)\n    @test fnt.nest_nt_shc == nt.nest.nt.shc.lowertriangle\n    @test fnt.nest_nt_f == 1.0\n    @test fnt.nest_tt_1 == Foo()\n    @test fnt.nest_tt_2 == Foo()\n    @test fnt.nest_t == Foo()\n\n    # Test that show doesn't break\n    sprint(show, v)\n    sprint(show, vg)\n\nend\n"
  },
  {
    "path": "test/Utilities/VariableTemplates/test_complex_models_gpu.jl",
    "content": "using Test\nusing StaticArrays\nusing ClimateMachine.VariableTemplates\nusing CUDA\nusing KernelAbstractions\nusing KernelAbstractions.Extras: @unroll\n\ninclude(\"complex_models.jl\")\n\nrun_gpu = CUDA.has_cuda_gpu()\nif run_gpu\n    CUDA.allowscalar(false)\nelse\n    CUDA.allowscalar(true)\nend\n\nget_device() = run_gpu ? CPU() : CUDADevice()\ndevice_array(a, ::CUDADevice) = CuArray(a)\ndevice_array(a, ::CPU) = Array(a)\ndevice_rand(::CUDADevice, args...) = CUDA.rand(args...)\ndevice_rand(::CPU, args...) = rand(args...)\nnumber_states(m) = varsize(state(m, Int))\n\n@kernel function mem_copy_kernel!(\n    m::AbstractModel,\n    dst::AbstractArray{FT, N},\n    src::AbstractArray{FT, N},\n) where {FT, N}\n    @uniform begin\n        ns = number_states(m)\n        vs = state(m, FT)\n        local_src = MArray{Tuple{ns}, FT}(undef)\n        local_dst = MArray{Tuple{ns}, FT}(undef)\n    end\n    i = @index(Group, Linear)\n    @inbounds begin\n        @unroll for s in 1:ns\n            local_src[s] = src[s, i]\n        end\n        mem_copy!(m, Vars{vs}(local_dst), Vars{vs}(local_src))\n        @unroll for s in 1:ns\n            dst[s, i] = local_dst[s]\n        end\n    end\nend\n\nfunction mem_copy!(m::ScalarModel, dst::Vars, src::Vars)\n    dst.x = src.x\nend\n\nfunction mem_copy!(m::NTupleContainingModel{N}, dst::Vars, src::Vars) where {N}\n    dst.vector_model.x = src.vector_model.x\n    dst.scalar_model.x = src.scalar_model.x\n\n    up = vuntuple(i -> src.ntuple_model[i].scalar_model.x, N)\n    up_v = vuntuple(i -> src.ntuple_model[i].vector_model.x, N)\n    up_sv = SVector(up...)\n\n    @unroll_map(N) do i\n        dst.ntuple_model[i].scalar_model.x = up[i]    # index into tuple\n        dst.ntuple_model[i].scalar_model.x = up_sv[i] # index into SArray\n        dst.ntuple_model[i].vector_model.x = up_v[i]\n    end\nend\n\n@testset \"ScalarModel\" begin\n    FT = Float32\n    device = get_device()\n    n_elem = 10\n    m = ScalarModel()\n    ns = number_states(m)\n    a_src = Array{FT}(undef, ns, n_elem)\n    a_dst = Array{FT}(undef, ns, n_elem)\n    d_src = device_array(a_src, device)\n    d_dst = device_array(a_dst, device)\n    d_src .= device_rand(device, FT, ns, n_elem)\n    fill!(d_dst, 0)\n\n    work_groups = (1,)\n    ndrange = (n_elem,)\n    kernel! = mem_copy_kernel!(device, work_groups)\n    event = kernel!(m, d_dst, d_src, ndrange = ndrange)\n    wait(device, event)\n\n    a_src = Array(d_src)\n    a_dst = Array(d_dst)\n    @test a_src == a_dst\nend\n\n@testset \"NTupleContainingModel\" begin\n    FT = Float32\n    device = get_device()\n    n_elem = 10\n    Nv = 4\n    N = 3\n    m = NTupleContainingModel(N, Nv)\n    ns = number_states(m)\n    a_src = Array{FT}(undef, ns, n_elem)\n    a_dst = Array{FT}(undef, ns, n_elem)\n    d_src = device_array(a_src, device)\n    d_dst = device_array(a_dst, device)\n    d_src .= device_rand(device, FT, ns, n_elem)\n    fill!(d_dst, 0)\n\n    work_groups = (1,)\n    ndrange = (n_elem,)\n    kernel! = mem_copy_kernel!(device, work_groups)\n    event = kernel!(m, d_dst, d_src, ndrange = ndrange)\n    wait(device, event)\n\n    a_src = Array(d_src)\n    a_dst = Array(d_dst)\n    @test a_src == a_dst\nend\n"
  },
  {
    "path": "test/Utilities/VariableTemplates/varsindex.jl",
    "content": "using Test, StaticArrays\nusing ClimateMachine.VariableTemplates: varsindex, @vars, varsindices\nusing StaticArrays\n\n@testset \"varsindex\" begin\n    struct TestMoistureModel{FT} end\n    struct TestAtmosModel{FT}\n        moisture::TestMoistureModel{FT}\n    end\n\n    function vars_state(::TestMoistureModel, FT)\n        @vars begin\n            ρq_tot::FT\n            ρq_x::SVector{5, FT}\n            ρq_liq::FT\n            ρq_vap::FT\n        end\n    end\n    function vars_state(m::TestAtmosModel, FT)\n        @vars begin\n            ρ::FT\n            ρu::SVector{3, FT}\n            ρe::FT\n            moisture::vars_state(m.moisture, FT)\n            S::SHermitianCompact{3, FT, 6}\n        end\n    end\n    FT = Float64\n    m = TestAtmosModel(TestMoistureModel{FT}())\n\n    @test 1:1 === varsindex(vars_state(m, FT), :ρ)\n    @test 2:4 === varsindex(vars_state(m, FT), :ρu)\n    @test 5:5 === varsindex(vars_state(m, FT), :ρe)\n\n    # Since moisture is defined recusively this will get all the fields\n    moist = varsindex(vars_state(m, FT), :moisture)\n    @test 6:13 === moist\n\n    # To get the specific ones we can do\n    @test 6:6 === varsindex(vars_state(m, FT), :moisture, :ρq_tot)\n    @test 7:11 === varsindex(vars_state(m, FT), :moisture, :ρq_x)\n    @test 12:12 === varsindex(vars_state(m, FT), :moisture, :ρq_liq)\n    @test 13:13 === varsindex(vars_state(m, FT), :moisture, :ρq_vap)\n    # or\n    @test 6:6 === moist[varsindex(vars_state(m.moisture, FT), :ρq_tot)]\n    @test 7:11 === moist[varsindex(vars_state(m.moisture, FT), :ρq_x)]\n    @test 12:12 === moist[varsindex(vars_state(m.moisture, FT), :ρq_liq)]\n    @test 13:13 === moist[varsindex(vars_state(m.moisture, FT), :ρq_vap)]\n\n    @test 14:19 == varsindex(vars_state(m, FT), :S)\n\n    @test (1,) === varsindices(vars_state(m, FT), :ρ)\n    @test (2, 3, 4) === varsindices(vars_state(m, FT), :ρu)\n    @test (1, 5) === varsindices(vars_state(m, FT), :ρ, :ρe)\n    @test (12,) === varsindices(vars_state(m, FT), :(moisture.ρq_liq))\n    let\n        vars = (\"ρe\", \"moisture.ρq_x\", \"moisture.ρq_vap\")\n        @test (5, 7, 8, 9, 10, 11, 13) === varsindices(vars_state(m, FT), vars)\n    end\nend\n"
  },
  {
    "path": "test/Utilities/runtests.jl",
    "content": "using Test, Pkg\n\n@testset \"Utilities\" begin\n    all_tests = isempty(ARGS) || \"all\" in ARGS ? true : false\n    for submodule in [\"TicToc\", \"VariableTemplates\", \"SingleStackUtils\"]\n        if all_tests ||\n           \"$submodule\" in ARGS ||\n           \"Utilities/$submodule\" in ARGS ||\n           \"Utilities\" in ARGS\n            include_test(submodule)\n        end\n    end\n\nend\n"
  },
  {
    "path": "test/runtests.jl",
    "content": "using Test, Pkg\n\nENV[\"DATADEPS_ALWAYS_ACCEPT\"] = true\nENV[\"JULIA_LOG_LEVEL\"] = \"WARN\"\n\nfunction include_test(_module)\n    println(\"Starting tests for $_module\")\n    t = @elapsed include(joinpath(_module, \"runtests.jl\"))\n    println(\"Completed tests for $_module, $(round(Int, t)) seconds elapsed\")\n    return nothing\nend\n\n\n@testset \"ClimateMachine\" begin\n    all_tests = isempty(ARGS) || \"all\" in ARGS ? true : false\n\n    function has_submodule(sm)\n        any(ARGS) do a\n            a == sm && return true\n            first(split(a, '/')) == sm && return true\n            return false\n        end\n    end\n\n    for submodule in [\n        \"InputOutput\",\n        \"Utilities\",\n        \"Common\",\n        \"Arrays\",\n        \"BalanceLaws\",\n        \"Atmos\",\n        \"Land\",\n        \"Numerics\",\n        \"Diagnostics\",\n        \"Ocean\",\n        \"Driver\",\n    ]\n        if all_tests || has_submodule(submodule) || \"ClimateMachine\" in ARGS\n            include_test(submodule)\n        end\n    end\nend\n"
  },
  {
    "path": "test/runtests_gpu.jl",
    "content": "using Test, Pkg, CUDA\n\nENV[\"JULIA_LOG_LEVEL\"] = \"WARN\"\n\n@test CUDA.functional()\n\nfor submodule in [\n    \"Arrays\",\n    #\"Numerics/Mesh\",\n    #\"Numerics/DGMethods\",\n    \"Numerics/ODESolvers\",\n]\n    println(\"Starting tests for $submodule\")\n    t = @elapsed include(joinpath(submodule, \"runtests.jl\"))\n    println(\"Completed tests for $submodule, $(round(Int, t)) seconds elapsed\")\nend\n"
  },
  {
    "path": "test/testhelpers.jl",
    "content": "using MPI\n\nfunction runmpi(file; ntasks = 1, localhost = false)\n    localhostenv = try\n        parse(Bool, get(ENV, \"CLIMATEMACHINE_TEST_RUNMPI_LOCALHOST\", \"false\"))\n    catch\n        false\n    end\n    # Force mpiexec to exec on the localhost node\n    # TODO: MicrosoftMPI mpiexec has issues if mpiexec.exe is in a different \n    # folder as the MPI script to run so ignore for now.\n    if (localhost || localhostenv) && (MPI.MPI_LIBRARY != MPI.MicrosoftMPI)\n        localhostonly = `-host localhost`\n    else\n        localhostonly = ``\n    end\n    # by default some mpi runtimes will\n    # complain if more resources (processes)\n    # are requested than available on the node\n    if MPI.MPI_LIBRARY == MPI.OpenMPI\n        oversubscribe = `--oversubscribe`\n    else\n        oversubscribe = ``\n    end\n    @info \"Running MPI test...\" file ntasks\n    # Running this way prevents:\n    #   Balance Law Solver | No tests\n    # since external tests are not returned as passed/fail\n    @time @test MPI.mpiexec() do cmd\n        Base.run(\n            `$cmd $localhostonly $oversubscribe -np $ntasks $(Base.julia_cmd()) --startup-file=no --project=$(Base.active_project()) $file`;\n            wait = true,\n        )\n        true\n    end\nend\n"
  },
  {
    "path": "tutorials/Atmos/agnesi_hs_lin.jl",
    "content": "# # [Linear HS mountain waves (Topography)](@id EX-LIN_HS-docs)\n#\n# ## Description of experiment\n# 1) Dry linear Hydrostatic Mountain Waves\n# The atmosphere is dry and the flow impinges against a witch of Agnesi mountain of heigh $h_{m}=1$m\n# and base parameter $a=10000m$ and centered on $x_{c} = 120km$ in a 2D domain\n# $\\Omega = 240km \\times 50km$. The mountain is defined as\n#\n# ```math\n# z = \\frac{h_m}{1 + \\frac{x - x_c}{a}}\n# ```\n#\n# The 2D problem is setup in 3D by using 1 element in the y direction.\n# To damp the upward moving gravity waves, a Reyleigh absorbing layer is added at $z = 15000 m$.\n#\n# The initial atmosphere is defined such that it has a stability frequency $N=g/\\sqrt{c_p T_0}$, where\n#\n# $T_0 = 250 K$\n# so that\n#\n# ```math\n# \\theta = \\theta_0 = T_0\n# ```\n#\n# ```math\n# \\pi = 1 + \\frac{g^2}{c_p \\theta_0 N^2}\\left(\\exp\\left(\\frac{-N^2 z}{g} \\right)\\right)\n# ```\n#\n# where $\\theta_0 = T_0 K$.\n#\n# so that\n#\n# ```math\n# ρ = \\frac{p_{sfc}}{R_{gas}\\theta}\\pi^{c_v/R_{gas}}\n# ```\n# and\n# ```math\n# T = \\theta \\pi\n# ```\n#\n# 2) Boundaries\n#    - `Impenetrable(FreeSlip())` - Top and bottom: no momentum flux, no mass flux through\n#      walls.\n#    - `Impermeable()` - non-porous walls, i.e. no diffusive fluxes through\n#       walls.\n#    - Agnesi topography built via meshwarp.\n#    - Laterally periodic\n# 3) Domain - 240,000 m (horizontal) x 4000 m (horizontal) x 30,000m (vertical)\n# 4) Resolution - 1000m X 240 m effective resolution\n# 5) Total simulation time - 15,000 s\n# 6) Overrides defaults for\n#    - CPU Initialisation\n#    - Time integrator\n#    - Sources\n#\n#md # !!! note\n#md #     This experiment setup assumes that you have installed the\n#md #     `ClimateMachine` according to the instructions on the landing page.\n#md #     We assume the users' familiarity with the conservative form of the\n#md #     equations of motion for a compressible fluid (see the\n#md #     [AtmosModel](@ref AtmosModel-docs) page).\n#md #\n#md #     The following topics are covered in this example\n#md #     - Defining the initial conditions\n#md #     - Applying source terms\n#md #     - Add an idealized topography defined by a warping function\n#\n# ## Boilerplate (Using Modules)\n#\n# The setup of this problem is taken from Case 6 of [giraldoRestelli2008a](@cite)\n#\nusing ClimateMachine\nClimateMachine.init(parse_clargs = true);\nnothing\n\n# Setting `parse_clargs=true` allows the use of command-line arguments (see API > Driver docs)\n# to control simulation update and output intervals.\n\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing StaticArrays\nusing Test\n\nusing CLIMAParameters\nusing CLIMAParameters.Atmos.SubgridScale: C_smag\nusing CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n# ## [Initial Conditions](@id init)\n#md # !!! note\n#md #     The following variables are assigned in the initial condition\n#md #     - `state.ρ` = Scalar quantity for initial density profile\n#md #     - `state.ρu`= 3-component vector for initial momentum profile\n#md #     - `state.energy.ρe`= Scalar quantity for initial total-energy profile\n#md #       humidity\nfunction init_agnesi_hs_lin!(problem, bl, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n\n    ## Problem float-type\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n\n    ## Unpack constant parameters\n    R_gas::FT = R_d(param_set)\n    c_p::FT = cp_d(param_set)\n    c_v::FT = cv_d(param_set)\n    p0::FT = MSLP(param_set)\n    _grav::FT = grav(param_set)\n    γ::FT = c_p / c_v\n\n    c::FT = c_v / R_gas\n    c2::FT = R_gas / c_p\n\n    Tiso::FT = 250.0\n    θ0::FT = Tiso\n\n    ## Calculate the Brunt-Vaisaila frequency for an isothermal field\n    Brunt::FT = _grav / sqrt(c_p * Tiso)\n    Brunt2::FT = Brunt * Brunt\n    g2::FT = _grav * _grav\n\n    π_exner::FT = exp(-_grav * z / (c_p * Tiso))\n    θ::FT = θ0 * exp(Brunt2 * z / _grav)\n    ρ::FT = p0 / (R_gas * θ) * (π_exner)^c\n\n    ## Compute perturbed thermodynamic state:\n    T = θ * π_exner\n    e_int = internal_energy(param_set, T)\n    ts = PhaseDry(param_set, e_int, ρ)\n\n    ## initial velocity\n    u = FT(20.0)\n\n    ## State (prognostic) variable assignment\n    e_kin = FT(0)                                       # kinetic energy\n    e_pot = gravitational_potential(bl.orientation, aux)# potential energy\n    ρe_tot = ρ * total_energy(e_kin, e_pot, ts)         # total energy\n\n    state.ρ = ρ\n    state.ρu = SVector{3, FT}(ρ * u, 0, 0)\n    state.energy.ρe = ρe_tot\nend\n\n# Define a `setmax` method\nfunction setmax(f, xmax, ymax, zmax)\n    function setmaxima(xin, yin, zin)\n        return f(xin, yin, zin; xmax = xmax, ymax = ymax, zmax = zmax)\n    end\n    return setmaxima\nend\n\n# Define a warping function to build an analytic topography:\nfunction warp_agnesi(xin, yin, zin; xmax = 1000.0, ymax = 1000.0, zmax = 1000.0)\n\n    FT = eltype(xin)\n\n    ac = FT(10000)\n    hm = FT(1)\n    xc = FT(0.5) * xmax\n    zdiff = hm / (FT(1) + ((xin - xc) / ac)^2)\n\n    ## Linear relaxation towards domain maximum height\n    x, y, z = xin, yin, zin + zdiff * (zmax - zin) / zmax\n    return x, y, z\nend\n\n# ## [Model Configuration](@id config-helper)\n# We define a configuration function to assist in prescribing the physical\n# model. The purpose of this is to populate the\n# `AtmosLESConfiguration` with arguments\n# appropriate to the problem being considered.\nfunction config_agnesi_hs_lin(\n    ::Type{FT},\n    N,\n    resolution,\n    xmax,\n    ymax,\n    zmax,\n) where {FT}\n    ##\n    ## Explicit Rayleigh damping:\n    ##\n    ## ``\n    ##   \\tau_s = \\alpha * \\sin\\left(0.5\\pi \\frac{z - z_s}{zmax - z_s} \\right)^2,\n    ## ``\n    ## where\n    ## ``sponge_ampz`` is the wave damping coefficient (1/s)\n    ## ``z_s`` is the level where the Rayleigh sponge starts\n    ## ``zmax`` is the domain top\n    ##\n    ## Setup the parameters for the gravity wave absorbing layer\n    ## at the top of the domain\n    ##\n    ## u_relaxation(xvelo, vvelo, wvelo) contains the background velocity values to which\n    ## the sponge relaxes the vertically moving wave\n    u_relaxation = SVector(FT(20), FT(0), FT(0))\n\n    ## Wave damping coefficient (1/s)\n    sponge_ampz = FT(0.5)\n\n    ## Vertical level where the absorbing layer starts\n    z_s = FT(25000.0)\n\n    ## Pass the sponge parameters to the sponge calculator\n    rayleigh_sponge =\n        RayleighSponge{FT}(zmax, z_s, sponge_ampz, u_relaxation, 2)\n\n    ## Setup the source terms for this problem:\n    source = (Gravity(), rayleigh_sponge)\n\n    ## Define the reference state:\n    T_virt = FT(250)\n    temp_profile_ref = IsothermalProfile(param_set, T_virt)\n    ref_state = HydrostaticState(temp_profile_ref)\n    nothing # hide\n\n    _C_smag = FT(0.21)\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = Vreman(_C_smag),\n        moisture = DryModel(),\n        tracers = NoTracers(),\n    )\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        init_state_prognostic = init_agnesi_hs_lin!,\n        source = source,\n    )\n\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"Agnesi_HS_LINEAR\",      # Problem title [String]\n        N,                       # Polynomial order [Int]\n        resolution,              # (Δx, Δy, Δz) effective resolution [m]\n        xmax,                    # Domain maximum size [m]\n        ymax,                    # Domain maximum size [m]\n        zmax,                    # Domain maximum size [m]\n        param_set,               # Parameter set.\n        init_agnesi_hs_lin!,     # Function specifying initial condition\n        model = model,           # Model type\n        meshwarp = setmax(warp_agnesi, xmax, ymax, zmax),\n    )\n\n    return config\nend\n\n# Define a `main` method (entry point)\nfunction main()\n\n    FT = Float64\n\n    ## Define the polynomial order and effective grid spacings:\n    N = 4\n\n    ## Define the domain size and spatial resolution\n    Nx = 20\n    Ny = 20\n    Nz = 20\n    xmax = FT(244000)\n    ymax = FT(4000)\n    zmax = FT(50000)\n    Δx = xmax / FT(Nx)\n    Δy = ymax / FT(Ny)\n    Δz = zmax / FT(Nz)\n    resolution = (Δx, Δy, Δz)\n\n    t0 = FT(0)\n    timeend = FT(150) #FT(hrs * 60 * 60)\n\n    ## Define the max Courant for the time time integrator (ode_solver).\n    ## The default value is 1.7 for LSRK144:\n    CFL = FT(1.5)\n\n    ## Assign configurations so they can be passed to the `invoke!` function\n    driver_config = config_agnesi_hs_lin(FT, N, resolution, xmax, ymax, zmax)\n\n    ## Define the time integrator:\n    ## We chose an explicit single-rate LSRK144 for this problem\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK144NiegemannDiehlBusch,\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFL,\n    )\n\n    ## Set up the spectral filter to remove the solutions spurious modes\n    ## Define the order of the exponential filter: use 32 or 64 for this problem.\n    ## The larger the value, the less dissipation you get:\n    filterorder = 64\n    filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)\n    cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            AtmosFilterPerturbations(driver_config.bl),\n            solver_config.dg.grid,\n            filter,\n            state_auxiliary = solver_config.dg.state_auxiliary,\n        )\n        nothing\n    end\n    ## End exponential filter\n\n    ## Invoke solver (calls `solve!` function for time-integrator),\n    ## pass the driver, solver and diagnostic config information.\n    result = ClimateMachine.invoke!(\n        solver_config;\n        user_callbacks = (cbfilter,),\n        check_euclidean_distance = true,\n    )\n\n    ## Check that the solution norm is reasonable.\n    @test isapprox(result, FT(1); atol = 1.5e-3)\nend\n\n# Call `main`\nmain()\n"
  },
  {
    "path": "tutorials/Atmos/agnesi_nh_lin.jl",
    "content": "# # [Linear NH mountain waves (Topography)](@id EX-LIN_NH-docs)\n#\n# ## Description of experiment\n# 1) Dry linear Non-hydrostatic Mountain Waves\n# This example of a non-linear hydrostatic mountain wave can be classified as an initial value\n# problem.\n#\n# The atmosphere is dry and the flow impinges against a witch of Agnesi mountain of heigh $h_m=1m$\n# and base parameter $a=1000$m and centered in $x_c = 72km$ in a 2D domain\n# $\\Omega = 144km \\times 30 km$. The mountain is defined as\n#\n# ```math\n#  z = \\frac{h_m}{1 + \\frac{x - x_c}{a}}\n# ```\n# The 2D problem is setup in 3D by using 1 element in the y direction.\n# To damp the upward moving gravity waves, a Reyleigh absorbing layer is added at $z = 10,000m$.\n#\n# The initial atmosphere is defined such that it has a stability frequency $N=0.01 s^{-1}$, where\n#\n# ```math\n#  N^2 = g\\frac{\\rm d \\ln \\theta}{ \\rm dz}\n# ```\n# so that\n#\n# ```math\n# \\theta = \\theta_0 \\exp\\left(\\frac{N^2 z}{g} \\right),\n# ```\n# ```math\n# \\pi = 1 + \\frac{g^2}{c_p \\theta_0 N^2}\\left(\\exp\\left(\\frac{-N^2 z}{g} \\right)\\right)\n# ```\n#\n# where $\\theta_0 = 280K$.\n#\n# so that\n#\n# $ρ = \\frac{p_{sfc}}{R_{gas}\\theta}pi^{c_v/R_{gas}}$\n# and $T = \\theta \\pi$\n#\n# 2) Boundaries\n#    - `Impenetrable(FreeSlip())` - Top and bottom: no momentum flux, no mass flux through\n#      walls.\n#    - `Impermeable()` - non-porous walls, i.e. no diffusive fluxes through\n#       walls.\n#    - Agnesi topography built via meshwarp.\n#    - Laterally periodic\n# 3) Domain - 144,000 m (horizontal) x 1360 m (horizontal) x 30,000m (vertical) (infinite domain in y)\n# 4) Resolution - 340 m X 200 m effective resolution\n# 5) Total simulation time - 18,000 s\n# 6) Overrides defaults for\n#    - CPU Initialisation\n#    - Time integrator\n#    - Sources\n#\n#md # !!! note\n#md #     This experiment setup assumes that you have installed the\n#md #     `ClimateMachine` according to the instructions on the landing page.\n#md #     We assume the users' familiarity with the conservative form of the\n#md #     equations of motion for a compressible fluid (see the\n#md #     [AtmosModel](@ref AtmosModel-docs) page).\n#md #\n#md #     The following topics are covered in this example\n#md #     - Defining the initial conditions\n#md #     - Applying source terms\n#md #     - Add an idealized topography defined by a warping function\n#\n# ## Boilerplate (Using Modules)\n#\n# The setup of this problem is taken from Case 6 of [giraldoRestelli2008a](@cite)\n#\nusing ClimateMachine\nClimateMachine.init(parse_clargs = true)\n\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing StaticArrays\nusing Test\n\nusing CLIMAParameters\nusing CLIMAParameters.Atmos.SubgridScale: C_smag\nusing CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n# ## [Initial Conditions](@id init)\n#md # !!! note\n#md #     The following variables are assigned in the initial condition\n#md #     - `state.ρ` = Scalar quantity for initial density profile\n#md #     - `state.ρu`= 3-component vector for initial momentum profile\n#md #     - `state.energy.ρe`= Scalar quantity for initial total-energy profile\n#md #       humidity\nfunction init_agnesi_hs_lin!(problem, bl, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n\n    ## Problem float-type\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n\n    ## Unpack constant parameters\n    R_gas::FT = R_d(param_set)\n    c_p::FT = cp_d(param_set)\n    c_v::FT = cv_d(param_set)\n    p0::FT = MSLP(param_set)\n    _grav::FT = grav(param_set)\n    γ::FT = c_p / c_v\n\n    c::FT = c_v / R_gas\n    c2::FT = R_gas / c_p\n\n    ## Define initial thermal field as isothermal\n    Tiso::FT = 250.0\n    θ0::FT = Tiso\n\n    ## Assign a value to the Brunt-Vaisala frquencey:\n    Brunt::FT = 0.01\n    Brunt2::FT = Brunt * Brunt\n    g2::FT = _grav * _grav\n\n    π_exner::FT = exp(-_grav * z / (c_p * Tiso))\n    θ::FT = θ0 * exp(Brunt2 * z / _grav)\n    ρ::FT = p0 / (R_gas * θ) * (π_exner)^c\n\n    ## Compute perturbed thermodynamic state:\n    T = θ * π_exner\n    e_int = internal_energy(param_set, T)\n    ts = PhaseDry(param_set, e_int, ρ)\n\n    ## initial velocity\n    u = FT(10.0)\n\n    ## State (prognostic) variable assignment\n    e_kin = FT(0)                                       # kinetic energy\n    e_pot = gravitational_potential(bl.orientation, aux)# potential energy\n    ρe_tot = ρ * total_energy(e_kin, e_pot, ts)         # total energy\n\n    state.ρ = ρ\n    state.ρu = SVector{3, FT}(ρ * u, 0, 0)\n    state.energy.ρe = ρe_tot\nend\n\nfunction setmax(f, xmax, ymax, zmax)\n    function setmaxima(xin, yin, zin)\n        return f(xin, yin, zin; xmax = xmax, ymax = ymax, zmax = zmax)\n    end\n    return setmaxima\nend\n\n# Define a warping function to build an analytic topography:\nfunction warp_agnesi(xin, yin, zin; xmax = 1000.0, ymax = 1000.0, zmax = 1000.0)\n\n    FT = eltype(xin)\n\n    ac = FT(1000)\n    hm = FT(1)\n    xc = FT(0.5) * xmax\n    zdiff = hm / (FT(1) + ((xin - xc) / ac)^2)\n\n    ## Linear relaxation towards domain maximum height\n    x, y, z = xin, yin, zin + zdiff * (zmax - zin) / zmax\n    return x, y, z\nend\n\n# ## [Model Configuration](@id config-helper)\n# We define a configuration function to assist in prescribing the physical\n# model. The purpose of this is to populate the\n# `AtmosLESConfiguration` with arguments\n# appropriate to the problem being considered.\nfunction config_agnesi_hs_lin(\n    ::Type{FT},\n    N,\n    resolution,\n    xmax,\n    ymax,\n    zmax,\n) where {FT}\n    ##\n    ## Explicit Rayleigh damping:\n    ##\n    ## ``\n    ##   \\tau_s = \\alpha * \\sin\\left(0.5\\pi \\frac{z - z_s}{zmax - z_s} \\right)^2,\n    ## ``\n    ## where\n    ## ``sponge_ampz`` is the wave damping coefficient (1/s)\n    ## ``z_s`` is the level where the Rayleigh sponge starts\n    ## ``zmax`` is the domain top\n    ##\n    ## Setup the parameters for the gravity wave absorbing layer\n    ## at the top of the domain\n    ##\n    ## u_relaxation(xvelo, vvelo, wvelo) contains the background velocity values to which\n    ## the sponge relaxes the vertically moving wave\n    u_relaxation = SVector(FT(10), FT(0), FT(0))\n\n    ## Wave damping coefficient (1/s)\n    sponge_ampz = FT(0.5)\n\n    ## Vertical level where the absorbing layer starts\n    z_s = FT(10000.0)\n\n    ## Pass the sponge parameters to the sponge calculator\n    rayleigh_sponge =\n        RayleighSponge{FT}(zmax, z_s, sponge_ampz, u_relaxation, 2)\n\n    ## Setup the source terms for this problem:\n    source = (Gravity(), rayleigh_sponge)\n\n    ## Define the reference state:\n    T_virt = FT(280)\n    temp_profile_ref = IsothermalProfile(param_set, T_virt)\n    ref_state = HydrostaticState(temp_profile_ref)\n    nothing # hide\n\n    _C_smag = FT(0.0)\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = Vreman(_C_smag),\n        moisture = DryModel(),\n        tracers = NoTracers(),\n    )\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        init_state_prognostic = init_agnesi_hs_lin!,\n        source = source,\n    )\n\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"Agnesi_NH_LINEAR\",      # Problem title [String]\n        N,                       # Polynomial order [Int]\n        resolution,              # (Δx, Δy, Δz) effective resolution [m]\n        xmax,                    # Domain maximum size [m]\n        ymax,                    # Domain maximum size [m]\n        zmax,                    # Domain maximum size [m]\n        param_set,               # Parameter set.\n        init_agnesi_hs_lin!,     # Function specifying initial condition\n        model = model,           # Model type\n        meshwarp = setmax(warp_agnesi, xmax, ymax, zmax),\n    )\n\n    return config\nend\n\n# Define a `main` method (entry point)\nfunction main()\n\n    FT = Float64\n\n    ## Define the polynomial order and effective grid spacings:\n    N = 4\n\n    ## Define the domain size and spatial resolution\n    Nx = 20\n    Ny = 20\n    Nz = 20\n    xmax = FT(144000)\n    ymax = FT(4000)\n    zmax = FT(30000)\n    Δx = xmax / FT(Nx)\n    Δy = ymax / FT(Ny)\n    Δz = zmax / FT(Nz)\n    resolution = (Δx, Δy, Δz)\n\n    t0 = FT(0)\n    timeend = FT(100)\n\n    ## Define the max Courant for the time time integrator (ode_solver).\n    ## The default value is 1.7 for LSRK144:\n    CFL = FT(1.5)\n\n    ## Assign configurations so they can be passed to the `invoke!` function\n    driver_config = config_agnesi_hs_lin(FT, N, resolution, xmax, ymax, zmax)\n\n    ## Define the time integrator:\n    ## We chose an explicit single-rate LSRK144 for this problem\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK144NiegemannDiehlBusch,\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFL,\n    )\n\n    ## Set up the spectral filter to remove the solutions spurious modes\n    ## Define the order of the exponential filter: use 32 or 64 for this problem.\n    ## The larger the value, the less dissipation you get:\n    filterorder = 64\n    filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)\n    cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n        Filters.apply!(\n            solver_config.Q,\n            AtmosFilterPerturbations(driver_config.bl),\n            solver_config.dg.grid,\n            filter,\n            state_auxiliary = solver_config.dg.state_auxiliary,\n        )\n        nothing\n    end\n    ## End exponential filter\n\n    ## Invoke solver (calls `solve!` function for time-integrator),\n    ## pass the driver, solver and diagnostic config information.\n    result = ClimateMachine.invoke!(\n        solver_config;\n        user_callbacks = (cbfilter,),\n        check_euclidean_distance = true,\n    )\n\n    ## Check that the solution norm is reasonable.\n    @test FT(0.9) < result < FT(1)\n    ## Some energy is lost from the sponge, so we cannot\n    ## expect perfect energy conservation.\nend\n\n# Call `main`\nmain();\n"
  },
  {
    "path": "tutorials/Atmos/burgers_single_stack.jl",
    "content": "# # Single stack tutorial based on the 3D Burgers + tracer equations\n\n# This tutorial implements the Burgers equations with a tracer field\n# in a single element stack. The flow is initialized with a horizontally\n# uniform profile of horizontal velocity and uniform initial temperature. The fluid\n# is heated from the bottom surface. Gaussian noise is imposed to the horizontal\n# velocity field at each node at the start of the simulation. The tutorial demonstrates how to\n#\n#   * Initialize a [`BalanceLaw`](@ref ClimateMachine.BalanceLaws.BalanceLaw) in a single stack configuration;\n#   * Return the horizontal velocity field to a given profile (e.g., large-scale advection);\n#   * Remove any horizontal inhomogeneities or noise from the flow.\n#\n# The second and third bullet points are demonstrated imposing Rayleigh friction, horizontal\n# diffusion and 2D divergence damping to the horizontal momentum prognostic equation.\n\n# Equations solved in balance law form:\n\n# ```math\n# \\begin{align}\n# \\frac{∂ ρ}{∂ t} =& - ∇ ⋅ (ρ\\mathbf{u}) \\\\\n# \\frac{∂ ρ\\mathbf{u}}{∂ t} =& - ∇ ⋅ (-μ ∇\\mathbf{u}) - ∇ ⋅ (ρ\\mathbf{u} \\mathbf{u}') - γ[ (ρ\\mathbf{u}-ρ̄\\mathbf{ū}) - (ρ\\mathbf{u}-ρ̄\\mathbf{ū})⋅ẑ ẑ] - ν_d ∇_h (∇_h ⋅ ρ\\mathbf{u}) \\\\\n# \\frac{∂ ρcT}{∂ t} =& - ∇ ⋅ (-α ∇ρcT) - ∇ ⋅ (\\mathbf{u} ρcT)\n# \\end{align}\n# ```\n\n# Boundary conditions:\n# ```math\n# \\begin{align}\n# z_{\\mathrm{min}}: & ρ = 1 \\\\\n# z_{\\mathrm{min}}: & ρ\\mathbf{u} = \\mathbf{0} \\\\\n# z_{\\mathrm{min}}: & ρcT = ρc T_{\\mathrm{fixed}} \\\\\n# z_{\\mathrm{max}}: & ρ = 1 \\\\\n# z_{\\mathrm{max}}: & ρ\\mathbf{u} = \\mathbf{0} \\\\\n# z_{\\mathrm{max}}: & -α∇ρcT = 0\n# \\end{align}\n# ```\n\n# where\n#  - ``t`` is time\n#  - ``ρ`` is the density\n#  - ``\\mathbf{u}`` is the velocity (vector)\n#  - ``\\mathbf{ū}`` is the horizontally averaged velocity (vector)\n#  - ``μ`` is the dynamic viscosity tensor\n#  - ``γ`` is the Rayleigh friction frequency\n#  - ``ν_d`` is the horizontal divergence damping coefficient\n#  - ``T`` is the temperature\n#  - ``α`` is the thermal diffusivity tensor\n#  - ``c`` is the heat capacity\n#  - ``ρcT`` is the thermal energy\n\n# Solving these equations is broken down into the following steps:\n# 1) Preliminary configuration\n# 2) PDEs\n# 3) Space discretization\n# 4) Time discretization\n# 5) Solver hooks / callbacks\n# 6) Solve\n# 7) Post-processing\n\n# # Preliminary configuration\n\n# ## [Loading code](@id Loading-code-burgers)\n\n# First, we'll load our pre-requisites\n#  - load external packages:\nusing MPI\nusing Distributions\nusing OrderedCollections\nusing Plots\nusing StaticArrays\nusing LinearAlgebra: Diagonal, tr\n\n#  - load CLIMAParameters and set up to use it:\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n#  - load necessary ClimateMachine modules:\nusing ClimateMachine\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Writers\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux, parameter_set\n\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\n\n#  - import necessary ClimateMachine modules: (`import`ing enables us to\n#  provide implementations of these structs/methods)\nusing ClimateMachine.Orientations:\n    Orientation,\n    FlatOrientation,\n    init_aux!,\n    vertical_unit_vector,\n    projection_tangential\n\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    source!,\n    flux_second_order!,\n    flux_first_order!,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    init_state_auxiliary!,\n    init_state_prognostic!,\n    BoundaryCondition,\n    boundary_conditions,\n    boundary_state!\n\n# ## Initialization\n\n# Define the float type (`Float64` or `Float32`)\nconst FT = Float64;\n# Initialize ClimateMachine for CPU.\nClimateMachine.init(; disable_gpu = true);\n\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\n\n# Load some helper functions for plotting\ninclude(joinpath(clima_dir, \"docs\", \"plothelpers.jl\"));\n\n# # Define the set of Partial Differential Equations (PDEs)\n\n# ## Define the model\n\n# Model parameters can be stored in the particular [`BalanceLaw`](@ref\n# ClimateMachine.BalanceLaws.BalanceLaw), in this case, the `BurgersEquation`:\n\nBase.@kwdef struct BurgersEquation{FT, APS, O} <: BalanceLaw\n    \"Parameters\"\n    param_set::APS\n    \"Orientation model\"\n    orientation::O\n    \"Heat capacity\"\n    c::FT = 1\n    \"Vertical dynamic viscosity\"\n    μv::FT = 1e-4\n    \"Horizontal dynamic viscosity\"\n    μh::FT = 1\n    \"Vertical thermal diffusivity\"\n    αv::FT = 1e-2\n    \"Horizontal thermal diffusivity\"\n    αh::FT = 1\n    \"IC Gaussian noise standard deviation\"\n    σ::FT = 5e-2\n    \"Rayleigh damping\"\n    γ::FT = 5\n    \"Domain height\"\n    zmax::FT = 1\n    \"Initial conditions for temperature\"\n    initialT::FT = 295.15\n    \"Bottom boundary value for temperature (Dirichlet boundary conditions)\"\n    T_bottom::FT = 300.0\n    \"Top flux (α∇ρcT) at top boundary (Neumann boundary conditions)\"\n    flux_top::FT = 0.0\n    \"Divergence damping coefficient (horizontal)\"\n    νd::FT = 1\nend\n\n# Create an instance of the `BurgersEquation`:\norientation = FlatOrientation()\n\nm = BurgersEquation{FT, typeof(param_set), typeof(orientation)}(\n    param_set = param_set,\n    orientation = orientation,\n);\n\n# This model dictates the flow control, using [Dynamic Multiple\n# Dispatch](https://en.wikipedia.org/wiki/Multiple_dispatch), for which\n# kernels are executed.\n\n# ## Define the variables\n\n# All of the methods defined in this section were `import`ed in\n# [Loading code](@ref Loading-code-burgers) to let us provide\n# implementations for our `BurgersEquation` as they will be used\n# by the solver.\n\n# Specify auxiliary variables for `BurgersEquation`\nfunction vars_state(m::BurgersEquation, st::Auxiliary, FT)\n    @vars begin\n        coord::SVector{3, FT}\n        orientation::vars_state(m.orientation, st, FT)\n    end\nend\n\n# Specify prognostic variables, the variables solved for in the PDEs, for\n# `BurgersEquation`\nvars_state(::BurgersEquation, ::Prognostic, FT) =\n    @vars(ρ::FT, ρu::SVector{3, FT}, ρcT::FT);\n\n# Specify state variables whose gradients are needed for `BurgersEquation`\nvars_state(::BurgersEquation, ::Gradient, FT) =\n    @vars(u::SVector{3, FT}, ρcT::FT, ρu::SVector{3, FT});\n\n# Specify gradient variables for `BurgersEquation`\nvars_state(::BurgersEquation, ::GradientFlux, FT) = @vars(\n    μ∇u::SMatrix{3, 3, FT, 9},\n    α∇ρcT::SVector{3, FT},\n    νd∇D::SMatrix{3, 3, FT, 9}\n);\n\n# ## Define the compute kernels\n\n# Specify the initial values in `aux::Vars`, which are available in\n# `init_state_prognostic!`. Note that\n# - this method is only called at `t=0`.\n# - `aux.coord` is available here because we've specified `coord` in `vars_state(m, aux, FT)`.\nfunction nodal_init_state_auxiliary!(\n    m::BurgersEquation,\n    aux::Vars,\n    tmp::Vars,\n    geom::LocalGeometry,\n)\n    aux.coord = geom.coord\nend;\n\n# `init_aux!` initializes the auxiliary gravitational potential field needed for vertical projections\nfunction init_state_auxiliary!(\n    m::BurgersEquation,\n    state_auxiliary::MPIStateArray,\n    grid,\n    direction,\n)\n    init_aux!(m, m.orientation, state_auxiliary, grid, direction)\n\n    init_state_auxiliary!(\n        m,\n        nodal_init_state_auxiliary!,\n        state_auxiliary,\n        grid,\n        direction,\n    )\nend;\n\n# Specify the initial values in `state::Vars`. Note that\n# - this method is only called at `t=0`.\n# - `state.ρ`, `state.ρu` and`state.ρcT` are available here because we've specified `ρ`, `ρu` and `ρcT` in `vars_state(m, state, FT)`.\nfunction init_state_prognostic!(\n    m::BurgersEquation,\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t::Real,\n)\n    z = aux.coord[3]\n    ε1 = rand(Normal(0, m.σ))\n    ε2 = rand(Normal(0, m.σ))\n    state.ρ = 1\n    ρu = 1 - 4 * (z - m.zmax / 2)^2 + ε1\n    ρv = 1 - 4 * (z - m.zmax / 2)^2 + ε2\n    ρw = 0\n    state.ρu = SVector(ρu, ρv, ρw)\n\n    state.ρcT = state.ρ * m.c * m.initialT\nend;\n\n# The remaining methods, defined in this section, are called at every\n# time-step in the solver by the [`BalanceLaw`](@ref\n# ClimateMachine.BalanceLaws.BalanceLaw) framework.\n\n# Since we have second-order fluxes, we must tell `ClimateMachine` to compute\n# the gradient of `ρcT`, `u` and `ρu`. Here, we specify how `ρcT`, `u` and `ρu` are computed. Note that\n# e.g. `transform.ρcT` is available here because we've specified `ρcT` in `vars_state(m, ::Gradient, FT)`.\nfunction compute_gradient_argument!(\n    m::BurgersEquation,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    transform.ρcT = state.ρcT\n    transform.u = state.ρu / state.ρ\n    transform.ρu = state.ρu\nend;\n\n# Specify where in `diffusive::Vars` to store the computed gradient from\n# `compute_gradient_argument!`. Note that:\n#  - `diffusive.μ∇u` is available here because we've specified `μ∇u` in `vars_state(m, ::GradientFlux, FT)`.\n#  - `∇transform.u` is available here because we've specified `u` in `vars_state(m, ::Gradient, FT)`.\n#  - `diffusive.μ∇u` is built using an anisotropic diffusivity tensor.\n#  - The `divergence` may be computed from the trace of tensor `∇ρu`.\nfunction compute_gradient_flux!(\n    m::BurgersEquation{FT},\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) where {FT}\n    param_set = parameter_set(m)\n    ∇ρu = ∇transform.ρu\n    ẑ = vertical_unit_vector(m.orientation, param_set, aux)\n    divergence = tr(∇ρu) - ẑ' * ∇ρu * ẑ\n    diffusive.α∇ρcT = Diagonal(SVector(m.αh, m.αh, m.αv)) * ∇transform.ρcT\n    diffusive.μ∇u = Diagonal(SVector(m.μh, m.μh, m.μv)) * ∇transform.u\n    diffusive.νd∇D =\n        Diagonal(SVector(m.νd, m.νd, FT(0))) *\n        Diagonal(SVector(divergence, divergence, FT(0)))\nend;\n\n# Introduce Rayleigh friction towards a target profile as a source.\n# Note that:\n# - Rayleigh damping is only applied in the horizontal using the `projection_tangential` method.\nfunction source!(\n    m::BurgersEquation{FT},\n    source::Vars,\n    state::Vars,\n    diffusive::Vars,\n    aux::Vars,\n    args...,\n) where {FT}\n    param_set = parameter_set(m)\n    ẑ = vertical_unit_vector(m.orientation, param_set, aux)\n    z = aux.coord[3]\n    ρ̄ū =\n        state.ρ * SVector{3, FT}(\n            0.5 - 2 * (z - m.zmax / 2)^2,\n            0.5 - 2 * (z - m.zmax / 2)^2,\n            0.0,\n        )\n    ρu_p = state.ρu - ρ̄ū\n    source.ρu -=\n        m.γ * projection_tangential(m.orientation, param_set, aux, ρu_p)\nend;\n\n# Compute advective flux.\n# Note that:\n# - `state.ρu` is available here because we've specified `ρu` in `vars_state(m, state, FT)`.\nfunction flux_first_order!(\n    m::BurgersEquation,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    _...,\n)\n    flux.ρ = state.ρu\n\n    u = state.ρu / state.ρ\n    flux.ρu = state.ρu * u'\n    flux.ρcT = u * state.ρcT\nend;\n\n# Compute diffusive flux (e.g. ``F(μ, \\mathbf{u}, t) = -μ∇\\mathbf{u}`` in the original PDE).\n# Note that:\n# - `diffusive.μ∇u` is available here because we've specified `μ∇u` in `vars_state(m, ::GradientFlux, FT)`.\n# - The divergence gradient can be written as a diffusive flux using a divergence diagonal tensor.\nfunction flux_second_order!(\n    m::BurgersEquation,\n    flux::Grad,\n    state::Vars,\n    diffusive::Vars,\n    hyperdiffusive::Vars,\n    aux::Vars,\n    t::Real,\n)\n    flux.ρcT -= diffusive.α∇ρcT\n    flux.ρu -= diffusive.μ∇u\n    flux.ρu -= diffusive.νd∇D\nend;\n\n# ### Boundary conditions\n\n# Second-order terms in our equations, ``∇⋅(G)`` where ``G = μ∇\\mathbf{u}``, are\n# internally reformulated to first-order unknowns.\n# Boundary conditions must be specified for all unknowns, both first-order and\n# second-order unknowns which have been reformulated.\n\nstruct TopBC <: BoundaryCondition end;\nstruct BottomBC <: BoundaryCondition end;\nboundary_conditions(::BurgersEquation) = (BottomBC(), TopBC());\n\n# The boundary conditions for `ρ`, `ρu` and `ρcT` (first order unknowns)\nfunction boundary_state!(\n    nf,\n    bc::BottomBC,\n    m::BurgersEquation,\n    state⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    _...,\n)\n    state⁺.ρ = 1\n    state⁺.ρu = SVector(0, 0, 0)\n    state⁺.ρcT = state⁺.ρ * m.c * m.T_bottom\nend;\nfunction boundary_state!(\n    nf,\n    bc::TopBC,\n    m::BurgersEquation,\n    state⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    _...,\n)\n    state⁺.ρ = 1\n    state⁺.ρu = SVector(0, 0, 0)\nend;\n\n# The boundary conditions for `ρ`, `ρu` and `ρcT` are specified here for\n# second-order unknowns\nfunction boundary_state!(\n    nf,\n    bc::BottomBC,\n    m::BurgersEquation,\n    state⁺::Vars,\n    diff⁺::Vars,\n    hyperdiff⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    _...,\n)\n    state⁺.ρ = 1\n    state⁺.ρu = SVector(0, 0, 0)\n    state⁺.ρcT = state⁺.ρ * m.c * m.T_bottom\nend;\nfunction boundary_state!(\n    nf,\n    bc::TopBC,\n    m::BurgersEquation,\n    state⁺::Vars,\n    diff⁺::Vars,\n    hyperdiff⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    _...,\n)\n    state⁺.ρ = 1\n    state⁺.ρu = SVector(0, 0, 0)\n    diff⁺.α∇ρcT = -n⁻ * m.flux_top\nend;\n\n# # Spatial discretization\n\n# Prescribe polynomial order of basis functions in finite elements\nN_poly = 5;\n\n# Specify the number of vertical elements\nnelem_vert = 10;\n\n# Specify the domain height\nzmax = m.zmax;\n\n# Establish a `ClimateMachine` single stack configuration\ndriver_config = ClimateMachine.SingleStackConfiguration(\n    \"BurgersEquation\",\n    N_poly,\n    nelem_vert,\n    zmax,\n    param_set,\n    m,\n    numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n);\n\n# # Time discretization\n\n# Specify simulation time (SI units)\nt0 = FT(0);\ntimeend = FT(1);\n\n# We'll define the time-step based on the Fourier\n# number and the [Courant number](https://en.wikipedia.org/wiki/Courant–Friedrichs–Lewy_condition)\n# of the flow\nΔ = min_node_distance(driver_config.grid)\n\ngiven_Fourier = FT(0.5);\nFourier_bound = given_Fourier * Δ^2 / max(m.αh, m.μh, m.νd);\nCourant_bound = FT(0.5) * Δ;\ndt = min(Fourier_bound, Courant_bound)\n\n# # Configure a `ClimateMachine` solver.\n\n# This initializes the state vector and allocates memory for the solution in\n# space (`dg` has the model `m`, which describes the PDEs as well as the\n# function used for initialization). This additionally initializes the ODE\n# solver, by default an explicit Low-Storage\n# [Runge-Kutta](https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods)\n# method.\n\nsolver_config =\n    ClimateMachine.SolverConfiguration(t0, timeend, driver_config, ode_dt = dt);\n\n# ## Inspect the initial conditions for a single nodal stack\n\n# Let's export plots of the initial state\noutput_dir = @__DIR__;\n\nmkpath(output_dir);\n\nz_scale = 100 # convert from meters to cm\nz_key = \"z\"\nz_label = \"z [cm]\"\nz = get_z(driver_config.grid; z_scale = z_scale)\nstate_vars = get_vars_from_nodal_stack(\n    driver_config.grid,\n    solver_config.Q,\n    vars_state(m, Prognostic(), FT),\n);\n\n# Create an array to store the solution:\nstate_data = Dict[state_vars]  # store initial condition at ``t=0``\ntime_data = FT[0]                                      # store time data\n\n# Generate plots of initial conditions for the southwest nodal stack\nexport_plot(\n    z,\n    time_data,\n    state_data,\n    (\"ρcT\",),\n    joinpath(output_dir, \"initial_condition_T_nodal.png\");\n    xlabel = \"ρcT at southwest node\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    state_data,\n    (\"ρu[1]\",),\n    joinpath(output_dir, \"initial_condition_u_nodal.png\");\n    xlabel = \"ρu at southwest node\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    state_data,\n    (\"ρu[2]\",),\n    joinpath(output_dir, \"initial_condition_v_nodal.png\");\n    xlabel = \"ρv at southwest node\",\n    ylabel = z_label,\n);\n\n# ![](initial_condition_T_nodal.png)\n# ![](initial_condition_u_nodal.png)\n\n# ## Inspect the initial conditions for the horizontal averages\n\n# Horizontal statistics of variables\n\nstate_vars_var = get_horizontal_variance(\n    driver_config.grid,\n    solver_config.Q,\n    vars_state(m, Prognostic(), FT),\n);\n\nstate_vars_avg = get_horizontal_mean(\n    driver_config.grid,\n    solver_config.Q,\n    vars_state(m, Prognostic(), FT),\n);\n\ndata_avg = Dict[state_vars_avg]\ndata_var = Dict[state_vars_var]\n\nexport_plot(\n    z,\n    time_data,\n    data_avg,\n    (\"ρu[1]\",),\n    joinpath(output_dir, \"initial_condition_avg_u.png\");\n    xlabel = \"Horizontal mean of ρu\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    data_var,\n    (\"ρu[1]\",),\n    joinpath(output_dir, \"initial_condition_variance_u.png\");\n    xlabel = \"Horizontal variance of ρu\",\n    ylabel = z_label,\n);\n\n# ![](initial_condition_avg_u.png)\n# ![](initial_condition_variance_u.png)\n\n# # Solver hooks / callbacks\n\n# Define the number of outputs from `t0` to `timeend`\nconst n_outputs = 5;\nconst every_x_simulation_time = timeend / n_outputs;\n\n# Create a dictionary for `z` coordinate (and convert to cm) NCDatasets IO:\ndims = OrderedDict(z_key => collect(z));\n\n# Create dictionaries to store outputs:\ndata_var = Dict[Dict([k => Dict() for k in 0:n_outputs]...),]\ndata_var[1] = state_vars_var\n\ndata_avg = Dict[Dict([k => Dict() for k in 0:n_outputs]...),]\ndata_avg[1] = state_vars_avg\n\ndata_nodal = Dict[Dict([k => Dict() for k in 0:n_outputs]...),]\ndata_nodal[1] = state_vars\n\n# The `ClimateMachine`'s time-steppers provide hooks, or callbacks, which\n# allow users to inject code to be executed at specified intervals. In this\n# callback, the state variables are collected, combined into a single\n# `OrderedDict` and written to a NetCDF file (for each output step).\ncallback = GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n    state_vars_var = get_horizontal_variance(\n        driver_config.grid,\n        solver_config.Q,\n        vars_state(m, Prognostic(), FT),\n    )\n    state_vars_avg = get_horizontal_mean(\n        driver_config.grid,\n        solver_config.Q,\n        vars_state(m, Prognostic(), FT),\n    )\n    state_vars = get_vars_from_nodal_stack(\n        driver_config.grid,\n        solver_config.Q,\n        vars_state(m, Prognostic(), FT),\n        i = 1,\n        j = 1,\n    )\n    push!(data_var, state_vars_var)\n    push!(data_avg, state_vars_avg)\n    push!(data_nodal, state_vars)\n    push!(time_data, gettime(solver_config.solver))\n    nothing\nend;\n\n# # Solve\n\n# This is the main `ClimateMachine` solver invocation. While users do not have\n# access to the time-stepping loop, code may be injected via `user_callbacks`,\n# which is a `Tuple` of [`GenericCallbacks`](@ref ClimateMachine.GenericCallbacks).\nClimateMachine.invoke!(solver_config; user_callbacks = (callback,))\n\n# # Post-processing\n\n# Our solution has now been calculated and exported to NetCDF files in\n# `output_dir`.\n\n# Let's plot the horizontal statistics of `ρu` and `ρcT`, as well as the evolution of\n# `ρu` for the southwest nodal stack:\nexport_plot(\n    z,\n    time_data,\n    data_avg,\n    (\"ρu[1]\"),\n    joinpath(output_dir, \"solution_vs_time_u_avg.png\");\n    xlabel = \"Horizontal mean of ρu\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    data_var,\n    (\"ρu[1]\"),\n    joinpath(output_dir, \"variance_vs_time_u.png\");\n    xlabel = \"Horizontal variance of ρu\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    data_avg,\n    (\"ρcT\"),\n    joinpath(output_dir, \"solution_vs_time_T_avg.png\");\n    xlabel = \"Horizontal mean of ρcT\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    data_var,\n    (\"ρcT\"),\n    joinpath(output_dir, \"variance_vs_time_T.png\");\n    xlabel = \"Horizontal variance of ρcT\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    data_nodal,\n    (\"ρu[1]\"),\n    joinpath(output_dir, \"solution_vs_time_u_nodal.png\");\n    xlabel = \"ρu at southwest node\",\n    ylabel = z_label,\n);\n# ![](solution_vs_time_u_avg.png)\n# ![](variance_vs_time_u.png)\n# ![](solution_vs_time_T_avg.png)\n# ![](variance_vs_time_T.png)\n# ![](solution_vs_time_u_nodal.png)\n\n# Rayleigh friction returns the horizontal velocity to the objective\n# profile on the timescale of the simulation (1 second), since `γ`∼1. The horizontal viscosity\n# and 2D divergence damping act to reduce the horizontal variance over the same timescale.\n# The initial Gaussian noise is propagated to the temperature field through advection.\n# The horizontal diffusivity acts to reduce this `ρcT` variance in time, although in a longer\n# timescale.\n\n# To run this file, and\n# inspect the solution, include this tutorial in the Julia REPL\n# with:\n\n# ```julia\n# include(joinpath(\"tutorials\", \"Atmos\", \"burgers_single_stack.jl\"))\n# ```\n"
  },
  {
    "path": "tutorials/Atmos/burgers_single_stack_bjfnk.jl",
    "content": "# # Single stack with HEVI solver tutorial based on the 3D Burgers + tracer equations\n\n# This tutorial implements the Burgers equations with a tracer field\n# in a single element stack. The flow is initialized with a horizontally\n# uniform profile of horizontal velocity and uniform initial temperature. The fluid\n# is heated from the bottom surface. Gaussian noise is imposed to the horizontal\n# velocity field at each node at the start of the simulation. The tutorial demonstrates how to\n#\n#   * Initialize a [`BalanceLaw`](@ref ClimateMachine.BalanceLaws.BalanceLaw) in a single stack configuration;\n#   * Return the horizontal velocity field to a given profile (e.g., large-scale advection);\n#   * Remove any horizontal inhomogeneities or noise from the flow.\n#   * Use horizontal explicit vertical implicit (HEVI) solver in the Single stack setup\n#\n# The second and third bullet points are demonstrated imposing Rayleigh friction, horizontal\n# diffusion and 2D divergence damping to the horizontal momentum prognostic equation.\n\n# Equations solved in balance law form:\n\n# ```math\n# \\begin{align}\n# \\frac{∂ ρ}{∂ t} =& - ∇ ⋅ (ρ\\mathbf{u}) \\\\\n# \\frac{∂ ρ\\mathbf{u}}{∂ t} =& - ∇ ⋅ (-μ ∇\\mathbf{u}) - ∇ ⋅ (ρ\\mathbf{u} \\mathbf{u}') - γ[ (ρ\\mathbf{u}-ρ̄\\mathbf{ū}) - (ρ\\mathbf{u}-ρ̄\\mathbf{ū})⋅ẑ ẑ] - ν_d ∇_h (∇_h ⋅ ρ\\mathbf{u}) \\\\\n# \\frac{∂ ρcT}{∂ t} =& - ∇ ⋅ (-α ∇ρcT) - ∇ ⋅ (\\mathbf{u} ρcT)\n# \\end{align}\n# ```\n\n# Boundary conditions:\n# ```math\n# \\begin{align}\n# z_{\\mathrm{min}}: & ρ = 1 \\\\\n# z_{\\mathrm{min}}: & ρ\\mathbf{u} = \\mathbf{0} \\\\\n# z_{\\mathrm{min}}: & ρcT = ρc T_{\\mathrm{fixed}} \\\\\n# z_{\\mathrm{max}}: & ρ = 1 \\\\\n# z_{\\mathrm{max}}: & ρ\\mathbf{u} = \\mathbf{0} \\\\\n# z_{\\mathrm{max}}: & -α∇ρcT = 0\n# \\end{align}\n# ```\n\n# where\n#  - ``t`` is time\n#  - ``ρ`` is the density\n#  - ``\\mathbf{u}`` is the velocity (vector)\n#  - ``\\mathbf{ū}`` is the horizontally averaged velocity (vector)\n#  - ``μ`` is the dynamic viscosity tensor\n#  - ``γ`` is the Rayleigh friction frequency\n#  - ``ν_d`` is the horizontal divergence damping coefficient\n#  - ``T`` is the temperature\n#  - ``α`` is the thermal diffusivity tensor\n#  - ``c`` is the heat capacity\n#  - ``ρcT`` is the thermal energy\n\n# Solving these equations is broken down into the following steps:\n# 1) Preliminary configuration\n# 2) PDEs\n# 3) Space discretization\n# 4) Time discretization\n# 5) Solver hooks / callbacks\n# 6) Solve\n# 7) Post-processing\n\n# # Preliminary configuration\n\n# ## [Loading code](@id Loading-code-burgers-bjfnk)\n\n# First, we'll load our pre-requisites\n#  - load external packages:\nusing MPI\nusing Distributions\nusing OrderedCollections\nusing Plots\nusing StaticArrays\nusing LinearAlgebra: Diagonal, tr\n\n#  - load CLIMAParameters and set up to use it:\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n#  - load necessary ClimateMachine modules:\nusing ClimateMachine\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Writers\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux, parameter_set\n\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\n\n#  - import necessary ClimateMachine modules: (`import`ing enables us to\n#  provide implementations of these structs/methods)\nusing ClimateMachine.Orientations:\n    Orientation,\n    FlatOrientation,\n    init_aux!,\n    vertical_unit_vector,\n    projection_tangential\n\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    source!,\n    flux_second_order!,\n    flux_first_order!,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    init_state_auxiliary!,\n    init_state_prognostic!,\n    BoundaryCondition,\n    boundary_conditions,\n    boundary_state!\n\n# ## Initialization\n\n# Define the float type (`Float64` or `Float32`)\nconst FT = Float64;\n# Initialize ClimateMachine for CPU.\nClimateMachine.init(; disable_gpu = true);\n\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\n\n# Load some helper functions for plotting\ninclude(joinpath(clima_dir, \"docs\", \"plothelpers.jl\"));\n\n# # Define the set of Partial Differential Equations (PDEs)\n\n# ## Define the model\n\n# Model parameters can be stored in the particular [`BalanceLaw`](@ref\n# ClimateMachine.BalanceLaws.BalanceLaw), in this case, the `BurgersEquation`:\n\nBase.@kwdef struct BurgersEquation{FT, APS, O} <: BalanceLaw\n    \"Parameters\"\n    param_set::APS\n    \"Orientation model\"\n    orientation::O\n    \"Heat capacity\"\n    c::FT = 1\n    \"Vertical dynamic viscosity\"\n    μv::FT = 1e-4\n    \"Horizontal dynamic viscosity\"\n    μh::FT = 1\n    \"Vertical thermal diffusivity\"\n    αv::FT = 1e-2\n    \"Horizontal thermal diffusivity\"\n    αh::FT = 1\n    \"IC Gaussian noise standard deviation\"\n    σ::FT = 5e-2\n    \"Rayleigh damping\"\n    γ::FT = 5\n    \"Domain height\"\n    zmax::FT = 1\n    \"Initial conditions for temperature\"\n    initialT::FT = 295.15\n    \"Bottom boundary value for temperature (Dirichlet boundary conditions)\"\n    T_bottom::FT = 300.0\n    \"Top flux (α∇ρcT) at top boundary (Neumann boundary conditions)\"\n    flux_top::FT = 0.0\n    \"Divergence damping coefficient (horizontal)\"\n    νd::FT = 1\nend\n\n# Create an instance of the `BurgersEquation`:\norientation = FlatOrientation()\n\nm = BurgersEquation{FT, typeof(param_set), typeof(orientation)}(\n    param_set = param_set,\n    orientation = orientation,\n);\n\n# This model dictates the flow control, using [Dynamic Multiple\n# Dispatch](https://en.wikipedia.org/wiki/Multiple_dispatch), for which\n# kernels are executed.\n\n# ## Define the variables\n\n# All of the methods defined in this section were `import`ed in\n# [Loading code](@ref Loading-code-burgers) to let us provide\n# implementations for our `BurgersEquation` as they will be used\n# by the solver.\n\n# Specify auxiliary variables for `BurgersEquation`\nfunction vars_state(m::BurgersEquation, st::Auxiliary, FT)\n    @vars begin\n        coord::SVector{3, FT}\n        orientation::vars_state(m.orientation, st, FT)\n    end\nend\n\n# Specify prognostic variables, the variables solved for in the PDEs, for\n# `BurgersEquation`\nvars_state(::BurgersEquation, ::Prognostic, FT) =\n    @vars(ρ::FT, ρu::SVector{3, FT}, ρcT::FT);\n\n# Specify state variables whose gradients are needed for `BurgersEquation`\nvars_state(::BurgersEquation, ::Gradient, FT) =\n    @vars(u::SVector{3, FT}, ρcT::FT, ρu::SVector{3, FT});\n\n# Specify gradient variables for `BurgersEquation`\nvars_state(::BurgersEquation, ::GradientFlux, FT) = @vars(\n    μ∇u::SMatrix{3, 3, FT, 9},\n    α∇ρcT::SVector{3, FT},\n    νd∇D::SMatrix{3, 3, FT, 9}\n);\n\n# ## Define the compute kernels\n\n# Specify the initial values in `aux::Vars`, which are available in\n# `init_state_prognostic!`. Note that\n# - this method is only called at `t=0`.\n# - `aux.coord` is available here because we've specified `coord` in `vars_state(m, aux, FT)`.\nfunction nodal_init_state_auxiliary!(\n    m::BurgersEquation,\n    aux::Vars,\n    tmp::Vars,\n    geom::LocalGeometry,\n)\n    aux.coord = geom.coord\nend;\n\n# `init_aux!` initializes the auxiliary gravitational potential field needed for vertical projections\nfunction init_state_auxiliary!(\n    m::BurgersEquation,\n    state_auxiliary::MPIStateArray,\n    grid,\n    direction,\n)\n    init_aux!(m, m.orientation, state_auxiliary, grid, direction)\n\n    init_state_auxiliary!(\n        m,\n        nodal_init_state_auxiliary!,\n        state_auxiliary,\n        grid,\n        direction,\n    )\nend;\n\n# Specify the initial values in `state::Vars`. Note that\n# - this method is only called at `t=0`.\n# - `state.ρ`, `state.ρu` and`state.ρcT` are available here because we've specified `ρ`, `ρu` and `ρcT` in `vars_state(m, state, FT)`.\nfunction init_state_prognostic!(\n    m::BurgersEquation,\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t::Real,\n)\n    z = aux.coord[3]\n    ε1 = rand(Normal(0, m.σ))\n    ε2 = rand(Normal(0, m.σ))\n    state.ρ = 1\n    ρu = 1 - 4 * (z - m.zmax / 2)^2 + ε1\n    ρv = 1 - 4 * (z - m.zmax / 2)^2 + ε2\n    ρw = 0\n    state.ρu = SVector(ρu, ρv, ρw)\n\n    state.ρcT = state.ρ * m.c * m.initialT\nend;\n\n# The remaining methods, defined in this section, are called at every\n# time-step in the solver by the [`BalanceLaw`](@ref\n# ClimateMachine.BalanceLaws.BalanceLaw) framework.\n\n# Since we have second-order fluxes, we must tell `ClimateMachine` to compute\n# the gradient of `ρcT`, `u` and `ρu`. Here, we specify how `ρcT`, `u` and `ρu` are computed. Note that\n# e.g. `transform.ρcT` is available here because we've specified `ρcT` in `vars_state(m, ::Gradient, FT)`.\nfunction compute_gradient_argument!(\n    m::BurgersEquation,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    transform.ρcT = state.ρcT\n    transform.u = state.ρu / state.ρ\n    transform.ρu = state.ρu\nend;\n\n# Specify where in `diffusive::Vars` to store the computed gradient from\n# `compute_gradient_argument!`. Note that:\n#  - `diffusive.μ∇u` is available here because we've specified `μ∇u` in `vars_state(m, ::GradientFlux, FT)`.\n#  - `∇transform.u` is available here because we've specified `u` in `vars_state(m, ::Gradient, FT)`.\n#  - `diffusive.μ∇u` is built using an anisotropic diffusivity tensor.\n#  - The `divergence` may be computed from the trace of tensor `∇ρu`.\nfunction compute_gradient_flux!(\n    m::BurgersEquation{FT},\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) where {FT}\n    param_set = parameter_set(m)\n    ∇ρu = ∇transform.ρu\n    ẑ = vertical_unit_vector(m.orientation, param_set, aux)\n    divergence = tr(∇ρu) - ẑ' * ∇ρu * ẑ\n    diffusive.α∇ρcT = Diagonal(SVector(m.αh, m.αh, m.αv)) * ∇transform.ρcT\n    diffusive.μ∇u = Diagonal(SVector(m.μh, m.μh, m.μv)) * ∇transform.u\n    diffusive.νd∇D =\n        Diagonal(SVector(m.νd, m.νd, FT(0))) *\n        Diagonal(SVector(divergence, divergence, FT(0)))\nend;\n\n# Introduce Rayleigh friction towards a target profile as a source.\n# Note that:\n# - Rayleigh damping is only applied in the horizontal using the `projection_tangential` method.\nfunction source!(\n    m::BurgersEquation{FT},\n    source::Vars,\n    state::Vars,\n    diffusive::Vars,\n    aux::Vars,\n    args...,\n) where {FT}\n    param_set = parameter_set(m)\n    ẑ = vertical_unit_vector(m.orientation, param_set, aux)\n    z = aux.coord[3]\n    ρ̄ū =\n        state.ρ * SVector{3, FT}(\n            0.5 - 2 * (z - m.zmax / 2)^2,\n            0.5 - 2 * (z - m.zmax / 2)^2,\n            0.0,\n        )\n    ρu_p = state.ρu - ρ̄ū\n    source.ρu -=\n        m.γ * projection_tangential(m.orientation, param_set, aux, ρu_p)\nend;\n\n# Compute advective flux.\n# Note that:\n# - `state.ρu` is available here because we've specified `ρu` in `vars_state(m, state, FT)`.\nfunction flux_first_order!(\n    m::BurgersEquation,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    _...,\n)\n    flux.ρ = state.ρu\n\n    u = state.ρu / state.ρ\n    flux.ρu = state.ρu * u'\n    flux.ρcT = u * state.ρcT\nend;\n\n# Compute diffusive flux (e.g. ``F(μ, \\mathbf{u}, t) = -μ∇\\mathbf{u}`` in the original PDE).\n# Note that:\n# - `diffusive.μ∇u` is available here because we've specified `μ∇u` in `vars_state(m, ::GradientFlux, FT)`.\n# - The divergence gradient can be written as a diffusive flux using a divergence diagonal tensor.\nfunction flux_second_order!(\n    m::BurgersEquation,\n    flux::Grad,\n    state::Vars,\n    diffusive::Vars,\n    hyperdiffusive::Vars,\n    aux::Vars,\n    t::Real,\n)\n    flux.ρcT -= diffusive.α∇ρcT\n    flux.ρu -= diffusive.μ∇u\n    flux.ρu -= diffusive.νd∇D\nend;\n\n# ### Boundary conditions\n\n# Second-order terms in our equations, ``∇⋅(G)`` where ``G = μ∇\\mathbf{u}``, are\n# internally reformulated to first-order unknowns.\n# Boundary conditions must be specified for all unknowns, both first-order and\n# second-order unknowns which have been reformulated.\n\nstruct TopBC <: BoundaryCondition end;\nstruct BottomBC <: BoundaryCondition end;\nboundary_conditions(::BurgersEquation) = (BottomBC(), TopBC());\n\n# The boundary conditions for `ρ`, `ρu` and `ρcT` (first order unknowns)\nfunction boundary_state!(\n    nf,\n    bc::BottomBC,\n    m::BurgersEquation,\n    state⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    _...,\n)\n    state⁺.ρ = 1\n    state⁺.ρu = SVector(0, 0, 0)\n    state⁺.ρcT = state⁺.ρ * m.c * m.T_bottom\nend;\nfunction boundary_state!(\n    nf,\n    bc::TopBC,\n    m::BurgersEquation,\n    state⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    _...,\n)\n    state⁺.ρ = 1\n    state⁺.ρu = SVector(0, 0, 0)\nend;\n\n# The boundary conditions for `ρ`, `ρu` and `ρcT` are specified here for\n# second-order unknowns\n\nfunction boundary_state!(\n    nf,\n    bc::BottomBC,\n    m::BurgersEquation,\n    state⁺::Vars,\n    diff⁺::Vars,\n    hyperdiff⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    _...,\n)\n    state⁺.ρ = 1\n    state⁺.ρu = SVector(0, 0, 0)\n    state⁺.ρcT = state⁺.ρ * m.c * m.T_bottom\nend;\n\nfunction boundary_state!(\n    nf,\n    bc::TopBC,\n    m::BurgersEquation,\n    state⁺::Vars,\n    diff⁺::Vars,\n    hyperdiff⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    _...,\n)\n    state⁺.ρ = 1\n    state⁺.ρu = SVector(0, 0, 0)\n    diff⁺.α∇ρcT = -n⁻ * m.flux_top\nend;\n\n# # Spatial discretization\n\n# Prescribe polynomial order of basis functions in finite elements\nN_poly = 5;\n\n# Specify the number of vertical elements\nnelem_vert = 10;\n\n# Specify the domain height\nzmax = m.zmax;\n\n# # Temporal discretization\n\n# This initializes the ODE\n# solver, the horizontal explicit vertical implicit scheme\n# with ARK2GiraldoKellyConstantinescu method.\n\node_solver_type = ClimateMachine.HEVISolverType(\n    FT;\n    solver_method = ARK2GiraldoKellyConstantinescu,\n    linear_max_subspace_size = Int(30),\n    linear_atol = FT(-1.0),\n    linear_rtol = FT(1e-5),\n    nonlinear_max_iterations = Int(10),\n    nonlinear_rtol = FT(1e-4),\n    nonlinear_ϵ = FT(1.e-10),\n    preconditioner_update_freq = Int(50),\n)\n\n\n# Establish a `ClimateMachine` single stack configuration\ndriver_config = ClimateMachine.SingleStackConfiguration(\n    \"BurgersEquation\",\n    N_poly,\n    nelem_vert,\n    zmax,\n    param_set,\n    m,\n    numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n);\n\n# # Time discretization\n\n# Specify simulation time (SI units)\nt0 = FT(0);\ntimeend = FT(1);\n\n# We'll define the time-step based on the Fourier\n# number and the [Courant number](https://en.wikipedia.org/wiki/Courant–Friedrichs–Lewy_condition)\n# of the flow\nΔ = min_node_distance(driver_config.grid)\n\ngiven_Fourier = FT(0.5);\nFourier_bound = given_Fourier * Δ^2 / max(m.αh, m.μh, m.νd);\nCourant_bound = FT(0.5) * Δ;\n\n# We define the time step 50 times larger than that defined by CFL law\ndt = FT(50.0) * min(Fourier_bound, Courant_bound)\n\n# # Configure a `ClimateMachine` solver.\n\n# This initializes the state vector and allocates memory for the solution in\n# space (`dg` has the model `m`, which describes the PDEs as well as the\n# function used for initialization). \n\n\nsolver_config = ClimateMachine.SolverConfiguration(\n    t0,\n    timeend,\n    driver_config,\n    ode_dt = dt,\n    ode_solver_type = ode_solver_type,\n);\n\n\n# ## Inspect the initial conditions for a single nodal stack\n\n# Let's export plots of the initial state\noutput_dir = @__DIR__;\n\nmkpath(output_dir);\n\nz_scale = 100 # convert from meters to cm\nz_key = \"z\"\nz_label = \"z [cm]\"\nz = get_z(driver_config.grid; z_scale = z_scale)\nstate_vars = get_vars_from_nodal_stack(\n    driver_config.grid,\n    solver_config.Q,\n    vars_state(m, Prognostic(), FT),\n);\n\n# Create an array to store the solution:\nstate_data = Dict[state_vars]  # store initial condition at ``t=0``\ntime_data = FT[0]                                      # store time data\n\n# Generate plots of initial conditions for the southwest nodal stack\nexport_plot(\n    z,\n    time_data,\n    state_data,\n    (\"ρcT\",),\n    joinpath(output_dir, \"initial_condition_T_nodal_bjfnk.png\");\n    xlabel = \"ρcT at southwest node\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    state_data,\n    (\"ρu[1]\",),\n    joinpath(output_dir, \"initial_condition_u_nodal_bjfnk.png\");\n    xlabel = \"ρu at southwest node\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    state_data,\n    (\"ρu[2]\",),\n    joinpath(output_dir, \"initial_condition_v_nodal_bjfnk.png\");\n    xlabel = \"ρv at southwest node\",\n    ylabel = z_label,\n);\n\n# ![](initial_condition_T_nodal_bjfnk.png)\n# ![](initial_condition_u_nodal_bjfnk.png)\n\n# ## Inspect the initial conditions for the horizontal averages\n\n# Horizontal statistics of variables\n\nstate_vars_var = get_horizontal_variance(\n    driver_config.grid,\n    solver_config.Q,\n    vars_state(m, Prognostic(), FT),\n);\n\nstate_vars_avg = get_horizontal_mean(\n    driver_config.grid,\n    solver_config.Q,\n    vars_state(m, Prognostic(), FT),\n);\n\ndata_avg = Dict[state_vars_avg]\ndata_var = Dict[state_vars_var]\n\nexport_plot(\n    z,\n    time_data,\n    data_avg,\n    (\"ρu[1]\",),\n    joinpath(output_dir, \"initial_condition_avg_u_bjfnk.png\");\n    xlabel = \"Horizontal mean of ρu\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    data_var,\n    (\"ρu[1]\",),\n    joinpath(output_dir, \"initial_condition_variance_u_bjfnk.png\");\n    xlabel = \"Horizontal variance of ρu\",\n    ylabel = z_label,\n);\n\n# ![](initial_condition_avg_u_bjfnk.png)\n# ![](initial_condition_variance_u_bjfnk.png)\n\n# # Solver hooks / callbacks\n\n# Define the number of outputs from `t0` to `timeend`\nconst n_outputs = 5;\nconst every_x_simulation_time = timeend / n_outputs;\n\n# Create a dictionary for `z` coordinate (and convert to cm) NCDatasets IO:\ndims = OrderedDict(z_key => collect(z));\n\n# Create dictionaries to store outputs:\ndata_var = Dict[Dict([k => Dict() for k in 0:n_outputs]...),]\ndata_var[1] = state_vars_var\n\ndata_avg = Dict[Dict([k => Dict() for k in 0:n_outputs]...),]\ndata_avg[1] = state_vars_avg\n\ndata_nodal = Dict[Dict([k => Dict() for k in 0:n_outputs]...),]\ndata_nodal[1] = state_vars\n\n# The `ClimateMachine`'s time-steppers provide hooks, or callbacks, which\n# allow users to inject code to be executed at specified intervals. In this\n# callback, the state variables are collected, combined into a single\n# `OrderedDict` and written to a NetCDF file (for each output step `step`).\nstep = [0];\ncallback = GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n    state_vars_var = get_horizontal_variance(\n        driver_config.grid,\n        solver_config.Q,\n        vars_state(m, Prognostic(), FT),\n    )\n    state_vars_avg = get_horizontal_mean(\n        driver_config.grid,\n        solver_config.Q,\n        vars_state(m, Prognostic(), FT),\n    )\n    state_vars = get_vars_from_nodal_stack(\n        driver_config.grid,\n        solver_config.Q,\n        vars_state(m, Prognostic(), FT),\n        i = 1,\n        j = 1,\n    )\n    step[1] += 1\n    push!(data_var, state_vars_var)\n    push!(data_avg, state_vars_avg)\n    push!(data_nodal, state_vars)\n    push!(time_data, gettime(solver_config.solver))\n    nothing\nend;\n\n# # Solve\n\n# This is the main `ClimateMachine` solver invocation. While users do not have\n# access to the time-stepping loop, code may be injected via `user_callbacks`,\n# which is a `Tuple` of [`GenericCallbacks`](@ref ClimateMachine.GenericCallbacks).\nClimateMachine.invoke!(solver_config; user_callbacks = (callback,))\n\n# # Post-processing\n\n# Our solution has now been calculated and exported to NetCDF files in\n# `output_dir`.\n\n# Let's plot the horizontal statistics of `ρu` and `ρcT`, as well as the evolution of\n# `ρu` for the southwest nodal stack:\nexport_plot(\n    z,\n    time_data,\n    data_avg,\n    (\"ρu[1]\"),\n    joinpath(output_dir, \"solution_vs_time_u_avg_bjfnk.png\");\n    xlabel = \"Horizontal mean of ρu\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    data_var,\n    (\"ρu[1]\"),\n    joinpath(output_dir, \"variance_vs_time_u_bjfnk.png\");\n    xlabel = \"Horizontal variance of ρu\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    data_avg,\n    (\"ρcT\"),\n    joinpath(output_dir, \"solution_vs_time_T_avg_bjfnk.png\");\n    xlabel = \"Horizontal mean of ρcT\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    data_var,\n    (\"ρcT\"),\n    joinpath(output_dir, \"variance_vs_time_T_bjfnk.png\");\n    xlabel = \"Horizontal variance of ρcT\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    data_nodal,\n    (\"ρu[1]\"),\n    joinpath(output_dir, \"solution_vs_time_u_nodal_bjfnk.png\");\n    xlabel = \"ρu at southwest node\",\n    ylabel = z_label,\n);\n# ![](solution_vs_time_u_avg_bjfnk.png)\n# ![](variance_vs_time_u_bjfnk.png)\n# ![](solution_vs_time_T_avg_bjfnk.png)\n# ![](variance_vs_time_T_bjfnk.png)\n# ![](solution_vs_time_u_nodal_bjfnk.png)\n\n# Rayleigh friction returns the horizontal velocity to the objective\n# profile on the timescale of the simulation (1 second), since `γ`∼1. The horizontal viscosity\n# and 2D divergence damping act to reduce the horizontal variance over the same timescale.\n# The initial Gaussian noise is propagated to the temperature field through advection.\n# The horizontal diffusivity acts to reduce this `ρcT` variance in time, although in a longer\n# timescale.\n\n# To run this file, and\n# inspect the solution, include this tutorial in the Julia REPL\n# with:\n\n# ```julia\n# include(joinpath(\"tutorials\", \"Atmos\", \"burgers_single_stack.jl\"))\n# ```\n"
  },
  {
    "path": "tutorials/Atmos/burgers_single_stack_fvm.jl",
    "content": "# # Finite volume single stack tutorial based on the 3D Burgers + tracer equations\n\n# This tutorial implements the Burgers equations with a tracer field\n# in a single element stack. The flow is initialized with a horizontally\n# uniform profile of horizontal velocity and uniform initial temperature. The fluid\n# is heated from the bottom surface. Gaussian noise is imposed to the horizontal\n# velocity field at each node at the start of the simulation. The tutorial demonstrates how to\n#\n#   * Initialize a [`BalanceLaw`](@ref ClimateMachine.BalanceLaws.BalanceLaw) in a single stack configuration;\n#   * Return the horizontal velocity field to a given profile (e.g., large-scale advection);\n#   * Remove any horizontal inhomogeneities or noise from the flow.\n#\n# The second and third bullet points are demonstrated imposing Rayleigh friction, horizontal\n# diffusion and 2D divergence damping to the horizontal momentum prognostic equation.\n\n# Equations solved in balance law form:\n\n# ```math\n# \\begin{align}\n# \\frac{∂ ρ}{∂ t} =& - ∇ ⋅ (ρ\\mathbf{u}) \\\\\n# \\frac{∂ ρ\\mathbf{u}}{∂ t} =& - ∇ ⋅ (-μ ∇\\mathbf{u}) - ∇ ⋅ (ρ\\mathbf{u} \\mathbf{u}') - γ[ (ρ\\mathbf{u}-ρ̄\\mathbf{ū}) - (ρ\\mathbf{u}-ρ̄\\mathbf{ū})⋅ẑ ẑ] - ν_d ∇_h (∇_h ⋅ ρ\\mathbf{u}) \\\\\n# \\frac{∂ ρcT}{∂ t} =& - ∇ ⋅ (-α ∇ρcT) - ∇ ⋅ (\\mathbf{u} ρcT)\n# \\end{align}\n# ```\n\n# Boundary conditions:\n# ```math\n# \\begin{align}\n# z_{\\mathrm{min}}: & ρ = 1 \\\\\n# z_{\\mathrm{min}}: & ρ\\mathbf{u} = \\mathbf{0} \\\\\n# z_{\\mathrm{min}}: & ρcT = ρc T_{\\mathrm{fixed}} \\\\\n# z_{\\mathrm{max}}: & ρ = 1 \\\\\n# z_{\\mathrm{max}}: & ρ\\mathbf{u} = \\mathbf{0} \\\\\n# z_{\\mathrm{max}}: & -α∇ρcT = 0\n# \\end{align}\n# ```\n\n# where\n#  - ``t`` is time\n#  - ``ρ`` is the density\n#  - ``\\mathbf{u}`` is the velocity (vector)\n#  - ``\\mathbf{ū}`` is the horizontally averaged velocity (vector)\n#  - ``μ`` is the dynamic viscosity tensor\n#  - ``γ`` is the Rayleigh friction frequency\n#  - ``ν_d`` is the horizontal divergence damping coefficient\n#  - ``T`` is the temperature\n#  - ``α`` is the thermal diffusivity tensor\n#  - ``c`` is the heat capacity\n#  - ``ρcT`` is the thermal energy\n\n# Solving these equations is broken down into the following steps:\n# 1) Preliminary configuration\n# 2) PDEs\n# 3) Space discretization\n# 4) Time discretization\n# 5) Solver hooks / callbacks\n# 6) Solve\n# 7) Post-processing\n\n# # Preliminary configuration\n\n# ## [Loading code](@id Loading-code-burgers-fvm)\n\n# First, we'll load our pre-requisites\n#  - load external packages:\nusing MPI\nusing Distributions\nusing OrderedCollections\nusing Plots\nusing StaticArrays\nusing LinearAlgebra: Diagonal, tr\n\n#  - load CLIMAParameters and set up to use it:\nusing CLIMAParameters\nusing CLIMAParameters.Planet: grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n#  - load necessary ClimateMachine modules:\nusing ClimateMachine\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Writers\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux, parameter_set\n\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nimport ClimateMachine.DGMethods.FVReconstructions: FVLinear\n\n#  - import necessary ClimateMachine modules: (`import`ing enables us to\n#  provide implementations of these structs/methods)\nusing ClimateMachine.Orientations:\n    Orientation,\n    NoOrientation,\n    FlatOrientation,\n    init_aux!,\n    vertical_unit_vector,\n    projection_tangential\n\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    source!,\n    flux_second_order!,\n    flux_first_order!,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    init_state_auxiliary!,\n    init_state_prognostic!,\n    construct_face_auxiliary_state!,\n    BoundaryCondition,\n    boundary_conditions,\n    boundary_state!\n\n# ## Initialization\n\n# Define the float type (`Float64` or `Float32`)\nconst FT = Float64;\n# Initialize ClimateMachine for CPU.\nClimateMachine.init(; disable_gpu = true);\n\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\n\n# Load some helper functions for plotting\ninclude(joinpath(clima_dir, \"docs\", \"plothelpers.jl\"));\n\n# # Define the set of Partial Differential Equations (PDEs)\n\n# ## Define the model\n\n# Model parameters can be stored in the particular [`BalanceLaw`](@ref\n# ClimateMachine.BalanceLaws.BalanceLaw), in this case, the `BurgersEquation`:\n\nBase.@kwdef struct BurgersEquation{FT, APS, O} <: BalanceLaw\n    \"Parameters\"\n    param_set::APS\n    \"Orientation model\"\n    orientation::O\n    \"Heat capacity\"\n    c::FT = 1\n    \"Vertical dynamic viscosity\"\n    μv::FT = 1e-4\n    \"Horizontal dynamic viscosity\"\n    μh::FT = 1\n    \"Vertical thermal diffusivity\"\n    αv::FT = 1e-2\n    \"Horizontal thermal diffusivity\"\n    αh::FT = 1\n    \"IC Gaussian noise standard deviation\"\n    σ::FT = 5e-2\n    \"Rayleigh damping\"\n    γ::FT = 5\n    \"Domain height\"\n    zmax::FT = 1\n    \"Initial conditions for temperature\"\n    initialT::FT = 295.15\n    \"Bottom boundary value for temperature (Dirichlet boundary conditions)\"\n    T_bottom::FT = 300.0\n    \"Top flux (α∇ρcT) at top boundary (Neumann boundary conditions)\"\n    flux_top::FT = 0.0\n    \"Divergence damping coefficient (horizontal)\"\n    νd::FT = 1\nend\n\n# Create an instance of the `BurgersEquation`:\norientation = FlatOrientation()\n\nm = BurgersEquation{FT, typeof(param_set), typeof(orientation)}(\n    param_set = param_set,\n    orientation = orientation,\n);\n\n# This model dictates the flow control, using [Dynamic Multiple\n# Dispatch](https://en.wikipedia.org/wiki/Multiple_dispatch), for which\n# kernels are executed.\n\n# ## Define the variables\n\n# All of the methods defined in this section were `import`ed in\n# [Loading code](@ref Loading-code-burgers) to let us provide\n# implementations for our `BurgersEquation` as they will be used\n# by the solver.\n\n# Specify auxiliary variables for `BurgersEquation`\nfunction vars_state(m::BurgersEquation, st::Auxiliary, FT)\n    @vars begin\n        coord::SVector{3, FT}\n        orientation::vars_state(m.orientation, st, FT)\n    end\nend\n\n# Specify prognostic variables, the variables solved for in the PDEs, for\n# `BurgersEquation`\nvars_state(::BurgersEquation, ::Prognostic, FT) =\n    @vars(ρ::FT, ρu::SVector{3, FT}, ρcT::FT);\n\n# Specify state variables whose gradients are needed for `BurgersEquation`\nvars_state(::BurgersEquation, ::Gradient, FT) =\n    @vars(u::SVector{3, FT}, ρcT::FT, ρu::SVector{3, FT});\n\n# Specify gradient variables for `BurgersEquation`\nvars_state(::BurgersEquation, ::GradientFlux, FT) = @vars(\n    μ∇u::SMatrix{3, 3, FT, 9},\n    α∇ρcT::SVector{3, FT},\n    νd∇D::SMatrix{3, 3, FT, 9}\n);\n\n# ## Define the compute kernels\n\n# Specify the initial values in `aux::Vars`, which are available in\n# `init_state_prognostic!`. Note that\n# - this method is only called at `t=0`.\n# - `aux.coord` is available here because we've specified `coord` in `vars_state(m, aux, FT)`.\nfunction nodal_init_state_auxiliary!(\n    m::BurgersEquation,\n    aux::Vars,\n    tmp::Vars,\n    geom::LocalGeometry,\n)\n    aux.coord = geom.coord\nend;\n\n# `init_aux!` initializes the auxiliary gravitational potential field needed for vertical projections\nfunction init_state_auxiliary!(\n    m::BurgersEquation,\n    state_auxiliary::MPIStateArray,\n    grid,\n    direction,\n)\n    init_aux!(m, m.orientation, state_auxiliary, grid, direction)\n\n    init_state_auxiliary!(\n        m,\n        nodal_init_state_auxiliary!,\n        state_auxiliary,\n        grid,\n        direction,\n    )\nend;\n\n# Specify the initial values in `state::Vars`. Note that\n# - this method is only called at `t=0`.\n# - `state.ρ`, `state.ρu` and`state.ρcT` are available here because we've specified `ρ`, `ρu` and `ρcT` in `vars_state(m, state, FT)`.\nfunction init_state_prognostic!(\n    m::BurgersEquation,\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t::Real,\n)\n    z = aux.coord[3]\n    ε1 = rand(Normal(0, m.σ))\n    ε2 = rand(Normal(0, m.σ))\n    state.ρ = 1\n    ρu = 1 - 4 * (z - m.zmax / 2)^2 + ε1\n    ρv = 1 - 4 * (z - m.zmax / 2)^2 + ε2\n    ρw = 0\n    state.ρu = SVector(ρu, ρv, ρw)\n\n    state.ρcT = state.ρ * m.c * m.initialT\nend;\n\nfunction construct_face_auxiliary_state!(\n    bl::BurgersEquation,\n    aux_face::AbstractArray,\n    aux_cell::AbstractArray,\n    Δz::FT,\n) where {FT <: Real}\n    param_set = parameter_set(bl)\n    _grav = FT(grav(param_set))\n    var_aux = Vars{vars_state(bl, Auxiliary(), FT)}\n    aux_face .= aux_cell\n\n    if !(bl.orientation isa NoOrientation)\n        var_aux(aux_face).orientation.Φ =\n            var_aux(aux_cell).orientation.Φ + _grav * Δz / 2\n    end\nend\n\n\n# The remaining methods, defined in this section, are called at every\n# time-step in the solver by the [`BalanceLaw`](@ref\n# ClimateMachine.BalanceLaws.BalanceLaw) framework.\n\n# Since we have second-order fluxes, we must tell `ClimateMachine` to compute\n# the gradient of `ρcT`, `u` and `ρu`. Here, we specify how `ρcT`, `u` and `ρu` are computed. Note that\n# e.g. `transform.ρcT` is available here because we've specified `ρcT` in `vars_state(m, ::Gradient, FT)`.\nfunction compute_gradient_argument!(\n    m::BurgersEquation,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    transform.ρcT = state.ρcT\n    transform.u = state.ρu / state.ρ\n    transform.ρu = state.ρu\nend;\n\n# Specify where in `diffusive::Vars` to store the computed gradient from\n# `compute_gradient_argument!`. Note that:\n#  - `diffusive.μ∇u` is available here because we've specified `μ∇u` in `vars_state(m, ::GradientFlux, FT)`.\n#  - `∇transform.u` is available here because we've specified `u` in `vars_state(m, ::Gradient, FT)`.\n#  - `diffusive.μ∇u` is built using an anisotropic diffusivity tensor.\n#  - The `divergence` may be computed from the trace of tensor `∇ρu`.\nfunction compute_gradient_flux!(\n    m::BurgersEquation{FT},\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n) where {FT}\n    param_set = parameter_set(m)\n    ∇ρu = ∇transform.ρu\n    ẑ = vertical_unit_vector(m.orientation, param_set, aux)\n    divergence = tr(∇ρu) - ẑ' * ∇ρu * ẑ\n    diffusive.α∇ρcT = Diagonal(SVector(m.αh, m.αh, m.αv)) * ∇transform.ρcT\n    diffusive.μ∇u = Diagonal(SVector(m.μh, m.μh, m.μv)) * ∇transform.u\n    diffusive.νd∇D =\n        Diagonal(SVector(m.νd, m.νd, FT(0))) *\n        Diagonal(SVector(divergence, divergence, FT(0)))\nend;\n\n# Introduce Rayleigh friction towards a target profile as a source.\n# Note that:\n# - Rayleigh damping is only applied in the horizontal using the `projection_tangential` method.\nfunction source!(\n    m::BurgersEquation{FT},\n    source::Vars,\n    state::Vars,\n    diffusive::Vars,\n    aux::Vars,\n    args...,\n) where {FT}\n    param_set = parameter_set(m)\n    ẑ = vertical_unit_vector(m.orientation, param_set, aux)\n    z = aux.coord[3]\n    ρ̄ū =\n        state.ρ * SVector{3, FT}(\n            0.5 - 2 * (z - m.zmax / 2)^2,\n            0.5 - 2 * (z - m.zmax / 2)^2,\n            0.0,\n        )\n    ρu_p = state.ρu - ρ̄ū\n    source.ρu -=\n        m.γ * projection_tangential(m.orientation, param_set, aux, ρu_p)\nend;\n\n# Compute advective flux.\n# Note that:\n# - `state.ρu` is available here because we've specified `ρu` in `vars_state(m, state, FT)`.\nfunction flux_first_order!(\n    m::BurgersEquation,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    _...,\n)\n    flux.ρ = state.ρu\n\n    u = state.ρu / state.ρ\n    flux.ρu = state.ρu * u'\n    flux.ρcT = u * state.ρcT\nend;\n\n# Compute diffusive flux (e.g. ``F(μ, \\mathbf{u}, t) = -μ∇\\mathbf{u}`` in the original PDE).\n# Note that:\n# - `diffusive.μ∇u` is available here because we've specified `μ∇u` in `vars_state(m, ::GradientFlux, FT)`.\n# - The divergence gradient can be written as a diffusive flux using a divergence diagonal tensor.\nfunction flux_second_order!(\n    m::BurgersEquation,\n    flux::Grad,\n    state::Vars,\n    diffusive::Vars,\n    hyperdiffusive::Vars,\n    aux::Vars,\n    t::Real,\n)\n    flux.ρcT -= diffusive.α∇ρcT\n    flux.ρu -= diffusive.μ∇u\n    flux.ρu -= diffusive.νd∇D\nend;\n\n# ### Boundary conditions\n\n# Second-order terms in our equations, ``∇⋅(G)`` where ``G = μ∇\\mathbf{u}``, are\n# internally reformulated to first-order unknowns.\n# Boundary conditions must be specified for all unknowns, both first-order and\n# second-order unknowns which have been reformulated.\n\nstruct TopBC <: BoundaryCondition end;\nstruct BottomBC <: BoundaryCondition end;\nboundary_conditions(::BurgersEquation) = (BottomBC(), TopBC());\n\n# The boundary conditions for `ρ`, `ρu` and `ρcT` (first order unknowns)\nfunction boundary_state!(\n    nf,\n    bc::BottomBC,\n    m::BurgersEquation,\n    state⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    _...,\n)\n    state⁺.ρ = 1\n    state⁺.ρu = SVector(0, 0, 0)\n    state⁺.ρcT = state⁺.ρ * m.c * m.T_bottom\nend;\nfunction boundary_state!(\n    nf,\n    bc::TopBC,\n    m::BurgersEquation,\n    state⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    _...,\n)\n    state⁺.ρ = 1\n    state⁺.ρu = SVector(0, 0, 0)\nend;\n\n# The boundary conditions for `ρ`, `ρu` and `ρcT` are specified here for\n# second-order unknowns\nfunction boundary_state!(\n    nf,\n    bc::BottomBC,\n    m::BurgersEquation,\n    state⁺::Vars,\n    diff⁺::Vars,\n    hyperdiff⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    _...,\n)\n    state⁺.ρ = 1\n    state⁺.ρu = SVector(0, 0, 0)\n    state⁺.ρcT = state⁺.ρ * m.c * m.T_bottom\nend;\nfunction boundary_state!(\n    nf,\n    bc::TopBC,\n    m::BurgersEquation,\n    state⁺::Vars,\n    diff⁺::Vars,\n    hyperdiff⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    _...,\n)\n    state⁺.ρ = 1\n    state⁺.ρu = SVector(0, 0, 0)\n    diff⁺.α∇ρcT = -n⁻ * m.flux_top\nend;\n\n# # Spatial discretization\n\n# Prescribe polynomial order of basis functions in finite elements\n# The second index 0 indicates that finite volume method is \n# applied in the vertical direction\nN_poly = (1, 0);\n\n# Specify the number of vertical elements\nnelem_vert = 50;\n\n# Specify the domain height\nzmax = m.zmax;\n\n# Establish a `ClimateMachine` single stack configuration\ndriver_config = ClimateMachine.SingleStackConfiguration(\n    \"BurgersEquation\",\n    N_poly,\n    nelem_vert,\n    zmax,\n    param_set,\n    m,\n    numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n    fv_reconstruction = FVLinear(),\n);\n\n# # Time discretization\n\n# Specify simulation time (SI units)\nt0 = FT(0);\ntimeend = FT(1);\n\n# We'll define the time-step based on the Fourier\n# number and the [Courant number](https://en.wikipedia.org/wiki/Courant–Friedrichs–Lewy_condition)\n# of the flow\nΔ = min_node_distance(driver_config.grid)\n\ngiven_Fourier = FT(0.5);\nFourier_bound = given_Fourier * Δ^2 / max(m.αh, m.μh, m.νd);\nCourant_bound = FT(0.5) * Δ;\ndt = min(Fourier_bound, Courant_bound)\n\n# # Configure a `ClimateMachine` solver.\n\n# This initializes the state vector and allocates memory for the solution in\n# space (`dg` has the model `m`, which describes the PDEs as well as the\n# function used for initialization). This additionally initializes the ODE\n# solver, by default an explicit Low-Storage\n# [Runge-Kutta](https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods)\n# method.\n\nsolver_config =\n    ClimateMachine.SolverConfiguration(t0, timeend, driver_config, ode_dt = dt);\n\n# ## Inspect the initial conditions for a single nodal stack\n\n# Let's export plots of the initial state\noutput_dir = @__DIR__;\n\nmkpath(output_dir);\n\nz_scale = 100 # convert from meters to cm\nz_key = \"z\"\nz_label = \"z [cm]\"\nz = get_z(driver_config.grid; z_scale = z_scale)\nstate_vars = get_vars_from_nodal_stack(\n    driver_config.grid,\n    solver_config.Q,\n    vars_state(m, Prognostic(), FT),\n);\n\n# Create an array to store the solution:\nstate_data = Dict[state_vars]  # store initial condition at ``t=0``\ntime_data = FT[0]                                      # store time data\n\n# Generate plots of initial conditions for the southwest nodal stack\nexport_plot(\n    z,\n    time_data,\n    state_data,\n    (\"ρcT\",),\n    joinpath(output_dir, \"initial_condition_T_nodal_fvm.png\");\n    xlabel = \"ρcT at southwest node\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    state_data,\n    (\"ρu[1]\",),\n    joinpath(output_dir, \"initial_condition_u_nodal_fvm.png\");\n    xlabel = \"ρu at southwest node\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    state_data,\n    (\"ρu[2]\",),\n    joinpath(output_dir, \"initial_condition_v_nodal_fvm.png\");\n    xlabel = \"ρv at southwest node\",\n    ylabel = z_label,\n);\n\n# ![](initial_condition_T_nodal_fvm.png)\n# ![](initial_condition_u_nodal_fvm.png)\n\n# ## Inspect the initial conditions for the horizontal averages\n\n# Horizontal statistics of variables\n\nstate_vars_var = get_horizontal_variance(\n    driver_config.grid,\n    solver_config.Q,\n    vars_state(m, Prognostic(), FT),\n);\n\nstate_vars_avg = get_horizontal_mean(\n    driver_config.grid,\n    solver_config.Q,\n    vars_state(m, Prognostic(), FT),\n);\n\ndata_avg = Dict[state_vars_avg]\ndata_var = Dict[state_vars_var]\n\nexport_plot(\n    z,\n    time_data,\n    data_avg,\n    (\"ρu[1]\",),\n    joinpath(output_dir, \"initial_condition_avg_u_fvm.png\");\n    xlabel = \"Horizontal mean of ρu\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    data_var,\n    (\"ρu[1]\",),\n    joinpath(output_dir, \"initial_condition_variance_u_fvm.png\");\n    xlabel = \"Horizontal variance of ρu\",\n    ylabel = z_label,\n);\n\n# ![](initial_condition_avg_u_fvm.png)\n# ![](initial_condition_variance_u_fvm.png)\n\n# # Solver hooks / callbacks\n\n# Define the number of outputs from `t0` to `timeend`\nconst n_outputs = 5;\nconst every_x_simulation_time = timeend / n_outputs;\n\n# Create a dictionary for `z` coordinate (and convert to cm) NCDatasets IO:\ndims = OrderedDict(z_key => collect(z));\n\n# Create dictionaries to store outputs:\ndata_var = Dict[Dict([k => Dict() for k in 0:n_outputs]...),]\ndata_var[1] = state_vars_var\n\ndata_avg = Dict[Dict([k => Dict() for k in 0:n_outputs]...),]\ndata_avg[1] = state_vars_avg\n\ndata_nodal = Dict[Dict([k => Dict() for k in 0:n_outputs]...),]\ndata_nodal[1] = state_vars\n\n# The `ClimateMachine`'s time-steppers provide hooks, or callbacks, which\n# allow users to inject code to be executed at specified intervals. In this\n# callback, the state variables are collected, combined into a single\n# `OrderedDict` and written to a NetCDF file (for each output step).\ncallback = GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n    state_vars_var = get_horizontal_variance(\n        driver_config.grid,\n        solver_config.Q,\n        vars_state(m, Prognostic(), FT),\n    )\n    state_vars_avg = get_horizontal_mean(\n        driver_config.grid,\n        solver_config.Q,\n        vars_state(m, Prognostic(), FT),\n    )\n    state_vars = get_vars_from_nodal_stack(\n        driver_config.grid,\n        solver_config.Q,\n        vars_state(m, Prognostic(), FT),\n        i = 1,\n        j = 1,\n    )\n    push!(data_var, state_vars_var)\n    push!(data_avg, state_vars_avg)\n    push!(data_nodal, state_vars)\n    push!(time_data, gettime(solver_config.solver))\n    nothing\nend;\n\n# # Solve\n\n# This is the main `ClimateMachine` solver invocation. While users do not have\n# access to the time-stepping loop, code may be injected via `user_callbacks`,\n# which is a `Tuple` of [`GenericCallbacks`](@ref ClimateMachine.GenericCallbacks).\nClimateMachine.invoke!(solver_config; user_callbacks = (callback,))\n\n# # Post-processing\n\n# Our solution has now been calculated and exported to NetCDF files in\n# `output_dir`.\n\n# Let's plot the horizontal statistics of `ρu` and `ρcT`, as well as the evolution of\n# `ρu` for the southwest nodal stack:\nexport_plot(\n    z,\n    time_data,\n    data_avg,\n    (\"ρu[1]\"),\n    joinpath(output_dir, \"solution_vs_time_u_avg_fvm.png\");\n    xlabel = \"Horizontal mean of ρu\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    data_var,\n    (\"ρu[1]\"),\n    joinpath(output_dir, \"variance_vs_time_u_fvm.png\");\n    xlabel = \"Horizontal variance of ρu\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    data_avg,\n    (\"ρcT\"),\n    joinpath(output_dir, \"solution_vs_time_T_avg_fvm.png\");\n    xlabel = \"Horizontal mean of ρcT\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    data_var,\n    (\"ρcT\"),\n    joinpath(output_dir, \"variance_vs_time_T_fvm.png\");\n    xlabel = \"Horizontal variance of ρcT\",\n    ylabel = z_label,\n);\nexport_plot(\n    z,\n    time_data,\n    data_nodal,\n    (\"ρu[1]\"),\n    joinpath(output_dir, \"solution_vs_time_u_nodal_fvm.png\");\n    xlabel = \"ρu at southwest node\",\n    ylabel = z_label,\n);\n# ![](solution_vs_time_u_avg_fvm.png)\n# ![](variance_vs_time_u_fvm.png)\n# ![](solution_vs_time_T_avg_fvm.png)\n# ![](variance_vs_time_T_fvm.png)\n# ![](solution_vs_time_u_nodal_fvm.png)\n\n# Rayleigh friction returns the horizontal velocity to the objective\n# profile on the timescale of the simulation (1 second), since `γ`∼1. The horizontal viscosity\n# and 2D divergence damping act to reduce the horizontal variance over the same timescale.\n# The initial Gaussian noise is propagated to the temperature field through advection.\n# The horizontal diffusivity acts to reduce this `ρcT` variance in time, although in a longer\n# timescale.\n\n# To run this file, and\n# inspect the solution, include this tutorial in the Julia REPL\n# with:\n\n# ```julia\n# include(joinpath(\"tutorials\", \"Atmos\", \"burgers_single_stack.jl\"))\n# ```\n"
  },
  {
    "path": "tutorials/Atmos/densitycurrent.jl",
    "content": "# # Density Current\n#\n# In this example, we demonstrate the usage of the `ClimateMachine`\n#  to solve the density current test by Straka 1993.\n# We solve a flow in a box configuration, which is\n# representative of a large-eddy simulation. Several versions of the problem\n# setup may be found in literature, but the general idea is to examine the\n# vertical ascent of a thermal _bubble_ (we can interpret these as simple\n# representation of convective updrafts).\n#\n# ## Description of experiment\n# The setup described below is such that the simulation reaches completion\n# (timeend = 900 s) in approximately 4 minutes of wall-clock time on 1 GPU\n#\n# 1) Dry Density Current (circular potential temperature perturbation)\n# 2) Boundaries\n#    - `Impenetrable(FreeSlip())` - no momentum flux, no mass flux through\n#      walls.\n#    - `Impermeable()` - non-porous walls, i.e. no diffusive fluxes through\n#       walls.\n# 3) Domain - 25600m (horizontal) x 10000m (horizontal) x 6400m (vertical)\n# 4) Resolution - 100m effective resolution\n# 5) Total simulation time - 900s\n# 6) Mesh Aspect Ratio (Effective resolution) 1:1\n# 7) Overrides defaults for\n#    - CPU Initialisation\n#    - Time integrator\n#    - Sources\n#    - Smagorinsky Coefficient _C_smag\n# 8) Default settings can be found in `src/Driver/<files>.jl`\n\n#md # !!! note\n#md #     This experiment setup assumes that you have installed the\n#md #     `ClimateMachine` according to the instructions on the landing page.\n#md #     We assume the users' familiarity with the conservative form of the\n#md #     equations of motion for a compressible fluid\n#md #\n#md #     The following topics are covered in this example\n#md #     - Package requirements\n#md #     - Defining a `model` subtype for the set of conservation equations\n#md #     - Defining the initial conditions\n#md #     - Applying boundary conditions\n#md #     - Applying source terms\n#md #     - Choosing a turbulence model\n#md #     - Adding tracers to the model\n#md #     - Choosing a time-integrator\n#md #\n#md #     The following topics are not covered in this example\n#md #     - Defining new boundary conditions\n#md #     - Defining new turbulence models\n#md #     - Building new time-integrators\n#\n# ## Boilerplate (Using Modules)\n#\n# #### [Skip Section](@ref init-dc)\n#\n# Before setting up our experiment, we recognize that we need to import some\n# pre-defined functions from other packages. Julia allows us to use existing\n# modules (variable workspaces), or write our own to do so.  Complete\n# documentation for the Julia module system can be found\n# [here](https://docs.julialang.org/en/v1/manual/modules/#).\n\n# We need to use the `ClimateMachine` module! This imports all functions\n# specific to atmospheric and ocean flow modeling.  While we do not cover the\n# ins-and-outs of the contents of each of these we provide brief descriptions\n# of the utility of each of the loaded packages.\nusing ClimateMachine\nClimateMachine.init(parse_clargs = true)\n\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\n# - Required so that we inherit the appropriate model types for the large-eddy\n#   simulation (LES) and global-circulation-model (GCM) configurations.\nusing ClimateMachine.ConfigTypes\n# - Required so that we may define diagnostics configurations, e.g. choice of\n#   file-writer, choice of output variable sets, output-frequency and directory,\nusing ClimateMachine.Diagnostics\n# - Required so that we may define (or utilise existing functions) functions\n#   that are `called-back` or executed at frequencies of either timesteps,\n#   simulation-time, or wall-clock time.\nusing ClimateMachine.GenericCallbacks\n# - Required so we load the appropriate functions for the time-integration\n#   component. Contains ODESolver methods.\nusing ClimateMachine.ODESolvers\n# - Required for utility of spatial filtering functions (e.g. positivity\n#   preservation)\nusing ClimateMachine.Mesh.Filters\n# - Required so functions for computation of temperature profiles.\nusing Thermodynamics.TemperatureProfiles\n# - Required so functions for computation of moist thermodynamic quantities and turbulence closures\n# are available.\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\n# - Required so we may access our variable arrays by a sensible naming\n#   convention rather than by numerical array indices.\nusing ClimateMachine.VariableTemplates\n# - Required so we may access planet parameters\n#   ([CLIMAParameters](https://github.com/CliMA/CLIMAParameters.jl)\n#   specific to this problem include the gas constant, specific heats,\n#   mean-sea-level pressure, gravity and the Smagorinsky coefficient)\n\n# In ClimateMachine we use `StaticArrays` for our variable arrays.\nusing StaticArrays\n# We also use the `Test` package to help with unit tests and continuous\n# integration systems to design sensible tests for our experiment to ensure new\n# / modified blocks of code don't damage the fidelity of the physics. The test\n# defined within this experiment is not a unit test for a specific\n# subcomponent, but ensures time-integration of the defined problem conditions\n# within a reasonable tolerance. Immediately useful macros and functions from\n# this include `@test` and `@testset` which will allow us to define the testing\n# parameter sets.\nusing Test\n\nusing CLIMAParameters\nusing CLIMAParameters.Atmos.SubgridScale: C_smag\nusing CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n# ## [Initial Conditions](@id init-dc)\n#md # !!! note\n#md #     The following variables are assigned in the initial condition\n#md #     - `state.ρ` = Scalar quantity for initial density profile\n#md #     - `state.ρu`= 3-component vector for initial momentum profile\n#md #     - `state.energy.ρe`= Scalar quantity for initial total-energy profile\n#md #       humidity\n#md #     - `state.tracers.ρχ` = Vector of four tracers (here, for demonstration\n#md #       only; we can interpret these as dye injections for visualisation\n#md #       purposes)\nfunction init_densitycurrent!(problem, bl, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n\n    ## Problem float-type\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n\n    ## Unpack constant parameters\n    R_gas::FT = R_d(param_set)\n    c_p::FT = cp_d(param_set)\n    c_v::FT = cv_d(param_set)\n    p0::FT = MSLP(param_set)\n    _grav::FT = grav(param_set)\n    γ::FT = c_p / c_v\n\n    ## Define bubble center and background potential temperature\n    xc::FT = 0\n    yc::FT = 0\n    zc::FT = 3000\n    rx::FT = 4000\n    rz::FT = 2000\n    r = sqrt(((x - xc)^2) / rx^2 + ((z - zc)^2) / rz^2)\n\n    ## TODO: clean this up, or add convenience function:\n    ## This is configured in the reference hydrostatic state\n    ref_state = reference_state(bl)\n    θ_ref::FT = ref_state.virtual_temperature_profile.T_surface\n    Δθ::FT = 0\n    θamplitude::FT = -15.0\n\n    ## Compute temperature difference over bubble region\n    if r <= 1\n        Δθ = 0.5 * θamplitude * (1 + cospi(r))\n    end\n\n    ## Compute perturbed thermodynamic state:\n    θ = θ_ref + Δθ                                      ## potential temperature\n    π_exner = FT(1) - _grav / (c_p * θ) * z             ## exner pressure\n    ρ = p0 / (R_gas * θ) * (π_exner)^(c_v / R_gas)      ## density\n    T = θ * π_exner\n    e_int = internal_energy(param_set, T)\n    ts = PhaseDry(param_set, e_int, ρ)\n    ρu = SVector(FT(0), FT(0), FT(0))                   ## momentum\n    ## State (prognostic) variable assignment\n    e_kin = FT(0)                                       ## kinetic energy\n    e_pot = gravitational_potential(bl.orientation, aux)## potential energy\n    ρe_tot = ρ * total_energy(e_kin, e_pot, ts)         ## total energy\n\n    ## Assign State Variables\n    state.ρ = ρ\n    state.ρu = ρu\n    state.energy.ρe = ρe_tot\nend\n\n# ## [Model Configuration](@id config-helper)\n# We define a configuration function to assist in prescribing the physical\n# model.\nfunction config_densitycurrent(\n    ::Type{FT},\n    N,\n    resolution,\n    xmax,\n    ymax,\n    zmax,\n) where {FT}\n\n    ## The model coefficient for the turbulence closure is defined via the\n    ## [CLIMAParameters\n    ## package](https://CliMA.github.io/CLIMAParameters.jl/dev/) A reference\n    ## state for the linearisation step is also defined.\n    T_surface = FT(300)\n    T_min_ref = FT(0)\n    T_profile = DryAdiabaticProfile{FT}(param_set, T_surface, T_min_ref)\n    ref_state = HydrostaticState(T_profile)\n\n    ## The fun part! Here we assemble the `AtmosModel`.\n    ##md # !!! note\n    ##md #     Docs on model subcomponent options can be found here:\n    ##md #     - [`param_set`](https://CliMA.github.io/CLIMAParameters.jl/dev/)\n    ##md #     - [`turbulence`](@ref Turbulence-Closures-docs)\n    ##md #     - [`source`](@ref atmos-sources)\n    ##md #     - [`init_state`](@ref init-dc)\n\n    _C_smag = FT(0.21)\n    physics = AtmosPhysics{FT}(\n        param_set;                                      # Parameter set corresponding to earth parameters\n        ref_state = ref_state,                          # Reference state\n        turbulence = Vreman(_C_smag),                   # Turbulence closure model\n        moisture = DryModel(),                          # Exclude moisture variables\n        tracers = NoTracers(),                          # Tracer model with diffusivity coefficients\n    )\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,                             # Flow in a box, requires the AtmosLESConfigType\n        physics;                                        # Atmos physics\n        init_state_prognostic = init_densitycurrent!,   # Apply the initial condition\n        source = (Gravity(),),                          # Gravity is the only source term here\n    )\n\n    ## Finally, we pass a `Problem Name` string, the mesh information, and the\n    ## model type to  the [`AtmosLESConfiguration`](@ref ClimateMachine.AtmosLESConfiguration) object.\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"DryDensitycurrent\",      # Problem title [String]\n        N,                        # Polynomial order [Int]\n        resolution,               # (Δx, Δy, Δz) effective resolution [m]\n        xmax,                     # Domain maximum size [m]\n        ymax,                     # Domain maximum size [m]\n        zmax,                     # Domain maximum size [m]\n        param_set,                # Parameter set.\n        init_densitycurrent!,     # Function specifying initial condition\n        model = model,            # Model type\n        periodicity = (false, false, false),\n        boundary = ((1, 1), (1, 1), (1, 1)),   # Set all boundaries to solid walls\n    )\n    return config\nend\n\n#md # !!! note\n#md #     `Keywords` are used to specify some arguments (see appropriate source\n#md #     files).\n\nfunction main()\n    ## These are essentially arguments passed to the\n    ## [`config_densitycurrent`](@ref config-helper) function.  For type\n    ## consistency we explicitly define the problem floating-precision.\n    FT = Float64\n    ## We need to specify the polynomial order for the DG discretization,\n    ## effective resolution, simulation end-time, the domain bounds, and the\n    ## courant-number for the time-integrator. Note how the time-integration\n    ## components `solver_config` are distinct from the spatial / model\n    ## components in `driver_config`. `init_on_cpu` is a helper keyword argument\n    ## that forces problem initialisation on CPU (thereby allowing the use of\n    ## random seeds, spline interpolants and other special functions at the\n    ## initialisation step.)\n    N = 4\n    Δx = FT(100)\n    Δy = FT(250)\n    Δv = FT(100)\n    resolution = (Δx, Δy, Δv)\n    xmax = FT(25600)\n    ymax = FT(1000)\n    zmax = FT(6400)\n    t0 = FT(0)\n    timeend = FT(100)\n    CFL = FT(1.5)\n\n    ## Assign configurations so they can be passed to the `invoke!` function\n    driver_config = config_densitycurrent(FT, N, resolution, xmax, ymax, zmax)\n\n    ## Choose an Explicit Single-rate Solver LSRK144 from the existing [ODESolvers](@ref\n    ## ODESolvers-docs) options Apply the outer constructor to define the\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK144NiegemannDiehlBusch,\n    )\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFL,\n    )\n\n    ## Invoke solver (calls `solve!` function for time-integrator), pass the driver, solver and diagnostic config\n    ## information.\n    result =\n        ClimateMachine.invoke!(solver_config; check_euclidean_distance = true)\n\n    ## Check that the solution norm is reasonable.\n    @test isapprox(result, FT(1); atol = 1.5e-2)\nend\n\n# The experiment definition is now complete. Time to run it.\n# `julia --project=$CLIMA_HOME tutorials/Atmos/densitycurrent.jl --vtk 1smins`\n# to run with VTK output enabled at intervals of 1 simulation minute.\n#\n# ## References\n#\n# - [Straka1993](@cite)\n# - [Carpenter1990](@cite)\n\nmain()\n"
  },
  {
    "path": "tutorials/Atmos/dry_rayleigh_benard.jl",
    "content": "# # Dry Rayleigh Benard\n\n# ## Problem description\n#\n# 1) Dry Rayleigh Benard Convection (re-entrant channel configuration)\n# 2) Boundaries - `Sides` : Periodic (Default `bctuple` used to identify bot,top walls)\n#                 `Top`   : Prescribed temperature, no-slip\n#                 `Bottom`: Prescribed temperature, no-slip\n# 3) Domain - 250m[horizontal] x 250m[horizontal] x 500m[vertical]\n# 4) Timeend - 100s\n# 5) Mesh Aspect Ratio (Effective resolution) 1:1\n# 6) Random values in initial condition (Requires `init_on_cpu=true` argument)\n# 7) Overrides defaults for\n#               `C_smag`\n#               `Courant_number`\n#               `init_on_cpu`\n#               `ref_state`\n#               `solver_type`\n#               `bc`\n#               `sources`\n# 8) Default settings can be found in src/Driver/Configurations.jl\n\n# ## Loading code\nusing Distributions\nusing Random\nusing StaticArrays\nusing Test\nusing DocStringExtensions\nusing Printf\n\nusing ClimateMachine\nClimateMachine.init()\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.Mesh.Filters\nusing Thermodynamics: PhaseEquil_pTq, internal_energy\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\n\nusing CLIMAParameters\nusing CLIMAParameters.Planet: R_d, cp_d, cv_d, grav, MSLP\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n# Convenience struct for sharing data between kernels\nstruct DryRayleighBenardConvectionDataConfig{FT}\n    xmin::FT\n    ymin::FT\n    zmin::FT\n    xmax::FT\n    ymax::FT\n    zmax::FT\n    T_bot::FT\n    T_lapse::FT\n    T_top::FT\nend\n\n# Define initial condition kernel\nfunction init_problem!(problem, bl, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n\n    dc = bl.data_config\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n\n    _R_d::FT = R_d(param_set)\n    _cp_d::FT = cp_d(param_set)\n    _grav::FT = grav(param_set)\n    _cv_d::FT = cv_d(param_set)\n    _MSLP::FT = MSLP(param_set)\n\n    γ::FT = _cp_d / _cv_d\n    δT =\n        sinpi(6 * z / (dc.zmax - dc.zmin)) *\n        cospi(6 * z / (dc.zmax - dc.zmin)) + rand()\n    δw =\n        sinpi(6 * z / (dc.zmax - dc.zmin)) *\n        cospi(6 * z / (dc.zmax - dc.zmin)) + rand()\n    ΔT = _grav / _cv_d * z + δT\n    T = dc.T_bot - ΔT\n    P = _MSLP * (T / dc.T_bot)^(_grav / _R_d / dc.T_lapse)\n    ρ = P / (_R_d * T)\n\n    q_tot = FT(0)\n    e_pot = gravitational_potential(bl.orientation, aux)\n    ts = PhaseEquil_pTq(param_set, P, T, q_tot)\n\n    ρu, ρv, ρw = FT(0), FT(0), ρ * δw\n\n    e_int = internal_energy(ts)\n    e_kin = FT(1 / 2) * δw^2\n\n    ρe_tot = ρ * (e_int + e_pot + e_kin)\n    state.ρ = ρ\n    state.ρu = SVector(ρu, ρv, ρw)\n    state.energy.ρe = ρe_tot\n    state.moisture.ρq_tot = FT(0)\n    ρχ = zero(FT)\n    if z <= 100\n        ρχ += FT(0.1) * (cospi(z / 2 / 100))^2\n    end\n    state.tracers.ρχ = SVector{1, FT}(ρχ)\nend\n\n# Define problem configuration kernel\nfunction config_problem(::Type{FT}, N, resolution, xmax, ymax, zmax) where {FT}\n\n    ## Boundary conditions\n    T_bot = FT(299)\n\n    _cp_d::FT = cp_d(param_set)\n    _grav::FT = grav(param_set)\n\n    T_lapse = FT(_grav / _cp_d)\n    T_top = T_bot - T_lapse * zmax\n\n    ntracers = 1\n    δ_χ = SVector{ntracers, FT}(1)\n\n    ## Turbulence\n    C_smag = FT(0.23)\n    data_config = DryRayleighBenardConvectionDataConfig{FT}(\n        0,\n        0,\n        0,\n        xmax,\n        ymax,\n        zmax,\n        T_bot,\n        T_lapse,\n        FT(T_bot - T_lapse * zmax),\n    )\n\n    ## Define the physics\n    physics = AtmosPhysics{FT}(\n        param_set;\n        turbulence = Vreman(C_smag),\n        tracers = NTracers{ntracers, FT}(δ_χ),\n    )\n\n    ## Set up the problem\n    problem = AtmosProblem(;\n        physics = physics,\n        boundaryconditions = (\n            AtmosBC(\n                physics;\n                momentum = Impenetrable(NoSlip()),\n                energy = PrescribedTemperature((state, aux, t) -> T_bot),\n            ),\n            AtmosBC(\n                physics;\n                momentum = Impenetrable(NoSlip()),\n                energy = PrescribedTemperature((state, aux, t) -> T_top),\n            ),\n        ),\n        init_state_prognostic = init_problem!,\n    )\n\n    ## Set up the model\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,\n        physics;\n        problem = problem,\n        source = (Gravity(),),\n        data_config = data_config,\n    )\n\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"DryRayleighBenardConvection\",\n        N,\n        resolution,\n        xmax,\n        ymax,\n        zmax,\n        param_set,\n        init_problem!,\n        model = model,\n    )\n    return config\nend\n\n# Define diagnostics configuration kernel\nfunction config_diagnostics(driver_config)\n    interval = \"10000steps\"\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosLESConfigType(),\n        interval,\n        driver_config.name,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp])\nend\n\n# Define main entry point kernel\nfunction main()\n    FT = Float64\n    ## DG polynomial order\n    N = 4\n    ## Domain resolution and size\n    Δh = FT(10)\n    ## Time integrator setup\n    t0 = FT(0)\n    ## Courant number\n    CFLmax = FT(20)\n    timeend = FT(1000)\n    xmax, ymax, zmax = FT(250), FT(250), FT(500)\n\n    @testset \"DryRayleighBenardTest\" begin\n        for Δh in Δh\n            Δv = Δh\n            resolution = (Δh, Δh, Δv)\n            driver_config = config_problem(FT, N, resolution, xmax, ymax, zmax)\n\n            ## Set up the time-integrator, using a multirate infinitesimal step\n            ## method. The option `splitting_type = ClimateMachine.SlowFastSplitting()`\n            ## separates fast-slow modes by splitting away the acoustic waves and\n            ## treating them via a sub-stepped explicit method.\n            ode_solver_type = ClimateMachine.MISSolverType(;\n                splitting_type = ClimateMachine.SlowFastSplitting(),\n                mis_method = MIS2,\n                fast_method = LSRK144NiegemannDiehlBusch,\n                nsubsteps = (40,),\n            )\n\n            solver_config = ClimateMachine.SolverConfiguration(\n                t0,\n                timeend,\n                driver_config,\n                ode_solver_type = ode_solver_type,\n                init_on_cpu = true,\n                Courant_number = CFLmax,\n            )\n            dgn_config = config_diagnostics(driver_config)\n            ## User defined callbacks (TMAR positivity preserving filter)\n            cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n                Filters.apply!(\n                    solver_config.Q,\n                    (\"moisture.ρq_tot\",),\n                    solver_config.dg.grid,\n                    TMARFilter(),\n                )\n                nothing\n            end\n            result = ClimateMachine.invoke!(\n                solver_config;\n                diagnostics_config = dgn_config,\n                user_callbacks = (cbtmarfilter,),\n                check_euclidean_distance = true,\n            )\n            ## result == engf/eng0\n            @test isapprox(result, FT(1); atol = 1.5e-2)\n        end\n    end\nend\n\n# Run\nmain()\n"
  },
  {
    "path": "tutorials/Atmos/heldsuarez.jl",
    "content": "# # Dry atmosphere GCM with Held-Suarez forcing\n#\n# The Held-Suarez setup (Held and Suarez, 1994) is a textbook example for a\n# simplified atmospheric global circulation model configuration which has been\n# used as a benchmark experiment for development of the dynamical cores (i.e.,\n# GCMs without continents, moisture or parametrization schemes of the physics)\n# for atmospheric models.  It is forced by a thermal relaxation to a reference\n# state and damped by linear (Rayleigh) friction. This example demonstrates how\n#\n#   * to set up a ClimateMachine-Atmos GCM configuration;\n#   * to select and save GCM diagnostics output.\n#\n# To begin, we load ClimateMachine and a few miscellaneous useful Julia packages.\nusing Distributions\nusing Random\nusing StaticArrays\nusing UnPack\n\n# ClimateMachine specific modules needed to make this example work (e.g., we will need\n# spectral filters, etc.).\nusing ClimateMachine\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Mesh.Filters\nusing Thermodynamics.TemperatureProfiles\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.ODESolvers\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.BalanceLaws\n\nimport ClimateMachine.BalanceLaws: source, prognostic_vars\n\n# [ClimateMachine parameters](https://github.com/CliMA/CLIMAParameters.jl) are\n# needed to have access to Earth's physical parameters.\nusing CLIMAParameters\nusing CLIMAParameters.Planet: MSLP, R_d, day, grav, cp_d, cv_d, planet_radius\n\n# We need to load the physical parameters for Earth to have an Earth-like simulation :).\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet();\n\n\"\"\"\n    HeldSuarezForcingTutorial <: TendencyDef{Source}\n\nDefines a forcing that parametrises radiative and frictional effects using\nNewtonian relaxation and Rayleigh friction, following Held and Suarez (1994)\n\"\"\"\nstruct HeldSuarezForcingTutorial <: TendencyDef{Source} end\n\nprognostic_vars(::HeldSuarezForcingTutorial) = (Momentum(), Energy())\n\nfunction held_suarez_forcing_coefficients(bl, args)\n    @unpack state, aux = args\n    @unpack ts = args.precomputed\n    FT = eltype(state)\n\n    ## Parameters\n    T_ref = FT(255)\n    param_set = parameter_set(bl)\n\n    _R_d = FT(R_d(param_set))\n    _day = FT(day(param_set))\n    _grav = FT(grav(param_set))\n    _cp_d = FT(cp_d(param_set))\n    _p0 = FT(MSLP(param_set))\n\n    ## Held-Suarez parameters\n    k_a = FT(1 / (40 * _day))\n    k_f = FT(1 / _day)\n    k_s = FT(1 / (4 * _day))\n    ΔT_y = FT(60)\n    Δθ_z = FT(10)\n    T_equator = FT(315)\n    T_min = FT(200)\n    σ_b = FT(7 / 10)\n\n    ## Held-Suarez forcing\n    φ = latitude(bl, aux)\n    p = air_pressure(ts)\n\n    ##TODO: replace _p0 with dynamic surface pressure in Δσ calculations to account\n    ##for topography, but leave unchanged for calculations of σ involved in T_equil\n    σ = p / _p0\n    exner_p = σ^(_R_d / _cp_d)\n    Δσ = (σ - σ_b) / (1 - σ_b)\n    height_factor = max(0, Δσ)\n    T_equil = (T_equator - ΔT_y * sin(φ)^2 - Δθ_z * log(σ) * cos(φ)^2) * exner_p\n    T_equil = max(T_min, T_equil)\n    k_T = k_a + (k_s - k_a) * height_factor * cos(φ)^4\n    k_v = k_f * height_factor\n    return (k_v = k_v, k_T = k_T, T_equil = T_equil)\nend\n\nfunction source(::Energy, s::HeldSuarezForcingTutorial, atmos, args)\n    @unpack state = args\n    FT = eltype(state)\n    @unpack ts = args.precomputed\n    nt = held_suarez_forcing_coefficients(atmos, args)\n    param_set = parameter_set(atmos)\n    _cv_d = FT(cv_d(param_set))\n    @unpack k_T, T_equil = nt\n    T = air_temperature(ts)\n    return -k_T * state.ρ * _cv_d * (T - T_equil)\nend\n\nfunction source(::Momentum, s::HeldSuarezForcingTutorial, atmos, args)\n    @unpack state, aux = args\n    nt = held_suarez_forcing_coefficients(atmos, args)\n    return -nt.k_v * projection_tangential(atmos, aux, state.ρu)\nend\n\n\n# ## Set initial condition\n# When using ClimateMachine, we need to define a function that sets the initial\n# state of our model run. In our case, we use the reference state of the\n# simulation (defined below) and add a little bit of noise. Note that the\n# initial states includes a zero initial velocity field.\nfunction init_heldsuarez!(problem, balance_law, state, aux, localgeo, time)\n    FT = eltype(state)\n\n    ## Set initial state to reference state with random perturbation\n    rnd = FT(1 + rand(Uniform(-1e-3, 1e-3)))\n    state.ρ = aux.ref_state.ρ\n    state.ρu = SVector{3, FT}(0, 0, 0)\n    state.energy.ρe = rnd * aux.ref_state.ρe\nend;\n\n\n# ## Initialize ClimateMachine\n# Before we do anything further, we need to initialize ClimateMachine. Among\n# other things, this will initialize the MPI us.\nClimateMachine.init();\n\n\n# ## Setting the floating-type precision\n# ClimateMachine allows us to run a model with different floating-type\n# precisions, with lower precision we get our results faster, and with higher\n# precision, we may get more accurate results, depending on the questions we\n# are after.\nconst FT = Float64;\n\n\n# ## Setup model configuration\n# Now that we have defined our forcing and initialization functions, and have\n# initialized ClimateMachine, we can set up the model.\n#\n# ## Set up a reference state\n# We start by setting up a reference state. This is simply a vector field that\n# we subtract from the solutions to the governing equations to both improve\n# numerical stability of the implicit time stepper and enable faster model\n# spin-up. The reference state assumes hydrostatic balance and ideal gas law,\n# with a pressure $p_r(z)$ and density $\\rho_r(z)$ that only depend on altitude\n# $z$ and are in hydrostatic balance with each other.\n#\n# In this example, the reference temperature field smoothly transitions from a\n# linearly decaying profile near the surface to a constant temperature profile\n# at the top of the domain.\ntemp_profile_ref = DecayingTemperatureProfile{FT}(param_set)\nref_state = HydrostaticState(temp_profile_ref);\n\n# ## Set up a Rayleigh sponge layer\n# To avoid wave reflection at the top of the domain, the model applies a sponge\n# layer that linearly damps the momentum equations.\ndomain_height = FT(30e3)               # height of the computational domain (m)\nz_sponge = FT(12e3)                    # height at which sponge begins (m)\nα_relax = FT(1 / 60 / 15)              # sponge relaxation rate (1/s)\nexponent = FT(2)                       # sponge exponent for squared-sinusoid profile\nu_relax = SVector(FT(0), FT(0), FT(0)) # relaxation velocity (m/s)\nsponge = RayleighSponge{FT}(domain_height, z_sponge, α_relax, u_relax, exponent);\n\n# ## Set up turbulence models\n# In order to produce a stable simulation, we need to dissipate energy and\n# enstrophy at the smallest scales of the developed flow field. To achieve this\n# we set up diffusive forcing functions.\nc_smag = FT(0.21);   # Smagorinsky constant\nτ_hyper = FT(4 * 3600); # hyperdiffusion time scale\nturbulence_model = SmagorinskyLilly(c_smag);\nhyperdiffusion_model = DryBiharmonic(FT(4 * 3600));\n\n\n# ## Instantiate the model\n# The Held Suarez setup was designed to produce an equilibrated state that is\n# comparable to the zonal mean of the Earth’s atmosphere.\nphysics = AtmosPhysics{FT}(\n    param_set;\n    ref_state = ref_state,\n    turbulence = turbulence_model,\n    hyperdiffusion = hyperdiffusion_model,\n    moisture = DryModel(),\n);\n\nmodel = AtmosModel{FT}(\n    AtmosGCMConfigType,\n    physics;\n    init_state_prognostic = init_heldsuarez!,\n    source = (Gravity(), Coriolis(), HeldSuarezForcingTutorial(), sponge),\n);\n\n# This concludes the setup of the physical model!\n\n# ## Set up the driver\n# We just need to set up a few parameters that define the resolution of the\n# discontinuous Galerkin method and for how long we want to run our model\n# setup.\npoly_order = 5;                        ## discontinuous Galerkin polynomial order\nn_horz = 2;                            ## horizontal element number\nn_vert = 2;                            ## vertical element number\nresolution = (n_horz, n_vert)\nn_days = 0.1;                          ## experiment day number\ntimestart = FT(0);                     ## start time (s)\ntimeend = FT(n_days * day(param_set)); ## end time (s);\n\n\n# The next lines set up the spatial grid.\ndriver_config = ClimateMachine.AtmosGCMConfiguration(\n    \"HeldSuarez\",\n    poly_order,\n    resolution,\n    domain_height,\n    param_set,\n    init_heldsuarez!;\n    model = model,\n);\n\n# The next lines set up the time stepper. Since the resolution\n# in the vertical is much finer than in the horizontal,\n# the 'stiff' parts of the PDE will be in the vertical.\n# Setting `splitting_type = HEVISplitting()` will treat\n# vertical acoustic waves implicitly, while all other dynamics\n# are treated explicitly.\node_solver_type = ClimateMachine.IMEXSolverType(\n    splitting_type = HEVISplitting(),\n    implicit_model = AtmosAcousticGravityLinearModel,\n    implicit_solver = ManyColumnLU,\n    solver_method = ARK2GiraldoKellyConstantinescu,\n);\n\nsolver_config = ClimateMachine.SolverConfiguration(\n    timestart,\n    timeend,\n    driver_config,\n    Courant_number = FT(0.1),\n    ode_solver_type = ode_solver_type,\n    init_on_cpu = true,\n    CFL_direction = HorizontalDirection(),\n    diffdir = HorizontalDirection(),\n);\n\n# ## Set up spectral exponential filter\n# After every completed time step we apply a spectral filter to remove\n# remaining small-scale noise introduced by the numerical procedures. This\n# assures that our run remains stable.\nfilterorder = 10;\nfilter = ExponentialFilter(solver_config.dg.grid, 0, filterorder);\ncbfilter = GenericCallbacks.EveryXSimulationSteps(1) do\n    Filters.apply!(\n        solver_config.Q,\n        AtmosFilterPerturbations(model),\n        solver_config.dg.grid,\n        filter,\n        state_auxiliary = solver_config.dg.state_auxiliary,\n    )\n    nothing\nend;\n\n# ## Setup diagnostic output\n#\n# Choose frequency and resolution of output, and a diagnostics group (dgngrp)\n# which defines output variables. This needs to be defined in\n# [`Diagnostics`](@ref ClimateMachine.Diagnostics).\ninterval = \"1000steps\";\n_planet_radius = FT(planet_radius(param_set));\ninfo = driver_config.config_info;\nboundaries = [\n    FT(-90.0) FT(-180.0) _planet_radius\n    FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)\n];\nresolution = (FT(10), FT(10), FT(1000)); # in (deg, deg, m)\ninterpol = ClimateMachine.InterpolationConfiguration(\n    driver_config,\n    boundaries,\n    resolution,\n);\n\ndgngrps = [\n    setup_dump_state_diagnostics(\n        AtmosGCMConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    ),\n    setup_dump_aux_diagnostics(\n        AtmosGCMConfigType(),\n        interval,\n        driver_config.name,\n        interpol = interpol,\n    ),\n];\ndgn_config = ClimateMachine.DiagnosticsConfiguration(dgngrps);\n\n# ## Run the model\n# Finally, we can run the model using the physical setup and solvers from\n# above. We use the spectral filter in our callbacks after every time step, and\n# collect the diagnostics output.\nresult = ClimateMachine.invoke!(\n    solver_config;\n    diagnostics_config = dgn_config,\n    user_callbacks = (cbfilter,),\n    check_euclidean_distance = true,\n);\n\n\n# ## References\n#\n# - [Held1994](@cite)\n"
  },
  {
    "path": "tutorials/Atmos/risingbubble.jl",
    "content": "# # Rising Thermal Bubble\n#\n# In this example, we demonstrate the usage of the `ClimateMachine`\n# [AtmosModel](@ref AtmosModel-docs) machinery to solve the fluid\n# dynamics of a thermal perturbation in a neutrally stratified background state\n# defined by its uniform potential temperature. We solve a flow in a box configuration -\n# this is representative of a large-eddy simulation. Several versions of the problem\n# setup may be found in literature, but the general idea is to examine the\n# vertical ascent of a thermal bubble (we can interpret these as simple\n# representation of convective updrafts).\n#\n# ## Description of experiment\n# 1) Dry Rising Bubble (circular potential temperature perturbation)\n# 2) Boundaries\n#    Top and Bottom boundaries:\n#    - `Impenetrable(FreeSlip())` - Top and bottom: no momentum flux, no mass flux through\n#      walls.\n#    - `Impermeable()` - non-porous walls, i.e. no diffusive fluxes through\n#       walls.\n#    Lateral boundaries\n#    - Laterally periodic\n# 3) Domain - 2500m (horizontal) x 2500m (horizontal) x 2500m (vertical)\n# 4) Resolution - 50m effective resolution\n# 5) Total simulation time - 1000s\n# 6) Mesh Aspect Ratio (Effective resolution) 1:1\n# 7) Overrides defaults for\n#    - CPU Initialisation\n#    - Time integrator\n#    - Sources\n#    - Smagorinsky Coefficient\n\n#md # !!! note\n#md #     This experiment setup assumes that you have installed the\n#md #     `ClimateMachine` according to the instructions on the landing page.\n#md #     We assume the users' familiarity with the conservative form of the\n#md #     equations of motion for a compressible fluid (see the\n#md #     [AtmosModel](@ref AtmosModel-docs) page).\n#md #\n#md #     The following topics are covered in this example\n#md #     - Package requirements\n#md #     - Defining a `model` subtype for the set of conservation equations\n#md #     - Defining the initial conditions\n#md #     - Applying source terms\n#md #     - Choosing a turbulence model\n#md #     - Adding tracers to the model\n#md #     - Choosing a time-integrator\n#md #     - Choosing diagnostics (output) configurations\n#md #\n#md #     The following topics are not covered in this example\n#md #     - Defining new boundary conditions\n#md #     - Defining new turbulence models\n#md #     - Building new time-integrators\n#md #     - Adding diagnostic variables (beyond a standard pre-defined list of\n#md #       variables)\n\n# ## [Loading code](@id Loading-code-rtb)\n\n# Before setting up our experiment, we recognize that we need to import some\n# pre-defined functions from other packages. Julia allows us to use existing\n# modules (variable workspaces), or write our own to do so.  Complete\n# documentation for the Julia module system can be found\n# [here](https://docs.julialang.org/en/v1/manual/modules/#).\n\n# We need to use the `ClimateMachine` module! This imports all functions\n# specific to atmospheric and ocean flow modeling.\n\nusing ClimateMachine\nClimateMachine.init()\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\n\n# In ClimateMachine we use `StaticArrays` for our variable arrays.\n# We also use the `Test` package to help with unit tests and continuous\n# integration systems to design sensible tests for our experiment to ensure new\n# / modified blocks of code don't damage the fidelity of the physics. The test\n# defined within this experiment is not a unit test for a specific\n# subcomponent, but ensures time-integration of the defined problem conditions\n# within a reasonable tolerance. Immediately useful macros and functions from\n# this include `@test` and `@testset` which will allow us to define the testing\n# parameter sets.\nusing StaticArrays\nusing Test\nusing CLIMAParameters\nusing CLIMAParameters.Atmos.SubgridScale: C_smag\nusing CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet();\n\n# ## [Initial Conditions](@id init-rtb)\n# This example demonstrates the use of functions defined\n# in the [`Thermodynamics`](@ref Thermodynamics) package to\n# generate the appropriate initial state for our problem.\n\n#md # !!! note\n#md #     The following variables are assigned in the initial condition\n#md #     - `state.ρ` = Scalar quantity for initial density profile\n#md #     - `state.ρu`= 3-component vector for initial momentum profile\n#md #     - `state.energy.ρe`= Scalar quantity for initial total-energy profile\n#md #       humidity\n#md #     - `state.tracers.ρχ` = Vector of four tracers (here, for demonstration\n#md #       only; we can interpret these as dye injections for visualization\n#md #       purposes)\nfunction init_risingbubble!(problem, bl, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n\n    ## Problem float-type\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n\n    ## Unpack constant parameters\n    R_gas::FT = R_d(param_set)\n    c_p::FT = cp_d(param_set)\n    c_v::FT = cv_d(param_set)\n    p0::FT = MSLP(param_set)\n    _grav::FT = grav(param_set)\n    γ::FT = c_p / c_v\n\n    ## Define bubble center and background potential temperature\n    xc::FT = 5000\n    yc::FT = 1000\n    zc::FT = 2000\n    r = sqrt((x - xc)^2 + (z - zc)^2)\n    rc::FT = 2000\n    θamplitude::FT = 2\n\n    ## This is configured in the reference hydrostatic state\n    ref_state = reference_state(bl)\n    θ_ref::FT = ref_state.virtual_temperature_profile.T_surface\n\n    ## Add the thermal perturbation:\n    Δθ::FT = 0\n    if r <= rc\n        Δθ = θamplitude * (1.0 - r / rc)\n    end\n\n    ## Compute perturbed thermodynamic state:\n    θ = θ_ref + Δθ                                      ## potential temperature\n    π_exner = FT(1) - _grav / (c_p * θ) * z             ## exner pressure\n    ρ = p0 / (R_gas * θ) * (π_exner)^(c_v / R_gas)      ## density\n    T = θ * π_exner\n    e_int = internal_energy(param_set, T)\n    ts = PhaseDry(param_set, e_int, ρ)\n    ρu = SVector(FT(0), FT(0), FT(0))                   ## momentum\n    ## State (prognostic) variable assignment\n    e_kin = FT(0)                                       ## kinetic energy\n    e_pot = gravitational_potential(bl, aux)            ## potential energy\n    ρe_tot = ρ * total_energy(e_kin, e_pot, ts)         ## total energy\n\n    ρχ = FT(0)                                          ## tracer\n\n    ## We inject tracers at the initial condition at some specified z coordinates\n    if 500 < z <= 550\n        ρχ += FT(0.05)\n    end\n\n    ## We want 4 tracers\n    ntracers = 4\n\n    ## Define 4 tracers, (arbitrary scaling for this demo problem)\n    ρχ = SVector{ntracers, FT}(ρχ, ρχ / 2, ρχ / 3, ρχ / 4)\n\n    ## Assign State Variables\n    state.ρ = ρ\n    state.ρu = ρu\n    state.energy.ρe = ρe_tot\n    state.tracers.ρχ = ρχ\nend\n\n# ## [Model Configuration](@id config-helper)\n# We define a configuration function to assist in prescribing the physical\n# model. The purpose of this is to populate the\n# `ClimateMachine.AtmosLESConfiguration` with arguments\n# appropriate to the problem being considered.\nfunction config_risingbubble(\n    ::Type{FT},\n    N,\n    resolution,\n    xmax,\n    ymax,\n    zmax,\n) where {FT}\n    ## Since we want four tracers, we specify this and include the appropriate\n    ## diffusivity scaling coefficients (normally these would be physically\n    ## informed but for this demonstration we use integers corresponding to the\n    ## tracer index identifier)\n    ntracers = 4\n    δ_χ = SVector{ntracers, FT}(1, 2, 3, 4)\n    ## To assemble `AtmosModel` with no tracers, set `tracers = NoTracers()`.\n\n    ## The model coefficient for the turbulence closure is defined via the\n    ## [CLIMAParameters\n    ## package](https://CliMA.github.io/CLIMAParameters.jl/latest/) A reference\n    ## state for the linearisation step is also defined.\n    T_surface = FT(300)\n    T_min_ref = FT(0)\n    T_profile = DryAdiabaticProfile{FT}(param_set, T_surface, T_min_ref)\n    ref_state = HydrostaticState(T_profile)\n\n    ## Here we assemble the `AtmosModel`.\n    _C_smag = FT(C_smag(param_set))\n    physics = AtmosPhysics{FT}(\n        param_set;                                     ## Parameter set corresponding to earth parameters\n        ref_state = ref_state,                         ## Reference state\n        turbulence = SmagorinskyLilly(_C_smag),        ## Turbulence closure model\n        moisture = DryModel(),                         ## Exclude moisture variables\n        tracers = NTracers{ntracers, FT}(δ_χ),         ## Tracer model with diffusivity coefficients\n    )\n\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,                            ## Flow in a box, requires the AtmosLESConfigType\n        physics;                                       ## Atmos physics\n        init_state_prognostic = init_risingbubble!,    ## Apply the initial condition\n        source = (Gravity(),),                         ## Gravity is the only source term here\n    )\n\n    ## Finally, we pass a `Problem Name` string, the mesh information, and the\n    ## model type to  the [`AtmosLESConfiguration`] object.\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"DryRisingBubble\",       ## Problem title [String]\n        N,                       ## Polynomial order [Int]\n        resolution,              ## (Δx, Δy, Δz) effective resolution [m]\n        xmax,                    ## Domain maximum size [m]\n        ymax,                    ## Domain maximum size [m]\n        zmax,                    ## Domain maximum size [m]\n        param_set,               ## Parameter set.\n        init_risingbubble!,      ## Function specifying initial condition\n        model = model,           ## Model type\n    )\n    return config\nend\n\n#md # !!! note\n#md #     `Keywords` are used to specify some arguments (see appropriate source\n#md #     files).\n\n# ## [Diagnostics](@id config_diagnostics)\n# Here we define the diagnostic configuration specific to this problem.\nfunction config_diagnostics(driver_config)\n    interval = \"10000steps\"\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosLESConfigType(),\n        interval,\n        driver_config.name,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp])\nend\n\nfunction main()\n    ## These are essentially arguments passed to the\n    ## [`config_risingbubble`](@ref config-helper) function.  For type\n    ## consistency we explicitly define the problem floating-precision.\n    FT = Float64\n    ## We need to specify the polynomial order for the DG discretization,\n    ## effective resolution, simulation end-time, the domain bounds, and the\n    ## courant-number for the time-integrator. Note how the time-integration\n    ## components `solver_config` are distinct from the spatial / model\n    ## components in `driver_config`. `init_on_cpu` is a helper keyword argument\n    ## that forces problem initialization on CPU (thereby allowing the use of\n    ## random seeds, spline interpolants and other special functions at the\n    ## initialization step.)\n    N = 4\n    Δh = FT(125)\n    Δv = FT(125)\n    resolution = (Δh, Δh, Δv)\n    xmax = FT(10000)\n    ymax = FT(500)\n    zmax = FT(10000)\n    t0 = FT(0)\n    timeend = FT(100)\n    ## For full simulation set `timeend = 1000`\n\n    ## Use up to 1.7 if ode_solver is the single rate LSRK144.\n    CFL = FT(1.7)\n\n    ## Assign configurations so they can be passed to the `invoke!` function\n    driver_config = config_risingbubble(FT, N, resolution, xmax, ymax, zmax)\n\n    ## Choose an Explicit Single-rate Solver from the existing [`ODESolvers`](@ref ClimateMachine.ODESolvers) options.\n    ## Apply the outer constructor to define the `ode_solver`.\n    ## The 1D-IMEX method is less appropriate for the problem given the current\n    ## mesh aspect ratio (1:1).\n    ode_solver_type = ClimateMachine.ExplicitSolverType(\n        solver_method = LSRK144NiegemannDiehlBusch,\n    )\n    ## If the user prefers a multi-rate explicit time integrator,\n    ## the ode_solver above can be replaced with\n    ##\n    ## `ode_solver = ClimateMachine.MultirateSolverType(\n    ##    fast_model = AtmosAcousticGravityLinearModel,\n    ##    slow_method = LSRK144NiegemannDiehlBusch,\n    ##    fast_method = LSRK144NiegemannDiehlBusch,\n    ##    timestep_ratio = 10,\n    ## )`\n    ## See [ODESolvers](@ref ODESolvers-docs) for all of the available solvers.\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_solver_type = ode_solver_type,\n        init_on_cpu = true,\n        Courant_number = CFL,\n    )\n    dgn_config = config_diagnostics(driver_config)\n\n    ## Invoke solver (calls `solve!` function for time-integrator), pass the driver,\n    ## solver and diagnostic config information.\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (),\n        check_euclidean_distance = true,\n    )\n\n    ## Check that the solution norm is reasonable.\n    @test isapprox(result, FT(1); atol = 1.5e-3)\nend\n\n# The experiment definition is now complete. Time to run it.\n\n# ## Running the file\n# `julia --project tutorials/Atmos/risingbubble.jl` will run the\n# experiment from the main ClimateMachine.jl directory, with diagnostics output\n# at the intervals specified in [`config_diagnostics`](@ref\n# config_diagnostics).  You can also prescribe command line arguments for\n# simulation update and output specifications.  For\n# rapid turnaround, we recommend that you run this experiment on a GPU.\n\n# VTK output can be controlled via command line by\n# setting `parse_clargs=true` in the `ClimateMachine.init`\n# arguments, and then using `--vtk=<interval>`.\n\n# ## [Output Visualisation](@id output-viz)\n# See the `ClimateMachine` API interface documentation\n# for generating output.\n#\n#\n# - [VisIt](https://wci.llnl.gov/simulation/computer-codes/visit/)\n# - [Paraview](https://www.paraview.org/)\n# are two commonly used programs for `.vtu` files.\n#\n# For NetCDF or JLD2 diagnostics you may use any of the following tools:\n# Julia's\n# [`NCDatasets`](https://github.com/Alexander-Barth/NCDatasets.jl) and\n# [`JLD2`](https://github.com/JuliaIO/JLD2.jl) packages with a suitable\n#\n# or the known and quick NCDF visualization tool:\n# [`ncview`](http://meteora.ucsd.edu/~pierce/ncview_home_page.html)\n# plotting program.\n\nmain()\n"
  },
  {
    "path": "tutorials/BalanceLaws/tendency_specification_layer.jl",
    "content": "# # A functional tendency specification layer\n\n# In the balance law (mutating) functions, where we specify fluxes and sources,\n\n# - [`flux_first_order!`](@ref ClimateMachine.BalanceLaws.flux_first_order!)\n# - [`flux_second_order!`](@ref ClimateMachine.BalanceLaws.flux_second_order!)\n# - and [`source!`](@ref ClimateMachine.BalanceLaws.source!),\n\n# an additional (functional) tendency specification\n# layer can be placed on-top that has several nice\n# properties. The functional layer:\n\n# - Separates tendency definitions from which tendencies are included in a particular model.\n# - Reduces duplicate implementations of tendency definitions (e.g., in optional submodel variants)\n# - Allows a more flexible combination of tendencies\n# - Allows a simple way to loop over all tendencies for all prognostic variables and recover\n#   _each_ flux / source term. This will allow us a simple way to evaluate, for example, the energy budget.\n\n# ## Used modules / imports\n\n# Make running locally easier from ClimateMachine.jl/:\nif !(\".\" in LOAD_PATH)\n    push!(LOAD_PATH, \".\")\n    nothing\nend\n\n# First, using necessary modules:\nusing ClimateMachine.BalanceLaws\nusing ClimateMachine.VariableTemplates\nusing StaticArrays, Test\n\n# Import methods to overload\nimport ClimateMachine.BalanceLaws: prognostic_vars, eq_tends, flux\n\n# ## Define a balance law\n\n# Here, we define a simple balance law:\n\nstruct MyBalanceLaw <: BalanceLaw end\n\n# ## Define prognostic variable types\n\n# Here, we'll define some prognostic variable types,\n# by sub-typing [`AbstractPrognosticVariable`](@ref ClimateMachine.BalanceLaws.AbstractPrognosticVariable),\n# for mass and energy:\nstruct Mass <: AbstractPrognosticVariable end\nstruct Energy <: AbstractPrognosticVariable end\n\n# Define [`prognostic_vars`](@ref ClimateMachine.BalanceLaws.prognostic_vars),\n# which returns _all_ prognostic variables\nprognostic_vars(::MyBalanceLaw) = (Mass(), Energy());\n\n# ## Define tendency definition types\n\n# Tendency definitions types are made by subtyping\n# [`TendencyDef`](@ref ClimateMachine.BalanceLaws.TendencyDef).\n# `TendencyDef` has one type parameters: the\n# `AbstractTendencyType`, which can be either\n# `Flux{FirstOrder}`, `Flux{SecondOrder}`, or `Source`.\nstruct Advection <: TendencyDef{Flux{FirstOrder}} end\nstruct Source1 <: TendencyDef{Source} end\nstruct Source2 <: TendencyDef{Source} end\nstruct Diffusion <: TendencyDef{Flux{SecondOrder}} end\n\n# Define [`eq_tends`](@ref ClimateMachine.BalanceLaws.eq_tends),\n# which returns a tuple of tendency definitions (those sub-typed\n# by [`TendencyDef`](@ref ClimateMachine.BalanceLaws.TendencyDef)),\n# given\n#  - the prognostic variable\n#  - the model (balance law)\n#  - the tendency type ([`Flux`](@ref ClimateMachine.BalanceLaws.Flux) or\n#    [`Source`](@ref ClimateMachine.BalanceLaws.Source))\neq_tends(::Mass, ::MyBalanceLaw, ::Flux{FirstOrder}) = (Advection(),);\neq_tends(::Energy, ::MyBalanceLaw, ::Flux{FirstOrder}) = (Advection(),);\neq_tends(::Mass, ::MyBalanceLaw, ::Flux{SecondOrder}) = ();\neq_tends(::Energy, ::MyBalanceLaw, ::Flux{SecondOrder}) = (Diffusion(),);\neq_tends(::Mass, ::MyBalanceLaw, ::Source) = (Source1(), Source2());\neq_tends(::Energy, ::MyBalanceLaw, ::Source) = (Source1(), Source2());\n\n# ## Testing `prognostic_vars` `eq_tends`\n\n# To test that `prognostic_vars` and `eq_tends` were\n# implemented correctly, we'll create a balance law\n# instance and call [`show_tendencies`](@ref ClimateMachine.BalanceLaws.show_tendencies),\n# to make sure that the tendency table is accurate.\n\nbl = MyBalanceLaw()\nshow_tendencies(bl; table_complete = true)\n\n# The table looks correct. Now we're ready to\n# add the specification layer.\n\n# ## Adding the tendency specification layer\n\n# For the purpose of this tutorial, we'll only focus\n# on adding the layer to the first order flux, since\n# doing so for the second order flux and source\n# functions follow the same exact pattern. In other words,\n# we'll add a layer that tests the `Flux{FirstOrder}` column\n# in the table above. First, we'll define individual\n# [`flux`](@ref ClimateMachine.BalanceLaws.flux) kernels:\nflux(::Mass, ::Advection, bl::MyBalanceLaw, args) =\n    args.state.ρ * SVector(1, 1, 1);\nflux(::Energy, ::Advection, bl::MyBalanceLaw, args) =\n    args.state.ρe * SVector(1, 1, 1);\n\n# !!! note\n#     - `flux` should return a 3-componet vector for scalar equations\n#     - `flux` should return a 3xN-componet tensor for N-component vector equations\n#     - `source` should return a scalar for scalar equations\n#     - `source` should return a N-componet vector for N-component vector equations\n\n# Define `flux_first_order!` and utilize `eq_tends`\nfunction flux_first_order!(\n    bl::MyBalanceLaw,\n    flx::Grad,\n    state::Vars,\n    aux,\n    t,\n    direction,\n)\n\n    tend_type = Flux{FirstOrder}()\n    args = (; state, aux, t, direction)\n\n    ## `Σfluxes(Mass(), eq_tends(Mass(), bl, tend_type), bl, args)` calls\n    ## `flux(::Mass, ::Advection, ...)` defined above:\n    eqt_ρ = eq_tends(Mass(), bl, tend_type)\n    flx.ρ = Σfluxes(Mass(), eqt_ρ, bl, args)\n\n    ## `Σfluxes(Energy(), eq_tends(Energy(), bl, tend_type), bl, args)` calls\n    ## `flux(::Energy, ::Advection, ...)` defined above:\n    eqt_ρe = eq_tends(Energy(), bl, tend_type)\n    flx.ρe = Σfluxes(Energy(), eqt_ρe, bl, args)\n    return nothing\nend;\n\n# ## Testing the tendency specification layer\n\n# Now, let's test `flux_first_order!` we need to initialize\n# some dummy data to call it first:\n\nFT = Float64; # float type\naux = (); # auxiliary fields\nt = 0.0; # time\ndirection = nothing; # Direction\n\nstate = Vars{@vars(ρ::FT, ρe::FT)}([1, 2]);\nflx = Grad{@vars(ρ::FT, ρe::FT)}(zeros(MArray{Tuple{3, 2}, FT}));\n\n# call `flux_first_order!`\nflux_first_order!(bl, flx, state, aux, t, direction);\n\n# Test that `flx` has been properly mutated:\n@testset \"Test results\" begin\n    @test flx.ρ == [1, 1, 1]\n    @test flx.ρe == [2, 2, 2]\nend\n\nnothing\n"
  },
  {
    "path": "tutorials/Diagnostics/Debug/StateCheck.jl",
    "content": "# # State debug statistics\n#\n# This page shows how to use the `StateCheck` functions to get basic\n# statistics for nodal values of fields held in ClimateMachine `MPIStateArray`\n# data structures. The `StateCheck` functions can be used to\n#\n# 1. Generate statistics on `MPIStateArray` holding the state of a ClimateMachine experiment.\n#\n#    and to\n#\n# 2. Compare against saved reference statistics from ClimateMachine `MPIStateArray`\n#    variables. This can enable simple automated regression test checks for\n#    detecting unexpected changes introduced into numerical experiments\n#    by code updates.\n#\n# These two cases are shown below:\n\n\n# ## 1. Generating statistics for a set of MPIStateArrays\n#\n# Here we create a callback that can generate statistics for an arbitrary\n# set of the MPIStateArray type variables of the sort that hold persistent state for\n# ClimateMachine models. We then invoke the call back to show the statistics.\n#\n# In regular use the `MPIStateArray` variables will come from model configurations.\n# Here we create a dummy set of `MPIStateArray` variables for use in stand alone\n# examples.\n\n# ### Create a dummy set of MPIStateArrays\n#\n# First we set up two `MPIStateArray` variables. This need a few packages to be in placeT,\n# and utilizes some utility functions to create the array and add named\n# persistent state variables.\n# This is usually handled automatically as part of model definition in regular\n# ClimateMachine activity.\n# Calling `ClimateMachine.init()` includes initializing GPU CUDA and MPI parallel\n# processing options that match the hardware/software system in use.\n\n# Set up a basic environment\nusing MPI\nusing StaticArrays\nusing Random\nusing ClimateMachine\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.StateCheck\n\nClimateMachine.init()\nFT = Float64\n\n# Define some dummy vector and tensor abstract variables with associated types\n# and dimensions\nF1 = @vars begin\n    ν∇u::SMatrix{3, 2, FT, 6}\n    κ∇θ::SVector{3, FT}\nend\nF2 = @vars begin\n    u::SVector{2, FT}\n    θ::SVector{1, FT}\nend\nnothing # hide\n\n# Create `MPIStateArray` variables with arrays to hold elements of the\n# vectors and tensors\nQ1 = MPIStateArray{Float32, F1}(\n    MPI.COMM_WORLD,\n    ClimateMachine.array_type(),\n    4,\n    9,\n    8,\n)\nQ2 = MPIStateArray{Float64, F2}(\n    MPI.COMM_WORLD,\n    ClimateMachine.array_type(),\n    4,\n    3,\n    8,\n)\nnothing # hide\n\n# ### Create a call-back\n#\n# Now we can create a `StateCheck` call-back, _cb_, tied to the `MPIStateArray`\n# variables _Q1_ and _Q2_. Each `MPIStateArray` in the array\n# of `MPIStateArray` variables tracked is paired with a label\n# to identify it. The call-back is also given a frequency (in time step numbers) and\n# precision for printing summary tables.\ncb = ClimateMachine.StateCheck.sccreate(\n    [(Q1, \"My gradients\"), (Q2, \"My fields\")],\n    1;\n    prec = 15,\n)\nGenericCallbacks.init!(cb, nothing, nothing, nothing, nothing)\nnothing # hide\n\n# ### Invoke the call-back\n#\n# The call-back is of type `ClimateMachine.GenericCallbacks.EveryXSimulationSteps`\n# and in regular use is designed to be passed to a ClimateMachine timestepping\n# solver e.g.\ntypeof(cb)\n\n# Here, for demonstration purposes, we can invoke\n# the call-back after simply initializing the `MPIStateArray` fields to a random\n# set of values e.g.\nQ1.data .= rand(MersenneTwister(0), Float32, size(Q1.data))\nQ2.data .= rand(MersenneTwister(0), Float64, size(Q2.data))\nGenericCallbacks.call!(cb, nothing, nothing, nothing, nothing)\n\n# ## 2. Comparing to reference values\n\n# ### Generate arrays of reference values\n#\n# StateCheck functions can generate text that can be used to set the value of stored\n# arrays that can be used in a reference test for subsequent regression testing. This\n# involves 3 steps.\n#\n# **Step 1.** First a reference array setting program code is generated from the latest\n# state of a given callback e.g.\n\nClimateMachine.StateCheck.scprintref(cb)\n\n# **Step 2.** Next the array setting program code is executed (see below). At this stage the _parr[]_ array\n# context may be hand edited. The parr[] array sets a target number of decimal places for\n# matching against reference values in _varr[]_. For different experiments and different fields\n# the degree of precision that constitutes failing a regression test may vary. Choosing the\n# _parr[]_ values requires some sense as to the stability of the particular numerical\n# and physical scenario an experiment represents. In the example below some precision\n# settings have been hand edited from the default of 16 to illustrate the process.\n\n#! format: off\nvarr = [\n [ \"My gradients\", \"ν∇u[1]\",  1.34348869323730468750e-04,  9.84732866287231445313e-01,  5.23545503616333007813e-01,  3.08209930764271777814e-01 ],\n [ \"My gradients\", \"ν∇u[2]\",  1.16317868232727050781e-01,  9.92088317871093750000e-01,  4.83800649642944335938e-01,  2.83350456014221541157e-01 ],\n [ \"My gradients\", \"ν∇u[3]\",  1.05845928192138671875e-03,  9.51775908470153808594e-01,  4.65474426746368408203e-01,  2.73615551085745090099e-01 ],\n [ \"My gradients\", \"ν∇u[4]\",  5.97668886184692382813e-02,  9.68048095703125000000e-01,  5.42618036270141601563e-01,  2.81570862027933854765e-01 ],\n [ \"My gradients\", \"ν∇u[5]\",  8.31030607223510742188e-02,  9.35931921005249023438e-01,  5.05405902862548828125e-01,  2.46073509972619536290e-01 ],\n [ \"My gradients\", \"ν∇u[6]\",  3.09681892395019531250e-02,  9.98341441154479980469e-01,  4.54375565052032470703e-01,  3.09461067853178561915e-01 ],\n [ \"My gradients\", \"κ∇θ[1]\",  8.47448110580444335938e-02,  9.94180679321289062500e-01,  5.27157366275787353516e-01,  2.92455951648181833313e-01 ],\n [ \"My gradients\", \"κ∇θ[2]\",  1.20514631271362304688e-02,  9.93527650833129882813e-01,  4.71063584089279174805e-01,  2.96449027197666359346e-01 ],\n [ \"My gradients\", \"κ∇θ[3]\",  8.14980268478393554688e-02,  9.55443382263183593750e-01,  5.05038917064666748047e-01,  2.77201022741208891187e-01 ],\n [    \"My fields\",   \"u[1]\",  4.31410233294131639781e-02,  9.97140933049696531754e-01,  4.62139750850942054861e-01,  3.23076684924287371725e-01 ],\n [    \"My fields\",   \"u[2]\",  1.01416659908237782872e-02,  9.14712023896926407218e-01,  4.76160523012988778913e-01,  2.71443440757963339038e-01 ],\n [    \"My fields\",   \"θ[1]\",  6.58965491052394547467e-02,  9.73216404386510802738e-01,  4.60007166313864512830e-01,  2.87310472114545079059e-01 ],\n]\nparr = [\n [ \"My gradients\", \"ν∇u[1]\",    16,     7,    16,     0 ],\n [ \"My gradients\", \"ν∇u[2]\",    16,     7,    16,     0 ],\n [ \"My gradients\", \"ν∇u[3]\",    16,     7,    16,     0 ],\n [ \"My gradients\", \"ν∇u[4]\",    16,     7,    16,     0 ],\n [ \"My gradients\", \"ν∇u[5]\",    16,     7,    16,     0 ],\n [ \"My gradients\", \"ν∇u[6]\",    16,     7,    16,     0 ],\n [ \"My gradients\", \"κ∇θ[1]\",    16,    16,    16,     0 ],\n [ \"My gradients\", \"κ∇θ[2]\",    16,    16,    16,     0 ],\n [ \"My gradients\", \"κ∇θ[3]\",    16,    16,    16,     0 ],\n [    \"My fields\",   \"u[1]\",    16,    16,    16,     0 ],\n [    \"My fields\",   \"u[2]\",    16,    16,    16,     0 ],\n [    \"My fields\",   \"θ[1]\",    16,    16,    16,     0 ],\n]\n#! format: on\n\n# **Step 3.** Finally a call-back stored value can be compared for consistency to with _parr[]_ decimal places\n\nClimateMachine.StateCheck.scdocheck(cb, (varr, parr))\nnothing # hide\n\n# In this trivial case the match is guaranteed. The function will return _true_ to the calling\n# routine and this can be passed to an `@test` block.\n#\n# However we can modify the reference test values to\n# see the effect of a mismatch e.g.\nvarr[1][3] = varr[1][3] * 10.0\nClimateMachine.StateCheck.scdocheck(cb, (varr, parr))\nnothing # hide\n\n# Here the mis-matching field is highlighted with _N(0)_ indicating that the precision\n# was not met and actual match length was (in this case) 0. If any field fails the test returns false\n# for use in any regression testing control logic.\n"
  },
  {
    "path": "tutorials/Land/Heat/heat_equation.jl",
    "content": "# # Heat equation tutorial\n\n# In this tutorial, we'll be solving the [heat\n# equation](https://en.wikipedia.org/wiki/Heat_equation):\n\n# ``\n# \\frac{∂ ρcT}{∂ t} + ∇ ⋅ (-α ∇ρcT) = 0\n# ``\n\n# where\n#  - `t` is time\n#  - `α` is the thermal diffusivity\n#  - `T` is the temperature\n#  - `ρ` is the density\n#  - `c` is the heat capacity\n#  - `ρcT` is the thermal energy\n\n# To put this in the form of ClimateMachine's [`BalanceLaw`](@ref\n# ClimateMachine.BalanceLaws.BalanceLaw), we'll re-write the equation as:\n\n# ``\n# \\frac{∂ ρcT}{∂ t} + ∇ ⋅ (F(α, ρcT, t)) = 0\n# ``\n\n# where\n#  - ``F(α, ρcT, t) = -α ∇ρcT`` is the second-order flux\n\n# with boundary conditions\n#  - Fixed temperature ``T_{surface}`` at ``z_{min}`` (non-zero Dirichlet)\n#  - No thermal flux at ``z_{min}`` (zero Neumann)\n\n# Solving these equations is broken down into the following steps:\n# 1) Preliminary configuration\n# 2) PDEs\n# 3) Space discretization\n# 4) Time discretization / solver\n# 5) Solver hooks / callbacks\n# 6) Solve\n# 7) Post-processing\n\n# # Preliminary configuration\n\n# ## [Loading code](@id Loading-code-heat)\n\n# First, we'll load our pre-requisites:\n#  - load external packages:\nusing MPI\nusing OrderedCollections\nusing Plots\nusing StaticArrays\nusing OrdinaryDiffEq\nusing DiffEqBase\n\n#  - load CLIMAParameters and set up to use it:\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\n#  - load necessary ClimateMachine modules:\nusing ClimateMachine\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux\n\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\n\n#  - import necessary ClimateMachine modules: (`import`ing enables us to\n#  provide implementations of these structs/methods)\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    source!,\n    flux_second_order!,\n    flux_first_order!,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    nodal_update_auxiliary_state!,\n    nodal_init_state_auxiliary!,\n    init_state_prognostic!,\n    BoundaryCondition,\n    boundary_conditions,\n    boundary_state!\n\nimport ClimateMachine.DGMethods: calculate_dt\n\n# ## Initialization\n\n# Define the float type (`Float64` or `Float32`)\nconst FT = Float64;\n# Initialize ClimateMachine for CPU.\nClimateMachine.init(; disable_gpu = true);\n\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\n\n# Load some helper functions for plotting\ninclude(joinpath(clima_dir, \"docs\", \"plothelpers.jl\"));\n\n# # Define the set of Partial Differential Equations (PDEs)\n\n# ## Define the model\n\n# Model parameters can be stored in the particular [`BalanceLaw`](@ref\n# ClimateMachine.BalanceLaws.BalanceLaw), in this case, a `HeatModel`:\n\nBase.@kwdef struct HeatModel{FT, APS} <: BalanceLaw\n    \"Parameters\"\n    param_set::APS\n    \"Heat capacity\"\n    ρc::FT = 1\n    \"Thermal diffusivity\"\n    α::FT = 0.01\n    \"Initial conditions for temperature\"\n    initialT::FT = 295.15\n    \"Bottom boundary value for temperature (Dirichlet boundary conditions)\"\n    T_bottom::FT = 300.0\n    \"Top flux (α∇ρcT) at top boundary (Neumann boundary conditions)\"\n    flux_top::FT = 0.0\nend\n\n# Create an instance of the `HeatModel`:\nm = HeatModel{FT, typeof(param_set)}(; param_set = param_set);\n\n# This model dictates the flow control, using [Dynamic Multiple\n# Dispatch](https://en.wikipedia.org/wiki/Multiple_dispatch), for which\n# kernels are executed.\n\n# ## Define the variables\n\n# All of the methods defined in this section were `import`ed in\n# [Loading code](@ref Loading-code-heat) to let us provide\n# implementations for our `HeatModel` as they will be used by\n# the solver.\n\n# Specify auxiliary variables for `HeatModel`\nvars_state(::HeatModel, ::Auxiliary, FT) = @vars(z::FT, T::FT);\n\n# Specify prognostic variables, the variables solved for in the PDEs, for\n# `HeatModel`\nvars_state(::HeatModel, ::Prognostic, FT) = @vars(ρcT::FT);\n\n# Specify state variables whose gradients are needed for `HeatModel`\nvars_state(::HeatModel, ::Gradient, FT) = @vars(ρcT::FT);\n\n# Specify gradient variables for `HeatModel`\nvars_state(::HeatModel, ::GradientFlux, FT) = @vars(α∇ρcT::SVector{3, FT});\n\n# ## Define the compute kernels\n\n# Specify the initial values in `aux::Vars`, which are available in\n# `init_state_prognostic!`. Note that\n# - this method is only called at `t=0`\n# - `aux.z` and `aux.T` are available here because we've specified `z` and `T`\n# in `vars_state` given `Auxiliary`\n# in `vars_state`\nfunction nodal_init_state_auxiliary!(\n    m::HeatModel,\n    aux::Vars,\n    tmp::Vars,\n    geom::LocalGeometry,\n)\n    aux.z = geom.coord[3]\n    aux.T = m.initialT\nend;\n\n# Specify the initial values in `state::Vars`. Note that\n# - this method is only called at `t=0`\n# - `state.ρcT` is available here because we've specified `ρcT` in\n# `vars_state` given `Prognostic`\nfunction init_state_prognostic!(\n    m::HeatModel,\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t::Real,\n)\n    state.ρcT = m.ρc * aux.T\nend;\n\n# The remaining methods, defined in this section, are called at every\n# time-step in the solver by the [`BalanceLaw`](@ref\n# ClimateMachine.BalanceLaws.BalanceLaw) framework.\n\n# Compute/update all auxiliary variables at each node. Note that\n# - `aux.T` is available here because we've specified `T` in\n# `vars_state` given `Auxiliary`\nfunction nodal_update_auxiliary_state!(\n    m::HeatModel,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    aux.T = state.ρcT / m.ρc\nend;\n\n# Since we have second-order fluxes, we must tell `ClimateMachine` to compute\n# the gradient of `ρcT`. Here, we specify how `ρcT` is computed. Note that\n#  - `transform.ρcT` is available here because we've specified `ρcT` in\n#  `vars_state` given `Gradient`\nfunction compute_gradient_argument!(\n    m::HeatModel,\n    transform::Vars,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    transform.ρcT = state.ρcT\nend;\n\n# Specify where in `diffusive::Vars` to store the computed gradient from\n# `compute_gradient_argument!`. Note that:\n#  - `diffusive.α∇ρcT` is available here because we've specified `α∇ρcT` in\n#  `vars_state` given `Gradient`\n#  - `∇transform.ρcT` is available here because we've specified `ρcT`  in\n#  `vars_state` given `Gradient`\nfunction compute_gradient_flux!(\n    m::HeatModel,\n    diffusive::Vars,\n    ∇transform::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n)\n    diffusive.α∇ρcT = -m.α * ∇transform.ρcT\nend;\n\n# We have no sources, nor non-diffusive fluxes.\nfunction source!(m::HeatModel, _...) end;\nfunction flux_first_order!(m::HeatModel, _...) end;\n\n# Compute diffusive flux (``F(α, ρcT, t) = -α ∇ρcT`` in the original PDE).\n# Note that:\n# - `diffusive.α∇ρcT` is available here because we've specified `α∇ρcT` in\n# `vars_state` given `GradientFlux`\nfunction flux_second_order!(\n    m::HeatModel,\n    flux::Grad,\n    state::Vars,\n    diffusive::Vars,\n    hyperdiffusive::Vars,\n    aux::Vars,\n    t::Real,\n)\n    flux.ρcT += diffusive.α∇ρcT\nend;\n\n# ### Boundary conditions\n\n# Second-order terms in our equations, ``∇⋅(F)`` where ``F = -α∇ρcT``, are\n# internally reformulated to first-order unknowns.\n# Boundary conditions must be specified for all unknowns, both first-order and\n# second-order unknowns which have been reformulated.\nstruct DirichletBC <: BoundaryCondition end;\nstruct NeumannBC <: BoundaryCondition end;\n\nboundary_conditions(::HeatModel) = (DirichletBC(), NeumannBC())\n\n# The boundary conditions for `ρcT` (first order unknown)\nfunction boundary_state!(\n    nf,\n    bc::DirichletBC,\n    m::HeatModel,\n    state⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    state⁻::Vars,\n    aux⁻::Vars,\n    t,\n    _...,\n)\n    ## Apply Dirichlet BCs\n    state⁺.ρcT = m.ρc * m.T_bottom\nend;\nfunction boundary_state!(\n    nf,\n    bc::NeumannBC,\n    m::HeatModel,\n    state⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    state⁻::Vars,\n    aux⁻::Vars,\n    t,\n    _...,\n)\n    nothing\nend;\n\n# The boundary conditions for `ρcT` are specified here for second-order\n# unknowns\nfunction boundary_state!(\n    nf,\n    bc::DirichletBC,\n    m::HeatModel,\n    state⁺::Vars,\n    diff⁺::Vars,\n    hyperdiff⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    state⁻::Vars,\n    diff⁻::Vars,\n    hyperdiff⁻::Vars,\n    aux⁻::Vars,\n    t,\n    _...,\n)\n    nothing\nend;\nfunction boundary_state!(\n    nf,\n    bc::NeumannBC,\n    m::HeatModel,\n    state⁺::Vars,\n    diff⁺::Vars,\n    hyperdiff⁺::Vars,\n    aux⁺::Vars,\n    n⁻,\n    state⁻::Vars,\n    diff⁻::Vars,\n    hyperdiff⁻::Vars,\n    aux⁻::Vars,\n    t,\n    _...,\n)\n    ## Apply Neumann BCs\n    diff⁺.α∇ρcT = n⁻ * m.flux_top\nend;\n\n# # Spatial discretization\n\n# Prescribe polynomial order of basis functions in finite elements\nN_poly = 5;\n\n# Specify the number of vertical elements\nnelem_vert = 10;\n\n# Specify the domain height\nzmax = FT(1);\n\n# Establish a `ClimateMachine` single stack configuration\ndriver_config = ClimateMachine.SingleStackConfiguration(\n    \"HeatEquation\",\n    N_poly,\n    nelem_vert,\n    zmax,\n    param_set,\n    m,\n    numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n);\n\n# # Time discretization / solver\n\n# Specify simulation time (SI units)\nt0 = FT(0)\ntimeend = FT(40)\n\n# In this section, we initialize the state vector and allocate memory for\n# the solution in space (`dg` has the model `m`, which describes the PDEs\n# as well as the function used for initialization). `SolverConfiguration`\n# initializes the ODE solver, by default an explicit Low-Storage\n# [Runge-Kutta](https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods)\n# method. In this tutorial, we prescribe an option for an implicit\n# `Kvaerno3` method.\n\n# First, let's define how the time-step is computed, based on the\n# [Fourier number](https://en.wikipedia.org/wiki/Fourier_number)\n# (i.e., diffusive Courant number) is defined. Because\n# the `HeatModel` is a custom model, we must define how both are computed.\n# First, we must define our own implementation of `DGMethods.calculate_dt`,\n# (which we imported):\nfunction calculate_dt(dg, model::HeatModel, Q, Courant_number, t, direction)\n    Δt = one(eltype(Q))\n    CFL = DGMethods.courant(diffusive_courant, dg, model, Q, Δt, t, direction)\n    return Courant_number / CFL\nend\n\n# Next, we'll define our implementation of `diffusive_courant`:\nfunction diffusive_courant(\n    m::HeatModel,\n    state::Vars,\n    aux::Vars,\n    diffusive::Vars,\n    Δx,\n    Δt,\n    t,\n    direction,\n)\n    return Δt * m.α / (Δx * Δx)\nend\n\n# Finally, we initialize the state vector and solver\n# configuration based on the given Fourier number.\n# Note that, we can use a much larger Fourier number\n# for implicit solvers as compared to explicit solvers.\nuse_implicit_solver = false\nif use_implicit_solver\n    given_Fourier = FT(30)\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config;\n        ode_solver_type = ImplicitSolverType(OrdinaryDiffEq.Kvaerno3(\n            autodiff = false,\n            linsolve = LinSolveGMRES(),\n        )),\n        Courant_number = given_Fourier,\n        CFL_direction = VerticalDirection(),\n    )\nelse\n    given_Fourier = FT(0.7)\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config;\n        Courant_number = given_Fourier,\n        CFL_direction = VerticalDirection(),\n    )\nend;\n\n\ngrid = solver_config.dg.grid;\nQ = solver_config.Q;\naux = solver_config.dg.state_auxiliary;\n\n# ## Inspect the initial conditions\n\n# Let's export a plot of the initial state\noutput_dir = @__DIR__;\n\nmkpath(output_dir);\n\nz_scale = 100; # convert from meters to cm\nz_key = \"z\";\nz_label = \"z [cm]\";\nz = get_z(grid; z_scale = z_scale, rm_dupes = true);\n\n# Create an array to store the solution:\ndons_arr = Dict[dict_of_nodal_states(solver_config; interp = true)]  # store initial condition at ``t=0``\ntime_data = FT[0]                                      # store time data\n\nexport_plot(\n    z,\n    time_data,\n    dons_arr,\n    (\"ρcT\",),\n    joinpath(output_dir, \"initial_condition.png\");\n    xlabel = \"ρcT\",\n    ylabel = z_label,\n    xlims = (m.initialT - 1, m.T_bottom + 1),\n);\n# ![](initial_condition.png)\n\n# It matches what we have in `init_state_prognostic!(m::HeatModel, ...)`, so\n# let's continue.\n\n# # Solver hooks / callbacks\n\n# Define the number of outputs from `t0` to `timeend`\nconst n_outputs = 5;\n\n# This equates to exports every ceil(Int, timeend/n_outputs) time-step:\nconst every_x_simulation_time = ceil(Int, timeend / n_outputs);\n\n# The `ClimateMachine`'s time-steppers provide hooks, or callbacks, which\n# allow users to inject code to be executed at specified intervals. In this\n# callback, a dictionary of prognostic and auxiliary states are appended to\n# `dons_arr` for time the callback is executed. In addition, time is collected\n# and appended to `time_data`.\ncallback = GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n    push!(dons_arr, dict_of_nodal_states(solver_config; interp = true))\n    push!(time_data, gettime(solver_config.solver))\n    nothing\nend;\n\n# # Solve\n\n# This is the main `ClimateMachine` solver invocation. While users do not have\n# access to the time-stepping loop, code may be injected via `user_callbacks`,\n# which is a `Tuple` of callbacks in [`GenericCallbacks`](@ref ClimateMachine.GenericCallbacks).\nClimateMachine.invoke!(solver_config; user_callbacks = (callback,));\n\n# Append result at the end of the last time step:\npush!(dons_arr, dict_of_nodal_states(solver_config; interp = true));\npush!(time_data, gettime(solver_config.solver));\n\n# # Post-processing\n\n# Our solution is stored in the array of dictionaries `dons_arr` whose keys are\n# the output interval. The next level keys are the variable names, and the\n# values are the values along the grid:\n\n# To get `T` at ``t=0``, we can use `T_at_t_0 = dons_arr[1][\"T\"][:]`\n@show keys(dons_arr[1])\n\n# Let's plot the solution:\n\nexport_plot(\n    z,\n    time_data,\n    dons_arr,\n    (\"ρcT\",),\n    joinpath(output_dir, \"solution_vs_time.png\");\n    xlabel = \"ρcT\",\n    ylabel = z_label,\n);\n# ![](solution_vs_time.png)\n\nexport_contour(\n    z,\n    time_data,\n    dons_arr,\n    \"ρcT\",\n    joinpath(output_dir, \"solution_contour.png\");\n    ylabel = \"z [cm]\",\n)\n# ![](solution_contour.png)\n\n# The results look as we would expect: a fixed temperature at the bottom is\n# resulting in heat flux that propagates up the domain. To run this file, and\n# inspect the solution in `dons_arr`, include this tutorial in the Julia REPL\n# with:\n\n# ```julia\n# include(joinpath(\"tutorials\", \"Land\", \"Heat\", \"heat_equation.jl\"))\n# ```\n"
  },
  {
    "path": "tutorials/Land/Soil/Artifacts.toml",
    "content": "[bonan_soil_heat]\ngit-tree-sha1 = \"9f0933ccd6d902d0b75cc5bc9eb9dea3aa3708d7\"\n"
  },
  {
    "path": "tutorials/Land/Soil/Coupled/equilibrium_test.jl",
    "content": "# # Coupled heat and water equations tending towards equilibrium\n\n# Other tutorials, such as the [soil heat tutorial](../Heat/bonan_heat_tutorial.md)\n# and [Richards equation tutorial](../Water/equilibrium_test.md)\n# demonstrate how to solve the heat\n# equation or Richard's equation without considering\n# dynamic interactions between the two. As an example, the user could\n# prescribe a fixed function of space and time for the liquid water content,\n# and use that to drive the heat equation, but without allowing the water\n# content to dynamically evolve according to Richard's equation and without\n# allowing the changing temperature of the soil to affect the water\n# evolution.\n\n# Here we show how to solve the interacting heat and water equations,\n# in sand, but without phase changes. This allows us to capture\n# behavior that is not present in the decoupled equations.\n\n# The equations\n# are:\n\n# ``\n# \\frac{∂ ρe_{int}}{∂ t} =  ∇ ⋅ κ(θ_l, θ_i; ν, ...) ∇T + ∇ ⋅ ρe_{int_{liq}} K (T,θ_l, θ_i; ν, ...) \\nabla h( ϑ_l, z; ν, ...)\n# ``\n\n# ``\n# \\frac{ ∂ ϑ_l}{∂ t} = ∇ ⋅ K (T,θ_l, θ_i; ν, ...) ∇h( ϑ_l, z; ν, ...).\n# ``\n\n# Here\n\n# ``t`` is the time (s),\n\n# ``z`` is the location in the vertical (m),\n\n# ``ρe_{int}`` is the volumetric internal energy of the soil (J/m^3),\n\n# ``T`` is the temperature of the soil (K),\n\n# ``κ`` is the thermal conductivity (W/m/K),\n\n# ``ρe_{int_{liq}}`` is the volumetric internal energy of liquid water (J/m^3),\n\n# ``K`` is the hydraulic conductivity (m/s),\n\n# ``h`` is the hydraulic head (m),\n\n# ``ϑ_l`` is the augmented volumetric liquid water fraction,\n\n# ``θ_i`` is the volumetric ice fraction, and\n\n# ``ν, ...`` denotes parameters relating to soil type, such as porosity.\n\n\n# We will solve this equation in an effectively 1-d domain with ``z ∈ [-1,0]``,\n# and with the following boundary and initial conditions:\n\n# ``- κ ∇T(t, z = 0) = 0 ẑ``\n\n# `` -κ ∇T(t, z = -1) = 0 ẑ ``\n\n# `` T(t = 0, z) = T_{min} + (T_{max}-T_{min}) e^{Cz}``\n\n# ``- K ∇h(t, z = 0) = 0 ẑ ``\n\n# `` -K ∇h(t, z = -1) = 0 ẑ``\n\n# `` ϑ(t = 0, z) = ϑ_{min} + (ϑ_{max}-ϑ_{min}) e^{Cz}, ``\n\n# where ``C, T_{min}, T_{max}, ϑ_{min},`` and ``ϑ_{max}`` are\n# constants.\n\n\n# If we evolve this system for times long compared to the dynamical timescales\n# of the system, we expect it to reach an equilibrium where\n# the LHS of these equations tends to zero.\n# Assuming zero fluxes at the boundaries, the resulting equilibrium state\n# should satisfy ``∂h/∂z = 0`` and ``∂T/∂z = 0``. Physically, this means that\n# the water settles into a vertical profile in which\n# the resulting pressure balances gravity and that the temperature\n# is constant across the domain.\n\n#  We verify that the system is approaching this equilibrium, and we also sketch out\n# an analytic calculation for the final temperature in equilibrium.\n\n# # Import necessary modules\n# External (non - CliMA) modules\nusing MPI\nusing OrderedCollections\nusing StaticArrays\nusing Statistics\nusing Plots\n\n# CliMA Parameters\nusing CLIMAParameters\nusing CLIMAParameters.Planet: ρ_cloud_liq, ρ_cloud_ice, cp_l, cp_i, T_0, LH_f0\n\n# ClimateMachine modules\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.SoilWaterParameterizations\nusing ClimateMachine.Land.SoilHeatParameterizations\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods: BalanceLaw, LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw,\n    Prognostic,\n    Auxiliary,\n    Gradient,\n    GradientFlux,\n    vars_state,\n    parameter_set\n\n# # Preliminary set-up\n\n# Get the parameter set, which holds constants used across CliMA models:\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet();\n# Initialize and pick a floating point precision:\nClimateMachine.init()\nconst FT = Float64;\n\n# Load plot helpers:\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\ninclude(joinpath(clima_dir, \"docs\", \"plothelpers.jl\"));\n\n# Set soil parameters to be consistent with sand.\n# Please see e.g. the [soil heat tutorial](../Heat/bonan_heat_tutorial.md)\n# for other soil type parameters, or [Cosby1984](@cite).\n\n# The porosity:\nporosity = FT(0.395);\n# Soil solids\n# are the components of soil besides water, ice, gases, and air.\n# We specify the soil component fractions, relative to all soil solids.\n# These should sum to unity; they do not account for pore space.\nν_ss_quartz = FT(0.92)\nν_ss_minerals = FT(0.08)\nν_ss_om = FT(0.0)\nν_ss_gravel = FT(0.0);\n# Other parameters include the hydraulic conductivity at saturation, the specific\n# storage, and the van Genuchten parameters for sand.\n# We recommend Chapter 8 of  [Bonan19a](@cite) for finding parameters\n# for other soil types.\nKsat = FT(4.42 / 3600 / 100) # m/s\nS_s = FT(1e-3) #inverse meters\nvg_n = FT(1.89)\nvg_α = FT(7.5); # inverse meters\n\n\n# Other constants needed:\nκ_quartz = FT(7.7) # W/m/K\nκ_minerals = FT(2.5) # W/m/K\nκ_om = FT(0.25) # W/m/K\nκ_liq = FT(0.57) # W/m/K\nκ_ice = FT(2.29); # W/m/K\n# The particle density of organic material-free soil is\n# equal to the particle density of quartz and other minerals ([BallandArp2005](@cite)):\nρp = FT(2700); # kg/m^3\n# We calculate the thermal conductivities for the solid material\n# and for saturated soil. These functions are taken from [BallandArp2005](@cite).\nκ_solid = k_solid(ν_ss_om, ν_ss_quartz, κ_quartz, κ_minerals, κ_om)\nκ_sat_frozen = ksat_frozen(κ_solid, porosity, κ_ice)\nκ_sat_unfrozen = ksat_unfrozen(κ_solid, porosity, κ_liq);\n# Next, we calculate the volumetric heat capacity of dry soil. Dry soil\n# refers to soil that has no water content.\nρc_ds = FT((1 - porosity) * 1.926e06); # J/m^3/K\n# We collect the majority of the parameters needed\n# for modeling heat and water flow in soil in `soil_param_functions`,\n# an object of type [`SoilParamFunctions`](@ref ClimateMachine.Land.SoilParamFunctions).\n# Parameters used only for hydrology are stored in `water`, which\n# is of type\n# [`WaterParamFunctions`](@ref ClimateMachine.Land.WaterParamFunctions).\n\nsoil_param_functions = SoilParamFunctions(\n    FT;\n    porosity = porosity,\n    ν_ss_gravel = ν_ss_gravel,\n    ν_ss_om = ν_ss_om,\n    ν_ss_quartz = ν_ss_quartz,\n    ρc_ds = ρc_ds,\n    ρp = ρp,\n    κ_solid = κ_solid,\n    κ_sat_unfrozen = κ_sat_unfrozen,\n    κ_sat_frozen = κ_sat_frozen,\n    water = WaterParamFunctions(FT; Ksat = Ksat, S_s = S_s),\n);\n\n# # Initial and Boundary conditions\n\n# As we are not including the equations for phase changes in this tutorial,\n# we chose temperatures that are above the freezing point of water.\n\n# The initial temperature profile:\nfunction T_init(aux)\n    FT = eltype(aux)\n    zmax = FT(0)\n    zmin = FT(-1)\n    T_max = FT(289.0)\n    T_min = FT(288.0)\n    c = FT(20.0)\n    z = aux.z\n    output = T_min + (T_max - T_min) * exp(-(z - zmax) / (zmin - zmax) * c)\n    return output\nend;\n\n\n# The initial water profile:\nfunction ϑ_l0(aux)\n    FT = eltype(aux)\n    zmax = FT(0)\n    zmin = FT(-1)\n    theta_max = FT(porosity * 0.5)\n    theta_min = FT(porosity * 0.4)\n    c = FT(20.0)\n    z = aux.z\n    output =\n        theta_min +\n        (theta_max - theta_min) * exp(-(z - zmax) / (zmin - zmax) * c)\n    return output\nend;\n\n\n\n# The boundary value problem in this case\n# requires a boundary condition at the top and the bottom of the domain\n# for each equation being solved. These conditions can be Dirichlet, or Neumann.\n\n# Dirichlet boundary conditions are on `ϑ_l` and\n# `T`, while Neumann boundary conditions are on `-κ∇T` and `-K∇h`. For Neumann\n# conditions, the user supplies a scalar, which is multiplied by `ẑ` within the code.\n\n# Water boundary conditions:\nsurface_water_flux = (aux, t) -> eltype(aux)(0.0)\nbottom_water_flux = (aux, t) -> eltype(aux)(0.0);\n\n# The boundary conditions for the heat equation:\nsurface_heat_flux = (aux, t) -> eltype(aux)(0.0)\nbottom_heat_flux = (aux, t) -> eltype(aux)(0.0);\n\nbc = LandDomainBC(\n    bottom_bc = LandComponentBC(\n        soil_heat = Neumann(bottom_heat_flux),\n        soil_water = Neumann(bottom_water_flux),\n    ),\n    surface_bc = LandComponentBC(\n        soil_heat = Neumann(surface_heat_flux),\n        soil_water = Neumann(surface_water_flux),\n    ),\n);\n\n# Next, we define the required `init_soil!` function, which takes the user\n# specified functions of space for `T_init` and `ϑ_l0` and initializes the state\n# variables of volumetric internal energy and augmented liquid fraction. This requires\n# a conversion from `T` to `ρe_int`.\nfunction init_soil!(land, state, aux, localgeo, time)\n    myFT = eltype(state)\n    ϑ_l = myFT(land.soil.water.initialϑ_l(aux))\n    θ_i = myFT(land.soil.water.initialθ_i(aux))\n    state.soil.water.ϑ_l = ϑ_l\n    state.soil.water.θ_i = θ_i\n    param_set = parameter_set(land)\n\n    θ_l = volumetric_liquid_fraction(ϑ_l, land.soil.param_functions.porosity)\n    ρc_ds = land.soil.param_functions.ρc_ds\n    ρc_s = volumetric_heat_capacity(θ_l, θ_i, ρc_ds, param_set)\n\n    state.soil.heat.ρe_int = volumetric_internal_energy(\n        θ_i,\n        ρc_s,\n        land.soil.heat.initialT(aux),\n        param_set,\n    )\nend;\n\n\n# # Create the soil model structure\n# First, for water (this is where the hydrology parameters\n# are supplied):\nsoil_water_model = SoilWaterModel(\n    FT;\n    viscosity_factor = TemperatureDependentViscosity{FT}(),\n    moisture_factor = MoistureDependent{FT}(),\n    hydraulics = vanGenuchten(FT; α = vg_α, n = vg_n),\n    initialϑ_l = ϑ_l0,\n);\n\n\n# Note that the viscosity of water depends on temperature.\n# We account for the effect that has on the hydraulic conductivity\n# by specifying `viscosity_factor = TemperatureDependentViscosity{FT}()`.\n# The default, if no `viscosity_factor` keyword argument is supplied,\n# is to not include the effect of `T` on viscosity. More guidance about\n# specifying the\n# hydraulic conductivity, and the `hydraulics` model,\n# can be found in the [`hydraulic functions`](../Water/hydraulic_functions.md)\n# tutorial.\n\n# Repeat for heat:\nsoil_heat_model = SoilHeatModel(FT; initialT = T_init)\n\n\n# Combine into a single soil model:\nm_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model);\n\n\n# We aren't using any sources or sinks in the equations here, but this is where\n# freeze/thaw terms, runoff, root extraction, etc. would go.\nsources = ();\n\n\n# Create the LandModel - without other components (canopy, carbon, etc):\nm = LandModel(\n    param_set,\n    m_soil;\n    boundary_conditions = bc,\n    source = sources,\n    init_state_prognostic = init_soil!,\n);\n\n\n# # Specify the numerical details\n# Choose a resolution, domain boundaries, integration time,\n# timestep, and ODE solver.\n\nN_poly = 1\nnelem_vert = 50\nzmin = FT(-1)\nzmax = FT(0)\n\ndriver_config = ClimateMachine.SingleStackConfiguration(\n    \"LandModel\",\n    N_poly,\n    nelem_vert,\n    zmax,\n    param_set,\n    m;\n    zmin = zmin,\n    numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n)\n\nt0 = FT(0)\ntimeend = FT(60 * 60 * 72)\ndt = FT(30.0)\n\n\nsolver_config =\n    ClimateMachine.SolverConfiguration(t0, timeend, driver_config, ode_dt = dt);\n\n\n# Determine how often you want output:\nconst n_outputs = 4\nconst every_x_simulation_time = ceil(Int, timeend / n_outputs);\n\n# Store initial condition at ``t=0``,\n# including prognostic, auxiliary, and\n# gradient flux variables:\nstate_types = (Prognostic(), Auxiliary(), GradientFlux())\ndons_arr = Dict[dict_of_nodal_states(solver_config, state_types; interp = true)]\ntime_data = FT[0] # store time data\n\n# We specify a function which evaluates `every_x_simulation_time` and returns\n# the state vector, appending the variables we are interested in into\n# `dons_arr`.\n\ncallback = GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n    dons = dict_of_nodal_states(solver_config, state_types; interp = true)\n    push!(dons_arr, dons)\n    push!(time_data, gettime(solver_config.solver))\n    nothing\nend;\n\n# # Run the integration\nClimateMachine.invoke!(solver_config; user_callbacks = (callback,));\n\n# Get z-coordinate\nz = get_z(solver_config.dg.grid; rm_dupes = true);\n\n# Let's export a plot of the initial state\noutput_dir = @__DIR__;\n\nmkpath(output_dir);\n\nexport_plot(\n    z,\n    time_data ./ (60 * 60 * 24),\n    dons_arr,\n    (\"soil.water.ϑ_l\",),\n    joinpath(output_dir, \"eq_moisture_plot.png\");\n    xlabel = \"ϑ_l\",\n    ylabel = \"z (m)\",\n    time_units = \"(days)\",\n)\n# ![](eq_moisture_plot.png)\n\nexport_plot(\n    z,\n    time_data[2:end] ./ (60 * 60 * 24),\n    dons_arr[2:end],\n    (\"soil.water.K∇h[3]\",),\n    joinpath(output_dir, \"eq_hydraulic_head_plot.png\");\n    xlabel = \"K∇h (m/s)\",\n    ylabel = \"z (m)\",\n    time_units = \"(days)\",\n)\n# ![](eq_hydraulic_head_plot.png)\n\nexport_plot(\n    z,\n    time_data ./ (60 * 60 * 24),\n    dons_arr,\n    (\"soil.heat.T\",),\n    joinpath(output_dir, \"eq_temperature_plot.png\");\n    xlabel = \"T (K)\",\n    ylabel = \"z (m)\",\n    time_units = \"(days)\",\n)\n# ![](eq_temperature_plot.png)\n\nexport_plot(\n    z,\n    time_data[2:end] ./ (60 * 60 * 24),\n    dons_arr[2:end],\n    (\"soil.heat.κ∇T[3]\",),\n    joinpath(output_dir, \"eq_heat_plot.png\");\n    xlabel = \"κ∇T\",\n    ylabel = \"z (m)\",\n    time_units = \"(days)\",\n)\n# ![](eq_heat_plot.png)\n\n# # Analytic Expectations\n\n# We can determine a priori what we expect the final temperature to be in\n# equilibrium.\n\n# Regardless of the final water profile in equilibrium, we know that\n# the final temperature `T_f` will be a constant across the domain. All\n# water that began with a temperature above this point will cool to `T_f`,\n# and water that began with a temperature below this point will warm to\n# `T_f`. The initial function `T(z)` is equal to `T_f` at a value of\n# `z = z̃`. This is the location in space which divides these two groups\n# (water that warms over time and water that cools over time) spatially.\n# We can solve for `z̃(T_f)` using `T_f = T(z̃)`.\n\n# Next, we can determine the change in energy required to cool\n# the water above `z̃` to `T_f`: it is the integral from `z̃` to the surface\n# at `z = 0` of ` c θ(z) T(z) `, where `c` is the volumetric heat capacity -\n# a constant here - and `θ(z)` is the initial water profile. Compute the energy\n# required to warm the water below `z̃` to `T_f` in a similar way, set equal, and solve\n# for `T_f`. This results in `T_f = 288.056`, which is very close to the mean `T` we observe\n# after 3 days, of `288.054`.\n\n# One could also solve the equation for `ϑ_l` specified by\n# ``∂ h/∂ z = 0`` to determine the functional form of the\n# equilibrium profile of the liquid water.\n\n# # References\n# - [Bonan19a](@cite)\n# - [BallandArp2005](@cite)\n# - [Cosby1984](@cite)\n"
  },
  {
    "path": "tutorials/Land/Soil/Heat/bonan_heat_tutorial.jl",
    "content": "# # Solving the heat equation in soil\n\n# This tutorial shows how to use CliMA code to solve the heat\n# equation in soil.\n# For background on the heat equation in general,\n# and how to solve it using CliMA code, please see the\n# [`heat_equation.jl`](../../Heat/heat_equation.md)\n# tutorial.\n\n# The version of the heat equation we are solving here assumes no\n# sources or sinks and no flow of liquid water. It takes the form\n\n# ``\n# \\frac{∂ ρe_{int}}{∂ t} =  ∇ ⋅ κ(θ_l, θ_i; ν, ...) ∇T\n# ``\n\n# Here\n\n# ``t`` is the time (s),\n\n# ``z`` is the location in the vertical (m),\n\n# ``ρe_{int}`` is the volumetric internal energy of the soil (J/m^3),\n\n# ``T`` is the temperature of the soil (K),\n\n# ``κ`` is the thermal conductivity (W/m/K),\n\n# ``ϑ_l`` is the augmented volumetric liquid water fraction,\n\n# ``θ_i`` is the volumetric ice fraction, and\n\n# ``ν, ...`` denotes parameters relating to soil type, such as porosity.\n\n\n# We will solve this equation in an effectively 1-d domain with ``z ∈ [-1,0]``,\n# and with the following boundary and initial conditions:\n\n# ``T(t=0, z) = 277.15^\\circ K``\n\n# ``T(t, z = 0) = 288.15^\\circ K ``\n\n# `` -κ ∇T(t, z = -1) = 0 ẑ``\n\n\n# The temperature ``T`` and\n# volumetric internal energy ``ρe_{int}`` are related as\n\n# ``\n# ρe_{int} = ρc_s (θ_l, θ_i; ν, ...) (T - T_0) - θ_i ρ_i LH_{f0}\n# ``\n\n# where\n\n# ``ρc_s`` is the volumetric heat capacity of the soil (J/m^3/K),\n\n# ``T_0`` is the freezing temperature of water,\n\n# ``ρ_i`` is the density of ice (kg/m^3), and\n\n# ``LH_{f0}`` is the latent heat of fusion at ``T_0``.\n\n# In this tutorial, we will use a [`PrescribedWaterModel`](@ref\n# ClimateMachine.Land.PrescribedWaterModel). This option allows\n# the user to specify a function for the spatial and temporal\n# behavior of `θ_i` and `θ_l`; it does not solve Richard's equation\n# for the evolution of moisture. Please see the tutorials\n# in the `Soil/Coupled/` folder or the `Soil/Water/`\n# folder for information on solving\n# Richard's equation, either coupled or uncoupled from the heat equation, respectively.\n\n\n\n# # Import necessary modules\n\n# External (non - CliMA) modules\nusing MPI\nusing OrderedCollections\nusing StaticArrays\nusing Statistics\nusing Dierckx\nusing Plots\nusing DelimitedFiles\n\n# CliMA Parameters\nusing CLIMAParameters\nusing CLIMAParameters.Planet: ρ_cloud_liq, ρ_cloud_ice, cp_l, cp_i, T_0, LH_f0\n\n\n# ClimateMachine modules\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.SoilWaterParameterizations\nusing ClimateMachine.Land.SoilHeatParameterizations\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods: BalanceLaw, LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw,\n    Prognostic,\n    Auxiliary,\n    Gradient,\n    GradientFlux,\n    vars_state,\n    parameter_set\nimport ClimateMachine.DGMethods: calculate_dt\nusing ArtifactWrappers\n\n# # Preliminary set-up\n# Get the parameter set, which holds constants used across CliMA models.\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet();\n# Initialize and pick a floating point precision.\nClimateMachine.init()\nconst FT = Float32;\n# Load functions that will help with plotting\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\ninclude(joinpath(clima_dir, \"docs\", \"plothelpers.jl\"));\n\n# # Determine soil parameters\n\n# Below are the soil component fractions for various soil\n# texture classes,  from [Cosby1984](@cite) and [Bonan19a](@cite).\n# Note that these fractions are volumetric fractions, relative\n# to other soil solids, i.e. not including pore space. These are denoted `ν_ss_i`; the CliMA\n# Land Documentation uses the symbol `ν_i` to denote the volumetric fraction\n# of a soil component `i` relative to the soil, including pore space.\n\nν_ss_silt_array =\n    FT.(\n        [5.0, 12.0, 32.0, 70.0, 39.0, 15.0, 56.0, 34.0, 6.0, 47.0, 20.0] ./\n        100.0,\n    )\nν_ss_quartz_array =\n    FT.(\n        [92.0, 82.0, 58.0, 17.0, 43.0, 58.0, 10.0, 32.0, 52.0, 6.0, 22.0] ./\n        100.0,\n    )\nν_ss_clay_array =\n    FT.(\n        [3.0, 6.0, 10.0, 13.0, 18.0, 27.0, 34.0, 34.0, 42.0, 47.0, 58.0] ./\n        100.0,\n    )\nporosity_array =\n    FT.([\n        0.395,\n        0.410,\n        0.435,\n        0.485,\n        0.451,\n        0.420,\n        0.477,\n        0.476,\n        0.426,\n        0.492,\n        0.482,\n    ]);\n\n\n# The soil types that correspond to array elements above are, in order,\n# sand, loamy sand, sandy loam, silty loam, loam, sandy clay loam,\n# silty clay loam, clay loam, sandy clay, silty clay, and clay.\n\n# Here we choose the soil type to be sandy.\n# The soil column is uniform in space and time.\nsoil_type_index = 1\nν_ss_minerals =\n    ν_ss_clay_array[soil_type_index] + ν_ss_silt_array[soil_type_index]\nν_ss_quartz = ν_ss_quartz_array[soil_type_index]\nporosity = porosity_array[soil_type_index];\n\n\n# This tutorial additionally compares the output of a ClimateMachine simulation with that\n# of Supplemental Program 2, Chapter 5, of [Bonan19a](@cite).\n#  We found this useful as it\n# allows us compare results from our code against a published version.\n\n\n# The simulation code of [Bonan19a](@cite) employs a formalism for the thermal\n# conductivity `κ` based on [Johanson1975](@cite). It assumes\n# no organic matter, and only requires the volumetric\n# fraction of soil solids for quartz and other minerals.\n# ClimateMachine employs the formalism of [BallandArp2005](@cite),\n# which requires the\n# fraction of soil solids for quartz, gravel,\n# organic matter, and other minerals. [Dai2019a](@cite) found\n# the model of [BallandArp2005](@cite) to better match\n# measured soil properties across a range of soil types.\n\n# To compare the output of the two simulations, we set the organic\n# matter content and gravel content to zero in the CliMA model.\n# The remaining soil components (quartz and other minerals) match between\n# the two. We also run the simulation for relatively wet soil\n# (water content at 80% of porosity). Under these conditions,\n# the two formulations for `κ`, though taking different functional forms,\n# are relatively consistent.\n# The differences between models are important\n# for soil with organic material and for soil that is relatively dry.\nν_ss_om = FT(0.0)\nν_ss_gravel = FT(0.0);\n\n\n# We next calculate a few intermediate quantities needed for the\n# determination of the thermal conductivity ([BallandArp2005](@cite)). These include\n# the conductivity of the solid material, the conductivity\n# of saturated soil, and the conductivity of frozen saturated soil.\n\nκ_quartz = FT(7.7) # W/m/K\nκ_minerals = FT(2.5) # W/m/K\nκ_om = FT(0.25) # W/m/K\nκ_liq = FT(0.57) # W/m/K\nκ_ice = FT(2.29); # W/m/K\n\n# The particle density of soil solids in moisture-free soil\n# is taken as a constant, across soil types, as in [Bonan19a](@cite).\n# This is a good estimate for organic material free soil. The user is referred to\n# [BallandArp2005](@cite) for a more general expression.\nρp = FT(2700) # kg/m^3\nκ_solid = k_solid(ν_ss_om, ν_ss_quartz, κ_quartz, κ_minerals, κ_om)\nκ_sat_frozen = ksat_frozen(κ_solid, porosity, κ_ice)\nκ_sat_unfrozen = ksat_unfrozen(κ_solid, porosity, κ_liq);\n\n# The thermal conductivity of dry soil is also required, but this is\n# calculated internally using the expression of [3].\n\n# The volumetric specific heat of dry soil is chosen so as to match Bonan's simulation.\n# The user could instead compute this using a volumetric fraction weighted average\n# across soil components.\nρc_ds = FT((1 - porosity) * 1.926e06) # J/m^3/K\n\n# Finally, we store the soil-specific parameters and functions\n# in a place where they will be accessible to the model\n# during integration.\nsoil_param_functions = SoilParamFunctions(\n    FT;\n    porosity = porosity,\n    ν_ss_gravel = ν_ss_gravel,\n    ν_ss_om = ν_ss_om,\n    ν_ss_quartz = ν_ss_quartz,\n    ρc_ds = ρc_ds,\n    ρp = ρp,\n    κ_solid = κ_solid,\n    κ_sat_unfrozen = κ_sat_unfrozen,\n    κ_sat_frozen = κ_sat_frozen,\n);\n\n\n# # Initial and Boundary conditions\n# We will be using a [`PrescribedWaterModel`](@ref\n# ClimateMachine.Land.PrescribedWaterModel), where the user supplies the augmented\n# liquid fraction and ice fraction as functions of space and time. Since we are not\n# implementing phase changes, it makes sense to either have entirely liquid or\n# frozen water. This tutorial shows liquid water.\n\n# Because the two models for thermal conductivity agree well for wetter soil, we'll\n# choose that here. However, the user could also explore how they differ by choosing\n# drier soil.\n\n# Please note that if the user uses a mix of liquid and frozen water, that they must\n# ensure that the total water content does not exceed porosity.\nprescribed_augmented_liquid_fraction = FT(porosity * 0.8)\nprescribed_volumetric_ice_fraction = FT(0.0);\n\n\n# Choose boundary and initial conditions for heat that will not lead to freezing of water:\nheat_surface_state = (aux, t) -> eltype(aux)(288.15)\nheat_bottom_flux = (aux, t) -> eltype(aux)(0.0)\nT_init = (aux) -> eltype(aux)(275.15);\n\n# The boundary value problem in this case, with two spatial derivatives,\n# requires a boundary condition at the top of the domain and the bottom.\n# Here we choose to specify a bottom flux condition, and a top state condition.\n# Our problem is effectively 1D, so we do not need to specify lateral boundary\n# conditions.\nbc = LandDomainBC(\n    bottom_bc = LandComponentBC(soil_heat = Neumann(heat_bottom_flux)),\n    surface_bc = LandComponentBC(soil_heat = Dirichlet(heat_surface_state)),\n);\n\n# We also need to define a function `init_soil!`, which\n# initializes all of the prognostic variables (here, we\n# only have `ρe_int`, the volumetric internal energy).\n# The initialization is based on user-specified\n# initial conditions. Note that the user provides initial\n# conditions for heat based on the temperature - `init_soil!` also\n# converts between `T` and `ρe_int`.\n\nfunction init_soil!(land, state, aux, localgeo, time)\n    param_set = parameter_set(land)\n    ϑ_l, θ_i = get_water_content(land.soil.water, aux, state, time)\n    θ_l = volumetric_liquid_fraction(ϑ_l, land.soil.param_functions.porosity)\n    ρc_ds = land.soil.param_functions.ρc_ds\n    ρc_s = volumetric_heat_capacity(θ_l, θ_i, ρc_ds, param_set)\n\n    state.soil.heat.ρe_int = volumetric_internal_energy(\n        θ_i,\n        ρc_s,\n        land.soil.heat.initialT(aux),\n        param_set,\n    )\nend;\n\n\n# # Create the model structure\nsoil_water_model = PrescribedWaterModel(\n    (aux, t) -> prescribed_augmented_liquid_fraction,\n    (aux, t) -> prescribed_volumetric_ice_fraction,\n);\n\nsoil_heat_model = SoilHeatModel(FT; initialT = T_init);\n\n# The full soil model requires a heat model and a water model, as well as the\n# soil parameter functions:\nm_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model);\n\n# The equations being solved in this tutorial have no sources or sinks:\nsources = ();\n\n# Finally, we create the `LandModel`. In more complex land models, this would\n# include the canopy, carbon state of the soil, etc.\nm = LandModel(\n    param_set,\n    m_soil;\n    boundary_conditions = bc,\n    source = sources,\n    init_state_prognostic = init_soil!,\n);\n\n\n# # Specify the numerical details\n# These include the resolution, domain boundaries, integration time,\n# Courant number, and ODE solver.\n\nN_poly = 1\nnelem_vert = 100\n\nzmax = FT(0)\nzmin = FT(-1)\n\ndriver_config = ClimateMachine.SingleStackConfiguration(\n    \"LandModel\",\n    N_poly,\n    nelem_vert,\n    zmax,\n    param_set,\n    m;\n    zmin = zmin,\n    numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n);\n\n\n# In this tutorial, we determine a timestep based on a Courant number (\n# also called a Fourier number in the context of the heat equation).\n# In short, we can use the parameters of the model (`κ` and `ρc_s`),\n# along with with the size of\n# elements of the grid used for discretizing the PDE, to estimate\n# a natural timescale for heat transfer across a grid cell.\n# Because we are using an explicit ODE solver, the timestep should\n# be a fraction of this in order to resolve the dynamics.\n\n# This allows us to automate, to a certain extent, choosing a value for\n# the timestep, even as we switch between soil types.\n\nfunction calculate_dt(dg, model::LandModel, Q, Courant_number, t, direction)\n    Δt = one(eltype(Q))\n    CFL = DGMethods.courant(diffusive_courant, dg, model, Q, Δt, t, direction)\n    return Courant_number / CFL\nend\n\nfunction diffusive_courant(\n    m::LandModel,\n    state::Vars,\n    aux::Vars,\n    diffusive::Vars,\n    Δx,\n    Δt,\n    t,\n    direction,\n)\n    param_set = parameter_set(m)\n    soil = m.soil\n    ϑ_l, θ_i = get_water_content(soil.water, aux, state, t)\n    θ_l = volumetric_liquid_fraction(ϑ_l, soil.param_functions.porosity)\n    κ_dry = k_dry(param_set, soil.param_functions)\n    S_r = relative_saturation(θ_l, θ_i, soil.param_functions.porosity)\n    kersten = kersten_number(θ_i, S_r, soil.param_functions)\n    κ_sat = saturated_thermal_conductivity(\n        θ_l,\n        θ_i,\n        soil.param_functions.κ_sat_unfrozen,\n        soil.param_functions.κ_sat_frozen,\n    )\n    κ = thermal_conductivity(κ_dry, kersten, κ_sat)\n    ρc_ds = soil.param_functions.ρc_ds\n    ρc_s = volumetric_heat_capacity(θ_l, θ_i, ρc_ds, param_set)\n    return Δt * κ / (Δx * Δx * ρc_ds)\nend\n\n\nt0 = FT(0)\ntimeend = FT(60 * 60 * 3)\nCourant_number = FT(0.5) # much bigger than this leads to domain errors\n\nsolver_config = ClimateMachine.SolverConfiguration(\n    t0,\n    timeend,\n    driver_config;\n    Courant_number = Courant_number,\n    CFL_direction = VerticalDirection(),\n);\n\n\n# # Run the integration\nClimateMachine.invoke!(solver_config);\nstate_types = (Prognostic(), Auxiliary())\ndons = dict_of_nodal_states(solver_config, state_types; interp = true)\n\n# # Plot results and comparison data from [Bonan19a](@cite)\n\nz = get_z(solver_config.dg.grid; rm_dupes = true);\nT = dons[\"soil.heat.T\"];\nplot(\n    T,\n    z,\n    label = \"ClimateMachine\",\n    ylabel = \"z (m)\",\n    xlabel = \"T (K)\",\n    title = \"Heat transfer in sand\",\n)\nplot!(T_init.(z), z, label = \"Initial condition\")\nfilename = \"bonan_heat_data.csv\"\nbonan_dataset = ArtifactWrapper(\n    @__DIR__,\n    isempty(get(ENV, \"CI\", \"\")),\n    \"bonan_soil_heat\",\n    ArtifactFile[ArtifactFile(\n        url = \"https://caltech.box.com/shared/static/99vm8q8tlyoulext6c35lnd3355tx6bu.csv\",\n        filename = filename,\n    ),],\n)\nbonan_dataset_path = get_data_folder(bonan_dataset)\ndata = joinpath(bonan_dataset_path, filename)\nds_bonan = readdlm(data, ',')\nbonan_T = reverse(ds_bonan[:, 2])\nbonan_z = reverse(ds_bonan[:, 1])\nbonan_T_continuous = Spline1D(bonan_z, bonan_T)\nbonan_at_clima_z = bonan_T_continuous.(z)\nplot!(bonan_at_clima_z, z, label = \"Bonan simulation\")\nplot!(legend = :bottomleft)\nsavefig(\"thermal_conductivity_comparison.png\")\n# ![](thermal_conductivity_comparison.png)\n\n# The plot shows that the temperature at the top of the\n# soil is gradually increasing. This is because the surface\n# temperature is held fixed at a value larger than\n# the initial temperature. If we ran this for longer,\n# we would see that the bottom of the domain would also\n# increase in temperature because there is no heat\n# leaving the bottom (due to zero heat flux specified in\n# the boundary condition).\n\n# # References\n# - [Bonan19a](@cite)\n# - [Johanson1975](@cite)\n# - [BallandArp2005](@cite)\n# - [Dai2019a](@cite)\n# - [Cosby1984](@cite)\n"
  },
  {
    "path": "tutorials/Land/Soil/PhaseChange/freezing_front.jl",
    "content": "# # Modeling a freezing front in unsaturated soil\n\n# Before reading this tutorial, \n# we recommend that you look over the coupled energy\n# and water [tutorial](../Coupled/equilibrium_test.md).\n# That tutorial showed how to solve the heat equation for soil volumetric\n# internal energy `ρe_int`, simultaneously\n# with Richards equation for volumetric liquid water fraction `ϑ_l`, assuming zero\n# volumetric ice fraction `θ_i` for all time, everywhere in the domain[^a].\n# In this example, we add in a source term to the right hand side for both `θ_i`\n# and `ϑ_l` which models freezing and thawing and conserves water mass during the process.\n# The equations are\n\n\n# ``\n# \\frac{∂ ρe_{int}}{∂ t} =  ∇ ⋅ κ(θ_l, θ_i; ν, ...) ∇T + ∇ ⋅ ρe_{int_{liq}} K (T,θ_l, θ_i; ν, ...) \\nabla h( ϑ_l, z; ν, ...)\n# ``\n\n# ``\n# \\frac{ ∂ ϑ_l}{∂ t} = ∇ ⋅ K (T,θ_l, θ_i; ν, ...) ∇h( ϑ_l, z; ν, ...) -\\frac{F_T}{ρ_l}\n# ``\n\n# ``\n# \\frac{ ∂ θ_i}{∂ t} = \\frac{F_T}{ρ_i}\n# ``\n\n# Here\n\n# ``t`` is the time (s),\n\n# ``z`` is the location in the vertical (m),\n\n# ``ρe_{int}`` is the volumetric internal energy of the soil (J/m^3),\n\n# ``T`` is the temperature of the soil (K),\n\n# ``κ`` is the thermal conductivity (W/m/K),\n\n# ``ρe_{int_{liq}}`` is the volumetric internal energy of liquid water (J/m^3),\n\n# ``K`` is the hydraulic conductivity (m/s),\n\n# ``h`` is the hydraulic head (m),\n\n# ``ϑ_l`` is the augmented volumetric liquid water fraction,\n\n# ``θ_i`` is the volumetric ice fraction,\n\n# ``ν, ...`` denotes parameters relating to soil type, such as porosity, and\n\n# ``F_T`` is the freeze-thaw term.\n\n# To begin, we will show how to implement adding in this source term. After the results are obtained,\n# we will [explain](#Discussion-and-Model-Explanation) how our model parameterizes this effect and\n# compare the results with some analytic expections.\n\n# We solve these equations in an effectively 1-d domain with ``z ∈ [-0.2,0]``,\n# and with the following boundary and initial conditions:\n\n# ``- κ ∇T(t, z = 0) = 28 W/m^2/K (T - 267.15K) ẑ``\n\n# ``- κ ∇T(t, z= -0.2) =  0 ẑ ``\n\n# `` T(t = 0, z) = 279.85 K``\n\n# ``- K ∇h(t, z = 0) = 0 ẑ ``\n\n# `` -K ∇h(t, z = -0.2) = 0 ẑ``\n\n# `` ϑ_l(t = 0, z) = 0.33 ``.\n\n# The problem setup and soil properties are chosen to match the lab experiment of [Mizoguchi1990](@cite), as detailed in [Hansson2004](@cite) and [DallAmico2011](@cite)].\n\n# # Import necessary modules\n# External (non - CliMA) modules\nusing MPI\nusing OrderedCollections\nusing StaticArrays\nusing Statistics\nusing Test\nusing DelimitedFiles\nusing Plots\n\n# CliMA Parameters\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\nusing CLIMAParameters.Planet: ρ_cloud_liq\nusing CLIMAParameters.Planet: ρ_cloud_ice\n\n# ClimateMachine modules\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.SoilWaterParameterizations\nusing ClimateMachine.Land.SoilHeatParameterizations\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods: BalanceLaw, LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux, vars_state\nusing ArtifactWrappers\n\n# # Preliminary set-up\n\n# Get the parameter set, which holds constants used across CliMA models:\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet();\n# Initialize and pick a floating point precision:\nClimateMachine.init()\nconst FT = Float64;\n\n# Load plot helpers:\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\ninclude(joinpath(clima_dir, \"docs\", \"plothelpers.jl\"));\n\n\n# # Simulation specific parameters\nN_poly = 1\nnelem_vert = 20\nzmax = FT(0)\nzmin = FT(-0.2)\nt0 = FT(0)\ndt = FT(6)\ntimeend = FT(3600 * 50)\nn_outputs = 50\nevery_x_simulation_time = ceil(Int, timeend / n_outputs)\nΔ = abs(zmin - zmax) / FT(nelem_vert);\n\n# # Soil properties.\n# All are given in mks units.\nν = FT(0.535)\nθ_r = FT(0.05)\nS_s = FT(1e-3)\nKsat = FT(3.2e-6)\nvg_α = 1.11\nvg_n = 1.48;\n\nν_ss_quartz = FT(0.7)\nν_ss_minerals = FT(0.0)\nν_ss_om = FT(0.3)\nν_ss_gravel = FT(0.0);\nκ_quartz = FT(7.7)\nκ_minerals = FT(2.4)\nκ_om = FT(0.25)\nκ_liq = FT(0.57)\nκ_ice = FT(2.29);\nκ_solid = k_solid(ν_ss_om, ν_ss_quartz, κ_quartz, κ_minerals, κ_om)\nκ_sat_frozen = ksat_frozen(κ_solid, ν, κ_ice)\nκ_sat_unfrozen = ksat_unfrozen(κ_solid, ν, κ_liq);\nρp = FT(3200)\nρc_ds = FT((1 - ν) * 2.3e6);\n\n\nsoil_param_functions = SoilParamFunctions(\n    FT;\n    porosity = ν,\n    ν_ss_gravel = ν_ss_gravel,\n    ν_ss_om = ν_ss_om,\n    ν_ss_quartz = ν_ss_quartz,\n    ρc_ds = ρc_ds,\n    ρp = ρp,\n    κ_solid = κ_solid,\n    κ_sat_unfrozen = κ_sat_unfrozen,\n    κ_sat_frozen = κ_sat_frozen,\n    water = WaterParamFunctions(FT; Ksat = Ksat, S_s = S_s, θ_r = θ_r),\n);\n\n\n# # Build the model\n# Initial and Boundary conditions. The default initial condition for\n# `θ_i` is zero everywhere, so we don't modify that. Furthermore, since\n# the equation for `θ_i` does not involve spatial derivatives, we don't need\n# to supply boundary conditions for it. Note that Neumann fluxes, when chosen,\n# are specified by giving the magnitude of the normal flux *into* the domain.\n# In this case, the normal vector at the surface n̂ = ẑ. Internally, we multiply\n# the flux magnitude by -n̂.\nzero_flux = (aux, t) -> eltype(aux)(0.0)\nsurface_heat_flux =\n    (aux, t) -> eltype(aux)(-28) * (aux.soil.heat.T - eltype(aux)(273.15 - 6))\nT_init = aux -> eltype(aux)(279.85)\nϑ_l0 = (aux) -> eltype(aux)(0.33);\n\nbc = LandDomainBC(\n    bottom_bc = LandComponentBC(\n        soil_heat = Neumann(zero_flux),\n        soil_water = Neumann(zero_flux),\n    ),\n    surface_bc = LandComponentBC(\n        soil_heat = Neumann(surface_heat_flux),\n        soil_water = Neumann(zero_flux),\n    ),\n);\n\n# Create the [`SoilWaterModel`](@ref ClimateMachine.Land.SoilWaterModel),\n# [`SoilHeatModel`](@ref ClimateMachine.Land.SoilHeatModel),\n# and the [`SoilModel`](@ref ClimateMachine.Land.SoilModel) instances.\n# Note that we are allowing for the hydraulic conductivity to be affected by\n# both temperature and ice fraction by choosing the following\n# [`viscosity_factor`](@ref ClimateMachine.Land.SoilWaterParameterizations.viscosity_factor)\n# and [`impedance_factor`](@ref ClimateMachine.Land.SoilWaterParameterizations.impedance_factor).\n# To turn these off - the default - just remove these lines. These factors are explained more\n# [here](../Water/hydraulic_functions.md).\nsoil_water_model = SoilWaterModel(\n    FT;\n    viscosity_factor = TemperatureDependentViscosity{FT}(),\n    moisture_factor = MoistureDependent{FT}(),\n    impedance_factor = IceImpedance{FT}(Ω = 7.0),\n    hydraulics = vanGenuchten(FT; α = vg_α, n = vg_n),\n    initialϑ_l = ϑ_l0,\n)\n\nsoil_heat_model = SoilHeatModel(FT; initialT = T_init);\n\nm_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model);\n\n# Create the source term instance. Our phase change model requires\n# knowledge of the vertical spacing, so we pass\n# that information in via an attribute of the\n# [`PhaseChange`](@ref ClimateMachine.Land.PhaseChange) structure.\nfreeze_thaw_source = PhaseChange{FT}(Δz = Δ);\n\n# Sources are added as elements of a list of sources. Here we just add freezing\n# and thawing.\nsources = (freeze_thaw_source,);\n\n# Next, we define the required `init_soil!` function, which takes the user\n# specified functions of space for `T_init` and `ϑ_l0` and initializes the state\n# variables of volumetric internal energy and augmented liquid fraction. This requires\n# a conversion from `T` to `ρe_int`.\nfunction init_soil!(land, state, aux, localgeo, time)\n    myFT = eltype(state)\n    ϑ_l = myFT(land.soil.water.initialϑ_l(aux))\n    θ_i = myFT(land.soil.water.initialθ_i(aux))\n    state.soil.water.ϑ_l = ϑ_l\n    state.soil.water.θ_i = θ_i\n    param_set = land.param_set\n\n    θ_l = volumetric_liquid_fraction(ϑ_l, land.soil.param_functions.porosity)\n    ρc_ds = land.soil.param_functions.ρc_ds\n    ρc_s = volumetric_heat_capacity(θ_l, θ_i, ρc_ds, param_set)\n\n    state.soil.heat.ρe_int = volumetric_internal_energy(\n        θ_i,\n        ρc_s,\n        land.soil.heat.initialT(aux),\n        param_set,\n    )\nend;\n\n# Lastly, package it all up in the `LandModel`:\nm = LandModel(\n    param_set,\n    m_soil;\n    boundary_conditions = bc,\n    source = sources,\n    init_state_prognostic = init_soil!,\n);\n\n# # Build the simulation domain, solver, and callbacks\n\ndriver_config = ClimateMachine.SingleStackConfiguration(\n    \"LandModel\",\n    N_poly,\n    nelem_vert,\n    zmax,\n    param_set,\n    m;\n    zmin = zmin,\n    numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n);\n\n\nsolver_config =\n    ClimateMachine.SolverConfiguration(t0, timeend, driver_config, ode_dt = dt);\n\ndg = solver_config.dg\nQ = solver_config.Q\nstate_types = (Prognostic(), Auxiliary(), GradientFlux())\ndons_arr = Dict[dict_of_nodal_states(solver_config, state_types; interp = true)]\ntime_data = FT[0]\ncallback = GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n    dons = dict_of_nodal_states(solver_config, state_types; interp = true)\n    push!(dons_arr, dons)\n    push!(time_data, gettime(solver_config.solver))\n    nothing\nend;\n\n# # Run the simulation, and plot the output\nClimateMachine.invoke!(solver_config; user_callbacks = (callback,));\n\nz = get_z(solver_config.dg.grid; rm_dupes = true);\n\noutput_dir = @__DIR__;\n\nmkpath(output_dir);\n\nexport_plot(\n    z,\n    time_data[[1, 16, 31, 46]] ./ (60 * 60),\n    dons_arr[[1, 16, 31, 46]],\n    (\"soil.water.ϑ_l\",),\n    joinpath(output_dir, \"moisture_plot.png\");\n    xlabel = \"ϑ_l\",\n    ylabel = \"z (m)\",\n    time_units = \"hrs \",\n)\n# ![](moisture_plot.png)\n\nexport_plot(\n    z,\n    time_data[[1, 16, 31, 46]] ./ (60 * 60),\n    dons_arr[[1, 16, 31, 46]],\n    (\"soil.water.θ_i\",),\n    joinpath(output_dir, \"ice_plot.png\");\n    xlabel = \"θ_i\",\n    ylabel = \"z (m)\",\n    time_units = \"hrs \",\n    legend = :bottomright,\n)\n# ![](ice_plot.png)\nexport_plot(\n    z,\n    time_data[[1, 16, 31, 46]] ./ (60 * 60),\n    dons_arr[[1, 16, 31, 46]],\n    (\"soil.heat.T\",),\n    joinpath(output_dir, \"T_plot.png\");\n    xlabel = \"T (K)\",\n    ylabel = \"z (m)\",\n    time_units = \"hrs \",\n)\n# ![](T_plot.png)\n\n# # Comparison to data\n# This data was obtained by us from the figures of [Hansson2004](@cite), but was originally obtained\n# by [Mizoguchi1990](@cite). No error bars were reported, and we haven't quantified the error in our\n# estimation of the data from images.\ndataset = ArtifactWrapper(\n    @__DIR__,\n    isempty(get(ENV, \"CI\", \"\")),\n    \"mizoguchi\",\n    ArtifactFile[ArtifactFile(\n        url = \"https://caltech.box.com/shared/static/3xbo4rlam8u390vmucc498cao6wmqlnd.csv\",\n        filename = \"mizoguchi_all_data.csv\",\n    ),],\n);\ndataset_path = get_data_folder(dataset);\ndata = joinpath(dataset_path, \"mizoguchi_all_data.csv\")\nds = readdlm(data, ',')\nhours = ds[:, 1][2:end]\nvwc = ds[:, 2][2:end] ./ 100.0\ndepth = ds[:, 3][2:end]\nmask_12h = hours .== 12\nmask_24h = hours .== 24\nmask_50h = hours .== 50;\n\nplot_12h =\n    scatter(vwc[mask_12h], -depth[mask_12h], label = \"\", color = \"purple\")\nplot!(\n    dons_arr[13][\"soil.water.θ_i\"] .+ dons_arr[13][\"soil.water.ϑ_l\"],\n    z,\n    label = \"\",\n    color = \"green\",\n)\nplot!(title = \"12h\")\nplot!(xlim = [0.2, 0.55])\nplot!(xticks = [0.2, 0.3, 0.4, 0.5])\nplot!(ylabel = \"Depth (m)\");\n\nplot_24h =\n    scatter(vwc[mask_24h], -depth[mask_24h], label = \"Data\", color = \"purple\")\nplot!(\n    dons_arr[25][\"soil.water.θ_i\"] .+ dons_arr[25][\"soil.water.ϑ_l\"],\n    z,\n    label = \"Simulation\",\n    color = \"green\",\n)\nplot!(title = \"24h\")\nplot!(legend = :bottomright)\nplot!(xlim = [0.2, 0.55])\nplot!(xticks = [0.2, 0.3, 0.4, 0.5]);\n\nplot_50h =\n    scatter(vwc[mask_50h], -depth[mask_50h], label = \"\", color = \"purple\")\nplot!(\n    dons_arr[51][\"soil.water.θ_i\"] .+ dons_arr[51][\"soil.water.ϑ_l\"],\n    z,\n    label = \"\",\n    color = \"green\",\n)\nplot!(title = \"50h\")\nplot!(xlim = [0.2, 0.55])\nplot!(xticks = [0.2, 0.3, 0.4, 0.5]);\n\nplot(plot_12h, plot_24h, plot_50h, layout = (1, 3))\nplot!(xlabel = \"θ_l+θ_i\")\nsavefig(\"mizoguchi_data_comparison.png\")\n# ![](mizoguchi_data_comparison.png)\n\n# # Discussion and Model Explanation\n\n# To begin, let's observe that the freeze thaw source term alone conserves water mass, as\n# it satisfies\n\n# ``\n# ρ_l \\partial_tϑ_l + ρ_i \\partial_tθ_i = -F_T + F_T = 0\n# ``\n\n# Next, we describe how we define `F_T`.\n# The Clausius-Clapeyron (CC) equation defines a pressure-temperature curve along which two\n# phases can co-exist. It assumes that the phases are at equal temperature and pressures.\n# For water in soil, however, the liquid water experiences pressure `ρ_l g ψ`, where\n# `ψ` is the matric potential. A more general form of the CC equation allows for different\n# pressures in the two phases. Usually the ice pressure is taken to be zero, which is reasonable\n# for unsaturated freezing soils. In saturated soils, freezing can lead to heaving of the soil which\n# we do not model. After that assumption is made, we obtain that, below freezing (``T < T_f``)\n\n# ``\n# \\frac{dp_l}{ρ_l} = L_f \\frac{dT}{T},\n# ``\n\n# or\n\n# ``\n# p_l = p_{l,0} + L_f ρ_l \\frac{T-T_f}{T_f} \\mathcal{H}(T_f-T)\n# ``\n\n# where we have assumed that assumed `T` is near the freezing point, and then\n# performed a Taylor explansion of the logarithm,\n# and we are ignoring the freezing point depression, which is small (less than one degree) for\n# non-clay soils. What we have sketched is further explained in [DallAmico2011](@cite) and [KurylykWatanabe2013](@cite).\n\n# What this implies is that above the freezing point, the pressure is equal to ``p_{l,0}``,\n# which is independent of temperature. Once the temperature drops below the freezing point,\n# the pressure drops. Since prior to freezing, the pressure ``p_{l,0}`` is equal to\n# `ρ_l g ψ(θ_l)`, water undergoing freezing alone (without flowing) should satisfy ([DallAmico2011](@cite)):\n\n# ``\n# p_{l,0} = ρ_l g ψ(θ_l+ρ_iθ_i/ρ_l)\n# ``\n\n# where `ψ` is the matric potential function of van Genuchten. At each step, we know both\n# the water and ice contents, as well as the temperature, and can then solve for\n\n# ``\n# θ_{l}^* = (ν-θ_r) ψ^{-1}(p_l/(ρ_l g)) + θ_r.\n# ``\n\n# For freezing, the freeze thaw function `F_T` is equal to\n\n# ``\n# F_T = \\frac{1}{τ} ρ_l (θ_l-θ_{l}^*) \\mathcal{H}(T_f-T) \\mathcal{H}(θ_l-θ_{l}^*)\n# ``\n\n# which brings the `θ_l` to a value which satisfies `p_l = ρ_l g ψ(θ_l)`.\n# This is why, in our simulation, we see the liquid\n# water fraction approaches a constant around 0.075 in the frozen region, rather than the residual fraction\n# of 0.019, or 0. This behavior is observed, for example, in the experiments of [Watanabe2011](@cite).\n\n# Although this approach may indicate that we should replace the pressure head appearing in the\n# diffusive water flux term in Richards equation ([DallAmico2011](@cite)), we do not do so at present. As such, we may not be modeling\n# the flow of water around the freezing front properly. However, we still observe cryosuction, which\n# is the flow of water towards the freezing front, from the unfrozen side. As the water freezes, the liquid\n# water content drops,\n# setting up a larger gradient in matric potential across the freezing front, which generates upward flow\n# against gravity. This is evident because the total water content at the top is larger at the end of the\n# simulation\n# than it was at `t=0` (when it was 0.33).\n\n# This model differs from others (e.g. [Painter2011](@cite), [Hansson2004](@cite), [DallAmico2011](@cite))  in that it requires us to set a timescale for the phase change, `τ`.\n# In a first-order\n# phase transition, the temperature is fixed while the necessary latent heat is either lost or gained by the\n# system.  Ignoring\n# changes in internal energy due to flowing water, we would expect\n\n\n# ``\n# \\partial_t ρe_{int} \\approx (ρ_l c_l \\partial_t θ_l + ρ_i c_i \\partial_t θ_i) (T-T_0) -ρ_i L_f \\partial_t θ_i\n# ``\n\n# ``\n# = [(c_i-c_l) (T-T_0) -L_f]F_T \\approx -L_f F_T\n# ``\n\n# or\n\n# ``\n# F_T ∼ \\frac{κ|∇²T|}{L_f} ∼\\frac{κ}{c̃ Δz²}\\frac{c̃ |∂zT| Δz}{L_f}\n# ``\n\n# suggesting\n\n# ``\n# τ ∼ τ_{LTE}\\frac{ρ_lL_f (ν-θ_r)}{c̃ |∂zT| Δz}\n# ``\n\n# with\n\n# ``\n# τ_{LTE}= c̃ Δz²/κ\n# ``\n\n# This is the value we use. This seems to work adequately for modeling freezing front propagation and\n# cryosuction, via comparisons with [Mizoguchi1990](@cite), but we plan to revisit it in the future. For example,\n# we do not see a strong temperature plateau at the freezing point ([Watanabe2011](@cite)),\n# which we would expect while the phase change is occuring. Experimentally, this timescale also affects the abruptness of the freezing front, which our simulation softens.\n\n# # References\n# - [Mizoguchi1990](@cite)\n# - [Hansson2004](@cite)\n# - [DallAmico2011](@cite)\n# - [KurylykWatanabe2013](@cite)\n# - [Watanabe2011](@cite)\n# - [Painter2011](@cite)\n\n# [^a]:\n#       Note that `θ_i` is always treated as a prognostic variable\n#       in the `SoilWaterModel`, but with\n#       zero terms on the RHS unless freezing and thawing is turn on, as demonstrated in this\n#       tutorial. That means that the user could, in principle, set the initial condition to be nonzero\n#       (`θ_i(x, y, z ,t=0) = 0` is the default), which in turn would allow a nonzero `θ_i`\n#       profile to affect things like thermal conductivity, etc,\n#       in a consistent way. However, it would not be enforced that ``θ_l+θ_i \\leq ν``, because there would\n#       be no physics linking the liquid and water content to each other, and they are independent\n#       variables in our model. We don't envision this being a common use case.\n"
  },
  {
    "path": "tutorials/Land/Soil/PhaseChange/phase_change_analytic_test.jl",
    "content": "# # Comparison to Neumann analytic solution\n\n# Before reading this tutorial, \n# we recommend that you look over the coupled energy\n# and water [tutorial](../Coupled/equilibrium_test.md)\n# and the freezing front [tutorial](freezing_front.md).\n# The former shows how to solve the heat equation for soil volumetric\n# internal energy `ρe_int` simultaneously\n# with Richards equation for volumetric liquid water fraction `ϑ_l`, assuming zero\n# volumetric ice fraction `θ_i` for all time, everywhere in the domain[^a].\n# The latter shows how to include freezing and thawing, and explains the freeze thaw model employed\n# by CliMA Land. This tutorial compares a simulated temperature profile\n# from a freezing front with an analytic solution.\n\n# The analytic solution applies to a freezing front propagating under certain assumptions. It assumes\n# that there is no water movement, which we mimic by setting `K_sat=0`. It also assumes a semi-infinite\n# domain, which we approximate by making the domain larger than the extent to which the front propagates.\n# The solution also assumes that all water freezes. Our model does not satisfy that assumption, as discussed\n# [here](freezing_front.md#Discussion-and-Model-Explanation), but as we will see, the model still matches the\n# analytic expectation well in the frozen region.\n\n# As such, our set of equations is\n\n\n# ``\n# \\frac{∂ ρe_{int}}{∂ t} =  ∇ ⋅ κ(θ_l, θ_i; ν, ...) ∇T \n# ``\n\n# ``\n# \\frac{ ∂ ϑ_l}{∂ t} = -\\frac{F_T}{ρ_l}\n# ``\n\n# ``\n# \\frac{ ∂ θ_i}{∂ t} = \\frac{F_T}{ρ_i}\n# ``\n\n# Here\n\n# ``t`` is the time (s),\n\n# ``z`` is the location in the vertical (m),\n\n# ``ρe_{int}`` is the volumetric internal energy of the soil (J/m^3),\n\n# ``T`` is the temperature of the soil (K),\n\n# ``κ`` is the thermal conductivity (W/m/K),\n\n# ``ϑ_l`` is the augmented volumetric liquid water fraction,\n\n# ``θ_i`` is the volumetric ice fraction,\n\n# ``ν, ...`` denotes parameters relating to soil type, such as porosity, and\n\n# ``F_T`` is the freeze-thaw term.\n\n# Our domain is effectively 1-d, with ``z ∈ [-3,0]``,\n# and with the following boundary and initial conditions:\n\n# `` T(t, z=0) = 263.15 K``\n\n# ``- κ ∇T(t, z= -3) =  0 ẑ ``\n\n# `` T(t = 0, z) = 275.15 K``\n\n# ``- K ∇h(t, z = 0) = 0 ẑ ``\n\n# `` -K ∇h(t, z = -3) = 0 ẑ``\n\n# `` ϑ(t = 0, z) = 0.33 ``.\n\n# # Import necessary modules\n# External (non - CliMA) modules\nusing MPI\nusing OrderedCollections\nusing StaticArrays\nusing Statistics\nusing Test\nusing DelimitedFiles\nusing Plots\n\n# CliMA Parameters\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\nusing CLIMAParameters.Planet: ρ_cloud_liq\nusing CLIMAParameters.Planet: ρ_cloud_ice\nusing CLIMAParameters.Planet: LH_f0\n\n# ClimateMachine modules\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.SoilWaterParameterizations\nusing ClimateMachine.Land.SoilHeatParameterizations\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods: BalanceLaw, LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.SystemSolvers\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux, vars_state\nusing SpecialFunctions\nusing ArtifactWrappers\n\n# # Preliminary set-up\n\n# Get the parameter set, which holds constants used across CliMA models:\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet();\n# Initialize and pick a floating point precision:\nClimateMachine.init()\nconst FT = Float64;\n\n# # Simulation specific parameters\nN_poly = 1\nnelem_vert = 40\nzmax = FT(0)\nzmin = FT(-3)\nt0 = FT(0)\ndt = FT(50)\ntimeend = FT(3600 * 24 * 20)\nn_outputs = 540\nevery_x_simulation_time = ceil(Int, timeend / n_outputs)\nΔ = abs(zmin - zmax) / FT(nelem_vert);\n\n# # Soil properties\n# All units are mks.\nporosity = FT(0.535)\nvg_α = 1.11\nvg_n = 1.48\nKsat = 0.0\nν_ss_quartz = FT(0.2)\nν_ss_minerals = FT(0.6)\nν_ss_om = FT(0.2)\nν_ss_gravel = FT(0.0);\nκ_quartz = FT(7.7)\nκ_minerals = FT(2.5)\nκ_om = FT(0.25)\nκ_liq = FT(0.57)\nκ_ice = FT(2.29)\nρp = FT(2700)\nκ_solid = k_solid(ν_ss_om, ν_ss_quartz, κ_quartz, κ_minerals, κ_om)\nκ_sat_frozen = ksat_frozen(κ_solid, porosity, κ_ice)\nκ_sat_unfrozen = ksat_unfrozen(κ_solid, porosity, κ_liq)\nρc_ds = FT((1 - porosity) * 2.3e6)\nsoil_param_functions = SoilParamFunctions(\n    FT;\n    porosity = porosity,\n    ν_ss_gravel = ν_ss_gravel,\n    ν_ss_om = ν_ss_om,\n    ν_ss_quartz = ν_ss_quartz,\n    ρc_ds = ρc_ds,\n    ρp = ρp,\n    κ_solid = κ_solid,\n    κ_sat_unfrozen = κ_sat_unfrozen,\n    κ_sat_frozen = κ_sat_frozen,\n    water = WaterParamFunctions(FT; Ksat = Ksat, S_s = 1e-3),\n);\n\n# # Build the model\n# Initial and Boundary conditions. The default initial condition for\n# `θ_i` is zero everywhere, so we don't modify that. Furthermore, since\n# the equation for `θ_i` does not involve spatial derivatives, we don't need\n# to supply boundary conditions for it.\nzero_flux = (aux, t) -> eltype(aux)(0.0)\nϑ_l0 = (aux) -> eltype(aux)(0.33)\nsurface_state = (aux, t) -> eltype(aux)(273.15 - 10.0)\nT_init = aux -> eltype(aux)(275.15)\nbc = LandDomainBC(\n    bottom_bc = LandComponentBC(\n        soil_heat = Neumann(zero_flux),\n        soil_water = Neumann(zero_flux),\n    ),\n    surface_bc = LandComponentBC(\n        soil_heat = Dirichlet(surface_state),\n        soil_water = Neumann(zero_flux),\n    ),\n);\n\n# Create the [`SoilWaterModel`](@ref ClimateMachine.Land.SoilWaterModel),\n# [`SoilHeatModel`](@ref ClimateMachine.Land.SoilHeatModel),\n# and the [`SoilModel`](@ref ClimateMachine.Land.SoilModel) instances.\n# Note that we are still specifying a hydraulics model, because the matric potential\n# and hydraulic conductivity functions are still evaluated (though they don't affect\n# the outcome). Setting `Ksat =0` is just a\n# hack for turning off water flow. \nsoil_water_model = SoilWaterModel(\n    FT;\n    hydraulics = vanGenuchten(FT; α = vg_α, n = vg_n),\n    initialϑ_l = ϑ_l0,\n);\n\n\nsoil_heat_model = SoilHeatModel(FT; initialT = T_init);\n\nm_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model);\n# Create the source term instance. Our phase change model requires\n# knowledge of the vertical spacing, so we pass\n# that information in via an attribute of the\n# [`PhaseChange`](@ref ClimateMachine.Land.PhaseChange) structure.\nfreeze_thaw_source = PhaseChange{FT}(Δz = Δ);\n\n# Sources are added as elements of a list of sources. Here we just add freezing\n# and thawing.\nsources = (freeze_thaw_source,);\n\n# Next, we define the required `init_soil!` function, which takes the user\n# specified functions of space for `T_init` and `ϑ_l0` and initializes the state\n# variables of volumetric internal energy and augmented liquid fraction. This requires\n# a conversion from `T` to `ρe_int`.\nfunction init_soil!(land, state, aux, localgeo, time)\n    myFT = eltype(state)\n    ϑ_l = myFT(land.soil.water.initialϑ_l(aux))\n    θ_i = myFT(land.soil.water.initialθ_i(aux))\n    state.soil.water.ϑ_l = ϑ_l\n    state.soil.water.θ_i = θ_i\n    param_set = land.param_set\n\n    θ_l = volumetric_liquid_fraction(ϑ_l, land.soil.param_functions.porosity)\n    ρc_ds = land.soil.param_functions.ρc_ds\n    ρc_s = volumetric_heat_capacity(θ_l, θ_i, ρc_ds, param_set)\n\n    state.soil.heat.ρe_int = volumetric_internal_energy(\n        θ_i,\n        ρc_s,\n        land.soil.heat.initialT(aux),\n        param_set,\n    )\nend;\n\n# Lastly, package it all up in the `LandModel`:\nm = LandModel(\n    param_set,\n    m_soil;\n    boundary_conditions = bc,\n    source = sources,\n    init_state_prognostic = init_soil!,\n);\n\n# # Set up and run the simulation\n\ndriver_config = ClimateMachine.SingleStackConfiguration(\n    \"LandModel\",\n    N_poly,\n    nelem_vert,\n    zmax,\n    param_set,\n    m;\n    zmin = zmin,\n    numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n);\n\nsolver_config =\n    ClimateMachine.SolverConfiguration(t0, timeend, driver_config, ode_dt = dt);\n\nstate_types = (Prognostic(), Auxiliary(), GradientFlux())\nall_data = Dict[dict_of_nodal_states(solver_config, state_types; interp = true)]\ntime_data = FT[0]\ncallback = GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n    dons = dict_of_nodal_states(solver_config, state_types; interp = true)\n    push!(all_data, dons)\n    push!(time_data, gettime(solver_config.solver))\n    nothing\nend;\n\nClimateMachine.invoke!(solver_config; user_callbacks = (callback,));\nz = get_z(solver_config.dg.grid; rm_dupes = true);\n\n# # Analytic Solution of Neumann\n# All details here are taken from [DallAmico2011](@cite) (see also [CarslawJaeger](@cite)), and the reader is referred to that\n# for further information on the solution. It takes the form of a function\n# for T(z) on each side of the freezing front interface, which depends on\n# the thermal properties in that region, and which is also parameterized by\n# a parameter (ζ), which we show how to solve for below. In computing the\n# thermal properties, we evaluate the conductivity and heat capacity\n# assuming that all of the water is either in liquid or frozen form, with total\n# mass proportional to ``θ_{l,0}ρ_l`` (as we have no water flow).\n\n# Compute the thermal conductivity and heat capacity in the frozen region - subscript 1.\nθ_l0 = FT(0.33)\nkdry = k_dry(param_set, soil_param_functions)\nksat = saturated_thermal_conductivity(\n    FT(0.0),\n    θ_l0 * ρ_cloud_liq(param_set) / ρ_cloud_ice(param_set),\n    κ_sat_unfrozen,\n    κ_sat_frozen,\n)\nkersten = kersten_number(\n    θ_l0 * ρ_cloud_liq(param_set) / ρ_cloud_ice(param_set),\n    θ_l0 * ρ_cloud_liq(param_set) / ρ_cloud_ice(param_set) / porosity,\n    soil_param_functions,\n)\nλ1 = thermal_conductivity(kdry, kersten, ksat)\nc1 = volumetric_heat_capacity(\n    FT(0.0),\n    θ_l0 * ρ_cloud_liq(param_set) / ρ_cloud_ice(param_set),\n    ρc_ds,\n    param_set,\n)\nd1 = λ1 / c1;\n\n# Compute the thermal conductivity and heat capacity in the region\n# with liquid water - subscript 2.\nksat =\n    saturated_thermal_conductivity(θ_l0, FT(0.0), κ_sat_unfrozen, κ_sat_frozen)\nkersten = kersten_number(FT(0.0), θ_l0 / porosity, soil_param_functions)\nλ2 = thermal_conductivity(kdry, kersten, ksat)\nc2 = volumetric_heat_capacity(θ_l0, FT(0.0), ρc_ds, param_set)\nd2 = λ2 / c2;\n\n# Initial T and surface T, in Celsius\nTi = FT(2)\nTs = FT(-10.0);\n\n# The solution requires the root of the implicit equation below\n# (you'll need to install `Roots` via the package manager).\n# ``` julia\n# using Roots\n# function implicit(ζ)\n#    term1 = exp(-ζ^2) / ζ / erf(ζ)\n#    term2 =\n#        -λ2 * sqrt(d1) * (Ti - 0) /\n#        (λ1 * sqrt(d2) * (0 - Ts) * ζ * erfc(ζ * sqrt(d1 / d2))) *\n#        exp(-d1 / d2 * ζ^2)\n#    term3 =\n#        -LH_f0(param_set) * ρ_cloud_liq(param_set) * θ_l0 * sqrt(π) / c1 /\n#        (0 - Ts)\n#    return (term1 + term2 + term3)\n# end\n# find_zero(implicit, (0.25,0.27), Bisection())\n# ```\n\n# The root is\nζ = 0.26447353269809687;\n\n\n\n# This function plots the analytic solution\n# and the simulated result, at the output time index\n# `k`.\nfunction f(k; ζ = ζ)\n    T = all_data[k][\"soil.heat.T\"][:] .- 273.15\n    plot(\n        T,\n        z[:],\n        xlim = [-10.5, 3],\n        ylim = [zmin, zmax],\n        xlabel = \"T\",\n        label = \"simulation\",\n    )\n    t = time_data[k]\n    zf = 2.0 * ζ * sqrt(d1 * t)\n    myz = zmin:0.001:0\n    spatially_varying =\n        (erfc.(abs.(myz) ./ (zf / ζ / (d1 / d2)^0.5))) ./\n        erfc(ζ * (d1 / d2)^0.5)\n    mask = abs.(myz) .>= zf\n    plot!(\n        Ti .- (Ti - 0.0) .* spatially_varying[mask],\n        myz[mask],\n        label = \"analytic\",\n        color = \"green\",\n    )\n    spatially_varying = ((erf.(abs.(myz) ./ (zf / ζ)))) ./ erf(ζ)\n    mask = abs.(myz) .< zf\n    plot!(\n        Ts .+ (0.0 - Ts) .* spatially_varying[mask],\n        myz[mask],\n        label = \"\",\n        color = \"green\",\n    )\nend\n\n# Now we will plot this and compare to other methods for modeling phase change without water movement.\n# These solutions were produced by modifying the Supplemental Program 5:3 from [Bonan19a](@cite).\n\n# Excess heat solution:\neh_dataset = ArtifactWrapper(\n    @__DIR__,\n    isempty(get(ENV, \"CI\", \"\")),\n    \"eh\",\n    ArtifactFile[ArtifactFile(\n        url = \"https://caltech.box.com/shared/static/6xs1r98wk7u1b0xjhpkdvt80q4sh16un.csv\",\n        filename = \"bonan_data.csv\",\n    ),],\n);\neh_dataset_path = get_data_folder(eh_dataset);\neh_data = joinpath(eh_dataset_path, \"bonan_data.csv\")\nds = readdlm(eh_data, ',');\n# Apparent heat capacity solution:\nahc_dataset = ArtifactWrapper(\n    @__DIR__,\n    isempty(get(ENV, \"CI\", \"\")),\n    \"ahc\",\n    ArtifactFile[ArtifactFile(\n        url = \"https://caltech.box.com/shared/static/d6xciskzl2djwi03xxfo8wu4obrptjmy.csv\",\n        filename = \"bonan_data_ahc.csv\",\n    ),],\n);\nahc_dataset_path = get_data_folder(ahc_dataset);\nahc_data = joinpath(ahc_dataset_path, \"bonan_data_ahc.csv\")\nds2 = readdlm(ahc_data, ',');\n\nk = n_outputs\nf(k)\nplot!(ds[:, 2], ds[:, 1], label = \"Excess Heat\")\nplot!(ds2[:, 2], ds2[:, 1], label = \"Apparent heat capacity\")\nplot!(legend = :bottomleft)\nsavefig(\"analytic_comparison.png\")\n# ![](analytic_comparison.png)\n\n# # References\n# - [DallAmico2011](@cite)\n# - [CarslawJaeger](@cite)\n# - [Bonan19a](@cite)\n\n# [^a]:\n#       Note that `θ_i` is always treated as a prognostic variable\n#       in the `SoilWaterModel`, but with\n#       zero terms on the RHS unless freezing and thawing is turn on, as demonstrated in this\n#       tutorial. That means that the user could, in principle, set the initial condition to be nonzero\n#       (`θ_i(x, y, z ,t=0) = 0` is the default), which in turn would allow a nonzero `θ_i`\n#       profile to affect things like thermal conductivity, etc,\n#       in a consistent way. However, it would not be enforced that ``θ_l+θ_i \\leq ν``, because there would\n#       be no physics linking the liquid and water content to each other, and they are independent\n#       variables in our model. We don't envision this being a common use case.\n"
  },
  {
    "path": "tutorials/Land/Soil/Water/equilibrium_test.jl",
    "content": "# # Hydrostatic Equilibrium test for Richards Equation\n\n# This tutorial shows how to use `ClimateMachine` code to solve\n# Richards equation in a column of soil. We choose boundary\n# conditions of zero flux at the top and bottom of the column,\n# and then run the simulation long enough to see that the system\n# is approaching hydrostatic equilibrium, where the gradient of the\n# pressure head is equal and opposite the gradient of the\n# gravitational head. Note that the [`SoilWaterModel`](@ref\n# ClimateMachine.Land.SoilWaterModel) includes\n# a prognostic equation for the volumetric ice fraction,\n# as ice is a form of water that must be accounted for to1 ensure\n# water mass conservation. If freezing and thawing are not turned on\n# (the default), the amount of ice in the model is zero for all space and time\n# (again by default). \n\n# The equations are:\n\n# ``\n# \\frac{ ∂ ϑ_l}{∂ t} = ∇ ⋅ K (T, ϑ_l, θ_i; ν, ...) ∇h( ϑ_l, z; ν, ...).\n# ``\n\n# ``\n# \\frac{ ∂ θ_i}{∂ t} = 0\n# ``\n\n# Here\n\n# ``t`` is the time (s),\n\n# ``z`` is the location in the vertical (m),\n\n# ``T`` is the temperature of the soil (K),\n\n# ``K`` is the hydraulic conductivity (m/s),\n\n# ``h`` is the hydraulic head (m),\n\n# ``ϑ_l`` is the augmented volumetric liquid water fraction,\n\n# ``θ_i`` is the volumetric ice fraction, and\n\n# ``ν, ...`` denotes parameters relating to soil type, such as porosity.\n\n\n# We will solve this equation in an effectively 1-d domain with ``z ∈ [-10,0]``,\n# and with the following boundary and initial conditions:\n\n# ``- K ∇h(t, z = 0) = 0 ẑ ``\n\n# `` -K ∇h(t, z = -10) = 0 ẑ``\n\n# `` ϑ(t = 0, z) = ν-0.001 ``\n\n# `` θ_i(t = 0, z) = 0.0. ``\n\n# where ``\\nu`` is the porosity.\n\n# A word about the hydraulic conductivity: please see the\n# [`hydraulic functions`](./hydraulic_functions.md) tutorial\n# for options regarding this function. The user can choose to make it depend\n# on the temperature and the amount of ice in the soil; the default, which we use\n# here, is that `K` only depends on the liquid moisture content.\n\n# Lastly, our formulation of this equation allows for a continuous solution in both\n# saturated and unsaturated areas, following [Woodward00a](@cite).\n\n# # Preliminary setup\n\n# - Load external packages\n\nusing MPI\nusing OrderedCollections\nusing StaticArrays\nusing Statistics\n\n# - Load CLIMAParameters and ClimateMachine modules\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.SoilWaterParameterizations\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.DGMethods: BalanceLaw, LocalGeometry\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux, vars_state\n\n\n# - Define the float type desired (`Float64` or `Float32`)\nconst FT = Float64;\n\n# - Initialize ClimateMachine for CPU\nClimateMachine.init(; disable_gpu = true);\n\n# Load plot helpers:\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\ninclude(joinpath(clima_dir, \"docs\", \"plothelpers.jl\"));\n\n# # Set up the soil model\n\n# We want to solve Richards equation alone, without simultaneously\n# solving the heat equation. Because of that, we choose a\n# [`PrescribedTemperatureModel`](@ref\n# ClimateMachine.Land.PrescribedTemperatureModel).\n# The user can supply a function for temperature,\n# depending on time and space; if this option is desired, one could also\n# choose to model the temperature dependence of viscosity, or to drive\n# a freeze/thaw cycle, for example. If the user simply wants to model\n# Richards equation for liquid water, the defaults will allow for that.\n# Here we ignore the effects of temperature and freezing and thawing,\n# using the defaults.\n\nsoil_heat_model = PrescribedTemperatureModel();\n\n# Define the porosity, Ksat, and specific storage values for the soil. Note\n# that all values must be given in mks units. The soil parameters chosen\n# roughly correspond to Yolo light clay, and are stored in \n# [`SoilParamFunctions`](@ref ClimateMachine.Land.SoilParamFunctions).\n# Hydrology specific parameters are further organized and stored in\n# [`WaterParamFunctions`](@ref ClimateMachine.Land.WaterParamFunctions),\n# with the exception of the hydraulic model and hydraulic conductivity model -\n# see the [`hydraulics`](./hydraulic_functions.md) tutorial.\nwpf = WaterParamFunctions(FT; Ksat = 0.0443 / (3600 * 100), S_s = 1e-3);\nsoil_param_functions = SoilParamFunctions(FT; porosity = 0.495, water = wpf);\n\n# Define the boundary conditions. The user can specify two conditions,\n# either at the top or at the bottom, and they can either be Dirichlet\n# (on `ϑ_l`) or Neumann (on `-K∇h`). Note that fluxes are supplied as\n# scalars, inside the code they are multiplied by ẑ.\n\nsurface_flux = (aux, t) -> eltype(aux)(0.0)\nbottom_flux = (aux, t) -> eltype(aux)(0.0);\n\n# Our problem is effectively 1D, so we do not need to specify lateral boundary\n# conditions.\nbc = LandDomainBC(\n    bottom_bc = LandComponentBC(soil_water = Neumann(bottom_flux)),\n    surface_bc = LandComponentBC(soil_water = Neumann(surface_flux)),\n);\n\n# Define the initial state function. The default for `θ_i` is zero.\nϑ_l0 = (aux) -> eltype(aux)(0.494);\n\n# Create the SoilWaterModel. The defaults are a temperature independent\n# viscosity, and no impedance factor due to ice. We choose to make the\n# hydraulic conductivity a function of the moisture content `ϑ_l`,\n# and employ the vanGenuchten hydraulic model with `n` = 2.0. The van\n# Genuchten parameter `m` is calculated from `n`, and we use the default\n# value for `α`.\nsoil_water_model = SoilWaterModel(\n    FT;\n    moisture_factor = MoistureDependent{FT}(),\n    hydraulics = vanGenuchten(FT; n = 2.0),\n    initialϑ_l = ϑ_l0,\n);\n\n# Create the soil model - the coupled soil water and soil heat models.\nm_soil = SoilModel(soil_param_functions, soil_water_model, soil_heat_model);\n\n# We are ignoring sources and sinks here, like runoff or freezing and thawing.\nsources = ();\n\n# Define the function that initializes the prognostic variables. This\n# in turn calls the functions supplied to `soil_water_model`.\nfunction init_soil_water!(land, state, aux, localgeo, time)\n    state.soil.water.ϑ_l = eltype(state)(land.soil.water.initialϑ_l(aux))\n    state.soil.water.θ_i = eltype(state)(land.soil.water.initialθ_i(aux))\nend\n\n\n# Create the land model - in this tutorial, it only includes the soil.\nm = LandModel(\n    param_set,\n    m_soil;\n    boundary_conditions = bc,\n    source = sources,\n    init_state_prognostic = init_soil_water!,\n);\n\n# # Specify the numerical configuration and output data.\n\n# Specify the polynomial order and vertical resolution.\nN_poly = 2;\nnelem_vert = 20;\n\n# Specify the domain boundaries.\nzmax = FT(0);\nzmin = FT(-10);\n\n# Create the driver configuration.\ndriver_config = ClimateMachine.SingleStackConfiguration(\n    \"LandModel\",\n    N_poly,\n    nelem_vert,\n    zmax,\n    param_set,\n    m;\n    zmin = zmin,\n    numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n);\n\n# Choose the initial and final times, as well as a timestep.\nt0 = FT(0)\ntimeend = FT(60 * 60 * 24 * 36)\ndt = FT(100);\n\n# Create the solver configuration.\nsolver_config =\n    ClimateMachine.SolverConfiguration(t0, timeend, driver_config, ode_dt = dt);\n\n# Determine how often you want output.\nconst n_outputs = 6;\n\nconst every_x_simulation_time = ceil(Int, timeend / n_outputs);\n\n# Create a place to store this output.\nstate_types = (Prognostic(), Auxiliary(), GradientFlux())\ndons_arr = Dict[dict_of_nodal_states(solver_config, state_types; interp = true)]\ntime_data = FT[0] # store time data\n\ncallback = GenericCallbacks.EveryXSimulationTime(every_x_simulation_time) do\n    dons = dict_of_nodal_states(solver_config, state_types; interp = true)\n    push!(dons_arr, dons)\n    push!(time_data, gettime(solver_config.solver))\n    nothing\nend;\n\n# # Run the integration\nClimateMachine.invoke!(solver_config; user_callbacks = (callback,));\n\n# Get z-coordinate\nz = get_z(solver_config.dg.grid; rm_dupes = true);\n\n# # Create some plots\n# We'll plot the moisture content vs depth in the soil, as well as\n# the expected profile of `ϑ_l` in hydrostatic equilibrium.\n# For `ϑ_l` values above porosity, the soil is\n# saturated, and the pressure head changes from being equal to the matric\n# potential to the pressure generated by compression of water and the soil\n# matrix. The profile can be solved\n# for analytically by (1) solving for the form that `ϑ_l(z)` must take\n# in both the saturated and unsaturated zones to satisfy the steady-state\n# requirement with zero flux boundary conditions, (2) requiring that\n# at the interface between saturated and unsaturated zones, the water content\n# equals porosity, and (3) solving for the location of the interface by\n# requiring that the integrated water content at the end matches that\n# at the beginning (yielding an interface location of `z≈-0.56m`).\noutput_dir = @__DIR__;\n\nt = time_data ./ (60 * 60 * 24);\n\nplot(\n    dons_arr[1][\"soil.water.ϑ_l\"],\n    dons_arr[1][\"z\"],\n    label = string(\"t = \", string(t[1]), \"days\"),\n    xlim = [0.47, 0.501],\n    ylabel = \"z\",\n    xlabel = \"ϑ_l\",\n    legend = :bottomleft,\n    title = \"Equilibrium test\",\n);\nplot!(\n    dons_arr[2][\"soil.water.ϑ_l\"],\n    dons_arr[2][\"z\"],\n    label = string(\"t = \", string(t[2]), \"days\"),\n);\nplot!(\n    dons_arr[7][\"soil.water.ϑ_l\"],\n    dons_arr[7][\"z\"],\n    label = string(\"t = \", string(t[7]), \"days\"),\n);\nfunction expected(z, z_interface)\n    ν = 0.495\n    S_s = 1e-3\n    α = 2.6\n    n = 2.0\n    m = 0.5\n    if z < z_interface\n        return -S_s * (z - z_interface) + ν\n    else\n        return ν * (1 + (α * (z - z_interface))^n)^(-m)\n    end\nend\nplot!(expected.(dons_arr[1][\"z\"], -0.56), dons_arr[1][\"z\"], label = \"expected\");\n\nplot!(\n    1e-3 .+ dons_arr[1][\"soil.water.ϑ_l\"],\n    dons_arr[1][\"z\"],\n    label = \"porosity\",\n);\n# save the output.\nsavefig(joinpath(output_dir, \"equilibrium_test_ϑ_l_vG.png\"))\n# ![](equilibrium_test_ϑ_l_vG.png)\n# # References\n# - [Woodward00a](@cite)\n"
  },
  {
    "path": "tutorials/Land/Soil/Water/hydraulic_functions.jl",
    "content": "# # Hydraulic functions\n\n# This tutorial shows how to specify the hydraulic functions\n# used in Richard's equation. In particular,\n# we show how to choose the formalism for matric potential and hydraulic\n# conductivity, and how to make the hydraulic conductivity account for\n# the presence of ice as well as the temperature dependence of the\n# viscosity of liquid water.\n\n# # Preliminary setup\n\n# External modules\nusing Plots\n\n# ClimateMachine modules\nusing ClimateMachine\nusing ClimateMachine.Land\nusing ClimateMachine.Land.SoilWaterParameterizations\n\nFT = Float32;\n# # Specifying a hydraulics model\n# ClimateMachine's Land model allows the user to pick between two hydraulics models,\n# that of van Genuchten [vanGenuchten1980](@cite) or that of Brooks and Corey, see [BrooksCorey1964](@cite) or [Corey1977](@cite). The\n# same model is consistently used for the matric potential\n# and hydraulic conductivity.\n\n# The van Genuchten model requires two free parameters, `α` and `n`.\n# A third parameter, `m`, is computed from `n`. Of these, only `α` carries\n# units, of inverse meters. The Brooks and Corey model also uses\n# two free parameters, `ψ_b`, the magnitude of the matric potential at saturation,\n#  and a constant `M`. `ψ_b` carries units of meters. These parameter sets are stored in\n# either the [`vanGenuchten`](@ref ClimateMachine.Land.SoilWaterParameterizations.vanGenuchten) or the\n# [`BrooksCorey`](@ref ClimateMachine.Land.SoilWaterParameterizations.BrooksCorey)\n# hydraulics model structures (more details below). These parameters are enough to compute the matric potential.\n\n# The hydraulic conductivity\n# requires an additional parameter, `Ksat` (m/s), which is the hydraulic conductivity\n# in saturated soil. This parameter is\n# not stored in the hydraulics model, but rather as part of the\n# [`WaterParamFunctions`](@ref ClimateMachine.Land.WaterParamFunctions), which stores\n# other parameters needed for the soil water modeling.\n\n# Below we show how to create two concrete examples of these hydraulics models,\n# for sandy loam ([Bonan19a](@cite)). Note that the parameters chosen are a function of soil type,\n# and that the parameters are converted to type `FT` internally.\nvg_α = 7.5 # m^-1\nvg_n = 1.89\nhydraulics = vanGenuchten(FT; α = vg_α, n = vg_n);\n\nψ_sat = 0.218 # m\nMval = 0.2041\nhydraulics_bc = BrooksCorey(FT; ψb = ψ_sat, m = Mval);\n# # Matric Potential\n# The matric potential `ψ` reflects the negative pressure of water\n# in unsaturated soil. The negative pressure (suction) of water arises\n# because of adhesive forces between water and soil.\n\n# The van Genuchten expression for matric potential is\n# ``\n# ψ = -\\frac{1}{α} S_l^{-1/(nm)}\\times (1-S_l^{1/m})^{1/n},\n# ``\n\n# and the Brooks and Corey expression is\n# ``\n# ψ = -ψ_b S_l^{-M}.\n# ``\n\n# Here `S_l` is the effective saturation of liquid water, `θ_l/ν`, where `ν` is\n# porosity of the soil. We generally neglect the residual pore space in the CliMA model,\n# but the user can set the parameter in the\n# [`WaterParamFunctions`](@ref ClimateMachine.Land.WaterParamFunctions) structure if it is\n# desired.\n\n# In the CliMA code, we use [multiple dispatch](https://en.wikipedia.org/wiki/Multiple_dispatch).\n# With multiple dispatch, a function can have many\n# ways of executing (called methods), depending on the *type* of the\n# variables passed in. A simple example of multiple dispatch is the division operation.\n# Integer division takes two numbers as input, and returns an integer - ignoring the decimal.\n# Float division takes two numbers as input, and returns a floating point number, including the decimal.\n# In Julia, we might write these as:\n\n# ```julia\n# function division(a::Int, b::Int)\n#      return floor(Int, a/b)\n# end\n# ```\n# ```julia\n# function division(a::Float64, b::Float64)\n#      return a/b\n# end\n# ```\n\n\n# We can see that `division` is now a function with two methods.\n\n# ```julia\n# julia> division\n# division (generic function with 2 methods)\n# ```\n\n# Now, using the same function signature, we can carry out integer\n# division or floating point division, depending on the types of the\n# arguments:\n\n# ```julia\n# julia> division(1,2)\n# 0\n#\n# julia> division(1.0,2.0)\n# 0.5\n# ```\n\n\n# Here are more pertinent examples:\n# Based on our choice of `FT = Float32`,\n\n# ```julia\n# julia> typeof(hydraulics)\n# vanGenuchten{Float32,Float32,Float32,Float32}\n# ```\n\n\n# but meanwhile,\n\n# ```julia\n# julia> typeof(hydraulics_bc)\n# BrooksCorey{Float32,Float32,Float32}\n# ```\n\n\n# The function `matric_potential` will execute different methods\n# depending on if we pass a hydraulics model of type `vanGenuchten` or\n# `BrooksCorey`. In both cases, it will return the correct value\n# for `ψ`.\n\n# Let's plot the matric potential as a function of the effective saturation `S_l = θ_l/ν`,\n# which can range from zero to one.\nS_l = FT.(0.01:0.01:0.99)\nψ = matric_potential.(Ref(hydraulics), S_l)\nψ_bc = matric_potential.(Ref(hydraulics_bc), S_l)\nplot(\n    S_l,\n    log10.(-ψ),\n    xlabel = \"effective saturation\",\n    ylabel = \"Log10(|ψ|)\",\n    label = \"van Genuchten\",\n)\nplot!(S_l, log10.(-ψ_bc), label = \"Brooks and Corey\")\nsavefig(\"bc_vg_matric_potential.png\")\n# ![](bc_vg_matric_potential.png)\n\n# The steep slope in\n# `ψ` near saturated and completely dry soil are part of the reason\n# why Richard's equation is such a challenging numerical problem.\n\n\n# # Hydraulic conductivity\n# The hydraulic conductivity is a more complex function than the matric potential,\n# as it depends on the temperature of the water, the volumetric ice fraction, and\n# the volumetric liquid water fraction. It also depends on the hydraulics model\n# chosen.\n\n# We represent the hydraulic conductivity `K` as the product of four factors:\n# `Ksat`, an impedance factor (which accounts for the effect of ice on conductivity)\n# a viscosity factor (which accounts for the effect of temperature on the\n# viscosity of liquid water, and how that in turn affects conductivity)\n# and a moisture factor (which accounts for the effect of liquid water, and is determined by the hydraulics model).\n# We are going to calculate `K = Ksat × viscosity factor × impedance factor × moisture factor`.\n# In the code, each of these factors is\n# computed by a function with multiple methods, except for `Ksat`.\n# Like we defined new type\n# classes for `vanGenuchten` and `BrooksCorey`, we also created new type classes\n# for the impedance choice, the viscosity choice, and the moisture choice.\n\n# The function [`viscosity_factor`](@ref ClimateMachine.Land.SoilWaterParameterizations.viscosity_factor)\n# takes as arguments the temperature of the soil and the\n# viscosity model desired, and returns the factor `k_v` by which the hydraulic conductivity is scaled.\n# One option is to account for this effect:\n\n# ``\n# k_v = e^{γ (T-T_{\\rm ref})}\n# ``\n\n# where γ = 0.0264/K and ``T_{\\rm ref}`` = 288K.\n\n# For example, at the freezing point of water, using the default values\n# for γ and T_ref, viscosity reduces the conductivity by a third:\nviscous_effect_model = TemperatureDependentViscosity{FT}();\nviscosity_factor(viscous_effect_model, FT(273.15))\n\n# The other option is to ignore this effect:\n\n# ``\n# k_v = 1\n# ``\n\n# This is the default approach.\nno_viscous_effect_model = ConstantViscosity{FT}();\nviscosity_factor(no_viscous_effect_model, FT(273.15))\n\n# Very similarly, the function\n# [`impedance_factor`](@ref ClimateMachine.Land.SoilWaterParameterizations.impedance_factor)\n# takes as arguments the liquid water and ice\n# volumetric fractions in the soil, as well as the impedance model being used, and returns\n# the factor `k_i` by which the hydraulic conductivity is scaled.\n# One option is to account for this effect:\n\n# ``\n# k_i = 10^{-Ω f_i},\n# ``\n\n# where `Ω = 7` is an empirical factor and\n# `f_i` is the ratio of the volumetric\n# ice fraction to total volumetric water fraction  ([Lundin1990](@cite)).\n\n# For example, with ``\\theta_i = \\theta_l``, or f_i = 0.5, ice reduces the conductivity by over 1000x.\nimpedance_effect_model = IceImpedance{FT}();\nimpedance_factor(impedance_effect_model, FT(0.5))\n\n# The other option is to ignore this effect:\n\n# ``\n# k_i = 1\n# ``\n\n# This is the default approach.\nno_impedance_effect_model = NoImpedance{FT}();\nimpedance_factor(no_impedance_effect_model, FT(0.5))\n\n# As for the moisture dependence of hydraulic conductivity, it can also be either\n# independent of moisture, or dependent on moisture. If it is dependent on moisture,\n# the specific function evaluated is dictated by the hydraulics model.\n# The [`moisture_factor`](@ref ClimateMachine.Land.SoilWaterParameterizations.moisture_factor)\n# for the van Genuchten model is (denoting it as ``k_m``)\n\n# ``\n#  k_m = \\sqrt{S_l}[1-(1-S_l^{1/m})^m]^2,\n# ``\n\n# for ``S_l < 1``,\n\n# and for the Brooks and Corey model it is\n\n# ``\n# k_m = S_l^{2M+3},\n# ``\n\n# also for ``S_l<1``. When ``S_l\\geq 1``, ``k_m = 1`` for each model.\n\n\n\n# Let's put all these factors together now. Below\n# we choose additional parameters, consistent with the hydraulics parameters\n# for sandy loam ([Bonan19a](@cite)), and show how hydraulic conductivity varies with\n# liquid water content, in the case without ice impedance or temperature effects.\nKsat = FT(4.42 / (3600 * 100))\nT = FT(0.0)\nf_i = FT(0.0)\nK =\n    hydraulic_conductivity.(\n        Ref(Ksat),\n        Ref(impedance_factor(NoImpedance{FT}(), f_i)),\n        Ref(viscosity_factor(ConstantViscosity{FT}(), T)),\n        moisture_factor.(Ref(MoistureDependent{FT}()), Ref(hydraulics), S_l),\n    );\n\n# Let's also compute `K` when we include the effects of temperature\n# and ice on the hydraulic conductivity.\n# In the cases where a\n# [`TemperatureDependentViscosity`](@ref ClimateMachine.Land.SoilWaterParameterizations.TemperatureDependentViscosity)\n# or\n# [`IceImpedance`](@ref ClimateMachine.Land.SoilWaterParameterizations.IceImpedance)\n# type is passed, the correct factors are calculated,\n# based on the temperature `T` and volumetric ice fraction `θ_i`.\n\nT = FT(273.15)\nS_i = FT(0.1); # = θ_i/ν\n# The total volumetric water fraction cannot\n# exceed unity, so the effective liquid water saturation\n# should have a max of 1-S_i.\nS_l_accounting_for_ice = FT.(0.01:0.01:(0.99 - S_i))\nf_i = S_i ./ (S_l_accounting_for_ice .+ S_i)\nK_w_factors =\n    hydraulic_conductivity.(\n        Ref(Ksat),\n        impedance_factor.(Ref(NoImpedance{FT}()), f_i),\n        Ref(viscosity_factor(ConstantViscosity{FT}(), T)),\n        moisture_factor.(\n            Ref(MoistureDependent{FT}()),\n            Ref(hydraulics),\n            S_l_accounting_for_ice,\n        ),\n    );\nplot(\n    S_l,\n    log10.(K),\n    xlabel = \"total effective saturation, (θ_i+θ_l)/ν\",\n    ylabel = \"Log10(K)\",\n    label = \"Base case\",\n    legend = :bottomright,\n)\nplot!(\n    S_l_accounting_for_ice .+ S_i,\n    log10.(K_w_factors),\n    label = \"θ_i = 0.1, T = 273.15\",\n)\nsavefig(\"T_ice_K.png\")\n# ![](T_ice_K.png)\n\n# If the user is not considering phase transitions\n# and does not add in Freeze/Thaw source terms, the default is for zero\n# ice in the model, for all time and space. In this case the ice impedance\n# factor evaluates to 1 regardless of which type is passed.\n\n# # Other features\n# The user also has the choice of making the conductivity constant by choosing\n# [`MoistureIndependent`](@ref ClimateMachine.Land.SoilWaterParameterizations.MoistureIndependent)\n# along with\n# [`ConstantViscosity`](@ref ClimateMachine.Land.SoilWaterParameterizations.ConstantViscosity)\n# and\n# [`NoImpedance`](@ref ClimateMachine.Land.SoilWaterParameterizations.NoImpedance).\n# This is useful for debugging!\nno_moisture_dependence = MoistureIndependent{FT}()\nK_constant =\n    hydraulic_conductivity.(\n        Ref(Ksat),\n        Ref(FT(1.0)),\n        Ref(FT(1.0)),\n        moisture_factor.(Ref(no_moisture_dependence), Ref(hydraulics), S_l),\n    );\n# ```julia\n# julia> unique(K_constant)\n# 1-element Array{Float32,1}:\n#  1.2277777f-5\n# ```\n\n# Note that choosing this option does not mean the matric potential\n# is constant, as a hydraulics model is still required and employed.\n\n\n# And, lastly, you might also find it helpful in debugging\n# to be able to turn off the flow of water by setting `Ksat = 0`.\n\n# # References\n# - [vanGenuchten1980](@cite)\n# - [BrooksCorey1964](@cite)\n# - [Corey1977](@cite)\n# - [Lundin1990](@cite)\n# - [Bonan19a](@cite)\n"
  },
  {
    "path": "tutorials/Land/Soil/interpolation_helper.jl",
    "content": "\"\"\"\n   function create_interpolation_grid(xbnd, xres, thegrid)\nGiven boundaries of a domain, and a resolution in each direction, create \nan interpolation grid. This is where the interpolation functions for \na set of variables will be evaluated.\n\"\"\"\nfunction create_interpolation_grid(xbnd, xres, thegrid)\n    x1g = collect(range(xbnd[1, 1], xbnd[2, 1], step = xres[1]))\n    x2g = collect(range(xbnd[1, 2], xbnd[2, 2], step = xres[2]))\n    x3g = collect(range(xbnd[1, 3], xbnd[2, 3], step = xres[3]))\n    intrp_brck = ClimateMachine.InterpolationBrick(thegrid, xbnd, x1g, x2g, x3g)\n    return intrp_brck\nend\n\n\"\"\"\n   function interpolate_variables(objects, brick)\nCreate an interpolation function from data and evaluate that\nfunction on the interpolation brick passed.\n\"\"\"\nfunction interpolate_variables(objects, brick)\n    i_objects = []\n    for object in objects\n        nvars = size(object.data, 2)\n        i_object = Array{FT}(undef, brick.Npl, nvars)\n        ClimateMachine.interpolate_local!(brick, object.data, i_object)\n        push!(i_objects, i_object)\n    end\n\n    return i_objects\nend\n"
  },
  {
    "path": "tutorials/Numerics/DGMethods/Box1D.jl",
    "content": "# A box advection test to visualise how different filters work\n\nusing MPI\nusing OrderedCollections\nusing Plots\nusing StaticArrays\nusing Printf\n\nusing CLIMAParameters\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nusing ClimateMachine\nusing ClimateMachine.Mesh.Topologies\nusing ClimateMachine.Mesh.Grids\nusing ClimateMachine.DGMethods\nusing ClimateMachine.DGMethods.NumericalFluxes\nusing ClimateMachine.BalanceLaws:\n    BalanceLaw, Prognostic, Auxiliary, Gradient, GradientFlux\nusing ClimateMachine.Mesh.Geometry: LocalGeometry\nusing ClimateMachine.Mesh.Filters\nusing ClimateMachine.MPIStateArrays\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.SingleStackUtils\n\nimport ClimateMachine.BalanceLaws:\n    vars_state,\n    source!,\n    flux_second_order!,\n    flux_first_order!,\n    compute_gradient_argument!,\n    compute_gradient_flux!,\n    update_auxiliary_state!,\n    nodal_init_state_auxiliary!,\n    init_state_prognostic!,\n    boundary_conditions,\n    wavespeed\n\nClimateMachine.init(; disable_gpu = true, log_level = \"warn\");\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\ninclude(joinpath(clima_dir, \"docs\", \"plothelpers.jl\"));\n\nBase.@kwdef struct Box1D{FT, _init_q, _amplitude, _velo} <: BalanceLaw\n    param_set::AbstractParameterSet = param_set\n    init_q::FT = _init_q\n    amplitude::FT = _amplitude\n    velo::FT = _velo\nend\n\nvars_state(::Box1D, ::Auxiliary, FT) = @vars(z_dim::FT);\nvars_state(::Box1D, ::Prognostic, FT) = @vars(q::FT);\nvars_state(::Box1D, ::Gradient, FT) = @vars();\nvars_state(::Box1D, ::GradientFlux, FT) = @vars();\n\nfunction wavespeed(\n    ::Box1D{FT, _init_q, _amplitude, _velo},\n    _...,\n) where {FT, _init_q, _amplitude, _velo}\n    return _velo\nend\n\nfunction nodal_init_state_auxiliary!(\n    m::Box1D,\n    aux::Vars,\n    tmp::Vars,\n    geom::LocalGeometry,\n)\n    aux.z_dim = geom.coord[3]\nend;\n\nfunction init_state_prognostic!(\n    m::Box1D,\n    state::Vars,\n    aux::Vars,\n    localgeo,\n    t::Real,\n)\n    if aux.z_dim >= 75 && aux.z_dim <= 125\n        state.q = m.init_q + m.amplitude\n    else\n        state.q = m.init_q\n    end\nend;\n\nfunction update_auxiliary_state!(\n    dg::DGModel,\n    m::Box1D,\n    Q::MPIStateArray,\n    t::Real,\n    elems::UnitRange,\n)\n    return true\nend;\n\nfunction source!(m::Box1D, _...) end;\n\n@inline function flux_first_order!(\n    m::Box1D,\n    flux::Grad,\n    state::Vars,\n    aux::Vars,\n    t::Real,\n    _...,\n)\n    FT = eltype(state)\n    @inbounds begin\n        flux.q = SVector(FT(0), FT(0), state.q * m.velo)\n    end\nend\n\n@inline function flux_second_order!(\n    m::Box1D,\n    flux::Grad,\n    state::Vars,\n    diffusive::Vars,\n    hyperdiffusive::Vars,\n    aux::Vars,\n    t::Real,\n) end\n\n@inline function flux_second_order!(\n    m::Box1D,\n    flux::Grad,\n    state::Vars,\n    τ,\n    d_h_tot,\n) end\n\nboundary_condtions(m::Box1D) = ()\n\nfunction run_box1D(\n    N_poly::Int,\n    init_q::FT,\n    amplitude::FT,\n    velo::FT,\n    plot_name::String;\n    tmar_filter::Bool = false,\n    cutoff_filter::Bool = false,\n    exp_filter::Bool = false,\n    boyd_filter::Bool = false,\n    cutoff_param::Int = 1,\n    exp_param_1::Int = 0,\n    exp_param_2::Int = 32,\n    boyd_param_1::Int = 0,\n    boyd_param_2::Int = 32,\n    numerical_flux_first_order = CentralNumericalFluxFirstOrder(),\n) where {FT}\n    N_poly = N_poly\n    nelem = 128\n    zmax = FT(350)\n\n    m = Box1D{FT, init_q, amplitude, velo}()\n\n    driver_config = ClimateMachine.SingleStackConfiguration(\n        \"Box1D\",\n        N_poly,\n        nelem,\n        zmax,\n        param_set,\n        m,\n        numerical_flux_first_order = numerical_flux_first_order,\n        boundary = ((0, 0), (0, 0), (0, 0)),\n        periodicity = (true, true, true),\n    )\n\n    t0 = FT(0)\n    timeend = FT(450)\n\n    Δ = min_node_distance(driver_config.grid, VerticalDirection())\n    max_vel = m.velo\n    dt = Δ / max_vel\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        ode_dt = dt,\n    )\n    grid = solver_config.dg.grid\n    Q = solver_config.Q\n    aux = solver_config.dg.state_auxiliary\n\n    output_dir = @__DIR__\n    mkpath(output_dir)\n\n    z_label = \"z\"\n    z = get_z(grid)\n\n    # store initial condition at ``t=0``\n    dons_arr = Dict[dict_of_nodal_states(solver_config)]\n    time_data = FT[0]                                      # store time data\n\n    # output\n    output_freq = floor(Int, timeend / dt) + 10\n\n    cb_output = GenericCallbacks.EveryXSimulationSteps(output_freq) do\n        push!(dons_arr, dict_of_nodal_states(solver_config))\n        push!(time_data, gettime(solver_config.solver))\n        nothing\n    end\n\n    filter_freq = 1\n    # tmar filter\n    cb_tmar =\n        GenericCallbacks.EveryXSimulationSteps(filter_freq) do (init = false)\n            Filters.apply!(\n                solver_config.Q,\n                (:q,),\n                solver_config.dg.grid,\n                TMARFilter(),\n            )\n            nothing\n        end\n    # cutoff filter\n    cb_cutoff =\n        GenericCallbacks.EveryXSimulationSteps(filter_freq) do (init = false)\n            Filters.apply!(\n                solver_config.Q,\n                (:q,),\n                solver_config.dg.grid,\n                CutoffFilter(solver_config.dg.grid, cutoff_param),\n            )\n            nothing\n        end\n    # exponential filter\n    cb_exp =\n        GenericCallbacks.EveryXSimulationSteps(filter_freq) do (init = false)\n            Filters.apply!(\n                solver_config.Q,\n                (:q,),\n                solver_config.dg.grid,\n                ExponentialFilter(\n                    solver_config.dg.grid,\n                    exp_param_1,\n                    exp_param_2,\n                ),\n            )\n            nothing\n        end\n    # Boyd Vandeven filter\n    cb_boyd =\n        GenericCallbacks.EveryXSimulationSteps(filter_freq) do (init = false)\n            Filters.apply!(\n                solver_config.Q,\n                (:q,),\n                solver_config.dg.grid,\n                BoydVandevenFilter(\n                    solver_config.dg.grid,\n                    boyd_param_1,\n                    boyd_param_2,\n                ),\n            )\n            nothing\n        end\n\n    user_cb_arr = [cb_output]\n    if tmar_filter\n        push!(user_cb_arr, cb_tmar)\n    end\n    if cutoff_filter\n        push!(user_cb_arr, cb_cutoff)\n    end\n    if exp_filter\n        push!(user_cb_arr, cb_exp)\n    end\n    if boyd_filter\n        push!(user_cb_arr, cb_boyd)\n    end\n    user_cb = (user_cb_arr...,)\n\n    initial_mass = weightedsum(solver_config.Q)\n    ClimateMachine.invoke!(solver_config; user_callbacks = (user_cb))\n    final_mass = weightedsum(solver_config.Q)\n    @info @sprintf(\n        \"\"\"\nMass Conservation:\n    initial mass          = %.16e\n    final mass            = %.16e\n    difference            = %.16e\n    normalized difference = %.16e\"\"\",\n        initial_mass,\n        final_mass,\n        final_mass - initial_mass,\n        (final_mass - initial_mass) / initial_mass\n    )\n\n    push!(dons_arr, dict_of_nodal_states(solver_config))\n    push!(time_data, gettime(solver_config.solver))\n\n    export_plot(\n        z,\n        time_data,\n        dons_arr,\n        (\"q\",),\n        joinpath(output_dir, plot_name);\n        xlabel = \"x\",\n        ylabel = \"q\",\n        horiz_layout = true,\n    )\n\nend\n"
  },
  {
    "path": "tutorials/Numerics/DGMethods/showcase_filters.jl",
    "content": "# # Filters\n# In this tutorial we show the result of applying filters\n# available in the CliMA codebase in a 1 dimensional box advection setup.\n# See [Filters API](https://clima.github.io/ClimateMachine.jl/latest/APIs/Numerics/Meshes/Mesh/#Filters-1) for filters interface details.\n\nusing ClimateMachine\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\ninclude(joinpath(clima_dir, \"tutorials\", \"Numerics\", \"DGMethods\", \"Box1D.jl\"))\n\nconst FT = Float64\n\noutput_dir = @__DIR__;\nmkpath(output_dir);\n\n# The unfiltered result of the box advection test for order 4 polynomial with\n# central flux is\nrun_box1D(\n    4,\n    FT(0.0),\n    FT(1.0),\n    FT(1.0),\n    joinpath(output_dir, \"box_1D_4_no_filter.svg\"),\n)\n# ![](box_1D_4_no_filter.svg)\n\n# The unfiltered result of the box advection test for order 4 polynomial with\n# Rusanov flux (aka upwinding for advection) is\nrun_box1D(\n    4,\n    FT(0.0),\n    FT(1.0),\n    FT(1.0),\n    joinpath(output_dir, \"box_1D_4_no_filter_upwind.svg\"),\n    numerical_flux_first_order = RusanovNumericalFlux(),\n)\n# ![](box_1D_4_no_filter_upwind.svg)\n\n\n# Below we show results for the same box advection test\n# but using different filters.\n#\n# As seen in the results, when the TMAR filter is used mass is not necessarily\n# conserved (mass increases are possible).\n\n# `TMARFilter()` with central numerical flux:\nrun_box1D(\n    4,\n    FT(0.0),\n    FT(1.0),\n    FT(1.0),\n    joinpath(output_dir, \"box_1D_4_tmar.svg\");\n    tmar_filter = true,\n)\n# ![](box_1D_4_tmar.svg)\n\n# Running the TMAR filter with Rusanov the mass conservation since some of the\n# are reduced, but mass is still not conserved.\n# `TMARFilter()` with Rusanov numerical flux:\nrun_box1D(\n    4,\n    FT(0.0),\n    FT(1.0),\n    FT(1.0),\n    joinpath(output_dir, \"box_1D_4_tmar_upwind.svg\");\n    tmar_filter = true,\n    numerical_flux_first_order = RusanovNumericalFlux(),\n)\n# ![](box_1D_4_tmar_upwind.svg)\n\n# `CutoffFilter(grid, Nc=1)` with central numerical flux:\nrun_box1D(\n    4,\n    FT(0.0),\n    FT(1.0),\n    FT(1.0),\n    joinpath(output_dir, \"box_1D_4_cutoff_1.svg\");\n    cutoff_filter = true,\n    cutoff_param = 1,\n)\n# ![](box_1D_4_cutoff_1.svg)\n\n# `CutoffFilter(grid, Nc=3)` with central numerical flux:\nrun_box1D(\n    4,\n    FT(0.0),\n    FT(1.0),\n    FT(1.0),\n    joinpath(output_dir, \"box_1D_4_cutoff_3.svg\");\n    cutoff_filter = true,\n    cutoff_param = 3,\n)\n# ![](box_1D_4_cutoff_3.svg)\n\n# `ExponentialFilter(grid, Nc=1, s=4)` with central numerical flux:\nrun_box1D(\n    4,\n    FT(0.0),\n    FT(1.0),\n    FT(1.0),\n    joinpath(output_dir, \"box_1D_4_exp_1_4.svg\");\n    exp_filter = true,\n    exp_param_1 = 1,\n    exp_param_2 = 4,\n)\n# ![](box_1D_4_exp_1_4.svg)\n\n# `ExponentialFilter(grid, Nc=1, s=8)` with central numerical flux:\nrun_box1D(\n    4,\n    FT(0.0),\n    FT(1.0),\n    FT(1.0),\n    joinpath(output_dir, \"box_1D_4_exp_1_8.svg\");\n    exp_filter = true,\n    exp_param_1 = 1,\n    exp_param_2 = 8,\n)\n# ![](box_1D_4_exp_1_8.svg)\n\n# `ExponentialFilter(grid, Nc=1, s=32)` with central numerical flux:\nrun_box1D(\n    4,\n    FT(0.0),\n    FT(1.0),\n    FT(1.0),\n    joinpath(output_dir, \"box_1D_4_exp_1_32.svg\");\n    exp_filter = true,\n    exp_param_1 = 1,\n    exp_param_2 = 32,\n)\n# ![](box_1D_4_exp_1_32.svg)\n\n# `BoydVandevenFilter(grid, Nc=1, s=4)` with central numerical flux:\nrun_box1D(\n    4,\n    FT(0.0),\n    FT(1.0),\n    FT(1.0),\n    joinpath(output_dir, \"box_1D_4_boyd_1_4.svg\");\n    boyd_filter = true,\n    boyd_param_1 = 1,\n    boyd_param_2 = 4,\n)\n# ![](box_1D_4_boyd_1_4.svg)\n\n# `BoydVandevenFilter(grid, Nc=1, s=8)` with central numerical flux:\nrun_box1D(\n    4,\n    FT(0.0),\n    FT(1.0),\n    FT(1.0),\n    joinpath(output_dir, \"box_1D_4_boyd_1_8.svg\");\n    boyd_filter = true,\n    boyd_param_1 = 1,\n    boyd_param_2 = 8,\n)\n# ![](box_1D_4_boyd_1_8.svg)\n\n# `BoydVandevenFilter(grid, Nc=1, s=32)` with central numerical flux:\nrun_box1D(\n    4,\n    FT(0.0),\n    FT(1.0),\n    FT(1.0),\n    joinpath(output_dir, \"box_1D_4_boyd_1_32.svg\");\n    boyd_filter = true,\n    boyd_param_1 = 1,\n    boyd_param_2 = 32,\n)\n# ![](box_1D_4_boyd_1_32.svg)\n\n# `ExponentialFilter(grid, Nc=1, s=8)` and `TMARFilter()` with central numerical\n# flux:\nrun_box1D(\n    4,\n    FT(0.0),\n    FT(1.0),\n    FT(1.0),\n    joinpath(output_dir, \"box_1D_4_tmar_exp_1_8.svg\");\n    exp_filter = true,\n    tmar_filter = true,\n    exp_param_1 = 1,\n    exp_param_2 = 8,\n)\n# ![](box_1D_4_tmar_exp_1_8.svg)\n\n# `BoydVandevenFilter(grid, Nc=1, s=8)` and `TMARFilter()` with central\n# numerical flux:\nrun_box1D(\n    4,\n    FT(0.0),\n    FT(1.0),\n    FT(1.0),\n    joinpath(output_dir, \"box_1D_4_tmar_boyd_1_8.svg\");\n    boyd_filter = true,\n    tmar_filter = true,\n    boyd_param_1 = 1,\n    boyd_param_2 = 8,\n)\n# ![](box_1D_4_tmar_boyd_1_8.svg)\n"
  },
  {
    "path": "tutorials/Numerics/SystemSolvers/bgmres.jl",
    "content": "# # Batched Generalized Minimal Residual\n# In this tutorial we describe the basics of using the batched gmres iterative solver.\n# At the end you should be able to\n# 1. Use BatchedGeneralizedMinimalResidual to solve batches of linear systems\n# 2. Construct a columnwise linear solver with BatchedGeneralizedMinimalResidual\n\n# ## What is the Generalized Minimal Residual Method?\n# The  Generalized Minimal Residual Method (GMRES) is a [Krylov subspace](https://en.wikipedia.org/wiki/Krylov_subspace) method for solving linear systems:\n# ```math\n#  Ax = b\n# ```\n# See the [wikipedia](https://en.wikipedia.org/wiki/Generalized_minimal_residual_method) for more details.\n\n# ## What is the Batched Generalized Minimal Residual Method?\n# As the name suggests it solves a whole bunch of independent GMRES problems\n\n# ## Basic Example\n# First we must load a few things\nusing ClimateMachine\nusing ClimateMachine.SystemSolvers\nusing LinearAlgebra, Random, Plots\n\n# Next we define two linear systems that we would like to solve simultaneously.\n# The matrix for the first linear system is\nA1 = [\n    2.0 -1.0 0.0\n    -1.0 2.0 -1.0\n    0.0 -1.0 2.0\n];\n# And the right hand side is\nb1 = ones(typeof(1.0), 3);\n# The exact solution to the first linear system is\nx1_exact = [1.5, 2.0, 1.5];\n# The matrix for the first linear system is\nA2 = [\n    2.0 -1.0 0.0\n    0.0 2.0 -1.0\n    0.0 0.0 2.0\n];\n# And the right hand side is\nb2 = ones(typeof(1.0), 3);\n# The exact solution to second linear system is\nx2_exact = [0.875, 0.75, 0.5];\n\n# We now define a function that performs the action of each linear operator independently.\nfunction closure_linear_operator(A1, A2)\n    function linear_operator!(x, y)\n        mul!(view(x, :, 1), A1, view(y, :, 1))\n        mul!(view(x, :, 2), A2, view(y, :, 2))\n        return nothing\n    end\n    return linear_operator!\nend;\n\n# To understand how this works let us construct an instance\n# of the linear operator and apply it to a vector\nlinear_operator! = closure_linear_operator(A1, A2);\n# Let us see what the action of this linear operator is\ny1 = ones(typeof(1.0), 3);\ny2 = ones(typeof(1.0), 3) * 2.0;\ny = [y1 y2];\nx = copy(y);\nlinear_operator!(x, y);\nx\n# We see that the first column is `A1 * [1 1 1]'`\n# and the second column is `A2 * [2 2 2]'`\n# that is,\n[A1 * y1 A2 * y2]\n\n# We are now ready to set up our Batched Generalized Minimal Residual solver\n# We must now set up the right hand side of the linear system\nb = [b1 b2];\n# as well as the exact solution, (to verify convergence)\nx_exact = [x1_exact x2_exact];\n# !!! warning\n#     For BatchedGeneralizedMinimalResidual the assumption is that each column of b is independent and corresponds to a batch. This will come back later.\n\n# We now use an instance of the solver\nlinearsolver = BatchedGeneralizedMinimalResidual(b, size(A1, 1), 2);\n# As well as an initial guess, denoted by the variable x\nx1 = ones(typeof(1.0), 3);\nx2 = ones(typeof(1.0), 3);\nx = [x1 x2];\n# To solve the linear system, we just need to pass to the linearsolve! function\niters = linearsolve!(linear_operator!, nothing, linearsolver, x, b)\n# which is guaranteed to converge in 3 iterations since `length(b1)=length(b2)=3`\n# We can now check that the solution that we computed, x\nx\n# has converged to the exact solution\nx_exact\n# Which indeed it has.\n# ## Advanced Example\n\n# We now go through a more advanced application of the Batched Generalized Minimal Residual solver\n# !!! warning\n#     Iterative methods should be used with preconditioners!\n# The first thing we do is define a linear operator that mimics\n# the behavior of a columnwise operator in ClimateMachine\nfunction closure_linear_operator!(A, tup)\n    function linear_operator!(y, x)\n        alias_x = reshape(x, tup)\n        alias_y = reshape(y, tup)\n        for i6 in 1:tup[6]\n            for i4 in 1:tup[4]\n                for i2 in 1:tup[2]\n                    for i1 in 1:tup[1]\n                        tmp = alias_x[i1, i2, :, i4, :, i6][:]\n                        tmp2 = A[i1, i2, i4, i6] * tmp\n                        alias_y[i1, i2, :, i4, :, i6] .=\n                            reshape(tmp2, (tup[3], tup[5]))\n                    end\n                end\n            end\n        end\n    end\nend;\n# Next we define the array structure of an MPIStateArray\n# in its true high dimensional form\ntup = (2, 2, 5, 2, 10, 2);\n# We define our linear operator as a random matrix\nRandom.seed!(1234);\nB = [\n    randn(tup[3] * tup[5], tup[3] * tup[5])\n    for i1 in 1:tup[1], i2 in 1:tup[2], i4 in 1:tup[4], i6 in 1:tup[6]\n];\ncolumnwise_A = [\n    B[i1, i2, i4, i6] + 3 * (i1 + i2 + i4 + i6) * I\n    for i1 in 1:tup[1], i2 in 1:tup[2], i4 in 1:tup[4], i6 in 1:tup[6]\n];\n# as well as its inverse\ncolumnwise_inv_A = [\n    inv(columnwise_A[i1, i2, i4, i6])\n    for i1 in 1:tup[1], i2 in 1:tup[2], i4 in 1:tup[4], i6 in 1:tup[6]\n];\ncolumnwise_linear_operator! = closure_linear_operator!(columnwise_A, tup);\ncolumnwise_inverse_linear_operator! =\n    closure_linear_operator!(columnwise_inv_A, tup);\n# The structure of an MPIStateArray is related to its true\n# higher dimensional form as follows:\nmpi_tup = (tup[1] * tup[2] * tup[3], tup[4], tup[5] * tup[6]);\n# We now define the right hand side of our Linear system\nb = randn(mpi_tup);\n# As well as the initial guess\nx = copy(b);\nx += randn(mpi_tup) * 0.1;\n# In the previous tutorial we mentioned that it is assumed that\n# the right hand side is an array whose column vectors all independent linear\n# systems. But right now the array structure of ``x`` and ``b`` do not follow\n# this requirement.\n# To handle this case we must pass in additional arguments that tell the\n# linear solver how to reconcile these differences.\n# The first thing that the linear solver must know of is the higher tensor\n# form of the MPIStateArray, which is just the `tup` from before\nreshape_tuple_f = tup;\n# The second thing it needs to know is which indices correspond to a column\n# and we want to make sure that these are the first set of indices that appear\n# in the permutation tuple (which can be thought of as enacting\n# a Tensor Transpose).\npermute_tuple_f = (5, 3, 4, 6, 1, 2);\n# It has this format since the 3 and 5 index slots\n# are the ones associated with traversing a column. And the 4 index\n# slot corresponds to a state.\n# We also need to tell our solver which kind of Array struct to use\nArrayType = Array;\n\n# We are now ready to finally define our linear solver, which uses a number\n# of keyword arguments\ngmres = BatchedGeneralizedMinimalResidual(\n    b,\n    tup[3] * tup[5] * tup[4],\n    tup[1] * tup[2] * tup[6];\n    atol = eps(Float64) * 100,\n    rtol = eps(Float64) * 100,\n    forward_reshape = reshape_tuple_f,\n    forward_permute = permute_tuple_f,\n);\n# `m` is the number of gridpoints along a column. As mentioned previously,\n# this is `tup[3]*tup[5]*tup[4]`. The `n` term corresponds to the batch size\n# or the number of columns in this case. `atol` and `rtol` are relative and\n# absolute tolerances\n\n# All the hard work is done, now we just call our linear solver\niters = linearsolve!(\n    columnwise_linear_operator!,\n    nothing,\n    gmres,\n    x,\n    b,\n    max_iters = tup[3] * tup[5] * tup[4],\n)\n# We see that it converged in less than `tup[3]*tup[5] = 50` iterations.\n# Let us verify that it is indeed correct by computing the exact answer\n# numerically and comparing it against the iterative solver.\nx_exact = copy(x);\ncolumnwise_inverse_linear_operator!(x_exact, b);\n# Now we can compare with some norms\nnorm(x - x_exact) / norm(x_exact)\ncolumnwise_linear_operator!(x_exact, x);\nnorm(x_exact - b) / norm(b)\n# Which we see are small, given our choice of `atol` and `rtol`.\n# The struct also keeps a record of its convergence rate\n# in the residual member (max over all batched solvers).\n# The can be visualized via\nplot(log.(gmres.resnorms[:]) / log(10));\nplot!(legend = false, xlims = (1, iters), ylims = (-15, 2));\nplot!(ylabel = \"log10 residual\", xlabel = \"iterations\")\n"
  },
  {
    "path": "tutorials/Numerics/SystemSolvers/cg.jl",
    "content": "# # Conjugate Gradient\n# In this tutorial we describe the basics of using the conjugate gradient iterative solvers\n# At the end you should be able to\n# 1. Use Conjugate Gradient to solve a linear system\n# 2. Know when to not use it\n# 3. Contruct a column-wise linear solver with Conjugate Gradient\n\n# ## What is it?\n# Conjugate Gradient is an iterative method for solving special kinds of linear systems:\n# ```math\n#  Ax = b\n# ```\n# via iterative methods.\n# !!! warning\n#     The linear operator need to be symmetric positive definite and the preconditioner must be symmetric.\n# See the [wikipedia](https://en.wikipedia.org/wiki/Conjugate_gradient_method) for more details.\n\n# ## Basic Example\n# First we must load a few things\nusing ClimateMachine\nusing ClimateMachine.SystemSolvers\nusing LinearAlgebra, Random\n\n# Next we define a 3x3 symmetric positive definite linear system. (In the ClimateMachine code a symmetric positive definite system could arise from treating diffusion implicitly.)\nA = [\n    2.0 -1.0 0.0\n    -1.0 2.0 -1.0\n    0.0 -1.0 2.0\n];\n# We define the matrix `A` here as a global variable for convenience later.\n\n# We can see that it is symmetric. We can check that it is positive definite by checking the spectrum\neigvals(A)\n\n# The linear operators that are passed into the abstract iterative solvers need to be defined as functions that act on vectors. Let us do that with our matrix. We are using function closures for type stability.\nfunction closure_linear_operator!(A)\n    function linear_operator!(x, y)\n        mul!(x, A, y)\n    end\n    return linear_operator!\nend;\n\n# We now define our linear operator using the function closure\n\nlinear_operator! = closure_linear_operator!(A)\n\n# We now define our `b` in the linear system\nb = ones(typeof(1.0), 3);\n\n# The exact solution to the system `Ax = b` is\nx_exact = [1.5, 2.0, 1.5];\n\n# Now we can set up the ConjugateGradient struct\nlinearsolver = ConjugateGradient(b);\n# and an initial guess for the iterative solver.\nx = ones(typeof(1.0), 3);\n# To solve the linear system we just need to pass to the linearsolve! function\niters = linearsolve!(linear_operator!, nothing, linearsolver, x, b)\n# The variable `x` gets overwritten during the linear solve\n# The norm of the error is\nnorm(x - x_exact) / norm(x_exact)\n# The relative norm of the residual is\nnorm(A * x - b) / norm(b)\n# The number of iterations is\niters\n# Conjugate Gradient is guaranteed to converge in 3 iterations with perfect arithmetic in this case.\n\n# ## Non-Example\n\n# Conjugate Gradient is not guaranteed to converge with nonsymmetric matrices. Consider\nA = [\n    2.0 -1.0 0.0\n    0.0 2.0 -1.0\n    0.0 0.0 2.0\n];\n# We define the matrix `A` here as a global variable for convenience later.\n\n# We can see that it is not symmetric, but it does have all positive eigenvalues\neigvals(A)\n\n# The linear operators that are passed into the abstract iterative solvers need to be defined as functions that act on vectors. Let us do that with our matrix. We are using function closures for type stability.\nfunction closure_linear_operator!(A)\n    function linear_operator!(x, y)\n        mul!(x, A, y)\n    end\n    return linear_operator!\nend;\n\n# We define the linear operator using our function closure\n\nlinear_operator! = closure_linear_operator!(A)\n\n# We now define our `b` in the linear system\nb = ones(typeof(1.0), 3);\n\n# The exact solution to the system `Ax = b` is\nx_exact = [0.875, 0.75, 0.5];\n\n# Now we can set up the ConjugateGradient struct\nlinearsolver = ConjugateGradient(b, max_iter = 100);\n# We also passed in the keyword argument \"max_iter\" for the maximum number of iterations of the iterative solver. By default it is assumed to be the size of the vector.\n# As before we need to define an initial guess\nx = ones(typeof(1.0), 3);\n# To (not) solve the linear system we just need to pass to the linearsolve! function\niters = linearsolve!(linear_operator!, nothing, linearsolver, x, b)\n# The variable `x` gets overwitten during the linear solve\n# The norm of the error is\nnorm(x - x_exact) / norm(x_exact)\n# The relative norm of the residual is\nnorm(A * x - b) / norm(b)\n# The number of iterations is\niters\n# Conjugate Gradient is guaranteed to converge in 3 iterations with perfect arithmetic for a symmetric positive definite matrix. Here we see that the matrix is not symmetric and it didn't converge even after 100 iterations.\n\n\n# ## More Complex Example\n# Here we show how to construct a column-wise iterative solver similar to what is is in the ClimateMachine code. The following is not for the faint of heart.\n# We must first define a linear operator that acts like one in the ClimateMachine\nfunction closure_linear_operator!(A, tup)\n    function linear_operator!(y, x)\n        alias_x = reshape(x, tup)\n        alias_y = reshape(y, tup)\n        for i6 in 1:tup[6]\n            for i4 in 1:tup[4]\n                for i2 in 1:tup[2]\n                    for i1 in 1:tup[1]\n                        tmp = alias_x[i1, i2, :, i4, :, i6][:]\n                        tmp2 = A[i1, i2, i4, i6] * tmp\n                        alias_y[i1, i2, :, i4, :, i6] .=\n                            reshape(tmp2, (tup[3], tup[5]))\n                    end\n                end\n            end\n        end\n    end\nend;\n\n# Now that we have this function, we can define a linear system that we will solve columnwise\n# First we define the structure of our array as `tup` in a manner that is similar to a stacked brick topology\ntup = (3, 4, 7, 2, 20, 2);\n# where\n# 1. tup[1] is the number of Gauss–Lobatto points in the x-direction\n# 2. tup[2] is the number of Gauss–Lobatto points in the y-direction\n# 3. tup[3] is the number of Gauss–Lobatto points in the z-direction\n# 4. tup[4] is the number of states\n# 6. tup[5] is the number of elements in the vertical direction\n# 7. tup[6] is the number of elements in the other directions\n\n# Now we define our linear operator as a random matrix.\nRandom.seed!(1235);\nB = [\n    randn(tup[3] * tup[5], tup[3] * tup[5])\n    for i1 in 1:tup[1], i2 in 1:tup[2], i4 in 1:tup[4], i6 in 1:tup[6]\n];\ncolumnwise_A = [\n    B[i1, i2, i4, i6] * B[i1, i2, i4, i6]' + 10I\n    for i1 in 1:tup[1], i2 in 1:tup[2], i4 in 1:tup[4], i6 in 1:tup[6]\n];\ncolumnwise_inv_A = [\n    inv(columnwise_A[i1, i2, i4, i6])\n    for i1 in 1:tup[1], i2 in 1:tup[2], i4 in 1:tup[4], i6 in 1:tup[6]\n];\ncolumnwise_linear_operator! = closure_linear_operator!(columnwise_A, tup);\ncolumnwise_inverse_linear_operator! =\n    closure_linear_operator!(columnwise_inv_A, tup);\n\n# We define our `x` and `b` with matrix structures similar to an MPIStateArray\nmpi_tup = (tup[1] * tup[2] * tup[3], tup[4], tup[5] * tup[6]);\nb = randn(mpi_tup);\nx = randn(mpi_tup);\n\n# Now we solve the linear system columnwise\nlinearsolver = ConjugateGradient(\n    x,\n    max_iter = tup[3] * tup[5],\n    dims = (3, 5),\n    reshape_tuple = tup,\n);\n# The keyword arguments dims is the reduction dimension for the linear solver. In this case dims = (3,5) are the ones associated with a column. The reshape_tuple argument is to convert the shapes of the array `x` in the a form that is more easily usable for reductions in the linear solver\n\n# Now we can solve it\niters = linearsolve!(columnwise_linear_operator!, nothing, linearsolver, x, b);\nx_exact = copy(x);\ncolumnwise_inverse_linear_operator!(x_exact, b);\n# The norm of the error is\nnorm(x - x_exact) / norm(x_exact)\n# The number of iterations is\niters\n# The algorithm converges within `tup[3]*tup[5] = 140` iterations\n\n# ## Tips\n# 1. The convergence criteria should be changed, machine precision is too small and the maximum iterations is often too large\n# 2. Use a preconditioner if possible\n# 3. Make sure that the linear system really is symmetric and positive-definite\n"
  },
  {
    "path": "tutorials/Numerics/TimeStepping/explicit_lsrk.jl",
    "content": "# # [Single-rate Explicit Timestepping](@id Single-rate-Explicit-Timestepping)\n\n# In this tutorial, we shall explore the use of explicit Runge-Kutta\n# methods for the solution of nonautonomous (or non time-invariant) equations.\n# For our model problem, we shall reuse the rising thermal bubble\n# tutorial. See its [tutorial page](@ref Rising-Thermal-Bubble-Configuration)\n# for details on the model and parameters. For the purposes of this tutorial,\n# we will only run the experiment for a total of 100 simulation seconds.\n\nusing ClimateMachine\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\ninclude(joinpath(\n    clima_dir,\n    \"tutorials\",\n    \"Numerics\",\n    \"TimeStepping\",\n    \"tutorial_risingbubble_config.jl\",\n))\n\nFT = Float64;\n\n# After discretizing the spatial terms in the equation, the semi-discretization\n# of the governing equations have the form:\n\n# ``\n# \\begin{aligned}\n#     \\frac{\\mathrm{d} \\boldsymbol{q}}{ \\mathrm{d} t} &= M^{-1}\\left(M S +\n#     D^{T} M (F^{adv} + F^{visc}) + \\sum_{f=1}^{N_f} L^T M_f(\\widehat{F}^{adv} + \\widehat{F}^{visc})\n#     \\right) \\equiv \\mathcal{T}(\\boldsymbol{q}).\n# \\end{aligned}\n# ``\n\n# Referencing the canonical form introduced in [Time integration](@ref\n# Time-integration) we have that in any explicit\n# formulation ``\\mathcal{F}(t, \\boldsymbol{q}) \\equiv 0`` and, in this particular\n# forumlation, ``\\mathcal{T}(t, \\boldsymbol{q}) \\equiv \\mathcal{G}(t, \\boldsymbol{q})``.\n\n# The time step restriction for an explicit method must satisfy the stable\n# [Courant number](https://en.wikipedia.org/wiki/Courant%E2%80%93Friedrichs%E2%80%93Lewy_condition)\n# for the specific time-integrator and must be selected from the following\n# constraints\n#\n# ``\n# \\Delta t_{\\mathrm{explicit}} = min \\left( \\frac{C \\Delta x_i}{u_i + a}, \\frac{C \\Delta x_i^2}{\\nu} \\right)\n# ``\n#\n# where ``C`` is the stable Courant number, ``u_i`` denotes the velocity components,\n# ``a`` the speed of sound, ``\\Delta x_i`` the grid spacing (non-uniform in case of\n# spectral element methods) along the direction ``(x_1,x_2,x_3)``, and ``\\nu`` the\n# kinematic viscosity. The first term on the right is the time step condition\n# due to the non-dissipative components, while the second term to the dissipation.\n# For explicit time-integrators, we have to find the minimum time step that\n# satisfies this condition along all three spatial directions.\n#\n# ## Runge-Kutta methods\n#\n# A single step of an ``s``-stage Runge-Kutta (RK) method for\n# solving the resulting ODE problem presented above and can be\n# expressed as the following:\n#\n# ```math\n# \\begin{align}\n# \t\\boldsymbol{q}^{n+1} = \\boldsymbol{q}^n + \\Delta t \\sum_{i=1}^{s} b_i \\mathcal{T}(\\boldsymbol{Q}^i),\n# \\end{align}\n# ```\n#\n# where ``\\boldsymbol{\\mathcal{T}}(\\boldsymbol{Q}^i)`` is the evaluation of the\n# right-hand side tendency at the stage value ``\\boldsymbol{Q}^i``, defined at\n# each stage of the RK method:\n#\n# ```math\n# \\begin{align}\n# \t\\boldsymbol{Q}^i = \\boldsymbol{q}^{n} +\n#     \\Delta t \\sum_{j=1}^{s} a_{i,j}\n#     \\mathcal{T}(\\boldsymbol{Q}^j).\n# \\end{align}\n# ```\n#\n# The first stage is initialized using the field at the previous time step:\n# ``\\boldsymbol{Q}^{1} \\leftarrow \\boldsymbol{q}^n``.\n#\n# In the above expressions, we define\n# ``\\boldsymbol{A} = \\lbrace a_{i,j} \\rbrace \\in \\mathbb{R}^{s\\times s}``,\n# ``\\boldsymbol{b} = \\lbrace b_i \\rbrace \\in \\mathbb{R}^s``, and\n# ``\\boldsymbol{c} = \\lbrace c_i \\rbrace \\in \\mathbb{R}^s`` as the\n# characteristic coefficients of a given RK method. This means we can\n# associate any RK method with its so-called *Butcher tableau*:\n#\n# ```math\n# \\begin{align}\n#     \\begin{array}{c|c}\n#         \\boldsymbol{c} &\\boldsymbol{A}\\\\\n#         \\hline\n#         & \\boldsymbol{b}^T\n#         \\end{array} =\n#         \\begin{array}{c|c c c c}\n#         c_1 & a_{1,1} & a_{1,2} & \\cdots & a_{1,s}\\\\\n#         c_2 & a_{2,1} & a_{2,2} & \\cdots & a_{2,s}\\\\\n#         \\vdots & \\vdots & \\vdots & \\ddots & \\vdots\\\\\n#         c_s & a_{s,1} & a_{s,2} & \\cdots & a_{s,s}\\\\\n#         \\hline\n#         & b_1 & b_2 & \\cdots & b_s\n#     \\end{array}.\n# \\end{align}\n# ```\n#\n# The vector ``\\boldsymbol{c}`` is often called the *consistency vector*,\n# and is typically subjected to the row-sum condition:\n#\n# ``\n# c_i = \\sum_{j=1}^{s} a_{i,j}, \\quad \\forall i = 1, \\cdots, s.\n# ``\n#\n# This simplifies the order conditions for higher-order RK methods.\n# For more information on general RK methods, we refer the interested reader\n# to Ch. 5.2 of [Atkinson2011](@cite).\n#\n# ### [Low-storage Runge-Kutta (LSRK) methods](@id lsrk)\n# `ClimateMachine.jl` contains the following low-storage methods:\n#   - Forward Euler [`LowStorageRungeKutta2N`](@ref ClimateMachine.ODESolvers.LowStorageRungeKutta2N),\n#   - A 5-stage 4th-order Runge-Kutta method of Carpenter and Kennedy [`LSRK54CarpenterKennedy`](@ref ClimateMachine.ODESolvers.LSRK54CarpenterKennedy)\n#   - A 14-stage 4th-order Runge-Kutta method developed by Niegemann, Diehl, and Busch [`LSRK144NiegemannDiehlBusch`](@ref ClimateMachine.ODESolvers.LSRK144NiegemannDiehlBusch).\n#\n# To start, let's try using the 5-stage method: `LSRK54CarpenterKennedy`.\n\n# As is the case for all explicit methods, we are limited by the fastest\n# propogating waves described by our governing equations. In our case,\n# these are the acoustic waves (with approximate wave speed given by the\n# speed of sound of 343 m/s).\n# For the rising bubble example used here, we use 4th order polynomials in\n# a discontinuous Galerkin approximation, with a domain resolution of\n# 125 meters in each spatial direction. This gives an effective\n# minimanl nodal distance (distance between LGL nodes) of 86 meters\n# over the entire mesh. Using the equation for the explcit time-step above,\n# we can determine the ``\\Delta t`` by specifying the desired Courant number\n# ``C`` (denoted `CFL` in the code below).\n# In our case, a heuristically determined value of 0.4 is used.\n\ntimeend = FT(100)\node_solver =\n    ClimateMachine.ExplicitSolverType(solver_method = LSRK54CarpenterKennedy)\nCFL = FT(0.4)\nrun_simulation(ode_solver, CFL, timeend);\n\n# What if we wish to take a larger timestep size?  We could\n# try to increase the target Courant number, say ``C = 1.7``, and\n# re-run the simulation. But this would break! In fact, in this case the numerical\n# scheme would fall outside of the stability region and the simulation would crash.\n# This occurs when the time step _exceeds_ the maximal stable time-step size\n# of the method. For the 5-stage method, one can typically get away with using\n# time-step sizes corresponding to a Courant number of ``C \\approx 0.4`` but\n# typically not much larger. In contrast, we can use an LSRK method with\n# a larger stability region. Let's illustrate this by using the 14-stage method\n# with a ``C = 1.7`` instead.\n\node_solver = ClimateMachine.ExplicitSolverType(\n    solver_method = LSRK144NiegemannDiehlBusch,\n)\nCFL = FT(1.7)\nrun_simulation(ode_solver, CFL, timeend);\n\n# And it successfully completes. Currently, the 14-stage LSRK method\n# `LSRK144NiegemannDiehlBusch` contains the largest stability region of the\n# low-storage methods available in `ClimateMachine.jl`.\n\n# ### [Strong Stability Preserving Runge--Kutta (SSPRK) methods](@id ssprk)\n\n# Just as with the LSRK methods, the SSPRK methods are self-starting,\n# with ``\\boldsymbol{Q}^{1} = \\boldsymbol{q}^n``, and stage-values are of the form\n\n# ```math\n# \\begin{align}\n#     \\boldsymbol{Q}^{i+1} = a_{i,1} \\boldsymbol{q}^n\n#     + a_{i,2} \\boldsymbol{Q}^{i}\n#     + \\Delta t b_i\\mathcal{T}(\\boldsymbol{Q}^{i})\n# \\end{align}\n# ```\n#\n# with the value at the next step being the ``(N+1)``-th stage value\n# ``\\boldsymbol{q}^{n+1} = \\boldsymbol{Q}^{(N+1)}``. This allows the updates\n# to be performed with only three copies of the state vector\n# (storing ``\\boldsymbol{q}^n``, ``\\boldsymbol{Q}^{i}`` and ``\\mathcal{T}(\\boldsymbol{Q}^{i})``).\n# We illustrate here the use of a [`SSPRK33ShuOsher`](@ref ClimateMachine.ODESolvers.SSPRK33ShuOsher) method.\n\node_solver = ClimateMachine.ExplicitSolverType(solver_method = SSPRK33ShuOsher)\nCFL = FT(0.2)\nrun_simulation(ode_solver, CFL, timeend);\n\n# ## References\n# - [Shu1988](@cite)\n# - [Heun1900](@cite)\n"
  },
  {
    "path": "tutorials/Numerics/TimeStepping/imex_ark.jl",
    "content": "# # [Implicit-Explicit (IMEX) Additively-Partitioned Runge-Kutta Timestepping](@id Single-rate-IMEXARK-Timestepping)\n\n# In this tutorial, we shall explore the use of IMplicit-EXplicit (IMEX) methods\n# for the solution of nonautonomous (or non time-invariant) equations.\n# For our model problem, we shall reuse the acoustic wave test in the GCM\n# configuration. See its [code](@ref Acoustic-Wave-Configuration)\n# for details on the model and parameters. For the purposes of this tutorial,\n# we will only run the experiment for a total of 3600 simulation seconds.\n# Details on this test case can be found in Sec. 4.3 of [Giraldo2013](@cite).\n\nusing ClimateMachine\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\ninclude(joinpath(\n    clima_dir,\n    \"tutorials\",\n    \"Numerics\",\n    \"TimeStepping\",\n    \"tutorial_acousticwave_config.jl\",\n));\n\n# The acoustic wave test case used in this tutorial represents a global-scale\n# problem with inertia-gravity waves traveling around the entire planet.\n# It has a hydrostatically balanced initial state that is given a pressure\n# perturbation.\n# This initial pressure perturbation causes an acoustic wave to travel to\n# the antipode, coalesce, and return to the initial position. The exact solution\n# of this test case is simple in that the (linear) acoustic theory allows one\n# to verify the analytic speed of sound based on the thermodynamics variables.\n# The initial condition is defined as a hydrostatically balanced atmosphere\n# with background (reference) potential temperature.\n\n# To fully demonstrate the advantages of using an IMEX scheme over fully explicit\n# schemes, we start here by going over a simple, fully explicit scheme. The\n# reader can refer to the [Single-rate Explicit Timestepping tutorial](@ref Single-rate-Explicit-Timestepping)\n# for detailes on such schemes. Here we use the the 14-stage LSRK method\n# [`LSRK144NiegemannDiehlBusch`]((@ref ClimateMachine.ODESolvers.LSRK144NiegemannDiehlBusch)), which contains the largest stability region of\n# the low-storage methods available in `ClimateMachine.jl`.\n\nFT = Float64\ntimeend = FT(100)\n\node_solver = ClimateMachine.ExplicitSolverType(\n    solver_method = LSRK144NiegemannDiehlBusch,\n);\n\n# In the following example, the timestep calculation is based on the CFL condition\n# for horizontally-propogating acoustic waves. We use a Courant number ``C = 0.002``\n# (denoted by `CFL` in the code bellow) in the horizontal, which corresponds\n# to a timestep size of approximately ``1`` second.\n\nCFL = FT(0.002)\ncfl_direction = HorizontalDirection()\nrun_acousticwave(ode_solver, CFL, cfl_direction, timeend);\n\n# However, as it is imaginable, for real-world climate processes a time step\n# of 1 second would lead to extemely long time-to-solution simulations.\n# How can we do better? To be able to take larger time step, we can treat the\n# most restrictive wave speeds (vertical acoustic) implicitly rather than\n# explicitly. This motivates the use of an IMplicit-EXplicit (IMEX) methods.\n\n# In general, a single step of an ``s``-stage, ``N``-part additive RK method\n# (`ARK_N`) is defined by its generalized Butcher tableau:\n\n# ```math\n# \\begin{align}\n#     \\begin{array}{c|c|c|c}\n#     \\boldsymbol{c} &\\boldsymbol{A}_{1} & \\cdots & \\boldsymbol{A}_{N}\\\\\n#     \\hline\n#     & \\boldsymbol{b}_1^T & \\cdots & \\boldsymbol{b}_N^T\\\\\n#     \\hline\n#     & \\widehat{\\boldsymbol{b}}_1^T & \\cdots & \\widehat{\\boldsymbol{b}}_N^T\n#     \\end{array} =\n#     \\begin{array}{c|c c c | c | c c c }\n#     c_1 & a^{[ 1 ]}_{1,1} & \\cdots & a^{[ 1 ]}_{1,s} & \\cdots\n#     & a^{[ \\nu ]}_{1,1} & \\cdots & a^{[ \\nu ]}_{1,s}\\\\\n#     \\vdots & \\vdots & \\ddots & \\vdots & \\cdots\n#     & \\vdots & \\ddots & \\vdots \\\\\n#     c_s & a^{[ 1 ]}_{s,1} & \\cdots & a^{[ 1 ]}_{s,s} & \\cdots\n#     & a^{[ \\nu ]}_{s,1} & \\cdots & a^{[ \\nu ]}_{s,s}\\\\\n#     \\hline\n#     & b^{[ 1 ]}_1 & \\cdots & b^{[ 1 ]}_s & \\cdots\n#     & b^{[ \\nu ]}_1 & \\cdots & b^{[ \\nu ]}_s\\\\\n#     \\hline\n#     & \\widehat{b}^{[ 1 ]}_1 & \\cdots & \\widehat{b}^{[ 1 ]}_s &\n#     & \\widehat{b}^{[ \\nu ]}_1 & \\cdots & \\widehat{b}^{[ \\nu ]}_s\n#     \\end{array}\n# \\end{align}\n# ```\n\n# and is given by\n\n# ``\n# \t\\boldsymbol{q}^{n+1} = \\boldsymbol{q}^n + \\Delta t \\left( \\underbrace{\\sum_{i=1}^{s}}_{\\textrm{Stages}} \\underbrace{\\sum_{\\nu=1}^{N}}_{\\textrm{Components}} b_i^{[ \\nu ]} {\\mathcal{T}}^{[ \\nu ]}(\\boldsymbol{Q}^i)) \\right)\n# ``\n\n# where ``s`` denotes the stages and ``N`` the components, and where the stage values are given by:\n#\n# ``\n# \t\\boldsymbol{Q}^i = \\boldsymbol{q}^n + \\Delta t \\sum_{j=1}^{s} \\sum_{\\nu = 1}^{N} a_{i,j}^{[ \\nu ]}\n# \t{\\mathcal{T}}^{[ \\nu]}(\\boldsymbol{Q}^j).\n# ``\n#\n# Similar to standard RK methods, the stage vectors are approximations to the state at each stage\n# of the ARK method. Moreover, the temporal coefficients ``c_i`` satisfy a similar\n# row-sum condition, holding for all ``\\nu = 1, \\cdots, N``:\n\n# ``\n# \tc_i = \\sum_{j=1}^{s} a_{i, j}^{[ \\nu ]}, \\quad \\forall \\nu = 1, \\cdots, N.\n# ``\n#\n# The Butcher coefficients ``\\boldsymbol{c}``, ``\\boldsymbol{b}_{\\nu}``, ``\\boldsymbol{A}_{\\nu}``, and ``\\widehat{\\boldsymbol{b}}_{\\nu}``\n# are constrained by certain accuracy and stability requirements, which are summarized in\n# [Kennedy2001](@cite).\n\n# A common setting is the case ``N = 2``. This gives the typical context for\n# Implicit-Explicit (IMEX) splitting methods, where the tendency ``{\\mathcal{T}}``\n# is assumed to have the decomposition:\n#\n# ``\n# \t\\dot{\\boldsymbol{q}} = \\mathcal{T}(\\boldsymbol{q}) \\equiv\n# \t{\\mathcal{T}}_{s}(\\boldsymbol{q}) + {\\mathcal{T}}_{ns}(\\boldsymbol{q}),\n# ``\n# where the right-hand side has been split into a \"stiff\" component ``{\\mathcal{T}}_{s}``,\n# to be treated implicitly, and a non-stiff part ``{\\mathcal{T}}_{ns}`` to be treated explicitly.\n\n# Referencing the canonical form introduced in [Time integration](@ref\n# Time-integration) we have that in this particular forumlation\n# ``\\mathcal{T}_{ns}(t, \\boldsymbol{q}) \\equiv \\mathcal{G}(t, \\boldsymbol{q})`` and\n# ``\\mathcal{T}_{s}(t, \\boldsymbol{q}) \\equiv \\mathcal{F}(t, \\boldsymbol{q})``.\n\n# Two different RK methods are applied to ``{\\mathcal{T}}_{s}`` and ``{\\mathcal{T}}_{ns}``\n# separately, which have been specifically designed and coupled. Examples can be found in\n# [Giraldo2013](@cite). The Butcher Tableau for an `ARK_2` method will have the\n# form\n#\n# ```math\n# \\begin{align}\n#     \\begin{array}{c|c|c}\n#     \\boldsymbol{c} &\\boldsymbol{A}_E &\\boldsymbol{A}_I\\\\\n#     \\hline\n#     & \\boldsymbol{b}_E^T & \\boldsymbol{b}_I^T \\\\\n#     \\hline\n#     & \\widehat{\\boldsymbol{b}}_E^T & \\widehat{\\boldsymbol{b}}_I^T\n#     \\end{array},\n# \\end{align}\n# ```\n#\n# with\n#\n# ``\n# \t\\boldsymbol{A}_O = \\left\\lbrace a_{i, j}^O \\right\\rbrace, \\quad\n# \t\\boldsymbol{b}_O = \\left\\lbrace b_{i}^O \\right\\rbrace, \\quad\n# \t\\widehat{\\boldsymbol{b}}_O = \\left\\lbrace \\widehat{b}_{i}^O \\right\\rbrace,\n# ``\n#\n# where ``O`` denotes the label (either ``E`` for explicit or ``I`` for implicit).\n\n# For the acoustic wave example used here, we use 4th order polynomials in\n# our discontinuous Galerkin approximation, with 6 elements in each horizontal\n# direction and 4 elements in the vertical direction, on the cubed-sphere.\n# This gives an effective minimal node-distance (distance between LGL nodes)\n# of roughly 203000 m.\n# As in the [previous tutorial](@ref Single-rate-Explicit-Timestepping),\n# we can determine our ``\\Delta t`` by specifying our desired horizontal\n# Courant number ``C`` (the timestep calculation is based on the CFL condition\n# for horizontally-propogating acoustic waves). In this very simple test case,\n# we can use a value of 0.5, which corresponds to a time-step size of\n# around 257 seconds. But for this particular example, even higher values\n# might work.\n\ntimeend = FT(3600)\node_solver = ClimateMachine.IMEXSolverType(\n    solver_method = ARK2GiraldoKellyConstantinescu,\n)\nCFL = FT(0.5)\ncfl_direction = HorizontalDirection()\nrun_acousticwave(ode_solver, CFL, cfl_direction, timeend);\n\n# ## References\n# - [Giraldo2013](@cite)\n# - [Kennedy2001](@cite)\n"
  },
  {
    "path": "tutorials/Numerics/TimeStepping/mis.jl",
    "content": "# # [Multirate Infinitesimal Step (MIS) Timestepping](@id MIS-Timestepping)\n\n# In this tutorial, we shall explore the use of Multirate Infinitesimal Step\n# (MIS) methods for the solution of nonautonomous (or non time-invariant) equations.\n# For our model problem, we shall reuse the rising thermal bubble\n# tutorial. See its [tutorial page](@ref Rising-Thermal-Bubble-Configuration)\n# for details on the model and parameters. For the purposes of this tutorial,\n# we will only run the experiment for a total of 500 simulation seconds.\n\nusing ClimateMachine\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\ninclude(joinpath(\n    clima_dir,\n    \"tutorials\",\n    \"Numerics\",\n    \"TimeStepping\",\n    \"tutorial_risingbubble_config.jl\",\n))\n\nFT = Float64;\n\n# Referencing the formulation introduced in the previous\n# [Multirate RK methods tutorial](@ref Multirate-RK-Timestepping), we can\n# describe Multirate Infinitesimal Step (MIS) methods by\n\n# ```math\n# \\begin{align}\n# v_i (0)\n#   &= q^n + \\sum_{j=1}^{i-1} \\alpha_{ij} (Q^{(j)} - q^n) \\\\\n# \\frac{dv_i}{d\\tau}\n#   &= \\sum_{j=1}^{i-1} \\frac{\\gamma_{ij}}{d_i \\Delta t} (Q^{(j)} - q^n)\n#     + \\sum_{j=1}^i \\frac{\\beta_{ij}}{d_i} \\mathcal{T}_S (Q^{(j)}, t + \\Delta t c_i)\n#     + \\mathcal{T}_F(v_i, t^n +  \\Delta t \\tilde c_i + \\frac{c_i - \\tilde c_i}{d_i} \\tau),\n# \\quad \\tau \\in [0, \\Delta t d_i] \\\\\n# Q^{(i)} &= v_i(\\Delta t d_i),\n# \\end{align}\n# ```\n#\n# where we have used the the stage values ``Q^{(i)} = v_i(\\tau_i)`` as the\n# solution to the _inner_ ODE problem, ``{\\mathcal{T}_{s}}``\n# for the slow component, and ``{\\mathcal{T}_{f}}` for the fast\n# one, as in the [Multirate RK methods tutorial](@ref Multirate-RK-Timestepping).\n\n# Referencing the canonical form introduced in [Time integration](@ref\n# Time-integration), both ``{\\mathcal{T}_{f}}`` and ``{\\mathcal{T}_{s}}``\n# could be discretized either explicitly or implicitly, hence, they could\n# belong to either ``\\mathcal{F}(t, \\boldsymbol{q})`` or ``\\mathcal{G}(t, \\boldsymbol{q})``\n# term.\n#\n# The method is defined in terms of the lower-triangular matrices ``\\alpha``,\n# ``\\beta`` and ``\\gamma``, with ``d_i = \\sum_j \\beta_{ij}``,\n# ``c_i = (I - \\alpha - \\gamma)^{-1} d`` and ``\\tilde c = \\alpha c``.\n# More details can be found in [WenschKnothGalant2009](@cite) and\n# [KnothWensch2014](@cite).\n\node_solver = ClimateMachine.MISSolverType(;\n    mis_method = MIS2,\n    fast_method = LSRK144NiegemannDiehlBusch,\n    nsubsteps = (40,),\n)\n\ntimeend = FT(500)\nCFL = FT(20)\nrun_simulation(ode_solver, CFL, timeend);\n\n# The reader can compare the Courant number (denoted by `CFL` in the code snippet)\n# used in this example, with the adopted in the\n# [single-rate explicit timestepping tutorial page](@ref Single-rate-Explicit-Timestepping)\n# in which we use the same scheme as the fast method employed in this case,\n# and notice that with this MIS method we are able to take a much larger\n# Courant number.\n\n# ## References\n# - [WenschKnothGalant2009](@cite)\n# - [KnothWensch2014](@cite)\n"
  },
  {
    "path": "tutorials/Numerics/TimeStepping/multirate_rk.jl",
    "content": "# # [Multirate Runge-Kutta Timestepping](@id Multirate-RK-Timestepping)\n\n# In this tutorial, we shall explore the use of Multirate Runge-Kutta\n# methods for the solution of nonautonomous (or non time-invariant) equations.\n# For our model problem, we shall reuse the acoustic wave test in the GCM\n# configuration. See its [code](@ref Acoustic-Wave-Configuration)\n# for details on the model and parameters. For the purposes of this tutorial,\n# we will only run the experiment for a total of 3600 simulation seconds.\n# Details on this test case can be found in Sec. 4.3 of [Giraldo2013](@cite).\n\nusing ClimateMachine\nconst clima_dir = dirname(dirname(pathof(ClimateMachine)));\ninclude(joinpath(\n    clima_dir,\n    \"tutorials\",\n    \"Numerics\",\n    \"TimeStepping\",\n    \"tutorial_acousticwave_config.jl\",\n))\n\nFT = Float64;\n\n# The typical context for Multirate splitting methods is given by problems\n# in which the tendency ``\\mathcal{T}`` is assumed to have single parts that\n# operate on different time rates (such as a slow time scale and a fast time scale).\n# A general form is given by\n#\n# ``\n# \t\\dot{\\boldsymbol{q}} = \\mathcal{T}(\\boldsymbol{q}) \\equiv\n# \t{\\mathcal{T}}_{f}(\\boldsymbol{q}) + {\\mathcal{T}}_{s}(\\boldsymbol{q}),\n# ``\n#\n# where the right-hand side has been split into a \"fast\" component ``{\\mathcal{T}_{f}}``,\n# and a \"slow\" component ``{\\mathcal{T}_{s}}``.\n\n# Referencing the canonical form introduced in [Time integration](@ref\n# Time-integration), both ``{\\mathcal{T}_{f}}`` and ``{\\mathcal{T}_{s}}``\n# could be discretized either explicitly or implicitly, hence, they could\n# belong to either ``\\mathcal{F}(t, \\boldsymbol{q})`` or ``\\mathcal{G}(t, \\boldsymbol{q})``\n# term.\n#\n# For a given time-step size ``\\Delta t``, the two-rate method in [Schlegel2009](@cite)\n# is summarized as the following:\n#\n# ```math\n# \\begin{align}\n#     \\boldsymbol{Q}_1 &= \\boldsymbol{q}(t_n), \\\\\n#     \\boldsymbol{r}_{i} &= \\sum_{j=1}^{i-1}\\tilde{a}^O_{ij} {\\mathcal{T}_{s}}(\\boldsymbol{Q}_{j}), \\\\\n#     \\boldsymbol{w}_{i,1} &= \\boldsymbol{Q}_{i-1},\\\\\n#     \\boldsymbol{w}_{i,k} &= \\boldsymbol{w}_{i,k-1} + \\Delta t \\tilde{c}_i^O \\sum_{j=1}^{k-1}\\tilde{a}^I_{k,j}\n#     \\left(\\frac{1}{\\tilde{c}_i^O}\\boldsymbol{r}_i + {\\mathcal{T}_{f}}(\\boldsymbol{w}_{i,j})\\right),\\\\\n#     & \\quad\\quad i = 2, \\cdots, s^O + 1 \\text{ and } k = 2, \\cdots, s^I + 1,\\nonumber \\\\\n#     \\boldsymbol{Q}_i &= \\boldsymbol{w}_{i,s^I + 1}\n# \\end{align}\n# ```\n#\n# where the tilde parameters denote increments per RK stage:\n#\n# ```math\n# \\begin{align}\n#     \\tilde{a}_{ij} &= \\begin{cases}\n#         a_{i,j} - a_{i-1, j} & \\text{if } i < s + 1 \\\\\n#         b_j - a_{s,j} & \\text{if } i = s + 1\n#     \\end{cases},\\\\\n#     \\tilde{c}_{i} &= \\begin{cases}\n#         c_{i} - c_{i-1} & \\text{if } i < s + 1 \\\\\n#         1 - c_{s} & \\text{if } i = s + 1\n#     \\end{cases},\n# \\end{align}\n# ```\n#\n# where the coefficients ``a``, ``b``, and ``c`` correspond to the Butcher\n# tableau for a given RK method. The superscripts ``O`` and ``I`` denote the\n# *outer* (slow) and *inner* (fast) components of the multirate method\n# respectively. Thus, tilde coefficients should be associated with the RK\n# method indicated by the superscripts. In other words, the RK methods\n# for the slow ``{\\mathcal{T}_{s}}`` and fast\n# ``{\\mathcal{T}_{f}}`` components have Butcher tables given by:\n#\n# ```math\n# \\begin{align}\n#     \\begin{array}{c|c}\n#     \\boldsymbol{c}_{O} &\\boldsymbol{A}_{O} \\\\\n#     \\hline\n#     & \\boldsymbol{b}_O^T\n#     \\end{array}, \\quad\n#     \\begin{array}{c|c}\n#     \\boldsymbol{c}_{I} &\\boldsymbol{A}_{I} \\\\\n#     \\hline\n#     & \\boldsymbol{b}_I^T\n#     \\end{array},\n# \\end{align}\n# ```\n#\n# where ``\\boldsymbol{A}_O = \\lbrace a_{i,j}^O\\rbrace``, ``\\boldsymbol{b}_O = \\lbrace b_i^O \\rbrace``, and\n# ``c_O = \\lbrace c_i^O \\rbrace`` (similarly for ``\\boldsymbol{A}_I``, ``\\boldsymbol{b}_I``, and ``\\boldsymbol{c}_I``).\n# The method described here is for an explicit RK outer method with ``s`` stages.\n# More details can be found in [Schlegel2012](@cite).\n\n# The acoustic wave test case used in this tutorial represents a global-scale\n# problem with inertia-gravity waves traveling around the entire planet.\n# It has a hydrostatically balanced initial state that is given a pressure\n# perturbation.\n# This initial pressure perturbation causes an acoustic wave to travel to\n# the antipode, coalesce, and return to the initial position. The exact solution\n# of this test case is simple in that the (linear) acoustic theory allows one\n# to verify the analytic speed of sound based on the thermodynamics variables.\n# The initial condition is defined as a hydrostatically balanced atmosphere\n# with background (reference) potential temperature.\n\node_solver = ClimateMachine.MultirateSolverType(\n    splitting_type = ClimateMachine.HEVISplitting(),\n    slow_method = LSRK54CarpenterKennedy,\n    fast_method = ARK2GiraldoKellyConstantinescu,\n    implicit_solver_adjustable = true,\n    timestep_ratio = 100,\n)\n\ntimeend = FT(3600)\nCFL = FT(5)\ncfl_direction = HorizontalDirection()\nrun_acousticwave(ode_solver, CFL, cfl_direction, timeend);\n\n# The interested reader can explore the combination of different slow and\n# fast methods for Multirate solvers, consulting the ones available in\n# `ClimateMachine.jl`, such as the\n# [`Low-Storage-Runge-Kutta-methods`](@ref ClimateMachine.ODESolvers.LowStorageRungeKutta2N),\n# [`Strong-Stability-Preserving-RungeKutta-methods`](@ref ClimateMachine.ODESolvers.StrongStabilityPreservingRungeKutta),\n# and [`Additive-Runge-Kutta-methods`](@ref ClimateMachine.ODESolvers.AdditiveRungeKutta).\n\n# ## References\n# - [Giraldo2013](@cite)\n# - [Schlegel2009](@cite)\n# - [Schlegel2012](@cite)\n"
  },
  {
    "path": "tutorials/Numerics/TimeStepping/ts_intro.jl",
    "content": "# # [Time integration](@id Time-integration)\n\n# Time integration methods for the numerical solution of Ordinary Differential\n# Equations (ODEs), also called timesteppers, can be of different nature and\n# flavor (e.g., explicit, semi-implicit, single-stage, multi-stage, single-step,\n# multi-step, single-rate, multi-rate, etc). ClimateMachine supports several\n# of them. Before showing the different nature of some of these methods, let us\n# introduce some common notation.\n\n# A commonly used notation for Initial Value Problems (IVPs) is:\n\n# ```math\n# \\begin{align}\n#     \\frac{\\mathrm{d} \\boldsymbol{q}}{ \\mathrm{d} t} &= \\mathcal{T}(t, \\boldsymbol{q}),\\\\\n#     \\boldsymbol{q}(t_0) &= \\boldsymbol{q_0},\n# \\end{align}\n# ```\n\n# where ``\\boldsymbol{q}`` is an unknown function (vector in most of our cases)\n# of time ``t``, which we would like to approximate, and at the initial time ``t_0``\n# the corresponding initial value ``\\boldsymbol{q}_0`` is given.\n\n# The given general formulation, is suitable for single-step explicit schemes.\n# Generally, the equation can be represented in the following canonical form:\n\n# ```math\n# \\begin{align}\n#      \\dot {\\boldsymbol{q}} + \\mathcal{F}(t, \\boldsymbol{q}) &= \\mathcal{G}(t, \\boldsymbol{q}),\n# \\end{align}\n# ```\n\n# where we have used ``\\dot {\\boldsymbol{q}} = d \\boldsymbol{q} / dt``.\n# We refer to the term ``\\mathcal{G}``\n# as the right-hand-side (RHS) or explicit term, and to the spatial terms of\n# ``\\mathcal{F}`` as the left-hand-side (LHS) or implicit term.\n"
  },
  {
    "path": "tutorials/Numerics/TimeStepping/tutorial_acousticwave_config.jl",
    "content": "# # [Acoustic Wave Configuration](@id Acoustic-Wave-Configuration)\n\n#\n# In this example, we demonstrate the usage of the `ClimateMachine`\n# [AtmosModel](@ref AtmosModel-docs) machinery to solve the fluid\n# dynamics of an acoustic wave.\n\nusing ClimateMachine\nClimateMachine.init()\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.Checkpoint\nusing ClimateMachine.ConfigTypes\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\nusing ClimateMachine.Grids\nusing ClimateMachine.ODESolvers\n\nusing CLIMAParameters\n\nusing StaticArrays\n\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet()\n\nBase.@kwdef struct AcousticWaveSetup{FT}\n    domain_height::FT = 10e3\n    T_ref::FT = 300\n    α::FT = 3\n    γ::FT = 100\n    nv::Int = 1\nend\n\nfunction (setup::AcousticWaveSetup)(problem, bl, state, aux, localgeo, t)\n    ## callable to set initial conditions\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n\n    λ = longitude(bl, aux)\n    φ = latitude(bl, aux)\n    z = altitude(bl, aux)\n\n    β = min(FT(1), setup.α * acos(cos(φ) * cos(λ)))\n    f = (1 + cos(FT(π) * β)) / 2\n    g = sin(setup.nv * FT(π) * z / setup.domain_height)\n    Δp = setup.γ * f * g\n    p = aux.ref_state.p + Δp\n\n    ts = PhaseDry_pT(param_set, p, setup.T_ref)\n    q_pt = PhasePartition(ts)\n    e_pot = gravitational_potential(bl.orientation, aux)\n    e_int = internal_energy(ts)\n\n    state.ρ = air_density(ts)\n    state.ρu = SVector{3, FT}(0, 0, 0)\n    state.energy.ρe = state.ρ * (e_int + e_pot)\n    return nothing\nend\n\nfunction run_acousticwave(\n    ode_solver_type,\n    CFL::FT,\n    CFL_direction,\n    timeend::FT,\n) where {FT}\n\n    ## DG polynomial orders\n    N = (4, 4)\n\n    ## Domain resolution\n    nelem_horz = 6\n    nelem_vert = 4\n    resolution = (nelem_horz, nelem_vert)\n\n    t0 = FT(0)\n\n    setup = AcousticWaveSetup{FT}()\n    T_profile = IsothermalProfile(param_set, setup.T_ref)\n    ref_state = HydrostaticState(T_profile)\n    turbulence = ConstantDynamicViscosity(FT(0))\n    physics = AtmosPhysics{FT}(\n        param_set;\n        ref_state = ref_state,\n        turbulence = turbulence,\n        moisture = DryModel(),\n    )\n    model = AtmosModel{FT}(\n        AtmosGCMConfigType,\n        physics;\n        init_state_prognostic = setup,\n        source = (Gravity(),),\n    )\n\n    driver_config = ClimateMachine.AtmosGCMConfiguration(\n        \"GCM Driver: Acoustic wave test\",\n        N,\n        resolution,\n        setup.domain_height,\n        param_set,\n        setup;\n        model = model,\n    )\n\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        Courant_number = CFL,\n        init_on_cpu = true,\n        ode_solver_type = ode_solver_type,\n        CFL_direction = CFL_direction,\n    )\n\n    ClimateMachine.invoke!(solver_config)\nend\n"
  },
  {
    "path": "tutorials/Numerics/TimeStepping/tutorial_risingbubble_config.jl",
    "content": "# # [Rising Thermal Bubble Configuration](@id Rising-Thermal-Bubble-Configuration)\n#\n# In this example, we demonstrate the usage of the `ClimateMachine`\n# [AtmosModel](@ref AtmosModel-docs) machinery to solve the fluid\n# dynamics of a thermal perturbation in a neutrally stratified background state\n# defined by its uniform potential temperature. We solve a flow in a box configuration -\n# this is representative of a large-eddy simulation. Several versions of the problem\n# setup may be found in literature, but the general idea is to examine the\n# vertical ascent of a thermal bubble (we can interpret these as simple\n# representation of convective updrafts).\n#\n# ## Description of experiment\n# 1) Dry Rising Bubble (circular potential temperature perturbation)\n# 2) Boundaries\n#    Top and Bottom boundaries:\n#    - `Impenetrable(FreeSlip())` - Top and bottom: no momentum flux, no mass flux through\n#      walls.\n#    - `Impermeable()` - non-porous walls, i.e. no diffusive fluxes through\n#       walls.\n#    Lateral boundaries\n#    - Laterally periodic\n# 3) Domain - 2500m (horizontal) x 2500m (horizontal) x 2500m (vertical)\n# 4) Resolution - 50m effective resolution\n# 5) Total simulation time - 1000s\n# 6) Mesh Aspect Ratio (Effective resolution) 1:1\n# 7) Overrides defaults for\n#    - CPU Initialisation\n#    - Time integrator\n#    - Sources\n#    - Smagorinsky Coefficient\n\n#md # !!! note\n#md #     This experiment setup assumes that you have installed the\n#md #     `ClimateMachine` according to the instructions on the landing page.\n#md #     We assume the users' familiarity with the conservative form of the\n#md #     equations of motion for a compressible fluid (see the\n#md #     [AtmosModel](@ref AtmosModel-docs) page).\n#md #\n#md #     The following topics are covered in this example\n#md #     - Package requirements\n#md #     - Defining a `model` subtype for the set of conservation equations\n#md #     - Defining the initial conditions\n#md #     - Applying source terms\n#md #     - Choosing a turbulence model\n#md #     - Adding tracers to the model\n#md #     - Choosing a time-integrator\n#md #     - Choosing diagnostics (output) configurations\n#md #\n#md #     The following topics are not covered in this example\n#md #     - Defining new boundary conditions\n#md #     - Defining new turbulence models\n#md #     - Building new time-integrators\n#md #     - Adding diagnostic variables (beyond a standard pre-defined list of\n#md #       variables)\n\n# ## [Loading code](@id Loading-code-rtb)\n\n# Before setting up our experiment, we recognize that we need to import some\n# pre-defined functions from other packages. Julia allows us to use existing\n# modules (variable workspaces), or write our own to do so.  Complete\n# documentation for the Julia module system can be found\n# [here](https://docs.julialang.org/en/v1/manual/modules/#).\n\n# We need to use the `ClimateMachine` module! This imports all functions\n# specific to atmospheric and ocean flow modeling.\n\nusing ClimateMachine\nClimateMachine.init()\nusing ClimateMachine.Atmos\nusing ClimateMachine.Orientations\nusing ClimateMachine.ConfigTypes\nusing ClimateMachine.Diagnostics\nusing ClimateMachine.GenericCallbacks\nusing ClimateMachine.ODESolvers\nusing Thermodynamics.TemperatureProfiles\nusing Thermodynamics\nusing ClimateMachine.TurbulenceClosures\nusing ClimateMachine.VariableTemplates\n\n# In ClimateMachine we use `StaticArrays` for our variable arrays.\n# We also use the `Test` package to help with unit tests and continuous\n# integration systems to design sensible tests for our experiment to ensure new\n# / modified blocks of code don't damage the fidelity of the physics. The test\n# defined within this experiment is not a unit test for a specific\n# subcomponent, but ensures time-integration of the defined problem conditions\n# within a reasonable tolerance. Immediately useful macros and functions from\n# this include `@test` and `@testset` which will allow us to define the testing\n# parameter sets.\nusing StaticArrays\nusing Test\nusing CLIMAParameters\nusing CLIMAParameters.Atmos.SubgridScale: C_smag\nusing CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav\nstruct EarthParameterSet <: AbstractEarthParameterSet end\nconst param_set = EarthParameterSet();\n\n# ## [Initial Conditions](@id init-rtb)\n# This example demonstrates the use of functions defined\n# in the [`Thermodynamics`](@ref Thermodynamics) module to\n# generate the appropriate initial state for our problem.\n\n#md # !!! note\n#md #     The following variables are assigned in the initial condition\n#md #     - `state.ρ` = Scalar quantity for initial density profile\n#md #     - `state.ρu`= 3-component vector for initial momentum profile\n#md #     - `state.energy.ρe`= Scalar quantity for initial total-energy profile\n#md #       humidity\n#md #     - `state.tracers.ρχ` = Vector of four tracers (here, for demonstration\n#md #       only; we can interpret these as dye injections for visualization\n#md #       purposes)\nfunction init_risingbubble!(problem, bl, state, aux, localgeo, t)\n    (x, y, z) = localgeo.coord\n\n    ## Problem float-type\n    FT = eltype(state)\n    param_set = parameter_set(bl)\n\n    ## Unpack constant parameters\n    R_gas::FT = R_d(param_set)\n    c_p::FT = cp_d(param_set)\n    c_v::FT = cv_d(param_set)\n    p0::FT = MSLP(param_set)\n    _grav::FT = grav(param_set)\n    γ::FT = c_p / c_v\n\n    ## Define bubble center and background potential temperature\n    xc::FT = 5000\n    yc::FT = 1000\n    zc::FT = 2000\n    r = sqrt((x - xc)^2 + (z - zc)^2)\n    rc::FT = 2000\n    θamplitude::FT = 2\n\n    ## This is configured in the reference hydrostatic state\n    ref_state = reference_state(bl)\n    θ_ref::FT = ref_state.virtual_temperature_profile.T_surface\n\n    ## Add the thermal perturbation:\n    Δθ::FT = 0\n    if r <= rc\n        Δθ = θamplitude * (1.0 - r / rc)\n    end\n\n    ## Compute perturbed thermodynamic state:\n    θ = θ_ref + Δθ                                      ## potential temperature\n    π_exner = FT(1) - _grav / (c_p * θ) * z             ## exner pressure\n    ρ = p0 / (R_gas * θ) * (π_exner)^(c_v / R_gas)      ## density\n    T = θ * π_exner\n    e_int = internal_energy(param_set, T)\n    ts = PhaseDry(param_set, e_int, ρ)\n    ρu = SVector(FT(0), FT(0), FT(0))                   ## momentum\n    ## State (prognostic) variable assignment\n    e_kin = FT(0)                                       ## kinetic energy\n    e_pot = gravitational_potential(bl, aux)            ## potential energy\n    ρe_tot = ρ * total_energy(e_kin, e_pot, ts)         ## total energy\n\n    ρχ = FT(0)                                          ## tracer\n\n    ## We inject tracers at the initial condition at some specified z coordinates\n    if 500 < z <= 550\n        ρχ += FT(0.05)\n    end\n\n    ## We want 4 tracers\n    ntracers = 4\n\n    ## Define 4 tracers, (arbitrary scaling for this demo problem)\n    ρχ = SVector{ntracers, FT}(ρχ, ρχ / 2, ρχ / 3, ρχ / 4)\n\n    ## Assign State Variables\n    state.ρ = ρ\n    state.ρu = ρu\n    state.energy.ρe = ρe_tot\n    state.tracers.ρχ = ρχ\nend\n\n# ## [Model Configuration](@id config-helper)\n# We define a configuration function to assist in prescribing the physical\n# model. The purpose of this is to populate the\n# `ClimateMachine.AtmosLESConfiguration` with arguments\n# appropriate to the problem being considered.\nfunction config_risingbubble(\n    ::Type{FT},\n    N,\n    resolution,\n    xmax,\n    ymax,\n    zmax,\n) where {FT}\n    ## Since we want four tracers, we specify this and include the appropriate\n    ## diffusivity scaling coefficients (normally these would be physically\n    ## informed but for this demonstration we use integers corresponding to the\n    ## tracer index identifier)\n    ntracers = 4\n    δ_χ = SVector{ntracers, FT}(1, 2, 3, 4)\n    ## To assemble `AtmosModel` with no tracers, set `tracers = NoTracers()`.\n\n    ## The model coefficient for the turbulence closure is defined via the\n    ## [CLIMAParameters\n    ## package](https://CliMA.github.io/CLIMAParameters.jl/latest/) A reference\n    ## state for the linearisation step is also defined.\n    T_surface = FT(300)\n    T_min_ref = FT(0)\n    T_profile = DryAdiabaticProfile{FT}(param_set, T_surface, T_min_ref)\n    ref_state = HydrostaticState(T_profile)\n\n    ## Here we assemble the `AtmosModel`.\n    _C_smag = FT(C_smag(param_set))\n    physics = AtmosPhysics{FT}(\n        param_set;                                     ## Parameter set corresponding to earth parameters\n        ref_state = ref_state,                         ## Reference state\n        turbulence = SmagorinskyLilly(_C_smag),        ## Turbulence closure model\n        moisture = DryModel(),                         ## Exclude moisture variables\n        tracers = NTracers{ntracers, FT}(δ_χ),         ## Tracer model with diffusivity coefficients\n    )\n\n    model = AtmosModel{FT}(\n        AtmosLESConfigType,                            ## Flow in a box, requires the AtmosLESConfigType\n        physics;                                       ## Atmos physics\n        init_state_prognostic = init_risingbubble!,    ## Apply the initial condition\n        source = (Gravity(),),                         ## Gravity is the only source term here\n    )\n\n    ## Finally, we pass a `Problem Name` string, the mesh information, and the\n    ## model type to  the [`AtmosLESConfiguration`] object.\n    config = ClimateMachine.AtmosLESConfiguration(\n        \"DryRisingBubble\",       ## Problem title [String]\n        N,                       ## Polynomial order [Int]\n        resolution,              ## (Δx, Δy, Δz) effective resolution [m]\n        xmax,                    ## Domain maximum size [m]\n        ymax,                    ## Domain maximum size [m]\n        zmax,                    ## Domain maximum size [m]\n        param_set,               ## Parameter set.\n        init_risingbubble!,      ## Function specifying initial condition\n        model = model,           ## Model type\n    )\n    return config\nend\n\n#md # !!! note\n#md #     `Keywords` are used to specify some arguments (see appropriate source\n#md #     files).\n\n# ## Diagnostics\n# Here we define the diagnostic configuration specific to this problem.\nfunction config_diagnostics(driver_config)\n    interval = \"10000steps\"\n    dgngrp = setup_atmos_default_diagnostics(\n        AtmosLESConfigType(),\n        interval,\n        driver_config.name,\n    )\n    return ClimateMachine.DiagnosticsConfiguration([dgngrp])\nend\n\nfunction run_simulation(ode_solver_type, CFL::FT, timeend::FT) where {FT}\n\n    ## We need to specify the polynomial order for the DG discretization,\n    ## effective resolution, simulation end-time, the domain bounds, and the\n    ## courant-number for the time-integrator. Note how the time-integration\n    ## components `solver_config` are distinct from the spatial / model\n    ## components in `driver_config`. `init_on_cpu` is a helper keyword argument\n    ## that forces problem initialization on CPU (thereby allowing the use of\n    ## random seeds, spline interpolants and other special functions at the\n    ## initialization step.)\n    N = 4\n    Δh = FT(125)\n    Δv = FT(125)\n    resolution = (Δh, Δh, Δv)\n    xmax = FT(10000)\n    ymax = FT(500)\n    zmax = FT(10000)\n    t0 = FT(0)\n    ## timeend = FT(100)\n    ## For full simulation set `timeend = 1000`\n\n    driver_config = config_risingbubble(FT, N, resolution, xmax, ymax, zmax)\n    solver_config = ClimateMachine.SolverConfiguration(\n        t0,\n        timeend,\n        driver_config,\n        init_on_cpu = true,\n        Courant_number = CFL,\n        ode_solver_type = ode_solver_type,\n    )\n    dgn_config = config_diagnostics(driver_config)\n    @show solver_config.ode_solver_type\n\n    ## Invoke solver (calls `solve!` function for time-integrator), pass the driver,\n    ## solver and diagnostic config information.\n    result = ClimateMachine.invoke!(\n        solver_config;\n        diagnostics_config = dgn_config,\n        user_callbacks = (),\n        check_euclidean_distance = true,\n    )\n    return result\nend\n"
  },
  {
    "path": "tutorials/Ocean/geostrophic_adjustment.jl",
    "content": "# # Geostrophic adjustment in the hydrostatic Boussinesq equations\n#\n# This example simulates a one-dimensional geostrophic adjustement problem\n# using the `ClimateMachine.Ocean` subcomponent to solve the hydrostatic\n# Boussinesq equations.\n#\n# First we `ClimateMachine.init()`.\n\nusing ClimateMachine\n\nClimateMachine.init()\n\n# # Domain setup\n#\n# We formulate our problem in a Cartesian domain 100 km in ``x, y`` and 400 m\n# deep, and discretized on a grid with 100 fourth-order elements in ``x``, and 1\n# fourth-order element in ``y, z``,\n\nusing ClimateMachine.CartesianDomains\n\ndomain = RectangularDomain(\n    Ne = (25, 1, 1),\n    Np = 4,\n    x = (0, 1e6),\n    y = (0, 1e6),\n    z = (-400, 0),\n    periodicity = (false, true, false),\n)\n\n# # Physical parameters\n#\n# We use a Coriolis parameter appropriate for mid-latitudes,\n\nf = 1e-4 # s⁻¹, Coriolis parameter\nnothing # hide\n\n# and Earth's gravitational acceleration,\n\nusing CLIMAParameters: AbstractEarthParameterSet, Planet\nstruct EarthParameters <: AbstractEarthParameterSet end\n\ng = Planet.grav(EarthParameters()) # m s⁻²\n\n# # An unbalanced initial state\n#\n# We use a Gaussian, partially-balanced initial condition with parameters\n\nU = 0.1              # geostrophic velocity (m s⁻¹)\nL = domain.L.x / 40  # Gaussian width (m)\na = f * U * L / g    # amplitude of the geostrophic surface displacement (m)\nx₀ = domain.L.x / 4  # Gaussian origin (m, recall that x ∈ [0, Lx])\n\n# and functional form\n\nGaussian(x, L) = exp(-x^2 / (2 * L^2))\n\n## Geostrophic ``y``-velocity: f V = g ∂_x η\nvᵍ(x, y, z) = -U * (x - x₀) / L * Gaussian(x - x₀, L)\n\n## Geostrophic surface displacement\nηᵍ(x, y, z) = a * Gaussian(x - x₀, L)\n\n# We double the initial surface displacement so that the surface is half-balanced,\n# half unbalanced,\n\nηⁱ(x, y, z) = 2 * ηᵍ(x, y, z)\n\n# In summary,\n\nusing ClimateMachine.Ocean.OceanProblems: InitialConditions\n\ninitial_conditions = InitialConditions(v = vᵍ, η = ηⁱ)\n\n@info \"\"\" Parameters for the Geostrophic adjustment problem are...\n\n    Coriolis parameter:                            $f s⁻¹\n    Gravitational acceleration:                    $g m s⁻²\n    Geostrophic velocity:                          $U m s⁻¹\n    Width of the initial geostrophic perturbation: $L m\n    Amplitude of the initial surface perturbation: $a m\n    Rossby number (U / f L):                       $(U / (f * L))\n\n\"\"\"\n\n# # Boundary conditions, Driver configuration, and Solver configuration\n#\n# Next, we configure the `HydrostaticBoussinesqModel` and build the `DriverConfiguration`.\n# We configure our model in a domain which is bounded in the ``x`` direction.\n# Both the boundary conditions in ``x`` and in ``z`` require boundary conditions,\n# which we define:\n\nusing ClimateMachine.Ocean:\n    Impenetrable, Penetrable, FreeSlip, Insulating, OceanBC\n\nsolid_surface_boundary_conditions = OceanBC(\n    Impenetrable(FreeSlip()), # Velocity boundary conditions\n    Insulating(),             # Temperature boundary conditions\n)\n\nfree_surface_boundary_conditions = OceanBC(\n    Penetrable(FreeSlip()),   # Velocity boundary conditions\n    Insulating(),             # Temperature boundary conditions\n)\n\nboundary_conditions =\n    (solid_surface_boundary_conditions, free_surface_boundary_conditions)\n\n# We refer to these boundary conditions by their indices in the `boundary_tags` tuple\n# when specifying the boundary conditions for the `state`; in other words, \"1\" corresponds to\n# `solid_surface_boundary_conditions`, while `2` corresponds to `free_surface_boundary_conditions`,\n\nboundary_tags = (\n    (1, 1), # (west, east) boundary conditions\n    (0, 0), # (south, north) boundary conditions\n    (1, 2), # (bottom, top) boundary conditions\n)\n\n# We're now ready to build the model.\n\nusing ClimateMachine.Ocean\n\nmodel = Ocean.HydrostaticBoussinesqSuperModel(\n    domain = domain,\n    time_step = 2.0,\n    initial_conditions = initial_conditions,\n    parameters = EarthParameters(),\n    turbulence_closure = (νʰ = 0, κʰ = 0, νᶻ = 0, κᶻ = 0),\n    coriolis = (f₀ = f, β = 0),\n    boundary_tags = boundary_tags,\n    boundary_conditions = boundary_conditions,\n);\nnothing\n\n# !!! info \"Horizontallly-periodic boundary conditions\"\n#     To set horizontally-periodic boundary conditions with\n#     `(solid_surface_boundary_conditions, free_surface_boundary_conditions)`\n#     in the vertical direction use `periodicity = (true, true, false)` in\n#     the `domain` constructor and `boundary_tags = ((0, 0), (0, 0), (1, 2))`\n#     in the constructor for `HydrostaticBoussinesqSuperModel`.\n\n# # Animating the solution\n#\n# To animate the `ClimateMachine.Ocean` solution, we'll create a callback\n# that draws a plot and stores it in an array. When the simulation is finished,\n# we'll string together the plotted frames into an animation.\n\nusing Printf\nusing Plots\nusing ClimateMachine.GenericCallbacks: EveryXSimulationSteps\nusing ClimateMachine.CartesianFields: assemble\nusing ClimateMachine.Ocean: current_step, current_time\n\nu, v, η, θ = model.fields\n\n## Container to hold the plotted frames\nmovie_plots = []\n\nplot_every = 200 # iterations\n\nplot_maker = EveryXSimulationSteps(plot_every) do\n\n    @info \"Steps: $(current_step(model)), time: $(current_time(model))\"\n\n    assembled_u = assemble(u.elements)\n    assembled_v = assemble(v.elements)\n    assembled_η = assemble(η.elements)\n\n    umax = 0.5 * max(maximum(abs, u), maximum(abs, v))\n    ulim = (-umax, umax)\n\n    u_plot = plot(\n        assembled_u.x[:, 1, 1],\n        [assembled_u.data[:, 1, 1] assembled_v.data[:, 1, 1]],\n        xlim = domain.x,\n        ylim = (-0.7U, 0.7U),\n        label = [\"u\" \"v\"],\n        linewidth = 2,\n        xlabel = \"x (m)\",\n        ylabel = \"Velocities (m s⁻¹)\",\n    )\n\n    η_plot = plot(\n        assembled_η.x[:, 1, 1],\n        assembled_η.data[:, 1, 1],\n        xlim = domain.x,\n        ylim = (-0.01a, 1.2a),\n        linewidth = 2,\n        label = nothing,\n        xlabel = \"x (m)\",\n        ylabel = \"η (m)\",\n    )\n\n    push!(movie_plots, (u = u_plot, η = η_plot, time = current_time(model)))\n\n    return nothing\nend\n\n# # Running the simulation and animating the results\n#\n# Finally, we run the simulation,\n\nhours = 3600.0\n\nmodel.solver_configuration.timeend = 2hours\n\nresult = ClimateMachine.invoke!(\n    model.solver_configuration;\n    user_callbacks = [plot_maker],\n)\n\n# and animate the results,\n\nanimation = @animate for p in movie_plots\n    title = @sprintf(\"Geostrophic adjustment at t = %.2f hours\", p.time / hours)\n    frame = plot(\n        p.u,\n        p.η,\n        layout = (2, 1),\n        size = (800, 600),\n        title = [title \"\"],\n    )\nend\n\ngif(animation, \"geostrophic_adjustment.mp4\", fps = 5) # hide\n"
  },
  {
    "path": "tutorials/Ocean/internal_wave.jl",
    "content": "# # Mode-1 internal wave reflection\n#\n# This example simulates the propagation of a mode-1 internal wave\n# using the `ClimateMachine.Ocean` subcomponent to solve the hydrostatic\n# Boussinesq equations.\n#\n# First we `ClimateMachine.init()`.\n\nusing ClimateMachine\n\nClimateMachine.init()\n\n# # Domain setup\n#\n# We formulate a non-dimension problem in a Cartesian domain with oceanic anisotropy,\n\nusing ClimateMachine.CartesianDomains\n\ndomain = RectangularDomain(\n    Ne = (32, 1, 4),\n    Np = 4,\n    x = (-128, 128),\n    y = (-128, 128),\n    z = (-1, 0),\n    periodicity = (false, false, false),\n)\n\n# # Parameters\n#\n# We choose parameters appropriate for a hydrostatic internal wave,\n\n# Non-dimensional internal wave parameters\nf = 1  # Coriolis\nN = 10 # Buoyancy frequency\n\n# Note that the validity of the hydrostatic approximation requires\n# small aspect ratio motions with ``k / m \\\\ll 1``.\n# The hydrostatic dispersion relation for inertia-gravity waves then implies that\n\nλ = 8      # horizontal wave-length\nk = 2π / λ # horizontal wavenumber\nm = π\n\nω² = f^2 + N^2 * k^2 / m^2 # and\n\nω = √(ω²)\n\n# # Internal wave initial condition\n# \n# We impose modest gravitational acceleration to render time-stepping feasible,\n\nusing CLIMAParameters: AbstractEarthParameterSet, Planet\nstruct NonDimensionalParameters <: AbstractEarthParameterSet end\n\nPlanet.grav(::NonDimensionalParameters) = 256.0\n\n# we'd like to use `θ` as a buoyancy variable, which requires\n# setting the thermal expansion coefficient ``αᵀ`` to\n\ng = Planet.grav(NonDimensionalParameters())\n\nαᵀ = 1 / g\n\n# We then use the \"polarization relations\" for vertically-standing, horizontally-\n# propagating hydrostatic internal waves to initialze two wave packets.\n# The hydrostatic polarization relations require\n#\n# ```math\n# \\begin{gather}\n# (∂_t^2 + f^2) u = - ∂_x ∂_t p\n# ∂_t v = - f u\n# b = ∂_z p\n# \\end{gather}\n# ```\n#\n# Thus given ``p = \\cos (k x - ω t) \\cos (m z)``, we find\n\nδ = domain.L.x / 15\na(x) = 1e-6 * exp(-x^2 / 2 * δ^2)\n\nũ(x, z, t) = +a(x) * ω * sin(k * x - ω * t) * cos(m * z)\nṽ(x, z, t) = -a(x) * f * cos(k * x - ω * t) * cos(m * z)\nθ̃(x, z, t) = -a(x) * m / k * (ω^2 - f^2) * sin(k * x - ω * t) * sin(m * z)\n\nuᵢ(x, y, z) = ũ(x, z, 0)\nvᵢ(x, y, z) = ṽ(x, z, 0)\nθᵢ(x, y, z) = θ̃(x, z, 0) + N^2 * z\n\nusing ClimateMachine.Ocean.OceanProblems: InitialConditions\n\ninitial_conditions = InitialConditions(u = uᵢ, v = vᵢ, θ = θᵢ)\n\n# # Model configuration\n# \n# We choose a time-step that resolves the gravity wave phase speed,\n\ntime_step = 0.005 # close to Δx / c = 0.5 * 1/16, where Δx is nominal resolution\n\n# and build a model with a smidgeon of viscosity and diffusion,\n\nusing ClimateMachine.Ocean: HydrostaticBoussinesqSuperModel\n\nmodel = HydrostaticBoussinesqSuperModel(\n    domain = domain,\n    time_step = time_step,\n    initial_conditions = initial_conditions,\n    parameters = NonDimensionalParameters(),\n    turbulence_closure = (νʰ = 1e-6, νᶻ = 1e-6, κʰ = 1e-6, κᶻ = 1e-6),\n    coriolis = (f₀ = f, β = 0),\n    buoyancy = (αᵀ = αᵀ,),\n    boundary_tags = ((1, 1), (1, 1), (1, 2)),\n);\nnothing\n\n# # Fetching data for an animation\n#\n# To animate the `ClimateMachine.Ocean` solution, we assemble and\n# cache the horizontal velocity ``u`` at periodic intervals:\n\nusing ClimateMachine.Ocean: current_time\nusing ClimateMachine.CartesianFields: assemble\nusing ClimateMachine.GenericCallbacks: EveryXSimulationTime\n\nfetched_states = []\nfetch_every = 0.2 * 2π / ω # time\n\ndata_fetcher = EveryXSimulationTime(fetch_every) do\n    push!(\n        fetched_states,\n        (\n            u = assemble(model.fields.u.elements),\n            θ = assemble(model.fields.θ.elements),\n            η = assemble(model.fields.η.elements),\n            time = current_time(model),\n        ),\n    )\n    return nothing\nend\n\n# We also build a callback to log the progress of our simulation,\n\nusing Printf\nusing ClimateMachine.GenericCallbacks: EveryXSimulationSteps\nusing ClimateMachine.Ocean: current_time, current_step, Δt\n\nprint_every = 100 # iterations\nwall_clock = [time_ns()]\n\ntiny_progress_printer = EveryXSimulationSteps(print_every) do\n\n    @info(@sprintf(\n        \"Steps: %d, time: %.2f, Δt: %.2f, max(|u|): %.2e, elapsed time: %.2f secs\",\n        current_step(model),\n        current_time(model),\n        Δt(model),\n        maximum(abs, model.fields.u),\n        1e-9 * (time_ns() - wall_clock[1])\n    ))\n\n    wall_clock[1] = time_ns()\nend\n\n# # Running the simulation and animating the results\n#\n# We're ready to launch.\n\nmodel.solver_configuration.timeend = 6 * 2π / ω\n## model.solver.dt = 0.05 # make this work\n\n@info \"\"\" Simulating a hydrostatic Gaussian wave packet with parameters\n\n    f (Coriolis parameter):       $f\n    N (buoyancy frequency):       $N\n    Internal wave frequency:      $(abs(ω))\n    Surface wave frequency:       $(k * sqrt(g * domain.L.z))\n    Surface wave group velocity:  $(sqrt(g * domain.L.z))\n    Internal wave group velocity: $(N^2 * k / (ω * m))\n    Domain width:                 $(domain.L.x)  \n    Domain height:                $(domain.L.z)  \n\n\"\"\"\n\nresult = ClimateMachine.invoke!(\n    model.solver_configuration;\n    user_callbacks = [tiny_progress_printer, data_fetcher],\n)\n\n# # Animating the result\n#\n# We first analye the results to generate plotting limits and contour levels\n\nηmax = maximum([maximum(abs, state.η.data) for state in fetched_states])\numax = maximum([maximum(abs, state.u.data) for state in fetched_states])\n\nηlim = (-ηmax, ηmax)\nulim = (-umax, umax)\nulevels = range(ulim[1], ulim[2], length = 31)\n\n# and then animate both fields in a loop,\n\nusing Plots\n\nanimation = @animate for (i, state) in enumerate(fetched_states)\n    @info \"Plotting frame $i of $(length(fetched_states))...\"\n\n    η_plot = plot(\n        state.u.x[:, 1, 1],\n        state.η.data[:, 1, 1],\n        ylim = ηlim,\n        label = nothing,\n        title = @sprintf(\"η at t = %.2f\", state.time),\n    )\n\n    u_plot = contourf(\n        state.u.x[:, 1, 1],\n        state.u.z[1, 1, :],\n        clamp.(state.u.data[:, 1, :], ulim[1], ulim[2])';\n        aspectratio = 64,\n        linewidth = 0,\n        xlim = domain.x,\n        ylim = domain.z,\n        xlabel = \"x\",\n        ylabel = \"z\",\n        color = :balance,\n        colorbar = false,\n        clim = ulim,\n        levels = ulevels,\n        title = @sprintf(\"u at t = %.2f\", state.time),\n    )\n\n    plot(\n        η_plot,\n        u_plot,\n        layout = Plots.grid(2, 1, heights = (0.3, 0.7)),\n        link = :x,\n        size = (600, 300),\n    )\nend\n\ngif(animation, \"internal_wave.mp4\", fps = 5) # hide\n"
  },
  {
    "path": "tutorials/Ocean/shear_instability.jl",
    "content": "# # Shear instability of a free-surface flow\n#\n# This script simulates the instability of a sheared, free-surface\n# flow using `ClimateMachine.Ocean.HydrostaticBoussinesqSuperModel`.\n\nusing Printf\nusing Plots\nusing ClimateMachine\n\nClimateMachine.init()\n\nClimateMachine.Settings.array_type = Array\n\nusing ClimateMachine.Ocean\nusing ClimateMachine.CartesianDomains\nusing ClimateMachine.CartesianFields\n\nusing ClimateMachine.GenericCallbacks: EveryXSimulationTime\nusing ClimateMachine.Ocean: current_step, Δt, current_time\nusing CLIMAParameters: AbstractEarthParameterSet, Planet\n\n# We begin by specifying the domain and mesh,\n\ndomain = RectangularDomain(\n    Ne = (24, 24, 1),\n    Np = 4,\n    x = (-3π, 3π),\n    y = (-3π, 3π),\n    z = (0, 1),\n    periodicity = (true, false, false),\n)\n\n# Note that the default solid-wall boundary conditions are free-slip and\n# insulating on tracers. Next, we specify model parameters and the sheared\n# initial conditions\n\nstruct NonDimensionalParameters <: AbstractEarthParameterSet end\nPlanet.grav(::NonDimensionalParameters) = 1\n\ninitial_conditions = InitialConditions(\n    u = (x, y, z) -> tanh(y) + 0.1 * cos(x / 3) + 0.01 * randn(),\n    v = (x, y, z) -> 0.1 * sin(y / 3),\n    θ = (x, y, z) -> x,\n)\n\nmodel = Ocean.HydrostaticBoussinesqSuperModel(\n    domain = domain,\n    time_step = 0.05,\n    initial_conditions = initial_conditions,\n    parameters = NonDimensionalParameters(),\n    turbulence_closure = (νʰ = 1e-2, κʰ = 1e-2, νᶻ = 1e-2, κᶻ = 1e-2),\n    rusanov_wave_speeds = (cʰ = 0.1, cᶻ = 1),\n    boundary_tags = ((0, 0), (1, 1), (1, 2)),\n    boundary_conditions = (\n        OceanBC(Impenetrable(FreeSlip()), Insulating()),\n        OceanBC(Penetrable(FreeSlip()), Insulating()),\n    ),\n);\nnothing\n\n# We prepare a callback that periodically fetches the horizontal velocity and\n# tracer concentration for later animation,\n\nu, v, η, θ = model.fields\nfetched_states = []\n\nstart_time = time_ns()\n\ndata_fetcher = EveryXSimulationTime(1) do\n    step = @sprintf(\"Step: %d\", current_step(model))\n    time = @sprintf(\"time: %.2f min\", current_time(model) / 60)\n    max_u = @sprintf(\"max|u|: %.6f\", maximum(abs, u))\n\n    elapsed = (time_ns() - start_time) * 1e-9\n    wall_time = @sprintf(\"elapsed wall time: %.2f min\", elapsed / 60)\n\n    @info \"$step, $time, $max_u, $wall_time\"\n\n    push!(\n        fetched_states,\n        (u = assemble(u), θ = assemble(θ), time = current_time(model)),\n    )\nend\n\n# and then run the simulation.\n\nmodel.solver_configuration.timeend = 100.0\n\nresult = ClimateMachine.invoke!(\n    model.solver_configuration;\n    user_callbacks = [data_fetcher],\n)\n\n# Finally, we make an animation of the evolving shear instability.\n\nanimation = @animate for (i, state) in enumerate(fetched_states)\n    local u\n    local θ\n\n    @info \"Plotting frame $i of $(length(fetched_states))...\"\n\n    kwargs =\n        (xlim = domain.x, ylim = domain.y, linewidth = 0, aspectratio = 1)\n\n    x, y = state.u.x[:, 1, 1], state.u.y[1, :, 1]\n\n    u = state.u.data[:, :, 1]\n    θ = state.θ.data[:, :, 1]\n\n    ulim = 1\n    θlim = 8\n\n    ulevels = range(-ulim, ulim, length = 31)\n    θlevels = range(-θlim, θlim, length = 31)\n\n    u_plot = contourf(\n        x,\n        y,\n        clamp.(u, -ulim, ulim)';\n        levels = ulevels,\n        color = :balance,\n        kwargs...,\n    )\n    θ_plot = contourf(\n        x,\n        y,\n        clamp.(θ, -θlim, θlim)';\n        levels = θlevels,\n        color = :thermal,\n        kwargs...,\n    )\n\n    u_title = @sprintf(\"u at t = %.2f\", state.time)\n    θ_title = @sprintf(\"θ at t = %.2f\", state.time)\n\n    plot(u_plot, θ_plot, title = [u_title θ_title], size = (600, 250))\nend\n\ngif(animation, \"shear_instability.mp4\", fps = 5)\n"
  },
  {
    "path": "tutorials/TutorialList.jl",
    "content": "# # Tutorials\n# A suite of concrete examples are provided here as a guidance for constructing experiments.\n# ## Balance Law\n# An introduction on components within a balance law is provided.\n# ## Atmos\n# Showcase drivers for atmospheric modelling in GCM, single stack, and LES simulations are provided.\n# - Dry Idealzed GCM:  The Held-Suarez configuration is used as a guidance to create a driver that runs a simple GCM simulation.\n# - Single Element Stack:  The Burgers Equations with a passive tracer is used as a guidance to run the simulation on a single element stack.\n# - LES Experiment:  The dry rising bubble case is used as a quigance in creating an LES driver.\n# - Topography:  Experiments of dry flow over prescirbe topography (Agnesi mountain) are provided for:\n#     * Linear Hydrostatic Mountain\n#     * Linear Non-Hydrostatic Mountain\n# ## Ocean\n# A showcase for Ocean model is still under construction.\n# ## Land\n# Examples are provided in constructing balance law and solving for fundemental equations in land modelling.\n# - Heat:  A tutorial shows how to create a HeatModel to solve the heat equation and visualize the outputs.\n# - Soil:  Examples of solving fundemental equations in the soil model are provided.\n#     * Hydraulic Functions:  a tutorial to specify the hydraulic function in the Richard's equation.\n#     * Soil Heat Equations:  a tutorial for solving the heat equation in the soil.\n#     * Coupled Water and Heat:  a tutorial for solving interactive heat and wateri in the soil model.\n# ## Numerics (need to be moved to How-to-Guide)\n# - System Solvers:  Two numerical methods to solve the linear system Ax=b are provided.\n#     * Conjugate Gradient\n#     * Batched Generalized Minimal Residual\n# - DG Methods\n#     * Filters\n# ## Diagnostics\n# A diagnostic tool that can\n# - generate statistics for MPIStateArrays\n# - validate with reference values\n# for debugging purposes.\n"
  },
  {
    "path": "tutorials/literate_markdown.jl",
    "content": "# # How to generate a literate tutorial file\n\n# To create an tutorial using ClimateMachine, please use [Literate.jl](https://github.com/fredrikekre/Literate.jl), and consult the [Literate documentation](https://fredrikekre.github.io/Literate.jl/stable/) for questions.\n#\n# For now, all literate tutorials are held in the `tutorials` directory\n\n# With Literate, all comments turn into markdown text and any Julia code is read and run *as if it is in the Julia REPL*.\n# As a small caveat to this, you might need to suppress the output of certain commands.\n# For example, if you define and run the following function\n\nfunction f()\n    return x = [i * i for i in 1:10]\nend\n\nx = f()\n\n# The entire list will be output, while\n\nf();\n\n# does not (because of the `;`).\n#\n# To show plots, you may do something like the following:\nusing Plots\nplot(x)\n\n# Please consider writing the comments in your tutorial as if they are meant to be read as an *article explaining the topic the tutorial is meant to explain.*\n# If there are any specific nuances to writing Literate documentation for ClimateMachine, please let us know!\n"
  }
]