| from __future__ import annotations |
|
|
| import importlib.util |
| from pathlib import Path |
|
|
| import numpy as np |
| import pytest |
|
|
|
|
| REPO = Path(__file__).resolve().parents[1] |
|
|
|
|
| def _generator(seed: int = 123): |
| spec = importlib.util.spec_from_file_location( |
| "profilefix_generator", REPO / "generator.py" |
| ) |
| module = importlib.util.module_from_spec(spec) |
| assert spec.loader is not None |
| spec.loader.exec_module(module) |
| module._CHUNK = 128 |
| return module.Generator(str(REPO), seed=seed) |
|
|
|
|
| def _module(): |
| spec = importlib.util.spec_from_file_location( |
| "profilefix_module", REPO / "generator.py" |
| ) |
| module = importlib.util.module_from_spec(spec) |
| assert spec.loader is not None |
| spec.loader.exec_module(module) |
| return module |
|
|
|
|
| def test_exact_count_allowed_lengths_and_finite(): |
| rows = list(_generator().generate(131)) |
| assert len(rows) == 131 |
| assert {row.shape for row in rows} == {(4096,)} |
| assert all(row.dtype == np.float64 for row in rows) |
| assert all(np.isfinite(row).all() and row.std() > 1e-9 for row in rows) |
|
|
|
|
| def test_deterministic_across_instances(): |
| left = list(_generator().generate(130)) |
| right = list(_generator().generate(130)) |
| assert all(np.array_equal(a, b) for a, b in zip(left, right, strict=True)) |
|
|
|
|
| def test_requested_size_does_not_change_prefix(): |
| short = list(_generator().generate(17)) |
| long = list(_generator().generate(130)) |
| assert all(np.array_equal(a, b) for a, b in zip(short, long[:17], strict=True)) |
|
|
|
|
| def test_nonpositive_request_is_empty(): |
| assert list(_generator().generate(0)) == [] |
|
|
|
|
| def test_genuine_short_outputs_are_disabled(): |
| rows = list(_generator(seed=211).generate(512)) |
| lengths = np.asarray([row.size for row in rows]) |
| assert set(lengths) == {4096} |
|
|
|
|
| def test_family_weights_are_exact_conservative_uid131_scaling(): |
| module = _module() |
| uid131 = { |
| "trend_seasonal_ar": 0.12, |
| "regime_shift": 0.12, |
| "multiplicative": 0.08, |
| "ar2": 0.15, |
| "integrated": 0.12, |
| "threshold_ar": 0.08, |
| "chaotic": 0.04, |
| "spectral_gp": 0.07, |
| "long_memory": 0.06, |
| "ou_stochastic_vol": 0.10, |
| "physical_sensors": 0.02, |
| "seasonal_counts": 0.02, |
| "intermittent": 0.01, |
| "pulse_outlier": 0.01, |
| } |
| assert sum(module._DEFAULT_WEIGHTS.values()) == pytest.approx(1.0) |
| assert module._DEFAULT_WEIGHTS["prefix_cumulative_counter"] == 0.025 |
| for family, weight in uid131.items(): |
| assert module._DEFAULT_WEIGHTS[family] == pytest.approx(0.975 * weight) |
|
|
|
|
| def test_period5_added_without_changing_period96_probability(): |
| module = _module() |
| assert module._SEASONAL_PROBS.sum() == pytest.approx(1.0) |
| period_to_probability = dict( |
| zip( |
| module._SEASONAL_PERIODS.astype(int), |
| module._SEASONAL_PROBS, |
| strict=True, |
| ) |
| ) |
| assert period_to_probability[5] > 0.0 |
| |
| |
| assert period_to_probability[96] == pytest.approx(0.07 / 1.07) |
|
|
|
|
| def test_first_value_padding_keeps_suffix_and_reads_no_later_value(): |
| module = _module() |
| raw = np.arange(3 * 32, dtype=np.float64).reshape(3, 32) |
| lengths = np.asarray([8, 12, 20]) |
| padded = module._left_pad_first_value(raw, lengths) |
| for row, active in enumerate(lengths): |
| start = raw.shape[1] - int(active) |
| assert np.array_equal(padded[row, start:], raw[row, start:]) |
| assert np.all(padded[row, :start] == raw[row, start]) |
| assert np.array_equal(raw, np.arange(3 * 32).reshape(3, 32)) |
|
|
|
|
| def test_cumulative_primitive_is_prefix_causal(): |
| module = _module() |
| steps = np.random.default_rng(223).normal(size=(3, 512)) |
| base = np.asarray([1.0e4, 2.0e8, 3.0e12]) |
| start = np.asarray([7, 31, 63]) |
| full = module._causal_cumulative_from_steps(steps, base, start) |
| prefix = module._causal_cumulative_from_steps( |
| steps[:, :257], base, start |
| ) |
| assert np.array_equal(full[:, :257], prefix) |
|
|
|
|
| def test_prefix_cumulative_family_is_deterministic_large_and_padded(): |
| module = _module() |
| left = module._prefix_cumulative_counter( |
| np.random.default_rng(227), 64, 4096 |
| ) |
| right = module._prefix_cumulative_counter( |
| np.random.default_rng(227), 64, 4096 |
| ) |
| assert np.array_equal(left, right) |
| assert np.isfinite(left).all() |
| assert (left >= 0.0).all() |
| assert float(left.max()) > 1.0e8 |
| changed = np.diff(left, axis=1) != 0.0 |
| first_change = changed.argmax(axis=1) + 1 |
| assert (first_change >= 4096 - 1024).all() |
| assert (changed.sum(axis=1) > 8).all() |
| assert "prefix_cumulative_counter" in module._FAMILIES |
| assert module._DEFAULT_WEIGHTS["prefix_cumulative_counter"] == 0.025 |
|
|
|
|
| def test_ou_stochastic_vol_is_deterministic_finite_and_active(): |
| module = _module() |
| left = module._ou_stochastic_vol(np.random.default_rng(77), 16, 512) |
| right = module._ou_stochastic_vol(np.random.default_rng(77), 16, 512) |
| assert np.array_equal(left, right) |
| assert np.isfinite(left).all() |
| assert (left.std(axis=1) > 1e-9).all() |
| assert "ou_stochastic_vol" in module._FAMILIES |
| assert module._DEFAULT_WEIGHTS["ou_stochastic_vol"] > 0 |
|
|
|
|
| def test_physical_sensors_are_deterministic_finite_and_active(): |
| module = _module() |
| left = module._physical_sensors(np.random.default_rng(91), 32, 512) |
| right = module._physical_sensors(np.random.default_rng(91), 32, 512) |
| assert np.array_equal(left, right) |
| assert np.isfinite(left).all() |
| assert (left.std(axis=1) > 1e-9).all() |
| assert "physical_sensors" in module._FAMILIES |
| assert module._DEFAULT_WEIGHTS["physical_sensors"] > 0 |
|
|
|
|
| def test_seasonal_counts_are_deterministic_integer_and_active(): |
| module = _module() |
| left = module._seasonal_counts(np.random.default_rng(109), 32, 512) |
| right = module._seasonal_counts(np.random.default_rng(109), 32, 512) |
| assert np.array_equal(left, right) |
| assert np.isfinite(left).all() |
| assert (left >= 0).all() |
| assert np.array_equal(left, np.round(left)) |
| assert (left.std(axis=1) > 1e-9).all() |
| assert "seasonal_counts" in module._FAMILIES |
| assert module._DEFAULT_WEIGHTS["seasonal_counts"] > 0 |
|
|
|
|
| def test_event_family_has_real_flat_runs_and_recovery(): |
| module = _module() |
| left = module._pulse_outlier(np.random.default_rng(131), 32, 512) |
| right = module._pulse_outlier(np.random.default_rng(131), 32, 512) |
| assert np.array_equal(left, right) |
| assert np.isfinite(left).all() |
| assert (left.std(axis=1) > 1e-9).all() |
| assert np.any(np.diff(left, axis=1) == 0.0) |
|
|
|
|
| def test_measurement_artifacts_are_deterministic_finite_and_shape_safe(): |
| module = _module() |
| raw = module._trend_seasonal_ar(np.random.default_rng(149), 64, 512) |
| left = module._measurement_artifacts( |
| np.random.default_rng(151), raw, preserve_nonnegative=False |
| ) |
| right = module._measurement_artifacts( |
| np.random.default_rng(151), raw, preserve_nonnegative=False |
| ) |
| assert np.array_equal(left, right) |
| assert left.shape == raw.shape |
| assert np.isfinite(left).all() |
| assert (left.std(axis=1) > 1e-9).all() |
|
|