Skip to content

Commit 1901622

Browse files
committed
MAINT: rewrite test suite with pytest-describe
1 parent 9c9a709 commit 1901622

16 files changed

Lines changed: 1052 additions & 999 deletions

pyproject.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ style = [
9797
test = [
9898
"pytest",
9999
"pytest-cov",
100+
"pytest-describe",
100101
"pytest-xdist",
101102
]
102103
types = [
@@ -419,14 +420,16 @@ known-first-party = ["ampform_dpd"]
419420
split-on-trailing-comma = false
420421

421422
[tool.ruff.lint.pep8-naming]
422-
ignore-names = [
423+
extend-ignore-names = [
423424
"A",
424425
"H",
425426
"L",
426427
"R",
427428
"R_dec",
428429
"R_prod",
429430
"S",
431+
"describe_*",
432+
"test_*",
430433
"Γ*",
431434
"λ*",
432435
]
@@ -480,12 +483,14 @@ ignore-names = [
480483
"T20",
481484
"assert",
482485
"boolean-type-hint-positional-argument",
486+
"complex-structure",
483487
"float-equality-comparison",
484488
"implicit-namespace-package",
485489
"import-private-name",
486490
"magic-value-comparison",
487491
"no-self-use",
488492
"private-member-access",
493+
"too-many-statements",
489494
]
490495

491496
[tool.ruff.lint.pydocstyle]

tests/adapter/test_qrules.py

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -117,42 +117,42 @@ def test_get_equal_final_state_ids(
117117
assert equal_ids == expected
118118

119119

120-
def test_normalize_state_ids_reaction(jpsi2pksigma_reaction: ReactionInfo):
121-
reaction012 = jpsi2pksigma_reaction
122-
reaction123 = normalize_state_ids(reaction012)
123-
assert set(reaction123.initial_state) == {0}
124-
assert set(reaction123.final_state) == {1, 2, 3}
125-
126-
transitions123 = normalize_state_ids(reaction012.transitions)
127-
for transition012, transition123 in zip(
128-
reaction012.transitions, transitions123, strict=True
129-
):
130-
assert set(transition123.initial_states) == {0}
131-
assert set(transition123.final_states) == {1, 2, 3}
132-
assert set(transition123.intermediate_states) == {4}
133-
134-
topology123 = normalize_state_ids(transition123.topology)
135-
assert topology123.incoming_edge_ids == {0}
136-
assert topology123.outgoing_edge_ids == {1, 2, 3}
137-
assert topology123.intermediate_edge_ids == {4}
138-
139-
for i in transition012.states:
140-
assert transition012.states[i] == transition123.states[i + 1]
141-
142-
143-
def test_normalize_state_ids_problem_set():
144-
stm = StateTransitionManager(
145-
initial_state=[("J/psi(1S)", [-1, +1])],
146-
final_state=["K0", "Sigma+", "p~"],
147-
allowed_intermediate_particles=["N(1700)", "Sigma(1750)"],
148-
formalism="helicity",
149-
mass_conservation_factor=0,
150-
)
151-
stm.set_allowed_interaction_types([InteractionType.STRONG, InteractionType.EM])
152-
problem_sets = stm.create_problem_sets()
153-
some_problem_set = normalize_state_ids(problem_sets[3600.0][0])
154-
assert set(some_problem_set.initial_facts.initial_states) == {0}
155-
assert set(some_problem_set.initial_facts.final_states) == {1, 2, 3}
120+
def describe_normalize_state_ids():
121+
def it_normalizes_a_reaction(jpsi2pksigma_reaction: ReactionInfo):
122+
reaction012 = jpsi2pksigma_reaction
123+
reaction123 = normalize_state_ids(reaction012)
124+
assert set(reaction123.initial_state) == {0}
125+
assert set(reaction123.final_state) == {1, 2, 3}
126+
127+
transitions123 = normalize_state_ids(reaction012.transitions)
128+
for transition012, transition123 in zip(
129+
reaction012.transitions, transitions123, strict=True
130+
):
131+
assert set(transition123.initial_states) == {0}
132+
assert set(transition123.final_states) == {1, 2, 3}
133+
assert set(transition123.intermediate_states) == {4}
134+
135+
topology123 = normalize_state_ids(transition123.topology)
136+
assert topology123.incoming_edge_ids == {0}
137+
assert topology123.outgoing_edge_ids == {1, 2, 3}
138+
assert topology123.intermediate_edge_ids == {4}
139+
140+
for i in transition012.states:
141+
assert transition012.states[i] == transition123.states[i + 1]
142+
143+
def it_normalizes_a_problem_set():
144+
stm = StateTransitionManager(
145+
initial_state=[("J/psi(1S)", [-1, +1])],
146+
final_state=["K0", "Sigma+", "p~"],
147+
allowed_intermediate_particles=["N(1700)", "Sigma(1750)"],
148+
formalism="helicity",
149+
mass_conservation_factor=0,
150+
)
151+
stm.set_allowed_interaction_types([InteractionType.STRONG, InteractionType.EM])
152+
problem_sets = stm.create_problem_sets()
153+
some_problem_set = normalize_state_ids(problem_sets[3600.0][0])
154+
assert set(some_problem_set.initial_facts.initial_states) == {0}
155+
assert set(some_problem_set.initial_facts.final_states) == {1, 2, 3}
156156

157157

158158
def test_permute_equal_final_states(

tests/io_serialization/test_amplitude.py

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,22 @@
1818
if TYPE_CHECKING:
1919
from ampform_dpd.io.serialization.format import ModelDefinition
2020

21+
half = sp.Rational(1, 2)
2122

22-
def test_get_decay_product_helicities(model_definition: ModelDefinition):
23+
24+
@pytest.mark.parametrize(
25+
("chain_id", "expected"),
26+
[
27+
(0, ((3, 0), (1, +half))),
28+
(15, ((1, +half), (2, 0))),
29+
(-1, ((2, 0), (3, 0))),
30+
],
31+
)
32+
def test_get_decay_product_helicities(
33+
model_definition: ModelDefinition, chain_id: int, expected: tuple
34+
):
2335
chain_defs = get_decay_chains(model_definition)
24-
half = sp.Rational(1 / 2)
25-
assert _get_decay_product_helicities(chain_defs[0]) == ((3, 0), (1, +half))
26-
assert _get_decay_product_helicities(chain_defs[15]) == ((1, +half), (2, 0))
27-
assert _get_decay_product_helicities(chain_defs[-1]) == ((2, 0), (3, 0))
36+
assert _get_decay_product_helicities(chain_defs[chain_id]) == expected
2837

2938

3039
def test_get_existing_subsystem_ids(model_definition: ModelDefinition):
@@ -35,25 +44,28 @@ def test_get_existing_subsystem_ids(model_definition: ModelDefinition):
3544
def test_get_final_state_helicities(model_definition: ModelDefinition, chain_id: int):
3645
chain_defs = get_decay_chains(model_definition)
3746
assert len(chain_defs) == 26
38-
if chain_id in {19, 20, 22, 25}:
39-
λp = -sp.Rational(1 / 2)
40-
else:
41-
λp = +sp.Rational(1 / 2)
47+
λp = -half if chain_id in {19, 20, 22, 25} else +half
4248
assert _get_final_state_helicities(chain_defs[chain_id]) == {1: λp, 2: 0, 3: 0}
4349

4450

45-
def test_get_resonance_helicity(model_definition: ModelDefinition):
51+
@pytest.mark.parametrize(
52+
("chain_id", "expected_node", "expected_helicity"),
53+
[
54+
(0, (3, 1), +half),
55+
(1, (3, 1), -half),
56+
(-1, (2, 3), 0),
57+
],
58+
)
59+
def test_get_resonance_helicity(
60+
model_definition: ModelDefinition,
61+
chain_id: int,
62+
expected_node: tuple[int, int],
63+
expected_helicity: sp.Rational,
64+
):
4665
chain_defs = get_decay_chains(model_definition)
47-
half = sp.Rational(1 / 2)
48-
node, helicity = _get_resonance_helicity(chain_defs[0])
49-
assert node == (3, 1)
50-
assert helicity == +half
51-
node, helicity = _get_resonance_helicity(chain_defs[1])
52-
assert node == (3, 1)
53-
assert helicity == -half
54-
node, helicity = _get_resonance_helicity(chain_defs[-1])
55-
assert node == (2, 3)
56-
assert helicity == 0
66+
node, helicity = _get_resonance_helicity(chain_defs[chain_id])
67+
assert node == expected_node
68+
assert helicity == expected_helicity
5769

5870

5971
def test_get_weight(model_definition: ModelDefinition):

tests/io_serialization/test_compiler.py

Lines changed: 95 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -40,38 +40,102 @@ def compiled_workspaces(workspace: Workspace) -> dict[str, CompiledWorkspace]:
4040
}
4141

4242

43-
def test_default_targets_are_checksum_targets(
44-
compiled_workspaces: dict[str, CompiledWorkspace],
45-
):
46-
for compiled in compiled_workspaces.values():
47-
assert set(compiled.functions) == set(CHECKSUM_IDS)
48-
assert set(compiled.coordinate_maps) == {"default_model"}
49-
50-
51-
@pytest.mark.parametrize("backend", BACKENDS)
52-
@pytest.mark.parametrize("checksum", CHECKSUMS, ids=CHECKSUM_IDS)
53-
def test_reproduce_checksums(
54-
compiled_workspaces: dict[str, CompiledWorkspace],
55-
model_definition: ModelDefinition,
56-
checksum: dict[str, Any],
57-
backend: str,
58-
):
59-
compiled = compiled_workspaces[backend]
60-
value = _evaluate(compiled, model_definition, checksum)
61-
assert value == pytest.approx(_parse_checksum(checksum["value"]), rel=1e-9)
62-
43+
def describe_compile_workspace():
44+
def it_uses_checksum_targets_by_default(
45+
compiled_workspaces: dict[str, CompiledWorkspace],
46+
):
47+
for compiled in compiled_workspaces.values():
48+
assert set(compiled.functions) == set(CHECKSUM_IDS)
49+
assert set(compiled.coordinate_maps) == {"default_model"}
50+
51+
@pytest.mark.parametrize("backend", BACKENDS)
52+
@pytest.mark.parametrize("checksum", CHECKSUMS, ids=CHECKSUM_IDS)
53+
def it_reproduces_checksums(
54+
compiled_workspaces: dict[str, CompiledWorkspace],
55+
model_definition: ModelDefinition,
56+
checksum: dict[str, Any],
57+
backend: str,
58+
):
59+
compiled = compiled_workspaces[backend]
60+
value = _evaluate(compiled, model_definition, checksum)
61+
assert value == pytest.approx(_parse_checksum(checksum["value"]), rel=1e-9)
62+
63+
@pytest.mark.parametrize("checksum", CHECKSUMS, ids=CHECKSUM_IDS)
64+
def it_makes_backends_agree(
65+
compiled_workspaces: dict[str, CompiledWorkspace],
66+
model_definition: ModelDefinition,
67+
checksum: dict[str, Any],
68+
):
69+
values = {
70+
backend: _evaluate(compiled, model_definition, checksum)
71+
for backend, compiled in compiled_workspaces.items()
72+
}
73+
assert values["jax"] == pytest.approx(values["numpy"], rel=1e-12)
6374

64-
@pytest.mark.parametrize("checksum", CHECKSUMS, ids=CHECKSUM_IDS)
65-
def test_backends_agree(
66-
compiled_workspaces: dict[str, CompiledWorkspace],
67-
model_definition: ModelDefinition,
68-
checksum: dict[str, Any],
69-
):
70-
values = {
71-
backend: _evaluate(compiled, model_definition, checksum)
72-
for backend, compiled in compiled_workspaces.items()
73-
}
74-
assert values["jax"] == pytest.approx(values["numpy"], rel=1e-12)
75+
def it_compiles_distribution_coordinates(model_definition: ModelDefinition):
76+
workspace = load_workspace(model_definition)
77+
compiled = compile_workspace(
78+
workspace, backend="numpy", targets=["default_model"]
79+
)
80+
coordinates = compiled.coordinate_maps["default_model"]
81+
point = {"m_31": 1.9101377207489973, "cos_theta_31": -0.2309352648098208}
82+
assert set(coordinates) == {"sigma1", "sigma2", "sigma3"}
83+
assert all(float(function(point)) > 0 for function in coordinates.values())
84+
85+
def it_compiles_a_named_function(workspace: Workspace):
86+
compiled = compile_workspace(workspace, backend="numpy", targets=["L1600_BW"])
87+
assert compiled.coordinate_maps == {}
88+
assert compiled.coordinates == {}
89+
90+
def it_selects_distribution_coordinates(workspace: Workspace):
91+
compiled = compile_workspace(
92+
workspace,
93+
backend="numpy",
94+
targets=["default_model"],
95+
coordinates=["sigma2", "sigma3"],
96+
)
97+
assert compiled.coordinates["default_model"] == ("sigma2", "sigma3")
98+
99+
def it_overrides_distribution_parameters(workspace: Workspace):
100+
model = workspace.distributions["default_model"]
101+
coupling_overrides = {
102+
symbol: 0
103+
for symbol in model.parameter_defaults
104+
if str(symbol).startswith("c^")
105+
}
106+
compiled = compile_workspace(
107+
workspace,
108+
backend="numpy",
109+
targets=["default_model"],
110+
parameter_overrides=coupling_overrides,
111+
)
112+
point = {"m_31": 1.9101377207489973, "cos_theta_31": -0.2309352648098208}
113+
invariants = {
114+
name: function(point)
115+
for name, function in compiled.coordinate_maps["default_model"].items()
116+
}
117+
value = compiled.functions["default_model"](invariants)
118+
assert float(value) == pytest.approx(0)
119+
120+
def it_rejects_unknown_backends_targets_and_coordinates(workspace: Workspace):
121+
with pytest.raises(ValueError, match="Unsupported numerical backend"):
122+
compile_workspace(workspace, backend="unknown")
123+
with pytest.raises(KeyError, match="missing"):
124+
compile_workspace(workspace, backend="numpy", targets=["missing"])
125+
with pytest.raises(ValueError, match="two distinct invariants"):
126+
compile_workspace(
127+
workspace,
128+
backend="numpy",
129+
targets=["default_model"],
130+
coordinates=["sigma1"],
131+
)
132+
133+
def it_reports_a_missing_backend_package(
134+
workspace: Workspace, monkeypatch: pytest.MonkeyPatch
135+
):
136+
monkeypatch.setattr("importlib.util.find_spec", lambda _: None)
137+
with pytest.raises(ImportError, match="requires the optional 'jax' package"):
138+
compile_workspace(workspace, backend="jax", targets=["L1600_BW"])
75139

76140

77141
def _evaluate(
@@ -125,70 +189,3 @@ def _parse_checksum(value: complex | str, /) -> complex:
125189
if isinstance(value, str):
126190
return complex(value.replace(" ", "").replace("i", "j"))
127191
return complex(value)
128-
129-
130-
def test_compile_distribution_coordinates(model_definition: ModelDefinition):
131-
workspace = load_workspace(model_definition)
132-
compiled = compile_workspace(workspace, backend="numpy", targets=["default_model"])
133-
coordinates = compiled.coordinate_maps["default_model"]
134-
point = {"m_31": 1.9101377207489973, "cos_theta_31": -0.2309352648098208}
135-
assert set(coordinates) == {"sigma1", "sigma2", "sigma3"}
136-
assert all(float(function(point)) > 0 for function in coordinates.values())
137-
138-
139-
def test_compile_named_function(workspace: Workspace):
140-
compiled = compile_workspace(workspace, backend="numpy", targets=["L1600_BW"])
141-
assert compiled.coordinate_maps == {}
142-
assert compiled.coordinates == {}
143-
144-
145-
def test_select_distribution_coordinates(workspace: Workspace):
146-
compiled = compile_workspace(
147-
workspace,
148-
backend="numpy",
149-
targets=["default_model"],
150-
coordinates=["sigma2", "sigma3"],
151-
)
152-
assert compiled.coordinates["default_model"] == ("sigma2", "sigma3")
153-
154-
155-
def test_override_distribution_parameters(workspace: Workspace):
156-
model = workspace.distributions["default_model"]
157-
coupling_overrides = {
158-
symbol: 0 for symbol in model.parameter_defaults if str(symbol).startswith("c^")
159-
}
160-
compiled = compile_workspace(
161-
workspace,
162-
backend="numpy",
163-
targets=["default_model"],
164-
parameter_overrides=coupling_overrides,
165-
)
166-
point = {"m_31": 1.9101377207489973, "cos_theta_31": -0.2309352648098208}
167-
invariants = {
168-
name: function(point)
169-
for name, function in compiled.coordinate_maps["default_model"].items()
170-
}
171-
value = compiled.functions["default_model"](invariants)
172-
assert float(value) == pytest.approx(0)
173-
174-
175-
def test_rejects_unknown_backend_and_target(workspace: Workspace):
176-
with pytest.raises(ValueError, match="Unsupported numerical backend"):
177-
compile_workspace(workspace, backend="unknown")
178-
with pytest.raises(KeyError, match="missing"):
179-
compile_workspace(workspace, backend="numpy", targets=["missing"])
180-
with pytest.raises(ValueError, match="two distinct invariants"):
181-
compile_workspace(
182-
workspace,
183-
backend="numpy",
184-
targets=["default_model"],
185-
coordinates=["sigma1"],
186-
)
187-
188-
189-
def test_reports_missing_backend_package(
190-
workspace: Workspace, monkeypatch: pytest.MonkeyPatch
191-
):
192-
monkeypatch.setattr("importlib.util.find_spec", lambda _: None)
193-
with pytest.raises(ImportError, match="requires the optional 'jax' package"):
194-
compile_workspace(workspace, backend="jax", targets=["L1600_BW"])

0 commit comments

Comments
 (0)