From 1c1b43ad979664ed8348a635f1bbeb364a6c200a Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Wed, 7 Jun 2023 16:11:40 +0200 Subject: [PATCH 01/24] add DecayChain.to_string method plus tests --- src/decaylanguage/decay/decay.py | 27 +++++++++++++++++++++++++++ tests/decay/test_descriptor.py | 23 +++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 tests/decay/test_descriptor.py diff --git a/src/decaylanguage/decay/decay.py b/src/decaylanguage/decay/decay.py index d756ccb5..9153f9cd 100644 --- a/src/decaylanguage/decay/decay.py +++ b/src/decaylanguage/decay/decay.py @@ -602,6 +602,33 @@ def ndecays(self) -> int: """ return len(self.decays) + def to_string(self, arrow = "->") -> str: + """ + One-line string representation of the entire decay chain. Sub-decays are + enclosed in round parentheses. + """ + + def _str( + decay: dict[str, list[dict[str, float | str | list[Any]]]] | str, + top = False, + ) -> str: + if isinstance(decay, dict): + mother = list(decay.keys())[0] + fs = [_str(fsp) for i_decay in decay[mother] for fsp in i_decay["fs"]] + descriptor = " ".join([mother, arrow] + fs) + if not top: + return f"({descriptor})" + return descriptor + + elif isinstance(decay, str): + return decay + + else: + raise TypeError(f"Don't know how to handle {type(decay)}") + + dc_dict = self.to_dict() + return _str(dc_dict, True) + def print_as_tree(self) -> None: # pragma: no cover """ Tree-structure like print of the entire decay chain. diff --git a/tests/decay/test_descriptor.py b/tests/decay/test_descriptor.py new file mode 100644 index 00000000..ec672e13 --- /dev/null +++ b/tests/decay/test_descriptor.py @@ -0,0 +1,23 @@ +import pytest +from decaylanguage import DecayMode, DecayChain + +dm1 = DecayMode(0.6770, "D0 pi+") # D*+ +dm2 = DecayMode(0.0124, "K_S0 pi0") # D0 +dm3 = DecayMode(0.692, "pi+ pi-") # K_S0 +dm4 = DecayMode(0.98823, "gamma gamma") # pi- +dm5 = DecayMode(0.0105, "D- tau+ nu_tau") # B0 +dm6 = DecayMode(0.0938, "K+ pi- pi-") #D- +dm7 = DecayMode(0.0931, "pi+ pi+ pi- anti-nu_tau") # tau+ +dm8 = DecayMode(1.85e-6, "phi phi'") # B_s0 +dm9a = DecayMode(0.491, "K+ K-") # phi +dm9b = DecayMode(0.154, "pi+ pi- pi0") # phi + +@pytest.mark.parametrize("dc,expected", [ + (DecayChain("D0", {"D0":dm2, "K_S0":dm3, "pi0":dm4}), "D0 -> (K_S0 -> pi+ pi-) (pi0 -> gamma gamma)"), + (DecayChain("D*+", {"D*+":dm1, "D0":dm2, "K_S0":dm3, "pi0":dm4}), "D*+ -> (D0 -> (K_S0 -> pi+ pi-) (pi0 -> gamma gamma)) pi+"), + (DecayChain("B0", {"B0": dm5, "D-": dm6, "tau+": dm7}), "B0 -> (D- -> K+ pi- pi-) nu_tau (tau+ -> anti-nu_tau pi+ pi+ pi-)"), + (DecayChain("B_s0", {"B_s0": dm8, "phi": dm9a, "phi'": dm9b}), "B_s0 -> (phi -> K+ K-) (phi' -> pi+ pi- pi0)"), +]) +def test_descriptor(dc: DecayChain, expected: str): + descriptor = dc.to_string() + assert descriptor == expected From e6e495e5000b5fab66a7ba42330a270c84b56f0f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 7 Jun 2023 14:13:22 +0000 Subject: [PATCH 02/24] style: pre-commit fixes --- src/decaylanguage/decay/decay.py | 6 ++-- tests/decay/test_descriptor.py | 53 ++++++++++++++++++++++---------- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/src/decaylanguage/decay/decay.py b/src/decaylanguage/decay/decay.py index 9153f9cd..8b2a3711 100644 --- a/src/decaylanguage/decay/decay.py +++ b/src/decaylanguage/decay/decay.py @@ -602,7 +602,7 @@ def ndecays(self) -> int: """ return len(self.decays) - def to_string(self, arrow = "->") -> str: + def to_string(self, arrow="->") -> str: """ One-line string representation of the entire decay chain. Sub-decays are enclosed in round parentheses. @@ -610,12 +610,12 @@ def to_string(self, arrow = "->") -> str: def _str( decay: dict[str, list[dict[str, float | str | list[Any]]]] | str, - top = False, + top=False, ) -> str: if isinstance(decay, dict): mother = list(decay.keys())[0] fs = [_str(fsp) for i_decay in decay[mother] for fsp in i_decay["fs"]] - descriptor = " ".join([mother, arrow] + fs) + descriptor = " ".join([mother, arrow, *fs]) if not top: return f"({descriptor})" return descriptor diff --git a/tests/decay/test_descriptor.py b/tests/decay/test_descriptor.py index ec672e13..989b52e1 100644 --- a/tests/decay/test_descriptor.py +++ b/tests/decay/test_descriptor.py @@ -1,23 +1,42 @@ +from __future__ import annotations + import pytest -from decaylanguage import DecayMode, DecayChain -dm1 = DecayMode(0.6770, "D0 pi+") # D*+ -dm2 = DecayMode(0.0124, "K_S0 pi0") # D0 -dm3 = DecayMode(0.692, "pi+ pi-") # K_S0 -dm4 = DecayMode(0.98823, "gamma gamma") # pi- -dm5 = DecayMode(0.0105, "D- tau+ nu_tau") # B0 -dm6 = DecayMode(0.0938, "K+ pi- pi-") #D- -dm7 = DecayMode(0.0931, "pi+ pi+ pi- anti-nu_tau") # tau+ -dm8 = DecayMode(1.85e-6, "phi phi'") # B_s0 -dm9a = DecayMode(0.491, "K+ K-") # phi -dm9b = DecayMode(0.154, "pi+ pi- pi0") # phi +from decaylanguage import DecayChain, DecayMode + +dm1 = DecayMode(0.6770, "D0 pi+") # D*+ +dm2 = DecayMode(0.0124, "K_S0 pi0") # D0 +dm3 = DecayMode(0.692, "pi+ pi-") # K_S0 +dm4 = DecayMode(0.98823, "gamma gamma") # pi- +dm5 = DecayMode(0.0105, "D- tau+ nu_tau") # B0 +dm6 = DecayMode(0.0938, "K+ pi- pi-") # D- +dm7 = DecayMode(0.0931, "pi+ pi+ pi- anti-nu_tau") # tau+ +dm8 = DecayMode(1.85e-6, "phi phi'") # B_s0 +dm9a = DecayMode(0.491, "K+ K-") # phi +dm9b = DecayMode(0.154, "pi+ pi- pi0") # phi + -@pytest.mark.parametrize("dc,expected", [ - (DecayChain("D0", {"D0":dm2, "K_S0":dm3, "pi0":dm4}), "D0 -> (K_S0 -> pi+ pi-) (pi0 -> gamma gamma)"), - (DecayChain("D*+", {"D*+":dm1, "D0":dm2, "K_S0":dm3, "pi0":dm4}), "D*+ -> (D0 -> (K_S0 -> pi+ pi-) (pi0 -> gamma gamma)) pi+"), - (DecayChain("B0", {"B0": dm5, "D-": dm6, "tau+": dm7}), "B0 -> (D- -> K+ pi- pi-) nu_tau (tau+ -> anti-nu_tau pi+ pi+ pi-)"), - (DecayChain("B_s0", {"B_s0": dm8, "phi": dm9a, "phi'": dm9b}), "B_s0 -> (phi -> K+ K-) (phi' -> pi+ pi- pi0)"), -]) +@pytest.mark.parametrize( + ("dc", "expected"), + [ + ( + DecayChain("D0", {"D0": dm2, "K_S0": dm3, "pi0": dm4}), + "D0 -> (K_S0 -> pi+ pi-) (pi0 -> gamma gamma)", + ), + ( + DecayChain("D*+", {"D*+": dm1, "D0": dm2, "K_S0": dm3, "pi0": dm4}), + "D*+ -> (D0 -> (K_S0 -> pi+ pi-) (pi0 -> gamma gamma)) pi+", + ), + ( + DecayChain("B0", {"B0": dm5, "D-": dm6, "tau+": dm7}), + "B0 -> (D- -> K+ pi- pi-) nu_tau (tau+ -> anti-nu_tau pi+ pi+ pi-)", + ), + ( + DecayChain("B_s0", {"B_s0": dm8, "phi": dm9a, "phi'": dm9b}), + "B_s0 -> (phi -> K+ K-) (phi' -> pi+ pi- pi0)", + ), + ], +) def test_descriptor(dc: DecayChain, expected: str): descriptor = dc.to_string() assert descriptor == expected From 4463eba60150d548d2b465426fd181e0fef526e8 Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Fri, 9 Jun 2023 11:46:12 +0200 Subject: [PATCH 03/24] Update src/decaylanguage/decay/decay.py Co-authored-by: Eduardo Rodrigues --- src/decaylanguage/decay/decay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decaylanguage/decay/decay.py b/src/decaylanguage/decay/decay.py index 8b2a3711..040173f2 100644 --- a/src/decaylanguage/decay/decay.py +++ b/src/decaylanguage/decay/decay.py @@ -602,7 +602,7 @@ def ndecays(self) -> int: """ return len(self.decays) - def to_string(self, arrow="->") -> str: + def to_string(self, arrow: str ="->") -> str: """ One-line string representation of the entire decay chain. Sub-decays are enclosed in round parentheses. From 88164a6e1ac1e96cd1509c8de5a8fcab343b16f1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 9 Jun 2023 09:46:22 +0000 Subject: [PATCH 04/24] style: pre-commit fixes --- src/decaylanguage/decay/decay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decaylanguage/decay/decay.py b/src/decaylanguage/decay/decay.py index 040173f2..81f66ca9 100644 --- a/src/decaylanguage/decay/decay.py +++ b/src/decaylanguage/decay/decay.py @@ -602,7 +602,7 @@ def ndecays(self) -> int: """ return len(self.decays) - def to_string(self, arrow: str ="->") -> str: + def to_string(self, arrow: str = "->") -> str: """ One-line string representation of the entire decay chain. Sub-decays are enclosed in round parentheses. From 26cc605f80531b335c2800a8dba25d78abdc9f90 Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Tue, 13 Jun 2023 17:57:38 +0200 Subject: [PATCH 05/24] add example to docstring, fix typo in test --- src/decaylanguage/decay/decay.py | 10 ++++++++++ tests/decay/test_descriptor.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/decaylanguage/decay/decay.py b/src/decaylanguage/decay/decay.py index 81f66ca9..d8375e0c 100644 --- a/src/decaylanguage/decay/decay.py +++ b/src/decaylanguage/decay/decay.py @@ -606,6 +606,16 @@ def to_string(self, arrow: str = "->") -> str: """ One-line string representation of the entire decay chain. Sub-decays are enclosed in round parentheses. + + Examples + -------- + >>> dm1 = DecayMode(0.6770, "D0 pi+") # D*+ + >>> dm2 = DecayMode(0.0124, "K_S0 pi0") # D0 + >>> dm3 = DecayMode(0.692, "pi+ pi-") # K_S0 + >>> dm4 = DecayMode(0.98823, "gamma gamma") # pi0 + >>> dc = DecayChain("D*+", {"D*+":dm1, "D0":dm2, "K_S0":dm3, "pi0":dm4}) + >>> print(dc.to_string()) + D*+ -> (D0 -> (K_S0 -> pi+ pi-) (pi0 -> gamma gamma)) pi+ """ def _str( diff --git a/tests/decay/test_descriptor.py b/tests/decay/test_descriptor.py index 989b52e1..c2cf6f88 100644 --- a/tests/decay/test_descriptor.py +++ b/tests/decay/test_descriptor.py @@ -7,7 +7,7 @@ dm1 = DecayMode(0.6770, "D0 pi+") # D*+ dm2 = DecayMode(0.0124, "K_S0 pi0") # D0 dm3 = DecayMode(0.692, "pi+ pi-") # K_S0 -dm4 = DecayMode(0.98823, "gamma gamma") # pi- +dm4 = DecayMode(0.98823, "gamma gamma") # pi0 dm5 = DecayMode(0.0105, "D- tau+ nu_tau") # B0 dm6 = DecayMode(0.0938, "K+ pi- pi-") # D- dm7 = DecayMode(0.0931, "pi+ pi+ pi- anti-nu_tau") # tau+ From 0691974bf01c0977be8cd694466dc6e450ba129e Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Wed, 14 Jun 2023 13:12:35 +0200 Subject: [PATCH 06/24] Update src/decaylanguage/decay/decay.py Co-authored-by: Eduardo Rodrigues --- src/decaylanguage/decay/decay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decaylanguage/decay/decay.py b/src/decaylanguage/decay/decay.py index d8375e0c..97bb64c2 100644 --- a/src/decaylanguage/decay/decay.py +++ b/src/decaylanguage/decay/decay.py @@ -620,7 +620,7 @@ def to_string(self, arrow: str = "->") -> str: def _str( decay: dict[str, list[dict[str, float | str | list[Any]]]] | str, - top=False, + top: bool = False, ) -> str: if isinstance(decay, dict): mother = list(decay.keys())[0] From 8833b58dc0eed1165dc994e347c4f45a8fd969bf Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Wed, 14 Jun 2023 13:13:48 +0200 Subject: [PATCH 07/24] use DaughtersDict --- src/decaylanguage/decay/decay.py | 11 ++++++----- tests/decay/test_descriptor.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/decaylanguage/decay/decay.py b/src/decaylanguage/decay/decay.py index d8375e0c..e4b21720 100644 --- a/src/decaylanguage/decay/decay.py +++ b/src/decaylanguage/decay/decay.py @@ -624,17 +624,18 @@ def _str( ) -> str: if isinstance(decay, dict): mother = list(decay.keys())[0] - fs = [_str(fsp) for i_decay in decay[mother] for fsp in i_decay["fs"]] - descriptor = " ".join([mother, arrow, *fs]) + fs = DaughtersDict( + [_str(fsp) for i_decay in decay[mother] for fsp in i_decay["fs"]] + ) + descriptor = " ".join([mother, arrow, fs.to_string()]) if not top: return f"({descriptor})" return descriptor - elif isinstance(decay, str): + if isinstance(decay, str): return decay - else: - raise TypeError(f"Don't know how to handle {type(decay)}") + raise TypeError(f"Don't know how to handle {type(decay)}") dc_dict = self.to_dict() return _str(dc_dict, True) diff --git a/tests/decay/test_descriptor.py b/tests/decay/test_descriptor.py index c2cf6f88..0605302a 100644 --- a/tests/decay/test_descriptor.py +++ b/tests/decay/test_descriptor.py @@ -29,7 +29,7 @@ ), ( DecayChain("B0", {"B0": dm5, "D-": dm6, "tau+": dm7}), - "B0 -> (D- -> K+ pi- pi-) nu_tau (tau+ -> anti-nu_tau pi+ pi+ pi-)", + "B0 -> (D- -> K+ pi- pi-) (tau+ -> anti-nu_tau pi+ pi+ pi-) nu_tau", ), ( DecayChain("B_s0", {"B_s0": dm8, "phi": dm9a, "phi'": dm9b}), From 3e46c92d5a98a7393f2f72cf916bf9e709e0fa4e Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Wed, 14 Jun 2023 13:58:59 +0200 Subject: [PATCH 08/24] enforce list type --- src/decaylanguage/decay/decay.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/decaylanguage/decay/decay.py b/src/decaylanguage/decay/decay.py index b41ac04c..243ccbfc 100644 --- a/src/decaylanguage/decay/decay.py +++ b/src/decaylanguage/decay/decay.py @@ -618,6 +618,12 @@ def to_string(self, arrow: str = "->") -> str: D*+ -> (D0 -> (K_S0 -> pi+ pi-) (pi0 -> gamma gamma)) pi+ """ + def _extract_fs(decay: dict[str, float | str | list[Any]]) -> list[Any]: + fs = decay["fs"] + if isinstance(fs, list): + return fs + raise TypeError(f"Expected list, not {type(fs)}") + def _str( decay: dict[str, list[dict[str, float | str | list[Any]]]] | str, top: bool = False, @@ -625,7 +631,11 @@ def _str( if isinstance(decay, dict): mother = list(decay.keys())[0] fs = DaughtersDict( - [_str(fsp) for i_decay in decay[mother] for fsp in i_decay["fs"]] + [ + _str(fsp) + for i_decay in decay[mother] + for fsp in _extract_fs(i_decay) + ] ) descriptor = " ".join([mother, arrow, fs.to_string()]) if not top: From 6a295522333b7b65de7fb5590395d45d5c840a7d Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Thu, 15 Jun 2023 15:12:49 +0200 Subject: [PATCH 09/24] functionality to expand decay chain dicts --- src/decaylanguage/dec/dec.py | 10 ++ src/decaylanguage/decay/decay.py | 188 ++++++++++++++++++++++++------- tests/dec/test_dec.py | 42 +++++++ 3 files changed, 201 insertions(+), 39 deletions(-) diff --git a/src/decaylanguage/dec/dec.py b/src/decaylanguage/dec/dec.py index e6cf7a0a..4e216afe 100644 --- a/src/decaylanguage/dec/dec.py +++ b/src/decaylanguage/dec/dec.py @@ -54,6 +54,7 @@ from particle.converters import PDG2EvtGenNameMap from .. import data +from ..decay.decay import _expand_decay_modes from ..utils import charge_conjugate_name from .enums import PhotosEnum @@ -390,6 +391,15 @@ def dict_jetset_definitions(self) -> dict[str, dict[int, int | float | str]]: self._check_parsing() return get_jetset_definitions(self._parsed_dec_file) + def expand_decay_modes(self, particle: str) -> list[str]: + """ + Return a list of expanded decay descriptors for the given particle + """ + self._check_parsing() + decay_chains = self.build_decay_chains(particle) + aliases = self.dict_aliases() + return _expand_decay_modes(decay_chains, aliases=aliases) + def list_lineshape_definitions(self) -> list[tuple[list[str], int]]: """ Return a list of all SetLineshapePW definitions in the input parsed file, diff --git a/src/decaylanguage/decay/decay.py b/src/decaylanguage/decay/decay.py index 243ccbfc..a7c208e2 100644 --- a/src/decaylanguage/decay/decay.py +++ b/src/decaylanguage/decay/decay.py @@ -8,7 +8,8 @@ from collections import Counter from copy import deepcopy -from typing import TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, TypeVar, Union +from itertools import product +from typing import TYPE_CHECKING, Any, Iterable, Iterator, TypeVar, Union from particle import PDGID, ParticleNotFound from particle.converters import EvtGenName2PDGIDBiMap @@ -446,7 +447,8 @@ def __str__(self) -> str: Self_DecayChain = TypeVar("Self_DecayChain", bound="DecayChain") -DecayModeDict = Dict[str, List[Dict[str, Union[float, str, List[Any]]]]] +DecayModeDict = dict[str, Union[float, str, list[Any]]] +DecayChainDict = dict[str, list[DecayModeDict]] def _has_no_subdecay(ds: list[Any]) -> bool: @@ -466,7 +468,7 @@ def _has_no_subdecay(ds: list[Any]) -> bool: def _build_decay_modes( - decay_modes: dict[str, DecayMode], dc_dict: DecayModeDict + decay_modes: dict[str, DecayMode], dc_dict: DecayChainDict ) -> None: """ Internal recursive function that identifies and creates all `DecayMode` instances @@ -505,6 +507,142 @@ def _build_decay_modes( decay_modes[mother] = DecayMode.from_dict(d) +def _expand_decay_modes( + decay_chain: DecayChainDict, + *, + top: bool = True, + aliases: dict[str, str] | None = None, +) -> list[str]: + """Given a dict with 1 key (the mother particle) whose value is a list of + decay modes, recursively replace all decay modes with decay descriptors. + + Parameters + ---------- + decay_chain: dict + + top: bool, optional, default=True + + aliases: dict[str, str], optional, default={} + + + Examples + -------- + A simple example with no sub-decays: + + { + "anti-D0": [ + { + "bf": 1.0, + "fs": ["K+", "pi-"], + "model": "PHSP", + "model_params": "" + } + ] + } + + becomes the dict + + { + "anti-D0": [ + "anti-D0 -> K+ pi-" + ] + } + + A more complicated example with a sub-decay and more than one mode: + { + "anti-D*0": [ + { + "bf": 0.619, + "fs": [ + { + "anti-D0": [ + { + "bf": 1.0, + "fs": ["K+", "pi-"], + "model": "PHSP", + "model_params": "" + } + ] + }, + "pi0" + ], + "model": "VSS", + "model_params": "" + }, + { + "bf": 0.381, + "fs": [ + { + "anti-D0": [ + { + "bf": 1.0, + "fs": ["K+", "pi-"], + "model": "PHSP", + "model_params": "" + } + ] + }, + "gamma" + ], + "model": "VSP_PWAVE", + "model_params": "" + } + ] + } + becomes the dict + { + "anti-D*0": [ + "anti-D*0 -> (anti-D0 -> K+ pi-) pi0", + "anti-D*0 -> (anti-D0 -> K+ pi-) gamma", + ] + } + """ + + def _get_modes(decay_chain: DecayChainDict) -> list[DecayModeDict]: + # The list of decay modes is the first (and only) value of the dict + assert len(decay_chain.values()) == 1 + modes = list(decay_chain.values()) + return modes[0] + + def _get_fs(decay: DecayModeDict) -> list[Any]: + fs = decay["fs"] + if isinstance(fs, list): + return fs + raise TypeError(f"Expected list, not {type(fs)}") + + # The mother particle is the first (and only) key of the dict + assert len(decay_chain.keys()) == 1 + orig_mother = list(decay_chain.keys())[0] + mother = aliases.get(orig_mother, orig_mother) if aliases else orig_mother + + for mode in _get_modes(decay_chain): + for fsp in _get_fs(mode): + if isinstance(fsp, dict): + _expand_decay_modes(fsp, top=False, aliases=aliases) + + # Replace dicts with strings (decay descriptors) + expanded_modes = [] + for mode in _get_modes(decay_chain): + fsp_options = [] + for fsp in _get_fs(mode): + if isinstance(fsp, dict): + fsp_options += [_get_modes(fsp)] + elif isinstance(fsp, str): + fsp_options += [[fsp]] # type: ignore[list-item] + for expanded_mode in product(*fsp_options): + # TODO: delegate descriptor-building to another function + # allow for different conventions? + final_state = DaughtersDict(list(expanded_mode)).to_string() + descriptor = f"{mother} -> {final_state}" + if not top: + descriptor = f"({descriptor})" + expanded_modes += [descriptor] + + decay_chain[orig_mother] = expanded_modes # type: ignore[assignment] + + return expanded_modes + + class DecayChain: """ Class holding a particle decay chain, which is typically a top-level decay @@ -553,7 +691,7 @@ def __init__(self, mother: str, decays: dict[str, DecayMode]) -> None: @classmethod def from_dict( - cls: type[Self_DecayChain], decay_chain_dict: DecayModeDict + cls: type[Self_DecayChain], decay_chain_dict: DecayChainDict ) -> Self_DecayChain: """ Constructor from a decay chain represented as a dictionary. @@ -602,7 +740,7 @@ def ndecays(self) -> int: """ return len(self.decays) - def to_string(self, arrow: str = "->") -> str: + def to_string(self) -> str: """ One-line string representation of the entire decay chain. Sub-decays are enclosed in round parentheses. @@ -617,38 +755,10 @@ def to_string(self, arrow: str = "->") -> str: >>> print(dc.to_string()) D*+ -> (D0 -> (K_S0 -> pi+ pi-) (pi0 -> gamma gamma)) pi+ """ - - def _extract_fs(decay: dict[str, float | str | list[Any]]) -> list[Any]: - fs = decay["fs"] - if isinstance(fs, list): - return fs - raise TypeError(f"Expected list, not {type(fs)}") - - def _str( - decay: dict[str, list[dict[str, float | str | list[Any]]]] | str, - top: bool = False, - ) -> str: - if isinstance(decay, dict): - mother = list(decay.keys())[0] - fs = DaughtersDict( - [ - _str(fsp) - for i_decay in decay[mother] - for fsp in _extract_fs(i_decay) - ] - ) - descriptor = " ".join([mother, arrow, fs.to_string()]) - if not top: - return f"({descriptor})" - return descriptor - - if isinstance(decay, str): - return decay - - raise TypeError(f"Don't know how to handle {type(decay)}") - dc_dict = self.to_dict() - return _str(dc_dict, True) + descriptors = _expand_decay_modes(dc_dict, top=True) + assert len(descriptors) == 1 + return descriptors[0] def print_as_tree(self) -> None: # pragma: no cover """ @@ -702,7 +812,7 @@ def print_as_tree(self) -> None: # pragma: no cover # TODO: simplify logic and perform further checks def _print( - decay_dict: dict[str, list[dict[str, float | str | list[Any]]]], + decay_dict: DecayChainDict, depth: int = 0, link: bool = False, last: bool = False, @@ -734,7 +844,7 @@ def _print( dc_dict = self.to_dict() _print(dc_dict) - def to_dict(self) -> dict[str, list[dict[str, float | str | list[Any]]]]: + def to_dict(self) -> DecayChainDict: """ Return the decay chain as a dictionary representation. The format is the same as `DecFileParser.build_decay_chains(...)`. @@ -757,7 +867,7 @@ def to_dict(self) -> dict[str, list[dict[str, float | str | list[Any]]]]: """ # Ideally this would be a recursive type, DecayDict = dict[str, list[str | DecayDict]] - DecayDict = Dict[str, List[Any]] + DecayDict = dict[str, list[Any]] def recursively_replace(mother: str) -> DecayDict: dm = self.decays[mother].to_dict() diff --git a/tests/dec/test_dec.py b/tests/dec/test_dec.py index e7e280c0..f3da7275 100644 --- a/tests/dec/test_dec.py +++ b/tests/dec/test_dec.py @@ -537,6 +537,48 @@ def test_build_decay_chains(): assert p.build_decay_chains("D+", stable_particles=["pi0"]) == output +def test_expand_decay_chains(): + p = DecFileParser(DIR / "../data/test_example_Dst.dec") + p.parse() + + output = { + "D*+": [ + "D*+ -> (D0 -> K- pi+) pi+", + "D*+ -> (D+ -> (pi0 -> gamma gamma) K- pi+ pi+) (pi0 -> gamma gamma)", + "D*+ -> (D+ -> (pi0 -> gamma gamma) K- pi+ pi+) (pi0 -> e+ e- gamma)", + "D*+ -> (D+ -> (pi0 -> gamma gamma) K- pi+ pi+) (pi0 -> e+ e+ e- e-)", + "D*+ -> (D+ -> (pi0 -> gamma gamma) K- pi+ pi+) (pi0 -> e+ e-)", + "D*+ -> (D+ -> (pi0 -> e+ e- gamma) K- pi+ pi+) (pi0 -> gamma gamma)", + "D*+ -> (D+ -> (pi0 -> e+ e- gamma) K- pi+ pi+) (pi0 -> e+ e- gamma)", + "D*+ -> (D+ -> (pi0 -> e+ e- gamma) K- pi+ pi+) (pi0 -> e+ e+ e- e-)", + "D*+ -> (D+ -> (pi0 -> e+ e- gamma) K- pi+ pi+) (pi0 -> e+ e-)", + "D*+ -> (D+ -> (pi0 -> e+ e+ e- e-) K- pi+ pi+) (pi0 -> gamma gamma)", + "D*+ -> (D+ -> (pi0 -> e+ e+ e- e-) K- pi+ pi+) (pi0 -> e+ e- gamma)", + "D*+ -> (D+ -> (pi0 -> e+ e+ e- e-) K- pi+ pi+) (pi0 -> e+ e+ e- e-)", + "D*+ -> (D+ -> (pi0 -> e+ e+ e- e-) K- pi+ pi+) (pi0 -> e+ e-)", + "D*+ -> (D+ -> (pi0 -> e+ e-) K- pi+ pi+) (pi0 -> gamma gamma)", + "D*+ -> (D+ -> (pi0 -> e+ e-) K- pi+ pi+) (pi0 -> e+ e- gamma)", + "D*+ -> (D+ -> (pi0 -> e+ e-) K- pi+ pi+) (pi0 -> e+ e+ e- e-)", + "D*+ -> (D+ -> (pi0 -> e+ e-) K- pi+ pi+) (pi0 -> e+ e-)", + "D*+ -> (D+ -> (pi0 -> gamma gamma) K- pi+ pi+) gamma", + "D*+ -> (D+ -> (pi0 -> e+ e- gamma) K- pi+ pi+) gamma", + "D*+ -> (D+ -> (pi0 -> e+ e+ e- e-) K- pi+ pi+) gamma", + "D*+ -> (D+ -> (pi0 -> e+ e-) K- pi+ pi+) gamma", + ], + "D*-": [ # NB: decays of anti-D0 and D- are not defined in this DecFile + "D*- -> anti-D0 pi-", + "D*- -> (pi0 -> gamma gamma) D-", + "D*- -> (pi0 -> e+ e- gamma) D-", + "D*- -> (pi0 -> e+ e+ e- e-) D-", + "D*- -> (pi0 -> e+ e-) D-", + "D*- -> D- gamma", + ], + } + for particle in output: + all_decays = p.expand_decay_modes(particle) + assert set(all_decays) == set(output[particle]) + + def test_Lark_ModelNameCleanup_Transformer_no_params(): t = Tree( "decay", From 36f193f3fd3b190aecb024862344bd8fe065bae3 Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Thu, 22 Jun 2023 13:40:55 +0200 Subject: [PATCH 10/24] Update src/decaylanguage/dec/dec.py Co-authored-by: Eduardo Rodrigues --- src/decaylanguage/dec/dec.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/decaylanguage/dec/dec.py b/src/decaylanguage/dec/dec.py index 4e216afe..30b86038 100644 --- a/src/decaylanguage/dec/dec.py +++ b/src/decaylanguage/dec/dec.py @@ -393,7 +393,8 @@ def dict_jetset_definitions(self) -> dict[str, dict[int, int | float | str]]: def expand_decay_modes(self, particle: str) -> list[str]: """ - Return a list of expanded decay descriptors for the given particle + Return a list of expanded decay descriptors for the given (mother) particle. + The set of decay final states is effectively split and returned as a list. """ self._check_parsing() decay_chains = self.build_decay_chains(particle) From 43759d6636204376fe387b512fd2d86d23699ea3 Mon Sep 17 00:00:00 2001 From: Eduardo Rodrigues Date: Thu, 22 Jun 2023 14:22:52 +0200 Subject: [PATCH 11/24] Apply suggestions from code review --- src/decaylanguage/decay/decay.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/decaylanguage/decay/decay.py b/src/decaylanguage/decay/decay.py index a7c208e2..9f509988 100644 --- a/src/decaylanguage/decay/decay.py +++ b/src/decaylanguage/decay/decay.py @@ -519,9 +519,7 @@ def _expand_decay_modes( Parameters ---------- decay_chain: dict - top: bool, optional, default=True - aliases: dict[str, str], optional, default={} From abc1bfd618e7160163eea047fdc8bab611b51c60 Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Thu, 22 Jun 2023 18:05:56 +0200 Subject: [PATCH 12/24] add DescriptorFormat class for customising the format of descriptors --- src/decaylanguage/decay/decay.py | 6 +- src/decaylanguage/utils/__init__.py | 8 ++- src/decaylanguage/utils/utilities.py | 100 +++++++++++++++++++++++++++ tests/decay/test_descriptor.py | 30 ++++++++ 4 files changed, 139 insertions(+), 5 deletions(-) diff --git a/src/decaylanguage/decay/decay.py b/src/decaylanguage/decay/decay.py index a7c208e2..3370f091 100644 --- a/src/decaylanguage/decay/decay.py +++ b/src/decaylanguage/decay/decay.py @@ -15,7 +15,7 @@ from particle.converters import EvtGenName2PDGIDBiMap from particle.exceptions import MatchingIDNotFound -from ..utils import charge_conjugate_name +from ..utils import DescriptorFormat, charge_conjugate_name Self_DaughtersDict = TypeVar("Self_DaughtersDict", bound="DaughtersDict") @@ -633,9 +633,7 @@ def _get_fs(decay: DecayModeDict) -> list[Any]: # TODO: delegate descriptor-building to another function # allow for different conventions? final_state = DaughtersDict(list(expanded_mode)).to_string() - descriptor = f"{mother} -> {final_state}" - if not top: - descriptor = f"({descriptor})" + descriptor = DescriptorFormat.format_descriptor(mother, final_state, top) expanded_modes += [descriptor] decay_chain[orig_mother] = expanded_modes # type: ignore[assignment] diff --git a/src/decaylanguage/utils/__init__.py b/src/decaylanguage/utils/__init__.py index f48f6bcd..e15d7ae2 100644 --- a/src/decaylanguage/utils/__init__.py +++ b/src/decaylanguage/utils/__init__.py @@ -7,9 +7,15 @@ from .errors import LineFailure from .particleutils import charge_conjugate_name -from .utilities import filter_lines, iter_flatten, split +from .utilities import ( + DescriptorFormat, + filter_lines, + iter_flatten, + split, +) __all__ = ( + "DescriptorFormat", "LineFailure", "iter_flatten", "split", diff --git a/src/decaylanguage/utils/utilities.py b/src/decaylanguage/utils/utilities.py index 24f6b861..87a6b5e8 100644 --- a/src/decaylanguage/utils/utilities.py +++ b/src/decaylanguage/utils/utilities.py @@ -5,6 +5,8 @@ from __future__ import annotations +import string +from copy import copy from typing import Any, Iterator, Pattern @@ -57,3 +59,101 @@ def filter_lines( output = [match.groupdict() for match in matches if match is not None] new_inp = [ln for ln in inp if matcher.match(ln) is None] return output, new_inp + + +class DescriptorFormat: + """ + Class to help with setting the descriptor format. The format is stored as a + class-level variable: `DescriptorFormat.config`. + + Examples + -------- + >>> dm1 = DecayMode(0.6770, "D0 pi+") # D*+ + >>> dm2 = DecayMode(0.0124, "K_S0 pi0") # D0 + >>> dm3 = DecayMode(0.692, "pi+ pi-") # K_S0 + >>> dm4 = DecayMode(0.98823, "gamma gamma") # pi0 + >>> dc = DecayChain("D*+", {"D*+": dm1, "D0": dm2, "K_S0": dm3, "pi0": dm4}) + >>> with DescriptorFormat("{mother} --> {daughters}", "[{mother} --> {daughters}]"): dc.to_string() + ... + 'D*+ --> [D0 --> [K_S0 --> pi+ pi-] [pi0 --> gamma gamma]] pi+' + >>> with DescriptorFormat("{mother} => {daughters}", "{mother} (=> {daughters})"): dc.to_string(); + ... + 'D*+ => D0 (=> K_S0 (=> pi+ pi-) pi0 (=> gamma gamma)) pi+' + >>> dc.to_string() + 'D*+ -> (D0 -> (K_S0 -> pi+ pi-) (pi0 -> gamma gamma)) pi+' + """ + + config = { + "decay_pattern": "{mother} -> {daughters}", + "sub_decay_pattern": "({mother} -> {daughters})", + } + + def __init__(self, decay_pattern: str, sub_decay_pattern: str) -> None: + self.new_config = { + "decay_pattern": decay_pattern, + "sub_decay_pattern": sub_decay_pattern, + } + self.old_config = copy(DescriptorFormat.config) + + def __enter__(self) -> None: + self.set_config(**self.new_config) + + def __exit__(self, *args: list[Any]) -> None: + self.set_config(**self.old_config) + + @staticmethod + def set_config(decay_pattern: str, sub_decay_pattern: str) -> None: + """ + Configure the descriptor patterns after checking that each pattern + has named-wildcards "mother" and "daughters". + + Parameters + ---------- + + decay_pattern: str + Format-string expression for a top-level decay, + e.g. "{mother} -> {daughters}" + sub_decay_pattern: str + Format-string expression for a sub-decay, + e.g. "({mother} -> {daughters}" + """ + new_config = { + "decay_pattern": decay_pattern, + "sub_decay_pattern": sub_decay_pattern, + } + expected_wildcards = {"mother", "daughters"} + for pattern in new_config.values(): + wildcards = { + t[1] for t in string.Formatter().parse(pattern) if isinstance(t[1], str) + } + if wildcards != expected_wildcards: + error_msg = ( + "The pattern should only have the wildcards " + f"{expected_wildcards}, while '{pattern}' has the wildcards " + f"{wildcards}." + ) + raise ValueError(error_msg) + DescriptorFormat.config = new_config + + @staticmethod + def format_descriptor(mother: str, daughters: str, top: bool = True) -> str: + """ + Apply the format to one "layer" of the decay. Does not handle nested + decays itself. It is assumed that the `daughters` string already contains + any sub-decays. + + Parameters + ---------- + + mother: str + The decaying particle + daughters: str + The final state particles + """ + args = { + "mother": mother, + "daughters": daughters, + } + if top: + return DescriptorFormat.config["decay_pattern"].format(**args) + return DescriptorFormat.config["sub_decay_pattern"].format(**args) diff --git a/tests/decay/test_descriptor.py b/tests/decay/test_descriptor.py index 0605302a..5657a123 100644 --- a/tests/decay/test_descriptor.py +++ b/tests/decay/test_descriptor.py @@ -3,6 +3,7 @@ import pytest from decaylanguage import DecayChain, DecayMode +from decaylanguage.utils import DescriptorFormat dm1 = DecayMode(0.6770, "D0 pi+") # D*+ dm2 = DecayMode(0.0124, "K_S0 pi0") # D0 @@ -40,3 +41,32 @@ def test_descriptor(dc: DecayChain, expected: str): descriptor = dc.to_string() assert descriptor == expected + + +@pytest.mark.parametrize( + ("decay_pattern", "sub_decay_pattern", "expected"), + [ + ( + "{mother} -> {daughters}", + "({mother} -> {daughters})", + "D*+ -> (D0 -> (K_S0 -> pi+ pi-) (pi0 -> gamma gamma)) pi+", + ), + ( + "{mother} --> {daughters}", + "[{mother} --> {daughters}]", + "D*+ --> [D0 --> [K_S0 --> pi+ pi-] [pi0 --> gamma gamma]] pi+", + ), + ( + "{mother} => {daughters}", + "{mother} (=> {daughters})", + "D*+ => D0 (=> K_S0 (=> pi+ pi-) pi0 (=> gamma gamma)) pi+", + ), + ], +) +def test_set_descriptor_pattern( + decay_pattern: str, sub_decay_pattern: str, expected: str +): + dc = DecayChain("D*+", {"D*+": dm1, "D0": dm2, "K_S0": dm3, "pi0": dm4}) + with DescriptorFormat(decay_pattern, sub_decay_pattern): + descriptor = dc.to_string() + assert descriptor == expected From 058d554d7c2589bc86821f63fc487d190dff21c7 Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Thu, 22 Jun 2023 18:14:02 +0200 Subject: [PATCH 13/24] Apply suggestions from code review Co-authored-by: Eduardo Rodrigues --- src/decaylanguage/decay/decay.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/decaylanguage/decay/decay.py b/src/decaylanguage/decay/decay.py index 307b2259..4541cc2e 100644 --- a/src/decaylanguage/decay/decay.py +++ b/src/decaylanguage/decay/decay.py @@ -738,8 +738,8 @@ def ndecays(self) -> int: def to_string(self) -> str: """ - One-line string representation of the entire decay chain. Sub-decays are - enclosed in round parentheses. + One-line string representation of the entire decay chain. + Sub-decays are enclosed in round parentheses. Examples -------- From a23c78db195dc60ddc526141c62829251f345f06 Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Tue, 27 Jun 2023 14:49:43 +0200 Subject: [PATCH 14/24] add FLATSQDALITZ to known EvtGen model names --- src/decaylanguage/data/decfile.lark | 2 +- src/decaylanguage/dec/enums.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/decaylanguage/data/decfile.lark b/src/decaylanguage/data/decfile.lark index ce99265a..bf6877b2 100644 --- a/src/decaylanguage/data/decfile.lark +++ b/src/decaylanguage/data/decfile.lark @@ -63,7 +63,7 @@ _WS: WS_INLINE MODEL_NAME_AND_WS.2: MODEL_NAME _WS+ MODEL_NAME_AND_SC.2: MODEL_NAME _WS* _SEMICOLON+ -MODEL_NAME : "BaryonPCR"|"BCL"|"BGL"|"BT02PI_CP_ISO"|"BTO3PI_CP"|"BTOSLLALI"|"BTOSLLBALL"|"BTOXSGAMMA"|"BTOXSLL"|"CB3PI-MPP"|"CB3PI-P00"|"D_DALITZ"|"ETAPRIME_DALITZ"|"ETA_DALITZ"|"ETA_FULLDALITZ"|"ETA_PI0DALITZ"|"FLATQ2"|"GENERIC_DALITZ"|"GOITY_ROBERTS"|"HELAMP"|"HQET3"|"HQET2"|"HQET"|"ISGW2"|"ISGW"|"LbAmpGen"|"LLSW"|"MELIKHOV"|"OMEGA_DALITZ"|"PARTWAVE"|"PHSP"|"PI0_DALITZ"|"PROPSLPOLE"|"PVV_CPLH"|"PYCONT"|"PYTHIA"|"SLBKPOLE"|"SLN"|"SLPOLE"|"SSD_CP"|"SSD_DirectCP"|"SSS_CP"|"SSS_CP_PNG"|"SSS_CPT"|"STS_CP"|"STS"|"SVP_CP"|"SVP_HELAMP"|"SVP"|"SVS_CP_ISO"|"SVS_CPLH"|"SVS_CP"|"SVS_NONCPEIGEN"|"SVS"|"SVV_CPLH"|"SVV_CP"|"SVV_HELAMP"|"SVV_NONCPEIGEN"|"SVVHELCPMIX"|"TAUHADNU"|"TAULNUNU"|"TAUSCALARNU"|"TAUVECTORNU"|"TSS"|"TVP"|"TVS_PWAVE"|"VLL"|"VSP_PWAVE"|"VSS_BMIX"|"VSS_MIX"|"VSS"|"VUB"|"VVPIPI"|"VVP"|"VVS_PWAVE"|"YMSTOYNSPIPICLEO"|"YMSTOYNSPIPICLEOBOOST" +MODEL_NAME : "BaryonPCR"|"BCL"|"BGL"|"BT02PI_CP_ISO"|"BTO3PI_CP"|"BTOSLLALI"|"BTOSLLBALL"|"BTOXSGAMMA"|"BTOXSLL"|"CB3PI-MPP"|"CB3PI-P00"|"D_DALITZ"|"ETAPRIME_DALITZ"|"ETA_DALITZ"|"ETA_FULLDALITZ"|"ETA_PI0DALITZ"|"FLATQ2"|"FLATSQDALITZ"|"GENERIC_DALITZ"|"GOITY_ROBERTS"|"HELAMP"|"HQET3"|"HQET2"|"HQET"|"ISGW2"|"ISGW"|"LbAmpGen"|"LLSW"|"MELIKHOV"|"OMEGA_DALITZ"|"PARTWAVE"|"PHSP"|"PI0_DALITZ"|"PROPSLPOLE"|"PVV_CPLH"|"PYCONT"|"PYTHIA"|"SLBKPOLE"|"SLN"|"SLPOLE"|"SSD_CP"|"SSD_DirectCP"|"SSS_CP"|"SSS_CP_PNG"|"SSS_CPT"|"STS_CP"|"STS"|"SVP_CP"|"SVP_HELAMP"|"SVP"|"SVS_CP_ISO"|"SVS_CPLH"|"SVS_CP"|"SVS_NONCPEIGEN"|"SVS"|"SVV_CPLH"|"SVV_CP"|"SVV_HELAMP"|"SVV_NONCPEIGEN"|"SVVHELCPMIX"|"TAUHADNU"|"TAULNUNU"|"TAUSCALARNU"|"TAUVECTORNU"|"TSS"|"TVP"|"TVS_PWAVE"|"VLL"|"VSP_PWAVE"|"VSS_BMIX"|"VSS_MIX"|"VSS"|"VUB"|"VVPIPI"|"VVP"|"VVS_PWAVE"|"YMSTOYNSPIPICLEO"|"YMSTOYNSPIPICLEOBOOST" LABEL : /[a-zA-Z0-9\/\-+*_()']+/ COMMENT : /[#][^\n]*/ diff --git a/src/decaylanguage/dec/enums.py b/src/decaylanguage/dec/enums.py index 43dc5142..d0437910 100644 --- a/src/decaylanguage/dec/enums.py +++ b/src/decaylanguage/dec/enums.py @@ -37,6 +37,7 @@ class PhotosEnum(IntEnum): "ETA_FULLDALITZ", "ETA_PI0DALITZ", "FLATQ2", + "FLATSQDALITZ", "GENERIC_DALITZ", "GOITY_ROBERTS", "HELAMP", From 4b9c113d9e20f3ac2c350a95abf57bff26e56b9c Mon Sep 17 00:00:00 2001 From: Eduardo Rodrigues Date: Tue, 27 Jun 2023 18:35:52 +0200 Subject: [PATCH 15/24] Apply suggestions from code review --- src/decaylanguage/utils/utilities.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/decaylanguage/utils/utilities.py b/src/decaylanguage/utils/utilities.py index 87a6b5e8..4227add2 100644 --- a/src/decaylanguage/utils/utilities.py +++ b/src/decaylanguage/utils/utilities.py @@ -63,7 +63,7 @@ def filter_lines( class DescriptorFormat: """ - Class to help with setting the descriptor format. The format is stored as a + Class to help with setting the decay descriptor format. The format is stored as a class-level variable: `DescriptorFormat.config`. Examples @@ -146,9 +146,9 @@ def format_descriptor(mother: str, daughters: str, top: bool = True) -> str: ---------- mother: str - The decaying particle + The decaying particle. daughters: str - The final state particles + The final-state particles. """ args = { "mother": mother, From a3198bb54aac18a6ce62ea463d323af261fa9983 Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Wed, 28 Jun 2023 16:38:57 +0200 Subject: [PATCH 16/24] move DescriptorFormat test to tests/utils --- tests/decay/test_descriptor.py | 35 ++++---------------------- tests/utils/test_utilities.py | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 30 deletions(-) create mode 100644 tests/utils/test_utilities.py diff --git a/tests/decay/test_descriptor.py b/tests/decay/test_descriptor.py index 5657a123..51471a92 100644 --- a/tests/decay/test_descriptor.py +++ b/tests/decay/test_descriptor.py @@ -1,9 +1,13 @@ +# Copyright (c) 2018-2023, Eduardo Rodrigues and Henry Schreiner. +# +# Distributed under the 3-clause BSD license, see accompanying file LICENSE +# or https://github.com/scikit-hep/decaylanguage for details. + from __future__ import annotations import pytest from decaylanguage import DecayChain, DecayMode -from decaylanguage.utils import DescriptorFormat dm1 = DecayMode(0.6770, "D0 pi+") # D*+ dm2 = DecayMode(0.0124, "K_S0 pi0") # D0 @@ -41,32 +45,3 @@ def test_descriptor(dc: DecayChain, expected: str): descriptor = dc.to_string() assert descriptor == expected - - -@pytest.mark.parametrize( - ("decay_pattern", "sub_decay_pattern", "expected"), - [ - ( - "{mother} -> {daughters}", - "({mother} -> {daughters})", - "D*+ -> (D0 -> (K_S0 -> pi+ pi-) (pi0 -> gamma gamma)) pi+", - ), - ( - "{mother} --> {daughters}", - "[{mother} --> {daughters}]", - "D*+ --> [D0 --> [K_S0 --> pi+ pi-] [pi0 --> gamma gamma]] pi+", - ), - ( - "{mother} => {daughters}", - "{mother} (=> {daughters})", - "D*+ => D0 (=> K_S0 (=> pi+ pi-) pi0 (=> gamma gamma)) pi+", - ), - ], -) -def test_set_descriptor_pattern( - decay_pattern: str, sub_decay_pattern: str, expected: str -): - dc = DecayChain("D*+", {"D*+": dm1, "D0": dm2, "K_S0": dm3, "pi0": dm4}) - with DescriptorFormat(decay_pattern, sub_decay_pattern): - descriptor = dc.to_string() - assert descriptor == expected diff --git a/tests/utils/test_utilities.py b/tests/utils/test_utilities.py new file mode 100644 index 00000000..8b652ae0 --- /dev/null +++ b/tests/utils/test_utilities.py @@ -0,0 +1,45 @@ +# Copyright (c) 2018-2023, Eduardo Rodrigues and Henry Schreiner. +# +# Distributed under the 3-clause BSD license, see accompanying file LICENSE +# or https://github.com/scikit-hep/decaylanguage for details. + +from __future__ import annotations + +import pytest + +from decaylanguage import DecayChain, DecayMode +from decaylanguage.utils import DescriptorFormat + +dm1 = DecayMode(0.6770, "D0 pi+") # D*+ +dm2 = DecayMode(0.0124, "K_S0 pi0") # D0 +dm3 = DecayMode(0.692, "pi+ pi-") # K_S0 +dm4 = DecayMode(0.98823, "gamma gamma") # pi0 + + +@pytest.mark.parametrize( + ("decay_pattern", "sub_decay_pattern", "expected"), + [ + ( + "{mother} -> {daughters}", + "({mother} -> {daughters})", + "D*+ -> (D0 -> (K_S0 -> pi+ pi-) (pi0 -> gamma gamma)) pi+", + ), + ( + "{mother} --> {daughters}", + "[{mother} --> {daughters}]", + "D*+ --> [D0 --> [K_S0 --> pi+ pi-] [pi0 --> gamma gamma]] pi+", + ), + ( + "{mother} => {daughters}", + "{mother} (=> {daughters})", + "D*+ => D0 (=> K_S0 (=> pi+ pi-) pi0 (=> gamma gamma)) pi+", + ), + ], +) +def test_set_descriptor_pattern( + decay_pattern: str, sub_decay_pattern: str, expected: str +): + dc = DecayChain("D*+", {"D*+": dm1, "D0": dm2, "K_S0": dm3, "pi0": dm4}) + with DescriptorFormat(decay_pattern, sub_decay_pattern): + descriptor = dc.to_string() + assert descriptor == expected From 39627343c72527cc7fe1daedbb98c88e03d14bd5 Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Wed, 28 Jun 2023 16:42:13 +0200 Subject: [PATCH 17/24] mention implicit use of aliases in docstring for DecFileParser.expand_decay_modes --- src/decaylanguage/dec/dec.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/decaylanguage/dec/dec.py b/src/decaylanguage/dec/dec.py index 30b86038..10f45448 100644 --- a/src/decaylanguage/dec/dec.py +++ b/src/decaylanguage/dec/dec.py @@ -395,6 +395,7 @@ def expand_decay_modes(self, particle: str) -> list[str]: """ Return a list of expanded decay descriptors for the given (mother) particle. The set of decay final states is effectively split and returned as a list. + NB: this implicitly reverts aliases back to the original (EvtGen) names. """ self._check_parsing() decay_chains = self.build_decay_chains(particle) From 901e5a47a647c2594b0c0a4a3862a2830d495c5d Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Wed, 28 Jun 2023 16:57:38 +0200 Subject: [PATCH 18/24] example of aliases in _expand_decay_modes --- src/decaylanguage/decay/decay.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/decaylanguage/decay/decay.py b/src/decaylanguage/decay/decay.py index 4541cc2e..91aad938 100644 --- a/src/decaylanguage/decay/decay.py +++ b/src/decaylanguage/decay/decay.py @@ -519,14 +519,18 @@ def _expand_decay_modes( Parameters ---------- decay_chain: dict + A dict representing decay chains, such as returned by DecayChain.to_dict + or DecFileParser.build_decay_chains. top: bool, optional, default=True + Whether the passed decay chain is the top-level or not (should usually + be True: only really set to False when recursing the function). aliases: dict[str, str], optional, default={} - + Mapping of names to replace. Useful when dealing with DecFiles that have + Alias statements. Examples -------- A simple example with no sub-decays: - { "anti-D0": [ { @@ -537,9 +541,7 @@ def _expand_decay_modes( } ] } - becomes the dict - { "anti-D0": [ "anti-D0 -> K+ pi-" @@ -594,6 +596,26 @@ def _expand_decay_modes( "anti-D*0 -> (anti-D0 -> K+ pi-) gamma", ] } + + and an example alias dict: + {"MyAntiD0": "anti-D0"} + can be used with + { + "MyAntiD0": [ + { + "bf": 1.0, + "fs": ["K+", "pi-"], + "model": "PHSP", + "model_params": "" + } + ] + } + to result in + { + 'MyAntiD0': [ + 'anti-D0 -> K+ pi-' + ] + } """ def _get_modes(decay_chain: DecayChainDict) -> list[DecayModeDict]: From 70313797dc1e14625c46ce354c2dc69a09c30573 Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Wed, 28 Jun 2023 17:23:03 +0200 Subject: [PATCH 19/24] add import statement to example to satisfy doctest(?) --- src/decaylanguage/utils/utilities.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/decaylanguage/utils/utilities.py b/src/decaylanguage/utils/utilities.py index 4227add2..fd52907b 100644 --- a/src/decaylanguage/utils/utilities.py +++ b/src/decaylanguage/utils/utilities.py @@ -68,6 +68,7 @@ class DescriptorFormat: Examples -------- + >>> from decaylanguage import DecayMode, DecayChain >>> dm1 = DecayMode(0.6770, "D0 pi+") # D*+ >>> dm2 = DecayMode(0.0124, "K_S0 pi0") # D0 >>> dm3 = DecayMode(0.692, "pi+ pi-") # K_S0 From 1ba9aaec54a0438cf847b9363e1caaf269d30661 Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Wed, 28 Jun 2023 17:26:49 +0200 Subject: [PATCH 20/24] comply with RUF012 --- src/decaylanguage/utils/utilities.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/decaylanguage/utils/utilities.py b/src/decaylanguage/utils/utilities.py index fd52907b..7dc6e9a0 100644 --- a/src/decaylanguage/utils/utilities.py +++ b/src/decaylanguage/utils/utilities.py @@ -7,7 +7,7 @@ import string from copy import copy -from typing import Any, Iterator, Pattern +from typing import Any, ClassVar, Iterator, Pattern def iter_flatten(iterable: list[str] | tuple[str, ...]) -> Iterator[str]: @@ -84,7 +84,7 @@ class DescriptorFormat: 'D*+ -> (D0 -> (K_S0 -> pi+ pi-) (pi0 -> gamma gamma)) pi+' """ - config = { + config: ClassVar[dict[str, str]] = { "decay_pattern": "{mother} -> {daughters}", "sub_decay_pattern": "({mother} -> {daughters})", } From ac5aedddcd361c3abe2db23788b0d25bd907f5e2 Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Wed, 28 Jun 2023 17:42:48 +0200 Subject: [PATCH 21/24] use typing.Dict in typedef --- src/decaylanguage/decay/decay.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/decaylanguage/decay/decay.py b/src/decaylanguage/decay/decay.py index 91aad938..5e9c7732 100644 --- a/src/decaylanguage/decay/decay.py +++ b/src/decaylanguage/decay/decay.py @@ -9,7 +9,7 @@ from collections import Counter from copy import deepcopy from itertools import product -from typing import TYPE_CHECKING, Any, Iterable, Iterator, TypeVar, Union +from typing import TYPE_CHECKING, Any, Dict, Iterable, Iterator, TypeVar, Union from particle import PDGID, ParticleNotFound from particle.converters import EvtGenName2PDGIDBiMap @@ -447,8 +447,8 @@ def __str__(self) -> str: Self_DecayChain = TypeVar("Self_DecayChain", bound="DecayChain") -DecayModeDict = dict[str, Union[float, str, list[Any]]] -DecayChainDict = dict[str, list[DecayModeDict]] +DecayModeDict = Dict[str, Union[float, str, list[Any]]] +DecayChainDict = Dict[str, list[DecayModeDict]] def _has_no_subdecay(ds: list[Any]) -> bool: From c84198afbf2ee5c122b58a3844277b1604f6580a Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Wed, 28 Jun 2023 17:46:28 +0200 Subject: [PATCH 22/24] use typing.List in typedef --- src/decaylanguage/decay/decay.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/decaylanguage/decay/decay.py b/src/decaylanguage/decay/decay.py index 5e9c7732..46480bcd 100644 --- a/src/decaylanguage/decay/decay.py +++ b/src/decaylanguage/decay/decay.py @@ -9,7 +9,7 @@ from collections import Counter from copy import deepcopy from itertools import product -from typing import TYPE_CHECKING, Any, Dict, Iterable, Iterator, TypeVar, Union +from typing import TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, TypeVar, Union from particle import PDGID, ParticleNotFound from particle.converters import EvtGenName2PDGIDBiMap @@ -447,8 +447,8 @@ def __str__(self) -> str: Self_DecayChain = TypeVar("Self_DecayChain", bound="DecayChain") -DecayModeDict = Dict[str, Union[float, str, list[Any]]] -DecayChainDict = Dict[str, list[DecayModeDict]] +DecayModeDict = Dict[str, Union[float, str, List[Any]]] +DecayChainDict = Dict[str, List[DecayModeDict]] def _has_no_subdecay(ds: list[Any]) -> bool: From e23cec200b6755327cce5afe12c50593fbe19daa Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Wed, 28 Jun 2023 20:00:13 +0200 Subject: [PATCH 23/24] one more --- src/decaylanguage/decay/decay.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/decaylanguage/decay/decay.py b/src/decaylanguage/decay/decay.py index 46480bcd..50d49a58 100644 --- a/src/decaylanguage/decay/decay.py +++ b/src/decaylanguage/decay/decay.py @@ -884,8 +884,8 @@ def to_dict(self) -> DecayChainDict: 'model_params': ''}]} """ - # Ideally this would be a recursive type, DecayDict = dict[str, list[str | DecayDict]] - DecayDict = dict[str, list[Any]] + # Ideally this would be a recursive type, DecayDict = Dict[str, list[str | DecayDict]] + DecayDict = Dict[str, list[Any]] def recursively_replace(mother: str) -> DecayDict: dm = self.decays[mother].to_dict() From 4291823b6e4c056506d47bc4b9d13fee5f987627 Mon Sep 17 00:00:00 2001 From: Adam Morris Date: Thu, 29 Jun 2023 00:37:38 +0200 Subject: [PATCH 24/24] Update decay.py --- src/decaylanguage/decay/decay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decaylanguage/decay/decay.py b/src/decaylanguage/decay/decay.py index 50d49a58..99388b7c 100644 --- a/src/decaylanguage/decay/decay.py +++ b/src/decaylanguage/decay/decay.py @@ -885,7 +885,7 @@ def to_dict(self) -> DecayChainDict: """ # Ideally this would be a recursive type, DecayDict = Dict[str, list[str | DecayDict]] - DecayDict = Dict[str, list[Any]] + DecayDict = Dict[str, List[Any]] def recursively_replace(mother: str) -> DecayDict: dm = self.decays[mother].to_dict()