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
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
@@ -0,0 +1,9 @@
1
+
2
+ """Entry point for launching the experimental PyMieSim GUI."""
3
+
4
+ from PyMieSim.gui.interface import OpticalSetupGUI
5
+
6
+
7
+ if __name__ == '__main__':
8
+ _gui = OpticalSetupGUI()
9
+ _gui.run(host='127.0.0.1', port='8050', open_browser=True)
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
@@ -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,2 @@
1
+ from .photodiode import Photodiode
2
+ from .coherent_mode import CoherentMode
@@ -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
+
@@ -0,0 +1,4 @@
1
+ from .sphere import Sphere
2
+ from .cylinder import Cylinder
3
+ from .core_shell import CoreShell
4
+ from .base import BaseScatterer