Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions code/saga.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ def __init__(self, mu, sigma, list_samples, tau=14, chi2_bucket=10, pmin=0.001):
"over Z), but received non-integer values. For continuous "
"multivariate data, MultivariateSamples skips the "
"per-coordinate discrete channel automatically.")
# Keep the samples in their original order (by reference -- do not
# mutate them afterwards). The sequence tests of the extended battery
# (Ljung-Box, runs, block homogeneity) measure ordering, so they cannot
# be run on a histogram-reconstructed sample list, which is sorted.
self.samples = list_samples
self.histogram = dict()
self.outlier = 0
# Initialize histogram
Expand Down Expand Up @@ -152,11 +157,17 @@ def __init__(self, mu, sigma, list_samples, tau=14, chi2_bucket=10, pmin=0.001):


def run_extended_battery(self, samples=None, mc_B=1000):
"""Run the extended test battery (Phase 3 tests)."""
"""
Run the extended test battery (Phase 3+4 tests).

`samples` defaults to the ordered samples this object was built from.
It must never be reconstructed from the histogram: that yields a sorted
sequence, and the sequence tests (Ljung-Box, runs, block homogeneity)
would then reject any correct sampler with p ~ 0.
"""
from univariate_tests import run_extended_battery
if samples is None:
samples = [z for z in self.histogram
for _ in range(self.histogram[z])]
samples = self.samples
self._extended = run_extended_battery(
self.exp_mu, self.exp_sigma, samples,
tau=self.tau, alpha=self.pmin, mc_B=mc_B,
Expand Down
17 changes: 17 additions & 0 deletions code/tests/test_univariate.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,23 @@ def test_extended_battery_passes(self, good_univariate_vector):
if isinstance(r, dict) and not r.get("passes", True))
)

def test_extended_battery_default_samples(self, good_univariate_vector):
"""The default (no `samples=`) path must use the ordered samples.

It once rebuilt them from the histogram, i.e. sorted, which made the
sequence tests reject every correct sampler at p ~ 0.
"""
v = good_univariate_vector
uv = UnivariateSamples(v["params"]["mu"], v["params"]["sigma"], v["samples"])
ext = uv.run_extended_battery(mc_B=200) # no samples= argument
for name in ("ljung_box", "runs_test", "block_homogeneity"):
assert ext[name]["passes"], (
f"Good vector {v['label']}: {name} rejects on the default "
f"path (p={ext[name]['pvalue']:.6f}) -- samples were "
f"probably reordered")
assert ext["all_pass"]
assert uv.is_valid_extended


class TestBadVectors:
"""Flawed distributions must be detected by chi-square or extended battery."""
Expand Down
Loading