Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
scottprahl committed Feb 21, 2024
1 parent dd998f7 commit 13c7641
Showing 1 changed file with 228 additions and 0 deletions.
228 changes: 228 additions & 0 deletions iadpython/port.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
# pylint: disable=invalid-name
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-arguments
# pylint: disable=consider-using-f-string
# pylint: disable=line-too-long

"""
This module defines the Port class, which is integral to the analysis of light properties
within an integrating sphere. The Port class specifically models a port on the sphere's
surface, calculating and storing its geometrical properties and its interaction with light.
Classes:
Port: Represents a port in an integrating sphere. It holds information about
the port's dimensions, position, and optical properties. The class provides
methods to calculate the port's geometrical characteristics, such as the
area of the spherical cap it forms, its sagitta, and the chord distance from
the port's center to its edge. Additionally, it includes functionality to
assess whether a given point on the sphere's surface falls within the port's
boundaries.
Functions:
- cap_area: Calculates the area of the spherical cap formed by the port.
- approx_relative_cap_area: Estimates the relative area of the spherical cap.
- calculate_sagitta: Determines the sagitta (or height) of the spherical cap.
- max_center_chord: Computes the maximum distance from the port's center to its edge.
- relative_cap_area: Calculates the exact relative area of the spherical cap.
- hit: Checks if a point on the sphere falls within the port's boundaries.
Example:
>>> import iadpython as iad
>>> sphere = iad.Sphere(200, 20) # Example of creating a sphere object
>>> port = Port(sphere, 20, 0.5, 0, 0, 0)
>>> print(port)
See Also:
Sphere: Another class/module within the iadpython package that models the entire integrating sphere.
"""

import random
import numpy as np

def uniform_disk():
"""
Generate a point uniformly distributed on a unit disk.
This function generates and returns a point (x, y) that is uniformly distributed
over the area of a unit disk centered at the origin (0, 0). The unit disk is
defined as the set of all points (x, y) such that x^2 + y^2 <= 1. The method
uses rejection sampling, where random points are generated within the square
that bounds the unit disk, and only points that fall within the disk are accepted.
Returns:
tuple of (float, float, float): A point (x, y) where x and y are the
coordinates of the point uniformly distributed within the unit disk,
and s is the square of the distance from the origin to this point.
"""
s = 2
while s > 1:
x = 2 * random.random() - 1
y = 2 * random.random() - 1
s = x*x + y*y
return x, y, s

class Port():
"""
A container class for a port in an integrating sphere, which is a structure
used to analyze light properties. The class calculates and stores various
geometrical properties of the port, such as its diameter, position, and the
relative area of the spherical cap it forms on the sphere's surface.
Attributes:
sphere (object): Reference to the sphere the port belongs to.
d (float): Diameter of the port in millimeters (mm).
uru (float): Diffuse reflectance of diffuse light on the port.
x (float): X-coordinate of the port's center on the sphere's surface.
y (float): Y-coordinate of the port's center on the sphere's surface.
z (float): Z-coordinate of the port's center on the sphere's surface.
a (float): Relative area of the port's spherical cap to the sphere's surface.
chord2 (float): Square of the distance from the port center to the port edge.
sagitta (float): The sagitta (height) of the spherical cap formed by the port.
Examples:
Importing the module and creating a sphere with a port:
```python
import iadpython as iad
s = iad.Sphere(200, 20)
print(s.sample)
"""

def __init__(self, sphere, d, uru=0, x=0, y=0, z=0):
"""
Initializes a Port object with specified dimensions and position within a sphere.
The coordinates (x,y,z) indicate the center of the port and must be located
on the sphere surface. They default to the center of the sphere (0,0,0). The
cap location is only used by the `MCSphere` class which uses the `hit()`
method.
Args:
sphere: The sphere object to which the port belongs.
d: Diameter of the port in millimeters.
uru: Diffuse reflectance of diffuse light on the port.
x: X-coordinate of the port's center on the sphere's surface.
y: Y-coordinate of the port's center on the sphere's surface.
z: Z-coordinate of the port's center on the sphere's surface.
"""
self.sphere = sphere
self._d = d
self.uru = uru

self.x = x
self.y = y
self.z = z

self.a = self.relative_cap_area()
self.sagitta = self.calculate_sagitta()
self.chord2 = self.max_center_chord()**2

@property
def d(self):
"""float: Gets the diameter of the port."""
return self._d

@d.setter
def d(self, value):
"""
Sets the diameter of the port and recalculates geometrical properties.
Args:
value (float): The new diameter of the port.
Raises:
AssertionError: If the new diameter exceeds the diameter of the sphere.
"""
assert self.sphere.d >= value, "Sphere must be bigger than the port."
self._d = value
self.a = self.relative_cap_area()
self.sagitta = self.calculate_sagitta()
self.chord2 = self.max_center_chord()
self.sphere._a_wall = 1 - self.sphere.sample.a - self.sphere.empty.a - self.sphere.detector.a

def __str__(self):
"""Return basic details as a string for printing."""
s = ""
s += " diameter = %7.2f mm\n" % self.d
s += " radius = %7.2f mm\n" % (self.d/2)
s += " chord = %7.2f mm\n" % np.sqrt(self.chord2)
s += " sagitta = %7.2f mm\n" % self.sagitta
s += " center = (%6.1f, %6.1f, %6.1f) mm\n" % (self.x, self.y, self.z)
s += " relative area = %7.4f\n" % self.a
s += " uru = %7.4f\n" % self.uru
return s

def cap_area(self):
"""Calculate area of spherical cap."""
R = self.sphere.d / 2
r = self.d / 2
h = R - np.sqrt(R**2 - r**2)
return 2 * np.pi * R * h

def approx_relative_cap_area(self):
"""Calculate approx relative area of spherical cap."""
R = self.sphere.d / 2
r = self.d / 2
return r**2 / (4 * R**2)

def calculate_sagitta(self):
"""Calculate sagitta of spherical cap."""
R = self.sphere.d / 2
r = self.d / 2
return R - np.sqrt(R**2 - r**2)

def max_center_chord(self):
"""Distance of chord from port center (on sphere) to port edge."""
r = self.d / 2
s = self.sagitta
return np.sqrt(r**2 + s**2)

def relative_cap_area(self):
"""Calculate relative area of spherical cap."""
h = (self.sphere.d - np.sqrt(self.sphere.d**2 - self.d**2)) / 2
return h / self.sphere.d

def hit(self):
"""Determine if point on the sphere within the port."""
r2 = (self.x - self.sphere.x)**2
r2 += (self.y - self.sphere.y)**2
r2 += (self.z - self.sphere.z)**2
print('cap center (%7.2f, %7.2f, %7.2f)' % (self.x,self.y,self.z))
print('pt on sph (%7.2f, %7.2f, %7.2f)' % (self.sphere.x,self.sphere.y,self.sphere.z))
print("cap distance %7.2f %7.2f"% (r2, self.chord2))
return r2 < self.chord2

def set_center(self, x, y, z):
"""Centers the cap at x,y,z."""
assert x**2 + y**2 + z**2 - (self.sphere.d / 2)**2 < 1e-6, "center not on sphere."
self.x = x
self.y = y
self.z = z

def uniform(self):
"""
Generate a point uniformly distributed on a spherical cap.
This function generates points uniformly distributed over a spherical cap,
defined by a specified sagitta (height of the cap from its base to the top)
and sphere radius. The spherical cap can be positioned either at the top or
bottom of the sphere. The method utilizes principles from uniform distribution
on a disk and transforms these points to the spherical cap geometry.
WARNING: The cap assumed to be at the top of a sphere. Nothing is done to rotate
the points so they align with the center of the cap!
The algorithm to generate a random point on a spherical cap is adapted from
http://marc-b-reynolds.github.io/distribution/2016/11/28/Uniform.html
Args:
sagitta: The height of the spherical cap from its base to the top.
sphere_radius: The radius of the sphere with the cap.
Returns:
A numpy array of a random point on the spherical cap surface.
"""
h = self.sagitta / (self.sphere.d / 2)
ux, uy, s = uniform_disk()
root = np.sqrt(h * (2 - h * s))
point = np.array([ux * root, uy * root, 1 - h * s]) * (self.sphere.d / 2)
return point

0 comments on commit 13c7641

Please sign in to comment.