Detectors Module
Detectors Module - Ray Collection and Measurement
This module provides detector classes for measuring ray arrival times, positions, angles, and intensities. Useful for simulating experimental measurements.
Module Structure
- small/
Point detectors (SphericalDetector, PlanarDetector, DirectionalDetector)
- extended/
Surface detectors (RecordingSphereDetector, LocalRecordingSphereDetector)
Primary Classes
- DetectorResultdataclass
Unified bulk numpy-based result container. Primary return type for all detectors and simulations.
- DetectorProtocolProtocol
Protocol defining the detector interface.
Available Detectors
- SphericalDetector
Spherical detector for omnidirectional collection.
- PlanarDetector
Rectangular planar detector for imaging.
- DirectionalDetector
Detector with angular acceptance cone.
- RecordingSphereDetector
Spherical surface at altitude for Earth-scale simulations.
- LocalRecordingSphereDetector
Spherical surface centered at origin for local simulations.
Analysis Functions
- compute_angular_distribution
Histogram of ray arrival angles.
- compute_time_distribution
Histogram of ray arrival times.
- compute_intensity_distribution
Histogram of detected intensities.
- compute_wavelength_distribution
Histogram of detected wavelengths.
- compute_statistics
Summary statistics for detection events.
Interface
All detectors implement the detect() method:
>>> result = detector.detect(rays)
Returns a DetectorResult object with bulk numpy arrays. For backward compatibility, result.to_detection_events() returns a list of DetectionEvent objects.
Examples
>>> from lsurf.detectors import SphericalDetector, DetectorResult
>>>
>>> # Create a spherical detector at 100m altitude
>>> sphere = SphericalDetector(
... center=(0, 0, 100),
... radius=10.0,
... name="Far-field detector"
... )
>>> result = sphere.detect(reflected_rays)
>>> print(f"Detected {result.num_rays} rays")
>>> print(f"Total intensity: {result.total_intensity:.3e}")
>>>
>>> # For backward compatibility
>>> events = result.to_detection_events()
- class lsurf.detectors.DetectorResult(positions, directions, times, intensities, wavelengths, ray_indices=None, generations=None, polarization_vectors=None, detector_name='unnamed', metadata=<factory>)[source]
Bases:
objectUnified bulk numpy-based result container for detector outputs.
This is the primary return type for all detectors and simulations, providing efficient bulk storage and analysis of detected rays.
- positions
Intersection positions (meters)
- Type:
ndarray, shape (N, 3)
- directions
Ray directions at intersection (unit vectors)
- Type:
ndarray, shape (N, 3)
- times
Time of arrival (seconds)
- Type:
ndarray, shape (N,)
- intensities
Ray intensity at detection
- Type:
ndarray, shape (N,)
- wavelengths
Ray wavelength (meters)
- Type:
ndarray, shape (N,)
- ray_indices
Original ray indices in the source RayBatch
- Type:
ndarray, shape (N,), optional
- generations
Ray generation (number of surface interactions)
- Type:
ndarray, shape (N,), optional
- polarization_vectors
3D polarization vectors (electric field direction)
- Type:
ndarray, shape (N, 3), optional
Examples
>>> result = detector.detect(rays) >>> print(f"Detected {result.num_rays} rays") >>> print(f"Total intensity: {result.total_intensity:.3e}") >>> >>> # Filter by time window >>> early = result.filter_by_time(0, 1e-6) >>> >>> # Merge multiple results >>> combined = DetectorResult.merge([result1, result2])
- compute_statistics()[source]
Compute summary statistics for the detected rays.
- Returns:
Dictionary containing: - count: number of rays - total_intensity: sum of intensities - mean_time: average arrival time - std_time: arrival time standard deviation - min_time: earliest arrival - max_time: latest arrival - mean_wavelength: average wavelength - time_spread: max_time - min_time
- Return type:
Examples
>>> stats = result.compute_statistics() >>> print(f"Detected {stats['count']} rays") >>> print(f"Time spread: {stats['time_spread']:.3e} s")
- compute_time_histogram(num_bins=50, time_range=None, weighted=True)[source]
Compute arrival time distribution histogram.
- Parameters:
- Returns:
bin_centers (ndarray) – Bin centers in seconds
values (ndarray) – Histogram values (counts or intensity sum per bin)
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float64]], ndarray[tuple[Any, …], dtype[float64]]]
Examples
>>> times, counts = result.compute_time_histogram(num_bins=100) >>> plt.bar(times * 1e9, counts, width=(times[1]-times[0])*1e9) >>> plt.xlabel('Time (ns)')
- compute_angular_distribution(reference_direction, num_bins=50, weighted=True)[source]
Compute angular distribution histogram.
- Parameters:
- Returns:
bin_centers (ndarray) – Bin centers in degrees (0-180)
values (ndarray) – Histogram values
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float64]], ndarray[tuple[Any, …], dtype[float64]]]
Examples
>>> angles, counts = result.compute_angular_distribution( ... reference_direction=np.array([0, 0, 1]) ... )
- compute_angular_coordinates(earth_center=None)[source]
Compute angular coordinates for all ray intersection points.
Computes spherical coordinates (latitude/longitude) of intersection points on a detection sphere relative to Earth’s center.
- Parameters:
earth_center (ndarray, optional) – Earth center position, default (0, 0, -EARTH_RADIUS)
- Returns:
Dictionary with: - ‘elevation’: Latitude angle above equator (radians, -π/2 to π/2) - ‘azimuth’: Longitude angle (radians, -π to π) - ‘zenith’: Zenith angle from north pole (radians, 0 to π) - ‘incidence’: Angle between ray direction and outward radial (radians)
- Return type:
Examples
>>> coords = result.compute_angular_coordinates() >>> elevation_deg = np.degrees(coords['elevation'])
- compute_viewing_angle_from_origin(origin=None)[source]
Compute viewing angle from horizontal at specified origin.
Calculates the angle above the horizontal plane (XY plane) when viewing each intersection point from the origin position.
- Parameters:
origin (ndarray, optional) – Observer position, default (0, 0, 0)
- Returns:
Viewing angle from horizontal in radians (-π/2 to π/2) Positive angles are above horizontal, negative below
- Return type:
ndarray
Examples
>>> viewing_angles = result.compute_viewing_angle_from_origin() >>> print(f"Mean viewing angle: {np.degrees(viewing_angles.mean()):.1f}°")
- compute_ray_direction_angles()[source]
Compute elevation and azimuth angles of ray directions.
- Returns:
Dictionary with: - ‘elevation’: Angle above horizontal plane in radians (-π/2 to π/2) - ‘azimuth’: Azimuth angle in horizontal plane in radians (-π to π)
- Return type:
Examples
>>> angles = result.compute_ray_direction_angles() >>> print(f"Mean elevation: {np.degrees(angles['elevation'].mean()):.1f}°")
- filter(mask)[source]
Filter rays by boolean mask.
- Parameters:
mask (ndarray of bool) – Boolean mask, True for rays to keep
- Returns:
Filtered result containing only selected rays
- Return type:
Examples
>>> high_intensity = result.filter(result.intensities > 0.1)
- filter_by_wavelength(min_wavelength, max_wavelength)[source]
Filter rays by wavelength range.
- Parameters:
- Returns:
Filtered result
- Return type:
Examples
>>> visible = result.filter_by_wavelength(400e-9, 700e-9)
- filter_by_time(min_time, max_time)[source]
Filter rays by time range.
- Parameters:
- Returns:
Filtered result
- Return type:
Examples
>>> early_arrivals = result.filter_by_time(0, 1e-6)
- filter_by_intensity(min_intensity=0.0, max_intensity=inf)[source]
Filter rays by intensity range.
- Parameters:
- Returns:
Filtered result
- Return type:
Examples
>>> bright = result.filter_by_intensity(min_intensity=0.1)
- classmethod empty(detector_name='unnamed')[source]
Create an empty DetectorResult.
- Parameters:
detector_name (str) – Name for the detector
- Returns:
Empty result with zero rays
- Return type:
Examples
>>> empty = DetectorResult.empty("my_detector") >>> print(empty.is_empty) # True
- classmethod merge(results)[source]
Merge multiple DetectorResults into one.
- Parameters:
results (list of DetectorResult) – Results to merge
- Returns:
Combined result
- Return type:
Examples
>>> combined = DetectorResult.merge([result1, result2, result3])
- save_npz(filepath)[source]
Save to numpy .npz file.
- Parameters:
filepath (str or Path) – Output file path
Examples
>>> result.save_npz("detections.npz")
- classmethod load_npz(filepath)[source]
Load from numpy .npz file.
- Parameters:
filepath (str or Path) – Input file path
- Returns:
Loaded result
- Return type:
Examples
>>> result = DetectorResult.load_npz("detections.npz")
- save_hdf5(filepath, compression='gzip')[source]
Save to HDF5 file.
- Parameters:
Examples
>>> result.save_hdf5("detections.h5")
- classmethod load_hdf5(filepath)[source]
Load from HDF5 file.
- Parameters:
filepath (str or Path) – Input file path
- Returns:
Loaded result
- Return type:
Examples
>>> result = DetectorResult.load_hdf5("detections.h5")
- to_detection_events()[source]
Convert to list of DetectionEvent objects for backward compatibility.
- Returns:
List of individual detection events
- Return type:
Examples
>>> events = result.to_detection_events() >>> for event in events: ... print(f"Ray {event.ray_index}: {event.intensity:.3f}")
- classmethod from_detection_events(events, detector_name='unnamed', generations=None, polarization_vectors=None)[source]
Create from list of DetectionEvent objects for backward compatibility.
- Parameters:
events (list of DetectionEvent) – List of detection events
detector_name (str) – Detector name
generations (ndarray, optional) – Ray generations if available
polarization_vectors (ndarray, optional) – Polarization vectors if available
- Returns:
Converted result
- Return type:
Examples
>>> result = DetectorResult.from_detection_events(detector.events)
- to_recorded_rays()[source]
Convert to RecordedRays for backward compatibility.
- Returns:
Converted RecordedRays object
- Return type:
Notes
This is provided for backward compatibility during migration. New code should use DetectorResult directly.
- classmethod from_recorded_rays(recorded, detector_name='unnamed', ray_indices=None)[source]
Create from RecordedRays for backward compatibility.
- Parameters:
recorded (RecordedRays) – RecordedRays object to convert
detector_name (str) – Detector name
ray_indices (ndarray, optional) – Original ray indices if available
- Returns:
Converted result
- Return type:
Notes
This is provided for backward compatibility during migration. New code should use DetectorResult directly.
- __iter__()[source]
Iterate over detection events for backward compatibility.
Yields DetectionEvent objects for each detected ray. New code should access arrays directly instead.
- __getitem__(index)[source]
Get a single detection event by index for backward compatibility.
- Parameters:
index (int) – Index of the detection event
- Returns:
Detection event at the specified index
- Return type:
- __init__(positions, directions, times, intensities, wavelengths, ray_indices=None, generations=None, polarization_vectors=None, detector_name='unnamed', metadata=<factory>)
- class lsurf.detectors.DetectorProtocol(*args, **kwargs)[source]
Bases:
ProtocolProtocol defining the interface for all detector implementations.
All detectors (small/point detectors and extended/surface detectors) should implement this protocol for consistent behavior.
- detect(rays) DetectorResult[source]
Detect rays and return results
Examples
>>> class MyDetector: ... def __init__(self, name: str = "My Detector"): ... self.name = name ... self._result = DetectorResult.empty(name) ... ... def detect(self, rays: RayBatch) -> DetectorResult: ... # Detection logic here ... return self._result ... ... def clear(self) -> None: ... self._result = DetectorResult.empty(self.name)
- detect(rays)[source]
Detect rays and return detection results.
- Parameters:
rays (RayBatch) – Ray batch to test for detection
- Returns:
Detection results containing all rays that hit this detector
- Return type:
- clear()[source]
Clear accumulated detection data.
Resets the detector to its initial state with no recorded detections.
- __init__(*args, **kwargs)
- class lsurf.detectors.AccumulatingDetectorProtocol(*args, **kwargs)[source]
Bases:
DetectorProtocol,ProtocolProtocol for detectors that accumulate results over multiple detect() calls.
These detectors maintain internal state and can return cumulative results.
- accumulated_result
All accumulated detections since last clear()
- Type:
Examples
>>> detector = SphericalDetector(center=(0, 0, 100), radius=10) >>> result1 = detector.detect(rays1) >>> result2 = detector.detect(rays2) >>> total = detector.accumulated_result # Contains both result1 and result2 >>> detector.clear() # Reset accumulation
- accumulated_result: DetectorResult
- class lsurf.detectors.ExtendedDetectorProtocol(*args, **kwargs)[source]
Bases:
DetectorProtocol,ProtocolProtocol for extended (surface) detectors with additional geometric properties.
Extended detectors have a defined geometric surface and can provide additional information about the detection geometry.
- center
Center position of the detector
- Type:
ndarray, shape (3,)
Examples
>>> from lsurf.detectors import RecordingSphereDetector >>> detector = RecordingSphereDetector(altitude=33000.0) >>> print(detector.sphere_radius) # Earth radius + altitude
- property center: NDArray
Center position of the detector.
- class lsurf.detectors.Detector(name='Detector')[source]
Bases:
ABCAbstract base class for all detectors.
A detector records rays that pass through its detection volume, capturing their positions, directions, timing, and other properties.
- Parameters:
name (str, optional) – Detector name for identification. Default is “Detector”.
- events
Recorded detection events.
- Type:
Notes
Derived classes must implement the detect() method which tests rays against the detector geometry and records detection events.
The base class provides common analysis methods for processing the recorded events.
Examples
Creating a custom detector:
>>> class MyDetector(Detector): ... def __init__(self, position, radius, name="MyDetector"): ... super().__init__(name) ... self.position = np.array(position) ... self.radius = radius ... ... def detect(self, rays, current_time=0.0): ... events = [] ... # ... detection logic ... ... return events
- __init__(name='Detector')[source]
Initialize detector.
- Parameters:
name (str, optional) – Detector name for identification. Default is “Detector”.
- abstractmethod detect(rays, current_time=0.0)[source]
Check which rays intersect this detector and record events.
- Parameters:
- Returns:
List of newly detected events.
- Return type:
Notes
Implementations should: - Only test active rays (rays.active == True) - Create DetectionEvent for each detected ray - Append events to self.events - Return the list of newly created events
- clear()[source]
Clear all recorded events.
Resets the detector to its initial state with no recorded events.
- get_arrival_times()[source]
Get array of all arrival times.
- Returns:
times – Arrival times in seconds for all detected rays.
- Return type:
ndarray, shape (N,)
Examples
>>> times = detector.get_arrival_times() >>> print(f"Mean arrival time: {times.mean():.3e} s")
- get_arrival_angles(reference_direction)[source]
Get angles between ray directions and reference direction.
- Parameters:
reference_direction (ndarray, shape (3,)) – Reference vector for angle calculation.
- Returns:
angles – Angles in radians between each detected ray’s direction and the reference direction.
- Return type:
ndarray, shape (N,)
Examples
>>> angles = detector.get_arrival_angles(np.array([0, 0, 1])) >>> print(f"Mean angle: {np.degrees(angles.mean()):.1f} degrees")
- get_intensities()[source]
Get array of all detected intensities.
- Returns:
intensities – Intensity values for all detected rays.
- Return type:
ndarray, shape (N,)
- get_wavelengths()[source]
Get array of all detected wavelengths.
- Returns:
wavelengths – Wavelengths in meters for all detected rays.
- Return type:
ndarray, shape (N,)
- get_positions()[source]
Get array of all detection positions.
- Returns:
positions – 3D positions where rays were detected.
- Return type:
ndarray, shape (N, 3)
- class lsurf.detectors.DetectionEvent(ray_index, position, direction, time, wavelength, intensity)[source]
Bases:
objectRepresents a single ray detection event.
Immutable record of a ray being detected, including its position, direction, timing, and intensity at the moment of detection.
- Parameters:
ray_index (int) – Index of the ray in the original RayBatch.
position (ndarray, shape (3,)) – 3D position where ray hit detector in meters.
direction (ndarray, shape (3,)) – Ray direction at detection (unit vector).
time (float) – Arrival time at detector in seconds.
wavelength (float) – Ray wavelength in meters.
intensity (float) – Ray intensity at detection.
Notes
This class is frozen (immutable) for thread safety and to prevent accidental modification of detection records.
- __init__(ray_index, position, direction, time, wavelength, intensity)
- class lsurf.detectors.SphericalDetector(center, radius, name='Spherical Detector', use_gpu=True)[source]
Bases:
objectSpherical detector centered at a point.
Detects all rays that pass within a certain radius of the center point. Good for collecting rays from all directions without directional bias.
- Parameters:
- center
Detector center position.
- Type:
ndarray, shape (3,)
- accumulated_result
All accumulated detections since last clear().
- Type:
Notes
Detection is based on the closest approach distance between each ray and the center point. A ray is detected if this distance is less than or equal to the detection radius.
The arrival time is computed assuming the ray travels through air (n approx 1.0) from its current position to the detection point.
Examples
>>> detector = SphericalDetector( ... center=(0, 0, 100), ... radius=10.0, ... use_gpu=True ... ) >>> result = detector.detect(reflected_rays) >>> print(f"Detected {result.num_rays} rays")
- __init__(center, radius, name='Spherical Detector', use_gpu=True)[source]
Initialize spherical detector.
- property accumulated_result: DetectorResult
All accumulated detections since last clear().
- property events: list[DetectionEvent]
list of DetectionEvent objects.
For new code, use accumulated_result instead.
- Type:
Backward compatibility
- detect(rays, current_time=0.0, accumulate=True)[source]
Detect rays that pass within detection radius.
For each ray, find the closest approach to center. If within radius, record a detection event.
- Parameters:
- Returns:
Newly detected rays.
- Return type:
- clear()[source]
Clear all recorded detections.
Resets the detector to its initial state with no recorded events.
- get_arrival_times()[source]
Get array of all arrival times.
- Returns:
times – Arrival times in seconds for all detected rays.
- Return type:
ndarray, shape (N,)
- get_arrival_angles(reference_direction)[source]
Get angles between ray directions and reference direction.
- Parameters:
reference_direction (ndarray, shape (3,)) – Reference vector for angle calculation.
- Returns:
angles – Angles in radians.
- Return type:
ndarray, shape (N,)
- get_intensities()[source]
Get array of all detected intensities.
- Returns:
intensities – Intensity values for all detected rays.
- Return type:
ndarray, shape (N,)
- get_wavelengths()[source]
Get array of all detected wavelengths.
- Returns:
wavelengths – Wavelengths in meters.
- Return type:
ndarray, shape (N,)
- class lsurf.detectors.PlanarDetector(center, normal, width, height, name='Planar Detector')[source]
Bases:
objectPlanar detector with finite rectangular size.
Detects rays that intersect a rectangular plane at a specific position and orientation. Useful for imaging applications and beam profiling.
- Parameters:
center (tuple of float) – Center position of detector plane (x, y, z) in meters.
normal (tuple of float) – Normal vector (defines which direction detector faces).
width (float) – Width of detector (in local u direction) in meters.
height (float) – Height of detector (in local v direction) in meters.
name (str, optional) – Detector name. Default is “Planar Detector”.
- center
Detector center position.
- Type:
ndarray, shape (3,)
- normal
Unit normal vector.
- Type:
ndarray, shape (3,)
- u
Local x-axis direction (width direction).
- Type:
ndarray, shape (3,)
- v
Local y-axis direction (height direction).
- Type:
ndarray, shape (3,)
- accumulated_result
All accumulated detections since last clear().
- Type:
Notes
The local coordinate system is constructed with: - normal: direction the detector faces - u: perpendicular to normal (width direction) - v: perpendicular to both normal and u (height direction)
Only rays traveling toward the front face of the detector (opposite to the normal direction) are detected.
Examples
>>> detector = PlanarDetector( ... center=(0, 0, 50), ... normal=(0, 0, -1), # Facing -z direction ... width=0.1, ... height=0.1 ... ) >>> result = detector.detect(rays)
- __init__(center, normal, width, height, name='Planar Detector')[source]
Initialize planar detector.
- property accumulated_result: DetectorResult
All accumulated detections since last clear().
- property events: list[DetectionEvent]
list of DetectionEvent objects.
For new code, use accumulated_result instead.
- Type:
Backward compatibility
- detect(rays, current_time=0.0, accumulate=True)[source]
Detect rays that intersect the detector plane within bounds.
- Parameters:
- Returns:
Newly detected rays.
- Return type:
Notes
Only detects rays that: - Are traveling toward the front face (opposite to normal) - Intersect the plane in the forward direction - Hit within the width x height bounds
- clear()[source]
Clear all recorded detections.
Resets the detector to its initial state with no recorded events.
- get_image(bins_u=100, bins_v=100)[source]
Generate intensity image from detection events.
- Parameters:
- Returns:
u_centers (ndarray, shape (bins_u,)) – Bin centers in u direction (meters).
v_centers (ndarray, shape (bins_v,)) – Bin centers in v direction (meters).
image (ndarray, shape (bins_v, bins_u)) – Intensity image (sum of intensities per bin).
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float64]], ndarray[tuple[Any, …], dtype[float64]], ndarray[tuple[Any, …], dtype[float64]]]
- class lsurf.detectors.DirectionalDetector(position, direction, acceptance_angle, radius, name='Directional Detector')[source]
Bases:
objectDetector with angular acceptance cone.
Detects rays that pass within a specified radius AND arrive within a specified angular acceptance cone. Useful for modeling detectors with limited field of view such as telescopes or fiber couplers.
- Parameters:
position (tuple of float) – Detector position (x, y, z) in meters.
direction (tuple of float) – Direction detector is pointing (acceptance cone axis).
acceptance_angle (float) – Half-angle of acceptance cone in radians.
radius (float) – Detection radius at position in meters.
name (str, optional) – Detector name. Default is “Directional Detector”.
- position
Detector position.
- Type:
ndarray, shape (3,)
- direction
Unit vector pointing in detector viewing direction.
- Type:
ndarray, shape (3,)
- accumulated_result
All accumulated detections since last clear().
- Type:
Notes
A ray is detected if: 1. Its closest approach to the detector position is within radius 2. The angle between the ray direction and the detector direction
(considering the ray as incoming) is within acceptance_angle
Examples
>>> # 10-degree acceptance cone detector >>> detector = DirectionalDetector( ... position=(0, 0, 100), ... direction=(0, 0, -1), ... acceptance_angle=np.radians(10), ... radius=5.0 ... ) >>> result = detector.detect(rays) >>> print(f"Detected {result.num_rays} rays within acceptance cone")
- __init__(position, direction, acceptance_angle, radius, name='Directional Detector')[source]
Initialize directional detector.
- property accumulated_result: DetectorResult
All accumulated detections since last clear().
- property events: list[DetectionEvent]
list of DetectionEvent objects.
For new code, use accumulated_result instead.
- Type:
Backward compatibility
- detect(rays, current_time=0.0, accumulate=True)[source]
Detect rays within acceptance cone and detection radius.
- Parameters:
- Returns:
Newly detected rays.
- Return type:
- clear()[source]
Clear all recorded detections.
Resets the detector to its initial state with no recorded events.
- class lsurf.detectors.RecordingSphereDetector(altitude=33000.0, earth_center=(0, 0, -6371000.0), earth_radius=6371000.0, name='Recording Sphere')[source]
Bases:
objectSpherical detection surface at a specified altitude above Earth.
Records all rays that intersect the sphere, capturing full ray state for later analysis. Used for Earth-scale simulations where the sphere is centered on Earth’s center.
- Parameters:
- center
Center position (Earth’s center).
- Type:
ndarray, shape (3,)
- accumulated_result
All accumulated detections since last clear().
- Type:
Notes
The recording sphere has radius = earth_radius + altitude, centered at earth_center. This creates a sphere that surrounds Earth at the specified altitude.
Examples
>>> # Detector at 33 km altitude >>> detector = RecordingSphereDetector(altitude=33000.0) >>> result = detector.detect(rays) >>> >>> # Access angular coordinates >>> coords = result.compute_angular_coordinates()
- __init__(altitude=33000.0, earth_center=(0, 0, -6371000.0), earth_radius=6371000.0, name='Recording Sphere')[source]
Initialize recording sphere detector.
- property center: ndarray[tuple[Any, ...], dtype[float64]]
Center position of the sphere (Earth’s center).
- property accumulated_result: DetectorResult
All accumulated detections since last clear().
- property events: list[DetectionEvent]
list of DetectionEvent objects.
For new code, use accumulated_result instead.
- Type:
Backward compatibility
- detect(rays, current_time=0.0, accumulate=True, compute_travel_time=True, speed_of_light=299792458.0, max_propagation_distance=None)[source]
Detect rays intersecting the recording sphere.
- Parameters:
rays (RayBatch) – Rays to detect
current_time (float) – Current simulation time (unused, for interface compatibility)
accumulate (bool) – Whether to accumulate results. Default is True.
compute_travel_time (bool) – If True, add travel time to intersection to ray’s accumulated time
speed_of_light (float) – Speed of light for time computation
max_propagation_distance (float, optional) – Maximum distance rays can propagate before detection (meters). If None, no limit is applied.
- Returns:
Detection results for all intersecting rays
- Return type:
- detect_rays(rays, compute_travel_time=True, speed_of_light=299792458.0, max_propagation_distance=None)[source]
Detect rays (alias for detect with accumulate=False).
This method is provided for backward compatibility with code that used the detect_rays() method.
- Parameters:
- Returns:
Detection results
- Return type:
- clear()[source]
Clear all recorded detections.
Resets the detector to its initial state with no recorded events.
- class lsurf.detectors.LocalRecordingSphereDetector(radius=33000.0, center=(0, 0, 0), name='Local Recording Sphere')[source]
Bases:
objectSpherical detection surface centered at a specified position.
Records all rays that intersect the sphere, useful for local-scale simulations without Earth curvature effects.
- Parameters:
- center
Center position of the sphere.
- Type:
ndarray, shape (3,)
- accumulated_result
All accumulated detections since last clear().
- Type:
Examples
>>> # Local detector at origin with 33 km radius >>> detector = LocalRecordingSphereDetector(radius=33000.0) >>> result = detector.detect(rays) >>> >>> # Detector at specific location >>> detector = LocalRecordingSphereDetector( ... radius=10000.0, ... center=(1000.0, 0.0, 500.0) ... )
- __init__(radius=33000.0, center=(0, 0, 0), name='Local Recording Sphere')[source]
Initialize local recording sphere detector.
- property accumulated_result: DetectorResult
All accumulated detections since last clear().
- property events: list[DetectionEvent]
list of DetectionEvent objects.
For new code, use accumulated_result instead.
- Type:
Backward compatibility
- detect(rays, current_time=0.0, accumulate=True, compute_travel_time=True, speed_of_light=299792458.0)[source]
Detect rays intersecting the recording sphere.
- Parameters:
rays (RayBatch) – Rays to detect
current_time (float) – Current simulation time (unused, for interface compatibility)
accumulate (bool) – Whether to accumulate results. Default is True.
compute_travel_time (bool) – If True, add travel time to intersection to ray’s accumulated time
speed_of_light (float) – Speed of light for time computation
- Returns:
Detection results for all intersecting rays
- Return type:
- detect_rays(rays, compute_travel_time=True, speed_of_light=299792458.0)[source]
Detect rays (alias for detect with accumulate=False).
This method is provided for backward compatibility with code that used the detect_rays() method.
- Parameters:
- Returns:
Detection results
- Return type:
- record_rays(rays)[source]
Record rays (backward compatibility alias for detect).
- Parameters:
rays (RayBatch) – Rays to record
- Returns:
Detection results
- Return type:
- clear()[source]
Clear all recorded detections.
Resets the detector to its initial state with no recorded events.
- lsurf.detectors.RecordingSphereBase
alias of
RecordingSphereDetector
- lsurf.detectors.RecordingSphere
alias of
RecordingSphereDetector
- lsurf.detectors.LocalRecordingSphere
alias of
LocalRecordingSphereDetector
- lsurf.detectors.compute_angular_distribution(events, reference_direction, num_bins=50)[source]
Compute angular distribution histogram.
- Parameters:
events (list of DetectionEvent) – Detection events to analyze.
reference_direction (ndarray, shape (3,)) – Reference direction for angle calculation.
num_bins (int, optional) – Number of histogram bins. Default is 50.
- Returns:
bin_centers (ndarray, shape (num_bins,)) – Bin centers in degrees.
counts (ndarray, shape (num_bins,)) – Number of events in each bin.
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float64]], ndarray[tuple[Any, …], dtype[int64]]]
Examples
>>> angles, counts = compute_angular_distribution( ... detector.events, ... reference_direction=np.array([0, 0, 1]), ... num_bins=90 # 2-degree bins ... ) >>> import matplotlib.pyplot as plt >>> plt.bar(angles, counts, width=2) >>> plt.xlabel('Angle (degrees)') >>> plt.ylabel('Count')
- lsurf.detectors.compute_time_distribution(events, num_bins=50, time_range=None)[source]
Compute arrival time distribution histogram.
- Parameters:
events (list of DetectionEvent) – Detection events to analyze.
num_bins (int, optional) – Number of histogram bins. Default is 50.
time_range (tuple of float, optional) – (min, max) time range for histogram. If None, uses data range.
- Returns:
bin_centers (ndarray, shape (N,)) – Bin centers in seconds.
counts (ndarray, shape (N,)) – Number of events in each bin.
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float64]], ndarray[tuple[Any, …], dtype[int64]]]
Notes
Automatically handles cases where all times are very similar by reducing the number of bins to avoid empty histograms.
Examples
>>> times, counts = compute_time_distribution( ... detector.events, ... num_bins=100 ... ) >>> print(f"Time spread: {times.max() - times.min():.3e} s")
- lsurf.detectors.compute_intensity_distribution(events, num_bins=50)[source]
Compute intensity distribution histogram.
- Parameters:
events (list of DetectionEvent) – Detection events to analyze.
num_bins (int, optional) – Number of histogram bins. Default is 50.
- Returns:
bin_centers (ndarray, shape (num_bins,)) – Bin centers (intensity values).
counts (ndarray, shape (num_bins,)) – Number of events in each bin.
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float64]], ndarray[tuple[Any, …], dtype[int64]]]
- lsurf.detectors.compute_wavelength_distribution(events, num_bins=50)[source]
Compute wavelength distribution histogram.
- Parameters:
events (list of DetectionEvent) – Detection events to analyze.
num_bins (int, optional) – Number of histogram bins. Default is 50.
- Returns:
bin_centers (ndarray, shape (num_bins,)) – Bin centers in meters.
counts (ndarray, shape (num_bins,)) – Number of events in each bin.
- Return type:
tuple[ndarray[tuple[Any, …], dtype[float64]], ndarray[tuple[Any, …], dtype[int64]]]
- lsurf.detectors.compute_statistics(events)[source]
Compute summary statistics for detection events.
- Parameters:
events (list of DetectionEvent) – Detection events to analyze.
- Returns:
stats – Dictionary containing: - count: number of events - total_intensity: sum of intensities - mean_time: average arrival time - std_time: arrival time standard deviation - min_time: earliest arrival - max_time: latest arrival - mean_wavelength: average wavelength - time_spread: max_time - min_time
- Return type:
Examples
>>> stats = compute_statistics(detector.events) >>> print(f"Detected {stats['count']} rays") >>> print(f"Total intensity: {stats['total_intensity']:.3e}") >>> print(f"Time spread: {stats['time_spread']:.3e} s")
- lsurf.detectors.detect_multi_spherical_gpu(*args, **kwargs)[source]
GPU-accelerated multi-detector detection. See detect_multi_position_gpu.
- lsurf.detectors.detect_multi_position_gpu(rays, detector_centers, detector_radius, threads_per_block=256)[source]
GPU-accelerated detection across multiple spherical detectors.
Wrapper function that accepts a RayBatch object for backward compatibility.
- Parameters:
- Returns:
hit_counts (ndarray, shape (M,)) – Number of rays hitting each detector.
hit_intensities (ndarray, shape (M,)) – Sum of intensities for rays hitting each detector.
- class lsurf.detectors.ConstantSizeDetectorRings(detector_radial_size=10000.0, detector_altitude=33000.0, max_elevation_deg=90.0, min_elevation_deg=-2.0, earth_radius=6371000.0, ring_boundaries_deg=None, ring_centers_deg=None, ring_distances=None, n_rings=0, detector_sphere_radius=0.0)[source]
Bases:
objectConstant-size detector ring geometry with no shadowing.
Creates a set of annular detector rings centered on a sphere at fixed altitude above Earth’s surface. Each ring has constant physical radial width, with angular size varying based on distance from origin.
Adjacent rings touch exactly (no overlap, no gaps) when viewed from origin, ensuring complete angular coverage with no shadowing.
- Parameters:
detector_radial_size (float) – Physical radial width of each detector in meters (default: 10 km)
detector_altitude (float) – Altitude of detector sphere above Earth’s surface in meters (default: 33 km)
max_elevation_deg (float) – Maximum elevation angle from horizontal (90° = zenith) (default: 90°)
min_elevation_deg (float) – Minimum elevation angle, where to stop generating rings (default: -2°)
earth_radius (float) – Earth radius in meters (default: EARTH_RADIUS constant)
- ring_boundaries_deg
Elevation angles of ring boundaries (N+1 values for N rings)
- Type:
ndarray
- ring_centers_deg
Elevation angles of ring centers (N values)
- Type:
ndarray
- ring_distances
Distances from origin to ring centers in meters (N values)
- Type:
ndarray
Examples
>>> rings = ConstantSizeDetectorRings( ... detector_radial_size=10000.0, # 10 km ... detector_altitude=33000.0, # 33 km ... ) >>> print(f"Created {rings.n_rings} rings") Created 17 rings >>> print(f"Coverage: {rings.ring_boundaries_deg[-1]:.1f}° to {rings.ring_boundaries_deg[0]:.1f}°") Coverage: -2.3° to 90.0°
- distance_at_elevation(elev_deg)[source]
Compute distance from origin (0,0,0) to detector sphere at given elevation.
- Uses the formula:
d(θ) = -sin(θ)·R_E + √(R_d² - R_E²·cos²(θ))
where R_E = Earth radius, R_d = detector sphere radius, θ = elevation angle.
- Parameters:
elev_deg (float) – Elevation angle from horizontal in degrees (90° = zenith)
- Returns:
Distance from origin to intersection point (meters)
- Return type:
- Raises:
ValueError – If no intersection exists at the given elevation
- point_at_elevation(elev_deg, azimuth_deg=0.0)[source]
Compute 3D point on detector sphere at given elevation and azimuth.
- find_detector_center(theta_top_deg)[source]
Find detector center elevation such that top edge is at theta_top.
For a detector with physical half-width w and center at elevation θ_c, the angular half-width as seen from origin is:
α = arctan(w / d(θ_c))
The top edge elevation is θ_top = θ_c + α.
This function solves for θ_c given θ_top using brentq root finding.
- horizontal_distance_at_elevation(elev_deg)[source]
Compute horizontal distance from origin to detector at given elevation.
- get_ring_horizontal_distances()[source]
Get horizontal distances for all ring boundaries.
- Returns:
Horizontal distances in meters for each ring boundary
- Return type:
ndarray
- summary()[source]
Return a summary string of the detector ring configuration.
- Returns:
Multi-line summary of configuration
- Return type:
- azimuth_bins_for_ring(ring_index, az_bin_size_m, az_range_deg=10.0)[source]
Compute azimuthal bins of constant physical size for a ring.
At each ring distance, computes how many bins of the given physical width fit within the ±az_range azimuth range.
- Parameters:
- Returns:
n_bins (int) – Number of azimuthal bins for this ring
az_edges_deg (ndarray) – Azimuth bin edges in degrees (n_bins + 1 values)
az_centers_deg (ndarray) – Azimuth bin centers in degrees (n_bins values)
- Return type:
- get_constant_size_grid(az_bin_size_m, az_range_deg=10.0)[source]
Get a grid of constant-size bins across all rings.
Each bin has approximately constant physical size: - Radial size: detector_radial_size (same for all rings) - Azimuthal size: az_bin_size_m (variable number of bins per ring)
- Parameters:
- Returns:
List of bin specifications with keys: - ring_idx: int - az_bin_idx: int - n_az_bins: int (total azimuth bins for this ring) - az_lo_deg, az_hi_deg: float - az_center_deg: float - distance_m: float - bin_area_m2: float (approximate)
- Return type:
- __init__(detector_radial_size=10000.0, detector_altitude=33000.0, max_elevation_deg=90.0, min_elevation_deg=-2.0, earth_radius=6371000.0, ring_boundaries_deg=None, ring_centers_deg=None, ring_distances=None, n_rings=0, detector_sphere_radius=0.0)
- lsurf.detectors.create_default_detector_rings()[source]
Create detector rings with default parameters (10 km size, 33 km altitude).
- Returns:
Detector ring configuration with default parameters
- Return type:
Detector Classes
|
Abstract base class for all detectors. |
|
Spherical detector centered at a point. |
|
Planar detector with finite rectangular size. |
|
Detector with angular acceptance cone. |
Detection Analysis
|
Compute angular distribution histogram. |
|
Compute arrival time distribution histogram. |
|
Compute intensity distribution histogram. |
|
Compute wavelength distribution histogram. |
|
Compute summary statistics for detection events. |