---
title: "Converting legacy INLA mesh code to fmesher"
output: rmarkdown::html_vignette
date: "Updated 11 July 2025"
vignette: >
  %\VignetteIndexEntry{Converting legacy INLA mesh code to fmesher}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

```{r setup, echo = FALSE}
suppressPackageStartupMessages(library(fmesher))
suppressPackageStartupMessages(library(tibble))

convert_fun_link <- function(x, webref) {
  if (all(is.na(x))) {
    return("N/A")
  }
  pkg_name <- sub(
    pattern = "^(([^:]*)::)?([^(]+)(\\(.*\\))(<-)?$",
    replacement = "\\2",
    x = x
  )
  if (identical(pkg_name, "")) {
    pkg_name <- "fmesher"
  }
  nm <- sub(
    pattern = "^(([^:]*)::)?([^(]+)(\\(.*\\))(<-)?$",
    replacement = "\\3",
    x = x
  )
  args <- sub(
    pattern = "^(([^:]*)::)?([^(]+)(\\(.*\\))(<-)?$",
    replacement = "\\4\\5",
    x = x
  )
  help_name <- tryCatch(
    {
      basename(utils::help((nm), package = (pkg_name)))
    },
    error = function(e) {
      nm
    }
  )
  paste0(
    '<a href="', webref[[pkg_name]], help_name, '.html">`',
    if (!identical(pkg_name, "fmesher")) {
      paste0(pkg_name, "::")
    } else {
      ""
    },
    nm,
    args,
    "`</a>"
  )
}

convert_fun_links <- function(df, packages = colnames(df)) {
  webref <- list(
    fmesher = "https://inlabru-org.github.io/fmesher/reference/",
    inlabru = "https://inlabru-org.github.io/inlabru/reference/"
  )
  packages <- setdiff(packages, "Comments")
  for (k in seq_len(nrow(df))) {
    for (pkg in packages) {
      if (identical(pkg, "INLA")) {
        df[["INLA"]][[k]] <- paste0("`", df$INLA[[k]], "`")
      } else {
        df[[pkg]][[k]] <-
          vapply(
            df[[pkg]][[k]],
            function(x) convert_fun_link(x, webref),
            ""
          )
      }
    }
  }
  df
}
```

## Deprecation of old methods and class names

Great effort has been taken to preserve backwards compatibility as far as practical,
with in particular the old `inla.mesh`, `inla.mesh.1d`, and `inla.mesh.segment`
object classes given fallback methods that carry out methods for the new `fm_mesh_2d`,
`fm_mesh_1d`, and `fm_segm` classes.  Starting in **November 2024** however, some of this direct fallback support is being phased out, so that old stored objects may need to be explicitly converted to `fmesher` objects using for example `fm_as_fm()`.
New code, in particular in packages that use `fmesher` objects, should use the new
interface methods, and replace references to `inla.mesh`, `inla.mesh.1d`, and `inla.mesh.segment` with `fm_mesh_2d`, `fm_mesh_1d`, and `fm_segm`, respectively,
as the old class names will eventually be dropped from the mesh classes.
This in particular applies to S3 method naming and class checking,
where `inherits(mesh, "inla.mesh")` must be replaced with
`inherits(mesh, "fm_mesh_2d")` in order to work in the future.

## Deprecation warnings

Three `INLA::inla.getOption()`/`INLA::inla.setOption()` options control the
transition behaviour of the `INLA` package use of `fmesher`.

* `fmesher.evolution`, integer:
  - `1L` uses the intermediate `fm_*` methods in `fmesher` that were already available via inlabru from 2.8.0,
    but calls the `INLA` built-in `fmesher` standalone programme for mesh construction
    and related operations. (From `INLA` version 23.06.29)
  - `2L` uses the full range of `fmesher` package methods, and does not use the standalone `fmesher` programme.
    (From `INLA` around version 23.08.20, and is now the only supported option.)

* `fmesher.evolution.warn`, logical:
  When `TRUE`, `INLA` will show deprecation methods for all the methods in `INLA` that
  are being replaced by `fmesher` package methods.  When `FALSE`, no warnings will be shown.
  Set this option to `TRUE` if you want to update your own code, but keep it at
  `FALSE` when you need to run existing code without changing it.
  
* `fmesher.evolution.verbosity`, character: Either "soft", "warn", or "stop",
  indicating the minimum warning level when `fmesher.evolution.warn` is `TRUE`.
  Set this to "warn" or "stop" to get more immediate feedback when testing
  conversion of old code, e.g. in package testing.

Packages using mesh methods should use these options in package testing, e.g. in
`tests/testthat/setup.R`:
```{r,eval=FALSE}
if (requireNamespace("INLA", quietly = TRUE)) {
  INLA::inla.setOption(fmesher.evolution = 2L)
  INLA::inla.setOption(fmesher.evolution.warn = TRUE)
  INLA::inla.setOption(fmesher.evolution.verbosity = "warn")
}
```

### Compatibility

An important change is that the handling
of mesh `crs` information is now more flexible, but stricter in the sense that
user code should make no assumption about how the information is stored in the
mesh, and should therefore avoid the direct `mesh$crs` access, and instead use
the `fm_crs()` and `fm_CRS()` access methods,
depending on what type of CRS object is needed.  The ideal way to
specify crs information is in the initial mesh creation call. If the crs needs
to be explicitly assigned a new value, use the `fm_crs(mesh) <- crs` assignment
method.

For mesh and curve creation, the `fm_rcdt_2d_inla()`, `fm_mesh_2d_inla()`, and
`fm_nonconvex_hull_inla()` methods will keep the interface syntax used by
`inla.mesh.create()`, `inla.mesh.2d()`, and `inla.nonconvex.hull()` functions,
respectively, whereas the `fm_rcdt_2d()`, `fm_mesh_2d()`, and
`fm_nonconvex_hull()` interfaces may change in the future.


## Mesh construction

```{r echo = FALSE}
df <- tribble(
  ~INLA, ~fmesher, ~Comments,
  "inla.mesh.create()", c("fm_rcdt_2d()", "fm_rcdt_2d_inla()"), "",
  "inla.mesh.2d()", c("fm_mesh_2d()", "fm_mesh_2d_inla()"), "",
  "inla.delaunay()", "fm_delaunay_2d()", "",
  "inla.mesh.1d()", "fm_mesh_1d()", "",
  "inla.mesh.lattice()", "fm_lattice_2d()", "",
  "inla.mesh.segment()", "fm_segm()", "",
  "inla.nonconvex.hull()", c(
    "fm_nonconvex_hull()",
    "fm_extensions()",
    "fm_simplify()"
  ), 'Use `format = "fm"` to get `fm_segm` output from `fm_nonconvex_hull()`.',
  c(
    "inla.contour.segment()",
    "inla.simplify.curve()"
  ),
  c(
    "fm_simplify_helper()",
    "fm_segm_contour_helper()"
  ), "",
  "inla.mesh.components()", "fm_mesh_components()", "",
  NA_character_, "fm_subdivide()", "",
  NA_character_,
  "fm_hexagon_lattice()",
  "Creates points for equilateral triangles inside a polygon domain.",
)
df <- convert_fun_links(df)
```

```{r, echo = FALSE}
knitr::kable(df)
```

## Location, basis and function evaluation

```{r echo = FALSE}
df <- tribble(
  ~INLA, ~fmesher, ~inlabru, ~Comments,
  "inla.mesh.query()",
  NA_character_, NA_character_,
  "For tt and vt multi-order neighbours. No fmesher equivalent in version <= 0.6.0",
  "inla.mesh.projector()", "fm_evaluator()", character(0), "",
  "inla.mesh.project()", "fm_evaluate()", character(0), "",
  "inla.spde.make.A()", c(
    "fm_bary()",
    "fm_basis()",
    "fm_row_kron()",
    "fm_block()",
    "fm_block_eval()"
  ), c(
    "inlabru::bm_multi()",
    "inlabru::ibm_jacobian()",
    "inlabru::bm_aggregate()"
  ), "",
  "inla.mesh.deriv()", "fm_basis()", character(0), ""
)
df <- convert_fun_links(df)
```

```{r, echo = FALSE}
knitr::kable(df)
```

## Finite element methods

```{r echo = FALSE}
df <- tribble(
  ~INLA, ~fmesher, ~Comments,
  c("inla.mesh.fem()", "inla.mesh.1d.fem()"), "fm_fem()", " ",
  NA_character_, "fm_matern_precision()", " ",
  NA_character_, "fm_matern_sample()", paste0(
    "Convenience function that combines",
    " `fm_matern_precision()`",
    " and `fm_sample()`."
  ),
  NA_character_, "fm_covariance()",
  paste0(
    "Basic helper function for computing",
    " covariances between different locations.",
    " Can produce sparse inverses like",
    " `inla.qinv()`, but currently (version 0.1.1) only",
    " by a 'brute force' method."
  ),
  " ", "fm_qinv()",
  paste0(
    "Produce sparse inverses like",
    " `inla.qinv()`, but currently (version 0.2.0.9010)",
    " by an R implementation."
  ),
  " ", "fm_sample()", "Basic sampling method, like `inla.qsample()`"
)
df <- convert_fun_links(df)
```

```{r, echo = FALSE}
knitr::kable(df)
```

## Printing

```{r echo = FALSE}
df <- tribble(
  ~INLA,
  ~fmesher,
  ~Comments,
  "summary.inla.mesh()",
  c(
    "print.fm_mesh_2d()",
    "print.fm_segm()",
    "print.fm_mesh_1d()"
  ),
  "Use `print(mesh)` etc."
)
df <- convert_fun_links(df)
```

```{r, echo = FALSE}
knitr::kable(df)
```

## CRS information and coordinate transformations

```{r, echo = FALSE}
df <-
  tibble::tribble(
    ~INLA, ~fmesher, ~Comments,
    "inla.spTransform()", "fm_transform()", "",
    "mesh$crs", c("fm_crs(mesh)", "fm_CRS(mesh)"),
    paste0(
      "The crs may now be stored in different formats;",
      " use `fm_crs()` for `sf` format, and `fm_CRS()` for `sp` format.",
      " `fmesher` will attempt to convert when needed."
    ),
    "mesh$crs<-", "fm_crs(mesh)<-",
    paste0(
      "Direct assignment of crs information should be avoided,",
      " but is allowed as long as its compatible with the actual",
      " mesh coordinates."
    )
  )
df <- convert_fun_links(df)
```

```{r, echo = FALSE}
knitr::kable(df)
```

## Plotting

```{r echo = FALSE}
df <- tribble(
  ~INLA, ~fmesher, ~inlabru, ~Comments,
  "No ggplot support",
  c("geom_fm(data = mesh)", "geom_fm(data = segm)"),
  "inlabru::gg(mesh)",
  "Use `ggplot() + geom_fm(data = mesh)` and `inlabru::gg()` methods",
  "plot.inla.mesh(rgl = FALSE)",
  c("plot.fm_mesh_2d()", "lines.fm_mesh_2d()"),
  character(0),
  "Use `plot()` or `lines()`",
  "lines.inla.mesh.segment(rgl = FALSE)",
  c(
    "plot.fm_segm()",
    "lines.fm_segm()"
  ),
  character(0),
  "Use `plot()` or `lines()`",
  "plot.inla.mesh(rgl = TRUE)",
  c("plot_rgl()", "lines_rgl()"),
  character(0),
  "",
  "lines.inla.mesh.segment(rgl = TRUE)",
  c(
    "plot_rgl()",
    "lines_rgl()"
  ),
  character(0),
  ""
)
df <- convert_fun_links(df)
```

```{r, echo = FALSE}
knitr::kable(df)
```

