# 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})"
)