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.
- msptools-0.1.0/LICENSE +21 -0
- msptools-0.1.0/PKG-INFO +81 -0
- msptools-0.1.0/README.md +39 -0
- msptools-0.1.0/pyproject.toml +29 -0
- msptools-0.1.0/setup.cfg +4 -0
- msptools-0.1.0/src/msptools/GreenTensor_Electric.py +236 -0
- msptools-0.1.0/src/msptools/MSP.py +175 -0
- msptools-0.1.0/src/msptools/OFO_calculations.py +36 -0
- msptools-0.1.0/src/msptools/__init__.py +192 -0
- msptools-0.1.0/src/msptools/backend.py +22 -0
- msptools-0.1.0/src/msptools/dipole_moments.py +27 -0
- msptools-0.1.0/src/msptools/field_mod.py +220 -0
- msptools-0.1.0/src/msptools/particle_types.py +31 -0
- msptools-0.1.0/src/msptools/particles_mod.py +78 -0
- msptools-0.1.0/src/msptools/permittivity.py +55 -0
- msptools-0.1.0/src/msptools/polarizability_mod.py +191 -0
- msptools-0.1.0/src/msptools/tools/field_tools.py +170 -0
- msptools-0.1.0/src/msptools/tools/ridx_usage.py +27 -0
- msptools-0.1.0/src/msptools/tools/unit_calcs.py +192 -0
- msptools-0.1.0/src/msptools.egg-info/PKG-INFO +81 -0
- msptools-0.1.0/src/msptools.egg-info/SOURCES.txt +35 -0
- msptools-0.1.0/src/msptools.egg-info/dependency_links.txt +1 -0
- msptools-0.1.0/src/msptools.egg-info/requires.txt +15 -0
- msptools-0.1.0/src/msptools.egg-info/top_level.txt +1 -0
- msptools-0.1.0/test/test_Green_Tensor_Electric.py +216 -0
- msptools-0.1.0/test/test_MSP.py +181 -0
- msptools-0.1.0/test/test_OFOcalc.py +65 -0
- msptools-0.1.0/test/test_backend.py +18 -0
- msptools-0.1.0/test/test_dipole_moments.py +26 -0
- msptools-0.1.0/test/test_field.py +86 -0
- msptools-0.1.0/test/test_field_tools.py +123 -0
- msptools-0.1.0/test/test_msptools.py +47 -0
- msptools-0.1.0/test/test_particles_mod.py +29 -0
- msptools-0.1.0/test/test_permittivity.py +50 -0
- msptools-0.1.0/test/test_polarizability_mod.py +107 -0
- msptools-0.1.0/test/test_types.py +14 -0
- 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.
|
msptools-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
+
|
msptools-0.1.0/README.md
ADDED
|
@@ -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
|
+
|
msptools-0.1.0/setup.cfg
ADDED
|
@@ -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
|
+
|