Skip to content

Commit f224d4d

Browse files
committed
Add AutoSparseJacobian algorithm for implicit solver
1 parent 5557f30 commit f224d4d

File tree

15 files changed

+500
-69
lines changed

15 files changed

+500
-69
lines changed

.buildkite/Manifest-v1.11.toml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ version = "0.5.18"
363363
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
364364

365365
[[deps.ClimaAtmos]]
366-
deps = ["Adapt", "ArgParse", "Artifacts", "AtmosphericProfilesLibrary", "ClimaComms", "ClimaCore", "ClimaDiagnostics", "ClimaInterpolations", "ClimaParams", "ClimaTimeSteppers", "ClimaUtilities", "CloudMicrophysics", "Dates", "ForwardDiff", "Insolation", "Interpolations", "LazyArtifacts", "LazyBroadcast", "LinearAlgebra", "Logging", "NCDatasets", "NVTX", "NullBroadcasts", "RRTMGP", "Random", "SciMLBase", "StaticArrays", "Statistics", "SurfaceFluxes", "Thermodynamics", "UnrolledUtilities", "YAML"]
366+
deps = ["Adapt", "ArgParse", "Artifacts", "AtmosphericProfilesLibrary", "ClimaComms", "ClimaCore", "ClimaDiagnostics", "ClimaInterpolations", "ClimaParams", "ClimaTimeSteppers", "ClimaUtilities", "CloudMicrophysics", "Dates", "ForwardDiff", "Insolation", "Interpolations", "LazyArtifacts", "LazyBroadcast", "LinearAlgebra", "Logging", "NCDatasets", "NVTX", "NullBroadcasts", "RRTMGP", "Random", "SciMLBase", "SparseMatrixColorings", "StaticArrays", "Statistics", "SurfaceFluxes", "Thermodynamics", "UnrolledUtilities", "YAML"]
367367
path = ".."
368368
uuid = "b2c96348-7fb7-4fe0-8da9-78d88439e717"
369369
version = "0.31.0"
@@ -2318,6 +2318,20 @@ deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"]
23182318
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
23192319
version = "1.11.0"
23202320

2321+
[[deps.SparseMatrixColorings]]
2322+
deps = ["ADTypes", "DocStringExtensions", "LinearAlgebra", "PrecompileTools", "Random", "SparseArrays"]
2323+
git-tree-sha1 = "ab958b4fec46d1f1d057bb8e2a99bfdb90744646"
2324+
uuid = "0a514795-09f3-496d-8182-132a7b665d35"
2325+
version = "0.4.20"
2326+
2327+
[deps.SparseMatrixColorings.extensions]
2328+
SparseMatrixColoringsCliqueTreesExt = "CliqueTrees"
2329+
SparseMatrixColoringsColorsExt = "Colors"
2330+
2331+
[deps.SparseMatrixColorings.weakdeps]
2332+
CliqueTrees = "60701a23-6482-424a-84db-faee86b9b1f8"
2333+
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
2334+
23212335
[[deps.SpecialFunctions]]
23222336
deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
23232337
git-tree-sha1 = "41852b8679f78c8d8961eeadc8f62cef861a52e3"

.buildkite/ci_driver.jl

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,71 @@ include(joinpath(pkgdir(CA), "post_processing", "ci_plots.jl"))
4545
ref_job_id = config.parsed_args["reference_job_id"]
4646
reference_job_id = isnothing(ref_job_id) ? simulation.job_id : ref_job_id
4747

48+
# TODO: Add support for manual Jacobian debugging with the AutoDenseJacobian.
49+
integrator_has_auto_sparse_jacobian =
50+
config.parsed_args["use_auto_jacobian"] &&
51+
!config.parsed_args["use_dense_jacobian"]
52+
debug_manual_jacobian =
53+
isnothing(config.parsed_args["debug_manual_jacobian"]) ?
54+
config.parsed_args["use_auto_jacobian"] :
55+
config.parsed_args["debug_manual_jacobian"]
56+
if integrator_has_auto_sparse_jacobian && debug_manual_jacobian
57+
Y_end = integrator.u
58+
t_end = integrator.t
59+
dt = integrator.dt
60+
timestepper_algorithm = integrator.alg
61+
tableau_coefficients =
62+
timestepper_algorithm isa CA.CTS.RosenbrockAlgorithm ?
63+
timestepper_algorithm.tableau.Γ : timestepper_algorithm.tableau.a_imp
64+
65+
FT = eltype(Y_end)
66+
γs = filter(!iszero, CA.LinearAlgebra.diag(tableau_coefficients))
67+
dtγ = FT(float(dt) * γs[end])
68+
69+
auto_jacobian =
70+
timestepper_algorithm isa CA.CTS.RosenbrockAlgorithm ?
71+
integrator.cache.W : integrator.cache.newtons_method_cache.j
72+
manual_jacobian = CA.Jacobian(auto_jacobian.alg.sparse_alg, Y_end, atmos)
73+
CA.update_jacobian!(auto_jacobian, Y_end, p, dtγ, t_end)
74+
CA.update_jacobian!(manual_jacobian, Y_end, p, dtγ, t_end)
75+
auto_matrix = auto_jacobian.cache.matrix.matrix
76+
manual_matrix = manual_jacobian.cache.matrix.matrix
77+
auto_scalar_matrix = CA.MatrixFields.scalar_field_matrix(auto_matrix)
78+
manual_scalar_matrix = CA.MatrixFields.scalar_field_matrix(manual_matrix)
79+
diff_scalar_matrix = auto_scalar_matrix .- manual_scalar_matrix
80+
81+
@info "Debugging manual Jacobian"
82+
for scalar_block_name in keys(auto_scalar_matrix)
83+
auto_block = auto_scalar_matrix[scalar_block_name]
84+
manual_block = manual_scalar_matrix[scalar_block_name]
85+
diff_block = diff_scalar_matrix[scalar_block_name]
86+
87+
auto_block isa CA.Fields.Field || continue
88+
89+
println("$scalar_block_name:")
90+
println("\t$(eltype(auto_block))")
91+
(_, _, lower_band, upper_band) =
92+
CA.MatrixFields.band_matrix_info(auto_block)
93+
for band in lower_band:upper_band
94+
band_index = band - lower_band + 1
95+
auto_band_average = mean(abs, auto_block.entries.:($band_index))
96+
manual_band_average = mean(abs, manual_block.entries.:($band_index))
97+
diff_band_average = mean(abs, diff_block.entries.:($band_index))
98+
averages =
99+
(auto_band_average, manual_band_average, diff_band_average)
100+
percent_error =
101+
diff_band_average == auto_band_average == 0 ? FT(0) :
102+
diff_band_average / auto_band_average * 100
103+
rounded_averages = map(x -> round(x; sigdigits = 2), averages)
104+
rounded_percent_error = round(percent_error; sigdigits = 2)
105+
println(
106+
"\tBand $band averages (auto/manual/difference) and error: \
107+
$(join(rounded_averages, '/')), $rounded_percent_error%",
108+
)
109+
end
110+
end
111+
end
112+
48113
if sol_res.ret_code == :simulation_crashed
49114
error(
50115
"The ClimaAtmos simulation has crashed. See the stack trace for details.",

.buildkite/gpu_pipeline/pipeline.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ steps:
2424
- julia --project=.buildkite -e 'using Pkg; Pkg.precompile()'
2525
- julia --project=.buildkite -e 'using CUDA; CUDA.precompile_runtime()'
2626
- julia --project=.buildkite -e 'using Pkg; Pkg.status()'
27+
- julia --project=.buildkite -e 'using Pkg; Pkg.add(Pkg.PackageSpec(;name="ClimaCore", rev="main"))'
2728

2829
agents:
2930
slurm_gpus: 1

.buildkite/longruns_gpu/pipeline.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ steps:
2525
- julia --project=.buildkite -e 'using Pkg; Pkg.precompile()'
2626
- julia --project=.buildkite -e 'using CUDA; CUDA.precompile_runtime()'
2727
- julia --project=.buildkite -e 'using Pkg; Pkg.status()'
28+
- julia --project=.buildkite -e 'using Pkg; Pkg.add(Pkg.PackageSpec(;name="ClimaCore", rev="main"))'
2829

2930
agents:
3031
slurm_gpus: 1

.buildkite/pipeline.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ steps:
2828

2929
- echo "--- Instantiate .buildkite"
3030
- "julia --project=.buildkite -e 'using Pkg; Pkg.instantiate(;verbose=true); Pkg.precompile(;strict=true); using CUDA; CUDA.precompile_runtime(); Pkg.status()'"
31+
- "julia --project=.buildkite -e 'using Pkg; Pkg.add(Pkg.PackageSpec(;name=\"ClimaCore\", rev=\"main\"))'"
3132

3233
agents:
3334
slurm_cpus_per_task: 8
@@ -631,7 +632,7 @@ steps:
631632
--job_id amip_target_edonly_nonequil
632633
artifact_paths: "amip_target_edonly_nonequil/output_active/*"
633634
agents:
634-
slurm_mem: 20GB
635+
slurm_mem: 64GB
635636

636637
- group: "Diagnostic EDMFX"
637638
steps:

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ NullBroadcasts = "0d71be07-595a-4f89-9529-4065a4ab43a6"
3030
RRTMGP = "a01a1ee8-cea4-48fc-987c-fc7878d79da1"
3131
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
3232
SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
33+
SparseMatrixColorings = "0a514795-09f3-496d-8182-132a7b665d35"
3334
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
3435
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
3536
SurfaceFluxes = "49b00bb7-8bd4-4f2b-b78c-51cd0450215f"
@@ -64,6 +65,7 @@ NullBroadcasts = "0.1"
6465
RRTMGP = "0.21.3"
6566
Random = "1"
6667
SciMLBase = "2.12"
68+
SparseMatrixColorings = "0.4.20"
6769
StaticArrays = "1.7"
6870
Statistics = "1"
6971
SurfaceFluxes = "0.11, 0.12"

config/default_configs/default_config.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ jvp_step_adjustment:
8989
use_dense_jacobian:
9090
help: "Whether to use a dense Jacobian matrix that is computed using forward-mode automatic differentiation and inverted using LU factorization [`true`, `false` (default)]"
9191
value: false
92+
use_auto_jacobian:
93+
help: "Whether to populate the entries of the sparse Jacobian matrix using forward-mode automatic differentiation with sparse matrix coloring (only used when `use_dense_jacobian` is `false`) [`true`, `false` (default)]"
94+
value: true # TODO: Change this to false
95+
auto_jacobian_padding_bands:
96+
help: "Minimum number of bands to add in every block of the sparse Jacobian matrix, eliminating errors from Jacobian entries that lie outside of the default sparsity pattern (only used when `use_auto_jacobian` is `true`; default is `0`)"
97+
value: 2 # TODO: Change this to 0
98+
debug_manual_jacobian:
99+
help: "Whether to compare the bands of the Jacobian matrix that is computed manually to those of the Jacobian matrix generated with automatic differentiation (default is equal to `use_auto_jacobian`) [`true`, `false`]"
100+
value: ~
92101
update_jacobian_every:
93102
help: "Frequency at which the Jacobian matrix should be updated (once per timestep, once per timestepper stage, or once per linear solve) [`dt`, `stage`, `solve` (default)]"
94103
value: "solve"

reproducibility_tests/ref_counter.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
248
1+
249
22

33
# **README**
44
#

src/ClimaAtmos.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ include(
6262
joinpath("prognostic_equations", "implicit", "manual_sparse_jacobian.jl"),
6363
)
6464
include(joinpath("prognostic_equations", "implicit", "auto_dense_jacobian.jl"))
65+
include(joinpath("prognostic_equations", "implicit", "auto_sparse_jacobian.jl"))
6566
include(joinpath("prognostic_equations", "implicit", "autodiff_utils.jl"))
6667

6768
include(joinpath("prognostic_equations", "water_advection.jl"))

src/prognostic_equations/implicit/auto_dense_jacobian.jl

Lines changed: 28 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -89,45 +89,36 @@ function update_column_matrices!(alg::AutoDenseJacobian, cache, Y, p, dtγ, t)
8989
device = ClimaComms.device(Y.c)
9090
column_indices = column_index_iterator(Y)
9191
scalar_names = scalar_field_names(Y)
92-
scalar_level_indices = scalar_level_index_pairs(Y)
93-
batch_size = max_simultaneous_derivatives(alg)
94-
batch_size_val = Val(batch_size)
95-
96-
p_dual_args = ntuple(Val(fieldcount(typeof(p)))) do cache_field_index
97-
cache_field_name = fieldname(typeof(p), cache_field_index)
98-
if cache_field_name == :precomputed
99-
(; p.precomputed..., precomputed_dual...)
100-
elseif cache_field_name == :scratch
101-
scratch_dual
102-
else
103-
getfield(p, cache_field_index)
104-
end
105-
end
106-
p_dual = AtmosCache(p_dual_args...)
107-
108-
batches = Iterators.partition(scalar_level_indices, batch_size)
109-
for batch_scalar_level_indices in ClimaComms.threadable(device, batches)
92+
jacobian_axis_index_to_field_vector_index_map =
93+
enumerate(field_vector_index_iterator(Y))
94+
n_εs = max_simultaneous_derivatives(alg)
95+
n_εs_val = Val(n_εs)
96+
p_dual = replace_precomputed_and_scratch(p, precomputed_dual, scratch_dual)
97+
98+
batches =
99+
Iterators.partition(jacobian_axis_index_to_field_vector_index_map, n_εs)
100+
for indices_for_Y_axis in ClimaComms.threadable(device, batches)
110101
Y_dual .= Y
111102

112103
# Add a unique ε to Y for each scalar level index in this batch. With
113104
# Y_col and Yᴰ_col denoting the columns of Y and Y_dual at column_index,
114-
# set Yᴰ_col to Y_col + I[:, batch_scalar_level_indices] * εs, where I
115-
# is the identity matrix for Y_col (i.e., the value of ∂Y_col/∂Y_col),
116-
# εs is a vector of batch_size dual number components, and
117-
# batch_scalar_level_indices are the batch's indices into Y_col.
105+
# set Yᴰ_col to Y_col + I[:, indices_for_Y_axis] * εs, where I is the
106+
# identity matrix for Y_col (i.e., the value of ∂Y_col/∂Y_col), εs is a
107+
# vector of n_εs dual number components, and indices_for_Y_axis are the
108+
# batch's indices into Y_col.
118109
ClimaComms.@threaded device begin
119110
# On multithreaded devices, assign one thread to each combination of
120111
# spatial column index and scalar level index in this batch.
121112
for column_index in column_indices,
122113
(ε_index, (_, (scalar_index, level_index))) in
123-
enumerate(batch_scalar_level_indices)
114+
enumerate(indices_for_Y_axis)
124115

125-
Y_partials = ntuple(i -> i == ε_index ? 1 : 0, batch_size_val)
126-
Y_dual_increment = ForwardDiff.Dual{Jacobian}(0, Y_partials...)
116+
Y_partials = ntuple(==(ε_index), n_εs_val)
117+
Y_dual_εs_value = ForwardDiff.Dual{Jacobian}(0, Y_partials)
127118
unrolled_applyat(scalar_index, scalar_names) do name
128119
field = MatrixFields.get_field(Y_dual, name)
129120
@inbounds point(field, level_index, column_index...)[] +=
130-
Y_dual_increment
121+
Y_dual_εs_value
131122
end
132123
end
133124
end
@@ -141,19 +132,19 @@ function update_column_matrices!(alg::AutoDenseJacobian, cache, Y, p, dtγ, t)
141132
# with col_matrix denoting the matrix at the corresponding matrix_index
142133
# in column_matrices, copy the coefficients of the εs in Yₜᴰ_col into
143134
# col_matrix, where the previous steps have set Yₜᴰ_col to
144-
# Yₜ_col + (∂Yₜ_col/∂Y_col)[:, batch_scalar_level_indices] * εs.
145-
# Specifically, set col_matrix[scalar_level_index1, scalar_level_index2]
146-
# to ∂Yₜ_col[scalar_level_index1]/∂Y_col[scalar_level_index2], obtaining
135+
# Yₜ_col + (∂Yₜ_col/∂Y_col)[:, indices_for_Y_axis] * εs. Specifically, set
136+
# col_matrix[scalar_level_index1, scalar_level_index2] to
137+
# ∂Yₜ_col[scalar_level_index1]/∂Y_col[scalar_level_index2], obtaining
147138
# this derivative from the coefficient of εs[ε_index] in
148139
# Yₜᴰ_col[scalar_level_index1], where ε_index is the index of
149-
# scalar_level_index2 in batch_scalar_level_indices. After all batches
150-
# have been processed, col_matrix is the full Jacobian ∂Yₜ_col/∂Y_col.
140+
# scalar_level_index2 in indices_for_Y_axis. After all batches have been
141+
# processed, col_matrix is the full Jacobian ∂Yₜ_col/∂Y_col.
151142
ClimaComms.@threaded device begin
152143
# On multithreaded devices, assign one thread to each combination of
153144
# spatial column index and scalar level index.
154145
for (matrix_index, column_index) in enumerate(column_indices),
155146
(scalar_level_index1, (scalar_index1, level_index1)) in
156-
scalar_level_indices
147+
jacobian_axis_index_to_field_vector_index_map
157148

158149
Yₜ_dual_value =
159150
unrolled_applyat(scalar_index1, scalar_names) do name
@@ -162,7 +153,7 @@ function update_column_matrices!(alg::AutoDenseJacobian, cache, Y, p, dtγ, t)
162153
end
163154
Yₜ_partials = ForwardDiff.partials(Yₜ_dual_value)
164155
for (ε_index, (scalar_level_index2, _)) in
165-
enumerate(batch_scalar_level_indices)
156+
enumerate(indices_for_Y_axis)
166157
cartesian_index =
167158
(scalar_level_index1, scalar_level_index2, matrix_index)
168159
@inbounds column_matrices[cartesian_index...] =
@@ -193,14 +184,15 @@ function invert_jacobian!(::AutoDenseJacobian, cache, ΔY, R)
193184
device = ClimaComms.device(ΔY.c)
194185
column_indices = column_index_iterator(ΔY)
195186
scalar_names = scalar_field_names(ΔY)
196-
scalar_level_indices = scalar_level_index_pairs(ΔY)
187+
vector_index_to_field_vector_index_map =
188+
enumerate(field_vector_index_iterator(ΔY))
197189

198190
# Copy all scalar values from R into column_lu_vectors.
199191
ClimaComms.@threaded device begin
200192
# On multithreaded devices, assign one thread to each index into R.
201193
for (vector_index, column_index) in enumerate(column_indices),
202194
(scalar_level_index, (scalar_index, level_index)) in
203-
scalar_level_indices
195+
vector_index_to_field_vector_index_map
204196

205197
value = unrolled_applyat(scalar_index, scalar_names) do name
206198
field = MatrixFields.get_field(R, name)
@@ -219,7 +211,7 @@ function invert_jacobian!(::AutoDenseJacobian, cache, ΔY, R)
219211
# On multithreaded devices, assign one thread to each index into ΔY.
220212
for (vector_index, column_index) in enumerate(column_indices),
221213
(scalar_level_index, (scalar_index, level_index)) in
222-
scalar_level_indices
214+
vector_index_to_field_vector_index_map
223215

224216
@inbounds value =
225217
column_lu_vectors[scalar_level_index, vector_index]

0 commit comments

Comments
 (0)