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.
@@ -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