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,98 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from PyOptik.material.base_class import BaseMaterial
5
+ from pydantic import field_validator
6
+ from PyMieSim.units import Quantity, meter, RIU
7
+ from pint_pandas import PintArray
8
+ from PyMieSim.binary.interface_sets import CppScattererProperties, CppMediumProperties
9
+ import numpy
10
+
11
+
12
+ class BaseScatterer():
13
+ """
14
+ Base class for scatterer objects. This class handles the initialization and setup of
15
+ scatterer parameters for use in PyMieSim simulations.
16
+
17
+ """
18
+ mapping = None
19
+ binding_kwargs = None
20
+ binding = None
21
+
22
+ @field_validator('property', 'core_property', 'shell_property', 'medium_property', mode='plain')
23
+ def _validate_array(cls, value):
24
+ """Ensure that arrays are properly converted to numpy arrays."""
25
+ value = numpy.atleast_1d(value)
26
+
27
+ if isinstance(value, Quantity):
28
+ assert value.check(RIU), f"{value} must be a Quantity with refractive index units (RIU)."
29
+ return value
30
+
31
+ assert numpy.all([isinstance(p, BaseMaterial) for p in value]), f"{value} must be either a refractive index quantity (RIU) or BaseMaterial."
32
+
33
+ return value
34
+
35
+ @field_validator('diameter', 'core_diameter', 'shell_thickness', mode='plain')
36
+ def _validate_length_quantity(cls, value):
37
+ """
38
+ Ensures that diameter is Quantity objects with length units.
39
+ """
40
+ value = numpy.atleast_1d(value)
41
+
42
+ if not isinstance(value, Quantity) or not value.check(meter):
43
+ raise ValueError(f"{value} must be a Quantity with meters units.")
44
+
45
+ return value
46
+
47
+ def _add_properties(self, name: str, properties: Quantity | BaseMaterial) -> None:
48
+ """
49
+ Determines whether the provided property is a refractive index (Quantity) or a material (BaseMaterial),
50
+ and returns the corresponding values.
51
+
52
+ Parameters
53
+ ----------
54
+ property : Quantity or BaseMaterial
55
+ The core property to be assigned, which can either be a refractive index (Quantity) or a material (BaseMaterial).
56
+
57
+ Raises
58
+ ------
59
+ ValueError:
60
+ If the provided property is neither a Quantity (refractive index) nor a BaseMaterial.
61
+ """
62
+ CPPClass = CppMediumProperties if name == 'medium' else CppScattererProperties
63
+
64
+ if all(isinstance(item, Quantity) for item in properties):
65
+ instance = CPPClass(index_properties=properties.magnitude)
66
+ self.binding_kwargs[f'{name}_properties'] = instance
67
+
68
+ return instance
69
+
70
+ elif all(isinstance(item, BaseMaterial) for item in properties):
71
+ eval_index = numpy.asarray([m.compute_refractive_index(self.source.wavelength.to_base_units().magnitude) for m in properties])
72
+ instance = CPPClass(material_properties=eval_index)
73
+ self.binding_kwargs[f'{name}_properties'] = instance
74
+
75
+ return instance
76
+
77
+ else:
78
+ raise TypeError("All elements in the list must be of type 'Quantity' or 'BaseMaterial', not a mix of both.")
79
+
80
+ def _generate_mapping(self) -> None:
81
+ """
82
+ Constructs a table of the scatterer's properties formatted for data visualization.
83
+ This method populates the `mapping` dictionary with user-friendly descriptions and formats of the scatterer properties.
84
+
85
+ Returns
86
+ -------
87
+ list
88
+ A list of visual representations for each property in the `mapping` dictionary that has been populated.
89
+ """
90
+ for attr in self.attributes:
91
+ values = getattr(self, attr)
92
+ if values is None:
93
+ continue
94
+
95
+ if hasattr(values, 'magnitude'):
96
+ self.mapping["scatterer:" + attr] = PintArray(values.magnitude, dtype=values.units)
97
+ else:
98
+ self.mapping["scatterer:" + attr] = [repr(m) for m in values]
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from pydantic.dataclasses import dataclass
5
+ from typing import List
6
+ from PyMieSim.binary.interface_sets import CppCoreShellSet
7
+ from PyOptik.material.base_class import BaseMaterial
8
+ from PyMieSim.units import Quantity
9
+ from PyMieSim.experiment.scatterer.base import BaseScatterer
10
+ from PyMieSim.experiment.source.base import BaseSource
11
+ from PyMieSim.experiment.utils import config_dict, Sequential
12
+
13
+
14
+ @dataclass(config=config_dict, kw_only=True)
15
+ class CoreShell(BaseScatterer, Sequential):
16
+ """
17
+ A data class representing a core-shell scatterer configuration used in PyMieSim simulations.
18
+
19
+ This class facilitates the setup and manipulation of core-shell scatterers by providing structured
20
+ attributes and methods that ensure the scatterers are configured correctly for simulations.
21
+ It extends the BaseScatterer class, adding specific attributes and methods relevant to core-shell geometries.
22
+
23
+ Parameters
24
+ ----------
25
+ source : PyMieSim.experiment.source.base.BaseSource
26
+ Light source configuration for the simulation.
27
+ core_diameter : Quantity
28
+ Diameters of the core components.
29
+ shell_thickness : Quantity
30
+ Thicknesses of the shell components.
31
+ core_property : List[BaseMaterial] | List[Quantity]
32
+ Refractive index or indices of the core.
33
+ shell_property : List[BaseMaterial] | List[Quantity]
34
+ Refractive index or indices of the shell.
35
+ medium_property : List[BaseMaterial] | List[Quantity]
36
+ BaseMaterial(s) defining the medium, used if `medium_index` is not provided.
37
+
38
+ """
39
+ source: BaseSource
40
+ core_diameter: Quantity
41
+ shell_thickness: Quantity
42
+ core_property: List[BaseMaterial] | List[Quantity]
43
+ shell_property: List[BaseMaterial] | List[Quantity]
44
+ medium_property: List[BaseMaterial] | List[Quantity]
45
+
46
+ available_measure_list = [
47
+ 'Qsca', 'Qext', 'Qabs', 'Qratio', 'Qforward', 'Qback', 'Qpr',
48
+ 'Csca', 'Cext', 'Cabs', 'Cratio', 'Cforward', 'Cback', 'Cpr', 'a1',
49
+ 'a2', 'a3', 'b1', 'b2', 'b3', 'g', 'coupling',
50
+ ]
51
+
52
+ attributes = ['core_diameter', 'shell_thickness', 'core_property', 'shell_property', 'medium_property']
53
+
54
+ def _generate_binding(self) -> None:
55
+ """
56
+ Assembles the keyword arguments necessary for C++ binding, tailored for core-shell scatterers.
57
+ Prepares structured data from scatterer properties for efficient simulation integration.
58
+
59
+ This function populates `binding_kwargs` with values formatted appropriately for the C++ backend used in simulations.
60
+ """
61
+ self.mapping = {}
62
+
63
+ self.binding_kwargs = dict(
64
+ core_diameter=self.core_diameter,
65
+ shell_thickness=self.shell_thickness,
66
+ is_sequential=self.is_sequential
67
+ )
68
+
69
+ mediun_properties = self._add_properties(name='medium', properties=self.medium_property)
70
+
71
+ core_properties = self._add_properties(name='core', properties=self.core_property)
72
+
73
+ shell_properties = self._add_properties(name='shell', properties=self.shell_property)
74
+
75
+ self.binding = CppCoreShellSet(
76
+ core_diameter=self.core_diameter.to('meter').magnitude,
77
+ shell_thickness=self.shell_thickness.to('meter').magnitude,
78
+ core_properties=core_properties,
79
+ shell_properties=shell_properties,
80
+ medium_properties=mediun_properties,
81
+ is_sequential=self.is_sequential,
82
+ )
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from typing import List
5
+ from PyMieSim.units import Quantity
6
+ from pydantic.dataclasses import dataclass
7
+ from PyMieSim.binary.interface_sets import CppCylinderSet
8
+ from PyOptik.material.base_class import BaseMaterial
9
+ from PyMieSim.experiment.scatterer.base import BaseScatterer
10
+ from PyMieSim.experiment.source.base import BaseSource
11
+ from PyMieSim.experiment.utils import config_dict, Sequential
12
+
13
+
14
+ @dataclass(config=config_dict, kw_only=True)
15
+ class Cylinder(BaseScatterer, Sequential):
16
+ """
17
+ Represents a cylindrical scatterer configuration for PyMieSim simulations.
18
+
19
+ Parameters
20
+ ----------
21
+ source : PyMieSim.experiment.source.base.BaseSource
22
+ Light source configuration for the simulation.
23
+ diameter : Quantity
24
+ Diameter(s) of the cylinder in meters.
25
+ property : List[BaseMaterial] | List[Quantity]
26
+ Refractive index or indices of the spherical scatterers themselves.
27
+ medium_property : List[BaseMaterial] | List[Quantity]
28
+ BaseMaterial(s) defining the medium, used if `medium_index` is not provided.
29
+ """
30
+ source: BaseSource
31
+ diameter: Quantity
32
+ property: List[BaseMaterial] | List[Quantity]
33
+ medium_property: List[BaseMaterial] | List[Quantity]
34
+
35
+ available_measure_list = [
36
+ 'Qsca', 'Qext', 'Qabs', 'Csca', 'Cext', 'Cabs', 'a11',
37
+ 'a21', 'a12', 'a22', 'a13', 'a23', 'b11', 'b21', 'b12',
38
+ 'b22', 'b13', 'b23', 'coupling',
39
+ ]
40
+
41
+ attributes = ['diameter', 'property', 'medium_property']
42
+
43
+ def _generate_binding(self) -> None:
44
+ """
45
+ Constructs the keyword arguments necessary for the C++ binding interface, specifically tailored for spherical scatterers.
46
+ This includes processing material indices and organizing them into a structured dictionary for simulation interaction.
47
+
48
+ This method automatically configures the `binding_kwargs` attribute with appropriately formatted values.
49
+ """
50
+ self.mapping = {}
51
+
52
+ self.binding_kwargs = dict(diameter=self.diameter, is_sequential=self.is_sequential)
53
+
54
+ mediun_properties = self._add_properties(name='medium', properties=self.medium_property)
55
+
56
+ scatterer_properties = self._add_properties(name='scatterer', properties=self.property)
57
+
58
+ self.binding = CppCylinderSet(
59
+ diameter=self.diameter.to('meter').magnitude,
60
+ scatterer_properties=scatterer_properties,
61
+ medium_properties=mediun_properties,
62
+ is_sequential=self.is_sequential,
63
+ )
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ from typing import List
4
+ from pydantic.dataclasses import dataclass
5
+ from PyMieSim.binary.interface_sets import CppSphereSet
6
+ from PyOptik.material.base_class import BaseMaterial
7
+ from PyMieSim.units import Quantity
8
+ from PyMieSim.experiment.scatterer.base import BaseScatterer
9
+ from PyMieSim.experiment.source.base import BaseSource
10
+ from PyMieSim.experiment.utils import config_dict, Sequential
11
+
12
+
13
+ @dataclass(config=config_dict, kw_only=True)
14
+ class Sphere(BaseScatterer, Sequential):
15
+ """
16
+ A data class that represents a spherical scatterer configuration used in PyMieSim simulations.
17
+
18
+ This class provides specific implementations for setting up and binding spherical scatterers
19
+ with their properties to a simulation environment. It extends the `BaseScatterer` class by
20
+ adding spherical-specific attributes and methods for handling simulation setups.
21
+
22
+ Parameters
23
+ ----------
24
+ source : PyMieSim.experiment.source.base.BaseSource
25
+ Light source configuration for the simulation.
26
+ diameter : Quantity
27
+ Diameter(s) of the spherical scatterers in meters.
28
+ property : List[BaseMaterial] | List[Quantity]
29
+ Refractive index or indices of the spherical scatterers themselves.
30
+ medium_property : List, optional
31
+ BaseMaterial(s) defining the medium, used if `medium_index` is not provided.
32
+ """
33
+ source: BaseSource
34
+ diameter: Quantity
35
+ property: List[BaseMaterial] | List[Quantity]
36
+ medium_property: List[BaseMaterial] | List[Quantity]
37
+
38
+ available_measure_list = [
39
+ 'Qsca', 'Qext', 'Qabs', 'Qratio', 'Qforward', 'Qback', 'Qpr',
40
+ 'Csca', 'Cext', 'Cabs', 'Cratio', 'Cforward', 'Cback', 'Cpr', 'a1',
41
+ 'a2', 'a3', 'b1', 'b2', 'b3', 'g', 'coupling',
42
+ ]
43
+
44
+ attributes = ['diameter', 'property', 'medium_property']
45
+
46
+ def _generate_binding(self):
47
+ """
48
+ Constructs the keyword arguments necessary for the C++ binding interface, specifically tailored for spherical scatterers.
49
+ This includes processing material indices and organizing them into a structured dictionary for simulation interaction.
50
+
51
+ This method automatically configures the `binding_kwargs` attribute with appropriately formatted values.
52
+ """
53
+ self.mapping = {}
54
+
55
+ self.binding_kwargs = dict(diameter=self.diameter, is_sequential=self.is_sequential)
56
+
57
+ mediun_properties = self._add_properties(name='medium', properties=self.medium_property)
58
+
59
+ scatterer_properties = self._add_properties(name='scatterer', properties=self.property)
60
+
61
+ self.binding = CppSphereSet(
62
+ diameter=self.diameter.to('meter').magnitude,
63
+ scatterer_properties=scatterer_properties,
64
+ medium_properties=mediun_properties,
65
+ is_sequential=self.is_sequential,
66
+ )
@@ -0,0 +1,356 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import numpy
5
+ import pandas as pd
6
+ from pydantic.dataclasses import dataclass
7
+ from PyMieSim.binary.interface_experiment import EXPERIMENT
8
+ from PyMieSim.units import AU, meter, watt
9
+ from typing import Union, Optional, List
10
+ from PyMieSim.experiment.scatterer import Sphere, Cylinder, CoreShell
11
+ from PyMieSim.experiment.detector import Photodiode, CoherentMode
12
+ from PyMieSim.experiment.source import Gaussian, PlaneWave
13
+ from PyMieSim.experiment.dataframe_subclass import PyMieSimDataFrame
14
+ import PyMieSim
15
+ from PyMieSim.binary.interface_sets import CppDetectorSet
16
+
17
+
18
+ class EmptyDetector:
19
+ def __init__(self):
20
+ self.binding = CppDetectorSet()
21
+ self.mapping = {}
22
+
23
+ def _generate_binding(self, *args, **kwargs):
24
+ pass
25
+
26
+ def _generate_mapping(self, *args, **kwargs):
27
+ pass
28
+
29
+ @dataclass
30
+ class Setup:
31
+ """
32
+ Orchestrates the setup and execution of light scattering experiments using PyMieSim.
33
+
34
+ Attributes:
35
+ scatterer (Union[Sphere, Cylinder, CoreShell]): Configuration for the scatterer in the experiment.
36
+ Defines the physical properties of the particle being studied.
37
+ source (Union[Gaussian, PlaneWave]): Configuration for the light source. Specifies the characteristics
38
+ of the light (e.g., wavelength, polarization) illuminating the scatterer.
39
+ detector (Union[Photodiode, CoherentMode, None], optional): Configuration for the detector, if any. Details the
40
+ method of detection for scattered light, including positional and analytical parameters. Defaults to None.
41
+
42
+ Methods provide functionality for initializing bindings, generating parameter tables for visualization,
43
+ and executing the simulation to compute and retrieve specified measures.
44
+ """
45
+ scatterer: Union[Sphere, Cylinder, CoreShell]
46
+ source: Union[Gaussian, PlaneWave]
47
+ detector: Optional[Union[Photodiode, CoherentMode]] = EmptyDetector()
48
+
49
+ def __post_init__(self):
50
+ """
51
+ Initializes the experiment by setting the source for the scatterer and establishing bindings
52
+ between the components and the simulation environment.
53
+ """
54
+ self.scatterer._generate_binding()
55
+
56
+ self.source._generate_binding()
57
+
58
+ self.detector._generate_binding()
59
+
60
+ self.scatterer.source = self.source
61
+
62
+ self.binding = EXPERIMENT(debug_mode=PyMieSim.debug_mode)
63
+
64
+ def _generate_mapping(self) -> None:
65
+ self.source._generate_mapping()
66
+ self.scatterer._generate_mapping()
67
+ self.detector._generate_mapping()
68
+
69
+ def get_sequential(self, measure: str) -> numpy.ndarray:
70
+ """
71
+ Executes the simulation to compute and retrieve the specified measures in a sequential manner contrary to the standard get function.
72
+ This means that ranges are not iterated in a structured fashion into a dataframe but are only run once.
73
+ This methods was developed for specific aims, for the best suited vizualization tools go for the .get() method.
74
+
75
+ Parameters
76
+ ----------
77
+ measures : str
78
+ The measure to be computed in the simulation, provided as arguments by the user.
79
+ Returns
80
+ -------
81
+ numpy.ndarray
82
+ An array containing the computed measures.
83
+ """
84
+ method_name = f'get_{measure}_sequential'
85
+
86
+ # Compute the values using the binding method
87
+ return getattr(self.binding, method_name)(
88
+ scatterer_set=self.scatterer.binding,
89
+ source_set=self.source.binding,
90
+ detector_set=self.detector.binding
91
+ )
92
+
93
+ def get(self, *measures, scale_unit: bool = False, drop_unique_level: bool = True, add_units: bool = True, as_numpy: bool = False) -> pd.DataFrame:
94
+ """
95
+ Run the simulation to compute specified measures and return the results.
96
+
97
+ Parameters
98
+ ----------
99
+ measures : tuple
100
+ Variable-length tuple of measure names to compute, specified by the user.
101
+ scale_unit : bool, optional
102
+ If True, scales the units in the output DataFrame to the most appropriate units. Defaults to False.
103
+ drop_unique_level : bool, optional
104
+ If True, removes levels from the DataFrame's MultiIndex where only one unique value exists, simplifying the DataFrame. Defaults to True.
105
+ add_units : bool, optional
106
+ If True, includes units in the output DataFrame using pint quantities. If False, the output will contain raw numeric values only. Defaults to True.
107
+ as_numpy : bool, optional
108
+ If True, returns the result as a NumPy array instead of a DataFrame. Defaults to False.
109
+
110
+ Returns
111
+ -------
112
+ pd.DataFrame or np.ndarray
113
+ A DataFrame containing the computed measures with optional units and simplified MultiIndex, or a NumPy array if `as_numpy` is set to True.
114
+ """
115
+ measures = set(numpy.atleast_1d(measures))
116
+
117
+ if 'coupling' in measures:
118
+ assert self.detector is not None, "To compute the coupling power the detector has to be provided to Setup class"
119
+
120
+ if as_numpy:
121
+ return self._get_measure_array(measures)
122
+
123
+ is_complex = self._is_complex_measure(measures)
124
+
125
+ df = self.generate_dataframe(
126
+ measure=list(measures),
127
+ is_complex=is_complex,
128
+ drop_unique_level=drop_unique_level
129
+ )
130
+
131
+ df = self._get_measure_dataframe(df, measures, is_complex, add_units)
132
+
133
+ # Optionally scale the units in the DataFrame
134
+ if scale_unit:
135
+ df = self.scale_dataframe_units(df)
136
+
137
+ return df
138
+
139
+ def _is_complex_measure(self, measures: set) -> bool:
140
+ """
141
+ Determines if the measures involve complex values based on measure names.
142
+ No complex value are computed now, as the a & b parameters a return as absolute values for convienience.
143
+
144
+ Parameters
145
+ ----------
146
+ measures : set
147
+ A set of measures requested for computation.
148
+
149
+ Returns
150
+ -------
151
+ bool
152
+ True if any measure involves complex values, False otherwise.
153
+ """
154
+ return False
155
+
156
+ def _get_measure_array(self, measures: List[str]) -> pd.DataFrame:
157
+ """
158
+ Retrieve data for specified measures and return them as a NumPy array.
159
+
160
+ Parameters
161
+ ----------
162
+ measures : List[str]
163
+ List of measure names to compute.
164
+
165
+ Returns
166
+ -------
167
+ np.ndarray
168
+ A NumPy array containing computed values for the specified measures.
169
+ """
170
+ output_array = []
171
+
172
+ for measure in measures:
173
+ method_name = f'get_{measure}'
174
+
175
+ method = getattr(self.binding, method_name)
176
+
177
+ array = method(
178
+ scatterer_set=self.scatterer.binding,
179
+ source_set=self.source.binding,
180
+ detector_set=self.detector.binding
181
+ )
182
+
183
+ output_array.append(array)
184
+
185
+ return numpy.asarray(output_array).squeeze()
186
+
187
+ def _get_measure_dataframe(self, df: pd.DataFrame, measures: List[str], is_complex: bool, add_units: bool) -> pd.DataFrame:
188
+ """
189
+ Populate a DataFrame with computed values for specified measures.
190
+
191
+ Parameters
192
+ ----------
193
+ df : pd.DataFrame
194
+ DataFrame in which to store computed values for the specified measures.
195
+ measures : List[str]
196
+ List of measure names to compute and add to the DataFrame.
197
+ is_complex : bool
198
+ Indicates whether any of the measures involve complex values.
199
+ add_units : bool
200
+ If True, adds units to the computed values in the DataFrame.
201
+
202
+ Returns
203
+ -------
204
+ pd.DataFrame
205
+ Updated DataFrame with computed values for the specified measures, with optional units if `add_units` is True.
206
+ """
207
+ for measure in measures:
208
+
209
+ method_name = f'get_{measure}'
210
+
211
+ # Compute the values using the binding method
212
+ array = getattr(self.binding, method_name)(
213
+ scatterer_set=self.scatterer.binding,
214
+ source_set=self.source.binding,
215
+ detector_set=self.detector.binding
216
+ )
217
+
218
+ # Determine the unit based on the measure type
219
+ dtype = self._determine_dtype(measure)
220
+
221
+ # Set the data in the DataFrame
222
+ df = self._set_data(
223
+ measure=measure,
224
+ dataframe=df,
225
+ array=array,
226
+ dtype=dtype,
227
+ is_complex=is_complex,
228
+ add_units=add_units
229
+ )
230
+
231
+ return df
232
+
233
+ def _determine_dtype(self, measure: str):
234
+ """
235
+ Determine the appropriate unit (dtype) based on the measure's first character.
236
+
237
+ Parameters
238
+ ----------
239
+ measure : str
240
+ The measure type to determine the unit for.
241
+
242
+ Returns
243
+ -------
244
+ pint.Quantity
245
+ The appropriate unit for the measure.
246
+ """
247
+ match measure[0]:
248
+ case 'C':
249
+ return meter**2 # Cross-section measure
250
+ case 'c':
251
+ assert self.detector is not None, "Detector needs to be defined in order to measure coupling"
252
+ return watt # Power measure
253
+ case _:
254
+ return AU # Arbitrary units for other measures
255
+
256
+ def _set_data(self, measure: str, dataframe: pd.DataFrame, array: numpy.ndarray, dtype: type, is_complex: bool, add_units: bool) -> None:
257
+ """
258
+ Sets the real and imaginary parts of a NumPy array as separate 'real' and 'imag' levels
259
+ in the 'type' index of a pandas DataFrame.
260
+
261
+ Parameters
262
+ ----------
263
+ dataframe : pd.DataFrame
264
+ The target DataFrame with a MultiIndex that includes a 'type' level (with possible values 'real' and 'imag').
265
+ array : np.ndarray
266
+ A complex NumPy array whose real and imaginary parts will be assigned to the DataFrame.
267
+ dtype : type
268
+ The data type to cast the real and imaginary parts into, using PintArrays.
269
+ is_complex : bool
270
+ A flag that indicates whether the input array is complex. If False, the 'type' level is not saved, and the array is treated as purely real.
271
+ """
272
+ dtype = f'pint[{dtype}]' if add_units else float
273
+
274
+ if is_complex:
275
+ dataframe[(measure, 'real')] = pd.Series(array.ravel().real, dtype=dtype, index=dataframe.index)
276
+ dataframe[(measure, 'imag')] = pd.Series(array.ravel().imag, dtype=dtype, index=dataframe.index)
277
+
278
+ else:
279
+ dataframe[measure] = pd.Series(array.ravel(), dtype=dtype, index=dataframe.index)
280
+
281
+ return dataframe
282
+
283
+ def scale_dataframe_units(self, dataframe: pd.DataFrame) -> pd.DataFrame:
284
+ """
285
+ Scales the units of all columns in a pandas DataFrame to the most compact unit
286
+ based on the maximum value in each column.
287
+
288
+ This method iterates over each column in the DataFrame, retrieves the maximum value's
289
+ unit, and converts the entire column to that unit using pint's unit system.
290
+
291
+ Parameters
292
+ ----------
293
+ dataframe : pd.DataFrame
294
+ A DataFrame where each column contains PintArray elements with units.
295
+
296
+ Returns
297
+ -------
298
+ pd.DataFrame
299
+ The updated DataFrame with all columns scaled to the most compact unit based on the maximum value in each column.
300
+ """
301
+ max_value_unit = dataframe.max().max().to_compact().units
302
+
303
+ for name, col in dataframe.items():
304
+ dataframe[name] = col.pint.to(max_value_unit)
305
+
306
+ return dataframe
307
+
308
+ def generate_dataframe(self, measure, is_complex: bool = False, drop_unique_level: bool = True):
309
+ """
310
+ Generates a pandas DataFrame with a MultiIndex based on the mapping
311
+ of 'source' and 'scatterer' from an experiment object.
312
+
313
+ Parameters
314
+ ----------
315
+ experiment :
316
+ The experiment object containing 'source' and 'scatterer' mappings.
317
+
318
+ Returns
319
+ -------
320
+ pd.DataFrame
321
+ A DataFrame with a MultiIndex created from the experiment mappings.
322
+ """
323
+ self._generate_mapping()
324
+
325
+ iterables = dict()
326
+
327
+ iterables.update(self.source.mapping)
328
+
329
+ iterables.update(self.scatterer.mapping)
330
+
331
+ if self.detector is not None:
332
+ iterables.update(self.detector.mapping)
333
+
334
+ if drop_unique_level:
335
+ _iterables = {
336
+ k: v for k, v in iterables.items() if len(v) > 1
337
+ }
338
+
339
+ if len(_iterables) != 0:
340
+ iterables = _iterables
341
+
342
+ # Create a MultiIndex from the iterables
343
+ row_index = pd.MultiIndex.from_product(
344
+ iterables.values(), names=iterables.keys()
345
+ )
346
+
347
+ if is_complex:
348
+ columns = pd.MultiIndex.from_product(
349
+ [measure, ['real', 'imag']],
350
+ names=['data', 'type']
351
+ )
352
+ else:
353
+ columns = pd.MultiIndex.from_product([measure], names=['data'])
354
+
355
+ # Return an empty DataFrame with the generated MultiIndex
356
+ return PyMieSimDataFrame(columns=columns, index=row_index)
@@ -0,0 +1,2 @@
1
+ from .gaussian import Gaussian
2
+ from .planewave import PlaneWave