Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Source repository: https://github.com/EDF-Lab/tam

The first official open-source release of the TAM framework under this structure is 1.2.3.

⚠️ Note: Versions 1.1.1–1.2.2 correspond to internal development milestones and were not publicly released.

0.0.6 corresponds to the legacy weakl package available on PyPI.


[Unreleased]


[1.3.2] - 2026-10-01

Patch release (tag on main; the latest release on PyPI and Zenodo stays 1.3.1): two crashes fixed. No prediction changes: every model that worked in 1.3.1 gives the same numbers.

Fixed

  • Grouped models predict a frame holding only some of their groups (StaticTAM with group_col): predict raised ValueError: Length of values (n) does not match length of index (0) and decompose_prediction returned NaN. The groups stacked from the frame were paired with the fitted group list by position, and the per-group coefficients were sliced by position in the stacked tensor, so a one-group frame would have used the first group’s coefficients. Prediction and decomposition now use the groups actually present: rows keep the input order and index, and a group never seen in training raises a ValueError naming it. plot_component no longer replicates its grid over every group.

  • Silent overflow in log-target quantiles (predict_quantile, predict_quantiles, predict_median, anomaly_score, mixture component_means and predict_mean): a scale prediction far outside the training range gave exp(...) = inf and a silent numpy RuntimeWarning: overflow encountered in exp. The value is still inf (it is beyond float64, and the columns stay ordered) but it comes with an explicit UserWarning naming the quantity and the first rows.


1.3.1 - 2026-09-30

Patch release: fixes that made some models wrong (formulas with te() or several features in one term) or not reproducible (rbf(), trees).

Predictions change for the models below; everything else is identical to 1.3.0. To get the 1.3.0 behaviour: pip install tam-ml==1.3.0.

Results change

  • Formulas with te() or several features in one term: they were fitted with mislabelled terms and now fit the formula as written; their errors drop sharply, and the ensembles built on them follow. The THEORY benchmark and its figures are regenerated with 1.3.1.

  • auto_fit (GCV): now fits exactly the model GCV selected. GCV minimises an in-sample criterion, not the holdout error, so holdout results move in both directions.

  • rbf(): centres are seeded and drawn from the whole training set; RBF models change once and are then reproducible.

  • Trees on grouped data (t(), lt() with group_col): splits and leaf counts come from the whole training set; results change, mostly for the better. Ungrouped trees do not change.

  • NeuralTAM (experimental): may change slightly in scripts that fit rbf() models before it, because both used to share the global torch random generator. A NeuralTAM fitted on its own after the same torch.manual_seed is unchanged.

  • Fixed-penalty models without these terms are identical to 1.3.0.

Added

  • tam.plot_component(model, data, component, kind="auto"): plots one additive component with a view chosen from its dimension: a curve for one feature, one curve per level for te(x, c), a 3D surface (or kind="heatmap" with the observed points overlaid) for te(x1, x2), evaluated on a regular grid for one group (group=, default the most frequent), a 3D scatter coloured by the contribution for te(x1, x2, x3). Rows with a non-finite contribution are skipped.

  • tam.common.plotting.resolve_component(model, feature, component=None, color_by=None): the component a feature maps to under the new decomposition names.

  • rbf(x, ..., seed=42): seed of the centre sampling (default 42, like n() and t()).

Fixed

  • Terms renamed by position (StaticTAM._prepare_data): each effect’s feature_name was overwritten from the deduplicated feature list by position, so with a te() or several features in one term, the effects after it were relabelled (e.g. c(day_type_week) became toy) and the fitted model changed, not only the labels (e.g. a national-load model with te(temperature, toy): test RMSE 3254 → 2432). The renaming is removed. decompose_prediction names components with the new decomposition_names(): unique feature names are kept, collisions get a basis prefix (s_x, l_x), and a collision that remains (two te() over the same features) gets an occurrence suffix, so no contribution overwrites another.

  • Plotting helpers after the renaming fix: plot_effect_with_model_and_data and plot_effect_with_data_decomposed raised KeyError: 'effect_x1' for a feature inside a te() or used by several effects. They now resolve the feature to its component: the only one using it, the tensor product whose other margin is color_by, or the new component= argument; a still-ambiguous feature raises a ValueError listing the candidates. Single-component features plot exactly as before.

  • add_base_effects in AdaptiveTAM and KalmanTAM: the base model’s components were added as l(effect_<feature>), a name that does not exist when two effects share a feature (s(x) + l(x) gives effect_s_x, effect_l_x), so the model crashed with a KeyError; and with a te(), the renaming bug above fed mislabelled components. The components are now added under their decomposition_names() columns.

  • GCV scored a different penalty from the one it stored (auto_fit, smart_solve_gcv): each trial rescaled a block that already carried the formula’s own weight, so the search scored λ_formula × λ_GCV but stored λ_GCV alone; a later fit() on the selected weights returned a different model, and with the default ap=-9 the nine highest decades of the search range were unreachable. Each trial block is now rebuilt by the effect at λ = 10^α, exactly as fit() assembles it; the trial weight is restored even if the search raises, and the initial alphas are clipped to the bounds. Models selected by auto_fit change.

  • GCV scored a slightly different system from the one fitted (auto_fit): the solver adds a ridge floor 1e-6·n·I, GCV added 1e-6·I. Both now use one helper (_ridge_floor), so the selected penalties are scored on the model fit() returns; a negative residual sum of squares from rounding is clamped at 0 instead of folded with abs(). Penalties below ap = -6 are dominated by that floor (documented). Models selected by auto_fit can change; fixed-penalty fits do not.

  • Trees and RBF centres were initialised on a memory probe, not on the training data (t(), lt(), rbf()): their data-dependent state was set by the first design matrix built, which is the solver’s size probe (one row per group). With 48 half-hourly groups, quantile splits came from 48 points and the sp_alpha leaf counts summed to 48 × n_trees instead of every training row; rbf() centres were drawn from those 48 points. StaticTAM now calls initialize_effects() on the full training tensor before any design matrix; a probe or a later chunk never changes that state. Tree, linear-tree and RBF predictions change for grouped models; ungrouped trees were already initialised on the full data and do not change (ungrouped rbf() changes through its new seed only).

  • rbf() centres were not reproducible: they were drawn from the global torch generator, so an RBF model changed with whatever code ran before it (two identical fits could differ). rbf(x, ..., seed=42) now seeds a local generator, and fitting no longer touches the global random state. RBF predictions change once (new, fixed centres).

  • Sparsity-adaptive tree penalty (t(..., sp_alpha>0)): empirical_counts summed only the first batch axis, so the leaf-density penalty was built from the wrong counts and could not be formed. It now counts every sample and group, one value per leaf in design-matrix order.

  • Linear tree weight (lt()): assigning lambda_p (as GCV does on every candidate) did not reach the intercept tree and the slope surface, so GCV could not tune lt(). The weight now propagates to both sub-blocks.

  • Dummy date overflow: without date_col, the internal dummy date was spaced one day apart and ran past the year 2262 after ~95,000 rows, which overflows pandas 2.x nanosecond datetimes (pandas 3 tolerates it). It is now spaced one second apart.

Changed

  • CI: the test workflow runs on every push and pull request (any branch) and on demand, and checks import tam first on every Python version (3.10-3.14).


1.3.0 - 2026-09-06

Added

  • Statistics layer (tam.model.statistics) : a modular “how” beside the structural spectrum “what”, built on the single P-WLS atom (BaseTAM._solve_pwls_step), so the default loss="l2" path stays bit-identical to ordinary least squares.

    • Reweighting & estimation (estimation/): GLM families (Gamma, Poisson, Binomial), asymmetric expectiles, and robust M-estimators (Huber, Student-t) via StaticTAM(loss=...), driven by an IRLS schedule over the atom.

    • Distributional location-scale fits via a dict formula/loss ({"mu": ..., "sigma": ...}), with automatic Normal/Student-t tail selection, predict_quantiles, cdf, anomaly_score and crps.

    • Mixture of TAM regressions (mixture_components=K) fitted by EM whose M-step is the responsibility-weighted atom.

    • Gaussian copula (GaussianCopulaTAM) binding several distributional margins.

    • Conformal & ACI (statistics.risk): distribution-free CQR intervals, conformal p-values, Mondrian (stratified) calibration (ConformalDistributionalTAM) and streaming Adaptive Conformal Inference, on the static SafetyTAM engine.

    • EVT & epistemic uncertainty: Generalized-Pareto tail scoring (GeneralizedParetoTail, fit_gpd_tail) and Bayesian posterior parameter uncertainty (posterior_prediction).

1.2.6 - 2026-07-17

Added

  • Added SPDX license identifiers (LGPL-3.0-or-later) and standardized file headers with proper authorship and copyright attribution across the entire codebase (2026-07-01).

Changed

  • Major Data Upgrade: Replaced the legacy dataset_national.csv with the new FORCE (French Open Research Catalogue of Energy) dataset. The library now defaults to this high-performance, harmonized dataset for baseline benchmarking and examples.

Fixed

  • Critical Tensor Alignment Bug (group_col): Fixed an order-sensitivity vulnerability in the tensor stacking and DataFrame reassembly layers. The framework now enforces strict chronological sorting (via date_col) before building 3D tensors, preventing penalty matrix corruption on non-sequential data. Additionally, it now safely maps chronological PyTorch predictions back to shuffled Pandas indices, guaranteeing row-to-row integrity.

1.2.5 - 2026-06-24

Added

  • Topological Split Strategies (TreeEffect): Formalized the split_strategy parameter. Users can now explicitly toggle between 'uniform' (creating mathematically orthogonal, shift-invariant Cartesian grids) and 'quantile' (applying the empirical Probability Integral Transform to create density-adaptive partitions that perfectly balance sample distributions across all leaves).

Changed

  • Empirical Sparsity-Adaptive Penalty (Anisotropic Ridge): Upgraded the structural penalty of the Random Forest (t(...)) and Linear Tree (lt(...)) modules. By setting sp_alpha > 0, the initialization pass now accurately records the empirical data density of each terminal leaf (\(C_i\)).

  • Drift & Singularity Prevention: Starved or empty edge-boundary leaves now receive geometrically massive penalties. This guarantees global matrix rank, eliminates the catastrophic test drift associated with hard Cartesian grids, and theoretically resolves the OLS singularities traditionally found in Model-Based Recursive Partitioning (MOB).

1.2.4 - 2026-06-22

Added

  • API Standardization: Introduced explicit fit() and predict() methods across all meta-models (AdaptiveTAM, OperaTAM, KalmanTAM). This establishes a unified, scikit-learn-like operational workflow (train on historical data, freeze state, predict out-of-sample) regardless of the underlying algorithm.

  • AdaptiveTAM: Added fit() and predict() methods for production deployment. The fit() method efficiently extracts and solves the linear system strictly for the final available training window. The predict() method then applies this frozen state (last_state_dict_) to new data in \(O(1)\) time with strict safety clipping, ensuring instant, deterministic inference without target leakage.

  • KalmanTAM: Added fit() and predict() methods alongside end-of-training state extraction (last_state_dict_ and scale_dict_). This allows users to project the finalized Kalman drift weights forward as a stable, static rule on new data, with the internal normalization math handled automatically.

  • OperaTAM: Added fit() and predict() methods to transition from continuous dynamic simulation to frozen-weight inference. fit() runs the historical simulation, while predict() cleanly extracts and applies the final expert aggregation weights to new out-of-sample data.

Fixed

  • StaticTAM: Removed the target_col requirement from the required features check in decompose_prediction. This resolves a critical blocker for operational inference pipelines where the target variable is naturally unavailable.

  • KalmanTAM: Patched _prepare_kalman_features to securely bypass target column extraction during out-of-sample inference, preventing crashes when the target variable is absent.

1.2.3 - 2026-06-08

The DOI was generated via Zenodo on release : https://doi.org/10.5281/zenodo.20543272.

✨ Added (New Models & Core Features)

  • Universal Extrapolation Wrapper: Introduced native Out-Of-Distribution (OOD) extrapolation for all base effects via the extrapolate parameter. It safely bounds the feature map to the \([-1, 1]^F\) hypercube and utilizes multidimensional directional derivatives (stepping strictly backward into the safe zone) for OOD inputs. Supported modes include continue (native topology), constant (plateau/clamping), linear (first-order Taylor expansion), and saturation (smooth asymptotic clamping).

  • Linear Tree (lt(...)): Added a native effect that generates piecewise linear models. It utilizes a dedicated LinearTreeEffect class to encapsulate a standard TreeEffect (acting as the local intercept/level) crossed with a TensorProductEffect (acting as the local linear slope). This provides a single, cohesive model for varying-coefficient trees, seamlessly handling multi-dimensional spatial data without requiring formula macro workarounds.

  • Flat N-ary Histograms (TreeEffect): Added the max_leaves parameter to bypass binary depth and force flat 1D N-ary splits. This includes an Anti-Starvation Protocol (evenly spaced bins) for single trees to guarantee full matrix rank and prevent over-complete matrix singularities in piecewise regressions.

  • Academic Reproductions: Added official benchmark scripts reproducing foundational load forecasting architectures using the TAM framework:

    • 2011_pierrot_goude.py: Benchmarks native grouping vs. PyGAM manual loops using local B-splines.

    • 2025_doumeche_et_al.py: Benchmarks the transition from local splines to global Fourier bases with Sobolev regularization.

  • Theory, Cheatsheets and Documentation: Added comprehensive TAM documentation:

🚀 Changed (Major Refactoring & Optimization)

  • OPERA Dual API Support (OperaTAM): Added a standard array-based initialization (target_col="y", expert_cols=["E1", "E2"]) alongside the existing R-like formula API (formula="y ~ l(E1) + l(E2)"), allowing for simpler dynamic aggregation.

  • Architectural Shape Normalization: Overhauled build_feature_map across TreeEffect, NeuralEffect, and RBFEffect. Added a dynamic dimensional router to natively resolve tensor broadcasting ambiguities across 1D (OOD wrappers), 2D (Kronecker te(...) interactions), and 3D+ (Primal Solver Factory) inputs.

  • Formula Parser Robustness: Upgraded parse_formula_to_terms to explicitly track and uniquely index nested sub-arguments using positional indices (i, j). Resolves parameter collision and overwriting issues when parsing nested interaction terms containing identical effect types.

  • Memory Probe Safeguards (Dummy Pass): Improved robustness of the VRAM footprint estimation during the dummy pass across all base effects. This prevents premature initialization of randomized partition geometries, NEPT weights, or RBF centers during the framework’s memory estimation phase.

  • Categorical Effect Automation: The Categorical effect (c(...)) now automatically parses the dataset to count n_cat if the parameter is omitted by the user.

  • MLOps Dashboard (plotting_dashboard):

    • Enhanced chronological forecast plots with a forecast_smoothing parameter (supports rolling averages and date-based resampling).

    • Implemented dynamic evaluation metrics for the Test Set Vulnerability heatmap (automatically scaling for RMSE, MAE, MAPE, etc.).

    • Unified color mapping across all subplots ensuring consistent model identification using Matplotlib’s tab10 colormap.


[Internal] 1.2.2 - 2026-03-26 (Not publicly released)

✨ Added (New Models & Core Features)

  • Evolutionary Orchestrator (AutoTAM): A multi-fidelity AutoML engine for automated GAM discovery. It utilizes a Hub-and-Spoke evolutionary architecture, strict topological sanitization, and bi-level optimization (GPU MSP-GCV) to solve the combinatorial explosion of adaptive models, ultimately deploying orthogonal experts into a Dual OPERA arena.

  • OPERA (OperaTAM): A new expert aggregation meta-learner featuring a fast GPU implementation natively optimized via @torch.jit.script.

  • Kalman Filter (KalmanTAM): Dynamically tracks coefficient drift over time via a Fast Dynamic Extended Kalman Filter (EKF), highly optimized using the Woodbury matrix identity (reducing inversion complexity to \(\mathcal{O}(T_{block}^3)\)) and compiled with TorchScript.

  • DeepGAM (NeuralTAM): A new Deep-GAM hybrid model (Additive + Deep Learning) implementing Group-wise Orthogonal Backfitting.

  • Tree Effect (TreeEffect): Added the Tree / Random Forest effect (t(...)) designed for GPU, based on Oblivious Random Trees and Random Binning Features approximation.

  • Hardware Manager (HardwareManager): Centralized hardware abstraction layer to dynamically manage the capabilities of different compute backends.

  • _dispatcher.py (Mathematical Solver Dispatcher): Created an intelligent routing layer between statistical modeling abstractions and PyTorch linear algebra engines. It dynamically routes resolution to either a chunked direct solver or a Matrix-Free Sparse Conjugate Gradient (CG) solver based on topological complexity and available VRAM.

  • _memory.py (Hardware Memory Management and Estimation): Completely isolated low-level hardware interactions into a dedicated module. It estimates the byte footprint of massive matrices and calculates safe algorithmic chunk sizes.

🚀 Changed (Major Refactoring & Optimization)

  • Neural Effect Improvements (NeuralEffect): Added support for multiple hidden layers to project variables into higher dimensions.

  • Native GPU Acceleration: Complete migration of intensive CPU to GPU calculation for Splines (s(...)), Wavelets (w(...)), and RBF (rbf(...)) effects, improving performance of design matrix construction.

  • Memory Management (Safeguards & Smart Chunking):

    • Overhauled memory safety to prevent and correct CUDA Out of Memory bugs via a smart chunking system.

    • Implemented a memory safeguard for CPU / group-chunking by independent series.

    • Established strict dynamic RAM & VRAM safety margins to guarantee the stability of large matrix inversion operations.


[Internal] 1.2.1 - 2025-12-18 (Not publicly released)

This version represents a complete architectural overhaul, introducing advanced functional bases (Spectrum), Conformal Prediction, and a full benchmark suite.

✨ Added (New Models & Core Features)

  • Auto-ML (GCV): Added StaticTAM.auto_fit() using Generalized Cross Validation (GCV) for automatic global regularization parameter selection, eliminating the need for a validation set.

  • Safety Module (Conformal Prediction): Added SafetyTAM implementing Split Conformal (static) and Adaptive Conformal Inference (ACI) (dynamic) to guarantee valid confidence intervals under distribution shift.

  • Hierarchical Reconciliation: Added HierarchicalTAM to solve global constraints (e.g., National = Sum of Regions) via joint optimization on the primal system.

  • Model Introspection: Added StaticTAM.summary() to display the model’s structure, complexity, and regularization parameters.

  • Core Effects Library (spectrum): Implemented a complete modular library of advanced functional bases:

    • ChebyshevEffect (p(...)): Global polynomials for stable trend approximation.

    • WaveletEffect (w(...)): Ricker wavelets for local anomaly and transient feature detection.

    • NeuralEffect (n(...)): Neural projection for high-dimensional non-linearity.

    • RBFEffect (rbf(...)): Support for both Gaussian and Matérn (physics-informed) kernels.

    • TensorProductEffect (te(...)): Multivariate interactions (Kronecker product) for surface modeling.

    • UniversalPhysicsEffect (phys(...)): PIKL (Physics-Informed Kernel Learning) for constraining models with differential operators (ODEs/PDEs).

🚀 Changed (Major Refactoring & Optimization)

  • Math Engine (Primal Solver): Formally validated the exact Primal Ridge Solver utilizing block-diagonal covariance accumulation. Corrected performance tracking to accurately reflect the framework’s time complexity of \(\mathcal{O}(G \times T \times D^2 + G \times D^3)\), ensuring isolated mathematical resolution per group \(G\).

  • Effect Architecture: Refactored the core around BaseEffect, establishing the List[BaseEffect] as the standard configuration.

  • Modularization: Monolithic _effects.py was entirely split into the spectrum package, improving modularity.

  • Normalization Domain: Changed global feature normalization from the Fourier-centric \([-\pi, \pi]\) to the strictly orthogonal \([-1, 1]\) domain in _data.py. Basis functions now apply internal scaling (e.g., Fourier rescales to \([-\pi, \pi]\)).

  • Decomposition Robustness: Implemented collision detection in _math.py to automatically prefix feature effects (e.g., l_time, s_time) when multiple bases share the same input variable.

🐛 Fixed (Critical)

  • Recursive Parsing: Implemented an architectural fix in parse_formula_to_terms to correctly identify and preserve string tokens (like ga_te or grid_k) during the recursive parsing of te(...) terms.

  • Syntax Stability: Converted all docstrings containing LaTeX math commands to raw strings (r"""...""") to eliminate Python SyntaxWarnings.


[Internal] 1.1.1 - 2025-11-21 (Not publicly released)

This version introduced the Formula API and the first object-oriented refactoring.

⚠️ Breaking Changes

  • Removed legacy dictionary-based API (m_orders, s_orders, alpha_list)

  • Introduced formula-based API as the primary interface

✨ Added

  • Formula-based API (model/additive.py): Implemented a new, intuitive R-like formula API (e.g., Load ~ s(temp, k=10) + l(day_type)) as the new standard for model initialization.

  • Spline Effects (model/_effects.py): Added SplineEffect (P-splines) as a new core effect type, available via s(...).

  • Formula Parser (common/utils.py): Added a parse_formula_to_terms function to support the new API.

  • StaticTAM & AdaptiveTAM: Implemented the full object-oriented API (.fit(), .predict()) and the online error correction model.

  • Multi-Start Grid Search: The grid_search_fit method now uses a Multi-Start Coordinate Descent strategy (Conservative, Median, Aggressive) to avoid local minima.

  • diagnostics Module: Added a module for model analysis, including t-tests and feature importance visualization.

⚙️ Changed

  • Legacy API Removed: Removed the old m_orders, s_orders, alpha_list dictionary-based configuration from v0.0.6.

  • Package Structure: The codebase was refactored into a modular package structure (common, model).

  • Internal Math: Math functions (_math.py) were cleaned of all effect-specific logic and made robust to 2D/3D tensor inputs.

  • Hardcoded Names Removed: Removed dependencies on specific column names (tod, timestamp, Load).


0.0.6 - 2025-05-27

Added

  • Initial project setup based on the original weakl v0.0.6 package.