addatmatrix 0.2.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 (92) hide show
  1. addatmatrix-0.2.0/PKG-INFO +15 -0
  2. addatmatrix-0.2.0/README.md +55 -0
  3. addatmatrix-0.2.0/addatmatrix/__init__.py +0 -0
  4. addatmatrix-0.2.0/addatmatrix/adda.bin +0 -0
  5. addatmatrix-0.2.0/addatmatrix/cross_section.py +60 -0
  6. addatmatrix-0.2.0/addatmatrix/data.py +177 -0
  7. addatmatrix-0.2.0/addatmatrix/far_field.py +151 -0
  8. addatmatrix-0.2.0/addatmatrix/gauss_legendre.py +97 -0
  9. addatmatrix-0.2.0/addatmatrix/incident_field.py +24 -0
  10. addatmatrix-0.2.0/addatmatrix/math.py +210 -0
  11. addatmatrix-0.2.0/addatmatrix/spatial.py +161 -0
  12. addatmatrix-0.2.0/addatmatrix/t_matrix.py +351 -0
  13. addatmatrix-0.2.0/addatmatrix/wrapper.py +223 -0
  14. addatmatrix-0.2.0/addatmatrix.egg-info/PKG-INFO +15 -0
  15. addatmatrix-0.2.0/addatmatrix.egg-info/SOURCES.txt +90 -0
  16. addatmatrix-0.2.0/addatmatrix.egg-info/dependency_links.txt +1 -0
  17. addatmatrix-0.2.0/addatmatrix.egg-info/top_level.txt +2 -0
  18. addatmatrix-0.2.0/benchmarks/__init__.py +0 -0
  19. addatmatrix-0.2.0/benchmarks/adda_orientation_average_multi_wl.py +98 -0
  20. addatmatrix-0.2.0/benchmarks/analyze_convergence_results.py +67 -0
  21. addatmatrix-0.2.0/benchmarks/benchmark_paper_test1.py +66 -0
  22. addatmatrix-0.2.0/benchmarks/compare_adda_orientation_average.py +98 -0
  23. addatmatrix-0.2.0/benchmarks/compare_single_runs_four_spheres.py +24 -0
  24. addatmatrix-0.2.0/benchmarks/compare_single_runs_sphere.py +25 -0
  25. addatmatrix-0.2.0/benchmarks/compare_single_runs_sphere_gold.py +25 -0
  26. addatmatrix-0.2.0/benchmarks/compare_single_runs_spheroid.py +25 -0
  27. addatmatrix-0.2.0/benchmarks/compare_single_runs_spheroid_nsurr1.py +23 -0
  28. addatmatrix-0.2.0/benchmarks/compare_smuthi_to_treams.py +54 -0
  29. addatmatrix-0.2.0/benchmarks/compare_to_treams.py +54 -0
  30. addatmatrix-0.2.0/benchmarks/convergence_run_adda.py +51 -0
  31. addatmatrix-0.2.0/benchmarks/convergence_run_adda_gl.py +47 -0
  32. addatmatrix-0.2.0/benchmarks/convergence_run_orientation_avg.py +104 -0
  33. addatmatrix-0.2.0/benchmarks/convergence_run_single_illumination_adda.py +105 -0
  34. addatmatrix-0.2.0/benchmarks/convergence_run_single_illumination_smuthi_multi_wl.py +142 -0
  35. addatmatrix-0.2.0/benchmarks/convergence_run_smuthi.py +49 -0
  36. addatmatrix-0.2.0/benchmarks/convergence_run_smuthi_multi_eps.py +144 -0
  37. addatmatrix-0.2.0/benchmarks/convergence_run_smuthi_multi_wl.py +141 -0
  38. addatmatrix-0.2.0/benchmarks/convergence_tests.py +103 -0
  39. addatmatrix-0.2.0/benchmarks/convergence_tests_get_t_matrix.py +128 -0
  40. addatmatrix-0.2.0/benchmarks/far_field_from_adda.py +99 -0
  41. addatmatrix-0.2.0/benchmarks/far_field_test.py +123 -0
  42. addatmatrix-0.2.0/benchmarks/far_field_test_analyze.py +325 -0
  43. addatmatrix-0.2.0/benchmarks/find_stable_point.py +65 -0
  44. addatmatrix-0.2.0/benchmarks/get_smuthi_t_format.py +47 -0
  45. addatmatrix-0.2.0/benchmarks/merge_dbs.py +226 -0
  46. addatmatrix-0.2.0/benchmarks/particles.py +74 -0
  47. addatmatrix-0.2.0/benchmarks/pipelines/__init__.py +4 -0
  48. addatmatrix-0.2.0/benchmarks/pipelines/adda_ops.py +47 -0
  49. addatmatrix-0.2.0/benchmarks/pipelines/common.py +151 -0
  50. addatmatrix-0.2.0/benchmarks/pipelines/db.py +89 -0
  51. addatmatrix-0.2.0/benchmarks/pipelines/pipeline1.py +279 -0
  52. addatmatrix-0.2.0/benchmarks/pipelines/pipeline1_5.py +535 -0
  53. addatmatrix-0.2.0/benchmarks/pipelines/pipeline2.py +531 -0
  54. addatmatrix-0.2.0/benchmarks/pipelines/smuthi_ops.py +93 -0
  55. addatmatrix-0.2.0/benchmarks/pipelines/utils.py +35 -0
  56. addatmatrix-0.2.0/benchmarks/read_far_field_from_db.py +82 -0
  57. addatmatrix-0.2.0/benchmarks/read_permittivity_data.py +41 -0
  58. addatmatrix-0.2.0/benchmarks/read_sm_conv_from_db.py +100 -0
  59. addatmatrix-0.2.0/benchmarks/read_t_matrix.py +28 -0
  60. addatmatrix-0.2.0/benchmarks/run_pipeline.py +46 -0
  61. addatmatrix-0.2.0/benchmarks/single_illumination_run_adda copy.py +32 -0
  62. addatmatrix-0.2.0/benchmarks/single_illumination_run_adda.py +17 -0
  63. addatmatrix-0.2.0/benchmarks/single_illumination_run_adda_four_spheres.py +20 -0
  64. addatmatrix-0.2.0/benchmarks/single_illumination_run_adda_sphere_diel.py +20 -0
  65. addatmatrix-0.2.0/benchmarks/single_illumination_run_adda_sphere_gold.py +20 -0
  66. addatmatrix-0.2.0/benchmarks/single_illumination_run_adda_spheroid.py +21 -0
  67. addatmatrix-0.2.0/benchmarks/single_illumination_run_adda_spheroid_nsurr_1.py +20 -0
  68. addatmatrix-0.2.0/benchmarks/single_illumination_smuthi_run.py +71 -0
  69. addatmatrix-0.2.0/benchmarks/single_illumination_smuthi_run_four_spheres.py +88 -0
  70. addatmatrix-0.2.0/benchmarks/single_illumination_smuthi_run_sphere_gold.py +77 -0
  71. addatmatrix-0.2.0/benchmarks/single_illumination_smuthi_run_spheroid.py +75 -0
  72. addatmatrix-0.2.0/benchmarks/single_illumination_smuthi_run_spheroid_nsurr1.py +74 -0
  73. addatmatrix-0.2.0/benchmarks/single_illumination_treams.py +12 -0
  74. addatmatrix-0.2.0/benchmarks/smuthi_indices.py +136 -0
  75. addatmatrix-0.2.0/benchmarks/test_gl.py +116 -0
  76. addatmatrix-0.2.0/benchmarks/test_hydra.py +30 -0
  77. addatmatrix-0.2.0/benchmarks/three_runs.py +66 -0
  78. addatmatrix-0.2.0/benchmarks/three_runs_sphere.py +54 -0
  79. addatmatrix-0.2.0/benchmarks/visualize_adda_orientation_average_multi_wl_results.py +156 -0
  80. addatmatrix-0.2.0/benchmarks/visualize_adda_vs_smuthi_multi_wl.py +315 -0
  81. addatmatrix-0.2.0/benchmarks/visualize_adda_vs_smuthi_orientation_avg.py +51 -0
  82. addatmatrix-0.2.0/benchmarks/visualize_conv_test.py +112 -0
  83. addatmatrix-0.2.0/benchmarks/visualize_conv_test_old_method.py +102 -0
  84. addatmatrix-0.2.0/benchmarks/visualize_convergence.py +96 -0
  85. addatmatrix-0.2.0/benchmarks/visualize_convergence_old_method.py +108 -0
  86. addatmatrix-0.2.0/benchmarks/visualize_convergence_run.py +206 -0
  87. addatmatrix-0.2.0/benchmarks/visualize_convergence_run_smuthi_multi_eps.py +176 -0
  88. addatmatrix-0.2.0/setup.cfg +4 -0
  89. addatmatrix-0.2.0/setup.py +75 -0
  90. addatmatrix-0.2.0/tests/test_gl.py +116 -0
  91. addatmatrix-0.2.0/tests/test_math.py +194 -0
  92. addatmatrix-0.2.0/tests/test_t_matrix.py +284 -0
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: addatmatrix
3
+ Version: 0.2.0
4
+ Summary: A package for calculating T matrices with ADDA
5
+ Home-page: https://github.com/k.czajkowski/addatmatrix
6
+ Author: Krzysztof Czajkowski
7
+ Author-email: kmczajkowski@int.pl
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Dynamic: author
12
+ Dynamic: author-email
13
+ Dynamic: classifier
14
+ Dynamic: home-page
15
+ Dynamic: summary
@@ -0,0 +1,55 @@
1
+ # addatmatrix
2
+
3
+ **addatmatrix** is a Python package that provides a convenient interface for calculating the T-matrix using the ADDA code.
4
+
5
+ ## Installation
6
+ Set your target device via environment variable `ADDA_DEVICE` to `cpu` or `gpu`:
7
+ ```commandline
8
+ export ADDA_DEVICE=gpu
9
+ ```
10
+
11
+ Install **addatmatrix** using pip:
12
+
13
+ ```bash
14
+ pip install -r requirements.txt
15
+ pip install .
16
+ ```
17
+ ## Usage
18
+ ### CLI
19
+ The most straight forward way of using `addatmatrix` is via the CLI:
20
+
21
+ `./addatmatrix_cli.py COMMAND [ARGS]`
22
+
23
+ Commands:
24
+ - `tmatrix` - Computes T-matrix from multiple runs of ADDA
25
+ - `spectrum` - Computes cross sections for various wavelengths
26
+
27
+ A minimal example:
28
+ ```commandline
29
+ ./addatmatrix_cli.py tmatrix --adda_string "-m 2.5 0.0"
30
+ ```
31
+ where `adda_string` argument is a string provided to ADDA CLI
32
+
33
+ Output format:
34
+ - The T-matrix is stored as an hdf5, following conventions decribed in Ref. 1.
35
+ - The spectrum is stored as a npy file.
36
+
37
+ Help on specific command:
38
+ ```commandline
39
+ ./addatmatrix_cli.py tmatrix --help
40
+ ```
41
+
42
+
43
+ ### Scripting
44
+ The `tmatrix` command is a wrapper for `addatmatrix.t_matrix.get_t_matrix` function.
45
+
46
+ A usage example has been shown in `examples/sphere_dscs.py`, which can be run like this:
47
+ ```commandline
48
+ python examples/sphere_dscs.py
49
+ ```
50
+ from the main repository directory.
51
+
52
+ ## References
53
+ If you use this code in your work, please cite the following papers:
54
+ - Asadova N. et al. T-matrix representation of optical scattering response: Suggestion for a data format, J. Quant. Spectrosc. Radiat. Transfer 333, 109310 (2025)
55
+ - Yurkin M.A. and Hoekstra A.G. The discrete-dipole-approximation code ADDA: capabilities and known limitations, J. Quant. Spectrosc. Radiat. Transfer 112, 2234–2247 (2011)
File without changes
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
+
@@ -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
@@ -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