Methods
jaxgsa implements thirteen methods for global sensitivity analysis (GSA). All of them answer the same broad question: which parameters actually drive my model's output? They differ in three ways. They measure different quantities, they cost different numbers of model evaluations, and some need a dedicated sampling design while others work with data you already have.
If you are new to the package, start with Choosing a method, then jump to the section for the method you picked. Every method section opens with what it measures, when to pick it, and what data it needs. The estimator details follow.
Throughout this page,
Two conventions for the code on this page. verbose defaults to True on every analyze() and every sample(), so a plain call prints a problem summary, timings and a top-k table to stdout. Every example here passes verbose=False so the printed output is only what the example asks for; drop it and you get the report as well. And the examples share one setup, which is Ishigami unless the text says otherwise:
import jax
import jax.numpy as jnp
import numpy as np
import jaxgsa
from jaxgsa.benchmarks.ishigami import PROBLEM, evaluate
X = jnp.asarray(jaxgsa.sampling.monte_carlo(PROBLEM, n=4000, seed=0))
Y = evaluate(X)Choosing a method
Three questions narrow the field quickly.
Can you still choose where to run the model? Four methods need their own sampling design, which jaxgsa generates for you: Sobol' (Saltelli matrices), eFAST (search curves), Morris (trajectories), and Kucherenko (conditional-copula blocks for dependent parameters). The other nine are given-data methods: HDMR, PCE, Shapley effects, DGSM, HSIC, PAWN, Borgonovo delta, optimal transport, and VKOGA. They accept any set of
pairs, including simulation runs you already have. DGSM has no sampler of its own: draw plain Monte Carlo points with jaxgsa.sampling.monte_carloand let autodiff do the rest.What should the number mean? Variance-based methods (Sobol', HDMR, PCE, eFAST, Shapley) report fractions of output variance, as in "parameter 3 explains 40% of the output's spread". Screening methods (Morris, DGSM) trade that precision for a cheap answer to a narrower question: which parameters can I stop worrying about? They are good at finding the ones that do nothing and less good at ordering the ones that do. Both misrank Ishigami's top two; see their sections. Moment-independent methods (HSIC, PAWN, Borgonovo delta, optimal transport) measure how strongly a parameter affects the whole output distribution. Use them when your output is skewed or heavy-tailed and variance is the wrong summary. Optimal transport also splits its index into a mean-shift part and a shape-change part.
What is your evaluation budget? Sobol' needs
model runs by default, where is the base sample count and is typically 128 or more. Morris needs only , with trajectories. DGSM costs Jacobians, each about evaluations; has_aux=Truereturns the primal output from the same forward/reverse pass, so the output itself costs nothing extra. It is cheap for a scalar output and stops being cheap for a long time series. The given-data methods cost nothing beyond the runs you already have.
One thing to get straight before you read any cost formula on this page. The jaxgsa.sobol.sample(problem, n_samples) takes the total evaluation budget and picks the base count for you. sample(problem, 8192) on a 3-parameter problem gives 8192 model runs from a base count of 1024, not 8192 × 8. jaxgsa.efast.sample and jaxgsa.morris.sample take per-curve and per-trajectory counts instead, so they do multiply. Check samples.samples.shape[0] if you are not sure.
Common situations:
"I can run the model freely and want the standard variance decomposition." Use Sobol' via Saltelli sampling, the reference method, with first-order, total-order, and second-order indices.
"My model is expensive and has many parameters." Screen first with Morris at
runs, or with DGSM if the model is JAX-differentiable. Fix the negligible parameters, then spend the remaining budget on Sobol' for the survivors. "I only have existing simulation data." Any given-data method works. Use HDMR or PCE for variance-based indices via a surrogate, or VKOGA when the parameters are dependent. Use HSIC, PAWN, Borgonovo delta, or optimal transport for distribution-based indices.
"My parameters are correlated." Sobol', PCE, eFAST, DGSM, Morris, and PCE-backed Shapley all assume independent parameters. They refuse to run when
problem.correlationis declared. Three routes remain, depending on what you have:- Declare the dependence and sample it. Put a Gaussian-copula matrix on the
Problem(correlation=, orproblem.with_correlation(R)), then draw withjaxgsa.sampling.monte_carlo. A copula is a way to build correlated samples that still keep each parameter's own declared marginal distribution exactly. - Analyze data you already have. Use VKOGA for variance fractions split into a correlated part and an uncorrelated part, fitted through a kernel surrogate. Use HDMR for the ANCOVA separation, which splits each interaction term's variance into a structural share and a correlation-driven share the same way. Or use optimal transport, Borgonovo delta, HSIC, or PAWN, none of which assume independence in the first place.
shapley.analyze(backend="hdmr", include_correlative=True)turns the HDMR split into one allocation per parameter, but read the HDMR section first: itsSTis not a total-effect index under dependence. - Run your model on a dedicated design. Use Kucherenko: it samples conditionally on the declared copula, evaluates your actual model, and returns
/ under the declared dependence. See Correlated Inputs for a worked example of all three routes.
The four variance-based routes measure different things and disagree on the same data. Four indices under dependence puts them side by side.
- Declare the dependence and sample it. Put a Gaussian-copula matrix on the
"Some of my parameters are categorical." Declare them with
{"dist": "categorical", "probs": [...]}, and samples then carry integer level codes. Four methods handle unordered levels correctly: Sobol', because the Saltelli column-swap scheme is distribution-agnostic, plus Borgonovo delta, optimal transport, and PAWN, which all condition on one class per level. Every other method refuses with aValueError, because its indices would depend on the arbitrary code order. See Categorical Inputs."I need to decide what to measure more accurately, or what to hold fixed." Use VKOGA:
is the prioritisation measure and the fixing measure. Under dependence they can rank parameters very differently. "My output distribution is skewed or heavy-tailed." Use PAWN, Borgonovo delta, or optimal transport. All three compare whole output distributions rather than variances.
"I want to know how a parameter matters: shift or shape?" Use optimal transport. Its index decomposes exactly into an advective (mean-shift, close to
) and a diffusive (spread/shape) component. "I want one number per parameter for a whole trajectory." Use optimal transport with
mode="trajectory". Point-cloud transport scores each parameter against the entire time course jointly."I want one fair importance number per parameter that sums to 1." Use Shapley effects.
"I also want a fast surrogate of my model." Use HDMR or PCE and call
result.predict(...).
When to use each method
The walkthrough above is the long version; this is the one-line version. It is the condensed table from the eight-method comparison on Ishigami, and "Best for" names the job each method does best at a small evaluation budget.
| Method | Best for |
|---|---|
| Sobol' | Gold standard for |
| eFAST | Screening at |
| DGSM | Differentiable models via autodiff |
| HDMR | Arbitrary |
| PCE | Emulation with a reusable surrogate |
| Morris | Cheapest screening / factor fixing |
| Shapley | Fair variance shares summing to 1 |
| Borgonovo delta | Moment-independent influence on the whole output density |
HSIC and PAWN are given-data methods that sit outside this variance-share comparison; use them when you want a dependence or distribution-based measure instead of a variance share. The table records the job each method is best at, not the only job it can do.
One model, four answers
The methods disagree, and the disagreement is the point. Here is Ishigami,
samples = jaxgsa.sobol.sample(PROBLEM, 8192, seed=0, verbose=False)
Ys = evaluate(samples.samples)
sobol = jaxgsa.sobol.analyze(samples, Ys, verbose=False)
morris = jaxgsa.morris.analyze(samples.to_morris(verbose=False), Ys, verbose=False)
# Optimal transport is a given-data method, so it can reuse these same rows.
ot = jaxgsa.optimal_transport.analyze(
PROBLEM, jnp.asarray(samples.samples), Ys, verbose=False
)
print("S1 ", sobol.S1)
print("ST ", sobol.ST)
print("mu* ", morris.mu_star)
print("advect ", ot.advective)
print("diffuse", ot.diffusive)S1 [0.32232326 0.43612355 0.00139014]
ST [0.55598414 0.44165453 0.24129711]
mu* [ 8.70476 15.02531 6.620432]
advect [0.15640084 0.21748434 0.00031262]
diffuse [0.0452387 0.05261128 0.09583226]Four readings of the same model:
- Sobol' says
owns 0.1% of the variance alone and 24% once you count its interaction with . The 24-point gap is the whole story about , and only a method with a total-order index tells you it exists. - Morris ranks
, which is not the ranking . is a mean absolute slope, not a variance share, and the two top parameters swap. If you screen with Morris and then drop everything but the top parameter, you drop the wrong one here. Drop only what is near the origin of the – plot. - Optimal transport splits
's influence into 0.004 of mean shift and 0.094 of shape change. That is a quantitative statement that changes the spread of the output without moving its mean. No variance-based index says that. - The Morris measures cost zero extra model runs here:
to_morris()reinterprets the Saltelli design you already paid for. See Free screening from a Sobol' design.
Method capabilities
This table is the one place that records what each method accepts. The other pages link here instead of repeating it. tests/test_docs_matrix.py checks the Own design, Correlated, Categorical, and Bootstrap CI columns against the method registry, and checks that every Reports cell holds prose rather than a stray capability mark. It does not check the wording inside Reports, and it does not check the Comparison table below, so those stay a human's responsibility to keep in step with the code.
| Method | Reports | Own design | Correlated | Categorical | Bootstrap CI |
|---|---|---|---|---|---|
borgonovo | ✗ | ✓ § | ✓ | n_bootstrap | |
dgsm | bounds on | ✗ | ✗ | ✗ | n_bootstrap |
efast | ✓ | ✗ | ✗ | — | |
hdmr | ✗ | ✓ † | ✗ | n_bootstrap | |
hsic | dependence measure | ✗ | ✓ § | ✗ | — |
kucherenko | ✓ | ✓ | ✗ | n_bootstrap | |
morris | ✓ | ✗ | ✗ | n_bootstrap | |
optimal_transport | ✗ | ✓ § | ✓ | n_bootstrap | |
pawn | KS distance | ✗ | ✓ § | ✓ | n_bootstrap |
pce | ✗ | ✗ | ✗ | n_bootstrap | |
shapley | allocation summing to 1 | ✗ | ✗ ‡ | ✗ | n_bootstrap |
sobol | ✓ | ✗ | ✓ | n_bootstrap | |
vkoga | ✗ | ✓ | ✗ | n_bootstrap |
Own design means the method builds its own sample matrix, so you must be able to run the model at the points it chooses. The other nine are given-data methods. They accept any
Correlated and Categorical say what the method does with a problem that declares a Gaussian-copula correlation, or that declares a categorical parameter. A ✗ is a refusal, not a silent approximation. The method raises a ValueError that names the parameters and the alternatives.
Bootstrap CI gives the keyword that asks for bootstrap confidence intervals. There is one spelling, n_bootstrap, and it defaults to 0 everywhere, so you never pay for an interval you did not ask for. Passing n_bootstrap > 0 without a key raises ValueError: key is required when n_bootstrap > 0; pass key=jax.random.key(0) so the interval is reproducible. See Confidence intervals for the result.ci record that comes back with them.
Two methods have no entry in that column, and the gap is deliberate. eFAST has one search curve per parameter, so there is nothing to resample: removing a point does not shrink the sample, it changes what the estimator computes. An eFAST interval would need replicated designs with different random phase shifts, which is a change to sample(), not a keyword on analyze(). HSIC already reports permutation p_values, which is the uncertainty statement for a V-statistic; a row bootstrap would repeat rows onto the kernel diagonal, where the kernel is exactly 1, so the resampled index is biased upward by construction.
The four surrogate-backed methods, pce, hdmr, vkoga and shapley, refit their surrogate on every replicate, so an interval there costs an order of magnitude more than a row resample on a direct estimator. That is why the default is 0 and not why it is unavailable.
† HDMR handles dependence through its ANCOVA decomposition:
‡ The default backend="pce" assumes independent parameters and refuses. The table records that default. shapley.analyze(backend="hdmr") does accept a correlated problem, and with include_correlative=True it allocates the ANCOVA decomposition.
§ Correlation-inclusive: a parameter that does not enter the model, but that correlates with one that does, scores above zero. That is the correct reading of these indices, not an estimation error. Use HDMR (
Comparison table
The rest of the differences, method by method. The capability columns above are not repeated here.
| Consideration | Sobol' | HDMR | PCE | Shapley | eFAST | DGSM | Morris | HSIC | PAWN | Borgonovo delta | Optimal transport | VKOGA | Kucherenko |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Sampling requirement | Structured Saltelli design, | Any | Any | Any | Search curves, | Plain MC, | Trajectory or radial design, | Any | Any | Any | Any | Any | Conditional-copula blocks, |
| Parameter distributions ‖ | Uniform + Gaussian | Uniform + Gaussian (via CDF mapping) | Uniform + Gaussian | Uniform + Gaussian (both backends) | Uniform + Gaussian | Uniform + Gaussian | Uniform + Gaussian (truncated-quantile grid) | Uniform + Gaussian (via CDF mapping) | Uniform + Gaussian (via CDF mapping) | Any (rank-based classes; marginals not used) | Any (rank-based classes; marginals not used) | Uniform + Gaussian (via CDF mapping) | Uniform + Gaussian (latent-copula inverse CDF) |
| Output shapes | Scalar, multi-output, time-series | Scalar, multi-output, time-series | Scalar, multi-output, time-series | Scalar, multi-output, time-series (both backends) | Scalar, multi-output, time-series | Scalar, multi-output, time-series | Scalar, multi-output, time-series | Scalar, multi-output, time-series | Scalar, multi-output, time-series | Scalar, multi-output, time-series | Scalar, multi-output, time-series; joint point-cloud modes over outputs/time | Scalar, multi-output, time-series | Scalar, multi-output, time-series |
| What the numbers mean | Exact variance fractions (given enough samples) | Variance fractions from a B-spline surrogate (fit-dependent) | Variance fractions from a polynomial surrogate (fit-dependent) | Exact allocation within the fitted surrogate; depends on fit quality | Exact variance fractions (given enough samples) | Bounds on | Screening ranks ( | Dependence measure, not variance fractions | Distributional (KS) distance, not variance fractions | Distributional (L1) distance, not variance fractions | Distributional ( | Correlated and uncorrelated variance fractions from a kernel surrogate (fit-dependent) | Exact conditional-variance fractions under the declared dependence (given enough samples) |
| Second-order indices | Direct estimation from cross-matrices | From interaction component functions | Analytical from coefficients | Not available (interaction variance folded into | Not available | Not available | Not available | Not available | Not available | Not available | Not available | Not available | Not available |
| Interaction detection | Via | Via explicit interaction component functions | Via | Via the gaps | Via the gap | Not available (bounds only) | Via large | Via the Total HSIC − R2-HSIC gap | Not available (first-order only) | Not available (the | Not available (the diffusive component flags influence beyond mean shift) | Via | Via the gap |
| Reusable surrogate | No | Yes (result.predict) | Yes (result.predict) | Derived from either fitted result | No | No | No | No | No | No | No | Yes (result.predict) | No |
‖ A truncated Gaussian marginal is not special to any one row here; every method that accepts a Gaussian marginal accepts a truncated one the same way it accepts the untruncated case. DGSM is the exception worth naming: it needs the Poincaré constant of the truncated marginal, computed by a finite-element spectral solve rather than read off a closed form, so it earns its own paragraph in Poincaré constants by distribution even though the cell above says the same "Uniform + Gaussian" as its neighbours.
Four indices under dependence
There is no single generalisation of the Sobol' indices to dependent inputs. There are several, they measure different things, and they disagree on the same data. jaxgsa ships four variance-based routes. Pick by the question you are asking, not by which one is closest to hand.
| Route | What it estimates | What it needs | Ask for it when |
|---|---|---|---|
kucherenko | Its own design, | You can still run the model and you want the conditional-variance quantities with no surrogate in the chain. | |
vkoga | The same two quantities as | Any | You cannot run the model again, or you want to sweep the same data under several correlation assumptions. |
hdmr ANCOVA split | Per term, not per parameter: each component function's variance split into a structural share | Any | You want to know which interaction carries the variance, and how much of it is coupling rather than structure. |
shapley(backend="hdmr", include_correlative=True) | One allocation per parameter, summing to 1, by splitting each term's | Any | You want a single fair-share number per parameter and you accept an ANCOVA attribution. |
Three things to hold on to.
They are different estimands. A disagreement between them is not a bug in one of them. kucherenko and vkoga estimate the same pair of conditional-variance quantities and should agree up to surrogate and Monte Carlo error; the test suite pins both to the same closed-form linear-Gaussian reference. The HDMR split and the ANCOVA Shapley allocation estimate something else entirely and have no reason to match.
Only two of the four are conditional-variance indices. HDMR's
None of them is comparable to jaxgsa.sobol. sobol refuses a correlated problem, and it is right to. Under dependence a first-order index that includes coupling is a different number from one that does not, so a kucherenko sobol
For the distribution-based alternatives, which never assumed independence in the first place, see optimal transport, Borgonovo delta, HSIC, and PAWN. Correlated Inputs works one model through several of these routes side by side.
Background: variance-based sensitivity analysis
Why global sensitivity analysis?
Local sensitivity methods, such as partial derivatives at a nominal point, describe the model at one location. Global sensitivity analysis explores the entire parameter space instead. This matters for non-linear models, where interactions and non-monotonic responses mean a gradient at one point can be misleading. GSA quantifies each parameter's contribution to output uncertainty across the whole parameter domain.
In practice, GSA serves several roles:
- Parameter identifiability: parameters with near-zero sensitivity across all outputs are effectively unidentifiable from data and may need to be fixed rather than estimated; high-sensitivity parameters are the ones data can constrain.
- Experimental design: for time-series outputs, watching sensitivity indices evolve over time helps pick measurement times when outputs are most informative about the parameters of interest.
- Model simplification: if interaction indices are negligible, the model response is approximately additive, and simpler surrogate models may suffice.
The Hoeffding–Sobol' decomposition
The theoretical foundation of variance-based GSA is the Hoeffding (ANOVA) decomposition. Any square-integrable function
where
where
Sobol' sensitivity indices
Dividing each variance component by
The first-order index
The second-order index
The total-order index
where
Sobol' indices via Saltelli sampling
Sobol' indices split the output variance into the share each parameter owns alone and the share it owns through interactions. This is the reference method and jaxgsa's default workflow: an exact, model-free variance decomposition with well-understood convergence.
Pick it when you can afford a dedicated sampling design and your parameters are independent. The method needs its own design, so you must be able to run the model at points jaxgsa chooses. jaxgsa uses the Saltelli sampling scheme (Saltelli 2002, 2010), which arranges quasi-random sample matrices so that first-order (
The Saltelli column-swap scheme
The method generates two independent scipy.stats.qmc.Sobol). For each parameter
The cost is calc_second_order=True, the default).
Estimators
The default estimator pair is estimator="saltelli-jansen": Sobol'-Mauntz for the first order, Jansen (1999) for the total order. Two reasons pick it. Jansen's total-order estimator is a mean of squares, so it can never come out negative, and users screen on
First-order, the improved form of Sobol' et al. (2007), tabulated by Saltelli et al. (2010):
Total-order, from Jansen (1999):
These two normalise by a pooled output variance computed over the concatenation of
Choosing a different estimator
jaxgsa.sobol.analyze(..., estimator=...) and jaxgsa.sobol.indices(..., estimator=...) accept six named pairs. All six converge to the same indices. They differ in how much sampling noise they carry at a small
estimator | First order | Total order | Design |
|---|---|---|---|
"saltelli-jansen" (default) | Sobol' et al. (2007) | Jansen (1999) | |
"jansen" | Jansen (1999) | Jansen (1999) | |
"janon-monod" | Monod et al. (2006), Janon et al. (2014) | same | |
"martinez" | Martinez (2011) | Martinez (2011) | |
"mauntz-kucherenko" | Sobol' et al. (2007) | Sobol' et al. (2007) | |
"azzini-rosati" | Azzini, Mara & Rosati (2021) | same |
"azzini-rosati" reads the calc_second_order=True. Asking for it on a first-order-only design raises a ValueError.
Second-order indices always use the Saltelli (2002) pairwise formula. Only the
Which one to use
The defaults were measured, not assumed. On Ishigami and Sobol-G against their analytical indices, over 100 seeds per point, with each estimator given the design it actually needs so the model-run budget is comparable:
- For the
design, "saltelli-jansen"is the best or joint-best choice at every budget tested. Its first-order formula ties with"mauntz-kucherenko"and beats"jansen","janon-monod"and"martinez"by about a factor of two on Sobol-G, where four parameters are nearly inert. Its total-order formula is the best or joint-best everywhere;"mauntz-kucherenko"'s total order is the worst of the menu, by up to a factor of three at a small. "azzini-rosati"is the better choice when your budget is tight, when you have many parameters, or when you are already paying for thedesign. On Sobol-G at 640 model runs its error is 0.029 against 0.087 for the default, and it is the only estimator that cannot report . Its advantage narrows as the budget grows, and on Ishigami above roughly 5000 runs the default overtakes it, because the cheaper design buys twice as many base points and the quasi-random design converges faster than . "janon-monod"and"martinez"give nearly identical numbers. Pick"martinez"if you want the point estimate that OpenTURNS'MartinezSensitivityAlgorithmcomputes, which jaxgsa reproduces to machine precision.
Negative index estimates
A first-order estimate can come out below zero, whichever estimator you choose. That is expected, and it is not a sign that the sample is too small.
Every first-order formula here is a difference of two correlated Monte Carlo estimates. The difference is unbiased but noisy, so when the true index is near zero the sampling error is bigger than the index and about half the estimates land below it. Owen (2013) states the mechanism: the cross-moment form "has very large variance when
Note the limit of that last point. Only the total order of "saltelli-jansen" and "jansen" is a bare sum of squares, and only it is guaranteed non-negative. A Jansen first-order estimate is one minus such a term, so it is bounded above by 1 and free to go below zero. Measured on Sobol-G over 40 seeds at base_n 64/256/1024/4096, the default estimator's negative-"azzini-rosati"'s is 0% throughout. The rate falls as "azzini-rosati" by construction (see the table above).
So read a negative value as "the interval covers zero", and turn on the bootstrap (n_bootstrap, with a key) to see that directly. Investigate only if the value is large, if it appears for a parameter whose index is demonstrably not near zero, or if it grows with
jaxgsa does not clip. Clipping to zero is a display choice, and it must never be done before ranking: it biases upward in exactly the near-zero regime where the ranking decision is being made.
How to use it
jaxgsa.sobol.sample()generates the Sobol' quasi-random sequence and builds the Saltelli cross-matrices. Duplicate rows are removed so your model only evaluates unique sample points.- You evaluate your model on
sampling_result.samples. jaxgsa.sobol.analyze()reconstructs the Saltelli layout internally and computes all indices in a singlejit(vmap(...))pass.
jaxgsa.sobol.analyze() always standardizes each output slice over the sample axis before it computes the estimators. The Saltelli/Sobol'-Mauntz n_bootstrap > 0, with a key), ci_method="quantile" reports percentile bootstrap bounds and ci_method="gaussian" reports symmetric bounds from the bootstrap standard deviation. Either way, jaxgsa returns explicit lower/upper endpoint arrays rather than SALib's symmetric confidence widths.
Index summary
| Index | Meaning |
|---|---|
| Fraction of output variance due to parameter | |
Fraction of output variance due to parameter "azzini-rosati" enforces it sample-wise, the others do not and can print | |
| Fraction of output variance due to the pairwise interaction between |
When to use it
- You can afford the structured Saltelli design:
evaluations for first-order and total-order only, or with second-order (the default) - You want an exact, model-free variance decomposition
- Your parameters are independent
If you can run the model and your parameters are independent, this is the first thing to try. It is the only method here that gives you
When it is the wrong choice
- Your parameters are correlated.
analyzerefuses, and it is right to. Use Kucherenko if you can still run the model, VKOGA if you cannot. - You cannot choose the sample points. No amount of existing
data can be reshaped into a Saltelli design. Go to PCE, HDMR or Borgonovo delta. - Your budget is under about
runs. At that size the estimates carry more sampling noise than signal, and you are better off screening with Morris at and coming back once you have fixed the inert parameters. - Variance is the wrong summary. A bimodal or heavy-tailed output makes
a poor denominator. The indices are still correct; they just answer a question you did not mean to ask. Use optimal transport or Borgonovo delta. - You only need a ranking. Second-order indices cost you
extra columns of design. Pass calc_second_order=Falseand nearly halve the bill.
RS-HDMR (Random Sampling High-Dimensional Model Representation)
RS-HDMR is a variance-based method that works from data you already have. It fits a B-spline surrogate to any set of
Pick it in three situations. Model runs are expensive and you want to reuse existing data. Your parameters may be correlated. Or you also want a fast emulator of the model. No sampling design is required.
Theoretical background
High-Dimensional Model Representation (HDMR) exploits the observation that, for many practical problems, only the low-order interactions among parameters significantly influence the output. The RS-HDMR variant constructs component functions from randomly sampled parameter and output data, rather than requiring structured grids. The model is decomposed as:
where each component function is expanded in a B-spline basis with Tikhonov regularisation. Only the first-order terms
ANCOVA decomposition
The classical Sobol' decomposition assumes independent parameters. RS-HDMR instead uses an ANCOVA (analysis of covariance) decomposition, which separates each component's variance into two parts:
- Structural variance (
): the contribution that would remain if all parameters were independent. It is the analogue of the classical Sobol' index. - Correlative variance (
): the additional contribution arising from correlations between parameters.
This distinction matters because many real-world models have correlated parameters, for example coupled physical parameters. Conflating structural and correlative contributions can produce misleading sensitivity rankings.
How to use it
- You provide any set of
pairs. No sampling design required. jaxgsa.hdmr.analyze()maps parameters tovia their marginal CDFs, builds B-spline basis matrices, backfits the first-order component functions with Tikhonov regularisation, and fits every higher-order component in one ridge solve each. - The ANCOVA decomposition splits each component's variance into structural (
) and correlative ( ) parts. Total-order indices ( ) sum contributions from all terms involving a given parameter.
The surrogate is trained on the outputs you supply. result.predict(...) and result.rmse are on that same scale. There is no inverse transform.
Index summary
| Index | Meaning |
|---|---|
| Structural (uncorrelated) variance contribution of term | |
| Correlative variance contribution of term | |
| Total contribution per term: | |
| SCSA total per parameter: |
HDMR's total under correlated inputs
With correlated parameters, HDMR's
It can be negative, because ST = [0.398, 0.397, 0.207]. The true conditional-variance totals are
The source paper invites the confusion. Its Eq. (4) uses the symbol
Li et al. also attach a precondition to the totals. They are reliable only when the per-term analyze reads that sum and warns when it falls below 0.5 or rises above 1.3 on any output slice, so a decomposition that never captured the model says so before you rank anything.
When you need a conditional-variance total under dependence, use Kucherenko (jaxgsa.hdmr.analyze() emits one JaxgsaWarning on a correlated problem to say all of this.
When to use it
- Model evaluations are expensive and you want to reuse existing runs
- Parameters may be correlated, and you want the per-term structural (
) versus correlative ( ) split. Read with care under dependence. See the note above - You need a surrogate for fast prediction at new parameter values (
result.predict)
When it is the wrong choice
- You want a total-order index under dependence. Read the warning above; HDMR's
is a different quantity. Use Kucherenko or VKOGA. - Your model is smooth and you only want
/ / . PCE fits in one linear solve and reads the indices off the coefficients. HDMR backfits only its first-order components, up to maxiter=100sweeps with an early stop once the coefficients settle (relative to their own scale); every higher-order component is a single ridge solve, no backfitting. That gives you the same numbers with more knobs to get wrong. Reach for HDMR when you specifically want the per-term/ split, or when the response has kinks a polynomial cannot follow. result.S.sum()is far from 1. That is unexplained variance, and every index derived from the fit inherits it.analyzewarns about it. Raisemaxorderorm, or accept that this model does not decompose into low-order terms.- Any of your parameters is categorical. HDMR raises. Use Sobol', Borgonovo delta, optimal transport or PAWN.
PCE (Polynomial Chaos Expansion)
PCE is the second surrogate-based route to Sobol indices that works from data you already have. It fits an orthogonal polynomial surrogate to
Pick it when your model is smooth. Any set of
How to use it
- You provide any set of
pairs; Ymay be scalar(N,), multi-output(N, K), or time-series(N, T, K). All output slices share one polynomial basis and are fitted in a single solve. jaxgsa.pce.analyze()maps parameters to the appropriate reference domain, builds the design matrix from a total-degree multi-index, and fits coefficients via regularized least squares.- Sobol indices (
, , ) are computed analytically from the squared coefficients. - Leave-one-out cross-validation RMSE quantifies surrogate accuracy.
Index summary
| Index | Meaning |
|---|---|
| First-order Sobol index for parameter | |
| Total-order Sobol index for parameter | |
| Second-order Sobol index for the pair | |
| Leave-one-out RMSE | Cross-validation error of the fitted surrogate. A fit-quality diagnostic, not a per-parameter index. |
explained_variance | Coefficient of determination of the fit: the sample variance of the fitted values over the sample variance of |
Check the fit before you read the indices
The indices are exact within the fitted polynomial. If the polynomial is wrong, they are exactly wrong. order defaults to 3, and 3 is not enough for anything with a strong nonlinearity. Ishigami makes this concrete:
for order in (3, 6, 10):
r = jaxgsa.pce.analyze(PROBLEM, X, Y, order=order, verbose=False)
print(order, round(float(r.explained_variance), 3),
np.round(np.asarray(r.loo_rmse), 3), np.round(np.asarray(r.S1), 3))3 0.463 2.72 [0.662 0.054 0.001]
6 0.982 0.512 [0.32 0.442 0. ]
10 1.0 0.006 [0.314 0.442 0. ]Ishigami's analytical order=3 the surrogate captured 46% of the variance and reported
Two numbers decide whether to trust a PCE result, and both come back on the result. explained_variance should sit near 1. loo_rmse should be small next to Y.std(), which is 3.69 here. At order=10 both pass and
For PCE, explained_variance is a coefficient of determination: the sample variance of the fitted values over the sample variance of Y. It measures the fit in sample, so it lies in
Raising order costs basis terms, not model runs, so raise it until loo_rmse stops falling. Watch for it turning back up: that is overfitting. loo_rmse is the signal, because it is the out-of-sample number. A high explained_variance next to a loo_rmse that approaches or passes Y.std() is the overfit signature. pce.analyze warns about both failures: it fires when explained_variance drops below 0.5 on any output slice, and separately when loo_rmse passes 0.71 times std(Y), which is the same line read out of sample. A silent run means both diagnostics passed. jaxgsa.pce.indices warns about neither, because it has to stay traceable.
When to use it
- You want analytical Sobol indices without Monte Carlo sampling noise
- Your model is smooth enough to be well-approximated by low-order polynomials
- You have mixed uniform and Gaussian parameters (the Wiener-Askey scheme selects the appropriate basis automatically)
- You need a fast surrogate (
result.predictmirrors the training output layout)
When it is the wrong choice
- Your response has a discontinuity, a threshold, or a hard saturation. Polynomials ring around a step and no
orderfixes it. Use HDMR's B-splines or a non-surrogate method like Borgonovo delta. - You have fewer samples than basis terms. A total-degree basis at order
in parameters has terms: 286 at , but 3003 at . jaxgsa.pce.effective_order(problem, n_samples, order=...)tells you the order the data can actually support, capped at theorderyou pass, andanalyzedrops to it the same way.effective_ordernever exceedsorder, whose own default is 3, so a call with noorder=argument returns 3 whenever the data support at least that much, whatevern_samplesis. On Ishigami,effective_order(problem, 2000, order=10)returns 10 andeffective_order(problem, 100, order=10)returns 4. If it comes back at 1 or 2, PCE is not the method for this dataset. - Your parameters are correlated. The orthogonality that makes the coefficients readable as variances is orthogonality under the independent product measure.
analyzerefuses. Use VKOGA or Kucherenko. - You can run the model freely and want a guarantee. A converged Saltelli estimate is model-free. PCE is only ever as good as its fit, and the fit is the thing you have to defend.
Shapley effects
The Shapley effect
Pick it when you need one defensible number per parameter, for ranking, reporting, or budget allocation, rather than the two-sided
Theoretical background
For independent parameters, the Hoeffding–Sobol' decomposition splits the output variance into partial variances
so a main-effect variance
- Bracketing:
. The Shapley effect always lies between the first-order and total-order Sobol indices. - Exact partition:
omits interactions, so , and counts each interaction once per participant, so . Shapley effects split every interaction fairly and sum to exactly 1 with no gaps or double counting.
The Hoeffding decomposition above defines the backend="pce" allocation, and it holds only for independent parameters. jaxgsa.shapley.analyze(backend="pce") refuses to run when problem.correlation declares a dependence structure. For correlated parameters, use backend="hdmr" with include_correlative=True. It folds HDMR's ANCOVA decomposition into the allocation: each term's structural plus correlation-induced variance (
How jaxgsa computes them
jaxgsa computes Shapley effects analytically from a fitted surrogate's variance decomposition. There is no permutation Monte Carlo, no conditional-variance sampling, and no external shap dependency:
backend="pce"(default) fits a polynomial chaos expansion and groups the squared orthonormal coefficients by the support of their multi-index (Sudret, 2008), exact within the fitted polynomial.backend="hdmr"fits the RS-HDMR B-spline surrogate and uses the structural () variances of its component functions as the partial variances , truncated at maxorder.
Both backends accept scalar (N,), multi-output (N, K), and time-series (N, T, K) Y.
Normalization is by the surrogate's total decomposed variance backend="pce" they therefore match jaxgsa.pce.analyze exactly. For backend="hdmr" they differ from jaxgsa.hdmr.analyze, which normalizes shapley's hdmr.S1 divided by explained_variance, one shared factor per output slice. ST does not divide out as cleanly, because HDMR's hdmr.ST is close to 1 / explained_variance but varies a little per parameter.
How much of the output variance the surrogate actually captured is reported separately in the explained_variance field. It is an honest diagnostic rather than a silently renormalized result, but the two backends put a different quantity in it, so read it against the backend you ran.
backend="pce": a coefficient of determination. It is the sample variance of the fitted values over the sample variance of. It lies in and cannot exceed 1. Close to 1 means the polynomial reproduces the sample; well below 1 means it misses variance, and a JaxgsaWarningfires. It is an in-sample number, so it does not flag an overfit. For that, readloo_rmseon the PCE result: the surrogate is unreliable onceloo_rmseapproaches or passesY.std(), and jaxgsa warns on that ratio.backend="hdmr": the decomposed fraction. It is close to 1 for a good fit, below 1 when truncation or fit error leaves variance unexplained, and above 1 when an overfit surrogate over-counts shared variance. A JaxgsaWarningis emitted when it strays far from 1 in either direction.
Interactions above maxorder (HDMR) or the polynomial order (PCE) are absent from the allocation.
How to use it
- You provide any set of
pairs. No sampling design required. - Call
.shapley()on a fitted PCE or HDMR result. Each partial variance is allocated equally among the parameters in its interaction set. - The result carries
ShalongsideS1andSTcomputed from the same surrogate, so the three indices are directly comparable and the orderingis visible at a glance.
result = jaxgsa.pce.analyze(PROBLEM, X, Y, order=10, verbose=False).shapley()
print("Sh ", result.Sh)
print("sum ", result.Sh.sum())
print("S1 ", result.S1)
print("ST ", result.ST)
print("explained ", result.explained_variance)Sh [0.4357345 0.44241282 0.12185249]
sum 0.9999999
S1 [3.1388211e-01 4.4241282e-01 1.8721769e-08]
ST [0.5575869 0.44241285 0.24370503]
explained 0.9999987Read it left to right.
That order=10 is not decoration. At the default order=3 the same call gives Sh = [0.803, 0.055, 0.141] and explained_variance drops to 0.463. The Shapley effects are exact within the surrogate, and a bad surrogate gives you exact nonsense. Check explained_variance first, every time. It sits on the result for that reason, and a JaxgsaWarning fires when the fit is too poor to trust.
The HDMR backend takes its own knobs:
result_hdmr = jaxgsa.hdmr.analyze(PROBLEM, X, Y, maxorder=2, verbose=False).shapley()Backend-specific keyword arguments are not validated against the selected backend: setting a knob that belongs to the other backend (for example backend="pce" with maxorder=3) is forwarded unchanged to pce.analyze, which does not accept it, so you get its plain TypeError: analyze() got an unexpected keyword argument 'maxorder', not a jaxgsa-specific message.
Index summary
| Index | Meaning |
|---|---|
| Shapley effect: parameter | |
| First-order index from the same surrogate (main effect only). | |
| Total-order index from the same surrogate (main effect plus all interactions counted in full). | |
explained_variance | How much of the output variance the surrogate captured. A separate fit-quality diagnostic, not a per-parameter index. For backend="pce" it is the coefficient of determination of the fit, in backend="hdmr" it is the decomposed fraction |
When to use it
- You want a single, fairly allocated importance score per parameter that sums to exactly 1, for example for ranking, reporting, or budget allocation
- Interactions matter and you want them attributed to their participants rather than omitted (
) or double-counted ( ) - You have existing
pairs and want analytical indices without permutation Monte Carlo noise - Your parameters are independent (required for
backend="pce"; under a declared correlation usebackend="hdmr"withinclude_correlative=Truefor the ANCOVA-based allocation)
When it is the wrong choice
- You want to know which parameters you can fix. Shapley gives every parameter a positive share, because splitting an interaction gives a share to both participants. A parameter can be safe to fix and still carry a visible
. is the fixing measure; use it. - You want the interaction itself. Shapley dissolves interactions into the participants by design. If you need to know that
and interact rather than that they both matter, read from Sobol' or PCE, or the gap . - You cannot get the surrogate to fit. Everything here rides on
explained_variance. There is no surrogate-free route to Shapley effects in jaxgsa; the conditional-variance estimator of Song et al. (2016) is not implemented. - Your parameters are correlated and you want a rigorous answer.
backend="hdmr", include_correlative=Truegives an ANCOVA-based allocation whose correlative shares can go negative. It is a defensible reading, not the Song et al. Shapley effects, and it no longer has the "fair split" interpretation that makes the method attractive in the first place.
References
- Owen, A.B. (2014). Sobol' indices and Shapley value. SIAM/ASA Journal on Uncertainty Quantification, 2(1), 245-251.
- Song, E., Nelson, B.L. & Staum, J. (2016). Shapley effects for global sensitivity analysis: Theory and computation. SIAM/ASA Journal on Uncertainty Quantification, 4(1), 1060-1083.
- Sudret, B. (2008). Global sensitivity analysis using polynomial chaos expansions. Reliability Engineering & System Safety, 93(7), 964-979.
eFAST (extended Fourier amplitude sensitivity test)
eFAST computes the same first-order and total-order Sobol indices as the Saltelli workflow, but through a frequency-based decomposition. Instead of column-swapped sample matrices, eFAST evaluates the model along sinusoidal search curves in the parameter space. It then applies the discrete Fourier transform to extract variance contributions from the spectral content of the output.
Pick it when you need
How it works
For each parameter
The Fourier power spectrum of the output along each curve is then decomposed. The first-order index is the fraction of total variance captured by harmonics of
where
The total-order index is the complement of the low-frequency (non-focal) variance:
The low-frequency content at or below
The interference factor M (default 4) sets how many harmonics of n_per_curve, sample raises ValueError. For Ishigami (
How to use it
jaxgsa.efast.sample(problem, n_per_curve, ...)returns anEFASTSamplesdesign whosesamplesarray has shape(n_per_curve * D, D), where each contiguous block ofn_per_curverows corresponds to one parameter's search curve.- You evaluate your model on all
n_per_curve * Drows ofsamples.samples, in order. jaxgsa.efast.analyze(samples, Y)splits the output by curve, computes the Fourier spectrum for each, and extractsand indices. The interference factor Mand the problem travel inside theEFASTSamplesobject, so they can never be mismatched between sampling and analysis.
eFAST does not produce second-order (
EFASTSamples.save(path) writes the design to an NPZ file and EFASTSamples.load(path) reads it back, including the problem, M and n_per_curve. Use it when the model runs somewhere else: save the design, ship the CSV of samples.samples, and load the design back to analyze the outputs weeks later. The other three design classes have the same pair.
Ishigami at n_per_curve=2048, so 6144 model runs:
samples = jaxgsa.efast.sample(PROBLEM, 2048, seed=0, verbose=False)
result = jaxgsa.efast.analyze(samples, evaluate(jnp.asarray(samples.samples)), verbose=False)
print(result.S1, result.ST)[3.0759403e-01 4.4230729e-01 7.8303506e-09] [0.5507463 0.46289188 0.23926514]The analytical values are
Index summary
| Index | Meaning |
|---|---|
| Fraction of output variance from the focal parameter's harmonics (main effect). | |
| Total effect including interactions, computed as |
When to use it
- You only need
and (no required) - You want a simpler sampling design without the Saltelli cross-matrix structure
- You are screening a large number of parameters
- The total cost is
evaluations, which can be lower than Saltelli's (first/total only) or (with second-order, the default) when is chosen smaller than the Saltelli base count
When it is the wrong choice
Honestly, most of the time. Saltelli gives you on_invalid="drop", and supports bootstrap intervals, none of which eFAST does. Pick eFAST when the run budget is the binding constraint and you have measured that
- Any model run can fail. eFAST's design is an ordered sweep read by a Fourier transform. One
NaNand you have"raise"or"propagate"and nothing else.on_invalid="drop"raises, and says why. - You need a confidence interval. There is nothing to resample inside one search curve. An eFAST interval needs replicated designs at different random phases, which is a change to
sample(), not a keyword onanalyze(). - You need
. It cannot produce them at all. - Your parameters are correlated or categorical. eFAST refuses both.
Reference
Saltelli, A., Tarantola, S. & Chan, K.P.-S. (1999). A quantitative model-independent method for global sensitivity analysis of model output. Technometrics, 41(1), 39-56.
DGSM (derivative-based global sensitivity measures)
DGSM uses exact gradients from automatic differentiation to compute bounds on the total Sobol index
That advantage is scalar-only. Reverse mode costs one pass per output slice, so a model with jax.jacfwd when jax.jacrev otherwise. A Jacobian costs about
Pick it as a fast screening or sanity-check step before committing to a full Sobol' analysis. Use Morris (below) instead if your model is a black box. DGSM has no sampler of its own; it is a given-data method that happens to need a JAX-differentiable fn too. A plain Monte Carlo sample of jaxgsa.sampling.monte_carlo, is the usual choice, but any
The DGSM moments
For a model
The second is the mean derivative:
These moments are estimated from
Bounds on the total Sobol index
DGSM does not compute Sobol indices directly. Instead, it provides an upper bound and a lower bound on the total-order index
The Poincaré upper bound (Sobol' & Kucherenko, 2009):
where
The Kucherenko–Song lower bound (Kucherenko & Song, 2016):
When the upper and lower bounds are close, DGSM gives a tight bracket on
Poincaré constants by distribution
The Poincaré constant depends on the marginal distribution of each parameter:
| Distribution | Poincaré Constant |
|---|---|
| Uniform | |
| Gaussian | |
| Truncated Normal | Spectral solve (P1 finite-element Neumann eigenproblem) |
For truncated normal parameters, the constant is computed numerically by solving a weighted eigenproblem on a finite-element grid. jaxgsa handles this automatically when the parameter spec declares truncation bounds.
How to use it
jaxgsa.sampling.monte_carlo()generates plain Monte Carlo samples from the declared parameter distributions.- You pass your JAX-differentiable function and the samples to
jaxgsa.dgsm.analyze(). - jaxgsa differentiates it, choosing forward or reverse mode from the shapes, and derives the moments and bounds.
- The returned
DGSMResultcontainsnu,sigma,upper_bound,lower_bound, andvar_y.
fn takes one sample row, shape (D,), and returns a scalar, a (K,) vector, or a (T, K) array. jaxgsa vmaps it for you. A batch model that expects (N, D) must be wrapped:
result = jaxgsa.dgsm.analyze(PROBLEM, lambda x: model(x[None, :])[0], X, verbose=False)Passing a batch model unwrapped raises a ValueError that spells out this exact fix, so you will not be left guessing.
Alternatively, if the Jacobian has been computed externally (for a non-JAX model, say), you can pass pre-computed Y= and dfdx= arrays directly and skip fn entirely.
The bounds can be far too loose to rank with
DGSM on Ishigami, 1024 Monte Carlo points:
X = jnp.asarray(jaxgsa.sampling.monte_carlo(PROBLEM, n=1024, seed=0))
d = jaxgsa.dgsm.analyze(PROBLEM, lambda x: evaluate(x[None, :])[0], X, verbose=False)
print("lower", d.lower_bound)
print("upper", d.upper_bound)JaxgsaWarning: jaxgsa.dgsm: lower_bound is a valid lower bound on the total
Sobol index only for untruncated Gaussian marginals (Kucherenko & Song 2016,
Theorem 6, Section 4.1, eq. 31). These marginals do not meet that condition:
x1, x2, x3. For them lower_bound is an estimate, not a bound: it is exact
when the response is linear in that input, and it can exceed the true total
index when the response is curved. Confirm anything that rests on it with
jaxgsa.sobol. upper_bound is unaffected: the Poincare bound holds for every
supported marginal.
lower [0.00099489 0.0073687 0.00224933]
upper [2.3450265 7.3845625 3.10674 ]The true
The lower bound is near zero for all three parameters, and raising
The upper bound is above 1 on every parameter, which tells you nothing, since analyze warns when that happens on an output slice, because an array of plausible positive numbers reads like a ranking whether or not it constrains anything. Worse, it ranks the parameters
So DGSM is a fast way to find parameters that do nothing at all. It is not a reliable ranking of the ones that do.
Index summary
| Field | Meaning |
|---|---|
| Mean squared derivative: | |
| Mean derivative: | |
| Upper bound | Poincaré bound: |
| Lower bound | Kucherenko–Song bound: |
When to use it
- You have a JAX-differentiable model, a scalar or short output, and want fast screening without the cost of Saltelli or eFAST sampling
- You want to find the parameters with no effect at all, cheaply
- You are screening many parameters where one Jacobian beats
model runs - You want a quick sanity check before running a full Sobol analysis
When it is the wrong choice
- You need a ranking you can act on. See above. The Poincaré bound is a bound, not an index, and on Ishigami it ranks the parameters wrong.
- Your output is a long time series. At
the Jacobian costs forward passes and the cost argument for DGSM evaporates. Run Sobol'. - Your model is not monotone in the parameter. The lower bound goes to zero and only the upper bound is left, which by itself brackets
. - Your model is not JAX-differentiable. Use Morris, which is the same idea at a finite step size and needs no gradient.
- Your parameters are correlated or categorical. DGSM refuses both. A derivative with respect to an unordered level code is meaningless.
References
- Sobol', I.M. & Kucherenko, S. (2009). Derivative based global sensitivity measures and their link with global sensitivity indices. Mathematics and Computers in Simulation, 79(10), 3009-3017.
- Kucherenko, S. & Song, S. (2016). Derivative-based global sensitivity measures and their link with Sobol' sensitivity indices. In Monte Carlo and Quasi-Monte Carlo Methods (MCQMC 2014), Springer Proceedings in Mathematics & Statistics 163, 455-469. doi:10.1007/978-3-319-33507-0_23.
- Lamboni, M., Iooss, B., Popelin, A.-L. & Gamboa, F. (2013). Derivative-based global sensitivity measures: General links with Sobol' indices and numerical tests. Mathematics and Computers in Simulation, 87, 45-54.
Morris (elementary effects screening)
Morris is a global screening method. With only
Pick it as a triage step for expensive black-box models. Fix the parameters Morris rules out, then spend your remaining budget on an exact method like Sobol' for the survivors. Morris needs its own design, which jaxgsa generates.
How it works
The design consists of
where
- Trajectory design (Morris 1991, default): each trajectory is a random walk on a
-level grid ( num_levels, default 4) with the canonical step, visiting parameters in a random order. - Radial design (Campolongo et al. 2011,
method="radial"): star designs around scrambled-Sobol' base points, where each elementary effect compares a one-coordinate swap against the shared base point with a per-step.
Both uniform and Gaussian marginals are supported. The design touches the unit-cube boundaries, and an unbounded inverse CDF maps 0 and 1 to infinity. Each open side of a Gaussian marginal is therefore pulled in by truncation_quantile, default low or high is left exactly where the user put it, so a two-sided truncated Gaussian is sampled as declared. Uniform marginals are untouched, and deduplication and prefix-nesting are unaffected. The elementary-effect divisor is the step the design really takes, so this rescaling does not bias
On an unbounded marginal there is no
problem = jaxgsa.Problem.from_dict(
{"x1": {"dist": "gaussian", "mean": 0.0, "variance": 1.0}},
truncate_gaussians=1e-4, # fills low/high at this marginal's own quantiles
)The
is the mean elementary effect. Sign cancellation can mask non-monotonic influence, which is why alone is unreliable. is the mean absolute elementary effect (Campolongo et al. 2007). This is the headline importance measure. Read it as "how strongly does the output respond, on average, when this parameter moves?". It is a good proxy for the total-order index ranking. is the standard deviation of the elementary effects (ddof=1). A large relative to means the effect of parameter changes across the domain, indicating nonlinearity or interactions with other parameters.
The canonical output is the
Morris is closely related to DGSM. As
How to use it
jaxgsa.morris.sample()builds the trajectories, removes exact duplicate rows, and returns only the unique rows. Grid designs collide often in low dimensions, so this saves real model evaluations, just like Saltelli sampling.- You evaluate your model on
sampling_result.samples. jaxgsa.morris.analyze()reconstructs the expanded design internally, applies theon_invalidpolicy at trajectory granularity (see Failed model runs), and reduces one elementary effect per trajectory and parameter to, , and . Pass n_bootstrap > 0with akeyfor bootstrap confidence intervals over trajectories. Useresample_chunk_sizeto bound the peak memory of that resampling; Morris spells it that way, notslice_chunk_size.
Elementary effects are computed in unit-cube coordinates, so MorrisResult.to_physical_units() rescales to derivative-scale values in the problem's native units. That rescaling covers uniform-marginal problems only: for Gaussian marginals the inverse-CDF transform is nonlinear, so there is no single linear rescaling to fall back to, and to_physical_units() raises ValueError rather than return a number on the wrong scale. MorrisSamples.downsample() prefix-slices to fewer trajectories without re-simulation, mirroring SobolSamples.downsample().
Compared to SALib's Morris implementation, jaxgsa adds unique-row deduplication, vectorized multi-output and time-series analysis (SALib's Morris is scalar-only, so its own num_resamples bootstrap does not extend to that case), the radial design, and prefix-nested downsampling.
Free screening from a Sobol' design
A Saltelli design is already a radial Morris design. Within each base point it holds a row jaxgsa.sobol.sample draws the same sequence the same way.
Write the step as
against Morris's
SobolSamples.to_morris() performs this reinterpretation, so screening measures cost no extra model evaluations:
samples = jaxgsa.sobol.sample(PROBLEM, 8192, seed=0, verbose=False)
Y = evaluate(samples.samples)
sobol_result = jaxgsa.sobol.analyze(samples, Y, verbose=False)
morris_result = jaxgsa.morris.analyze(samples.to_morris(), Y, verbose=False)
print(sobol_result.ST)
print(morris_result.mu_star, morris_result.sigma)jaxgsa.sobol.SobolSamples.to_morris: D=3, mode=second-order, base_n=1024, blocks=1024, effects=3072, reusing n_runs=8192 existing evaluations (0 new model runs)
[0.55598414 0.44165453 0.24129711]
[ 8.70476 15.02531 6.620432] [12.5912485 20.024467 11.469142 ]to_morris() prints that line because it is worth knowing what it reused; pass verbose=False to silence it. The 3072 elementary effects came out of model runs you had already paid for.
You get one radial block per base point, so n_trajectories == base_n for both design variants. A second-order design also contains a block based at to_morris() does not use it. The reason is not that it is a duplicate. That equality holds only for additive contributions: whenever parameter
but in general it does not. Measured on Ishigami the paired effects correlate 0.50 / 1.00 / −0.06, so only base_n=128 the pooled estimator's variance ratio against the
Take care over which estimand you get. The derived design is a radial design, so it estimates jaxgsa.morris.sample defaults to method="trajectory", so compare against morris.sample(..., method="radial"), never against the default. On Ishigami at
Three further caveats:
The derived measures reuse the same model outputs as the Sobol' indices, so agreement between
and is not an independent check of either. They may also legitimately rank parameters differently, because is a mean absolute derivative, not a variance share. Saltelli takes
and from the same Sobol' row, whereas jaxgsa.morris.sample's own radial design offsets them by four draws precisely to keepaway from zero, and it raises ValueErroroutright if a step still comes out numerically zero.to_morris(), which reinterprets an existing Saltelli design instead of building a fresh one, cannot raise its way out of a bad block without discarding model runs you already paid for, so there it drops the block and warns. At the defaultscramble=Truethis is a non-issue: 0 of 65536 blocks were dropped across 8 seeds at. With scramble=Falsethe drop rate is real but falls off withbase_n: 21.9% atbase_n=64, 9.4% at 256, 2.3% at 1024, 1.2% at 4096. The survivors are a biased subsequence, givingat base_n=64againstscrambled, so reads 16% low. Keep scramble=True.For unbounded Gaussian marginals,
has no fixed scale. How far a design reaches into the tail sets the magnitude, and the Saltelli design (bounded only by the library's own support clip) and morris.samplereach different distances. Only rankings are comparable. Bound the marginals once if magnitudes must match:pythonproblem = jaxgsa.Problem.from_dict(params, truncate_gaussians=1e-4)Both sides are then genuinely bounded,
morris.sampledoes not squash them again, and the derived and native radial measures agree. The measured ratios are 0.999 (linear), 0.997 (), 0.988 ( ), 0.987 ( ), each within its own seed-to-seed spread. to_morris()warns when unbounded Gaussians are present.
The reverse derivation is impossible: a radial Morris design never evaluates the
Index summary
| Measure | Meaning |
|---|---|
| Mean elementary effect. Sign cancellation can hide non-monotonic influence. | |
| Mean absolute elementary effect. Headline importance measure; proxy for the | |
| Standard deviation of the elementary effects. Large |
When to use it
- You want a cheap screening pass before committing to a full Sobol' run
- Your model is a black box (not JAX-differentiable; otherwise consider DGSM)
- You have many parameters and a tight evaluation budget. The cost is
with typically 10-50 - You only need a ranking and an interaction flag, not exact variance fractions
When it is the wrong choice
- You want to trust the ranking of the parameters that matter.
is a mean absolute slope, and it is a proxy for the ranking, not a substitute. On Ishigami it swaps the top two: ranks first, while ranks first. Use Morris to decide what to drop, which is what it is good at, and let Sobol' rank what is left. - You want a number that means something.
is on the scale of in unit-cube coordinates. It is not a variance fraction and it does not sum to anything. to_physical_units()puts it on a derivative scale, and only for uniform marginals; it raisesValueErroron a Gaussian one rather than return something on the wrong scale. - You have unbounded Gaussian marginals.
has no fixed magnitude then: how far the design reaches into the tail sets it, and truncation_quantilesets that. Only rankings survive a change of setting. Declaretruncate_gaussians=once on theProblemif the magnitudes have to mean anything. - You already have a Saltelli design. Then Morris is free rather than cheap, via
to_morris()above, and there is no reason to run a separate design. - Your model is JAX-differentiable and the output is scalar. DGSM is the same measure at
and costs less. Take its warnings above with it. - Your parameters are correlated or categorical. Morris refuses both. A one-at-a-time step off a correlation ridge lands somewhere the model never sees.
References
- Morris, M.D. (1991). Factorial sampling plans for preliminary computational experiments. Technometrics, 33(2), 161-174.
- Campolongo, F., Cariboni, J. & Saltelli, A. (2007). An effective screening design for sensitivity analysis of large models. Environmental Modelling & Software, 22(10), 1509-1518.
- Campolongo, F., Cariboni, J. & Saltelli, A. (2011). From screening to quantitative sensitivity analysis. A unified approach. Computer Physics Communications, 182(4), 978-988.
- Jansen, M.J.W. (1999). Analysis of variance designs for model output. Computer Physics Communications, 117(1-2), 35-43.
- Saltelli, A. et al. (2008). Global Sensitivity Analysis: The Primer, ch. 3. Wiley.
HSIC (Hilbert–Schmidt Independence Criterion)
HSIC measures the statistical dependence between each parameter and the output. It captures any dependence, including nonlinear, non-monotone, and heteroscedastic effects that variance-based indices can underweight. It works in a reproducing kernel Hilbert space (RKHS), mapping parameters and outputs through Gaussian RBF kernels.
Pick it when you suspect your model's behaviour is not well summarised by variance, when your parameters may be correlated, or when you want statistical significance tests attached to the indices. Like HDMR, it works from data you already have: any set of
The HSIC dependence measure
Each parameter
where
First-order and total indices
jaxgsa reports two normalised indices per parameter.
R2-HSIC is the first-order index: the normalised dependence between parameter
Total HSIC is the analogue of a total-order index, capturing dependence carried through interactions with the other parameters:
where
Unlike Sobol indices, R2-HSIC values are individual dependence measures and do not sum to 1.
Permutation p-values
HSIC is a dependence measure rather than a variance fraction, so jaxgsa attaches a permutation test to each first-order index. The output labels are randomly shuffled n_perms times to build a null distribution of HSIC values. The p-value uses the Phipson–Smyth correction n_perms) and
How to use it
jaxgsa.sampling.monte_carlo()generates plain Monte Carlo samples. Any sampling strategy works, since no structured design is required.- You evaluate your model on the samples.
jaxgsa.hsic.analyze()transforms each parameter tovia its marginal CDF and builds the input kernel matrices with the median heuristic eagerly, then maps the indices and permutation p-values over output columns in one JIT-compiled pass. keyis required;hsic.analyze(problem, X, Y)without one raisesValueError, because the permutation test always runs and there is non_bootstrap=0equivalent that skips it.
HSIC is hsic.analyze has no batch_size and no slice_chunk_size, because there is no axis to chunk along: the kernel matrices are the computation. Reduce (Y - Y.mean(0)) / Y.std(0), which changes nothing else.
Turn on float64 before you run HSIC. The V-statistic cancels three large sums against each other, so float32 leaves about three or four correct digits, and the index changes with the order of the sample rows. analyze warns about this. Small indices and close rankings are not reliable without it.
import jax
jax.config.update("jax_enable_x64", True) # before the analysisThe bandwidth is a real choice
bandwidth (default 1.0) multiplies the median-heuristic length scale. It is not a tuning detail. On Ishigami with 2000 samples in float64, sweeping it changes which parameter comes first:
bandwidth | R2-HSIC |
|---|---|
| 0.25 | [0.058, 0.111, 0.025] |
| 0.5 | [0.085, 0.070, 0.028] |
| 1.0 | [0.135, 0.008, 0.025] |
| 2.0 | [0.177, 0.002, 0.009] |
At 0.25 the ranking is
So sweep bandwidth before you report an HSIC ranking, and say which value you used. A single HSIC number without its bandwidth is not reproducible. The result carries bandwidth and n_perms for that reason, and to_dataset() writes both into the dataset attributes.
Index summary
| Index | Meaning |
|---|---|
| Normalised first-order kernel dependence between parameter | |
| Total HSIC | Total dependence of parameter |
| p-value | Permutation p-value for the first-order dependence (Phipson–Smyth corrected). |
When to use it
- You want a measure that captures any dependence, nonlinear, non-monotone or heteroscedastic, and not only variance contributions
- Your parameters may be correlated (HSIC makes no independence assumption)
- You have existing
pairs and want indices without additional model runs - You want statistical significance testing via permutation p-values
When it is the wrong choice
- You want a number to report. R2-HSIC has no units, does not sum to 1, and moves with the bandwidth. What it answers well is "is this parameter doing anything at all", via the p-value. For a magnitude, use optimal transport or Borgonovo delta, which are both on a fixed
scale. is above about 20000. The kernel matrices are and there are of them. At and that is 11 matrices of 3.2 GB each in float64, so 35 GB. Nothing chunks it. - You want a confidence interval. There is none, deliberately. A row bootstrap repeats rows onto the kernel diagonal where the kernel is exactly 1, which biases the resampled index upward by construction. The permutation p-values are the uncertainty statement.
- Any of your parameters is categorical. HSIC refuses: the RBF kernel would read the level codes as distances.
- You want interaction attribution. The Total HSIC minus R2-HSIC gap says interactions exist, not which pairs. No
.
References
- Gretton, A., Bousquet, O., Smola, A. & Schölkopf, B. (2005). Measuring statistical dependence with Hilbert-Schmidt norms. In Algorithmic Learning Theory (ALT 2005), LNCS 3734, 63-77. This is the source of the
tr(KHLH)/n^2estimator jaxgsa implements. - Da Veiga, S. (2015). Global sensitivity analysis with dependence measures. Journal of Statistical Computation and Simulation, 85(7), 1283-1305. doi:10.1080/00949655.2014.945932.
- Larsen, K. & Alexanderian, A. (2026). A new kernel-based approach for the global sensitivity analysis of models with correlated inputs. arXiv preprint arXiv:2603.00849. Definition 9 gives
; Eq. 32 gives the total index used above.
PAWN (CDF-based sensitivity)
PAWN asks a different question from the variance-based methods. Not "how much variance does this parameter explain?", but "how much does the entire output distribution shift when this parameter is held fixed?". It compares the unconditional output CDF against conditional CDFs obtained by fixing each parameter within a bin, using the Kolmogorov–Smirnov (KS) distance as the measure of separation (Pianosi & Wagener, 2015).
Pick it when you care about tails, skewness, or other distributional features that variance misses. Like HSIC and HDMR, it works from data you already have: any
The KS distance
For parameter n_bins equal-width bins. Because the mapping is the marginal's own CDF, the bins are equal-probability on the parameter's original scale, whatever its marginal shape. Within each bin
A large KS value in a bin means fixing
Aggregating across bins
Each parameter yields one KS value per bin. The PAWN index reduces these to a single number per parameter using one of three statistics:
- median (default). Robust to a single anomalous bin.
- max. The worst-case shift across the parameter range.
- mean. The average shift.
The PAWN index is built on CDFs rather than moments, so it is moment-independent and invariant under monotone transformations of the output. It captures tail and skewness changes that variance-based indices miss.
How to use it
jaxgsa.sampling.monte_carlo()generates plain Monte Carlo samples (Monte Carlo, Latin Hypercube, or Sobol sequences all work; no structured design required).- You evaluate your model on the samples.
jaxgsa.pawn.analyze()maps each parameter to, assigns samples to bins, and computes the per-bin KS distances and their aggregate in a single JIT-compiled pass. Pass n_bootstrap > 0for bootstrap confidence intervals.
The number of bins (n_bins, default 10) trades conditioning resolution against sample density per bin. With very few samples per bin the KS statistic becomes noisy, so increase n_bins.
result.n_valid_bins tells you whether that happened. It counts, per parameter, the bins that held at least 2 samples. Bins below that are dropped, and the median, max and mean run over what is left. When a parameter keeps fewer than half its bins, analyze warns. Because the bins are equal-probability on the marginal's own CDF, a skewed marginal does not by itself starve a tail bin; jaxgsa has no built-in lognormal marginal, and every continuous marginal it does support (uniform, Gaussian, truncated Gaussian) gets the same equal-probability treatment. What empties a bin is a small N, a large n_bins, or samples that land outside the declared marginal (dropped with a -1 sentinel, the same as a NaN).
result = jaxgsa.pawn.analyze(PROBLEM, X, Y, verbose=False)
print(result.pawn, result.n_valid_bins)[0.2484047 0.402167 0.08681974] [10 10 10]All 10 bins survived for all three Ishigami parameters, so those indices stand on the full sample.
Categorical parameters are supported. A categorical parameter needs no binning: its level code already names the conditioning class, so PAWN uses one bin per level and n_bins does not apply to it. Bins with too few samples yield NaN, and the median, max, and mean over bins all drop them. The index is therefore unchanged by the order of the level codes. Relabel the levels and you get the same number.
Index summary
| Index | Meaning |
|---|---|
| PAWN | Aggregated (median / max / mean) KS distance between the unconditional and conditional output CDFs for parameter |
When to use it
- You care about distributional changes beyond variance, such as tail behaviour or skewness shifts
- You want a moment-independent index, invariant under monotone output transforms
- You have existing
pairs from any sampling strategy - Your parameters may be correlated (no independence assumption or structured design)
- Some of your parameters are categorical, or your output is discrete. PAWN needs neither an ordering on the parameters nor a density on the output
When it is the wrong choice
- You want to compare parameters on a meaningful scale. The KS distance is bounded in
, but the aggregate over bins is a summary statistic and not a share of anything. On Ishigami, PAWN gives where gives and OT gives . They rank the same but the spacings differ, and none of them is "the" answer. - You want interactions. PAWN conditions on one parameter at a time and stops there. No total-order equivalent, no
. - Your marginals are skewed and
is small. Equal-width bins go empty in the tail. n_valid_binstells you; a parameter down to 3 or 4 bins has a median over 3 or 4 numbers. - The KS statistic is the wrong summary for your question. It is a supremum, so it reacts to the single largest gap between two CDFs and ignores everything else. If a parameter shifts the whole distribution a little,
or the OT index sees more of it. If a parameter moves one part of the range a lot, PAWN is the sharper instrument.
Reference
Pianosi, F. & Wagener, T. (2015). A simple and efficient method for global sensitivity analysis based on cumulative distribution functions. Environmental Modelling & Software, 67, 1-11.
Pianosi, F. & Wagener, T. (2018). Distribution-based sensitivity analysis from a generic input-output sample. Environmental Modelling & Software, 108, 197-207. The 2015 paper introduces the index; the estimator implemented here — bins built on a generic sample and each bin's KS distance measured against the whole-sample unconditional CDF — is theirs.
Borgonovo delta (density-based sensitivity)
Borgonovo's
It is the second moment-independent method in jaxgsa, and the natural companion to PAWN. PAWN summarises a distributional shift by the largest gap between CDFs; SALib.analyze.delta.
The index is
How it works
jaxgsa implements the given-data estimator of Plischke, Borgonovo & Smith (2013):
- For each parameter, the samples are ordered by that parameter's rank and split into
equal-frequency classes. By default follows the Plischke sample-size heuristic (roughly , at most 48 classes); override it with n_classes. Categorical parameters instead get one class per level (n_classesdoes not apply to them), so the index never depends on the arbitrary code order. - The unconditional density
and each class-conditional density are estimated by Gaussian KDE with Silverman bandwidths on a fixed grid of grid_sizepoints spanning. - The L1 distances are integrated with the trapezoid rule and averaged with class weights, giving the plug-in estimate
The plug-in estimate is biased upward at finite bias_correct defaults to None, which means "correct if you are bootstrapping anyway": with n_bootstrap > 0 jaxgsa applies Plischke's bias reduction bias_correct=True to keep the correction and silence the warning, or bias_correct=False to keep the intervals and report the uncorrected estimate.
The correction subtracts a bootstrap mean from twice the plug-in estimate, so the reported
The same class partition also yields the given-data first-order Sobol index (variance of the class means over the total variance) at negligible extra cost, so every analysis returns both
result = jaxgsa.borgonovo.analyze(
PROBLEM, X, Y, n_bootstrap=100, key=jax.random.key(0), verbose=False
)
print(result.delta)
print(result.S1)[0.21102615 0.33395138 0.15578218]
[0.30567423 0.42081362 0.00262259]The estimator matches SALib.analyze.delta on the equal-frequency rank partition, the class-count heuristic, the Silverman KDE factors, and the 100-point output grid. It differs in three ways. The central estimate is computed on the original sample, so it is deterministic given the data, where SALib evaluates it on a random resample. A constant output column yields LinAlgError.
How to use it
jaxgsa.sampling.monte_carlo()generates plain Monte Carlo samples (any sampling strategy works; no structured design is required).- You evaluate your model on the samples.
jaxgsa.borgonovo.analyze()partitions each parameter into rank classes and computes, , and their bootstrap intervals in a single JIT-compiled kernel, vmapped over output columns and scanned over bootstrap replicates.
Set n_bootstrap=0 to skip bias correction and confidence intervals (raw plug-in estimate), or bias_correct=False to keep the intervals but report the uncorrected estimate. Peak memory is dominated by the class layout, about slice_chunk_size * D * N for continuous parameters (more for an imbalanced categorical one, which pads every level up to the largest). The output grid itself is evaluated in tiles rather than held whole, so it does not multiply into that figure, and slice_chunk_size is rarely the knob that saves memory for a large grid_size.
Continuous outputs only
The borgonovo.analyze checks the output first and raises ValueError when a column takes at most 20 distinct values and each value repeats at least 5 times on average (equivalently, at most 20% of the samples are distinct). Use optimal transport or PAWN for a discrete output: both compare empirical distributions and need no density. A constant column is exempt, because its exact answer is
analyze raises ValueError. The message names the parameter, reports what the kernel did to the offending class, and points at the knob that applies to that case. The value is never clipped, because a clipped value looks plausible and is still wrong. A confidence bound outside the range only warns: the point estimate is the contract, and the interval is a diagnostic.
Two settings control how a near-degenerate conditioning class is treated. degenerate_tol says when a class counts as degenerate. degenerate_bandwidth says how wide a kernel such a class is given.
degenerate_bandwidth="auto", the default, floors the kernel at max(0.1 * h_full, grid_step), so it never goes below what the output grid can integrate. A float is a fraction of the full-sample bandwidth and is applied exactly.
analyze does not refuse a degenerate_bandwidth on the setting alone, because the setting alone does not say whether the run works. Two conditions have to hold first. The floor only ever reaches a class the estimator already called degenerate, so on data with no such class the setting changes nothing at any value. And even on a degenerate class, a kernel narrower than one grid step only aliases if a grid point lands on the narrow peak. On one test problem with a genuine point mass, a floor of 0.01 of the full-sample bandwidth, a tenth of one grid step, still returns a
analyze therefore checks the returned grid_size, because a finer grid always shortens the step. What else it names depends on what happened during the run:
| What happened | The message also names |
|---|---|
| No class was floored | degenerate_tol. The floor changed nothing, so the tolerance is what kept it away. |
A class was floored by an explicit degenerate_bandwidth | That floor width, one grid step, and the fraction of the full-sample bandwidth equal to one grid step. |
A class was floored by the "auto" default | Nothing further. The "auto" floor is already at least one grid step wide by construction, so grid_size is the whole of the advice. |
The value itself is never clipped, for the same reason as above.
Raising degenerate_tol does not raise. A higher tolerance calls more classes degenerate, and each of those is then given the floor. When the floor is narrower than a class's own bandwidth, that class gets a narrower kernel than it had, which biases analyze does warn when this happens: the floor-width warning quoted above fires on the run that triggered the floor, whatever set degenerate_tol to call that class degenerate. On degenerate_tol=0.5 floors a class of x2 that the default tolerance left alone, warns, and moves delta[0] from 0.901 to 0.884. What analyze does not do is judge the size of the bias from the setting alone: the warning names the mechanism, not the resulting error, because that depends on the data inside the kernel.
Index summary
| Index | Meaning |
|---|---|
| Expected L1 distance between the unconditional and conditional output densities for parameter | |
| Given-data first-order Sobol index from the same class partition. The variance-based view of the same conditioning, for comparison at no extra cost. |
When to use it
- You care about influence on the whole output distribution, so tails, skewness and multimodality, not only variance
- You want a moment-independent index with a fixed
scale, invariant under monotone output transforms - You have existing
pairs from any sampling strategy, possibly with correlated parameters - You use
SALib.analyze.deltaand want a deterministic, JIT-compiled equivalent that also handles multi-output and time-seriesY
When it is the wrong choice
- Your output is discrete.
analyzeraises. It compares kernel density estimates, and a discrete output has atoms no grid resolves. The check fires when a column takes at most 20 distinct values and each value repeats at least 5 times on average. Use PAWN or optimal transport, which compare empirical distributions and need no density. - You need interactions or a total-order index.
conditions on one parameter. The gap tells you influence exists beyond the first-order variance, and nothing more. - You want to separate direct influence from correlation-borne influence.
is correlation-inclusive: a parameter the model never reads scores above zero when it correlates with one the model does read. That is the correct reading of the index, not an error. Use VKOGA or Kucherenko for the split. - Your output has a point mass or a hard bound. The KDE has to be told what to do with a near-degenerate conditioning class; see
degenerate_tolanddegenerate_bandwidthbelow. OT handles the same data with no bandwidth at all. is small. The plug-in estimate is biased upward and the bias correction can push weak parameters below zero. Both are visible, neither is comfortable. Below about 500 samples, read the ranking and ignore the magnitudes.
Reference
- Borgonovo, E. (2007). A new uncertainty importance measure. Reliability Engineering & System Safety, 92(6), 771-784.
- Plischke, E., Borgonovo, E. & Smith, C.L. (2013). Global sensitivity measures from given data. European Journal of Operational Research, 226(3), 536-550.
Optimal transport (Wasserstein-based sensitivity)
The optimal-transport index (Borgonovo, Figalli, Plischke & Savaré, 2024) measures how far knowing a parameter moves the whole output distribution. It uses the squared 2-Wasserstein distance, which is the minimal quadratic work needed to transport the unconditional output distribution onto the conditional one:
The denominator is the theoretical maximum of the numerator, so
- advective, the class-averaged squared shift of the conditional mean, which is half the given-data first-order Sobol index up to a finite-sample factor (
), and - diffusive, the remainder: changes in spread, tails, and shape.
So the OT index subsumes the variance-based first-order view and quantifies what lies beyond it, on one scale. It works from data you already have: any
How it works
- For each parameter, samples are split into
n_partitionsequal-frequency classes by the parameter's rank (defaultmin(25, N // 2)). Rank-based conditioning is distribution-free: uniform, Gaussian, or mixed marginals work unchanged, and monotone parameter transforms change nothing. Correlated parameters are supported, and the index then measures total, correlation-inclusive influence. Categorical parameters instead get one class per level (n_partitionsdoes not apply to them), so the index never depends on the arbitrary code order. - Per class,
between the conditional and unconditional output samples is computed. In the default mode="univariate"(per output column) this uses the closed form of 1-D optimal transport: both empirical quantile functions evaluated at theuniform mass points via sorting, no iterative solver. The "multivariate"and"trajectory"modes treat the output vector as a point cloud and solve entropic transport with a pure-JAX log-domain Sinkhorn solver (regularizationepsilon, reported cost is the unregularized). - Class results are averaged with class-size weights and divided by
(point-cloud modes: , with per-column standardization on by default so no output dominates through its units).
Entropic and finite-sample bias keep point-cloud-mode indices of irrelevant parameters strictly positive. Pass dummy=True (with a key) to run a synthetic, provably independent parameter through the same estimator. Its index comes back as ot_dummy, the irrelevance floor, and above_dummy is max(ot - ot_dummy, 0) computed for you.
The split, on real numbers
Ishigami, 4000 samples:
result = jaxgsa.optimal_transport.analyze(
PROBLEM, X, Y, dummy=True, key=jax.random.key(0), verbose=False
)
print("ot ", result.ot)
print("advective ", result.advective)
print("diffusive ", result.diffusive)
print("S1 ", result.S1)
print("ot_dummy ", result.ot_dummy)
print("above_dummy", result.above_dummy)ot [0.20130877 0.27754727 0.09772307]
advective [0.15357937 0.21982558 0.00371554]
diffusive [0.04772939 0.0577217 0.09400754]
S1 [0.30723557 0.43976113 0.00743294]
ot_dummy [0.00946424 0.00946424 0.00946424]
above_dummy [0.19184452 0.26808304 0.08825883]Three things to read off it.
S1 is advective S1 and 2 * advective agree to within that correction, not bit for bit.
ot_dummy is a per-parameter array, one permutation floor per column. All three read 0.0095 here because every Ishigami parameter shares the same continuous marginal and class count. The 0.098 for "univariate" mode the floor is small; in the point-cloud modes it is not, and dummy=True stops being optional there.
How to use it
jaxgsa.sampling.monte_carlo()or any existingdata. No structured design required. jaxgsa.optimal_transport.analyze()computesot,advective, anddiffusiveper parameter (and per output column in"univariate"mode), with an optional row bootstrap for confidence intervals.
Pick the mode by the question: "univariate" for per-column indices across (N,)/(N, K)/(N, T, K) outputs, "multivariate" for one index per parameter over the flattened joint output, "trajectory" for one index per parameter per output over the whole time course. The point-cloud modes solve one entropic transport problem per parameter, per class, per replicate, and per point cloud. For continuous parameters that is (n_bootstrap + 1) * D * n_partitions solves, and dummy=True adds one more single-replicate pass of n_partitions solves. Both figures multiply by the output count K in "trajectory" mode, which builds one cloud per output; "multivariate" mode builds one cloud in total. A categorical parameter costs its own level count instead of n_partitions, and adds its own dummy pass. Keep it modest.
Index summary
| Index | Meaning |
|---|---|
ot) | Normalized expected |
advective | Mean-shift component; |
diffusive | Spread/shape component, ot - advective; flags influence invisible to the conditional mean. |
S1 | The given-data first-order Sobol index, advective up to the |
ot_dummy | Per-parameter index of a synthetic independent column, permuted against that parameter's own partition (with dummy=True). The irrelevance floor. None otherwise. |
above_dummy | max(ot - ot_dummy, 0), the index with the floor subtracted. None unless you passed dummy=True. |
Valid under correlated inputs
The OT index is valid under correlated parameters, and jaxgsa certifies it. optimal_transport.analyze accepts a problem with a declared problem.correlation, because it is exempt from the correlated-input error. The definition
Read the index as total, correlation-inclusive influence. A parameter the model never uses still gets a clearly non-zero index when it is correlated with one the model does use (tested at
When to use it
- You want a moment-independent index that still ties exactly to the variance-based world
- You want to distinguish parameters that move the output from parameters that reshape it
- You want one index per parameter for a whole trajectory or multivariate output (
multivariate/trajectorymodes) - Your parameters have mixed marginals or are correlated
If you have
When it is the wrong choice
- You need interactions. OT conditions on one parameter at a time. The diffusive part says influence exists beyond the mean shift; it does not say which parameter it is shared with. No
, no total order. - You need to separate direct from correlation-borne influence. The index is correlation-inclusive by construction. Use VKOGA or Kucherenko.
- You are in a point-cloud mode without a dummy.
"multivariate"and"trajectory"solve entropic transport, and the entropic bias keeps irrelevant parameters visibly above zero. Reading those indices withoutdummy=Truewill make you believe in parameters that do nothing, which is whyanalyzewarns when either mode runs without one. - You are bootstrapping a point-cloud mode. The bill is
(n_bootstrap + 1) * D * n_partitionsSinkhorn solves. At 100 replicates, 10 parameters and 25 partitions that is just over 25000 solves. - You want a surrogate too. OT gives you indices and nothing else. Use PCE, HDMR or VKOGA.
Reference
- Borgonovo, E., Figalli, A., Plischke, E. & Savaré, G. (2024). Global sensitivity analysis via optimal transport. Management Science, 71(5), 3809-3828 (in print 2025; online-first 2024). doi:10.1287/mnsc.2023.01796
VKOGA (correlated-input variance indices)
VKOGA reports variance-based sensitivity indices for parameters that are genuinely dependent. It separates what a parameter explains by itself from what it explains through its correlations. Apart from VKOGA and Kucherenko, every variance-based method on this page assumes independent parameters, or sidesteps the question by measuring something other than variance.
VKOGA is the given-data route of that pair. It is the surrogate-based sensitivity analysis (SSA) of Hilhorst, Quicken, van de Vosse & Huberts (2024), which computes the correlated variance-based indices of Li et al. (2010), five of them. Pick it when your parameters are dependent and you still want variance fractions, not a distributional distance. Any set of
The method runs in two stages, and the split is the whole point. The indices need nested conditional sampling. That is hopeless against an expensive model, but trivial against a cheap emulator:
- Fit a VKOGA surrogate (Vectorial Kernel Orthogonal Greedy Algorithm; the greedy fit itself follows De Marchi, Schaback & Wendland's P-greedy, 2005) to the given
data. It uses a Gaussian RBF kernel, with centres chosen one at a time at the maximiser of the power function (P-greedy) expressed in a nested Newton basis, and coefficients from an RKHS-regularised least-squares solve. Which point is picked next depends only on , so all output slices share one basis. How many get picked does not: the greedy loop stops on a residual tolerance measured against , so the final centre count also depends on the fit quality. That is the "vectorial" part: one shared basis, not a target-independent one. gammaandridgeare chosen by k-fold cross validation. - Estimate the indices against that surrogate by quasi-Monte-Carlo, with a Gaussian copula supplying the dependency structure. The copula keeps each parameter's declared marginal exactly as written and adds a rank-correlation structure on top. All conditioning happens in the latent standard-normal space, where the conditionals are closed-form, so no iterative sampler is involved.
The five indices
When parameters are dependent the Sobol' decomposition no longer holds. The conditional-variance quantities remain perfectly well defined, though. They simply change connotation, and split into correlated and uncorrelated halves:
These are the same two formulas as
(total correlated) answers "what should I measure more accurately?", the parameter prioritisation setting. It counts everything explains, including variance it only explains because it moves together with something else. Learning exactly removes that variance whatever put it there. (total uncorrelated) answers "what can I freeze?", the parameter fixing setting. It counts only what nothing else can account for. A parameter whose is near zero can be fixed at a nominal value, even if its is large, because its apparent influence is carried by its correlates.
Two strongly correlated parameters will both show a large
Here it is on a model simple enough to check by hand:
import jax
jax.config.update("jax_enable_x64", True)
import numpy as np
import jax.numpy as jnp
import jaxgsa
spec = {n: {"dist": "gaussian", "mean": 0.0, "variance": 1.0} for n in ("x1", "x2", "x3")}
R = np.eye(3)
R[0, 1] = R[1, 0] = 0.9
problem = jaxgsa.Problem.from_dict(spec).with_correlation(R)
X = jnp.asarray(jaxgsa.sampling.monte_carlo(jaxgsa.Problem.from_dict(spec), n=1024, seed=0))
result = jaxgsa.vkoga.analyze(
problem, X, X.sum(axis=1), gamma=0.5, ridge=1e-8, key=jax.random.key(0), verbose=False
)
print("S_TC", np.round(np.asarray(result.S_TC), 3))
print("S_TU", np.round(np.asarray(result.S_TU), 3))
print("S_U ", np.round(np.asarray(result.S_U), 3))
print("S_C ", np.round(np.asarray(result.S_C), 3))S_TC [0.74 0.74 0.22]
S_TU [0.041 0.041 0.222]
S_U [0.039 0.039 0.219]
S_C [0.7 0.701 0.001]The exact values are
Read the decision off it.
The remaining three split
| Index | Definition | Meaning |
|---|---|---|
| Total correlated: what | ||
| Total uncorrelated: what only | ||
| The contribution of | ||
| The correlation-borne contribution. It can be negative, when a correlation works against a direct effect. | ||
| Independent interactions. Zero for an additive model, non-negative always. |
The name
Under independent parameters the whole structure collapses back to the familiar one.
VKOGA or HDMR's ANCOVA split?
Both handle correlated given data, and both report a decomposition. They decompose different things, so they answer different questions.
HDMR fits an explicit additive expansion shapley(include_correlative=True).
VKOGA fits a kernel expansion, which is a sum over centres rather than over parameter subsets, so it has no term-wise structure at all. What it has instead is direct access to the conditional-variance definitions. The surrogate is cheap enough to sample
Practical guidance:
- Want the prioritise / fix distinction under dependence, with an explicit and auditable dependency structure? Use VKOGA.
- Want to know which interaction carries the variance, or a fair per-parameter allocation summing to 1? Use HDMR: its terms are labelled, and only it can produce Shapley effects.
VKOGAResult.shapley()deliberately raisesNotImplementedError. - Want to declare a dependency structure rather than infer one from the data (a copula from expert knowledge, a sensitivity sweep over
, or the same data analysed under several correlation assumptions)? Only VKOGA takes a correlation matrix as an argument; HDMR reads correlation implicitly out of whatever you hand it. - The two are complementary, not redundant: HDMR's
tells you that correlation matters, and VKOGA's gap tells you what to do about it.
How to use it
- You provide any set of
pairs. No sampling design required. jaxgsa.vkoga.analyze()maps parameters tothrough their marginal CDFs (the RBF kernel is isotropic, so every column must share a scale), centres the outputs, cross-validates gammaandridge, and fits the greedy kernel surrogate.- The same call then draws the nested conditional samples in latent copula space and returns the five indices, along with the surrogate's
n_centers,gamma,ridge, and per-slice trainingrmse.keyis required, because that draw is always a Monte-Carlo estimate:vkoga.analyze(problem, X, Y)without one raisesValueError. result.predict(X_new)reuses the fitted surrogate;result.to_dataset()exports everything, including the correlation matrix, as a labeledxarray.Dataset.
The dependency structure comes from the problem. analyze reads problem.correlation by default and falls back to independent parameters when the problem declares none. A (D, D) matrix passed as correlation= overrides the declaration for one call. To fit a matrix from observed data, use jaxgsa.sampling.fit_correlation(problem, X_data) and attach it with problem.with_correlation(...). Whichever route you choose, the matrix actually used is returned on result.correlation.
Cost is dominated by the hyperparameter search, a 10×10 grid of k-fold refits, so pass gamma and ridge explicitly to skip it once you know good values. The estimator sample sizes (n_outer, n_inner, n_variance) only ever touch the surrogate, so they are cheap to raise.
Two caveats
Train on an independent, space-filling design, even when the analysis is correlated. This is the easy way to get wrong answers. A correlated sample concentrates on a ridge through the parameter space. But
conditions on and then resamples across its whole marginal, which is precisely the off-ridge region a correlated training set never visited. A surrogate fitted there is extrapolating exactly where the estimator queries it hardest. If your data is observational and correlated, you can still fit the copula from it ( problem.with_correlation(jaxgsa.sampling.fit_correlation(problem, X))). Read, and hence , and , as carrying the surrogate's extrapolation error. Use float64. The coefficient step forms the normal matrix
, which squares the condition number of the cross kernel. For small gammathat exceeds what single precision can carry, and the surrogate can come out an order of magnitude worse than the same equations solved in double.
import jax
jax.config.update("jax_enable_x64", True) # before fittingjaxgsa.vkoga.analyze() emits a JaxgsaWarning when x64 is off. Cross validation partly self-corrects, because the scores are computed in the same arithmetic and so penalise the blown-up corner of the grid, but the ceiling is real.
When to use it
- Your parameters are correlated and you want variance fractions, not a distributional distance
- You need to separate "worth measuring" (
) from "safe to fix" ( ) - You want to state the dependency structure explicitly, or sweep over several
- You have existing
pairs and also want a fast surrogate ( result.predict)
When it is the wrong choice
- You can still run the model. Then run Kucherenko and get the same two quantities with no surrogate error in between. VKOGA is the given-data fallback, not the better estimator.
- Your training data is correlated. Caveat 1 above is the one that bites.
resamples across its whole marginal while holding the rest fixed, which is exactly the region off the correlation ridge that your training set never visited. The surrogate is extrapolating where the estimator leans on it hardest. - You want to know which interaction carries the variance. A kernel expansion sums over centres, not over parameter subsets, so there is no term to point at. No
, and VKOGAResult.shapley()raisesNotImplementedErroron purpose. Use HDMR. - You cannot enable float64. The coefficient step forms
and squares the condition number. In float32 the surrogate can come out an order of magnitude worse. analyzewarns; take the warning seriously. - Any of your parameters is categorical. VKOGA refuses. The isotropic RBF kernel would read level codes as distances.
is large and you left gammaandridgeunset. The default is a 10×10 grid of 10-fold refits, so up to 1000 solves before a single index is computed. Most of that is cheap: the greedy centre search, the expensive step, runs once per(fold, gamma)pair (100 sweeps), and only the ridge solve on top of it repeats for each of the 10ridgevalues. Setgammaandridgeonce you know good values, to skip the search entirely.
References
- Hilhorst, G., Quicken, S., van de Vosse, F.N. & Huberts, W. (2024). Efficient sensitivity analysis for biomechanical models with correlated inputs. International Journal for Numerical Methods in Biomedical Engineering, 40(2), e3797.
- Li, G., Rabitz, H., Yelvington, P.E., Oluwole, O.O., Bacon, F., Kolb, C.E. & Schoendorf, J. (2010). Global sensitivity analysis for systems with independent and/or correlated inputs. Journal of Physical Chemistry A, 114(19), 6022-6032.
- De Marchi, S., Schaback, R. & Wendland, H. (2005). Near-optimal data-independent point locations for radial basis function interpolation. Advances in Computational Mathematics, 23(3), 317-330. The centre-selection rule this module implements, P-greedy.
- Wirtz, D. & Haasdonk, B. (2013). A vectorial kernel orthogonal greedy algorithm. Dolomites Research Notes on Approximation, 6, 83-100. Their VKOGA is target-dependent (f-greedy); the "VKOGA" name is borrowed here for a target-independent P-greedy fit, correctly, with residual-based stopping only.
- Santin, G. & Haasdonk, B. (2021). Kernel methods for surrogate modeling. In Model Order Reduction, Volume 1: System- and Data-Driven Methods and Algorithms, De Gruyter, 311-354.
Kucherenko (dependent-input Sobol' indices)
Kucherenko, Tarantola & Annoni (2012) generalise the Sobol' indices to dependent parameters. They keep the two defining quantities and estimate them by direct model evaluation:
Under independent parameters these are the classic first-order and total-order Sobol' indices. Under a declared problem.correlation they keep their exact conditional-variance meaning.
This is the design-based counterpart to VKOGA: the same two quantities, but estimated on your actual model instead of a fitted surrogate. Kucherenko needs its own design, so pick it when you can still run the model and want estimates free of surrogate error. Choose VKOGA when all you have is existing
How it works
jaxgsa.kucherenko.sample(problem, n)builds a design ofrows, where is nrounded up to a power of two. It contains one joint block drawn from the full copula, then per parameter one block whereis kept and the rest is redrawn from (for ), and one block where the rest is kept and is redrawn from (for ). Both conditionals are closed-form Gaussians in the latent copula space, so no iterative sampler is involved. Under an identity correlation the design reduces exactly to the Saltelli column-swap scheme. - You evaluate your model on
samples.samples. This is the whole model cost. jaxgsa.kucherenko.analyze(samples, Y)applies the single-loop estimators: the paired product over the shared-rows for , and the Jansen squared difference over the shared- rows for . The exact formulas are stated in the jaxgsa.kucherenko._analyzemodule docstring.
kucherenko.sample reads problem.correlation and is deliberately exempt from the correlated-design error on sobol / morris / efast, because conditioning on the declared copula is the method's purpose. Categorical problems raise, since the conditional copula needs continuous marginals, as do problems with fewer than two parameters. Like the other samplers it takes seed: int | np.random.Generator | None; scramble=False together with a seed raises ValueError.
The same model as the VKOGA section,
samples = jaxgsa.kucherenko.sample(problem, 4096, seed=0, verbose=False)
print("model runs:", samples.samples.shape[0])
result = jaxgsa.kucherenko.analyze(
samples, jnp.asarray(samples.samples).sum(axis=1), verbose=False
)
print("S1", np.round(np.asarray(result.S1), 3))
print("ST", np.round(np.asarray(result.ST), 3))model runs: 28672
S1 [0.752 0.752 0.208]
ST [0.04 0.04 0.208]Both match the closed-form values to three decimals. The cost is the point:
Note
Index summary
| Index | Meaning |
|---|---|
When to use it
- Your parameters are correlated, you want
/ with their exact conditional-variance meaning, and you can still run the model - You want a design-based cross-check of a VKOGA (surrogate) analysis
- Your parameters are independent and you want the classic Sobol' indices from a conditional design (it reduces to them exactly)
When it is the wrong choice
- Your parameters are independent. The design reduces to the Saltelli column-swap scheme, so
kucherenko.S1/STestimate the same quantities assobol.S1/STforruns, but not with the same formula: Kucherenko's is the Homma–Saltelli estimator ( ), not the Sobol'-Mauntz form soboluses by default, so the two numbers agree only up to Monte-Carlo noise, not bit for bit.sobolalso gives youfor . Just run sobol. - You cannot run the model. The whole method is a design. Use VKOGA.
- You do not know the correlation matrix. The design is built from the declared copula, and a wrong copula gives you clean estimates of the wrong quantity. Fit one with
jaxgsa.sampling.fit_correlationif your data supports it, but understand that you are then assuming a Gaussian copula. - Your dependence is not a Gaussian copula. Conditioning is closed-form only in the latent normal space. A tail-dependent or non-monotone dependence is not representable here.
- Any of your parameters is categorical. It raises: the conditional copula needs continuous marginals.
- You want
or a surrogate. Neither is available.
Reference
- Kucherenko, S., Tarantola, S. & Annoni, P. (2012). Estimation of global sensitivity indices for models with dependent variables. Computer Physics Communications, 183(4), 937-946.
Output shapes
All thirteen methods share the same output contract: scalar, multi-output, and time-series outputs. The shape of Y determines the shape of all returned index arrays. Read S1 / ST as the method's per-parameter measures: mu / mu_star / sigma for Morris, and nu / sigma and the bounds for DGSM. Sobol, PCE, and HDMR (S2 and S3, from its explicit interaction terms) produce second-order indices; no other method does.
| Y shape | S1 / ST shape | S2 shape |
|---|---|---|
(N,) | (D,) | (D, D) |
(N, K) | (K, D) | (K, D, D) |
(N, T, K) | (T, K, D) | (T, K, D, D) |
D is always the last axis. Confidence interval arrays (when using bootstrap) prepend a leading dimension of 2 for [lower, upper].
How a 2-D Y is read
Shapes are taken as given. A 2-D Y is always (N, K). There is no heuristic that might read it as (N, T) instead, problem.output_names does not change the reading, and there is no shape jaxgsa will quietly transpose for you. A time series is (N, T, K), so a single time-varying output is written explicitly as (N, T, 1).
result = jaxgsa.sobol.analyze(samples, Y_2d) # Y_2d is (8192, 5)
result.S1.shape # (5, 3) -> (K, D)
result = jaxgsa.sobol.analyze(samples, Y_2d[:, :, None])
result.S1.shape # (5, 1, 3) -> (T, K, D)The index shape is the tell. (K, D) means jaxgsa read 5 separate outputs at one time step. (T, K, D) means it read 5 time steps of one output. Check it once on the first run and a transposed array cannot reach your plots.
A transposed array is caught by the row count, not repaired. Passing (5, 8192) where (8192, 5) was meant raises ValueError: Y has 5 sample rows but 8192 were expected; pass Y as (N,), (N, K), or (N, T, K).
Setting problem.output_names is the guard rail worth having. When it is present, its length must equal the trailing axis, and the mismatch is caught before any array work: output_names of length 1 against a (8192, 5) Y raises ValueError: output_names length 1 does not match the output axis K=5. A 1-D (N,) Y is one output whatever the names say.
Every warning that jaxgsa raises uses the JaxgsaWarning category. The class is a subclass of UserWarning, so a filter on UserWarning still catches it. Filter on JaxgsaWarning to select the jaxgsa warnings alone:
import warnings
from jaxgsa import JaxgsaWarning
warnings.filterwarnings("ignore", category=JaxgsaWarning)Time-series outputs are particularly useful for dynamic models. Watching the sensitivity indices evolve over time reveals which parameters dominate at different stages of a process. For example, a parameter that is highly influential early in a batch but negligible later.
Failed model runs
A model that fails on some of its runs returns NaN or Inf. Every analyze() function takes the same on_invalid keyword to say what should happen then.
| Value | What it does |
|---|---|
"raise" | Refuse the analysis. This is the default. |
"propagate" | Compute anyway, and let the non-finite value reach the indices. |
"drop" | Remove the affected data, and use what is left. |
The default refuses, because an index computed from part of a sample is a different quantity from the one you asked for, and analyze() is cheap to run again once you know which runs failed.
What "drop" removes depends on the design. A Saltelli group, a Morris trajectory and a Kucherenko base point are each read as one block, so a single bad value removes the whole block. Keeping part of a block would leave the estimator reading rows that no longer line up, and nothing would report an error. For the methods that take any (X, Y) sample, one bad value removes one row. A bad input row always takes its matching output row with it.
jaxgsa.efast.analyze() accepts only "raise" and "propagate". Its design is an ordered sweep read by a Fourier transform, so removing a point does not shrink the sample; it changes what the estimator computes. Asking for "drop" there raises and says so.
Whatever you choose, the result carries an invalid report:
result = jaxgsa.sobol.analyze(samples, Y, on_invalid="drop")
result.invalid.n_invalid # how many blocks held a bad value
result.invalid.unit_indices # which blocks
result.invalid.bad_row_indices # the rows that actually failed
result.invalid.row_indices # every row those blocks cover
result.invalid.sources # whether the bad values were in X, in Y, or bothThe positions are the useful part: they name the model runs to investigate.
bad_row_indices and row_indices answer different questions, and for a block design the difference is large. One failed run inside an eFAST search curve gives one entry in bad_row_indices and 257 in row_indices. The first tells you which model run to look at. The second tells you what "drop" would remove.
Both always refer to the array as you passed it. A Saltelli or Morris design is analysed in an expanded form that repeats rows, but you evaluated the model once per unique row, so the report is translated back to the numbering you hold.
References
- Sobol', I.M. (2001). Global sensitivity indices for nonlinear mathematical models and their Monte Carlo estimates. Mathematics and Computers in Simulation, 55(1-3), 271-280.
- Saltelli, A. (2002). Making best use of model evaluations to compute sensitivity indices. Computer Physics Communications, 145(2), 280-297.
- Saltelli, A., Annoni, P., Azzini, I., Campolongo, F., Ratto, M., & Tarantola, S. (2010). Variance based sensitivity analysis of model output. Computer Physics Communications, 181(2), 259-270.
- Jansen, M.J.W. (1999). Analysis of variance designs for model output. Computer Physics Communications, 117(1-2), 35-43.
- Li, G., Rabitz, H., Yelvington, P. E., Oluwole, O. O., Bacon, F., Kolb, C. E. & Schoendorf, J. (2010). Global sensitivity analysis for systems with independent and/or correlated inputs. The Journal of Physical Chemistry A, 114(19), 6022-6032. (Defines the SCSA method and its per-term indices
, , in Eqs. 19-22, the per-input totals in Section 2.2.3, and the reliability criterion in Eq. 24.) - Sarazin, G., Viaud, C. & Cournède, P.-H. (2017). Analyse de sensibilité globale pour les modèles à entrées corrélées. Journal de la Société Française de Statistique, 158(1), 68-89. (Restates the SCSA total as
in Eq. 8, and states explicitly that it is no longer confined to in the correlated case.) - Rabitz, H. & Alis, O. (1999). General foundations of high-dimensional model representations. Journal of Mathematical Chemistry, 25(2-3), 197-233.
- Sudret, B. (2008). Global sensitivity analysis using polynomial chaos expansions. Reliability Engineering & System Safety, 93(7), 964-979.
- Owen, A.B. (2014). Sobol' indices and Shapley value. SIAM/ASA Journal on Uncertainty Quantification, 2(1), 245-251.
- Song, E., Nelson, B.L. & Staum, J. (2016). Shapley effects for global sensitivity analysis: Theory and computation. SIAM/ASA Journal on Uncertainty Quantification, 4(1), 1060-1083.
- Saltelli, A., Tarantola, S. & Chan, K.P.-S. (1999). A quantitative model-independent method for global sensitivity analysis of model output. Technometrics, 41(1), 39-56.
- Kucherenko, S., Tarantola, S. & Annoni, P. (2012). Estimation of global sensitivity indices for models with dependent variables. Computer Physics Communications, 183(4), 937-946.
- Borgonovo, E., Figalli, A., Plischke, E. & Savaré, G. (2024). Global sensitivity analysis via optimal transport. Management Science, 71(5), 3809-3828 (in print 2025; online-first 2024). doi:10.1287/mnsc.2023.01796