Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1399: Add operator_matrix to MatrixFields, along with tests and docs r=dennisYatunin a=dennisYatunin ## Purpose Second PR of #1230. Refactors what is currently in `src/operators/operator2stencil.jl`. ## Content - Adds the function `operator_matrix(op)`, which will replace `Operator2Stencil(op)`. This function has a detailed docstring and throws descriptive errors for operators without well-defined operator matrices. - Improves the usability of operator matrices. With this new interface, users will no longer need to specify intermediate fields to compute operator matrices. For example, with our pre-existing code, the matrix of the interpolation operator (a bidiagonal matrix whose entries are `-1/2` and `+1/2`) needs to be computed with ``@.` matrix_field = interp_matrix(ones_field)`, where `ones_field` is a field filled with the number `1` that is used to infer the space and entry type of the matrix. Now, this matrix can just be computed with ``@.` matrix_field = interp_matrix()`. In order to make this work, `interp_matrix` is now defined as a "lazy operator". When a broadcast expression containing lazy operators is evaluated, each lazy operator is replaced with an actual operator, and it is given one or more fields as input arguments. In this case, `interp_matrix` is given the local geometry field as an input argument, and this field is used to infer the space and entry type of the operator matrix. - This usability improvement slightly changes the computation of derivative matrices. With our pre-existing code, ``@.` op(func(field))` is equivalent to ``@.` op_matrix(ones_field) ⋅ func(field)`, and the derivative of this expression with respect to `field` can be specified as ``@.` op_matrix(func'(field))`, where `func'` is the derivative of the point-wise function `func`. With this new interface, ``@.` op(func(field))` is equivalent to ``@.` op_matrix() ⋅ func(field)`, and the derivative can be specified as ``@.` op_matrix() ⋅ DiagonalMatrixRow(func'(field))`. Similarly, the derivative of ``@.` op2(func2(op1(func1(field))))` with respect to `field` is `op2_matrix(func2'(op1(func1(field)))) ⋅ op1_matrix(func1'(field))` with our pre-existing code and `op2_matrix() ⋅ DiagonalMatrixRow(func2'(op1(func1(field)))) ⋅ op1_matrix() ⋅ DiagonalMatrixRow(func1'(field))` with the new interface. Although the new interface leads to longer derivative expressions, those expressions are more similar to how the chain rule is usually written out, and they can be debugged/analyzed more incrementally. - Adds support for computing operator matrices of multi-argument operators. For example, if `op` is the `Upwind3rdOrderBiasedProductC2F` operator, then ``@.` op(velocity_field, tracer_field)` is equivalent to ``@.` op_matrix(velocity_field) ⋅ tracer_field`. The implementation is similar to that of single-argument operators, except that it does not require the use of "lazy operators" (since there is already a field being passed to the operator matrix, the local geometry field can be obtained from that field during the evaluation of `Base.Broadcast.broadcasted`). - Adds support for computing operator matrices of operators with `Extrapolate` boundary conditions. These boundary conditions cause the matrices to have larger bandwidths than other boundary conditions. - Tests the `operator_matrix` function with every valid combination of finite difference operators and boundary conditions. The tests check for correctness, type stability, and lack of allocations. The tests are run on both CPUs and GPUs. In addition, the tests print out how the performance of ``@.` op_matrix() ⋅ field` compares to the performance of ``@.` op(field)`; the two expressions are similarly fast on GPUs (between `-70%` and `+40%` relative change in speed), though the operator matrix expressions tend to be slower on CPUs. - Tests a few more complicated broadcast expressions involving products and linear combinations of operator matrices. These tests indicate that operator matrices are similarly performant to regular matrix fields. - Modifies `test_field_broadcast` so that it also tests whether `get_result` generates the same result as `set_result!`. - Modifies the `*` method for `BandMatrixRow` so that matrix fields can be scaled by vectors/covectors in addition to numbers. This simplifies a few of the complicated broadcast tests. - Fixes a typo (`Geometery`) in the method of `stencil_left_boundary` for `GradientF2C`. - Adds a missing method to `rpromote_type` that was preventing empty matrix rows from being constructed. - Modifies the `Base.Broadcast.broadcasted` method for `FiniteDifferenceOperator` and `SpectralElementOperator` so that lazy operators can work correctly (the original versions of these methods would always overwrite the `LazyOperatorStyle` with `StencilStyle` or `SpectralStyle`, respectively). - Unfortunately, this PR also adds 2 method invalidations. These are due to a new definition of `broadcasted` for lazy operators: ``` Base.Broadcast.broadcasted(::LazyOperatorStyle, f::F, args...) where {F} = LazyOperatorBroadcasted(f, args) ``` As explained in an accompanying code comment, removing this method requires modifying several other method definitions, and one of these modifications adds 11 invalidations. So, there doesn't seem to be a good way to avoid these 2 invalidations. Co-authored-by: Dennis Yatunin <[email protected]>
- Loading branch information