atlas-ftag-tools 0.0.4__tar.gz → 0.0.6__tar.gz

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.
Files changed (22) hide show
  1. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/PKG-INFO +3 -2
  2. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/README.md +1 -0
  3. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/atlas_ftag_tools.egg-info/PKG-INFO +3 -2
  4. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/atlas_ftag_tools.egg-info/SOURCES.txt +1 -0
  5. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/atlas_ftag_tools.egg-info/requires.txt +3 -3
  6. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/ftag/__init__.py +3 -2
  7. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/ftag/flavour.py +24 -1
  8. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/ftag/hdf5/__init__.py +3 -7
  9. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/ftag/hdf5/h5reader.py +48 -30
  10. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/ftag/hdf5/h5utils.py +2 -56
  11. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/ftag/hdf5/h5writer.py +5 -0
  12. atlas-ftag-tools-0.0.6/ftag/mock.py +105 -0
  13. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/ftag/sample.py +2 -0
  14. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/ftag/vds.py +2 -0
  15. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/pyproject.toml +5 -4
  16. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/MANIFEST.in +0 -0
  17. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/atlas_ftag_tools.egg-info/dependency_links.txt +0 -0
  18. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/atlas_ftag_tools.egg-info/top_level.txt +0 -0
  19. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/ftag/cuts.py +0 -0
  20. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/ftag/flavours.yaml +0 -0
  21. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/ftag/region.py +0 -0
  22. {atlas-ftag-tools-0.0.4 → atlas-ftag-tools-0.0.6}/setup.cfg +0 -0
@@ -1,17 +1,18 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: atlas-ftag-tools
3
- Version: 0.0.4
3
+ Version: 0.0.6
4
4
  Summary: ATLAS Flavour Tagging Tools
5
5
  Author: Sam Van Stroud, Philipp Gadow
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/umami-hep/atlas-ftag-tools/
8
- Requires-Python: >=3.10
8
+ Requires-Python: >=3.8
9
9
  Description-Content-Type: text/markdown
10
10
  Provides-Extra: dev
11
11
 
12
12
  # ATLAS FTAG Python Tools
13
13
 
14
14
  This is a collection of Python tools for working with files produced with the FTAG [ntuple dumper](https://gitlab.cern.ch/atlas-flavor-tagging-tools/training-dataset-dumper/).
15
+ The code is intended to be used a [library](https://iscinumpy.dev/post/app-vs-library/) for other projects.
15
16
  Please see the [example notebook](ftag/example.ipynb) for usage.
16
17
 
17
18
  ## Installation
@@ -1,6 +1,7 @@
1
1
  # ATLAS FTAG Python Tools
2
2
 
3
3
  This is a collection of Python tools for working with files produced with the FTAG [ntuple dumper](https://gitlab.cern.ch/atlas-flavor-tagging-tools/training-dataset-dumper/).
4
+ The code is intended to be used a [library](https://iscinumpy.dev/post/app-vs-library/) for other projects.
4
5
  Please see the [example notebook](ftag/example.ipynb) for usage.
5
6
 
6
7
  ## Installation
@@ -1,17 +1,18 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: atlas-ftag-tools
3
- Version: 0.0.4
3
+ Version: 0.0.6
4
4
  Summary: ATLAS Flavour Tagging Tools
5
5
  Author: Sam Van Stroud, Philipp Gadow
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/umami-hep/atlas-ftag-tools/
8
- Requires-Python: >=3.10
8
+ Requires-Python: >=3.8
9
9
  Description-Content-Type: text/markdown
10
10
  Provides-Extra: dev
11
11
 
12
12
  # ATLAS FTAG Python Tools
13
13
 
14
14
  This is a collection of Python tools for working with files produced with the FTAG [ntuple dumper](https://gitlab.cern.ch/atlas-flavor-tagging-tools/training-dataset-dumper/).
15
+ The code is intended to be used a [library](https://iscinumpy.dev/post/app-vs-library/) for other projects.
15
16
  Please see the [example notebook](ftag/example.ipynb) for usage.
16
17
 
17
18
  ## Installation
@@ -10,6 +10,7 @@ ftag/__init__.py
10
10
  ftag/cuts.py
11
11
  ftag/flavour.py
12
12
  ftag/flavours.yaml
13
+ ftag/mock.py
13
14
  ftag/region.py
14
15
  ftag/sample.py
15
16
  ftag/vds.py
@@ -1,6 +1,6 @@
1
- h5py==3.8.0
2
- numpy==1.24.*
3
- PyYAML==6.0
1
+ h5py>=3.0
2
+ numpy
3
+ PyYAML>=5.1
4
4
 
5
5
  [dev]
6
6
  black==23.1.0
@@ -1,7 +1,7 @@
1
1
  """atlas-ftag-tools - Common tools for ATLAS flavour tagging software."""
2
2
 
3
3
 
4
- __version__ = "v0.0.4"
4
+ __version__ = "v0.0.6"
5
5
 
6
6
  from pathlib import Path
7
7
 
@@ -10,6 +10,7 @@ import yaml
10
10
  import ftag.hdf5 as hdf5
11
11
  from ftag.cuts import Cuts
12
12
  from ftag.flavour import Flavour, FlavourContainer
13
+ from ftag.mock import get_mock_file
13
14
  from ftag.sample import Sample
14
15
 
15
16
  # load flavours
@@ -19,4 +20,4 @@ flavours_dict = {f["name"]: Flavour(cuts=Cuts.from_list(f.pop("cuts")), **f) for
19
20
  assert len(flavours_dict) == len(flavours_yaml), "Duplicate flavour names detected"
20
21
  Flavours = FlavourContainer(flavours_dict)
21
22
 
22
- __all__ = ["Cuts", "Flavours", "Sample", "hdf5", "__version__"]
23
+ __all__ = ["Cuts", "Flavours", "Sample", "hdf5", "get_mock_file", "__version__"]
@@ -16,7 +16,17 @@ class Flavour:
16
16
 
17
17
  @property
18
18
  def px(self) -> str:
19
- return f"p{self.name.removesuffix('jets')}"
19
+ if self.name.endswith("jets"):
20
+ return f"p{self.name[: -len('jets')]}"
21
+ return f"p{self.name}"
22
+
23
+ @property
24
+ def eff_str(self) -> str:
25
+ return self.label.replace("jets", "jet") + " efficiency"
26
+
27
+ @property
28
+ def rej_str(self) -> str:
29
+ return self.label.replace("jets", "jet") + " rejection"
20
30
 
21
31
  def __str__(self) -> str:
22
32
  return self.name
@@ -38,6 +48,11 @@ class FlavourContainer:
38
48
  def __getattr__(self, name) -> Flavour:
39
49
  return self[name]
40
50
 
51
+ def __contains__(self, flavour: str | Flavour) -> bool:
52
+ if isinstance(flavour, Flavour):
53
+ flavour = flavour.name
54
+ return flavour in self.flavours
55
+
41
56
  def __repr__(self) -> str:
42
57
  return f"{self.__class__.__name__}({', '.join(list(f.name for f in self))})"
43
58
 
@@ -47,3 +62,11 @@ class FlavourContainer:
47
62
 
48
63
  def by_category(self, category: str) -> FlavourContainer:
49
64
  return FlavourContainer({k: v for k, v in self.flavours.items() if v.category == category})
65
+
66
+ def from_cuts(self, cuts: list | Cuts) -> Flavour:
67
+ if isinstance(cuts, list):
68
+ cuts = Cuts.from_list(cuts)
69
+ for flavour in self:
70
+ if flavour.cuts == cuts:
71
+ return flavour
72
+ raise KeyError(f"Flavour with {cuts} not found")
@@ -1,17 +1,13 @@
1
1
  from ftag.hdf5.h5reader import H5Reader
2
- from ftag.hdf5.h5utils import (
3
- cast_dtype,
4
- get_dtype,
5
- get_dummy_file,
6
- join_structured_arrays,
7
- )
2
+ from ftag.hdf5.h5utils import cast_dtype, get_dtype, join_structured_arrays
8
3
  from ftag.hdf5.h5writer import H5Writer
4
+ from ftag.mock import get_mock_file
9
5
 
10
6
  __all__ = [
11
7
  "H5Reader",
12
8
  "H5Writer",
13
- "get_dummy_file",
14
9
  "get_dtype",
15
10
  "cast_dtype",
16
11
  "join_structured_arrays",
12
+ "get_mock_file",
17
13
  ]
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import logging as log
2
4
  import math
3
5
  from collections.abc import Generator
@@ -58,43 +60,28 @@ class H5SingleReader:
58
60
  )
59
61
  return {name: array[keep_idx] for name, array in data.items()}
60
62
 
61
- def stream(self, variables: dict, num_jets: int, cuts: Cuts | None = None) -> Generator:
62
- """Generate batches of selected jets.
63
-
64
- Parameters
65
- ----------
66
- variables : dict
67
- Dictionary of variables to for each group.
68
- num_jets : int
69
- Total number of selected jets to generate.
70
- cuts : Cuts | None, optional
71
- Cuts to apply, by default None
72
-
73
- Yields
74
- ------
75
- Generator
76
- Generator of batches of selected jets.
77
-
78
- Raises
79
- ------
80
- ValueError
81
- If more jets are requested than available.
82
- """
63
+ def stream(
64
+ self, variables: dict | None = None, num_jets: int | None = None, cuts: Cuts | None = None
65
+ ) -> Generator:
66
+ if num_jets is None:
67
+ num_jets = self.num_jets
83
68
  if num_jets > self.num_jets:
84
69
  raise ValueError(
85
70
  f"{num_jets:,} jets requested but only {self.num_jets:,} available in {self.fname}"
86
71
  )
87
72
 
88
- jet_vars = list(variables.get(self.jets_name, []))
89
- variables[self.jets_name] = jet_vars + (cuts.variables if cuts else [])
73
+ if variables is None:
74
+ variables = {self.jets_name: None}
75
+
90
76
  total = 0
77
+ rng = np.random.default_rng(42)
91
78
  with h5py.File(self.fname) as f:
92
79
  data = {name: self.empty(f[name], var) for name, var in variables.items()}
93
80
 
94
81
  # get indices
95
82
  indices = list(range(0, self.num_jets, self.batch_size))
96
83
  if self.shuffle:
97
- np.random.default_rng(42).shuffle(indices)
84
+ rng.shuffle(indices)
98
85
 
99
86
  # loop over batches and read file
100
87
  for low in indices:
@@ -130,7 +117,7 @@ class H5Reader:
130
117
  weights: list[float] | None = None
131
118
 
132
119
  def __post_init__(self) -> None:
133
- if isinstance(self.fname, str | Path):
120
+ if isinstance(self.fname, (str, Path)):
134
121
  self.fname = [self.fname]
135
122
 
136
123
  # calculate batch sizes
@@ -141,7 +128,7 @@ class H5Reader:
141
128
  # create readers
142
129
  self.readers = [
143
130
  H5SingleReader(fname, batch_size, self.jets_name, self.precision, self.shuffle)
144
- for fname, batch_size in zip(self.fname, self.batch_sizes, strict=True)
131
+ for fname, batch_size in zip(self.fname, self.batch_sizes)
145
132
  ]
146
133
 
147
134
  @property
@@ -158,7 +145,33 @@ class H5Reader:
158
145
  with h5py.File(self.readers[0].fname) as f:
159
146
  return f[name].dtype
160
147
 
161
- def stream(self, variables: dict, num_jets, cuts: Cuts | None = None) -> Generator:
148
+ def stream(
149
+ self, variables: dict | None = None, num_jets: int | None = None, cuts: Cuts | None = None
150
+ ) -> Generator:
151
+ """Generate batches of selected jets.
152
+
153
+ Parameters
154
+ ----------
155
+ variables : dict | None, optional
156
+ Dictionary of variables to for each group, by default use all jet variables.
157
+ num_jets : int | None, optional
158
+ Total number of selected jets to generate, by default all.
159
+ cuts : Cuts | None, optional
160
+ Selection cuts to apply, by default None
161
+
162
+ Yields
163
+ ------
164
+ Generator
165
+ Generator of batches of selected jets.
166
+ """
167
+ if num_jets is None:
168
+ num_jets = self.num_jets
169
+ if variables is None:
170
+ variables = {self.jets_name: None}
171
+ if self.jets_name not in variables or variables[self.jets_name] is not None:
172
+ jet_vars = variables.get(self.jets_name, [])
173
+ variables[self.jets_name] = list(jet_vars) + (cuts.variables if cuts else [])
174
+
162
175
  # get streams for selected jets from each reader
163
176
  streams = [
164
177
  r.stream(variables, int(r.num_jets / self.num_jets * num_jets), cuts)
@@ -184,11 +197,16 @@ class H5Reader:
184
197
  # select
185
198
  yield data
186
199
 
187
- def load(self, variables: dict, num_jets: int, cuts: Cuts | None = None) -> dict:
200
+ def load(
201
+ self, variables: dict | None = None, num_jets: int | None = None, cuts: Cuts | None = None
202
+ ) -> dict:
203
+ if variables is None:
204
+ variables = {self.jets_name: None}
188
205
  data: dict[str, list] = {name: [] for name in variables}
189
206
  for sample in self.stream(variables, num_jets, cuts):
190
207
  for name, array in sample.items():
191
- data[name].append(array)
208
+ if name in data:
209
+ data[name].append(array)
192
210
  return {name: np.concatenate(array) for name, array in data.items()}
193
211
 
194
212
  def estimate_available_jets(self, cuts: Cuts, num: int = 1_000_000) -> int:
@@ -1,10 +1,8 @@
1
- from tempfile import NamedTemporaryFile, mkdtemp
1
+ from __future__ import annotations
2
2
 
3
- import h5py
4
3
  import numpy as np
5
- from numpy.lib.recfunctions import unstructured_to_structured as u2s
6
4
 
7
- __all__ = ["get_dummy_file", "join_structured_arrays"]
5
+ __all__ = ["join_structured_arrays"]
8
6
 
9
7
 
10
8
  def get_dtype(ds, variables: list[str] | None = None, precision: str | None = None) -> np.dtype:
@@ -74,58 +72,6 @@ def cast_dtype(typestr: str, precision: str) -> np.dtype:
74
72
  raise ValueError(f"Invalid precision {precision}")
75
73
 
76
74
 
77
- def get_dummy_file():
78
- jet_vars = [
79
- "pt",
80
- "eta",
81
- "abs_eta",
82
- "mass",
83
- "HadronConeExclTruthLabelID",
84
- "n_tracks",
85
- "n_truth_promptLepton",
86
- ]
87
-
88
- track_vars = ["pt", "deta", "dphi", "dr"]
89
-
90
- # settings
91
- n_jets = 1000
92
- n_tracks_per_jet = 40
93
-
94
- # setup jets
95
- shapes_jets = {
96
- "inputs": [n_jets, len(jet_vars)],
97
- }
98
-
99
- # setup tracks
100
- shapes_tracks = {
101
- "inputs": [n_jets, n_tracks_per_jet, len(track_vars)],
102
- "valid": [n_jets, n_tracks_per_jet],
103
- }
104
-
105
- # setup jets
106
- rng = np.random.default_rng()
107
- jets_dtype = np.dtype([(n, "f4") for n in jet_vars])
108
- jets = u2s(rng.random(shapes_jets["inputs"]), jets_dtype)
109
- jets["HadronConeExclTruthLabelID"] = np.random.choice([0, 4, 5], size=n_jets)
110
- jets["pt"] *= 400e3
111
- jets["eta"] = (jets["eta"] - 0.5) * 6.0
112
- jets["abs_eta"] = np.abs(jets["eta"])
113
-
114
- # setup tracks
115
- tracks_dtype = np.dtype([(n, "f4") for n in track_vars])
116
- tracks = u2s(rng.random(shapes_tracks["inputs"]), tracks_dtype)
117
- valid = rng.random(shapes_tracks["valid"])
118
- valid = valid.astype(bool).view(dtype=np.dtype([("valid", bool)]))
119
- tracks = join_structured_arrays([tracks, valid])
120
-
121
- fname = NamedTemporaryFile(suffix=".h5", dir=mkdtemp()).name
122
- f = h5py.File(fname, "w")
123
- f.create_dataset("jets", data=jets)
124
- f.create_dataset("tracks", data=tracks)
125
- f.create_dataset("flow", data=tracks)
126
- return fname, f
127
-
128
-
129
75
  def join_structured_arrays(arrays: list):
130
76
  """Join a list of structured numpy arrays.
131
77
 
@@ -1,5 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  from dataclasses import dataclass
2
4
  from pathlib import Path
5
+ from subprocess import check_output
3
6
 
4
7
  import h5py
5
8
  import numpy as np
@@ -27,6 +30,8 @@ class H5Writer:
27
30
  self.dst.parent.mkdir(parents=True, exist_ok=True)
28
31
  self.file = h5py.File(self.dst, "w")
29
32
  self.add_attr("srcfile", str(self.src))
33
+ self.git_hash = check_output(["git", "rev-parse", "HEAD"]).decode("ascii").strip()
34
+ self.add_attr("git_hash", self.git_hash)
30
35
  for name, var in self.variables.items():
31
36
  self.create_ds(name, var)
32
37
 
@@ -0,0 +1,105 @@
1
+ from __future__ import annotations
2
+
3
+ from tempfile import NamedTemporaryFile, mkdtemp
4
+
5
+ import h5py
6
+ import numpy as np
7
+ from numpy.lib.recfunctions import unstructured_to_structured as u2s
8
+
9
+ from ftag.hdf5 import join_structured_arrays
10
+
11
+ __all__ = ["get_mock_file"]
12
+
13
+ JET_VARS = [
14
+ ("pt", "f4"),
15
+ ("eta", "f4"),
16
+ ("abs_eta", "f4"),
17
+ ("mass", "f4"),
18
+ ("pt_btagJes", "f4"),
19
+ ("eta_btagJes", "f4"),
20
+ ("n_tracks", "i4"),
21
+ ("HadronConeExclTruthLabelID", "i4"),
22
+ ("HadronConeExclTruthLabelPt", "f4"),
23
+ ("n_truth_promptLepton", "i4"),
24
+ ("flavour_label", "i4"),
25
+ ]
26
+
27
+ TRACK_VARS = [
28
+ ("d0", "f4"),
29
+ ("z0SinTheta", "f4"),
30
+ ("dphi", "f4"),
31
+ ("deta", "f4"),
32
+ ("qOverP", "f4"),
33
+ ("IP3D_signed_d0_significance", "f4"),
34
+ ("IP3D_signed_z0_significance", "f4"),
35
+ ("phiUncertainty", "f4"),
36
+ ("thetaUncertainty", "f4"),
37
+ ("qOverPUncertainty", "f4"),
38
+ ("numberOfPixelHits", "i4"),
39
+ ("numberOfSCTHits", "i4"),
40
+ ("numberOfInnermostPixelLayerHits", "i4"),
41
+ ("numberOfNextToInnermostPixelLayerHits", "i4"),
42
+ ("numberOfInnermostPixelLayerSharedHits", "i4"),
43
+ ("numberOfInnermostPixelLayerSplitHits", "i4"),
44
+ ("numberOfPixelSharedHits", "i4"),
45
+ ("numberOfPixelSplitHits", "i4"),
46
+ ("numberOfSCTSharedHits", "i4"),
47
+ ("numberOfPixelHoles", "i4"),
48
+ ("numberOfSCTHoles", "i4"),
49
+ ]
50
+
51
+
52
+ def softmax(x, axis=None):
53
+ """Compute softmax values for each sets of scores in x."""
54
+ e_x = np.exp(x - np.max(x, axis=axis, keepdims=True))
55
+ return e_x / e_x.sum(axis=axis, keepdims=True)
56
+
57
+
58
+ def get_mock_scores(labels: np.ndarray):
59
+ rng = np.random.default_rng(42)
60
+ scores = np.zeros((len(labels), 3))
61
+ for label, count in zip(*np.unique(labels, return_counts=True)):
62
+ if label == 0:
63
+ scores[labels == label] = rng.normal(loc=[2, 0, 0], scale=1, size=(count, 3))
64
+ elif label == 4:
65
+ scores[labels == label] = rng.normal(loc=[0, 1, 0], scale=2.5, size=(count, 3))
66
+ elif label == 5:
67
+ scores[labels == label] = rng.normal(loc=[0, 0, 3.5], scale=5, size=(count, 3))
68
+ scores = softmax(scores, axis=1)
69
+ cols = [f"MockTagger_p{x}" for x in ["u", "c", "b"]]
70
+ scores = u2s(scores, dtype=np.dtype([(name, "f4") for name in cols]))
71
+ return scores
72
+
73
+
74
+ def get_mock_file(num_jets=1000, tracks_name: str = "tracks", num_tracks: int = 40):
75
+ # setup jets
76
+ rng = np.random.default_rng(42)
77
+ jets_dtype = np.dtype(JET_VARS)
78
+ jets = u2s(rng.random((num_jets, len(JET_VARS))), jets_dtype)
79
+ jets["HadronConeExclTruthLabelID"] = rng.choice([0, 4, 5], size=num_jets)
80
+ jets["flavour_label"] = rng.choice([0, 4, 5], size=num_jets)
81
+ jets["pt"] *= 400e3
82
+ jets["mass"] *= 50e3
83
+ jets["eta"] = (jets["eta"] - 0.5) * 6.0
84
+ jets["abs_eta"] = np.abs(jets["eta"])
85
+ jets["n_truth_promptLepton"] = 0
86
+
87
+ # add tagger scores
88
+ scores = get_mock_scores(jets["HadronConeExclTruthLabelID"])
89
+ jets = join_structured_arrays([jets, scores])
90
+
91
+ # create a tempfile in a new folder
92
+ fname = NamedTemporaryFile(suffix=".h5", dir=mkdtemp()).name
93
+ f = h5py.File(fname, "w")
94
+ f.create_dataset("jets", data=jets)
95
+
96
+ # setup tracks
97
+ if tracks_name:
98
+ tracks_dtype = np.dtype(TRACK_VARS)
99
+ tracks = u2s(rng.random((num_jets, num_tracks, len(TRACK_VARS))), tracks_dtype)
100
+ valid = rng.choice([True, False], size=(num_jets, num_tracks))
101
+ valid = valid.astype(bool).view(dtype=np.dtype([("valid", bool)]))
102
+ tracks = join_structured_arrays([tracks, valid])
103
+ f.create_dataset(tracks_name, data=tracks)
104
+
105
+ return fname, f
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from dataclasses import dataclass
2
4
  from pathlib import Path
3
5
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import glob
2
4
  from pathlib import Path
3
5
 
@@ -5,11 +5,11 @@ authors = [{name="Sam Van Stroud"}, {name="Philipp Gadow"}]
5
5
  dynamic = ["version"]
6
6
  license = {text = "MIT"}
7
7
  readme = "README.md"
8
- requires-python = ">=3.10"
8
+ requires-python = ">=3.8"
9
9
  dependencies = [
10
- "h5py==3.8.0",
11
- "numpy==1.24.*",
12
- "PyYAML==6.0"
10
+ "h5py>=3.0", # requires numpy
11
+ "numpy",
12
+ "PyYAML>=5.1"
13
13
  ]
14
14
 
15
15
  [project.urls]
@@ -42,6 +42,7 @@ line-length = 100
42
42
  preview = "True"
43
43
 
44
44
  [tool.ruff]
45
+ target-version = "py38"
45
46
  select = ["I", "E", "W", "F", "B", "UP", "ARG", "SIM", "TID", "RUF", "D2", "D3", "D4"]
46
47
  ignore = ["D211", "D213", "RUF005"]
47
48
  line-length = 100