Skip to content

Prepare v2.2.0 release - #147

Merged
HadenSmith merged 225 commits into
mainfrom
bug-fixes-and-enhancements
Sep 18, 2026
Merged

HadenSmith merged 225 commits into
mainfrom
bug-fixes-and-enhancements

Conversation

@HadenSmith

Copy link
Copy Markdown
Collaborator

Summary

  • Prepare RMC.Numerics 2.2.0 with assembly version 2.2.0.0.
  • Set citation and CodeMeta release dates to September 18, 2026.
  • Refresh the concise NuGet package release notes.
  • Advance the snapshot version from the previous release's 2.1.5 to 2.2.1.
  • Include all 223 commits since PR Prepare v2.1.4 release #144 / v2.1.4, including this release-preparation commit.
  • Target public release outcome: RMC.Numerics 2.2.0 is the latest stable package on NuGet.org.

Summary

  • Statistics and probability: Add weighted statistics, given-data global sensitivity estimators, lazy exclusive-event enumeration, and single-factor equicorrelated union calculations. Correct moment accumulation, adjusted RMSE, percentile validation, and hypothesis-test tie terms.
  • Distributions and uncertainty: Improve logarithmic tails, censored/interval likelihoods, endpoint behavior, moments, gradients, and covariance calculations. Add local-MLE uncertainty for generalized logistic, generalized normal, and Kappa Four distributions, plus logarithmic quantile-Jacobian evaluation.
  • Composite distributions: Improve mixture and competing-risk evaluation, positive conditioning, dependency-aware simulation, mutable-configuration caching, and fitting performance.
  • Copulas and multivariate distributions: Add conditional copula APIs, IndependenceCopula, XML factories, Frank/Joe method-of-moments fitting, and optional singular-covariance MVN density/sampling through SVD.
  • Sampling: Modernize R-hat/ESS, enable NUTS mass adaptation by default, expose acceptance controls and per-chain diagnostics, reuse HMC/NUTS gradients, and add seeded Sobol/Vegas scrambling.
  • Integration and optimization: Add adaptive 2-D Gauss–Kronrod integration and node recording. Improve BFGS stationarity and safeguarded bounded searches, Powell feasibility, Differential Evolution boundary repair, bounded numerical derivatives, and Cholesky regularization.
  • Functions and data: Add segmented/composite functions, posterior ensembles, supported function XML serialization, empirical convolution, sided extrapolation, and TimeSeries.SmoothedSeries.
  • Routing and machine learning: Expand Dijkstra/Network routing and repair custom-weight detours. Improve tree-training allocations and tie handling, pure-node termination, clustering, covariance repair, and likelihood reporting.
  • Compatibility and coverage: Preserve existing bootstrap and paired-data binary overloads; improve legacy distribution XML loading, input validation, documentation, and numerical-oracle/serialization/seeded/concurrency regression coverage.

Upgrade Considerations

  • R-hat now uses rank-normalized split/folded diagnostics. ESS summarizes bulk and both tails; existing diagnostic baselines may change.
  • NUTS.AdaptMassMatrix defaults to true; setting it to false retains a supplied fixed metric.
  • Corrected fitting constraints, distribution arithmetic, optimizer termination, and tie handling can change fitted values and exact seeded outputs, including positive-parameter Frank copula sampling.
  • Zero-inflated Mixture is a zero atom plus components conditioned on positive values.
  • BFGS exposes OptimizationStatus.LineSearchFailed.
  • Local-MLE uncertainty requires each distribution's documented regularity conditions and does not guarantee a finite global MLE.
  • Optimizer trace entries can share parameter arrays; consumers must treat them as read-only or copy them.
  • Singular-covariance MVN support is an optional density/sampling path. Extrapolation and Sobol scrambling remain opt-in.

Validation

  • dotnet restore
  • dotnet build -c Release
  • dotnet build Numerics/Numerics.csproj -c Release /p:Version=2.2.0
  • Full tests with VSTEST_CONNECTION_TIMEOUT=600, using dotnet test -c Release --no-build and complete affected-framework reruns.
  • dotnet pack Numerics/Numerics.csproj -c Release /p:Version=2.2.0 --no-build -o ./packages
  • Inspect package identity, version, release notes, README, license, dependencies, and all four DLL/XML pairs.
  • Confirm packaged DLL/XML hashes match the validated build outputs.
  • git diff --check

Both Release builds completed with zero warnings and errors.

Framework Passed Failed Skipped
.NET Framework 4.8.1 2,880 0 0
.NET 8 2,895 0 0
.NET 9 2,895 0 0
.NET 10 2,895 0 0
Total 11,565 0 0

These are complete passing framework runs. Initial combined runs encountered BOM HTTP 500 DatasourceError responses. Every affected method/framework passed its exact isolated rerun; the full affected framework gates were then rerun successfully. No tests were excluded and no test settings or numerical behavior were changed during release preparation.

New append-only UnivariateFunctionType enum and UnivariateFunctionFactory
(CreateFunction by type, CreateFromXElement dispatching on the element
local name, and GetFunctionType for live instances), mirroring the
LinkFunctionFactory and UnivariateDistributionFactory patterns. Concrete
ToXElement instance methods with static FromXElement counterparts land
on LinearFunction, PowerFunction, and TabularFunction - deliberately NOT
on IUnivariateFunction: external implementors of the interface exist and
net481 has no default interface members, so an interface member would be
a breaking change. G17 invariant-culture doubles; ConfidenceLevel is
runtime sampling state and never serializes; PowerFunction.Minimum
derives from Xi and never serializes; TabularFunction embeds its
UncertainOrderedPairedData via SaveToXElement. Round-trip and dispatch
tests cover all three types and the factory guards.
Q(h) = sum over controls of 10^(log10 alpha_k) (h - h_k)^beta_k for
h > h_k, zero at and below the main-channel cease-to-flow stage h1,
with a log10-space Gaussian residual applied at the confidence level -
the exact body and stochastic form of the RMC-BestFit rating-curve
Predict. The parameter-vector layout is the BestFit layout
[h1, log10a1, b1, ..., sigma] (3 segments + 1), so a fitted posterior
ParameterSet applies directly through SetParameters; breakpoints must
be strictly ordered and exponents non-negative (the monotone-rating
constraint the numeric Brent inverse relies on). One segment
degenerates to PowerFunction. Parity constants in the tests were
evaluated independently from the closed form; serialization rides the
function factory (Minimum derives from h1 and ConfidenceLevel is runtime
state - neither serializes).
A weighted combination over IUnivariateFunction children: the pointwise
weighted average, and a mixture whose single confidence-level draw
selects a child by cumulative weight and re-scales the remainder as the
child draw - deterministic composition sampling with no internal random
source (the math behind composite risk input functions). Weights are
the composite parameters (non-negative, sum to one); children own and
re-validate their own parameters at evaluation, so the composite
validity flag deliberately does not fold in the latent child-flag quirk
where a deterministic two-argument LinearFunction reports invalid until
first evaluation. Mean convention outside [0,1] evaluates the weighted
average of child means in both modes; a multi-branch mixture reports
nondeterministic even over deterministic children. Serialization embeds
children through their own forms and reconstructs through the function
factory (nesting supported).
A template function plus posterior ParameterSet draws, sampled by index
(range-checked) or percentile (min(floor(u*N), N-1)) as fresh configured
clones - pure by construction: the template is captured as an immutable
serialized snapshot at construction and every sample re-materializes
through the function factory before applying its parameter set, so concurrent
realizations share no mutable state (the thread-safe alternative to
mutating ConfidenceLevel/SetParameters inside Parallel.For hot loops;
pinned by a parallel no-shared-mutation test). This is the vehicle for
imported fitted functions (BestFit rating curves) carrying knowledge
uncertainty into a simulation engine. Serialization embeds the template
form plus every parameter set.
Both types reported parameter property names without scalar parameter
values, so the inherited scalar-only ToXElement could not even complete
- the X/probability tables and the kernel sample were lost entirely.
Each now overrides ToXElement (tables and sample data as G17
invariant-culture lists, interpolation transforms, the probability sort
order for exceedance-convention ladders, kernel type, bandwidth, and
the optional per-sample weights) with a static FromXElement
counterpart, wired into UnivariateDistributionFactory.CreateDistribution
alongside the existing Mixture/CompetingRisks/PertPercentile special
cases. Round-trip tests cover ascending and descending probability
conventions, evaluation equality, weighted kernels, and the loud
missing-table guards.
An optional Recorder callback (x, weight, f(x)) that fires only when an
interval is ACCEPTED into the final composite rule - the design that
makes a weight-aware adaptive quadrature work at all: a naive
per-evaluation weight hand-off double-counts, because a rejected
interval's 21 evaluations are superseded by its children's and must
never carry measure. Each evaluation caches its interval's nodes and
values (only while a recorder is attached; null leaves the integration
path unchanged with zero overhead, pinned bit-for-bit), and acceptance
flushes them with half-length-scaled Kronrod weights. Two structural
identities follow and are pinned under forced deep subdivision: the
recorded weights sum to exactly the integration domain's width (any
double count would overshoot), and the weighted node sum reproduces the
returned integral to floating-point reassociation - so a consumer can
take loss-exceedance probability mass directly from the quadrature.
Works identically through the whole-interval and stratified-bin forms;
G10K21 nodes are strictly interior, so adjacent intervals never repeat
an abscissa.
ConvolveDiscrete convolves point-mass (atom) distributions EXACTLY on a
shared uniform lattice - the form the continuous-PDF pipeline cannot
represent (a zero-inflation atom of a defective risk curve is a CDF
jump with no density): atoms deposit with a moment-preserving two-node
split, the mass vectors convolve by FFT, ringing floors at zero, and
the total renormalizes to the product of the input totals. Pinned
against exact enumeration: total mass, the convolved mean = sum of
input means (exact by the split), interior cumulative probes, and the
joint zero atom of two defective curves. The new logSpacedOutput
overload keeps the existing linear-grid FFT pipeline byte-identical
(false delegates to it directly) and re-reads the convolved CDF on a
log-spaced ladder for order-of-magnitude supports, with a loud guard on
non-positive support. The existing five continuous Convolve tests pass
unchanged.
The upstream unit-test half of the engine-level gamma audit: a known
heavy-upper-tail integrand (21(1-p)^20, integral exactly one) must stay
unbiased at gamma in {1, 4, 10}, and the weights handed to the
integrand must sum to the domain volume per evaluation batch at every
gamma - a missing Jacobian would distort both by the measure change,
an order-of-magnitude error at gamma = 10. Seeded MersenneTwister with
the Sobol default off, so the runs are deterministic.
The pooled combination-enumeration path allocated its output lists and
every indicator row per call - a per-evaluation cost in joint-risk hot
loops. The new overload takes caller-owned output lists, refills
existing row arrays of matching length in place (steady-state zero
output allocations, pinned by reference-identity tests across calls),
trims stale tails from larger prior calls, and returns TRUE when the
inclusion-exclusion expansion converged early - surfacing the
previously silent truncation of the deepest combinations behind the
closing pseudo-row. The allocating out-parameter overload now delegates
to it, so outputs are identical by construction (pinned for full and
truncated enumerations). The sibling exclusive kernels
(PositivelyDependentExclusive/ExclusivePCM/ExclusiveMVN) follow the
same recipe when their call sites go pooled.
MVNDST is a randomized lattice rule -- it draws from MultivariateNormal's
own generator to randomize its quadrature, so the returned probability
carries a small stochastic error and two calls with different generator
states do not agree bit-for-bit. That generator defaulted to
`new MersenneTwister()`, whose parameterless constructor seeds from
DateTime.UtcNow.Ticks. Every CDF and Interval evaluation above two
dimensions was therefore irreproducible across runs.

It stayed hidden because the error sits near the requested tolerance, so
statistical tests absorb it. RMC-TotalRisk found it downstream: dependent
competing-risks incidence curves run through Interval once per unit per
hazard level, and a results hash over that path changed on every run.

MVNUNI now defaults to the fixed DefaultMVNUNISeed, and CompetingRisks
gains a PRNGSeed applied when it builds its multivariate normal, so callers
deriving seeds from model content can tie the result to the model instead of
to a shared constant. Both are documented, including MVNUNI's thread-safety
constraint (MVNDST advances it, so a shared instance needs a clone per
thread, as JointProbabilitiesMVN already does).

Gates: build 0 warnings; 1943/1943 on net10.0, net9.0, net8.0 and net481.
The reductions over bootstrap replications merged thread-local partials
through Tools.ParallelAdd, whose partitioning follows the scheduler. Results
therefore varied in their last bits between runs and between machines --
including Estimate's mean curve, which feeds a monotonic filter that can
admit a different number of interpolation knots from a last-bit difference.

Every such reduction now splits its work into a fixed number of chunks, each
summed sequentially and merged in chunk order, so the association is
independent of the thread count: ExpectedProbabilities (both overloads),
UncertaintyAnalysisResults.ProcessMeanCurve, the P0 proportions in
BiasCorrectedQuantileCI and BCaQuantileCI, AccelerationConstants,
StandardError, and Statistics.JackKnifeStandardError. Parallelism is
retained throughout.

Defects found while reworking the class:

- ExpectedProbabilities summed over the successfully fitted distributions
  but divided by the full replication count, biasing the expectation toward
  zero in proportion to the failure rate. Both now exclude failures.
- Fit failures were silent. Distributions() records FailedReplications, so a
  caller can tell when results rest on fewer samples than requested.
- The NaN guards in BiasCorrectedQuantileCI and BCaQuantileCI were written
  `x != double.NaN`, which is always true in IEEE-754, so the intended
  rejection never ran. Harmless, since the following comparison is false for
  NaN, but now correct.
- Estimate's quantile ladder accumulated Log10(previous + shift) + delta, so
  rounding compounded across up to a thousand bins. Each ordinate is now
  computed from the origin, matching ProcessMeanCurve.
- Quantiles() and Probabilities() could not accept an existing set of
  bootstrapped distributions, so a caller wanting both paid for the whole
  bootstrap twice.
- ComputeMinMaxQuantiles took a lock per distribution; it now merges
  thread-local extremes once per partition.
- The jackknife in AccelerationConstants, StandardError, JackKnifeSample and
  JackKnifeStandardError copied the whole sample into a fresh list per
  point. Each chunk now refills one leave-one-out buffer.

Adds two pins: Estimate is bit-identical across calls at the same seed, and
ExpectedProbabilities is bit-identical when one arm is constrained to a
single worker thread.

Gates: build 0 warnings; 1945/1945 on net10.0, net9.0, net8.0 and net481.
The exclusive expansions read their combinations from a dense n-by-(2^n - 1)
indicator matrix built by Factorial.AllCombinations. That matrix is the
binding constraint on dimension long before the arithmetic is: it is 80 MB
at twenty events and 3.3 GB at twenty-five, and it refuses to build at all
past thirty. Callers therefore carried dimension caps that had nothing to do
with the model.

Factorial gains NextCombination and AllCombinationsLazy, which generate the
same rows in the same order -- subset size ascending, then lexicographic --
allocating nothing per row and imposing no upper bound on n.

Probability gains IndependentExclusiveLazy on top of them, with caller-owned
output buffers and an ExclusiveEnumerationStatus distinguishing a completed
expansion from one the inclusion-exclusion bracket closed early and one that
stopped at a caller-supplied cap. Where the dense form runs, the lazy form
emits bit-identical probabilities and identical indicator rows. On
convergence it closes with the same half-gap row; at the cap it closes with
the exact residual 1 - sum(emitted), which for independent events is the
mass of everything not enumerated.

The cap is a backstop, not the operating mechanism. The bracket cannot be
tested before the third subset size, so the floor is n + C(n,2) + C(n,3),
and at the failure probabilities a risk model carries it closes at or just
after that: a forty-event expansion enumerates around 10^5 of its 10^12
combinations.

Gates: build 0 warnings; 1951/1951 on net10.0, net9.0, net8.0 and net481.
The constructor refused more than twenty dimensions. Nothing structural
required that: every internal array sizes from Dimensions, and the Sobol
sequence supports 21,201. It was also stricter than the reference
implementations -- GSL, Cuba and Lepage's vegas impose no dimension cap at
all, and Lepage's largest documented example is itself twenty-dimensional.

The algorithm already degrades the way those implementations rely on. The
importance-sampling grid is separable, so it holds NumberOfBins x D bins
rather than bins^D, and the stratification self-limits: strata per axis are
(calls/2)^(1/D), which reaches one around fifteen dimensions, after which
the run is pure adaptive importance sampling. At twenty dimensions with ten
thousand calls it is already in that regime, so raising the guard changes
nothing about how it behaves there.

Kept as a guard rather than removed, so a runaway input still fails fast.

The new test integrates the mean of 2*x_i over thirty dimensions. A product
of the same factors would concentrate its mass in one corner and is
hopeless at that dimension for any sample budget -- the curse of
dimensionality rather than a property of the integrator -- which is worth
knowing before reaching for high dimensions.

Gates: build 0 warnings; 1955/1955 on net10.0, net9.0, net8.0 and net481.
With a recorder attached, every interval allocated two 21-element buffers
to hold its nodes and values -- including the rejected intervals, which are
the majority under adaptive refinement. That is a per-evaluation allocation
introduced purely by observing the integration.

The buffers now come from a per-slot pool grown on demand. Depth-first
recursion means the only intervals alive at once are the two halves of the
current interval and their ancestors, so a slot index of 2*level+1 and
2*level+2 is unique among live intervals and every subtree reuses the same
slots after its sibling finishes. Nothing is allocated when no recorder is
attached.

Gates: build 0 warnings; 1955/1955 on all four TFMs, including the recorder
invariants -- weights summing to the domain width and the weighted node sum
reproducing the result -- under forced deep subdivision.
ProcessParameterSets left a null entry wherever a sampled distribution had
failed to fit, while BootstrapAnalysis.ParameterSets filled the same slot
with a NaN-valued set. ParameterSets is a public array that consumers index
directly, so the two paths differed in whether a failed replication throws
or propagates. Both now fill NaN, taking the parameter count from the
parent distribution.

Also merges the mean curve's min/max extremes once per worker instead of
once per distribution. Min and max are order-independent, so the result is
unchanged however the loop partitions.
The recorder must flush the frozen composite rule on budget and depth
exhaustion, not only on success, with weights summing to the domain area and
weighted values reproducing the result on every non-throwing outcome, and
report nothing when the integrand throws. Consumers adopting the recorded mass
as an exhaustive partition rely on exactly these behaviors.
Stabilize probabilities, support, L-moments, moments, modes, and quantile derivatives. Reject unsuccessful MLE results and add independent oracle regressions. Release build has zero warnings and errors; all 32 K4 tests pass on each target framework. Haden approved committing with the documented unrelated BOM HTTP 500 test failure.
Restores the pre-serialization assembly attribute
Parallelize(Scope = ExecutionScope.ClassLevel). The serialization commit
replaced it with DoNotParallelize alongside MaxCpuCount=1 and
TestTfmsInParallel=false; the follow-up settings change reverted the latter
two but left the assembly attribute, so every method ran single-threaded
per test host.

Measured on the full suite (Release, VSTest, 22 logical processors):
net10.0 2,850/2,850 in 1m48s parallel vs 2m46s serial (-35%);
net481 2,835/2,835 in 2m20s parallel vs 3m21s serial (-30%);
zero failures under parallelism on this evidence, so no per-class
DoNotParallelize pins are needed yet. The wall-clock runs were taken with
the unstaged configuration-snapshot working-tree changes present, which
affect a handful of guard tests at millisecond scale and no test results.
Adds DistributionSnapshot, an immutable bitwise capture of a distribution
tree's mutable configuration whose equality implies an identical canonical
configuration string: exact built-in leaves mirror their GetParameters
scalar order (extended by the logarithm base on the log families and the
physical-moment surface on LnNormal, which sit outside the flattened
parameters), and exact Mixture/CompetingRisks nodes capture their flags,
weights, seed, and correlation entries and recurse into children. Derived
types and table-backed families defeat capture, so their owners retain the
generic canonical-string path; a bitwise mismatch always falls back to the
string comparison, which remains the deciding authority.

CompetingRisks' WeibullConfiguration becomes DependentConfigurationCache
around the shared snapshot, so the dependent arm stops rebuilding the
canonical string on every call for any exact supported component set, and
the derivative-step cache now also engages there (the step is a pure
function of the pinned parameters). The fixed-support dependent fast arm
stays gated on AllExactWeibull: extending DependentCDFCore reuse to other
families could move boundary-stencil values and is left as a separate
evidence-carrying change.

Measured (2 LnNormal components, PerfectlyPositive, Release, per call):
dependent CDF 31,057 B -> 136 B (-99.6%) and 4,030 ns -> 1,237 ns (-69%);
LogPDF 62,362 B -> 464 B (-99.3%) and 8,995 ns -> 3,097 ns (-66%).
All-Weibull behavior and results are unchanged. All 36 CompetingRisks
family tests pass, and the full suites passed with these changes present:
net10.0 2,850/2,850, net481 2,835/2,835.
…shot

Mixture.RefreshCachedConfiguration serialized the full canonical
configuration string on every call - recursively rendering every component
to XML - and then discarded it whenever nothing had changed, which every
InverseCDF call, CreateEmpiricalCDF, and moment getter paid. A published
DistributionSnapshot now short-circuits the unchanged path bitwise, exactly
the CompetingRisks pattern; a mismatch or an uncapturable component tree
still builds and compares the string, which remains the deciding authority
for cache invalidation, so incomplete capture can only cost a string
comparison, never a wrong match.

Measured (four LnNormal components over the empirical-CDF fast path,
Release, per call): InverseCDF 62,512 B -> 928 B (-98.5%) and
11.6 us -> 2.8 us (-76%). CDF is unchanged (its cost is per-call
validation, addressed separately). All 47 Mixture family tests pass;
values are bit-identical - a snapshot match implies the identical
canonical string.
Mixture evaluation validated every component on every call - allocating a
parameter array per non-Normal component - and read its support bounds
through capturing LINQ closures on every quantile clamp. A
ValidationCertificate now publishes the exact bitwise snapshot that passed
the full validator: a match skips re-validation without allocating, any
mutation re-runs the verbatim validator with identical exceptions and
precedence, and uncapturable component trees keep per-call validation
unchanged. The certificate also carries lazily published support bounds
(pure functions of the certified bits), and InverseCDF unifies its refresh
and validation checks into one snapshot walk by reference identity with
the refresh-published instance. The weight-log cache now serves LogCDF,
LogCCDF, and interval probabilities as it already served LogPDF, hot loops
index Length instead of LINQ Count(), and LnNormal exposes its stored
physical-moment fields internally so snapshot capture pins the moment
surface without evaluating it.

The snapshot compare path is rewritten as inline per-family arms over the
stored bits (capture keeps the shared cursor walk): the consuming
libraries link this assembly's Debug build, whose minimal-optimization
jitting keeps every helper call, so the hot path minimizes call count.

Measured (four LnNormal components over the empirical-CDF fast path,
Debug assembly, median of three, per call): InverseCDF 928 B -> 96 B and
2.8 us -> 0.79 us - now better than the pre-hardening baseline
(144 B / 0.97 us) on both axes; CDF 160 B -> 0 B at wall parity with the
prior state (the remaining gap to the pre-hardening direct sum is the
retained log-sum-exp tail repair, deliberately unchanged). All 135
Mixture, CompetingRisks, and LnNormal family tests pass; values are
bit-identical.
Adds the snapshot regression suite: a capture/compare round trip per
supported family (pinning the capture and inline-compare switches
together), a reflection sweep asserting every public settable double,
int, bool, and enum property on a supported family flips the compare (the
completeness guard for the per-family scalar lists), nested-grandchild
invalidation through composite recursion, the derived-component capture
refusal, and correlation-matrix entry participation including the
bitwise-identical clone case.

The sweep immediately caught GeneralizedPareto.Lambda - carried
peaks-per-block metadata outside the flattened parameters - which is now
captured in both arms so the uniform invariant holds with no exception
list. Lambda does not enter evaluation or the canonical string, so the
addition is strictness only; extra strictness can only force a string
re-comparison, never a wrong match.
LogPearsonTypeIII allocated and validated a fresh PearsonTypeIII on every
LogCDF, LogCCDF, CDF, and InverseCDF call. Following the existing static
LogPDF precedent, the Pearson tail and quantile interiors are extracted as
internal statics over already-validated parameters (the instance methods
keep their guards and delegate verbatim), and the wrapper's hot sites call
them directly - its own validation and support short-circuits already
guarantee the preconditions, and both classes validate the identical
constraint set, so the bypassed instance guards were unreachable. The
statics compute the gamma shape once per call where the instance path
re-evaluated the Alpha property expression; both evaluations of the same
pure expression produce identical bits, so results are unchanged.

Measured (Debug assembly, per call): wrapper CDF 56 B -> 0 B and
1,029 ns -> 934 ns; wrapper InverseCDF 56 B -> 0 B and
4,566 ns -> 4,358 ns. The uncertainty and fitting paths keep their
per-call construction (cold by design). All 84 Pearson and log-Pearson
family tests pass.
DistributionEndpointTail is consumed only by CompetingRisks and
MixtureLogWeights only by Mixture; both internal helpers move into their
consumers' source files verbatim. KappaFourBoundary and
KappaExpectedInformation are shared kernels - the boundary transform
serves KappaFour, GeneralizedLogistic, and GeneralizedNormal, and the
expected-information integration serves KappaFour, GeneralizedExtremeValue,
and GeneralizedLogistic - so their files move to the Base folder beside
the other shared distribution numerics rather than into KappaFour.
StandardErrorExtensions stays in its own file: it is a public extension
API surface. Pure file organization; no code changes, both target
frameworks build clean, and all 305 touched-family tests pass.
@HadenSmith
HadenSmith merged commit 7e8e8d1 into main Sep 18, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant