API Reference¶
This section contains the complete API reference for metalog-jax.
Table of Contents
metalog_jax¶
Main Module¶
Metalog JAX library.
Metalog Distribution¶
Metalog distribution implementations and fitting functions.
This module provides the main metalog distribution classes and fitting functions:
- Classes:
Metalog: Standard metalog distribution fitted via regression methods. SPTMetalog: Symmetric Percentile Triplet metalog with closed-form coefficients. GridResult: Results container for grid search and hyperparameter optimization.
- Functions:
fit: Fit a standard metalog distribution using configurable regression methods. fit_spt_metalog: Fit an SPT metalog distribution using closed-form formulas.
The fit function uses a dispatch table pattern to route to the appropriate regression implementation based on the MetalogFitMethod specified in MetalogParameters:
MetalogFitMethod.OLS -> metalog_jax.regression.ols.fit_ordinary_least_squares
MetalogFitMethod.Lasso -> metalog_jax.regression.lasso.fit_lasso
MetalogFitMethod.Feasible -> metalog_jax.feasibility.a_star.fit_feasible
See also
metalog_jax.base: Base classes and parameter configurations. metalog_jax.regression: Regression implementations for fitting.
- class metalog_jax.metalog.SPTMetalog(metalog_params, a)[source]¶
Bases:
MetalogBaseSymmetric Percentile Triplet (SPT) metalog distribution.
SPTMetalog is a specialized metalog implementation that uses exactly three terms fitted from three quantiles: the alpha-quantile, median (0.5), and (1-alpha)-quantile, where alpha < 0.5. This method provides a computationally efficient way to fit metalog distributions with closed-form coefficient solutions, avoiding the need for optimization or linear regression.
The SPT approach is particularly useful when you need: - A quick approximation using only three data points - Guaranteed feasibility through explicit feasibility checks - Analytical coefficient formulas for different boundedness types - A distribution that matches specific symmetric or asymmetric quantile triplets
Unlike the standard Metalog class which uses OLS or LASSO regression to fit arbitrary numbers of terms, SPTMetalog always has exactly 3 terms and computes coefficients directly from the quantile triplet. This makes it faster for small datasets but less flexible than the full metalog approach.
- Variables:
metalog_params (
SPTMetalogParameters) – Configuration parameters specific to SPT fitting, including boundedness type, boundary values, and the alpha parameter that determines which quantiles are used (alpha, 0.5, 1-alpha).a (
Union[Array,ndarray,bool,number,float,int]) – Coefficient vector of shape (3,) representing the three fitted metalog distribution parameters. These are computed using closed-form formulas specific to the boundedness type. A list or tuple is converted to a float JAX array on construction.
Examples
Fit an unbounded SPT metalog from data:
>>> import jax.numpy as jnp >>> from metalog_jax.base import MetalogBoundedness, SPTMetalogParameters >>> from metalog_jax.metalog import SPTMetalog, fit_spt_metalog >>> >>> # Generate sample data >>> data = jnp.array([1.2, 2.3, 2.8, 3.5, 4.1, 5.6, 6.2, 7.8, 9.1]) >>> >>> # Configure SPT parameters >>> params = SPTMetalogParameters( ... boundedness=MetalogBoundedness.UNBOUNDED, ... alpha=0.1, # Use 10th, 50th, and 90th percentiles ... lower_bound=0.0, ... upper_bound=0.0, ... ) >>> >>> # Fit the SPT metalog >>> spt = fit_spt_metalog(data, params) >>> >>> # The fit passes through the data's 10th, 50th and 90th percentiles >>> spt.ppf(jnp.array([0.1, 0.5, 0.9])).round(2).tolist() [2.08, 4.1, 8.06] >>> >>> # Summary statistics are properties >>> mean, std = spt.mean, spt.std >>> >>> # Serialize and load it back (save and load do the same with a file) >>> SPTMetalog.loads(spt.dumps()) == spt True
Show the fitted quantile function.
printwrites one line (wrapped here),reprthe same line at full precision, and theprintmethod one term per line. With a lower bound of 0, Q(y) = 0 + exp(M(y)):>>> lower = fit_spt_metalog( ... data, params.replace(boundedness=MetalogBoundedness.STRICTLY_LOWER_BOUND) ... ) >>> print(lower) SPTMetalog(3 terms, alpha = 0.1, lower bound 0): Q(y) = 0 + exp(M(y)), M(y) = 1.41099 + 0.30824 ln(y/(1-y)) - 0.00153176 (y - 0.5) ln(y/(1-y)) >>> lower.print() SPTMetalog: 3 terms, alpha = 0.1, lower bound 0 Q(y) = 0 + exp(M(y)) M(y) = 1.41099 + 0.30824 ln(y/(1-y)) - 0.00153176 (y - 0.5) ln(y/(1-y)) >>> lower.print(m=False, verbose=True) SPTMetalog: 3 terms, alpha = 0.1, lower bound 0 Q(y) = 0 + exp(1.41099 + 0.30824 ln(y/(1-y)) - 0.00153176 (y - 0.5) ln(y/(1-y)))
Note
SPT metalog distributions may fail feasibility checks if the data does not satisfy specific constraints on the symmetry ratio r. The feasibility constraints depend on the boundedness type and alpha parameter. When feasibility fails, fit_spt_metalog will raise an AssertionError.
The SPT method is described in Keelin (2016) as a special case of the metalog family that enables rapid approximation with minimal data.
See also
Metalog: Standard metalog implementation using regression fitting. MetalogBase: Base class defining the common interface. fit_spt_metalog: Function to fit SPT metalog distributions from data.
References
Keelin, T. W. (2016). The Metalog Distributions. Decision Analysis, 13(4), 243-277. https://doi.org/10.1287/deca.2016.0338
- Parameters:
- metalog_params: SPTMetalogParameters¶
- __eq__(other)[source]¶
Check equality between two SPTMetalog distribution instances.
Delegates to the parent MetalogBase.__eq__ method.
- __repr__()[source]¶
Show the SPTMetalog and its quantile function on one line, at full precision.
Delegates to MetalogBase.__repr__, which flax.struct.dataclass would otherwise replace with a list of the dataclass fields.
- Returns:
- The one-line equation, or a summary if the model is traced or
batched.
- Return type:
- property num_terms: int¶
Get the number of terms in the metalog expansion.
- Returns:
3 - an SPT Metalog always has three terms.
- property method: NoReturn¶
An SPT metalog has no fit method.
fit_spt_metalog computes the coefficients in closed form (Keelin 2016, Propositions 1-4) rather than by regression, so SPTMetalogParameters has no
method.- Raises:
AttributeError – Always, so
hasattr(spt, "method")is False.
- __init__(metalog_params, a)¶
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
- class metalog_jax.metalog.Metalog(metalog_params, a)[source]¶
Bases:
MetalogBaseStandard metalog distribution fitted via regression.
Metalog is the full-featured metalog implementation that extends MetalogBase and uses ordinary least squares (OLS) or LASSO regression to fit metalog distributions with arbitrary numbers of terms (typically 2-30). This approach provides maximum flexibility and accuracy for modeling complex distributions from larger datasets.
Unlike SPTMetalog which uses exactly 3 terms with closed-form coefficient formulas, the standard Metalog class: - Supports any number of terms (configurable via num_terms parameter) - Uses regression-based fitting (OLS or LASSO methods) - Can model more complex distribution shapes with higher-order terms - Requires more data for reliable fitting (recommended minimum: 3 x num_terms) - May encounter numerical issues with very high term counts
This class inherits all distribution methods from MetalogBase including ppf (quantile function), cdf, pdf, and statistical moments (mean, variance, std, skewness, kurtosis). It also provides serialization capabilities for saving and loading fitted distributions.
- Variables:
metalog_params (
MetalogParameters) – Configuration parameters for the standard metalog fit, including boundedness type (UNBOUNDED, STRICTLY_LOWER_BOUND, STRICTLY_UPPER_BOUND, or BOUNDED), boundary values, fitting method (OLS or LASSO), and number of terms in the expansion.a (
Union[Array,ndarray,bool,number,float,int]) – Coefficient vector of shape (num_terms,) representing the fitted metalog distribution parameters. Each coefficient corresponds to one basis function in the metalog expansion. These are computed via regression from the input data. A list or tuple is converted to a float JAX array on construction.
Examples
Fit an unbounded metalog with 9 terms:
>>> import jax.numpy as jnp >>> from metalog_jax.base import MetalogBoundedness, MetalogFitMethod >>> from metalog_jax.base import MetalogInputData, MetalogParameters >>> from metalog_jax.metalog import fit >>> from metalog_jax.utils import DEFAULT_Y >>> >>> # Sample data >>> samples = jnp.array([1.2, 2.3, 2.8, 3.5, 4.1, 5.6, 6.2, 7.8, 9.1, 10.5]) >>> data = MetalogInputData.from_values( ... samples, DEFAULT_Y, precomputed_quantiles=False ... ) >>> >>> # Configure metalog parameters >>> params = MetalogParameters( ... boundedness=MetalogBoundedness.UNBOUNDED, ... method=MetalogFitMethod.Feasible, ... num_terms=9, ... lower_bound=0.0, ... upper_bound=0.0, ... ) >>> >>> # Fit the metalog distribution >>> m = fit(data, params) >>> >>> # Evaluate the quantile function at probability 0.75 >>> round(float(m.ppf(jnp.array([0.75]))[0]), 2) 7.4 >>> >>> # Summary statistics are properties >>> mean, variance, skewness = m.mean, m.var, m.skewness
Show the fitted quantile function of a 5-term OLS fit bounded on (0, 15).
printwrites one line (wrapped here),reprthe same line at full precision, and theprintmethod one term per line, with options:>>> bounded = fit( ... data, ... MetalogParameters( ... boundedness=MetalogBoundedness.BOUNDED, ... method=MetalogFitMethod.OLS, ... num_terms=5, ... lower_bound=0.0, ... upper_bound=15.0, ... ), ... ) >>> print(bounded) Metalog(5 terms, OLS, bounded on (0, 15)): Q(y) = (0 + 15 exp(M(y))) / (1 + exp(M(y))), M(y) = -0.754464 + 0.0719273 ln(y/(1-y)) - 0.110759 (y - 0.5) ln(y/(1-y)) + 2.45794 (y - 0.5) + 1.1187 (y - 0.5)^2 >>> bounded.print() Metalog: 5 terms, OLS, bounded on (0, 15) Q(y) = (0 + 15 exp(M(y))) / (1 + exp(M(y))) M(y) = -0.754464 + 0.0719273 ln(y/(1-y)) - 0.110759 (y - 0.5) ln(y/(1-y)) + 2.45794 (y - 0.5) + 1.1187 (y - 0.5)^2 >>> bounded.print(m=False, verbose=True) Metalog: 5 terms, OLS, bounded on (0, 15) Q(y) = (0 + 15 exp(-0.754464 + 0.0719273 ln(y/(1-y)) - 0.110759 (y - 0.5) ln(y/(1-y)) + 2.45794 (y - 0.5) + 1.1187 (y - 0.5)^2)) / (1 + exp(-0.754464 + 0.0719273 ln(y/(1-y)) - 0.110759 (y - 0.5) ln(y/(1-y)) + 2.45794 (y - 0.5) + 1.1187 (y - 0.5)^2))
Note
The standard Metalog class is the recommended choice for most use cases involving sufficient data. Use SPTMetalog only when you need a quick three-term approximation or when you have exactly three representative quantiles. For datasets with at least 10-30 observations, the standard metalog typically provides superior fit quality.
Higher term counts generally improve accuracy but require more data and may cause overfitting or numerical instability. As a rule of thumb, ensure your sample size is at least 3 times the number of terms.
See also
SPTMetalog: Specialized three-term metalog using closed-form coefficients. MetalogBase: Base class defining the common distribution interface. fit_metalog: Function to fit standard metalog distributions from data.
References
Keelin, T. W. (2016). The Metalog Distributions. Decision Analysis, 13(4), 243-277. https://doi.org/10.1287/deca.2016.0338
- metalog_params: MetalogParameters¶
- __eq__(other)[source]¶
Check equality between two Metalog distribution instances.
Delegates to the parent MetalogBase.__eq__ method.
- __repr__()[source]¶
Show the Metalog and its quantile function on one line, at full precision.
Delegates to MetalogBase.__repr__, which flax.struct.dataclass would otherwise replace with a list of the dataclass fields.
- Returns:
- The one-line equation, or a summary if the model is traced or
batched.
- Return type:
- property num_terms: int¶
Get the number of terms in the metalog expansion.
- Returns:
Integer representing the number of basis functions used in the metalog approximation. Higher values generally provide better accuracy but require more data.
- property method: MetalogFitMethod¶
Get the method used to fit the metalog distribution.
- Returns:
MetalogFitMethod enum of regression methods for fitting metalog distribution.
- __init__(metalog_params, a)¶
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
- metalog_jax.metalog.fit(data, metalog_params, regression_hyperparams=None)[source]¶
Fit a metalog distribution to data using the specified configuration.
Estimates the metalog distribution coefficients by solving a linear regression problem that maps the basis functions (target matrix) to the transformed quantiles. The regression method specified in metalog_params determines the fitting approach, and optional regression_hyperparams allow fine-tuning of regularization settings.
This function processes a MetalogInputData instance containing validated input data (quantiles and probability levels) and fits a metalog distribution according to the specified parameters. The data must be created using MetalogInputData.from_values() which performs comprehensive validation.
- Parameters:
data (
MetalogInputData) – Validated input data created via MetalogInputData.from_values(). Contains: - x: Quantile values (precomputed or computed from raw samples) - y: Probability levels in (0, 1) - precomputed_quantiles: Flag indicating data type Direct instantiation of MetalogInputData is prevented by __post_init__ validation to ensure data integrity.metalog_params (
MetalogParameters) – Configuration parameters specifying the distribution characteristics (boundedness, bounds, number of terms) and the regression method to use for fitting. The method field determines which regression approach is used: - MetalogFitMethod.OLS: Ordinary Least Squares (no regularization) - MetalogFitMethod.Lasso: L1 regularization - MetalogFitMethod.Feasible: Best least-squares fit among valid metalogsregression_hyperparams (
RegularizedParameters) – Optional regularization hyperparameters for controlling the fitting process when using LASSO regression method. Must be None for OLS and Feasible. If None, default hyperparameters are used. Must be an instance of LassoParameters for MetalogFitMethod.Lasso.
- Return type:
- Returns:
Metalog containing the fitted coefficient vector and the configuration parameters used to fit the metalog distribution. The returned object is validated via
assert_fitted()to ensure the fit produces a feasible distribution with strictly positive PDF values.- Raises:
TypeError – If metalog_params.method is not a valid MetalogFitMethod instance, or if regression_hyperparams is given for OLS or Feasible.
checkify.JaxRuntimeError – If the fitted distribution produces non-positive PDF values, indicating an infeasible fit. This validation is performed by
assert_fitted()before returning.checkify.JaxRuntimeError – For OLS and Feasible, if the basis matrix is rank deficient so the fit is not unique (e.g. n = k symmetric probabilities with 7, 11, 15, … terms; Baucells et al. 2025, Proposition 2), or if Feasible cannot certify its result.
Example
Basic usage with OLS (no regularization):
>>> import jax.numpy as jnp >>> from metalog_jax.base import MetalogInputData, MetalogParameters >>> from metalog_jax.base import MetalogBoundedness, MetalogFitMethod >>> from metalog_jax.metalog import fit >>> >>> # Create validated input data using from_values() >>> raw_data = jnp.array([1.2, 2.3, 2.8, 3.5, 4.1, 5.6]) >>> data = MetalogInputData.from_values( ... x=raw_data, ... y=jnp.array([0.1, 0.25, 0.5, 0.75, 0.9, 0.95]), ... precomputed_quantiles=False # Raw samples, not precomputed quantiles ... ) >>> >>> # Configure metalog parameters with OLS >>> metalog_params = MetalogParameters( ... boundedness=MetalogBoundedness.UNBOUNDED, ... method=MetalogFitMethod.OLS, ... lower_bound=0.0, ... upper_bound=0.0, ... num_terms=5 ... ) >>> >>> # Fit the metalog distribution >>> m = fit(data, metalog_params)
Note
The regression_hyperparams parameter is only applicable for LASSO method. Passing it with OLS or Feasible raises a TypeError.
If regression_hyperparams is None, sensible defaults are used for each method.
For production use, consider tuning regularization hyperparameters via cross-validation to prevent overfitting while maintaining good fit quality.
See also
fit_spt_metalog: Alternative fitting method using Symmetric Percentile Triplet. MetalogInputData.from_values: Required method for creating validated input data. metalog_jax.regression.lasso.LassoParameters: Hyperparameters for LASSO regression. metalog_jax.feasibility.best_feasible_fit: Best feasible fit (Baucells et al.).
References
Keelin, T. W. (2016). The Metalog Distributions. Decision Analysis, 13(4), 243-277. https://doi.org/10.1287/deca.2016.0338
Baucells, M., Chrisman, L., Keelin, T. W., & Xu, Z. S. (2025). On the Properties of the Metalog Distribution. Darden Business School Working Paper No. 5279416. https://doi.org/10.2139/ssrn.5279416
- metalog_jax.metalog.fit_spt_metalog(array, spt_metalog_params, *, precomputed_quantiles=False)[source]¶
Fit a Symmetric Percentile Triplet (SPT) metalog to data or assessed quantiles.
This function fits a 3-term metalog distribution using the Symmetric Percentile Triplet (SPT) parameterization method described in Keelin (2016). Unlike the standard metalog fit which uses many quantiles, the SPT method uses exactly three symmetric quantiles: alpha, 0.5 (median), and (1 - alpha), where 0 < alpha < 0.5.
The SPT approach provides a quick, analytical solution for fitting a metalog distribution when: - You have limited data or only three quantiles available - You want a simple, closed-form solution without regression - You need a fast approximation with 3 terms only
The method computes the 3 metalog coefficients directly from the three quantile values without requiring least squares regression. The formulas vary based on the boundedness type (unbounded, semi-bounded, or bounded).
- Parameters:
array (
Union[Array,ndarray,bool,number,float,int]) – Input data array with at least 3 elements from which to compute empirical quantiles. The array will be used to compute the alpha-th, 50th, and (1-alpha)-th percentiles. Ifprecomputed_quantilesis True, it is instead the triplet [q_alpha, q_0.5, q_1-alpha] itself, for example an expert’s assessed 10th, 50th and 90th percentiles.spt_metalog_params (
SPTMetalogParameters) –Configuration parameters containing:
alpha: Lower percentile parameter in (0, 0.5). Common values are 0.1 (10-50-90 percentiles) or 0.25 (25-50-75 percentiles/IQR).
boundedness: Domain constraint type (UNBOUNDED, STRICTLY_LOWER_BOUND, STRICTLY_UPPER_BOUND or BOUNDED).
lower_bound: Lower boundary value (used for STRICTLY_LOWER_BOUND and BOUNDED).
upper_bound: Upper boundary value (used for STRICTLY_UPPER_BOUND and BOUNDED).
precomputed_quantiles (
bool) – Whetherarrayholds the three quantiles themselves rather than data. Defaults to False.
- Returns:
Fitted SPT metalog distribution containing:
metalog_params: The input configuration parameters
a: Coefficient vector of length 3 containing the fitted metalog parameters [a1, a2, a3] that define the quantile function
- Return type:
- Raises:
AssertionError – If array has fewer than 3 elements, is not rank 1, or contains non-numeric values.
AssertionError – If precomputed_quantiles is True and array doesn’t have shape (3,).
AssertionError – If alpha is not positive or alpha >= 0.5.
AssertionError – If the computed quantiles violate feasibility constraints (q_alpha < median < q_complement).
AssertionError – If a quantile lies outside the support: at or below lower_bound (STRICTLY_LOWER_BOUND and BOUNDED), or at or above upper_bound (STRICTLY_UPPER_BOUND and BOUNDED).
AssertionError – If the triplet is infeasible for the boundedness type (Keelin 2016, Propositions 2-4, and the mirror image of Proposition 3 for STRICTLY_UPPER_BOUND).
AssertionError – If the coefficients are not finite, for example because the data or bounds contain NaN.
checkify.JaxRuntimeError – Under
jax.vmap, for the value checks above (array shape and type checks still raise AssertionError).
Example
>>> import jax.numpy as jnp >>> from metalog_jax.metalog import fit_spt_metalog, SPTMetalogParameters >>> from metalog_jax.metalog import MetalogBoundedness >>> # Generate sample data >>> data = jnp.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]) >>> # Configure SPT parameters using 10-50-90 percentiles >>> params = SPTMetalogParameters( ... alpha=0.1, ... boundedness=MetalogBoundedness.UNBOUNDED, ... lower_bound=0.0, ... upper_bound=1.0 ... ) >>> # Fit the SPT metalog >>> spt_metalog = fit_spt_metalog(data, params) >>> # The result contains 3 coefficients >>> assert len(spt_metalog.a) == 3 >>> >>> # Fit several datasets at once >>> import jax >>> batch = jnp.stack([data, 2 * data]) >>> jax.vmap(lambda d: fit_spt_metalog(d, params).a)(batch).shape (2, 3) >>> >>> # Fit an expert's 10th, 50th and 90th percentile assessments directly >>> assessed = jnp.array([2.08, 4.1, 8.06]) >>> spt = fit_spt_metalog(assessed, params, precomputed_quantiles=True) >>> spt.ppf(jnp.array([0.1, 0.5, 0.9])).round(2).tolist() [2.08, 4.1, 8.06]
Note
The SPT method always produces exactly 3 metalog terms, regardless of data size. For more flexible fits with more terms, use the standard fit() function.
The method performs multiple feasibility checks to ensure the quantiles and parameters produce a valid probability distribution.
Pass
precomputed_quantiles=Truefor quantiles you already have. By default a 3-element array is treated as data, andjnp.quantileinterpolates between its three values, so the fit would not pass through them.For unbounded distributions, the median position ratio r must satisfy specific constraints related to k_alpha to ensure feasibility, where r = (median - q_alpha) / (q_complement - q_alpha).
Works under
jax.vmapandjax.jitover the data, alpha and bounds, and the parameters can be passed as a jit argument (boundedness is a static field, so each boundedness type compiles once). Inside transforms the value checks usecheckify, so wrap a jitted call withcheckify.checkifyand callthrow()on the returned error.
References
Keelin, T. W. (2016). The Metalog Distributions. Decision Analysis, 13(4).
- class metalog_jax.metalog.GridResult(metalog, ks_dist, feasible=None)[source]¶
Bases:
objectResults from metalog grid search or hyperparameter optimization.
GridResult encapsulates the outcome of fitting a metalog distribution along with a goodness-of-fit metric. This dataclass is typically used when performing grid searches over hyperparameters (e.g., number of terms, regularization penalties) or when comparing multiple metalog configurations to select the best-fitting distribution.
The primary use case is hyperparameter tuning via grid search with JAX’s vmap to fit multiple metalog configurations in parallel, then selecting the best configuration based on the Kolmogorov-Smirnov distance.
The class uses Flax’s struct.dataclass decorator to ensure immutability and compatibility with JAX transformations (jit, vmap, grad), making it suitable for use in vectorized grid search operations.
- Variables:
metalog (
Metalog|SPTMetalog) – The fitted metalog distribution. Can be either a standard Metalog (fitted via regression with configurable terms) or SPTMetalog (fitted using Symmetric Percentile Triplet with exactly 3 terms). Contains: - metalog_params: Configuration parameters (boundedness, method, etc.) - a: Coefficient vector representing the fitted distributionks_dist (
Union[float,int]) – Scalar Kolmogorov-Smirnov distance measuring the goodness-of-fit between the fitted metalog distribution and the input random variable. This is the maximum absolute difference between the empirical CDF of the input data and the fitted metalog’s CDF: - 0: Perfect fit (empirical CDFs are identical) - 1: Worst possible fit (completely non-overlapping distributions) - Lower values indicate better fit quality Used to select the best metalog configuration from a grid search.feasible (
Union[Array,ndarray,bool,number,None]) – Whether each fit is a valid distribution, with the same shape asks_dist: True whenM'(y) >= 0on all of (0, 1), asmetalog_jax.feasibility.check_feasibilitydecides exactly (Baucells et al. 2025, Proposition 5 with Algorithm 1). An infeasible fit’s quantile function isn’t increasing, so itsks_distisn’t a distance between two distributions, andfind_best_configandextract_best_from_gridskip it. Every grid function fills this in; it’s None for a GridResult built without it.
Note
This class is immutable due to the struct.dataclass decorator
The ks_dist attribute provides a single goodness-of-fit metric for comparing different metalog configurations
When used with jax.vmap, the metalog and ks_dist fields will be batched arrays/PyTrees, allowing vectorized comparison
Lower KS distances indicate better fit quality, but extremely low values may indicate overfitting (especially with high term counts)
For production use, consider cross-validation instead of or in addition to KS distance for hyperparameter selection
See also
ks_distance: Function to compute the Kolmogorov-Smirnov distance. fit: Standard metalog fitting function. fit_spt_metalog: SPT metalog fitting function. Metalog: Standard metalog distribution class. SPTMetalog: Symmetric Percentile Triplet metalog class.
References
Kolmogorov, A. N. (1933). “Sulla determinazione empirica di una legge di distribuzione”. Giornale dell’Istituto Italiano degli Attuari, 4: 83-91.
Keelin, T. W. (2016). The Metalog Distributions. Decision Analysis, 13(4), 243-277. https://doi.org/10.1287/deca.2016.0338
- Parameters:
- metalog: Metalog | SPTMetalog¶
- __init__(metalog, ks_dist, feasible=None)¶
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
Grid Search¶
Grid search methods for the Metalog JAX library.
This module provides generalized vmap-based grid search functions for fitting metalog distributions across multiple datasets, hyperparameters, and term counts.
The functions are designed to work with JAX’s vectorization primitives for efficient parallel computation on CPU, GPU, or TPU devices.
- Key functions:
fit_grid: Unified grid search over any combination of axes
fit_grid_datasets: 1D grid over datasets (shared params)
fit_grid_hyperparams: 1D grid over hyperparameters for a single dataset
fit_grid_num_terms: 1D grid over num_terms for a single dataset
fit_grid_datasets_hyperparams: 2D grid over datasets x hyperparameters
fit_grid_datasets_num_terms: 2D grid over datasets x num_terms
fit_grid_full: 3D grid over datasets x hyperparameters x num_terms
find_best_config: Find best configuration in a grid
extract_best_from_grid: Extract best result from grid for a dataset
Every grid result says which fits are valid distributions: GridResult.feasible
is True where M'(y) >= 0 on all of (0, 1), decided exactly by
check_feasibility (Baucells et al. 2025, Proposition 5 with Algorithm 1). OLS and
Lasso fits can be infeasible, and an infeasible fit’s KS distance doesn’t compare two
distributions, so find_best_config and extract_best_from_grid skip infeasible fits.
The grids that fit with fit() (fit_grid_datasets, fit_grid_hyperparams and
fit_grid_datasets_hyperparams) raise for an infeasible fit by default; pass
on_infeasible="flag" to get every fit with the infeasible ones marked instead.
- metalog_jax.grid_search.fit_grid(x, y, params, *, num_terms=None, l1_penalties=None, precomputed_quantiles=False, lasso_params=None, learning_rate=0.01, num_iters=500, tol=1e-06, momentum=0.9)[source]¶
Unified grid search over any combination of axes.
Automatically detects which axes to search based on inputs: - If x.ndim == 2: grid over datasets (batch dimension) - If num_terms provided: grid over num_terms values - If l1_penalties provided: grid over L1 penalties (uses Lasso)
Output shape depends on active axes (ordered: datasets, l1_penalties, num_terms): - (): single dataset, no grids - (n_datasets,): only datasets batched - (n_penalties,): only l1_penalties grid - (n_terms,): only num_terms grid - (n_datasets, n_penalties): datasets x l1_penalties - (n_datasets, n_terms): datasets x num_terms - (n_penalties, n_terms): l1_penalties x num_terms - (n_datasets, n_penalties, n_terms): all three axes
- Parameters:
x (
Array) – Quantile values. Shape (n,) for single dataset or (batch, n) for batched.y (
Array) – Probability levels. Shape (n,) for single dataset or (batch, n) for batched.params (
MetalogParameters) – MetalogParameters configuration. The num_terms field is ignored if num_terms argument is provided.num_terms (
Sequence[int] |None) – Optional sequence of term counts to search over.l1_penalties (
Array|None) – Optional array of L1 penalties to search over (implies Lasso).precomputed_quantiles (
bool) – Whether x values are precomputed quantiles.lasso_params (
LassoParameters|None) – Optional fixed LassoParameters (used when l1_penalties is None but params.method is Lasso).learning_rate (
float) – Learning rate for Lasso. Default: 0.01.num_iters (
int) – Max iterations for Lasso. Default: 500.tol (
float) – Convergence tolerance for Lasso. Default: 1e-6.momentum (
float) – Momentum for Lasso optimizer. Default: 0.9.
- Return type:
- Returns:
GridResult with metalog fits, KS distances and
feasible, whether each fit is a valid distribution. Shape depends on active axes. Passfeasibleto find_best_config so that it skips infeasible fits.- Raises:
ValueError – If params.method is MetalogFitMethod.Feasible, which cannot run inside the vmapped grid (use fit or fit_grid_datasets instead).
- metalog_jax.grid_search.stack_leaves(*leaves)[source]¶
Stack leaf values from multiple PyTrees along a new batch dimension.
This helper function is used with jax.tree.map to transform a list of dataclass instances into a single batched dataclass where each field is stacked along axis 0. It enables efficient vectorized operations by converting multiple independent structures into a batched structure.
- Parameters:
*leaves (
Array) – Variable number of leaf arrays from corresponding positions in multiple PyTrees (e.g., the x field from multiple MetalogInputData instances). Each leaf should be a JAX array or scalar value.- Return type:
- Returns:
A single JAX array with the leaves stacked along a new first dimension, shape (n_leaves, …) where n_leaves is the number of input leaves and … represents the original shape of each leaf.
- metalog_jax.grid_search.make_batch(data)[source]¶
Convert a list of MetalogBaseData instances into a single batched instance.
This function transforms a list of individual MetalogBaseData instances into a single MetalogBaseData instance where each field is batched along the first dimension. This enables vectorized operations across multiple datasets using JAX’s vmap.
- Parameters:
data (
list[MetalogBaseData]) – List of MetalogBaseData instances to batch together. Each instance should have the same structure (same fields), but can have different values. All instances should have arrays with compatible shapes.- Return type:
- Returns:
A single MetalogBaseData instance where each field is stacked along axis 0. If the input list has n elements and each instance has a field with shape (m,), the output will have that field with shape (n, m).
- metalog_jax.grid_search.unvmap(batched_out)[source]¶
Convert batched vmap output into a list of individual outputs.
This function reverses the batching operation performed by jax.vmap by converting a single batched PyTree into a list of individual PyTrees, one per batch element. Each element in the output list has the same structure as the batched output but without the batch dimension.
- Parameters:
batched_out (
Any) – The batched output from jax.vmap(f)(…). Can be a JAX array, tuple, dict, dataclass, or any PyTree structure where each leaf has a batch dimension as its first axis.- Return type:
- Returns:
A list of length n_batch where each element is a PyTree with the same structure as batched_out but with the batch dimension removed.
- metalog_jax.grid_search.pad_metalog_coeffs(metalog, max_terms)[source]¶
Pad metalog coefficient array to max_terms length with zeros.
Since different term counts produce different coefficient array lengths, we pad shorter arrays with zeros to enable stacking into a consistent batched structure.
- Parameters:
metalog (
Metalog|SPTMetalog) – Metalog or SPTMetalog instance to pad.max_terms (
int) – Target length for the coefficient array.
- Return type:
- Returns:
Metalog instance with coefficients padded to max_terms.
- metalog_jax.grid_search.fit_grid_datasets(batched_x, batched_y, params, fit_params=None, precomputed_quantiles=False, *, on_infeasible='raise')[source]¶
Fit multiple datasets with shared parameters using vmap.
This function efficiently fits metalog distributions to multiple datasets in parallel using JAX’s vmap. All datasets share the same MetalogParameters and optional regularization settings.
- Parameters:
batched_x (
Array) – Stacked quantile values, shape (n_datasets, n_samples).batched_y (
Array) – Stacked probability levels, shape (n_datasets, n_samples).params (
MetalogParameters) – MetalogParameters shared across all fits.fit_params (
RegularizedParameters|None) – Optional regularization parameters (e.g., LassoParameters).precomputed_quantiles (
bool) – Whether x values are precomputed quantiles.on_infeasible (
Literal['raise','flag']) – What to do if an OLS or Lasso fit is infeasible. “raise” raises for the batch, naming the infeasible fits; “flag” returns every fit, withfeasibleFalse for the infeasible ones. Feasible fits are always valid.
- Returns:
metalog.a: Coefficients of shape (n_datasets, num_terms)
ks_dist: KS distances of shape (n_datasets,)
feasible: Whether each fit is a valid distribution, shape (n_datasets,)
- Return type:
- Raises:
ValueError – If
on_infeasibleisn’t “raise” or “flag”.checkify.JaxRuntimeError – If
on_infeasibleis “raise” and a fit is infeasible.
- metalog_jax.grid_search.fit_grid_hyperparams(data, params, l1_penalties, learning_rate=0.01, num_iters=500, tol=1e-06, momentum=0.9, *, on_infeasible='raise')[source]¶
Fit a single dataset with a grid of L1 penalty values (Lasso).
This function performs a 1D grid search over L1 regularization penalties for a single dataset using JAX’s vmap for efficient parallel computation.
- Parameters:
data (
MetalogBaseData) – MetalogBaseData instance containing the dataset to fit.params (
MetalogParameters) – MetalogParameters configuration. The method should be Lasso.l1_penalties (
Array) – Array of L1 penalty values to test, shape (n_penalties,).learning_rate (
float) – Learning rate for Lasso gradient descent. Default: 0.01.num_iters (
int) – Maximum iterations for Lasso. Default: 500.tol (
float) – Convergence tolerance for Lasso. Default: 1e-6.momentum (
float) – Momentum for Lasso optimizer. Default: 0.9.on_infeasible (
Literal['raise','flag']) – What to do if a fit is infeasible. “raise” raises for the grid, naming the infeasible fits; “flag” returns every fit, withfeasibleFalse for the infeasible ones.
- Returns:
metalog.a: Coefficients of shape (n_penalties, num_terms)
ks_dist: KS distances of shape (n_penalties,)
feasible: Whether each fit is a valid distribution, shape (n_penalties,)
- Return type:
- Raises:
ValueError – If
on_infeasibleisn’t “raise” or “flag”.checkify.JaxRuntimeError – If
on_infeasibleis “raise” and a fit is infeasible.
- metalog_jax.grid_search.fit_grid_datasets_hyperparams(batched_x, batched_y, params, l1_penalties, precomputed_quantiles=False, learning_rate=0.01, num_iters=500, tol=1e-06, momentum=0.9, *, on_infeasible='raise')[source]¶
Fit multiple datasets with a grid of L1 penalty values (Lasso).
This function performs a 2D grid search over datasets x L1 regularization penalties using nested vmap for efficient parallel computation.
- Parameters:
batched_x (
Array) – Stacked quantile values, shape (n_datasets, n_samples).batched_y (
Array) – Stacked probability levels, shape (n_datasets, n_samples).params (
MetalogParameters) – MetalogParameters configuration. The method should be Lasso.l1_penalties (
Array) – Array of L1 penalty values to test, shape (n_penalties,).precomputed_quantiles (
bool) – Whether x values are precomputed quantiles.learning_rate (
float) – Learning rate for Lasso gradient descent. Default: 0.01.num_iters (
int) – Maximum iterations for Lasso. Default: 500.tol (
float) – Convergence tolerance for Lasso. Default: 1e-6.momentum (
float) – Momentum for Lasso optimizer. Default: 0.9.on_infeasible (
Literal['raise','flag']) – What to do if a fit is infeasible. “raise” raises for the grid, naming the infeasible fits; “flag” returns every fit, withfeasibleFalse for the infeasible ones.
- Returns:
metalog.a: Coefficients of shape (n_datasets, n_penalties, num_terms)
ks_dist: KS distances of shape (n_datasets, n_penalties)
feasible: Whether each fit is a valid distribution, shape (n_datasets, n_penalties)
- Return type:
- Raises:
ValueError – If
on_infeasibleisn’t “raise” or “flag”.checkify.JaxRuntimeError – If
on_infeasibleis “raise” and a fit is infeasible.
- metalog_jax.grid_search.fit_grid_num_terms(data, params, num_terms_list, regression_params=None)[source]¶
Fit a single dataset with different num_terms values.
This function performs a 1D grid search over term counts for a single dataset using JAX’s vmap for efficient parallel computation. Supports OLS and Lasso regression methods based on the params.method setting.
- Vectorization strategy:
num_terms dimension: Fully vectorized via vmap
Uses MAX_TERMS-sized arrays with masking for uniform shapes
- Parameters:
data (
MetalogBaseData) – MetalogBaseData instance containing the dataset to fit.params (
MetalogParameters) – MetalogParameters configuration. The num_terms field is ignored and replaced by values from num_terms_list.regression_params (
Any) – Optional regression hyperparameters (e.g., LassoParameters). If None, uses defaults for the method.
- Returns:
- metalog.a: Coefficients of shape (n_terms, max_terms)
ks_dist: KS distances of shape (n_terms,)
feasible: Whether each fit is a valid distribution, shape (n_terms,)
Where max_terms = max(num_terms_list) and coefficients for smaller term counts are zero-padded.
- Return type:
- Raises:
ValueError – If params.method is not OLS or Lasso.
- metalog_jax.grid_search.fit_grid_datasets_num_terms(batched_x, batched_y, params, num_terms_list, precomputed_quantiles=False, regression_params=None)[source]¶
Fit multiple datasets with different num_terms values.
This function performs a 2D grid search over datasets x term counts using nested vmap for efficient parallel computation. Supports OLS and Lasso regression methods based on the params.method setting.
- Vectorization strategy:
datasets dimension: Fully vectorized via vmap
num_terms dimension: Fully vectorized via vmap
Uses MAX_TERMS-sized arrays with masking for uniform shapes
- Parameters:
batched_x (
Array) – Stacked quantile values, shape (n_datasets, n_samples).batched_y (
Array) – Stacked probability levels, shape (n_datasets, n_samples).params (
MetalogParameters) – MetalogParameters configuration. The num_terms field is ignored and replaced by values from num_terms_list.precomputed_quantiles (
bool) – Whether x values are precomputed quantiles.regression_params (
Any) – Optional regression hyperparameters (e.g., LassoParameters). If None, uses defaults for the method.
- Returns:
- metalog.a: Coefficients of shape (n_datasets, n_terms, max_terms)
ks_dist: KS distances of shape (n_datasets, n_terms)
feasible: Whether each fit is a valid distribution, shape (n_datasets, n_terms)
Where max_terms = max(num_terms_list) and coefficients for smaller term counts are zero-padded.
- Return type:
- Raises:
ValueError – If params.method is not OLS or Lasso.
- metalog_jax.grid_search.fit_grid_full(batched_x, batched_y, params, l1_penalties, num_terms_list, precomputed_quantiles=False, learning_rate=0.01, num_iters=500, tol=1e-06, momentum=0.9)[source]¶
Fit multiple datasets with grids of L1 penalties and term counts (Lasso).
This function performs a 3D grid search over datasets x L1 penalties x num_terms.
- Vectorization strategy:
All three dimensions (datasets, penalties, num_terms) are fully vectorized using nested jax.vmap calls
Uses MAX_TERMS-sized arrays with masking to enable vmap over num_terms
No Python for loops in the computation path
- The implementation uses masked arrays throughout to handle varying num_terms:
Design matrix is always MAX_TERMS columns with unused columns masked to 0
Coefficients are always MAX_TERMS with unused terms masked to 0
PPF computation uses active_mask to only use valid terms
- Parameters:
batched_x (
Array) – Stacked quantile values, shape (n_datasets, n_samples).batched_y (
Array) – Stacked probability levels, shape (n_datasets, n_samples).params (
MetalogParameters) – Base MetalogParameters configuration. The num_terms field is ignored and replaced by values from num_terms_list.l1_penalties (
Array) – Array of L1 penalty values to test, shape (n_penalties,).precomputed_quantiles (
bool) – Whether x values are precomputed quantiles.learning_rate (
float) – Learning rate for Lasso gradient descent. Default: 0.01.num_iters (
int) – Maximum iterations for Lasso. Default: 500.tol (
float) – Convergence tolerance for Lasso. Default: 1e-6.momentum (
float) – Momentum for Lasso optimizer. Default: 0.9.
- Returns:
- metalog.a: Coefficients of shape (n_datasets, n_penalties, n_terms, max_terms)
ks_dist: KS distances of shape (n_datasets, n_penalties, n_terms)
feasible: Whether each fit is a valid distribution, shape (n_datasets, n_penalties, n_terms)
Where max_terms = max(num_terms_list) and coefficients for smaller term counts are zero-padded.
- Return type:
- metalog_jax.grid_search.find_best_config(ks_dists, feasible=None)[source]¶
Find the best configuration (minimum KS distance) in a grid.
Works with grids of any dimensionality (1D, 2D, 3D, etc.). Pass the grid’s
feasiblemask so that infeasible fits, whose KS distances don’t compare two distributions, can’t be chosen: their KS distances count as infinite.- Parameters:
- Returns:
best_indices: Indices of the minimum, shape (n,) for n dimensions
best_ks: Minimum KS distance value (scalar); infinite under jit or vmap if no fit is feasible
- Return type:
- Raises:
ValueError – If
feasibleis given and no fit is feasible.
- metalog_jax.grid_search.extract_best_from_grid(grid_results, dataset_idx)[source]¶
Extract the best result from a grid for a specific dataset.
Finds the feasible configuration with minimum KS distance for the given dataset and extracts the corresponding metalog and KS distance. Infeasible fits, marked by
grid_results.feasible, can’t be chosen.- Parameters:
grid_results (
GridResult) – GridResult from fit_grid_datasets_hyperparams or fit_grid_full. - For 2D grid: ks_dist shape (n_datasets, n_penalties) - For 3D grid: ks_dist shape (n_datasets, n_penalties, n_terms)dataset_idx (
int) – Index of the dataset to extract best result for.
- Returns:
metalog: Best metalog for this dataset
ks_dist: Corresponding KS distance (scalar); infinite under jit or vmap if none of the dataset’s fits is feasible
feasible: Whether that fit is feasible, or None if the grid has no
feasiblemask
- Return type:
- Raises:
ValueError – If none of the dataset’s fits is feasible.
- metalog_jax.grid_search.extract_metalog(grid_result, *indices)[source]¶
Extract a usable Metalog from grid results at given indices.
After grid search, metalog fields become JAX arrays due to vmap stacking. This function extracts a single metalog at the specified indices and converts all scalar fields back to Python types, making the metalog compatible with JIT-compiled methods like ppf, pdf, cdf, etc.
- Parameters:
grid_result (
GridResult) – GridResult from fit_grid or related functions.*indices (
int) – Integer indices to extract. The number of indices should match the dimensionality of the grid: - 1D grid (e.g., L1 penalties): single index like extract_metalog(result, 0) - 2D grid (e.g., L1 x num_terms): two indices like extract_metalog(result, 0, 1) - 3D grid (e.g., datasets x L1 x num_terms): three indices
- Return type:
- Returns:
Metalog instance with Python-typed fields, ready to use with ppf, pdf, cdf, rvs, and other distribution methods.
Examples
Extract the best metalog from a 1D grid search over L1 penalties:
>>> import jax.numpy as jnp >>> from metalog_jax.base import MetalogBoundedness, MetalogFitMethod >>> from metalog_jax.base import MetalogInputData, MetalogParameters >>> from metalog_jax.utils import DEFAULT_Y >>> >>> samples = jnp.array([1.2, 2.3, 2.8, 3.5, 4.1, 5.6, 6.2, 7.8, 9.1, 10.5]) >>> data = MetalogInputData.from_values( ... samples, DEFAULT_Y, precomputed_quantiles=False ... ) >>> params = MetalogParameters( ... boundedness=MetalogBoundedness.UNBOUNDED, ... method=MetalogFitMethod.Lasso, ... num_terms=5, lower_bound=0.0, upper_bound=0.0 ... ) >>> l1_vals = jnp.array([0.0, 0.01, 0.1]) >>> result = fit_grid(data.x, data.y, params, l1_penalties=l1_vals) >>> best_idx, best_ks = find_best_config(result.ks_dist) >>> best_metalog = extract_metalog(result, int(best_idx)) >>> median = best_metalog.ppf(jnp.array([0.5]))
Extract the best metalog from a 2D grid search:
>>> result = fit_grid(data.x, data.y, params, ... l1_penalties=l1_vals, num_terms=[5, 7, 9]) >>> result.ks_dist.shape (3, 3) >>> best_idx, best_ks = find_best_config(result.ks_dist) >>> best_l1_idx, best_terms_idx = int(best_idx[0]), int(best_idx[1]) >>> best_metalog = extract_metalog(result, best_l1_idx, best_terms_idx)
See also
find_best_config: Find indices of best configuration in a grid. extract_best_from_grid: Extract best result for a specific dataset.
Base Classes and Configuration¶
Core¶
Core MetalogBase class for metalog distributions.
This module contains the MetalogBase class, which is the abstract base class for all metalog distribution implementations. It provides the common interface and core functionality shared by Metalog and SPTMetalog classes defined in metalog_jax.metalog.
- The MetalogBase class includes:
Quantile function (PPF) computation
Probability density function (PDF) computation
Cumulative distribution function (CDF) computation
Statistical moments (mean, median, variance, std, mode)
Random variable sampling (rvs)
Serialization/deserialization (save, load, dumps, loads)
Visualization (plot)
Display of the fitted equation (str, repr, _repr_latex_, print)
See also
metalog_jax.base.data: Input data container classes. metalog_jax.base.display: Text and LaTeX formatting of the fitted equation. metalog_jax.base.enums: Enumeration types for boundedness and fit methods. metalog_jax.base.parameters: Parameter configuration classes. metalog_jax.metalog: Metalog and SPTMetalog distribution classes.
- class metalog_jax.base.core.MetalogBase(metalog_params, a)[source]¶
Bases:
objectBase class for metalog distribution implementations.
This abstract base class provides the common interface and core functionality shared by all metalog distribution types (Metalog and SPTMetalog). It implements the fundamental metalog distribution operations including quantile function (PPF), probability density function (PDF), cumulative distribution function (CDF), and statistical moments.
- Subclasses:
- Metalog: Standard metalog fitted via regression (OLS, Ridge, ElasticNet, Lasso).
Defined in metalog_jax.metalog.
- SPTMetalog: Symmetric Percentile Triplet metalog with closed-form coefficients.
Defined in metalog_jax.metalog.
- Variables:
metalog_params (
SPTMetalogParameters|MetalogParameters) – Configuration parameters defining the distribution type. Can be MetalogParameters (for Metalog) or SPTMetalogParameters (for SPTMetalog). Defined in metalog_jax.base.parameters.a (
Union[Array,ndarray,bool,number,float,int]) – Coefficient vector defining the metalog quantile function. Shape is (num_terms,) where num_terms depends on the configuration. A list or tuple is converted to a float JAX array on construction.
Example
MetalogBase is not instantiated directly. Use Metalog or SPTMetalog:
>>> import jax.numpy as jnp >>> from metalog_jax.base import MetalogBoundedness, MetalogFitMethod >>> from metalog_jax.base import MetalogInputData, MetalogParameters >>> from metalog_jax.metalog import fit >>> from metalog_jax.utils import DEFAULT_Y >>> >>> samples = jnp.array([1.2, 2.3, 2.8, 3.5, 4.1, 5.6, 6.2, 7.8, 9.1, 10.5]) >>> data = MetalogInputData.from_values( ... samples, DEFAULT_Y, precomputed_quantiles=False ... ) >>> params = MetalogParameters( ... boundedness=MetalogBoundedness.UNBOUNDED, ... method=MetalogFitMethod.Feasible, ... num_terms=5, lower_bound=0.0, upper_bound=0.0 ... ) >>> m = fit(data, params) # Returns a Metalog >>> isinstance(m, MetalogBase) True
See also
metalog_jax.metalog.Metalog: Standard metalog distribution class. metalog_jax.metalog.SPTMetalog: SPT metalog distribution class. metalog_jax.base.parameters.MetalogParameters: Configuration for Metalog. metalog_jax.base.parameters.SPTMetalogParameters: Configuration for SPTMetalog.
- Parameters:
metalog_params (
SPTMetalogParameters|MetalogParameters)
- __post_init__()[source]¶
Coerce a list or tuple of coefficients to a float JAX array.
Every method assumes
ais an array (jnp.result_type,.shape,.tolist(),@), so a plain sequence such as hand-copied coefficients would otherwise fail deep inside JAX (issue #9). Only lists and tuples are converted: pytree unflattening underjit/vmap/tree.mapre-runs__init__with tracers,ShapeDtypeStructorNoneasa, and those must pass through untouched.- Return type:
- __eq__(other)[source]¶
Check equality between two metalog distribution instances.
Compares the metalog parameters and coefficient arrays to determine equality. For JAX arrays, uses jnp.allclose for numerical comparison.
- __str__()[source]¶
Show the model and its quantile function on one line.
The coefficients and bounds have 6 significant digits. A traced or batched model shows a one-line summary instead.
- Returns:
- The one-line equation, such as
Metalog(3 terms, OLS, unbounded): Q(y) = 1.5 - 0.25 ln(y/(1-y)) ..., or a one-line summary if the model is traced or batched.
- Return type:
- __repr__()[source]¶
Show the model and its quantile function on one line, at full precision.
The same line as
str, with each number written as Python’s shortestrepr(float), so the coefficients parse back toaexactly. Each subclass defines__repr__to call this one, becauseflax.struct.dataclasswould otherwise generate its own.- Returns:
- The one-line equation, or a one-line summary if the model is traced
or batched.
- Return type:
- print(*, header=True, q=True, m=True, verbose=False, precision=6, file=None)[source]¶
Print the quantile function, one term per line.
For a bounded or semi-bounded model, Q(y) maps M(y) back to the support, and the options choose which lines appear. An unbounded model’s Q(y) is M(y), so its terms print once, labelled Q(y) (or M(y) when
qis False). A traced or batched model prints a one-line summary instead. The builtinprint(m)still printsstr(m).- Parameters:
header (
bool) – Print a first line describing the model, such asMetalog: 5 terms, OLS, bounded on (0, 15), and indent the equations under it. Without it, the equations start at the left margin.q (
bool) – Print the quantile function Q(y).m (
bool) – Print M(y), one term per line. For an unbounded model,m=Falsekeeps the terms on Q(y)’s line.verbose (
bool) – Write Q(y) with M(y)’s terms in place of M(y), so it reads on its own. A bounded Q(y) uses M(y) twice, so the terms appear twice.precision (
int) – Significant digits of the coefficients and bounds.file (
TextIO|None) – Where to write, such as an open file orio.StringIO; standard output by default.
- Raises:
ValueError – If
qandmare both False,verboseis True whileqis False, orprecisionisn’t a positive integer.- Return type:
- property boundedness: MetalogBoundedness¶
Get the boundedness type of the metalog distribution.
- Returns:
- The boundedness type (UNBOUNDED, STRICTLY_LOWER_BOUND,
STRICTLY_UPPER_BOUND, or BOUNDED).
- Return type:
- property lower_bound: Union[float, int]¶
Get the lower bound of the metalog distribution.
- Returns:
- The lower bound value. Only meaningful when boundedness
is STRICTLY_LOWER_BOUND or BOUNDED.
- Return type:
chex.Scalar
- property upper_bound: Union[float, int]¶
Get the upper bound of the metalog distribution.
- Returns:
- The upper bound value. Only meaningful when boundedness
is STRICTLY_UPPER_BOUND or BOUNDED.
- Return type:
chex.Scalar
- property num_terms: int¶
Get the number of terms in the metalog expansion.
- Returns:
The number of terms (coefficients) in the metalog quantile function.
- Return type:
- property method: MetalogFitMethod¶
Get the fit method used for the metalog distribution.
- Returns:
The regression method (OLS or Lasso).
- Return type:
- assert_fitted()[source]¶
Validate that the metalog distribution has been fitted and is feasible.
Performs two validation checks:
Verifies that coefficients exist (distribution has been fit).
Computes the PDF at standard quantile points to verify feasibility, which internally calls
assert_fit_validto ensure all PDF values are strictly positive.
This method is vmap-compatible through the use of
jax.experimental.checkifyin the underlying PDF validation.- Raises:
NotFittedError – If the distribution has not been fitted (no coefficients).
checkify.JaxRuntimeError – If the fitted distribution produces non-positive PDF values, indicating an infeasible fit.
- Return type:
- static assert_fit_valid(m)[source]¶
Validate that PDF values are strictly positive.
Checks that all probability density values are positive, which is a necessary condition for a valid probability distribution. A metalog fit that produces non-positive PDF values is theoretically infeasible.
This method is vmap-compatible by using
jax.experimental.checkifyfor runtime error checking inside JAX transforms.
- static strictly_lower_bound_quantile_transform(x, lower_bound)[source]¶
Apply logarithmic transformation for strictly lower-bounded quantiles.
Transforms quantiles from (lower_bound, inf) to (-inf, inf) using log(x - L).
- Parameters:
- Returns:
Transformed quantile values on the unbounded scale.
- Return type:
- static strictly_upper_bound_quantile_transform(x, upper_bound)[source]¶
Apply negative logarithmic transformation for strictly upper-bounded quantiles.
Transforms quantiles from (-inf, upper_bound) to (-inf, inf) using -log(U - x).
- Parameters:
- Returns:
Transformed quantile values on the unbounded scale.
- Return type:
- static bounded_quantile_transform(x, lower_bound, upper_bound)[source]¶
Apply logit-like transformation for fully bounded quantiles.
Transforms quantiles from (lower_bound, upper_bound) to (-inf, inf) using log((x - L) / (U - x)).
- Parameters:
- Returns:
Transformed quantile values on the unbounded scale.
- Return type:
- static quantile_transform(x, boundedness, lower_bound, upper_bound)[source]¶
Transform quantiles to the scale on which the metalog is unbounded.
Applies the transform that
boundednessselects: none for UNBOUNDED, then strictly_lower_bound_quantile_transform, strictly_upper_bound_quantile_transform or bounded_quantile_transform.boundednessis a static parameter field, so this dispatches in Python and works insidejax.jitandjax.vmap.- Parameters:
x (
Union[Array,ndarray,bool,number,float,int]) – Quantile values inside the distribution’s support.boundedness (
MetalogBoundedness) – Boundedness type of the distribution.lower_bound (
Union[float,int]) – Lower bound, used by STRICTLY_LOWER_BOUND and BOUNDED.upper_bound (
Union[float,int]) – Upper bound, used by STRICTLY_UPPER_BOUND and BOUNDED.
- Returns:
Transformed quantile values on the unbounded scale.
- Return type:
- static inverse_quantile_transform(m, boundedness, lower_bound, upper_bound)[source]¶
Map values of the unbounded metalog back to the distribution’s support.
The inverse of quantile_transform (Keelin 2016, Section 4):
L + e^Mfor STRICTLY_LOWER_BOUND,U - e^(-M)for STRICTLY_UPPER_BOUND and(L + U e^M) / (1 + e^M)for BOUNDED.- Parameters:
m (
Union[Array,ndarray,bool,number,float,int]) – Values of the unbounded metalog quantile function.boundedness (
MetalogBoundedness) – Boundedness type of the distribution.lower_bound (
Union[float,int]) – Lower bound, used by STRICTLY_LOWER_BOUND and BOUNDED.upper_bound (
Union[float,int]) – Upper bound, used by STRICTLY_UPPER_BOUND and BOUNDED.
- Returns:
Quantile values in the distribution’s support.
- Return type:
- static product_term(x)[source]¶
Compute the product term x * (1 - x).
This term appears in the derivative of the logit function and is used in PDF computation.
- static get_quantiles(data, metalog_params)[source]¶
Extract and transform quantiles from validated input data.
Applies the appropriate boundedness transformation to convert quantiles from their natural domain to the unbounded (-inf, inf) scale used internally by the metalog.
- Parameters:
data (
MetalogInputData) – Validated input data containing quantile values (x) and probability levels (y).metalog_params (
MetalogParameters) – Configuration parameters specifying boundedness type and bound values.
- Returns:
Transformed quantile values on the unbounded scale.
- Return type:
- Raises:
chex.AssertionError – If the number of data points is less than num_terms.
- static get_target(data, metalog_params)[source]¶
Construct the design matrix from input data and parameters.
Convenience wrapper around _get_target that extracts the probability levels and num_terms from the input objects.
- Parameters:
data (
MetalogInputData) – Validated input data containing probability levels (y).metalog_params (
MetalogParameters) – Configuration parameters specifying num_terms.
- Returns:
Design matrix of shape (m, num_terms).
- Return type:
- ppf(x)[source]¶
Compute the percent point function (quantile function) of the distribution.
The PPF is the inverse of the CDF: for a probability p, ppf(p) returns the value x such that P(X <= x) = p.
- Parameters:
x (
Union[Array,ndarray,bool,number,float,int]) – Probability values in (0, 1) at which to evaluate the quantile function. Can be a scalar or array.- Returns:
- Quantile values corresponding to the input probabilities.
Same shape as input.
- Return type:
- Raises:
ValueError – If x contains values outside (0, 1).
NotFittedError – If the distribution has not been fitted.
- pdf(x)[source]¶
Compute the probability density function of the metalog distribution.
The PDF is computed as the reciprocal of the derivative of the quantile function (PPF), with appropriate transformations for bounded distributions.
- Parameters:
x (
Union[Array,ndarray,bool,number,float,int]) – Probability values in (0, 1) at which to evaluate the PDF. Can be a scalar or array.- Returns:
Probability density values. Same shape as input.
- Return type:
- Raises:
ValueError – If x contains values outside (0, 1).
NotFittedError – If the distribution has not been fitted.
checkify.JaxRuntimeError – If the PDF contains non-positive values, indicating an infeasible metalog fit.
- cdf(q)[source]¶
Compute the cumulative distribution function.
Returns P(X <= q) for the fitted metalog distribution. Uses numerical inversion of the quantile function via nearest-neighbor lookup.
- rvs(rv_params)[source]¶
Generate random variates from the fitted metalog distribution.
Samples are generated by drawing uniform random values and applying the quantile function (inverse transform sampling). With HDRPRNGParameters, draw i uses HDR trial
trial + i, so the defaulttrial=1draws trials 1 to size, andtrial=size + 1continues the same stream.- Parameters:
rv_params (
MetalogRandomVariableParameters) – Parameters controlling random variate generation, including PRNG configuration (HDR or JAX-based) and sample size.- Returns:
Random samples from the distribution, shape (size,).
- Return type:
- Raises:
chex.AssertionError – If size is not positive or exceeds max_draws.
checkify.JaxRuntimeError – If the PDF is infeasible (non-positive values).
- sf(x)[source]¶
Compute the survival function (complement of CDF).
The survival function is S(x) = 1 - F(x) = P(X > x).
- isf(q)[source]¶
Compute the inverse survival function (inverse complementary CDF).
Returns the value x such that P(X > x) = q, equivalent to ppf(1 - q).
- property mean: Union[float, int]¶
Compute the mean (expected value) of the metalog distribution.
Exact for unbounded metalogs, via the closed-form integrals of Baucells, Chrisman, Keelin and Xu (2025) (Lemma 1, Proposition 3). Bounded and semi-bounded metalogs have no closed form after the log/logit back-transform, so their mean is estimated by Monte Carlo sampling with 20,000 draws.
- Returns:
The mean of the distribution.
- Return type:
chex.Scalar
References
Baucells, M., Chrisman, L., Keelin, T. W., & Xu, Z. S. (2025). On the Properties of the Metalog Distribution. Darden Business School Working Paper No. 5279416. https://doi.org/10.2139/ssrn.5279416
- property median: Union[float, int]¶
Compute the median of the metalog distribution.
The median is the 50th percentile, computed exactly via ppf(0.5).
- Returns:
The median of the distribution.
- Return type:
chex.Scalar
- property var: Union[float, int]¶
Compute the variance of the metalog distribution.
Exact for unbounded metalogs (see
mean); estimated by Monte Carlo sampling with 20,000 draws for bounded and semi-bounded metalogs.- Returns:
The variance of the distribution.
- Return type:
chex.Scalar
- property std: Union[float, int]¶
Compute the standard deviation of the metalog distribution.
Exact for unbounded metalogs (see
mean); estimated by Monte Carlo sampling with 20,000 draws for bounded and semi-bounded metalogs.- Returns:
The standard deviation of the distribution.
- Return type:
chex.Scalar
- property skewness: Union[float, int]¶
Compute the skewness of the metalog distribution.
Skewness is the third standardized central moment, E[(X - mean)^3] / std^3. Exact for unbounded metalogs (see
mean); estimated from 20,000 draws for bounded and semi-bounded metalogs.- Returns:
The skewness of the distribution.
- Return type:
chex.Scalar
- property kurtosis: Union[float, int]¶
Compute the kurtosis of the metalog distribution.
Pearson kurtosis, E[(X - mean)^4] / var^2: 3 for a normal distribution and 4.2 for the logistic. Subtract 3 for excess kurtosis (scipy’s default convention). Exact for unbounded metalogs (see
mean); estimated from 20,000 draws for bounded and semi-bounded metalogs.- Returns:
The kurtosis of the distribution.
- Return type:
chex.Scalar
- property mode: Union[float, int]¶
Compute the mode (most likely value) of the fitted metalog distribution.
For unbounded metalogs the mode is exact: the highest-density root of
M'', found by Algorithm 1 of Baucells, Chrisman, Keelin and Xu (2025). This also finds modes that a grid misses, such as the binding modes of a Feasible fit, which can sit at y < 0.001. Bounded and semi-bounded metalogs (whose back-transform moves the modes) and metalogs without an interior mode use the maximum of the PDF over a fine grid of probabilities.- Returns:
The mode of the distribution.
- Return type:
chex.Scalar
- plot(plot_option)[source]¶
Visualize the fitted metalog distribution using interactive Plotly plots.
Creates an interactive Plotly figure showing the selected distribution function (PDF, CDF, or survival function).
- Parameters:
plot_option (
MetalogPlotOptions) – Which function to plot. One of MetalogPlotOptions.PDF, MetalogPlotOptions.CDF, or MetalogPlotOptions.SF.- Returns:
The Plotly figure object.
- Return type:
Figure- Raises:
NotFittedError – If the distribution has not been fitted.
- save(path)[source]¶
Save the fitted metalog distribution to a JSON file.
Writes the JSON that
dumpsreturns. Read it back withloadon the same class:Metalog.loadorSPTMetalog.load.
- dumps()[source]¶
Serialize the fitted metalog distribution to a JSON string.
The JSON holds the coefficients
aand the parameters’to_dict(): boundedness, fit method, bounds and number of terms for a Metalog; boundedness, bounds and alpha for an SPTMetalog.- Returns:
JSON string representation of the distribution.
- Return type:
- classmethod load(path)[source]¶
Load a fitted metalog distribution from a JSON file.
- Parameters:
path (
Path) – File path to the JSON file containing the serialized distribution.- Returns:
- A new instance of the metalog class with loaded
coefficients and parameters.
- Return type:
T_MetalogBase- Raises:
KeyError – If the JSON has no
aormetalog_params.ValueError – If the parameters were saved by the other metalog class (for example, an SPTMetalog’s JSON loaded with Metalog.load), or hold an unknown enum value.
- classmethod loads(json_string)[source]¶
Load a fitted metalog distribution from a JSON string.
- Parameters:
json_string (
str) – JSON string containing the serialized distribution.- Returns:
- A new instance of the metalog class with loaded
coefficients and parameters.
- Return type:
T_MetalogBase- Raises:
KeyError – If the JSON has no
aormetalog_params.ValueError – If the parameters were saved by the other metalog class (for example, an SPTMetalog’s JSON loaded with Metalog.loads), or hold an unknown enum value.
- q(x)[source]¶
Quantile function alias for R metalog package compatibility.
This method provides compatibility with the R metalog package’s qmetalog function naming convention. Equivalent to ppf().
- Parameters:
x (
Union[Array,ndarray,bool,number,float,int]) – Probability values in (0, 1) at which to evaluate the quantile function.- Returns:
Quantile values corresponding to the input probabilities.
- Return type:
- Raises:
ValueError – If x contains values outside (0, 1).
NotFittedError – If the distribution has not been fitted.
- p(q)[source]¶
Probability function alias for R metalog package compatibility.
This method provides compatibility with the R metalog package’s pmetalog function naming convention. Equivalent to cdf().
- d(q)[source]¶
Density function alias for R metalog package compatibility.
This method provides compatibility with the R metalog package’s dmetalog function naming convention. Computes pdf(cdf(q)).
- __init__(metalog_params, a)¶
- Parameters:
metalog_params (
SPTMetalogParameters|MetalogParameters)
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
Display¶
Text and LaTeX display of fitted metalog distributions.
These functions write out a model’s quantile function with its coefficients. The
model methods call them: str(m) and repr(m) use format_line, Jupyter’s
_repr_latex_ uses format_latex, and m.print() uses format_block.
The terms follow the library’s Keelin (2016, Definition 1) order, the one
MetalogBase._ppf evaluates: 1, ln(y/(1-y)), (y - 0.5) ln(y/(1-y)), (y - 0.5),
then alternating powers of (y - 0.5), with and without the logit. Bounded and
semi-bounded models map M(y) back to their support (Keelin 2016, Section 4):
L + exp(M(y)), U - exp(-M(y)) and (L + U exp(M(y))) / (1 + exp(M(y))).
Formatting never raises on a model, because JAX, flax and debuggers call repr on
traced and placeholder values. A model whose coefficients or bounds aren’t concrete
(under jax.jit, jax.vmap or jax.eval_shape), or that holds a batch of
models, prints a one-line summary instead of its equation, and format_latex returns
None for it, which tells Jupyter to fall back to repr.
See also
- metalog_jax.base.core.MetalogBase: The
__str__,__repr__, _repr_latex_andprintmethods.
- metalog_jax.base.display.format_line(model, precision=6)[source]¶
Write a model and its equation on one line.
str(m)isformat_line(m), andrepr(m)isformat_line(m, precision=None).- Parameters:
- Returns:
- The one-line equation, such as
Metalog(3 terms, OLS, lower bound 0): Q(y) = 0 + exp(M(y)), M(y) = ..., or a one-line summary if the model is traced or batched.
- Return type:
- metalog_jax.base.display.format_latex(model, precision=6)[source]¶
Write a model’s equation in LaTeX, for Jupyter’s
_repr_latex_.Up to four terms stay on one line, between
$signs. A longer model is analignedenvironment between$$signs, with Q(y) on its own line and M(y)’s terms wrapped after every four, because MathJax doesn’t break long equations.\ell(y)stands for\ln(y / (1 - y)).
- metalog_jax.base.display.format_block(model, *, header=True, q=True, m=True, verbose=False, precision=6)[source]¶
Write a model’s equation one term per line, as
m.print()prints it.- Parameters:
model (
Any) – A Metalog or SPTMetalog.header (
bool) – Start with a line describing the model, such asMetalog: 5 terms, OLS, bounded on (0, 15), and indent the equations under it.q (
bool) – Include the quantile function Q(y).m (
bool) – Include M(y), one term per line. An unbounded model’s Q(y) is M(y), so its terms appear once, labelled Q(y), or M(y) whenqis False; withm=Falsethey stay on Q(y)’s line.verbose (
bool) – Write Q(y) with M(y)’s terms in place of M(y), so it can be read on its own.precision (
int) – Significant digits of the coefficients and bounds.
- Returns:
- The lines, without a trailing newline, or a one-line summary if the
model is traced or batched.
- Return type:
- Raises:
ValueError – If
qandmare both False,verboseis True whileqis False, orprecisionisn’t a positive integer.
Input Data¶
Data container classes for the Metalog JAX library.
This module provides data container classes for metalog input data:
- Classes:
- MetalogBaseData: Base container for metalog input data without validation.
Intended for internal use within JAX-traced functions.
- MetalogInputData: Validated container for metalog input data, created
exclusively via the from_values() factory method.
See also
metalog_jax.base.core: MetalogBase class. metalog_jax.base.parameters: Parameter configuration classes. metalog_jax.metalog.fit: Function that uses MetalogInputData for fitting.
- class metalog_jax.base.data.MetalogBaseData(x, y, precomputed_quantiles)[source]¶
Bases:
objectBase container for metalog input data without validation.
This is a minimal dataclass that holds the core data required for metalog fitting without performing any validation or transformation. It serves as the parent class for MetalogInputData and is primarily used for internal operations where validation has already been performed or needs to be bypassed, such as within JAX-traced functions like vmap.
IMPORTANT: This class is intended for internal library use and advanced users only. Most users should use MetalogInputData.from_values() instead, which provides comprehensive validation and proper error handling.
Use MetalogBaseData directly only when:
Working inside JAX-traced functions (vmap, jit, etc.) where validation functions would fail due to tracer boolean conversion issues
Data has already been validated externally
Building custom pipelines that require manual control over validation
- Variables:
x (
Union[Array,ndarray,bool,number,float,int]) – Quantile values array corresponding to the probability levels in y. This should be a 1D JAX array of numeric values. When precomputed_quantiles=False, these are typically computed via jnp.quantile(samples, y). When precomputed_quantiles=True, these are user-provided quantile values.y (
Union[Array,ndarray,bool,number,float,int]) – Probability levels array in the open interval (0, 1), sorted in strictly ascending order. This should be a 1D JAX array with the same length as x, representing the cumulative probabilities at which the quantiles in x are evaluated.precomputed_quantiles (
bool) – Boolean flag indicating whether x contains pre-computed quantiles (True) or was originally raw sample data (False). This is marked as pytree_node=False to exclude it from JAX transformations, as it’s metadata rather than data to be transformed.
Note
This class uses Flax’s struct.dataclass decorator to make instances immutable and compatible with JAX transformations (jit, vmap, grad, etc.). The precomputed_quantiles field is excluded from the pytree structure via pytree_node=False since it’s metadata.
See also
MetalogInputData: Subclass with validation via from_values() factory method. metalog_jax.base.parameters.MetalogParameters: Configuration for fitting. metalog_jax.metalog.fit: Function that processes input data for fitting.
- Parameters:
- __init__(x, y, precomputed_quantiles)¶
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
- class metalog_jax.base.data.MetalogInputData(x, y, precomputed_quantiles, _from_factory=False)[source]¶
Bases:
MetalogBaseDataContainer for input data used to fit metalog distributions.
This dataclass encapsulates the input data for metalog fitting, supporting two modes: (1) fitting from pre-computed quantiles at specified probability levels, or (2) fitting from raw sample data where quantiles are computed automatically.
The mode is controlled by the precomputed_quantiles flag, which determines how the x array should be interpreted and processed during fitting.
IMPORTANT: Instances MUST be created using the from_values() classmethod. Direct instantiation via __init__() is prevented by a __post_init__ validation that raises TypeError if the instance was not created through from_values().
- Variables:
x (
Union[Array,ndarray,bool,number,float,int]) – Input data array. The interpretation depends on precomputed_quantiles: - If precomputed_quantiles=True: Array of quantile values - If precomputed_quantiles=False: Array of raw sample datay (
Union[Array,ndarray,bool,number,float,int]) – Array of probability levels in the open interval (0, 1), sorted in ascending order.precomputed_quantiles (
bool) – Flag indicating the interpretation of x.
See also
MetalogBaseData: Parent class without validation (for advanced use). from_values: Required factory method for creating validated instances. metalog_jax.metalog.fit: Function that uses MetalogInputData for fitting.
- Parameters:
- classmethod from_values(x, y, precomputed_quantiles)[source]¶
Create a validated MetalogInputData instance with comprehensive input checks.
This is the ONLY way to create valid MetalogInputData instances.
- Parameters:
- Return type:
T_MetalogInputData- Returns:
A validated MetalogInputData instance ready for use in metalog fitting.
- Raises:
AssertionError – If any validation check fails.
- __init__(x, y, precomputed_quantiles, _from_factory=False)¶
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
Enumerations¶
Enumeration types for the Metalog JAX library.
This module provides enumeration types for configuring metalog distributions:
- Classes:
CustomIntEnum: Base class for integer enumerations with enhanced value lookup. MetalogBoundedness: Boundedness types (UNBOUNDED, STRICTLY_LOWER_BOUND, …). MetalogFitMethod: Fit methods (Feasible, OLS, Lasso). MetalogPlotOptions: Enumeration of plot types (PDF, CDF, SF).
See also
metalog_jax.base.parameters: Parameter configuration classes that use these enums. metalog_jax.regression: OLS and Lasso implementations. metalog_jax.feasibility: Feasible implementation (Baucells et al. 2025).
- class metalog_jax.base.enums.CustomIntEnum(new_class_name, /, names, *, module=None, qualname=None, type=None, start=1, boundary=None)[source]¶
Bases:
IntEnumBase class for custom integer enumerations with enhanced value lookup.
Extends Python’s IntEnum to provide additional utility methods for working with enumeration values. This class serves as a base for all custom integer enumerations in the metalog JAX library, providing consistent behavior for value-to-member conversion and validation.
The class maintains all IntEnum functionality (enum members are instances of int and can be used in arithmetic operations) while adding convenient class methods for value lookup and conversion.
Note
This is an abstract base class. Use specific subclasses like MetalogBoundedness or MetalogFitMethod for actual enum definitions.
- classmethod from_value(value)[source]¶
Convert an integer value to the corresponding enum member.
Searches through all members of the enumeration and returns the member whose value matches the input. This provides a safe way to reconstruct enum members from integer values, such as when loading from JSON files or deserializing data.
- Parameters:
value (
int) – Integer value to convert to an enum member. Must match the value of an existing enum member exactly.- Returns:
The enum member whose value matches the input value.
- Raises:
ValueError – If no enum member with the specified value exists.
Example
>>> boundedness = MetalogBoundedness.from_value(1) >>> boundedness == MetalogBoundedness.UNBOUNDED True >>> MetalogBoundedness.from_value(99) Traceback (most recent call last): ... ValueError: No enum member with value 99
- class metalog_jax.base.enums.MetalogBoundedness(*values)[source]¶
Bases:
CustomIntEnumEnumeration of boundedness types for metalog distributions.
Defines the domain constraints for metalog probability distributions, specifying whether the distribution has lower bounds, upper bounds, both, or neither.
- Variables:
UNBOUNDED – Distribution with no bounds (support on entire real line).
STRICTLY_LOWER_BOUND – Distribution with only a lower bound.
STRICTLY_UPPER_BOUND – Distribution with only an upper bound.
BOUNDED – Distribution with both lower and upper bounds.
- class metalog_jax.base.enums.MetalogFitMethod(*values)[source]¶
Bases:
CustomIntEnumEnumeration of regression methods for fitting metalog distributions.
Defines the available regression techniques that can be used to estimate metalog distribution coefficients from data.
- Variables:
OLS – Ordinary Least Squares regression (no regularization, closed-form solution). Implementation: metalog_jax.regression.ols.fit_ordinary_least_squares
Lasso – LASSO regression (L1 regularization via proximal gradient descent). Implementation: metalog_jax.regression.lasso.fit_lasso
Feasible – Best feasible least-squares fit a* (Baucells, Chrisman, Keelin and Xu, 2025): the closest coefficients whose density is non-negative everywhere. Recommended default; equals OLS whenever the OLS fit is already valid. Implementation: metalog_jax.feasibility.a_star.fit_feasible
See also
metalog_jax.regression: Module containing all regression implementations. metalog_jax.regression.ols: OLS regression implementation. metalog_jax.regression.lasso: LASSO regression implementation. metalog_jax.feasibility: Best feasible fit implementation.
References
Baucells, M., Chrisman, L., Keelin, T. W., & Xu, Z. S. (2025). On the Properties of the Metalog Distribution. Darden Business School Working Paper No. 5279416. https://doi.org/10.2139/ssrn.5279416
- class metalog_jax.base.enums.MetalogPlotOptions(*values)[source]¶
Bases:
IntEnumEnumeration of available plot types for metalog distributions.
Defines the types of statistical plots that can be generated for visualizing fitted metalog distributions. Each option corresponds to a different view of the probability distribution.
- Variables:
PDF – Probability Density Function plot showing the probability density across the distribution’s support.
CDF – Cumulative Distribution Function plot showing the cumulative probability from the lower bound to each point.
SF – Survival Function plot showing the survival function from the lower bound to each point.
Parameters¶
Parameter configuration classes for the Metalog JAX library.
This module provides parameter configuration classes for metalog distributions:
- Classes:
MetalogRandomVariableParameters: Configuration for random variable sampling. MetalogParametersBase: Base class for metalog distribution parameters. MetalogParameters: Configuration for standard regression-based metalog fitting. SPTMetalogParameters: Configuration for Symmetric Percentile Triplet metalog fitting.
See also
metalog_jax.base.enums: Enumeration types (MetalogBoundedness, MetalogFitMethod). metalog_jax.base.core: MetalogBase class that uses these parameters. metalog_jax.metalog: Fitting functions that accept these parameter configurations.
- class metalog_jax.base.parameters.MetalogRandomVariableParameters(prng_params, size, max_draws=100000)[source]¶
Bases:
objectConfiguration parameters for drawing rvs from fitted metalog distribution.
sizeandmax_drawsset array shapes, so they are static fields: they belong to the pytree structure rather than its leaves, and the parameters can be passed tojax.jit. The PRNG parameters are leaves, sojax.vmapcan run over seeds or HDR trials.- Variables:
prng_params (
JaxUniformDistributionParameters|HDRPRNGParameters) – Params for the psuedorandom number generator.size (
int) – The number of random variables to draw. Must be strictly positive. Static.max_draws (
int) – Maximum number of draws for rejection sampling. Static.
- Parameters:
prng_params (
JaxUniformDistributionParameters|HDRPRNGParameters)size (
int)max_draws (
int)
- __post_init__()[source]¶
Post init data validation.
- Raises:
AssertionError – If
sizeis not a positive scalar.
- __init__(prng_params, size, max_draws=100000)¶
- Parameters:
prng_params (
JaxUniformDistributionParameters|HDRPRNGParameters)size (
int)max_draws (
int)
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
- class metalog_jax.base.parameters.MetalogParametersBase(boundedness, lower_bound, upper_bound)[source]¶
Bases:
objectBase class for metalog distribution parameter configurations.
This abstract base class defines the common parameters shared by all metalog distribution types, specifically the boundedness constraints and boundary values that determine the support of the distribution.
Fields that select code paths or array shapes (
boundednesshere, andmethodandnum_termsin MetalogParameters) are static: they belong to the pytree structure rather than its leaves. So a model or parameters object can be passed tojax.jit, which compiles once per combination of static values, and models stacked forjax.vmapmust share them. The bounds are ordinary leaves and can be traced.- Variables:
- Parameters:
- __init__(boundedness, lower_bound, upper_bound)¶
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
- class metalog_jax.base.parameters.MetalogParameters(boundedness, lower_bound, upper_bound, method, num_terms)[source]¶
Bases:
MetalogParametersBaseConfiguration parameters for standard regression-based metalog distributions.
Extends MetalogParametersBase with regression-specific parameters for fitting metalog distributions via regression methods including OLS and Lasso.
- Variables:
boundedness (
MetalogBoundedness) – Inherited. Type of domain constraint for the distribution. Defined in metalog_jax.base.enums.MetalogBoundedness.lower_bound (
Union[float,int]) – Inherited. Lower boundary value.upper_bound (
Union[float,int]) – Inherited. Upper boundary value.method – Regression method used for fitting. Static. One of: - MetalogFitMethod.OLS: metalog_jax.regression.ols - MetalogFitMethod.Lasso: metalog_jax.regression.lasso
num_terms (
int) – Number of terms in the metalog series expansion. Static.
See also
MetalogParametersBase: Parent class with boundedness and bound attributes. metalog_jax.base.enums.MetalogFitMethod: Enumeration of regression methods. metalog_jax.metalog.fit: Function that uses MetalogParameters for fitting. metalog_jax.regression: Module containing regression implementations.
- Parameters:
boundedness (
MetalogBoundedness)method (
MetalogFitMethod)num_terms (
int)
- to_dict()[source]¶
Return the parameters as the JSON-ready dict that Metalog.dumps writes.
- Returns:
boundednessandmethodas integers, the bounds as floats,and
num_termsas an integer.
- Return type:
- classmethod from_dict(params)[source]¶
Rebuild parameters from a dict written by to_dict.
- Parameters:
params (
dict) – Themetalog_paramsdict of a saved Metalog.- Returns:
- The parameters, with enum members for
boundedness and
method.
- The parameters, with enum members for
- Return type:
- Raises:
ValueError – If the keys aren’t exactly those that to_dict writes (as for an SPTMetalog’s parameters), or an enum value is unknown.
- __init__(boundedness, lower_bound, upper_bound, method, num_terms)¶
- Parameters:
boundedness (
MetalogBoundedness)method (
MetalogFitMethod)num_terms (
int)
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
- class metalog_jax.base.parameters.SPTMetalogParameters(boundedness, lower_bound, upper_bound, alpha)[source]¶
Bases:
MetalogParametersBaseConfiguration parameters for Symmetric Percentile Triplet (SPT) metalog distributions.
Extends MetalogParametersBase with the alpha parameter for SPT metalog fitting, which uses exactly 3 terms fitted from three symmetric quantiles.
- Variables:
boundedness (
MetalogBoundedness) – Inherited. Type of domain constraint for the distribution.lower_bound (
Union[float,int]) – Inherited. Lower boundary value.upper_bound (
Union[float,int]) – Inherited. Upper boundary value.alpha (
float) – The lower percentile parameter for SPT fitting. Must be in (0, 0.5).
- Parameters:
- to_dict()[source]¶
Return the parameters as the JSON-ready dict that SPTMetalog.dumps writes.
- Returns:
boundednessas an integer, and the bounds andalphaasfloats.
- Return type:
- classmethod from_dict(params)[source]¶
Rebuild parameters from a dict written by to_dict.
- Parameters:
params (
dict) – Themetalog_paramsdict of a saved SPTMetalog.- Returns:
- The parameters, with an enum member for
boundedness.
- Return type:
- Raises:
ValueError – If the keys aren’t exactly those that to_dict writes (as for a Metalog’s parameters), or the boundedness value is unknown.
- __init__(boundedness, lower_bound, upper_bound, alpha)¶
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
Regression¶
Base Classes¶
Base classes for regression models and parameters.
This module provides the abstract base classes for regression models and their parameters used in metalog distribution fitting.
- Classes:
- RegularizedParameters: Base class for regularized regression hyperparameters.
Subclasses: LassoParameters
- RegressionModel: Base class for trained regression model weights.
Subclasses: OLSModel, LassoModel
- Functions:
has_full_column_rank: Whether a design matrix has full column rank. assert_full_column_rank: Raise when least squares has no unique solution.
See also
metalog_jax.regression.ols: OLS regression implementation. metalog_jax.regression.lasso: LASSO regression implementation.
- class metalog_jax.regression.base.RegularizedParameters[source]¶
Bases:
objectBase class for regularized regression hyperparameters.
This abstract base class serves as the parent for all regularized regression parameter configurations in the metalog JAX library. It provides a common type hierarchy for regression methods that incorporate regularization penalties (L1, L2, or both) to prevent overfitting and improve generalization.
Regularization adds penalty terms to the regression objective function to constrain model complexity. This base class enables polymorphic handling of different regularization strategies while maintaining type safety and consistency across the library.
The class uses Flax’s struct.dataclass decorator to ensure immutability and compatibility with JAX transformations (jit, vmap, grad, etc.).
- Subclasses:
- LassoParameters: L1 regularization parameters (LASSO regression).
Defined in metalog_jax.regression.lasso. Penalizes the absolute magnitude of coefficients: λ||w||₁
- Design Pattern:
This class follows the Template Method pattern, providing a common interface while allowing subclasses to define specific regularization configurations. It enables functions to accept any regularized regression parameters through polymorphic type hints.
Example
Using the base class for polymorphic type hints:
>>> from typing import Union >>> from metalog_jax.regression.base import RegularizedParameters >>> from metalog_jax.regression.lasso import LassoParameters >>> >>> def validate_regularization(params: RegularizedParameters): ... '''Accept any regularized regression parameters.''' ... if isinstance(params, LassoParameters): ... print(f"Lasso with L1={params.lam}") >>> >>> lasso_params = LassoParameters( ... lam=1.0, ... learning_rate=0.01, ... num_iters=500, ... tol=1e-6, ... momentum=0.9 ... ) >>> validate_regularization(lasso_params) # Valid Lasso with L1=1.0
Creating subclass instances:
>>> # Lasso regression (L1 only) >>> from metalog_jax.regression.lasso import LassoParameters >>> lasso = LassoParameters( ... lam=0.5, ... learning_rate=0.01, ... num_iters=500, ... tol=1e-6, ... momentum=0.9 ... ) >>> isinstance(lasso, RegularizedParameters) True
Note
This is an abstract base class with no attributes or methods
Direct instantiation is possible but not meaningful (use subclasses)
All instances are immutable due to struct.dataclass decorator
Subclasses must define their own specific regularization parameters
See also
metalog_jax.regression.lasso.LassoParameters: LASSO (L1) regression parameters. metalog_jax.regression.lasso.fit_lasso: Function that uses LassoParameters.
References
Hastie, T., Tibshirani, R., & Friedman, J. (2009). The Elements of Statistical Learning: Data Mining, Inference, and Prediction (2nd ed.). Springer. Chapter 3: Linear Methods for Regression.
- __init__()¶
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
- class metalog_jax.regression.base.RegressionModel(weights)[source]¶
Bases:
objectStructure for trained Regression model weights.
- Variables:
weights (
Union[Array,ndarray,bool,number]) – Coefficient vector of shape (n_features,).- Parameters:
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
- metalog_jax.regression.base.has_full_column_rank(X)[source]¶
Whether
Xof shape (n, k) has rank k, using numpy’s default SVD tolerance.
- metalog_jax.regression.base.assert_full_column_rank(X)[source]¶
Raise when
Xis rank deficient, so least squares has no unique solution.Uses
checkifyso it also works undervmap(e.g.fit_grid_datasets).- Parameters:
X (
Union[Array,ndarray,bool,number]) – Design matrix of shape (n_samples, n_features).- Raises:
checkify.JaxRuntimeError – If
Xdoes not have full column rank.- Return type:
References
Baucells, M., Chrisman, L., Keelin, T. W., & Xu, Z. S. (2025). On the Properties of the Metalog Distribution. Darden Business School Working Paper No. 5279416. https://doi.org/10.2139/ssrn.5279416 (Proposition 2).
Ordinary Least Squares¶
Ordinary Least Squares (OLS) regression implementation.
This module provides OLS regression for fitting metalog distributions when no regularization is needed.
- Classes:
OLSModel: Structure for trained OLS model weights.
- Functions:
fit_ordinary_least_squares: Fit an OLS regression model. predict_ordinary_least_squares: Make predictions using a fitted OLS model.
This module is used when MetalogFitMethod.OLS is specified in MetalogParameters.
See also
metalog_jax.regression.lasso: LASSO (L1 regularization). metalog_jax.metalog.fit: High-level fitting function that dispatches to OLS.
- class metalog_jax.regression.ols.OLSModel(weights)[source]¶
Bases:
RegressionModelStructure for trained Ordinary Least Squares model weights.
- Variables:
- Parameters:
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
- metalog_jax.regression.ols.fit_ordinary_least_squares(X, y)[source]¶
Fit an Ordinary Least Squares (OLS) regression model.
Computes the closed-form solution to linear regression using the normal equations via least squares. This method finds the optimal weights and bias that minimize the sum of squared residuals with no regularization.
The model solves: min ||y - (Xw + b)||^2
- Parameters:
- Return type:
- Returns:
OLSModel containing the fitted weights and bias that minimize the squared error.
Example
>>> X = jnp.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]) >>> y = jnp.array([1.0, 2.0, 3.0]) >>> model = fit_ordinary_least_squares(X, y)
- metalog_jax.regression.ols.predict_ordinary_least_squares(X, model)[source]¶
Make predictions using a fitted Regression model.
Computes predictions for new data using the trained weights and bias: y_pred = X @ weights + bias
- Parameters:
- Return type:
- Returns:
Predictions of shape (n_samples,).
Example
>>> import jax.numpy as jnp >>> X = jnp.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]) >>> model = fit_ordinary_least_squares(X, jnp.array([1.0, 2.0, 3.0])) >>> X_test = jnp.array([[2.0, 3.0], [4.0, 5.0]]) >>> predict_ordinary_least_squares(X_test, model).round(4).tolist() [1.5, 2.5]
LASSO¶
LASSO regression implementation with L1 regularization.
This module provides LASSO (Least Absolute Shrinkage and Selection Operator) regression for fitting metalog distributions with L1 regularization using proximal gradient descent with Nesterov acceleration.
- Classes:
LassoParameters: Hyperparameters for LASSO regression. LassoModel: Structure for trained LASSO model weights.
- Functions:
fit_lasso: Fit a LASSO regression model via proximal gradient descent. soft_thresholding: Apply the soft-thresholding operator (proximal for L1).
- Constants:
DEFAULT_LASSO_LAMBDA: Default L1 regularization strength (0). DEFAULT_LASSO_LEARNING_RATE: Default learning rate (0.01). DEFAULT_LASSO_ITERATIONS: Default maximum iterations (500). DEFAULT_LASSO_TOLERANCE: Default convergence tolerance (1e-6). DEFAULT_LASSO_MOMENTUM: Default Nesterov momentum factor (0.9). DEFAULT_LASSO_PARAMETERS: Default LassoParameters instance.
This module is used when MetalogFitMethod.Lasso is specified in MetalogParameters.
See also
metalog_jax.regression.ols: OLS regression (no regularization). metalog_jax.metalog.fit: High-level fitting function that dispatches to LASSO.
- class metalog_jax.regression.lasso.LassoParameters(lam, learning_rate, num_iters, tol, momentum)[source]¶
Bases:
RegularizedParametersStructure for Lasso parameters.
- Variables:
lam – L1 regularization strength λ ≥ 0. Controls sparsity of the solution: - λ = 0: No regularization (equivalent to OLS, but solved iteratively) - λ > 0: Promotes sparsity; larger values → more coefficients set to zero - Typical values: 0.001 to 10.0, depending on data scale
learning_rate – Learning rate (step size) for gradient descent. Default: 0.01. Controls the size of parameter updates. Should be tuned based on problem: - Too large: May cause divergence or oscillation - Too small: Slow convergence - Typical values: 0.001 to 0.1
num_iters – Maximum number of iterations. Default: 500. Training will stop early if convergence is reached before this limit.
tol – Convergence tolerance for weight changes. Default: 1e-6. Training stops when ||w_new - w|| < tol, indicating the solution has stabilized. Smaller values require more precise convergence but may take longer.
momentum – Nesterov momentum factor, must satisfy 0 < momentum < 1. Default: 0.9. Controls acceleration of convergence: - Higher values (e.g., 0.9, 0.99): Faster convergence but less stable - Lower values (e.g., 0.5, 0.7): More stable but slower convergence - Standard choice: 0.9
- Parameters:
- __init__(lam, learning_rate, num_iters, tol, momentum)¶
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
- class metalog_jax.regression.lasso.LassoModel(weights)[source]¶
Bases:
RegressionModelStructure for trained LASSO Regression model weights.
- Variables:
- Parameters:
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
- metalog_jax.regression.lasso.soft_thresholding(x, lam)[source]¶
Apply the soft-thresholding operator (proximal operator for L1 norm).
The soft-thresholding operator is the proximal operator for the L1 norm and is fundamental to LASSO regression and other sparse optimization methods. It shrinks values toward zero and sets values below the threshold to exactly zero, promoting sparsity in the solution.
- The operator is defined element-wise as:
soft_threshold(x, λ) = sign(x) * max(|x| - λ, 0)- Equivalently:
If x > λ: return x - λ
If x < -λ: return x + λ
If
|x| ≤ λ: return 0
- This function is the proximal operator for the L1 penalty:
prox_{λ||·||₁}(x) = argmin_z { (1/2)||z - x||² + λ||z||₁ }
- Parameters:
x (
Union[Array,ndarray,bool,number,float,int]) – Input value(s) to threshold. Can be a scalar or array of any shape.lam (
float) – Threshold parameter λ ≥ 0. Controls the amount of shrinkage: - λ = 0: No shrinkage (returns x unchanged) - λ > 0: Shrinks values toward zero, setting small values to 0 - Larger λ produces sparser solutions with more zeros
- Return type:
- Returns:
Thresholded value(s) with the same shape as x. Values are shrunk toward zero by amount λ, with values
|x| ≤ λset to exactly zero.
Example
Scalar inputs:
>>> import jax.numpy as jnp >>> float(soft_thresholding(5.0, 2.0)) 3.0 >>> float(soft_thresholding(-3.0, 1.0)) -2.0 >>> float(soft_thresholding(1.5, 2.0)) # Below threshold -> zero 0.0
Array inputs:
>>> x = jnp.array([-5.0, -1.0, 0.5, 2.0, 4.0]) >>> soft_thresholding(x, 1.5).tolist() [-3.5, -0.0, 0.0, 0.5, 2.5]
Note
This function is JIT-compiled for efficient execution
The operation is element-wise and preserves the input shape
Setting lam=0 returns the input unchanged
This is also known as the “shrinkage operator” or “soft thresholding”
See also
fit_lasso: LASSO regression that uses this operator in proximal gradient descent.
References
Parikh, N., & Boyd, S. (2014). Proximal Algorithms. Foundations and Trends in Optimization, 1(3), 127-239.
Beck, A., & Teboulle, M. (2009). A Fast Iterative Shrinkage-Thresholding Algorithm for Linear Inverse Problems. SIAM Journal on Imaging Sciences, 2(1), 183-202.
- metalog_jax.regression.lasso.fit_lasso(X, y, params=LassoParameters(lam=0.0, learning_rate=0.01, num_iters=500, tol=1e-06, momentum=0.9))[source]¶
Fit a LASSO regression model using proximal gradient descent with Nesterov acceleration.
Trains a linear regression model with L1 regularization (LASSO - Least Absolute Shrinkage and Selection Operator) using an accelerated proximal gradient method. LASSO promotes sparse solutions by shrinking coefficients toward zero and setting many coefficients to exactly zero, making it useful for feature selection and interpretable models.
This implementation uses:
Proximal gradient descent: Combines gradient steps on the smooth loss (MSE) with the proximal operator (soft-thresholding) for the non-smooth L1 penalty
Nesterov momentum: Accelerates convergence using momentum-based lookahead steps
Early stopping: Terminates when weight changes fall below tolerance threshold
JAX compatibility: Fully vectorized and compatible with JAX transformations
- The LASSO objective function is:
min_w { (1/2n)||y - Xw||² + λ||w||₁ }
where λ is the L1 regularization strength that controls sparsity.
- Algorithm:
Initialize weights w and velocity v to zero
For each iteration (until convergence or max iterations): a. Compute lookahead weights: w_lookahead = w + momentum * v b. Compute gradient at lookahead position c. Update velocity with momentum and gradient: v = momentum * v - learning_rate * grad d. Apply proximal operator: w = soft_threshold(w + v, learning_rate * λ) e. Check convergence: stop if ||w_new - w|| < tol
Return final weights
- Parameters:
X (
Union[Array,ndarray,bool,number,float,int]) – Feature matrix of shape (n_samples, n_features). The design matrix containing the independent variables for regression.y (
Union[Array,ndarray,bool,number,float,int]) – Target vector of shape (n_samples,). The dependent variable values to predict.params (
LassoParameters) – LassoParameters containing optimization hyperparameters. Defaults to DEFAULT_LASSO_PARAMETERS. The object contains: - lam: L1 regularization strength λ ≥ 0. Default: 0. - learning_rate (float): Step size for gradient descent. Default: 0.01. - num_iters (int): Maximum number of iterations. Default: 500. - tol (float): Convergence tolerance for weight changes. Default: 1e-6. - momentum (float): Nesterov momentum factor (0 < momentum < 1). Default: 0.9.
- Return type:
- Returns:
LassoModel containing the fitted weights vector of shape (n_features,). Many coefficients may be exactly zero due to L1 regularization, achieving feature selection and model sparsity.
Example
Basic LASSO regression with default parameters:
>>> import jax.numpy as jnp >>> from metalog_jax.regression.lasso import fit_lasso >>> >>> # Create sample data >>> X = jnp.array([[1.0, 2.0, 0.5], ... [3.0, 4.0, 1.5], ... [5.0, 6.0, 2.5]]) >>> y = jnp.array([1.0, 2.0, 3.0]) >>> >>> # Fit LASSO with defaults >>> model = fit_lasso(X, y) >>> model.weights.shape # Some coefficients may be exactly zero (3,)
Note
Unlike Ridge regression, LASSO does not have a closed-form solution and requires iterative optimization
The algorithm may converge before num_iters if tol is reached
Feature scaling (standardization) is recommended before fitting LASSO
See also
soft_thresholding: The proximal operator used in each iteration. fit_ordinary_least_squares: No regularization (from regression.ols). LassoModel: Return type containing fitted weights. LassoParameters: Configuration dataclass for LASSO hyperparameters.
References
Tibshirani, R. (1996). Regression Shrinkage and Selection via the Lasso. Journal of the Royal Statistical Society: Series B (Methodological), 58(1), 267-288.
Beck, A., & Teboulle, M. (2009). A Fast Iterative Shrinkage-Thresholding Algorithm for Linear Inverse Problems. SIAM Journal on Imaging Sciences, 2(1), 183-202.
Feasibility (Metalog 2.0)¶
Feasibility tools of Baucells, Chrisman, Keelin and Xu (2025).
JAX ports of the algorithms in “On the Properties of the Metalog Distribution” and its reference implementation by Zixin (Stephen) Xu (CC BY 4.0; see NOTICE):
best_feasible_fit()- the optimal feasible coefficientsa*.check_feasibility()- air-tight feasibility test (Algorithm 1 + Prop. 5).inflection_points()- all modes and anti-modes of the density.highest_mode()- the highest-density mode (used byMetalog.mode).summary_stats()/raw_moment()- exact moments (Lemma 1, Prop. 3).
Every function is jit-compatible and can be vmapped over batches. They use the
Keelin (2016) term assignment of metalog_jax by default; TermOrder.METALOG_2
selects the paper’s Metalog 2.0 assignment, which differs from the 7th term on.
References
Baucells, M., Chrisman, L., Keelin, T. W., & Xu, Z. S. (2025). On the Properties of the Metalog Distribution. Darden Business School Working Paper No. 5279416. https://doi.org/10.2139/ssrn.5279416 Xu, Z. S. metalog_algorithm (reference implementation), licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/); adapted and ported to JAX, see NOTICE. https://github.com/Stephenxuu/metalog_algorithm
Best Feasible Fit¶
Best feasible metalog fit a* (semi-infinite QP solved by the exchange method).
Solves min_a ||x - Y a||^2 subject to G(y) = y(1-y) M'(y) >= epsilon for all
y in [0, 1], where G(0) = s(0) and G(1) = s(1). The constraint is linear in
a, so each violated point becomes one linear cut. The loop alternates between
(i) finding the most violated points of the current fit and (ii) re-solving a small
QP with all cuts so far, and stops when an air-tight check finds no violation. This
mirrors find_a_star in the authors’ reference code, with these differences:
The QP is whitened with a QR factorization of Y (Hessian becomes the identity) and solved by a fixed-iteration Mehrotra predictor-corrector interior-point method.
Shapes are static (a masked buffer of
max_cutscuts), so the whole fit runs underjitand can bevmap-ed over many datasets.Violations are found on a logit grid (reaching y ~ 1e-16 from either edge) with golden-section refinement, then Algorithm 1 plus the Proposition 5 tail tests as an exact fallback. A point is a violation when G(y) < 0 beyond rounding, the same sign test
check_feasibility()uses, and the final certificate ischeck_feasibility()itself.As in the paper, each iteration adds every flagged point: all confirmed grid minima (at least
floor((k + 1) / 2) + 1are refined, the most G can have) or every inflection point withM' < 0plus any infeasible tail.epsilonis relative to the spread of x, so results do not depend on the units of the data.
Note: “a*” is the paper’s name for the optimal coefficient vector; this is not the A* graph-search algorithm.
References
Baucells, M., Chrisman, L., Keelin, T. W., & Xu, Z. S. (2025). On the Properties of the Metalog Distribution. Darden Business School Working Paper No. 5279416. https://doi.org/10.2139/ssrn.5279416 Xu, Z. S. metalog_algorithm (reference implementation), licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/); adapted and ported to JAX, see NOTICE. https://github.com/Stephenxuu/metalog_algorithm
- class metalog_jax.feasibility.a_star.FeasibleFitResult(a_star, a_ols, rss_star, rss_ols, iterations, converged, feasible, full_rank, cut_points)[source]¶
Bases:
objectResult of
best_feasible_fit().- Variables:
a_star (
Union[Array,ndarray,bool,number]) – Best feasible coefficients.a_ols (
Union[Array,ndarray,bool,number]) – Unconstrained least-squares coefficients.rss_star (
Union[Array,ndarray,bool,number]) – Residual sum of squares ofa_star.rss_ols (
Union[Array,ndarray,bool,number]) – Residual sum of squares ofa_ols.iterations (
Union[Array,ndarray,bool,number]) – Number of QP solves (0 when OLS is already feasible).converged (
Union[Array,ndarray,bool,number]) – The exchange loop found no remaining violation.feasible (
Union[Array,ndarray,bool,number]) – Air-tight certificate fora_star:check_feasibility()(Algorithm 1 + Proposition 5, exact up to rounding).full_rank (
Union[Array,ndarray,bool,number]) – The basis matrix has full column rank, soa_olsanda_starare unique (Proposition 2 describes when it does not).cut_points (
Union[Array,ndarray,bool,number]) – Probabilities where cuts were placed, NaN-padded.
- Parameters:
- __init__(a_star, a_ols, rss_star, rss_ols, iterations, converged, feasible, full_rank, cut_points)¶
- Parameters:
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
- class metalog_jax.feasibility.a_star.FeasibleModel(weights)[source]¶
Bases:
RegressionModelRegression-style model returned by
fit_feasible()(weights =a*).- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
- metalog_jax.feasibility.a_star.design_matrix(engine, y)[source]¶
Metalog basis matrix of shape (n, k) for the engine’s term ordering.
- metalog_jax.feasibility.a_star.project_onto_polyhedron(z0, A, b, mask, iters=40)[source]¶
argmin 0.5||z - z0||^2s.t.A z >= bon rows wheremaskis True.Mehrotra predictor-corrector interior-point method with a fixed iteration budget. The iterate is frozen once converged or if a step becomes non-finite (the barrier Hessian overflows as active slacks reach zero).
- Parameters:
iters (
int)
- metalog_jax.feasibility.a_star.best_feasible_fit(x, y, num_terms, order=TermOrder.KEELIN_2016, epsilon=1e-06, tol=0.0, max_cuts=64, n_new=8, max_iter=60, n_grid=2049)[source]¶
Fit the best feasible k-term metalog to quantile/probability pairs.
- Parameters:
x (
Union[Array,ndarray,bool,number]) – Quantile values, shape (n,).y (
Union[Array,ndarray,bool,number]) – Cumulative probabilities in (0, 1), shape (n,).num_terms (
int) – Number of metalog terms k (2 <= k <= n).order (
TermOrder) – Basis ordering (KEELIN_2016matchesmetalog_jax; useMETALOG_2to reproduce the paper and the reference code).epsilon (
float) – Margin inG(y) >= epsilon, relative tomax|x - median(x)|.tol (
float) – Extra slack for the violation and certificate sign tests, relative to the magnitude of G’s terms. With 0 (the default) a violation is anyG(y) < 0beyond rounding, as in the paper.max_cuts (
int) – Capacity of the cut buffer (static).n_new (
int) – Minimum number of grid minima refined per iteration (static); at leastfloor((k + 1) / 2) + 1are always refined.max_iter (
int) – Maximum number of exchange iterations (static).n_grid (
int) – Number of logit grid points used to locate violations (static).
- Return type:
- Returns:
FeasibleFitResult. Check
feasible(the certificate),convergedandfull_rank(with n = k symmetric probabilities the Keelin 2016 basis is singular for 7, 11, 15, … terms; useMETALOG_2or more quantiles).
Example
>>> import jax.numpy as jnp >>> from metalog_jax.feasibility import best_feasible_fit >>> x = jnp.array([1.0, 2.0, 4.0, 8.0, 12.0]) >>> y = jnp.array([0.1, 0.3, 0.5, 0.7, 0.9]) >>> res = best_feasible_fit(x, y, num_terms=4) >>> bool(res.feasible) True
References
Baucells, M., Chrisman, L., Keelin, T. W., & Xu, Z. S. (2025). On the Properties of the Metalog Distribution. Darden Business School Working Paper No. 5279416. https://doi.org/10.2139/ssrn.5279416
- metalog_jax.feasibility.a_star.fit_feasible(X, y)[source]¶
Regression-dispatch entry point: best feasible fit from a metalog design matrix.
Xmust be ametalog_jaxdesign matrix (Keelin 2016 term ordering), as built byMetalogBase.get_target;yholds the (possibly transformed) quantiles. Used byfitwhenmethod=MetalogFitMethod.Feasible.- Parameters:
- Return type:
- Returns:
FeasibleModel whose
weightsare the best feasible coefficients.- Raises:
checkify.JaxRuntimeError – If the result cannot be certified feasible (the exchange loop hit its iteration or cut limit).
vmapcompatible.
References
Baucells, M., Chrisman, L., Keelin, T. W., & Xu, Z. S. (2025). On the Properties of the Metalog Distribution. Darden Business School Working Paper No. 5279416. https://doi.org/10.2139/ssrn.5279416
Feasibility Checks and Exact Moments¶
Feasibility, modes and exact moments of an (unbounded) metalog.
All functions are jit/vmap/grad compatible once the engine is fixed.
For bounded metalogs these statements apply to the underlying unbounded metalog
fitted to the transformed quantiles; feasibility is preserved by the monotone
back-transform, but the moment formulas are not.
References
Baucells, M., Chrisman, L., Keelin, T. W., & Xu, Z. S. (2025). On the Properties of the Metalog Distribution. Darden Business School Working Paper No. 5279416. https://doi.org/10.2139/ssrn.5279416
- class metalog_jax.feasibility.analysis.FeasibilityReport(feasible, interior_feasible, tail_feasible_zero, tail_feasible_one, roots, slopes, is_mode)[source]¶
Bases:
objectResult of
check_feasibility().- Variables:
feasible (
Union[Array,ndarray,bool,number]) –M'(y) >= 0on (0, 1) (interior and both tails).interior_feasible (
Union[Array,ndarray,bool,number]) –M' >= 0at every root ofM''.tail_feasible_zero (
Union[Array,ndarray,bool,number]) – Proposition 5 condition at y -> 0.tail_feasible_one (
Union[Array,ndarray,bool,number]) – Proposition 5 condition at y -> 1.roots (
Union[Array,ndarray,bool,number]) – Roots ofM''as probabilities, NaN-padded.slopes (
Union[Array,ndarray,bool,number]) –M'at each root, NaN-padded.is_mode (
Union[Array,ndarray,bool,number]) – True where the root is a mode of the density (M''' >= 0).
- Parameters:
- __init__(feasible, interior_feasible, tail_feasible_zero, tail_feasible_one, roots, slopes, is_mode)¶
- Parameters:
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
- metalog_jax.feasibility.analysis.feasibility_function(engine, a, u)[source]¶
G(y) = y(1-y) M'(y)aty = sigmoid(u)(u = -/+infgives s(0), s(1)).
- metalog_jax.feasibility.analysis.zero_test_width(tol=0.0)[source]¶
Width of the zero tests, in machine epsilons of the terms’ magnitude.
A value counts as zero when
|v| <= width * eps * magnitude, wheremagnitudeis the sum of the absolute values of the terms that producedv(a running bound on its rounding error).toladds slack relative to the same magnitude;tol = 0decides every sign exactly up to rounding.
- metalog_jax.feasibility.analysis.tail_feasibility(engine, a, tol=0.0)[source]¶
Proposition 5 tail conditions at y -> 0 and y -> 1.
Each of
s,s'andmu'at the edge counts as zero only within its own rounding error (seezero_test_width()), so a small but resolveds(0) > 0is tail feasible whatever the scale ofa.
- metalog_jax.feasibility.analysis.check_feasibility(engine, a, tol=0.0)[source]¶
Air-tight feasibility test (Proposition 5 with Algorithm 1).
Unlike a grid test this cannot miss a negative-density region:
M'is checked at every root ofM''(its local extrema) plus both tails. Every sign is decided exactly up to rounding (seezero_test_width()), so the verdict does not depend on the scale ofa.- Parameters:
- Return type:
- Returns:
FeasibilityReport.
References
Baucells, M., Chrisman, L., Keelin, T. W., & Xu, Z. S. (2025). On the Properties of the Metalog Distribution. Darden Business School Working Paper No. 5279416. https://doi.org/10.2139/ssrn.5279416
- metalog_jax.feasibility.analysis.is_feasible(engine, a, tol=0.0)[source]¶
Shorthand for
check_feasibility(engine, a, tol).feasible.
- metalog_jax.feasibility.analysis.highest_mode(engine, a)[source]¶
Highest-density interior mode of a feasible unbounded metalog (Algorithm 1).
The density is
1 / M'(y), so among the modes (roots ofM''where the third derivative is non-negative) the highest has the smallest slope. A binding mode of a best feasible fit, whereM'(y)is close to 0, has the highest density.- Parameters:
- Returns:
the mode’s quantile
M(y), its probabilityy, and whether an interior mode exists (a uniform metalog, for example, has none).- Return type:
tuple[Union[Array,ndarray,bool,number,float,int],Union[Array,ndarray,bool,number,float,int],Union[Array,ndarray,bool,number]]
References
Baucells, M., Chrisman, L., Keelin, T. W., & Xu, Z. S. (2025). On the Properties of the Metalog Distribution. Darden Business School Working Paper No. 5279416. https://doi.org/10.2139/ssrn.5279416
- metalog_jax.feasibility.analysis.moment_table(engine, max_order=4)[source]¶
Float table
I(m, u)large enough for moments up tomax_order.
- metalog_jax.feasibility.analysis.raw_moment(engine, a, p, table=None)[source]¶
Exact
E[X^p] = int_0^1 M(y)^p dyfor an unbounded metalog (Proposition 3).Expands
(mu + s L)^p = sum_n C(p, n) mu^(p-n) s^n L^nby polynomial convolution and integrates each monomial with the exactI(m, n).References
Baucells, M., Chrisman, L., Keelin, T. W., & Xu, Z. S. (2025). On the Properties of the Metalog Distribution. Darden Business School Working Paper No. 5279416. https://doi.org/10.2139/ssrn.5279416
- metalog_jax.feasibility.analysis.mean_and_variance(engine, a)[source]¶
Exact mean and variance of an unbounded metalog (Lemma 1, Proposition 3).
- Parameters:
- Return type:
tuple[Union[Array,ndarray,bool,number,float,int],Union[Array,ndarray,bool,number,float,int]]- Returns:
(mean, variance).
References
Baucells, M., Chrisman, L., Keelin, T. W., & Xu, Z. S. (2025). On the Properties of the Metalog Distribution. Darden Business School Working Paper No. 5279416. https://doi.org/10.2139/ssrn.5279416
- metalog_jax.feasibility.analysis.summary_stats(engine, a)[source]¶
Exact mean, variance, standard deviation, skewness and kurtosis.
Central moments are the raw moments of the metalog shifted by its mean, which avoids the cancellation of expanding
E[(X - m)^p].- Parameters:
- Return type:
- Returns:
Dict with keys
mean,variance,std,skewness,kurtosis. The kurtosis is Pearson’s (not excess): 3 for a normal, 4.2 for the logistic (the 2-term metalog).
References
Baucells, M., Chrisman, L., Keelin, T. W., & Xu, Z. S. (2025). On the Properties of the Metalog Distribution. Darden Business School Working Paper No. 5279416. https://doi.org/10.2139/ssrn.5279416
Engine¶
Static-shape engine for the feasibility algorithms of Baucells et al. (Algorithm 1).
A k-term metalog is written as M(y) = mu(x) + s(x) * logit(y) with x = y - 1/2.
The engine splits a coefficient vector into the ascending coefficients of mu and
s and evaluates the scaled derivative levels G_j(y) = (y(1-y))^j M^(j)(y) in
logit coordinates u = logit(y), which keeps the tails (y within 1e-300 of 0 or 1)
well conditioned.
Two term orderings are supported:
TermOrder.KEELIN_2016- the ordering used bymetalog_jax(Keelin 2016): 1, L, xL, x, x^2, x^2 L, x^3, x^3 L, …TermOrder.METALOG_2- the Metalog 2.0 ordering of the paper: 1, L, xL, x, x^2, x^2 L, x^3 L, x^3, x^4, …
They coincide for k <= 6 and differ from the 7th term on (paper, footnote 2).
All loops are over static sizes, so every public function here is jit/vmap
compatible.
References
Baucells, M., Chrisman, L., Keelin, T. W., & Xu, Z. S. (2025). On the Properties of the Metalog Distribution. Darden Business School Working Paper No. 5279416. https://doi.org/10.2139/ssrn.5279416
- class metalog_jax.feasibility.engine.TermOrder(*values)[source]¶
Bases:
IntEnumOrdering of the metalog basis terms.
- Variables:
KEELIN_2016 – Ordering used by
metalog_jax(Keelin 2016): 1, L, xL, x, x^2, x^2 L, x^3, x^3 L, …METALOG_2 – Metalog 2.0 ordering of the paper: 1, L, xL, x, x^2, x^2 L, x^3 L, x^3, x^4, …
- metalog_jax.feasibility.engine.term_layout(num_terms, order)[source]¶
Coefficient indices of the mu- and s-polynomials, sorted by power of (y - 1/2).
- class metalog_jax.feasibility.engine.Engine(num_terms, order=TermOrder.KEELIN_2016)[source]¶
Bases:
objectTrace-time tables and level evaluators for one
(num_terms, order)pair.Engines are hashable by identity and cached by
get_engine(), so they can be passed as static arguments tojax.jit.- __init__(num_terms, order=TermOrder.KEELIN_2016)[source]¶
Build the exact tables for a k-term metalog.
- Parameters:
- Raises:
ValueError – If
num_termsis less than 2.
- level(j, mu, s, u)[source]¶
Value of
G_j = (y(1-y))^j M^(j)(y)aty = sigmoid(u).G_1(y) = y(1-y) M'(y)is the paper’s feasibility function; its limits atu = -inf, +infares(0)ands(1).
- level_sign(j, mu, s, u, kappa=64.0)[source]¶
Sign of
M^(j)atsigmoid(u)with a running-error zero test.Both parts are rescaled by their joint log-magnitude before combining, so the sign is exact up to rounding even when
(y(1-y))^junderflows. Returns 0 where the value is indistinguishable from zero (e.g. levels that vanish identically).
- metalog_jax.feasibility.engine.get_engine(num_terms, order=TermOrder.KEELIN_2016)[source]¶
Cached
Engine(stable identity keepsjitcaches warm).
- metalog_jax.feasibility.engine.inflection_points(engine, a)[source]¶
All roots of
M''on (0, 1), i.e. the modes and anti-modes (Algorithm 1).The highest level
(y(1-y))^i M^(i)is an exact polynomial of degreei-1(Lemma 4). Its roots are bracketed by running the same cascade over its own polynomial derivatives, so no eigenvalue solver is needed, and the roots are then carried down one level at a time toM''using Lemma 2 edge signs.- Parameters:
- Return type:
tuple[Union[Array,ndarray,bool,number],Union[Array,ndarray,bool,number]]- Returns:
(u, mask)- roots in logit coordinates (y = sigmoid(u)), sorted and padded withUMAX, and a boolean mask of valid entries. The static capacity is2 * engine.istar - 3, exactly Proposition 1’s bound on the roots ofM''(2 * floor((k - 1) / 2) - 1), so no root can be dropped.
References
Baucells, M., Chrisman, L., Keelin, T. W., & Xu, Z. S. (2025). On the Properties of the Metalog Distribution. Darden Business School Working Paper No. 5279416. https://doi.org/10.2139/ssrn.5279416
Utilities¶
Utility functions for the Metalog JAX library.
- exception metalog_jax.utils.NotFittedError[source]¶
Bases:
ExceptionA custom exception that raises when metalog has not been fit yet.
- exception metalog_jax.utils.InvalidPDFError[source]¶
Bases:
ExceptionA custom exception that raises when metalog returns an invalid PDF.
- class metalog_jax.utils.JaxUniformDistributionParameters(seed)[source]¶
Bases:
objectConfiguration parameters for a JAX uniform distribution.
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
- class metalog_jax.utils.HDRPRNGParameters(trial=1, variable=0, entity=0, time=0, agent=0)[source]¶
Bases:
objectStructure for Hubbard Decision Research Parameterized PRNG.
See paper below:
Douglas W. Hubbard. 2019. A multi-dimensional, counter-based psuedo random number generator as a standard for Monte Carlo simulations. In Proceedings of the Winter Simulation Conference. IEEE Press, 3064 - 3073.
- Variables:
trial (
int) – First simulation trial. MetalogBase.rvs draws trialstrialtotrial + size - 1, one per variate. Defaults to 1.variable (
int) – Parameter representing the random variable being sampled. Defaults to 0.entity (
int) – Parameter representing the entity or object being simulated. Defaults to 0.time (
int) – Parameter representing the time step in the simulation. Defaults to 0.agent (
int) – Parameter representing the agent or actor in the simulation. Defaults to 0.
- Parameters:
- __init__(trial=1, variable=0, entity=0, time=0, agent=0)¶
- replace(**updates)¶
Returns a new object replacing the specified fields with new values.
- metalog_jax.utils.assert_numeric_array(array)[source]¶
Assert that an array contains all float or int values.
- metalog_jax.utils.assert_float_array(array)[source]¶
Assert that an array contains all float values.
- metalog_jax.utils.assert_condition(predicate, message, *format_args, error=<class 'AssertionError'>)[source]¶
Check a scalar condition, inside or outside JAX transforms.
Outside transforms the predicate is concrete, and a false one raises
errorwithmessage.format(*format_args). Underjax.vmaporjax.jitit is traced, so the check goes throughcheckify.check, which formats the message with the runtime values:jax.vmapraisescheckify.JaxRuntimeError, andjax.jitneeds the caller to functionalize it withcheckify.checkify. Comparisons with NaN are false, so NaN fails the check.- Parameters:
predicate (
Union[Array,ndarray,bool,number,bool]) – Scalar boolean that must be true.message (
str) – Error message, with a{}placeholder for each format argument.*format_args (
Union[Array,ndarray,bool,number,float,int]) – Values to put into the message.error (
type[Exception]) – Exception type to raise outside transforms.
- Raises:
AssertionError – If the predicate is concrete and false, or
errorif given instead.checkify.JaxRuntimeError – Under
jax.vmap, if the predicate is false for any element of the batch.
- Return type:
Example
>>> assert_condition(jnp.array(1.0) > 0, "x must be positive") # No error >>> assert_condition(jnp.array(-1.0) > 0, "x must be positive") Traceback (most recent call last): ... AssertionError: x must be positive
- metalog_jax.utils.assert_probability_range(array)[source]¶
Assert that all array values are in the open interval (0, 1).
Validates that the minimum value in the array is strictly greater than 0 and the maximum value is strictly less than 1, ensuring all values represent valid probabilities in the open unit interval. Works inside
jax.jitandjax.vmapthrough assert_condition.- Parameters:
array (
Union[Array,ndarray,bool,number]) – Input array to validate.- Raises:
ValueError – If any value is <= 0 or >= 1, or is NaN.
checkify.JaxRuntimeError – Under
jax.vmap, for the same values.
- Return type:
Example
>>> valid_probs = jnp.array([0.1, 0.5, 0.9]) >>> assert_probability_range(valid_probs) # No error >>> invalid_probs = jnp.array([0.0, 0.5, 1.0]) >>> assert_probability_range(invalid_probs) Traceback (most recent call last): ... ValueError: Array minimum 0.0 must be strictly greater than 0
- metalog_jax.utils.assert_strictly_ascending(x, tol=1e-12)[source]¶
Assert that array values are strictly ascending (monotonically increasing).
Validates that each element in the array is strictly greater than the previous element, with differences exceeding a specified tolerance threshold. This ensures the array represents a strictly increasing sequence, which is required for valid probability levels and quantile values in metalog distributions.
The function computes consecutive differences between array elements and verifies that all differences are strictly positive (greater than the tolerance). This is more robust than simple inequality checks as it accounts for numerical precision issues in floating-point comparisons.
- Parameters:
x (
Union[Array,ndarray,bool,number,float,int]) – Input array to validate. Should be a 1D numeric array representing values that must be in strictly ascending order (e.g., probability levels, quantile values, or time steps).tol (
float) – Tolerance threshold for considering differences as strictly positive. Must be a small positive value. Default is 1e-12, which is appropriate for double-precision floating-point arithmetic. Values separated by less than this tolerance are considered equal (not strictly ascending).
- Raises:
AssertionError – If any consecutive difference is less than or equal to the tolerance threshold, indicating the array is not strictly ascending. This can occur if: - The array contains duplicate values (difference = 0) - The array is not sorted in ascending order (negative differences) - Consecutive values are too close together (difference < tol)
- Return type:
Note
This function is used to validate inputs for metalog fitting, particularly probability levels (y values) which must be strictly increasing to ensure a valid probability distribution. The tolerance parameter helps handle numerical precision issues while still enforcing strict monotonicity.
Unlike simple sorting checks, this function ensures strict inequality (no duplicate values allowed), which is essential for metalog distributions where each probability level must map to a unique quantile value.
Example
Valid strictly ascending array:
>>> import jax.numpy as jnp >>> from metalog_jax.utils import assert_strictly_ascending >>> >>> x = jnp.array([0.1, 0.2, 0.5, 0.9]) >>> assert_strictly_ascending(x) # No error >>> # Differences: [0.1, 0.3, 0.4] all > 1e-12
Invalid array with duplicate values:
>>> x_dup = jnp.array([0.1, 0.2, 0.2, 0.9]) >>> assert_strictly_ascending(x_dup) Traceback (most recent call last): ... AssertionError: Array values must be strictly ascending, with consecutive differences above 1e-12
Invalid array not in ascending order:
>>> x_unsorted = jnp.array([0.1, 0.5, 0.2, 0.9]) >>> assert_strictly_ascending(x_unsorted) Traceback (most recent call last): ... AssertionError: Array values must be strictly ascending, with consecutive differences above 1e-12
Using custom tolerance:
>>> # Values closer together than the default tolerance (1e-12) >>> x_close = jnp.array([0.1, 0.1 + 1e-13, 0.2]) >>> assert_strictly_ascending(x_close) Traceback (most recent call last): ... AssertionError: Array values must be strictly ascending, with consecutive differences above 1e-12 >>> assert_strictly_ascending(x_close, tol=1e-14) # Passes
See also
assert_probability_range: Validates array values are in (0, 1). jnp.diff: Computes consecutive differences between array elements.
References
Used extensively in metalog fitting to validate probability level arrays (y values) before computing quantile functions or fitting coefficients.
- metalog_jax.utils.hdrprng(hdr_parameters)[source]¶
Generate a pseudo-random number using the Hubbard Decision Research PRNG algorithm.
Implements a multi-dimensional, counter-based pseudo-random number generator designed for reproducible Monte Carlo simulations. This method validates the input parameters and delegates to the JIT-compiled internal implementation.
This PRNG is particularly useful for large-scale simulations where: - Reproducibility is critical - Multiple independent random streams are needed - Parallel execution requires uncorrelated random sequences
- Parameters:
hdr_parameters (
HDRPRNGParameters) – HDRPRNGParameters instance containing the five integer parameters (trial, variable, entity, time, agent) that determine the random output.- Return type:
- Returns:
A pseudo-random float in the open interval (0, 1).
- Raises:
AssertionError – If hdr_parameters.trial is not a positive scalar (validated by HDRPRNGParameters.__post_init__).
Example
>>> from metalog_jax.utils import HDRPRNGParameters, hdrprng >>> params = HDRPRNGParameters(trial=1, variable=0, entity=0, time=0, agent=0) >>> random_value = hdrprng(params) >>> # Same parameters always produce the same output >>> random_value_2 = hdrprng(params) >>> assert random_value == random_value_2
References
Douglas W. Hubbard. 2019. A multi-dimensional, counter-based pseudo random number generator as a standard for Monte Carlo simulations. In Proceedings of the Winter Simulation Conference. IEEE Press, 3064-3073.
Note
This is the public API method. The actual computation is performed by the internal _hdrprng() function, which is JIT-compiled for performance. The algorithm uses a combination of large prime numbers and modular arithmetic to generate uniformly distributed pseudo-random values.
- metalog_jax.utils.find_nearest_index(array, value)[source]¶
Find the index of the nearest value in an array.
- metalog_jax.utils.ks_distance(x, y)[source]¶
Compute the two-sample Kolmogorov-Smirnov (KS) distance between two samples.
Calculates the maximum absolute difference between the empirical cumulative distribution functions (ECDFs) of two samples. This is the Kolmogorov-Smirnov statistic for the two-sample test, which measures the degree of dissimilarity between two probability distributions.
The KS distance is defined as
D = sup_t |F_x(t) - F_y(t)|, where F_x and F_y are the empirical CDFs of samples x and y, and the supremum is taken over all values t.This implementation is fully vectorized and JIT-compiled for efficient execution with JAX, making it suitable for use in gradient-based optimization and parallel computation workflows. The function uses jnp.searchsorted for efficient CDF computation, avoiding explicit loops.
- Parameters:
x (
Union[Array,ndarray,bool,number,float,int]) – First sample array of shape (n,). Contains n independent observations from the first distribution. Can be unsorted (will be sorted internally).y (
Union[Array,ndarray,bool,number,float,int]) – Second sample array of shape (m,). Contains m independent observations from the second distribution. Can be unsorted (will be sorted internally). Does not need to have the same length as x.
- Return type:
- Returns:
Scalar KS distance in the range [0, 1]. The KS statistic represents the maximum vertical distance between the two empirical CDFs: - 0: The two samples have identical empirical distributions - 1: The two samples are completely non-overlapping - Values closer to 0 indicate more similar distributions
Note
Both input samples are sorted internally, so there’s no need to pre-sort
The function is JIT-compiled, so the first call may be slower due to compilation overhead, but subsequent calls will be very fast
This computes only the KS distance (D statistic), not the p-value for the hypothesis test
The function is differentiable and can be used in gradient-based optimization (e.g., for distribution matching)
Memory complexity is O(n + m) due to the combined sorted values array
Example
Basic usage with identical distributions:
>>> import jax.numpy as jnp >>> from metalog_jax.utils import ks_distance >>> >>> # Two samples from the same distribution >>> x = jnp.array([0.1, 0.5, 0.3, 0.7, 0.9]) >>> y = jnp.array([0.2, 0.4, 0.6, 0.8]) >>> round(float(ks_distance(x, y)), 2) # Small: similar distributions 0.2
Comparing different distributions:
>>> # Sample from uniform(0, 1) >>> uniform_sample = jnp.array([0.1, 0.3, 0.5, 0.7, 0.9]) >>> # Sample from distribution concentrated near 0 >>> skewed_sample = jnp.array([0.05, 0.1, 0.15, 0.2, 0.25]) >>> round(float(ks_distance(uniform_sample, skewed_sample)), 2) 0.8
Using for metalog goodness-of-fit:
>>> from metalog_jax.base import MetalogBoundedness, MetalogFitMethod >>> from metalog_jax.base import MetalogInputData, MetalogParameters >>> from metalog_jax.base import MetalogRandomVariableParameters >>> from metalog_jax.metalog import fit >>> >>> # Fit metalog to data >>> samples = jnp.array([1.2, 2.3, 2.8, 3.5, 4.1, 5.6, 6.2, 7.8, 9.1, 10.5]) >>> data = MetalogInputData.from_values(samples, DEFAULT_Y, False) >>> params = MetalogParameters( ... boundedness=MetalogBoundedness.UNBOUNDED, ... method=MetalogFitMethod.Feasible, ... num_terms=5, lower_bound=0.0, upper_bound=0.0 ... ) >>> metalog = fit(data, params) >>> >>> # Draw samples from the fitted distribution >>> rv_params = MetalogRandomVariableParameters( ... prng_params=JaxUniformDistributionParameters(seed=0), size=1000 ... ) >>> fitted_samples = metalog.rvs(rv_params) >>> >>> # A low KS distance indicates a good fit >>> bool(ks_distance(samples, fitted_samples) < 0.2) True
See also
jnp.searchsorted: Efficient binary search used for CDF computation. scipy.stats.ks_2samp: SciPy equivalent (includes p-value calculation).
References
Kolmogorov, A. N. (1933). “Sulla determinazione empirica di una legge di distribuzione”. Giornale dell’Istituto Italiano degli Attuari, 4: 83-91.
Smirnov, N. V. (1948). “Table for estimating the goodness of fit of empirical distributions”. Annals of Mathematical Statistics, 19(2): 279-281.
- Implementation Details:
The algorithm works as follows: 1. Sort both input samples (O(n log n + m log m)) 2. Combine all unique values from both samples and sort (O((n+m) log(n+m))) 3. Use binary search to compute empirical CDFs at all combined points (O((n+m) log n)) 4. Find maximum absolute difference between the CDFs (O(n+m)) Overall complexity: O((n+m) log(n+m))