Skip to content

Dev/next - #14

Open
elkronos wants to merge 98 commits into
mainfrom
dev/next
Open

elkronos wants to merge 98 commits into
mainfrom
dev/next

Conversation

@elkronos

@elkronos elkronos commented Nov 9, 2026

Copy link
Copy Markdown
Owner

No description provided.

A seed outside a boundary that is not convex lost its cell: index gave it
the cell that holds the point, which is NA outside the boundary, so
cell_values_at(seeds =) filled or mislabelled locations near the edge.

build_tessellation() and create_voronoi_polygons() now carry each cell's
generator through the clip, and for seeds (the package's seed layers, a
selection's seeds, or points given with a cover) index names the cell the
seed generates. A seed keeps NA only when the clip removed its whole cell,
and the warning now says so. Plain points keep the observation rule. The
cells themselves are unchanged.

The work-round section in ?cell_values_at is removed. Two tests that pinned
the old index are turned round, and test-fix-cells-seed-index.R covers the
notched layers, every kind of seed layer and a seed whose cell the clip
removed.
- select_resolution()'s size note calls 4.5 rows per cell "the default"
  rather than "the caller's choice".
- The pooled print of a column with tau2 = 0 shows the full block and says
  each value is its neighbours' mean.
- A profile with one level prints "1 level" and drops the WSS-steps clause
  when fewer than two levels have a WSS; the WSS-steps count on a row
  subset now counts that subset.

The help and the resolution vignette no longer say the size stands because
the caller set it. One frozen record, small_five_rows, changes in two
printed lines; dev/baseline-core-frozen.R gains a --retake mode to take a
record from new code.
…lbow

The default on_no_elbow is now "na": with no elbow, too short a ladder or
too few distinct points, the call returns NA_integer_ with a
spatialkit_no_elbow warning that carries k, sag and structured = FALSE.
On the model-aware criteria the NA also carries the sweep's diagnostics.
A count the elbow chose carries structured and sag. on_no_elbow = "warn"
gives the earlier count and warning exactly.

get_voronoi_seeds(n = NA) stops with a message naming the default. The
help, README, ?spatialkit and the vignettes tie the NA to the elbow, not
to clusters; getting-started names on_no_elbow = "warn". Four frozen calls
name "warn" and no record changes.
- Cp no longer takes a nugget at the variogram fit's lower bound; it takes
  the finest level's residual mean square instead.
- Cp's noise variance is held to the short-lag semivariance, and
  attr(, "cp_noise") and the print say which source was used.
- The two variogram warnings carry classes, spatialkit_variogram_unusable
  and spatialkit_cp_noise.
- A cp selection carries a count_rule record, so a pooled summary on its
  cells raises spatialkit_estimate_mismatch.
- A profile without an estimate fitted to a subsample refits the chosen
  level to every point before handing on its seeds. A profile with an
  estimate keeps the centres it scored.

NEWS and ?spatialkit name the two ways the old-default call no longer
gives the old count. 76 frozen records are retaken; test-fix-cp-predictor-
path.R covers the repair, including a held-out regret check on six fields.
A cross-validated error is the error at the distance the folds put between
held-out points and their training data. The help, vignettes, README, tour
scripts and make_folds()'s warnings said instead that random folds flatter,
that blocked folds are the ones to report, or that a score leaks, with no
target. They now say which way the error reads for predictions farther from
or closer to the data, the cv_*() pages state what folds = NULL estimates,
and three vignettes check their folds against the map with
fold_separation(). The reporting vignette's reading had the direction
backwards for its own map and is now computed.

No default changes. test-fix-cv-direction-wording.R checks the wording rule
across the help, vignettes and scripts.
determine_optimal_levels() switched criterion = "geometric", the
default, to "combined" whenever both response_var and predictor_vars
were given, also when "geometric" was named, so the count read the
response unasked. "combined" averaged the rank of the WSS curve's
log-log sag with the rank of Moran's z of the cell means, which put
first ten, the lowest count Moran's z can be computed at, or the count
after it: on 600 uniform points with a smooth field, under
on_no_elbow = "warn", it returned 10 6 5 where "geometric" returns
6 5 7. Its sag came from a curve carrying the WSS of the k-means re-run,
so where the re-run landed in a worse optimum at the elbow another count
went first, and on eighteen loose clusters the elbow, 15, dropped out of
the counts returned.

The criterion the caller names, or the default, now stands. With both
variables under "geometric", a line logged at INFO says they are not
read. "combined" ranks the counts around the elbow by the sag of the
sweep's WSS curve, the one the elbow is read from, so the elbow comes
first. moran_i, moran_z and wss_eval stay in the diagnostics and rank
nothing. Where Moran's I can be computed at no count of a window that
reaches ten cells, "combined" ranks as usual and returns the
diagnostics with moran_i and moran_z NA and a logged warning, where it
fell back to the geometric ranking without them; "morans_i" still
falls back there.

Two Breaking changes entries in NEWS describe this. A call with both
variables and no criterion returns the geometric vector with no
"diagnostics" attribute. "combined" returns 11 12 10 on eleven
clusters, where it returned 10 11 12, and the same vector with
select_on = "split" or a constant response. It raises no
spatialkit_diagnostic_ranking warning; that warning comes with
"morans_i" alone, its condition no longer carries `upgraded`, and its
message names resolution_profile() and select_resolution() in place of
select_resolution(criterion = "cp"), an error on the default profile.
A factor response is refused only under a criterion that reads it. The
help, the class list on the resolution_profile() help page, the
vignette, script 02, the README troubleshooting entry and four older
NEWS entries are brought in line.

No frozen record changes. Tests turned round: the ranking-warning test
in test-fix-resolution-moran-diagnostic.R (no `upgraded`, the plain
advice, no warning from "combined", "geometric" with both variables);
two tests in test-review3-S1-resolution.R, now "combined ranks the
window on the log-log sag alone, the elbow first" and "combined on
points with no elbow keeps the geometric order"; the "warn" leg in
test-fix-levels-no-elbow.R, which expects the ranking class under
"morans_i" only; test-resolution-profile.R and test-return-accounting.R,
which expect no ranking warning from "combined"; and test-audit-pass5.R,
which names criterion = "combined" for the factor-response error. Calls
in test-select-on.R and test-review-resolution.R name the criterion.
test-fix-levels-combined.R is new.
.estimate_icc() returned 0 both for an ICC it could not estimate (a
column's values all in one cell, fewer than two cells holding two or
more of them, fewer than four values) and for an estimate at or below
0, so summarize_by_cell(deff = "kish"), and a "variogram" request that
fell back to it, gave an unidentified column the uncorrected standard
errors under the label of an ICC of 0. Under deff = "variogram", a
response with no spatial structure gave every predictor a design effect
of 1 in silence, whatever structure the predictor had of its own.

.estimate_icc() now returns NA with a reason in the first case and 0 in
the second. It reads whether a column varies from all its non-missing
values, rows with no cell id included, and names columns whose values
fall in no cell or differ only there. For a column with an NA ICC the
..se_*, ..neff_* and ..ci_* values are NA in every cell of two or more
values (..df_* stays n - 1), and so is cell_weight when that column sets
it; a cell of one value keeps a weight of 1. A column that does not vary
keeps standard errors of 0 with no warning.

A warning of class spatialkit_icc_unavailable (fields columns and
reason) names the withheld columns. attr(, "icc") gains a reason field
only when some ICC is NA, the variogram fallback warning names those
columns, and under estimate = "pooled" the method of attr(, "intervals")
names them after 1 as after "kish", with .intervals_table() still giving
the default's text for the other columns. A predictor summarised under a
no-structure verdict gets a warning of class
spatialkit_deff_no_structure (fields columns and reason); its numbers do
not change. NEWS records both under Breaking changes.

Three records of tests/testthat/fixtures/core-frozen.rds are retaken:
values_one_region (its standard errors, effective n, intervals and
cell_weight become NA, with one spatialkit_icc_unavailable warning),
values_one_row_regions and values_constant (attr(, "icc") gains
reason). No existing test is turned round. "the ICC guard matches its
documentation" in test-audit-pass5.R and "a variogram fallback with no
fit reports why, as an R warning" in test-review2-assignment.R gain
expectations for the NA ICC.
area_of_applicability() measured location for a forest fitted with
include_coords = TRUE only through its ..x and ..y predictor columns.
Under weights = "auto" those columns take the forest's own importance,
so a forest that barely split on its coordinates gave location almost
no weight. With half of 50 prediction points one half to one and a half
extents beyond the training data, 41 read as inside, 16 of them beyond
the extent.

Such a forest now gets the location index a gwr_fit and a bayesian_fit
get (DI_location, element location), with the same folds and the same
rule for repeated locations, and a point is inside only when it is
inside on both indices. ..x and ..y stay in the predictor index with the
same weights, so DI and threshold are unchanged and DI <= threshold is
the old answer. The location coordinates are read after the coordinate
block, so a CRS on one side only is reconciled and warned about once.
In the example above none of the 25 points beyond the extent is inside.

For the forest, params$coords reads "location index and predictors",
print() adds "and as the predictors ..x and ..y" under the location
line, and plot() counts points outside on location in its subtitle. A
threshold the caller passes sets the predictor index alone, and
coords = "no" leaves the coordinates out of both indices. The Breaking
changes entry in NEWS states the rest: a row outside on location is
FALSE also when a predictor is missing, where it was NA; folds that
split repeat visits can give a location threshold of 0 with the
spatialkit_aoa_zero_threshold warning; and a train_sf in a geographic
CRS is refused. The data.frame refusal now suggests coords = "no".

No frozen record changes. The test "a forest with the coordinates as
predictors keeps its own path" in test-fix-aoa-rf-location.R is turned
round to "... keeps them and gains the location index", and the
reprojection test in test-area-of-applicability.R also compares
DI_location. New tests are in test-fix-aoa-coord-forest.R.
With 2 to 9 cells, summarize_by_cell() takes each cell's target as the
mean of every row outside it, so a cell whose own weight is below its
share of the column's rows gets a pooled value on the far side of the
overall mean from its plain mean. With two cells and `tau2` at 0 every
own weight is 0, and each cell's pooled value is the other cell's mean.
The spatialkit_pool_few_cells warning said only that a cell "with a
small own weight" crosses the overall mean and that two cells can
change order.

The warning now gives the share of the column's rows as the bound, and
adds one sentence naming the columns with two cells and `tau2` at 0:
"For 'p' and 'q', with 2 cells and `tau2` at 0, each cell's pooled
value is the other cell's mean: the two cells' values change places."
The sentence is left out when no column is in that state. The
"Conditions" entry of ?summarize_by_cell says the same, and says such a
column has `cells` 2 and `own_weight` 0.

The pooled values, the condition's class and its fields (`columns`,
`cells`, `own_weight`) are unchanged. NEWS has a Bug fixes entry, and
nothing goes under Breaking changes.

No frozen fixture record was retaken. Two tests were turned round:
"with 2 to 9 cells the target is the mean of all the other cells, with
a warning" in test-core-pool-edge.R pinned the old sentence for 2, 3
and 9 cells, and "no number stands in those texts but the simulation's
figures, its settings and the code's constants" in test-core-docs.R
gains 2 and 0 for the "Conditions" entry. New tests check the exchange
sentence, that it names no other column, the share when values are
missing, and the help and NEWS text.
Texts that print only in a particular case misstated that case. Each
now says what happened:

- FNN installs with spatialkit, and the texts for when it cannot be
  loaded said to install it: the work limits of resolution_profile(),
  summarize_by_cell() and cell_values_at(), the density kind's log
  line, area_of_applicability(use_fnn = TRUE), the k-NN weights and
  spatialkit_moran_skipped. They say it could not be loaded and is to
  be reinstalled. The work-limit errors wrote both numbers with %.2g,
  so a work 0.04 percent over the limit read "1e+10 ... above the
  limit of 1e+10"; .count_text() writes them in full with the digits
  that tell them apart, and the fit guard no longer offers "the FNN
  package installed".
- On a constant response the spatialkit_constant_response warning
  left out `loo`, `loo_se` and `own_weight`, which are NA too, and
  summary() stopped with an error that named neither the constant
  response nor `loo`. The warning names the three, and the error says
  the response is constant and names `loo`.
- select_resolution() said one mean was "within 2% of the optimum"
  also where it was lower; it says "is not above the optimum by more
  than 2%", the rule. After Cp's note the variogram's no-structure
  note was pasted in lower case with its close said twice; it is a
  sentence of its own. summary() said that one cell does as well where
  the cells beat one cell and only the F test or the variogram showed
  no structure; it says which, in attr(, "not_identified"), and picks
  that agree read "every pick is 60 cells" in place of "picks span 60
  to 60 cells (1.0x)".
- The support-ceiling hint said to lower min_cell_n, which moves every
  level; it names `levels`. The prints write "the default" for the
  default size, "(1 of 1 level)", "(133 cells)", "1 k-means++ restart"
  and the numbers of one line in one notation (.one_notation()). The
  profile's print marks a bound nugget where no level gives a noise
  variance, and says that `cp` is NA.
- summarize_by_cell()'s no-structure warning called predictors "the
  response"; it says "the column" or "the columns". cell_values_at()
  on a logical, factor or character column advised a summary that
  summarize_by_cell() cannot make; it says how to convert each class.
  assign_features_to_polygons() blamed the polygons or the CRS for
  features with empty geometry; it counts them.
- make_folds(auto_range = TRUE) with a `block_size` said the range set
  the minimum block size; it says the `block_size` does and gives the
  grid of blocks. On one row it writes "1 fold" and offers no fewer
  folds. A random or provided seeding's density warning names the method
  in place of the density kind. model_metrics(se.fit = TRUE) names
  cv_bayes() only for draws or a Bayesian fit. The REML fallback of
  estimate_sac_range() said the fit did not converge below 30 locations
  or without nlme; it says it was not fitted, and why.
  kriging_adequacy()'s print says what its gain is under a pure nugget.
  voronoi_seeds_kmeans() and voronoi_seeds_random() refuse the NA of
  determine_optimal_levels() with the note that names `on_no_elbow`.

NEWS has four Bug fixes entries and two under Breaking changes.
resolution_profile() raises one spatialkit_size_not_reached for all
the sizes held for the same reason, where it raised one a size; its
`obs_per_cell` and `wanted` are vectors in ladder order, and one size
held gives the condition it gave. The rejected_reason of
estimate_sac_range() when fewer than three lag bins hold pairs is "no
variogram model could be fitted: fewer than three lag bins hold point
pairs", where it ended "(singular fits)".

The frozen fixture retakes the 24 records whose value, print, conditions
or log carry these texts: select_density_cp,
select_density_arguments_cp, select_density_clustered_cp,
select_integer_cp, select_lattice_cp, select_lattice_density_cp,
select_lonlat_density_cp, select_min_cell_n_cp, select_n_levels_cp,
select_sampled_cp, seeds_density_selection, seeds_density_profile,
kmeans_density_selection, kmeans_density_profile,
count_kmeans_selection_kmeans_named, select_noise_cp,
small_five_rows_selected, small_five_rows_count, count_hex_noise,
count_kmeans_noise, count_seeds_noise, seeds_noise_selection,
seeds_random_power_named and summary_constant; the last two are retaken
for the first time. Tests that pinned the old texts are turned round in
test-core-count-ladder.R, test-core-count-loo.R, test-core-count-size.R,
test-core-pool-edge.R, test-core-pool-properties.R,
test-core-pool-summary.R, test-core-seeding-starts.R,
test-core-values-at.R, test-fix-resolution-bound-warning.R,
test-fix-resolution-structure-test.R and
test-fix-resolution-usability.R. test-fix-resolution-no-structure.R
accepts the capitalised variogram note, and "kriging_adequacy() says why
there is no variogram model, and whose" takes the new reason. New tests
are in test-fix-texts-misstated.R, and test-fix-cp-predictor-path.R
checks the bound-nugget print.
Four run-time texts said which way an error errs with no word on the
map it errs for. print() on an rf_fit called the out-of-bag error
"optimistic under spatial autocorrelation"; the caution that
fit_rf_model(include_coords = TRUE) logs said the out-of-bag error and
random cross-validation "overstate its accuracy"; the caution that
select_features_forward(method = "random_kfold") logs said random inner
folds "leak spatial autocorrelation" and that a blocked outer loop
reports "honest-looking numbers"; and the warning of .remap_folds() on
its fallback to random folds said blocked folds "avoid optimistic
performance estimates".

Each text now says what the error is the error of and names the
predictions it reads low for. The out-of-bag error and random folds are
the error of predicting among the samples, which reads low for
predictions made farther from the data, and the advice is cv_rf() or
make_folds() on folds at the distances the map will predict at
(?fold_separation). The random_kfold caution says the inner folds choose
the variables that predict best among the samples and a blocked outer
loop scores the chosen set at the blocks' distance, for which it was
not chosen, and it advises inner folds that hold points out as the
outer ones do: 'block_kfold', the default. A forest with no row out of
bag closes its print with how to score it with cv_rf() in place of the
paragraph on an out-of-bag error it does not have.

Only the text of a print, two log lines and one warning changes: no
result, default, class or error. NEWS has one Bug fixes entry. It leaves
out the .remap_folds() warning, which no exported function reaches,
since each caller builds block_kfold folds when folds is NULL.

Three pins of the old wording are turned round to refuse it and match
the new sentences: "print.rf_fit states the out-of-bag caveat" (now
"print.rf_fit says what the out-of-bag error is the error of") in
test-model-rf.R, the first test of test-fix-aoa-rf-coords-text.R, and
"random inner folds are permitted but warned about" in
test-feature-selection.R. test-fix-cv-direction-wording.R bars four more
phrases and judges the new texts, with the prints of a forest fitted
with coordinates and of one with no row out of bag, by the rules the
pages follow, each header line of a print as a sentence of its own, and
test-review3-S8-bayes-rf.R pins the closing lines of the no-OOB print.
No frozen fixture record is retaken.
.crs_distance_error() also scores a layer of fewer than 40 polygons or
lines on its outlines' vertices, and above 160 vertices it thinned them
at even spacing along the rows of st_coordinates(), which follow the
order of the features. Five outlines of 200 vertices over the
conterminous United States got an Albers CRS in three of eight feature
orders and a Lambert azimuthal one in the other five. .crs_area_error()
measures at most 200 of a layer's polygons and took them at even
spacing along the rows, so the area error moved with the order: 300
cells in a UTM zone, one of them 700 km east of its central meridian,
were refused by summarize_by_cell(area = TRUE) in five of eight orders.

The outline is now thinned along its distinct vertices sorted by x and
then y, as a point layer's points are sampled: a vertex listed twice (a
ring's closing vertex, a border two features share) counts once, and a
thinned outline is no longer densified between the vertices kept. Above
200 polygons, the new internal .polygon_sort_keys() orders the polygons
by bounding box and then by the means of their vertices' x, y, x^2, y^2
and x*y, which separates polygons that share a box, such as Delaunay
triangles of gridded points. The 200 are taken at even spacing along
that order, and the polygons with the smallest xmin and ymin and the
largest xmax and ymax are always added, so up to 203 are measured.

For a given layer the chosen CRS, attr(, "area_error") and the outcome
of the area check in summarize_by_cell(area = TRUE) and in
ensure_projected(purpose = "area") on projected input can differ from
before, and are the same in every row order. The help for the `area`
argument of summarize_by_cell() says how the cells are sampled, and
NEWS has an entry under Bug fixes. No default or exported contract
changes, so there is no Breaking changes entry.

No frozen fixture record is retaken and no existing test is turned
round. tests/testthat/test-fix-crs-polygon-order.R checks the CRS, the
area error and the refusal over several row orders, rings started from
another vertex, shared borders, cells that share a bounding box, and a
far cell at the edge of the layer.
build_tessellation(crs = 4326) builds the cells of the scored centres
in the profile's projected CRS and transforms them. On gridded
locations four centres can stand the same distance from one point, and
two cells meet there at two corners less than a nanometre apart. The
transform to degrees puts them at one point, s2 refused the cell, and
assign_features_to_polygons() stopped with "duplicate vertex" on rows
in lon/lat. On 16 lattices with rows in EPSG:4326, 27 of 32 paths
under the two kinds stopped, and 3 of the other 5 put rows on an edge
in another cell than the profile's.

When the polygons are in lon/lat and their count record says a
profile scored them and holds its centres in a projected CRS,
assign_features_to_polygons() moves the join's copy of the features
and the polygons to the record's CRS and joins there. The rows
returned keep the coordinates they arrived with. Projected cells are
joined in their own CRS as before, and lon/lat cells without the
record, or with scored_cells = FALSE, still go to s2.

On such cells every row lands in the cell the profile scored it in, a
point on an edge goes to its nearest centre's cell, and the check in
summarize_by_cell() reads "match" on all 32 paths. Features that are
not points get overlaps and areas measured in the projected CRS, rows
without a CRS join instead of stopping, and cell_values_at() without
seeds places locations there too. The help for
assign_features_to_polygons(), summarize_by_cell(),
create_voronoi_polygons() and build_tessellation() says which cells s2
can still refuse, and NEWS has a Bug fixes entry.

In the test "only points on cells with the scored centres in a CRS the
join can measure are placed by the profile's rule", the meridian
squares with a record in EPSG:32617 now expect the nearer centre's
cell for all five points in place of the sphere's answer, and the six
points and squares in lon/lat with that record now expect the
profile's rule on the points' coordinates in EPSG:32617. No frozen
record is retaken.
The seeding functions attach the convex hull of the points they cluster
as attr(, "cover"), and .cover_follow() moves it into the cells' CRS
with its edges read as straight lines in degrees. For lon/lat points
.cover_of() built the hull with s2, whose great-circle edges bow toward
the pole. Over the box of the conterminous United States one of 800
points lay 130 km outside the moved cover against a 127 km margin and
fell in no cell at 44 and 178 seeds. Round a pole the hull held none of
the seeds and was set aside with spatialkit_no_cover (230 of 600 points
in no cell at 10 seeds), round the globe it was the whole sphere, and
for points on both sides of 180 degrees it ran the long way round.

.cover_of() now takes the planar hull of the bare coordinates and sets
the layer's CRS on it after. For lon/lat points the new
.lon_narrowest() reads the longitudes from -180 to 180 or from 0 to
360, whichever spans fewer degrees, so points on both sides of 180 get
a hull whose longitudes run past 180 (-170 as 190). .cover_follow()
moves such a cover as it is into a CRS that joins the two sides of 180,
as tested by the new .joins_across_180(). Into a CRS that splits the
map there, as Web Mercator does, the new .cut_at_180() cuts it at 180
and moves the far part back by 360 degrees, before the edges are
densified, so the edges along 180 follow a curved 180th meridian in
Mollweide or Equal Earth.

On these layers every observation has a cell, the summary's check
reads "match", and between 170 E and 170 W the cells on 10 seeds go
from 11 times to 1.3 times the points' hull. Cells on seeds placed from
lon/lat points can differ at their outer edge, and differ throughout
where the cover was set aside or ran the long way round. Layers in a
projected CRS, seeds drawn in a boundary and the hull a profile keeps
are unchanged. ?get_voronoi_seeds and ?voronoi_seeds_kmeans describe
the cover, and NEWS has a Bug fixes entry; nothing goes under Breaking
changes.

Ten records in core-frozen.rds are retaken, in value only, with and
without FNN and Matrix: seeds_lonlat, seeds_lonlat_density,
seeds_lonlat_selection, kmeans_lonlat, kmeans_lonlat_density,
cells_lonlat, cells_lonlat_selection, values_lonlat,
values_lonlat_variogram and values_lonlat_area. Their cover has the
same vertices in another order, and the cells move by at most 2.2e-5 m.
The test "cells returned in EPSG:4326 are joined in the profile's CRS:
rows in degrees on the sixteen lattices ..." in
test-core-gridded-ties.R is turned round: s2 now refuses a cell on 10
of the 16 density-kind lattices, where it was 11. New tests are in
test-fix-cells-cover.R.
With `what = "loo"` (the same rows in the same number of cells, another
leave-one-out error), the spatialkit_cells_differ warning from
summarize_by_cell() said that rows exactly on a boundary between two
cells (gridded locations) can be assigned to the other cell.
assign_features_to_polygons() gives such a row the cell the profile
scored it in on the scored cells that carry their count record, kept in
the CRS the profile measured the rows in. The sentence named a cause
that no longer applies there and left out the ones that do.

The closing sentence now names those cases: rows the polygons alone
placed (rows without a CRS, rows assigned to cells without their count
record, such as cells read back from a file, and rows given their cells
by another join than assign_features_to_polygons()), cells built in, or
transformed to, another CRS than the one in the count record, cells
that are not the Voronoi cells of the scored centres (seeds moved,
cells edited), and coordinates that reached that CRS by another
transformation. The `what = "cells"` sentence is unchanged.

The condition's class, its fields and every result are unchanged.
?summarize_by_cell names the same cases, says that cells transformed to
another projected CRS are joined there, and that on a layer wider than
a UTM zone the round trip of cells returned in a geographic `crs` can
move a corner past the join's margin. NEWS has a Bug fixes entry, and
nothing goes under Breaking changes.

No frozen fixture record was retaken. Two tests were turned round:
"two rows exchanged between neighbouring cells are told from rows
dropped" in test-core-count-rule.R pinned the old sentence and now pins
the new one, and checks that a cell moved whole into its neighbour
reads `what = "cells"`; the else branch of "on gridded locations the
check says match exactly when no row was placed in another cell" in
test-core-pipeline.R pinned the old cause and now pins the
polygons-alone clause. New tests in test-core-gridded-ties.R cover
cells drawn in a projected CRS the caller names, rows the polygons
alone place, cells transformed to EPSG:5070 and 3857, and the help and
NEWS text.
new_spatial_fit() checked only that subclass was one string and built a
fit from any data_sf, formula, response_var, predictor_vars and info. A
column number or a factor as response_var made summary() and
model_metrics() score the fitted values against the column at that
position, since a factor indexes by its code. A misspelt name, the
geometry column's name or a list as data_sf failed later in another
function with a message about something else, a data_sf without
geometry made residual_morans_i() blame a missing residuals() method,
and a data.frame as info stopped with "replacement has 0 rows".

The constructor now stops, without a call and with a message that names
the argument and what it got, unless subclass is one non-empty class
name, data_sf an sf object, formula a formula, response_var one string
naming an attribute column of data_sf, predictor_vars a character
vector of attribute columns (character(0) for none) and info a list.
The geometry column, the one sf names whatever it is called, is refused
as the response or a predictor with a message of its own. NULL as info
is still taken as list(). The response's type is not checked, since a
Bayesian fit can have a factor or ordinal response; summary() and
model_metrics() refuse a non-numeric one as before.

NEWS has a Breaking changes entry, and ?new_spatial_fit says what the
constructor checks and what each argument must be. A fit_fn that passed
st_drop_geometry(train_sf) has to pass train_sf, and a brms::bf()
formula is passed as its $formula. A fit that names a column the
training rows lack fails every fold of cv_spatial() with the
constructor's message in fold_status. In select_features_forward(), a
fit_fn whose fit for no predictors is refused (predictor_vars = NULL,
say) gets the training-fold mean as its baseline, and baseline$type
says "fold mean".

In "new_spatial_fit builds the documented structure" in
test-spatial-fit-s3.R, the expect_error() for subclass = c("a", "b")
now matches "`subclass` must be one class name", and the check that a
cache passed in info is kept, which held only because testthat's
edition 3 finds two empty environments equal, now expects the fresh
cache the constructor puts in its place. "area_of_applicability takes
training data from a fitted model" gives its stand-in fit the response
column z it named. test-fix-new-spatial-fit-args.R pins each refusal
and what still builds. No frozen fixture record is retaken.
With seeds, cell_values_at() fills a location whose cell has no row in
values from its nearest scored row. When no seed carried the ID of a
cell of values (an ID column from elsewhere, the index of another
tessellation, or IDs that are all NA), every location was filled and
nothing was raised: on a layer of 107 cells, seeds numbered 1001 to
1107 filled all 120 locations asked for, and 18 values differed from
those of the locations' own cells. A values with no cell ID gave every
location NA. Seeds without a CRS beside points with one stopped with an
error that named no CRS, though get_voronoi_seeds() returns seeds
without one for rows in lon/lat without a CRS.

cell_values_at() now compares the seeds' IDs with those of values
before any CRS work or search, and stops when none matches. The error
names the seeds' ID column and says to set it from the tessellation's
index; IDs that are all NA get a text of their own. When values carries
the cell polygons and some seed ID is NA, the check runs after seeds
outside every polygon take the cells they generate, so points placed by
hand outside the boundary still run. A values with no row or only NA
IDs is an error that names values. One matching seed is enough.

Seeds without a CRS beside points with one are read as
build_tessellation() reads a boundary without one, with a plain
warning. Beside a projected CRS, coordinates that look like lon/lat are
taken as EPSG:4326 and transformed, and others are given the CRS of
points. Beside a geographic CRS, coordinates inside the lon/lat ranges
are given it, and others are an error that names the CRS the summary
measured in. newdata without a CRS is still refused. The help for
cell_values_at() and two Bug fixes entries in NEWS describe both.

In "what `seeds` and `values` must be", a values of no rows with seeds
now expects the values error in place of every value NA. In "`newdata`
without a CRS is refused beside rows with one, and runs beside rows
without", seeds without a CRS now expect one warning and the values of
the call with the CRS set. In "a location the same distance from two
scored locations takes the lower x, then the lower y", the lone seed
with an NA ID gains a second seed at (1e4, 1e4) with the ID of cell 1;
every expectation is as it was. No frozen record is retaken.
Under select_on = "split", resolution_profile() scores the estimate on
the rows of the selection half that have a response, and the check that
estimate has rows to read is made on the layer. A layer whose rows with
a response all lay in the estimation half passed it, under a named
estimate and under "auto", which became "pooled". The profile returned
loo and the estimate's other columns NA at every level, as were rss,
cp, moran_i and moran_z, and said nothing. Its print said "fewer than
two rows", the count record's print read "in NA cells",
select_resolution(criterion = "loo") said loo needs a cell that holds
two rows with a response, and summary() said "cp needs a response".

The columns stay NA. After the levels are scored, resolution_profile()
raises one warning of class spatialkit_no_scored_rows that names the
NA columns and counts the rows of the selection half and the rows with
a finite value, all in the estimation half. The condition carries
reason, n, selection, n_response and column. A call a guard stops
raises none. Both prints now read "loo NA: no row of the selection half
has a response", select_resolution(criterion = "loo"), the criterion a
profile of plain means is read by, stops with that reason, and
summary() of the profile names the selection half in its error. The
other criteria keep their own refusals.

No number, default, column or result class changes, and NEWS has one
Bug fixes entry. The help for resolution_profile() (estimate, Value,
Conditions) and select_resolution() says when the columns are NA and
why.

The new test-fix-split-no-response.R pins the warning and its fields,
on halves of different sizes too, the prints, the refusals of
select_resolution() and summary(), and the calls that raise no warning.
No test is turned round and no frozen fixture record is retaken.
fit_bayesian_spatial_model() merged `control` over list(adapt_delta =
0.9, max_treedepth = 12). At 0.9 ordinary data left a few divergent
transitions, and one is enough to set `convergence_ok` to FALSE and
raise spatialkit_convergence: four layers of 250 points over a smooth
field with a 60 km range, fitted at the default four chains of 2,000
iterations from three seeds each, had 1 to 24 in every fit. The
warning's advice printed adapt_delta with %.2f, so 0.975 read 0.97,
0.995 read 0.99 and 0.999 read 1.00, and a fit raised to 0.995 was told
it ran at 0.99.

The defaults are now one list, .bayes_control_default, with adapt_delta
0.99 and max_treedepth 12, and `control` is still merged over it. The
four layers had no divergent transition at 0.99. cv_bayes(),
compare_models_cv() and every other call that leaves `control` unset
take the new default. The advice prints adapt_delta with %.15g, which
follows neither options(digits) nor options(OutDec). The `control`
entry of ?fit_bayesian_spatial_model gives the measurement, the cost,
the way back and 0.995 or 0.999 for divergences that remain at 0.99, as
on a weak spatial field; `check_convergence` says its R-hat figures
come from fits at 0.9 or 0.95; and the example says Stan may warn about
effective sample size.

Sampling takes about two to three times as long, and a fit at a given
seed draws other values, so its estimates, intervals and predictions
change. `control = list(adapt_delta = 0.9)` restores the previous
setting. NEWS has a Breaking changes entry, and the README says its
recorded compare_models_cv() timing was taken at 0.9.

No frozen fixture record was retaken. Four pins were turned round: "a
supplied control list keeps the package's adapt_delta default" in
test-bayes-smoke.R expects 0.99 after a partial `control`; in
test-fix-bayes-convergence.R, "divergences and low E-BFMI fail;
tree-depth saturation is recorded only" and "several failures arrive as
one warning" expect "(now 0.99)" at the default, and
".bayes_convergence_advice() gives the remedy for the checks that
failed" expects "(now 0.9)" for an explicit 0.9. New tests check the
merged control, the advice under options(digits) and OutDec, and the
help and NEWS text, and test-fix-bayes-default-control.R (opt-in with
SPATIALKIT_TEST_BRMS) fits the four layers at the default and expects
no divergent transition.
estimate_sac_range() held its floor of 30 against rows. Rows that share
a location are one place to the variogram and to its test for spatial
structure, which runs on the location means. Five locations visited 20
times passed the floor, got a variogram of ten pairs of locations and a
structure test on five means, which called most such layers
unstructured even where the range was three times the extent, and
summarize_by_cell(deff = "variogram") then gave a design effect of 1 in
every cell.

After the n_max subsample and before the detrending, the function now
counts the distinct locations of the rows the variogram is computed on
with .sac_locations(), where two rows are at one location when both
coordinates are equal to the last bit after the projection. Below 30 it
logs a WARN line, fits nothing and returns the unclassed NA whose
rejected_reason, from the new .sac_reason_few_locations(), gives the
count: "<k> distinct location(s) among the <n> point(s) with a finite
value to model, fewer than the 30 a variogram range is estimated from".
When the layer has 30 or more locations and the subsample kept fewer,
the counts are the subsample's and the reason says "of the n_max
subsample". The check runs again after the rows with no finite value
to model are dropped. With detrend = "reml" no REML fit is tried and no
REML warning is raised, and the warning that the variogram is fitted to
the raw response comes only once the floor has passed.

A layer of 30 or more rows at 2 to 29 locations, which 2.0.0 fitted,
gets the NA (Breaking changes in NEWS): the range is NA,
summarize_by_cell(deff = "variogram") returns Kish standard errors with
spatialkit_deff_fallback, make_folds(auto_range = TRUE) builds
geometric blocks, kriging_adequacy() stops with the reason, and the
Bayesian fit's check before fitting reports the range as not estimated.
A layer whose rows all lie at one location gets this reason in place of
"the points have no extent". The help of estimate_sac_range() states
the floor under Details, predictor_vars, n_max, seed, detrend and
Value, and NEWS has Bug fixes and New features entries as well.

test-fix-variogram-locations.R is new. Two tests are turned round:
"estimate_sac_range()'s early NA returns carry a rejected_reason and
nothing else" in test-review3-final-B.R expects the location reason for
40 rows at one location, and "the REML detrending on fewer than 30
locations is said not to be fitted" in test-fix-texts-misstated.R
expects no REML warning and the location NA for 40 rows at 20
locations. No frozen fixture record is retaken.
On a lattice two to four locations are equally near. .sac_short_lag()
paired each location with its nearest other one through FNN's kd-tree or
the base-R search, and the two took different ones, so short_lag_pairs,
short_lag_semivariance, Cp's noise variance and the `cp` column differed
between a session with FNN and one without (80 pairs against 143 on a 12
x 12 lattice). The pooling's searches, .region_neighbours() and
.nearest_location_search(), counted exact ties alone, and lattice
centroids that tie in one unit differ in their last bits in another or
after a shift of the origin. The same layer in metres and in feet gave
some cells other neighbours, and since `tau2` is fitted over the layer
every pooled value of summarize_by_cell() moved, and so did the
leave-one-out error of resolution_profile().

Squared distances within one part in ten million (.TIE_TOL) of the least
are now a tie. A tie goes to the lower x, then the lower y, coordinates
within .TIE_TOL times the distance counting as equal, then to the lowest
number: .knn_exact_rows() and .knn_tied_rows() take `tol` and `num`, and
a new .knn_pick_rows() applies the rule. The partition passes neither
and keeps exact ties. .region_neighbours() passes the region's own
number, so regions whose centroids coincide stay told apart by number
when rounding parts them, and the FNN paths widen their shortlist
margins by .TIE_TOL. .sac_short_lag() puts the locations in coordinate
order, takes each one's nearest other location from
.nearest_location_search() with FNN or .knn_exact_rows(tol = .TIE_TOL)
without, and computes the separation in R.

On gridded layers a session with FNN now gets the short-lag pairs and
`cp` values the search without FNN gave where ties were exact; on a
square lattice the pairs run along x. summarize_by_cell() gives the same
neighbours and pooled values in another unit or origin while the spacing
is above about a ten-millionth of the coordinates. Where three or more
cells' centroids coincide the values agree to rounding, and where ten or
more do, which 8 each takes can still change. ?summarize_by_cell and
?sac_nugget state the rule, and ?resolution_profile now says the density
kind's cells can differ in another unit or origin, since the partition
counts exact ties alone. No default, class or error changes. NEWS has
two Bug fixes entries and nothing under Breaking changes.

Nine frozen records are retaken, each to the record the fixture held for
its call without FNN: profile_lattice, profile_lattice_density and
profile_integer (value, conditions, printed, log: short_lag_pairs 80 to
143, the semivariance and cp_noise 0.1507 to 0.1257 on the lattice and
15.18 to 12.81 for profile_integer, the `cp` column, the print's
variogram line and the two variogram warnings), and select_lattice_cp,
summary_lattice, select_lattice_density_cp, summary_lattice_density,
select_integer_cp and summary_integer (value). Turned round in
test-core-pool-properties.R: .pt_neighbours() and .pt_nearest_other()
write the rule with the tolerance and the number through a new
.pt_pick(), test 13 holds squared distances nearest first to within
.TIE_TOL, the spies on .knn_exact_rows() and .knn_tied_rows() pass `...`
on, and the kd-tree stand-ins group candidates within .TIE_TOL (was
1e-9). test-fix-neighbour-ties.R is new.
.no_spread() compared a column's sums of squares with the size of its
values. So 1e15 plus a spread of 1, whose values are stored to an
eighth, and 1e6 plus 1e-9 times a spread of 1 were called constant, and
so were values near 1e200 or 1e-170, whose squares overflow or
underflow, whatever their spread. summarize_by_cell() then said their
values "do not vary inside any cell" and returned the plain means with
weights 1, and resolution_profile() left `loo` NA. The standard error
of a leave-one-out error squares the squares, so `loo_se` was Inf for
residuals above about 1e77 and lost its precision, then read 0, below
about 1e-80.

.pool_fit() now reads no spread twice: on the raw values, and on the
column centred on its median and scaled by powers of two
(.spread_scaled()). A column has no spread only where neither reading
finds any. On the scaled column a standard deviation, over all the rows
or inside the cells, of at most .POOL_ULPS (3) units in the last place
of the largest absolute value is rounding (.scaled_rounding()), so a
column whose values differ only by their rounding (0.1 beside 0.3 / 3,
2 beside sqrt(2)^2) is constant as before. Where the raw values lose
their spread and the scaled column keeps it, the fit runs on the scaled
column with the column's own readings and is put back in its units
(.pool_unscale()). A column that is not constant, with an absolute value
above 1e150 or a largest distance from its median below 1e-150, is
refused with the status "values out of range". .loo_mean_square()
divides residuals whose largest lies outside 2^-255 to 2^255 by a power
of two before it squares them, and inside those bounds gives the
formula's numbers bit for bit. resolution_profile() reads its estimate's
columns by the same test (.constant_column()); its test of a constant
response for `cp` still reads the raw values alone.

summarize_by_cell() pools 1e15 plus a spread of 1, with the weights,
`sigma2`, `tau2` and leave-one-out errors of the spread alone. The
degenerate warning says the values do not vary inside any cell "to the
precision of the values". A column beyond the bounds gets NA pooled
columns and a spatialkit_pool_unavailable warning whose `reason` is
"values out of range"; at 800 rows, 1e150 to 1e152 and 1e-151 to
1e-160 times a spread of 1 were fitted before and are now refused.
`loo_se` is finite inside the bounds, in the record and in
resolution_profile(). The profile scores `loo` for 1e15 plus a spread
of 1; where the test for `cp` calls the response constant and the
estimate's test does not, the warning, print() and
select_resolution(criterion = "cp" or "moran_z") say what each test
found and that `loo` is scored. Beyond the bounds the profile raises
one spatialkit_pool_unavailable warning, which names `cp`, `moran_i`
and `moran_z` too where the test for `cp` calls the values constant,
and print(), summary() and select_resolution() say the values are out
of range. NEWS has a Bug fixes entry, and the Breaking changes list of
reasons for spatialkit_pool_unavailable adds values beyond what the
arithmetic of pooled values holds.

No frozen fixture record was retaken. Four tests were turned round:
"11. the rule for no spread is relative to the size of the values" in
test-core-pool-properties.R, now "11. the rule for no spread does not
depend on where the values sit or on their size", expects 1e6 plus
1e-9 times noise to be fitted as the noise; "a constant response has
no loo, and the warning names it among the columns that are NA" in
test-core-count-loo.R expects that response constant to the test for
`cp` and scored by `loo`; "ten constant predictors raise one
spatialkit_pool_degenerate, six of them named" in
test-core-pool-summary.R holds the new warning text; and "a response
that is constant to within rounding scores nothing, as the message
says" in test-fix-resolution-usability.R expects select_resolution(p,
"cp") to say what the test for `cp` found, and holds the old error on
a profile with `estimate = NULL`. test-fix-pool-extreme-scales.R tests
the two readings, the rounding bound, the fit of the scaled column, the
bounds and their warnings, the standard errors and the profile's texts.
.crs_distance_error() adds the outline of a layer with fewer than 40
features and, when the outline has fewer than 40 distinct vertices,
fills each of its edges with points. An edge two features shared was
filled once from each end, some copies of an added point differed in
their last bits, unique() kept both, and the pair, about 1e-9 m apart,
set the worst-case ratio of every candidate CRS. Two lon/lat boxes of
12 by 15 degrees, one above the other, scored 0.7692 in EPSG:32613 and
0.8682 in EPSG:5070, where their outline as one box scores 0.0051 and
0.1087, and ensure_projected() kept UTM zone 13 with "76.92% vs
76.92%". A 2 by 2 grid of 3.7 by 2.9 degree boxes scored 0.2715 and
0.0965 against 0.0066 and 0.0098, and moved to a Lambert azimuthal CRS
with "11.22% against the zone's 13.50%" where its outline gives 0.06%
and 0.26%. Two boxes side by side scored 0.3179 and 1, and two boxes
one above the other across the antimeridian scored 0.7923 in a Lambert
azimuthal CRS on 180, where their outline scores 0.0025. The same held
where an added point fell on a corner of another feature (a T-junction,
or a corner touching an edge) or on a point added along an edge that
overlaps it in part: a T of boxes at (-111.7, 28.3) scored 0.5605 and 1
against its union's 0.0049 and 0.0069.

.crs_distance_error() now fills each edge from the end with the smaller
longitude, or the smaller latitude when the longitudes are equal, so
the two copies of a point on a shared edge are computed the same way
and are the same double, whichever way each ring runs. The added points
are sorted by coordinate and put after the vertices, and an added point
is left out when it shares a cell of 1e-9 degrees (about 0.1 mm) with
an earlier point, the cells in each coordinate taken from two grids
half a cell apart (round() and floor() of the coordinate times 1e9), so
copies a few bits apart are never kept either side of a cell's border.
The vertices are kept as they are. The comments and the internal doc
of `x_ll` say why.

The layers above score within a tenth of a percentage point of their
outlines, and the log of ensure_projected() quotes the outlines'
figures. A layer on which no added point lies within 1e-9 degrees of
another point scores as before, but for the last bits. For the others
the CRS, attr(, "crs_choice") and the logged figures can differ from
before, and the score no longer depends on the direction of a ring or
the order of the features. No default, class or error changes. NEWS
has a Bug fixes entry and nothing under Breaking changes.

No frozen fixture record is retaken, and no test is turned round.
test-fix-crs-shared-edges.R tests shared edges, an edge across the
antimeridian, ring direction, feature order and ring start, the
figures ensure_projected() logs, corners on another feature's edge,
points either side of a half step of the 9th decimal, and pins the
scores of layers with no added point on another feature's point.
.crs_area_error() took the geodesic areas of all its probes in one call
on their copy in EPSG:4326. Two corners a fraction of a nanometre apart
in the cells' CRS can fall on one point in degrees, where s2 refuses the
ring ("duplicate vertex"), and a vertex listed twice, which GEOS
accepts, does the same; one such probe made the call fail and the
figure NA for the whole layer. The k-means cells of a 30 by 20 lattice
at 30 m, built and kept in EPSG:32615 from rows in degrees, are all
valid in EPSG:32615 and s2 refused 4 of the 133, so
summarize_by_cell(area = TRUE) stopped because "the area distortion of
`cells_sf`'s CRS could not be measured". ensure_projected(purpose =
"area") skipped its check on projected input in silence the same way:
20 cells of 1 degree from 30N to 69N in Web Mercator, which distort
areas by 214 percent, logged nothing when one cell had such a pair of
corners.

When the one call fails, .crs_area_error() now takes the geodesic
areas one probe at a time, and a probe s2 refuses is measured again
without the vertices that repeat the one before them in degrees. A new
helper, .drop_repeated_vertices(), drops them from each ring of a
POLYGON or MULTIPOLYGON, holes included, comparing x and y alone, so
the ring's points and its area stay as they were. A probe s2 refuses
even then, such as one whose edges cross or one with a spike, makes the
figure NA as before: leaving such probes out of the spread could leave
out the ones that carry the distortion.
st_make_valid() is not used: it snaps vertices to 1e-7 degrees and on
cells a few metres across moves the ratio by more than the tolerance.

The lattice's k-means cells now get a density, with attr(, "area_error")
the spread over all 133 cells (below 1 percent). For the Web Mercator
cells ensure_projected() logs the warning, and summarize_by_cell(area =
TRUE) refuses them for their distortion, no longer because it could not
be measured. A layer whose probes s2 all accepts gets the same figure
as before, to the last bit. A cell s2 refuses for another reason still
leaves the spread unmeasured, and the request is refused as before.
?summarize_by_cell says how a cell s2 refuses is measured and when the
request is still refused. No default, class or error changes. NEWS has
a Bug fixes entry and nothing under Breaking changes.

No frozen fixture record is retaken, and no existing test is turned
round. test-fix-crs-area-refused-probe.R tests the lattice's k-means
cells with rows in degrees and in EPSG:32615, the one-call figure kept
where s2 accepts every probe, cells with a corner beside another or a
repeated vertex in Web Mercator, UTM and a Lambert conformal conic
layer of 300 cells, multipolygons, holes and z coordinates, the log of
ensure_projected() and the error of summarize_by_cell(), and the NA
for a bowtie or a spike.
assign_features_to_polygons() joins rows to lon/lat cells on the sphere
with s2 unless the cells' record names the projected CRS a profile
scored them in. Cells returned in lon/lat can hold two corners that
differ only by rounding and fall on one point in degrees, which s2
refuses. Three kinds of cell still went to s2: cells that had lost their
record, cells whose record says `scored_cells = FALSE`, and cells
without a CRS, which take the rows' EPSG:4326. On them the plain join
stopped with s2's "Loop 0 is not valid: Edge 20 is degenerate (duplicate
vertex)", an error with no class that named neither the polygon nor a
way on. s2 refuses 4 of the 133 k-means cells of a 30 by 20 lattice at
30 m returned in EPSG:4326; with their record the cells joined 600 rows,
and without the record or the CRS the join stopped. The cells of centres
refitted to every point on a 50 by 40 lattice at 250 m stopped it the
same way. A hole that crossed its outer ring and two holes that shared
an edge stopped it with s2's bare message too. The largest-overlap join
wrapped a refusal that outlived its repair in advice to pass
`largest = FALSE`, which meets the same refusal.

The plain join now runs inside withCallingHandlers(), and the
largest-overlap join's handler calls .stop_cells_degenerate() before its
own message. That helper acts when the error names a ring ("Loop N" or
"loop orientations"), the polygons are in lon/lat, sf_use_s2() is on and
sf::st_is_valid() reads at least one polygon as invalid; otherwise it
returns NULL and the error is raised as before.
.raise_cells_degenerate() builds the error of class
spatialkit_cells_degenerate. It gives the number of polygons s2 refuses
and their rows (six at most, then "and N more"), quotes the first line
of s2's message, names the cause of two corners read as one point where
s2's reason is "duplicate vertex", and says to transform `polygons_sf`
to a projected CRS, such as the one the cells were built in or the one
ensure_projected() picks, and assign again; for polygons without a CRS
it says to set the CRS with sf::st_set_crs() first. The condition
carries `rows`, `n`, `reasons` and `parent` (s2's error) and no call.
cell_values_at() without `seeds` catches the class around its join and
raises it again through .raise_cells_degenerate() in its own words: it
names `values` and the placement of the locations, gives the rows of
`values` (rows without an ID are not handed to the join), counts "the N
polygons of `values` that carry an ID" when some lack one, and says to
call cell_values_at() again.

A join on polygons s2 refuses now stops with spatialkit_cells_degenerate
where it stopped with sf's bare message or the advice to pass
`largest = FALSE`. Polygons s2 accepts join as before, the
largest-overlap join still repairs the polygons it can, with its
warning, and runs, and an error of another kind keeps its own message.
No default changes. The Details of ?assign_features_to_polygons, step 3
of ?cell_values_at and `crs` in ?create_voronoi_polygons name the error.
NEWS has a Bug fixes entry and nothing under Breaking changes, since
every call that now raises the class stopped before.

No frozen fixture record is retaken and no test is turned round.
test-fix-join-cells-degenerate.R tests the refused squares and the rows
the message gives, the join in degrees with s2 off, a bowtie, squares
without a CRS, an error of another kind and the predicate's own
condition, the largest-overlap join with a mocked repair, a crossing
hole and two identical holes on both joins and after the transform, a
hole that runs the way its outer ring runs, the lattice's cells without
their record or CRS, cell_values_at() on the lattice summary with and
without its record, an ID or a CRS, the refitted centres' cells, and
the help and NEWS text.
Under the model-aware criteria determine_optimal_levels() re-runs
k-means at each count of the window around the elbow, the elbow plus or
minus max(4, `top_n`). The re-runs drew from one random stream in
order, so a `top_n` above 4 started the window lower, more re-runs came
before each count, and every count in the window got other cells, and
with them another `wss_eval`, `moran_i` and `moran_z`. On 480 points in
twelve clusters with `max_levels = 30` and `set_seed = 123`, "morans_i"
put 15 first at `top_n` 1 to 4, 16 at 5 and 17 at 6 and 7, though 15
and 16 lie in all of those windows, and k = 17 had a |z| of 0.0013 at
`top_n = 6` and of 0.30 at `top_n = 7`. Of the 21 pairs of calls at
`top_n` 1 to 7, 30 columns differed at a shared count under "morans_i"
and 45 under "combined", whose ranking stayed while its diagnostics
moved.

After the sweep the function now draws one integer, `rerun_seed`, from
1 to .Machine$integer.max - k_max, and runs the re-run at k
(.partition_centres()) under .with_seed(rerun_seed + k), putting the
stream back after each re-run. The integer depends on `set_seed`, or
with `set_seed = NULL` on the caller's stream.

With the other arguments fixed, a count gets the same cells, `wss_eval`
and Moran's I whichever `top_n` sets the window. A wider window still
scores more counts, and under "morans_i" one of them can rank first: on
the same layer the first count is 15 at `top_n` 1 to 4, 17 at 5 and 18
at 6 and 7, each the count of smallest |z| in its window. For a given
seed `moran_i`, `moran_z`, `wss_eval` and the "morans_i" ranking can
differ from before, and a "morans_i" call can fall back to the
geometric ranking where it ranked, or rank where it fell back; the
sweep, the elbow and the "combined" ranking are as before. With
`set_seed = NULL` the re-runs take one draw from the caller's stream in
place of the draws of every re-run, so the stream after the call no
longer depends on `top_n`. ?determine_optimal_levels says under `top_n`
that it sets the counts scored and that a count scores the same
whichever `top_n` puts it in the window, and its Details say each re-run
draws from a stream of its own. No default, class or error changes.
NEWS has a Bug fixes entry and nothing under Breaking changes.

No frozen fixture record is retaken: levels_morans and levels_combined
run at `max_levels = 8`, where every count of the window has fewer than
ten cells and the call returns the geometric ranking before any re-run
value is stored. No test is turned round. The new
test-fix-levels-rerun-seed.R checks that a count scores the same in
every window that holds it under both model-aware criteria, on twelve
clusters at two seeds and on eighteen clusters under `select_on` "all"
and "split"; that under "morans_i" a wider window moves the first count
only to a count it adds, with a smaller |z|; that the re-run at a count
starts from the same stream in every window; that the caller's stream
is left as it was and not read; that the re-runs depend on `set_seed`;
that with `set_seed = NULL` the scores and the stream after the call do
not depend on `top_n`; and that the help and NEWS say so.
summarize_by_cell(deff = "kish") skipped a column as one that does not
vary when its standard deviation was below .Machine$double.eps (about
2.2e-16), on all its values and again on the rows with a cell id,
whatever the size of its values. A column that varies, in units that
make its values of order 1e-16 or smaller, got an ICC of NA with the
reason "the column does not vary", no design effect and the uncorrected
standard errors, with no warning. On 800 points in 178 cells the ICC was
0.478 at a scale of 1 and of 1e-14 and NA at 1e-17, 1e-20 and 1e-100,
and the median standard error over the scale fell from 0.666 to 0.303.
Where only the rows with a cell id were that small, the ICC was NA with
"its values differ only on rows that fall in no cell" and the standard
errors were NA. A column near 1e200 had an ICC of NA with "the variance
decomposition is undefined". The standard deviation inside a cell,
behind ..sd_*, ..se_*, ..neff_*, ..df_* and ..ci_*, squared the raw
deviations. Above about 1e154 the squares overflowed, and in each cell
where they did ..sd_* was Inf and the standard errors and intervals NA,
under any deff and with no warning: 7 of 60 cells at 1e154 and all 60 at
1e155 and 1e200. At a spread of 1e-170 ..sd_* and the standard errors
were 0.

.estimate_icc() inside summarize_by_cell() now reads a column whose
standard deviation is below .Machine$double.eps or not finite again with
.constant_column(), the pool's test, and skips it only where neither its
raw values nor its values centred and scaled show spread beyond their
rounding. On the rows with a cell id, such a column with spread is
z-scored from .spread_scaled(), which divides by powers of two; the ICC
does not change under a shift and a scale. Where the standard deviation
is finite and at or above the floor, the raw arithmetic is as it was. A
new internal .cell_sd() returns sd(x, na.rm = TRUE) where that is finite
and at or above .POOL_Y_MIN (1e-150), and elsewhere the standard
deviation of .spread_scaled() of the values times its scale. The ..sd_
closure and .col_stats() call it. After the summarise, a cell whose
standard deviation is still Inf raises a warning of the new class
"spatialkit_sd_overflow", whose fields `columns` and `cells` give the
columns and the number of such cells of each. The internal documentation
of .constant_column() names the Kish ICC as a reader.

The 800-point layer now gives an ICC of 0.478 and a median standard
error over the scale of 0.666 at every scale from 1 to 1e-100, and the
ICC of the column near 1e200 is estimated. ..sd_*, ..se_* and the
intervals follow the unit of the column from 1e150 to 1e-310, under deff
= 1 and "kish", with no NA, no 0 and no warning. A column whose values
are all equal still has an ICC of NA and standard errors of 0. A column
whose standard deviation is below the floor and whose values differ only
by their rounding (0.1 beside 0.3 / 3) still reads "the column does not
vary", with standard errors of 0 in a cell whose values are equal and of
the size of that rounding in a cell whose values differ. A cell whose
spread is of order 1e308 still has ..sd_* of Inf and standard errors of
NA, now with the warning. No default or error changes, and the one new
class is that of the warning. ?summarize_by_cell gains a sentence on
columns that differ only by their rounding and a paragraph on ..sd_* at
any size and the new warning class. NEWS has two Bug fixes entries and
nothing under Breaking changes.

No frozen fixture record is retaken: the one frozen call that reaches
the new ICC reading, values_constant, has a column of one value that
.constant_column() keeps constant, and the raw standard deviation stays
sd(x, na.rm = TRUE), so no ..sd_* record moves. No test is turned round.
test-fix-cells-icc-small-units.R is new: the ICC and standard errors
over the scale do not depend on the unit, rows with a cell id in small
units beside rows with no cell id get the ICC, a column whose standard
deviation overflows gets its ICC, a column of one value or of values
equal to their rounding still does not vary, the standard deviation and
standard error inside a cell and the interval of a cell do not depend on
the unit, a cell beyond the largest double is named in the warning, and
a column just above the floor keeps the ICC of its raw values.
On gridded locations the density partition depended on the unit and
the origin of the coordinates. On a lattice many distances tie exactly,
and the last bits of the coordinates, which move with the unit and the
origin, settled the ties in four places: the neighbour searches of the
density weights and the nearest-centre search of Lloyd's iteration
counted exact ties alone; the first k-means++ centre was drawn from
weights equal but for their last bits, which sample.int() read as
different; the locations the random draws index were put in order by
first coordinates equal but for their last bits; and of two starts
whose sums agreed to rounding, the rounding chose. On a 40 by 30
lattice at 250 m the default resolution_profile() gave `loo` 0.2978 in
EPSG:26915, 0.3068 in UTM 15N in US feet and 0.3006 with the false
origin moved by 0.1 m, and `rss` 258.33, 270.72 and 258.80. A profile
that drew a subsample took its rows in exact coordinate order, so it
drew other locations after a change of origin: on a 50 by 40 lattice
with no response, wss as a fraction of the total was 0.6958 in metres
and in US feet and 0.6997 with the origin moved.

The density partition now uses the pooling's tie rule: squared
distances within .TIE_TOL (one part in ten million) of each other are
a tie. .knn_locations() passes `tol = .TIE_TOL` to .knn_tied_rows()
and .knn_exact_rows(), so a tie between neighbours goes to the lower
first coordinate, then the lower second, and its FNN margin widens by
.TIE_TOL. .nearest_exact() finds the least squared distance and then
the lowest-numbered centre within .TIE_TOL of it, in two passes, and
.nearest_centre()'s FNN re-check widens to .TIE_TOL. .wkmeanspp() draws
the first centre on the weights with runs of near ties given the run's
lowest value. A new .near_columns() counts first coordinates within
.TIE_TOL of the extent as one column, and a new .locations_by_column()
puts the locations of .density_kmeans() in order by column and then by
the second coordinate. .density_kmeans() keeps a later start only when
its sum is lower by more than .TIE_TOL. resolution_profile() orders
its rows by .near_columns(), then the second and the first coordinate,
the response and the predictors, under either kind.
.values_at_nearest() takes the same tie between centres and widens its
kd-tree shortlist by .TIE_TOL. .scored_edge_cells() searches every
point row placed in a cell, and for a row the rule gives to a centre
that ties with the strictly nearer centre of the cell holding it, the
margin adds the row's distance from the bisector of the two, measured
in the record's CRS and scaled to the join's CRS by the ratio of the
centres' separations; every other row keeps the margin it had.

The lattice above gives `loo` 0.3016 and `rss` 262.94 and the same
regions in all three frames, and a uniform layer gives the profile it
gave. The regions get_voronoi_seeds() and voronoi_seeds_kmeans() draw
under partition = "density" no longer move with the unit or origin
either. The cells are the same in another unit or origin as long as no
coordinate moves by more than about a forty-millionth of the distance
between neighbouring locations; coordinates rounded more coarsely, such
as a lattice at 250 m stored in feet to four decimals, can still give
other cells. On layers with near ties in distances, weights, first
coordinates or starts' sums the regions, and every figure drawn from
them, can differ from before for the same seed, under partition =
"kmeans" too where two first coordinates are that close. A row the
profile scores in the cell of the lower-numbered of two tied centres
is assigned there by assign_features_to_polygons() and placed there by
cell_values_at(), so summarize_by_cell() still reads `cells = "match"`.
?resolution_profile states the tie rules and the bound in place of the
sentence that the cells still differed in another unit or origin;
?assign_features_to_polygons names the tolerance and the wider margin;
?summarize_by_cell names the tolerance; ?cell_values_at names it and
says another unit or origin of the same CRS settles its ties the same
way; and the diagnostics vignette names the tolerance. No default,
class or error changes. NEWS has a new Bug fixes entry, the earlier Bug
fixes entry on tied neighbours loses its sentence that the partition
still counted exact ties, and the New features entry on the count
record names the tolerance; nothing goes under Breaking changes.

Fourteen frozen records are retaken. The lattice records
profile_lattice_density, select_lattice_density_cp,
select_lattice_density_moran_z_diagnostic, summary_lattice_density,
seeds_lattice_density and kmeans_lattice_density move with the tie
rules. profile_density_clustered, select_density_clustered_cp,
select_density_clustered_elbow and
select_density_clustered_moran_z_diagnostic, and under the k-means kind
profile_geometry, select_geometry_elbow, profile_geometry_sac and
select_geometry_sac_reliability, move with the new order: the
clustered layer has one pair of first coordinates 7.5e-8 of the extent
apart, which now count as one column. Turned round: "the kd-tree's
search and the exact search pick by one rule, and the partition keeps
exact ties" in test-fix-neighbour-ties.R, now "... counts near ties as
ties"; "tied neighbour distances are settled by the kd-tree as the
exact search settles them" in test-core-partition-properties.R passes
.TIE_TOL and expects the first tied locations in coordinate order; "the
nearest centre is the exact one where centres are nearly the same
distance away" in test-core-values-at.R, now "centres whose squared
distances agree to one part in ten million tie, and the lower number
takes the point", with a case whose first kd-tree shortlist ends inside
the tie; in test-core-gridded-ties.R the platform pins of moved rows
(the polygons alone move rows on 15 of the 16 lattices under the
density kind, not 11), of rows elsewhere, and of refused cells (s2
refuses a k-means cell on 15 of the 16 lattices, not 16, and a density
cell on 11, not 10), the row a projected CRS puts elsewhere, the round
trip through degrees and the cells of seeds moved by 1e-9; and the
platform pin of rows on an edge in test-core-pipeline.R, now 0 2 0 0.
The helper drop_in_own_crs() of test-fix-crs-area-refused-probe.R keeps
a ring closed where the vertex it drops closes it, and "the cells of
centres refitted to every point, returned in lon/lat, stop with the
error where s2 refuses one" in test-fix-join-cells-degenerate.R uses
the k-means kind, since s2 now refuses none of the density cells of its
lattice. test-core-gridded-ties.R gains "a point two centres tie for
goes to the lower-numbered one beyond the margin of an edge, and only
such a point". test-fix-partition-ties.R is new: the lattice in three
frames, the subsample, the bound in the help, the uniform layer, each
tie rule and the assignment of a tied row.
A cell whose centroid shared its point with 8 other cells or more took
the first 8 of them without a search, but .region_neighbours() found
the point by exact equality. In another unit, or with the origin moved,
rounding parted such centroids by a few units in the last place, and
the cells were then searched for among themselves by those distances.
Twelve cells of 1 to 12 rows on one point beside a 10 by 10 lattice of
one-row cells at 250 m gave 3 cells other neighbours in feet and in US
feet, and all 12 with the origin moved by 0.1 m. Since `tau2` is fitted
over the layer, every pooled value of summarize_by_cell() moved, by up
to 0.171 in feet and 0.208 with the origin moved, and so did the
leave-one-out error of resolution_profile() on the same cells. Rows
that carry a placeholder or a coarser unit's coordinates make such
crowds.

.region_neighbours() now finds points with the reach of a tie,
.TIE_TOL times the largest absolute coordinate, through a new
.shared_points(). Centroids that chains of pairs within the reach in x
and in y link are one point while they lie within the reach of one
another, and equal centroids are one point in any case. A chain that
spans further is linked again at half the reach, the chains still too
wide at a quarter, and so on; centroids left apart to the last fall
back to exact equality. The linkage is in .reach_chains(), which puts
each chain's centroids in squares of the reach's side counted from the
chain's own least x and y, and .chain_span() measures a chain along one
coordinate. A crowded point's centroids are ordered by region number,
and each takes the first 8 of the others. No new constant is added.

summarize_by_cell() gives crowded cells the same neighbours and pooled
values in another unit and with the origin moved by little against the
coordinates. On a point of nine or more centroids, centroids that lie
within the reach of one another without coinciding (about 0.46 m at
UTM northings) now take each other by ID, not by distance. The reach
follows the largest coordinate, so with the origin moved far, to the
corner of the data, such centroids can lie beyond it and their cells
then take the nearest. A layer whose centroids are equal or further
apart than the reach in x or in y gets the neighbours and values it got
before. ?summarize_by_cell states the rule and the far-origin limit,
and says values agree to rounding where three to eight centroids
coincide, not three or more. No default, class or error changes. NEWS
has a new Bug fixes entry, and the earlier Bug fixes entry on tied
neighbours narrows "three or more" to "three to eight" and drops its
sentence that with ten or more cells on one point which 8 each takes
can still change. Nothing is added under Breaking changes.

No frozen fixture record is retaken. One test is turned round in
test-fix-neighbour-ties.R: "regions whose centroids coincide to
rounding get the rule's neighbours on both paths" treats its triple of
regions 25, 26 and 50, a few units in the last place apart, as one
point, so at one neighbour region 25 takes 26 where the rule by
distance took 50; its reference fnt_neighbours() gains the reach and
the halving, and an expectation pins 25, 26 and 50 to 26, 25 and 25.
The new test-fix-neighbour-crowds.R checks crowds of ten or more in
metres, feet, US feet and with the origin moved by 0.1, with and
without FNN, a crowd beside centroids that chain on beyond the reach, a
crowd parted by rounding, summarize_by_cell() at UTM magnitudes,
centroids within the reach against centroids beyond it, the far-origin
case and its help and NEWS text, and .shared_points() against the rule
written out.
cell_values_at(seeds = ) checked that some seed has the ID of a cell of
`values`, and counted every seed whatever its geometry. A seed with an
empty point, or a coordinate that is not finite or lies beyond 1e150, is
the nearest seed of no location, yet it passed the check, and the
coordinates were read only as given, so seeds that leave the globe in
the working CRS (metres stamped EPSG:4326, a latitude beyond 90) passed
too. Seeds that kept their IDs and lost their points left every location
without a cell, and each location took the value of the cell of its
nearest scored row with no error: the seeds of a tessellation of 107
cells, every point emptied, filled all 120 locations asked for, and so
did the one seed with a cell's ID emptied among seeds numbered from
1001. A seed at a northing of 1e200 in EPSG:32618 came back finite in
EPSG:32617 and was searched where it landed. .values_at_free_cells()
took a cell as named by any seed with its ID, so a seed without a
location kept that cell from a point placed by hand outside the
boundary, and the 144 locations of that cell were filled.

cell_values_at() now reads the seeds' coordinates as given with
.values_at_xy() before any CRS work. The check counts only seeds with a
location that carry a cell's ID; the check that waits for step 3 needs a
seed with a location and an NA ID beside polygons in `values`, and reads
the seeds with a location in the working CRS. Once the seeds are placed,
a seed without a location as given has none there either, and when the
seeds with cells' IDs have a location as given and none in the working
CRS, the call stops before the free cells and before any search.
.values_at_no_seed_cell() takes `nowhere`, `crs` and `seeds_crs` and
returns the two new texts, and the first stop passes whether `values`
carries polygons, so seeds with no location and IDs that are all NA keep
the text for polygons. .values_at_free_cells() takes a cell as named
only by a seed with a location in the working CRS.

Such seeds now stop with an error, raised without a call and with no
warning for seeds without a CRS: "every seed in `seeds` with the ID of a
cell of `values` has no location", or, when they had one as given, a
text that names the working CRS and the CRS of `seeds` (or says it has
none) and points to sf::st_set_crs(). One seed with a location and the
ID of a cell is still enough for the call to run. A seed with a
coordinate beyond 1e150 is no longer searched where a move between CRSs
puts it. Beside a seed without a location that carries a cell's ID,
points placed by hand outside the boundary take their own cells. No
default or class changes. ?cell_values_at says which seeds count. NEWS
has a Bug fixes entry, and the entry for seeds whose IDs match no cell
now says one seed with a location and a cell's ID is enough; nothing
under Breaking changes.

No frozen fixture record is retaken; no cell_values_at() call is
recorded. One test is turned round in test-core-values-at.R: "`seeds`
without a CRS beside rows with one are read as build_tessellation()
reads a boundary without one" pinned the silent fill for seeds of which
none has a location, and now expects the error with and without their
CRS and no warning, and calls .values_at_bare_seeds() for the reader's
own rule. Two tests are added there, "seeds with the IDs of cells and no
location are refused, and one with a location suffices" and "seeds with
the IDs of cells and no location in the working CRS are refused, and
seeds that have one there run", and "points placed by hand whose IDs are
all NA outside the cell polygons of `values` take the cells they
generate, and the call is refused only when none does" gains a seed with
a cell's ID and an empty point or x = Inf. No test file is new.
fit_gwr_model() regresses a response at a large offset about its
centre, and residuals.gwr_fit() returns the residuals of the centred
response, which carry no rounding of the offset. The second OLS test of
.morans_ols_design() still allowed them the rounding of a regression at
the full offset: 10 eps times the offset, about 2 at 1e15. So a GWR at
a working bandwidth passed as an OLS fit, and residual_morans_i() with
`null = "auto"` took the residual null with `exact` TRUE. On 300 points
with z = 2 w + sin(x/2000) + cos(y/1500) + N(0, 0.5) and
fit_gwr_model(d, "z", "p", bandwidth = 30), offsets 0 to 1e14 gave the
randomisation null, E[I] -0.003344481605, z -2.80112244, p 0.00509252,
while 1e15 gave the residual null, E[I] -0.003200223252, z -2.80823692,
p 0.00498136; bandwidth 60 did the same (z 2.29550409 against
2.29111526).

A new internal helper in R/evaluation.R, .morans_fit_centre(), returns
the centre fit_gwr_model() took the response about, for a gwr_fit that
records a finite non-zero centre and keeps a residual column of the
data's length, which is when residuals.gwr_fit() returns the centred
residuals; for every other fit it returns 0. Test 2 of
.morans_ols_design() now puts the offset left after that centre on the
intercept of the rounding design. With a centre of 0 it runs the old
expression, so every other fit gives the same result to the last bit.

Users now get at every offset from 0 to 1e15, at both bandwidths, the
randomisation null with `exact` FALSE and offset 0's I, E[I], z and p,
and compare_models() gives the same Moran columns. A GWR at a global
window keeps the residual null with `exact` TRUE. The Details of
?residual_morans_i say the bound counts the offset the fit regressed
the response at. NEWS adds a sentence to the Bug fixes entry on
residual_morans_i() reading its response, design and residuals at their
origin and unit.

No frozen fixture record is retaken and no test is turned round.
test-fix-moran-gwr-centre.R is new, with 4 tests: bandwidths 30 and 60
at offsets up to 1e15 and -1e15, a response four times wider stored at
4e15, a global window at 0, 1e8 and 1e15, and the cases in which
.morans_fit_centre() returns 0.
estimate_sac_range(), make_folds(), resolution_profile(),
determine_optimal_levels() and fold_separation() dropped a row whose
geometry was empty or whose coordinates were missing, and kept a row
at Inf or beyond 1e150, or read it only after the projection or the
move into another CRS. On 300 points in EPSG:32617 with row 1 at
(Inf, 5e6) or (5e5, 1e200), estimate_sac_range() returned NA with "the
points have no extent", where the same row empty gives 35060.33; at
latitude 1e200 or 95 in EPSG:4326 it stopped with FNN's "Data include
NAs". make_folds(method = "block_kfold") stopped with "'to' must be a
finite number" on the Inf row and gave the 1e200 row a fold of its own
(299 and 1, against 59 57 67 58 58 for the row empty), random_kfold
put the row in a test fold, and cv_block_size_sweep() stopped in
make_folds(). resolution_profile() gave a WSS of NA at every level,
and with `sac` in EPSG:32617 a row at 1e200 in EPSG:32616 or 3857 was
moved to a finite point (WSS 9.98e11, against 3.12e11 for the row
empty); determine_optimal_levels() stopped with "missing value where
TRUE/FALSE needed". A prediction point at 1e200 in EPSG:32616 was moved
into EPSG:32617 and measured by fold_separation() ("prediction target:
60 point(s)" where 59 have a location) and by make_folds(method =
"nndm"), which gave 39363 training memberships against 39763.
make_folds()' probe of the rows read the layer as given: in EPSG:3857
a row at x = 1e200 made it wait without end on PROJ, and in a layer
without a CRS it widened the check's tolerance to 1e191, so folds
built on other data were accepted.

Two helpers in R/cross-validation.R, .located_rows() (not empty, finite
and within 1e150) and the phrase .UNLOCATED_WHAT, carry the test. The
bound is read on the coordinates as given, before the reduction to
points and before any move, and again after it.
estimate_sac_range() and make_folds() drop such rows before the
projection and log the rows that lose their location once projected
(a local .projected_located() in block_kfold, buffered_loo, nndm and
knndm). resolution_profile(), determine_optimal_levels(),
fold_separation(), .nndm_target() and .fold_sep_prediction_points()
empty them with .empty_unlocated() before the pointize and the move
and keep .located_rows() after it. resolution_profile() drops a row
that loses its location in the move into the CRS of `sac` with a
warning, where it stopped with "some points have no coordinates in the
CRS of `sac`", and boxes and balances the halves of the split again
from the rows each keeps. .fold_row_probe() empties rows without a
location, so the probe leaves them out as it leaves out empty rows.

Each case above now equals the same layer with the row empty
(35060.33; folds 59 57 67 58 58; row 1 in no test fold; WSS 3.11796e11
at the first level; 59 prediction points; 39763 memberships), and the
probe refuses folds from other data. The drop warnings and log lines
name these rows as points without a location, where they said "with
empty or non-finite coordinates". make_folds()' n_prediction_points no
longer counts an empty prediction point. Layers whose rows all have a
location give the same results as before. The help of the five
functions says so.

NEWS has a Bug fixes entry and nothing under Breaking changes. No
frozen fixture record is retaken. One test is turned round:
test-degenerate-geometry.R matches the new make_folds() drop text.
test-fix-unlocated-rows.R gains 14 tests, among them the probe in a
layer without a CRS and in EPSG:3857 and the split's halves after the
move into the CRS of `sac`.
ensure_stable_poly_id(make_valid = TRUE), the default, gave every row
of a layer in longitude and latitude to s2's rebuild when
sf::sf_use_s2() was on, valid or not. On nc.shp rows 1 to 8 in
EPSG:4326, create_grid_polygons(nc8, cellsize = 0.1, crs = 4326) (127
cells) and target_cells = 40 (27 cells), every row valid under s2 and
GEOS, no row came back unchanged: the corners moved by up to 6.96e-8,
6.22e-8 and 6.89e-8 degrees, and POLYGON rows came back MULTIPOLYGON.
With s2 off no corner moved. create_grid_polygons_cached(nc8, cellsize
= 0.1, crs = 4326) handed back cells 6.22e-8 degrees from those of
create_grid_polygons(), with their types not kept.

A new internal helper in R/stable-ids-cache.R, .make_valid_rows(),
takes an sf or sfc. On a layer in degrees with s2 on it gives
.safe_make_valid() only to the rows that s2's sf::st_is_valid(), or
GEOS's check on the bare coordinates, does not call TRUE, and returns
the other rows as they arrived. GEOS's check is read too because s2
calls valid a hole outside its shell, a part inside another part and a
ring of collinear points: a 0.5 degree square with a hole outside it,
taken to EPSG:32615, came back GEOS-invalid with an area of 2302.2 km2
and stopped st_intersection() with "unable to assign free hole to a
shell", where s2 off gives a valid 2494.9 km2; now both settings give
2494.9 km2. Projected layers, layers with no CRS and layers in degrees
with s2 off are repaired by GEOS on every row, as before.
ensure_stable_poly_id() repairs the rows it returns with the helper;
the repair of the copy the sort key is measured on is unchanged, so no
ID moves. create_voronoi_polygons() matches its returned cells to the
clipped cells through the same helper; the cells there are projected,
so its result is the same.

Users now get every valid row of the three layers above back identical
to its input, of its type, with s2 on, and the cached grid's 127 cells
match the builder's. The IDs equal those before on nc.shp's counties in
EPSG:4267 and 4326, the eight rows in EPSG:4267, three grids and a hex
tessellation in EPSG:4326, under both settings. ?ensure_stable_poly_id
(`make_valid`) and ?create_grid_polygons_cached (Details) state the
rule. NEWS has a new Bug fixes entry, and the sentence on the repair in
the entry on the cache key and the s2 setting now says the same.

No frozen fixture record is retaken and no test is turned round.
test-fix-stable-ids-valid-rows.R is new, with 8 tests: valid layers
under both settings, the pinned IDs, refused rows beside valid ones,
rows s2 calls valid and GEOS does not, projected and s2 off layers, the
Voronoi seeds in degrees, the cached grid and the help pages.
cv_block_size_sweep() stated its fits wrongly in four texts. With
block_sizes = seq(50, 230, by = 20) and k = 5, max_fits = 20 stopped
with "10 block sizes x 5 folds + 5 for the random reference = 55 model
fits ... or lower `n_sizes` or `k`", and n_sizes = 1 gave the same
refusal, since n_sizes is read only for the default ladder. On the
default ladder with n_sizes = 1, k = 2 and max_fits = 3 the count of 4
fits included the random reference, and the advice named only n_sizes
and k, neither of which can be lowered. With 200 points on an E-W
transect 0 to 6000 m with 1e-6 m of jitter in y (EPSG:32632), every
rung of the default ladder needs a grid of more than 1,000,000 blocks,
and the sweep refused with "no block size leaves at least k = 5 blocks
... pass smaller `block_sizes`", where the rungs held too many blocks
and smaller sizes are refused. On two clusters at opposite corners of a
5 km square (EPSG:32615) with block_sizes = c(300, 1600) and k = 5 the
k column read 5, 5, 2 (12 fits), but the print header said 15 fits and
the plot caption "5 folds per size, 15 fits in total".

The max_fits refusal now picks its advice by ladder: pass fewer
`block_sizes` or a smaller `k` with block_sizes supplied, lower
`n_sizes` or `k` on the default ladder, and on either one pass
include_random = FALSE when the count includes the reference. When every
size of the default ladder is too fine the sweep refuses with a message
that says so and names the largest size whose grid holds k blocks (1170
on the transect, which runs). The generic refusal keeps "pass smaller
`block_sizes` or a smaller `k`" for a supplied ladder. A new internal
helper, .sweep_fits_made(), sums the k column; print() and plot() use
it, and the caption says "up to k folds per size" unless every row built
k. The clustered header now reads 12 fits. attr(, "n_fits") stays the
count planned, which the max_fits check reads.

?cv_block_size_sweep states the advice of both ladders in the fit
budget section, the refusal of a ladder that is too fine in the ladder
section, and what n_fits counts in the value. NEWS has a new Bug fixes
entry. No frozen fixture record is retaken and no test is turned round.
test-fix-block-sweep-text.R is new, with 7 tests: the refusals on both
ladders with and without the reference, the transect refusal and the
size it names, the fit counts of a clustered and a uniform sweep and of
a subset, a fit_fn that fails on one fold, and the help page.
PROJ 9.5.1 moves a point out of Web Mercator into WGS 84 degrees in
time that grows with x: 1.6 s at x = 1e17, more than 20 s at 1e19, and
no return at 1e20. The package moves a caller's rows to EPSG:4326 for
the record of locations that make_folds() keeps and the folds are
checked against, and for the record of a fit's rows that
compare_models() checks its AICc and LOOIC with. On an EPSG:3857 layer
with one row at x = 1e20, make_folds() on every method, every cv_*()
function, fold_separation(), compare_models_cv() and compare_models()
with two or more AICc or LOOIC values did not return. The same move
stalled ensure_projected(purpose = "area") and summarize_by_cell(area =
TRUE) on a polygon with such a vertex, get_voronoi_seeds() given a
resolution profile and points in degrees, get_voronoi_seeds(method =
"kmeans") with a boundary in degrees and such a sample point, and
harmonize_crs(target_crs = 4326) on such a layer. At x = 1e12, where
PROJ returns, such a row came back at a finite, wrapped longitude.

R/cross-validation.R now holds .LONLAT_MOVE_MAX (1e10, further from
the origin than any projected CRS places a point of the Earth),
.beyond_lonlat_move(), which reads every vertex of every part of a
feature, and .lonlat_of_placed(g, crs). When `g` is projected and
`crs` is in degrees, on any datum, the latter empties the rows beyond
the bound with .empty_unlocated() and then moves the set; any other
move is sf::st_transform() as before. .fold_row_probe(), .fit_rowset()
in R/evaluation.R, both moves of .seeds_in_crs_of() in R/seeding.R,
.align_crs() and .transform_or_stamp() in R/utils.R, and the move in
harmonize_crs() go through it, and .crs_area_error() gives NA for a
probe of a projected CRS with a coordinate beyond the bound.

Users now get these calls back at once. The folds, the fits and the
rows each function keeps are unchanged, and the far row is read as a
row without a location. get_voronoi_seeds() with the profile gives 16
seeds, one empty, in 0.01 s; with method = "kmeans" it gives 5 seeds in
0.1 s and drops the far point with its warning about empty points;
harmonize_crs() gives 60 rows, one empty; the area checks find the
distortion unmeasured. A row at 1e12 moved into degrees now comes back
empty. Layers in degrees or without a CRS, layers whose rows all lie
within 1e10 of the origin, and moves into a projected CRS give the
same results as before, to the bit. ?harmonize_crs says such a row
comes back empty.

NEWS has one Bug fixes entry. No frozen fixture record is retaken and
no test is turned round. test-fix-fold-probe-far.R is new, with 17
tests over the fold probe, the row fingerprint, the folds, the seeds,
the area check, harmonize_crs(), the shared moves and multi-part
geometries; each test first mocks sf::st_transform() to refuse such a
move, so a regression fails and does not hang.
.morans_i_for_k(), behind the counts of determine_optimal_levels() under
criterion = "morans_i" or "combined" and the `moran_z` of those calls
and of resolution_profile(), regressed the cell means on the cell
design. Where the raw response lost its spread and was taken about its
centre (1e12 or 1e15 plus a spread of a few units), the residuals were
tested only against the centred cell means, so the residuals of an exact
fit, the rounding of the offset, were scored: on 800 points, 30 of 30
random fits a + b w + c w2 at 1e15, and determine_optimal_levels()
returned 10 11 at 1e15 where it returned 7 6 8 at 0. Where the cell
means were regressed again about the centre, the residuals were refused
when within three units in the last place of the raw response, the
rounding of one row, though a mean of n rows has the rounding and the
spread of a row over sqrt(n): 1e15 + 1e5 w + f on w gave NA at 20 to 80
cells, and the call fell back to the geometric ranking.

.residuals_rounding() in R/pool.R takes two new arguments for
residuals of means: `n`, the rows behind each mean, and `m`, the
rounding of forming each mean. The bound is then the residual degrees
of freedom (the means less the rank of the design) times the mean over
the cells of (three units in the last place)^2 / n plus m^2, in the
units of the response. Callers that pass neither get the old bound to
the last bit. .morans_i_for_k() counts the rows with a response and
all rows per cell on both the grouped and the loop path, fits the rows
by OLS on the centred predictors and returns NA where their residuals
are within the rounding of a row, as resolution_profile() reads its
own; the fit is taken again about .residual_centre() where the centre
is 0 and the rows' residual sum of squares is under 1e-12 of the
response's. The cell-mean residuals are then read against the bound
for means, with `m` the rounding of summing each cell's rows.

Users now get the same counts and `moran_z` at offsets 0 and 1e15.
Exact fits give NA at offsets 0, 1e6, 1e12 and 1e15, on cells of 1,200
to 2,200 rows, and on a predictor along which the response at 1e15
changes by a unit in the last place; determine_optimal_levels() gives
7 6 8 for the exact fit at both offsets. On 20,000 rows, the exact fits
k 10 a 1e4 b 500 and k 15 a 100 b 5 were scored on the loop path at
offset 0 and are now NA, and 2 + 0.05 ws with max_levels = 100 gives 9
8 10, the geometric fallback, with no finite `moran_z` where it gave 12
with z 0.372. 1e15 + 1e5 w + f gives the same I and z at 0 and 1e15;
residuals that the rounding of a row holds (1e15 + 1e5 w + 0.3 f) give
NA, and residuals above the bounds are scored as before. On a layer
with real spread, resolution_profile() gives the `moran_z` and `cp` it
gave.

NEWS has one Bug fixes entry. No frozen fixture record is retaken and
no test is turned round. test-fix-cell-moran-offset.R is new, with 8
tests: exact fits at four offsets and on 20,000 rows, the spread at 0
and 1e15, near-exact fits within the rounding of the sums, a smooth
predictor at 1e15, determine_optimal_levels() at both offsets, and
.residuals_rounding() with unequal cells, a design of rank 4 and `m`.
create_grid_polygons_cached() projected a boundary in degrees to the
CRS create_grid_polygons() picks without `crs`, and with `crs` given
the builder then transformed that copy to `crs`, where the uncached
builder transforms the boundary once. On rows 1 to 8 of nc.shp in
EPSG:4326 with s2 off, `cellsize = 0.1, crs = 4326` matched 0 of 127
cells of ensure_stable_poly_id(create_grid_polygons()) on geometry,
corners off by up to 4.26e-13 degrees, and `cellsize = 10000, crs =
32617` matched 0 of 133, off by up to 9.83e-8 m; `target_cells = 40`
did the same with either `crs`. The counts and IDs agreed. A boundary
with no CRS and `crs` given differed too.

ensure_stable_poly_id() with `make_valid = TRUE` gave the rows of a
lon/lat layer that s2 or GEOS calls invalid s2's rebuild with s2 on,
and s2's rebuild does not split crossing edges. A bowtie, a figure
eight or a MULTIPOLYGON whose parts overlap, beside a 0.3 degree square
in EPSG:4326, came back invalid under s2 and GEOS: sf::st_area() of
the row stopped, and its EPSG:32615 copy cut by its bbox stopped with
"TopologyException: side location conflict". With s2 off the row came
back valid (s2 areas 1.19686e9, 4.2069e9 and 1.19685e9 m2).

A new internal helper in R/stable-ids-cache.R, .grid_crs_arg(), reads
the `crs` create_grid_polygons() is given, by name, partial name or
position, and .grid_returned_in_degrees() uses it.
create_grid_polygons_cached() still takes its key on the
.project_for_grid() copy, so the keys are unchanged to the bit, and
builds on the caller's boundary when `crs` is given, on the projected
copy otherwise. .make_valid_rows() gives a row that s2's rebuild leaves
invalid under s2 or GEOS GEOS's repair of the coordinates it arrived
with, which is what the call gives with s2 off. A row still invalid
after that, such as a polygon whose edges cross only as great circles,
is returned as GEOS repaired it with one warning naming its rows. The
help of ensure_stable_poly_id() (`make_valid`) says so.

Users now get the uncached grid from the cached builder in every case
above, under both s2 settings, and from ensure_stable_poly_id() with s2
on the row s2 off gives, valid under both checks; the square comes back
as it arrived. A repaired row is keyed on GEOS's repair, so its ID is
the one s2 off gives. With s2 on it was keyed from a second repair that
drops the overlap of overlapping parts: in a layer of a 1 degree square
overlapping a 0.6 degree square plus 111 small squares, that row was
ID 52 with s2 on and 55 with s2 off, and is now 55 under both, and the
rows that had IDs 53 to 55 with s2 on move down by one.

NEWS has two Bug fixes entries. No frozen fixture record is retaken.
Three tests are turned round: test-fix-stable-ids-valid-rows.R test 3
now expects GEOS's repair of the bowtie, not s2's rebuild;
test-stable-ids-crossing-edges.R expects the Texas cell transformed to
EPSG:4326 to come back valid with no warning; test-fix-cache-key-s2.R
test 2 compares against the uncached grid of the caller's boundary.
test-fix-cached-grid-crs.R and test-fix-stable-ids-crossing-edges.R
are new: the four grid calls under both s2 settings, `crs` by
position, partial name and as a layer, the keys against the old
formula, the three layers with crossing edges, the overlapping parts
layer and the great circle polygon with its warning.
A row with a coordinate that is not finite or lies beyond 1e150 has no
location, but several readers took it as one or moved it first.
harmonize_crs(), ensure_projected(target_crs = ) on a projected layer,
.transform_or_stamp() and .align_crs() moved such a row with
sf::st_transform(), which drops the vertex: two half-degree squares near
(-93.5, 45.4) in EPSG:4326, the second with a vertex at (1e200, 45.8),
(-93.4, 1e200) or (Inf, 45.8), came back with row 2 as a triangle of
1,088,897,076 m^2 (the square is 2,168,128,746). create_grid_polygons()
laid 55 cells over that triangle where it refuses the row empty, and
assign_features_to_polygons() joined the point (-93.85, 45.35) to it.
From EPSG:32616 a vertex at (3.2e5, 1e200) came back at (733095.47,
5751433.9).

With one of 300 sample points at (5e5, 1e200) in EPSG:32617,
voronoi_seeds_kmeans(k = 15) and get_voronoi_seeds(method = "kmeans", n
= 15) stopped with "no start gave a partition of the 300 point(s) into
15 cells"; in EPSG:32616 beside a boundary in EPSG:32617 the point was
moved to a finite place and clustered (seed x summing to 8300090 against
8255199 for the row empty), with no warning. A seed at Inf or 1e200
stopped create_voronoi_polygons() and build_tessellation(method =
"voronoi") in GEOS ("Could not locate vertex", "encountered NaN/Inf
numbers", "distance must be a finite value", "Points of LinearRing do
not form a closed linestring"); clip_target_for() stopped at Inf and
gave a target of infinite area at 1e200. One point of a `cover` at Inf
or 1e200 set the whole cover aside and the cells covered 5,334 km^2
against 10,516 km^2 for the point empty; a polygon cover in EPSG:32616
with a vertex at a northing of 1e200 became a boundary of 68,562 km^2
against 8,983 km^2. summarize_by_cell(deff = "variogram") correlated
such a point with its cell (standard error 0.5428794 against 0.6636533),
and kriging_adequacy() stopped at Inf and kriged from 1e200 (3272.494,
or 3274.911 with folds of 399 and 1, against 3264.932).
assign_features_to_polygons() joined a point at 1e200 in EPSG:32616 to
the cell of EPSG:32617 the move put it in.

The bound is now read on the coordinates as given, before any move, with
.unlocated_features() and .empty_unlocated(), and again after it
(.values_at_xy(), .located_rows()). Every move of a caller's layer goes
through .transform_keep_unlocated() in R/crs-geometry.R:
harmonize_crs(), ensure_projected(), .transform_or_stamp() and
.align_crs() in R/utils.R, .cover_follow() for the builders' `cover`,
the move of the polygons into a scored record's CRS in
assign_features_to_polygons(), and .values_at_free_cells() of
cell_values_at(). get_voronoi_seeds(), voronoi_seeds_kmeans() and
.scored_seeds_record() in R/seeding.R; create_voronoi_polygons(),
build_tessellation(), clip_target_for() and .build_point_cell_index() in
R/tessellation.R; summarize_by_cell() and .cor_stats_from_coords() in
R/assignment.R; and kriging_adequacy() empty such rows as given.

.transform_keep_unlocated() now moves with .lonlat_of_placed() in place
of sf::st_transform(), so a row of a projected layer with a coordinate
beyond 1e10 is still emptied before a move into a CRS in degrees, as
harmonize_crs(), .align_crs() and .transform_or_stamp() did before this
change; without it a row at x = 1e20 in EPSG:3857 went back to PROJ,
which does not return. .lonlat_of_placed() reads the source CRS alone,
so a layer in degrees with a longitude of 1e200 no longer draws sf's
warning "bounding box has potentially an invalid value range for longlat
data" from ensure_projected() or cell_values_at(). The comments of
.lonlat_of_placed() and .transform_keep_unlocated(), and ?harmonize_crs
(Value), name both bounds.

Users now see such a row read as the same row empty: the moved square
comes back as POLYGON EMPTY, the grid is refused, the join gives NA; the
seeds sum to 8219723 (EPSG:32617) and 8255199 (EPSG:32616) with the
warning "dropping 1 point(s) without a location (an empty point, or a
coordinate that is not finite or lies beyond 1e150)."; 29 Voronoi cells
over 1e10 m^2 with one NA in `index`; a clip target of 8,197,779,700
m^2; covers of 10,516 and 8,983 km^2; a standard error of 0.6636533; and
3264.932 from kriging_adequacy(). Warnings and log lines that said "with
empty or non-finite coordinates" or "empty geometry" now count these
rows as rows without a location. The help of
assign_features_to_polygons(), build_tessellation(),
create_voronoi_polygons(), ensure_projected(), get_voronoi_seeds(),
harmonize_crs() and voronoi_seeds_kmeans() says so. NEWS widens the
entry on rows moved to another CRS and has a new Bug fixes entry; the
entry on rows beyond 1e10 now names the warning as one about points
without a location. Nothing is under Breaking changes.

No frozen fixture record is retaken. Expectations on message text are
turned round, with the same assertions, in test-fix-cells-seed-index.R,
test-fix-texts-misstated.R (four), test-review2-assignment.R,
test-review3-S2-tessellation.R, test-seeding.R and test-audit-pass5.R.
test-fix-unlocated-rows.R gains tests for the moving readers, the
seeders, the Voronoi cells and clip target, point and polygon covers,
the variogram design effect on points and polygons, kriging_adequacy()
and the scored join and free cells. test-fix-fold-probe-far.R gains a
test that the shared move empties both a row beyond 1e150 and a
projected row beyond 1e10 moved into degrees, a polygon cover included,
and a test that it moves a layer in degrees with a longitude of 1e200
without that warning.
estimate_sac_range(detrend = "reml") handed nlme::gls() the predictors
and the response as given, and gls() fails on a column such as 1e8
plus a spread of 1. The estimate then warned that the REML detrending
"did not converge", detrended by OLS and returned the OLS range and
nugget. On 600 points with z = 2 w + field + noise and a predictor at
1e8 or 1e15 plus w, it gave detrend_method "ols", a range of 1428.861
and a nugget of 0, where the predictor at 0 gives "reml", 1561.566 and
0.02143349. On 300 points with the response at 1e8 or 1e12 it gave
"ols" and a range of about 1400 with a nugget of 0, against the REML
range 1361.364756 and nugget 0.01423461 at 0; at 1e7 the REML range
moved in the fifth digit.

Two subsamples were drawn by position from rows in the exact order of
the first coordinate. After another unit or origin the first
coordinates of one column of a lattice differ in their last bits, so
the draws took other rows. On a 40 by 30 lattice at 250 m with
reml_max_n = 300 the REML nugget was 0.20623529 in EPSG:26915 and in
UTM 15N in US feet and 0.24105594 with the false origin moved by
0.1 m. With n_max = 300 the range was 646.9834 m in the first two
frames and 794.7406 m in the third.

In the REML branch of estimate_sac_range() the predictors and the
response now go through .centre_columns() before .reml_trend(), with
the gate of the OLS fit: a column whose centre is more than 1000 times
its spread is taken less its centre. The structure test reads the same
centred design. For the exact-fit test the response's centre is added
back to the intercept, and the predictors' centres reach
.rounding_design() through attr(X, "assign"), as in the OLS branch.
Below the gate every fit gets the same frame to the last bit.

.reml_trend() and the canonical row order of estimate_sac_range(),
before ensure_projected() and the n_max draw, now sort by
.near_columns() of the coordinates, then the second coordinate, the
first and the other columns, as resolution_profile() does. First
coordinates within 1e-7 of the diagonal of the bounding box count as
one column. Where no two first coordinates come that close, the order
is that of the coordinates, as before.

Users now get the REML fit at any offset of a predictor or the
response: offsets 0, 1e5, 1e8 and 1e15 of the predictor give "reml",
1561.566 and 0.02143349, and the response at 1e7, 1e8 and 1e12 gives
1361.364756 and 0.01423461, with no fallback warning. The lattice gives
a REML nugget of 0.20623529 and an n_max range of 646.9834 m in all
three frames; a jittered lattice keeps the results it had. The help of
estimate_sac_range() says how the rows are ordered, and its claim that
the result does not depend on the unit or the origin now holds for the
subsample.

NEWS has four Bug fixes entries, and the closing sentence of the entry
on the OLS detrending at an offset now points to them. The frozen
record profile_density_clustered is retaken: two points of that layer
have first coordinates 1e-4 m apart on a layer 1,340 m across, which
now count as one column, so the variogram's pair sums move in their
last bits. No test is turned round.
test-fix-reml-offset.R is new, with 9 tests: a predictor and a
response at large offsets, the centred frame gls() gets, the exact-fit
test with a factor and with the response at 1e8, and the REML and
n_max subsamples of lattices across units and origins.
predict_surface() took a grid point's covariates from
sf::st_nearest_feature(), which settles a point the same distance from
several covariate points by the last bits of the distances. A grid laid
between the points of a lattice ties with four of them. On a 20 by 15
lattice at 250 m with the grid at the midpoints, 147 of 266 grid points
took another covariate row in UTM 15N in US feet than in EPSG:26915,
and 189 with the false origin moved by 0.1 m; `.pred` moved by up to
0.0305.

The `attr(, "kmeans")` record of get_voronoi_seeds() for the scored
centres of a profile or selection put each point with the seed
st_nearest_feature() returned. On a 40 by 30 lattice with the 267
centres of resolution_profile(seed = 1), `cluster` differed from the
metre record in 5 rows in US feet and 2 with the origin moved, 1 to 4
rows per frame were not in the cell the profile scored them in, and the
tot_withinss ratio was 10.792861 against the unit factor 10.763867, and
0.999679 with the origin moved.

coerce_to_points("auto") ordered tied midpoints of a MULTILINESTRING by
exact x, so two congruent parts whose midpoints share one x, reached
from other coordinates, were settled by the last bits of x. Over 200
origins 167 took the lower part and 33 the upper; in degrees 52 of 60
symmetric features took the southern part and 8 the northern.

All three now follow the package's tie rule: squared distances within
one part in ten million of the least are a tie, settled by the
.near_columns() column, then y, then the lower number.
predict_surface() calls the new internal .surface_nearest_rows(): for a
POINT covariate layer not in degrees it orders the covariate points by
column, then y, then row, and searches them with .values_at_nearest();
lines, polygons and points in degrees keep sf::st_nearest_feature().
.scored_seeds_record() in R/seeding.R assigns each cloud point with
.nearest_centre(), seeds numbered by seed_id, and leaves seeds without
a location out of the search. The MULTILINESTRING loop of
coerce_to_points() orders tied midpoints by column, y, Z or M, then x.

Users now get the same covariates at every grid point in all three
frames, with or without FNN, and `.pred` does not move. A grid point
that ties with no other point takes the row it took before. A point of
a grid kept as given with a non-finite coordinate or one beyond 1e150
now gets NA covariates; its `.pred` is NA as before. The seeds record
holds the profile's cells in every frame, the same rows as the metre
record, and tot_withinss scales by 10.763867 and 1.000000.
coerce_to_points() takes the lower part at all 200 origins in both
part orders, and the southern part of all 60 features in degrees.

?predict_surface (`covariates`), ?get_voronoi_seeds (Value) and
?coerce_to_points (Details) state the rule. NEWS has two new Bug fixes
entries, for predict_surface() and the kmeans record, and the entry on
coerce_to_points() and the longest part is amended. No frozen record is
retaken and no test is turned round. test-fix-nearest-ties.R is new,
with 12 tests: lattice midpoints in three frames and without FNN, the
row order of the covariates, untied, polygon and degrees controls, the
seeds record on a lattice and of a profile, a seed without a location,
tied midpoints at 100 origins, by the box diagonal and in degrees, and
the help pages.
ensure_projected() scored each candidate CRS on the points PROJ could
place and left the rest out, so a UTM zone that cannot place part of a
lon/lat layer could win. LINESTRING (0 0, 180 0) in EPSG:4326 kept UTM
zone 46 (2.788 against 11.69 for the Lambert azimuthal CRS centred on
(90, 0)), which places neither vertex, and came back empty with no
warning. Five points on the equator at longitudes 0, 45, 90, 135 and
180 kept zone 46 (0.153 against 0.174) with 2 of the 5 rows empty.
coerce_to_points() in mode "auto" or "line_midpoint" then handed the
empty line to sf::st_line_sample(), which segfaulted and ended the R
session; a lon/lat line with an Inf vertex did the same, and
MULTILINESTRING ((0 0, 180 0)) under "auto" gave POINT EMPTY with no
word.

.pick_local_projected_crs() in R/crs-geometry.R now uses a candidate
only if it places every located vertex of the layer, on the comparison
path and on the purpose = "area" path. New helpers .located_vertices()
and .places_vertices() test this with one sf::st_transform() of the
distinct vertices per candidate; a vertex at a latitude beyond 90,
which no CRS places, takes no part in the test. A candidate that fails
gets distance_error NA, is not chosen and is named in the log line.
When no candidate places the layer, the global fallback is used
(EPSG:3857, Equal Earth for "area") with crs_choice NULL and its own
log line, and the first-candidate fallback is now the first candidate
that places the layer. .crs_distance_error() gives NA when a sampled
point with a location cannot be placed.

coerce_to_points() never passes to sf::st_line_sample() a line its
temporary projection returns empty, short of vertices or not finite,
in the LINESTRING and MULTILINESTRING branches. It stops with class
spatialkit_features_degenerate, site "projection" and field crs,
naming the rows and offering mode = "centroid" or tmp_project = FALSE
(.raise_unplaced_lines(), new); .pointize_refused() in R/model-prep.R
raises it again in the caller's words. A lon/lat line with a
coordinate not finite or beyond 1e150 gets POINT EMPTY.
ensure_projected() warns with class spatialkit_rows_unplaced (fields
rows and n) when a move out of lon/lat leaves a located row empty or
short of vertices (.rows_unplaced(), .warn_rows_unplaced(),
.rows_text()). .values_at_place() in R/cell-values-at.R and .pooled_xy()
in R/assignment.R, which report such rows in their own words, take the
class over (.answer_for_classed()).

Users now get the Lambert azimuthal CRS for the line and the five
points with every row placed, and (90, 0) as the line's midpoint under
"auto" and "line_midpoint". 39 stations over the conterminous United
States with a row at (-96, 95) get the Lambert azimuthal CRS centred on
(-96.7, 40.7) with 0.88%, as before, and the warning names the one row
that comes back empty. Layers that every candidate places get the CRS,
crs_choice, geometry and points they got. The help of
ensure_projected() and coerce_to_points() states the placement rule.
NEWS has a Bug fixes entry and nothing under Breaking changes.

No frozen fixture record is retaken. Two tests in
test-fix-unlocated-rows.R are turned round: resolution_profile() and
determine_optimal_levels() on a row at (-80, 95) moved to EPSG:32617
now give the spatialkit_rows_unplaced line before the control's
messages, with the values still identical. test-fix-crs-places-rows.R
is new, with 15 tests: the line and the equator points, the NA score,
the area path, the global fallback, layers placed as before, the
distinct vertices, a row beyond latitude 90, the pooled readers, the
warning, the midpoint, prep_model_data() and make_folds(), the
refusal, its re-raise and the help.
kriging_adequacy() handed gstat::krige() its nmax with the points in
the unit, origin and row order given. On a lattice several locations
are often the same distance from a cell's centre or a held-out point,
and gstat took its nmax among them by the last bits of the coordinates
and by the order of the rows. On a 32 x 24 lattice at 250 m with 8 x 6
cells and nmax = 12, the same layer in US feet moved kr_pred in 26 of
48 cells, by up to 0.017, and the false origin moved by 0.1 m moved it
in 27, by up to 0.031, kr_shift by up to 1.03 and the cross-validation
statistic by 5e-4. On 18 x 8 at 250 m with 3 x 1 cells and nmax = 4,
kr_n_used differed by up to 3, kr_pred by 0.28 and kr_gain by 1.1, and
20 entries were NA in one frame only. On a 30 x 30 lattice at 100 m
with 3 x 3 cells and nmax = 20, the rows reversed moved kr_pred in 5 of
9 cells, by up to 0.0035, and the statistic by 9e-4. A location whose
rows named cells 5 and 6 belonged to the cell of its first row, so both
cells moved with the order, with no warning. Points XYZ throughout were
kriged in three dimensions with a variogram fitted in x and y: on 400
points with heights of 0 to 1000 m, kr_pred differed in 16 of 16 cells,
kr_var by 13 percent on average and kr_gain by up to 0.14.

The kept rows now go in .canonical_order(), a new helper in
R/kriging-adequacy.R: by the .near_columns() columns, then cell ID as
text, then response, sorted by radix so text sorts the same in any
locale. Each row keeps its position as given, so a location is held out
under the fold of its first row as given and a vector of fold labels is
read by position (.location_splits() and .fold_labels_for() take that
position). A location whose rows name more than one cell is now no
cell's own, with a warning giving the count; its rows still count in
n, mean, se and plain_var of the cell each names. A second new helper,
.tie_pick(), takes the k nearest by the package's tie rule
(.knn_pick_rows(), .TIE_TOL) and says whether the k-th ties with the
next. .cell_neighbourhoods() picks each cell's nmax and the locations
added outside an enlarged cell with it, and counts whether a cell is
enlarged with the rule's tolerance. A cell or held-out location whose
nmax-th nearest ties with the next is kriged on its own from the rule's
pick; untied ones stay in gstat's own nmax search, which picks the same
set. The points are read with sf::st_zm(drop = TRUE) in place of
.xy_if_mixed().

Users see every column and the statistic agree to 2e-12 across units,
origins and row orders on both lattices above, the two-ID location in
neither cell's own set (cell 5 kriged from 83 locations, cell 6 from
84) with the warning, and points XYZ or XYM throughout give the result
of their XY copy. Layers with no tied distances give the same figures
to rounding; a lattice takes longer. ?kriging_adequacy states the tie
rule, its bound, the row order and the location named by two cells.
NEWS has two Bug fixes entries and nothing under Breaking changes.

No frozen fixture record is retaken. In test-fix-mixed-zm-summaries.R
two tests are turned round: points XYZ throughout are now kriged as
their XY copy, and the help test follows.
test-fix-kriging-large-offset.R takes its reference mean over the rows
in the canonical order.
test-fix-kriging-ties.R is new, with 11 tests: units and origins on
lattices, the enlargement tolerance, a jittered control, the tied route
against gstat's search, row order, the location named by two cells,
fold labels by position and by first row, .canonical_order(), points
XYZ and XYM, and the help.
estimate_sac_range(detrend = "reml") handed nlme::gls() the
coordinates, the response and the predictors as given. Where the REML
range is not identified the likelihood is flat, the range runs to 1e8 m
and more with a nugget proportion near 1e-6, and whether gls() returned
a fit or failed on the same rows was set by the last bits of its input.
On a 20 by 15 lattice at 250 m with reml_max_n = 100, EPSG:26915 gave
detrend_method "reml" and a nugget of 0.2165, and UTM 15N in US feet
and the false origin moved by 0.1 m gave "ols", a nugget of 0.1542 and
the warning that the REML detrending did not converge. On a 24 by 18
lattice with reml_max_n = 120 it was the other way round. The response
times 1000, or changed by two units in the last place, gave "reml"
where the response as given gave "ols", and a predictor so changed did
the same. estimate_sac_range() also passed .reml_trend() an extent
with a floor of 1 in the layer's unit, so the same lattice at 25 m,
475 m across, gave "ols" and a nugget of 0.1542 in metres and "reml"
and 0.2165 in km.

.reml_trend() (R/cross-validation.R) now puts the coordinates of the
rows it uses in the frame of .kmeans_frame(): centred on the middle of
their bounding box, divided by its diagonal and rounded to 2^-30. The
distances, the pair floor, the profile grid, the starts, every gls()
call and the null model read that frame, and the range and pair floor
are multiplied back by the diagonal, which .kmeans_frame()
(R/partition.R) now returns as `scale`, with `mid`. The new helper
.reml_value_frame() puts the response and each numeric predictor that
is a term of its own in the frame of its range on those rows. The
coefficients, sigma2 and both log-likelihoods are taken back to the
layer's units. Formulas without an intercept and a response that is not
a numeric column are left as they are. The extent floor is dropped, and
an extent that is not finite or not positive reads as the diagonal.

Users now get one detrend_method, one warning and one nugget for a
layer in any unit or origin, and for a response or predictor scaled or
changed in its last bits: 0.2165221642 in metres and km on the 25 m
lattice. The range is NA on these lattices, before and after. On a 40
by 30 lattice whose REML range is identified the range is the same
length in metres and in US feet, and on the same lattice jittered with
reml_max_n = 150 it moves from 632.983630502 to 632.983627542. The
help of estimate_sac_range() says how the REML fit reads its input.

NEWS has one Bug fixes entry. No frozen fixture record is retaken. One
test in test-fix-reml-offset.R is turned round: it compared the
response a mocked gls() receives with the raw values and now compares
it with their frame. test-fix-reml-frame.R is new, with 10 tests:
lattices across units and origins, the data a mocked gls() receives,
the range in the caller's unit, the jittered control, a layer under
1 km across in metres and km, the response and a predictor scaled and
changed in their last bits, the coefficients and log-likelihoods
against gls() on the data as given, a missing or zero extent, and the
fields .kmeans_frame() returns.
ensure_projected() read a row of a lon/lat layer with a vertex at a
latitude beyond 90 in the extent, the longitudes of the antimeridian
test and the centre, though no CRS places such a row. 39 stations over
the conterminous United States (longitudes -120 to -75, latitudes 30 to
47) get an Albers CRS with standard parallels 32.917007 and 42.998514
and lon_0 -96.698554, for both purposes. With a 40th row at (-96, 95)
they got a Lambert azimuthal CRS with lat_0 40.674623 and lon_0
-96.700618, and the Albers candidate's parallels moved to 41.2 and
84.2. A row at (-100, -95) gave the Lambert azimuthal CRS with lat_0
38.373631, and a swapped row at (40, -96) gave it with lat_0 38.221954
and put the box 139.0 degrees from the zone's central meridian.

The new internal helper .beyond_pole_features() in R/crs-geometry.R
flags a feature with a vertex whose latitude is finite and beyond 90 in
absolute value; it reads points, lines, polygons, multi types and
collections, and skips the walk when the bounding box lies within
[-90, 90]. .pick_local_projected_crs() empties those rows on its WGS84
copy with .empty_unlocated(), the whole row, as it does a row with a
coordinate beyond 1e150, so the box, the wrap test, the centre, the
parallels, the placement test and the scores read only the other rows.
The test reads degrees on the EPSG:4326 copy, so a layer in grads is
read in degrees too. The log line for a layer with no located row now
names a latitude beyond 90.

The 39 stations with any of the three rows now get the CRS and
crs_choice they get alone. The layer returned is not changed by the
choice: the row is still moved, comes back empty or
without the vertices the CRS cannot place, and the
spatialkit_rows_unplaced warning names it. A layer each row of which
has such a vertex gets Web Mercator (Equal Earth for purpose = "area").
Layers with no latitude beyond 90 get the CRS they got. The help of
ensure_projected() says the choice does not read such a row.

NEWS has one Bug fixes entry. No frozen fixture record is retaken. One
test in test-fix-crs-places-rows.R is turned round: it required the
Lambert azimuthal CRS that the row at (-96, 95) or (40, -96) produced,
and now requires the CRS of the 39 stations alone. The new
test-fix-crs-lat-beyond-90.R has 5 tests: the helper on each geometry
type and at latitudes 90 and 90.000001, the three rows with both
purposes, a polygon row with one vertex beyond 90, a layer whose every
row is beyond 90, and layers in NAD83 and in grads.
?resolution_profile and the NEWS entry for the density partition's tie
rule said the cells stay the same in another unit or with another
origin as long as no coordinate moves by more than about a
forty-millionth (2.5e-8) of the distance between neighbouring
locations, and that PROJ's rounding of about 1e-9 m is below that for
locations more than a few centimetres apart. The tie rule counts
squared distances within one part in ten million (.TIE_TOL, 1e-7) as
equal, which keeps an exact tie between two centres only while the
move is at most 1e-7 / (8 sqrt 2) = 8.8e-9 of the distance to them,
and that distance is at least half the spacing: 4.4e-9 of the spacing.
With a location midway between two centres 250 m apart, the location
moved by +delta in x and both centres by -delta, a delta of 4e-9 or
5e-9 of the spacing kept centre 1, and 1e-8 or 2.5e-8 gave centre 2:
a move inside the stated figure broke the tie. PROJ's 1e-9 m over
4.4e-9 is 0.23 m, not a few centimetres.

The Details of ?resolution_profile, in R/resolution-profile.R, now say
"about a two-hundred-millionth of the distance between neighbouring
locations" and "more than about a quarter of a metre apart", the
wording ?kriging_adequacy uses; man/resolution_profile.Rd is
regenerated. The tie rule and .TIE_TOL are unchanged, so no result
changes; users see the help state a bound the rule keeps. The
development-version NEWS entry for the density partition's tie rule
gives the same figure; there is no new NEWS entry.

No frozen fixture record is retaken. In test-fix-partition-ties.R the
help test is turned round: it pinned the forty-millionth and now pins
the two-hundred-millionth and the quarter of a metre, and checks that
the old figure and "few centimetres" are gone.
Five summaries read the spread of a column from values that carry the
rounding of a large offset. .compute_reg_metrics() took R-squared only
where the total sum of squares passed a test scaled by the sum of the
squared values: on 200 rows, R-squared was 0.80419 at offset 0, 0.80407
at 1e14, and NA with adjusted R-squared at 1e15, with RMSE computed.
.estimate_icc() read a column whose values differ only by their
rounding (a0 or a0 (1 + eps)) as one that does not vary only while its
standard deviation was below eps: the same values times 16, 1024 or
2^20 got a Kish ICC of 0.3946 and a design effect, while the pooled
summary of the same call said the column does not vary.
prep_model_data() took the shift of the dropped rows from the raw
means: a shift of 0.75 gave 0.865940 SD at 0 and 1e5 and 0.929802 at
1e15, with "not missing at random" only at 1e15, and a response of one
value up to rounding read "a difference of 1.61 SD" from its last bit
at 1.23, 4.5e6 and 1e15. .standardise_by() in the Bayes model scaled a
column a + {-1, 0, 1} ulp by 7.53e-10 at 4.5e6 and 0.106 at 1e15, and a
transect at x = 4.5e6 with jitter in the last bit got an x scale of
7.7e-10. A GWR fitted about a centre was scored on its fitted values
and predictions in the response's units: on 300 points with bandwidth
60, in-sample RMSE was 0.49543466 at 0 and 0.49555314 at 1e15 with
R-squared NA, and cv_gwr() gave RMSE 0.6553265 at 0 and 0.6531574 at
1e15.

.compute_reg_metrics() keeps its raw test and, where it fails, computes
R-squared when the response is not constant by .constant_column(); it
gains a `centre` argument, a scalar or one per row. .estimate_icc()
reads .constant_column() at the all-values gate and at the gate of the
rows with a cell id, and z-scores a column with spread from
.spread_scaled(). prep_model_data() takes the means and standard
deviation less .regression_centre() of the response and adds the
centre back only to the means its message quotes; a response of one
value up to rounding gets NA. .standardise_by() keeps the caller's mean
and sd only where the raw values show spread, and asks
.constant_column() elsewhere. New helpers .gwr_predict_centred_at() and
.gwr_predict_rows() sit behind predict.gwr_fit(), whose return value is
unchanged, and .scored_fitted() and .scored_predictions() in
R/model-classes.R feed model_metrics(), summary(), evaluate_insample(),
compare_models(), the cross-validation folds and
select_features_forward()'s score_holdout. The centred path is taken
when the predict arguments are empty or are list(se.fit = FALSE).
.cv_centred_columns() fills the centred columns of folds fitted without
a centre when a run mixes both, and .cv_overall_metrics() reads them.

Users now get R-squared 0.80351 at 1e15 where it was NA, ICC NA with
"the column does not vary" at every scale for a column that differs
only by its rounding, a shift of 0.865940 SD at every offset, a Bayes
scale of 1 for a rounding-only column at every size, and for the GWR
in-sample RMSE 0.49543466, MAE 0.40823795, R-squared 0.95501081 and
cv_gwr() RMSE 0.6553265, R-squared 0.9218483 at every offset. The
predictions of a cross-validation whose folds fit about a centre gain
the columns y_centre, yhat_centred and y_train_mean_centred; yhat stays
in the response's units. Responses and columns whose raw values show
their spread give the figures they gave. The help of model_metrics(),
fit_gwr_model(), cv_gwr(), cv_spatial(), compare_models(),
evaluate_insample(), prep_model_data() and summarize_by_cell() states
the change. NEWS has two new Bug fixes entries, on R-squared at a large
offset and on the shift of the dropped rows, and extends the entries on
the Kish ICC, the Bayes standardisation and the GWR fitted about a
centre.

No frozen fixture record is retaken and no test is turned round.
test-fix-spread-summaries.R is new, with 20 tests, and a test title in
test-fix-gp-prefit-offset.R now names the gate of .standardise_by().
On a box with no height the Albers candidate's standard parallels fall
back to 5 degrees either side of the centre on the sphere. A line along
one parallel that spans 180 degrees of longitude runs over the pole, so
its centre is the pole: on LINESTRING (0 45, 180 45), (0 20, 180 20),
(0 60, 180 60), (-10 70, 170 70) and (0 -30, 180 -30) the parallels
were 85 and 95 (-95 and -85 in the south), and ensure_projected()
stopped with "invalid crs: +proj=aea +lat_1=85.000000 +lat_2=95.000000"
with either purpose. Centres near a pole but not on it did the same:
(0 80, 160 80), centred on (80, 88.2), gave lat_2 93.2. With purpose =
"area" a point layer stopped too: a point at (10, 89) ("lat_1=84
+lat_2=94"), at (10, 85.5) or at (10, -87), and two points along 87N.
Every function that moves such a layer through ensure_projected()
stopped with it.

A new internal helper in R/crs-geometry.R, .albers_parallels_ok(),
holds the cone test as before and requires both parallels to lie
between -90 and 90; PROJ builds lat_2 = 90 and refuses 90.0000001, so
the bound is inclusive. The purpose = "area" path and the comparison
path of .pick_local_projected_crs() call it in place of the inline cone
test, and an Albers candidate that fails it is left out of the
comparison, as a degenerate cone is. The parallels are not clamped.

Users now get, on the five lines above and with either purpose, the
Lambert azimuthal equal-area CRS centred on the pole (lat_0 90, or -90
for the south line); its worst distance error on the line at 20N is
21.91% against the zone's 181.36%. The lines off the pole and the point
layers with purpose = "area" get the Lambert azimuthal CRS centred on
the data; with purpose = "distance" the points keep their UTM zone. A
point at (10, 85), whose parallels are 80 and 90, keeps its Albers
candidate. Layers whose parallels lie in range get the CRS, the
crs_choice and the geometry they got, identical() on 13 layers with
both purposes. ?ensure_projected says the Albers conic is scored where
its parallels do not degenerate or pass a pole. NEWS has one Bug fixes
entry.

No frozen fixture record is retaken and no test is turned round.
test-fix-crs-albers-pole.R is new, with 4 tests: the helper's bounds
and cone test, the lines near a pole with both purposes, layers in
range left as they were, and point layers near a pole.
make_folds() let the last bits of the coordinates decide three ties. A
lattice point on the edge two blocks share went to the first block
sf::st_intersects() found it in, which was the lower block only while
the edge passed through the point exactly. On a 37 x 25 lattice at 250
m, 3 x 2 blocks put 469 of 925 rows in another fold with the false
origin moved by 0.1 m, 4 x 3 blocks 448 rows in UTM 15N in US feet and
824 with the origin moved, and block_size 1500 m 745 rows with the
origin moved. The block counts behind the leakage advice disagreed as
well: on 36 x 24 cells the polygon count gave 864, 576 and 680 occupied
cells in metres, US feet and the moved origin, and the fast count 864,
612 and 681. With the origin moved, the selection half of select_on =
"split" on a 40 x 30 lattice moved by 786 rows, and the rss of
resolution_profile(select_on = "split") was 131.8186 against 122.8157 in
metres. Lattice points on a `boundary` drawn through them were read as
outside: on 469 points clipped to a triangle the call warned about 8 (US
feet) to 50 (km) of them and extended the block region from 27,000,000
m^2 to 40,500,000 m^2. A point outside every supplied block as near two
went to whichever which.min() met first (3 to 10 of 72 such points moved
in feet, kilometres and moved origins). method = "buffered_loo" kept a
neighbour at the buffer distance in one frame and excluded it in
another: on a 20 x 15 lattice with buffer 500 m, 295 of 300 training
sets differed in US feet and 253 with the origin moved.

Two new internal helpers in R/count-rule.R, beside .edge_tol(), hold the
ties. .edge_join() gives each point the lowest index of the polygons it
lies within .edge_tol() of, one part in 1e10 of the largest coordinate
of both layers; on projected layers it buffers the smaller layer by that
margin and calls st_intersects(), and in degrees it uses
st_is_within_distance() with s2 on and st_intersects() with s2 off.
.lowest_nearest() gives each row of a distance matrix the lowest column
within the tie tolerance of its smallest distance.

In R/cross-validation.R the block join of make_folds(block_kfold), for
the grid and for supplied blocks, the `boundary` test and
.block_occupied() use .edge_join(); .block_occupied_fast() reads a
quotient within the margin above a whole number as that number before
ceiling(); a point in no supplied block, and the grid's centroid
fallback, use .lowest_nearest(); and buffered_loo compares distances
with the buffer times sqrt(1 + .TIE_TOL).

Users now get the metre folds in every unit and origin: 0 rows in
another fold on every lattice above, 864 occupied cells in every frame,
identical split halves and rss 122.8157, no boundary warning and
27,000,000 m^2 of blocks, and mean buffered_loo training sizes of 288.15
and 275.08 in every frame. An edge point goes to the cell to its left or
below, or to the lower row of supplied blocks. The count of points
outside the design and its warning are unchanged, and a point 1 km
outside the triangle is still warned about. Layers whose points sit off
the block edges and the buffer distance get the folds they got.
?make_folds now matches its roxygen on params$blocks$source_row. NEWS
has one Bug fixes entry.

No frozen fixture record is retaken and no test is turned round.
test-fix-fold-ties.R is new, with 14 tests: block edges in three frames
for the grid and supplied blocks, the boundary, the fast and the polygon
block counts, the split halves, buffered_loo at the buffer, outside
points as near two blocks, the two directions of .edge_join() and
.lowest_nearest(), with jittered controls.
.compute_reg_metrics() took the sums of squared errors and deviations
on the values as they stand. For 200 rows with R-squared 0.768 scaled
by 2^-540, every squared error fell below the smallest double: RMSE was
0 and R-squared 1, and at 2^-560 R-squared was NA. Scaled by 2^510 to
2^540 the sums passed the largest double: RMSE was Inf and R-squared
NA. One prediction of 1e200 among the 200 gave RMSE Inf where it is
7.07107e198, and the response c(x, x/2, x/4, -x/8) with x the largest
double gave RMSE Inf and R-squared NA. The scores reach
model_metrics(), summary(), evaluate_insample(), compare_models(), the
folds and pooled scores of the cv_*() functions and
compare_models_cv(), and select_features_forward().

.compute_reg_metrics() now reads a sum again only where it is not
finite, or is below n * .POOL_Y_MIN^2 while its own terms are not all
0: RSS on y and yhat, TSS on y and the baseline. The other sum keeps
its value, so a far prediction or baseline does not move the scale of
the sum it is not part of. New internal helpers in R/utils.R:
.pow2_floor() gives the power of two at or below a value, capped at
2^1023; .mean_pow2() takes the mean of y divided by that power, since
mean() of values near the largest double can be Inf or -Inf where a
long double is no wider than a double; .ss_pow2() returns a sum of
squared differences as m * (s * f)^2, the differences divided by the
power of two at or below their largest absolute value, and taken on
the halves (f = 2) where a difference overflows. RMSE is multiplied
back, and R-squared takes the ratio of the sums as m_rss / m_tss times
the square of the ratio of their scales, 0 for an exact fit. The test
on TSS reads y in the units of that scale. .constant_column() takes y
as before.

Users now get, for every scale 2^k from k = -900 to 1000, RMSE over
the scale 1.0079481633, R-squared 0.76766134 and adjusted R-squared
0.76648791. One prediction of 1e200 gives RMSE 7.07107e198 and
R-squared -Inf, and the largest-double response gives R-squared
0.6023391813, that of the same values at unit size. RMSE is Inf only
where it passes the largest double. MAE, MAPE and SMAPE do not change,
and where both sums lie between n * 1e-300 and the largest double
every score is as it was, to the bit.

NEWS has a new Bug fixes entry on RMSE and R-squared for a response
whose squares underflow or overflow. test-fix-metrics-range.R is new,
with 11 tests. No frozen fixture record is retaken and no test is
turned round.
.pick_local_projected_crs() measured how far a lon/lat box reaches from
the central meridian of its candidate UTM zone as the larger of
abs(lon_min - cm) and abs(lon_max - cm), not wrapped. The zone comes
from the centre on the sphere, which for a layer across a pole, or one
whose longitudes are given outside -180 to 180, can lie far outside the
box, and ensure_projected()'s comparison warnings then quoted figures
above 180. For two lines from (-10, 70) to (170, 70) and from (-10, 75)
to (170, 75) the warning said the extent "reaches 257.0 deg from the
central meridian of UTM zone 16"; points at longitudes 170, 185 and 200
gave "reaches 377.0 deg ... UTM zone 1". Points at 181 and 183, 2
degrees from that meridian, were said to reach 360.0 degrees and got an
Albers CRS, and points at -181 and -183 the same against UTM zone 60.

A new internal helper in R/crs-geometry.R, .lon_offset_from(), wraps
each end's difference to 0 to 180, keeps the larger, and gives 180 when
the meridian opposite cm lies in the box; a box or meridian that is not
finite gives a figure that is not finite. .pick_local_projected_crs()
reads its offset from it, and the 5-degree test that sends a layer to
the comparison of candidates and both warnings read that one figure.

Users now see 180.0 for the lines across the pole, with the same CRS,
candidates and distance error, and 17.0 for 170, 185 and 200 with the
same choice. Points at 181 and 183 keep UTM zone 1 (EPSG:32601) and
points at -181 and -183 keep UTM zone 60 (EPSG:32660), as layers at
-179 and -177 or at 179 and 177 do. Other layers get the CRS and the
crs_choice they got. NEWS has a Bug fixes entry for the offset.

No frozen fixture record is retaken and no test is turned round.
test-fix-crs-lon-offset.R is new, with 5 tests: the helper against a
brute-force maximum, the polar lines, layers whose choice and log stay
as they were, the boxes beyond 180 and -180, and the 377.0 example.
predict_surface() took a grid point's covariates from a layer of lines
or polygons by sf::st_nearest_feature(), and a grid point on the edge
two polygons share, or at the shared end of two lines, is at distance 0
from both, so GEOS gave it either one. On 10 by 8 square cells of 500 m
with w the row number, 72 grid points on the shared vertical edges and
lm(z ~ w) fitted on a 20 by 15 lattice at 250 m, 34 of the 72 took
another cell (another w and .pred) than in EPSG:26915 with the false
origin moved by 0.1 m, and 29 with the rows of `covariates` shuffled.
Lines did the same: 21 to 27 of 72 shared ends did not take the left
segment in a frame, and 26 to 36 with the rows shuffled. A point of a
grid kept as given at x = 1e200 got the covariates of a row GEOS gave
it: w = 1 2 1 and .pred = 1.048 2.048 1.048 with lines or polygons, and
w = 1 71 1 with any layer in degrees, where ?predict_surface (`grid`)
states NA.

.surface_nearest_rows() sends a layer that is not in degrees and not
all POINT to a new helper, .surface_nearest_shapes(). It takes the
least distance d from st_nearest_feature(), and the features within
d * (1 + 1e-7) plus .edge_tol() of the layer's box are a tie. They are
shortlisted by st_intersects() of a square around the point, then by
the distance from the point to each feature's bounding box, then kept
by st_distance(). The tie goes to the feature whose box centre is in
the lowest column by .near_columns(), then the lowest by the same rule
on y, then the lower row of the layer. A point with one nearest feature
takes the row it took before. Point layers keep the search of
cell_values_at(), and a layer in degrees keeps st_nearest_feature(),
now asked only for grid points with a location. A grid point without a
location gets NA covariates whatever the layer.

Users now get the cell or segment to the left for each of the 72 edge
points and line ends in EPSG:26915, in UTM 15N in US feet, with the
origin moved and with the rows shuffled, with the same w and .pred in
each. The point at x = 1e200 gets w = NA and .pred = NA for every
layer. The shortlist keeps far grid points cheap: 10,000 points over
24 km against 3,600 cells take 2.4 s. ?predict_surface (`covariates`)
states the rule for lines, polygons and mixed layers. The NEWS entry
under Bug fixes on predict_surface() ties now covers features, with the
figures above, the rule, layers in degrees, and the NA for a grid point
without a location.

No frozen fixture record is retaken and no test is turned round.
test-fix-surface-edge-ties.R is new, with 14 tests: edge points in three
frames, row order, line ends, a tie 100 m outside the layer, a near
miss, empty grid points and covariate rows, the unit ranking, an
oblique tie, unlocated grid points for each layer type, y by the column
rule, the relative reach, the dispatch for layers in degrees, the
distances measured far from the layer, and the help text.
make_folds(method = "nndm") and method = "knndm" let the last bits of
the coordinates choose the folds of a lattice. NNDM took each point's
neighbours in the order FNN's kd-tree or order() gave equidistant ones,
broke ties between held-out distances by the exact order of the
coordinates, and compared its distances with the target and with `phi`
exactly. On a 20 x 15 lattice at 250 m predicted onto a 30 x 25
lattice at 400 m from 3 km outside, 195 of the 300 training sets in UTM
15N in US feet differed from those in EPSG:26915, and 234 with the
false origin moved by 0.1 m; the training rows were 60670, 61078 and
60845. With phi = 500 m, n_removed_total was 2675, 2188 and 2363.
max_ecdf_excess was 0.42, 0.42 and 0.413 with prediction points 250 m
apart beside the lattice, and max_ecdf_deficit 0, 0 and 0.003.
kNNDM gave Ward's method and its k-means the coordinates as given and
took the centre FNN or sf::st_nearest_feature() returned, so with k = 5
and seed 1 on the same lattice 170 (US feet) and 290 (moved origin) of
the 300 rows were in another fold, with W 1191.21, 1223.84 and 1288.05
m. A chain of candidates with W = 1, 1 - 0.6e-7 and 1 - 1.2e-7 returned
the third, where ?make_folds said the earlier of two within the
tolerance.

In R/cross-validation.R, .nndm_tie_key() ranks points by the column of
.near_columns(), then the second coordinate, then the row. The new
.nndm_neighbours() and .nndm_sort_rows() give each point its k nearest
by squared distance, distances within .TIE_TOL a tie ordered by that
key; an FNN shortlist is rechecked and the same answer comes without
FNN. They replace get.knn() and the twin repair. The sweep takes the
least value of each run of distances within .TIE_TOL, and its
insertion, G_target(r), `phi` and the floor warning's excess_phi count
distances within that part as equal. .ecdf_deficit() evaluates at
at * (1 + .TIE_TOL), and .nndm_reach() reads the target as separated
only when max(g_target) * (1 + .TIE_TOL) < min(loo_fin). In
.knndm_folds() and .knndm_kmeans(), hclust(), k-means and
.ward_from_groups() read .kmeans_frame(xy)$z, with held-out distances
and W in the layer's unit as before; .nearest_centre() gives a point as
near two centres to one part in ten million the lower one, with or
without FNN; and the earliest candidate whose W is within .TIE_TOL of
the least is returned, the random partition first.

Users now get the same folds and figures in metres, US feet and a moved
origin: 60670 training rows in all three, 2675 rows removed at phi =
500 m and 1812 at phi = 250 * sqrt(2), equal ECDF figures, and the
kNNDM folds with W 1349.59 in every frame. These need not be the folds
any frame gave before. The mocked W chain returns the second candidate.
params$target_separated, and the out-of-reach warning it raises on
small samples, take a prediction distance within one part in ten
million of a leave-one-out distance as not shorter. Jittered layers
keep their folds. ?make_folds states the rules for phi, separation and
the choice among candidates. NEWS has one Bug fixes entry.

No frozen fixture record is retaken and no test is turned round.
test-fix-fold-ties.R gains tests of NNDM and kNNDM folds in three
frames, of phi at a lattice length, of the tie key, of the ECDF
figures, of equal and chained W among kNNDM candidates, and of the
separation rule.
The last bits of the coordinates, which move with the unit and the
false origin, settled ties in the tessellation builders and in
cell_values_at(). On a 40 x 30 lattice at 250 m with square cells of
1000 m, build_tessellation() put 80 of 1200 points in another cell in
UTM 15N in US feet than in EPSG:26915, 235 in kilometres and 226 with
the origin moved by 0.1 m; with hexagons, 156 with the origin moved.
Of 180 points just outside the cells beside corners two boundary cells
share, 4 to 27 were snapped to another cell in other frames, since
st_nearest_feature() picked either of two cells the same distance
away. Triangles of a 37 x 25 lattice were 1728 in metres and US feet
(860 of them other triangles) and 1823 to 1827 in other frames, 95 to
99 of them flat. create_voronoi_polygons() merged near-coincident
points by ten decimals of the unit: 60 points with copies of 20 moved
by 1e-9 m gave 80 cells in metres, 62 in US feet, 79 with the origin
moved and 60 in kilometres. create_grid_polygons(cellsize = ) read a
9000 m side as 9.0000000000001741 cells of 1000 m with the origin
moved and laid 60 square cells, the smallest of 5e-8 m^2, against 54,
and 79 hexagons against 76. cell_values_at() without `seeds` broke a
tie between two scored locations of one column by their exact x: 2 of
14 locations midway between two points of a 20 x 15 lattice's first
column took the upper point with the origin moved.

In R/tessellation.R, .build_point_cell_index() joins points to cells
with .edge_join() and a margin of .edge_tol() of the cells' box, skips
rows without a location, and gives a point in several cells the
smallest cell_id. A new .snap_lowest_cell() gives a snapped point the
smallest cell_id among the cells within its distance times
(1 + .TIE_TOL) plus that margin. build_tessellation(method =
"triangles") hands geometry::delaunayn() the points in the frame of
.kmeans_frame() and draws the triangles on the points' own
coordinates; the GEOS fallback does the same in the new
.triangulate_in_frame(). create_grid_polygons() counts the cells per
side with the new .grid_side_counts(), which reads a side within
.edge_tol() of a whole number of cells as that number, for a caller's
`cellsize` and for the side `target_cells` derives, and the new
.drop_edge_slivers() drops a clipped part that lies within that margin
of its cell's edge (not for grids in degrees). In R/utils.R,
.dedup_points() keys each located vertex by its place in the frame of
.kmeans_frame(), and its unused `digits` argument is gone. In
R/cell-values-at.R the scored locations are ordered by .near_columns()
of x, then y.

Users now get the same index, snapped cells, triangles, Voronoi cells,
grid and fill in every unit and origin tried: 0 points in another cell
on both layers, 162 and 1728 triangles with none flat, 60 Voronoi
cells, 54 square cells and 76 hexagons, and the lower point of the
column. params$snapped no longer lists a point within the margin of an
edge. Copies 1e-6 m apart on that 5 km layer, which were kept, now
merge (80 cells before, 64 after). The help of build_tessellation(),
create_grid_polygons(), create_voronoi_polygons() and cell_values_at()
states the rules. NEWS has five Bug fixes entries and nothing under
Breaking changes.

No frozen fixture record is retaken. One test is turned round: in
test-fix-s2-boundaries.R, the test that a cell s2 reads with two
corners as one point names the clip in degrees reached that error
through the extra row of cells; it now expects the 4 parts of the
grid, and checks the error and its message with the side counted as
sf counts it. test-fix-tessellation-ties.R (13 tests) and
test-fix-values-at-column-ties.R (3 tests) are new. The merge applied
without conflicts and needed no further change.
assign_features_to_polygons() found a point on the edge two polygons
share in both only while the edge passed through it exactly. The last
bits of its coordinates put it a hair inside one or the other, and the
bounding-box centres that `tie_break = "smallest_area"` compares were
compared exactly, so the unit and the origin of the coordinates chose
the cell. On a 37 x 25 lattice at 250 m in EPSG:26915 and a 10 x 7 grid
of square cells over it, 25 of 925 rows went to another cell with the
grid built in UTM 15N in US feet and 11 with the false origin moved by
0.1 m; with the grid moved with the points, 13 rows moved with the
origin, and `tie_break = "first"` did the same. Polygons of one area
and one bounding-box centre went to the first in the rows of
`polygons_sf`, where the help said the choice does not depend on their
order: of the two triangles of a split square, points on the diagonal
went to T1 as given and to T2 with the rows swapped; a square and a
rhombus of its area about its centre, and one polygon under two IDs,
went to whichever came first, with no warning, in the join by
`predicate` and in the largest-overlap join.

With the default `predicate` (sf::st_intersects), the join is now
.intersects_margin(): the exact hits, and for POINT and MULTIPOINT
features also the polygons .edge_join() finds within one part in 1e10
of the largest coordinate of the polygons (.edge_tol()). Lines and
polygons get no margin, and another `predicate` is applied as given.
A new .tie_rule_rows() keeps, per feature, the candidates of least
area, then key by key those within that margin of the least:
bounding-box centre y, x, then the mean of the vertices y, x
(.vertex_means(), from .polygon_sort_keys()). The predicate join and
.largest_overlap_ties() both use it. A feature still left with more
than one candidate gets an NA ID, is counted in attr(, "ties") and
listed in its new element `unresolved`, is kept or dropped as
`keep_unassigned` says, is not counted as unassigned, and raises a
warning of class spatialkit_ties_unresolved (data `n` and `total`).

Users now see every row of the lattice in the same cell in every
frame, under both tie-breaks, and a jittered control is unchanged.
A point 1e-5 m east of a shared edge, as a POINT or a MULTIPOINT, is a
tie the rule decides; a line along the edge stays in the cell it lies
in. The triangles go to T1 in both orders, by vertex means (66.7,
33.3) and (33.3, 66.7). The square and rhombus and the polygon under
two IDs give NA in both orders with the new warning. Points off the
edges, and features whose candidates differ in area or centre, are
assigned as before. The help of `predicate` and `tie_break` states the
margin. NEWS has one Bug fixes entry and nothing under Breaking
changes.

Two frozen fixture records are retaken, assigned_units_lonlat and
values_units_lonlat: a lon/lat point on the units' outer edge came
back a hair outside after the move into EPSG:32632 and was dropped; it
is now assigned, 400 rows with the GEOIDs of the metre record. Tests
turned round in test-core-gridded-ties.R (the cell counts and edge
rows on lattices, the sphere and meridian points, and the point 1e-5
east of an edge, now in cell 1, with a mirrored case added) and in
test-core-pipeline.R (`on_edge` c(0, 2, 0, 0) to c(0, 6, 0, 6), with a
check that those rows lie on a boundary). test-fix-assignment-ties.R
is new, with 13 tests: the frames, the margin, degrees with s2 on and
off, the triangles, unresolved candidates, the order of the centre
keys in both joins, exact hits kept on a bowtie, MULTIPOINT features,
and the help and NEWS sentences.
predict_surface() with lines or polygons as `covariates` settles a
distance-0 tie by the features' bounding-box centres, read by
.near_columns(), whose step was a part in ten million of the diagonal
of the centres' own bounding box. Where every centre lies on one point
that box is last-bits small, and the last bits of the centres, which
move with the unit and the origin, chose the feature. A 1000 m square
with a 500 m hole and the island that fills it, alone in the layer,
with five grid points on the hole's ring, gave the square (`w` 1) in
EPSG:26915 and in UTM 15N US feet and the island (`w` 2) with the
false origin moved by 0.1 m, where the centres differ by 9.31e-10 m in
y; `.pred` was 0.7378 against 0.7308. Two lines crossing at their
midpoints, and a ring of 32 sides with the disc that fills its hole,
took the second feature in US feet or with the origin moved, where the
centres differ by 2.33e-10 or 5.82e-11 in x.

.near_columns() in R/partition.R takes an optional extent `ex`, by
default the diagonal of the points' own bounding box as before, so its
other callers do not change. .surface_nearest_shapes() in
R/predict-surface.R passes the diagonal of the layer's bounding box to
both the column and the row read, so centre x values and y values
within that part of the layer's diagonal are one column and one row,
and the tie goes to the lower row of the layer.

Users now see the same feature, `w` and `.pred` in every frame for
each of the three layers above, in both row orders. Layers whose
centres part by more than the step take the feature they took. The
help of `covariates` in ?predict_surface states the extent. The NEWS
entry on surface ties for lines and polygons states it and adds the
polygon with a hole and its island; nothing under Breaking changes.

No frozen fixture record is retaken and no test is turned round.
test-fix-surface-edge-ties.R gains tests of the polygon with a hole
and its island, of the crossing lines and the ring with its disc, and
of .near_columns() with and without an extent, and its help-text test
reads the new wording.
Six paths ran in silence, gave base R's, sf's or GEOS's bare message,
blamed the wrong cause or measured in the wrong unit:

- resolution_profile() with `predictor_vars` on which no row is
  complete fit OLS on zero rows. On 400 points that all carry a
  response and a predictor that is NA on every row, rss, cp and moran_z
  were NA at 6 of 6 levels, with base R's "number of rows of result is
  not a multiple of vector length", a log line saying the raw response
  was scored, and summary() saying "cp needs a response".
- cell_values_at() without `seeds` read only the geometry type of
  `values`, so a `values` of POLYGON EMPTY rows filled 120 of 120
  locations with cell_id NA and nothing said.
- create_voronoi_polygons() and build_tessellation(method = "voronoi")
  on points none of which has a location gave 0 cells and sf's "x
  contains no geometries of specified type", or stopped with GEOS's
  "BufferOp::getResultGeometry distance must be a finite value". 20
  points at latitudes 91 to 95 in EPSG:4326 did the same after the move
  to the working CRS.
- get_voronoi_seeds() ignored `seeds` under "kmeans" and "random", and
  `sample_points` under "random", in silence. `seed = 1` is matched to
  `seeds`, so two such calls gave two different layouts.
- create_grid_polygons() returned 0 cells without a word when the clip
  left none: the box (-179, -80) to (179, 80) at cellsize
  c(358 / 3, 10), and build_tessellation() then gave NA for 50 of 50
  points.
- build_tessellation() on cells in degrees held its snap tolerance in
  degrees against distances in metres, so on a 0.1 degree grid a point
  1.2 m outside its cell was NA (21 NA, 0 snapped). With s2 off it
  stopped with "there is no package called 'lwgeom'".

resolution_profile() now skips the fit when no row is complete and
raises spatialkit_no_scored_rows with or without the split; the bounds
record carries n_response_only, and summary() and select_resolution()
(cp, moran_z) name the predictors through the new .no_complete_row()
and .no_complete_text(). cell_values_at() refuses a `values` none of
whose polygons has a location, after .drop_empty_parts(), so rows of
MULTIPOLYGON(EMPTY, cell) still run. create_voronoi_polygons() refuses
points with no location before the cover and boundary branches, and
points the move to the working CRS leaves without one get their own
refusal, named by create_voronoi_polygons() or
build_tessellation(voronoi), whichever made the move.
.build_point_cell_index() no longer returns early on zero cells, so
spatialkit_unassigned gives the count there too. get_voronoi_seeds()
warns for each argument the method ignores, after the checks that
would refuse the call, and returns what the call without it returns.
create_grid_polygons() stops with "the clip to `boundary` left zero
cells", naming the cause and the remedy in degrees with s2 on;
build_tessellation() and create_grid_polygons_cached() pass it on. In
.build_point_cell_index() the snap tolerance for cells in degrees is
the median cell width as an arc in metres, the nearest cell and the
distances are taken under .with_s2(), and the log line names the unit.

Users see a refusal or a classed warning in place of the silent or
bare results above. On the 0.1 degree grid, 20 points are NA (all 21 m
or more out) and 1 is snapped with s2 on, and with s2 off 0 are NA
and 2 snapped. Projected and CRS-less cells are indexed as before. The
help of resolution_profile, get_voronoi_seeds, create_grid_polygons
and build_tessellation is updated, and six Bug fixes entries are added
to NEWS; nothing under Breaking changes.

No frozen fixture record is retaken and no test is turned round. The
new test-fix-guards-sibling.R holds 17 tests of the refusals, the
warnings, their controls and a point the planar and spherical nearest
cells disagree on.
Four subsamples were drawn, or thinned, by row position, so their
results moved with the row order of the input:

- summarize_by_cell(deff = "variogram") on a cell of more than
  `deff_max_n` points. One cell of 1500 uniform points at the default
  deff_max_n = 500 had `..se_resp_z` 0.2613 with its rows as given and
  0.2550 reversed, and `cell_weight` 12.77 against 13.36.
- area_of_applicability() with more training rows than
  `normalizer_max_n`. 1000 rows at normalizer_max_n = 300 gave a
  normaliser of 1.018 as given and 1.155 reversed, and DI and the
  threshold with it; with `threshold = 0.0065` supplied, 2 of 500
  inside or outside calls differed.
- gp_lengthscale_bounds() above `max_n` distinct locations, and the
  GP prior and basis fit_bayesian_spatial_model() and cv_bayes() build
  from it. 1500 locations gave bounds of 675.7 and 2810.3 as given and
  684.0 and 2822.3 reversed.
- The GP pre-fit check of fit_bayesian_spatial_model() and cv_bayes()
  on more than 1500 rows, which thins the rows at even steps. On 2400
  rows, 10 row orders gave Moran's I from 0.031 to 0.061 at a floor of
  1140 m, so the status was "below_floor_long_range" in 4 and
  "below_floor" in 6.

Each now orders the rows before the draw, with .canonical_order() of
R/kriging-adequacy.R, the coordinates passed again so that the exact
coordinates decide after the .near_columns() keys.
.cor_stats_from_coords() (R/assignment.R) orders the cell's points by
their coordinates. .aoa_normalizer() (R/area-of-applicability.R)
orders the predictor matrix by its columns in turn and the location
matrix by its coordinates. gp_lengthscale_bounds() (R/model-prep.R)
puts the distinct locations in coordinate order before .safe_dist().
.gp_floor_moran() (R/model-bayesian.R) takes its even steps through
the rows ordered by coordinates, then the response and the
predictors, and still draws no random number. The located-row filters
that run before these draws are kept.

Users now get one result for the input in any row order: the cell
above gives 0.2592 and 12.96, the normaliser 1.124 with no call
differing, the bounds 683.4 and 2822.3, and the pre-fit check I 0.049
and "below_floor" in all 10 orders. Under each cap nothing is drawn
and every figure is as before; above it the subsample is another one,
so the figures differ from before for the rows as given. The help of
`deff_max_n`, `normalizer_max_n` and `max_n` says the subsample does
not depend on the row order. NEWS has one Bug fixes entry and nothing
under Breaking changes.

One frozen fixture record is retaken, values_variogram_max_n
(summarize_by_cell(deff = "variogram", deff_max_n = 5) on the
number-count cells), whose 5-point subsamples are now drawn in
coordinate order. No test is turned round. test-fix-subsample-order.R
is new, with 14 tests: each subsample in random row orders, on uniform
points, gridded cells and layers, tied integer and indicator-coded
predictors, a supplied threshold and twice-visited sites, with the
results under each cap and the random stream left as it was.
Two functions drew the starts of one level from a stream that the
other levels of the call had already drawn from, so a count's cells
moved with arguments that should not reach them:

- determine_optimal_levels() under criterion = "morans_i" and
  "combined" drew the integer that seeds the re-runs in the window
  after the sweep, which takes more draws the more counts it runs. On
  480 points in twelve clusters (set_seed = 123, top_n = 3),
  max_levels 30 and 31 both put the elbow at 12 and the window at 8
  to 16, but moran_z at 14, 15 and 16 cells was -0.449, -0.447 and
  -0.585 against -1.265, -0.408 and 0.528, and "morans_i" returned
  15, 14, 16 against 15, 16, 10.
- resolution_profile() ran every level's partition on one stream in
  ladder order, so a level's cells, and wss, rss, cp, moran_z, loo and
  the other columns read off them, depended on the levels before it.
  On the same layer at seed = 123 the 20-cell k-means level had wss
  106785.80 and moran_z 0.647 on the ladder 10, 20, 30, 40 and
  106029.56 and -1.024 on 20, 30, 40; the density partition gave
  109735.12 against 113160.10. select_resolution() on the even counts
  to 36 from 6, 8, 10 and 12 chose 34, 36, 30 and 28 under "cp" and
  18, 26, 30 and 28 under "loo".

determine_optimal_levels() (R/level-selection.R) now reads the
re-run integer before the sweep, with the bound
.Machine$integer.max - n_uniq, and puts .Random.seed back after the
read, so the sweep starts where it did; each re-run keeps
.with_seed(rerun_seed + k) and its restore. resolution_profile()
(R/resolution-profile.R) draws one integer after the subsample and
before the variogram, and runs each level's .partition_centres()
under .with_seed(level_seed + L), restored in tryCatch(finally = ).

Users now get, for one seed, the same cells and scores for a count in
any window or ladder that holds it. On the layer above moran_z at 14
to 16 is -0.729, -0.536 and -0.421 for max_levels 30 and 31, the
counts 16, 15, 10 under "morans_i" and 12, 13, 14 under "combined";
the 20-cell level has wss 107615.29 and moran_z 0.111 on every
ladder, and "cp" and "loo" choose 32 on all four ladders. The sweep,
the elbow and the geometric ranking are as before. Profiles,
selections, seeds and cells for a given seed differ from before. With
set_seed = NULL the re-runs no longer draw from the caller's stream,
and with seed = NULL the levels take one draw from it in place of
their own draws. The Details of ?determine_optimal_levels and `seed` of
?resolution_profile say so; the latter adds that on a layer of more
than sample_n rows a ladder of counts and a ladder of sizes are
fitted to other rows and can give a level other cells.

NEWS has two Bug fixes entries. The entry on re-runs seeded per count
now says the integer is drawn before the sweep and gives the first
counts 16 at top_n 1 to 4, 17 at 5 and 6 and 19 at 7; the entry on
the 40 x 30 lattice at 250 m and the gridded k-means entry give loo
0.3066 and 0.2929.

169 frozen fixture records built on a profile are retaken (profiles,
summaries, selections, the select_*_moran_z refusals, seeds, cells,
counts, values_selection, small_five_rows* and print_default_digits);
no levels_* record moves and the other 192 are unchanged. Tests
turned round, as they pinned what the old streams drew: the partition
arguments, convergence, reference and defaults tests replay the
engine on the level's stream; count-rule, count-loo, gridded-ties,
pipeline, values-at, partition-ties, texts-misstated and the
white-noise structure test take the new figures; the levels re-run
test takes the new wording; and the no-structure test lets through
the spatialkit_bound_chosen warning a white-noise optimum at the
ladder's first level now raises. test-fix-streams-per-step.R and its
helper helper-level-streams.R are new: max_levels 28 to 32 under both
criteria, ladders of counts and sizes in both kinds, the count chosen
on four ladders, each level's starting state, and the stream after a
call with seed = NULL.
Five constructors and entry points took inputs that disagreed with
each other and went on in silence or stopped late:

- new_spatial_fit() built a fit whose formula and `predictor_vars`
  disagreed. `price ~ elev` with `predictor_vars = "slope"` printed a
  model of elev while area_of_applicability() measured distances in
  slope and residual_morans_i() rebuilt the OLS design from it.
- cv_spatial() scored a fold's predictions against `response_var`
  without checking that the fold's fit models it. A fit_fn that fits
  price, called with `response_var = "slope"`, gave 3 of 3 folds ok
  and an RMSE of 16.03, a fit of price scored against slope.
- kriging_adequacy() kriged each location once, so hand-built folds
  whose test sets shared rows kept the last fold's prediction. 200
  points with rows 1 to 50 in every test set gave a variance of the
  z-scores of 0.978021 and 0.988830 with the folds in two orders.
- kriging_adequacy(folds =) read a vector of labels against the rows
  kept, so with one response set to NA a vector of 200 labels, one
  per row, stopped after the variogram fit with a message saying the
  labels must have one entry per row.
- create_voronoi_polygons() and build_tessellation(method =
  "voronoi") handed on a seeds layer's record of its count unchanged
  when the seeds were fewer or more. A selection's 32 seeds cut down
  to 16 gave 16 cells whose record said 32 with scored_cells TRUE,
  and summarize_by_cell() warned spatialkit_cells_differ while every
  row was assigned.

new_spatial_fit() (R/model-classes.R) now stops, unless the right side
of `formula` holds `.`, when `predictor_vars` names a column the right
side's all.vars() does not hold, or when the right side uses an
attribute column of `data_sf`, other than the response and the reserved
names ..x and ..y, that `predictor_vars` does not name. The message
names both arguments and says what to do: name the column in
`predictor_vars`, or give a column the fit derives itself a name that is
not a column of `data_sf`. .cv_fit_one_fold() (R/cross-validation.R)
fails a fold whose fit's `response_var` is not the call's, compared
without names, with both names in fold_status; cv_block_size_sweep() and
select_features_forward() run their folds through it, and the hold-out
score of select_features_forward(select_on = "split") is NA with a
warning for such a fit (R/feature-selection.R). In R/kriging-adequacy.R,
.location_splits() refuses folds whose test sets share a location before
the variogram, naming the location's row and the two folds by their
positions in `folds`, and .fold_labels_for() reads a label vector of one
per row of `assigned_points_sf` by position, or one per row kept as
before; any other length stops at entry with its length, the number of
rows and, when rows were dropped, the number kept. .seeds_count_rule()
(R/tessellation.R) gives seeds that are not their record's count the
record with scored_cells FALSE, as get_voronoi_seeds(method =
"provided") marks them.

Users see an error in place of a fit whose parts disagree; a fit_fn
that derives coordinates x and y on a layer that keeps x and y as
columns is refused, and offset() and grouping columns count as used.
cv_spatial() fails every fold of a fit of another response, with 0 of
3 folds and the cause in the all-folds-failed warning. Shared test
locations are refused; the 200 labels above now give k 4, n_pred 199
and a zscore_var of 1.054370641, identical to the labels of the rows
kept. The 16 cut-down seeds give cells whose summary reads "not
checked: other cells were built from this count", with no warning.
The help of new_spatial_fit, cv_spatial, kriging_adequacy,
create_voronoi_polygons and build_tessellation is updated. NEWS has
one Breaking changes entry for new_spatial_fit() and four Bug fixes
entries.

No frozen fixture record is retaken. Four tests are turned round: the
left-side test of new_spatial_fit() now passes `predictor_vars =
character(0)` with `cbind(price, elev) ~ 1`, the oracle backend of
the area of applicability tests builds its formula from its
predictors, the count-rule test of a selection's twenty seeds expects
scored_cells FALSE and the "not checked" summary, and
kriging_adequacy(folds = 1:5) expects the new length message.
test-fix-entry-checks.R is new, with 15 tests, and
test-fix-new-spatial-fit-args.R gains tests of the right-side rule,
the reserved names, the trend-surface message and the help.
@elkronos elkronos added the draft label Nov 9, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant