par() saves the previous settings
and resets them with par(op) afterward, so a vignette no
longer leaves mfrow and mar altered for later
code.topocast() and window_regression() gain a
threads argument, the number of threads the fitting loop
may use, defaulting to every available core as before. The fitting loop
previously took every core with no per-call way to ask for fewer, since
the only control was OMP_NUM_THREADS, which the OpenMP
runtime reads once at start-up. A caller fitting many targets by
parallelizing at the R level therefore got a full thread team in each
worker, W x C threads for W workers on C cores; such a caller can now
pass threads = 1 and own the cores itself. The threads buy
little on their own – 24 of them fit a 121 x 146 grid with 19 responses
about 1.5 times as fast as one – so more workers at one thread each is
generally the better use of a machine (#36)._R_CHECK_LIMIT_CORES_. A check environment allows a package
two cores, so the loop’s previous fan-out over the whole machine spent
more CPU time than the elapsed time a check budgets for it.topocast() now brings every coarse grid a call fits
– each response’s coefficients and diagnostics, plus the shared
valid-cell count – onto the target in one resample (grid target) or
extract (point target), resolves the target predictors once, and
assembles the coarse grids as a single multi-layer object rather than
one terra object per coefficient. Each of those was previously done once
per response, so a call repeated its whole coarse-to-target trip for
every response it fit. The per-call fixed cost falls from about 0.58 s
to about 0.11 s, a 25,000-point call with 19 responses from 1.28 s to
0.46 s, and the S4 construction and dispatch that took over a fifth of a
call no longer appears in its profile. With anomaly, the
period stack likewise travels in one trip rather than one per period.
Results are unchanged (#35).
The moving-window engine’s summed-area tables are now built and
queried in double-double (extended) precision rather than plain
double. A summed-area table entry near the far side of a
large grid holds a sum over most of the grid’s cells, so its magnitude
grows with the grid size; on a plain-double table, a window’s sum
(extracted as the difference of four such entries) then loses precision
proportional to that magnitude rather than to the window’s own size,
which by roughly a million cells could already turn a genuinely zero
within-window variance into a small positive number that wrongly cleared
min_variance, defeating the no-spread guard (#21).
arma::uword is now explicitly 64-bit
(ARMA_64BIT_WORD), and the moving window’s row/column
bounds are computed in a 64-bit type, closing two overflow classes left
open after #13/#14 for a grid whose total cell count or single dimension
is large enough to overflow a 32-bit computation (#31, #32).
The C++ engine calls Rcpp::checkUserInterrupt()
between chunks of rows (and during single-threaded setup), so a
long-running fit on a large grid can now be interrupted rather than only
checked for completion (#33).
A response or predictor named r.squared,
residual.sd, n.valid, or
(Intercept) is now rejected at the formula-parsing stage;
those names are reserved for topocast()’s own diagnostic
and coefficient columns, and a response sharing one previously had its
downscaled values silently overwritten by the diagnostic column of the
same name when diagnostics = TRUE (#22).
n.valid is now bounded to
[0, (2 * radius + 1)^2] after resampling, the true range a
window’s valid-cell count can hold; like r.squared, it
could previously be carried slightly outside its true range by some
resampling methods (#24).
radius = 0 is now rejected: its one-cell window can
never hold the k + 1 valid cells a fit with k
predictors needs, so it previously returned an all-NA
result with no error or warning (#25).
An invalid aggregate or grid-target
method now errors with a message naming the actual
topocast() argument, instead of terra’s raw
'arg' should be one of ... message, which named neither
(#26).
With type = "ratio", a coarse cell where the
baseline and the anomaly period are both exactly zero (e.g. a dry-season
precipitation cell with no rain in either) is now treated as ratio
1, “no change”, rather than discarding the fine baseline
the same way a true x / 0 does (#27).
A CRS mismatch error now falls back to a PROJ4 or WKT snippet when neither side carries an identifiable EPSG code (common for a custom or regional projection), instead of printing “unknown CRS” on both sides with no way to tell how they actually differ (#28).
An entirely non-finite response/predictor stack now errors with a
message naming the actual inputs (data/onto
for topocast(), y/x for
window_regression()) instead of the C++ engine’s generic
"no finite cells to regress" (#30, #34).
output = "raster" no longer silently renames the
(Intercept) coefficient column via the raster
package’s own make.names()-based names<-,
which is now documented as an inherent limitation of that output class
rather than left unmentioned (#23).
inst/CITATION now reports the installed package
version dynamically instead of a hardcoded string that had drifted out
of date (#29).
Several responses that share the predictors are downscaled in one
call by naming them as cbind(prec, tmin) ~ elev. The
moving-window design depends only on the predictors, so it is assembled
and factored once and solved against every response; each extra response
adds only a back-substitution. The result has one layer (or column) per
response, and coefficients/diagnostics grids
are prefixed by the response name. window_regression()
likewise accepts a list of response matrices.
diagnostics = TRUE returns an r.squared
grid: the per-window coefficient of determination of the local fit,
computed from the same summed-area sufficient statistics, mapping where
the terrain relationship is strong. window_regression() now
returns r_squared alongside intercept and
slope.
diagnostics = TRUE also returns
residual.sd (the residual standard deviation of the local
fit, in the response’s own units) and n.valid (the count of
valid coarse cells the window held), from the same sufficient statistics
as r.squared. n.valid is shared across
responses rather than prefixed, since the valid-cell mask is
complete-case across them; residual.sd is prefixed like
r.squared is. window_regression() likewise
returns residual_sd and n_valid (#8).
clamp = TRUE bounds the downscaled field to the
observed range of the coarse response, a guard against the local linear
fit extrapolating without limit where a fine predictor lies outside the
range it was fit on.
The per-cell window solve runs in parallel with OpenMP where the toolchain provides it; results are unchanged.
anomaly and baseline accept a
Raster* (raster) or stars object, like
data and onto already did, instead of only a
SpatRaster (#5).
radius and min_cells are validated as
non-negative whole numbers before reaching the C++ engine. A negative
radius previously indexed the summed-area table out of
bounds and crashed the R session rather than erroring (#4); the same
validation now also rejects a value too large for a 32-bit integer,
which previously overflowed to NA on coercion and crashed
the session the same way (#9).
min_variance is validated as a non-negative number.
NA or a negative value previously compared as false against
every window variance, silently disabling the documented guard against a
predictor with no spread instead of being rejected (#10).
window_regression() rejects a partially-named list
of responses instead of silently making the unnamed ones’ results
unreachable by name (#6).
window_regression() rejects a list of responses with
a repeated name, which had the same unreachable-by-name problem as #6
but was not caught by it (#15).
A min_cells close to
.Machine$integer.max no longer overflows the C++ engine’s
valid-cell threshold; previously the overflow silently disabled the
guard instead of enforcing it, so windows with far fewer than the
requested minimum could still be fit (#13).
A radius close to .Machine$integer.max
no longer crashes the R session; it is now clamped to the grid’s span,
which covers every cell in every window anyway and so changes no result
(#14).
diagnostics = TRUE’s n.valid (and
window_regression()’s n_valid) now reports a
window’s valid-cell count whenever the window held enough cells to
attempt a fit, rather than only when the fit also succeeded; a
degenerate (no-spread predictor) or singular window previously reported
n.valid = NA indistinguishably from a window that never had
enough cells (#16).
A window whose predictors are exactly collinear (rank-deficient
design) now reliably returns NA instead of a spurious
least-squares coefficient; Armadillo’s default solver silently falls
back to an approximate solution on a detected-singular system rather
than failing, which defeated the documented singular-design guard
(#17).
data and onto accept Raster*
(raster) and stars objects in addition to
SpatRaster, and the result is returned in the class of
onto. The new output argument requests a
specific class.onto may be an sf or
SpatVector of points: the fitted relationship is evaluated
at each point and returned as a prediction column, with the points
carrying the fine predictor values as attributes. This makes downscaling
to station or plot locations a single call. The
coefficients and anomaly results are returned
as columns in the same way.topocast() derives a coarse predictor from
onto when the predictor named in the formula is not a layer
of data, aggregating it to the response grid with the new
aggregate argument. The one-DEM case is now a single call
from a coarse climate layer and a fine elevation model (#1).coefficients = TRUE returns the fitted layer together
with the (Intercept) and per-predictor slope grids on the
onto grid, so the local relationship, such as a
precipitation lapse rate, can be mapped (#2).topocast() downscales a coarse raster onto fine terrain
by moving-window regression. The relationship is a formula of layer
names (prec ~ elev + slope); the response and coarse
predictors live in one data raster and the fine predictors
in onto. Coefficient grids are estimated with summed-area
tables, so the cost is independent of the window radius.anomaly: the baseline climatology is downscaled once and
each period’s anomaly is carried onto it, type = "ratio" or
type = "additive".window_regression() exposes the terra-free matrix
engine.