msptools 0.1.0__tar.gz

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 (37) hide show
  1. msptools-0.1.0/LICENSE +21 -0
  2. msptools-0.1.0/PKG-INFO +81 -0
  3. msptools-0.1.0/README.md +39 -0
  4. msptools-0.1.0/pyproject.toml +29 -0
  5. msptools-0.1.0/setup.cfg +4 -0
  6. msptools-0.1.0/src/msptools/GreenTensor_Electric.py +236 -0
  7. msptools-0.1.0/src/msptools/MSP.py +175 -0
  8. msptools-0.1.0/src/msptools/OFO_calculations.py +36 -0
  9. msptools-0.1.0/src/msptools/__init__.py +192 -0
  10. msptools-0.1.0/src/msptools/backend.py +22 -0
  11. msptools-0.1.0/src/msptools/dipole_moments.py +27 -0
  12. msptools-0.1.0/src/msptools/field_mod.py +220 -0
  13. msptools-0.1.0/src/msptools/particle_types.py +31 -0
  14. msptools-0.1.0/src/msptools/particles_mod.py +78 -0
  15. msptools-0.1.0/src/msptools/permittivity.py +55 -0
  16. msptools-0.1.0/src/msptools/polarizability_mod.py +191 -0
  17. msptools-0.1.0/src/msptools/tools/field_tools.py +170 -0
  18. msptools-0.1.0/src/msptools/tools/ridx_usage.py +27 -0
  19. msptools-0.1.0/src/msptools/tools/unit_calcs.py +192 -0
  20. msptools-0.1.0/src/msptools.egg-info/PKG-INFO +81 -0
  21. msptools-0.1.0/src/msptools.egg-info/SOURCES.txt +35 -0
  22. msptools-0.1.0/src/msptools.egg-info/dependency_links.txt +1 -0
  23. msptools-0.1.0/src/msptools.egg-info/requires.txt +15 -0
  24. msptools-0.1.0/src/msptools.egg-info/top_level.txt +1 -0
  25. msptools-0.1.0/test/test_Green_Tensor_Electric.py +216 -0
  26. msptools-0.1.0/test/test_MSP.py +181 -0
  27. msptools-0.1.0/test/test_OFOcalc.py +65 -0
  28. msptools-0.1.0/test/test_backend.py +18 -0
  29. msptools-0.1.0/test/test_dipole_moments.py +26 -0
  30. msptools-0.1.0/test/test_field.py +86 -0
  31. msptools-0.1.0/test/test_field_tools.py +123 -0
  32. msptools-0.1.0/test/test_msptools.py +47 -0
  33. msptools-0.1.0/test/test_particles_mod.py +29 -0
  34. msptools-0.1.0/test/test_permittivity.py +50 -0
  35. msptools-0.1.0/test/test_polarizability_mod.py +107 -0
  36. msptools-0.1.0/test/test_types.py +14 -0
  37. msptools-0.1.0/test/test_units.py +43 -0
msptools-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) <year> Joan Ronquillo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.4
2
+ Name: msptools
3
+ Version: 0.1.0
4
+ Summary: Tools for Optical Forces Calculation in Multiple Scattering Problems
5
+ Author-email: Joan ronquillo <joan.ronquillo@uam.es>
6
+ License: The MIT License (MIT)
7
+
8
+ Copyright (c) <year> Joan Ronquillo
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in
18
+ all copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ THE SOFTWARE.
27
+ Requires-Python: >=3.8
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: numpy
31
+ Requires-Dist: scipy
32
+ Requires-Dist: refractiveindex==1.0.0
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest; extra == "dev"
35
+ Provides-Extra: docs
36
+ Requires-Dist: sphinx; extra == "docs"
37
+ Provides-Extra: gpu
38
+ Requires-Dist: cupy; extra == "gpu"
39
+ Provides-Extra: plotting
40
+ Requires-Dist: matplotlib; extra == "plotting"
41
+ Dynamic: license-file
42
+
43
+ <p align="center">
44
+ <img src="assets/logo_1_white.svg" alt="Project Logo" width="200">
45
+ </p>
46
+
47
+ ## Overview
48
+
49
+ **msptools** is a collection of utilities and scripts designed to facilitate the analysis and processing of Optical Forces in plasmonic particle systems characterized by the Multiple Scattering Problem (MSP).
50
+
51
+ ## Features
52
+
53
+ -
54
+
55
+ ## Installation
56
+
57
+ Clone the repository and install dependencies:
58
+
59
+ To use the code in this repository, follow these steps:
60
+
61
+ 1. Clone the repository: `git clone https://github.com/JJkrokoder/msptools` or `git clone git@github.com:JJkrokoder/msptools.git`
62
+ 2. Install the required dependencies in a new environment: `conda env create -f environment.yml`
63
+ 3. Activate the virtual enironment: `conda activate msptools`
64
+ 4. Install the package in this environment: `pip install .`
65
+
66
+ ## Usage
67
+
68
+ Basic usage example:
69
+
70
+ ## License
71
+
72
+ This project is liscensed under the MIT License
73
+
74
+ ## Documentation
75
+
76
+ Further documentation can be found in: https://msptools.readthedocs.io/en/latest/#msptools
77
+
78
+ ## Contact
79
+
80
+ For questions, issues, or discussions, please use the GitHub Issues page.
81
+
@@ -0,0 +1,39 @@
1
+ <p align="center">
2
+ <img src="assets/logo_1_white.svg" alt="Project Logo" width="200">
3
+ </p>
4
+
5
+ ## Overview
6
+
7
+ **msptools** is a collection of utilities and scripts designed to facilitate the analysis and processing of Optical Forces in plasmonic particle systems characterized by the Multiple Scattering Problem (MSP).
8
+
9
+ ## Features
10
+
11
+ -
12
+
13
+ ## Installation
14
+
15
+ Clone the repository and install dependencies:
16
+
17
+ To use the code in this repository, follow these steps:
18
+
19
+ 1. Clone the repository: `git clone https://github.com/JJkrokoder/msptools` or `git clone git@github.com:JJkrokoder/msptools.git`
20
+ 2. Install the required dependencies in a new environment: `conda env create -f environment.yml`
21
+ 3. Activate the virtual enironment: `conda activate msptools`
22
+ 4. Install the package in this environment: `pip install .`
23
+
24
+ ## Usage
25
+
26
+ Basic usage example:
27
+
28
+ ## License
29
+
30
+ This project is liscensed under the MIT License
31
+
32
+ ## Documentation
33
+
34
+ Further documentation can be found in: https://msptools.readthedocs.io/en/latest/#msptools
35
+
36
+ ## Contact
37
+
38
+ For questions, issues, or discussions, please use the GitHub Issues page.
39
+
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "msptools"
7
+ version = "0.1.0"
8
+ requires-python = ">=3.8"
9
+ description = "Tools for Optical Forces Calculation in Multiple Scattering Problems"
10
+ authors = [
11
+ { name = "Joan ronquillo", email = "joan.ronquillo@uam.es" }
12
+ ]
13
+ readme = "README.md"
14
+ license = { file = "LICENSE" }
15
+ dependencies = [
16
+ "numpy",
17
+ "scipy",
18
+ "refractiveindex==1.0.0"
19
+ ]
20
+
21
+ [project.optional-dependencies]
22
+ dev = ["pytest"]
23
+ docs = ["sphinx"]
24
+ gpu = ["cupy"]
25
+ plotting = ["matplotlib"]
26
+
27
+ [tool.setuptools.packages.find]
28
+ where = ["src"]
29
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,236 @@
1
+ from .backend import get_backend
2
+ from numpy.typing import ArrayLike
3
+ import numpy as np
4
+ from scipy.constants import pi
5
+ from cmath import exp
6
+
7
+ def G_0_function(r: float | ArrayLike, wave_number: float) -> complex | ArrayLike:
8
+ """
9
+ Computes the G_0 function for a given distance r and wave number.
10
+
11
+ Parameters
12
+ ----------
13
+ r :
14
+ The distance between two points.
15
+ wave_number :
16
+ The wave number.
17
+
18
+ Returns
19
+ -------
20
+ complex | ArrayLike
21
+ The value of the G_0 function.
22
+ """
23
+ if isinstance(r, (float, int)):
24
+ if r > 0:
25
+ kr = wave_number * r
26
+ return exp(1j * wave_number * r) / (4 * pi * r) * (1 + 1j/kr - 1/kr**2)
27
+ else:
28
+ return 0.0j
29
+ else:
30
+ xp = get_backend(r)
31
+ mask = r > 0
32
+ result = xp.zeros_like(r, dtype=complex)
33
+ kr = wave_number * r[mask]
34
+ result[mask] = xp.exp(1j * kr) / (4 * pi * r[mask]) * (1 + 1j/kr - 1/kr**2)
35
+ return result
36
+
37
+ def G_1_function(r: float | ArrayLike, wave_number: float) -> complex | ArrayLike:
38
+ """
39
+ Computes the G_1 function for a given distance r and wave number.
40
+
41
+ Parameters
42
+ ----------
43
+ r :
44
+ The distance between two points.
45
+ wave_number :
46
+ The wave number.
47
+
48
+ Returns
49
+ -------
50
+ complex | ArrayLike
51
+ The value of the G_1 function.
52
+ """
53
+ if isinstance(r, (float, int)):
54
+ if r > 0:
55
+ kr = wave_number * r
56
+ return -exp(1j * wave_number * r) / (4 * pi * r**3) * (1 + 3j/kr - 3/kr**2)
57
+ else:
58
+ return 0.0j
59
+ else:
60
+ xp = get_backend(r)
61
+ mask = r > 0
62
+ result = xp.zeros_like(r, dtype=complex)
63
+ kr = wave_number * r[mask]
64
+ result[mask] = -xp.exp(1j * kr) / (4 * xp.pi * r[mask]**3) * (1 + 3j/kr - 3/kr**2)
65
+ return result
66
+
67
+ def G_0_derivative_function(r: float | ArrayLike, wave_number: float) -> complex:
68
+ """
69
+ Computes the derivative of the G_0 function with respect to r.
70
+
71
+ Parameters
72
+ ----------
73
+ r : float
74
+ The distance between two points.
75
+ wave_number : float
76
+ The wave number.
77
+
78
+ Returns
79
+ -------
80
+ complex
81
+ The value of the derivative of the G_0 function.
82
+ """
83
+ if isinstance(r, (float, int)):
84
+ xp = np
85
+ else:
86
+ xp = get_backend(r)
87
+ return wave_number * xp.exp(1j * wave_number * r) / (4 * xp.pi * r) * \
88
+ (1j - 2/(wave_number * r) - 3j/(wave_number * r)**2 + 3/(wave_number * r)**3)
89
+
90
+ def G_1_derivative_function(r: float | ArrayLike, wave_number: float) -> complex:
91
+ """
92
+ Computes the derivative of the G_1 function with respect to r.
93
+
94
+ Parameters
95
+ ----------
96
+ r : float
97
+ The distance between two points.
98
+ wave_number : float
99
+ The wave number.
100
+
101
+ Returns
102
+ -------
103
+ complex
104
+ The value of the derivative of the G_1 function.
105
+ """
106
+ if isinstance(r, (float, int)):
107
+ xp = np
108
+ else:
109
+ xp = get_backend(r)
110
+ return -wave_number * xp.exp(1j * wave_number * r) / (4 * xp.pi * r**3) * \
111
+ (1j - 6/(wave_number * r) - 15j/(wave_number * r)**2 + 15/(wave_number * r)**3)
112
+
113
+ def v_cross_derivative(r_vec: ArrayLike, coordinate: int) -> np.ndarray:
114
+ """
115
+ Computes the derivative of a vector cross dyadic product with respect to a specific coordinate.
116
+
117
+ Parameters
118
+ ----------
119
+ r_vec :
120
+ The vector for which the derivative is computed.
121
+ coordinate :
122
+ The coordinate with respect to which the derivative is taken (0, 1, or 2).
123
+
124
+ Returns
125
+ -------
126
+ np.ndarray
127
+ The derivative of the cross product with respect to the specified coordinate.
128
+ """
129
+ xp = get_backend(r_vec)
130
+
131
+ dimensions = r_vec.shape[0]
132
+ if coordinate < 0 or coordinate >= dimensions:
133
+ raise ValueError("Coordinate must be in the range [0, {}]".format(dimensions - 1))
134
+
135
+ der_R_cross = xp.zeros((dimensions, dimensions))
136
+
137
+ for i in range(dimensions):
138
+ if i == coordinate:
139
+ der_R_cross[i, i] = 2 * r_vec[i]
140
+ else:
141
+ der_R_cross[i, coordinate] = r_vec[i]
142
+ der_R_cross[coordinate, i] = r_vec[i]
143
+
144
+ return der_R_cross
145
+
146
+ def construct_green_tensor(positions : np.ndarray, wave_number: float) -> np.ndarray:
147
+ """
148
+ Constructs the Green's tensor for a given set of positions and wave number.
149
+
150
+ Parameters
151
+ ----------
152
+ positions : np.ndarray
153
+ Array of shape (num_particles, dimension) containing the positions of the particles.
154
+ wave_number : float
155
+ The wave number.
156
+
157
+ Returns
158
+ -------
159
+ np.ndarray
160
+ Green's tensor of shape (num_particles, num_particles, dimension, dimension).
161
+ """
162
+ xp = get_backend(positions)
163
+ dimensions = positions.shape[1]
164
+ rel_vec_matrix = positions[:, None, :] - positions[None, :, :]
165
+ distances = xp.linalg.norm(rel_vec_matrix, axis=-1)
166
+ G_0_matrix = G_0_function(distances, wave_number)
167
+ G_1_matrix = G_1_function(distances, wave_number)
168
+ R_cross_matrix = rel_vec_matrix[:, :, :, None] * rel_vec_matrix[:, :, None, :]
169
+ green_tensor = G_0_matrix[:, :, None, None] * xp.eye(dimensions) + G_1_matrix[:, :, None, None] * R_cross_matrix
170
+
171
+ return green_tensor
172
+
173
+
174
+ def pair_green_tensor_derivative(pos_i: np.ndarray, pos_j: np.ndarray, coordinate : int, wave_number: float):
175
+ """
176
+ Constructs the derivative of the pair Green's tensor with respect to a specific coordinate.
177
+
178
+ Parameters
179
+ ----------
180
+ pos_i : np.ndarray
181
+ Position of the first particle.
182
+ pos_j : np.ndarray
183
+ Position of the second particle.
184
+ coordinate : int
185
+ The coordinate with respect to which the derivative is taken (0, 1, or 2).
186
+ wave_number : float
187
+ The wave number.
188
+
189
+ Returns
190
+ -------
191
+ np.ndarray
192
+ Derivative of the pair Green's tensor with respect to the specified coordinate.
193
+ """
194
+ xp = get_backend(pos_i)
195
+ dimensions = pos_i.shape[0]
196
+ R_vec = pos_i - pos_j
197
+ r = xp.linalg.norm(R_vec)
198
+
199
+ g_1 = G_1_function(r, wave_number)
200
+ der_g_0 = G_0_derivative_function(r, wave_number) * R_vec[coordinate] / r
201
+ der_g_1 = G_1_derivative_function(r, wave_number) * R_vec[coordinate] / r
202
+ R_cross = R_vec[:, None] @ R_vec[None, :]
203
+ der_R_cross = v_cross_derivative(R_vec, coordinate)
204
+
205
+ derivative_tensor = der_g_0 * xp.eye(dimensions) + der_g_1 * R_cross + g_1 * der_R_cross
206
+
207
+ return derivative_tensor
208
+
209
+ def construct_green_tensor_gradient(positions : np.ndarray, wave_number: float) -> np.ndarray:
210
+ """
211
+ Constructs the derivative of the Green's tensor for a given set of positions and wave number.
212
+
213
+ Parameters
214
+ ----------
215
+ positions : np.ndarray
216
+ Array of shape (num_particles, dimension) containing the positions of the particles.
217
+ wave_number : float
218
+ The wave number.
219
+
220
+ Returns
221
+ -------
222
+ np.ndarray
223
+ Derivative of Green's tensor of shape (num_particles, num_particles, dimension, dimension, dimension).
224
+ """
225
+ xp = get_backend(positions)
226
+
227
+ num_particles, dimensions = positions.shape
228
+ green_tensor_derivative = xp.zeros((num_particles, num_particles, dimensions, dimensions, dimensions), dtype=xp.complex128)
229
+
230
+ for i in range(num_particles):
231
+ for j in range(i + 1, num_particles):
232
+ for coord in range(dimensions):
233
+ green_tensor_derivative[i, j, coord, :, :] = pair_green_tensor_derivative(positions[i], positions[j], coord, wave_number)
234
+ green_tensor_derivative[j, i, coord, :, :] = -green_tensor_derivative[i, j, coord, :, :]
235
+ return green_tensor_derivative
236
+
@@ -0,0 +1,175 @@
1
+ from .backend import get_backend
2
+ from typing import Iterable
3
+ from numpy.typing import ArrayLike
4
+
5
+ from msptools.dipole_moments import calculate_dipole_moments_linear
6
+
7
+ def solve_MSP_from_arrays(polarizability: ArrayLike,
8
+ external_field : ArrayLike,
9
+ wave_number : float,
10
+ green_tensor : ArrayLike,
11
+ method : str = 'Inverse',
12
+ **kwargs) -> ArrayLike:
13
+
14
+ """
15
+ Solve the Multiple Scattering Problem (MSP) using the provided arrays.
16
+
17
+ Parameters
18
+ ----------
19
+ polarizability :
20
+ Polarizability of the particles, can be a complex number, float, int, list, or numpy array.
21
+ external_field :
22
+ External field on particles positions.
23
+ wave_number :
24
+ Wave number of the incident wave.
25
+ green_tensor :
26
+ Green's tensor for the system.
27
+ method :
28
+ Method to solve the MSP, either 'Iterative' or 'Inverse'. The default is 'Iterative'.
29
+
30
+ Returns
31
+ -------
32
+ ArrayLike
33
+ The solution to the MSP.
34
+
35
+ """
36
+
37
+
38
+ if green_tensor.ndim != 4 or green_tensor.shape[0] != green_tensor.shape[1] or green_tensor.shape[2] != green_tensor.shape[3]:
39
+ raise ValueError("Invalid green_tensor shape. Expected shape (N, N, d, d), got {}".format(green_tensor.shape))
40
+ if green_tensor.shape[0] != external_field.shape[0]:
41
+ raise ValueError("The first dimension of green_tensor must match the number of particles in external_field. Expected {}, got {}".format(external_field.shape[0], green_tensor.shape[0]))
42
+ if green_tensor.shape[2] != external_field.shape[1]:
43
+ raise ValueError("The third dimension of green_tensor must match the system dimensionality. Expected {}, got {}".format(external_field.shape[1], green_tensor.shape[2]))
44
+
45
+ if method == 'Iterative':
46
+ if 'tolerance' in kwargs:
47
+ tolerance = kwargs['tolerance']
48
+ return array_MSP_iterative(polarizability, external_field, wave_number, green_tensor, tolerance=tolerance)
49
+ else:
50
+ return array_MSP_iterative(polarizability, external_field, wave_number, green_tensor)
51
+ elif method == 'Inverse':
52
+ return array_MSP_inverse(polarizability, external_field, wave_number, green_tensor)
53
+ else:
54
+ raise ValueError("Unknown method: {}".format(method))
55
+
56
+ def array_MSP_iterative(polarizability : ArrayLike,
57
+ external_field : ArrayLike,
58
+ wave_number : float,
59
+ green_tensor : ArrayLike,
60
+ num_iterations : int = 500,
61
+ tolerance : float = 1e-6) -> ArrayLike:
62
+
63
+ """
64
+ Solve the MSP using an iterative method.
65
+
66
+ Parameters
67
+ ----------
68
+ polarizability :
69
+ Polarizability of the particles.
70
+ external_field :
71
+ External field on particles positions.
72
+ wave_number :
73
+ Wave number of the incident wave.
74
+ green_tensor :
75
+ Green's tensor for the system.
76
+ num_iterations : optional
77
+ Maximum number of iterations for the iterative method. Default is 500.
78
+ tolerance : optional
79
+ Convergence tolerance for the iterative method. Default is 1e-6.
80
+
81
+ Returns
82
+ -------
83
+ xp.ndarray
84
+ The solution to the MSP.
85
+ """
86
+
87
+ xp = get_backend(external_field)
88
+ old_field = external_field.copy()
89
+
90
+ for iteration in range(num_iterations):
91
+
92
+ dipole_moments = calculate_dipole_moments_linear(polarizability, old_field)
93
+ scattered_field = wave_number**2 * xp.einsum('ijmn,jn->im', green_tensor, dipole_moments)
94
+ new_field = external_field + scattered_field
95
+
96
+ if xp.allclose(new_field, old_field, rtol=tolerance):
97
+ break
98
+ old_field = new_field.copy()
99
+
100
+ if iteration == num_iterations - 1:
101
+ print(f"Warning: MSP iterative solution did not converge within {num_iterations} iterations.")
102
+
103
+ return new_field
104
+
105
+ def array_MSP_inverse(polarizability : ArrayLike,
106
+ external_field : ArrayLike,
107
+ wave_number : float,
108
+ green_tensor : ArrayLike) -> ArrayLike:
109
+ """
110
+ Solve the MSP using the inverse method.
111
+
112
+ Parameters
113
+ ----------
114
+ polarizability :
115
+ Polarizability of the particles.
116
+ external_field :
117
+ External field on particles positions.
118
+ wave_number :
119
+ Wave number of the incident wave.
120
+ green_tensor :
121
+ Green's tensor for the system.
122
+
123
+ Returns
124
+ -------
125
+ xp.ndarray
126
+ The solution to the MSP.
127
+ """
128
+ xp = get_backend(external_field)
129
+ num_particles = external_field.shape[0]
130
+ dimensions = external_field.shape[1]
131
+
132
+ scattering_matrix = wave_number**2 * xp.einsum('ijmk,jkl->ijml', green_tensor, polarizability)
133
+
134
+ MSP_matrix = xp.eye(num_particles * dimensions) - scattering_matrix.transpose(0,2,1,3).reshape(num_particles * dimensions, num_particles * dimensions)
135
+
136
+ total_field = xp.linalg.solve(MSP_matrix, external_field.flatten())
137
+ total_field = total_field.reshape(num_particles, dimensions)
138
+ return total_field
139
+
140
+ def MSP_gradient_from_arrays(dipole_moments: ArrayLike,
141
+ external_gradient : ArrayLike,
142
+ wave_number : float,
143
+ green_tensor_derivative : ArrayLike) -> ArrayLike:
144
+ """
145
+ Compute the gradient of the MSP solution with respect to particle positions.
146
+
147
+ Parameters
148
+ ----------
149
+ polarizability :
150
+ Polarizability of the particles.
151
+ MS_field :
152
+ Multiple scattering field on particles positions.
153
+ external_gradient :
154
+ Gradient of the external field on particles positions.
155
+ wave_number :
156
+ Wave number of the incident wave.
157
+ green_tensor_derivative :
158
+ Derivative of the Green's tensor with respect to particle positions.
159
+
160
+ Returns
161
+ -------
162
+ xp.ndarray
163
+ The gradient of the MSP solution with respect to particle positions.
164
+
165
+ Notes
166
+ -----
167
+ The gradient is returned as an array of shape (N, d, d) where N is the number of particles and d is the dimensionality.
168
+ """
169
+
170
+ xp = get_backend(external_gradient)
171
+ scattered_gradient = wave_number**2 * xp.einsum('ijcmn,jn->icm', green_tensor_derivative, dipole_moments)
172
+
173
+ MSP_gradient = external_gradient + scattered_gradient
174
+
175
+ return MSP_gradient
@@ -0,0 +1,36 @@
1
+ from typing import Iterable
2
+ from .backend import get_backend
3
+ from numpy.typing import ArrayLike
4
+
5
+ def calculate_forces_eppgrad(medium_permittivity: float, dipole_moments: ArrayLike, field_gradient: ArrayLike) -> ArrayLike:
6
+ """
7
+ Calculate the force on a set of dipoles in an electric field gradient.
8
+
9
+ Parameters
10
+ ----------
11
+ medium_permittivity :
12
+ The permittivity of the medium in which the dipoles are located.
13
+ dipole_moments :
14
+ An array representing the dipole moments of the particles. Shape should be (N, d),
15
+ where N is the number of dipoles and d is the dimensionality.
16
+ field_gradient :
17
+ An array representing the electric field gradient at the location of the dipoles.
18
+ Shape should be (N, d, d), where N is the number of dipoles and d is the dimensionality.
19
+
20
+ Returns
21
+ -------
22
+ Forces :
23
+ An array representing the force on each dipole.
24
+
25
+ Notes
26
+ -----
27
+ The force is calculated using the formula:
28
+ F = (ε/2) * Re{ p · ∇E* }
29
+ where ε is the medium permittivity, p is the dipole moment, and ∇E* is the complex conjugate of the electric field gradient.
30
+ """
31
+ xp = get_backend(dipole_moments)
32
+
33
+ forces = (medium_permittivity / 2) * xp.real(xp.einsum('im,inm->in', dipole_moments, xp.conj(field_gradient)))
34
+
35
+ return forces
36
+