Fixes a test failure reported by CRAN on macOS arm64 (r-release and r-oldrel) and on the M1mac additional check, and with it a genuine platform dependence in the size of the exact LR support.
A log10 LR atom is a real number that the C++ engine and the R reference
implementation reach by different arithmetic routes. On a platform whose
compiler contracts a * b + c into a fused multiply-add, which is the
default on aarch64, one route rounds once where the other rounds twice, and
two mathematically equal atoms end up differing in the last bits. Grouping
them by IEEE equality, as 2.0.0 did, then split one atom into two, so the
number of support points of lr_distribution() depended on the platform and
the cross-check against the reference failed on arm64 while passing on x86.
lr_distribution(), per_marker_lr_dist() and the evidence combination
kernel now close an atom group by a relative tolerance (kAtomRelTol,
1e-12) rather than by exact equality. The measured separation justifies
the constant: consecutive keys are either within one unit in the last
place of each other or more than 1e-8 apart in relative terms, with
nothing in between.aggregate_lr_dist() applies the same rule, so the two
engines agree on the size of the support on every platform.This release adds an exact computational engine alongside the simulation workflow of the 1.x series. Where 1.x estimated likelihood-ratio distributions by Monte Carlo, the new layer computes them exactly from the pedigree and the allele frequencies, so quantities such as the expected weight of evidence or a tail probability no longer carry simulation error. The kernel is written in C++ and reached through Rcpp.
All functions from 1.x remain exported and continue to work.
marker_model() builds a validated marker model from a pedigree, a
marker identifier, allele frequencies, and an optional mutation or
linkage specification. It has print() and validation methods, so
input errors surface at construction rather than mid-computation.nongenetic_feature() gives non-genetic evidence (biological sex,
pigmentation, age, birthdate) the same treatment as a marker: a
population distribution, an observed value, and an error rate.get_allele_freqs() converts the bundled population databases into the
per-marker format the model layer expects.lr_distribution() returns the full distribution of the profile
log10 LR under both hypotheses, not a point estimate. method = "exact" performs a sparse convolution over the per-marker supports;
method = "grid" projects onto a fixed lattice, which preserves total
mass and the first moment exactly and keeps the computation bounded
when the exact support would grow past what memory allows.summary(), quantile() and plot() methods for the resulting
object.per_marker_kl() and per_marker_kl_profile() report the
Kullback-Leibler divergence between the two hypotheses for each
marker, which quantifies how much discriminating power each marker
contributes before any data are observed.concentration_index_positive(), leave_one_out(),
calibrate_concentration_cutoff() and fragility_report() measure how
much of the weight of evidence rests on a single marker, and produce a
reportable statement for the case file.belief_trajectory(), binary_belief_trajectory() and
trajectory_metrics() follow the posterior as evidence accumulates.The engine is checked against independent implementations rather than
against itself: pedprobr and pedmut for genotype distributions under
mutation, Familias and forrel for per-marker likelihood ratios, and
analytic results where they exist. These cross-engine comparisons run as
part of the test suite.
The kernel uses OpenMP where the toolchain provides it and falls back to
single-threaded execution otherwise; results are identical either way.
The exact engine enumerates joint genotype states, so its cost grows with
pedigree size and with the number of alleles per marker. For large
pedigrees or full profiles, use method = "grid" in lr_distribution().
See the CRAN release notes for the 1.x series.