autots.evaluator.tva package

Submodules

autots.evaluator.tva.coherence module

Post-forecast coherence correction for the TVA factor trend mode.

Pulls per-series trend forecasts back toward their shared factor consensus, as a selected correction (never assumed) applied to the trend block only — seasonality/holidays/anomalies/level-shifts are the caller’s to re-add untouched. Loading signs are factor-model-indeterminate, so resolve_signs picks an orientation from the whole panel (not one series) and reports a confidence that gates whether coherence_shrink may act on that factor.

strength=0.0 is always a bitwise identity, and nothing here raises — degenerate inputs (one series, all-zero/NaN loadings, empty or singular graphs) just return the input unchanged.

autots.evaluator.tva.coherence.apply_selection(trend_fc, selection, graphs, config=None)

Apply a select_coherence result and report what it actually did.

adjustment_rms matters because a selected graph/strength that moves the forecast by ~0 is a no-op wearing a hat; the diagnostics should say so.

Parameters:
  • trend_fc – (H, N) standardized trend forecast.

  • selection – a select_coherence result (or None).

  • graphs – the same {name: graph} mapping the selection ran over.

  • config – overrides for DEFAULT_COHERENCE_CONFIG.

Returns:

(adjusted, info). adjusted is the input itself when nothing was selected. info is JSON-safe with graph, strength, applied, sign_confidence, skipped_groups, n_links, adjustment_rms, mase_cost and coherence_gain.

autots.evaluator.tva.coherence.block_adjacency(graph, n_series: int) ndarray

(N, N) block-constant adjacency for a group_graph result.

A group is exactly a clique of +1 links, so this makes the group and Laplacian views share one solver instead of two.

Parameters:
  • graph – a group_graph result dict, or a plain {key: [indices]}.

  • n_series

Returns:

(N, N) symmetric hollow adjacency of 0.0 / 1.0.

autots.evaluator.tva.coherence.build_candidates(loadings, config=None, prior=None)

Convenience: the candidate graph set implied by config['graph'].

Wiring helper so tva.py builds the same candidate set for the inner selection and for the final application with one call each.

When a prior is supplied and config['prior_weight'] > 0, the set gains a prior-blended laplacian_k{k}_prior per neighbour count plus a prior_only adjacency. They are only candidates: select_coherence grades them on held-out inner origins against the unblended graphs and the always-admissible “do nothing” baseline, so a wrong prior costs nothing.

Parameters:
  • loadings – (N, K) loading matrix.

  • config – overrides for DEFAULT_COHERENCE_CONFIG. graph selects the family: 'group', 'laplacian', 'auto' (both) or 'none' (empty, i.e. the correction is disabled).

  • prior – optional (N, N) signed user prior. None reproduces the candidate dict exactly as before.

Returns:

(graphs, meta) where graphs is {name: graph} and meta carries the JSON-safe sign_confidence and signs.

autots.evaluator.tva.coherence.coherence_shrink(trend_fc, graph, strength, weights=None, config=None)

Pull standardized trend changes toward their graph consensus.

Minimizes, independently at each horizon step,

||dx - dx0||^2_W + strength * sum_ij |A_ij| (s_ij * dx_i - dx_j)^2

where dx is each series’ cumulative trend change from the forecast anchor, s_ij = sign(A_ij), and W = diag(weights). This reduces to the signed graph Laplacian L = diag(sum_j |A_ij|) - A and normal equations (W + strength * L) dx = W dx0 — one symmetric solve per forecast, shared across horizon steps since W/L don’t depend on h.

Shrinks changes, not levels, so the forecast anchor (row 0, where dx0 = 0) is structurally preserved for the safety layer’s re-anchoring to own. The group form is this same solve on a block-constant adjacency (block_adjacency), so both views share one solver.

Parameters:
  • trend_fc – (H, N) standardized trend forecast only — seasonality, holidays, anomalies and level-shift intercepts must not be in here; the factor structure has no claim on them.

  • graph – a group_graph dict or an (N, N) signed adjacency.

  • strength – shrinkage weight. 0.0 returns the input unchanged, bitwise.

  • weights – optional (N,) precision weights, overriding the graph’s.

  • config – overrides for DEFAULT_COHERENCE_CONFIG.

Returns:

(H, N) adjusted trend forecast. The input itself on any degenerate case: fewer than two series, an empty or all-zero graph, a non-finite panel, or a solve that fails.

autots.evaluator.tva.coherence.group_graph(loadings, config=None) dict

Block-structured graph: one group per (dominant factor, loading sign).

Grouping by dominant factor and sign keeps every in-group link a +1 link — series loading the same factor with opposite signs are supposed to diverge, not be pulled together. Factors below min_sign_confidence are dropped: without a trusted orientation, “same sign” isn’t meaningful.

dominance_margin and min_loading_share let a series abstain when its dominant factor isn’t decisive; both default to exact no-ops.

Parameters:
  • loadings – (N, K) loading matrix (raw; signs are resolved internally).

  • config – overrides for DEFAULT_COHERENCE_CONFIG. Reads gated (series indices to exclude), sigma, min_sign_confidence, dominance_margin and min_loading_share.

Returns:

dict with

  • groups: {group_key: [series indices]}, keys are strings 'f{k}+' / 'f{k}-' so the result is JSON-safe.

  • weights: (N,) precision weights.

  • confidence: (K,) per-factor sign confidence.

  • signs: (K,) resolved orientation per factor.

  • kind: 'group'.

autots.evaluator.tva.coherence.laplacian_graph(loadings, config=None, n_neighbors: int = 5, prior=None) ndarray

Signed k-nearest-neighbour graph over sign-resolved loading vectors.

General form for panels with no clean dominant-factor partition (overlapping exposures, more factors than the group view expresses). Links are cosine similarity of loading vectors (+1 same profile, -1 opposite), keeping each node’s n_neighbors strongest before symmetrizing — negative links stay meaningful so anti-correlated pairs get pulled apart, not together.

Series below min_sign_confidence on their dominant factor, and series in gated, are isolated (links zeroed) at build time.

Parameters:
  • loadings – (N, K) loading matrix.

  • config – overrides for DEFAULT_COHERENCE_CONFIG.

  • n_neighbors – links retained per node before symmetrization.

  • prior – optional (N, N) signed user prior, blended into the cosine similarity at config['prior_weight'] before the kNN step. Both are signed, so a negative prior link pushes apart natively.

Returns:

(N, N) signed, symmetric, hollow weighted adjacency. All-zero when the graph is degenerate, which every consumer reads as “do nothing”.

autots.evaluator.tva.coherence.net_direction(paths: ndarray, window: int = None) ndarray

(N,) sign of the windowed end-to-end change of each column.

Windowed rather than last-minus-first so a single noisy endpoint can’t decide the direction. A genuinely flat column returns exactly 0.

autots.evaluator.tva.coherence.resolve_signs(loadings, factors=None, stability=None)

Orient each factor by its loading mass, and report the margin.

Unlike the single-largest-loading convention, this votes with every series (sign(sum_i lambda_ik * |lambda_ik|)), so one outlier loading can’t flip the orientation. The margin of that vote becomes the confidence, so a near-50/50 factor can be flagged as having no reliable orientation.

Parameters:
  • loadings – (N, K) loading matrix (or (N,) for a single factor).

  • factors – optional (T, K) factor paths, used only to break an exact zero-mass tie via the factor’s own net drift.

  • stability – optional (K,) per-factor stability in [0, 1], multiplied into the confidence.

Returns:

(signed_loadings, confidence) — the (N, K) loadings with each column multiplied by its resolved sign, and a (K,) array of |sum(w * lambda)| / sum(|w * lambda|) in [0, 1].

autots.evaluator.tva.coherence.select_coherence(trend_folds, actual_folds, graph_candidates, scale, config=None) dict

Choose (graph, strength) on inner rolling origins. Accuracy vetoes.

Two stages: (1) reject any candidate whose pooled scaled MAE exceeds the strength=0 baseline by more than mase_guardrail (accuracy is the floor, not something coherence should be traded for), then (2) among survivors minimize trend-only coherence error, ties going to the smaller strength. strength=0.0 is always admissible, so “do nothing” is always reachable.

Parameters:
  • trend_folds – list of (H, N) standardized trend forecasts, one per inner origin.

  • actual_folds – list of (H, N) realized values aligned to those origins, in the same standardized space.

  • graph_candidates{name: graph}, each a group_graph dict or an adjacency array.

  • scale – (N,) MASE denominators in the same standardized space.

  • config – overrides for DEFAULT_COHERENCE_CONFIG.

Returns:

dict with graph (name or None), strength, baseline_mae, baseline_coherence_error, selected_mae, selected_coherence_error, n_admissible, reason and a JSON-safe table of every candidate evaluated.

autots.evaluator.tva.continuation module

Validation-selected factor continuation (plan item 1a).

Replaces the single damped local-linear extrapolation rule with a candidate set (constant, damped, ridge_ar, regime) chosen per factor by held-out reconstruction error over inner origins, never in-sample fit — an in-sample criterion always prefers the wiggliest continuation.

Pure numpy: nothing here imports torch, so the candidate maths is testable without fitting a model.

autots.evaluator.tva.continuation.apply_choice(paths: ndarray, origins: ndarray, horizon: int, choice: dict, specs: list = None, knot_times: ndarray = None, coef: ndarray = None, model_deltas: ndarray = None, config: dict = None) ndarray

(O, H, K) future deltas for an already-selected per-factor choice.

autots.evaluator.tva.continuation.build_specs(config: dict = None) list

Enumerate candidate continuation specs.

Order is deliberate: model’s own rule first, then the constant null, so a tie during selection resolves toward the incumbent and then toward doing nothing.

Returns:

List of dicts, each with a kind and a stable name.

autots.evaluator.tva.continuation.candidate_futures(paths: ndarray, origins: ndarray, horizon: int, specs: list, knot_times: ndarray = None, coef: ndarray = None, model_deltas: ndarray = None) dict

{(factor_index, spec_name): (O, H) deltas} for every candidate.

model_deltas is the incumbent extrapolator’s (O, H, K) deltas (lives in the torch model, so supplied by the caller); backs the model spec.

autots.evaluator.tva.continuation.continue_factor(path: ndarray, origins: ndarray, horizon: int, spec: dict, knot_times: ndarray = None, coef_k: ndarray = None) ndarray

Future deltas of one factor, relative to its value at each origin.

Deltas (not levels) so every candidate is anchored identically — an anchoring difference would otherwise masquerade as a slope difference during selection.

Returns:

(O, H) array of deltas.

autots.evaluator.tva.continuation.select_continuations(paths: ndarray, origins: ndarray, horizon: int, score_fn, specs: list = None, knot_times: ndarray = None, coef: ndarray = None, model_deltas: ndarray = None, config: dict = None) dict

Choose a continuation per factor by held-out reconstruction error.

Coordinate descent: factors are not independent once loadings mix them, so each factor’s candidate is scored with the others held at their current choice, and the sweep repeated (default 2 passes; the objective is monotone non-increasing by construction).

Parameters:

score_fn – callable taking (O, H, K) future deltas, returning a scalar loss (lower is better) — reconstruction into series space, lag weights, loadings and MASE scaling happen here, deliberately outside this module so the selection logic stays pure.

Returns:

spec_name}), score (final loss), baseline_score (the incumbent’s loss) and scores (per-factor per-candidate losses from the final pass).

Return type:

dict with choice ({factor_index

autots.evaluator.tva.covariance module

Forecast covariance (Sigma) assembly for TVA.

One cross-series forecast-error covariance object, built where the residuals already exist, serving three callers that otherwise each invent their own weighting:

  • MinT reconciliation, which currently falls back to W = I in the default forecasting modes because no residual matrix is available there;

  • the closed-form what-if solver in autots.evaluator.tva.scenario, whose minimum-disruption update is a Gaussian conditioning step under Sigma;

  • interval coherence (later — nothing here consumes it yet).

The estimator is a shrinkage blend of an empirical covariance and a low-rank structural target:

Sigma = (1 - alpha) * Sigma_emp + alpha * Sigma_struct
Sigma_struct = beta * (Lambda Sigma_f Lambda') + diag(psi)

with alpha the Ledoit-Wolf shrinkage intensity of the residual matrix. beta is a single least-squares scalar fitted on the off-diagonals, which is what makes the same assembly usable with loadings that do not live in the residuals’ units (the trend_network='none' path takes its loadings from structure discovery, where they are in standardized-difference units). In 'factor' mode Lambda is already in the residual metric and beta lands near 1, reducing the expression to the plain Lambda Sigma_f Lambda' + diag(psi) factor-model target. psi is then set residually, so diag(Sigma_struct) == diag(Sigma_emp) exactly and the blend only ever changes the off-diagonal (correlation) structure.

autots.evaluator.tva.covariance.apply_variance_floor(sigma: ndarray, floor_sd: ndarray) tuple

Raise diag(sigma) to floor_sd**2 without distorting correlations.

The idiosyncratic term in the factor model is capped at 2 degrees of freedom by design, which is why factor_variance_share reads 1.00 on every panel including factor-free ones: the model’s own residual spread is structurally over-confident. Floor the variances at the same sigma the shipped prediction intervals use, rescaling rows and columns so the correlation matrix is untouched and PSD-ness is preserved.

Parameters:
  • sigma – (N, N) covariance.

  • floor_sd – (N,) minimum standard deviation per series.

Returns:

(sigma_floored, binding) where binding is an (N,) bool array marking the series whose variance the floor actually raised.

autots.evaluator.tva.covariance.assemble_covariance(residuals: ndarray, loadings: ndarray = None, factor_var: ndarray = None, scale: ndarray = None, jacobian: ndarray = None, floor_sd: ndarray = None) tuple

Assemble the forecast covariance from a residual matrix and a factor model.

Parameters:
  • residuals – (n_samples, N) rolling-origin forecast residuals, in the space the model was fit in (normalized, for 'factor' mode).

  • loadings – optional (N, K) factor loadings for the structural target. Without them the estimate is the shrunk empirical covariance alone.

  • factor_var – optional (K,) factor forecast-error variances; required when loadings is given.

  • scale – optional (N,) per-series scale mapping the model space back to raw units.

  • jacobian – optional (N,) additional per-series derivative, used for the signed-log1p modeling space (d level / d log-space).

  • floor_sd – optional (N,) raw-unit standard-deviation floor.

Returns:

(sigma, info) — the (N, N) raw-unit covariance and a diagnostics dict with alpha, beta, psi, n_samples, floor_binding and has_structure.

autots.evaluator.tva.covariance.damped_accumulated_variance(phi: ndarray, horizon: int) ndarray

Per-factor forecast-error variance of a damped random walk, horizon-averaged.

A factor continued with damping phi accumulates unit innovations as e_h = sum_{j=1..h} phi^(j-1) eps_j, so Var(e_h) = (1 - phi^(2h)) / (1 - phi^2). The residual matrix this is compared against pools every horizon step 1..H into one sample set, so the matching summary is the mean over h, not the terminal value.

Parameters:
  • phi – (K,) damping coefficients in [0, 1).

  • horizon – forecast horizon H.

Returns:

(K,) mean-over-horizon accumulated variance per factor.

autots.evaluator.tva.covariance.ledoit_wolf_shrinkage(X: ndarray) tuple

Ledoit-Wolf shrunk covariance and the shrinkage intensity that made it.

Reproduces autots.tools.hierarchial.ledoit_wolf_covariance() (there is a test asserting they agree) while also returning gamma, which that function computes and discards. The intensity is the piece this module actually needs: it is a data-driven statement about how badly conditioned the sample covariance is, which is exactly the question “how far should we lean on a structural target instead”.

Parameters:

X – (n_samples, n_features) residual matrix.

Returns:

(cov, gamma) — the (n_features, n_features) shrunk covariance and the shrinkage intensity in [0, 1].

autots.evaluator.tva.covariance.nearest_psd(sigma: ndarray, ridge: float = 1e-10) ndarray

Symmetrize, clip negative eigenvalues to zero, add a relative ridge.

autots.evaluator.tva.covariance.structural_target(sigma_emp: ndarray, loadings: ndarray, factor_var: ndarray) tuple

Low-rank structural covariance target matched to sigma_emp’s diagonal.

Parameters:
  • sigma_emp – (N, N) empirical covariance.

  • loadings – (N, K) factor loadings, any consistent scaling.

  • factor_var – (K,) factor forecast-error variances.

Returns:

(sigma_struct, beta, psi).

autots.evaluator.tva.decomposition module

NornDecomposer — Wraps TimeSeriesFeatureDetector for TVA decomposition.

In the spirit of structural weaving, this class decomposes time series into constituent threads (trend, seasonality, noise, etc.) for joint forecasting.

class autots.evaluator.tva.decomposition.NornDecomposer(detector_params: dict = None, holiday_country=None, holiday_countries: dict = None)

Bases: object

Wraps TimeSeriesFeatureDetector to extract structured component DataFrames.

Produces trend, seasonality, holidays, level_shifts, anomalies, and noise as aligned DataFrames sharing the same index and columns as the input.

Parameters:

detector_params – Dict of kwargs passed to TimeSeriesFeatureDetector.__init__.

fit(df: DataFrame) NornDecomposer

Fit the feature detector on a wide DataFrame.

Parameters:

df – Wide DataFrame with DatetimeIndex and one numeric column per series.

Returns:

self

get_components() dict

Extract decomposition components as DataFrames.

Returns:

Dict with keys ‘trend’, ‘seasonality’, ‘holidays’, ‘level_shifts’, ‘anomalies’, ‘noise’. Each value is a DataFrame (T, N) aligned with the original input index and columns.

get_features() dict

Return the full detected features dict from the underlying detector.

Includes changepoints, holiday impacts, anomaly records, etc. Useful for constructing priors or inspecting detection results.

get_forecast_components(forecast_length: int) dict

Forward-project seasonality, holidays, level shifts, and trend.

Uses the detector’s built-in forecast method to project known deterministic components into the future.

Parameters:

forecast_length – Number of future periods to project.

Returns:

Dict with same keys as get_components(), each a DataFrame of shape (forecast_length, N).

get_residual_sigma()

Per-series one-step residual sigma from the underlying detector.

Exposes the detector’s own reconstruction-residual sigma (the same quantity its OLS prediction intervals are built from) so TVA can add it in quadrature to the network’s learned sigma. Returns an (N,) float array aligned to the fitted columns, or None when unavailable.

autots.evaluator.tva.discovery module

TVA structure discovery — factors first, conditional edges second.

Torch-free (numpy + sklearn + statsmodels only). This module replaces the free-fit latent adjacency approach: composite trends are discovered as varimax-rotated factors of the differenced trend panel (identifiable, nameable, stable across seeds), and pairwise lead-lag edges are discovered only on the factor RESIDUALS (killing the common-driver false positives that dominate co-trending panels), directed by lag structure through stability-selected lasso VAR, and pruned by edge-level falsification.

Single entry point: discover_structure().

autots.evaluator.tva.discovery.circular_shift_null(residuals: ndarray, screen_fn, n_reps: int = 20, seed: int = 42, quantile: float = 0.95) dict

How many edges does this screening procedure invent on coupling-free data?

Circularly shifting each series destroys cross-series timing while preserving its own autocorrelation and marginal distribution, so any edge still found is manufactured by the procedure rather than the data.

Parameters:

screen_fn – callable (T, N) -> number of edges found.

Returns:

dict with counts, mean, quantile and floor.

autots.evaluator.tva.discovery.conditional_candidates(R: ndarray, top_k: int = 8, max_lag: int = 3) dict

Candidate neighbors per node: conditional dependence + lagged screening.

Contemporaneous screening uses GraphicalLassoCV when well-posed (Ledoit-Wolf + pinv fallback when N > T/2 or it fails to converge) — structurally what pairwise Granger cannot do: a candidate survives conditioning on all other series. Because a purely LAGGED parent can leave little contemporaneous partial correlation, the candidate set is unioned with the top-k series by |cross-correlation| over lags 1..max_lag. Stability-selected lasso downstream rejects the false ones.

autots.evaluator.tva.discovery.deconfound(X: ndarray, scores: ndarray, factor_lags=(0, 1), shared: ndarray = None) ndarray

Regress each series on the factors at the given lags; return residuals.

Downstream edges then mean “B leads A after the shared macro force is removed.”

Parameters:

shared – optional (T, M) extra nuisance columns (detected seasonality, holidays, shifts, anomalies), entered contemporaneously only – lagging a calendar effect would let it absorb real lead-lag signal.

autots.evaluator.tva.discovery.discover_structure(trend_df: DataFrame, anchor_mask: ndarray | None = None, series_metadata: list | None = None, config: dict | None = None, seed: int = 42, extra_adjacencies: dict | None = None, external_factors: ndarray | None = None, components: dict | None = None) dict

Discover factors, loadings, and a conditional lead-lag edge table.

Parameters:
  • trend_df – (T, N) wide DataFrame of decomposed trend components.

  • anchor_mask – optional (N,) bool; factor extraction uses anchors only (responders still get loadings via regression on the factors).

  • series_metadata – optional list of SeriesMetadata (or dicts) used for factor naming.

  • config – overrides for DEFAULT_DISCOVERY_CONFIG.

  • seed – RNG seed for block subsampling (topology is deterministic given seed).

  • extra_adjacencies – optional {family_name: (N, N) adjacency} for the ‘event’, ‘metadata’, ‘business’, and ‘causal’ families built elsewhere. Signed entries are honoured.

  • external_factors – optional (T, K) level-space factor paths to deconfound against instead of the internal difference-space SVD.

  • components – optional detector component dict (seasonality, holidays, level_shifts, anomalies), regressed out alongside the factors when config['deconfound_components'] is set.

Returns:

dict with ‘factors’ (T, r) level-space composite tracks, ‘loadings’ (N, r) sparse signed, ‘factor_names’, ‘edges’ (list of dicts over real series names), ‘adjacency’ (N, N) merged dense form, ‘family_adjacencies’ {family: (N, N)}, ‘communities’ (N,), ‘config’ (resolved).

autots.evaluator.tva.discovery.extract_factors(X: ndarray, config: dict) tuple

Truncated SVD + varimax + soft-thresholding on the differenced panel.

Parameters:
  • X – (T-1, N) differenced, standardized panel.

  • config – resolved discovery config.

Returns:

(factors_level (T, r), loadings (N, r), scores_diff (T-1, r))

autots.evaluator.tva.discovery.falsify_edges(R: ndarray, edges: list, config: dict) list

Held-out one-step MSE with vs without each edge (zeroed coefficient).

Effect-level validation: an edge that does not reduce held-out error is pruned regardless of how significant it looked in-sample.

autots.evaluator.tva.discovery.forecast_factors(factors: ndarray, horizon: int, phi: float = 0.9, fit_window: int = 90) ndarray

Damped local-linear forecast of the factor tracks.

Factors are few series with the full history each — a well-posed estimation problem — so a cheap damped-trend continuation is reliable.

Parameters:
  • factors – (T, r) level-space factor tracks.

  • horizon – steps ahead.

  • phi – damping coefficient.

  • fit_window – trailing window used for the slope estimate.

Returns:

(horizon, r) forecast deltas RELATIVE to the last factor value.

autots.evaluator.tva.discovery.leadlag_edges(R: ndarray, config: dict) list

Windowed cross-correlation lead-lag candidates on trailing residuals.

Keep j -> i where the |cross-correlation| over positive lags of j peaks at lag > 0 and exceeds leadlag_min_corr; top leadlag_top_k per target. Cheap, fitting-free, recomputable at predict time.

autots.evaluator.tva.discovery.match_factors(true_factors, est_factors) dict

Align estimated factors to known/true factors and score the recovery.

Both inputs are level-space tracks (discovery returns cumulative sums of the differenced factor scores; generator factors are levels), so they are differenced before comparison — the shared movement is what identifies a factor, not its arbitrary integration constant. Matching is a Hungarian assignment on the |correlation| matrix, which handles both permutation and sign indeterminacy of the factor basis.

Parameters:
  • true_factors – (T, K) array-like (DataFrame ok) of reference factors.

  • est_factors – (T, r) array-like of estimated factors. May have a different number of factors than true_factors.

Returns:

‘assignment’: {true_index: est_index} for the matched pairs ‘correlations’: {true_index: signed correlation} per matched pair ‘signs’: {true_index: +1/-1} sign of each matched correlation ‘mean_abs_corr’: mean |corr| over matched pairs, averaged over

max(K, r) so missing/spurious factors are penalized

’n_true’, ‘n_est’: factor counts

Return type:

dict with

autots.evaluator.tva.discovery.name_factors(loadings: ndarray, series_names: list, series_metadata: list | None) list

Name each factor by the modal shared metadata attribute of its top loaders.

autots.evaluator.tva.discovery.shared_component_columns(components: dict, columns=None) ndarray

(T, M) panel-mean nuisance columns from a detector component dict.

One column per available component family. Uses the panel mean, not the per-series value, so the per-series remainder stays visible to screening.

autots.evaluator.tva.discovery.stability_selected_edges(R: ndarray, candidates: dict, config: dict, rng: Generator = None) list

Per-target stability-selected LassoLarsIC over lagged candidates.

Subsamples are contiguous windows (block length >> 2 * max_lag) because a plain iid bootstrap is wrong under serial dependence. The windows are a DETERMINISTIC rolling grid over the sample — seed-variance of the discovered topology is zero by construction. An edge (j -> i, lag) is kept when its selection frequency >= stability_threshold.

autots.evaluator.tva.factor_network module

Learned latent-factor trend mode (trend_network='factor').

class autots.evaluator.tva.factor_network.LatentFactorTrend(*args, **kwargs)

Bases: object

autots.evaluator.tva.factor_network.estimate_factors_alternating(values: ndarray, n_factors: int, knot_spacing: int = 7, alpha: float = 0.001, iters: int = 6, init_window: int = 91, gls: bool = True, loading_l1: float = 0.0, loading_l1_adaptive: bool = True, loading_relax: bool = True)

Identify latent factor paths and loadings by alternating GLS/l1-TF.

Torch-free. This is the stage that actually finds the factors; the torch model refines around it.

loading_l1 > 0 swaps every loading solve for the sparse solve in _fit_loadings() — including the final one, which would otherwise overwrite the sparse structure the loop just found.

Returns:

dict with ‘factors’ (T, K), ‘loadings’ (N, K), ‘coefs’ (P, K), ‘design’ (T, P), ‘weights’ (N,).

autots.evaluator.tva.factor_network.fit_anchor_factor_model(*args, **kwargs)
autots.evaluator.tva.factor_network.fit_latent_factor_model(*args, **kwargs)
autots.evaluator.tva.factor_network.hinge_design(n_time: int, knot_spacing: int, max_knots: int = 200) ndarray

Trend-filtering basis: [t/T, (t-k)_+/T] for knots every spacing.

B @ c is piecewise linear with breakpoints only where c is nonzero, and is exactly zero at t = 0 — which is the model’s factor anchor.

Parameters:
  • n_time – number of time steps T.

  • knot_spacing – steps between candidate knots (>= 1).

  • max_knots – cap on candidate knots; widens spacing instead, since the l1 solve is superlinear in column count.

Returns:

(T, P) design matrix, float32.

autots.evaluator.tva.factor_network.hinge_knot_times(n_time: int, knot_spacing: int, max_knots: int = 200) ndarray

Time indices of the hinge knots hinge_design would build.

Derived rather than stored so callers (e.g. the regime-slope continuation candidate) can’t drift out of sync with hinge_design’s widening rule.

autots.evaluator.tva.factor_network.identify_factors(values, n_factors, cfg, seed=42, device='cpu')

Identification dispatch: alternating estimator, optional sparse refit, rotation.

At the default config this is exactly estimate_factors_alternating() followed by the optional C1 rotation, so the seam is a no-op until a knob moves. The sparse backends refine from the alternating fit and fall back to it, which is why they are safe to expose as a plain config value.

Parameters:
  • values – (T, N) normalized panel.

  • n_factors – fitted rank K.

  • cfg – a merged factor config (see DEFAULT_FACTOR_CONFIG).

  • seed – passed to the gradient tier.

  • device – passed to the gradient tier.

Returns:

The identification contract dict.

autots.evaluator.tva.factor_network.observed_mask(values: ndarray, mask=None) ndarray

(T, N) boolean mask of genuinely observed cells.

When mask is None, derived from finiteness of values — correct for a raw panel, degenerates to “everything observed” for a filled one (the back-compatible default).

autots.evaluator.tva.factor_network.robust_level_scale(values: ndarray, floor_frac: float = 0.001)

Per-series robust center/scale in level space.

Parameters:
  • values – (T, N) adjusted level panel.

  • floor_frac – floor on the scale as a fraction of the panel’s median scale, so near-constant series do not explode when normalized.

Returns:

(center (N,), scale (N,)) float arrays.

autots.evaluator.tva.factor_network.rotate_identification(ident: dict, method: str = 'varimax', kaiser: bool = True) dict

Re-express an identification result in a simple-structure basis.

Nothing upstream breaks the factor model’s rotational indeterminacy: the SVD initialization picks an arbitrary orthogonal basis and every loading solve is rotation-invariant lstsq, so the fit recovers the factor span but lands on a rotation of the true basis within it. Since generator (and, by assumption, real) loadings are simple-structure – each series predominantly loading one factor – rotating toward simple structure is the identifying restriction that was missing.

The reconstruction factors @ loadings.T is preserved exactly (to floating point), so this is a re-parameterization, not a refit: canonical correlation, subspace recovery and forecast accuracy are all invariant to it. Only basis-dependent quantities – which series loads which factor, and with what sign – change, which is precisely what the coherence graph reads.

Three transforms are composed:

  1. the rotation itself (R from discovery._varimax(), or its promax extension), applied as L @ R with the counter-transform F @ inv(R).T so the product is unchanged;

  2. re-normalization to unit-std factor increments, matching the convention estimate_factors_alternating() exits on (a rotation mixes columns of differing scale, so this is not a no-op);

  3. a mass-vote sign orientation per factor – the same rule coherence.resolve_signs uses – baked into the parameters, so the model itself is oriented rather than re-oriented at graph-build time.

Parameters:
  • ident – the dict returned by estimate_factors_alternating().

  • method'varimax' | 'quartimax' | 'promax'.

  • kaiser – row-normalize loadings before computing the rotation.

Returns:

A new dict with rotated factors, loadings and coefs (design/weights passed through). Returns ident unchanged on any numerical failure or unknown method – never raises, since a rotation failure must degrade to today’s behavior, not kill a fit.

autots.evaluator.tva.factor_network.select_anchors(values: ndarray, mask=None, min_observed: int = 540)

Split a panel into long-history anchors and short-history responders.

Anchors are chosen purely from actual observed history — never from metadata, series names or declared launch dates, which are routinely wrong in production panels. Shared factor paths are then identified from anchors only, so a late-launching series can’t inject fabricated pre-launch rows into the common-trend estimate.

Callers should set min_observed to roughly 3 x forecast_horizon: a series needs enough history to identify its own loading and cover several non-overlapping validation windows before it defines the factor basis.

Returns:

(anchor_idx, responder_idx) — both ascending int arrays partitioning range(N). If no column clears the threshold the longest-history columns are promoted instead, since a factor model needs anchors.

autots.evaluator.tva.factor_network.select_n_factors(values: ndarray, cap: int = 6, window: int = 181) int

Rank hint from the smoothed level panel’s singular spectrum.

Daily noise dominates the raw spectrum, so it is smoothed first; rank is the Ahn-Horenstein eigenvalue-ratio argmax (argmax_k s_k / s_{k+1}), which needs no threshold calibration.

Returns:

Integer in [1, cap].

autots.evaluator.tva.factor_network.split_half_factor_stability(values: ndarray, n_factors: int, reference=None, n_reps: int = 3, seed: int = 42, **kwargs) ndarray

Per-factor version of split_half_stability(). Returns (K,).

The panel-level score answers “is there shared structure here at all”; this answers “which of these K columns is shared structure” — the question the coherence graph needs, since a single unreplicable column is enough to partition the panel wrongly while the panel-level score still looks fine.

Each replicate fits both disjoint halves, matches each half’s columns to the full-panel reference with match_factors, and scores a factor by the weaker of its two matches: a column that only one half can find is not shared structure. Scores are averaged over replicates.

Parameters:
  • values – (T, N) panel, same input the full fit received.

  • n_factors – K, the reference rank.

  • reference – optional (T, K) full-panel factor paths. Fitted here when not supplied.

  • n_reps – split replicates.

  • seed – RNG seed for the splits.

  • **kwargs – forwarded to estimate_factors_alternating().

Returns:

(K,) array in [0, 1]; NaN entries where nothing could be scored. Never raises — a degenerate panel returns all-NaN.

autots.evaluator.tva.factor_network.split_half_stability(values: ndarray, n_factors: int, n_reps: int = 3, seed: int = 42, **kwargs) float

Truth-free check that the factors are shared structure, not noise.

Real shared factors are recoverable from any subset of the series, so fitting two disjoint halves of the panel should yield matching paths. Factors that are merely absorbing idiosyncratic trends do not replicate.

Returns:

Mean match_factors score between the two halves, in [0, 1].

autots.evaluator.tva.fusion module

Digital Twin Fusion — Component Recombination.

Reconstructs full forecasts by combining trend network output with seasonality, holidays, level shifts, and anomalies. Different variants (attention-weighted or additive) serve as the final weaving mechanism.

autots.evaluator.tva.grouping module

Data-derived global / group factor structure.

Groups are found by consensus clustering (refit on random time/series subsets, cluster loadings, keep pairs stable across refits) rather than metadata, so a block only enters the model if it reproduces out of sample.

autots.evaluator.tva.grouping.average_linkage_clusters(sim: ndarray, threshold: float) ndarray

Average-linkage agglomerative clustering on a similarity matrix.

Implemented directly rather than via scipy/sklearn so this module runs in minimal environments. Merging stops when no two clusters have average similarity above threshold.

Parameters:
  • sim – (n, n) symmetric similarity matrix, larger = more similar.

  • threshold – minimum average similarity to merge.

Returns:

(n,) integer cluster labels, densely numbered from 0.

autots.evaluator.tva.grouping.discover_groups(values: ndarray, global_factors=None, config: dict = None, seed: int = 42, **fit_kwargs) dict

Find residual groups that survive a bootstrap reproduction test.

Projects the global factors out, then refits a small residual factor model on refits random time/series draws, clustering signed loading vectors each time (signed because two series moving opposite a common factor are a hedge pair, not the same group). A group is a set of series whose co-membership frequency across refits clears threshold.

Parameters:
  • values – (T, N) normalized panel.

  • global_factors – (T, Kg) already-fitted global factor paths, or None.

  • config – overrides for DEFAULT_GROUPING_CONFIG.

  • seed – RNG seed.

  • **fit_kwargs – forwarded to estimate_factors_alternating.

Returns:

dict with ‘labels’ (N,) group id per series (-1 = ungrouped), ‘groups’ {gid: [series indices]}, ‘co_membership’ (N, N) frequency, ‘n_refits’, and ‘residual’ (T, N) the panel groups were found in.

autots.evaluator.tva.grouping.loading_graph(loadings: ndarray, labels=None, series_names=None, factor_names=None) dict

The series x factor loading matrix as the panel’s structural graph.

A factor-to-series bipartite graph (N x K) instead of series->series (N^2): exposes the structure actually fit rather than a separately-estimated adjacency.

Parameters:
  • loadings – (N, K) signed loadings.

  • labels – optional (N,) group assignment (-1 for ungrouped).

  • series_names – optional list of series names.

  • factor_names – optional list of factor names.

Returns:

dict with ‘loadings’, ‘group_assignment’, ‘series’, ‘factors’, ‘dominant_factor’ (argmax |loading| per series).

autots.evaluator.tva.grouping.rolling_origin_score(values: ndarray, rank: int, horizon: int, n_origins: int = 3, membership=None, n_global: int = 0, **fit_kwargs) float

Held-out MASE of a rank-r factor model, torch-free.

Each origin refits on truncated history and extrapolates via the same damped local-linear rule the trend model uses, so this is not an in-sample criterion. Rank 0 degenerates to a residual-only continuation.

Parameters:
  • values – (T, N) normalized panel.

  • rank – number of factors (0 allowed).

  • horizon – forecast horizon per origin.

  • n_origins – non-overlapping origins, most recent first.

  • membership – optional (N,) group label array restricting a factor’s loadings to its own group’s series.

  • **fit_kwargs – forwarded to estimate_factors_alternating.

Returns:

Mean MASE over series and origins (lower is better); NaN if no origin has enough history.

autots.evaluator.tva.grouping.select_rank(values: ndarray, candidates=(0, 1, 2, 3, 4, 6), horizon: int = 180, n_origins: int = 3, stability_threshold: float = 0.5, seed: int = 42, **fit_kwargs) dict

Choose a factor rank by held-out error and split-half stability.

In-sample fit is monotone in rank, so it can’t choose one, and held-out error alone is noisy on short panels. Rank is the minimum held-out MASE among ranks whose split-half factor agreement clears stability_threshold; rank 0 is always admissible.

Parameters:
  • values – (T, N) normalized panel.

  • candidates – ranks to consider.

  • horizon – inner validation horizon.

  • n_origins – inner rolling origins.

  • stability_threshold – minimum split-half match_factors score.

  • seed – RNG seed for the split-half repetitions.

  • **fit_kwargs – forwarded to estimate_factors_alternating.

Returns:

dict with ‘rank’, ‘table’ [{‘rank’, ‘score’, ‘stability’, ‘admissible’}].

autots.evaluator.tva.losses module

TVA Loss Functions — The Allfather’s Judgment.

Six loss components that together enforce coherent, high-quality composite trends.

autots.evaluator.tva.metrics module

Shared scoring metrics for the TVA benchmark / validation harnesses.

Kept dependency-light (numpy + pandas only) so harnesses, notebooks and CI gate scripts import the same definitions instead of drifting private copies. All reductions are NaN-tolerant; degenerate inputs return np.nan rather than raising, since these run inside sweep harnesses where one bad cell must not abort the run.

autots.evaluator.tva.metrics.correlated_pairs(train, season_m: int = 7, threshold: float = 0.5, max_pairs: int = 200) list

Pairs of columns whose smoothed, differenced training trends correlate.

Derived from history alone (never the future), so the ground-set pairs are usable to score any model. Smoothing before differencing suppresses seasonal/noise so correlation reflects shared low-frequency drift. Capped at max_pairs via a seed-0 RNG subsample for reproducibility.

autots.evaluator.tva.metrics.dca_error(forecast, actual, pairs) float

Mean |sign-agreement(forecast) - sign-agreement(actual)| over pairs.

Scored against the realised future rather than as a raw rate, since a model that forces lockstep is as wrong as one that scatters. Lower is better; np.nan when there are no usable pairs.

autots.evaluator.tva.metrics.direction_coherence(frame, pairs, window: int = 28) float

Fraction of pairs whose net-change directions agree and are nonzero.

The net-change analogue of sign_agreement(). Zero-direction (flat) columns count toward the denominator but never toward the numerator, so “predict nothing moves” scores 0 rather than winning by abstention. np.nan when no pair is usable.

autots.evaluator.tva.metrics.direction_coherence_error(forecast, actual, pairs, window: int = 28) float

|direction_coherence(forecast) - direction_coherence(actual)|.

Net-change analogue of dca_error(): penalises both over- and under-coupled forecasts against the realised future. Lower is better.

autots.evaluator.tva.metrics.directional_coherence(forecast, loadings) float

Fraction of same-dominant-factor, same-sign pairs forecast to agree.

Scored against the known factor structure since MASE alone won’t surface a mixed-direction forecast where everything should move together.

Parameters:
  • forecast – DataFrame, horizon rows x series columns.

  • loadings – series (index) x factor (columns) loading matrix.

Returns np.nan when loadings are empty or fewer than two series carry a loading – undefined, not zero, for a factorless negative control.

autots.evaluator.tva.metrics.loading_structure_score(true_loadings, est_loadings, true_factors=None, est_factors=None, asserted_pairs=None) dict

Score how well estimated loadings recover the true loading structure.

Canonical correlation says whether the factor span was found; this says whether the basis within the span is the true one. A rotation of the correct span scores near chance here while scoring ~1.0 on span metrics, which is exactly the failure mode the coherence graph is built on top of.

Parameters:
  • true_loadings – (N, K_true) generator loadings (DataFrame or array).

  • est_loadings – (N, K_est) fitted loadings, same series order.

  • true_factors – optional (T, K_true) true factor paths.

  • est_factors – optional (T, K_est) estimated factor paths. When both are supplied, columns are matched with discovery.match_factors (Hungarian on differenced |corr|); otherwise a greedy |corr| match on the loading columns themselves is used.

  • asserted_pairs – optional iterable of (i, j) or (i, j, sign) series-index pairs the caller’s graph asserts as same-group (e.g. coherence._graph_pairs(group_graph(est_loadings, cfg), N)). Defaults to the pairs implied by the estimated loadings’ own (dominant factor, sign) partition.

Returns:

dict with pair_precision, pair_recall, pair_f1, n_pairs_asserted, n_pairs_true, dominant_recovery, dominant_recovery_matched, sign_agreement, matched_loading_corr, n_true, n_est. Undefined quantities are np.nan (counts are 0); never raises.

autots.evaluator.tva.metrics.mase_value(actual, forecast, train, m: int = 7) float

Mean (over series) of MAE / in-sample seasonal-naive MAE.

Scaling by seasonal-naive error makes the number comparable across datasets of different magnitude; the scale <= 1e-9 guard drops constant series that would otherwise divide by ~zero and dominate.

autots.evaluator.tva.metrics.net_change_direction(frame, window: int = 28) Series

Sign of (mean of last window rows - mean of first window rows).

Deliberately not last-point minus first-point: a single-point comparison is dominated by seasonality/noise and lets a near-flat forecast pick up a direction from an arbitrarily small difference.

Shrinks the window to max(1, len(frame) // 2) when the frame has fewer than 2 * window rows, so the two windows never overlap.

Returns a pd.Series of -1.0 / 0.0 / 1.0 indexed by column (NaN for all-NaN columns).

autots.evaluator.tva.metrics.oracle_normalized_coherence(forecast, actual, loadings) float

Coherence relative to the metric’s own ceiling on the real future.

A perfect forecast doesn’t score 1.0: the realised future only partly honours the factor structure once noise is added. Dividing by the actual future’s score stops gates from chasing an unreachable number.

Returns np.nan when the oracle denominator is NaN or <= 0.

autots.evaluator.tva.metrics.real_data_coherence(forecast, actual, pairs, window: int = 28) float

Recall of co-movement that actually happened.

Among pairs whose actual net-change directions agreed, the fraction the forecast also agreed on – conditional on truth, unlike direction_coherence_error(), so it reads as a plain success rate on real panels with no factor loadings.

Returns np.nan when no actual pair agrees – an undefined denominator, not a score of zero.

autots.evaluator.tva.metrics.sign_agreement(df, pairs) dict

Per-pair rate of first-difference sign agreement.

The raw ingredient of dca_error(). Reported per pair (rather than averaged immediately) so callers can compare the same pair between a forecast and the actual future.

autots.evaluator.tva.metrics.trend_only_coherence(trend_forecast, loadings) float

directional_coherence() applied to a trend-only forecast path.

Isolates the structural component: seasonality/holidays can flip a first->last comparison on a short horizon. Named separately so gate tables and result JSONs record which path was scored.

autots.evaluator.tva.priors module

YggdrasilPriors — Prior specification and encoding for the TVA graph.

Acts as the ‘world tree’ for the forecasting graph, connecting related time series across different domains through shared metadata.

class autots.evaluator.tva.priors.SeriesMetadata(name: str, attribute_values: Dict[str, str] | None = None, attribute_weights: Dict[str, float] | None = None, hierarchy_path: list | None = None, history_periods: int = 0, metric_type: str | None = None, surface: str | None = None, geography: str | None = None)

Bases: object

Metadata descriptor for a single time series.

name

Series identifier matching DataFrame column name.

Type:

str

attribute_values

Arbitrary categorical metadata for the series.

Type:

Dict[str, str]

hierarchy_path

Ordered path from root to leaf (e.g. [‘global’, ‘NA’, ‘US’]).

Type:

list | None

history_periods

Number of observed time periods available.

Type:

int

attribute_values: Dict[str, str]
attribute_weights: Dict[str, float]
property geography: str | None
hierarchy_path: list | None
history_periods: int = 0
property metric_type: str | None
name: str
property surface: str | None
class autots.evaluator.tva.priors.YggdrasilPriors(series_metadata: list = None, relationship_matrix: ndarray = None, prior_confidence: float = 0.3, detected_features: dict = None, trend_data: DataFrame = None, observed_history: Dict[str, int] = None, prior_construction_config: dict = None, causal_prior_construction_config: dict = None, series_names: list = None)

Bases: object

Constructs prior adjacency matrices, metadata embeddings, and hierarchy matrices from series metadata. All methods return sensible defaults when metadata is absent, so TVA runs with or without priors.

Parameters:
  • series_metadata – List of SeriesMetadata, one per series.

  • relationship_matrix – Optional (N, N) soft prior adjacency override.

  • prior_confidence – Weight of prior vs learned structure (0=ignore, 1=rigid).

build_hierarchy_matrix() ndarray

Build summing matrix S for hierarchical reconciliation.

S has shape (L, M) where L = total nodes (aggregates + bottom), M = number of bottom-level series. Compatible with mint_reconcile(S, y_all, W).

Returns identity if no hierarchy paths are specified.

build_metadata_embeddings() ndarray

Build (N, D_meta) one-hot embedding matrix from categorical metadata.

Encodes arbitrary categorical metadata keys as concatenated one-hot vectors. Returns zeros if no metadata is available.

build_prior_adjacency() ndarray | None

Backward-compatible alias for structural prior construction.

build_structural_prior_adjacency() ndarray | None

Construct a soft structural prior from explicit, metadata, and event sources.

get_anchor_mask(min_history: int) ndarray

Return boolean mask (N,) where True = series has enough history to be an anchor.

Anchors shape the core latent trend graph. Responders inherit composite trends but do not perturb the core geometry.

get_series_names() list

Return ordered list of series names.

property n_series
autots.evaluator.tva.priors.coerce_prior_adjacency(prior, series_names) ndarray | None

Normalize any supported user prior spec into a signed (N, N) matrix.

The single entry point for user-supplied graph priors, so every downstream consumer reads one shape regardless of how the prior was expressed.

Supported forms:

  • None -> None

  • (N, N) array-like -> validated and passed through

  • pd.DataFrame -> reindexed onto series_names (order-safe)

  • list of {'source', 'target', 'weight', 'directed'} dicts -> symmetric unless the row sets directed: True

  • list of name groups ([['a', 'b'], ...]) -> each group is a clique at weight 1.0; {'series': [...], 'weight': 0.6} for a weaker group

Unknown series names raise a RuntimeWarning naming them and are dropped, never silently ignored.

Parameters:
  • prior – user-supplied prior in any of the forms above.

  • series_names – panel column order defining the matrix axes.

Returns:

(N, N) float32, signed, clipped to [-1, 1], hollow diagonal; or None.

autots.evaluator.tva.reconciliation module

ReconciliationBridge — Bridge to AutoTS hierarchical reconciliation.

Thin adapter between TVA forecasts and the reconciliation functions in autots/tools/hierarchial.py (mint_reconcile, erm_reconcile, etc.).

class autots.evaluator.tva.reconciliation.ReconciliationBridge(method: str = 'mint', covariance_method: str = 'ledoit_wolf')

Bases: object

Bridges TVA forecasts to hierarchical reconciliation methods.

Handles S matrix construction, covariance estimation, and method dispatch.

Parameters:
  • method – Reconciliation method (‘mint’, ‘erm’, ‘iterative_mint’, ‘volatility_mint’). Default ‘mint’.

  • covariance_method – How to estimate W (‘ledoit_wolf’, ‘identity’, ‘sample’, or ‘structural’). ‘structural’ means the caller supplies W directly (see reconcile’s W argument) rather than estimating it from a residual matrix — the case for TVA’s factor and torch-free modes, which have no per-node residual history but do produce a cross-series forecast covariance.

reconcile(forecasts: DataFrame, S: ndarray, residuals: ndarray = None, W: ndarray = None) DataFrame

Apply hierarchical reconciliation to forecast DataFrame.

Parameters:
  • forecasts – (T, L) DataFrame with all hierarchy levels. Columns should be ordered: aggregate nodes first, then bottom nodes, matching the row order of S.

  • S – (L, M) summing matrix from YggdrasilPriors.build_hierarchy_matrix().

  • residuals – (T_hist, L) historical residuals for covariance estimation. If None, uses identity covariance.

  • W – optional precomputed (L, L) covariance, used instead of estimating one from residuals. Supplying it switches the covariance method to ‘structural’ for this call only, so the default residual-based path is untouched.

Returns:

Reconciled DataFrame with same shape and columns as input.

reconcile_with_volatility(forecasts: DataFrame, S: ndarray, residuals: ndarray, cov_bottom: ndarray = None, volatility_method: str = 'variance', volatility_power: float = 1.0, alpha: float = 0.5) DataFrame

Volatility-weighted reconciliation for covariance-aware adjustment.

Parameters:
  • forecasts – (T, L) DataFrame.

  • S – (L, M) summing matrix.

  • residuals – (T_hist, L) historical residuals.

  • cov_bottom – (M, M) bottom-level covariance. Estimated if None.

  • volatility_method – ‘variance’, ‘std’, or ‘cv’.

  • volatility_power – Power to raise volatility weights to.

  • alpha – Blend between standard W and volatility weights.

Returns:

Reconciled DataFrame.

autots.evaluator.tva.robust_input module

Robust trend-isolation input estimator for the latent-factor stage.

Alternative to TVA._build_adjusted_panel: jointly and robustly splits a (T, N) level panel into shared low-rank trend, series-local trend, sparse anomalies, and persistent intercept shifts, holding steps/spikes out of the low-rank part so they can’t be absorbed into a spurious shared factor.

autots.evaluator.tva.robust_input.compare_inputs(raw, detector_adjusted, robust_adjusted, oracle_trend) dict

Score four candidate factor-model inputs against the true trend.

Separates decomposition failure from factor-model failure: if the robust input scores near the oracle and the factor model still recovers poorly, the loss is downstream; if the robust input sits near the detector-adjusted panel, decomposition is the binding constraint.

Scores (all column-wise on mean-removed series, then averaged): mean_abs_corr – mean |correlation| with oracle (1 is best); nrmse – RMSE / oracle std (0 is best, 1.0 = no better than flat); residual_energy_retained – non-trend energy left, as a fraction of the raw panel’s (raw scores 1.0, oracle 0.0, >1.0 means added noise).

Returns:

dict keyed by 'raw', 'detector', 'robust', 'oracle', each a dict of the three scores plus 'n_scored', and a 'ranking' key listing input names best-first by mean_abs_corr.

autots.evaluator.tva.robust_input.robust_adjusted_panel(values, mask=None, components=None, shrink=None, config=None) dict

Jointly estimate a trend-only level panel from raw levels + nuisances.

Drop-in alternative to TVA._build_adjusted_panel: treats detector components as nuisance regressors with adjustable trust (shrink) and alternates, to convergence, between an IRLS-Huber l1 trend fit per series, a truncated SVD splitting shared vs. series-local trend, soft-thresholded anomalies, and binary-segmented intercept shifts. Anomaly/shift terms are subtracted before the next pass’s trend fit so a step can’t be laundered into a shared “regime” via the low-rank factorization.

Never raises on degenerate input; falls back to the raw panel minus the shrunk components (the pre-existing behaviour) on any failure.

Parameters:
  • mask – (T, N) bool, True where observed; inferred from finiteness of values when None. A filled run must be False here, or it carries estimation weight it shouldn’t.

  • components – dict of detector estimates, any subset of ('seasonality', 'holidays', 'anomalies', 'level_shifts').

  • shrink – {component_name: float in [0, 1]} – how much of each component to subtract; < 1 hedges against a component estimate that absorbed real trend.

  • config – overrides of DEFAULT_ROBUST_INPUT_CONFIG.

Returns:

dict with 'adjusted', 'shared', 'idio', 'anomalies', 'shifts' (all (T, N) level units), 'scale', 'center' (N,), 'n_iters', 'converged', 'diagnostics'.

autots.evaluator.tva.safety module

Safety floor for the TVA factor trend mode.

trend_network='factor' extrapolates a whole-history latent trend, which is prone to integrating a small slope error into a large level error at long horizons. Four independent, optional corrections — blend toward SeasonalNaive, cap the extrapolation, re-anchor the level, calibrate sigma by horizon bucket — each selected by inner rolling-origin validation rather than assumed.

Every identity path (blend weight 1.0, alpha 0.0, no cap, bucket_scales=None) is bitwise exact, so wiring this module in cannot perturb existing results until a selector actually fires. Nothing here raises; degenerate input returns the safe default. Pure numpy/pandas — no torch, no sklearn.

autots.evaluator.tva.safety.apply_error_cap(forecast, lower, upper) ndarray

Clip a forecast into the band, elementwise.

NaN in a bound means “no cap on that cell”. With both bounds all-NaN the output is bitwise identical to the input.

Parameters:
  • forecast – (H, N) forecast to clip.

  • lower – (H, N) lower bounds, NaN where uncapped.

  • upper – (H, N) upper bounds, NaN where uncapped.

Returns:

(H, N) clipped forecast. Use count_capped() to learn how many cells the band actually bound.

autots.evaluator.tva.safety.apply_reanchor(forecast, offset, alpha) ndarray

Shift a forecast by alpha * offset, one constant per series.

Parameters:
  • forecast – (H, N) forecast.

  • offset – scalar or (N,) level offset (anchor - origin). Non-finite entries are treated as 0.0.

  • alpha – scalar or (N,) selected fraction of the offset to apply.

Returns:

(H, N) shifted forecast. With alpha == 0 the output is bitwise identical to the input.

autots.evaluator.tva.safety.blend_forecasts(tva_fc, sn_fc, weights) ndarray

Convex combination w * tva + (1 - w) * sn.

Parameters:
  • tva_fc – (H, N) factor-mode forecast.

  • sn_fc – (H, N) seasonal-naive forecast.

  • weights – scalar or (N,) weight on the TVA forecast, broadcast over the horizon. Non-finite weights are treated as 0.0.

Returns:

(H, N) blended forecast. weights == 1.0/0.0 return tva_fc/ sn_fc bitwise identical — callers rely on this.

autots.evaluator.tva.safety.conformal_sigma(base_sigma, horizon: int, bucket_scales, config=None) ndarray

Expand a per-series sigma into a per-(step, series) sigma.

Parameters:
  • base_sigma – scalar or (N,) per-series sigma from the model.

  • horizon

  • bucket_scales – (H,) multipliers from horizon_bucket_scales(), or None for the flat tile.

  • config – unused today; accepted so callers can pass their config through uniformly with the rest of this module.

Returns:

(H, N) sigma. With bucket_scales=None this is exactly np.tile(base_sigma, (H, 1)), bitwise.

autots.evaluator.tva.safety.count_capped(forecast, lower, upper) int

Number of cells apply_error_cap() would move.

Reported through summarize() — a large fraction of H * N signals structural divergence worth surfacing, not just silently correcting.

autots.evaluator.tva.safety.error_cap_bounds(sn_fc, inner_abs_errors, horizon: int, config=None, bucket_scales=None) tuple

Band around SeasonalNaive that the final forecast may not leave.

The blend bounds the average damage; the cap bounds the worst cell. Band is sn_fc ± error_cap_mult * q, where q is the error_cap_quantile of the baseline’s own absolute inner-validation error. When bucket_scales is supplied the half-width is scaled per horizon step (1b’), since a flat band is too tight late and too loose early.

Parameters:
  • sn_fc – (H, N) seasonal-naive forecast, the band’s center.

  • inner_abs_errors – either a (N,) array of per-series quantiles already computed by the caller, or a list/array of (H, N) per-fold absolute error arrays, in which case the quantile is computed here.

  • horizon – H, the number of steps the bounds must cover.

  • config – overrides for DEFAULT_SAFETY_CONFIG.

  • bucket_scales – optional (H,) per-step multiplier from horizon_bucket_scales(). None gives a flat band.

Returns:

(lower, upper), each (H, N). Cells whose quantile could not be estimated are nan, which apply_error_cap() reads as “no cap”.

autots.evaluator.tva.safety.horizon_bucket_scales(residuals_by_h, config=None, quantile=None) ndarray

How much wider the error distribution gets as the horizon grows.

Replaces a single sigma tiled flat across H, which under- and over-covers in opposite directions and averages out to look fine. Each bucket’s scale is the empirical absolute-residual quantile pooled over origins/steps/series, normalized so the first bucket is 1.0 — a pure shape that composes with any per-series sigma the caller has.

Parameters:
  • residuals_by_h – (O, H, N) rolling-origin residuals (actual - forecast).

  • config – overrides for DEFAULT_SAFETY_CONFIG; horizon_buckets are inclusive 1-indexed step ranges.

  • quantile – nominal level for the conformal scale. Defaults to the config’s error_cap_quantile.

Returns:

(H,) multiplier per horizon step. Steps past the last bucket take the last bucket’s scale; a bucket with no usable residuals inherits the previous bucket’s scale rather than collapsing to zero. Degenerate input returns all ones (flat, back-compatible).

autots.evaluator.tva.safety.seasonal_naive_forecast(history, horizon: int, season_m: int = 7) ndarray

Tile the last season of observations forward.

The baseline the safety floor blends toward and caps around: no parameters to overfit and no slope to integrate, so error stays bounded by the seasonal pattern rather than growing with h. A missing seasonal slot falls back to the last finite value in that column (0.0 if none).

Parameters:
  • history – (T, N) observed panel, most recent observation last.

  • horizon – number of steps to produce (H).

  • season_m – seasonal period, in steps. Values < 1 are treated as 1 (last-value persistence).

Returns:

(H, N) float array. Empty history returns zeros.

autots.evaluator.tva.safety.select_blend_weights(tva_folds, sn_folds, actual_folds, scale, config=None) ndarray

Pick, per series, how much weight the factor forecast has earned.

Scored on inner rolling-origin validation only, never on data the factor model was fit to or the outer test set. Ties favor the smaller TVA weight: equal measured accuracy is not equal risk, since the higher weight carries unmeasured extrapolation risk into the future.

Parameters:
  • tva_folds – list of (H, N) factor forecasts, one per inner origin.

  • sn_folds – list of (H, N) seasonal-naive forecasts, same origins/shapes.

  • actual_folds – list of (H, N) realized values, same origins/shapes.

  • scale – (N,) MASE denominator (in-sample seasonal-naive MAE per series).

  • config – overrides for DEFAULT_SAFETY_CONFIG.

Returns:

(N,) weights on the TVA forecast, each drawn from blend_grid. A series with no finite score anywhere — all-NaN history, no usable fold — gets 0.0, i.e. pure SeasonalNaive, the safe default.

autots.evaluator.tva.safety.select_reanchor_alpha(tva_folds, actual_folds, anchor_levels, origin_levels, scale, config=None) ndarray

Decide how much of the origin level offset to remove, per series.

The whole-history factor reconstruction can sit at a level that disagrees with recent data — a constant bias worth removing over a long horizon. Not removed unconditionally: the recent median is also the noisiest level estimate available, so alpha is selected on inner folds with the same tie rule preferring alpha=0.0.

Parameters:
  • tva_folds – list of F (H, N) factor forecasts at each inner origin.

  • actual_folds – list of F (H, N) realized values.

  • anchor_levels – (F, N) robust recent level — the median of the last reanchor_window observations of the adjusted panel at each inner origin.

  • origin_levels – (F, N) level the model’s own forecast implies at step 0 of each fold.

  • scale – (N,) MASE denominator.

  • config – overrides for DEFAULT_SAFETY_CONFIG.

Returns:

(N,) alpha per series, drawn from reanchor_alphas. Series with no finite score, or with no usable offset, get 0.0 — no correction.

autots.evaluator.tva.safety.summarize(blend_weights=None, reanchor_alphas=None, bucket_scales=None, capped_cells=None, scale=None, offsets=None, n_series=None, horizon=None, config=None, **extra) dict

JSON-safe diagnostics for get_factor_diagnostics().

Reports both aggregate summary stats and full per-series arrays. n_series_zero_weight == N is a valid outcome (factor mode earned nothing on this dataset), not a bug to tune away.

Parameters:
  • blend_weights – (N,) selected weights on the TVA forecast.

  • reanchor_alphas – (N,) selected re-anchor fractions.

  • bucket_scales – (H,) horizon-bucket sigma multipliers.

  • capped_cells – number of cells the error cap moved.

  • scale – (N,) MASE denominators used for selection.

  • offsets – (N,) applied level offsets.

  • n_series – N, if the caller wants it recorded explicitly.

  • horizon – H, if the caller wants it recorded explicitly.

  • config – the safety config in force; stored stringified-safe.

  • **extra – any additional scalars/arrays to record verbatim.

Returns:

dict of json-serializable values; NaN becomes None.

autots.evaluator.tva.scenario module

What-If Scenario Planning.

Two solvers for one problem — satisfy a user constraint while minimizing disruption to the rest of the forecast.

BifrostOptimizer freezes the trend network’s weights and optimizes a latent perturbation by gradient descent. It needs a network, so it serves trend_network='v1' and 'v2'.

ClosedFormScenario serves 'factor' and 'none', where there is no network. Those forecasts are linear in a few factor paths, so the minimum-disruption update is a Gaussian conditioning solve against the forecast covariance — deterministic, torch-free, and cross-series aware through the same Sigma that weights MinT reconciliation.

class autots.evaluator.tva.scenario.BifrostOptimizer(tva_model, n_steps: int = 50, lr: float = 0.01)

Bases: object

Inference-time optimizer for scenario planning and what-if analysis.

Freezes all network weights. Creates an optimizable perturbation in latent space and runs gradient descent to satisfy user constraints while minimizing total perturbation. The perturbation penalty is covariance-weighted: high-variance components absorb more of the adjustment, protecting high-confidence elements.

Parameters:
  • tva_model – Fitted TVA instance.

  • n_steps – Number of optimization steps.

  • lr – Learning rate for the perturbation optimizer.

apply_constraint(series_name: str, timestep: int, target_value: float) DataFrame

Pin a specific series at a specific timestep to a target value.

Parameters:
  • series_name – Column name of the series to constrain.

  • timestep – Forecast timestep index (0-based).

  • target_value – Desired value at that timestep.

Returns:

Adjusted forecast DataFrame for ALL series.

apply_growth_constraint(series_name: str, growth_rate: float) DataFrame

Constrain a series to a specific growth rate over the forecast horizon.

Parameters:
  • series_name – Column name.

  • growth_rate – Desired growth rate (e.g. 0.05 for 5% growth).

Returns:

Adjusted forecast DataFrame.

apply_hierarchical_adjustment(level_name: str, target_value: float, hierarchy_matrix: ndarray = None) DataFrame

Set an aggregate level to target_value and propagate down proportionally.

This is an adjustment (set to exactly target_value at every timestep), not a constraint (threshold/clamp that only fires when exceeded).

The delta between target_value and the current aggregate sum is distributed to constituent bottom-level series proportional to their current share of that aggregate. When the current aggregate is near zero, the delta is split equally across constituent series.

Parameters:
  • level_name – Name of the aggregate node to adjust, matched against the last component of hierarchy path tuples (e.g. ‘global’, ‘NA’, ‘EU’). If multiple nodes share the same last component, the first (shallowest) match is used.

  • target_value – Desired flat aggregate value applied across all forecast timesteps. The constituent series are scaled so their sum equals this value at every step.

  • hierarchy_matrix – (L, M) summing matrix S. If None, built automatically from priors configured at fit time. Required when no series metadata was provided.

Returns:

Adjusted forecast DataFrame (same shape as predict()), with constituent series modified so their aggregate sum equals target_value.

class autots.evaluator.tva.scenario.ClosedFormScenario(tva_model, covariance: ndarray = None)

Bases: object

Covariance-weighted what-if solver for the factor and torch-free modes.

BifrostOptimizer backpropagates a perturbation through the trend network, which is unavailable in trend_network='factor' and 'none' (tva._network is None there, so the optimizer dereferences None and raises). Those modes do not need backprop: their forecast is linear in a small set of factor paths, so any user constraint is a linear functional of the forecast vector and the minimum-disruption update has a closed form — the same Gaussian-conditioning / MinT-shaped solve:

delta = Sigma A' (A Sigma A')^-1 (b - A y_hat)

This is the minimum-Sigma-norm correction satisfying A(y+delta) = b exactly, is deterministic, needs no optimizer steps, and reads the same covariance object as reconciliation — which is what makes the adjustment genuinely cross-series aware instead of a proportional top-down split.

Non-factor components (seasonality, holidays, level shifts) need no special handling: they are additive offsets already inside the base forecast, so they enter only through the residual b - A y_hat. Declared ratio identities are re-applied last, matching _predict_factor’s ordering.

The solve happens in raw forecast units. When the factor model was fit in log space, TVA.forecast_covariance() has already carried that geometry across with a delta-method Jacobian; solving in raw units keeps linear aggregate constraints (which a log-space solve would not preserve) exactly satisfied.

Parameters:
  • tva_model – Fitted TVA instance.

  • covariance – Optional precomputed (N, N) covariance. Fetched from tva_model.forecast_covariance() when omitted.

apply_constraint(series_name: str, timestep: int, target_value: float) DataFrame

Pin a series at a timestep; other series move along Sigma.

Parameters:
  • series_name – Column name of the series to constrain.

  • timestep – Forecast timestep index (0-based).

  • target_value – Desired value at that timestep.

Returns:

Adjusted forecast DataFrame for ALL series.

apply_growth_constraint(series_name: str, growth_rate: float) DataFrame

Constrain a series to a growth rate over the forecast horizon.

Parameters:
  • series_name – Column name.

  • growth_rate – Desired growth rate (e.g. 0.05 for 5% growth).

Returns:

Adjusted forecast DataFrame.

apply_hierarchical_adjustment(level_name: str, target_value: float, hierarchy_matrix: ndarray = None) DataFrame

Set an aggregate to target_value at every timestep.

With a forecast covariance available the delta is distributed by the covariance-weighted solve — series that co-move with the rest of the aggregate absorb more of it than their raw share would suggest. Without one, this falls back to the proportional split BifrostOptimizer performs.

Parameters:
  • level_name – Aggregate node name, matched against the last component of hierarchy path tuples (e.g. ‘global’, ‘NA’) or the full slash-joined path. Shallowest match wins.

  • target_value – Desired flat aggregate value across all timesteps.

  • hierarchy_matrix – (L, M) summing matrix S. Built from the fitted priors when omitted.

Returns:

Adjusted forecast DataFrame whose constituent series sum to target_value at every timestep.

covariance(n_series: int = None)

(N, N) forecast covariance, or None when unavailable.

autots.evaluator.tva.seasonal module

Per-series seasonal-path arbitration.

Chooses per series, by held-out error, among: datepart (the panel-wide incumbent, kept on ties since only it expresses holiday/calendar structure), empirical (tile the series’ own recent detrended cycles), and amplitude (datepart path rescaled by a ridge-estimated per-series scalar).

autots.evaluator.tva.seasonal.amplitude_scale(fitted: ndarray, residual: ndarray, config: dict = None) ndarray

(N,) per-series multiplier on the fitted seasonal path.

Ridge projection of recent detrended residual onto the fitted path, shrunk toward 1 (not 0) and clipped — a weak seasonal signal should keep the model’s estimate rather than collapse to no seasonality.

autots.evaluator.tva.seasonal.assemble_choice(candidates: dict, choice: dict, default: str) ndarray

(H, N) seasonal path assembled column-by-column from a selection.

autots.evaluator.tva.seasonal.empirical_profile(residual: ndarray, season_m: int, n_cycles: int = 2)

(season_m, N) average of the last n_cycles detrended cycles.

Parameters:
  • residual – (T, N) panel with trend and level shifts already removed.

  • season_m – cycle length in steps.

  • n_cycles – how many trailing cycles to average.

Returns:

(season_m, N) profile aligned so row 0 is the phase immediately following the end of residual, or None when history is too short. Centered per series: a nonzero-mean cycle would double-count level, which the trend term already owns.

autots.evaluator.tva.seasonal.select_seasonal_paths(candidates: dict, actual: ndarray, scale: ndarray = None, default: str = 'datepart', config: dict = None) dict

Choose a seasonal path per series from held-out error.

Parameters:
  • candidates – {name: (H, N) full forecast for the held-out window}, each already summed with the same trend/holiday/shift terms so the only difference between them is the seasonal path.

  • actual – (H, N) realized values over that window.

  • scale – (N,) MASE denominator; None scores in raw units.

  • default – the incumbent’s name – kept unless beaten by more than the selection margin.

  • config – overrides for DEFAULT_SEASONAL_CONFIG.

Returns:

name}), scores ({name: (N,) scaled MAE}) and n_changed.

Return type:

dict with choice ({series_index

autots.evaluator.tva.seasonal.tile_profile(profile: ndarray, horizon: int) ndarray

(H, N) forward tiling of a phase-aligned cycle profile.

autots.evaluator.tva.sparse_factor module

Sparse-code factor identification for trend_network='factor' (C9).

The alternating estimator in factor_network finds the factor span by SVD and then solves loadings by regression. Both steps are rotation-invariant, so the basis it lands on is arbitrary – and the basis is exactly what the coherence graph consumes. Varimax (C1) addresses that by rotating afterwards, and demonstrably works on a clean panel (dominant recovery 0.972), but a post-hoc rotation needs an already-clean span: on the detector-adjusted panel the C1-C8 ladder measured it moving dominant recovery 0.250 -> 0.139, i.e. actively backwards.

This module takes the other route. Treat each series as a sample whose code is its loading vector, parameterize the dictionary directly in the hinge trend basis, and allow each code at most code_topk nonzeros. Sparsity is then learned jointly with the span instead of rotated in afterwards, and a hard support constraint is not rotation-invariant, so the basis is pinned without any rotation step at all. Atoms nobody selects fall out, which makes the live atom count an implicit rank estimate.

Two tiers share one objective:

'sparse_alt'

Torch-free coordinate descent. Greedy support selection per series against an unpenalized idiosyncratic line, an unpenalized refit on the chosen support, then a dictionary update through the existing factor_network._l1_trend_filter(). Support is re-selected from scratch every iteration, so nothing locks in.

'sparse_ae'

The same objective trained by Adam as an autoencoder over free codes. Signed magnitude-TopK, AuxK revival for dead atoms, dense warmup. Support does lock in after warmup, which is the substantive difference from the tier above and the reason both are worth measuring. Falls back to 'sparse_alt' when torch is unavailable.

Both are initialized from – and fall back to – the alternating estimator, so enabling one can never leave a panel worse identified than the default.

Returns the same identification contract the rest of the module speaks: factors (T, K) centered with unit-std increments, loadings (N, K), coefs (P, K) in the hinge basis, design (T, P), weights (N,), plus idio_level/idio_slope and the atom diagnostics.

autots.evaluator.tva.sparse_factor.identify(values, n_factors, init, method='sparse_alt', config=None, alpha=0.001, seed=42, device='cpu')

Refine an alternating identification into a sparse-code one.

Parameters:
  • values – (T, N) normalized panel, as handed to the alternating estimator.

  • n_factors – fitted rank K.

  • init – the identification dict from factor_network.estimate_factors_alternating(). Used as both the initializer and the fallback.

  • method'sparse_alt' or 'sparse_ae'.

  • config – overrides for DEFAULT_SPARSE_FACTOR_CONFIG.

  • alpha – trend-filter smoothness, from the parent factor config.

  • seed – torch seed for the autoencoder tier.

  • device – torch device for the autoencoder tier.

Returns:

An identification dict, or None when the sparse fit failed or reconstructed materially worse than init – in which case the caller keeps init. Never raises.

autots.evaluator.tva.sparse_factor.signed_topk(z, k)

Keep the k largest-magnitude entries of each row, with their signs.

Non-negative activations are wrong for this problem: coherence builds 'f{k}+'/'f{k}-' group keys and a series can be negatively exposed to a factor, so the sign is load-bearing information rather than a nuisance to be rectified away.

Selection is by exact rank, never by comparing against the k-th value: at initialization many entries are exactly zero and a >= threshold would silently admit more than k of them.

Parameters:
  • z – (N, K) array of unconstrained codes.

  • k – number of nonzeros to keep per row.

Returns:

(N, K) array, zero everywhere outside the selected support.

autots.evaluator.tva.structure module

Structure learning helpers for TVA.

This module keeps graph discovery, hierarchy discovery, export, and plotting separate from the forecast orchestration layer so new approaches can be swapped in without rewriting the TVA training loop.

class autots.evaluator.tva.structure.GraphSnapshot(node_table: list, edge_table: list, adjacency_dense: ndarray, adjacency_thresholded: ndarray, assignment_matrices: list, topological_order: list, prior_adjacency: ndarray | None, is_acyclic: bool, cycle_score: float, series_table: list | None = None, prototype_table: list | None = None, affinity_table: list | None = None, prototype_edge_table: list | None = None)

Bases: object

Serializable export of learned TVA structure.

adjacency_dense: ndarray
adjacency_thresholded: ndarray
affinity_table: list | None = None
assignment_matrices: list
cycle_score: float
edge_table: list
is_acyclic: bool
node_table: list
prior_adjacency: ndarray | None
prototype_edge_table: list | None = None
prototype_table: list | None = None
series_table: list | None = None
to_dict() dict
topological_order: list
class autots.evaluator.tva.structure.StructureLearningConfig(enabled: bool = False, learn_hierarchy: bool = True, learn_dag: bool = True, max_levels: int = 3, pool_ratio: float = 0.5, min_nodes_per_level: int = 2, dag_penalty: float = 0.0, dag_warmup_epochs: float = 0.2, sparsity_weight: float = 0.01, assignment_entropy_weight: float = 0.01, assignment_full_rank_weight: float = 0.01, prior_tether_weight: float = 0.05, temporal_drift_weight: float = 0.0, threshold_for_export: float = 0.2)

Bases: object

Opt-in configuration for TVA structure discovery.

assignment_entropy_weight: float = 0.01
assignment_full_rank_weight: float = 0.01
dag_penalty: float = 0.0
dag_warmup_epochs: float = 0.2
derive_latent_sizes(n_anchor: int, fallback_sizes: list | None = None) list

Return deterministic latent widths from anchor count.

The returned list excludes the input anchor level and contains only derived latent widths ordered bottom-up.

enabled: bool = False
classmethod from_dict(config: dict | None = None) StructureLearningConfig
learn_dag: bool = True
learn_hierarchy: bool = True
max_levels: int = 3
min_nodes_per_level: int = 2
pool_ratio: float = 0.5
prior_tether_weight: float = 0.05
sparsity_weight: float = 0.01
structure_scale(epoch_index: int, total_epochs: int) float
temporal_drift_weight: float = 0.0
threshold_for_export: float = 0.2
to_dict() dict
warmup_start_epoch(total_epochs: int) int
autots.evaluator.tva.structure.build_graph_snapshot(adjacency_dense: ndarray, assignment_matrices: list | None = None, threshold: float = 0.2, prior_adjacency: ndarray = None, anchor_names: list | None = None, full_series_names: list | None = None, anchor_mask: ndarray = None, series_metadata: list | None = None, prototype_weights: ndarray = None, prototype_forecasts: ndarray = None, global_prototype_weights: ndarray = None, decoded_top_trends: ndarray = None, dag_level: str = 'top') GraphSnapshot

Create a serializable snapshot from TVA structure state.

Parameters:

dag_level – ‘top’ maps the dense adjacency onto the top latent level (legacy). ‘series’ maps it onto level 0 so the DAG is drawn over real series names (W-7).

autots.evaluator.tva.structure.plot_graph_snapshot(snapshot: GraphSnapshot, view: str = 'dag', max_edges: int = 50, show_priors: bool = False, ax=None, metadata_color_by: str = 'auto')

Render a TVA structure snapshot with matplotlib.

autots.evaluator.tva.structure.topological_order_from_adjacency(adjacency: ndarray) list

Return a node ordering if the graph is acyclic, otherwise [].

autots.evaluator.tva.trend_network module

Composite Trend Network — The Sacred Timeline.

V1: Hierarchical Latent Trend Graph (deterministic structure). V2: Learned Directed Composite Graph (learned adjacency, adaptive prototypes).

The trend network operates only on isolated trend components, not raw series. It compresses per-series trends into a small set of shared composite prototypes and reconstructs per-series trend forecasts that are structurally consistent with those shared states.

autots.evaluator.tva.tva module

TVA — Time Variant Architecture.

Top-level orchestrator for the TVA forecasting graph. Ties together decomposition, priors, trend network, fusion, losses, reconciliation, and scenario planning into a single fit/predict interface.

Note a wrapper exists in /autots/models/tva_model.py for the AutoTS search integration.

Some reference papers: https://www.mdpi.com/2227-7390/13/20/3288 https://openreview.net/pdf?id=GYSG2vF6z5 https://arxiv.org/html/2409.10996v2 https://arxiv.org/html/2507.15119v2#S4 https://www.researchgate.net/profile/Jawad-Chowdhury-6/publication/379087074_CD-_NOTEAR[…]N0UGFnZSI6InB1YmxpY2F0aW9uIiwicGFnZSI6InB1YmxpY2F0aW9uIn19 https://openreview.net/pdf?id=80g3Yqlo1a https://openreview.net/pdf?id=WjDjem8mWE

class autots.evaluator.tva.tva.TVA(detector_params: dict = None, trend_network: str = 'v2', fusion: str = 'attention', series_metadata: list = None, prior_adjacency: ndarray = None, prior_confidence: float = 0.3, causal_prior: ndarray = None, prior_construction_config: dict = None, causal_prior_construction_config: dict = None, discovery_config: dict = None, d_token: int = 32, n_meso: int | str = 'auto', n_global: int | str = 'auto', n_prototypes: int | str = 'auto', n_heads: int = 2, epochs: int = 200, lr: float = 0.001, batch_size: int = 32, window_size: int = 91, forecast_horizon: int = 28, recency_halflife_days: float | None = None, loss_weights: dict = None, reconciliation_method: str = None, min_anchor_history: int = 180, holiday_country=None, holiday_countries: dict = None, device: str = None, random_seed: int = 42, verbose: int = 1, prototype_assignment_method: str = 'cosine', prototype_assignment_temperature: float = 1.0, structure_learning_config: dict = None, n_factors: int | str = 'auto', factor_knot_spacing: int = 7, factor_max_lag: int = 0, factor_config: dict = None, coherence_config: dict = None, derived_definitions: dict = None, factor_deconfound_edges: bool = False, reconciliation_covariance: str = 'auto')

Bases: object

Time Variant Architecture — end-to-end coherent forecasting graph.

The Common Operating Picture for time series. Produces structurally consistent forecasts across related series by routing all trends through shared composite prototypes. For all Time. Always.

Parameters:
  • detector_params – Dict passed to TimeSeriesFeatureDetector.

  • trend_network – ‘v2’ (learned directed, default), ‘v1’ (hierarchical latent), ‘factor’ (learned latent-factor trend), or ‘none’ with a damped rolling-trend extrapolation while still running factor/edge discovery, MinT reconciliation, and residual-sigma intervals. ‘none’ is the Phase-4 kill-rule configuration: fast, interpretable, and a strong baseline the network must beat.

  • fusion

    How stochastic components (trend, seasonality, holidays) are recombined before level shifts are added additively. ‘attention’ (default): DigitalTwinFusion — self-attention contextualizes

    component embeddings; sigmoid gates independently fade each component in/out (gate in [0,1]) applied to the original values.

    ’direct’: DirectAttentionFusion — self-attention contextualizes component

    embeddings; attended representations are projected directly to scalar contributions summed as a residual over the originals. More purely attention-driven; zero-init ensures pure-additive start.

    ’additive’: AdditiveFusion — plain sum, no learned parameters.

  • series_metadata – List of SeriesMetadata for prior construction.

  • prior_adjacency – Optional co-movement graph prior. Accepts an (N, N) matrix, a labelled DataFrame, a list of {'source', 'target', 'weight', 'directed'} edge dicts, or a list of name groups (each group a clique) – see priors.coerce_prior_adjacency. Signed: a negative weight is a substitution claim and pushes the pair apart.

  • prior_confidence – Weight of priors (0=ignore, 1=rigid). Sets the merge weight of the ‘business’/’causal’ edge families (against 1.0 for the data-derived ones) and the coherence blend weight.

  • causal_prior – Optional directed prior in the same forms, used for V2 adjacency regularization and kept as its own edge family.

  • prior_construction_config – Dict configuring automatic structural prior construction from event clusters and metadata. Defaults to blending detected changepoints/anomalies with metadata similarity ({‘sources’: [‘event’, ‘metadata’], …}). Pass {} to disable.

  • causal_prior_construction_config – Deprecated and ignored — series-level causal structure now comes from factor-residual discovery (autots.evaluator.tva.discovery). Use discovery_config instead.

  • discovery_config – Dict overriding discovery.DEFAULT_DISCOVERY_CONFIG (factor extraction, conditional screening, stability selection, lead-lag scan, falsification). Pass {‘enabled’: False} to skip structure discovery entirely.

  • d_token – Token/latent dimension.

  • n_meso – Number of meso latent nodes, or ‘auto’ (default) to set as 2 * n_global derived from N series.

  • n_global – Number of global latent nodes, or ‘auto’ (default) to set as max(2, ceil(sqrt(N_anchors))). Controls the DAG size.

  • n_prototypes – Number of prototype trend signatures, or ‘auto’ (default) to set as max(2, round(log2(N_anchors + 1))). Capped at 8.

  • n_heads – Attention heads.

  • epochs – Maximum training epochs. Default 200; training normally stops earlier via time-ordered validation early stopping (patience 8).

  • lr – Learning rate.

  • batch_size – Training batch size.

  • window_size – Input trend window length.

  • forecast_horizon – Output forecast length.

  • loss_weights – Dict overriding loss component weights.

  • reconciliation_method – None, ‘mint’, ‘erm’, etc.

  • reconciliation_covariance – how MinT’s W is obtained when no per-node residual matrix is available (the ‘factor’ and ‘none’ modes, where the network-based residual estimator returns None and W therefore falls back to the identity). ‘auto’ (default) follows the module constant RECONCILIATION_COVARIANCE_AUTO, ‘structural’ uses S @ forecast_covariance() @ S.T, ‘identity’ forces the previous behaviour.

  • min_anchor_history – Minimum periods for a series to be an anchor.

  • device – ‘cpu’, ‘cuda’, or None (auto-detects cuda if available, else cpu).

  • random_seed – Reproducibility seed.

  • verbose – 0=silent, 1=progress bar, 2=per-epoch loss.

  • prototype_assignment_method – Prototype assignment method for bottleneck (‘cosine’, ‘l2’, ‘linear’). Defaults to ‘cosine’.

  • prototype_assignment_temperature – Temperature for prototype assignment logits.

  • n_factors – Number of latent factors for trend_network=’factor’, or ‘auto’ (default) to pick a rank from the smoothed level-panel singular spectrum.

  • factor_knot_spacing – Candidate-knot spacing (steps) of the factor trend-filter basis in ‘factor’ mode.

  • factor_max_lag – Maximum learned per-series response lag in ‘factor’ mode. Defaults to 0 (contemporaneous)

  • factor_config – Dict overriding factor_network.DEFAULT_FACTOR_CONFIG.

  • coherence_config – Post-forecast coherence-shrink settings. Merged over factor_config['coherence_config']; supplying it also enables the shrink unless factor_config set ‘coherence’ explicitly. None (default) leaves the shrink off.

  • derived_definitions – Declared ratio identities {column: (num, den)}; never inferred from column names.

  • factor_deconfound_edges – Re-run series edge discovery deconfounded against the learned factors (‘factor’ mode). Off by default

  • structure_learning_config – Dict enabling DAG and dynamic hierarchy learning in the V2 trend network.

fit(df: DataFrame) TVA

Full TVA pipeline: decompose, build priors, train network.

Parameters:

df – Wide DataFrame with DatetimeIndex and numeric columns.

Returns:

self

forecast_covariance(horizon: int = None)

(N, N) raw-unit cross-series forecast-error covariance, or None.

One covariance object for the callers that each otherwise invent their own weighting: MinT’s W, the closed-form what-if solver, and (later) interval coherence. It is built from the rolling-origin residual matrix the trend model already forms, blended toward a low-rank Lambda Sigma_f Lambda' + diag(psi) target, and floored at the same per-series sigma the shipped prediction intervals use.

Only the modes whose trend model is linear in a small set of factors are served: trend_network='factor' (loadings from the fitted factor model) and 'none' (loadings from structure discovery, residuals from rolling the damped-trend rule). 'v1'/'v2' return None — those already have _compute_recent_reconciliation_residuals() for reconciliation and the backprop scenario solver for what-if, and a covariance built from a different model than the one forecasting would misdescribe them.

Parameters:

horizon – forecast horizon the covariance should describe. Defaults to forecast_horizon.

Returns:

(sigma, info) with sigma an (N, N) covariance in the raw units of the input data and info a diagnostics dict carrying alpha (shrinkage intensity), beta (structural scale), psi, floor_binding and n_samples. Returns None when unfitted, when the mode is not served, or when no usable residual matrix or loadings exist — callers degrade to their previous behaviour rather than seeing an exception.

Return learned composite/prototype trends for inspection.

Returns:

Dict with ‘prototypes’ (K, D), ‘composite_trend’ (n_global, T_forecast), and ‘prototype_weights’ (N, K).

get_edges() DataFrame

Return the discovered edge table over real series names (W-7).

These are predictive screening results, not causal claims. An edge means “after removing the shared factors, this series’ history helped predict that one’s, stably across subsamples” — useful for screening, not evidence of mechanism. Series edges stay disabled for forecasting until they clear the false-positive/lag-recovery/follower-forecast gates together; the trustworthy structural output today is the factor -> series loading graph (get_factor_graph / get_factor_diagnostics’s loading_graph).

Columns: source, target, lag, sign, weight, family, stability, delta_mse. Empty DataFrame when discovery was disabled or found nothing. In trend_network=’factor’ mode the induced factor -> series bipartite edges (family ‘factor’) are appended; see get_factor_graph.

get_factor_diagnostics() dict

Everything the factor mode decided, in one JSON-safe dict.

Returns:

dict with the selected rank, factors/loadings, anchor/responder masks, stability scores, continuation choice, per-series blend weights, re-anchor alphas, gated series (by reason), the learned loading graph, validation scores and the coherence adjustment magnitude. Keys are absent rather than fabricated when the corresponding mechanism was not enabled.

get_factor_graph() DataFrame

Induced factor -> series bipartite graph (‘factor’ mode).

One row per (factor, series) pair with a non-negligible loading, giving the signed loading weight and the series’ learned response lag. This is the structure the latent-factor mode actually uses to forecast, as opposed to the series->series edges from discovery.

Returns:

DataFrame with columns source, target, lag, sign, weight, family. Empty when not in ‘factor’ mode or not fitted.

get_factors() dict

Return named composite factors and their sparse signed loadings (W-7).

Returns:

Dict with ‘factors’ (DataFrame, one named column per factor, level space, indexed like the training data), ‘loadings’ (DataFrame, series x factor), and ‘factor_names’. In trend_network=’factor’ mode these are the learned level-space paths, plus ‘lags’, ‘variance_share’, ‘phi’ and ‘diag’.

get_graph() ndarray

Return the adjacency matrix (learned for V2, prior for V1).

Returns:

(M, M) numpy array.

get_graph_snapshot(threshold: float = None, include_priors: bool = True) dict

Return a serializable snapshot of the learned graph and hierarchy.

plot_graph(view: str = 'dag', threshold: float = None, max_edges: int = 50, show_priors: bool = False, ax=None, metadata_color_by: str = 'auto')

Plot the learned graph, hierarchy, or adjacency heatmap.

predict(forecast_length: int = None) DataFrame

Generate forecasts for all series.

Parameters:

forecast_length – Number of future periods. Defaults to forecast_horizon.

Returns:

Wide DataFrame (forecast_length, N) with forecasted values.

reconcile(forecasts: DataFrame = None, residuals: ndarray = None, aggregate_sigma=None) DataFrame

Apply hierarchical reconciliation if configured.

Note

When forecasts holds only the bottom level, this method builds the aggregate rows by summing it through S — which places the input exactly in the coherent subspace MinT projects onto, so reconciliation returns it unchanged for any W, in any trend mode. Reconciliation can only move a number when the aggregate levels carry a forecast that was not derived from the bottom one; pass a full (L-column) forecasts frame, ordered aggregates-then-bottom, to supply one.

Parameters:
  • forecasts – Forecast DataFrame. If None, generates fresh predictions. Bottom-level columns only, or all L hierarchy levels with the aggregate columns first (see the note above).

  • residuals – Optional historical residual matrix for all hierarchy levels. If omitted, TVA estimates recent one-step residuals from the last 30-90 training windows so covariance-aware reconciliation methods can use a meaningful W matrix.

  • aggregate_sigma – Optional (n_agg,) forecast-error standard deviations for independently-produced aggregate forecasts. Only consulted on the structural-covariance path, where it is what stops Sigma from cancelling out of MinT’s estimator (see _structural_reconciliation_W()).

Returns:

Reconciled DataFrame.

what_if(**constraints) DataFrame

Scenario planning: adjust the forecast to satisfy user constraints.

Two solvers behind one signature, picked by what the fit produced. trend_network='v1'/'v2' backpropagate a latent perturbation through the trend network (BifrostOptimizer). 'factor' and 'none' have no network to backprop through — the optimizer used to dereference self._network and raise there — but their forecast is linear in the factor paths, so ClosedFormScenario solves the same minimum-disruption problem in closed form under forecast_covariance().

Parameters:

**constraints – Passed to the solver’s methods. Supported keys: - series_name, timestep, target_value -> apply_constraint - series_name, growth_rate -> apply_growth_constraint - level_name, target_value -> apply_hierarchical_constraint

Returns:

Adjusted forecast DataFrame.

Module contents

TVA — Time-varying Vectorized Architecture.

A modular forecasting graph that produces structurally consistent forecasts across related time series via shared composite trend prototypes.

class autots.evaluator.tva.GraphSnapshot(node_table: list, edge_table: list, adjacency_dense: ndarray, adjacency_thresholded: ndarray, assignment_matrices: list, topological_order: list, prior_adjacency: ndarray | None, is_acyclic: bool, cycle_score: float, series_table: list | None = None, prototype_table: list | None = None, affinity_table: list | None = None, prototype_edge_table: list | None = None)

Bases: object

Serializable export of learned TVA structure.

adjacency_dense: ndarray
adjacency_thresholded: ndarray
affinity_table: list | None = None
assignment_matrices: list
cycle_score: float
edge_table: list
is_acyclic: bool
node_table: list
prior_adjacency: ndarray | None
prototype_edge_table: list | None = None
prototype_table: list | None = None
series_table: list | None = None
to_dict() dict
topological_order: list
class autots.evaluator.tva.NornDecomposer(detector_params: dict = None, holiday_country=None, holiday_countries: dict = None)

Bases: object

Wraps TimeSeriesFeatureDetector to extract structured component DataFrames.

Produces trend, seasonality, holidays, level_shifts, anomalies, and noise as aligned DataFrames sharing the same index and columns as the input.

Parameters:

detector_params – Dict of kwargs passed to TimeSeriesFeatureDetector.__init__.

fit(df: DataFrame) NornDecomposer

Fit the feature detector on a wide DataFrame.

Parameters:

df – Wide DataFrame with DatetimeIndex and one numeric column per series.

Returns:

self

get_components() dict

Extract decomposition components as DataFrames.

Returns:

Dict with keys ‘trend’, ‘seasonality’, ‘holidays’, ‘level_shifts’, ‘anomalies’, ‘noise’. Each value is a DataFrame (T, N) aligned with the original input index and columns.

get_features() dict

Return the full detected features dict from the underlying detector.

Includes changepoints, holiday impacts, anomaly records, etc. Useful for constructing priors or inspecting detection results.

get_forecast_components(forecast_length: int) dict

Forward-project seasonality, holidays, level shifts, and trend.

Uses the detector’s built-in forecast method to project known deterministic components into the future.

Parameters:

forecast_length – Number of future periods to project.

Returns:

Dict with same keys as get_components(), each a DataFrame of shape (forecast_length, N).

get_residual_sigma()

Per-series one-step residual sigma from the underlying detector.

Exposes the detector’s own reconstruction-residual sigma (the same quantity its OLS prediction intervals are built from) so TVA can add it in quadrature to the network’s learned sigma. Returns an (N,) float array aligned to the fitted columns, or None when unavailable.

class autots.evaluator.tva.SeriesMetadata(name: str, attribute_values: Dict[str, str] | None = None, attribute_weights: Dict[str, float] | None = None, hierarchy_path: list | None = None, history_periods: int = 0, metric_type: str | None = None, surface: str | None = None, geography: str | None = None)

Bases: object

Metadata descriptor for a single time series.

name

Series identifier matching DataFrame column name.

Type:

str

attribute_values

Arbitrary categorical metadata for the series.

Type:

Dict[str, str]

hierarchy_path

Ordered path from root to leaf (e.g. [‘global’, ‘NA’, ‘US’]).

Type:

list | None

history_periods

Number of observed time periods available.

Type:

int

attribute_values: Dict[str, str]
attribute_weights: Dict[str, float]
property geography: str | None
hierarchy_path: list | None
history_periods: int = 0
property metric_type: str | None
name: str
property surface: str | None
class autots.evaluator.tva.StructureLearningConfig(enabled: bool = False, learn_hierarchy: bool = True, learn_dag: bool = True, max_levels: int = 3, pool_ratio: float = 0.5, min_nodes_per_level: int = 2, dag_penalty: float = 0.0, dag_warmup_epochs: float = 0.2, sparsity_weight: float = 0.01, assignment_entropy_weight: float = 0.01, assignment_full_rank_weight: float = 0.01, prior_tether_weight: float = 0.05, temporal_drift_weight: float = 0.0, threshold_for_export: float = 0.2)

Bases: object

Opt-in configuration for TVA structure discovery.

assignment_entropy_weight: float = 0.01
assignment_full_rank_weight: float = 0.01
dag_penalty: float = 0.0
dag_warmup_epochs: float = 0.2
derive_latent_sizes(n_anchor: int, fallback_sizes: list | None = None) list

Return deterministic latent widths from anchor count.

The returned list excludes the input anchor level and contains only derived latent widths ordered bottom-up.

enabled: bool = False
classmethod from_dict(config: dict | None = None) StructureLearningConfig
learn_dag: bool = True
learn_hierarchy: bool = True
max_levels: int = 3
min_nodes_per_level: int = 2
pool_ratio: float = 0.5
prior_tether_weight: float = 0.05
sparsity_weight: float = 0.01
structure_scale(epoch_index: int, total_epochs: int) float
temporal_drift_weight: float = 0.0
threshold_for_export: float = 0.2
to_dict() dict
warmup_start_epoch(total_epochs: int) int
class autots.evaluator.tva.TVA(detector_params: dict = None, trend_network: str = 'v2', fusion: str = 'attention', series_metadata: list = None, prior_adjacency: ndarray = None, prior_confidence: float = 0.3, causal_prior: ndarray = None, prior_construction_config: dict = None, causal_prior_construction_config: dict = None, discovery_config: dict = None, d_token: int = 32, n_meso: int | str = 'auto', n_global: int | str = 'auto', n_prototypes: int | str = 'auto', n_heads: int = 2, epochs: int = 200, lr: float = 0.001, batch_size: int = 32, window_size: int = 91, forecast_horizon: int = 28, recency_halflife_days: float | None = None, loss_weights: dict = None, reconciliation_method: str = None, min_anchor_history: int = 180, holiday_country=None, holiday_countries: dict = None, device: str = None, random_seed: int = 42, verbose: int = 1, prototype_assignment_method: str = 'cosine', prototype_assignment_temperature: float = 1.0, structure_learning_config: dict = None, n_factors: int | str = 'auto', factor_knot_spacing: int = 7, factor_max_lag: int = 0, factor_config: dict = None, coherence_config: dict = None, derived_definitions: dict = None, factor_deconfound_edges: bool = False, reconciliation_covariance: str = 'auto')

Bases: object

Time Variant Architecture — end-to-end coherent forecasting graph.

The Common Operating Picture for time series. Produces structurally consistent forecasts across related series by routing all trends through shared composite prototypes. For all Time. Always.

Parameters:
  • detector_params – Dict passed to TimeSeriesFeatureDetector.

  • trend_network – ‘v2’ (learned directed, default), ‘v1’ (hierarchical latent), ‘factor’ (learned latent-factor trend), or ‘none’ with a damped rolling-trend extrapolation while still running factor/edge discovery, MinT reconciliation, and residual-sigma intervals. ‘none’ is the Phase-4 kill-rule configuration: fast, interpretable, and a strong baseline the network must beat.

  • fusion

    How stochastic components (trend, seasonality, holidays) are recombined before level shifts are added additively. ‘attention’ (default): DigitalTwinFusion — self-attention contextualizes

    component embeddings; sigmoid gates independently fade each component in/out (gate in [0,1]) applied to the original values.

    ’direct’: DirectAttentionFusion — self-attention contextualizes component

    embeddings; attended representations are projected directly to scalar contributions summed as a residual over the originals. More purely attention-driven; zero-init ensures pure-additive start.

    ’additive’: AdditiveFusion — plain sum, no learned parameters.

  • series_metadata – List of SeriesMetadata for prior construction.

  • prior_adjacency – Optional co-movement graph prior. Accepts an (N, N) matrix, a labelled DataFrame, a list of {'source', 'target', 'weight', 'directed'} edge dicts, or a list of name groups (each group a clique) – see priors.coerce_prior_adjacency. Signed: a negative weight is a substitution claim and pushes the pair apart.

  • prior_confidence – Weight of priors (0=ignore, 1=rigid). Sets the merge weight of the ‘business’/’causal’ edge families (against 1.0 for the data-derived ones) and the coherence blend weight.

  • causal_prior – Optional directed prior in the same forms, used for V2 adjacency regularization and kept as its own edge family.

  • prior_construction_config – Dict configuring automatic structural prior construction from event clusters and metadata. Defaults to blending detected changepoints/anomalies with metadata similarity ({‘sources’: [‘event’, ‘metadata’], …}). Pass {} to disable.

  • causal_prior_construction_config – Deprecated and ignored — series-level causal structure now comes from factor-residual discovery (autots.evaluator.tva.discovery). Use discovery_config instead.

  • discovery_config – Dict overriding discovery.DEFAULT_DISCOVERY_CONFIG (factor extraction, conditional screening, stability selection, lead-lag scan, falsification). Pass {‘enabled’: False} to skip structure discovery entirely.

  • d_token – Token/latent dimension.

  • n_meso – Number of meso latent nodes, or ‘auto’ (default) to set as 2 * n_global derived from N series.

  • n_global – Number of global latent nodes, or ‘auto’ (default) to set as max(2, ceil(sqrt(N_anchors))). Controls the DAG size.

  • n_prototypes – Number of prototype trend signatures, or ‘auto’ (default) to set as max(2, round(log2(N_anchors + 1))). Capped at 8.

  • n_heads – Attention heads.

  • epochs – Maximum training epochs. Default 200; training normally stops earlier via time-ordered validation early stopping (patience 8).

  • lr – Learning rate.

  • batch_size – Training batch size.

  • window_size – Input trend window length.

  • forecast_horizon – Output forecast length.

  • loss_weights – Dict overriding loss component weights.

  • reconciliation_method – None, ‘mint’, ‘erm’, etc.

  • reconciliation_covariance – how MinT’s W is obtained when no per-node residual matrix is available (the ‘factor’ and ‘none’ modes, where the network-based residual estimator returns None and W therefore falls back to the identity). ‘auto’ (default) follows the module constant RECONCILIATION_COVARIANCE_AUTO, ‘structural’ uses S @ forecast_covariance() @ S.T, ‘identity’ forces the previous behaviour.

  • min_anchor_history – Minimum periods for a series to be an anchor.

  • device – ‘cpu’, ‘cuda’, or None (auto-detects cuda if available, else cpu).

  • random_seed – Reproducibility seed.

  • verbose – 0=silent, 1=progress bar, 2=per-epoch loss.

  • prototype_assignment_method – Prototype assignment method for bottleneck (‘cosine’, ‘l2’, ‘linear’). Defaults to ‘cosine’.

  • prototype_assignment_temperature – Temperature for prototype assignment logits.

  • n_factors – Number of latent factors for trend_network=’factor’, or ‘auto’ (default) to pick a rank from the smoothed level-panel singular spectrum.

  • factor_knot_spacing – Candidate-knot spacing (steps) of the factor trend-filter basis in ‘factor’ mode.

  • factor_max_lag – Maximum learned per-series response lag in ‘factor’ mode. Defaults to 0 (contemporaneous)

  • factor_config – Dict overriding factor_network.DEFAULT_FACTOR_CONFIG.

  • coherence_config – Post-forecast coherence-shrink settings. Merged over factor_config['coherence_config']; supplying it also enables the shrink unless factor_config set ‘coherence’ explicitly. None (default) leaves the shrink off.

  • derived_definitions – Declared ratio identities {column: (num, den)}; never inferred from column names.

  • factor_deconfound_edges – Re-run series edge discovery deconfounded against the learned factors (‘factor’ mode). Off by default

  • structure_learning_config – Dict enabling DAG and dynamic hierarchy learning in the V2 trend network.

fit(df: DataFrame) TVA

Full TVA pipeline: decompose, build priors, train network.

Parameters:

df – Wide DataFrame with DatetimeIndex and numeric columns.

Returns:

self

forecast_covariance(horizon: int = None)

(N, N) raw-unit cross-series forecast-error covariance, or None.

One covariance object for the callers that each otherwise invent their own weighting: MinT’s W, the closed-form what-if solver, and (later) interval coherence. It is built from the rolling-origin residual matrix the trend model already forms, blended toward a low-rank Lambda Sigma_f Lambda' + diag(psi) target, and floored at the same per-series sigma the shipped prediction intervals use.

Only the modes whose trend model is linear in a small set of factors are served: trend_network='factor' (loadings from the fitted factor model) and 'none' (loadings from structure discovery, residuals from rolling the damped-trend rule). 'v1'/'v2' return None — those already have _compute_recent_reconciliation_residuals() for reconciliation and the backprop scenario solver for what-if, and a covariance built from a different model than the one forecasting would misdescribe them.

Parameters:

horizon – forecast horizon the covariance should describe. Defaults to forecast_horizon.

Returns:

(sigma, info) with sigma an (N, N) covariance in the raw units of the input data and info a diagnostics dict carrying alpha (shrinkage intensity), beta (structural scale), psi, floor_binding and n_samples. Returns None when unfitted, when the mode is not served, or when no usable residual matrix or loadings exist — callers degrade to their previous behaviour rather than seeing an exception.

Return learned composite/prototype trends for inspection.

Returns:

Dict with ‘prototypes’ (K, D), ‘composite_trend’ (n_global, T_forecast), and ‘prototype_weights’ (N, K).

get_edges() DataFrame

Return the discovered edge table over real series names (W-7).

These are predictive screening results, not causal claims. An edge means “after removing the shared factors, this series’ history helped predict that one’s, stably across subsamples” — useful for screening, not evidence of mechanism. Series edges stay disabled for forecasting until they clear the false-positive/lag-recovery/follower-forecast gates together; the trustworthy structural output today is the factor -> series loading graph (get_factor_graph / get_factor_diagnostics’s loading_graph).

Columns: source, target, lag, sign, weight, family, stability, delta_mse. Empty DataFrame when discovery was disabled or found nothing. In trend_network=’factor’ mode the induced factor -> series bipartite edges (family ‘factor’) are appended; see get_factor_graph.

get_factor_diagnostics() dict

Everything the factor mode decided, in one JSON-safe dict.

Returns:

dict with the selected rank, factors/loadings, anchor/responder masks, stability scores, continuation choice, per-series blend weights, re-anchor alphas, gated series (by reason), the learned loading graph, validation scores and the coherence adjustment magnitude. Keys are absent rather than fabricated when the corresponding mechanism was not enabled.

get_factor_graph() DataFrame

Induced factor -> series bipartite graph (‘factor’ mode).

One row per (factor, series) pair with a non-negligible loading, giving the signed loading weight and the series’ learned response lag. This is the structure the latent-factor mode actually uses to forecast, as opposed to the series->series edges from discovery.

Returns:

DataFrame with columns source, target, lag, sign, weight, family. Empty when not in ‘factor’ mode or not fitted.

get_factors() dict

Return named composite factors and their sparse signed loadings (W-7).

Returns:

Dict with ‘factors’ (DataFrame, one named column per factor, level space, indexed like the training data), ‘loadings’ (DataFrame, series x factor), and ‘factor_names’. In trend_network=’factor’ mode these are the learned level-space paths, plus ‘lags’, ‘variance_share’, ‘phi’ and ‘diag’.

get_graph() ndarray

Return the adjacency matrix (learned for V2, prior for V1).

Returns:

(M, M) numpy array.

get_graph_snapshot(threshold: float = None, include_priors: bool = True) dict

Return a serializable snapshot of the learned graph and hierarchy.

plot_graph(view: str = 'dag', threshold: float = None, max_edges: int = 50, show_priors: bool = False, ax=None, metadata_color_by: str = 'auto')

Plot the learned graph, hierarchy, or adjacency heatmap.

predict(forecast_length: int = None) DataFrame

Generate forecasts for all series.

Parameters:

forecast_length – Number of future periods. Defaults to forecast_horizon.

Returns:

Wide DataFrame (forecast_length, N) with forecasted values.

reconcile(forecasts: DataFrame = None, residuals: ndarray = None, aggregate_sigma=None) DataFrame

Apply hierarchical reconciliation if configured.

Note

When forecasts holds only the bottom level, this method builds the aggregate rows by summing it through S — which places the input exactly in the coherent subspace MinT projects onto, so reconciliation returns it unchanged for any W, in any trend mode. Reconciliation can only move a number when the aggregate levels carry a forecast that was not derived from the bottom one; pass a full (L-column) forecasts frame, ordered aggregates-then-bottom, to supply one.

Parameters:
  • forecasts – Forecast DataFrame. If None, generates fresh predictions. Bottom-level columns only, or all L hierarchy levels with the aggregate columns first (see the note above).

  • residuals – Optional historical residual matrix for all hierarchy levels. If omitted, TVA estimates recent one-step residuals from the last 30-90 training windows so covariance-aware reconciliation methods can use a meaningful W matrix.

  • aggregate_sigma – Optional (n_agg,) forecast-error standard deviations for independently-produced aggregate forecasts. Only consulted on the structural-covariance path, where it is what stops Sigma from cancelling out of MinT’s estimator (see _structural_reconciliation_W()).

Returns:

Reconciled DataFrame.

what_if(**constraints) DataFrame

Scenario planning: adjust the forecast to satisfy user constraints.

Two solvers behind one signature, picked by what the fit produced. trend_network='v1'/'v2' backpropagate a latent perturbation through the trend network (BifrostOptimizer). 'factor' and 'none' have no network to backprop through — the optimizer used to dereference self._network and raise there — but their forecast is linear in the factor paths, so ClosedFormScenario solves the same minimum-disruption problem in closed form under forecast_covariance().

Parameters:

**constraints – Passed to the solver’s methods. Supported keys: - series_name, timestep, target_value -> apply_constraint - series_name, growth_rate -> apply_growth_constraint - level_name, target_value -> apply_hierarchical_constraint

Returns:

Adjusted forecast DataFrame.

class autots.evaluator.tva.YggdrasilPriors(series_metadata: list = None, relationship_matrix: ndarray = None, prior_confidence: float = 0.3, detected_features: dict = None, trend_data: DataFrame = None, observed_history: Dict[str, int] = None, prior_construction_config: dict = None, causal_prior_construction_config: dict = None, series_names: list = None)

Bases: object

Constructs prior adjacency matrices, metadata embeddings, and hierarchy matrices from series metadata. All methods return sensible defaults when metadata is absent, so TVA runs with or without priors.

Parameters:
  • series_metadata – List of SeriesMetadata, one per series.

  • relationship_matrix – Optional (N, N) soft prior adjacency override.

  • prior_confidence – Weight of prior vs learned structure (0=ignore, 1=rigid).

build_hierarchy_matrix() ndarray

Build summing matrix S for hierarchical reconciliation.

S has shape (L, M) where L = total nodes (aggregates + bottom), M = number of bottom-level series. Compatible with mint_reconcile(S, y_all, W).

Returns identity if no hierarchy paths are specified.

build_metadata_embeddings() ndarray

Build (N, D_meta) one-hot embedding matrix from categorical metadata.

Encodes arbitrary categorical metadata keys as concatenated one-hot vectors. Returns zeros if no metadata is available.

build_prior_adjacency() ndarray | None

Backward-compatible alias for structural prior construction.

build_structural_prior_adjacency() ndarray | None

Construct a soft structural prior from explicit, metadata, and event sources.

get_anchor_mask(min_history: int) ndarray

Return boolean mask (N,) where True = series has enough history to be an anchor.

Anchors shape the core latent trend graph. Responders inherit composite trends but do not perturb the core geometry.

get_series_names() list

Return ordered list of series names.

property n_series
autots.evaluator.tva.coerce_prior_adjacency(prior, series_names) ndarray | None

Normalize any supported user prior spec into a signed (N, N) matrix.

The single entry point for user-supplied graph priors, so every downstream consumer reads one shape regardless of how the prior was expressed.

Supported forms:

  • None -> None

  • (N, N) array-like -> validated and passed through

  • pd.DataFrame -> reindexed onto series_names (order-safe)

  • list of {'source', 'target', 'weight', 'directed'} dicts -> symmetric unless the row sets directed: True

  • list of name groups ([['a', 'b'], ...]) -> each group is a clique at weight 1.0; {'series': [...], 'weight': 0.6} for a weaker group

Unknown series names raise a RuntimeWarning naming them and are dropped, never silently ignored.

Parameters:
  • prior – user-supplied prior in any of the forms above.

  • series_names – panel column order defining the matrix axes.

Returns:

(N, N) float32, signed, clipped to [-1, 1], hollow diagonal; or None.