Skip to content

Commit

Permalink
improve sphere handling
Browse files Browse the repository at this point in the history
scottprahl committed Feb 21, 2024
1 parent 852a462 commit 2d1a04f
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
@@ -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
@@ -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
@@ -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

@@ -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
@@ -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:
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
@@ -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",
@@ -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",
@@ -386,7 +386,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.10"
"version": "3.11.6"
}
},
"nbformat": 4,
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
Loading
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
@@ -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
@@ -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
8 changes: 4 additions & 4 deletions iadpython/iadc.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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):
@@ -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
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))

409 changes: 226 additions & 183 deletions iadpython/sphere.py

Large diffs are not rendered by default.

152 changes: 72 additions & 80 deletions tests/test_one_sphere.py
Original file line number Diff line number Diff line change
@@ -22,17 +22,16 @@ def test_forward_01(self):
s = iad.Sample(a=0.95, b=1)
ur1, ut1, uru, utu = s.rt()

r_wall = 0
d_sphere = 2500
d_sample = 10
rsph = iad.Sphere(d_sphere, d_sample, d_entrance=1, r_wall=r_wall)
M = rsph.multiplier(UR1=ur1, URU=uru, r_wall=r_wall)
M100 = rsph.multiplier(UR1=1.0, URU=1.0, r_wall=r_wall)
rsph = iad.Sphere(d_sphere, d_sample, d_empty=1, r_wall=0)
M = rsph.multiplier(UX1=ur1, URU=uru)
M100 = rsph.multiplier(UX1=1.0, URU=1.0)
ur1a = M / M100

tsph = iad.Sphere(d_sphere, d_sample, d_entrance=10, r_wall=r_wall)
M = tsph.multiplier(UR1=ut1, URU=uru, r_wall=r_wall)
M100 = tsph.multiplier(UR1=1, URU=0.0, r_wall=r_wall)
tsph = iad.Sphere(d_sphere, d_sample, d_empty=10, r_wall=0)
M = tsph.multiplier(UX1=ut1, URU=uru)
M100 = tsph.multiplier(UX1=1, URU=0.0)
ut1a = M / M100
self.assertAlmostEqual(ur1, ur1a, delta=1e-5)
self.assertAlmostEqual(ut1, ut1a, delta=1e-5)
@@ -42,17 +41,16 @@ def test_forward_02(self):
s = iad.Sample(a=0.95, b=1)
ur1, ut1, uru, utu = s.rt()

r_wall = 0.9
d_sphere = 2500
d_sample = 10
rsph = iad.Sphere(d_sphere, d_sample, d_entrance=1, r_wall=r_wall)
M = rsph.multiplier(UR1=ur1, URU=uru, r_wall=r_wall)
M100 = rsph.multiplier(UR1=1.0, URU=1.0, r_wall=r_wall)
rsph = iad.Sphere(d_sphere, d_sample, d_empty=1, r_wall=0.9)
M = rsph.multiplier(UX1=ur1, URU=uru)
M100 = rsph.multiplier(UX1=1.0, URU=1.0)
ur1a = M / M100

tsph = iad.Sphere(d_sphere, d_sample, d_entrance=10, r_wall=r_wall)
M = tsph.multiplier(UR1=ut1, URU=uru, r_wall=r_wall)
M100 = tsph.multiplier(UR1=1, URU=0.0, r_wall=r_wall)
tsph = iad.Sphere(d_sphere, d_sample, d_empty=10, r_wall=0.9)
M = tsph.multiplier(UX1=ut1, URU=uru)
M100 = tsph.multiplier(UX1=1, URU=0.0)
ut1a = M / M100
self.assertAlmostEqual(ur1, ur1a, delta=1e-5)
self.assertAlmostEqual(ut1, ut1a, delta=1e-5)
@@ -62,17 +60,16 @@ def test_forward_03(self):
s = iad.Sample(a=0.95, b=1)
ur1, ut1, uru, utu = s.rt()

r_wall = 0.98
d_sphere = 2500
d_sample = 10
rsph = iad.Sphere(d_sphere, d_sample, d_entrance=1, r_wall=r_wall)
M = rsph.multiplier(UR1=ur1, URU=uru, r_wall=r_wall)
M100 = rsph.multiplier(UR1=1.0, URU=1.0, r_wall=r_wall)
rsph = iad.Sphere(d_sphere, d_sample, d_empty=1, r_wall=0.98)
M = rsph.multiplier(UX1=ur1, URU=uru)
M100 = rsph.multiplier(UX1=1.0, URU=1.0)
ur1a = M / M100

tsph = iad.Sphere(d_sphere, d_sample, d_entrance=10, r_wall=r_wall)
M = tsph.multiplier(UR1=ut1, URU=uru, r_wall=r_wall)
M100 = tsph.multiplier(UR1=1, URU=0.0, r_wall=r_wall)
tsph = iad.Sphere(d_sphere, d_sample, d_empty=10, r_wall=0.98)
M = tsph.multiplier(UX1=ut1, URU=uru)
M100 = tsph.multiplier(UX1=1, URU=0.0)
ut1a = M / M100
self.assertAlmostEqual(ur1, ur1a, delta=1e-4)
self.assertAlmostEqual(ut1, ut1a, delta=1e-4)
@@ -82,17 +79,16 @@ def test_forward_04(self):
s = iad.Sample(a=0.95, b=1)
ur1, ut1, uru, utu = s.rt()

r_wall = 1.0
d_sphere = 2500
d_sample = 10
rsph = iad.Sphere(d_sphere, d_sample, d_entrance=1, r_wall=r_wall)
M = rsph.multiplier(UR1=ur1, URU=uru, r_wall=r_wall)
M100 = rsph.multiplier(UR1=1.0, URU=1.0, r_wall=r_wall)
rsph = iad.Sphere(d_sphere, d_sample, d_empty=1, r_wall=1.0)
M = rsph.multiplier(UX1=ur1, URU=uru)
M100 = rsph.multiplier(UX1=1.0, URU=1.0)
ur1a = M / M100

tsph = iad.Sphere(d_sphere, d_sample, d_entrance=10, r_wall=r_wall)
M = tsph.multiplier(UR1=ut1, URU=uru, r_wall=r_wall)
M100 = tsph.multiplier(UR1=1, URU=0.0, r_wall=r_wall)
tsph = iad.Sphere(d_sphere, d_sample, d_empty=10, r_wall=1.0)
M = tsph.multiplier(UX1=ut1, URU=uru)
M100 = tsph.multiplier(UX1=1, URU=0.0)
ut1a = M / M100

# actual result is complicated.
@@ -108,17 +104,16 @@ def test_forward_01(self):
s = iad.Sample(a=0.95, b=1)
ur1, ut1, uru, utu = s.rt()

r_wall = 0
d_sphere = 200
d_sample = 10
rsph = iad.Sphere(d_sphere, d_sample, d_entrance=1, r_wall=r_wall)
M = rsph.multiplier(UR1=ur1, URU=uru, r_wall=r_wall)
M100 = rsph.multiplier(UR1=1.0, URU=1.0, r_wall=r_wall)
rsph = iad.Sphere(d_sphere, d_sample, d_empty=1, r_wall=0)
M = rsph.multiplier(UX1=ur1, URU=uru)
M100 = rsph.multiplier(UX1=1.0, URU=1.0)
ur1a = M / M100

tsph = iad.Sphere(d_sphere, d_sample, d_entrance=10, r_wall=r_wall)
M = tsph.multiplier(UR1=ut1, URU=uru, r_wall=r_wall)
M100 = tsph.multiplier(UR1=1, URU=0.0, r_wall=r_wall)
tsph = iad.Sphere(d_sphere, d_sample, d_empty=10, r_wall=0)
M = tsph.multiplier(UX1=ut1, URU=uru)
M100 = tsph.multiplier(UX1=1, URU=0.0)
ut1a = M / M100
self.assertAlmostEqual(ur1, ur1a, delta=2e-4)
self.assertAlmostEqual(ut1, ut1a, delta=2e-4)
@@ -128,17 +123,16 @@ def test_forward_02(self):
s = iad.Sample(a=0.95, b=1)
ur1, ut1, uru, utu = s.rt()

r_wall = 0.9
d_sphere = 200
d_sample = 10
rsph = iad.Sphere(d_sphere, d_sample, d_entrance=1, r_wall=r_wall)
M = rsph.multiplier(UR1=ur1, URU=uru, r_wall=r_wall)
M100 = rsph.multiplier(UR1=1.0, URU=1.0, r_wall=r_wall)
rsph = iad.Sphere(d_sphere, d_sample, d_empty=1, r_wall=0.9)
M = rsph.multiplier(UX1=ur1, URU=uru)
M100 = rsph.multiplier(UX1=1.0, URU=1.0)
ur1a = M / M100

tsph = iad.Sphere(d_sphere, d_sample, d_entrance=10, r_wall=r_wall)
M = tsph.multiplier(UR1=ut1, URU=uru, r_wall=r_wall)
M100 = tsph.multiplier(UR1=1, URU=0.0, r_wall=r_wall)
tsph = iad.Sphere(d_sphere, d_sample, d_empty=10, r_wall=0.9)
M = tsph.multiplier(UX1=ut1, URU=uru)
M100 = tsph.multiplier(UX1=1, URU=0.0)
ut1a = M / M100
self.assertAlmostEqual(ur1, ur1a, delta=2e-3)
self.assertAlmostEqual(ut1, ut1a, delta=2e-3)
@@ -148,17 +142,16 @@ def test_forward_03(self):
s = iad.Sample(a=0.95, b=1)
ur1, ut1, uru, utu = s.rt()

r_wall = 0.98
d_sphere = 200
d_sample = 10
rsph = iad.Sphere(d_sphere, d_sample, d_entrance=1, r_wall=r_wall)
M = rsph.multiplier(UR1=ur1, URU=uru, r_wall=r_wall)
M100 = rsph.multiplier(UR1=1.0, URU=1.0, r_wall=r_wall)
rsph = iad.Sphere(d_sphere, d_sample, d_empty=1, r_wall=0.98)
M = rsph.multiplier(UX1=ur1, URU=uru)
M100 = rsph.multiplier(UX1=1.0, URU=1.0)
ur1a = M / M100

tsph = iad.Sphere(d_sphere, d_sample, d_entrance=10, r_wall=r_wall)
M = tsph.multiplier(UR1=ut1, URU=uru, r_wall=r_wall)
M100 = tsph.multiplier(UR1=1, URU=0.0, r_wall=r_wall)
tsph = iad.Sphere(d_sphere, d_sample, d_empty=10, r_wall=0.98)
M = tsph.multiplier(UX1=ut1, URU=uru)
M100 = tsph.multiplier(UX1=1, URU=0.0)
ut1a = M / M100
self.assertAlmostEqual(ur1, ur1a, delta=2e-2)
self.assertAlmostEqual(ut1, ut1a, delta=2e-2)
@@ -168,17 +161,16 @@ def test_forward_04(self):
s = iad.Sample(a=0.95, b=1)
ur1, ut1, uru, utu = s.rt()

r_wall = 1.0
d_sphere = 200
d_sample = 10
rsph = iad.Sphere(d_sphere, d_sample, d_entrance=1, r_wall=r_wall)
M = rsph.multiplier(UR1=ur1, URU=uru, r_wall=r_wall)
M100 = rsph.multiplier(UR1=1.0, URU=1.0, r_wall=r_wall)
rsph = iad.Sphere(d_sphere, d_sample, d_empty=1, r_wall=1)
M = rsph.multiplier(UX1=ur1, URU=uru)
M100 = rsph.multiplier(UX1=1.0, URU=1.0)
ur1a = M / M100

tsph = iad.Sphere(d_sphere, d_sample, d_entrance=10, r_wall=r_wall)
M = tsph.multiplier(UR1=ut1, URU=uru, r_wall=r_wall)
M100 = tsph.multiplier(UR1=1, URU=0.0, r_wall=r_wall)
tsph = iad.Sphere(d_sphere, d_sample, d_empty=10, r_wall=1)
M = tsph.multiplier(UX1=ut1, URU=uru)
M100 = tsph.multiplier(UX1=1, URU=0.0)
ut1a = M / M100

# actual result is complicated.
@@ -197,14 +189,14 @@ def test_forward_04(self):
# r_wall = 0
# d_sphere = 200
# d_sample = 10
# rsph = iad.Sphere(d_sphere, d_sample, d_entrance=1, r_wall=r_wall)
# M = rsph.multiplier(UR1=ur1, URU=uru, r_wall=r_wall)
# M100 = rsph.multiplier(UR1=1.0, URU=1.0, r_wall=r_wall)
# rsph = iad.Sphere(d_sphere, d_sample, d_empty=1)
# M = rsph.multiplier(UX1=ur1, URU=uru)
# M100 = rsph.multiplier(UX1=1.0, URU=1.0)
# ur1a = M / M100
#
# tsph = iad.Sphere(d_sphere, d_sample, d_entrance=10, r_wall=r_wall)
# M = tsph.multiplier(UR1=ut1, URU=uru, r_wall=r_wall)
# M100 = tsph.multiplier(UR1=1, URU=0.0, r_wall=r_wall)
# tsph = iad.Sphere(d_sphere, d_sample, d_empty=10)
# M = tsph.multiplier(UX1=ut1, URU=uru)
# M100 = tsph.multiplier(UX1=1, URU=0.0)
# ut1a = M / M100
#
# exp = iad.Experiment(sample=s, r_sphere=rsph, t_sphere=tsph, num_spheres=1)
@@ -221,14 +213,14 @@ def test_forward_04(self):
# r_wall = 0.9
# d_sphere = 200
# d_sample = 10
# rsph = iad.Sphere(d_sphere, d_sample, d_entrance=1, r_wall=r_wall)
# M = rsph.multiplier(UR1=ur1, URU=uru, r_wall=r_wall)
# M100 = rsph.multiplier(UR1=1.0, URU=1.0, r_wall=r_wall)
# rsph = iad.Sphere(d_sphere, d_sample, d_empty=1)
# M = rsph.multiplier(UX1=ur1, URU=uru)
# M100 = rsph.multiplier(UX1=1.0, URU=1.0)
# ur1a = M / M100
#
# tsph = iad.Sphere(d_sphere, d_sample, d_entrance=10, r_wall=r_wall)
# M = tsph.multiplier(UR1=ut1, URU=uru, r_wall=r_wall)
# M100 = tsph.multiplier(UR1=1, URU=0.0, r_wall=r_wall)
# tsph = iad.Sphere(d_sphere, d_sample, d_empty=10)
# M = tsph.multiplier(UX1=ut1, URU=uru)
# M100 = tsph.multiplier(UX1=1, URU=0.0)
# ut1a = M / M100
# exp = iad.Experiment(sample=s, r_sphere=rsph, t_sphere=tsph, num_spheres=1)
# ur1b, ut1b = exp.measured_rt()
@@ -244,14 +236,14 @@ def test_forward_04(self):
# r_wall = 0.98
# d_sphere = 200
# d_sample = 10
# rsph = iad.Sphere(d_sphere, d_sample, d_entrance=1, r_wall=r_wall)
# M = rsph.multiplier(UR1=ur1, URU=uru, r_wall=r_wall)
# M100 = rsph.multiplier(UR1=1.0, URU=1.0, r_wall=r_wall)
# rsph = iad.Sphere(d_sphere, d_sample, d_empty=1)
# M = rsph.multiplier(UX1=ur1, URU=uru)
# M100 = rsph.multiplier(UX1=1.0, URU=1.0)
# ur1a = M / M100
#
# tsph = iad.Sphere(d_sphere, d_sample, d_entrance=10, r_wall=r_wall)
# M = tsph.multiplier(UR1=ut1, URU=uru, r_wall=r_wall)
# M100 = tsph.multiplier(UR1=1, URU=0.0, r_wall=r_wall)
# tsph = iad.Sphere(d_sphere, d_sample, d_empty=10)
# M = tsph.multiplier(UX1=ut1, URU=uru)
# M100 = tsph.multiplier(UX1=1, URU=0.0)
# ut1a = M / M100
# exp = iad.Experiment(sample=s, r_sphere=rsph, t_sphere=tsph, num_spheres=1)
# ur1b, ut1b = exp.measured_rt()
@@ -267,14 +259,14 @@ def test_forward_04(self):
# r_wall = 1.0
# d_sphere = 200
# d_sample = 10
# rsph = iad.Sphere(d_sphere, d_sample, d_entrance=1, r_wall=r_wall)
# M = rsph.multiplier(UR1=ur1, URU=uru, r_wall=r_wall)
# M100 = rsph.multiplier(UR1=1.0, URU=1.0, r_wall=r_wall)
# rsph = iad.Sphere(d_sphere, d_sample, d_empty=1)
# M = rsph.multiplier(UX1=ur1, URU=uru)
# M100 = rsph.multiplier(UX1=1.0, URU=1.0)
# ur1a = M / M100
#
# tsph = iad.Sphere(d_sphere, d_sample, d_entrance=10, r_wall=r_wall)
# M = tsph.multiplier(UR1=ut1, URU=uru, r_wall=r_wall)
# M100 = tsph.multiplier(UR1=1, URU=0.0, r_wall=r_wall)
# tsph = iad.Sphere(d_sphere, d_sample, d_empty=10)
# M = tsph.multiplier(UX1=ut1, URU=uru)
# M100 = tsph.multiplier(UX1=1, URU=0.0)
# ut1a = M / M100
#
# exp = iad.Experiment(sample=s, r_sphere=rsph, t_sphere=tsph, num_spheres=1)
55 changes: 55 additions & 0 deletions tests/test_port.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import unittest
import numpy as np
from iadpython import Sphere, Port

class TestPort(unittest.TestCase):
"""Unit tests for the Port class."""

def setUp(self):
"""Set up test conditions for Port tests."""
d_sample = 25 # 20 mm sample port
d_sphere = 200 # 200 mm diameter sphere
R = d_sphere /2
d_port = 20
uru_port = 0.5
self.sphere = Sphere(d_sphere, d_sample) #
self.sphere.z = R
self.port = Port(self.sphere, d_port, uru=uru_port, z=R) # Example port

def test_cap_area(self):
"""Test the cap_area method."""
area = self.port.cap_area()
self.assertTrue(np.isclose(area, 314.94861522998946), "Cap area calculation is incorrect")

def test_approx_relative_cap_area(self):
"""Test the approx_relative_cap_area method."""
approx_area = self.port.approx_relative_cap_area()
self.assertTrue(np.isclose(approx_area, 0.0025), "Approximate relative cap area calculation is incorrect")

def test_calculate_sagitta(self):
"""Test the calculate_sagitta method."""
sagitta = self.port.calculate_sagitta()
self.assertTrue(np.isclose(sagitta, 0.5012562893380021), "Sagitta calculation is incorrect")

def test_max_center_chord(self):
"""Test the max_center_chord method."""
max_chord = self.port.max_center_chord()
self.assertTrue(np.isclose(max_chord, 10.012555011963775), "Max center chord calculation is incorrect")

def test_relative_cap_area(self):
"""Test the relative_cap_area method."""
rel_area = self.port.relative_cap_area()
self.assertTrue(np.isclose(rel_area, 0.002506281446690011), "Relative cap area calculation is incorrect")

def test_hit(self):
"""Test the hit method."""
self.assertTrue(self.port.hit(), "Hit detection is incorrect")

def test_uniform_01(self):
"""Test the generating points on the top of the sphere."""
for i in range(20):
x, y, z = self.port.uniform()
self.assertTrue(self.port.hit(), "Hit detection is incorrect")

if __name__ == '__main__':
unittest.main()
132 changes: 71 additions & 61 deletions tests/test_sphere.py
Original file line number Diff line number Diff line change
@@ -15,50 +15,45 @@ class SimpleSphere(unittest.TestCase):
def test_01_object_creation(self):
"""Simple sphere creation."""
s = iadpython.sphere.Sphere(200, 20)
np.testing.assert_allclose(s.d_sphere, 200, atol=1e-5)
np.testing.assert_allclose(s.d_sample, 20, atol=1e-5)
np.testing.assert_allclose(s.d_entrance, 0, atol=1e-5)
np.testing.assert_allclose(s.d_detector, 0, atol=1e-5)
np.testing.assert_allclose(s.d, 200, atol=1e-5)
np.testing.assert_allclose(s.sample.d, 20, atol=1e-5)
np.testing.assert_allclose(s.empty.d, 0, atol=1e-5)
np.testing.assert_allclose(s.detector.d, 0, atol=1e-5)

def test_02_portsize(self):
"""Test setting values."""
s = iadpython.sphere.Sphere(200, 20)
s.d_entrance = 10
s.d_detector = 5
s.d_sample = 18
np.testing.assert_allclose(s._d_sphere, 200, atol=1e-5)
np.testing.assert_allclose(s._d_sample, 18, atol=1e-5)
np.testing.assert_allclose(s._d_entrance, 10, atol=1e-5)
np.testing.assert_allclose(s._d_detector, 5, atol=1e-5)
np.testing.assert_allclose(s.d_sphere, 200, atol=1e-5)
np.testing.assert_allclose(s.d_sample, 18, atol=1e-5)
np.testing.assert_allclose(s.d_entrance, 10, atol=1e-5)
np.testing.assert_allclose(s.d_detector, 5, atol=1e-5)
s.empty.d = 10
s.detector.d = 5
s.sample.d = 18
np.testing.assert_allclose(s.d, 200, atol=1e-5)
np.testing.assert_allclose(s.sample.d, 18, atol=1e-5)
np.testing.assert_allclose(s.empty.d, 10, atol=1e-5)
np.testing.assert_allclose(s.detector.d, 5, atol=1e-5)

def test_03_cap(self):
"""Spherical cap calculations."""
R = 100
s = iadpython.sphere.Sphere(2 * R, 5)
r = 10
acap = s.cap_area(2 * r) / (4 * np.pi * R**2)
a_cap = s.relative_cap_area(2 * r)
np.testing.assert_allclose(acap, a_cap, atol=1e-5)
s = iadpython.sphere.Sphere(2 * R, 2 * r)
acap = s.sample.cap_area() / (4 * np.pi * R**2)
np.testing.assert_allclose(acap, s.sample.a, atol=1e-5)

acap1 = np.pi * r**2 / (4 * np.pi * R**2)
a_cap1 = s.approx_relative_cap_area(20)
a_cap1 = s.sample.approx_relative_cap_area()
np.testing.assert_allclose(acap1, a_cap1, atol=1e-5)


class SphereGain(unittest.TestCase):
"""Basic tests of gain relative to black sphere."""

def test_01_gain(self):
"""Gain calculations, r_wall=0."""
s = iadpython.sphere.Sphere(200, 25)
s.r_wall = 0
g = s.gain(0)
np.testing.assert_allclose(g, 1, atol=1e-5)

# class SphereGain(unittest.TestCase):
# """Basic tests of gain relative to black sphere."""
#
# def test_01_gain(self):
# """Gain calculations, r_wall=0."""
# s = iadpython.sphere.Sphere(200, 25)
# s.r_wall = 0
# g = s.gain(0)
# np.testing.assert_allclose(g, 1, atol=1e-5)
#
# def test_02_gain(self):
# """Gain calculations, r_wall=1."""
# s = iadpython.sphere.Sphere(200, 25)
@@ -67,57 +62,72 @@ def test_01_gain(self):
# Asample = np.pi * (25/2)**2
# gg = Asphere/Asample
# np.testing.assert_allclose(g, gg, atol=1e-4)

def test_03_gain(self):
"""Gain calculations, r_wall=0."""
s = iadpython.sphere.Sphere(200, 25, d_entrance=5, d_detector=10)
s.r_wall = 0
g = s.gain(0)
np.testing.assert_allclose(g, 1, atol=1e-5)

#
# def test_03_gain(self):
# """Gain calculations, r_wall=0."""
# s = iadpython.sphere.Sphere(200, 25, d_empty=5, d_detector=10)
# s.r_wall = 0
# g = s.gain(0)
# np.testing.assert_allclose(g, 1, atol=1e-5)
#
# def test_04_gain(self):
# """Gain calculations, r_wall=1."""
# s = iadpython.sphere.Sphere(200, 25, d_entrance=5, d_detector=10)
# s = iadpython.sphere.Sphere(200, 25, d_empty=5, d_detector=10)
# s.r_wall = 1
# g = s.gain(0)
# gg = 1/(s.a_detector+s.a_entrance+s.a_sample)
# gg = 1/(s.a_detector+s.a_empty+s.a_sample)
# np.testing.assert_allclose(g, gg, atol=1e-5)


class SphereMultiplier(unittest.TestCase):
"""Relative increase in radiative flux on sphere walls."""

def test_01_multiplier(self):
"""Scalar calculations."""
def setUp(self):
"""Set up test conditions for SphereMultiplier tests."""
R = 100
s = iadpython.sphere.Sphere(2 * R, 5)
s.a_wall = 0.98
s.r_wall = 0.8 / s.a_wall
M = s.multiplier(UR1=1, URU=0)
np.testing.assert_allclose(5, M, atol=1e-5)
d_sphere = 2 * R
d_sample = 20
self.sphere = iadpython.Sphere(d_sphere, d_sample, r_wall=0)
self.sphere.sample.set_center(0,0,-R)
self.sphere.empty.set_center(0,0,R)
self.sphere.detector.set_center(R,0,0)

M = s.multiplier(UR1=0.8, URU=0)
np.testing.assert_allclose(0.8 * 5, M, atol=1e-5)
def test_01_multiplier(self):
"""Scalar calculations no reflection from sample, black walls."""
M = self.sphere.multiplier(UX1=1, URU=0)
np.testing.assert_allclose(self.sphere.a_wall, M, atol=1e-5)

M = s.multiplier(UR1=1, URU=1)
M1 = 1 / (1 - s.a_wall * s.r_wall - s.a_sample)
def test_02_multiplier(self):
"""Scalar calculation 80% transmission."""
M = self.sphere.multiplier(UX1=0.8, URU=0)
np.testing.assert_allclose(0.8 * self.sphere.a_wall, M, atol=1e-5)

def test_03_multiplier(self):
"""Scalar calculations total transmission with total sample reflection"""
M = self.sphere.multiplier(UX1=1, URU=1)
M1 = self.sphere.a_wall / (1 - self.sphere.a_wall * self.sphere.r_wall - self.sphere.sample.a)
np.testing.assert_allclose(M, M1, atol=1e-5)

def test_02_multiplier(self):
def test_04_multiplier(self):
"""Array of r_wall values."""
R = 100
s = iadpython.sphere.Sphere(2 * R, 25, d_entrance=5, r_detector=0.1)
s.r_wall = np.linspace(0, 1, 4)
M = s.multiplier(UR1=1, URU=0)
mm = [1.0, 1.496948, 2.975731, 245.224041]
self.sphere.r_wall = np.linspace(0, 1, 4)
M = self.sphere.multiplier(UX1=1, URU=0)
mm = self.sphere.a_wall / (1 - self.sphere.a_wall * self.sphere.r_wall)
np.testing.assert_allclose(mm, M, atol=1e-5)

M = s.multiplier(UR1=0.8, URU=0)
mm = [0.8, 1.197558, 2.380584, 196.179233]
def test_05_multiplier(self):
"""Array of r_wall values."""
self.sphere.r_wall = np.linspace(0, 1, 4)
M = self.sphere.multiplier(UX1=0.8, URU=0)
mm = 0.8 * self.sphere.a_wall / (1 - self.sphere.a_wall * self.sphere.r_wall)
np.testing.assert_allclose(mm, M, atol=1e-5)

M = s.multiplier(UR1=1, URU=1)
M1 = 1 / (1 - s.a_wall * s.r_wall - s.a_sample)
def test_06_multiplier(self):
"""Array of r_wall values."""
self.sphere.r_wall = np.linspace(0, 1, 4)
M = self.sphere.multiplier(UX1=1, URU=1)
M1 = self.sphere.a_wall / (1 - self.sphere.a_wall * self.sphere.r_wall - self.sphere.sample.a)
M1[3] = np.inf
np.testing.assert_allclose(M, M1, atol=1e-5)


0 comments on commit 2d1a04f

Please sign in to comment.