Skip to content

Commit

Permalink
improve sphere handling
Browse files Browse the repository at this point in the history
  • Loading branch information
scottprahl committed Feb 21, 2024
1 parent 852a462 commit 2d1a04f
Show file tree
Hide file tree
Showing 15 changed files with 1,488 additions and 728 deletions.
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ lint:
-pylint iadpython/redistribution.py
-pylint iadpython/rxt.py
-pylint iadpython/sphere.py
-pylint iadpython/port.py
-pylint iadpython/mc_sphere.py
-pylint iadpython/start.py
-pylint tests/test_boundary.py
-pylint tests/test_combo.py
Expand Down Expand Up @@ -50,6 +52,8 @@ doccheck:
-pydocstyle --convention=google iadpython/redistribution.py
-pydocstyle --convention=google iadpython/rxt.py
-pydocstyle --convention=google iadpython/sphere.py
-pydocstyle --convention=google iadpython/port.py
-pydocstyle --convention=google iadpython/mc_sphere.py
-pydocstyle --convention=google iadpython/start.py
-pydocstyle tests/test_boundary.py
-pydocstyle tests/test_combo.py
Expand All @@ -63,6 +67,7 @@ doccheck:
-pydocstyle tests/test_start.py
-pydocstyle tests/test_ur1_uru.py
-pydocstyle tests/test_nist.py
-pydocstyle tests/test_port.py
-pydocstyle tests_iadc/test_iadc.py
-pydocstyle tests_iadc/test_performance.py

Expand All @@ -89,7 +94,6 @@ test:
pytest --verbose tests/test_combo.py
pytest --verbose tests/test_fresnel.py
pytest --verbose tests/test_grid.py
pytest --verbose tests/test_iad.py
pytest --verbose tests/test_layer.py
pytest --verbose tests/test_layers.py
pytest --verbose tests/test_nist.py
Expand All @@ -98,8 +102,10 @@ test:
pytest --verbose tests/test_redistribution.py
pytest --verbose tests/test_rxt.py
pytest --verbose tests/test_sphere.py
pytest --verbose tests/test_port.py
pytest --verbose tests/test_start.py
pytest --verbose tests/test_ur1_uru.py
pytest --verbose tests/test_iad.py
pytest --verbose tests/test_all_notebooks.py

testc:
Expand Down
255 changes: 82 additions & 173 deletions docs/Adding-Doubling-Basics.ipynb

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions docs/IAD-with-spheres.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,12 @@
"Sphere diameter = 152.4 mm\n",
"Port diameters\n",
" sample = 25.4 mm\n",
" entrance = 0.0 mm\n",
" empty = 0.0 mm\n",
" detector = 0.0 mm\n",
"Fractional areas of sphere\n",
" walls = 0.99301\n",
" sample = 0.00699\n",
" entrance = 0.00000\n",
" empty = 0.00000\n",
" detector = 0.00000\n",
"Diffuse reflectivities\n",
" walls = 99.0%\n",
Expand All @@ -112,12 +112,12 @@
"Sphere diameter = 152.4 mm\n",
"Port diameters\n",
" sample = 25.4 mm\n",
" entrance = 0.0 mm\n",
" empty = 0.0 mm\n",
" detector = 0.0 mm\n",
"Fractional areas of sphere\n",
" walls = 0.99301\n",
" sample = 0.00699\n",
" entrance = 0.00000\n",
" empty = 0.00000\n",
" detector = 0.00000\n",
"Diffuse reflectivities\n",
" walls = 99.0%\n",
Expand Down Expand Up @@ -386,7 +386,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.10"
"version": "3.11.6"
}
},
"nbformat": 4,
Expand Down
405 changes: 405 additions & 0 deletions docs/mcsphere_random.ipynb

Large diffs are not rendered by default.

92 changes: 53 additions & 39 deletions docs/sphere-basics.ipynb

Large diffs are not rendered by default.

513 changes: 332 additions & 181 deletions docs/sphere-single.ipynb

Large diffs are not rendered by default.

Binary file modified docs/sphere.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions iadpython/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,6 @@
from .iad import *
from .grid import *
from .rxt import *
from .port import *
from .mc_sphere import *

2 changes: 1 addition & 1 deletion iadpython/iad.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ def measured_rt(self):
.. math:: P_d'= a_d' t_{direct} r_w' (1-a_e') P ⋅ G'(r_s)
when the entrance port in the transmission sphere is closed,
when the empty port in the transmission sphere is closed,
:math:`a_e'=0`.
The normalized sphere measurements are
Expand Down
8 changes: 4 additions & 4 deletions iadpython/iadc.py
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,7 @@ def __init__(self):
"""Initialize class."""
self.d_sphere = 8.0 * 25.4
self.d_sample = 1.0 * 25.4
self.d_entrance = 1.0 * 25.4
self.d_empty = 1.0 * 25.4
self.d_detector = 0.5 * 25.4
self.refl_wall = 0.98
self.refl_detector = 0.05
Expand All @@ -794,14 +794,14 @@ def init_from_array(self, a):
"""Initialize with an array."""
self.d_sphere = a[0]
self.d_sample = a[1]
self.d_entrance = a[2]
self.d_empty = a[2]
self.d_detector = a[3]
self.refl_wall = a[4]
self.refl_detector = a[5]

def as_array(self):
"""Representation class as an array."""
return [self.d_sphere, self.d_sample, self.d_entrance,
return [self.d_sphere, self.d_sample, self.d_empty,
self.d_detector, self.refl_wall, self.refl_detector]

def as_c_array(self):
Expand All @@ -814,7 +814,7 @@ def __str__(self):
s = ""
s += "sphere diameter = %.1f mm\n" % self.d_sphere
s += "sample port diameter = %.1f mm\n" % self.d_sample
s += "entrance port diameter = %.1f mm\n" % self.d_entrance
s += "empty port diameter = %.1f mm\n" % self.d_empty
s += "detector port diameter = %.1f mm\n" % self.d_detector
s += "wall reflectivity = %.3f\n" % self.refl_wall
s += "detector reflectivity = %.3f" % self.refl_detector
Expand Down
172 changes: 172 additions & 0 deletions iadpython/mc_sphere.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# 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

"""
Class for managing integrating spheres.
This module contains the Sphere class, designed to simulate and analyze the
behavior of light within an integrating sphere. An integrating sphere is a
device used in optical measurements, which allows for the uniform scattering
of light. It is commonly used for reflection and transmission measurements
of materials.
The Sphere class models the geometrical and optical properties of an
integrating sphere, enabling the calculation of various parameters such as
the areas of spherical caps given port diameters, relative port areas,
and the gain caused by reflections within the sphere. It supports different
measurements scenarios by adjusting port diameters, detector reflectivity,
wall reflectivity, and using a reflectance standard.
Attributes:
d_sphere (float): Diameter of the integrating sphere in millimeters.
d_sample (float): Diameter of the port that holds the sample.
d_empty (float): Diameter of the empty port.
d_detector (float): Diameter of the port with the detector.
r_detector (float): Reflectivity of the detector.
r_wall (float): Reflectivity of the sphere's internal wall.
r_std (float): Reflectivity of the standard used for calibration.
Methods:
cap_area: actual area of a spherical cap for a port
relative_cap_area: relative area of spherical cap to the sphere's area.
gain: sphere gain relative to a black sphere
multiplier: multiplier for wall power due to sphere
Example usage:
>>> import iadpython
>>> s = iadpython.Sphere(250,20)
>>> print(s)
>>> s = iadpython.Sphere(200, 20, d_empty=10, d_detector=10, r_detector=0.8, r_wall=0.99, r_std=0.99)
>>> print(sphere)
>>> area_sample = sphere.cap_area(sphere.d_sample)
>>> print(f"Sample port area: {area_sample:.2f} mm²")
"""

import numpy as np
import random
from enum import Enum
from iadpython import Sphere

class PortType(Enum):
"""Possible sphere wall locations."""
EMPTY = 0
WALL = 1
SAMPLE = 2
DETECTOR = 3

class MCSphere(Sphere):
"""Class for an Monte Carlo integrating sphere calcs.
The center of the sphere is at (0,0,0)
For a reflection measurement, the empty port is the diameter of through
which the light enters to hit the sample. For a transmission measurement
this is the port that might allow unscattered transmission to leave. In
either case, the reflectance from this port is assumed to be zero.
Attributes:
- d_sphere: diameter of integrating sphere [mm]
- d_sample: diameter of the port that has the sample [mm]
- d_empty: diameter of the empty port [mm]
- d_detector: diameter of port with detector [mm]
- r_detector: reflectivity of the detector
- r_wall: reflectivity of the wall
- r_std: reflectivity of the standard used with the sphere
Example::
>>> import iadpython as iad
>>> s = iad.Sphere(200, 20)
>>> print(s)
"""

def __init__(self, d_sphere, d_sample, d_empty=0,
d_detector=0, r_detector=0, r_wall=0.99, r_std=0.99):

super().__init__(d_sphere, d_sample, d_empty, d_detector,
r_detector, r_wall, r_std)
self.weight = 1

def __str__(self):
"""Return basic details as a string for printing."""
s = super().__str__()
s = "\n"
s += "Sample Port\n"
s += " center = (%.1f, %.1f %.1f) mm\n" % (self.sample_x, self.sample_y, self.sample_z)
s += " chord = %.1f mm\n" % np.sqrt(self.sample_chord_sqr)
s += " radius = %.1f mm\n" % (np.sample_d/2)
s += " sagitta = %.1f mm\n" % (np.sample_sagitta)
s += "Detector Port\n"
s += " center = (%.1f, %.1f %.1f) mm\n" % (self.detector_x, self.detector_y, self.detector_z)
s += " chord = %.1f mm\n" % np.sqrt(self.detector_chord_sqr)
s += " radius = %.1f mm\n" % (np.detector_d/2)
s += " sagitta = %.1f mm\n" % (np.detector_sagitta)
s += "Empty Port\n"
s += " center = (%.1f, %.1f %.1f) mm\n" % (self.empty_x, self.empty_y, self.empty_z)
s += " chord = %.1f mm\n" % np.sqrt(self.empty_chord_sqr)
s += " radius = %.1f mm\n" % (np.empty_d/2)
s += " sagitta = %.1f mm\n" % (np.empty_sagitta)
return s

def do_one_photon(self):
"""Bounce photon inside sphere until it leaves."""
bounces = 0
detected = 0

# assume photon launched form sample
weight = 1
last_location = PortType.SAMPLE

while weight > 1e-4:

self.x, self.y, self.z = self.uniform()

if self.detector.hit():
if last_location == PortType.DETECTOR: # avoid hitting self
continue
if last_location == PortType.SAMPLE and self.baffle: # sample --> detector prohibited
continue

# record detected light and update weight
transmitted = weight * (1-self.r_detector)
detected += transmitted
weight -= transmitted
last_location = PortType.DETECTOR

elif self.sample.hit():
if last_location == PortType.SAMPLE: # avoid hitting self
continue
if last_location == PortType.DETECTOR and self.baffle: # detector --> sample prohibited
continue
weight *= self.r_sample
last_location = PortType.SAMPLE

elif self.empty.hit():
weight = 0
last_location = PortType.EMPTY

else:
# must have hit wall
weight *= self.r_wall
last_location = PortType.WALL

bounces +=1

return detected, bounces

def do_N_photons(self):

total_detected = 0
total_bounces = 0

for i in range(N):
detected, bounces = self.do_one_photon()
total_detected += detected
total_bounces += bounces

print("average detected = %.5f" % (total_detected/N))
print("average bounces = %.5f" % (total_bounces/N))

0 comments on commit 2d1a04f

Please sign in to comment.