SCAR-TM-JACOBI Numerics¶
SCAR-TM-JACOBI¶
SCAR-TM-JACOBI uses a Jacobi diffusion for Kendall's tau on (0, 1). It is
available only for copulas with a Kendall-tau parameter mapping. The main
numerical difference from SCAR-TM-OU is that the transition is built on a
Jacobi quadrature grid in tau space instead of an OU grid in an unbounded
latent coordinate.
| Parameter | Where | Default | Effect |
|---|---|---|---|
alpha0 |
fit kwarg | smart/MLE-based | Initial \([\kappa, m, \xi]\). |
gtol |
fit kwarg / scar_optimizer.gtol |
1e-3 |
L-BFGS-B projected-gradient tolerance. |
maxfun |
fit kwarg / scar_optimizer.maxfun |
300 |
Maximum function evaluations. |
maxiter |
fit kwarg / scar_optimizer.maxiter |
100 |
Maximum optimizer iterations. |
maxls |
fit kwarg / scar_optimizer.maxls |
100 |
Maximum L-BFGS-B line-search steps per iteration. |
eps |
fit kwarg / scar_optimizer.eps |
1e-4 |
Absolute step in raw optimizer coordinates for numerical-gradient fits when finite_diff_rel_step is unset. Inactive for native analytical gradients. |
finite_diff_rel_step |
fit kwarg / scar_optimizer.finite_diff_rel_step |
None |
Relative step in raw optimizer coordinates for numerical-gradient fits. A non-None value takes precedence over eps; a non-None fit kwarg overrides the config. Inactive for native analytical gradients. |
transition_method |
strategy kwarg | 'auto' |
'auto', 'spectral_matrix', 'local', 'local_fixed', or 'spectral_coeff'. |
transition_storage |
strategy kwarg | 'dense' |
Dense transition storage, or opt-in 'sparse' storage for explicit local and local_fixed backends. |
stationarity_correction |
strategy kwarg | 'none' |
Experimental sparse moving-grid correction: 'none', 'mh', or 'ipfp'. |
adaptive_quad_order |
strategy kwarg | False |
Experimentally select and then freeze a sparse moving-grid quadrature order before optimization. |
adaptive_quad_orders |
strategy kwarg | (48, 80, 128, 192, 384, 768) |
Strictly increasing candidate ladder for adaptive quadrature. |
adaptive_max_full_horizon_tv |
strategy kwarg | 0.02 |
Maximum full-horizon total-variation error in the adaptive gate. |
adaptive_max_relative_variance_error |
strategy kwarg | 0.10 |
Maximum relative stationary-variance error in the adaptive gate. |
adaptive_max_conditional_mean_rmse |
strategy kwarg | 1e-3 |
Maximum conditional-mean RMSE in the adaptive gate. |
adaptive_max_lag_one_correlation_error |
strategy kwarg | 1e-2 |
Maximum absolute lag-one correlation error in the adaptive gate. |
adaptive_require_pass |
strategy kwarg | False |
Raise instead of returning an exhausted adaptive-order result when no candidate passes. |
spectral_basis_order / basis_order |
strategy kwarg | 32 |
Number of Jacobi basis functions. |
spectral_quad_order / quad_order |
strategy kwarg | auto | Jacobi quadrature order; default is max(2 * basis_order + 16, 48). |
analytical_grad |
strategy kwarg | False |
Passes a model-provided Jacobian to the optimizer. Fully analytical for local_fixed; semi-analytical for local, spectral_matrix, and auto; native finite differences for spectral_coeff. |
negative_mass_tol |
strategy kwarg | 1e-5 |
Maximum spectral truncation noise that may be clipped and renormalized into a probability transition. Larger negative mass makes auto fall back and makes explicit spectral_matrix fail unless clip_negative=True. |
gh_order |
strategy kwarg | 5 |
Gauss-Hermite order for the local Lamperti transition. |
theta_cap |
strategy kwarg | None |
Optional cap on the copula parameter after mapping from tau. Useful for very high positive dependence. |
clip_negative |
strategy kwarg | False |
Clips negative entries in the truncated spectral matrix before row normalization. Use mainly for diagnostics. |
kappa_bounds |
strategy kwarg | (1e-3, 100.0) |
Bounds for mean-reversion speed. |
xi_bounds |
strategy kwarg | (1e-3, 5.0) |
Bounds for Jacobi volatility. |
stationary_shape_max |
strategy kwarg | 500.0 |
Rejects extremely concentrated stationary beta shapes. |
memory_budget_bytes |
strategy kwarg | 1 GiB |
Conservative pre-allocation limit for basis, transition, gradient, T x K emission, and simultaneous Python/native fixed-draw boundary buffers. |
sampling_method |
strategy kwarg | 'tm_grid' |
Unconditional sampler: likelihood-consistent 'tm_grid' or experimental continuous 'lamperti_euler'. |
lamperti_substeps |
strategy kwarg | 8 |
Euler substeps per observation interval for lamperti_euler. |
lamperti_boundary |
strategy kwarg | 'reflect' |
Boundary policy for lamperti_euler: 'reflect' or diagnostic 'clip'. |
lamperti_eps |
strategy kwarg | 1e-10 |
Interior epsilon used only to evaluate the singular Lamperti drift. |
lamperti_engine |
strategy kwarg | 'native' |
Mandatory fixed-draw C++ Lamperti--Euler engine; legacy 'numba'/'python' labels normalize to 'native'. |
lamperti_chunk_observations |
strategy kwarg | 4096 |
Maximum complete observation intervals per Gaussian-innovation chunk. |
tau_eps |
strategy kwarg | 1e-6 |
Keeps tau away from the endpoints. |
smart_init |
strategy kwarg | True |
Tries an MLE-derived tau initial point and falls back to the fixed initializer if validation fails. |
result = fit(
copula,
u,
method='scar-tm-jacobi',
transition_method='auto',
basis_order=32,
)
Jacobi orders are strict positive integers: booleans and fractional values
are rejected instead of being silently converted. Empty data, non-finite
options, non-positive theta_cap, and invalid physical
alpha0=[kappa, m, xi] are rejected before optimization. All Jacobi orders
have a safety cap of 2048; the default adaptive candidate ladder spans
orders 48 through 768. Before the native Gauss-Jacobi eigensolver or
quadratic transition arrays are allocated, the native domain core performs
checked arithmetic for the simultaneous float64 workspace. The estimate
includes the full Golub--Welsch eigenvector peak for both the Jacobi grid and
the configured Gauss-Hermite order, less any larger dense transition/gradient
workspace already included in the same conservative peak. If
memory_budget_bytes is too small, it raises MemoryError with the
required-byte estimate and guidance to reduce the grid/basis order. The same
1 GiB default guard applies to direct numerical Jacobi entry points. Jacobi
parameter transforms, stationary Beta shapes, Gauss-Jacobi/Gauss-Hermite
rules, normalized basis recurrence, and Lamperti transforms are C++17-owned;
SciPy is not used on these production paths.
Jacobi transfer methods¶
transition_method='auto' first tries spectral_matrix. If the truncated
spectral matrix has negative mass above negative_mass_tol, or if spectral
matrix construction raises a floating-point error, auto uses local.
Forcing transition_method='spectral_matrix' keeps those numerical failures
visible and does not fall back. Spectral negative mass within the configured
tolerance is treated as truncation noise: it is clipped, rows are renormalized,
and the cleanup magnitude is reported in transition diagnostics. A material
negative mass in an explicitly requested spectral matrix raises
FloatingPointError unless clip_negative=True.
transition_method='local_fixed' uses a parameter-independent tau grid and is
the fully analytical backend for analytical_grad=True. The local and
spectral_matrix backends use finite differences for setup-level arrays and
analytical differentiation for the filtering recursion, so their reported
gradient_kind is semi_analytical. For auto, diagnostics record the
backend selected at the fitted parameters. Finite-difference setup
perturbations use that same selected backend, so a single gradient evaluation
cannot mix spectral and local objectives. Analytical-gradient fits also
recompute the ordinary objective at the final point and report
final_objective_consistent in result diagnostics.
Final fit validation checks the native parameter domain and evaluates the
objective and requested gradient without optimizer penalties. A domain or
numerical failure marks the fit unsuccessful regardless of config.fail_value,
so vine fitting can apply its configured fallback policy. The
final_evaluation_status diagnostic records 0 for successful evaluation,
6 for invalid parameters, or 7 for a native numerical failure.
transition_method='spectral_coeff' uses coefficient-space filtering instead
of a transition matrix. It is available for diagnostic comparisons. With
analytical_grad=True, the native evaluator computes the complete objective
gradient by central finite differences and reports
gradient_kind='native_finite_difference'.
Fit and history-dependent evaluations need T >= 2 to define
dt = 1 / (T - 1). A prepared evaluator with one observation can still
condition an existing state. Native observation inputs must be finite and in
[0, 1]; out-of-range data are rejected before copula evaluation. State
conditioning and fixed-draw sampling require strictly increasing tau atoms in
[0, 1] and finite nonnegative masses with a finite positive total. Masses
need not sum to one: both operations normalize the measure without mutating
the caller's arrays. If conditioning has no finite likelihood at any
positive-mass atom, it retains the normalized prior.
Prepared-evaluator construction, state conditioning, and fixed-draw state
sampling reject complex inputs before conversion to float64, including
complex NumPy scalars stored in object arrays. This applies to both the Python
facade and direct native bindings. Real lists, integer/float arrays, and real
object arrays retain their supported conversion behavior.
Unconditional Jacobi sampling¶
By default, sample() reproduces the same discrete Markov model used by the
matrix likelihood. It draws the first state from the stationary quadrature masses,
builds the transition with dt = 1 / (n - 1), converts the probability-safe
transition to row-wise CDFs in place, and advances quadrature-grid indices.
The resulting tau atoms are mapped through tau_to_param, including the
fitted theta_cap, before the existing copula sampler generates observations.
The selected sampling backend follows the fitted backend. For
spectral_coeff, which has no probability transition matrix, sampling
explicitly uses auto with the fitted basis and quadrature orders. Explicit
spectral_matrix sampling fails if its transition contains material negative
mass; signed rows are never converted with absolute values.
n must be a non-negative integer. n=0 returns an empty sample without
advancing the supplied generator, and n=1 performs only a stationary grid
draw. Transition construction is O(K^2 B) for the spectral backend or
O(KG) for local construction, the in-place CDF is O(K^2), and path
generation is O(n log K). Peak memory is conservatively checked before
transition or RNG allocation, including simultaneously live Python, binding,
native, and returned path buffers. Hyphenated transition aliases are
normalized before this preflight and before any RNG draw. The same
parameter-path implementation is used by dynamic edges during C-vine and
R-vine sampling.
sampling_method='lamperti_euler' enables an experimental continuous-path
alternative. It starts from the exact stationary beta law, applies
Euler--Maruyama with lamperti_substeps in
and maps back with \(\tau=\sin^2(\xi y/2)\). The default boundary policy
reflects an overshoot in constant time; 'clip' is retained only for
numerical comparison. A mutable dictionary passed as
sampling_diagnostics= receives the intervention count and rate. This
sampler approximates the continuous SDE but is not the transition backend
used by fitting and is not an exact Wright--Fisher sampler.
The fitted result persists the selected sampling method, Lamperti settings,
and a non-default memory budget. Call-time keyword arguments can override
them. Use tools/validate_jacobi_sampling.py to compare stationary
mean/variance, KS/TV error, conditional first-moment error, interventions,
and runtime over independent path ensembles.
The state evolution is sequential because each Euler update depends on the
preceding state. Random draws remain in the Python orchestration layer and are
passed to C++ in bounded chunks of complete observation intervals.
Consequently, changing lamperti_chunk_observations does not change the path
or RNG state. External applications may parallelize independent paths only
with separate explicitly managed random streams.
Lamperti--Euler evolution now has one mandatory fixed-draw C++17 path. Python
generates the stationary Beta draw and bounded normal chunks; C++ owns the
drift, substeps, boundary policy, tau reconstruction, diagnostics, and exact
draw-consumption counters. Chunk sizing includes both Python/C++ normal
buffers and both native/NumPy result buffers in the memory peak. n=1 has no
Euler work.
Accuracy near singular boundaries remains a separate gate. A symmetric
stationary law with a=b=0.4 remained reasonably stable, but the extreme
asymmetric case a=0.04, b=0.16 showed severe reflection bias even at 64
substeps. Diagnostics therefore expose stationary_boundary_singular and the
boundary-intervention rate. Lamperti--Euler remains opt-in; native execution
does not make it a universally valid default.
Sparse local Jacobi transitions¶
The moving-grid local transition has at most 2 * gh_order active targets
per source node. It can be selected explicitly for likelihood, filtering,
prediction, and state distributions:
result = copula.fit(
u,
method="scar-tm-jacobi",
transition_method="local",
transition_storage="sparse",
)
This backend stores and applies the local transition in O(K * gh_order)
space and time per filtering step. It does not materialize a dense K x K
matrix. The default remains transition_storage="dense", and sparse storage
currently requires an explicit transition_method="local" or
transition_method="local_fixed". The fixed-grid sparse backend also stores
the three transition derivative arrays in O(K * gh_order) space and
supports the existing fully analytical gradient. Spectral transitions
continue to use their dense representation.
Experimental stationarity corrections are available only on the same end-to-end sparse path:
result = copula.fit(
u,
method="scar-tm-jacobi",
transition_method="local",
transition_storage="sparse",
stationarity_correction="mh",
)
The MH correction is available only with the moving-grid local backend. It
enforces the quadrature stationary weights and detailed
balance, but may materially increase stay probabilities and distort
short-horizon dynamics. It is therefore opt-in and is not selected by
auto. Use tools/validate_jacobi_sampling.py to compare transition memory,
filter timings, full-horizon stationarity, conditional moments, and lag-one
correlation.
stationarity_correction="ipfp" instead balances the stationary joint flux
on the existing sparse support. It does not silently add diagonal edges or
regularization. Consequently, IPFP fails explicitly when the proposal
support cannot deliver incoming mass to every stationary node. Even when it
is feasible, its conditional-moment and autocorrelation distortion must be
checked. Current validation does not support either MH or IPFP as a default;
increasing K preserved short-horizon dynamics better in the tested matrix.
Experimental adaptive Jacobi order¶
For an uncorrected sparse moving-grid local backend,
adaptive_quad_order=True evaluates a strictly increasing candidate ladder
before optimization. It selects the first order satisfying deterministic
full-horizon stationarity, variance, conditional-mean, and lag-one
correlation gates:
result = copula.fit(
u,
method="scar-tm-jacobi",
transition_method="local",
transition_storage="sparse",
adaptive_quad_order=True,
adaptive_quad_orders=(48, 80, 128, 192, 384),
)
The selected order is frozen for every fitting evaluation and persisted as
spectral_quad_order. A second diagnostic evaluates the same frozen order at
the fitted parameters; it does not silently increase the sampling grid.
Candidate records and both initial/final gate results are stored in
LatentResult.diagnostics. This mode remains experimental while its default
thresholds are calibrated over a wider parameter and data matrix.
The validation tool's --adaptive-calibration option runs predefined
baseline, high-kappa, symmetric-boundary, and asymmetric-boundary cases over
multiple observation counts.
The current matrix shows strongly non-monotone requirements across parameter
and observation-count regimes: some boundary cases do not pass through
K=384, while the baseline may select K=80 for shorter series but require
K=384 at n=400. Consequently, no stationary-shape-only order heuristic is
used.
The local method applies a Gaussian step in the Lamperti coordinate
then maps the Gauss-Hermite nodes back to tau and interpolates on the Jacobi
quadrature grid. It produces a nonnegative row-normalized transition matrix
and is selected by auto when the one-step transition is too narrow for the
spectral path.
The spectral matrix method uses the Jacobi eigenbasis of the diffusion. It can be useful as a diagnostic, but for high-frequency data the code uses
Large T therefore makes the one-step transition close to a delta kernel.
Representing such a narrow kernel with a truncated global Jacobi series can
produce oscillations, negative entries, or invalid row sums. Increasing
basis_order may reduce the truncation error in some parameter regions, but it
also raises cost sharply and can worsen conditioning. In that regime, matching
the high-order spectral likelihood and the local likelihood at a fitted point
isolates backend approximation error. Leave transition_method='auto' unless
performing that comparison or reproducing a fixed-backend result.