atlas-ftag-tools 0.2.5__py3-none-any.whl → 0.2.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: atlas-ftag-tools
3
- Version: 0.2.5
3
+ Version: 0.2.7
4
4
  Summary: ATLAS Flavour Tagging Tools
5
5
  Author: Sam Van Stroud, Philipp Gadow
6
6
  License: MIT
@@ -0,0 +1,28 @@
1
+ ftag/__init__.py,sha256=k5qBmtC7Ieh0trgm2Ba9Qj_6A2wQSpmAmXo2iIOAaI0,737
2
+ ftag/cli_utils.py,sha256=w3TtQmUHSyAKChS3ewvOtcSDAUJAZGIIomaNi8f446U,298
3
+ ftag/cuts.py,sha256=9_ooLZHaO3SnIQBNxwbaPZn-qptGdKnB27FdKQGTiTY,2933
4
+ ftag/flavours.py,sha256=ShH4M2UjQZpZ_NlCctTm2q1tJbzYxjmGteioQ2GcqEU,114
5
+ ftag/flavours.yaml,sha256=E_vpn38qJ3-Tygg2aHlH4wkn_rR1On_lMeaG8OemHCQ,8285
6
+ ftag/git_check.py,sha256=Y-XqM80CVXZ5ZKrDdZcYOJt3X64uU6W3OP6Z0D7AZU0,1663
7
+ ftag/labeller.py,sha256=IXUgU9UBir39PxVWRKs5r5fqI66Tv0x7nJD3-RYpbrg,2780
8
+ ftag/labels.py,sha256=C7IylPTnc32dFXq8C2Ks2wuljYK3WaY2EsPLGrhtXy8,3932
9
+ ftag/mock.py,sha256=Eyj3tkkaSSnqvS3G6NS7fq8sB__Nx8YE9-OM2_lpdoQ,4992
10
+ ftag/region.py,sha256=ANv0dGI2W6NJqD9fp7EfqAUReH4FOjc1gwl_Qn8llcM,360
11
+ ftag/sample.py,sha256=3N0FrRcu9l1sX8ohuGOHuMYGD0See6gMO4--7NzR2tE,2538
12
+ ftag/track_selector.py,sha256=fJNk_kIBQriBqV4CPT_3ReJbOUnavDDzO-u3EQlRuyk,2654
13
+ ftag/transform.py,sha256=uEGGJSnqoKOzLYQv650XdK_kDNw4Aw-5dc60z9Dp_y0,3963
14
+ ftag/vds.py,sha256=nRViQZQIORB95nC7NZsW3KsSoGkLzEdOsuCViH5h8-U,3296
15
+ ftag/hdf5/__init__.py,sha256=LFDNxVOCp58SvLHwQhdT68Q-KBMS_i6jBrbXoRpHzbM,354
16
+ ftag/hdf5/h5move.py,sha256=oYpRu0IDCIJIQ2ML52HBAdoyDxmKkHTeM9JdbPEgKfI,947
17
+ ftag/hdf5/h5reader.py,sha256=i31pDAqmOSaxdeRhc4iSBlld8xJ0pmp4rNd7CugNzw0,13706
18
+ ftag/hdf5/h5split.py,sha256=4Wy6Xc3J58MdD9aBaSZHf5ZcVFnJSkWsm42R5Pgo-R4,2448
19
+ ftag/hdf5/h5utils.py,sha256=-4zKTMtNCrDZr_9Ww7uzfsB7M7muBKpmm_1IkKJnHOI,3222
20
+ ftag/hdf5/h5writer.py,sha256=9FkClV__UbBqmFsq_h2jwiZnbWVm8QFRL_4mDZZBbTs,5316
21
+ ftag/wps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ ftag/wps/discriminant.py,sha256=VJdZlJJUwaTeyxmIDEk23rQSAuvWs6wDA3XRjDI6-_c,4277
23
+ ftag/wps/working_points.py,sha256=cvStSpP8Cbb_FWM8v59tFsscUvdeqi831tLn5BiHUEg,9741
24
+ atlas_ftag_tools-0.2.7.dist-info/METADATA,sha256=oo5m85dK467AWuK-L8xaIbLDVmRUO3r7vA_J1vgR5b8,5169
25
+ atlas_ftag_tools-0.2.7.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
26
+ atlas_ftag_tools-0.2.7.dist-info/entry_points.txt,sha256=LfVLsZHQolqbPnwPgtmc5IQTh527BKkN2v-IpXWTNHw,137
27
+ atlas_ftag_tools-0.2.7.dist-info/top_level.txt,sha256=qiYQuKcAvMim-31FwkT3MTQu7WQm0s58tPAia5KKWqs,5
28
+ atlas_ftag_tools-0.2.7.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.1.0)
2
+ Generator: setuptools (75.5.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
ftag/__init__.py CHANGED
@@ -2,12 +2,13 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "v0.2.5"
6
-
5
+ __version__ = "v0.2.7"
7
6
 
8
7
  from ftag import hdf5
9
8
  from ftag.cuts import Cuts
10
- from ftag.flavour import Flavour, Flavours
9
+ from ftag.flavours import Flavours
10
+ from ftag.labeller import Labeller
11
+ from ftag.labels import Label, LabelContainer
11
12
  from ftag.mock import get_mock_file
12
13
  from ftag.sample import Sample
13
14
  from ftag.transform import Transform
@@ -16,8 +17,10 @@ from ftag.wps.working_points import get_working_points
16
17
 
17
18
  __all__ = [
18
19
  "Cuts",
19
- "Flavour",
20
20
  "Flavours",
21
+ "Label",
22
+ "LabelContainer",
23
+ "Labeller",
21
24
  "Sample",
22
25
  "Transform",
23
26
  "__version__",
ftag/flavours.py ADDED
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+ from ftag.labels import LabelContainer
4
+
5
+ Flavours = LabelContainer.from_yaml()
ftag/flavours.yaml CHANGED
@@ -53,21 +53,25 @@
53
53
  cuts: ["HadronGhostTruthLabelID == 5"]
54
54
  colour: tab:blue
55
55
  category: single-btag-ghost
56
+ _px: pb
56
57
  - name: ghostcjets
57
58
  label: $c$-jets
58
59
  cuts: ["HadronGhostTruthLabelID == 4"]
59
60
  colour: tab:orange
60
61
  category: single-btag-ghost
62
+ _px: pc
61
63
  - name: ghostujets
62
64
  label: Light-jets
63
65
  cuts: ["HadronGhostTruthLabelID == 0"]
64
66
  colour: tab:green
65
67
  category: single-btag-ghost
68
+ _px: pu
66
69
  - name: ghosttaujets
67
70
  label: $\tau$-jets
68
71
  cuts: ["HadronGhostTruthLabelID == 15"]
69
72
  colour: tab:purple
70
73
  category: single-btag-ghost
74
+ _px: ptau
71
75
 
72
76
  # Xbb tagging
73
77
  - name: hbb
@@ -90,6 +94,31 @@
90
94
  cuts: ["R10TruthLabel_R22v1 == 10"]
91
95
  colour: "#38761D"
92
96
  category: xbb
97
+ - name: qcdbb
98
+ label: QCD->bb
99
+ cuts: ["R10TruthLabel_R22v1 == 10", "GhostBHadronsFinalCount == 2"]
100
+ colour: "red"
101
+ category: xbb
102
+ - name: qcdnonbb
103
+ label: QCD
104
+ cuts: ["R10TruthLabel_R22v1 == 10", "GhostBHadronsFinalCount != 2"]
105
+ colour: "silver"
106
+ category: xbb
107
+ - name: qcdbx
108
+ label: QCD
109
+ cuts: ["R10TruthLabel_R22v1 == 10", "GhostBHadronsFinalCount == 1"]
110
+ colour: "gold"
111
+ category: xbb
112
+ - name: qcdcx
113
+ label: QCD
114
+ cuts: ["R10TruthLabel_R22v1 == 10", "GhostCHadronsFinalCount >= 1", "GhostBHadronsFinalCount == 0"]
115
+ colour: "pink"
116
+ category: xbb
117
+ - name: qcdll
118
+ label: QCD
119
+ cuts: ["R10TruthLabel_R22v1 == 10", "GhostBHadronsFinalCount == 0", "GhostCHadronsFinalCount == 0"]
120
+ colour: "green"
121
+ category: xbb
93
122
 
94
123
  # extended Xbb tagging
95
124
  - name: tqqb
ftag/labeller.py CHANGED
@@ -5,8 +5,8 @@ from dataclasses import dataclass
5
5
  import numpy as np
6
6
 
7
7
  from ftag import Flavours
8
- from ftag.flavour import Flavour, FlavourContainer
9
8
  from ftag.hdf5 import join_structured_arrays, structured_from_dict
9
+ from ftag.labels import Label, LabelContainer
10
10
 
11
11
 
12
12
  @dataclass
@@ -14,24 +14,36 @@ class Labeller:
14
14
  """
15
15
  Defines a labelling scheme.
16
16
 
17
- Labels are [0, ..., n] and are assigned using pre-defined selections.
17
+ Classes are assigned integer labels in [0, ..., n] based on pre-defined selections.
18
18
 
19
19
  Parameters
20
20
  ----------
21
- labels : FlavourContainer | list[str | Flavour]
21
+ labels : LabelContainer | list[str | Label]
22
22
  The labels to be use.
23
23
  require_labels : bool
24
24
  Whether to require that all objects are labelled.
25
25
  """
26
26
 
27
- labels: FlavourContainer | list[str | Flavour]
27
+ labels: LabelContainer | list[str | Label]
28
28
  require_labels: bool = True
29
29
 
30
30
  def __post_init__(self) -> None:
31
- if isinstance(self.labels, FlavourContainer):
31
+ if isinstance(self.labels, LabelContainer):
32
32
  self.labels = list(self.labels)
33
33
  self.labels = sorted([Flavours[label] for label in self.labels])
34
34
 
35
+ @property
36
+ def variables(self) -> list[str]:
37
+ """
38
+ Returns the variables used for labelling.
39
+
40
+ Returns
41
+ -------
42
+ list[str]
43
+ The variables used for labelling.
44
+ """
45
+ return sum((label.cuts.variables for label in self.labels), []) # type: ignore[union-attr]
46
+
35
47
  def get_labels(self, array: np.ndarray) -> np.ndarray:
36
48
  """
37
49
  Returns the labels for the given array.
ftag/labels.py ADDED
@@ -0,0 +1,127 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterator
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+ import yaml
8
+
9
+ from ftag.cuts import Cuts
10
+
11
+
12
+ def remove_suffix(string: str, suffix: str) -> str:
13
+ if string.endswith(suffix):
14
+ return string[: -len(suffix)]
15
+ return string
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class Label:
20
+ name: str
21
+ label: str
22
+ cuts: Cuts
23
+ colour: str
24
+ category: str
25
+ _px: str | None = None
26
+
27
+ @property
28
+ def px(self) -> str:
29
+ return self._px or f"p{remove_suffix(self.name, 'jets')}"
30
+
31
+ @property
32
+ def eff_str(self) -> str:
33
+ return self.label.replace("jets", "jet") + " efficiency"
34
+
35
+ @property
36
+ def rej_str(self) -> str:
37
+ return self.label.replace("jets", "jet") + " rejection"
38
+
39
+ @property
40
+ def frac_str(self) -> str:
41
+ return "f" + remove_suffix(self.name, "jets")
42
+
43
+ def __str__(self) -> str:
44
+ return self.name
45
+
46
+ def __lt__(self, other) -> bool:
47
+ return self.name < other.name
48
+
49
+
50
+ @dataclass
51
+ class LabelContainer:
52
+ labels: dict[str, Label]
53
+
54
+ def __iter__(self) -> Iterator:
55
+ yield from self.labels.values()
56
+
57
+ def __getitem__(self, key) -> Label:
58
+ if isinstance(key, Label):
59
+ key = key.name
60
+ try:
61
+ return self.labels[key]
62
+ except KeyError as e:
63
+ raise KeyError(f"Label '{key}' not found") from e
64
+
65
+ def __getattr__(self, name) -> Label:
66
+ return self[name]
67
+
68
+ def __contains__(self, label: str | Label) -> bool:
69
+ if isinstance(label, Label):
70
+ label = label.name
71
+ return label in self.labels
72
+
73
+ def __eq__(self, other) -> bool:
74
+ if isinstance(other, LabelContainer):
75
+ return self.labels == other.labels
76
+ if isinstance(other, list) and all(isinstance(f, str) for f in other):
77
+ return {f.name for f in self} == set(other)
78
+ return False
79
+
80
+ def __repr__(self) -> str:
81
+ return f"{self.__class__.__name__}({', '.join([f.name for f in self])})"
82
+
83
+ @property
84
+ def categories(self) -> list[str]:
85
+ return list(dict.fromkeys(f.category for f in self))
86
+
87
+ def by_category(self, category: str) -> LabelContainer:
88
+ f = LabelContainer({k: v for k, v in self.labels.items() if v.category == category})
89
+ if not f.labels:
90
+ raise KeyError(f"No labels with category '{category}' found")
91
+ return f
92
+
93
+ def from_cuts(self, cuts: list | Cuts) -> Label:
94
+ if isinstance(cuts, list):
95
+ cuts = Cuts.from_list(cuts)
96
+ for label in self:
97
+ if label.cuts == cuts:
98
+ return label
99
+ raise KeyError(f"Label with {cuts} not found")
100
+
101
+ @classmethod
102
+ def from_yaml(cls, yaml_path: Path | None = None) -> LabelContainer:
103
+ if yaml_path is None:
104
+ yaml_path = Path(__file__).parent / "flavours.yaml"
105
+ with open(yaml_path) as f:
106
+ config = yaml.safe_load(f)
107
+
108
+ # sanity checks
109
+ cuts = [Cuts.from_list(f["cuts"]) for f in config]
110
+ if duplicates := [c for c in cuts if cuts.count(c) > 1]:
111
+ raise ValueError(f"Duplicate label definitions detected: {duplicates}")
112
+ names = [f["name"] for f in config]
113
+ if duplicates := [n for n in names if names.count(n) > 1]:
114
+ raise ValueError(f"Duplicate label names detected: {duplicates}")
115
+
116
+ labels = {f["name"]: Label(cuts=Cuts.from_list(f.pop("cuts")), **f) for f in config}
117
+ return cls(labels)
118
+
119
+ @classmethod
120
+ def from_list(cls, labels: list[Label]) -> LabelContainer:
121
+ return cls({f.name: f for f in labels})
122
+
123
+ def backgrounds(self, label: Label, only_signals: bool = True) -> LabelContainer:
124
+ bkg = [f for f in self if f.category == label.category and f != label]
125
+ if not only_signals:
126
+ bkg = [f for f in bkg if f.name not in {"ujets", "qcd"}]
127
+ return LabelContainer.from_list(bkg)
ftag/mock.py CHANGED
@@ -22,6 +22,8 @@ JET_VARS = [
22
22
  ("HadronConeExclTruthLabelID", "i4"),
23
23
  ("HadronConeExclTruthLabelPt", "f4"),
24
24
  ("R10TruthLabel_R22v1", "i4"),
25
+ ("GhostBHadronsFinalCount", "i4"),
26
+ ("GhostCHadronsFinalCount", "i4"),
25
27
  ("n_truth_promptLepton", "i4"),
26
28
  ("flavour_label", "i4"),
27
29
  ]
@@ -37,17 +39,18 @@ TRACK_VARS = [
37
39
  ("phiUncertainty", "f4"),
38
40
  ("thetaUncertainty", "f4"),
39
41
  ("qOverPUncertainty", "f4"),
40
- ("numberOfPixelHits", "i4"),
41
- ("numberOfSCTHits", "i4"),
42
- ("numberOfInnermostPixelLayerHits", "i4"),
43
- ("numberOfNextToInnermostPixelLayerHits", "i4"),
44
- ("numberOfInnermostPixelLayerSharedHits", "i4"),
45
- ("numberOfInnermostPixelLayerSplitHits", "i4"),
46
- ("numberOfPixelSharedHits", "i4"),
47
- ("numberOfPixelSplitHits", "i4"),
48
- ("numberOfSCTSharedHits", "i4"),
49
- ("numberOfPixelHoles", "i4"),
50
- ("numberOfSCTHoles", "i4"),
42
+ ("numberOfPixelHits", "u1"),
43
+ ("numberOfSCTHits", "u1"),
44
+ ("numberOfInnermostPixelLayerHits", "u1"),
45
+ ("numberOfNextToInnermostPixelLayerHits", "u1"),
46
+ ("numberOfInnermostPixelLayerSharedHits", "u1"),
47
+ ("numberOfInnermostPixelLayerSplitHits", "u1"),
48
+ ("numberOfPixelSharedHits", "u1"),
49
+ ("numberOfPixelSplitHits", "u1"),
50
+ ("numberOfSCTSharedHits", "u1"),
51
+ ("numberOfPixelHoles", "u1"),
52
+ ("numberOfSCTHoles", "u1"),
53
+ ("leptonID", "i1"),
51
54
  ]
52
55
 
53
56
 
@@ -98,6 +101,8 @@ def mock_jets(num_jets=1000) -> np.ndarray:
98
101
 
99
102
  # add tagger scores
100
103
  jets["HadronConeExclTruthLabelID"] = rng.choice([0, 4, 5, 15], size=num_jets)
104
+ jets["GhostBHadronsFinalCount"] = rng.choice([0, 1, 2], size=num_jets)
105
+ jets["GhostCHadronsFinalCount"] = rng.choice([0, 1, 2], size=num_jets)
101
106
  jets["R10TruthLabel_R22v1"] = rng.choice([1, 10, 11, 12], size=num_jets)
102
107
  scores = get_mock_scores(jets["HadronConeExclTruthLabelID"])
103
108
  xbb_scores = get_mock_scores(jets["R10TruthLabel_R22v1"], is_xbb=True)
ftag/sample.py CHANGED
@@ -4,7 +4,7 @@ import glob
4
4
  from dataclasses import dataclass
5
5
  from pathlib import Path
6
6
 
7
- from ftag.flavour import remove_suffix
7
+ from ftag.labels import remove_suffix
8
8
  from ftag.vds import create_virtual_file
9
9
 
10
10
 
@@ -13,6 +13,7 @@ class Sample:
13
13
  pattern: Path | str | tuple[Path | str, ...]
14
14
  ntuple_dir: Path | str | None = None
15
15
  name: str | None = None
16
+ weights: list[float] | None = None
16
17
 
17
18
  def __post_init__(self) -> None:
18
19
  if not self.pattern:
ftag/track_selector.py CHANGED
@@ -41,8 +41,10 @@ class TrackSelector:
41
41
  for var in tracks.dtype.names:
42
42
  if issubclass(tracks[var].dtype.type, np.floating):
43
43
  tracks[var][rm_idx] = np.nan
44
- elif issubclass(tracks[var].dtype.type, np.integer):
44
+ elif issubclass(tracks[var].dtype.type, np.signedinteger):
45
45
  tracks[var][rm_idx] = -1
46
+ elif issubclass(tracks[var].dtype.type, np.unsignedinteger):
47
+ tracks[var][rm_idx] = 0
46
48
  elif issubclass(tracks[var].dtype.type, np.bool_):
47
49
  tracks[var][rm_idx] = False
48
50
  else:
ftag/wps/discriminant.py CHANGED
@@ -4,13 +4,14 @@ from typing import Callable
4
4
 
5
5
  import numpy as np
6
6
 
7
- from ftag.flavour import Flavour, Flavours
7
+ from ftag import Flavours
8
+ from ftag.labels import Label, remove_suffix
8
9
 
9
10
 
10
11
  def discriminant(
11
12
  jets: np.ndarray,
12
13
  tagger: str,
13
- signal: Flavour,
14
+ signal: Label,
14
15
  fxs: dict[str, float],
15
16
  epsilon: float = 1e-10,
16
17
  ) -> np.ndarray:
@@ -50,7 +51,10 @@ def discriminant(
50
51
  if fx > 0 and name not in jets.dtype.names:
51
52
  raise ValueError(f"Nonzero fx for {d}, but '{name}' not found in input array.")
52
53
  denominator += jets[name] * fx if name in jets.dtype.names else 0
53
- return np.log((jets[f"{tagger}_{signal.px}"] + epsilon) / (denominator + epsilon))
54
+ signal_field = f"{tagger}_{signal.px}"
55
+ if signal_field not in jets.dtype.names:
56
+ signal_field = f"{tagger}_p{remove_suffix(signal.name, 'jets')}"
57
+ return np.log((jets[signal_field] + epsilon) / (denominator + epsilon))
54
58
 
55
59
 
56
60
  def tautag_dicriminant(jets, tagger, fb, fc, epsilon=1e-10):
@@ -63,6 +67,11 @@ def btag_discriminant(jets, tagger, fc, ftau=0, epsilon=1e-10):
63
67
  return discriminant(jets, tagger, Flavours.bjets, fxs, epsilon=epsilon)
64
68
 
65
69
 
70
+ def ghostbtag_discriminant(jets, tagger, fc, ftau=0, epsilon=1e-10):
71
+ fxs = {"pghostc": fc, "pghosttau": ftau, "pghostu": 1 - fc - ftau}
72
+ return discriminant(jets, tagger, Flavours.ghostbjets, fxs, epsilon=epsilon)
73
+
74
+
66
75
  def ctag_discriminant(jets, tagger, fb, ftau=0, epsilon=1e-10):
67
76
  fxs = {"pb": fb, "ptau": ftau, "pu": 1 - fb - ftau}
68
77
  return discriminant(jets, tagger, Flavours.cjets, fxs, epsilon=epsilon)
@@ -79,7 +88,7 @@ def hcc_discriminant(jets, tagger, ftop=0.25, fhbb=0.3, epsilon=1e-10):
79
88
 
80
89
 
81
90
  def get_discriminant(
82
- jets: np.ndarray, tagger: str, signal: Flavour | str, epsilon: float = 1e-10, **fxs
91
+ jets: np.ndarray, tagger: str, signal: Label | str, epsilon: float = 1e-10, **fxs
83
92
  ):
84
93
  """Calculate the b-tag or c-tag discriminant for a given tagger.
85
94
 
@@ -89,7 +98,7 @@ def get_discriminant(
89
98
  Structured array of jets containing tagger outputs
90
99
  tagger : str
91
100
  Name of the tagger
92
- signal : Flavour
101
+ signal : Label
93
102
  Signal flavour (bjets/cjets or hbb/hcc)
94
103
  epsilon : float, optional
95
104
  Small number to avoid division by zero, by default 1e-10
@@ -112,6 +121,7 @@ def get_discriminant(
112
121
  "taujets": tautag_dicriminant,
113
122
  "hbb": hbb_discriminant,
114
123
  "hcc": hcc_discriminant,
124
+ "ghostbjets": ghostbtag_discriminant,
115
125
  }
116
126
 
117
127
  if str(signal) not in tagger_funcs:
@@ -8,9 +8,9 @@ from pathlib import Path
8
8
  import numpy as np
9
9
  import yaml
10
10
 
11
+ from ftag import Flavours
11
12
  from ftag.cli_utils import HelpFormatter
12
13
  from ftag.cuts import Cuts
13
- from ftag.flavour import Flavours
14
14
  from ftag.hdf5 import H5Reader
15
15
  from ftag.wps.discriminant import get_discriminant
16
16
 
@@ -218,7 +218,7 @@ def setup_common_parts(args):
218
218
  zprime_cuts = Cuts.from_list(args.zprime_cuts) + default_cuts
219
219
 
220
220
  # prepare to load jets
221
- all_vars = next(iter(flavs)).cuts.variables
221
+ all_vars = list(set(sum((flav.cuts.variables for flav in flavs), [])))
222
222
  reader = H5Reader(args.ttbar)
223
223
  jet_vars = reader.dtypes()["jets"].names
224
224
  for tagger in args.tagger:
@@ -1,28 +0,0 @@
1
- ftag/__init__.py,sha256=zyUUWzZ1zOpMhxY_LPiudOCUQhPzJN0HNgCrfi8dhqk,629
2
- ftag/cli_utils.py,sha256=w3TtQmUHSyAKChS3ewvOtcSDAUJAZGIIomaNi8f446U,298
3
- ftag/cuts.py,sha256=9_ooLZHaO3SnIQBNxwbaPZn-qptGdKnB27FdKQGTiTY,2933
4
- ftag/flavour.py,sha256=EMZZLyl6lSdvkfrYxHhMcSn3aqP_FU7OpCFkvZpTksU,3761
5
- ftag/flavours.yaml,sha256=lFnVwjh_DwLhOc3mr5n6bSIWyHgxQvAXas4lEmEDncU,7520
6
- ftag/git_check.py,sha256=Y-XqM80CVXZ5ZKrDdZcYOJt3X64uU6W3OP6Z0D7AZU0,1663
7
- ftag/labeller.py,sha256=uDygOhVGSNn96DWw8aErHpTtFsFX0RnxYYpy4g1FRog,2457
8
- ftag/mock.py,sha256=_oy-r3eLllFy33NAoZaKfAx-Rp2vrCdrGj3UsTMks94,4740
9
- ftag/region.py,sha256=ANv0dGI2W6NJqD9fp7EfqAUReH4FOjc1gwl_Qn8llcM,360
10
- ftag/sample.py,sha256=TFXMhDkbPmjkms9-b-bINJ32T3bO86JcU70C0nY7wa8,2500
11
- ftag/test_cli_utils.py,sha256=xa08vf6SEOow58SSFagYdAselb-dkNOVvWsWheMnW-g,1001
12
- ftag/track_selector.py,sha256=piSYAN_IkOsrXxKXjXbJpMSseUig5P2BJW5mCwsMUDM,2535
13
- ftag/transform.py,sha256=uEGGJSnqoKOzLYQv650XdK_kDNw4Aw-5dc60z9Dp_y0,3963
14
- ftag/vds.py,sha256=nRViQZQIORB95nC7NZsW3KsSoGkLzEdOsuCViH5h8-U,3296
15
- ftag/hdf5/__init__.py,sha256=LFDNxVOCp58SvLHwQhdT68Q-KBMS_i6jBrbXoRpHzbM,354
16
- ftag/hdf5/h5move.py,sha256=oYpRu0IDCIJIQ2ML52HBAdoyDxmKkHTeM9JdbPEgKfI,947
17
- ftag/hdf5/h5reader.py,sha256=i31pDAqmOSaxdeRhc4iSBlld8xJ0pmp4rNd7CugNzw0,13706
18
- ftag/hdf5/h5split.py,sha256=4Wy6Xc3J58MdD9aBaSZHf5ZcVFnJSkWsm42R5Pgo-R4,2448
19
- ftag/hdf5/h5utils.py,sha256=-4zKTMtNCrDZr_9Ww7uzfsB7M7muBKpmm_1IkKJnHOI,3222
20
- ftag/hdf5/h5writer.py,sha256=9FkClV__UbBqmFsq_h2jwiZnbWVm8QFRL_4mDZZBbTs,5316
21
- ftag/wps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- ftag/wps/discriminant.py,sha256=kJFekUTPNIvCabJCon6OqOAQEzz5hj3XrWFFRLOgGOs,3836
23
- ftag/wps/working_points.py,sha256=VTU6OD40ULAJQD0MlD1EZd33q8ociUvFX1YrhgJFvXc,9722
24
- atlas_ftag_tools-0.2.5.dist-info/METADATA,sha256=iDolDKoU7iyKkH-w-tY6RwhtYhkbq7Ehdw6j1FlUq4g,5169
25
- atlas_ftag_tools-0.2.5.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
26
- atlas_ftag_tools-0.2.5.dist-info/entry_points.txt,sha256=LfVLsZHQolqbPnwPgtmc5IQTh527BKkN2v-IpXWTNHw,137
27
- atlas_ftag_tools-0.2.5.dist-info/top_level.txt,sha256=qiYQuKcAvMim-31FwkT3MTQu7WQm0s58tPAia5KKWqs,5
28
- atlas_ftag_tools-0.2.5.dist-info/RECORD,,
ftag/flavour.py DELETED
@@ -1,126 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from collections.abc import Iterator
4
- from dataclasses import dataclass
5
- from pathlib import Path
6
-
7
- import yaml
8
-
9
- from ftag.cuts import Cuts
10
-
11
-
12
- def remove_suffix(string: str, suffix: str) -> str:
13
- if string.endswith(suffix):
14
- return string[: -len(suffix)]
15
- return string
16
-
17
-
18
- @dataclass(frozen=True)
19
- class Flavour:
20
- name: str
21
- label: str
22
- cuts: Cuts
23
- colour: str
24
- category: str
25
-
26
- @property
27
- def px(self) -> str:
28
- return f"p{remove_suffix(self.name, 'jets')}"
29
-
30
- @property
31
- def eff_str(self) -> str:
32
- return self.label.replace("jets", "jet") + " efficiency"
33
-
34
- @property
35
- def rej_str(self) -> str:
36
- return self.label.replace("jets", "jet") + " rejection"
37
-
38
- @property
39
- def frac_str(self) -> str:
40
- return "f" + remove_suffix(self.name, "jets")
41
-
42
- def __str__(self) -> str:
43
- return self.name
44
-
45
- def __lt__(self, other) -> bool:
46
- return self.name < other.name
47
-
48
-
49
- @dataclass
50
- class FlavourContainer:
51
- flavours: dict[str, Flavour]
52
-
53
- def __iter__(self) -> Iterator:
54
- yield from self.flavours.values()
55
-
56
- def __getitem__(self, key) -> Flavour:
57
- if isinstance(key, Flavour):
58
- key = key.name
59
- try:
60
- return self.flavours[key]
61
- except KeyError as e:
62
- raise KeyError(f"Flavour '{key}' not found") from e
63
-
64
- def __getattr__(self, name) -> Flavour:
65
- return self[name]
66
-
67
- def __contains__(self, flavour: str | Flavour) -> bool:
68
- if isinstance(flavour, Flavour):
69
- flavour = flavour.name
70
- return flavour in self.flavours
71
-
72
- def __eq__(self, other) -> bool:
73
- if isinstance(other, FlavourContainer):
74
- return self.flavours == other.flavours
75
- if isinstance(other, list) and all(isinstance(f, str) for f in other):
76
- return {f.name for f in self} == set(other)
77
- return False
78
-
79
- def __repr__(self) -> str:
80
- return f"{self.__class__.__name__}({', '.join([f.name for f in self])})"
81
-
82
- @property
83
- def categories(self) -> list[str]:
84
- return list(dict.fromkeys(f.category for f in self))
85
-
86
- def by_category(self, category: str) -> FlavourContainer:
87
- f = FlavourContainer({k: v for k, v in self.flavours.items() if v.category == category})
88
- if not f.flavours:
89
- raise KeyError(f"No flavours with category '{category}' found")
90
- return f
91
-
92
- def from_cuts(self, cuts: list | Cuts) -> Flavour:
93
- if isinstance(cuts, list):
94
- cuts = Cuts.from_list(cuts)
95
- for flavour in self:
96
- if flavour.cuts == cuts:
97
- return flavour
98
- raise KeyError(f"Flavour with {cuts} not found")
99
-
100
- @classmethod
101
- def from_yaml(cls, yaml_path: Path | None = None) -> FlavourContainer:
102
- if yaml_path is None:
103
- yaml_path = Path(__file__).parent / "flavours.yaml"
104
-
105
- with open(yaml_path) as f:
106
- flavours_yaml = yaml.safe_load(f)
107
-
108
- flavours_dict = {
109
- f["name"]: Flavour(cuts=Cuts.from_list(f.pop("cuts")), **f) for f in flavours_yaml
110
- }
111
- assert len(flavours_dict) == len(flavours_yaml), "Duplicate flavour names detected"
112
-
113
- return cls(flavours_dict)
114
-
115
- @classmethod
116
- def from_list(cls, flavours: list[Flavour]) -> FlavourContainer:
117
- return cls({f.name: f for f in flavours})
118
-
119
- def backgrounds(self, flavour: Flavour, keep_possible_signals: bool = True) -> FlavourContainer:
120
- bkg = [f for f in self if f.category == flavour.category and f != flavour]
121
- if not keep_possible_signals:
122
- bkg = [f for f in bkg if f.name not in {"ujets", "qcd"}]
123
- return FlavourContainer.from_list(bkg)
124
-
125
-
126
- Flavours = FlavourContainer.from_yaml()
ftag/test_cli_utils.py DELETED
@@ -1,34 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from pathlib import Path
4
- from tempfile import NamedTemporaryFile, TemporaryDirectory
5
-
6
- import pytest
7
-
8
- from ftag.cli_utils import valid_path
9
-
10
-
11
- def test_valid_path_existing_file():
12
- # Test when the input path is an existing file
13
- # get a temp directory
14
- with TemporaryDirectory() as tmpdir, NamedTemporaryFile(dir=tmpdir) as f:
15
- input_path = f.name
16
- expected_output = Path(f.name)
17
- result = valid_path(input_path)
18
- assert result == expected_output
19
-
20
-
21
- def test_valid_path_non_existing_file():
22
- # Test when the input path is a non-existing file
23
- input_path = "non_existing_file.txt"
24
- with pytest.raises(FileNotFoundError) as e:
25
- valid_path(input_path)
26
- assert str(e.value) == input_path
27
-
28
-
29
- def test_valid_path_directory():
30
- # Test when the input path is a directory
31
- input_path = "directory/"
32
- with pytest.raises(FileNotFoundError) as e:
33
- valid_path(input_path)
34
- assert str(e.value) == input_path