Skip to content

CES in Python#398

Merged
config-i1 merged 16 commits into
masterfrom
Python
Jun 9, 2026
Merged

CES in Python#398
config-i1 merged 16 commits into
masterfrom
Python

Conversation

@config-i1

@config-i1 config-i1 commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

What issue does this PR fix?

Closes #350
Closes #349

Language

  • R
  • Python
  • Both

FilTheo and others added 16 commits April 16, 2026 12:21
Translates R's adam-ces.R to Python with full CES pipeline: creator,
filler, initialiser, cost function, and CES/AutoCES model classes.
Supports all four seasonality modes (none, simple, partial, full).
188 parity tests pass against R reference outputs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add six standalone simulators (sim_es, sim_ssarima, sim_sma, sim_oes,
sim_ces, sim_gum) and ADAM.simulate() — all driving the shared C++
adamCore::simulate kernel. Each accepts an R-name string or a Python
callable for ``randomizer``, enabling plug-in-numbers R-parity tests
that match R to floating-point via the same kernel. SimulateResult
dataclass mirrors R's smooth.sim / oes.sim S3 lists with __repr__
matching print.smooth.sim / print.oes.sim. Version bumped to 1.0.6.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Extract simulateADAMCore() helper out of simulate.adam (matrix prep,
distribution-aware error sampling, occurrence mask, C++ call). The
public simulate.adam now wraps that helper with seed + ts/zoo timing
+ adam.sim assembly, behaviour unchanged. The new simulate.om calls
the same helper, applies omLinkFunction to map the latent state-space
output to a probability series, and draws rbinom for 0/1 occurrence
indicators. simulate.omg recursively calls simulate.om on the two
sub-models and combines via omgLinkFunction on the latent series.

Two side-effect adjustments inside simulateADAMCore so the helper
covers om/omg: handle is.character(object$occurrence) (om stores a
scheme string, not a fitted occurrence object) and reconstruct the
state cube's lag head from profileInitial when nrow(states) ==
obsInSample (om's compact state shape).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
OM.simulate overrides ADAM.simulate: calls super() to get the
latent ETS draw, applies om_link_function to convert to
probability, draws rng.binomial(1, prob) for the 0/1 indicator
series. OMG.simulate calls model_a.simulate / model_b.simulate
with derived seeds and combines the two latent series via
omg_link_function. SimulateResult gains a ``latent`` field that
exposes the pre-link state-space output for OMG to consume.

The default randomizer for OM is a Gaussian sampler keyed on the
empirical residual std — substitutes for the missing ``plogis``
branch in generate_errors(). Callable randomizers bypass that
and enable the plug-in-numbers R-parity trick.

ADAM.simulate tweaked to accept the structured-dict ``persistence``
form OM stores (falls back to ``vec_g``). R-side simulateADAMCore
gained a one-block randomizer override so the same plug-in-numbers
trick works on the R side too.

10 new smoke tests + 4 R-parity tests (1 passing, 3 xfailed with
explanatory notes on the OM-fit-parity / Python-numerics /
R-side-ARMA-bug they each expose).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes the historical ULP gap on ADAM initial="optimal" / "two-stage"
vcov by routing both languages' msdecompose() through the same
Armadillo pivoted-QR with a scale-invariant LINPACK-dqrls-style rank
cutoff. R-parity bench: max |ΔSE/SE| drops from ~2% to 1e-8 on ETS
and 1e-4 on ARIMA (the FD-Hessian floor). The vcov R-parity test
tightens from rtol=2e-2 to rtol=1e-3.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The historical 5% tolerance reflected the OLS-ULP-into-x0-into-NLopt
cascade that pushed Python's optimiser onto a slightly different B
than R's. With (a) the per-parameter relative FD Hessian step in
hessianCore.h and (b) the shared olsCore.h backend for msdecompose,
that cascade is gone — R and Python converge to the same B on the
ANN/AAN/ARIMA/OM-MNN scenarios, the analytical formula is a closed
form, and multicov agrees to machine precision (worst case ~1e-14,
mostly at or below 1e-15). Empirical multicov is also at machine
precision because the underlying ferrors C++ backend is shared.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Resolve conflicts:
- src/headers/lowess.h: delete (moved to greybox on origin/Python)
- python/pyproject.toml: take origin's nlopt (unpinned) and scipy<1.19.0
- python/src/smooth/__init__.py: union CES/AutoCES with AutoMSARIMA/AutoOM
- python/src/smooth/adam_general/core/__init__.py: union CES/AutoCES with AutoMSARIMA
Bringing up to speeeeeeeeeeed
- ces_model.py: annotate a/b dicts, B and class attributes, cast
  Literal seasonality, replace dict.get key with explicit lambda
- utils.py: type-ignore on the _ols pybind import (mirrors other
  C++ extension imports)
- test_ces.py: add pytestmark = pytest.mark.r_parity so the strict
  1e-9-tolerance R-parity tests opt out of the default run, matching
  test_msdecompose_r_parity.py / test_multicov_r_parity.py / etc.
NEWS.md gets a v1.0.6 entry for the new CES + AutoCES classes
(port of R's ces() / auto.ces()). README.md lists CES in the model
overview, the per-model wiki links, and adds a usage block with
both fixed-seasonality CES() and AutoCES seasonality selection.
@config-i1
config-i1 merged commit 47e8ad0 into master Jun 9, 2026
10 checks passed
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.

CES model in Python Simulation functions in Python

2 participants