Skip to content

Commit 1d03372

Browse files
committed
Fix energy conservation with sparse autodiff
1 parent d94fd43 commit 1d03372

File tree

7 files changed

+83
-33
lines changed

7 files changed

+83
-33
lines changed

.buildkite/pipeline.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,12 @@ steps:
286286
--job_id baroclinic_wave_conservation
287287
artifact_paths: "baroclinic_wave_conservation/output_active/*"
288288

289+
- label: ":computer: baroclinic wave check conservation float64 sparse autodiff"
290+
command: >
291+
julia --color=yes --project=.buildkite .buildkite/ci_driver.jl --config_file $CONFIG_PATH/baroclinic_wave_conservation_ft64_sparse_autodiff.yml
292+
--job_id baroclinic_wave_conservation_ft64_sparse_autodiff
293+
artifact_paths: "baroclinic_wave_conservation_ft64_sparse_autodiff/output_active/*"
294+
289295
- label: ":computer: baroclinic wave moist check conservation"
290296
command: >
291297
julia --color=yes --project=.buildkite .buildkite/ci_driver.jl --config_file $CONFIG_PATH/baroclinic_wave_equil_conservation.yml
@@ -303,7 +309,6 @@ steps:
303309
julia --color=yes --project=.buildkite .buildkite/ci_driver.jl --config_file $CONFIG_PATH/baroclinic_wave_equil_conservation_ft64_sparse_autodiff.yml
304310
--job_id baroclinic_wave_equil_conservation_ft64_sparse_autodiff
305311
artifact_paths: "baroclinic_wave_equil_conservation_ft64_sparse_autodiff/output_active/*"
306-
soft_fail: true
307312

308313
- label: ":computer: baroclinic wave moist check conservation with sources"
309314
command: >
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
FLOAT_TYPE: "Float64"
2+
initial_condition: "DryBaroclinicWave"
3+
t_end: "5days"
4+
dt_save_state_to_disk: "5days"
5+
disable_surface_flux_tendency: true
6+
use_auto_jacobian: true
7+
update_jacobian_every: dt
8+
debug_jacobian: true
9+
check_conservation: true
10+
diagnostics:
11+
- short_name: [massa, energya]
12+
period: 1days
13+
writer: dict
14+
use_itime: true

post_processing/jacobian_summary.jl

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -77,22 +77,24 @@ function print_jacobian_summary(integrator)
7777
dense_bandwidth_values = map(block_keys) do block_key
7878
bandwidth(dense_blocks[block_key])
7979
end
80-
sparse_bandwidth_values = map(block_keys) do block_key
81-
sparse_blocks = first(all_sparse_blocks)
82-
haskey(sparse_blocks, block_key) ?
83-
(
84-
sparse_blocks[block_key] isa UniformScaling ? 1 :
85-
bandwidth(sparse_blocks[block_key])
86-
) : 0
87-
end
88-
missing_bandwidth_values =
89-
max.(dense_bandwidth_values .- sparse_bandwidth_values, 0)
90-
@info "dense, number of nonzero bands per block:"
80+
@info "dense, nonzero bands per block:"
9181
pretty_table(dense_bandwidth_values; bandwidth_table_kwargs...)
92-
@info "sparse, number of nonzero bands per block:"
93-
pretty_table(sparse_bandwidth_values; bandwidth_table_kwargs...)
94-
@info "dense - sparse, number of missing nonzero bands per block:"
95-
pretty_table(missing_bandwidth_values; bandwidth_table_kwargs...)
82+
for (sparse_name, sparse_blocks) in pairs(all_sparse_blocks)
83+
sparse_bandwidth_values = map(block_keys) do block_key
84+
haskey(sparse_blocks, block_key) ?
85+
(
86+
sparse_blocks[block_key] isa UniformScaling ?
87+
1 - iszero(sparse_blocks[block_key]) :
88+
bandwidth(sparse_blocks[block_key])
89+
) : 0
90+
end
91+
missing_bandwidth_values =
92+
max.(dense_bandwidth_values .- sparse_bandwidth_values, 0)
93+
@info "$sparse_name sparse, nonzero bands per block:"
94+
pretty_table(sparse_bandwidth_values; bandwidth_table_kwargs...)
95+
@info "dense - $sparse_name sparse, missing nonzero bands per block:"
96+
pretty_table(missing_bandwidth_values; bandwidth_table_kwargs...)
97+
end
9698
println("<$('='^70)>\n")
9799

98100
rms(block) = sqrt(mean(abs2.(block)))

src/prognostic_equations/implicit/auto_dense_jacobian.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ AutoDenseJacobian(max_simultaneous_derivatives = 32) =
4848
# The number of derivatives computed simultaneously by AutoDenseJacobian.
4949
max_simultaneous_derivatives(::AutoDenseJacobian{S}) where {S} = S
5050

51-
function jacobian_cache(alg::AutoDenseJacobian, Y, atmos)
51+
function jacobian_cache(alg::AutoDenseJacobian, Y, atmos; kwargs...)
5252
FT = eltype(Y)
5353
DA = ClimaComms.array_type(Y)
5454

src/prognostic_equations/implicit/auto_sparse_jacobian.jl

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@ end
2323
AutoSparseJacobian(sparse_jacobian_alg) =
2424
AutoSparseJacobian(sparse_jacobian_alg, nothing)
2525

26-
function jacobian_cache(alg::AutoSparseJacobian, Y, atmos; verbose = true)
26+
function jacobian_cache(
27+
alg::AutoSparseJacobian,
28+
Y,
29+
atmos;
30+
verbose = true,
31+
kwargs...,
32+
)
2733
(; sparse_jacobian_alg, padding_bands_per_block) = alg
2834

2935
FT = eltype(Y)
@@ -38,10 +44,13 @@ function jacobian_cache(alg::AutoSparseJacobian, Y, atmos; verbose = true)
3844
scratch = implicit_temporary_quantities(Y, atmos)
3945

4046
# Allocate ∂R/∂Y and its corresponding linear solver.
47+
sparse_jacobian_cache =
48+
jacobian_cache(sparse_jacobian_alg, Y, atmos; verbose, auto_pad = true)
49+
4150
# TODO: Add FieldNameTree(Y) to the matrix in FieldMatrixWithSolver. The
4251
# tree is needed to evaluate scalar_tendency_matrix[autodiff_matrix_keys].
43-
# (; matrix) = jacobian_cache(sparse_jacobian_alg, Y, atmos)
44-
matrix_without_tree = jacobian_cache(sparse_jacobian_alg, Y, atmos).matrix
52+
# (; matrix) = sparse_jacobian_cache
53+
matrix_without_tree = sparse_jacobian_cache.matrix
4554
tree = MatrixFields.FieldNameTree(Y)
4655
matrix = MatrixFields.FieldMatrixWithSolver(
4756
MatrixFields.replace_name_tree(matrix_without_tree.matrix, tree),
@@ -137,6 +146,17 @@ function jacobian_cache(alg::AutoSparseJacobian, Y, atmos; verbose = true)
137146
(@name(c.ρq_liq), @name(c.ρq_ice), @name(c.ρq_rai), @name(c.ρq_sno))
138147
max_padding_bands = if !isnothing(padding_bands_per_block)
139148
padding_bands_per_block
149+
elseif (
150+
block_row_name == @name(c.ρe_tot) &&
151+
block_column_name in
152+
(@name(c.ρ), @name(c.ρe_tot), uₕ_component_names...) &&
153+
!(block_key in keys(autodiff_matrix))
154+
)
155+
# ‖∂ᶜρe_totₜ/∂ᶜχ‖ ≳ ‖∂ᶜρe_totₜ/∂ᶠu₃‖ / 10^12 for any center field ᶜχ.
156+
# The ∂ᶜρe_totₜ/∂ᶠu₃ block is critical for conservation of energy,
157+
# and even relative errors as small as one part in a trillion can
158+
# noticeably degrade conservation with Float64 precision.
159+
3
140160
elseif (
141161
(
142162
block_row_name in uₕ_component_names &&
@@ -169,7 +189,7 @@ function jacobian_cache(alg::AutoSparseJacobian, Y, atmos; verbose = true)
169189
# relatively smaller than ‖δᶜρaʲ‖.
170190
# - ‖∂ᶠu₃ʲₜ/∂ᶜu₁‖ and ‖∂ᶠu₃ʲₜ/∂ᶜu₁‖ ≳ ‖∂ᶠu₃ʲₜ/∂ᶠu₃ʲ‖, as long as
171191
# ‖δᶜu₁‖ and ‖δᶜu₂‖ are relatively smaller than ‖δᶠu₃ʲ‖.
172-
# Diagonal blocks are critical for conservation and stability, so
192+
# Diagonal blocks are important for conservation and stability, so
173193
# these potential errors from off-diagonal blocks should be avoided.
174194
3
175195
elseif (

src/prognostic_equations/implicit/jacobian.jl

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
A description of how to compute the matrix ``∂R/∂Y``, where ``R(Y)`` denotes the
55
residual of an implicit step with the state ``Y``. Concrete implementations of
66
this abstract type should define 3 methods:
7-
- `jacobian_cache(alg::JacobianAlgorithm, Y, atmos; [verbose])`
7+
- `jacobian_cache(alg::JacobianAlgorithm, Y, atmos; kwargs...)`
88
- `update_jacobian!(alg::JacobianAlgorithm, cache, Y, p, dtγ, t)`
99
- `invert_jacobian!(alg::JacobianAlgorithm, cache, ΔY, R)`
1010
To facilitate debugging, concrete implementations should also define
@@ -17,25 +17,22 @@ abstract type JacobianAlgorithm end
1717
abstract type SparseJacobian <: JacobianAlgorithm end
1818

1919
"""
20-
Jacobian(alg, Y, atmos; [verbose])
20+
Jacobian(alg, Y, atmos; kwargs...)
2121
2222
Wrapper for a [`JacobianAlgorithm`](@ref) and its cache, which it uses to update
23-
and invert the Jacobian. The optional `verbose` flag specifies whether debugging
24-
information should be printed during initialization.
23+
and invert the Jacobian. Optional keyword arguments can also be passed to
24+
some algorithms during construction.
2525
"""
2626
struct Jacobian{A <: JacobianAlgorithm, C}
2727
alg::A
2828
cache::C
2929
end
30-
function Jacobian(alg, Y, atmos; verbose = false)
30+
function Jacobian(alg, Y, atmos; kwargs...)
3131
krylov_cache = (; ΔY_krylov = similar(Y), R_krylov = similar(Y))
32-
cache = (; jacobian_cache(alg, Y, atmos; verbose)..., krylov_cache...)
32+
cache = (; jacobian_cache(alg, Y, atmos; kwargs...)..., krylov_cache...)
3333
return Jacobian(alg, cache)
3434
end
3535

36-
# Ignore the verbose flag in jacobian_cache when it is not needed.
37-
jacobian_cache(alg, Y, atmos; verbose) = jacobian_cache(alg, Y, atmos)
38-
3936
# ClimaTimeSteppers.jl calls zero(jac_prototype) to initialize the Jacobian, but
4037
# we don't need to allocate a second Jacobian for this (in particular, the exact
4138
# Jacobian can be very expensive to allocate).

src/prognostic_equations/implicit/manual_sparse_jacobian.jl

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,13 @@ struct ManualSparseJacobian{F1, F2, F3, F4, F5, F6} <: SparseJacobian
5858
approximate_solve_iters::Int
5959
end
6060

61-
function jacobian_cache(alg::ManualSparseJacobian, Y, atmos)
61+
function jacobian_cache(
62+
alg::ManualSparseJacobian,
63+
Y,
64+
atmos;
65+
auto_pad = false,
66+
kwargs...,
67+
)
6268
(;
6369
topography_flag,
6470
diffusion_flag,
@@ -74,6 +80,7 @@ function jacobian_cache(alg::ManualSparseJacobian, Y, atmos)
7480
BidiagonalRow_C3 = BidiagonalMatrixRow{C3{FT}}
7581
TridiagonalRow_ACTh = TridiagonalMatrixRow{Adjoint{FT, CTh{FT}}}
7682
BidiagonalRow_ACT3 = BidiagonalMatrixRow{Adjoint{FT, CT3{FT}}}
83+
QuaddiagonalRow_ACT3 = QuaddiagonalMatrixRow{Adjoint{FT, CT3{FT}}}
7784
BidiagonalRow_C3xACTh =
7885
BidiagonalMatrixRow{typeof(zero(C3{FT}) * zero(CTh{FT})')}
7986
DiagonalRow_C3xACT3 =
@@ -128,7 +135,8 @@ function jacobian_cache(alg::ManualSparseJacobian, Y, atmos)
128135
(@name(c.ρ), sfc_if_available...),
129136
)
130137

131-
active_scalar_names = (@name(c.ρ), @name(c.ρe_tot), ρq_tot_if_available...)
138+
active_scalar_names_except_ρe = (@name(c.ρ), ρq_tot_if_available...)
139+
active_scalar_names = (@name(c.ρe_tot), active_scalar_names_except_ρe...)
132140
advection_blocks = (
133141
(
134142
use_derivative(topography_flag) ?
@@ -141,8 +149,12 @@ function jacobian_cache(alg::ManualSparseJacobian, Y, atmos)
141149
)...,
142150
MatrixFields.unrolled_map(
143151
name -> (name, @name(f.u₃)) => similar(Y.c, BidiagonalRow_ACT3),
144-
active_scalar_names,
152+
active_scalar_names_except_ρe,
145153
)...,
154+
(@name(c.ρe_tot), @name(f.u₃)) => similar(
155+
Y.c,
156+
auto_pad ? QuaddiagonalRow_ACT3 : BidiagonalRow_ACT3,
157+
),
146158
MatrixFields.unrolled_map(
147159
name -> (@name(f.u₃), name) => similar(Y.f, BidiagonalRow_C3),
148160
active_scalar_names,

0 commit comments

Comments
 (0)