convmatrix() builds a sparse Toeplitz (convolution) matrix
A such that
as.vector(A %*% x) is numerically identical to
gsignal::conv(x, kernel, shape).
If
n_colsis omitted, the function returns the classical banded design matrix: square whenshape = "same"and tall whenshape = "full".If
n_colsis supplied, only the firstn_colstime-shifts ofkernelare kept, yielding ann x n_colsrectangular design. Such matrices are useful for point-spread-function (PSF) update steps in blind-deconvolution algorithms.
The matrix is produced in a single pass, so no intermediate (larger) object is ever allocated, keeping memory usage to a minimum.
Usage
convmatrix(kernel, n, shape = c("same", "full"), n_cols = NULL, origin = NULL)Arguments
- kernel
Numeric vector (`length L`) - FIR filter / PSF **in forward-time order**.
- n
Integer \(>\) 0 - length of the observed signal (the number of rows for a *"same"* design).
- shape
`"same"` (default, \(n\) rows) or `"full"` (\(n+L-1\) rows).
- n_cols
Optional. Number of columns to keep. *If omitted* `n_cols <- n` and the result is the usual banded convolution matrix.
- origin
Integer (1 ... \(L\)) - element of `kernel` that is aligned with time 0. Defaults to the centre for `"same"` and to 1 for `"full"`.
Value
A sparse matrix of class **`dgCMatrix`** with
* `n_rows = n` (shape "same") or * `n_rows = n + L - 1` (shape "full")
rows, and `n_cols` columns.
See also
convkernel to generate the 1-D PSF vectors used here.
Author
Tom Wenseleers tom.wenseleers@kuleuven.be \ Laboratory of Socioecology and Social Evolution, KU Leuven \ https://bio.kuleuven.be/eeb/tw/research \ ORCID: 0000-0002-1434-861X
Examples
## 1. synthetic spike-train convolved with a bi-Gaussian PSF ------------
if (requireNamespace("gsignal", quietly = TRUE)) {
library(Matrix)
library(gsignal)
### ---- synthetic spike train ----------------------------------------
set.seed(1)
n <- 100
a <- numeric(n)
a[sample(n, 20)] <- rpois(20, 10) # random spikes
### ---- bi-Gaussian PSF via kernel() ---------------------------------
L <- 31
g <- convkernel("bigaussian", L,
params = list(sigma1 = 1, sigma2 = 4)) # default p = 0.5
plot(g, type = "l", main = "Bi-Gaussian PSF", ylab = "density")
### ---------- 1.1 FULL convolution -----------------------------------
y_ref_full <- as.vector(conv(a, g, shape = "full")) # reference
A_full <- convmatrix(g, n, shape = "full") # sparse full conv matrix
all.equal(as.matrix(A_full), convmtx(g, n), tol = 1e-12) # matches convmtx
y_full <- as.vector(A_full %*% a) # A %*% a
plot(1:(n + L - 1), y_ref_full, type = "l",
main = "FULL convolution", xlab = "time index", ylab = "signal")
lines(1:(n + L - 1), y_full, col = 2, lty = 3) # identical
centre_shift <- (L + 1) / 2 - 1 # PSF peak offset
lines((1:n) + centre_shift - 1, a, type = "h", col = 4) # spike locations
all.equal(y_full, y_ref_full, tol = 1e-12) # matches conv
### ---------- 1.2 SAME-size convolution ------------------------------
y_ref_same <- as.vector(conv(a, g, shape = "same"))
A_same <- convmatrix(g, n, shape = "same") # sparse conv matrix
y_same <- as.vector(A_same %*% a)
plot(1:n, y_ref_same, type = "l",
main = "SAME-length convolution", xlab = "time index", ylab = "signal")
lines(1:n, y_same, col = 2, lty = 3) # identical
lines((1:n) - 1, a, type = "h", col = 4) # spike locations
all.equal(y_same, y_ref_same, tol = 1e-12) # matches conv
}
## 2. rectangular design for PSF-update step in blinv deconv algo -------
set.seed(1)
n <- 1000 # length of obs convolved signal
Lpsf <- 101 # length of PSF to be estimated
spikes <- rpois(n, 2) # true or estimated spikes
G <- convmatrix(kernel = spikes,
n = n,
n_cols = Lpsf, # keep just the first Lpsf shifts
shape = "same",
origin = (Lpsf + 1L) %/% 2L)
all.equal( # identical to the first Lpsf columns of the banded design
G,
convmatrix(spikes, n, shape = "same",
origin = (Lpsf + 1L) %/% 2L)[, 1:Lpsf, drop = FALSE], tol = 0)