addatmatrix 0.2.0__py3-none-any.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.
- addatmatrix/__init__.py +0 -0
- addatmatrix/adda.bin +0 -0
- addatmatrix/cross_section.py +60 -0
- addatmatrix/data.py +177 -0
- addatmatrix/far_field.py +151 -0
- addatmatrix/gauss_legendre.py +97 -0
- addatmatrix/incident_field.py +24 -0
- addatmatrix/math.py +210 -0
- addatmatrix/spatial.py +161 -0
- addatmatrix/t_matrix.py +351 -0
- addatmatrix/wrapper.py +223 -0
- addatmatrix-0.2.0.dist-info/METADATA +15 -0
- addatmatrix-0.2.0.dist-info/RECORD +85 -0
- addatmatrix-0.2.0.dist-info/WHEEL +5 -0
- addatmatrix-0.2.0.dist-info/top_level.txt +2 -0
- benchmarks/__init__.py +0 -0
- benchmarks/adda_orientation_average_multi_wl.py +98 -0
- benchmarks/analyze_convergence_results.py +67 -0
- benchmarks/benchmark_paper_test1.py +66 -0
- benchmarks/compare_adda_orientation_average.py +98 -0
- benchmarks/compare_single_runs_four_spheres.py +24 -0
- benchmarks/compare_single_runs_sphere.py +25 -0
- benchmarks/compare_single_runs_sphere_gold.py +25 -0
- benchmarks/compare_single_runs_spheroid.py +25 -0
- benchmarks/compare_single_runs_spheroid_nsurr1.py +23 -0
- benchmarks/compare_smuthi_to_treams.py +54 -0
- benchmarks/compare_to_treams.py +54 -0
- benchmarks/convergence_run_adda.py +51 -0
- benchmarks/convergence_run_adda_gl.py +47 -0
- benchmarks/convergence_run_orientation_avg.py +104 -0
- benchmarks/convergence_run_single_illumination_adda.py +105 -0
- benchmarks/convergence_run_single_illumination_smuthi_multi_wl.py +142 -0
- benchmarks/convergence_run_smuthi.py +49 -0
- benchmarks/convergence_run_smuthi_multi_eps.py +144 -0
- benchmarks/convergence_run_smuthi_multi_wl.py +141 -0
- benchmarks/convergence_tests.py +103 -0
- benchmarks/convergence_tests_get_t_matrix.py +128 -0
- benchmarks/far_field_from_adda.py +99 -0
- benchmarks/far_field_test.py +123 -0
- benchmarks/far_field_test_analyze.py +325 -0
- benchmarks/find_stable_point.py +65 -0
- benchmarks/get_smuthi_t_format.py +47 -0
- benchmarks/merge_dbs.py +226 -0
- benchmarks/particles.py +74 -0
- benchmarks/pipelines/__init__.py +4 -0
- benchmarks/pipelines/adda_ops.py +47 -0
- benchmarks/pipelines/common.py +151 -0
- benchmarks/pipelines/db.py +89 -0
- benchmarks/pipelines/pipeline1.py +279 -0
- benchmarks/pipelines/pipeline1_5.py +535 -0
- benchmarks/pipelines/pipeline2.py +531 -0
- benchmarks/pipelines/smuthi_ops.py +93 -0
- benchmarks/pipelines/utils.py +35 -0
- benchmarks/read_far_field_from_db.py +82 -0
- benchmarks/read_permittivity_data.py +41 -0
- benchmarks/read_sm_conv_from_db.py +100 -0
- benchmarks/read_t_matrix.py +28 -0
- benchmarks/run_pipeline.py +46 -0
- benchmarks/single_illumination_run_adda copy.py +32 -0
- benchmarks/single_illumination_run_adda.py +17 -0
- benchmarks/single_illumination_run_adda_four_spheres.py +20 -0
- benchmarks/single_illumination_run_adda_sphere_diel.py +20 -0
- benchmarks/single_illumination_run_adda_sphere_gold.py +20 -0
- benchmarks/single_illumination_run_adda_spheroid.py +21 -0
- benchmarks/single_illumination_run_adda_spheroid_nsurr_1.py +20 -0
- benchmarks/single_illumination_smuthi_run.py +71 -0
- benchmarks/single_illumination_smuthi_run_four_spheres.py +88 -0
- benchmarks/single_illumination_smuthi_run_sphere_gold.py +77 -0
- benchmarks/single_illumination_smuthi_run_spheroid.py +75 -0
- benchmarks/single_illumination_smuthi_run_spheroid_nsurr1.py +74 -0
- benchmarks/single_illumination_treams.py +12 -0
- benchmarks/smuthi_indices.py +136 -0
- benchmarks/test_gl.py +116 -0
- benchmarks/test_hydra.py +30 -0
- benchmarks/three_runs.py +66 -0
- benchmarks/three_runs_sphere.py +54 -0
- benchmarks/visualize_adda_orientation_average_multi_wl_results.py +156 -0
- benchmarks/visualize_adda_vs_smuthi_multi_wl.py +315 -0
- benchmarks/visualize_adda_vs_smuthi_orientation_avg.py +51 -0
- benchmarks/visualize_conv_test.py +112 -0
- benchmarks/visualize_conv_test_old_method.py +102 -0
- benchmarks/visualize_convergence.py +96 -0
- benchmarks/visualize_convergence_old_method.py +108 -0
- benchmarks/visualize_convergence_run.py +206 -0
- benchmarks/visualize_convergence_run_smuthi_multi_eps.py +176 -0
addatmatrix/__init__.py
ADDED
|
File without changes
|
addatmatrix/adda.bin
ADDED
|
Binary file
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import addatmatrix.wrapper
|
|
2
|
+
import sys
|
|
3
|
+
import addatmatrix.far_field
|
|
4
|
+
import numpy as np
|
|
5
|
+
import tqdm
|
|
6
|
+
from tqdm import trange
|
|
7
|
+
|
|
8
|
+
from addatmatrix.t_matrix import ILLUMINATION_GRID
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_cross_section(min_wavelength, max_wavelength, num_wavelength, adda_string, material_refractive_index=None):
|
|
12
|
+
wavelength_range = np.linspace(min_wavelength, max_wavelength, num_wavelength)
|
|
13
|
+
if material_refractive_index is not None and len(material_refractive_index) != num_wavelength:
|
|
14
|
+
raise ValueError("Length of material_refractive_index must be equal to num_wavelength")
|
|
15
|
+
|
|
16
|
+
csx = []
|
|
17
|
+
csy = []
|
|
18
|
+
for iwl, wl in enumerate(tqdm.tqdm(wavelength_range)):
|
|
19
|
+
if material_refractive_index is not None:
|
|
20
|
+
material_adda_string = f" -m {material_refractive_index[iwl].real} {material_refractive_index[iwl].imag}"
|
|
21
|
+
else:
|
|
22
|
+
material_adda_string = ""
|
|
23
|
+
adda_output = addatmatrix.wrapper.run_from_args(adda_string + material_adda_string + f" -scat_matr none -lambda {wl}")
|
|
24
|
+
csx.append(adda_output.cross_section_x)
|
|
25
|
+
csy.append(adda_output.cross_section_y)
|
|
26
|
+
|
|
27
|
+
csx = np.array(csx)
|
|
28
|
+
csy = np.array(csy)
|
|
29
|
+
return csx, csy
|
|
30
|
+
|
|
31
|
+
def calculate_orientation_averaged_extinction_cross_section(illumination_number, wavenumber, **kwargs):
|
|
32
|
+
def adda_callable(adda_string, kth, kph):
|
|
33
|
+
nz = np.cos(kth)
|
|
34
|
+
ny = np.sin(kth) * np.sin(kph)
|
|
35
|
+
nx = np.sin(kth) * np.cos(kph)
|
|
36
|
+
return addatmatrix.wrapper.run_from_args(adda_string +' -prop '+str(nx)+' '+str(ny)+' '+str(nz))
|
|
37
|
+
|
|
38
|
+
illumination_grid = ILLUMINATION_GRID(illumination_number=illumination_number, wavenumber=wavenumber)
|
|
39
|
+
csx = []
|
|
40
|
+
csy = []
|
|
41
|
+
for iang in trange(len(illumination_grid)):
|
|
42
|
+
illumination = illumination_grid[iang]
|
|
43
|
+
|
|
44
|
+
adda_output = adda_callable(**kwargs, kth=illumination.theta,
|
|
45
|
+
kph=illumination.phi)
|
|
46
|
+
csx.append(adda_output.cross_section_x)
|
|
47
|
+
csy.append(adda_output.cross_section_y)
|
|
48
|
+
return np.mean(np.array(csx)[:,0]), np.mean(np.array(csy)[:,0])
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
min_wavelength = float(sys.argv[1])
|
|
52
|
+
max_wavelength = float(sys.argv[2])
|
|
53
|
+
num_wavelength = int(sys.argv[3])
|
|
54
|
+
adda_string = sys.argv[4]
|
|
55
|
+
|
|
56
|
+
csx, csy = get_cross_section(min_wavelength, max_wavelength, num_wavelength, adda_string)
|
|
57
|
+
|
|
58
|
+
np.save("csx",csx)
|
|
59
|
+
np.save("csy",csy)
|
|
60
|
+
|
addatmatrix/data.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import h5py
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_smuthi_indices(lmax):
|
|
6
|
+
l = [l for l in range(1,lmax+1) for m in range(-l,l+1)]
|
|
7
|
+
m = [m for l in range(1,lmax+1) for m in range(-l,l+1)]
|
|
8
|
+
l = np.array(2*l)
|
|
9
|
+
m = np.array(2*m)
|
|
10
|
+
pol = np.array([0]*(len(l)//2) + [1]*(len(l)//2))
|
|
11
|
+
ind = np.array(range(len(l)))
|
|
12
|
+
return l, m, pol, ind
|
|
13
|
+
|
|
14
|
+
def rearrange_blocks(T, lmax):
|
|
15
|
+
l = [l for l in range(1,lmax+1) for m in range(-l,l+1)]
|
|
16
|
+
num_block = len(l)
|
|
17
|
+
T_mm = T[:num_block,:num_block]
|
|
18
|
+
T_ee = T[num_block:,num_block:]
|
|
19
|
+
T_em = T[num_block:,:num_block]
|
|
20
|
+
T_me = T[:num_block,num_block:]
|
|
21
|
+
|
|
22
|
+
T_arr = np.block([[T_ee,T_em],[T_me,T_mm]])
|
|
23
|
+
return T_arr
|
|
24
|
+
|
|
25
|
+
def convert_indices(pol, m, l, ind):
|
|
26
|
+
pol_conv = pol.reshape(2,-1).T.flatten()
|
|
27
|
+
m_conv = m.reshape(2,-1).T.flatten()
|
|
28
|
+
l_conv = l.reshape(2,-1).T.flatten()
|
|
29
|
+
ind_conv = ind.reshape(2,-1).T.flatten()
|
|
30
|
+
return pol_conv, m_conv, l_conv, ind_conv
|
|
31
|
+
|
|
32
|
+
def invert_indices(ind_conv):
|
|
33
|
+
inverse = np.zeros_like(ind_conv)
|
|
34
|
+
|
|
35
|
+
for idx, value in enumerate(ind_conv):
|
|
36
|
+
inverse[value] = idx
|
|
37
|
+
return inverse
|
|
38
|
+
|
|
39
|
+
def normalize_t_matrix(t_matrix, m_conv):
|
|
40
|
+
m_a,m_b = np.meshgrid(m_conv,m_conv)
|
|
41
|
+
norm_factor_t = (-1.0)**(m_a-m_b)
|
|
42
|
+
return norm_factor_t * t_matrix
|
|
43
|
+
|
|
44
|
+
def convert_t_matrix(T, ind_conv):
|
|
45
|
+
T_conv = T[ind_conv][:,ind_conv]
|
|
46
|
+
return T_conv
|
|
47
|
+
|
|
48
|
+
def prepare_data_for_storage(T, lmax):
|
|
49
|
+
l, m, pol, ind = get_smuthi_indices(lmax)
|
|
50
|
+
pol_conv, m_conv, l_conv, ind_conv = convert_indices(pol, m, l, ind)
|
|
51
|
+
T_arr = rearrange_blocks(T, lmax)
|
|
52
|
+
T_conv = convert_t_matrix(T_arr, ind_conv)
|
|
53
|
+
T_norm = normalize_t_matrix(T_conv, m_conv)
|
|
54
|
+
indices = (l_conv, m_conv, pol_conv)
|
|
55
|
+
return T_norm, indices
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def t_matrix_to_smuthi_convention(t_ref, lmax):
|
|
59
|
+
l, m, pol, ind = get_smuthi_indices(lmax)
|
|
60
|
+
pol_conv, m_conv, l_conv, ind_conv = convert_indices(pol, m, l, ind)
|
|
61
|
+
inv = invert_indices(ind_conv)
|
|
62
|
+
|
|
63
|
+
t_ref_unnorm = normalize_t_matrix(t_ref, m_conv)
|
|
64
|
+
T_conv = convert_t_matrix(t_ref_unnorm, inv)
|
|
65
|
+
T_conv2 = rearrange_blocks(T_conv, lmax)
|
|
66
|
+
return T_conv2
|
|
67
|
+
|
|
68
|
+
def get_metadata(adda_string):
|
|
69
|
+
adda_command_split = adda_string.split("-")
|
|
70
|
+
metadata = {ind.split()[0]:ind.split()[1:] for ind in adda_command_split[1:]}
|
|
71
|
+
return metadata
|
|
72
|
+
|
|
73
|
+
def write_t_matrix_data(path, tmatrix_data, indices, metadata):
|
|
74
|
+
l, m_ind, pol = indices
|
|
75
|
+
mstring = metadata["m"]
|
|
76
|
+
m = float(mstring[0]) + 1j * float(mstring[1])
|
|
77
|
+
relative_permittivity = m ** 2
|
|
78
|
+
relative_permeability = 1.0
|
|
79
|
+
|
|
80
|
+
# Create the HDF5 file
|
|
81
|
+
with h5py.File(path, 'w') as h5file:
|
|
82
|
+
# Creating groups
|
|
83
|
+
computation_group = h5file.create_group('computation')
|
|
84
|
+
embedding_group = h5file.create_group('embedding')
|
|
85
|
+
modes_group = h5file.create_group('modes')
|
|
86
|
+
scatterer_group = h5file.create_group('scatterer')
|
|
87
|
+
|
|
88
|
+
# Creating datasets
|
|
89
|
+
h5file.create_dataset('vacuum_wavelength', data=metadata["lambda"])
|
|
90
|
+
|
|
91
|
+
mesh_msh_data = "Mesh data as a string or binary blob" # Example data for mesh.msh
|
|
92
|
+
h5file.create_dataset('mesh.msh', data=np.void(mesh_msh_data.encode('utf-8')))
|
|
93
|
+
|
|
94
|
+
h5file.create_dataset('tmatrix', data=tmatrix_data)
|
|
95
|
+
|
|
96
|
+
# Adding sub-groups and datasets in computation group
|
|
97
|
+
method_parameters = computation_group.create_group('method_parameters')
|
|
98
|
+
for key, val in metadata.items():
|
|
99
|
+
method_parameters.create_dataset(key, data=np.string_(str(val)))
|
|
100
|
+
|
|
101
|
+
# Adding sub-groups and datasets in embedding group
|
|
102
|
+
embedding_group.create_dataset('relative_permeability', data=1.0)
|
|
103
|
+
embedding_group.create_dataset('relative_permittivity', data=1.0)
|
|
104
|
+
|
|
105
|
+
# Adding sub-groups and datasets in modes group
|
|
106
|
+
modes_group.create_dataset('l', data=l)
|
|
107
|
+
modes_group.create_dataset('m', data=m_ind)
|
|
108
|
+
modes_group.create_dataset('polarization', data=pol)
|
|
109
|
+
|
|
110
|
+
# Adding sub-groups and datasets in scatterer group
|
|
111
|
+
geometry_group = scatterer_group.create_group('geometry')
|
|
112
|
+
material_group = scatterer_group.create_group('material')
|
|
113
|
+
material_group.create_dataset('relative_permeability', data=relative_permeability)
|
|
114
|
+
material_group.create_dataset('relative_permittivity', data=relative_permittivity)
|
|
115
|
+
|
|
116
|
+
def write_cross_section(csx,csy,output_path):
|
|
117
|
+
np.savez(output_path, csx, csy)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def read_t_matrix_data(path):
|
|
121
|
+
with h5py.File(path, 'r') as h5file:
|
|
122
|
+
# Read basic metadata
|
|
123
|
+
vacuum_wavelength = h5file['vacuum_wavelength'][()]
|
|
124
|
+
print(f"Vacuum Wavelength: {vacuum_wavelength}")
|
|
125
|
+
|
|
126
|
+
# Read mesh data (assuming it's stored as a string)
|
|
127
|
+
#mesh_msh_data = h5file['mesh.msh']
|
|
128
|
+
#mesh_msh_data = mesh_msh_data.tobytes().decode('utf-8') # Decoding binary data back to string
|
|
129
|
+
#print(f"Mesh MSH Data: {mesh_msh_data}")
|
|
130
|
+
|
|
131
|
+
# Read the T-matrix data
|
|
132
|
+
tmatrix_data = h5file['tmatrix'][:]
|
|
133
|
+
print(f"T-matrix Data shape: {tmatrix_data.shape}")
|
|
134
|
+
|
|
135
|
+
# Read method parameters from the 'computation' group
|
|
136
|
+
computation_group = h5file['computation/method_parameters']
|
|
137
|
+
method_parameters = {key: computation_group[key][()] for key in computation_group}
|
|
138
|
+
print("Method Parameters:")
|
|
139
|
+
for key, value in method_parameters.items():
|
|
140
|
+
print(f"{key}: {value}")
|
|
141
|
+
|
|
142
|
+
# Read data from the 'embedding' group
|
|
143
|
+
embedding_group = h5file['embedding']
|
|
144
|
+
relative_permeability = embedding_group['relative_permeability'][()]
|
|
145
|
+
relative_permittivity = embedding_group['relative_permittivity'][()]
|
|
146
|
+
print(
|
|
147
|
+
f"Embedding Group: Relative Permeability = {relative_permeability}, Relative Permittivity = {relative_permittivity}")
|
|
148
|
+
|
|
149
|
+
# Read data from the 'modes' group
|
|
150
|
+
modes_group = h5file['modes']
|
|
151
|
+
l = modes_group['l'][:]
|
|
152
|
+
m = modes_group['m'][:]
|
|
153
|
+
polarization = modes_group['polarization'][:]
|
|
154
|
+
print(f"Modes Group: l = {l}, m = {m}, Polarization = {polarization}")
|
|
155
|
+
|
|
156
|
+
# Read data from the 'scatterer' group
|
|
157
|
+
scatterer_group = h5file['scatterer']
|
|
158
|
+
material_group = scatterer_group['material']
|
|
159
|
+
scatterer_relative_permittivity = material_group['relative_permittivity'][()]
|
|
160
|
+
scatterer_relative_permeability = material_group['relative_permeability'][()]
|
|
161
|
+
print(
|
|
162
|
+
f"Scatterer Group: Relative Permittivity = {scatterer_relative_permittivity}, Relative Permeability = {scatterer_relative_permeability}")
|
|
163
|
+
|
|
164
|
+
# Return the loaded data as a dictionary or any other format as needed
|
|
165
|
+
data = {
|
|
166
|
+
'vacuum_wavelength': vacuum_wavelength,
|
|
167
|
+
'tmatrix_data': tmatrix_data,
|
|
168
|
+
'method_parameters': method_parameters,
|
|
169
|
+
'relative_permittivity': relative_permittivity,
|
|
170
|
+
'relative_permeability': relative_permeability,
|
|
171
|
+
'l': l,
|
|
172
|
+
'm': m,
|
|
173
|
+
'polarization': polarization,
|
|
174
|
+
'scatterer_relative_permittivity': scatterer_relative_permittivity,
|
|
175
|
+
'scatterer_relative_permeability': scatterer_relative_permeability
|
|
176
|
+
}
|
|
177
|
+
return data
|
addatmatrix/far_field.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from numba import njit
|
|
5
|
+
from tqdm import tqdm
|
|
6
|
+
|
|
7
|
+
from addatmatrix.spatial import IntegrationGrid
|
|
8
|
+
from addatmatrix.math import spherical_harmonic_m, spherical_harmonic_n
|
|
9
|
+
from addatmatrix.wrapper import pol_from_adda_string
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
from multiprocessing import Pool, cpu_count
|
|
13
|
+
from functools import partial
|
|
14
|
+
from tqdm import tqdm
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@njit
|
|
18
|
+
def getposfac(ks, er, pos):
|
|
19
|
+
er_dot_r = er[0] * pos[0] + er[1] * pos[1] + er[2] * pos[2]
|
|
20
|
+
return np.exp(-1j * ks * er_dot_r)
|
|
21
|
+
|
|
22
|
+
def parse_pol_file(data: np.array) -> Tuple[np.array, np.array]:
|
|
23
|
+
r = data[:, :3]
|
|
24
|
+
pol = np.zeros((data.shape[0], 3), dtype=complex)
|
|
25
|
+
pol[:, 0] = data[:, 4] + 1j * data[:, 5]
|
|
26
|
+
pol[:, 1] = data[:, 6] + 1j * data[:, 7]
|
|
27
|
+
pol[:, 2] = data[:, 8] + 1j * data[:, 9]
|
|
28
|
+
return pol, r
|
|
29
|
+
|
|
30
|
+
@njit
|
|
31
|
+
def get_total_polarization(pol: np.array, er: np.array, r: np.array, ks: float) -> np.array:
|
|
32
|
+
pol_eff = np.zeros_like(er) + 1j * np.zeros_like(er)
|
|
33
|
+
for ip, pos in enumerate(r):
|
|
34
|
+
posfac = getposfac(ks, er, pos)
|
|
35
|
+
pol_eff[0,:,:] = pol_eff[0,:,:] + pol[ip, 0] * posfac
|
|
36
|
+
pol_eff[1,:,:] = pol_eff[1,:,:] + pol[ip, 1] * posfac
|
|
37
|
+
pol_eff[2,:,:] = pol_eff[2,:,:] + pol[ip, 2] * posfac
|
|
38
|
+
return pol_eff
|
|
39
|
+
|
|
40
|
+
def convert_cartesian_to_spherical(pol_eff: np.array, grid: IntegrationGrid) -> np.array:
|
|
41
|
+
T, P = (grid.T, grid.P)
|
|
42
|
+
pol_sph = np.zeros((2, pol_eff.shape[1], pol_eff.shape[2]), dtype=complex)
|
|
43
|
+
pol_sph[0,:,:] = np.cos(T)*np.cos(P)*pol_eff[0,:,:]+np.cos(T)*np.sin(P)*pol_eff[1,:,:]-np.sin(T)*pol_eff[2,:,:]
|
|
44
|
+
pol_sph[1,:,:] = -np.sin(P)*pol_eff[0,:,:]+np.cos(P)*pol_eff[1,:,:]
|
|
45
|
+
return pol_sph
|
|
46
|
+
|
|
47
|
+
def calculate_far_field(pol: np.array, ks: float) -> np.array:
|
|
48
|
+
Escat = np.zeros(pol.shape, dtype=complex)
|
|
49
|
+
Escat[1, :, :] = 1j * ks ** 3 * pol[0, :, :]
|
|
50
|
+
Escat[0, :, :] = 1j * ks ** 3 * pol[1, :, :]
|
|
51
|
+
Escat[1, :, :] = -Escat[1, :, :]
|
|
52
|
+
Escat = -np.nan_to_num(Escat)
|
|
53
|
+
return Escat
|
|
54
|
+
|
|
55
|
+
def azimuthal_average_far_field(b_coeffs, theta, phi, k, lmax):
|
|
56
|
+
res = []
|
|
57
|
+
phi_shape = phi.shape
|
|
58
|
+
azimuthal_angles = phi[0]
|
|
59
|
+
phi = phi.flatten()
|
|
60
|
+
theta = theta.flatten()
|
|
61
|
+
for tau in [0, 1]:
|
|
62
|
+
for l in range(1, lmax + 1):
|
|
63
|
+
for m in range(-l, l + 1):
|
|
64
|
+
if tau:
|
|
65
|
+
mr = spherical_harmonic_m(theta, phi, l, m)
|
|
66
|
+
else:
|
|
67
|
+
mr = 1j * spherical_harmonic_n(theta, phi, l, m)
|
|
68
|
+
prefactor = (-1j) ** (l + 1)
|
|
69
|
+
res.append(prefactor * mr)
|
|
70
|
+
res = np.array(res)
|
|
71
|
+
myff = (b_coeffs[:, None, None] * res).sum(axis=0)
|
|
72
|
+
|
|
73
|
+
myff = myff.reshape(-1, *phi_shape)
|
|
74
|
+
myff[1] = -myff[1]
|
|
75
|
+
|
|
76
|
+
azimuthal_average = np.trapz(np.abs(myff) ** 2 / (k ** 2), azimuthal_angles)
|
|
77
|
+
return azimuthal_average
|
|
78
|
+
|
|
79
|
+
def convert_adda_output_to_far_field(adda_output, grid, pol, ks):
|
|
80
|
+
data = adda_output.dipole_polarization_x if pol == 1 else adda_output.dipole_polarization_y
|
|
81
|
+
pol_cart, r = parse_pol_file(data)
|
|
82
|
+
pol_eff = get_total_polarization(pol_cart, grid.er, r, ks)
|
|
83
|
+
pol_eff_sph = convert_cartesian_to_spherical(pol_eff, grid)
|
|
84
|
+
Escat = calculate_far_field(pol_eff_sph, ks)
|
|
85
|
+
return Escat
|
|
86
|
+
|
|
87
|
+
def get_far_field_from_adda(illumination, grid, **kwargs):
|
|
88
|
+
adda_output = pol_from_adda_string(**kwargs, kth=illumination.theta,
|
|
89
|
+
kph=illumination.phi)
|
|
90
|
+
|
|
91
|
+
Escat = convert_adda_output_to_far_field(adda_output, grid,
|
|
92
|
+
illumination.polarization,
|
|
93
|
+
illumination.wavenumber)
|
|
94
|
+
return Escat
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _far_field_worker(args):
|
|
98
|
+
"""Worker function for multiprocessing."""
|
|
99
|
+
iang, illumination, grid, kwargs = args
|
|
100
|
+
Escat = get_far_field_from_adda(illumination, grid, **kwargs)
|
|
101
|
+
return iang, Escat
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_plane_wave_t_matrix(illumination_grid, grid, n_processes=None, **kwargs):
|
|
105
|
+
Ntheta, Nphi = grid.T.shape
|
|
106
|
+
n_illum = illumination_grid.illumination_number
|
|
107
|
+
|
|
108
|
+
Escat_tot = np.zeros(
|
|
109
|
+
(2 * n_illum, 2, Ntheta, Nphi),
|
|
110
|
+
dtype=complex
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if n_processes is None:
|
|
114
|
+
n_processes = cpu_count()
|
|
115
|
+
|
|
116
|
+
# Prepare arguments for workers
|
|
117
|
+
tasks = [
|
|
118
|
+
(iang, illumination, grid, kwargs)
|
|
119
|
+
for iang, illumination in enumerate(illumination_grid)
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
with Pool(processes=n_processes) as pool:
|
|
123
|
+
with tqdm(total=len(tasks)) as pbar:
|
|
124
|
+
for iang, Escat in pool.imap_unordered(_far_field_worker, tasks):
|
|
125
|
+
Escat_tot[iang, :, :, :] = Escat
|
|
126
|
+
pbar.update(1)
|
|
127
|
+
|
|
128
|
+
T_pl = Escat_tot.reshape(2 * n_illum, -1).T
|
|
129
|
+
return T_pl
|
|
130
|
+
|
|
131
|
+
def vsh_far_field(b_coeffs, grid, k, lmax):
|
|
132
|
+
phi = grid.P
|
|
133
|
+
theta = grid.T
|
|
134
|
+
res = []
|
|
135
|
+
phi_shape = phi.shape
|
|
136
|
+
phi = phi.flatten()
|
|
137
|
+
theta = theta.flatten()
|
|
138
|
+
for tau in [0, 1]:
|
|
139
|
+
for l in range(1, lmax + 1):
|
|
140
|
+
for m in range(-l, l + 1):
|
|
141
|
+
if tau:
|
|
142
|
+
mr = spherical_harmonic_m(theta, phi, l, m)
|
|
143
|
+
else:
|
|
144
|
+
mr = 1j * spherical_harmonic_n(theta, phi, l, m)
|
|
145
|
+
prefactor = (-1j) ** (l + 1)
|
|
146
|
+
res.append(prefactor * mr)
|
|
147
|
+
res = np.array(res)
|
|
148
|
+
myff = (b_coeffs[:, None, None] * res).sum(axis=0)
|
|
149
|
+
|
|
150
|
+
myff = myff.reshape(-1, *phi_shape)
|
|
151
|
+
return myff
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from addatmatrix.math import spherical_harmonic_m, spherical_harmonic_n
|
|
2
|
+
from collections import namedtuple
|
|
3
|
+
from scipy.special import roots_legendre
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
Grid = namedtuple('Grid', ['T', 'P'])
|
|
7
|
+
|
|
8
|
+
def get_all_harmonics(lmax, grid):
|
|
9
|
+
all_harmonics = []
|
|
10
|
+
for tau in [0, 1]:
|
|
11
|
+
for l in range(1, lmax + 1):
|
|
12
|
+
for m in range(-l, l + 1):
|
|
13
|
+
if tau:
|
|
14
|
+
mr = spherical_harmonic_m(grid.T, grid.P, l, m)
|
|
15
|
+
else:
|
|
16
|
+
mr = 1j * spherical_harmonic_n(grid.T, grid.P, l, m)
|
|
17
|
+
all_harmonics.append(mr)
|
|
18
|
+
all_harmonics = np.array(all_harmonics)
|
|
19
|
+
return all_harmonics
|
|
20
|
+
|
|
21
|
+
def get_weights(grid):
|
|
22
|
+
theta_grid_new, phi_grid_new = grid.T, grid.P
|
|
23
|
+
n_theta, n_phi = theta_grid_new.shape
|
|
24
|
+
_, theta_weights = roots_legendre(n_theta)
|
|
25
|
+
phi_weights = (2 * np.pi / n_phi) * np.ones(n_phi)
|
|
26
|
+
|
|
27
|
+
# Compute weights
|
|
28
|
+
weights_grid = theta_weights[:, None] * phi_weights[None, :]
|
|
29
|
+
return weights_grid
|
|
30
|
+
|
|
31
|
+
def get_grid(n_theta=10, n_phi=20):
|
|
32
|
+
# Gauss-Legendre for cos(theta)
|
|
33
|
+
theta_nodes, _ = roots_legendre(n_theta)
|
|
34
|
+
theta_nodes = np.arccos(theta_nodes)
|
|
35
|
+
|
|
36
|
+
# Trapezoidal rule for phi
|
|
37
|
+
phi_nodes = np.linspace(0, 2 * np.pi, n_phi, endpoint=False)
|
|
38
|
+
|
|
39
|
+
theta_grid_new, phi_grid_new = np.meshgrid(theta_nodes, phi_nodes,
|
|
40
|
+
indexing='ij')
|
|
41
|
+
return Grid(theta_grid_new, phi_grid_new)
|
|
42
|
+
|
|
43
|
+
def get_prefactors(lmax):
|
|
44
|
+
prefactors = []
|
|
45
|
+
for _ in range(2):
|
|
46
|
+
for l in range(1, lmax + 1):
|
|
47
|
+
for m in range(-l, l + 1):
|
|
48
|
+
prefactor = 1 / (-1j) ** (l + 1)
|
|
49
|
+
prefactors.append(prefactor)
|
|
50
|
+
return np.array(prefactors)
|
|
51
|
+
|
|
52
|
+
def get_indicator_vector(theta_target, phi_target, grid, prepend_flag = False):
|
|
53
|
+
theta_grid_new, phi_grid_new = grid.T, grid.P
|
|
54
|
+
|
|
55
|
+
theta_flat = theta_grid_new.flatten()
|
|
56
|
+
phi_flat = phi_grid_new.flatten()
|
|
57
|
+
|
|
58
|
+
distances = np.sqrt((theta_flat - theta_target)**2 + (phi_flat - phi_target)**2)
|
|
59
|
+
|
|
60
|
+
min_index = np.argmin(distances)
|
|
61
|
+
|
|
62
|
+
indicator_vector = np.zeros_like(theta_flat)
|
|
63
|
+
indicator_vector[min_index] = 1 # Set the closest index to 1
|
|
64
|
+
|
|
65
|
+
zero_clone = np.zeros_like(indicator_vector)
|
|
66
|
+
|
|
67
|
+
if prepend_flag:
|
|
68
|
+
final_vector = np.concatenate((zero_clone, indicator_vector))
|
|
69
|
+
else:
|
|
70
|
+
final_vector = np.concatenate((indicator_vector, zero_clone))
|
|
71
|
+
return final_vector
|
|
72
|
+
|
|
73
|
+
def build_matrices_from_grid(grid, lmax):
|
|
74
|
+
all_harmonics = grid.get_all_harmonics(lmax)
|
|
75
|
+
prefactors = get_prefactors(lmax)
|
|
76
|
+
W = np.diag(np.concatenate(2 * [grid.weights.flatten()]))
|
|
77
|
+
D = np.diag(prefactors.flatten())
|
|
78
|
+
U = all_harmonics.reshape(len(prefactors), -1).T
|
|
79
|
+
return W, D, U
|
|
80
|
+
|
|
81
|
+
def get_initial_field_vsh_conversion_matrix(W, D, U):
|
|
82
|
+
N = W.shape[0]
|
|
83
|
+
S = np.diag(np.ones(N))
|
|
84
|
+
S[N//2:, N//2:] = -S[N//2:, N//2:]
|
|
85
|
+
sph_to_pw = -1 / (4 * np.pi) * S @ W @ U @ D.conj()
|
|
86
|
+
return sph_to_pw
|
|
87
|
+
|
|
88
|
+
def get_scattered_pw_conversion_matrix(W, D, U):
|
|
89
|
+
scat_pw_to_sph = 1 / np.pi * D @ U.T.conj() @ W
|
|
90
|
+
return scat_pw_to_sph
|
|
91
|
+
|
|
92
|
+
def get_initial_field_pw_conversion_matrix(W, D, U):
|
|
93
|
+
N = W.shape[0]
|
|
94
|
+
S = np.diag(np.ones(N))
|
|
95
|
+
S[N//2:, N//2:] = -S[N//2:, N//2:]
|
|
96
|
+
pw_to_sph = -4 * D @ U.T.conj() @ S
|
|
97
|
+
return pw_to_sph
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from addatmatrix.math import pwe_to_swe_conversion
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class PlaneWaveIllumination:
|
|
9
|
+
theta: float
|
|
10
|
+
phi: float
|
|
11
|
+
polarization: int
|
|
12
|
+
wavenumber: float
|
|
13
|
+
|
|
14
|
+
def get_spherical_wave_coefficients(self, particle_z, lmax):
|
|
15
|
+
kth = self.theta
|
|
16
|
+
kph = self.phi
|
|
17
|
+
pol = self.polarization
|
|
18
|
+
|
|
19
|
+
pos = [0, 0, particle_z]
|
|
20
|
+
kth_arr = np.array([kth])
|
|
21
|
+
azimuthal_angle = np.array([kph])[:, None]
|
|
22
|
+
incf = pwe_to_swe_conversion(lmax, lmax, pos, pos, int(pol), np.sin(kth_arr),
|
|
23
|
+
np.cos(kth_arr), azimuthal_angle)
|
|
24
|
+
return incf
|