Source code for lsurf.sources.point

# The Clear BSD License
#
# Copyright (c) 2026 Tobias Heibges
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted (subject to the limitations in the disclaimer
# below) provided that the following conditions are met:
#
#      * Redistributions of source code must retain the above copyright notice,
#      this list of conditions and the following disclaimer.
#
#      * Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in the
#      documentation and/or other materials provided with the distribution.
#
#      * Neither the name of the copyright holder nor the names of its
#      contributors may be used to endorse or promote products derived from this
#      software without specific prior written permission.
#
# NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
# THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

"""
Point Source Implementation

Provides an isotropic point source that emits rays uniformly in all
directions from a single point.

Examples
--------
>>> from surface_roughness.sources import PointSource
>>>
>>> source = PointSource(
...     position=(0, 0, 0),
...     num_rays=10000,
...     wavelength=532e-9,  # Green laser
...     power=1e-3
... )
>>> rays = source.generate()
"""

import numpy as np

from ..utilities.ray_data import RayBatch
from .base import RaySource


[docs] class PointSource(RaySource): """ Isotropic point source emitting in all directions. Generates rays from a single point with directions uniformly distributed over the unit sphere. Parameters ---------- position : tuple of float Source position (x, y, z) in meters. num_rays : int Number of rays to generate. wavelength : float or tuple of float Single wavelength (m) or (min, max) range. power : float, optional Total source power in watts. Default is 1.0. Attributes ---------- position : ndarray, shape (3,) Source position in meters. Notes ----- The angular distribution is uniform over the full 4π steradians. Each ray carries equal intensity (power / num_rays). Examples -------- >>> # Monochromatic point source >>> source = PointSource( ... position=(0, 0, 0), ... num_rays=10000, ... wavelength=532e-9, ... power=1e-3 ... ) >>> rays = source.generate() >>> # Polychromatic source (white light LED) >>> source = PointSource( ... position=(0, 0.1, 0), ... num_rays=5000, ... wavelength=(400e-9, 700e-9), ... power=0.5 ... ) """
[docs] def __init__( self, position: tuple[float, float, float], num_rays: int, wavelength: float | tuple[float, float], power: float = 1.0, ): """ Initialize point source. Parameters ---------- position : tuple of float Source position (x, y, z) in meters. num_rays : int Number of rays to generate. wavelength : float or tuple of float Wavelength in meters or (min, max) range. power : float, optional Total source power in watts. Default is 1.0. """ super().__init__(num_rays, wavelength, power) self.position = np.array(position, dtype=np.float32)
[docs] def generate(self) -> RayBatch: """ Generate isotropic ray distribution. Creates rays emanating from the source position with directions uniformly distributed over the unit sphere. Returns ------- RayBatch Ray batch with isotropic direction distribution. Notes ----- Uses the standard method for uniform sphere sampling: - θ uniformly distributed in [0, 2π) - cos(φ) uniformly distributed in [-1, 1] """ rays = self._allocate_rays() # All rays start at same position rays.positions[:] = self.position # Generate uniform distribution on sphere theta = np.random.uniform(0, 2 * np.pi, self.num_rays) cos_phi = np.random.uniform(-1, 1, self.num_rays) sin_phi = np.sqrt(1 - cos_phi**2) rays.directions[:, 0] = sin_phi * np.cos(theta) rays.directions[:, 1] = sin_phi * np.sin(theta) rays.directions[:, 2] = cos_phi self._assign_wavelengths(rays) return rays
[docs] def __repr__(self) -> str: """Return string representation.""" return ( f"PointSource(position={self.position.tolist()}, " f"num_rays={self.num_rays})" )