@@ -191,9 +191,6 @@ the following situations:
191
191
192
192
1 . The internal key indexes to a type different than the basetype of the entry
193
193
2 . The internal key indexes to a zero-ed value
194
- 3 . The internal key slices an ` AxisTensor `
195
-
196
- ### Implicit Tensor Structure Optimization
197
194
198
195
``` @setup 2
199
196
using ClimaCore.CommonSpaces
@@ -208,124 +205,147 @@ space = ColumnSpace(FT ;
208
205
z_max = 10,
209
206
staggering = CellCenter()
210
207
)
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)
211
216
```
212
217
213
- If using a ` FieldMatrix ` to represent a jacobian, entries with certain structures
214
- can be stored in an optimized manner.
218
+ ## Optimizations
215
219
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 ` .
221
222
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 ` .
227
229
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.
232
232
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.
234
235
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}$
237
238
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)
245
248
```
246
249
247
- Individual components can be indexed into:
250
+ can also be represented by
248
251
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)
252
254
```
253
255
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
256
260
```
257
261
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
259
263
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 ` .
262
267
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
266
271
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.
270
273
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}
273
291
```
274
292
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 .
276
294
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)
280
298
```
281
299
300
+ ` ∂f_∂g ` can be indexed into to get the partial derrivatives of individual components.
301
+
302
+
282
303
``` @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)))]
285
305
```
286
306
287
307
``` @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 )))]
289
309
```
290
310
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:
300
312
301
313
``` @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
303
316
```
304
317
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)
308
321
```
309
322
310
323
``` @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)))]
312
325
```
313
326
314
- #### bandwidth > 1 case
327
+ ``` @example 2
328
+ Base.Broadcast.materialize(J[(@name(f.components.data.:(2)), @name(g.components.data.:(1)))])
329
+ ```
315
330
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
319
332
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}
322
338
```
323
339
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)
327
345
```
328
346
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)
331
351
```
0 commit comments