Skip to content

Commit c8572ee

Browse files
committed
finish docs
1 parent d1b1f77 commit c8572ee

File tree

3 files changed

+136
-89
lines changed

3 files changed

+136
-89
lines changed

docs/src/matrix_fields.md

Lines changed: 98 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,6 @@ the following situations:
191191

192192
1. The internal key indexes to a type different than the basetype of the entry
193193
2. The internal key indexes to a zero-ed value
194-
3. The internal key slices an `AxisTensor`
195-
196-
### Implicit Tensor Structure Optimization
197194

198195
```@setup 2
199196
using ClimaCore.CommonSpaces
@@ -208,124 +205,147 @@ space = ColumnSpace(FT ;
208205
z_max = 10,
209206
staggering = CellCenter()
210207
)
208+
f = map(x -> rand(Geometry.Covariant12Vector{Float64}), Fields.local_geometry_field(space))
209+
g = map(x -> rand(Geometry.Covariant12Vector{Float64}), Fields.local_geometry_field(space))
210+
identity_axis2tensor = Geometry.Covariant12Vector(FT(1), FT(0)) *
211+
Geometry.Contravariant12Vector(FT(1), FT(0))' +
212+
Geometry.Covariant12Vector(FT(0), FT(1)) *
213+
Geometry.Contravariant12Vector(FT(0), FT(1))'
214+
∂f_∂g = fill(MatrixFields.TridiagonalMatrixRow(-0.5 * identity_axis2tensor, identity_axis2tensor, -0.5 * identity_axis2tensor), space)
215+
J = MatrixFields.FieldMatrix((@name(f), @name(g))=> ∂f_∂g)
211216
```
212217

213-
If using a `FieldMatrix` to represent a jacobian, entries with certain structures
214-
can be stored in an optimized manner.
218+
## Optimizations
215219

216-
The optimization assumes that if indexing into an entry of scalars, the user intends the
217-
entry to have an implicit tensor structure, with the scalar values representing a scaling of the
218-
tensor identity. If both the first and second name in the name pair are equivalent, then they index onto the diagonal,
219-
and the scalar value is returned. Otherwise, they index off the diagonal, and a zero value
220-
is returned.
220+
Each entry of a `FieldMatrix` can be a `ColumnwiseBandMatrixField`, a `DiagonalMatrixRow`, or an
221+
`UniformScaling`.
221222

222-
The following sections refer the `Field`s
223-
$f$ and $g$, which both have values of type `Covariant12Vector` and are defined on a column domain, which is discretized with $N_v$ layers.
224-
The notation $f_{n}[i]$ where $ 0 < n \leq N_v$ and $i \in (1,2)$ refers to the $i$ component of the element of $f$
225-
at the $i$ vertical level. $g$ is indexed similarly. Although $f$ and $g$ have values of type
226-
`Covariant12Vector`, this optimization works for any two `Field`s of `AxisVector`s
223+
A `ColumnwiseBandMatrixField` is a `Field` with a `BandMatrixRow` at each point. It is intended
224+
to represent a collection of banded matrices, where there is one band matrix for each column
225+
of the space the `Field` is on. Beyond only storing the diagonals of the band matrix, an `entry`
226+
can be optimized to use less memory. Each optimized representation can be indexed equivalently to
227+
non optimized representation, and used in addition, subtraction, Matrix-vector multiplication,
228+
Matrix-matrix multiplication, `RecursiveApply`, and `FieldMatrixSolver`.
227229

228-
```@example 2
229-
f = map(x -> rand(Geometry.Covariant12Vector{Float64}), Fields.local_geometry_field(space))
230-
g = map(x -> rand(Geometry.Covariant12Vector{Float64}), Fields.local_geometry_field(space))
231-
```
230+
For the following sections, `space` is a column space with $N_v$ levels. A column space is
231+
used for simplicity in this example, but the optimizations work with any space with columns.
232232

233-
#### Uniform Scaling Case
233+
$f$ and $g$ are both `Fields` on `space` with elements of type with elements of type
234+
`T_f` and `T_g`. $f_i$ and $g_i$ refers to the values of $f$ and $g$ at the $ 0 < i \leq N_v$ level.
234235

235-
If $\frac{\partial f_n[i]}{\partial g_n[j]} = [i = j]$ for some scalar $k$, then the
236-
non-optimized entry would be represented by a diagonal matrix with values of an identity 2d tensor. If $k=2$, then
236+
$M$ is a $N_v \times N_v$ banded matrix with lower and upper bandwidth of $b_1$ and $b_2$.
237+
$M$ represents $\frac{\partial f}{\partial g}$, so $M_{i,j} = \frac{\partial f_i}{\partial g_j}$
237238

238-
```@example 2
239-
identity_axis2tensor = Geometry.Covariant12Vector(FT(1), FT(0)) * # hide
240-
Geometry.Contravariant12Vector(FT(1), FT(0))' + # hide
241-
Geometry.Covariant12Vector(FT(0), FT(1)) * # hide
242-
Geometry.Contravariant12Vector(FT(0), FT(1))' # hide
243-
k = 2
244-
∂f_∂g = fill(MatrixFields.DiagonalMatrixRow(k * identity_axis2tensor), space)
239+
### `ScalingFieldMatrixEntry` Optimization
240+
241+
Consider the case where $b_1 = 0$ and $b_2 = 0$, i.e $M$ is a diagonal matrix, and
242+
where $M = k * I$, and $k$ is of type `T_k`. This would happen if
243+
$\frac{\partial f_i}{\partial g_j} = [i=j] * k$. Instead of storing
244+
each element on the diagonal, less memory can be used by storing a single value that represents a scaling of the identity. This reduces memory usage by a factor of $N_v$.
245+
246+
```julia
247+
entry = fill(DiagonalMatrixRow(k), space)
245248
```
246249

247-
Individual components can be indexed into:
250+
can also be represented by
248251

249-
```@example 2
250-
J = MatrixFields.FieldMatrix((@name(f), @name(g))=> ∂f_∂g)
251-
J[[(@name(f.components.data.:(1)), @name(g.components.data.:(1)))]]
252+
```julia
253+
entry = DiagonalMatrixRow(k)
252254
```
253255

254-
```@example 2
255-
J[[(@name(f.components.data.:(2)), @name(g.components.data.:(1)))]]
256+
or, if `T_k` is a scalar, then
257+
258+
```julia
259+
entry = I * k
256260
```
257261

258-
The above example indexes into $\frac{\partial f_n[1]}{\partial g_n[1]}$ where $ 0 < n \leq N_v$
262+
### Implicit Tensor Structure Optimization
259263

260-
The entry can
261-
also be represeted with a single `DiagonalMatrixRow`, as follows:
264+
The functions that index an entry with an internal key assume the implicit tensor structure optimization is being used
265+
when all of the following are true for `entry` where `T_k` is the element type of each band, and
266+
`(internal_key_1, internal_key_2)` is the internal key indexing `entry`.
262267

263-
```@example 2
264-
∂f_∂g_optimized = MatrixFields.DiagonalMatrixRow(k * identity_axis2tensor)
265-
```
268+
- the first key in the `internal_key_1` name chain is not a fieldname of `T_k`
269+
- the first key in the `internal_key_2` name chain is not a fieldname of `T_k`
270+
- `internal_key_1` and `internal_key_2` are both not empty
266271

267-
`∂f_∂g_optimized` is a single `DiagonalMatrixRow`, which represents a diagonal matrix with the
268-
same tensor along the diagonal. In this case, that tensor is $k$ multiplied by the identity matrix, and that can be
269-
represented with `k * I` as follows
272+
For most use cases, `T_k` is a scalar.
270273

271-
```@example 2
272-
∂f_∂g_more_optimized = MatrixFields.DiagonalMatrixRow(k * identity_axis2tensor)
274+
If the above conditions are met, the optimization assumes that the user intends the
275+
entry to have an implicit tensor structure, with the values of type `T_k` representing a scaling of the
276+
tensor identity. If both the first and second name in the name pair are equivalent, then they index onto the diagonal,
277+
and the value is returned. Otherwise, they index off the diagonal, and a zero value
278+
is returned.
279+
280+
This optimization is intended to be used when `T_f = T_g`, and they are both `AxisVectors`
281+
The notation $f_{n}[i]$ where $ 0 < n \leq N_v$ refers to the $i$ component of the element
282+
at the $n$ vertical level of $f$. In the following example, `T_f=T_g=Covariant12Vector`, and
283+
$b_1 = b_2 = 1$, and
284+
285+
```math
286+
\frac{\partial f_n[i]}{\partial g_m[j]} = \begin{cases}
287+
-0.5, & \text{if } i = j \text{ and } m = n-1 \text{ or } m = n+1 \\
288+
1, & \text{if } i = j \text{ and } m = n \\
289+
0, & \text{if } i \neq j \text{ or } m < n -1 \text{ or } m > n +1
290+
\end{cases}
273291
```
274292

275-
Individual components of `∂f_∂g_optimized` can be indexed in the same way as `∂f_∂g`.
293+
The non-zero values of each row of `M` are equivalent in this example, but they can also vary in value.
276294

277-
```@example 2
278-
J_unoptimized = MatrixFields.FieldMatrix((@name(f), @name(g)) => ∂f_∂g)
279-
J_unoptimized[(@name(f.components.data.:(1)), @name(g.components.data.:(1)))]
295+
```julia
296+
∂f_∂g = fill(MatrixFields.TridiagonalMatrixRow(-0.5 * identity_axis2tensor, identity_axis2tensor, -0.5 * identity_axis2tensor), space)
297+
J = MatrixFields.FieldMatrix((@name(f), @name(g))=> ∂f_∂g)
280298
```
281299

300+
`∂f_∂g` can be indexed into to get the partial derrivatives of individual components.
301+
302+
282303
```@example 2
283-
J_more_optimized = MatrixFields.FieldMatrix((@name(f), @name(g)) => ∂f_∂g_optimized)
284-
J_more_optimized[(@name(f.components.data.:(1)), @name(g.components.data.:(1)))]
304+
J[(@name(f.components.data.:(1)), @name(g.components.data.:(1)))]
285305
```
286306

287307
```@example 2
288-
J_more_optimized[(@name(f.components.data.:(1)), @name(g.components.data.:(2)))]
308+
J[(@name(f.components.data.:(2)), @name(g.components.data.:(1)))]
289309
```
290310

291-
`∂f_∂g` stores $2 * 2 * N_v$ floats in memory, `∂f_∂g_optimized` stores `$2*2$ floats, and
292-
`∂f_∂g_more_optimized` stores only one float.
293-
294-
#### Vertically Varying Case
295-
296-
The implicit tensor optimization can also be used when
297-
$\frac{\partial f_n[i]}{\partial g_n[j]} = [i = j] * h(f_n, g_n)$.
298-
299-
In this case, a full `ColumnWiseBandMatrixField` must be used.
311+
This can be more optimally with the implicit tensor structure:
300312

301313
```@example 2
302-
∂f_∂g_optimized = map(x -> MatrixFields.DiagonalMatrixRow(rand(Float64)), ∂f_∂g)
314+
∂f_∂g = fill(MatrixFields.TridiagonalMatrixRow(-0.5, 1.0, -0.5), space) # hide
315+
J = MatrixFields.FieldMatrix((@name(f), @name(g))=> ∂f_∂g) # hide
303316
```
304317

305-
```@example 2
306-
J_optimized = MatrixFields.FieldMatrix((@name(f), @name(g)) => ∂f_∂g_optimized)
307-
J_optimized[(@name(f.components.data.:(1)), @name(g.components.data.:(1)))]
318+
```julia
319+
∂f_∂g = fill(MatrixFields.TridiagonalMatrixRow(-0.5, 1.0, -0.5), space)
320+
J = MatrixFields.FieldMatrix((@name(f), @name(g))=> ∂f_∂g)
308321
```
309322

310323
```@example 2
311-
Base.Broadcast.materialize(J_optimized[(@name(f.components.data.:(2)), @name(g.components.data.:(1)))])
324+
J[(@name(f.components.data.:(1)), @name(g.components.data.:(1)))]
312325
```
313326

314-
#### bandwidth > 1 case
327+
```@example 2
328+
Base.Broadcast.materialize(J[(@name(f.components.data.:(2)), @name(g.components.data.:(1)))])
329+
```
315330

316-
The implicit tensor optimization can also be used when
317-
$\frac{\partial f_n[i]}{\partial g[j]} = [i = j] * h(f_n, g_{n-k_1}, ..., g_{n+k_2})$ where
318-
$b_1$ and $b_2$ are the lower and upper bandwidth. Say $b_1 = b_2 = 1$. Then
331+
If it is the case that
319332

320-
```@example 2
321-
∂f_∂g_optimized = map(x -> MatrixFields.TridiagonalMatrixRow(rand(Float64), rand(Float64), rand(Float64)), ∂f_∂g)
333+
```math
334+
\frac{\partial f_n[i]}{\partial g_m[j]} = \begin{cases}
335+
k, & \text{if } i = j \text{ and } m = n \\
336+
0, & \text{if } i \neq j \text{ or } m \neq n
337+
\end{cases}
322338
```
323339

324-
```@example 2
325-
J_optimized = MatrixFields.FieldMatrix((@name(f), @name(g)) => ∂f_∂g_optimized)
326-
J_optimized[(@name(f.components.data.:(1)), @name(g.components.data.:(1)))]
340+
where $k$ is a constant scalar, both the implicit tensor structure optimization and
341+
`ScalingFieldMatrixEntry` optimization can be applied:
342+
343+
```julia
344+
∂f_∂g = fill(MatrixFields.DiagonalMatrixRow(k), space)
327345
```
328346

329-
```@example 2
330-
Base.Broadcast.materialize(J_optimized[(@name(f.components.data.:(2)), @name(g.components.data.:(1)))])
347+
can equivalently be represented with
348+
349+
```julia
350+
∂f_∂g = MatrixFields.DiagonalMatrixRow(k)
331351
```

src/MatrixFields/field_name_dict.jl

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,22 @@ get_internal_key(child_name_pair::FieldNamePair, name_pair::FieldNamePair) = (
148148
extract_internal_name(child_name_pair[1], name_pair[1]),
149149
extract_internal_name(child_name_pair[2], name_pair[2]),
150150
)
151+
"""
152+
get_internal_entry(entry, name::FieldName)
151153
154+
Returns the field indexed to by `name` from `entry`
155+
"""
152156
get_internal_entry(entry, name::FieldName) = get_field(entry, name)
153157
# call get_internal_entry on scaling value, and rebuild entry container
158+
"""
159+
get_internal_entry(entry, name_pair::FieldNamePair)
160+
161+
Returns the field indexed to by `name_pair` from `entry`. Indexing behavior is described
162+
in the MatrixFields section of the documentation. If `entry` is a `ColumnwiseBandMatrixField`,
163+
and the field indexed to by `name_pair` is not a field of scalars, a broadcasted object
164+
is returned. This also happens when indexing off diagonal with the implicit tensor structure
165+
optimization (see MatrixFields documentation).
166+
"""
154167
get_internal_entry(entry::UniformScaling, name_pair::FieldNamePair) =
155168
UniformScaling(get_internal_entry(scaling_value(entry), name_pair))
156169
get_internal_entry(entry::DiagonalMatrixRow, name_pair::FieldNamePair) =
@@ -179,6 +192,24 @@ function get_internal_entry(
179192
(drop_first(internal_row_name), drop_first(internal_col_name)),
180193
full_key,
181194
)
195+
elseif T <: Geometry.Axis2Tensor && # slicing a 2d tensor
196+
is_child_name(name_pair[1], @name(components.data))
197+
internal_row_name =
198+
extract_internal_name(name_pair[1], @name(components.data))
199+
return get_internal_entry(
200+
entry[extract_first(internal_row_name), :],
201+
(drop_first(internal_row_name), name_pair[2]),
202+
full_key,
203+
)
204+
elseif T <: Geometry.Axis2Tensor && # slicing a 2d tensor
205+
is_child_name(name_pair[2], @name(components.data))
206+
internal_col_name =
207+
extract_internal_name(name_pair[2], @name(components.data))
208+
return get_internal_entry(
209+
entry[:, extract_first(internal_col_name)],
210+
(name_pair[1], drop_first(internal_col_name)),
211+
full_key,
212+
)
182213
elseif T <: Geometry.AdjointAxisVector # bypass parent for adjoint vectors
183214
return get_internal_entry(getfield(entry, :parent), name_pair, full_key)
184215
elseif name_pair[1] != @name() &&
@@ -335,17 +366,15 @@ function field_offset_and_type(
335366
# if S <: T, then its possible to construct a strided view in the indexing function
336367
return (0, S, S <: T ? Val(:view) : Val(:view_of_blocks))
337368
elseif S <: Geometry.Axis2Tensor &&
338-
all(n -> is_child_name(n, @name(components.data)), name_pair) # special case to calculate index
369+
any(n -> is_child_name(n, @name(components.data)), name_pair) # special case to calculate index
370+
all(n -> is_child_name(n, @name(components.data)), name_pair) ||
371+
return (0, S, Val{:broadcasted_fallback}())
339372
internal_row_name =
340373
extract_internal_name(name_pair[1], @name(components.data))
341374
internal_col_name =
342375
extract_internal_name(name_pair[2], @name(components.data))
343376
row_index = extract_first(internal_row_name)
344377
col_index = extract_first(internal_col_name)
345-
if ((row_index isa Number) && (col_index isa Colon)) ||
346-
((row_index isa Colon) && (col_index isa Number))
347-
return (0, S, Val{:broadcasted_fallback}()) # slice case, return trigger fallback
348-
end
349378
((row_index isa Number) && (col_index isa Number)) ||
350379
throw(KeyError(full_key))
351380
(n_rows, n_cols) = map(length, axes(S))

test/MatrixFields/field_matrix_indexing.jl

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -184,19 +184,17 @@ end
184184
A, _ = dycore_prognostic_EDMF_FieldMatrix(FT)
185185
A_scaling, _ = scaling_only_dycore_prognostic_EDMF_FieldMatrix(FT)
186186

187-
colon_field_name =
188-
append_internal_name(@name(c.uₕ.components.data), FieldName(Colon()))
189187
index_name_1 = @name(c.uₕ.components.data.:(1))
190188
index_name_2 = @name(c.uₕ.components.data.:(2))
191189

192190
@test eltype(
193-
eltype(Base.Broadcast.materialize(A[(colon_field_name, index_name_1)])),
191+
eltype(Base.Broadcast.materialize(A[(@name(c.uₕ), index_name_1)])),
194192
) <: Geometry.CovariantVector
195193
@test eltype(
196-
eltype(Base.Broadcast.materialize(A[(index_name_2, colon_field_name)])),
194+
eltype(Base.Broadcast.materialize(A[(index_name_2, @name(c.uₕ))])),
197195
) <: Geometry.ContravariantVector
198-
@test eltype(A_scaling[(colon_field_name, index_name_1)]) <:
196+
@test eltype(A_scaling[(@name(c.uₕ), index_name_1)]) <:
199197
Geometry.CovariantVector
200-
@test eltype(A_scaling[(index_name_2, colon_field_name)]) <:
198+
@test eltype(A_scaling[(index_name_2, @name(c.uₕ))]) <:
201199
Geometry.ContravariantVector
202200
end

0 commit comments

Comments
 (0)