vectorwaves 1.0.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.
- vectorwaves/__init__.py +88 -0
- vectorwaves/backends/__init__.py +0 -0
- vectorwaves/backends/cupy_backend.py +197 -0
- vectorwaves/backends/numba_backend.py +271 -0
- vectorwaves/backends/numpy_backend.py +193 -0
- vectorwaves/beam_stuff.py +623 -0
- vectorwaves/config_stuff.py +747 -0
- vectorwaves/engine_stuff.py +379 -0
- vectorwaves/py.typed +0 -0
- vectorwaves/singularities.py +617 -0
- vectorwaves/spectra.py +341 -0
- vectorwaves/utils.py +130 -0
- vectorwaves/version.py +5 -0
- vectorwaves-1.0.0.dist-info/METADATA +114 -0
- vectorwaves-1.0.0.dist-info/RECORD +18 -0
- vectorwaves-1.0.0.dist-info/WHEEL +5 -0
- vectorwaves-1.0.0.dist-info/licenses/LICENSE +21 -0
- vectorwaves-1.0.0.dist-info/top_level.txt +1 -0
vectorwaves/__init__.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
VectorWaves - A Python library for electromagnetic field simulations using plane wave expansion.
|
|
3
|
+
==========================================================================
|
|
4
|
+
|
|
5
|
+
Main workflow:
|
|
6
|
+
>>> import vectorwaves as vw
|
|
7
|
+
>>> config = vw.get_config()
|
|
8
|
+
>>> config.source.num_modes = 10000
|
|
9
|
+
>>> expt = vw.setup_engine(config)
|
|
10
|
+
>>> result = expt.compute_on_op(z=0.0)
|
|
11
|
+
|
|
12
|
+
Author: Mayank Soni
|
|
13
|
+
Year: 2026
|
|
14
|
+
"""
|
|
15
|
+
from .version import current_version, current_version_str
|
|
16
|
+
__version_info__ = current_version
|
|
17
|
+
__version__ = current_version_str
|
|
18
|
+
|
|
19
|
+
# Core configuration
|
|
20
|
+
from .config_stuff import (
|
|
21
|
+
Config,
|
|
22
|
+
OpConfig,
|
|
23
|
+
SourceConfig,
|
|
24
|
+
RandomizeConfig,
|
|
25
|
+
KSpaceConfig,
|
|
26
|
+
PolychromaticConfig,
|
|
27
|
+
get_config,
|
|
28
|
+
load_config
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Core Pipeline
|
|
32
|
+
from .beam_stuff import BeamMaker, Beam
|
|
33
|
+
from .engine_stuff import FieldEngine, FieldResult, tqdm
|
|
34
|
+
|
|
35
|
+
# Utilities & Physics Math
|
|
36
|
+
from .utils import (
|
|
37
|
+
get_stokes_params,
|
|
38
|
+
get_pol_ellipse_params,
|
|
39
|
+
decompose_in_basis,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Singularity Finders
|
|
43
|
+
from .singularities import SingularityFinder
|
|
44
|
+
|
|
45
|
+
def setup_beam(config: Config) -> Beam:
|
|
46
|
+
"""High-level wrapper to generate the beam from config object."""
|
|
47
|
+
return BeamMaker(config).generate_beam()
|
|
48
|
+
|
|
49
|
+
def setup_engine(config: Config) -> FieldEngine:
|
|
50
|
+
"""
|
|
51
|
+
High-level wrapper to generate the beam and initialize the computation engine.
|
|
52
|
+
"""
|
|
53
|
+
return FieldEngine(setup_beam(config), config)
|
|
54
|
+
|
|
55
|
+
__all__ =[
|
|
56
|
+
# Config
|
|
57
|
+
"Config",
|
|
58
|
+
"OpConfig",
|
|
59
|
+
"SourceConfig",
|
|
60
|
+
"RandomizeConfig",
|
|
61
|
+
"KSpaceConfig",
|
|
62
|
+
"PolychromaticConfig",
|
|
63
|
+
"get_config",
|
|
64
|
+
"load_config",
|
|
65
|
+
|
|
66
|
+
# Engine Pipeline
|
|
67
|
+
"setup_engine",
|
|
68
|
+
"setup_beam",
|
|
69
|
+
"BeamMaker",
|
|
70
|
+
"Beam",
|
|
71
|
+
"FieldEngine",
|
|
72
|
+
"FieldResult",
|
|
73
|
+
|
|
74
|
+
# Utilities
|
|
75
|
+
"get_stokes_params",
|
|
76
|
+
"get_pol_ellipse_params",
|
|
77
|
+
"decompose_in_basis",
|
|
78
|
+
|
|
79
|
+
# Topologies
|
|
80
|
+
"SingularityFinder",
|
|
81
|
+
|
|
82
|
+
# tqdm
|
|
83
|
+
"tqdm",
|
|
84
|
+
|
|
85
|
+
# Version
|
|
86
|
+
"__version__",
|
|
87
|
+
"__version_info__"
|
|
88
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
try:
|
|
3
|
+
import cupy as cp
|
|
4
|
+
has_cupy = True
|
|
5
|
+
except ImportError:
|
|
6
|
+
has_cupy = False
|
|
7
|
+
|
|
8
|
+
class CupyMethods:
|
|
9
|
+
def __init__(self, beam, use_single_precision=True):
|
|
10
|
+
if not has_cupy:
|
|
11
|
+
raise RuntimeError("CuPy/CUDA not found.")
|
|
12
|
+
|
|
13
|
+
self.use_single = use_single_precision
|
|
14
|
+
self.real_dt = cp.float32 if self.use_single else cp.float64
|
|
15
|
+
self.comp_dt = cp.complex64 if self.use_single else cp.complex128
|
|
16
|
+
|
|
17
|
+
# --- Persistent Model Data ---
|
|
18
|
+
def to_gpu(arr, dtype): return cp.ascontiguousarray(cp.asarray(arr, dtype=dtype))
|
|
19
|
+
|
|
20
|
+
self.kx = to_gpu(beam.k[0], self.real_dt)
|
|
21
|
+
self.ky = to_gpu(beam.k[1], self.real_dt)
|
|
22
|
+
self.kz = to_gpu(beam.k[2], self.real_dt)
|
|
23
|
+
self.w = to_gpu(beam.w, self.real_dt)
|
|
24
|
+
self.inv_w = to_gpu(beam.inv_w, self.real_dt)
|
|
25
|
+
self.c_base = to_gpu(beam.c, self.comp_dt)
|
|
26
|
+
self.num_waves = len(self.w)
|
|
27
|
+
|
|
28
|
+
self._kernel_cache = {}
|
|
29
|
+
|
|
30
|
+
def _get_kernel(self, num_components, is_grid=False):
|
|
31
|
+
key = (num_components, is_grid)
|
|
32
|
+
if key in self._kernel_cache: return self._kernel_cache[key]
|
|
33
|
+
|
|
34
|
+
real_t = "float" if self.use_single else "double"
|
|
35
|
+
comp_t = "complex<float>" if self.use_single else "complex<double>"
|
|
36
|
+
sincos_f = "sincosf" if self.use_single else "sincos"
|
|
37
|
+
|
|
38
|
+
coord_logic = """
|
|
39
|
+
int ix = p % nx;
|
|
40
|
+
int iy = p / nx;
|
|
41
|
+
{real_t} px = x_vec[ix];
|
|
42
|
+
{real_t} py = y_vec[iy];
|
|
43
|
+
{real_t} pz = z_scalar;
|
|
44
|
+
""" if is_grid else """
|
|
45
|
+
{real_t} px = x_vec[p];
|
|
46
|
+
{real_t} py = y_vec[p];
|
|
47
|
+
{real_t} pz = z_vec[p];
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
kernel_code = f"""
|
|
51
|
+
#include <cupy/complex.cuh>
|
|
52
|
+
extern "C" __global__
|
|
53
|
+
void compute_kernel(
|
|
54
|
+
const {real_t}* __restrict__ x_vec, const {real_t}* __restrict__ y_vec, const {real_t}* __restrict__ z_vec,
|
|
55
|
+
const {real_t}* __restrict__ kx, const {real_t}* __restrict__ ky, const {real_t}* __restrict__ kz,
|
|
56
|
+
const {real_t}* __restrict__ w, const {comp_t}* __restrict__ super_vec,
|
|
57
|
+
{comp_t}* __restrict__ out,
|
|
58
|
+
{real_t} z_scalar, {real_t} t, int nx, int num_pts, int num_waves
|
|
59
|
+
) {{
|
|
60
|
+
int p = blockDim.x * blockIdx.x + threadIdx.x;
|
|
61
|
+
if (p >= num_pts) return;
|
|
62
|
+
|
|
63
|
+
{coord_logic.format(real_t=real_t)}
|
|
64
|
+
|
|
65
|
+
{comp_t} acc[{num_components}];
|
|
66
|
+
#pragma unroll
|
|
67
|
+
for(int i=0; i<{num_components}; i++) acc[i] = {comp_t}(0, 0);
|
|
68
|
+
|
|
69
|
+
for (int i = 0; i < num_waves; i++) {{
|
|
70
|
+
{real_t} phase = kx[i]*px + ky[i]*py + kz[i]*pz - w[i]*t;
|
|
71
|
+
{real_t} s, c;
|
|
72
|
+
{sincos_f}(phase, &s, &c);
|
|
73
|
+
{comp_t} wf(c, s);
|
|
74
|
+
|
|
75
|
+
#pragma unroll
|
|
76
|
+
for (int j = 0; j < {num_components}; j++) {{
|
|
77
|
+
acc[j] += super_vec[i * {num_components} + j] * wf;
|
|
78
|
+
}}
|
|
79
|
+
}}
|
|
80
|
+
|
|
81
|
+
for (int j = 0; j < {num_components}; j++) {{
|
|
82
|
+
out[j * num_pts + p] = acc[j];
|
|
83
|
+
}}
|
|
84
|
+
}}
|
|
85
|
+
"""
|
|
86
|
+
kernel = cp.RawKernel(kernel_code, 'compute_kernel')
|
|
87
|
+
self._kernel_cache[key] = kernel
|
|
88
|
+
return kernel
|
|
89
|
+
|
|
90
|
+
def _prepare_super_vec(self, need_b, need_derivs):
|
|
91
|
+
vecs = [self.c_base]
|
|
92
|
+
if need_b:
|
|
93
|
+
bx = (self.ky * self.c_base[2] - self.kz * self.c_base[1]) * self.inv_w
|
|
94
|
+
by = (self.kz * self.c_base[0] - self.kx * self.c_base[2]) * self.inv_w
|
|
95
|
+
bz = (self.kx * self.c_base[1] - self.ky * self.c_base[0]) * self.inv_w
|
|
96
|
+
vecs.append(cp.stack([bx, by, bz]))
|
|
97
|
+
if need_derivs:
|
|
98
|
+
ik = 1j * cp.stack([self.kx, self.ky, self.kz])
|
|
99
|
+
for i in range(3): vecs.append(self.c_base * ik[i])
|
|
100
|
+
|
|
101
|
+
return cp.ascontiguousarray(cp.vstack(vecs).T)
|
|
102
|
+
|
|
103
|
+
def compute_grid(self, x_vec, y_vec, z, t, need_b=True, need_derivs=True, progress_callback=None):
|
|
104
|
+
nx, ny = len(x_vec), len(y_vec)
|
|
105
|
+
|
|
106
|
+
super_vec = self._prepare_super_vec(need_b, need_derivs)
|
|
107
|
+
num_comps = super_vec.shape[1]
|
|
108
|
+
kernel = self._get_kernel(num_comps, is_grid=True)
|
|
109
|
+
|
|
110
|
+
# Pre-allocate the final CPU array to store results
|
|
111
|
+
out_h = np.zeros((num_comps, ny, nx), dtype=np.complex128)
|
|
112
|
+
|
|
113
|
+
# Move the X vector to GPU once
|
|
114
|
+
x_g = cp.asarray(x_vec, dtype=self.real_dt)
|
|
115
|
+
|
|
116
|
+
# Target ~500MB maximum VRAM footprint for the output buffer
|
|
117
|
+
bytes_per_element = 8 if self.use_single else 16
|
|
118
|
+
bytes_per_row = nx * num_comps * bytes_per_element
|
|
119
|
+
MAX_VRAM_BYTES = 1000 * 1024 * 1024 # 1 GB
|
|
120
|
+
|
|
121
|
+
# Calculate how many rows we can safely process at once
|
|
122
|
+
rows_per_batch = max(1, MAX_VRAM_BYTES // bytes_per_row)
|
|
123
|
+
|
|
124
|
+
for i in range(0, ny, rows_per_batch):
|
|
125
|
+
end = min(i + rows_per_batch, ny)
|
|
126
|
+
cur_ny = end - i
|
|
127
|
+
cur_pts = nx * cur_ny
|
|
128
|
+
|
|
129
|
+
# Move just this chunk of Y coordinates to GPU
|
|
130
|
+
y_g = cp.asarray(y_vec[i:end], dtype=self.real_dt)
|
|
131
|
+
|
|
132
|
+
# Allocate GPU buffer for just this batch
|
|
133
|
+
out_g = cp.empty((num_comps, cur_pts), dtype=self.comp_dt)
|
|
134
|
+
|
|
135
|
+
threads = 256
|
|
136
|
+
blocks = (cur_pts + threads - 1) // threads
|
|
137
|
+
|
|
138
|
+
kernel((blocks,), (threads,), (
|
|
139
|
+
x_g, y_g, None, self.kx, self.ky, self.kz, self.w,
|
|
140
|
+
super_vec, out_g,
|
|
141
|
+
self.real_dt(z), self.real_dt(t), cp.int32(nx), cp.int32(cur_pts), cp.int32(self.num_waves)
|
|
142
|
+
))
|
|
143
|
+
|
|
144
|
+
# Fetch result to CPU and reshape directly into the pre-allocated CPU array
|
|
145
|
+
out_h[:, i:end, :] = out_g.get().reshape(num_comps, cur_ny, nx)
|
|
146
|
+
|
|
147
|
+
if progress_callback:
|
|
148
|
+
progress_callback(cur_ny)
|
|
149
|
+
|
|
150
|
+
# Unpack the CPU array into standard shapes
|
|
151
|
+
E = out_h[0:3]
|
|
152
|
+
idx = 3
|
|
153
|
+
B = out_h[idx:idx+3] if need_b else None
|
|
154
|
+
if need_b: idx += 3
|
|
155
|
+
D = (out_h[idx:idx+3], out_h[idx+3:idx+6], out_h[idx+6:idx+9]) if need_derivs else (None,None,None)
|
|
156
|
+
|
|
157
|
+
return E, D, B
|
|
158
|
+
|
|
159
|
+
def compute_cloud(self, x, y, z, t, need_b=True, need_derivs=True, progress_callback=None):
|
|
160
|
+
num_pts = len(x)
|
|
161
|
+
super_vec = self._prepare_super_vec(need_b, need_derivs)
|
|
162
|
+
num_comps = super_vec.shape[1]
|
|
163
|
+
kernel = self._get_kernel(num_comps, is_grid=False)
|
|
164
|
+
|
|
165
|
+
E_h, B_h, dx_h, dy_h, dz_h = self._allocate_cpu_arrays((num_pts,))
|
|
166
|
+
CHUNK = 500_000
|
|
167
|
+
|
|
168
|
+
for s in range(0, num_pts, CHUNK):
|
|
169
|
+
e = min(s + CHUNK, num_pts)
|
|
170
|
+
cur_n = e - s
|
|
171
|
+
x_g, y_g, z_g = [cp.asarray(arr[s:e], dtype=self.real_dt) for arr in [x,y,z]]
|
|
172
|
+
out_g = cp.empty((num_comps, cur_n), dtype=self.comp_dt)
|
|
173
|
+
|
|
174
|
+
kernel(((cur_n+255)//256,), (256,), (
|
|
175
|
+
x_g, y_g, z_g, self.kx, self.ky, self.kz, self.w,
|
|
176
|
+
super_vec, out_g, 0.0, self.real_dt(t), 0, cp.int32(cur_n), cp.int32(self.num_waves)
|
|
177
|
+
))
|
|
178
|
+
|
|
179
|
+
out_c = out_g.get()
|
|
180
|
+
if progress_callback: progress_callback(cur_n)
|
|
181
|
+
E_h[:, s:e] = out_c[0:3]
|
|
182
|
+
idx = 3
|
|
183
|
+
if need_b: B_h[:, s:e] = out_c[idx:idx+3]; idx += 3
|
|
184
|
+
if need_derivs:
|
|
185
|
+
dx_h[:, s:e], dy_h[:, s:e], dz_h[:, s:e] = out_c[idx:idx+3], out_c[idx+3:idx+6], out_c[idx+6:idx+9]
|
|
186
|
+
|
|
187
|
+
return E_h, (dx_h, dy_h, dz_h) if need_derivs else (None,None,None), B_h if need_b else None
|
|
188
|
+
|
|
189
|
+
def _allocate_cpu_arrays(self, shape):
|
|
190
|
+
return tuple(np.zeros((3, *shape), dtype=np.complex128) for _ in range(5))
|
|
191
|
+
|
|
192
|
+
def compute_point(self, x, y, z, t, need_b=True, need_derivs=True):
|
|
193
|
+
E, D, B = self.compute_cloud(np.array([x]), np.array([y]), np.array([z]), t, need_b, need_derivs)
|
|
194
|
+
return E[:,0], (tuple(d[:,0] for d in D) if need_derivs else (None,None,None)), (B[:,0] if need_b else None) # type: ignore
|
|
195
|
+
|
|
196
|
+
def __del__(self):
|
|
197
|
+
self._kernel_cache.clear()
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import time; from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from numba import jit, prange
|
|
6
|
+
has_numba = True
|
|
7
|
+
except ImportError:
|
|
8
|
+
def jit(*args, **kwargs):
|
|
9
|
+
def decor(f): return f
|
|
10
|
+
return decor
|
|
11
|
+
prange = range
|
|
12
|
+
has_numba = False
|
|
13
|
+
|
|
14
|
+
K_ARGS = {'nopython': True, 'parallel': True, 'fastmath': True, 'cache': True}
|
|
15
|
+
|
|
16
|
+
@jit(**K_ARGS)
|
|
17
|
+
def _kernel_grid_rect(x_vec, y_vec, z, t, kx, ky, kz, cx, cy, cz, w, inv_w,
|
|
18
|
+
need_b, need_derivs,
|
|
19
|
+
E_out, B_out, dx_out, dy_out, dz_out):
|
|
20
|
+
nx, ny = len(x_vec), len(y_vec)
|
|
21
|
+
num_waves = len(w)
|
|
22
|
+
|
|
23
|
+
for iy in prange(ny):
|
|
24
|
+
y = y_vec[iy]
|
|
25
|
+
for ix in range(nx):
|
|
26
|
+
x = x_vec[ix]
|
|
27
|
+
|
|
28
|
+
ex, ey, ez = 0j, 0j, 0j
|
|
29
|
+
bx, by, bz = 0j, 0j, 0j
|
|
30
|
+
dxx, dxy, dxz, dyx, dyy, dyz, dzx, dzy, dzz = 0j,0j,0j,0j,0j,0j,0j,0j,0j
|
|
31
|
+
|
|
32
|
+
if not need_b and not need_derivs:
|
|
33
|
+
for i in range(num_waves):
|
|
34
|
+
wf = np.exp(1j * (kx[i]*x + ky[i]*y + kz[i]*z - w[i]*t))
|
|
35
|
+
ex += cx[i] * wf; ey += cy[i] * wf; ez += cz[i] * wf
|
|
36
|
+
|
|
37
|
+
elif need_b and not need_derivs:
|
|
38
|
+
for i in range(num_waves):
|
|
39
|
+
wf = np.exp(1j * (kx[i]*x + ky[i]*y + kz[i]*z - w[i]*t))
|
|
40
|
+
c_x, c_y, c_z = cx[i]*wf, cy[i]*wf, cz[i]*wf
|
|
41
|
+
ex += c_x; ey += c_y; ez += c_z
|
|
42
|
+
|
|
43
|
+
bx += (ky[i]*c_z - kz[i]*c_y) * inv_w[i]
|
|
44
|
+
by += (kz[i]*c_x - kx[i]*c_z) * inv_w[i]
|
|
45
|
+
bz += (kx[i]*c_y - ky[i]*c_x) * inv_w[i]
|
|
46
|
+
|
|
47
|
+
elif not need_b and need_derivs:
|
|
48
|
+
for i in range(num_waves):
|
|
49
|
+
wf = np.exp(1j * (kx[i]*x + ky[i]*y + kz[i]*z - w[i]*t))
|
|
50
|
+
c_x, c_y, c_z = cx[i]*wf, cy[i]*wf, cz[i]*wf
|
|
51
|
+
ex += c_x; ey += c_y; ez += c_z
|
|
52
|
+
|
|
53
|
+
ikx, iky, ikz = 1j*kx[i], 1j*ky[i], 1j*kz[i]
|
|
54
|
+
dxx += ikx*c_x; dxy += ikx*c_y; dxz += ikx*c_z
|
|
55
|
+
dyx += iky*c_x; dyy += iky*c_y; dyz += iky*c_z
|
|
56
|
+
dzx += ikz*c_x; dzy += ikz*c_y; dzz += ikz*c_z
|
|
57
|
+
|
|
58
|
+
else: # need_b AND need_derivs
|
|
59
|
+
for i in range(num_waves):
|
|
60
|
+
wf = np.exp(1j * (kx[i]*x + ky[i]*y + kz[i]*z - w[i]*t))
|
|
61
|
+
c_x, c_y, c_z = cx[i]*wf, cy[i]*wf, cz[i]*wf
|
|
62
|
+
ex += c_x; ey += c_y; ez += c_z
|
|
63
|
+
|
|
64
|
+
bx += (ky[i]*c_z - kz[i]*c_y) * inv_w[i]
|
|
65
|
+
by += (kz[i]*c_x - kx[i]*c_z) * inv_w[i]
|
|
66
|
+
bz += (kx[i]*c_y - ky[i]*c_x) * inv_w[i]
|
|
67
|
+
|
|
68
|
+
ikx, iky, ikz = 1j*kx[i], 1j*ky[i], 1j*kz[i]
|
|
69
|
+
dxx += ikx*c_x; dxy += ikx*c_y; dxz += ikx*c_z
|
|
70
|
+
dyx += iky*c_x; dyy += iky*c_y; dyz += iky*c_z
|
|
71
|
+
dzx += ikz*c_x; dzy += ikz*c_y; dzz += ikz*c_z
|
|
72
|
+
|
|
73
|
+
E_out[0, iy, ix], E_out[1, iy, ix], E_out[2, iy, ix] = ex, ey, ez
|
|
74
|
+
|
|
75
|
+
if need_b:
|
|
76
|
+
B_out[0, iy, ix], B_out[1, iy, ix], B_out[2, iy, ix] = bx, by, bz
|
|
77
|
+
|
|
78
|
+
if need_derivs:
|
|
79
|
+
dx_out[0, iy, ix], dx_out[1, iy, ix], dx_out[2, iy, ix] = dxx, dxy, dxz
|
|
80
|
+
dy_out[0, iy, ix], dy_out[1, iy, ix], dy_out[2, iy, ix] = dyx, dyy, dyz
|
|
81
|
+
dz_out[0, iy, ix], dz_out[1, iy, ix], dz_out[2, iy, ix] = dzx, dzy, dzz
|
|
82
|
+
|
|
83
|
+
@jit(**K_ARGS)
|
|
84
|
+
def _kernel_cloud_flat(x_arr, y_arr, z_arr, t, kx, ky, kz, cx, cy, cz, w, inv_w,
|
|
85
|
+
need_b, need_derivs,
|
|
86
|
+
E_out, B_out, dx_out, dy_out, dz_out):
|
|
87
|
+
num_points = len(x_arr)
|
|
88
|
+
num_waves = len(w)
|
|
89
|
+
|
|
90
|
+
for p in prange(num_points):
|
|
91
|
+
x, y, z = x_arr[p], y_arr[p], z_arr[p]
|
|
92
|
+
ex, ey, ez = 0j, 0j, 0j
|
|
93
|
+
bx, by, bz = 0j, 0j, 0j
|
|
94
|
+
dxx, dxy, dxz, dyx, dyy, dyz, dzx, dzy, dzz = 0j,0j,0j,0j,0j,0j,0j,0j,0j
|
|
95
|
+
|
|
96
|
+
if not need_b and not need_derivs:
|
|
97
|
+
for i in range(num_waves):
|
|
98
|
+
wf = np.exp(1j * (kx[i]*x + ky[i]*y + kz[i]*z - w[i]*t))
|
|
99
|
+
ex += cx[i] * wf; ey += cy[i] * wf; ez += cz[i] * wf
|
|
100
|
+
|
|
101
|
+
elif need_b and not need_derivs:
|
|
102
|
+
for i in range(num_waves):
|
|
103
|
+
wf = np.exp(1j * (kx[i]*x + ky[i]*y + kz[i]*z - w[i]*t))
|
|
104
|
+
c_x, c_y, c_z = cx[i]*wf, cy[i]*wf, cz[i]*wf
|
|
105
|
+
ex += c_x; ey += c_y; ez += c_z
|
|
106
|
+
|
|
107
|
+
bx += (ky[i]*c_z - kz[i]*c_y)*inv_w[i]
|
|
108
|
+
by += (kz[i]*c_x - kx[i]*c_z)*inv_w[i]
|
|
109
|
+
bz += (kx[i]*c_y - ky[i]*c_x)*inv_w[i]
|
|
110
|
+
|
|
111
|
+
elif not need_b and need_derivs:
|
|
112
|
+
for i in range(num_waves):
|
|
113
|
+
wf = np.exp(1j * (kx[i]*x + ky[i]*y + kz[i]*z - w[i]*t))
|
|
114
|
+
c_x, c_y, c_z = cx[i]*wf, cy[i]*wf, cz[i]*wf
|
|
115
|
+
ex += c_x; ey += c_y; ez += c_z
|
|
116
|
+
|
|
117
|
+
ikx, iky, ikz = 1j*kx[i], 1j*ky[i], 1j*kz[i]
|
|
118
|
+
dxx += ikx*c_x; dxy += ikx*c_y; dxz += ikx*c_z
|
|
119
|
+
dyx += iky*c_x; dyy += iky*c_y; dyz += iky*c_z
|
|
120
|
+
dzx += ikz*c_x; dzy += ikz*c_y; dzz += ikz*c_z
|
|
121
|
+
|
|
122
|
+
else:
|
|
123
|
+
for i in range(num_waves):
|
|
124
|
+
wf = np.exp(1j * (kx[i]*x + ky[i]*y + kz[i]*z - w[i]*t))
|
|
125
|
+
c_x, c_y, c_z = cx[i]*wf, cy[i]*wf, cz[i]*wf
|
|
126
|
+
ex += c_x; ey += c_y; ez += c_z
|
|
127
|
+
|
|
128
|
+
bx += (ky[i]*c_z - kz[i]*c_y)*inv_w[i]
|
|
129
|
+
by += (kz[i]*c_x - kx[i]*c_z)*inv_w[i]
|
|
130
|
+
bz += (kx[i]*c_y - ky[i]*c_x)*inv_w[i]
|
|
131
|
+
|
|
132
|
+
ikx, iky, ikz = 1j*kx[i], 1j*ky[i], 1j*kz[i]
|
|
133
|
+
dxx += ikx*c_x; dxy += ikx*c_y; dxz += ikx*c_z
|
|
134
|
+
dyx += iky*c_x; dyy += iky*c_y; dyz += iky*c_z
|
|
135
|
+
dzx += ikz*c_x; dzy += ikz*c_y; dzz += ikz*c_z
|
|
136
|
+
|
|
137
|
+
E_out[0, p], E_out[1, p], E_out[2, p] = ex, ey, ez
|
|
138
|
+
|
|
139
|
+
if need_b: B_out[0, p], B_out[1, p], B_out[2, p] = bx, by, bz
|
|
140
|
+
|
|
141
|
+
if need_derivs:
|
|
142
|
+
dx_out[0,p], dx_out[1,p], dx_out[2,p] = dxx, dxy, dxz
|
|
143
|
+
dy_out[0,p], dy_out[1,p], dy_out[2,p] = dyx, dyy, dyz
|
|
144
|
+
dz_out[0,p], dz_out[1,p], dz_out[2,p] = dzx, dzy, dzz
|
|
145
|
+
|
|
146
|
+
@jit(nopython=True, parallel=False, fastmath=True, cache=True)
|
|
147
|
+
def _kernel_point(x, y, z, t, kx, ky, kz, cx, cy, cz, w, inv_w, need_b, need_derivs):
|
|
148
|
+
num_waves = len(w)
|
|
149
|
+
ex, ey, ez = 0j, 0j, 0j
|
|
150
|
+
bx, by, bz = 0j, 0j, 0j
|
|
151
|
+
dxx, dxy, dxz, dyx, dyy, dyz, dzx, dzy, dzz = 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j
|
|
152
|
+
|
|
153
|
+
if not need_b and not need_derivs:
|
|
154
|
+
for i in range(num_waves):
|
|
155
|
+
wf = np.exp(1j * (kx[i]*x + ky[i]*y + kz[i]*z - w[i]*t))
|
|
156
|
+
ex += cx[i] * wf; ey += cy[i] * wf; ez += cz[i] * wf
|
|
157
|
+
|
|
158
|
+
elif need_b and not need_derivs:
|
|
159
|
+
for i in range(num_waves):
|
|
160
|
+
wf = np.exp(1j * (kx[i]*x + ky[i]*y + kz[i]*z - w[i]*t))
|
|
161
|
+
c_x, c_y, c_z = cx[i]*wf, cy[i]*wf, cz[i]*wf
|
|
162
|
+
ex += c_x; ey += c_y; ez += c_z
|
|
163
|
+
|
|
164
|
+
bx += (ky[i]*c_z - kz[i]*c_y)*inv_w[i]
|
|
165
|
+
by += (kz[i]*c_x - kx[i]*c_z)*inv_w[i]
|
|
166
|
+
bz += (kx[i]*c_y - ky[i]*c_x)*inv_w[i]
|
|
167
|
+
|
|
168
|
+
elif not need_b and need_derivs:
|
|
169
|
+
for i in range(num_waves):
|
|
170
|
+
wf = np.exp(1j * (kx[i]*x + ky[i]*y + kz[i]*z - w[i]*t))
|
|
171
|
+
c_x, c_y, c_z = cx[i]*wf, cy[i]*wf, cz[i]*wf
|
|
172
|
+
ex += c_x; ey += c_y; ez += c_z
|
|
173
|
+
|
|
174
|
+
ikx, iky, ikz = 1j*kx[i], 1j*ky[i], 1j*kz[i]
|
|
175
|
+
dxx += ikx*c_x; dxy += ikx*c_y; dxz += ikx*c_z
|
|
176
|
+
dyx += iky*c_x; dyy += iky*c_y; dyz += iky*c_z
|
|
177
|
+
dzx += ikz*c_x; dzy += ikz*c_y; dzz += ikz*c_z
|
|
178
|
+
|
|
179
|
+
else:
|
|
180
|
+
for i in range(num_waves):
|
|
181
|
+
wf = np.exp(1j * (kx[i]*x + ky[i]*y + kz[i]*z - w[i]*t))
|
|
182
|
+
c_x, c_y, c_z = cx[i]*wf, cy[i]*wf, cz[i]*wf
|
|
183
|
+
ex += c_x; ey += c_y; ez += c_z
|
|
184
|
+
|
|
185
|
+
bx += (ky[i]*c_z - kz[i]*c_y)*inv_w[i]
|
|
186
|
+
by += (kz[i]*c_x - kx[i]*c_z)*inv_w[i]
|
|
187
|
+
bz += (kx[i]*c_y - ky[i]*c_x)*inv_w[i]
|
|
188
|
+
|
|
189
|
+
ikx, iky, ikz = 1j*kx[i], 1j*ky[i], 1j*kz[i]
|
|
190
|
+
dxx += ikx*c_x; dxy += ikx*c_y; dxz += ikx*c_z
|
|
191
|
+
dyx += iky*c_x; dyy += iky*c_y; dyz += iky*c_z
|
|
192
|
+
dzx += ikz*c_x; dzy += ikz*c_y; dzz += ikz*c_z
|
|
193
|
+
|
|
194
|
+
E = np.empty(3, dtype=np.complex128)
|
|
195
|
+
E[0], E[1], E[2] = ex, ey, ez
|
|
196
|
+
|
|
197
|
+
B = np.empty(3, dtype=np.complex128)
|
|
198
|
+
if need_b: B[0], B[1], B[2] = bx, by, bz
|
|
199
|
+
|
|
200
|
+
dx = np.empty(3, dtype=np.complex128)
|
|
201
|
+
dy = np.empty(3, dtype=np.complex128)
|
|
202
|
+
dz = np.empty(3, dtype=np.complex128)
|
|
203
|
+
if need_derivs:
|
|
204
|
+
dx[0], dx[1], dx[2] = dxx, dxy, dxz
|
|
205
|
+
dy[0], dy[1], dy[2] = dyx, dyy, dyz
|
|
206
|
+
dz[0], dz[1], dz[2] = dzx, dzy, dzz
|
|
207
|
+
|
|
208
|
+
return E, (dx, dy, dz), B
|
|
209
|
+
|
|
210
|
+
class NumbaMethods:
|
|
211
|
+
def __init__(self, beam, max_points_per_batch=250_000):
|
|
212
|
+
self.kx, self.ky, self.kz = beam.k
|
|
213
|
+
self.cx, self.cy, self.cz = beam.c
|
|
214
|
+
self.w, self.inv_w = beam.w, beam.inv_w
|
|
215
|
+
self.max_batch_size = max_points_per_batch
|
|
216
|
+
|
|
217
|
+
def _allocate_arrays(self, shape):
|
|
218
|
+
return tuple(np.empty((3,*shape), dtype=np.complex128) for _ in range(5))
|
|
219
|
+
|
|
220
|
+
def compute_cloud(self, x, y, z, t, need_b=True, need_derivs=True, progress_callback=None):
|
|
221
|
+
total_points = len(x)
|
|
222
|
+
batch_size = self.max_batch_size
|
|
223
|
+
if progress_callback:
|
|
224
|
+
batch_size = min(self.max_batch_size, max(1, total_points // 5))
|
|
225
|
+
|
|
226
|
+
E, B, dx, dy, dz = self._allocate_arrays((total_points,))
|
|
227
|
+
|
|
228
|
+
for i in range(0, total_points, batch_size):
|
|
229
|
+
end = min(i + batch_size, total_points)
|
|
230
|
+
_kernel_cloud_flat(
|
|
231
|
+
x[i:end], y[i:end], z[i:end], t,
|
|
232
|
+
self.kx, self.ky, self.kz, self.cx, self.cy, self.cz, self.w, self.inv_w,
|
|
233
|
+
need_b, need_derivs,
|
|
234
|
+
E[:, i:end], B[:, i:end], dx[:,i:end], dy[:, i:end], dz[:, i:end]
|
|
235
|
+
)
|
|
236
|
+
if progress_callback: progress_callback(end - i)
|
|
237
|
+
|
|
238
|
+
D = (dx, dy, dz) if need_derivs else (None, None, None)
|
|
239
|
+
B = B if need_b else None
|
|
240
|
+
|
|
241
|
+
return E, D, B
|
|
242
|
+
|
|
243
|
+
def compute_grid(self, x_vec, y_vec, z, t, need_b=True, need_derivs=True, progress_callback=None):
|
|
244
|
+
nx, ny = len(x_vec), len(y_vec)
|
|
245
|
+
rows_per_batch = max(1, self.max_batch_size // nx)
|
|
246
|
+
|
|
247
|
+
E, B, dx, dy, dz = self._allocate_arrays((ny, nx))
|
|
248
|
+
|
|
249
|
+
for i in range(0, ny, rows_per_batch):
|
|
250
|
+
end = min(i + rows_per_batch, ny)
|
|
251
|
+
_kernel_grid_rect(
|
|
252
|
+
x_vec, y_vec[i:end], z, t,
|
|
253
|
+
self.kx, self.ky, self.kz, self.cx, self.cy, self.cz, self.w, self.inv_w,
|
|
254
|
+
need_b, need_derivs,
|
|
255
|
+
E[:, i:end, :], B[:, i:end, :], dx[:, i:end, :], dy[:, i:end, :], dz[:, i:end, :]
|
|
256
|
+
)
|
|
257
|
+
if progress_callback: progress_callback(end - i)
|
|
258
|
+
|
|
259
|
+
D = (dx, dy, dz) if need_derivs else (None, None, None)
|
|
260
|
+
B = B if need_b else None
|
|
261
|
+
|
|
262
|
+
return E, D, B
|
|
263
|
+
|
|
264
|
+
def compute_point(self, x, y, z, t, need_b=True, need_derivs=True):
|
|
265
|
+
E, D, B = _kernel_point(
|
|
266
|
+
x, y, z, t, self.kx, self.ky, self.kz, self.cx, self.cy, self.cz,
|
|
267
|
+
self.w, self.inv_w, need_b, need_derivs
|
|
268
|
+
)
|
|
269
|
+
if not need_derivs: D = (None, None, None)
|
|
270
|
+
if not need_b: B = None
|
|
271
|
+
return E, D, B
|