Skip to content

Commit b2de35b

Browse files
committed
Switch to generalized python reader
1 parent d2e5d15 commit b2de35b

File tree

5 files changed

+220
-22
lines changed

5 files changed

+220
-22
lines changed

include/particlezoo/PDGParticleCodes.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <cstdint>
44
#include <string_view>
5+
#include <vector>
56

67
namespace ParticleZoo {
78

@@ -911,6 +912,27 @@ namespace ParticleZoo {
911912
return static_cast<std::int32_t>(type);
912913
}
913914

915+
/**
916+
* @brief Get a complete list of particle names and types.
917+
*
918+
* Exposes all ParticleType values generated by PARTICLE_LIST for use in bindings.
919+
* Returns a stable reference to a static vector of pairs (name, ParticleType).
920+
*/
921+
inline const std::vector<std::pair<std::string_view, ParticleType>>& getAllParticleTypes() {
922+
static const std::vector<std::pair<std::string_view, ParticleType>> list = []{
923+
std::vector<std::pair<std::string_view, ParticleType>> v;
924+
v.reserve(1024);
925+
#define X(name, code) v.emplace_back(std::string_view(#name), ParticleType::name);
926+
PARTICLE_LIST
927+
#undef X
928+
// Include sentinel values as well
929+
v.emplace_back(std::string_view("Unsupported"), ParticleType::Unsupported);
930+
v.emplace_back(std::string_view("PseudoParticle"), ParticleType::PseudoParticle);
931+
return v;
932+
}();
933+
return list;
934+
}
935+
914936
#undef PARTICLE_LIST
915937

916938
} // namespace ParticleZoo

python/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ This directory contains a Python package that exposes the C++ ParticleZoo API us
77
- A C++20 compiler (g++ or clang++)
88
- pybind11 (will be installed automatically by pip)
99

10+
## Setup
11+
Create a new Python virtual environment and activate it by running the following commands:
12+
13+
```bash
14+
python -m venv .venv
15+
. .venv/bin/activate
16+
python -m pip install -U pip setuptools wheel pybind11
17+
```
18+
1019
## Build and install (editable)
1120
From the repository root or from this folder:
1221

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Example: List supported formats and read a phase space file.
4+
5+
Usage:
6+
python read_phase_space.py /path/to/file.IAEAphsp [--format IAEA] [--limit N]
7+
python read_phase_space.py --formats-only
8+
9+
Arguments:
10+
path Path to a phase space file to read. If omitted with --formats-only,
11+
the script just lists formats and exits.
12+
13+
Options:
14+
--format NAME Explicit format name (e.g., IAEA, EGS, TOPAS, penEasy).
15+
--limit N Print at most N particles (default: all).
16+
--formats-only Only list formats and exit.
17+
"""
18+
19+
from __future__ import annotations
20+
import argparse
21+
import sys
22+
from typing import Optional
23+
24+
import particlezoo as pz
25+
26+
27+
def print_supported_formats() -> None:
28+
fmts = pz.FormatRegistry.supported_formats()
29+
print("Supported formats ({}):".format(len(fmts)))
30+
for f in fmts:
31+
print("- {name:<8} ext={ext:<12} {desc}".format(
32+
name=f.name, ext=f.file_extension, desc=f.description
33+
))
34+
35+
36+
def create_reader(path: str, fmt: Optional[str]) -> pz.PhaseSpaceFileReader:
37+
if fmt:
38+
return pz.create_reader_for_format(fmt, path)
39+
return pz.create_reader(path)
40+
41+
42+
def print_particle_summary(p: pz.Particle, idx: int) -> None:
43+
tname = pz.get_particle_type_name(p.type)
44+
print(
45+
f"[{idx}] type={tname:<20} KE={p.kinetic_energy:.6g}"
46+
f" pos=({p.x:.6g},{p.y:.6g},{p.z:.6g})"
47+
f" dir=({p.px:.6g},{p.py:.6g},{p.pz:.6g})"
48+
f" w={p.weight:.6g} newHist={p.is_new_history}"
49+
)
50+
51+
52+
def main(argv: list[str]) -> int:
53+
ap = argparse.ArgumentParser(description="List formats and read a phase space file")
54+
ap.add_argument("path", nargs="?", help="Path to phase space file")
55+
ap.add_argument("--format", dest="format", help="Explicit format name to use")
56+
ap.add_argument("--limit", type=int, default=None, help="Print at most N particles")
57+
ap.add_argument("--formats-only", action="store_true", help="Only list formats and exit")
58+
args = ap.parse_args(argv)
59+
60+
print_supported_formats()
61+
if args.formats_only and not args.path:
62+
return 0
63+
64+
if not args.path:
65+
ap.error("path is required unless --formats-only is provided")
66+
67+
# Create reader
68+
reader = create_reader(args.path, args.format)
69+
70+
# File summary
71+
try:
72+
n = reader.get_number_of_particles()
73+
except Exception:
74+
# Some formats may compute this lazily: fall back to counting
75+
n = None
76+
try:
77+
h = reader.get_number_of_original_histories()
78+
except Exception:
79+
h = None
80+
print(
81+
f"\nReading: {reader.get_file_name()} (format={reader.get_phsp_format()})\n"
82+
f"File size: {reader.get_file_size()} bytes\n"
83+
f"Particles: {n if n is not None else 'unknown'}\n"
84+
f"Original histories: {h if h is not None else 'unknown'}\n"
85+
)
86+
87+
# Iterate particles
88+
count = 0
89+
for idx, p in enumerate(reader):
90+
print_particle_summary(p, idx)
91+
count += 1
92+
if args.limit is not None and count >= args.limit:
93+
break
94+
95+
print(f"\nRead {count} particles. Histories read: {reader.get_histories_read()}.")
96+
reader.close()
97+
return 0
98+
99+
100+
if __name__ == "__main__":
101+
raise SystemExit(main(sys.argv[1:]))

python/src/particlezoo/__init__.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
IntPropertyType,
55
FloatPropertyType,
66
BoolPropertyType,
7-
IAEAReader,
7+
PhaseSpaceFileReader,
8+
SupportedFormat,
9+
FormatRegistry,
10+
create_reader,
11+
create_reader_for_format,
12+
all_particle_types,
813
get_particle_type_from_pdgid,
914
get_pdgid,
1015
get_particle_type_name,
@@ -17,7 +22,12 @@
1722
"IntPropertyType",
1823
"FloatPropertyType",
1924
"BoolPropertyType",
20-
"IAEAReader",
25+
"PhaseSpaceFileReader",
26+
"SupportedFormat",
27+
"FormatRegistry",
28+
"create_reader",
29+
"create_reader_for_format",
30+
"all_particle_types",
2131
"get_particle_type_from_pdgid",
2232
"get_pdgid",
2333
"get_particle_type_name",

python/src/pybind/module.cpp

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,34 @@
33

44
#include "particlezoo/Particle.h"
55
#include "particlezoo/PDGParticleCodes.h"
6-
#include "particlezoo/IAEA/IAEAphspFile.h"
6+
#include "particlezoo/PhaseSpaceFileReader.h"
7+
#include "particlezoo/utilities/formats.h"
78

89
namespace py = pybind11;
910
using namespace ParticleZoo;
1011

1112
PYBIND11_MODULE(_pz, m) {
1213
m.doc() = "Python bindings for ParticleZoo core and IAEA reader";
1314

14-
// ParticleType enum (expose only sentinel values; use helpers for full mapping)
15-
py::enum_<ParticleType>(m, "ParticleType")
16-
.value("Unsupported", ParticleType::Unsupported)
17-
.value("PseudoParticle", ParticleType::PseudoParticle);
15+
// Ensure built-in formats are registered before any factory calls
16+
try { FormatRegistry::RegisterStandardFormats(); } catch (...) { /* idempotent; ignore */ }
17+
18+
// ParticleType enum (full mapping from X-macro via helper in header)
19+
auto pyParticleType = py::enum_<ParticleType>(m, "ParticleType");
20+
for (const auto &entry : getAllParticleTypes()) {
21+
pyParticleType.value(std::string(entry.first).c_str(), entry.second);
22+
}
23+
// ensure enum is closed/usable
24+
pyParticleType.export_values();
25+
26+
// Convenience: return all particle types as a dict name -> enum value
27+
m.def("all_particle_types", [](){
28+
py::dict d;
29+
for (const auto &entry : getAllParticleTypes()) {
30+
d[py::str(entry.first)] = entry.second;
31+
}
32+
return d;
33+
}, "Return a mapping of ParticleType names to enum values");
1834

1935
// Property enums
2036
py::enum_<IntPropertyType>(m, "IntPropertyType")
@@ -75,26 +91,66 @@ PYBIND11_MODULE(_pz, m) {
7591
.def("set_incremental_histories", &Particle::setIncrementalHistories)
7692
;
7793

78-
// IAEA Reader
79-
using IAEAReader = IAEAphspFile::Reader;
80-
py::class_<IAEAReader>(m, "IAEAReader")
81-
.def(py::init<const std::string&, const UserOptions&>(),
82-
py::arg("filename"), py::arg("options") = UserOptions{})
83-
.def("get_number_of_particles", static_cast<std::uint64_t (IAEAReader::*)() const>(&IAEAReader::getNumberOfParticles))
84-
.def("get_number_of_particles_by_type", static_cast<std::uint64_t (IAEAReader::*)(ParticleType) const>(&IAEAReader::getNumberOfParticles), py::arg("particle_type"))
85-
.def("get_number_of_original_histories", &IAEAReader::getNumberOfOriginalHistories)
86-
.def("has_more_particles", &IAEAReader::hasMoreParticles)
87-
.def("get_next_particle", static_cast<Particle (IAEAReader::*)()>(&IAEAReader::getNextParticle))
88-
.def("get_file_size", &IAEAReader::getFileSize)
89-
.def("get_file_name", &IAEAReader::getFileName)
90-
.def("close", &IAEAReader::close)
91-
.def("__iter__", [](IAEAReader &self) -> IAEAReader& { return self; }, py::return_value_policy::reference_internal)
92-
.def("__next__", [](IAEAReader &self) {
94+
// PhaseSpaceFileReader (abstract base; created via FormatRegistry factories)
95+
py::class_<PhaseSpaceFileReader, std::unique_ptr<PhaseSpaceFileReader>>(m, "PhaseSpaceFileReader")
96+
.def("get_number_of_particles", &PhaseSpaceFileReader::getNumberOfParticles)
97+
.def("get_number_of_original_histories", &PhaseSpaceFileReader::getNumberOfOriginalHistories)
98+
.def("get_histories_read", &PhaseSpaceFileReader::getHistoriesRead)
99+
.def("get_particles_read", static_cast<std::uint64_t (PhaseSpaceFileReader::*)()>(&PhaseSpaceFileReader::getParticlesRead))
100+
.def("has_more_particles", &PhaseSpaceFileReader::hasMoreParticles)
101+
.def("get_next_particle", static_cast<Particle (PhaseSpaceFileReader::*)()>(&PhaseSpaceFileReader::getNextParticle))
102+
.def("get_file_size", &PhaseSpaceFileReader::getFileSize)
103+
.def("get_file_name", &PhaseSpaceFileReader::getFileName)
104+
.def("get_phsp_format", &PhaseSpaceFileReader::getPHSPFormat)
105+
.def("move_to_particle", &PhaseSpaceFileReader::moveToParticle, py::arg("index"))
106+
.def("is_x_constant", &PhaseSpaceFileReader::isXConstant)
107+
.def("is_y_constant", &PhaseSpaceFileReader::isYConstant)
108+
.def("is_z_constant", &PhaseSpaceFileReader::isZConstant)
109+
.def("is_px_constant", &PhaseSpaceFileReader::isPxConstant)
110+
.def("is_py_constant", &PhaseSpaceFileReader::isPyConstant)
111+
.def("is_pz_constant", &PhaseSpaceFileReader::isPzConstant)
112+
.def("is_weight_constant", &PhaseSpaceFileReader::isWeightConstant)
113+
.def("get_constant_x", &PhaseSpaceFileReader::getConstantX)
114+
.def("get_constant_y", &PhaseSpaceFileReader::getConstantY)
115+
.def("get_constant_z", &PhaseSpaceFileReader::getConstantZ)
116+
.def("get_constant_px", &PhaseSpaceFileReader::getConstantPx)
117+
.def("get_constant_py", &PhaseSpaceFileReader::getConstantPy)
118+
.def("get_constant_pz", &PhaseSpaceFileReader::getConstantPz)
119+
.def("get_constant_weight", &PhaseSpaceFileReader::getConstantWeight)
120+
.def("close", &PhaseSpaceFileReader::close)
121+
.def("__iter__", [](PhaseSpaceFileReader &self) -> PhaseSpaceFileReader& { return self; }, py::return_value_policy::reference_internal)
122+
.def("__next__", [](PhaseSpaceFileReader &self) {
93123
if (!self.hasMoreParticles()) throw py::stop_iteration();
94124
return self.getNextParticle();
95125
})
96126
;
97127

128+
// SupportedFormat struct
129+
py::class_<SupportedFormat>(m, "SupportedFormat")
130+
.def_property_readonly("name", [](const SupportedFormat& f){ return f.name; })
131+
.def_property_readonly("description", [](const SupportedFormat& f){ return f.description; })
132+
.def_property_readonly("file_extension", [](const SupportedFormat& f){ return f.fileExtension; })
133+
.def_property_readonly("file_extension_can_have_suffix", [](const SupportedFormat& f){ return f.fileExtensionCanHaveSuffix; })
134+
;
135+
136+
// FormatRegistry static interface
137+
py::class_<FormatRegistry>(m, "FormatRegistry")
138+
.def_static("register_standard_formats", &FormatRegistry::RegisterStandardFormats)
139+
.def_static("supported_formats", &FormatRegistry::SupportedFormats)
140+
.def_static("formats_for_extension", &FormatRegistry::FormatsForExtension, py::arg("extension"))
141+
.def_static("extension_for_format", &FormatRegistry::ExtensionForFormat, py::arg("format_name"))
142+
;
143+
144+
// Convenience factory functions for readers
145+
m.def("create_reader", [](const std::string& filename){
146+
FormatRegistry::RegisterStandardFormats();
147+
return FormatRegistry::CreateReader(filename, UserOptions{});
148+
}, py::arg("filename"));
149+
m.def("create_reader_for_format", [](const std::string& format, const std::string& filename){
150+
FormatRegistry::RegisterStandardFormats();
151+
return FormatRegistry::CreateReader(format, filename, UserOptions{});
152+
}, py::arg("format_name"), py::arg("filename"));
153+
98154
// Expose a simple alias to create a Particle by PDG code
99155
m.def("particle_from_pdg", [](int pdg, float kineticEnergy, float x, float y, float z, float px, float py, float pz, bool isNewHistory, float weight){
100156
ParticleType t = getParticleTypeFromPDGID(static_cast<std::int32_t>(pdg));

0 commit comments

Comments
 (0)