Skip to content

Feature: CLUMPY style allocation#30

Merged
mmyrte merged 42 commits into
mainfrom
copilot/discuss-transition-probability-estimation
Jul 2, 2026
Merged

Feature: CLUMPY style allocation#30
mmyrte merged 42 commits into
mainfrom
copilot/discuss-transition-probability-estimation

Conversation

Copilot AI commented May 12, 2026

Copy link
Copy Markdown
Contributor

R CMD check (light) failed during package load because evoland_db_views.R attempted to add public methods that are now already declared in evoland_db.R. The failure was caused by duplicate R6 method registration at load time.

  • R6 method override compatibility

    • Updated evoland_db$set(...) calls in R/evoland_db_views.R to explicitly allow replacing predeclared methods:
      • trans_rates_dinamica_v
      • adjusted_trans_pot_v
      • alloc_params_clumpy_v
    • This keeps evoland_db.R as the canonical method declaration surface while preserving view-backed implementations in evoland_db_views.R.
  • Behavioral impact

    • No API shape changes.
    • Resolves class initialization/load error by making view implementations intentional overrides instead of duplicate adds.
evoland_db$set(
  "public",
  "trans_rates_dinamica_v",
  overwrite = TRUE,
  function(id_period) { ... }
)

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit

air

[air] reported by reviewdog 🐶

params[c("mean_patch_size", "patch_size_variance", "patch_isometry",
"frac_expander", "frac_patcher")],


[air] reported by reviewdog 🐶


[air] reported by reviewdog 🐶

id_coord = 1L,
value = 0.3


[air] reported by reviewdog 🐶

id_trans = c(1L, 2L, 1L, 2L, 1L, 2L),
id_coord = c(1L, 1L, 2L, 2L, 3L, 3L),


[air] reported by reviewdog 🐶

value = c(0.6, 0.2, 0.1, 0.5, 0.3, 0.4)


[air] reported by reviewdog 🐶

id_trans = c(1L, 2L),
rate = c(0.2, 0.1)


[air] reported by reviewdog 🐶

mean_t1 <- mean(raw_vals$value[raw_vals$id_trans == 1L]) # (0.6+0.1+0.3)/3
mean_t2 <- mean(raw_vals$value[raw_vals$id_trans == 2L]) # (0.2+0.5+0.4)/3


[air] reported by reviewdog 🐶

expect_true(abs(col_mean_t1_scaled - rates$rate[1L]) <=
abs(mean_t1 - rates$rate[1L]) + 1e-9)

Copilot AI changed the title feat: CLUMPY allocation backend + raw/adjusted transition potential architecture Fix R CMD check lazy-load failure from duplicate R6 method registration Jun 16, 2026
@mmyrte mmyrte marked this pull request as ready for review June 16, 2026 13:54
@mmyrte mmyrte changed the title Fix R CMD check lazy-load failure from duplicate R6 method registration Feature: CLUMPY style allocation Jun 25, 2026
@mmyrte mmyrte force-pushed the copilot/discuss-transition-probability-estimation branch from 6a135de to a672f32 Compare June 26, 2026 08:59
…esis

Adds dev/pivot-mechanism-verification.md documenting:
- GART/MuST pivot test is a faithful inverse-CDF translation
- the allocator implements the simplified single-pass strategy, not the
  bias-free uPAM (no per-patch P(v|u) update, no merge rollback)
- the 1/E(sigma) pivot rarefaction (thesis Fig. 3.2) is missing
- determinism behaviour under 0/1 transition potentials

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0189ymK6BeCwBC8iZ45CGuVb
claude and others added 9 commits June 26, 2026 12:30
…hape metric

Move the whole CLUMPY pivot-selection + patch-growth routine into C++
(allocate_clumpy_cpp) so the hot loop no longer crosses into R per patch.

- Add selectable allocation methods:
  * uSAM (single GART pass, quantity of change in expectation)
  * uPAM (iterative GART with a per-transition pixel quota and sampling
    without replacement; batch_size dials speed vs fidelity, 1 = strict
    one-pivot-per-draw). Affordable here because evoland's fixed-model
    potentials are pool-independent, so rho(z|u) is never re-estimated.
- Apply the 1/E(sigma) pivot rarefaction before GART (thesis Fig. 3.2) so the
  allocated quantity of change matches the target rate; rate confirmed to be a
  quantity-of-change rate via get_obs_trans_rates.
- Clamp negative/NaN potentials inside GART (matches reference clumpy).
- Move gart/sample_lognorm_area/raster_neighbors from R into C++
  (gart_cpp, sample_lognorm_area_cpp, raster_neighbors_cpp).
- Unify the duplicate patch-shape metric (patch_eccentricity vs
  calculate_elongation) into clumpy::elongation_from_raw_moments
  (src/clumpy_geometry.h), shared by the grower and calculate_class_stats_cpp.
- Patch grower uses incremental moments (O(1) per candidate) and the shared
  metric; thread method/batch_size through alloc_clumpy() and the evoland_db
  binding; update unit tests to the C++ entry points.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0189ymK6BeCwBC8iZ45CGuVb
…nterface cleanup

- avoid_aggregation (default TRUE for uPAM): deferred-write, all-or-nothing patch
  growth that fails (allocating nothing) if a patch would merge with another or
  cannot reach its sampled area; attempted cells are removed from the pool
  (sampling without replacement). Replicates clumpy's GaussianPatcher.
- Auto-select the method from patch params (all mono-pixel -> uSAM, else uPAM)
  instead of a user switch; uSAM is now strictly mono-pixel. The C++ keeps an
  explicit method flag for tests/comparison.
- Expose patch-area distribution via area_dist ('lognormal' default, 'normal');
  area_var is a variance (normal uses sd = sqrt(area_var)).
- allocate_clumpy_cpp drops ant_landscape (anterior snapshotted internally) and
  from_classes (derived from trans_from).
- Rename eccentricity -> elongation everywhere (incl. alloc_params_clumpy_v
  column), matching the thesis.
- Update unit + integration tests and RcppExports; extend dev verification note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0189ymK6BeCwBC8iZ45CGuVb
claude and others added 25 commits June 27, 2026 16:14
The thesis only uses the 'Multinomial Sampling Test (MuST)' name (Mazy App. 3.B);
'GART'/'generalized allocation rejection test' is local to the clumpy codebase,
and 'rejection test' in the thesis refers only to Dinamica EGO's distinct
two-stage process. What clumpy's GART computes is exactly MuST, so:

- export the pivot test as must_cpp (was gart_cpp); rename the internal
  must_draw_one helper and all GART comments/docs to MuST.
- the docstring notes the clumpy GART correspondence.
- update RcppExports, unit tests, and R-layer docs accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0189ymK6BeCwBC8iZ45CGuVb
…place

For large rasters (8M+ cells) the dense n_cells x T potential matrix is the
dominant memory cost. Pass the adjusted potentials as per-transition sparse R
lists (prob_cell / prob_value) instead, and store them in C++ as SparseColumn
(sorted cell indices + values, O(log nnz) lookup, ~12 B/nonzero). The grower is
templated on a probability accessor so both the sparse allocator and the dense
grow_patch_cpp test primitive share one implementation.

Also: remove the now-unused shuffle_in_place (uSAM is mono-pixel/order-free;
uPAM uses shuffle_pair); add a comment that cell indices are int (R 32-bit,
caps rasters at ~2.1e9 cells). Update RcppExports and unit tests; the sparse
subset test confirms only cells carrying a potential can transition.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0189ymK6BeCwBC8iZ45CGuVb
The previous big-grid check sum(agg)<=sum(noagg) was not a valid invariant:
both runs are quota-bounded uPAM (target_rate), so totals are ~equal not ordered;
with avoidance, successful patches are full-size (all-or-nothing) so the last
patch overshoots the quota more, and the FP-sensitive greedy growth diverges
across compilers/arch (passed on x86/gcc by hitting the quota exactly, failed on
arm/clang). Replace with a deterministic 1x5 case (forced potentials, area
variance 0, no shuffle) where avoidance unambiguously rejects the middle
merging patch: noagg=5, agg=4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0189ymK6BeCwBC8iZ45CGuVb
Strict batch_size=1 re-scans the whole pool with MuST once per patch
(O(#patches x pool)); at 200x200 that is ~22s vs ~0.08s for the batched path.
Make batch_size=0 (new default) auto-scale to ~1% of each class's source pool,
bounding the number of MuST passes (~100) so cost stays ~linear from 500k up to
the int32 cell limit. Semantics: >0 explicit cap (1 = strict uPAM), <0 = all
candidates in one pass, 0 = auto. Mirrors the reference's fraction-of-#J batch
parameter (which itself defaults to strict, with no scaling).

Thread the new default through alloc_clumpy()/one_period and the evoland_db
binding; update docs and tests (deterministic row test pinned to explicit
batch_size=1; add an auto-batch smoke test).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0189ymK6BeCwBC8iZ45CGuVb
…act checks

- Annotate the allocation backend with the relevant Mazy (2022) references so the
  rationale is recoverable from the thesis: MuST inverse-CDF (App. 3.B.1,
  eta_w), patch construction + elongation (App. 3.I, eq. 3.I.12), the 1/E(sigma)
  pivot rarefaction (Fig. 3.2, eq. 3.11), the uPAM quota N_{u->v}=P(v|u)#J
  (sec. 3.4.2 / App. 3.E.2), and patch-merging avoidance (sec. 3.2.4 / 3.4.2).
- Add an optional uniform-replay argument to must_cpp (split must_pick out of
  must_draw_one) so an external uniform stream (e.g. numpy's, from the reference
  clumpy GART) can be replayed for a deterministic, pixel-perfect cross-tool
  comparison of the pivot test. Backwards-compatible (u defaults to NULL/RNG).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0189ymK6BeCwBC8iZ45CGuVb
Replace terra::animate() in the stochastic-allocation-sensitivity vignette
with a per-frame plot loop encoded into a browser-looping GIF via knitr's
gifski animation hook, which (unlike interactive terra playback) survives
into HTML output. Add gifski to Suggests.

Also correct text that no longer matched the code:
- runs inherit baseline alloc_params_t via run lineage (parent_id_run = 0),
  they are not duplicated per run; variation comes from per-run seeds
- the allocation loop is a plain .mapply over runs, not a defensive
  signature-probing wrapper
- the individual-realization section now describes the animation rather
  than side-by-side panels

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019E9zE7mVS9ijuYaJvkjy9r
The vignette intermittently failed in CI with "No model found for id_trans=6".
Root cause: the rpart importance filter can return no score for a sparse
transition whose tree has no splits, dropping it from trans_preds_t. Model
fitting inner-joins viable transitions against trans_preds_t and silently
skips it, but predict_trans_pot() loops over every viable transition and
hard-errors. Whether the degenerate tree occurs depends on platform RNG,
hence pass-locally / fail-in-CI.

- vignette: after pruning predictors, demote any viable transition that
  retained no predictor so the viable set and the fitted models stay in
  lockstep; pass id_periods (not the partial-match id_period) to alloc_clumpy.
- trans_pot_t.R: TODO flagging that the viable/modelled mismatch should be
  reconciled in the library rather than relying on callers.
- alloc_params_t.R: drop the broken \link to the undocumented internal
  calculate_class_stats_cpp (Rd cross-reference WARNING) in favour of a code
  span; regenerate the matching man page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019E9zE7mVS9ijuYaJvkjy9r
The previous vignette reconcile keyed on trans_preds_t, but the real failure
mode is different: fit_full_model_worker() catches a training error (e.g. an
rpart tree that never splits, or too few positive cases) and returns a row with
learner_full = NULL rather than dropping it. predict_trans_pot() filters
"learner_full is not null", so such a viable transition yields no model and the
loop aborts with "No model found for id_trans=6". Whether training fails depends
on platform RNG, hence pass-locally / fail-in-CI. (The is_viable subsetting
itself is fine.)

- predict_trans_pot(): up-front check that every viable transition has a
  non-null learner_full, erroring with an actionable message that names the
  offending transitions and shows how to demote them. Replaces the per-row
  "No model found" stop() as the primary guard (kept below as a fallback).
- vignette: move the viability reconciliation after model fitting and key it on
  transitions that actually produced a usable model
  (learner_full is not null), which is the correct invariant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019E9zE7mVS9ijuYaJvkjy9r
The fetch-based reconcile demoted every transition: trans_models_t is
partitioned by id_run and learner_full is a BLOB, and a plain (id_run = NULL)
directory read does not round-trip "learner_full is not null", so the fetch
came back empty and create_alloc_params_t() then hit "No viable transitions".

- vignette: capture the trans_models_t object returned by fit_full_models()
  and demote transitions whose learner_full element is NULL, filtering the
  in-memory result directly instead of re-reading the partitioned table.
- predict_trans_pot(): drop the db$fetch(...) incantation from the error
  message (unreliable under a plain read) in favour of a representation-neutral
  instruction.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019E9zE7mVS9ijuYaJvkjy9r
Compute modeled_trans from the object returned by fit_full_models() before
committing it (removes any residual by-reference risk) and pin the fit chunk
seed. If no model trained at all, fail with the actual counts (rows, trained,
viable, trans_preds, id lists) instead of the opaque downstream
"No viable transitions found" error, so the cause is visible in CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019E9zE7mVS9ijuYaJvkjy9r
trained=0 for all 3 viable transitions in CI; the worker swallows the rpart
training error as a (deferred, lost-on-abort) warning. Capture those warnings
during fitting and include them in the diagnostic so the next CI log shows why
every model fails to train.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019E9zE7mVS9ijuYaJvkjy9r
The captured fit warning was conclusive: "Package 'rpart' required but not
installed for Learner 'classif.rpart'". rpart is a soft dependency of mlr3's
classif.rpart learner and a "recommended" R package, but it was undeclared in
DESCRIPTION. R CMD check restricts the vignette-build library to declared
dependencies, so every classif.rpart fit failed, leaving all models NULL and
aborting the stochastic vignette (evoland.qmd uses classif.rpart too). Tests
pass because they use classif.featureless.

- DESCRIPTION: add rpart to Suggests.
- vignette: drop the diagnostic scaffolding (warning capture + degenerate-
  calibration stop) added while tracing this; keep the clean reconcile that
  demotes viable transitions whose full model did not train, which pairs with
  the new predict_trans_pot() guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019E9zE7mVS9ijuYaJvkjy9r
@mmyrte mmyrte merged commit 5714e8d into main Jul 2, 2026
2 checks passed
@mmyrte mmyrte deleted the copilot/discuss-transition-probability-estimation branch July 2, 2026 14:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants