PyMieSim 3.6.0__cp313-cp313-win_amd64.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.
- PyMieSim/__init__.py +16 -0
- PyMieSim/__main__.py +9 -0
- PyMieSim/_version.py +21 -0
- PyMieSim/binary/__init__.py +0 -0
- PyMieSim/binary/interface_detector.cp310-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_detector.cp311-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_detector.cp312-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_detector.cp313-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_experiment.cp310-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_experiment.cp311-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_experiment.cp312-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_experiment.cp313-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_scatterer.cp310-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_scatterer.cp311-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_scatterer.cp312-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_scatterer.cp313-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_sets.cp310-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_sets.cp311-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_sets.cp312-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_sets.cp313-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_source.cp310-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_source.cp311-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_source.cp312-win_amd64.pyd +0 -0
- PyMieSim/binary/interface_source.cp313-win_amd64.pyd +0 -0
- PyMieSim/binary/libcpp_coordinates.a +0 -0
- PyMieSim/binary/libcpp_detector.a +0 -0
- PyMieSim/binary/libcpp_experiment.a +0 -0
- PyMieSim/binary/libcpp_fibonacci.a +0 -0
- PyMieSim/binary/libcpp_mode_field.a +0 -0
- PyMieSim/binary/libcpp_sets.a +0 -0
- PyMieSim/binary/libcpp_source.a +0 -0
- PyMieSim/directories.py +31 -0
- PyMieSim/experiment/__init__.py +1 -0
- PyMieSim/experiment/dataframe_subclass.py +220 -0
- PyMieSim/experiment/detector/__init__.py +2 -0
- PyMieSim/experiment/detector/base.py +169 -0
- PyMieSim/experiment/detector/coherent_mode.py +50 -0
- PyMieSim/experiment/detector/photodiode.py +52 -0
- PyMieSim/experiment/scatterer/__init__.py +4 -0
- PyMieSim/experiment/scatterer/base.py +98 -0
- PyMieSim/experiment/scatterer/core_shell.py +82 -0
- PyMieSim/experiment/scatterer/cylinder.py +63 -0
- PyMieSim/experiment/scatterer/sphere.py +66 -0
- PyMieSim/experiment/setup.py +356 -0
- PyMieSim/experiment/source/__init__.py +2 -0
- PyMieSim/experiment/source/base.py +85 -0
- PyMieSim/experiment/source/gaussian.py +60 -0
- PyMieSim/experiment/source/planewave.py +69 -0
- PyMieSim/experiment/utils.py +132 -0
- PyMieSim/gui/__init__.py +0 -0
- PyMieSim/gui/helper.py +60 -0
- PyMieSim/gui/interface.py +136 -0
- PyMieSim/gui/section.py +606 -0
- PyMieSim/mesh.py +368 -0
- PyMieSim/polarization.py +174 -0
- PyMieSim/single/__init__.py +48 -0
- PyMieSim/single/detector/__init__.py +2 -0
- PyMieSim/single/detector/base.py +271 -0
- PyMieSim/single/detector/coherent.py +99 -0
- PyMieSim/single/detector/uncoherent.py +105 -0
- PyMieSim/single/representations.py +734 -0
- PyMieSim/single/scatterer/__init__.py +4 -0
- PyMieSim/single/scatterer/base.py +405 -0
- PyMieSim/single/scatterer/core_shell.py +126 -0
- PyMieSim/single/scatterer/cylinder.py +113 -0
- PyMieSim/single/scatterer/sphere.py +108 -0
- PyMieSim/single/source/__init__.py +3 -0
- PyMieSim/single/source/base.py +7 -0
- PyMieSim/single/source/gaussian.py +137 -0
- PyMieSim/single/source/planewave.py +97 -0
- PyMieSim/special_functions.py +81 -0
- PyMieSim/units.py +130 -0
- PyMieSim/validation_data/bohren_huffman/figure_810.csv +245 -0
- PyMieSim/validation_data/bohren_huffman/figure_87.csv +2 -0
- PyMieSim/validation_data/bohren_huffman/figure_88.csv +2 -0
- PyMieSim/validation_data/pymiescatt/example_coreshell_0.csv +41 -0
- PyMieSim/validation_data/pymiescatt/example_coreshell_1.csv +401 -0
- PyMieSim/validation_data/pymiescatt/example_shpere_0.csv +51 -0
- PyMieSim/validation_data/pymiescatt/example_shpere_1.csv +801 -0
- PyMieSim/validation_data/pymiescatt/example_shpere_2.csv +41 -0
- PyMieSim/validation_data/pymiescatt/example_shpere_3.csv +401 -0
- PyMieSim/validation_data/pymiescatt/example_sphere_0.csv +51 -0
- PyMieSim/validation_data/pymiescatt/example_sphere_1.csv +801 -0
- PyMieSim/validation_data/pymiescatt/example_sphere_2.csv +41 -0
- PyMieSim/validation_data/pymiescatt/example_sphere_3.csv +401 -0
- PyMieSim/validation_data/pymiescatt/validation_Qsca.csv +800 -0
- PyMieSim/validation_data/pymiescatt/validation_Qsca_coreshell_1.csv +400 -0
- PyMieSim/validation_data/pymiescatt/validation_Qsca_coreshell_2.csv +400 -0
- PyMieSim/validation_data/pymiescatt/validation_Qsca_medium.csv +800 -0
- PyMieSim/validation_data/pymiescatt/validation_coreshell.csv +81 -0
- PyMieSim/validation_data/pymiescatt/validation_sphere.csv +801 -0
- lib/libZBessel.a +0 -0
- lib/lib_ZBessel.a +0 -0
- lib/libcpp_base_scatterer.a +0 -0
- lib/libcpp_coreshell.a +0 -0
- lib/libcpp_cylinder.a +0 -0
- lib/libcpp_sphere.a +0 -0
- pymiesim-3.6.0.dist-info/METADATA +246 -0
- pymiesim-3.6.0.dist-info/RECORD +101 -0
- pymiesim-3.6.0.dist-info/WHEEL +5 -0
- pymiesim-3.6.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,271 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
import numpy
|
5
|
+
from typing import Optional
|
6
|
+
|
7
|
+
from PyMieSim.single.representations import Footprint
|
8
|
+
from MPSPlots.colormaps import blue_black_red
|
9
|
+
from PyMieSim.units import Quantity, watt, meter, second, farad, volt
|
10
|
+
from PyMieSim import units
|
11
|
+
from PyMieSim.single.scatterer.base import BaseScatterer
|
12
|
+
import pyvista
|
13
|
+
|
14
|
+
c = 299792458.0 * meter / second #: Speed of light in vacuum (m/s).
|
15
|
+
epsilon0 = 8.854187817620389e-12 * farad / meter #: Vacuum permittivity (F/m).
|
16
|
+
|
17
|
+
|
18
|
+
class BaseDetector(units.UnitsValidation):
|
19
|
+
medium_refractive_index: Optional[Quantity] = 1.0 * units.RIU
|
20
|
+
|
21
|
+
@property
|
22
|
+
def max_angle(self) -> Quantity:
|
23
|
+
"""
|
24
|
+
Returns the maximum angle of the detector in radians.
|
25
|
+
This is used to determine the angular coverage of the detector.
|
26
|
+
"""
|
27
|
+
return self._cpp_max_angle * units.radian
|
28
|
+
|
29
|
+
@property
|
30
|
+
def min_angle(self) -> Quantity:
|
31
|
+
"""
|
32
|
+
Returns the minimum angle of the detector in radians.
|
33
|
+
This is used to determine the angular coverage of the detector.
|
34
|
+
"""
|
35
|
+
return self._cpp_min_angle * units.radian
|
36
|
+
|
37
|
+
def _validate_angle_units(cls, value):
|
38
|
+
"""
|
39
|
+
Ensures that gamma_offset, phi_offset, and rotation are Quantity objects with angle units.
|
40
|
+
Converts them to numpy arrays after validation.
|
41
|
+
"""
|
42
|
+
if not isinstance(value, Quantity) or not value.check(units.degree):
|
43
|
+
raise ValueError(f"{value} must be a Quantity with angle units [degree or radian].")
|
44
|
+
|
45
|
+
return value
|
46
|
+
|
47
|
+
def get_coupling(self, scatterer: BaseScatterer) -> Quantity:
|
48
|
+
r"""
|
49
|
+
Compute the light coupling between the detector and a scatterer.
|
50
|
+
|
51
|
+
The coupling quantifies the interaction between the field captured by the detector and the scattered field produced by the scatterer. Mathematically, the coupling is calculated as:
|
52
|
+
|
53
|
+
.. math::
|
54
|
+
|\iint_{\Omega} \Phi_{det} \, \Psi_{scat}^* \, d \Omega|^2
|
55
|
+
|
56
|
+
Where:
|
57
|
+
|
58
|
+
- \( \Phi_{det} \): The capturing field of the detector, representing the sensitivity of the detector to the incoming scattered field.
|
59
|
+
- \( \Psi_{scat} \): The scattered field produced by the scatterer.
|
60
|
+
- \( \Omega \): The solid angle over which the integration is performed, typically covering the full \( 4\pi \) steradians around the scatterer.
|
61
|
+
- \( d\Omega \): The differential solid angle element.
|
62
|
+
|
63
|
+
This integral computes the overlap between the detector's sensitivity pattern and the scattered field, which is then squared to represent the power coupled into the detector.
|
64
|
+
|
65
|
+
Parameters
|
66
|
+
----------
|
67
|
+
scatterer : BaseScatterer
|
68
|
+
The scatterer object that interacts with the incident light, producing the scattered field.
|
69
|
+
|
70
|
+
Returns
|
71
|
+
-------
|
72
|
+
Quantity
|
73
|
+
The power coupling between the detector and the scatterer, expressed in watts (W). This value represents the amount of scattered power that is captured by the detector.
|
74
|
+
|
75
|
+
Notes
|
76
|
+
-----
|
77
|
+
- The method internally invokes the appropriate binding method based on the type of scatterer (e.g., Sphere, Cylinder) to calculate the coupling.
|
78
|
+
- The coupling depends on both the geometry of the detector and the nature of the scattered field, making it essential for evaluating the efficiency of light collection in scattering experiments.
|
79
|
+
|
80
|
+
Example
|
81
|
+
-------
|
82
|
+
A common use case is to evaluate how much of the scattered light from a nanoparticle is captured by a photodiode or integrating sphere. The result can be used to estimate the efficiency of light collection for scattering measurements.
|
83
|
+
|
84
|
+
"""
|
85
|
+
return self._cpp_get_coupling(scatterer) * units.watt
|
86
|
+
|
87
|
+
def get_footprint(self, scatterer: BaseScatterer) -> Footprint:
|
88
|
+
r"""
|
89
|
+
Generate the footprint of the scattered light coupling with the detector.
|
90
|
+
|
91
|
+
.. math::
|
92
|
+
\big| \mathscr{F}^{-1} \big\{ \tilde{ \psi } (\xi, \nu),\
|
93
|
+
\tilde{ \phi}_{l,m}(\xi, \nu) \big\}
|
94
|
+
(\delta_x, \delta_y) \big|^2
|
95
|
+
|
96
|
+
| Where:
|
97
|
+
| :math:`\Phi_{det}` is the capturing field of the detector and
|
98
|
+
| :math:`\Psi_{scat}` is the scattered field.
|
99
|
+
|
100
|
+
Args:
|
101
|
+
scatterer (BaseScatterer): The scatterer object.
|
102
|
+
|
103
|
+
Returns:
|
104
|
+
Footprint: The scatterer footprint with this detector.
|
105
|
+
"""
|
106
|
+
return Footprint(scatterer=scatterer, detector=self)
|
107
|
+
|
108
|
+
def _add_to_3d_ax(self, scene: pyvista.Plotter, colormap: str = blue_black_red) -> None:
|
109
|
+
"""
|
110
|
+
Adds the scalar field and a directional cone to a 3D PyVista plotting scene.
|
111
|
+
|
112
|
+
This method adds points representing the real part of the scalar field to the given 3D scene,
|
113
|
+
along with a cone mesh to indicate directional information. It also includes a scalar bar
|
114
|
+
to display the values of the scalar field.
|
115
|
+
|
116
|
+
Parameters
|
117
|
+
----------
|
118
|
+
scene : pyvista.Plotter
|
119
|
+
The PyVista plotting scene where the elements will be added.
|
120
|
+
colormap : str
|
121
|
+
The colormap to use for the scalar field visualization.
|
122
|
+
|
123
|
+
Returns:
|
124
|
+
None: This method does not return a value. It modifies the provided scene.
|
125
|
+
"""
|
126
|
+
# Stack the mesh coordinates into a single array
|
127
|
+
coordinates = numpy.vstack((
|
128
|
+
self._cpp_mesh.cartesian.x,
|
129
|
+
self._cpp_mesh.cartesian.y,
|
130
|
+
self._cpp_mesh.cartesian.z
|
131
|
+
))
|
132
|
+
|
133
|
+
# Wrap the coordinates for PyVista visualization
|
134
|
+
points = pyvista.wrap(coordinates.T)
|
135
|
+
|
136
|
+
scalar_field = numpy.asarray(self._cpp_scalar_field).real
|
137
|
+
|
138
|
+
abs_max = abs(scalar_field).max()
|
139
|
+
|
140
|
+
# Add the points to the scene, representing the real part of the scalar field
|
141
|
+
mapping = scene.add_points(
|
142
|
+
points,
|
143
|
+
scalars=scalar_field,
|
144
|
+
point_size=20,
|
145
|
+
render_points_as_spheres=True,
|
146
|
+
cmap=colormap,
|
147
|
+
show_scalar_bar=False,
|
148
|
+
clim=[-abs_max, abs_max]
|
149
|
+
)
|
150
|
+
|
151
|
+
# Create a cone mesh to indicate directional information
|
152
|
+
cone_mesh = pyvista.Cone(
|
153
|
+
center=coordinates.mean(axis=1) / 2,
|
154
|
+
direction=-coordinates.mean(axis=1),
|
155
|
+
height=numpy.cos(self.max_angle),
|
156
|
+
resolution=100,
|
157
|
+
angle=self.max_angle.to('degree').magnitude,
|
158
|
+
)
|
159
|
+
|
160
|
+
# Add the cone mesh to the scene with specified color and opacity
|
161
|
+
scene.add_mesh(cone_mesh, color='blue', opacity=0.6)
|
162
|
+
|
163
|
+
# Add a scalar bar to the scene for the real part of the field
|
164
|
+
scene.add_scalar_bar(mapper=mapping.mapper, title='Collecting Field Real Part')
|
165
|
+
|
166
|
+
def get_poynting_vector(self, scatterer: BaseScatterer, distance: Quantity = 1 * meter) -> float:
|
167
|
+
r"""
|
168
|
+
Compute the Poynting vector norm, representing the energy flux density of the electromagnetic field.
|
169
|
+
|
170
|
+
The Poynting vector describes the directional energy transfer per unit area for an electromagnetic wave. It is defined as:
|
171
|
+
|
172
|
+
.. math::
|
173
|
+
\vec{S} = \epsilon_0 c^2 \, \vec{E} \times \vec{B}
|
174
|
+
|
175
|
+
Where:
|
176
|
+
|
177
|
+
- \( \vec{S} \): Poynting vector (W/m²)
|
178
|
+
- \( \epsilon_0 \): Permittivity of free space (F/m)
|
179
|
+
- \( c \): Speed of light in vacuum (m/s)
|
180
|
+
- \( \vec{E} \): Electric field vector (V/m)
|
181
|
+
- \( \vec{B} \): Magnetic field vector (T)
|
182
|
+
|
183
|
+
The cross product of the electric and magnetic field vectors results in the Poynting vector, which represents the flow of electromagnetic energy in space.
|
184
|
+
|
185
|
+
Parameters
|
186
|
+
----------
|
187
|
+
scatterer : BaseScatterer
|
188
|
+
The scatterer object that interacts with the incident electromagnetic wave, affecting the fields and energy flow.
|
189
|
+
distance : Quantity
|
190
|
+
The distance at which the Poynting vector is computed.
|
191
|
+
|
192
|
+
Returns
|
193
|
+
-------
|
194
|
+
Quantity
|
195
|
+
The norm of the Poynting vector, which gives the magnitude of the energy flux density in watts per square meter (W/m²).
|
196
|
+
|
197
|
+
Notes
|
198
|
+
-----
|
199
|
+
The Poynting vector is computed over a 3D mesh of voxels that cover the entire solid angle of \( 4\pi \) steradians. This method calculates the local energy flux at each voxel and returns the norm, which represents the magnitude of energy flow at each point in space around the scatterer.
|
200
|
+
|
201
|
+
The Poynting vector is fundamental in understanding how energy is transmitted through space in the form of electromagnetic waves.
|
202
|
+
|
203
|
+
Example
|
204
|
+
-------
|
205
|
+
This method is used to assess the distribution of energy around a scatterer. The total energy flow can be obtained by integrating the Poynting vector over the surface enclosing the scatterer.
|
206
|
+
|
207
|
+
"""
|
208
|
+
Ephi, Etheta = scatterer.get_farfields_array(
|
209
|
+
phi=self._cpp_mesh.spherical.phi,
|
210
|
+
theta=self._cpp_mesh.spherical.theta,
|
211
|
+
r=distance
|
212
|
+
)
|
213
|
+
|
214
|
+
E_norm = numpy.sqrt(numpy.abs(Ephi)**2 + numpy.abs(Etheta)**2) * volt / meter
|
215
|
+
|
216
|
+
B_norm = (E_norm / c).to('tesla')
|
217
|
+
|
218
|
+
poynting = epsilon0 * c**2 * E_norm * B_norm
|
219
|
+
|
220
|
+
return poynting.to(watt/meter ** 2)
|
221
|
+
|
222
|
+
def get_energy_flow(self, scatterer: BaseScatterer, distance: Quantity = 1 * meter) -> Quantity:
|
223
|
+
r"""
|
224
|
+
Calculate the total energy flow (or radiated power) from the scatterer based on the Poynting vector.
|
225
|
+
|
226
|
+
The energy flow is computed using the following relationship between the scattered energy and the incident intensity:
|
227
|
+
|
228
|
+
.. math::
|
229
|
+
W_a &= \sigma_{sca} \cdot I_{inc} \\[10pt]
|
230
|
+
P &= \int_{A} I \, dA \\[10pt]
|
231
|
+
I &= \frac{c n \epsilon_0}{2} \, |E|^2
|
232
|
+
|
233
|
+
Where:
|
234
|
+
|
235
|
+
- \( W_a \): Energy flow (W)
|
236
|
+
- \( \sigma_{sca} \): Scattering cross section (m²)
|
237
|
+
- \( I_{inc} \): Incident intensity (W/m²)
|
238
|
+
- \( P \): Radiated power (W)
|
239
|
+
- \( I \): Energy density (W/m²)
|
240
|
+
- \( c \): Speed of light in vacuum (m/s)
|
241
|
+
- \( n \): Refractive index of the surrounding medium
|
242
|
+
- \( \epsilon_0 \): Permittivity of free space (F/m)
|
243
|
+
- \( E \): Electric field (V/m)
|
244
|
+
|
245
|
+
The total power is computed by integrating the intensity over the surface area of the scatterer.
|
246
|
+
|
247
|
+
Parameters
|
248
|
+
----------
|
249
|
+
scatterer : BaseScatterer
|
250
|
+
The scatterer object, which contains information about the scattering properties of the particle, such as geometry and material.
|
251
|
+
distance : Quantity
|
252
|
+
The distance at which the Poynting vector is computed. It should change the computed total power.
|
253
|
+
|
254
|
+
Returns
|
255
|
+
-------
|
256
|
+
Quantity
|
257
|
+
The total energy flow (radiated power) from the scatterer, expressed in watts.
|
258
|
+
|
259
|
+
Notes
|
260
|
+
-----
|
261
|
+
This method computes the energy flow by calculating the Poynting vector (which represents the directional energy flux) and summing it over the surface mesh of the scatterer. The final result is the total radiated power.
|
262
|
+
|
263
|
+
"""
|
264
|
+
poynting_vector = self.get_poynting_vector(scatterer=scatterer, distance=distance)
|
265
|
+
|
266
|
+
dA = self._cpp_mesh._cpp_d_omega * distance ** 2
|
267
|
+
|
268
|
+
total_power = 0.5 * numpy.trapezoid(y=poynting_vector, dx=dA)
|
269
|
+
|
270
|
+
return total_power
|
271
|
+
|
@@ -0,0 +1,99 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
import logging
|
5
|
+
from typing import Optional
|
6
|
+
from PyMieSim import units
|
7
|
+
from PyMieSim.binary.interface_detector import DETECTOR
|
8
|
+
from PyMieSim.single.detector.base import BaseDetector
|
9
|
+
|
10
|
+
|
11
|
+
class CoherentMode(DETECTOR, BaseDetector):
|
12
|
+
def __init__(self,
|
13
|
+
mode_number: str,
|
14
|
+
NA: units.Quantity,
|
15
|
+
gamma_offset: units.Quantity,
|
16
|
+
phi_offset: units.Quantity,
|
17
|
+
sampling: units.Quantity = 200,
|
18
|
+
polarization_filter: Optional[units.Quantity] = None,
|
19
|
+
cache_NA: Optional[units.Quantity] = 0 * units.AU,
|
20
|
+
mean_coupling: bool = False,
|
21
|
+
rotation: units.Quantity = 90 * units.degree,
|
22
|
+
medium_refractive_index: units.Quantity = 1.0 * units.RIU,
|
23
|
+
):
|
24
|
+
"""
|
25
|
+
Initialize the CoherentMode detector with its parameters.
|
26
|
+
|
27
|
+
Depending on the mode_number, this detector can represent different types of modes such as Hermite-Gauss (HG), Laguerre-Gauss (LG), or fiber linearly polarized (LP).
|
28
|
+
This class is designed to handle coherent light coupling mechanisms, meaning it depends on the phase of the impinging scattered light field.
|
29
|
+
|
30
|
+
Parameters
|
31
|
+
----------
|
32
|
+
mode_number : str
|
33
|
+
String representing the mode to be initialized (e.g., 'LP01', 'HG11', 'LG22').
|
34
|
+
NA : units.Quantity
|
35
|
+
The numerical aperture of the detector, a dimensionless number that defines its light-gathering
|
36
|
+
ability. Higher NA values indicate a wider acceptance angle for capturing light.
|
37
|
+
gamma_offset : units.Quantity
|
38
|
+
The rotational offset in the gamma direction, controlling the angular positioning of the detector relative to the scatterer.
|
39
|
+
phi_offset : units.Quantity
|
40
|
+
The rotational offset in the phi direction, specifying the azimuthal angle of the detector's orientation.
|
41
|
+
sampling : units.Quantity
|
42
|
+
The sampling resolution of the detector, controlling how finely the detector's field is sampled
|
43
|
+
over the surface. A higher value increases precision but may require more computational resources. Default is 200.
|
44
|
+
polarization_filter : Optional[units.Quantity]
|
45
|
+
The polarization filter applied to the detected light. If specified, it allows the detector to
|
46
|
+
selectively capture light with a particular polarization. If set to `nan`, no filtering is applied.
|
47
|
+
cache_NA : Optional[units.Quantity]
|
48
|
+
Numerical aperture of the detector cache. Default is 0 AU.
|
49
|
+
mean_coupling : bool
|
50
|
+
A flag indicating whether the mean value of the coupling is used when calculating the light
|
51
|
+
interaction with the scatterer. This is typically set for cases where an average coupling is
|
52
|
+
needed over multiple angles or configurations.
|
53
|
+
rotation : units.Quantity
|
54
|
+
The rotational angle of the detector, defining its orientation relative to the incoming light or
|
55
|
+
scatterer. The default value rotates the detector by 90 degrees.
|
56
|
+
medium_refractive_index : units.Quantity
|
57
|
+
The refractive index of the medium in which the detector operates. This is important for
|
58
|
+
determining the acceptance cone of light.
|
59
|
+
Default is 1.0 (vacuum or air).
|
60
|
+
"""
|
61
|
+
|
62
|
+
if NA > 0.3 * units.AU or NA < 0 * units.AU:
|
63
|
+
logging.warning(f"High values of NA: {NA} do not comply with the paraxial approximation. Values under 0.3 are preferred.")
|
64
|
+
|
65
|
+
self.mode_family = mode_number[:2]
|
66
|
+
|
67
|
+
if self.mode_family.lower() not in ['lp', 'lg', 'hg']:
|
68
|
+
raise ValueError(f'Invalid mode family: {self.mode_family}. Options are ["LP", "LG", "HG"]')
|
69
|
+
|
70
|
+
self.mode_number = mode_number
|
71
|
+
self.mean_coupling = mean_coupling
|
72
|
+
|
73
|
+
self.NA = self._validate_units(NA, dimension='arbitrary', units=units.AU)
|
74
|
+
self.cache_NA = self._validate_units(cache_NA, dimension='arbitrary', units=units.AU)
|
75
|
+
self.sampling = self._validate_units(sampling, dimension='arbitrary', units=units.AU)
|
76
|
+
|
77
|
+
self.gamma_offset = self._validate_units(gamma_offset, dimension='angle', units=units.degree)
|
78
|
+
self.phi_offset = self._validate_units(phi_offset, dimension='angle', units=units.degree)
|
79
|
+
self.rotation = self._validate_units(rotation, dimension='angle', units=units.degree)
|
80
|
+
|
81
|
+
self.medium_refractive_index = self._validate_units(medium_refractive_index, dimension='refractive index', units=units.RIU)
|
82
|
+
|
83
|
+
self.polarization_filter = self._validate_detector_polarization_units(polarization_filter)
|
84
|
+
|
85
|
+
super().__init__(
|
86
|
+
mode_number=self.mode_number,
|
87
|
+
sampling=self.sampling,
|
88
|
+
NA=self.NA.to(units.AU).magnitude,
|
89
|
+
cache_NA=self.cache_NA.to(units.AU).magnitude,
|
90
|
+
phi_offset=self.phi_offset.to(units.radian).magnitude,
|
91
|
+
gamma_offset=self.gamma_offset.to(units.radian).magnitude,
|
92
|
+
polarization_filter=self.polarization_filter.to(units.radian).magnitude,
|
93
|
+
rotation=self.rotation.to(units.radian).magnitude,
|
94
|
+
coherent=False,
|
95
|
+
mean_coupling=self.mean_coupling,
|
96
|
+
medium_refractive_index=self.medium_refractive_index.to(units.RIU).magnitude,
|
97
|
+
)
|
98
|
+
|
99
|
+
# -
|
@@ -0,0 +1,105 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
from typing import Optional
|
5
|
+
from PyMieSim.binary.interface_detector import DETECTOR
|
6
|
+
from PyMieSim import units
|
7
|
+
from PyMieSim.single.detector.base import BaseDetector
|
8
|
+
|
9
|
+
|
10
|
+
class Photodiode(DETECTOR, BaseDetector):
|
11
|
+
"""
|
12
|
+
Detector class representing a photodiode with a non-coherent light coupling mechanism.
|
13
|
+
This means it is independent of the phase of the impinging scattered light field.
|
14
|
+
|
15
|
+
"""
|
16
|
+
def __init__(self,
|
17
|
+
NA: units.Quantity,
|
18
|
+
gamma_offset: units.Quantity,
|
19
|
+
phi_offset: units.Quantity,
|
20
|
+
sampling: units.Quantity = 200,
|
21
|
+
polarization_filter: Optional[units.Quantity] = None,
|
22
|
+
cache_NA: Optional[units.Quantity] = 0 * units.AU,
|
23
|
+
mean_coupling: bool = False,
|
24
|
+
medium_refractive_index: units.Quantity = 1.0 * units.RIU):
|
25
|
+
"""
|
26
|
+
Initialize the Photodiode detector with its parameters.
|
27
|
+
|
28
|
+
Parameters
|
29
|
+
----------
|
30
|
+
|
31
|
+
NA : units.Quantity
|
32
|
+
Numerical aperture of the imaging system.
|
33
|
+
gamma_offset : units.Quantity
|
34
|
+
Angle [Degree] offset of the detector in the direction perpendicular to polarization.
|
35
|
+
phi_offset : units.Quantity
|
36
|
+
Angle [Degree] offset of the detector in the direction parallel to polarization.
|
37
|
+
sampling : units.Quantity
|
38
|
+
Sampling rate of the far-field distribution. Default is 200.
|
39
|
+
polarization_filter : Optional[units.Quantity]
|
40
|
+
Angle [Degree] of the polarization filter in front of the detector.
|
41
|
+
cache_NA : Optional[units.Quantity]
|
42
|
+
Numerical aperture of the detector cache. Default is 0 AU.
|
43
|
+
mean_coupling : bool
|
44
|
+
Indicates if the coupling mechanism is point-wise (True) or mean-wise (False). Default is False.
|
45
|
+
medium_refractive_index : units.Quantity
|
46
|
+
The refractive index of the medium in which the detector operates. This is important for
|
47
|
+
determining the acceptance cone of light.
|
48
|
+
Default is 1.0 (vacuum or air).
|
49
|
+
"""
|
50
|
+
self.mean_coupling = mean_coupling
|
51
|
+
|
52
|
+
self.NA = self._validate_units(NA, dimension='arbitrary', units=units.AU)
|
53
|
+
self.cache_NA = self._validate_units(cache_NA, dimension='arbitrary', units=units.AU)
|
54
|
+
self.sampling = self._validate_units(sampling, dimension='arbitrary', units=units.AU)
|
55
|
+
|
56
|
+
self.gamma_offset = self._validate_units(gamma_offset, dimension='angle', units=units.degree)
|
57
|
+
self.phi_offset = self._validate_units(phi_offset, dimension='angle', units=units.degree)
|
58
|
+
|
59
|
+
self.medium_refractive_index = self._validate_units(medium_refractive_index, dimension='refractive index', units=units.RIU)
|
60
|
+
|
61
|
+
self.polarization_filter = self._validate_detector_polarization_units(polarization_filter)
|
62
|
+
|
63
|
+
super().__init__(
|
64
|
+
mode_number="NC00",
|
65
|
+
NA=self.NA.to(units.AU).magnitude,
|
66
|
+
cache_NA=self.cache_NA.to(units.AU).magnitude,
|
67
|
+
gamma_offset=self.gamma_offset.to(units.radian).magnitude,
|
68
|
+
phi_offset=self.phi_offset.to(units.radian).magnitude,
|
69
|
+
sampling=self.sampling,
|
70
|
+
polarization_filter=self.polarization_filter.to(units.radian).magnitude,
|
71
|
+
mean_coupling=self.mean_coupling,
|
72
|
+
rotation=0,
|
73
|
+
coherent=False,
|
74
|
+
medium_refractive_index=self.medium_refractive_index.to(units.RIU).magnitude
|
75
|
+
)
|
76
|
+
|
77
|
+
|
78
|
+
class IntegratingSphere(Photodiode):
|
79
|
+
def __init__(self, sampling: units.Quantity, polarization_filter: Optional[units.Quantity] = None, mean_coupling: bool = False):
|
80
|
+
"""
|
81
|
+
Detector class representing a photodiode with a non-coherent light coupling mechanism.
|
82
|
+
This implies independence from the phase of the impinging scattered light field.
|
83
|
+
|
84
|
+
Parameters
|
85
|
+
----------
|
86
|
+
|
87
|
+
sampling : units.Quantity
|
88
|
+
Sampling rate of the far-field distribution. Default is 200.
|
89
|
+
polarization_filter : Optional[units.Quantity]
|
90
|
+
Angle [Degree] of the polarization filter in front of the detector.
|
91
|
+
cache_NA : Optional[units.Quantity]
|
92
|
+
Numerical aperture of the detector cache. Default is 0 AU.
|
93
|
+
mean_coupling : bool
|
94
|
+
Indicates if the coupling mechanism is point-wise (True) or mean-wise (False). Default is False.
|
95
|
+
"""
|
96
|
+
|
97
|
+
super().__init__(
|
98
|
+
sampling=sampling,
|
99
|
+
polarization_filter=polarization_filter,
|
100
|
+
mean_coupling=mean_coupling,
|
101
|
+
NA=2.0 * units.AU, # Fixed NA for IntegratingSphere
|
102
|
+
gamma_offset=0 * units.degree, # Fixed gamma offset for IntegratingSphere
|
103
|
+
phi_offset=0 * units.degree, # Fixed phi offset for IntegratingSphere
|
104
|
+
cache_NA=0 * units.AU, # Fixed cache NA for IntegratingSphere
|
105
|
+
)
|