radphen-iadpython 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iadpython/__init__.py +59 -0
- iadpython/ad.py +1377 -0
- iadpython/agrid.py +306 -0
- iadpython/cache.py +133 -0
- iadpython/combine.py +620 -0
- iadpython/constants.py +3 -0
- iadpython/data/M38597.csv +759 -0
- iadpython/double.py +376 -0
- iadpython/fresnel.py +642 -0
- iadpython/grid.py +125 -0
- iadpython/iad.py +615 -0
- iadpython/iadc.py +1003 -0
- iadpython/iadcommand.py +712 -0
- iadpython/layer.py +241 -0
- iadpython/nist.py +114 -0
- iadpython/port.py +242 -0
- iadpython/quadrature.py +172 -0
- iadpython/redistribution.py +318 -0
- iadpython/rxt.py +201 -0
- iadpython/sphere.py +582 -0
- iadpython/start.py +377 -0
- iadpython/txt.py +147 -0
- radphen_iadpython-0.7.0.dist-info/METADATA +264 -0
- radphen_iadpython-0.7.0.dist-info/RECORD +28 -0
- radphen_iadpython-0.7.0.dist-info/WHEEL +5 -0
- radphen_iadpython-0.7.0.dist-info/entry_points.txt +2 -0
- radphen_iadpython-0.7.0.dist-info/licenses/LICENSE.txt +18 -0
- radphen_iadpython-0.7.0.dist-info/top_level.txt +1 -0
iadpython/double.py
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Class for managing double integrating spheres.
|
|
3
|
+
|
|
4
|
+
This module contains the DoubleSphere class, designed to simulate and analyze the
|
|
5
|
+
behavior of light with a sample sandwiched between a reflection and transmission
|
|
6
|
+
sphere.
|
|
7
|
+
|
|
8
|
+
Attributes:
|
|
9
|
+
r_sphere: Reflectance sphere
|
|
10
|
+
t_sphere: Transmittance sphere
|
|
11
|
+
ur1: total reflection of sample for normal incidence
|
|
12
|
+
ut1: total transmission of sample for normal incidence
|
|
13
|
+
uru: total reflection of sample for diffuse incidence
|
|
14
|
+
utu: total transmission of sample for diffuse incidence
|
|
15
|
+
|
|
16
|
+
Example usage:
|
|
17
|
+
>>> import iadpython
|
|
18
|
+
>>> rs = iadpython.Sphere(250,20)
|
|
19
|
+
>>> ts = iadpython.Sphere(250,20)
|
|
20
|
+
>>> d = iadpython.DoubleSphere(rs, ts)
|
|
21
|
+
>>> print(d)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import random
|
|
25
|
+
import time
|
|
26
|
+
import numpy as np
|
|
27
|
+
import iadpython as iad
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DoubleSphere:
|
|
31
|
+
"""Container class for two three-port integrating sphere.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
- r_sphere: Reflectance sphere
|
|
35
|
+
- t_sphere: Transmittance sphere
|
|
36
|
+
- current: Current sphere photon is in
|
|
37
|
+
- ur1: total reflection of sample for normal incidence
|
|
38
|
+
- ut1: total transmission of sample for normal incidence
|
|
39
|
+
- uru: total reflection of sample for diffuse incidence
|
|
40
|
+
- utu: total transmission of sample for diffuse incidence
|
|
41
|
+
|
|
42
|
+
Example::
|
|
43
|
+
|
|
44
|
+
>>> import iadpython as iad
|
|
45
|
+
>>> s = iad.Sphere(200, 20)
|
|
46
|
+
>>> print(s)
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
REFLECTION_SPHERE = 1
|
|
50
|
+
TRANSMISSION_SPHERE = 2
|
|
51
|
+
|
|
52
|
+
def __init__(self, r_sphere, t_sphere):
|
|
53
|
+
"""Initialization."""
|
|
54
|
+
self.r_sphere = r_sphere
|
|
55
|
+
self.r_sphere.refl = True
|
|
56
|
+
self.t_sphere = t_sphere
|
|
57
|
+
self.t_sphere.refl = False
|
|
58
|
+
self.current = self.r_sphere
|
|
59
|
+
self.ur1 = 0
|
|
60
|
+
self.ut1 = 1
|
|
61
|
+
self._uru = self.ur1
|
|
62
|
+
self._utu = self.ut1
|
|
63
|
+
self.f_r = 0.0
|
|
64
|
+
|
|
65
|
+
def __repr__(self):
|
|
66
|
+
"""Return basic details as a string for printing."""
|
|
67
|
+
s = "Double sphere experiment\n"
|
|
68
|
+
s += " ur1 = %s\n" % iad.stringify("%5.1f%%", self.ur1 * 100)
|
|
69
|
+
s += " uru = %s\n" % iad.stringify("%5.1f%%", self.uru * 100)
|
|
70
|
+
s += " ut1 = %s\n" % iad.stringify("%5.1f%%", self.ut1 * 100)
|
|
71
|
+
s += " utu = %s\n" % iad.stringify("%5.1f%%", self.utu * 100)
|
|
72
|
+
s += "R " + self.r_sphere.__repr__()
|
|
73
|
+
s += "T " + self.t_sphere.__repr__()
|
|
74
|
+
return s
|
|
75
|
+
|
|
76
|
+
def __str__(self):
|
|
77
|
+
"""Return full details as a string for printing."""
|
|
78
|
+
s = ""
|
|
79
|
+
s += "Reflection Sphere\n"
|
|
80
|
+
s += str(self.r_sphere) + "\n"
|
|
81
|
+
s += "Transmission Sphere\n"
|
|
82
|
+
s += str(self.t_sphere) + "\n"
|
|
83
|
+
s += "Sample Properties\n"
|
|
84
|
+
s += " ur1 = %7.3f\n" % self.ur1
|
|
85
|
+
s += " ut1 = %7.3f\n" % self.ut1
|
|
86
|
+
s += " uru = %7.3f\n" % self.uru
|
|
87
|
+
s += " utu = %7.3f\n" % self.utu
|
|
88
|
+
s += "Gain Properties\n"
|
|
89
|
+
gr, gt = self.gain()
|
|
90
|
+
s += " r gain = %7.3f\n" % gr
|
|
91
|
+
s += " t gain = %7.3f\n" % gt
|
|
92
|
+
return s
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def uru(self):
|
|
96
|
+
"""Getter property for sample uru."""
|
|
97
|
+
return self._uru
|
|
98
|
+
|
|
99
|
+
@uru.setter
|
|
100
|
+
def uru(self, value):
|
|
101
|
+
"""Need to update sample uru."""
|
|
102
|
+
self._uru = value
|
|
103
|
+
self.r_sphere.sample.uru = value
|
|
104
|
+
self.t_sphere.sample.uru = value
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def utu(self):
|
|
108
|
+
"""Need to update sample utu."""
|
|
109
|
+
return self._utu
|
|
110
|
+
|
|
111
|
+
@utu.setter
|
|
112
|
+
def utu(self, value):
|
|
113
|
+
"""When size is changed ratios become invalid."""
|
|
114
|
+
self._utu = value
|
|
115
|
+
|
|
116
|
+
def do_one_photon(self, return_passes=False):
|
|
117
|
+
"""Bounce photon in double spheres until it is detected or lost.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
return_passes: When True, include sample pass count in return value.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
(r_detected, t_detected) or (r_detected, t_detected, passes)
|
|
124
|
+
"""
|
|
125
|
+
weight = 1
|
|
126
|
+
passes = 0
|
|
127
|
+
r_detected = 0
|
|
128
|
+
t_detected = 0
|
|
129
|
+
|
|
130
|
+
# photon normally incident on sample
|
|
131
|
+
x = random.random()
|
|
132
|
+
if x < self.ur1: # reflected by sample
|
|
133
|
+
self.current = self.r_sphere
|
|
134
|
+
|
|
135
|
+
elif x < self.ur1 + self.ut1: # transmitted through sample
|
|
136
|
+
self.current = self.t_sphere
|
|
137
|
+
passes = 1
|
|
138
|
+
|
|
139
|
+
else: # absorbed by sample
|
|
140
|
+
weight = 0
|
|
141
|
+
|
|
142
|
+
while weight > 0:
|
|
143
|
+
detected, transmitted, _ = self.current.do_one_photon(weight=weight, double=True)
|
|
144
|
+
|
|
145
|
+
if transmitted > 0: # hit sample
|
|
146
|
+
if random.random() < self.utu: # passed through sample, switch spheres
|
|
147
|
+
passes += 1
|
|
148
|
+
if self.current == self.r_sphere:
|
|
149
|
+
self.current = self.t_sphere
|
|
150
|
+
else:
|
|
151
|
+
self.current = self.r_sphere
|
|
152
|
+
weight = transmitted
|
|
153
|
+
else: # absorbed by sample
|
|
154
|
+
weight = 0
|
|
155
|
+
else:
|
|
156
|
+
weight = 0
|
|
157
|
+
if self.current == self.r_sphere:
|
|
158
|
+
assert passes % 2 == 0, "reflection sphere should have even number of passes"
|
|
159
|
+
r_detected += detected
|
|
160
|
+
else:
|
|
161
|
+
assert passes % 2 == 1, "reflection sphere should have odd number of passes"
|
|
162
|
+
t_detected += detected
|
|
163
|
+
|
|
164
|
+
if return_passes:
|
|
165
|
+
return r_detected, t_detected, passes
|
|
166
|
+
return r_detected, t_detected
|
|
167
|
+
|
|
168
|
+
def do_N_photons(self, N):
|
|
169
|
+
"""Do a Monte Carlo simulation with N photons."""
|
|
170
|
+
# Use current time as seed
|
|
171
|
+
random.seed(time.time())
|
|
172
|
+
|
|
173
|
+
num_trials = 10
|
|
174
|
+
total_r_detected = np.zeros(num_trials)
|
|
175
|
+
total_t_detected = np.zeros(num_trials)
|
|
176
|
+
# total_bounces = np.zeros(num_trials)
|
|
177
|
+
|
|
178
|
+
N_per_trial = N // num_trials
|
|
179
|
+
|
|
180
|
+
total = 0
|
|
181
|
+
for j in range(num_trials):
|
|
182
|
+
for _i in range(N_per_trial):
|
|
183
|
+
r_detected, t_detected = self.do_one_photon()
|
|
184
|
+
# print("%d %8.3f %8.3f" % (i, r_detected, t_detected))
|
|
185
|
+
total_r_detected[j] += r_detected
|
|
186
|
+
total_t_detected[j] += t_detected
|
|
187
|
+
total += 1
|
|
188
|
+
|
|
189
|
+
ave_r = np.mean(total_r_detected) / N_per_trial
|
|
190
|
+
std_r = np.std(total_r_detected) / N_per_trial
|
|
191
|
+
stderr_r = std_r / np.sqrt(num_trials)
|
|
192
|
+
|
|
193
|
+
ave_t = np.mean(total_t_detected) / N_per_trial
|
|
194
|
+
std_t = np.std(total_t_detected) / N_per_trial
|
|
195
|
+
stderr_t = std_t / np.sqrt(num_trials)
|
|
196
|
+
|
|
197
|
+
gr, gt = self.gain()
|
|
198
|
+
print("r_average detected = %.3f ± %.3f" % (ave_r, stderr_r))
|
|
199
|
+
scale = self.r_sphere.detector.a * (1 - self.r_sphere.detector.uru)
|
|
200
|
+
if self.r_sphere.baffle:
|
|
201
|
+
scale *= (
|
|
202
|
+
1 - self.r_sphere.third.a
|
|
203
|
+
) * self.r_sphere.r_wall + self.r_sphere.third.a * self.r_sphere.third.uru
|
|
204
|
+
ave_g = ave_r / scale
|
|
205
|
+
std_g = std_r / scale
|
|
206
|
+
stderr_g = std_g / np.sqrt(num_trials)
|
|
207
|
+
print("average gain = %.3f ± %.3f" % (ave_g, stderr_g))
|
|
208
|
+
print("calculated gain = %.3f" % gr)
|
|
209
|
+
print()
|
|
210
|
+
|
|
211
|
+
print("t_average detected = %.3f ± %.3f" % (ave_t, stderr_t))
|
|
212
|
+
scale = self.t_sphere.detector.a * (1 - self.t_sphere.detector.uru)
|
|
213
|
+
if self.t_sphere.baffle:
|
|
214
|
+
scale *= (
|
|
215
|
+
1 - self.t_sphere.third.a
|
|
216
|
+
) * self.t_sphere.r_wall + self.t_sphere.third.a * self.t_sphere.third.uru
|
|
217
|
+
ave_g = ave_t / scale
|
|
218
|
+
std_g = std_t / scale
|
|
219
|
+
stderr_g = std_g / np.sqrt(num_trials)
|
|
220
|
+
print("average gain = %.3f ± %.3f" % (ave_g, stderr_g))
|
|
221
|
+
print("calculated gain = %.3f" % gt)
|
|
222
|
+
|
|
223
|
+
return ave_r, stderr_r, ave_t, stderr_t
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def _safe_inverse(x):
|
|
227
|
+
"""Return 1/x with infinities for zero denominators."""
|
|
228
|
+
x = np.asarray(x, dtype=float)
|
|
229
|
+
result = np.where(x == 0, np.inf, 1.0 / x)
|
|
230
|
+
if np.ndim(result) == 0:
|
|
231
|
+
return float(result)
|
|
232
|
+
return result
|
|
233
|
+
|
|
234
|
+
def _sphere_terms(self, sphere):
|
|
235
|
+
"""Return CWEB gain terms for reflection or transmission sphere."""
|
|
236
|
+
if sphere == self.REFLECTION_SPHERE:
|
|
237
|
+
s = self.r_sphere
|
|
238
|
+
elif sphere == self.TRANSMISSION_SPHERE:
|
|
239
|
+
s = self.t_sphere
|
|
240
|
+
else:
|
|
241
|
+
raise ValueError(f"Unknown sphere selector: {sphere}")
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
s.sample.a,
|
|
245
|
+
s.detector.a,
|
|
246
|
+
s.third.a,
|
|
247
|
+
s.a_wall,
|
|
248
|
+
s.detector.uru,
|
|
249
|
+
s.r_wall,
|
|
250
|
+
s.baffle,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
def gain_single(self, sphere, uru_sample, uru_third):
|
|
254
|
+
"""
|
|
255
|
+
Single-sphere gain from CWEB `Gain()`.
|
|
256
|
+
|
|
257
|
+
This is the exact algebra used in `iad/src/iad_calc.c`.
|
|
258
|
+
"""
|
|
259
|
+
as_x, ad_x, at_x, aw_x, rd_x, rw_x, baffle_x = self._sphere_terms(sphere)
|
|
260
|
+
|
|
261
|
+
if baffle_x:
|
|
262
|
+
inv_gain = rw_x + (at_x / aw_x) * uru_third
|
|
263
|
+
inv_gain *= aw_x + (1 - at_x) * (ad_x * rd_x + as_x * uru_sample)
|
|
264
|
+
inv_gain = 1.0 - inv_gain
|
|
265
|
+
else:
|
|
266
|
+
inv_gain = 1.0 - aw_x * rw_x - ad_x * rd_x - as_x * uru_sample - at_x * uru_third
|
|
267
|
+
|
|
268
|
+
return self._safe_inverse(inv_gain)
|
|
269
|
+
|
|
270
|
+
def gain_11(self, uru, tdiffuse):
|
|
271
|
+
"""Gain for light starting/ending in the reflection sphere (`Gain_11`)."""
|
|
272
|
+
g = self.gain_single(self.REFLECTION_SPHERE, uru, 0.0)
|
|
273
|
+
gp = self.gain_single(self.TRANSMISSION_SPHERE, uru, 0.0)
|
|
274
|
+
|
|
275
|
+
coupling = (
|
|
276
|
+
self.r_sphere.sample.a
|
|
277
|
+
* self.t_sphere.sample.a
|
|
278
|
+
* self.r_sphere.a_wall
|
|
279
|
+
* self.t_sphere.a_wall
|
|
280
|
+
* (1 - self.r_sphere.third.a)
|
|
281
|
+
* (1 - self.t_sphere.third.a)
|
|
282
|
+
* g
|
|
283
|
+
* gp
|
|
284
|
+
* tdiffuse
|
|
285
|
+
* tdiffuse
|
|
286
|
+
)
|
|
287
|
+
return g * self._safe_inverse(1.0 - coupling)
|
|
288
|
+
|
|
289
|
+
def gain_22(self, uru, tdiffuse):
|
|
290
|
+
"""Gain for light starting/ending in the transmission sphere (`Gain_22`)."""
|
|
291
|
+
g = self.gain_single(self.REFLECTION_SPHERE, uru, 0.0)
|
|
292
|
+
gp = self.gain_single(self.TRANSMISSION_SPHERE, uru, 0.0)
|
|
293
|
+
|
|
294
|
+
coupling = (
|
|
295
|
+
self.r_sphere.sample.a
|
|
296
|
+
* self.t_sphere.sample.a
|
|
297
|
+
* self.r_sphere.a_wall
|
|
298
|
+
* self.t_sphere.a_wall
|
|
299
|
+
* (1 - self.r_sphere.third.a)
|
|
300
|
+
* (1 - self.t_sphere.third.a)
|
|
301
|
+
* g
|
|
302
|
+
* gp
|
|
303
|
+
* tdiffuse
|
|
304
|
+
* tdiffuse
|
|
305
|
+
)
|
|
306
|
+
return gp * self._safe_inverse(1.0 - coupling)
|
|
307
|
+
|
|
308
|
+
def gain(self, uru=None, utu=None):
|
|
309
|
+
"""
|
|
310
|
+
Return `(Gain_11, Gain_22)` for the current sample diffuse properties.
|
|
311
|
+
|
|
312
|
+
This preserves the historical `DoubleSphere.gain()` API while using
|
|
313
|
+
the exact CWEB algebra.
|
|
314
|
+
"""
|
|
315
|
+
if uru is None:
|
|
316
|
+
uru = self.uru
|
|
317
|
+
if utu is None:
|
|
318
|
+
utu = self.utu
|
|
319
|
+
return self.gain_11(uru, utu), self.gain_22(uru, utu)
|
|
320
|
+
|
|
321
|
+
def two_sphere_r(self, ur1, uru, ut1, utu):
|
|
322
|
+
"""Exact CWEB `Two_Sphere_R()` power expression."""
|
|
323
|
+
gp = self.gain_single(self.TRANSMISSION_SPHERE, uru, 0.0)
|
|
324
|
+
x = self.r_sphere.detector.a * (1 - self.r_sphere.third.a) * self.r_sphere.r_wall
|
|
325
|
+
x *= self.gain_11(uru, utu)
|
|
326
|
+
x *= (
|
|
327
|
+
(1 - self.f_r) * ur1
|
|
328
|
+
+ self.r_sphere.r_wall * self.f_r
|
|
329
|
+
+ (1 - self.f_r)
|
|
330
|
+
* self.t_sphere.sample.a
|
|
331
|
+
* (1 - self.t_sphere.third.a)
|
|
332
|
+
* self.t_sphere.r_wall
|
|
333
|
+
* ut1
|
|
334
|
+
* utu
|
|
335
|
+
* gp
|
|
336
|
+
)
|
|
337
|
+
return x
|
|
338
|
+
|
|
339
|
+
def two_sphere_t(self, ur1, uru, ut1, utu):
|
|
340
|
+
"""Exact CWEB `Two_Sphere_T()` power expression."""
|
|
341
|
+
g = self.gain_single(self.REFLECTION_SPHERE, uru, 0.0)
|
|
342
|
+
x = self.t_sphere.detector.a * (1 - self.t_sphere.third.a) * self.t_sphere.r_wall
|
|
343
|
+
x *= self.gain_22(uru, utu)
|
|
344
|
+
x *= (1 - self.f_r) * ut1 + (
|
|
345
|
+
1 - self.r_sphere.third.a
|
|
346
|
+
) * self.r_sphere.r_wall * self.r_sphere.sample.a * utu * (
|
|
347
|
+
self.f_r * self.r_sphere.r_wall + (1 - self.f_r) * ur1
|
|
348
|
+
) * g
|
|
349
|
+
return x
|
|
350
|
+
|
|
351
|
+
def measured_rt(self, ur1, uru, ut1, utu):
|
|
352
|
+
"""
|
|
353
|
+
Exact CWEB two-sphere normalization for measured `M_R` and `M_T`.
|
|
354
|
+
|
|
355
|
+
Inputs must already include any unscattered/lost-light corrections.
|
|
356
|
+
"""
|
|
357
|
+
r_0 = self.two_sphere_r(0.0, 0.0, 0.0, 0.0)
|
|
358
|
+
t_0 = self.two_sphere_t(0.0, 0.0, 0.0, 0.0)
|
|
359
|
+
|
|
360
|
+
m_r = self.r_sphere.r_std * (self.two_sphere_r(ur1, uru, ut1, utu) - r_0)
|
|
361
|
+
m_r *= self._safe_inverse(self.two_sphere_r(self.r_sphere.r_std, self.r_sphere.r_std, 0.0, 0.0) - r_0)
|
|
362
|
+
|
|
363
|
+
m_t = self.two_sphere_t(ur1, uru, ut1, utu) - t_0
|
|
364
|
+
m_t *= self._safe_inverse(self.two_sphere_t(0.0, 0.0, 1.0, 1.0) - t_0)
|
|
365
|
+
|
|
366
|
+
return m_r, m_t
|
|
367
|
+
|
|
368
|
+
def MR(self, ur1, uru, ut1, utu):
|
|
369
|
+
"""Convenience wrapper for the two-sphere measured reflection."""
|
|
370
|
+
m_r, _ = self.measured_rt(ur1, uru, ut1, utu)
|
|
371
|
+
return m_r
|
|
372
|
+
|
|
373
|
+
def MT(self, ur1, uru, ut1, utu):
|
|
374
|
+
"""Convenience wrapper for the two-sphere measured transmission."""
|
|
375
|
+
_, m_t = self.measured_rt(ur1, uru, ut1, utu)
|
|
376
|
+
return m_t
|