PyMieSim 3.6.0__cp313-cp313-macosx_14_0_arm64.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/.dylibs/libgcc_s.1.1.dylib +0 -0
- PyMieSim/.dylibs/libgfortran.5.dylib +0 -0
- PyMieSim/.dylibs/libgomp.1.dylib +0 -0
- PyMieSim/.dylibs/libquadmath.0.dylib +0 -0
- PyMieSim/.dylibs/libstdc++.6.dylib +0 -0
- 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.cpython-310-darwin.so +0 -0
- PyMieSim/binary/interface_detector.cpython-311-darwin.so +0 -0
- PyMieSim/binary/interface_detector.cpython-312-darwin.so +0 -0
- PyMieSim/binary/interface_detector.cpython-313-darwin.so +0 -0
- PyMieSim/binary/interface_experiment.cpython-310-darwin.so +0 -0
- PyMieSim/binary/interface_experiment.cpython-311-darwin.so +0 -0
- PyMieSim/binary/interface_experiment.cpython-312-darwin.so +0 -0
- PyMieSim/binary/interface_experiment.cpython-313-darwin.so +0 -0
- PyMieSim/binary/interface_scatterer.cpython-310-darwin.so +0 -0
- PyMieSim/binary/interface_scatterer.cpython-311-darwin.so +0 -0
- PyMieSim/binary/interface_scatterer.cpython-312-darwin.so +0 -0
- PyMieSim/binary/interface_scatterer.cpython-313-darwin.so +0 -0
- PyMieSim/binary/interface_sets.cpython-310-darwin.so +0 -0
- PyMieSim/binary/interface_sets.cpython-311-darwin.so +0 -0
- PyMieSim/binary/interface_sets.cpython-312-darwin.so +0 -0
- PyMieSim/binary/interface_sets.cpython-313-darwin.so +0 -0
- PyMieSim/binary/interface_source.cpython-310-darwin.so +0 -0
- PyMieSim/binary/interface_source.cpython-311-darwin.so +0 -0
- PyMieSim/binary/interface_source.cpython-312-darwin.so +0 -0
- PyMieSim/binary/interface_source.cpython-313-darwin.so +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 +106 -0
- pymiesim-3.6.0.dist-info/WHEEL +5 -0
- pymiesim-3.6.0.dist-info/licenses/LICENSE +21 -0
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
PyMieSim/__init__.py
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
"""PyMieSim package initialization.
|
2
|
+
|
3
|
+
This module exposes the package version and sets up unit handling. Importing
|
4
|
+
``Quantity`` here ensures proper initialization on all platforms. Removing this
|
5
|
+
import causes unit tests to fail on macOS systems.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from PyMieSim.units import Quantity # required for unit registration on macOS
|
9
|
+
|
10
|
+
try:
|
11
|
+
from ._version import version as __version__
|
12
|
+
|
13
|
+
except ImportError:
|
14
|
+
__version__ = "0.0.0"
|
15
|
+
|
16
|
+
debug_mode = False
|
PyMieSim/__main__.py
ADDED
PyMieSim/_version.py
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# file generated by setuptools-scm
|
2
|
+
# don't change, don't track in version control
|
3
|
+
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
5
|
+
|
6
|
+
TYPE_CHECKING = False
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from typing import Tuple
|
9
|
+
from typing import Union
|
10
|
+
|
11
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
12
|
+
else:
|
13
|
+
VERSION_TUPLE = object
|
14
|
+
|
15
|
+
version: str
|
16
|
+
__version__: str
|
17
|
+
__version_tuple__: VERSION_TUPLE
|
18
|
+
version_tuple: VERSION_TUPLE
|
19
|
+
|
20
|
+
__version__ = version = '3.6.0'
|
21
|
+
__version_tuple__ = version_tuple = (3, 6, 0)
|
File without changes
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
PyMieSim/directories.py
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
from pathlib import Path
|
5
|
+
import PyMieSim
|
6
|
+
|
7
|
+
__all__ = [
|
8
|
+
'root_path',
|
9
|
+
'validation_data_path',
|
10
|
+
'doc_path',
|
11
|
+
'logo_path',
|
12
|
+
'doc_css_path'
|
13
|
+
]
|
14
|
+
|
15
|
+
root_path = Path(PyMieSim.__path__[0])
|
16
|
+
|
17
|
+
validation_data_path = root_path.joinpath('validation_data')
|
18
|
+
|
19
|
+
doc_path = root_path.parents[0].joinpath('docs')
|
20
|
+
|
21
|
+
logo_path = doc_path.joinpath('images/logo.png')
|
22
|
+
|
23
|
+
doc_css_path = doc_path.joinpath('source/_static/default.css')
|
24
|
+
|
25
|
+
if __name__ == '__main__':
|
26
|
+
for path_name in __all__:
|
27
|
+
path = locals()[path_name]
|
28
|
+
print(path)
|
29
|
+
assert path.exists(), f"Path {path_name} do not exists"
|
30
|
+
|
31
|
+
# -
|
@@ -0,0 +1 @@
|
|
1
|
+
from .setup import Setup # noqa: F401, W292
|
@@ -0,0 +1,220 @@
|
|
1
|
+
from MPSPlots.styles import mps
|
2
|
+
import matplotlib.pyplot as plt
|
3
|
+
import pandas as pd
|
4
|
+
import numpy as np
|
5
|
+
from typing import Optional
|
6
|
+
|
7
|
+
class PyMieSimDataFrame(pd.DataFrame):
|
8
|
+
"""
|
9
|
+
A subclass of pandas DataFrame with custom plot methods tailored for
|
10
|
+
multi-indexed data containing pint-quantified values.
|
11
|
+
"""
|
12
|
+
|
13
|
+
@property
|
14
|
+
def _constructor(self):
|
15
|
+
"""
|
16
|
+
Ensures that operations returning DataFrames return instances of PyMieSimDataFrame.
|
17
|
+
"""
|
18
|
+
return PyMieSimDataFrame
|
19
|
+
|
20
|
+
def _validate_axis(self, axis: str) -> None:
|
21
|
+
# Validate that 'x' is a valid index level.
|
22
|
+
if axis not in self.index.names:
|
23
|
+
available = ", ".join(self.index.names)
|
24
|
+
raise ValueError(f"x parameter '{axis}' is not in the DataFrame index. Available index levels: {available}")
|
25
|
+
|
26
|
+
def _get_complementary_axis(self, *axis) -> tuple:
|
27
|
+
return [level for level in self.index.names if level not in axis]
|
28
|
+
|
29
|
+
def plot(self,
|
30
|
+
x: str,
|
31
|
+
std: Optional[str] = None,
|
32
|
+
alpha: float = 0.4,
|
33
|
+
ax: Optional[plt.Axes] = None,
|
34
|
+
show: bool = True,
|
35
|
+
xscale: bool = 'linear',
|
36
|
+
yscale: bool = 'linear',
|
37
|
+
**kwargs) -> plt.Axes:
|
38
|
+
"""
|
39
|
+
Plots the DataFrame using a specified MultiIndex level for the x-axis.
|
40
|
+
Optionally, if a standard deviation level is provided, it will also
|
41
|
+
plot the mean ± std.
|
42
|
+
|
43
|
+
Parameters
|
44
|
+
----------
|
45
|
+
x : str
|
46
|
+
The MultiIndex level to use for the x-axis.
|
47
|
+
alpha : float, optional
|
48
|
+
The transparency level for the shaded standard deviation region.
|
49
|
+
std : Optional[str], optional
|
50
|
+
The MultiIndex level used for standard deviation calculation.
|
51
|
+
ax : Optional[plt.Axes], optional
|
52
|
+
The matplotlib Axes on which to plot. If None, a new Axes is created.
|
53
|
+
show : bool, optional
|
54
|
+
If True, displays the plot.
|
55
|
+
**kwargs : dict
|
56
|
+
Additional keyword arguments passed to the underlying plotting functions.
|
57
|
+
|
58
|
+
Returns
|
59
|
+
-------
|
60
|
+
plt.Axes
|
61
|
+
The matplotlib Axes with the plot.
|
62
|
+
"""
|
63
|
+
self._validate_axis(axis=x)
|
64
|
+
|
65
|
+
if std is not None:
|
66
|
+
self._validate_axis(axis=std)
|
67
|
+
|
68
|
+
|
69
|
+
with plt.style.context(mps):
|
70
|
+
if ax is None:
|
71
|
+
_, ax = plt.subplots()
|
72
|
+
|
73
|
+
ax.set(xscale=xscale, yscale=yscale)
|
74
|
+
|
75
|
+
if std is not None:
|
76
|
+
self._plot_with_std(ax=ax, x=x, std=std, alpha=alpha, **kwargs)
|
77
|
+
else:
|
78
|
+
self._plot_without_std(ax=ax, x=x, **kwargs)
|
79
|
+
|
80
|
+
self._format_legend(ax)
|
81
|
+
|
82
|
+
if show:
|
83
|
+
plt.show()
|
84
|
+
|
85
|
+
return ax
|
86
|
+
|
87
|
+
def _plot_with_std(self, ax: plt.Axes, x: str, std: str, alpha: float = 0.5, **kwargs) -> None:
|
88
|
+
"""
|
89
|
+
Plot the mean with standard deviation shading.
|
90
|
+
Expects the DataFrame to have a MultiIndex and a 'pint' attribute.
|
91
|
+
|
92
|
+
Parameters
|
93
|
+
----------
|
94
|
+
ax : plt.Axes
|
95
|
+
The matplotlib Axes to plot on.
|
96
|
+
x : str
|
97
|
+
The MultiIndex level to use for the x-axis.
|
98
|
+
std : str
|
99
|
+
The MultiIndex level used for standard deviation calculation.
|
100
|
+
alpha : float, optional
|
101
|
+
Transparency for the std shading.
|
102
|
+
show : bool, optional
|
103
|
+
Whether to call plt.show() after plotting.
|
104
|
+
**kwargs : dict
|
105
|
+
Additional keyword arguments for line styling.
|
106
|
+
"""
|
107
|
+
|
108
|
+
# Determine levels to unstack
|
109
|
+
no_std_levels = self._get_complementary_axis(std)
|
110
|
+
no_x_levels = self._get_complementary_axis(x, std)
|
111
|
+
|
112
|
+
# Calculate standard deviation and mean, preserving pint units
|
113
|
+
std_df = (
|
114
|
+
self.pint.dequantify()
|
115
|
+
.unstack(no_std_levels)
|
116
|
+
.apply(np.std)
|
117
|
+
.to_frame(name="std")
|
118
|
+
.unstack("unit")
|
119
|
+
.pint.quantify()
|
120
|
+
)
|
121
|
+
mean_df = (
|
122
|
+
self.pint.dequantify()
|
123
|
+
.unstack(no_std_levels)
|
124
|
+
.apply(np.mean)
|
125
|
+
.to_frame(name="mean")
|
126
|
+
.unstack("unit")
|
127
|
+
.pint.quantify()
|
128
|
+
)
|
129
|
+
|
130
|
+
combined_df = pd.concat([mean_df, std_df], axis=1)
|
131
|
+
groupby_levels = self.columns.names + no_x_levels
|
132
|
+
|
133
|
+
for name, group in combined_df.groupby(groupby_levels):
|
134
|
+
group = group.droplevel(groupby_levels)
|
135
|
+
|
136
|
+
label = [item for pair in zip(groupby_levels, name) for item in pair]
|
137
|
+
|
138
|
+
label = " : ".join(map(str, label))
|
139
|
+
|
140
|
+
ax.plot(group.index, group['mean'], linewidth=1, linestyle='--', **kwargs)
|
141
|
+
ax.fill_between(
|
142
|
+
x=group.index,
|
143
|
+
y1=group['mean'] + group['std'],
|
144
|
+
y2=group['mean'] - group['std'],
|
145
|
+
alpha=alpha,
|
146
|
+
edgecolor="black",
|
147
|
+
label=label
|
148
|
+
)
|
149
|
+
|
150
|
+
ax.legend()
|
151
|
+
|
152
|
+
def _format_legend(self, ax: plt.Axes) -> None:
|
153
|
+
leg = ax.get_legend() # Get the existing legend from the axes
|
154
|
+
for text in leg.get_texts():
|
155
|
+
original_label = text.get_text()
|
156
|
+
new_label = original_label.replace(')', '').replace('(', '').replace(', ', ' | ')
|
157
|
+
text.set_text(new_label)
|
158
|
+
|
159
|
+
def _plot_without_std(self, ax: plt.Axes, x: str, **kwargs) -> None:
|
160
|
+
"""
|
161
|
+
Plots the data without standard deviation shading.
|
162
|
+
Handles both real and imaginary parts for complex data.
|
163
|
+
|
164
|
+
Parameters
|
165
|
+
----------
|
166
|
+
ax : plt.Axes
|
167
|
+
The matplotlib Axes on which to plot.
|
168
|
+
x : str
|
169
|
+
The index level to use for the x-axis.
|
170
|
+
y : Optional[str], optional
|
171
|
+
The column to use for the y-axis. If None, the first available column is used.
|
172
|
+
show : bool, optional
|
173
|
+
Whether to display the plot.
|
174
|
+
log_scale_x : bool, optional
|
175
|
+
If True, sets a logarithmic scale for the x-axis.
|
176
|
+
log_scale_y : bool, optional
|
177
|
+
If True, sets a logarithmic scale for the y-axis.
|
178
|
+
**kwargs : dict
|
179
|
+
Additional keyword arguments for the plot.
|
180
|
+
|
181
|
+
Returns
|
182
|
+
-------
|
183
|
+
None
|
184
|
+
"""
|
185
|
+
df = self.copy()
|
186
|
+
|
187
|
+
groupby_levels = df._get_complementary_axis(x)
|
188
|
+
|
189
|
+
df = df.unstack(groupby_levels)
|
190
|
+
|
191
|
+
if isinstance(df.index, pd.MultiIndex) and df.index.nlevels == 1:
|
192
|
+
df.index = df.index.get_level_values(0)
|
193
|
+
|
194
|
+
super(PyMieSimDataFrame, df).plot(ax=ax, **kwargs)
|
195
|
+
|
196
|
+
legend = ax.legend()
|
197
|
+
|
198
|
+
legend.set_title(' | '.join(df.columns.names))
|
199
|
+
|
200
|
+
def add_weight(self, weight_index: str, weight: np.ndarray) -> "PyMieSimDataFrame":
|
201
|
+
|
202
|
+
self._validate_axis(axis=weight_index)
|
203
|
+
|
204
|
+
stacking_index = self._get_complementary_axis(weight_index)
|
205
|
+
|
206
|
+
return (
|
207
|
+
self
|
208
|
+
.unstack(stacking_index)
|
209
|
+
.multiply(weight.squeeze(), axis='index')
|
210
|
+
.stack(stacking_index)
|
211
|
+
)
|
212
|
+
|
213
|
+
def sum_over(self, axis: str) -> "PyMieSimDataFrame":
|
214
|
+
|
215
|
+
self._validate_axis(axis=axis)
|
216
|
+
|
217
|
+
stacking_index = [name for name in self.index.names if name != axis]
|
218
|
+
|
219
|
+
return self.groupby(stacking_index).sum()#.to_frame().T
|
220
|
+
|
@@ -0,0 +1,169 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
import numpy
|
4
|
+
from pint_pandas import PintArray
|
5
|
+
from pydantic import field_validator
|
6
|
+
from PyMieSim.binary.interface_sets import CppDetectorSet
|
7
|
+
from PyMieSim.units import Quantity, radian, degree
|
8
|
+
|
9
|
+
|
10
|
+
class BaseDetector:
|
11
|
+
"""
|
12
|
+
A base class for defining detectors in Mie scattering simulations.
|
13
|
+
|
14
|
+
This class provides the foundational structure for defining detectors that interface with C++ backends to perform
|
15
|
+
high-performance scattering calculations. It is designed to handle common parameters such as numerical aperture (NA),
|
16
|
+
angular offsets, rotation, and polarization. Subclasses should extend this base class to create specific detector types.
|
17
|
+
|
18
|
+
Attributes
|
19
|
+
----------
|
20
|
+
mode_number : str
|
21
|
+
The mode number used in the detection process, identifying the angular momentum mode involved.
|
22
|
+
NA : Quantity
|
23
|
+
The numerical aperture of the detector, typically a float-like value wrapped in a physical unit (e.g., steradian).
|
24
|
+
gamma_offset : Quantity
|
25
|
+
The angular offset in the gamma direction (in degrees).
|
26
|
+
phi_offset : Quantity
|
27
|
+
The angular offset in the phi direction (in degrees).
|
28
|
+
rotation : Quantity
|
29
|
+
The rotational angle of the detector (in degrees).
|
30
|
+
mean_coupling : bool
|
31
|
+
Specifies whether the mean coupling of detected modes is to be considered (default is False).
|
32
|
+
coherent : bool
|
33
|
+
Specifies whether the detection process is coherent (default is True).
|
34
|
+
sampling : Optional[Quantity]
|
35
|
+
The sampling rate of the detector. If not specified, defaults to 200.
|
36
|
+
polarization_filter : Optional[Quantity]
|
37
|
+
The polarization filter angle (in degrees). Defaults to NaN if not provided.
|
38
|
+
|
39
|
+
Methods
|
40
|
+
-------
|
41
|
+
validate_polarization_filter(cls, value)
|
42
|
+
Ensures that the polarization filter is either None or a Quantity with angle units (degree or radian).
|
43
|
+
validate_angle_quantity(cls, value)
|
44
|
+
Validates that angles like gamma_offset, phi_offset, and rotation are Quantities with angle units.
|
45
|
+
validate_au_quantity(cls, value)
|
46
|
+
Ensures that numerical values are correctly cast as NumPy arrays.
|
47
|
+
__post_init__()
|
48
|
+
Initializes the internal detector mapping and binds the detector to the C++ backend.
|
49
|
+
_initialize_binding()
|
50
|
+
Sets up the C++ bindings required for efficient simulation by passing detector parameters.
|
51
|
+
_generate_mapping()
|
52
|
+
Generates a mapping of scatterer properties to be used for visualization purposes.
|
53
|
+
"""
|
54
|
+
@field_validator('mode_number', mode='plain')
|
55
|
+
def _validate_mode_number(cls, mode_number):
|
56
|
+
"""Ensure mode numbers are valid and belong to supported families."""
|
57
|
+
mode_number = numpy.atleast_1d(mode_number).astype(str)
|
58
|
+
for mode in mode_number:
|
59
|
+
if mode[:2] not in ['LP', 'HG', 'LG', 'NC']:
|
60
|
+
raise ValueError(f'Invalid mode family {mode[:2]}. Must be one of: LP, HG, LG, NC')
|
61
|
+
return mode_number
|
62
|
+
|
63
|
+
@field_validator('polarization_filter', mode='plain')
|
64
|
+
def validate_polarization(cls, value):
|
65
|
+
"""
|
66
|
+
Validates the polarization filter. If not provided, defaults to NaN degrees.
|
67
|
+
Ensures the value has angular units (degree or radian).
|
68
|
+
|
69
|
+
Parameters
|
70
|
+
----------
|
71
|
+
value : Any
|
72
|
+
The input value for polarization filter.
|
73
|
+
|
74
|
+
Returns
|
75
|
+
-------
|
76
|
+
numpy.ndarray
|
77
|
+
A NumPy array containing the validated and converted polarization filter value.
|
78
|
+
"""
|
79
|
+
if value is None:
|
80
|
+
value = numpy.nan * degree
|
81
|
+
|
82
|
+
if not isinstance(value, Quantity) or not value.check(degree):
|
83
|
+
raise ValueError(f"{value} must have angle units (degree or radian).")
|
84
|
+
|
85
|
+
return numpy.atleast_1d(value).astype(float)
|
86
|
+
|
87
|
+
@field_validator('gamma_offset', 'phi_offset', 'rotation', mode='plain')
|
88
|
+
def validate_angle_quantity(cls, value):
|
89
|
+
"""
|
90
|
+
Ensures that angular quantities (gamma_offset, phi_offset, rotation) are correctly formatted as Quantities with angle units.
|
91
|
+
|
92
|
+
Parameters
|
93
|
+
----------
|
94
|
+
value : Any
|
95
|
+
The input value for the angle.
|
96
|
+
|
97
|
+
Returns
|
98
|
+
-------
|
99
|
+
numpy.ndarray
|
100
|
+
A NumPy array containing the validated and converted angle value.
|
101
|
+
"""
|
102
|
+
if not isinstance(value, Quantity) or not value.check(degree):
|
103
|
+
raise ValueError(f"{value} must be a Quantity with angle units [degree or radian].")
|
104
|
+
|
105
|
+
return numpy.atleast_1d(value)
|
106
|
+
|
107
|
+
@field_validator('NA', 'cache_NA', 'sampling', mode='plain')
|
108
|
+
def validate_au_quantity(cls, value):
|
109
|
+
"""
|
110
|
+
Ensures that numerical values such as numerical aperture (NA) and sampling rate are correctly cast into NumPy arrays.
|
111
|
+
|
112
|
+
Parameters
|
113
|
+
----------
|
114
|
+
value : Any
|
115
|
+
The input value to be validated.
|
116
|
+
|
117
|
+
Returns
|
118
|
+
-------
|
119
|
+
numpy.ndarray
|
120
|
+
A NumPy array representing the validated input value.
|
121
|
+
"""
|
122
|
+
if not isinstance(value, Quantity) or not value.check(degree):
|
123
|
+
raise ValueError(f"{value} must be a Quantity with arbitrary units [AU].")
|
124
|
+
|
125
|
+
if not isinstance(value, numpy.ndarray):
|
126
|
+
value = numpy.atleast_1d(value)
|
127
|
+
|
128
|
+
return value
|
129
|
+
|
130
|
+
def _generate_binding(self) -> None:
|
131
|
+
"""
|
132
|
+
Initializes the C++ binding for the detector using the given simulation parameters. This ensures that the
|
133
|
+
detector is correctly linked to the backend, enabling high-performance Mie scattering calculations.
|
134
|
+
|
135
|
+
Sets up parameters such as mode number, sampling rate, NA, and various offsets for the simulation.
|
136
|
+
"""
|
137
|
+
self.binding_kwargs = {
|
138
|
+
"mode_number": self.mode_number,
|
139
|
+
"sampling": self.sampling,
|
140
|
+
"NA": self.NA,
|
141
|
+
"cache_NA": self.cache_NA,
|
142
|
+
"polarization_filter": self.polarization_filter.to(radian).magnitude if self.polarization_filter is not None else numpy.nan,
|
143
|
+
"phi_offset": self.phi_offset.to(radian).magnitude,
|
144
|
+
"gamma_offset": self.gamma_offset.to(radian).magnitude,
|
145
|
+
"rotation": self.rotation.to(radian).magnitude,
|
146
|
+
"is_sequential": self.is_sequential
|
147
|
+
}
|
148
|
+
|
149
|
+
# Ensure all values are at least 1D arrays for compatibility
|
150
|
+
self.binding_kwargs = {k: numpy.atleast_1d(v) for k, v in self.binding_kwargs.items()}
|
151
|
+
|
152
|
+
# Additional detector settings
|
153
|
+
self.binding_kwargs["mean_coupling"] = self.mean_coupling
|
154
|
+
self.binding_kwargs["coherent"] = self.coherent
|
155
|
+
|
156
|
+
self.binding = CppDetectorSet(**self.binding_kwargs)
|
157
|
+
|
158
|
+
def _generate_mapping(self) -> None:
|
159
|
+
"""
|
160
|
+
Updates the internal mapping of the detector with current parameter values, allowing for visual representation
|
161
|
+
of the detector's properties in a tabular format (useful for debugging and visualization).
|
162
|
+
|
163
|
+
Attributes like mode number, NA, offsets, and sampling are included in this mapping.
|
164
|
+
"""
|
165
|
+
self.mapping = {}
|
166
|
+
self.mapping.update({'detector:mode_number': self.mode_number})
|
167
|
+
|
168
|
+
for attr in ['NA', 'sampling', 'cache_NA', 'phi_offset', 'gamma_offset', 'rotation', 'polarization_filter']:
|
169
|
+
self.mapping["detector:" + attr] = PintArray(getattr(self, attr), dtype=getattr(self, attr).units)
|
@@ -0,0 +1,50 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
import numpy
|
5
|
+
from pydantic.dataclasses import dataclass
|
6
|
+
from typing import List, Union, Optional
|
7
|
+
from PyMieSim.experiment.detector.base import BaseDetector
|
8
|
+
from PyMieSim.units import Quantity, degree, AU
|
9
|
+
from PyMieSim.experiment.utils import config_dict, Sequential
|
10
|
+
|
11
|
+
@dataclass(config=config_dict)
|
12
|
+
class CoherentMode(BaseDetector, Sequential):
|
13
|
+
"""
|
14
|
+
Coherent mode detector for Mie scattering simulations, handling coherent detection modes.
|
15
|
+
|
16
|
+
This detector is designed specifically for coherent modes, such as LP, HG, LG, and NC modes, which require specific handling
|
17
|
+
in Mie scattering experiments.
|
18
|
+
|
19
|
+
Parameters
|
20
|
+
----------
|
21
|
+
mode_number : Union[List[str], str]
|
22
|
+
Mode number(s) involved in the detection.
|
23
|
+
NA : Union[List[float], float]
|
24
|
+
Numerical aperture(s) of the detector.
|
25
|
+
gamma_offset : Quantity
|
26
|
+
Gamma angular offset (in degrees).
|
27
|
+
phi_offset : Quantity
|
28
|
+
Phi angular offset (in degrees).
|
29
|
+
rotation : Quantity
|
30
|
+
Rotation angle of the detector.
|
31
|
+
sampling : Union[List[int], int]
|
32
|
+
Sampling rate(s) for the detector.
|
33
|
+
polarization_filter : Optional[Quantity]
|
34
|
+
Polarization filter angle (in degrees).
|
35
|
+
mean_coupling : Optional[bool]
|
36
|
+
Whether mean coupling is used. Defaults to False.
|
37
|
+
coherent : bool
|
38
|
+
Specifies if the detection is coherent. Defaults to True.
|
39
|
+
"""
|
40
|
+
mode_number: Union[List[str], str]
|
41
|
+
NA: Quantity
|
42
|
+
gamma_offset: Quantity
|
43
|
+
phi_offset: Quantity
|
44
|
+
rotation: Quantity
|
45
|
+
mean_coupling: bool
|
46
|
+
coherent: bool = True
|
47
|
+
mean_coupling: Optional[bool] = False
|
48
|
+
cache_NA: Quantity = (0.,) * AU
|
49
|
+
sampling: Optional[Quantity] = (200,) * AU
|
50
|
+
polarization_filter: Optional[Quantity | None] = (numpy.nan, ) * degree
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
from typing import Optional
|
4
|
+
import numpy
|
5
|
+
from dataclasses import field
|
6
|
+
from pydantic.dataclasses import dataclass
|
7
|
+
from PyMieSim.units import Quantity, degree, AU
|
8
|
+
from typing import Tuple
|
9
|
+
from PyMieSim.experiment.detector.base import BaseDetector
|
10
|
+
from PyMieSim.experiment.utils import config_dict, Sequential
|
11
|
+
|
12
|
+
|
13
|
+
@dataclass(config=config_dict)
|
14
|
+
class Photodiode(BaseDetector, Sequential):
|
15
|
+
"""
|
16
|
+
Photodiode detector tailored for Mie scattering simulations, extending BaseDetector with specific features.
|
17
|
+
|
18
|
+
Parameters
|
19
|
+
----------
|
20
|
+
NA : Union[List[float], float]
|
21
|
+
Numerical aperture(s) of the detector.
|
22
|
+
gamma_offset : Quantity
|
23
|
+
Gamma angular offset (in degrees).
|
24
|
+
phi_offset : Quantity
|
25
|
+
Phi angular offset (in degrees).
|
26
|
+
polarization_filter : Optional[Quantity]
|
27
|
+
Polarization filter angle (in degrees).
|
28
|
+
sampling : Union[List[int], int]
|
29
|
+
Sampling rate(s) for the detector.
|
30
|
+
mean_coupling : bool
|
31
|
+
Whether mean coupling is used. Defaults to True.
|
32
|
+
rotation : Quantity
|
33
|
+
Rotation angle of the detector. Defaults to 0 degrees.
|
34
|
+
coherent : bool
|
35
|
+
Indicates if the detection is coherent. Defaults to False.
|
36
|
+
mode_number : str
|
37
|
+
Mode number of the detector. Defaults to 'NC00'.
|
38
|
+
"""
|
39
|
+
NA: Quantity
|
40
|
+
gamma_offset: Quantity
|
41
|
+
phi_offset: Quantity
|
42
|
+
mean_coupling: bool
|
43
|
+
coherent: bool
|
44
|
+
cache_NA: Quantity = (0.,) * AU
|
45
|
+
sampling: Optional[Quantity] = (200,) * AU
|
46
|
+
polarization_filter: Optional[Quantity | None] = (numpy.nan, ) * degree
|
47
|
+
mode_number: Tuple[str] = field(default_factory=lambda: ['NC00'], init=True)
|
48
|
+
rotation: Quantity = field(default=(0,) * degree, init=True)
|
49
|
+
|
50
|
+
coherent: bool = field(default=False, init=False)
|
51
|
+
mean_coupling: bool = field(default=False, init=False)
|
52
|
+
|