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,108 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
import numpy
|
5
|
+
import pyvista
|
6
|
+
from PyOptik.material.base_class import BaseMaterial
|
7
|
+
|
8
|
+
from PyMieSim import units
|
9
|
+
from PyMieSim.single.scatterer.base import BaseScatterer
|
10
|
+
from PyMieSim.binary.interface_scatterer import SPHERE
|
11
|
+
from PyMieSim.single.source.base import BaseSource
|
12
|
+
|
13
|
+
|
14
|
+
# @dataclass(config=config_dict, kw_only=True)
|
15
|
+
class Sphere(SPHERE, BaseScatterer):
|
16
|
+
"""
|
17
|
+
Class representing a homogeneous spherical scatterer.
|
18
|
+
|
19
|
+
Parameters
|
20
|
+
----------
|
21
|
+
diameter : units.Quantity
|
22
|
+
Diameter of the cylindrical scatterer, given in meters.
|
23
|
+
property : units.Quantity or BaseMaterial
|
24
|
+
Defines either the refractive index (`Quantity`) or material (`BaseMaterial`) of the scatterer. Only one can be provided.
|
25
|
+
|
26
|
+
"""
|
27
|
+
diameter: units.Quantity
|
28
|
+
property: units.Quantity | BaseMaterial
|
29
|
+
medium_property: units.Quantity | BaseMaterial
|
30
|
+
source: BaseSource
|
31
|
+
|
32
|
+
property_names = [
|
33
|
+
"size_parameter", "radius", "volume", "cross_section", "g",
|
34
|
+
"Qsca", "Qext", "Qabs", "Qback", "Qratio", "Qpr",
|
35
|
+
"Csca", "Cext", "Cabs", "Cback", "Cratio", "Cpr"
|
36
|
+
]
|
37
|
+
|
38
|
+
|
39
|
+
def __init__(self, diameter: units.Quantity, property: units.Quantity | BaseMaterial, medium_property: units.Quantity | BaseMaterial, source: BaseSource):
|
40
|
+
"""
|
41
|
+
Initialize the Sphere scatterer with its diameter and material properties.
|
42
|
+
|
43
|
+
Parameters
|
44
|
+
----------
|
45
|
+
diameter : units.Quantity
|
46
|
+
Diameter of the sphere in meters.
|
47
|
+
property : units.Quantity or BaseMaterial
|
48
|
+
Refractive index or material of the sphere.
|
49
|
+
medium_property : units.Quantity
|
50
|
+
Refractive index of the surrounding medium.
|
51
|
+
source : Source
|
52
|
+
Source object associated with the scatterer.
|
53
|
+
"""
|
54
|
+
self.diameter = self._validate_units(diameter, dimension='distance', units=units.meter)
|
55
|
+
|
56
|
+
self.property = self._validate_property(property)
|
57
|
+
self.medium_property = self._validate_property(medium_property)
|
58
|
+
self.source = source
|
59
|
+
|
60
|
+
self.index, self.material = self._assign_index_or_material(self.property)
|
61
|
+
self.medium_index, self.medium_material = self._assign_index_or_material(self.medium_property)
|
62
|
+
|
63
|
+
super().__init__(
|
64
|
+
diameter=diameter.to(units.meter).magnitude,
|
65
|
+
refractive_index=self.index.to(units.RIU).magnitude,
|
66
|
+
medium_refractive_index=self.medium_index.to(units.RIU).magnitude,
|
67
|
+
source=self.source
|
68
|
+
)
|
69
|
+
|
70
|
+
@property
|
71
|
+
def radius(self) -> units.Quantity:
|
72
|
+
"""Return the radius of the sphere."""
|
73
|
+
return self.diameter / 2
|
74
|
+
|
75
|
+
@property
|
76
|
+
def volume(self) -> units.Quantity:
|
77
|
+
"""Return the volume of the sphere."""
|
78
|
+
vol = (4/3) * numpy.pi * (self.radius ** 3)
|
79
|
+
return vol.to(units.meter ** 3)
|
80
|
+
|
81
|
+
def _add_to_3d_ax(self, scene: pyvista.Plotter, color: str = 'black', opacity: float = 1.0) -> None:
|
82
|
+
"""
|
83
|
+
Adds a 3D cone representation to the given PyVista plotting scene.
|
84
|
+
|
85
|
+
The cone represents the acceptance angle determined by the numerical aperture (NA) of the system.
|
86
|
+
The cone is positioned at the origin and points downward along the z-axis.
|
87
|
+
|
88
|
+
Parameters
|
89
|
+
----------
|
90
|
+
scene : pyvista.Plotter
|
91
|
+
The 3D plotting scene to which the cone will be added.
|
92
|
+
color : str
|
93
|
+
The color of the cone mesh. Default is 'red'.
|
94
|
+
opacity : float
|
95
|
+
The opacity of the cone mesh. Default is 0.8.
|
96
|
+
|
97
|
+
"""
|
98
|
+
# Create the cone mesh
|
99
|
+
shape = pyvista.Sphere(
|
100
|
+
center=(0.0, 0.0, 0.0),
|
101
|
+
radius=0.1,
|
102
|
+
theta_resolution=100,
|
103
|
+
phi_resolution=100
|
104
|
+
)
|
105
|
+
|
106
|
+
# Add the cone mesh to the scene
|
107
|
+
scene.add_mesh(shape, color=color, opacity=opacity)
|
108
|
+
|
@@ -0,0 +1,137 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
import numpy
|
5
|
+
import pyvista
|
6
|
+
from PyMieSim import units
|
7
|
+
from PyMieSim.polarization import BasePolarization
|
8
|
+
from PyMieSim.binary.interface_source import GAUSSIAN
|
9
|
+
from PyMieSim.single.source.base import BaseSource
|
10
|
+
|
11
|
+
|
12
|
+
class Gaussian(GAUSSIAN, BaseSource):
|
13
|
+
amplitude: units.Quantity
|
14
|
+
|
15
|
+
def __init__(self,
|
16
|
+
wavelength: units.Quantity,
|
17
|
+
polarization: units.Quantity | BasePolarization,
|
18
|
+
optical_power: units.Quantity,
|
19
|
+
NA: units.Quantity) -> None:
|
20
|
+
"""
|
21
|
+
Initializes a Gaussian light source for light scattering simulations, characterized by its optical power and numerical aperture.
|
22
|
+
|
23
|
+
Parameters
|
24
|
+
----------
|
25
|
+
|
26
|
+
wavelength: units.Quantity
|
27
|
+
Wavelength of the light field in meters.
|
28
|
+
polarization : units.Quantity | BasePolarization
|
29
|
+
Polarization state of the light field, if float is given it is assumed Linear polarization of angle theta.
|
30
|
+
optical_power: units.Quantity
|
31
|
+
Optical power of the source in Watts.
|
32
|
+
NA: units.Quantity
|
33
|
+
Numerical aperture of the source.
|
34
|
+
"""
|
35
|
+
self.wavelength = self._validate_units(wavelength, dimension="distance", units=units.meter)
|
36
|
+
self.optical_power = self._validate_units(optical_power, dimension="power", units=units.watt)
|
37
|
+
self.NA = self._validate_units(NA, dimension="arbitrary", units=units.AU)
|
38
|
+
self.polarization = self._validate_source_polarization(polarization)
|
39
|
+
|
40
|
+
self.wavenumber = 2 * numpy.pi / self.wavelength
|
41
|
+
self.waist = self.wavelength / (self.NA * numpy.pi)
|
42
|
+
self.peak_intensity = 2 * self.optical_power / (numpy.pi * self.waist**2)
|
43
|
+
|
44
|
+
|
45
|
+
super().__init__(
|
46
|
+
wavelength=self.wavelength.to(units.meter).magnitude,
|
47
|
+
jones_vector=self.polarization.element[0],
|
48
|
+
optical_power=self.optical_power.to(units.watt).magnitude,
|
49
|
+
NA=self.NA.to(units.AU).magnitude,
|
50
|
+
)
|
51
|
+
|
52
|
+
def numerical_aperture_to_angle(self, numerical_aperture: float) -> float:
|
53
|
+
"""
|
54
|
+
Convert numerical aperture (NA) to angle in radians.
|
55
|
+
|
56
|
+
Parameters
|
57
|
+
----------
|
58
|
+
NA : float
|
59
|
+
The numerical aperture.
|
60
|
+
|
61
|
+
Returns
|
62
|
+
-------
|
63
|
+
float
|
64
|
+
The angle in radians.
|
65
|
+
"""
|
66
|
+
return numpy.arcsin(numerical_aperture) if numerical_aperture <= 1.0 else numpy.arcsin(numerical_aperture - 1) + numpy.pi / 2
|
67
|
+
|
68
|
+
def plot(self, color: str = 'red', opacity: float = 0.8, show_axis_label: bool = False) -> None:
|
69
|
+
"""
|
70
|
+
Plots the 3D structure of the Gaussian source.
|
71
|
+
|
72
|
+
This method creates a 3D plot of the Gaussian source, adds the structure to the plot,
|
73
|
+
and optionally displays axis labels.
|
74
|
+
|
75
|
+
Parameters
|
76
|
+
----------
|
77
|
+
color : str
|
78
|
+
The color of the structure in the plot. Default is 'red'.
|
79
|
+
opacity : float
|
80
|
+
The opacity of the structure. Default is 0.8.
|
81
|
+
show_axis_label : bool
|
82
|
+
If True, axis labels will be shown. Default is False.
|
83
|
+
|
84
|
+
"""
|
85
|
+
# Create a 3D plotting scene
|
86
|
+
scene = pyvista.Plotter()
|
87
|
+
|
88
|
+
# Add the structure to the scene
|
89
|
+
self._add_to_3d_ax(scene=scene, color=color, opacity=opacity)
|
90
|
+
|
91
|
+
# Add axes at the origin, optionally showing axis labels
|
92
|
+
scene.add_axes_at_origin(labels_off=not show_axis_label)
|
93
|
+
|
94
|
+
# Add a translucent sphere to the scene
|
95
|
+
sphere = pyvista.Sphere(radius=1)
|
96
|
+
scene.add_mesh(sphere, opacity=0.3)
|
97
|
+
|
98
|
+
# Display the scene
|
99
|
+
scene.show()
|
100
|
+
|
101
|
+
def _add_to_3d_ax(self, scene: pyvista.Plotter, color: str = 'red', opacity: float = 0.8) -> None:
|
102
|
+
"""
|
103
|
+
Adds a 3D cone representation to the given PyVista plotting scene.
|
104
|
+
|
105
|
+
The cone represents the acceptance angle determined by the numerical aperture (NA) of the system.
|
106
|
+
The cone is positioned at the origin and points downward along the z-axis.
|
107
|
+
|
108
|
+
Parameters
|
109
|
+
----------
|
110
|
+
scene : pyvista.Plotter
|
111
|
+
The 3D plotting scene to which the cone will be added.
|
112
|
+
color : str
|
113
|
+
The color of the cone mesh. Default is 'red'.
|
114
|
+
opacity : float
|
115
|
+
The opacity of the cone mesh. Default is 0.8.
|
116
|
+
|
117
|
+
"""
|
118
|
+
# Calculate the maximum angle from the numerical aperture (NA)
|
119
|
+
|
120
|
+
numerical_aperture = self.NA.magnitude
|
121
|
+
|
122
|
+
angle = self.numerical_aperture_to_angle(numerical_aperture)
|
123
|
+
|
124
|
+
max_angle = numpy.rad2deg(angle)
|
125
|
+
|
126
|
+
# Create the cone mesh
|
127
|
+
cone_mesh = pyvista.Cone(
|
128
|
+
center=(0.0, 0.0, -0.5),
|
129
|
+
direction=(0.0, 0.0, 1.0),
|
130
|
+
height=0.9,
|
131
|
+
resolution=100,
|
132
|
+
angle=max_angle
|
133
|
+
)
|
134
|
+
|
135
|
+
# Add the cone mesh to the scene
|
136
|
+
scene.add_mesh(cone_mesh, color=color, opacity=0.3)
|
137
|
+
# -
|
@@ -0,0 +1,97 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
import numpy
|
5
|
+
import pyvista
|
6
|
+
from PyMieSim import units
|
7
|
+
from PyMieSim.polarization import BasePolarization
|
8
|
+
from PyMieSim.binary.interface_source import PLANEWAVE
|
9
|
+
from PyMieSim.single.source.base import BaseSource
|
10
|
+
|
11
|
+
|
12
|
+
class PlaneWave(PLANEWAVE, BaseSource):
|
13
|
+
def __init__(self,
|
14
|
+
wavelength: units.Quantity,
|
15
|
+
polarization: units.Quantity | BasePolarization,
|
16
|
+
amplitude: units.Quantity) -> None:
|
17
|
+
"""
|
18
|
+
Initializes a plane wave light source for light scattering simulations.
|
19
|
+
|
20
|
+
Parameters
|
21
|
+
----------
|
22
|
+
wavelength : units.Quantity
|
23
|
+
Wavelength of the light field in meters.
|
24
|
+
polarization : BasePolarization | units.Quantity
|
25
|
+
Polarization state of the light field, if float is given it is assumed Linear polarization of angle theta.
|
26
|
+
amplitude : units.Quantity
|
27
|
+
Amplitude of the electric field.
|
28
|
+
"""
|
29
|
+
self.wavelength = self._validate_units(wavelength, dimension="distance", units=units.meter)
|
30
|
+
self.polarization = self._validate_source_polarization(polarization)
|
31
|
+
self.amplitude = self._validate_units(amplitude, dimension="amplitude", units=units.volt/units.meter)
|
32
|
+
|
33
|
+
self.wavenumber = 2 * numpy.pi / self.wavelength
|
34
|
+
|
35
|
+
super().__init__(
|
36
|
+
wavelength=self.wavelength.to(units.meter).magnitude,
|
37
|
+
jones_vector=self.polarization.element[0],
|
38
|
+
amplitude=self.amplitude.to(units.volt / units.meter).magnitude,
|
39
|
+
)
|
40
|
+
|
41
|
+
def plot(self, color: str = 'red', opacity: float = 0.8, show_axis_label: bool = False) -> None:
|
42
|
+
"""
|
43
|
+
Plots the 3D structure of the Gaussian source.
|
44
|
+
|
45
|
+
This method creates a 3D plot of the Gaussian source, adds the structure to the plot,
|
46
|
+
and optionally displays axis labels.
|
47
|
+
|
48
|
+
Parameters
|
49
|
+
----------
|
50
|
+
color : str
|
51
|
+
The color of the structure in the plot. Default is 'red'.
|
52
|
+
opacity : float
|
53
|
+
The opacity of the structure. Default is 0.8.
|
54
|
+
show_axis_label : bool
|
55
|
+
If True, axis labels will be shown. Default is False.
|
56
|
+
|
57
|
+
"""
|
58
|
+
# Create a 3D plotting scene
|
59
|
+
scene = pyvista.Plotter()
|
60
|
+
|
61
|
+
# Add the structure to the scene
|
62
|
+
self._add_to_3d_ax(scene=scene, color=color, opacity=opacity)
|
63
|
+
|
64
|
+
# Add axes at the origin, optionally showing axis labels
|
65
|
+
scene.add_axes_at_origin(labels_off=not show_axis_label)
|
66
|
+
|
67
|
+
# Display the scene
|
68
|
+
scene.show()
|
69
|
+
|
70
|
+
def _add_to_3d_ax(self, scene: pyvista.Plotter, color: str = 'red', opacity: float = 0.8) -> None:
|
71
|
+
"""
|
72
|
+
Adds a 3D cone representation to the given PyVista plotting scene.
|
73
|
+
|
74
|
+
The cylinder represents the acceptance angle determined by the numerical aperture (NA) of the system.
|
75
|
+
The cylinder is positioned at the origin and points downward along the z-axis.
|
76
|
+
|
77
|
+
Parameters
|
78
|
+
----------
|
79
|
+
scene : pyvista.Plotter
|
80
|
+
The 3D plotting scene to which the cone will be added.
|
81
|
+
color : str
|
82
|
+
The color of the cone mesh. Default is 'red'.
|
83
|
+
opacity : float
|
84
|
+
The opacity of the cone mesh. Default is 0.8.
|
85
|
+
|
86
|
+
"""
|
87
|
+
# Define the cylinder parameters
|
88
|
+
cylinder_mesh = pyvista.Cylinder(
|
89
|
+
center=(0.0, 0.0, 0.5),
|
90
|
+
direction=(0.0, 0.0, -1.0),
|
91
|
+
radius=0.2,
|
92
|
+
height=1.0,
|
93
|
+
resolution=100
|
94
|
+
)
|
95
|
+
|
96
|
+
# Add the cone mesh to the scene
|
97
|
+
scene.add_mesh(cylinder_mesh, color='blue', opacity=0.3)
|
@@ -0,0 +1,81 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
import numpy
|
5
|
+
|
6
|
+
|
7
|
+
def cartesian_to_spherical(x: numpy.ndarray, y: numpy.ndarray, z: numpy.ndarray) -> tuple:
|
8
|
+
"""
|
9
|
+
Convert Cartesian coordinates to spherical coordinates.
|
10
|
+
|
11
|
+
Parameters
|
12
|
+
----------
|
13
|
+
x : numpy.ndarray
|
14
|
+
The x coordinates.
|
15
|
+
y : numpy.ndarray
|
16
|
+
The y coordinates.
|
17
|
+
z : numpy.ndarray
|
18
|
+
The z coordinates.
|
19
|
+
|
20
|
+
Returns
|
21
|
+
-------
|
22
|
+
numpy.ndarray
|
23
|
+
The spherical coordinates (r, phi, theta).
|
24
|
+
"""
|
25
|
+
r = numpy.sqrt(x**2 + y**2 + z**2)
|
26
|
+
phi = numpy.arcsin(z / r)
|
27
|
+
theta = numpy.arctan2(y, x)
|
28
|
+
return r, phi, theta
|
29
|
+
|
30
|
+
|
31
|
+
def spherical_to_cartesian(phi: numpy.ndarray, theta: numpy.ndarray, r: numpy.ndarray = None) -> tuple:
|
32
|
+
"""
|
33
|
+
Convert spherical coordinates to Cartesian coordinates.
|
34
|
+
|
35
|
+
Parameters
|
36
|
+
----------
|
37
|
+
phi : numpy.ndarray
|
38
|
+
The phi angles.
|
39
|
+
theta : numpy.ndarray
|
40
|
+
The theta angles.
|
41
|
+
r : numpy.ndarray
|
42
|
+
The radial distances; defaults to unit radius if None.
|
43
|
+
|
44
|
+
Returns
|
45
|
+
-------
|
46
|
+
numpy.ndarray
|
47
|
+
The Cartesian coordinates (x, y, z).
|
48
|
+
"""
|
49
|
+
if r is None:
|
50
|
+
r = numpy.ones_like(phi)
|
51
|
+
|
52
|
+
x = r * numpy.cos(phi) * numpy.cos(theta)
|
53
|
+
y = r * numpy.cos(phi) * numpy.sin(theta)
|
54
|
+
z = r * numpy.sin(phi)
|
55
|
+
return x, y, z
|
56
|
+
|
57
|
+
|
58
|
+
def rotate_on_x(phi: numpy.ndarray, theta: numpy.ndarray, angle: float) -> tuple:
|
59
|
+
"""Rotate spherical coordinates around the X-axis.
|
60
|
+
|
61
|
+
Parameters
|
62
|
+
----------
|
63
|
+
phi : numpy.ndarray
|
64
|
+
Azimuthal angles in radians.
|
65
|
+
theta : numpy.ndarray
|
66
|
+
Polar angles in radians.
|
67
|
+
angle : float
|
68
|
+
Rotation angle about the X-axis, in radians.
|
69
|
+
|
70
|
+
Returns
|
71
|
+
-------
|
72
|
+
tuple
|
73
|
+
The rotated spherical coordinates ``(r, phi, theta)``.
|
74
|
+
"""
|
75
|
+
# Convert to Cartesian for rotation
|
76
|
+
x, y, z = spherical_to_cartesian(phi, theta)
|
77
|
+
# Apply rotation around the X-axis
|
78
|
+
yp = y * numpy.cos(angle) - z * numpy.sin(angle)
|
79
|
+
zp = y * numpy.sin(angle) + z * numpy.cos(angle)
|
80
|
+
# Convert back to spherical coordinates
|
81
|
+
return cartesian_to_spherical(x, yp, zp)
|
PyMieSim/units.py
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
from PyOptik.units import ureg
|
3
|
+
import pint as _pint
|
4
|
+
import pint_pandas as pint
|
5
|
+
|
6
|
+
_pint.set_application_registry(ureg)
|
7
|
+
|
8
|
+
|
9
|
+
# Define a list of base units to scale
|
10
|
+
BASE_UNITS = [
|
11
|
+
'watt', 'volt', 'meter', 'second', 'liter', 'hertz', 'ohm', 'ampere'
|
12
|
+
]
|
13
|
+
|
14
|
+
# Define prefixes for scaling units
|
15
|
+
SCALES = ['nano', 'micro', 'milli', '', 'kilo', 'mega']
|
16
|
+
|
17
|
+
|
18
|
+
def initialize_registry(ureg: Optional[object] = None):
|
19
|
+
"""
|
20
|
+
Initialize and set up a unit registry. This function also leaks
|
21
|
+
the units into the global namespace for easy access throughout
|
22
|
+
the module.
|
23
|
+
|
24
|
+
Parameters
|
25
|
+
----------
|
26
|
+
ureg: Optional[pint.UnitRegistry]
|
27
|
+
A UnitRegistry object to use. If None, the default PintType.ureg will be used.
|
28
|
+
"""
|
29
|
+
|
30
|
+
# If no unit registry is provided, use the default
|
31
|
+
ureg = ureg or pint.PintType.ureg
|
32
|
+
|
33
|
+
# Set up matplotlib integration and unit formatting
|
34
|
+
ureg.setup_matplotlib()
|
35
|
+
ureg.formatter.default_format = '~P' # Compact format without units like 'meter'
|
36
|
+
|
37
|
+
# Leak scaled units into the global namespace
|
38
|
+
for unit in BASE_UNITS:
|
39
|
+
for scale in SCALES:
|
40
|
+
scaled_unit_name = scale + unit
|
41
|
+
globals()[scaled_unit_name] = getattr(ureg, scaled_unit_name)
|
42
|
+
|
43
|
+
# Leak commonly used specific units into the global namespace
|
44
|
+
common_units = {
|
45
|
+
'farad': ureg.farad,
|
46
|
+
'joule': ureg.joule,
|
47
|
+
'coulomb': ureg.coulomb,
|
48
|
+
'power': ureg.watt.dimensionality,
|
49
|
+
'kelvin': ureg.kelvin,
|
50
|
+
'celsius': ureg.celsius,
|
51
|
+
'particle': ureg.particle,
|
52
|
+
'RIU': ureg.refractive_index_unit,
|
53
|
+
'refractive_index_unit': ureg.refractive_index_unit,
|
54
|
+
'degree': ureg.degree,
|
55
|
+
'radian': ureg.radian,
|
56
|
+
'steradian': ureg.steradian,
|
57
|
+
'AU': ureg.dimensionless,
|
58
|
+
'distance': ureg.meter.dimensionality,
|
59
|
+
'time': ureg.second.dimensionality,
|
60
|
+
'volume': ureg.liter.dimensionality,
|
61
|
+
'frequency': ureg.hertz.dimensionality,
|
62
|
+
'Quantity': ureg.Quantity
|
63
|
+
}
|
64
|
+
|
65
|
+
# Leak the common units into the global namespace
|
66
|
+
globals().update(common_units)
|
67
|
+
|
68
|
+
globals()['ureg'] = ureg
|
69
|
+
|
70
|
+
|
71
|
+
initialize_registry()
|
72
|
+
|
73
|
+
|
74
|
+
class UnitsValidation():
|
75
|
+
def _validate_units(cls, value, dimension: str, units: str):
|
76
|
+
"""
|
77
|
+
Ensures that diameter is Quantity objects with power units.
|
78
|
+
"""
|
79
|
+
|
80
|
+
if not isinstance(value, ureg.Quantity):
|
81
|
+
if units == ureg.dimensionless:
|
82
|
+
return value * ureg.dimensionless
|
83
|
+
|
84
|
+
raise ValueError(f"{value} must be a Quantity instance, use PyMieSim.units module.")
|
85
|
+
|
86
|
+
if not value.check(units):
|
87
|
+
raise ValueError(f"{value} must be a Quantity with {dimension} units [{units}] but has {value.units}.")
|
88
|
+
|
89
|
+
return value
|
90
|
+
|
91
|
+
def _validate_source_polarization(cls, value):
|
92
|
+
"""
|
93
|
+
Ensures that polarization is well defined.
|
94
|
+
"""
|
95
|
+
from PyMieSim.polarization import BasePolarization, Linear
|
96
|
+
|
97
|
+
if isinstance(value, BasePolarization):
|
98
|
+
return value
|
99
|
+
|
100
|
+
if isinstance(value, ureg.Quantity):
|
101
|
+
value = cls._validate_units(value, dimension='angle', units=ureg.degree)
|
102
|
+
return Linear(value)
|
103
|
+
|
104
|
+
raise ValueError(f"{value} must be a Linear or a Quantity with degree units.")
|
105
|
+
|
106
|
+
def _validate_property(cls, value):
|
107
|
+
"""
|
108
|
+
Ensures that diameter is Quantity objects with RIU units.
|
109
|
+
"""
|
110
|
+
from PyOptik.material.base_class import BaseMaterial
|
111
|
+
|
112
|
+
if not isinstance(value, ureg.Quantity | BaseMaterial):
|
113
|
+
raise ValueError(f"{value} must be a Quantity (RIU units) or a material (BaseMaterial from PyOptik).")
|
114
|
+
|
115
|
+
return value
|
116
|
+
|
117
|
+
def _validate_detector_polarization_units(cls, value):
|
118
|
+
"""
|
119
|
+
Ensures that medium_refractive_index, and rotation are Quantity objects with angle units.
|
120
|
+
Converts them to numpy arrays after validation.
|
121
|
+
"""
|
122
|
+
import numpy
|
123
|
+
|
124
|
+
if value is None:
|
125
|
+
value = numpy.nan * ureg.degree
|
126
|
+
|
127
|
+
if not isinstance(value, ureg.Quantity) or not value.check(ureg.refractive_index_unit):
|
128
|
+
raise ValueError(f"{value} must be a Quantity with refractive index units [RIU].")
|
129
|
+
|
130
|
+
return value
|