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,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,3 @@
1
+ from .base import BaseSource
2
+ from .gaussian import Gaussian
3
+ from .planewave import PlaneWave
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from PyMieSim import units
5
+
6
+ class BaseSource(units.UnitsValidation):
7
+ pass
@@ -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