-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14 from jacanchaplais/tests
Added testing, updated repeated_hadronize(), and ensured python 3.8 compliance
- Loading branch information
Showing
8 changed files
with
265 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
name: tests | ||
|
||
on: | ||
- push | ||
- pull_request | ||
|
||
jobs: | ||
test: | ||
runs-on: ${{ matrix.os }} | ||
strategy: | ||
matrix: | ||
os: [ubuntu-latest, macos-latest] | ||
pyver: ["3.8", "3.9", "3.10"] | ||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- name: Install Conda environment from environment.yml | ||
uses: mamba-org/provision-with-micromamba@main | ||
with: | ||
cache-env: true | ||
extra-specs: | | ||
python=${{ matrix.pyver }} | ||
pytest=6.2.5 | ||
hypothesis=6.62.0 | ||
- name: Install showerpipe | ||
shell: bash -l {0} | ||
run: pip install . | ||
|
||
- name: Run tests | ||
shell: bash -l {0} | ||
run: pytest |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,6 +43,7 @@ htmlcov/ | |
nosetests.xml | ||
coverage.xml | ||
*.cover | ||
.hypothesis/ | ||
|
||
# Translations | ||
*.mo | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
import tempfile as tf | ||
import contextlib as ctx | ||
from pathlib import Path | ||
import operator as op | ||
import itertools as it | ||
import typing as ty | ||
import shutil | ||
|
||
import pytest | ||
from hypothesis import given, settings, strategies as st | ||
import numpy as np | ||
import numpy.lib.recfunctions as rfn | ||
|
||
import showerpipe as shp | ||
|
||
|
||
TEST_DIR = Path(__file__).parent.resolve() | ||
DATA_PATH = TEST_DIR / "tt_bb_100.lhe.gz" | ||
|
||
|
||
def all_equal(iterable: ty.Iterable[ty.Any]) -> bool: | ||
"""Returns True if all the elements are equal to each other.""" | ||
g = it.groupby(iterable) | ||
return next(g, True) and not next(g, False) | ||
|
||
|
||
@ctx.contextmanager | ||
def config_file( | ||
isr: bool = True, fsr: bool = True, mpi: bool = True, hadron: bool = True | ||
) -> ty.Generator[Path, None, None]: | ||
"""Context manager creates a temporary config file, and yields the | ||
path, for the purpose of instantiating a new ``PythiaGenerator``. | ||
Parameters | ||
---------- | ||
isr : bool | ||
Initial state radiation. Default is ``True``. | ||
fsr : bool | ||
Final state radiation. Default is ``True``. | ||
mpi : bool | ||
Multi-parton interaction. Default is ``True``. | ||
hadron : bool | ||
Hadronization. Default is ``True``. | ||
""" | ||
f = tf.NamedTemporaryFile("w") | ||
switch = {True: "on", False: "off"} | ||
f.write(f"PartonLevel:ISR = {switch[isr]}\n") | ||
f.write(f"PartonLevel:FSR = {switch[fsr]}\n") | ||
f.write(f"PartonLevel:MPI = {switch[mpi]}\n") | ||
if hadron is True: | ||
f.write("HadronLevel:Hadronize = on\n") | ||
else: | ||
f.write("HadronLevel:all = off\n") | ||
f.seek(0) | ||
try: | ||
yield Path(f.name) | ||
finally: | ||
f.close() | ||
|
||
|
||
@st.composite | ||
def generators( | ||
draw: st.DrawFn, | ||
min_seed: int = 1, | ||
max_seed: int = 10_000, | ||
**config_kwargs: bool, | ||
) -> shp.generator.PythiaGenerator: | ||
"""Custom strategy providing a ``PythiaGenerator`` with a random | ||
seed. | ||
""" | ||
seed = draw(st.integers(min_seed, max_seed)) | ||
with config_file(**config_kwargs) as conf_path: | ||
return shp.generator.PythiaGenerator(conf_path, DATA_PATH, seed, False) | ||
|
||
|
||
@given(generators()) | ||
@settings(max_examples=5, deadline=None) | ||
def test_gen_len(gen: shp.generator.PythiaGenerator) -> None: | ||
"""Tests that the generator length matches the number of events.""" | ||
assert len(gen) == 100 | ||
|
||
|
||
@given(generators()) | ||
@settings(max_examples=5, deadline=None) | ||
def test_event_len(gen: shp.generator.PythiaGenerator) -> None: | ||
"""Tests that the event length and data lengths match.""" | ||
event = next(gen) | ||
event_len = len(event) | ||
prop_names = ( | ||
"edges", | ||
"pmu", | ||
"color", | ||
"pdg", | ||
"final", | ||
"helicity", | ||
"status", | ||
) | ||
props = (prop(event) for prop in map(op.attrgetter, prop_names)) | ||
lens = tuple(map(len, props)) | ||
assert event_len != 0 and all_equal(lens) and lens[0] == event_len | ||
|
||
|
||
@st.composite | ||
def hadron_repeater( | ||
draw: st.DrawFn, | ||
min_seed: int = 1, | ||
max_seed: int = 10_000, | ||
reps: int = 100, | ||
) -> ty.Iterable[shp.generator.PythiaEvent]: | ||
"""Custom strategy, providing a repeated hadronization generator | ||
with a random seed, and a user-defined number of repetitions. | ||
""" | ||
gen = draw(generators(min_seed, max_seed, hadron=False)) | ||
_ = next(gen) | ||
return shp.generator.repeat_hadronize(gen, reps=reps) | ||
|
||
|
||
@given(gen=generators(hadron=True)) | ||
@settings(max_examples=1, deadline=None) | ||
def test_rep_hadron_conf_valerr(gen: shp.generator.PythiaGenerator) -> None: | ||
"""Tests if ``ValueError`` is raised for incorrect config.""" | ||
_ = next(gen) | ||
with pytest.raises(ValueError): | ||
hadron_gen = shp.generator.repeat_hadronize(gen, 10, False) | ||
_ = next(hadron_gen) | ||
|
||
|
||
def test_rep_hadron_conf_keyerr() -> None: | ||
"""Tests if ``KeyError`` is raised for incorrect config.""" | ||
with ctx.ExitStack() as stack: | ||
stack.enter_context(pytest.raises(KeyError)) | ||
conf = stack.enter_context(tf.NamedTemporaryFile("wb")) | ||
prelim_path = stack.enter_context(config_file()) | ||
prelim_conf = stack.enter_context(open(prelim_path, "rb")) | ||
shutil.copyfileobj(prelim_conf, conf) | ||
conf.write(b"HadronLevel:all = off") | ||
conf.seek(0) | ||
gen = shp.generator.PythiaGenerator(conf.name, DATA_PATH) | ||
_ = next(gen) | ||
hadron_gen = shp.generator.repeat_hadronize(gen, 10, False) | ||
_ = next(hadron_gen) | ||
|
||
|
||
@given(gen=generators(hadron=False)) | ||
@settings(max_examples=1, deadline=None) | ||
def test_rep_hadron_conf_runerr(gen: shp.generator.PythiaGenerator) -> None: | ||
"""Tests if ``RuntimeError`` is raised for instantiating without | ||
existing event.""" | ||
with pytest.raises(RuntimeError): | ||
hadron_gen = shp.generator.repeat_hadronize(gen, 10, False) | ||
_ = next(hadron_gen) | ||
|
||
|
||
@given(gen=generators(hadron=False)) | ||
@settings(max_examples=1, deadline=None) | ||
def test_rep_hadron_conf_stopit(gen: shp.generator.PythiaGenerator) -> None: | ||
"""Tests if ``StopIteration`` is raised for iterating the | ||
``PythiaGenerator`` before between iterations of the repeat | ||
hadronization.""" | ||
_ = next(gen) | ||
with pytest.raises(StopIteration): | ||
hadron_gen = shp.generator.repeat_hadronize(gen, 10, False) | ||
_ = next(hadron_gen) | ||
_ = next(gen) | ||
_ = next(hadron_gen) | ||
|
||
|
||
@given(gen=hadron_repeater()) | ||
@settings(max_examples=20, deadline=None) | ||
def test_rep_hadron_uniq(gen: ty.Iterable[shp.generator.PythiaEvent]) -> None: | ||
"""Tests if the events following repeated hadronization are unique.""" | ||
data = sorted(map(op.attrgetter("pdg"), gen), key=len) | ||
for _, data_arrays in it.groupby(data, len): | ||
data_pairs = it.combinations(data_arrays, 2) | ||
assert any(it.starmap(np.array_equal, data_pairs)) is False | ||
|
||
|
||
def rep_hadron_colorless(gen: ty.Iterable[shp.generator.PythiaEvent]) -> bool: | ||
"""Given a rehadronization generator, return ``False`` if colored | ||
particles make it to the final state. | ||
""" | ||
for event in gen: | ||
final_colors = rfn.structured_to_unstructured(event.color[event.final]) | ||
if not np.all(final_colors == 0): | ||
return False | ||
return True | ||
|
||
|
||
@given(gen=generators(hadron=False)) | ||
@settings(max_examples=5, deadline=None) | ||
def test_rep_hadron_colorless(gen: shp.generator.PythiaGenerator) -> None: | ||
"""Tests if colored particles make it to final state after | ||
rehadronization. Repeats for successive hard events. | ||
""" | ||
for _ in it.islice(gen, 50): | ||
hadron_gen = shp.generator.repeat_hadronize(gen, 10, False) | ||
assert rep_hadron_colorless(hadron_gen) is True |
Binary file not shown.