Users and package developers can add fmesher
support to
their own classes. A minimal interface needs to define
fm_dof()
and fm_basis()
methods. Assuming the
class is called custom
, the methods should be named
fm_dof.custom()
and fm_basis.custom()
.
The fm_dof.custom(x)
method must take an
object x
of class custom
and return the number
of degrees of freedom of the function space.
The fm_basis.custom(x, loc, ..., full = FALSE)
method must take an object x
of class
custom
and return a sparseMatrix
or
Matrix
matrix with each column containing the basis
function evaluated at the locations determined by loc
.
The type of loc
may be any type (or types) that is
supported by the custom class.
The ...
part can include further named arguments
specific to the custom class. These must be optional arguments so that
fm_basis(x, loc)
works.
When full = TRUE
, a full fm_basis
object
must be returned, which is a list containing at least
the basis matrix as A
, and a logical vector,
ok
, indicating which loc
values were valid
evaluation points. The A
matrix must be all-zero
for invalid loc
.
With the above requirements fulfilled, the default
fm_evaluator()
and fm_evaluate()
methods can
be used to evaluate functions at any location, with the need fo the user
to define any further methods.
Special fm_evaluator.custom()
and
fm_evaluate.custom()
methods may be defined if needed,
e.g. to support semi-automated output reformatting.
# Custom class for harmonic functions up to order `n`
create_custom <- function(n) {
stopifnot(n >= 0)
structure(
list(n = n),
class = "custom"
)
}
fm_dof.custom <- function(x) {
# Return the number of degrees of freedom
1L + 2L * x[["n"]]
}
fm_basis.custom <- function(x, loc, ..., full = FALSE) {
# Return the evaluated basis functions
A <- Matrix::Matrix(0.0, NROW(loc), fm_dof(x))
ok <- !is.na(loc)
A[ok, 1L] <- 1.0
for (k in seq_len(x[["n"]])) {
A[ok, 2 * k] <- cos(2 * pi * k * loc[ok])
A[ok, 2 * k + 1L] <- sin(2 * pi * k * loc[ok])
}
result <- structure(
list(
A = A,
ok = ok, # Required prior to version 0.2.0.9003
loc = loc
),
class = "fm_basis"
)
# Use the fm_basis method to extract the A matrix if full is FALSE:
fm_basis(result, full = full)
}
Note: From version 0.2.0.9004
, the
fm_basis.matrix
, fm_basis.Matrix
, and
fm_basis.list
methods provide an easier way to construct
the fm_basis
object, by creating the object and optionally
extracting A
in a single call:
# 'matrix' and 'Matrix' methods:
fm_basis(
A = A,
ok = ok, # If missing or NULL, inferred to be all TRUE
loc = loc, # Optional additional content
full = full
)
# 'list' method:
fm_basis(
list(
A = A,
ok = ok, # If missing or NULL, inferred to be all TRUE
loc = loc
),
full = full
)
These S3 methods must be registered with the S3method()
function in scripts, and with special NAMESPACE tags in packages. In a
script, one should use
In a package, if R is version 3.6 or newer, one can use roxygen2 tags
#' @rawNamespace S3method(fmesher::fm_dof, custom)
#' @rawNamespace S3method(fmesher::fm_basis, custom)
or before each method, use @exportS3Method
, like
this:
#' @title Degrees of freedom for custom mesh
#' @description the number of degrees of freedom
#' # The rest of the documentation goes here
#' @exportS3method fmesher::fm_dof
fm_dof.custom <- function(x) {
1L + 2L * x[["n"]]
}
which semi-automates it.
We can the use the new methods with
m <- create_custom(2)
# How many latent variables are needed?
fm_dof(m)
#> [1] 5
# Evaluate the basis functions at some locations:
fm_basis(m, seq(0, 1, length.out = 6))
#> 6 x 5 sparse Matrix of class "dgCMatrix"
#>
#> [1,] 1 1.000000 . 1.000000 .
#> [2,] 1 0.309017 9.510565e-01 -0.809017 5.877853e-01
#> [3,] 1 -0.809017 5.877853e-01 0.309017 -9.510565e-01
#> [4,] 1 -0.809017 -5.877853e-01 0.309017 9.510565e-01
#> [5,] 1 0.309017 -9.510565e-01 -0.809017 -5.877853e-01
#> [6,] 1 1.000000 -2.449294e-16 1.000000 -4.898587e-16
fm_basis(m, seq(0, 1, length.out = 6), full = TRUE)
#> fm_basis object
#> Projection matrix (A): 6-by-5
#> Valid evaluations (ok): 6 out of 6
#> Additional information: loc
# Check if missing values are handled correctly:
fm_basis(m, c(0.1, NA, 0.2))
#> 3 x 5 sparse Matrix of class "dgCMatrix"
#>
#> [1,] 1 0.809017 0.5877853 0.309017 0.9510565
#> [2,] . . . . .
#> [3,] 1 0.309017 0.9510565 -0.809017 0.5877853
fm_basis(m, c(0.1, NA, 0.2), full = TRUE)
#> fm_basis object
#> Projection matrix (A): 3-by-5
#> Valid evaluations (ok): 2 out of 3
#> Additional information: loc
The main additional method that can be defined is the
fm_int()
integration scheme method. This must have the call
structure
fm_int.custom(domain, samplers = NULL, name = "x", ...)
.
domain
argument is the custom
class
object over which to integrate.samplers
argument is any object, typically an
sf
or tibble
specifying one or more subsets of
the domain, e.g. polygons. When NULL
, the entire domain
should be integrated.name
argument is a character string specifying the
name of the integration point variable....
arguments can be augmented with further
optional arguments, e.g. options controlling the integration scheme
construction and/or the output format.out <- fm_int(domain, samplers, name)
should return a
data.frame
, tibble
, or sf
object
with integration points in a column with the name indicated by
weight
with
corresponding integration weights, and a .block
column.
fm_basis(domain, out[[name]])
..block
column should be an integer vector
indicating which subdomain each integration point belongs to, usable by
fm_block_eval()
:#> x weight .block
#> 1 0.0 0.05 1
#> 2 0.1 0.10 1
#> 3 0.2 0.10 1
#> 4 0.3 0.05 1
#> 5 0.3 0.05 2
#> 6 0.4 0.10 2
#> 7 0.5 0.05 2
#> 8 0.5 0.05 3
#> 9 0.6 0.10 3
#> 10 0.7 0.10 3
#> 11 0.8 0.10 3
#> 12 0.9 0.10 3
#> 13 1.0 0.05 3
values <- fm_evaluate(
m,
field = c(1, 1, 0, 0, 0),
loc = out[["x"]]
)
# Blockwise aggregation:
fm_block_eval(
block = out$.block,
weights = out$weight,
values = values
)
#> [1] 0.44635255 0.05364745 0.50000000
# Exact integrals:
c(0.3, 0.2, 0.5) +
c(
sin(2 * pi * 0.3),
sin(2 * pi * 0.5) - sin(2 * pi * 0.3),
sin(2 * pi) - sin(2 * pi * 0.5)
) / (2 * pi)
#> [1] 0.45136535 0.04863465 0.50000000