Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
Unreleased
----------
* Offset random event times by 1 s so the first chunk never starts before 0 (ChunkInput 0.4.2)
* Replace `np.in1d`, removed in NumPy 2.4, with `np.isin`

1.6.3 / 2026-01-29
------------------
* Add offset to noise sampling (https://github.com/XENONnT/fuse/pull/370)
Expand Down
2 changes: 1 addition & 1 deletion fuse/plugins/detector_physics/s1_photon_propagation.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ def photon_channels(self, positions, n_photon_hits):
map returns nested array with photon channels."""
channels = np.arange(self.n_tpc_pmts) # +1 for the channel map
p_per_channel = self.s1_pattern_map(positions)
p_per_channel[:, np.in1d(channels, self.turned_off_pmts)] = 0
p_per_channel[:, np.isin(channels, self.turned_off_pmts)] = 0

_photon_channels = []
for ppc, n in zip(p_per_channel, n_photon_hits):
Expand Down
2 changes: 1 addition & 1 deletion fuse/plugins/detector_physics/secondary_scintillation.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def compute(self, individual_electrons, interactions_in_roi):

# Just apply this to clusters with electrons
clusters_with_extracted_electrons = np.unique(individual_electrons["cluster_id"])
mask = np.in1d(interactions_in_roi["cluster_id"], clusters_with_extracted_electrons)
mask = np.isin(interactions_in_roi["cluster_id"], clusters_with_extracted_electrons)

if len(interactions_in_roi[mask]) == 0:
empty_result = np.zeros(
Expand Down
13 changes: 10 additions & 3 deletions fuse/plugins/micro_physics/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class ChunkInput(FuseBasePlugin):
and will create multiple chunks of data if needed.
"""

__version__ = "0.4.1"
__version__ = "0.4.2"

depends_on: Tuple = tuple()
provides = "geant4_interactions"
Expand Down Expand Up @@ -310,8 +310,15 @@ def output_chunk(self):
) # ns
else:
self.log.info("Using random event times.")
event_times = self.rng.uniform(
low=start / self.event_rate, high=stop / self.event_rate, size=num_interactions
# Same 1 s offset as the fixed spacing: an event drawn within
# first_chunk_left of 0 would make the first chunk start before 0
event_times = (
self.rng.uniform(
low=start / self.event_rate,
high=stop / self.event_rate,
size=num_interactions,
)
+ 1e9
).astype(np.int64)
event_times = stable_sort(event_times)

Expand Down
2 changes: 1 addition & 1 deletion fuse/plugins/micro_physics/lineage_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ def propagate_mega_type(mega_type):
previous_assigned_tracks = assigned

# Find all tracks that are children of the assigned tracks
children_mask = np.in1d(parents, list(assigned))
children_mask = np.isin(parents, list(assigned))

# Assign the mega type to the children
main_cluster_types[children_mask] = mega_type
Expand Down
58 changes: 58 additions & 0 deletions tests/test_random_event_times.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import os
import tempfile
import unittest
import logging
import numpy as np
import pandas as pd
from fuse.plugins.micro_physics.input import file_loader


class EarliestTimesRng:
"""Stand-in random generator drawing every event time at the lower edge."""

def uniform(self, low, high, size):
return np.full(size, low, dtype=np.float64)


class TestRandomEventTimes(unittest.TestCase):
def setUp(self):
self.temp_dir = tempfile.TemporaryDirectory()
n = 3
df = pd.DataFrame(
{
"eventid": np.arange(n),
"xp": np.zeros(n),
"yp": np.zeros(n),
"zp": np.full(n, -500.0),
"xp_pri": np.zeros(n),
"yp_pri": np.zeros(n),
"zp_pri": np.full(n, -500.0),
"time": np.zeros(n),
"ed": np.full(n, 10.0),
"type": ["e-"] * n,
"trackid": np.ones(n, dtype=int),
"parenttype": ["none"] * n,
"parentid": np.zeros(n, dtype=int),
"creaproc": ["none"] * n,
"edproc": ["eIoni"] * n,
}
)
self.file_name = "events.csv"
df.to_csv(os.path.join(self.temp_dir.name, self.file_name), index=False)

def tearDown(self):
self.temp_dir.cleanup()

def test_first_chunk_does_not_start_before_zero(self):
loader = file_loader(
self.temp_dir.name,
self.file_name,
EarliestTimesRng(),
fixed_event_spacing=False,
log=logging.getLogger("fuse"),
)
chunks = list(loader.output_chunk())
self.assertGreaterEqual(chunks[0][1], 0)
for data, left, right, _ in chunks:
self.assertTrue(np.all((data["time"] >= left) & (data["time"] <= right)))
self.assertEqual(sum(len(c[0]) for c in chunks), 3)
Loading