Coverage for tests/test_experiment.py: 100%
53 statements
« prev ^ index » next coverage.py v7.15.3, created at 2026-09-03 17:23 +1200
« prev ^ index » next coverage.py v7.15.3, created at 2026-09-03 17:23 +1200
1import logging
3from hypothesis import given, strategies as st
4from hypothesis.extra.numpy import arrays
5import numpy as np
6import pytest
7from scipy import stats
9from crispatt._noise import _generate_noise_on_lengths, _RandomVariableProtocol
10from crispatt.crispatt import (
11 Ensemble,
12 _compute_edges,
13 _init_seed_sequence,
14 _n_floes_from_width,
15)
17fixed_seed = 0xcc1b7f18deee44e8928d0043a9e78540 # fmt: skip
19length_strategy = st.floats(0.1, 10, allow_nan=False)
20concentration_strategy = st.floats(0.15, 1 - 1e-6, exclude_max=True, allow_nan=False)
23@given(
24 lengths_ensemble=arrays(
25 np.float64,
26 st.tuples(st.integers(1, 10), st.integers(2, 100)),
27 elements=length_strategy,
28 ),
29 concentration=concentration_strategy,
30 ice_edge=st.floats(-1e6, 1e6, allow_nan=False),
31)
32def test_compute_edge(lengths_ensemble, concentration, ice_edge):
33 edges_ensemble = _compute_edges(lengths_ensemble, concentration, ice_edge)
34 assert np.allclose(edges_ensemble[:, 0], ice_edge)
36 right_edges_ensemble = edges_ensemble + lengths_ensemble
37 assert np.all(right_edges_ensemble[:, :-1] < edges_ensemble[:, 1:])
39 assert np.allclose(
40 edges_ensemble[:, -1] - ice_edge,
41 lengths_ensemble[:, :-1].sum(axis=1) / concentration,
42 )
45@given(
46 domain_width=st.floats(100, 50e3, allow_nan=False),
47 average_length=length_strategy,
48 concentration=concentration_strategy,
49)
50def test_n_floes_from_width(
51 domain_width: float, average_length: float, concentration: float
52):
53 n_floes = _n_floes_from_width(domain_width, average_length, concentration)
54 assert n_floes >= 1
55 assert n_floes * average_length >= concentration * domain_width or np.isclose(
56 n_floes * average_length, concentration * domain_width
57 )
60@pytest.mark.parametrize("seed", (None, fixed_seed, np.random.SeedSequence(fixed_seed)))
61def test_seed_logging(seed, caplog: pytest.LogCaptureFixture):
62 caplog.set_level(logging.INFO)
63 seed_sequence = _init_seed_sequence(seed)
64 assert len(caplog.messages) == 1
65 assert "0x" in caplog.text
66 assert isinstance(seed_sequence, np.random.SeedSequence)
69@given(seed=st.integers(min_value=0))
70def test_seed_int(seed):
71 seed_sequence = _init_seed_sequence(seed)
72 assert seed_sequence.entropy == seed
75@given(seed=st.builds(np.random.SeedSequence))
76def test_seed_seed_sequence(seed):
77 seed_sequence = _init_seed_sequence(seed)
78 assert seed_sequence is seed
79 assert seed_sequence.entropy == seed.entropy
82@pytest.mark.parametrize(
83 "dist", (stats.Normal(mu=0, sigma=1), stats.Uniform(a=-1, b=1))
84)
85@given(n_floes=st.integers(1, 20), n_members=st.integers(1, 10))
86def test_length_noise_shape(
87 n_floes: int,
88 n_members: int,
89 dist: _RandomVariableProtocol,
90):
91 seed_sequence = np.random.SeedSequence(None)
92 noise = _generate_noise_on_lengths(n_floes, n_members, seed_sequence, dist)
93 assert noise.shape == (n_members, n_floes)
96@given(
97 lengths=arrays(np.float64, st.integers(2, 100), elements=length_strategy),
98 n_members=st.integers(1, 10),
99)
100def test_noise_sequence_state(lengths: np.ndarray, n_members: int):
101 seed_sequence = np.random.SeedSequence(fixed_seed)
102 dist = stats.Normal()
103 assert (
104 seed_sequence.state["n_children_spawned"] == 0
105 and seed_sequence.state["spawn_key"] == tuple()
106 )
107 l_ensemble = Ensemble._add_noise_to_lengths(lengths, n_members, seed_sequence, dist)
108 assert l_ensemble.shape == (n_members, lengths.size)
109 assert (
110 seed_sequence.state["n_children_spawned"] == 1
111 and seed_sequence.state["spawn_key"] == tuple()
112 )