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.
Files changed (101) hide show
  1. PyMieSim/__init__.py +16 -0
  2. PyMieSim/__main__.py +9 -0
  3. PyMieSim/_version.py +21 -0
  4. PyMieSim/binary/__init__.py +0 -0
  5. PyMieSim/binary/interface_detector.cp310-win_amd64.pyd +0 -0
  6. PyMieSim/binary/interface_detector.cp311-win_amd64.pyd +0 -0
  7. PyMieSim/binary/interface_detector.cp312-win_amd64.pyd +0 -0
  8. PyMieSim/binary/interface_detector.cp313-win_amd64.pyd +0 -0
  9. PyMieSim/binary/interface_experiment.cp310-win_amd64.pyd +0 -0
  10. PyMieSim/binary/interface_experiment.cp311-win_amd64.pyd +0 -0
  11. PyMieSim/binary/interface_experiment.cp312-win_amd64.pyd +0 -0
  12. PyMieSim/binary/interface_experiment.cp313-win_amd64.pyd +0 -0
  13. PyMieSim/binary/interface_scatterer.cp310-win_amd64.pyd +0 -0
  14. PyMieSim/binary/interface_scatterer.cp311-win_amd64.pyd +0 -0
  15. PyMieSim/binary/interface_scatterer.cp312-win_amd64.pyd +0 -0
  16. PyMieSim/binary/interface_scatterer.cp313-win_amd64.pyd +0 -0
  17. PyMieSim/binary/interface_sets.cp310-win_amd64.pyd +0 -0
  18. PyMieSim/binary/interface_sets.cp311-win_amd64.pyd +0 -0
  19. PyMieSim/binary/interface_sets.cp312-win_amd64.pyd +0 -0
  20. PyMieSim/binary/interface_sets.cp313-win_amd64.pyd +0 -0
  21. PyMieSim/binary/interface_source.cp310-win_amd64.pyd +0 -0
  22. PyMieSim/binary/interface_source.cp311-win_amd64.pyd +0 -0
  23. PyMieSim/binary/interface_source.cp312-win_amd64.pyd +0 -0
  24. PyMieSim/binary/interface_source.cp313-win_amd64.pyd +0 -0
  25. PyMieSim/binary/libcpp_coordinates.a +0 -0
  26. PyMieSim/binary/libcpp_detector.a +0 -0
  27. PyMieSim/binary/libcpp_experiment.a +0 -0
  28. PyMieSim/binary/libcpp_fibonacci.a +0 -0
  29. PyMieSim/binary/libcpp_mode_field.a +0 -0
  30. PyMieSim/binary/libcpp_sets.a +0 -0
  31. PyMieSim/binary/libcpp_source.a +0 -0
  32. PyMieSim/directories.py +31 -0
  33. PyMieSim/experiment/__init__.py +1 -0
  34. PyMieSim/experiment/dataframe_subclass.py +220 -0
  35. PyMieSim/experiment/detector/__init__.py +2 -0
  36. PyMieSim/experiment/detector/base.py +169 -0
  37. PyMieSim/experiment/detector/coherent_mode.py +50 -0
  38. PyMieSim/experiment/detector/photodiode.py +52 -0
  39. PyMieSim/experiment/scatterer/__init__.py +4 -0
  40. PyMieSim/experiment/scatterer/base.py +98 -0
  41. PyMieSim/experiment/scatterer/core_shell.py +82 -0
  42. PyMieSim/experiment/scatterer/cylinder.py +63 -0
  43. PyMieSim/experiment/scatterer/sphere.py +66 -0
  44. PyMieSim/experiment/setup.py +356 -0
  45. PyMieSim/experiment/source/__init__.py +2 -0
  46. PyMieSim/experiment/source/base.py +85 -0
  47. PyMieSim/experiment/source/gaussian.py +60 -0
  48. PyMieSim/experiment/source/planewave.py +69 -0
  49. PyMieSim/experiment/utils.py +132 -0
  50. PyMieSim/gui/__init__.py +0 -0
  51. PyMieSim/gui/helper.py +60 -0
  52. PyMieSim/gui/interface.py +136 -0
  53. PyMieSim/gui/section.py +606 -0
  54. PyMieSim/mesh.py +368 -0
  55. PyMieSim/polarization.py +174 -0
  56. PyMieSim/single/__init__.py +48 -0
  57. PyMieSim/single/detector/__init__.py +2 -0
  58. PyMieSim/single/detector/base.py +271 -0
  59. PyMieSim/single/detector/coherent.py +99 -0
  60. PyMieSim/single/detector/uncoherent.py +105 -0
  61. PyMieSim/single/representations.py +734 -0
  62. PyMieSim/single/scatterer/__init__.py +4 -0
  63. PyMieSim/single/scatterer/base.py +405 -0
  64. PyMieSim/single/scatterer/core_shell.py +126 -0
  65. PyMieSim/single/scatterer/cylinder.py +113 -0
  66. PyMieSim/single/scatterer/sphere.py +108 -0
  67. PyMieSim/single/source/__init__.py +3 -0
  68. PyMieSim/single/source/base.py +7 -0
  69. PyMieSim/single/source/gaussian.py +137 -0
  70. PyMieSim/single/source/planewave.py +97 -0
  71. PyMieSim/special_functions.py +81 -0
  72. PyMieSim/units.py +130 -0
  73. PyMieSim/validation_data/bohren_huffman/figure_810.csv +245 -0
  74. PyMieSim/validation_data/bohren_huffman/figure_87.csv +2 -0
  75. PyMieSim/validation_data/bohren_huffman/figure_88.csv +2 -0
  76. PyMieSim/validation_data/pymiescatt/example_coreshell_0.csv +41 -0
  77. PyMieSim/validation_data/pymiescatt/example_coreshell_1.csv +401 -0
  78. PyMieSim/validation_data/pymiescatt/example_shpere_0.csv +51 -0
  79. PyMieSim/validation_data/pymiescatt/example_shpere_1.csv +801 -0
  80. PyMieSim/validation_data/pymiescatt/example_shpere_2.csv +41 -0
  81. PyMieSim/validation_data/pymiescatt/example_shpere_3.csv +401 -0
  82. PyMieSim/validation_data/pymiescatt/example_sphere_0.csv +51 -0
  83. PyMieSim/validation_data/pymiescatt/example_sphere_1.csv +801 -0
  84. PyMieSim/validation_data/pymiescatt/example_sphere_2.csv +41 -0
  85. PyMieSim/validation_data/pymiescatt/example_sphere_3.csv +401 -0
  86. PyMieSim/validation_data/pymiescatt/validation_Qsca.csv +800 -0
  87. PyMieSim/validation_data/pymiescatt/validation_Qsca_coreshell_1.csv +400 -0
  88. PyMieSim/validation_data/pymiescatt/validation_Qsca_coreshell_2.csv +400 -0
  89. PyMieSim/validation_data/pymiescatt/validation_Qsca_medium.csv +800 -0
  90. PyMieSim/validation_data/pymiescatt/validation_coreshell.csv +81 -0
  91. PyMieSim/validation_data/pymiescatt/validation_sphere.csv +801 -0
  92. lib/libZBessel.a +0 -0
  93. lib/lib_ZBessel.a +0 -0
  94. lib/libcpp_base_scatterer.a +0 -0
  95. lib/libcpp_coreshell.a +0 -0
  96. lib/libcpp_cylinder.a +0 -0
  97. lib/libcpp_sphere.a +0 -0
  98. pymiesim-3.6.0.dist-info/METADATA +246 -0
  99. pymiesim-3.6.0.dist-info/RECORD +101 -0
  100. pymiesim-3.6.0.dist-info/WHEEL +5 -0
  101. 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
+ )