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

1import logging 

2 

3from hypothesis import given, strategies as st 

4from hypothesis.extra.numpy import arrays 

5import numpy as np 

6import pytest 

7from scipy import stats 

8 

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) 

16 

17fixed_seed = 0xcc1b7f18deee44e8928d0043a9e78540 # fmt: skip 

18 

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) 

21 

22 

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) 

35 

36 right_edges_ensemble = edges_ensemble + lengths_ensemble 

37 assert np.all(right_edges_ensemble[:, :-1] < edges_ensemble[:, 1:]) 

38 

39 assert np.allclose( 

40 edges_ensemble[:, -1] - ice_edge, 

41 lengths_ensemble[:, :-1].sum(axis=1) / concentration, 

42 ) 

43 

44 

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 ) 

58 

59 

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) 

67 

68 

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 

73 

74 

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 

80 

81 

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) 

94 

95 

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 )