Truncation
If only (a<X<b) can enter the population, probability outside the range is discarded. The retained density is
Distributions.truncate(base, a, b)Beginner tutorial · composition · JDistlib 0.6.0+
A composition design is a pipeline. Start with one or more base distributions, then mix populations, condition on an allowed range, change units or scale, and finally represent observation limits. Each step produces another ordinary JDistlib distribution.
Available from 0.6.0Start with the scientific story
| Story | Operation | What changes |
|---|---|---|
| An observation comes from one of several populations. | Distributions.mixture | Density and CDF become weighted averages. |
| Only values inside a range belong to the population. | Distributions.truncate | Outside values are removed and the retained range is renormalized. |
| A device reports every low or high value at a limit. | Distributions.censor | Tail probability becomes point mass at the limits. |
Units or calibration change by shift + scale*x. | Distributions.affine | Support, density, CDF, quantiles, and draws are transformed. |
| A differentiable one-to-one formula maps X to Y. | Distributions.transform | The inverse and its Jacobian define the new density. |
Complete design
MixtureDistribution latent = Distributions.mixture(
new double[] {0.8, 0.2},
new Normal(50.0, 8.0),
new Normal(75.0, 12.0));
TruncatedContinuousDistribution physicallyPossible =
Distributions.truncate(
latent, 0.0, Double.POSITIVE_INFINITY);
MonotoneTransformDistribution calibrated =
Distributions.affine(physicallyPossible, 2.0, 1.1);
CensoredDistribution observed =
Distributions.censor(calibrated, 5.0, 100.0);
This design says that latent measurements come from two subpopulations,
negative latent values are physically impossible, calibration reports
2 + 1.1*x, and the instrument records anything beyond its range
at 5 or 100. The final object still supports density or mass, CDF,
quantile, and random generation.
double below60 = observed.cumulative(60.0);
double median = observed.quantile(0.5);
double atCeiling = observed.getUpperAtomProbability();
double simulated = observed.random();
The complete snippet is compiled during the release build in
CompositionExamples.java.
Several populations
MixtureDistribution response = Distributions.mixture(
new double[] {3.0, 1.0},
new Normal(100.0, 15.0),
new Normal(150.0, 20.0));
Weights need only be nonnegative and have a positive finite sum;
JDistlib normalizes them. Here, 3:1 means 75% and 25%.
A mixture is not the same as averaging two random variables: one
component is selected for each draw.
Two different boundary stories
If only (a<X<b) can enter the population, probability outside the range is discarded. The retained density is
Distributions.truncate(base, a, b)If values outside the range still occur but the instrument reports the nearest limit, their probability accumulates as atoms at (a) and (b).
CensoredDistribution measured =
Distributions.censor(base, a, b);
double lowerMass = measured.getLowerAtomProbability();Units and calibration
// Celsius to Fahrenheit
MonotoneTransformDistribution fahrenheit =
Distributions.affine(celsius, 32.0, 9.0 / 5.0);
// Reflection is supported because scale may be negative.
MonotoneTransformDistribution reflected =
Distributions.affine(base, 0.0, -1.0);
The affine factory derives the inverse, Jacobian, direction, and output support automatically. Prefer it over a general transformation whenever it expresses the design.
One-to-one formulas
For a differentiable strictly monotone transformation,
This example constructs (Y=\exp(X)) explicitly:
MonotoneTransformDistribution positive =
Distributions.transform(
new Normal(0.0, 0.5),
Math::exp, // h(x)
Math::log, // h^-1(y)
y -> -Math.log(y), // log |d log(y)/dy| = -log(y)
true, // h is increasing
0.0,
Double.POSITIVE_INFINITY);
The derivative is supplied in log-absolute form because transformed densities can span an extreme numerical range. Output bounds describe the support of Y, not X.
Worked vignette · mixture + nonlinear transform
Suppose normal traffic has a median response time of 4 seconds, while 15% of requests arrive during a degraded regime with a median of 20 seconds. Model the logarithm of response time as a normal mixture, then exponentiate the complete mixture:
MixtureDistribution logSeconds = Distributions.mixture(
new double[] {0.85, 0.15},
new Normal(Math.log(4.0), 0.30),
new Normal(Math.log(20.0), 0.45));
MonotoneTransformDistribution responseTime =
Distributions.transform(
logSeconds,
Math::exp,
Math::log,
y -> -Math.log(y),
true,
0.0,
Double.POSITIVE_INFINITY);
The transformed mixture has the same scalar API as its components. Density uses both the mixture weights and the transformation Jacobian; the CDF reverses through the inverse transformation; quantiles invert the mixture CDF numerically; and random draws follow the transformed mixture law.
// Analytical questions in seconds.
double densityAtTen = responseTime.density(10.0, false);
double withinTen = responseTime.cumulative(10.0);
double percentile95 = responseTime.quantile(0.95);
// Reproducible random scenarios from the same model.
// import jdistlib.rng.MersenneTwister;
responseTime.setRandomEngine(new MersenneTwister(20260826L));
double oneScenario = responseTime.random();
double[] scenarios = responseTime.random(1_000);
Applying one common monotone transformation after mixing is equivalent
to transforming every component first and then mixing with the same
weights. Transforming the mixture directly keeps the shared change of
scale in one place. The complete example is compiled during every
check build in
CompositionExamples.java.
Direction matters
For (Y=\exp(-X)), the inverse is (-\log y), the absolute inverse
derivative is still (1/y), and increasing must be false:
MonotoneTransformDistribution decreasing =
Distributions.transform(
base,
x -> Math.exp(-x),
y -> -Math.log(y),
y -> -Math.log(y),
false,
0.0,
Double.POSITIVE_INFINITY);
The direction flag lets CDF and quantile operations reverse tails correctly. The Jacobian uses an absolute derivative, so its sign is never included in the density.
Before relying on the result
Composition is preferable to rewriting a custom kernel when these operations match the scientific design: it preserves exact component CDFs and quantiles, exposes intermediate assumptions, and reduces the amount of numerical integration required.
Next
Use the custom-distribution tutorial to create a component from a kernel or discrete weights. Use the copula tutorial (0.7.0+) when the goal is a multivariate model rather than a one-dimensional composition.