emsutil 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ tests/
@@ -0,0 +1 @@
1
+ 3.10
emsutil-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: emsutil
3
+ Version: 0.1.0
4
+ Summary: Common utilities for Emerge projects EMerge, Optycal and Heavi
5
+ Project-URL: Homepage, https://github.com/FennisRobert/emsutil
6
+ Project-URL: Issues, https://github.com/FennisRobert/emsutil/issues
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: matplotlib>=3.8.0
9
+ Requires-Dist: numpy<2.3,>=1.24
10
+ Requires-Dist: scipy>=1.14.0
11
+ Description-Content-Type: text/markdown
12
+
13
+ # emsutil
14
+ This is a library with common function for the emerge software modules EMerge, Optycal and Heavi.
@@ -0,0 +1,2 @@
1
+ # emsutil
2
+ This is a library with common function for the emerge software modules EMerge, Optycal and Heavi.
@@ -0,0 +1,48 @@
1
+ [tool.hatch.metadata]
2
+ allow-direct-references = true
3
+
4
+
5
+ [project]
6
+ name = "emsutil"
7
+ version = "0.1.0"
8
+ description = "Common utilities for Emerge projects EMerge, Optycal and Heavi"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "matplotlib>=3.8.0",
13
+ "numpy>=1.24, <2.3",
14
+ "scipy>=1.14.0",
15
+ ]
16
+
17
+ [build-system]
18
+ requires = ["hatchling"]
19
+ build-backend = "hatchling.build"
20
+
21
+ [tool.hatch.build.targets.wheel]
22
+ packages = ["src/emsutil"]
23
+
24
+ [tool.uv]
25
+ no-build-isolation-package = ["cchardet"]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/FennisRobert/emsutil"
29
+ Issues = "https://github.com/FennisRobert/emsutil/issues"
30
+
31
+ [[tool.uv.index]]
32
+ name = "pypi"
33
+ url = "https://pypi.org/simple"
34
+ publish-url = "https://upload.pypi.org/legacy"
35
+
36
+ [[tool.uv.index]]
37
+ name = "testpypi"
38
+ url = "https://test.pypi.org/simple"
39
+ publish-url = "https://test.pypi.org/legacy"
40
+
41
+ [tool.pytest.ini_options]
42
+ addopts = [
43
+ "--import-mode=importlib",
44
+ "--ignore=trial_scripts"
45
+ ]
46
+
47
+ testpaths = ["tests"]
48
+ python_files = ["test_*.py", "*_test.py"]
@@ -0,0 +1,5 @@
1
+ from .plot.plot2d import plot, plot_ff, plot_ff_polar, plot_sp, plot_vswr, smith
2
+ from . import lib
3
+ from .const import C0, Z0, PI, MU0, EPS0
4
+ import isola
5
+ import rogers
@@ -0,0 +1,5 @@
1
+ C0 = 299792458
2
+ Z0 = 376.73031366857
3
+ PI = 3.14159265358979323846
4
+ EPS0 = 8.854187818814e-12
5
+ MU0 = 1/(C0*C0*EPS0)
@@ -0,0 +1,493 @@
1
+ import numpy as np
2
+ from dataclasses import dataclass, field
3
+ from typing import Literal, Callable
4
+ from .const import Z0, EPS0
5
+ from .lib import EISO, EOMNI
6
+
7
+ @dataclass
8
+ class FarFieldComponent:
9
+ F: np.ndarray
10
+
11
+ @property
12
+ def x(self) -> np.ndarray:
13
+ return self.F[0,:]
14
+
15
+ @property
16
+ def y(self) -> np.ndarray:
17
+ return self.F[1,:]
18
+
19
+ @property
20
+ def z(self) -> np.ndarray:
21
+ return self.F[2,:]
22
+
23
+ @property
24
+ def theta(self) -> np.ndarray:
25
+ thx = np.cos(self.theta)*np.cos(self.phi)
26
+ thy = np.cos(self.theta)*np.sin(self.phi)
27
+ thz = -np.sin(self.theta)
28
+ return thx*self.F[0,:] + thy*self.F[1,:] + thz*self.F[2,:]
29
+
30
+ @property
31
+ def phi(self) -> np.ndarray:
32
+ phx = -np.sin(self.phi)
33
+ phy = np.cos(self.phi)
34
+ phz = np.zeros_like(self.theta)
35
+ return phx*self.F[0,:] + phy*self.F[1,:] + phz*self.F[2,:]
36
+
37
+ @property
38
+ def rhcp(self) -> np.ndarray:
39
+ return (self.theta + 1j*self.phi)/np.sqrt(2)
40
+
41
+ @property
42
+ def lhcp(self) -> np.ndarray:
43
+ return (self.theta - 1j*self.phi)/np.sqrt(2)
44
+
45
+ @property
46
+ def AR(self) -> np.ndarray:
47
+ R = np.abs(self.rhcp)
48
+ L = np.abs(self.lchp)
49
+ return (R+L)/(R-L)
50
+
51
+ @property
52
+ def norm(self) -> np.ndarray:
53
+ return np.sqrt(np.abs(self.F[0,:])**2 + np.abs(self.F[1,:])**2 + np.abs(self.F[2,:])**2)
54
+
55
+ @dataclass
56
+ class FarFieldData:
57
+ _E: np.ndarray
58
+ _H: np.ndarray
59
+ theta: np.ndarray
60
+ phi: np.ndarray
61
+ Ptot: float
62
+ ang: np.ndarray | None = None
63
+
64
+ def total_radiated_power_integral(
65
+ self,
66
+ r: float = 1.0,
67
+ use: str = "EH", # "EH" or "E"
68
+ degrees: bool | None = None, # auto if None
69
+ ) -> float:
70
+ E = np.asarray(self._E)
71
+ H = np.asarray(self._H)
72
+ th = np.asarray(self.theta, float)
73
+ ph = np.asarray(self.phi, float)
74
+
75
+ if E.shape[:1] != (3,) or E.ndim != 3:
76
+ raise ValueError(f"_E must be (3,N,M), got {E.shape}")
77
+ if th.shape != E.shape[1:] or ph.shape != E.shape[1:]:
78
+ raise ValueError(f"theta/phi must be (N,M) matching _E[1:], got {th.shape}, {ph.shape}")
79
+ if use.upper() == "EH" and H.shape != E.shape:
80
+ raise ValueError(f"_H must match _E for use='EH', got {H.shape}")
81
+
82
+ if degrees is None:
83
+ degrees = (np.nanmax(th) > 2*np.pi + 0.5) or (np.nanmax(ph) > 2*np.pi + 0.5)
84
+ if degrees:
85
+ th = np.deg2rad(th)
86
+ ph = np.deg2rad(ph)
87
+
88
+ # rhat(θ,φ)
89
+ rhat = np.stack(
90
+ [np.sin(th) * np.cos(ph), np.sin(th) * np.sin(ph), np.cos(th)],
91
+ axis=0,
92
+ ) # (3,N,M)
93
+
94
+ # S_r
95
+ if use.upper() == "EH":
96
+ S = 0.5 * np.real(
97
+ np.cross(np.moveaxis(E, 0, -1), np.conj(np.moveaxis(H, 0, -1)))
98
+ ) # (N,M,3)
99
+ Sr = np.sum(S * np.moveaxis(rhat, 0, -1), axis=-1) # (N,M)
100
+ elif use.upper() == "E":
101
+ Sr = (np.sum(np.abs(E) ** 2, axis=0) / (2.0 * Z0)).real # (N,M)
102
+ else:
103
+ raise ValueError("use must be 'EH' or 'E'")
104
+
105
+ # dΩ for general (θ(i,j), φ(i,j)) grid: dΩ = sinθ * |∂(θ,φ)/∂(i,j)| di dj
106
+ dth_di, dth_dj = np.gradient(th, edge_order=2)
107
+ dph_di, dph_dj = np.gradient(ph, edge_order=2)
108
+ J = dth_di * dph_dj - dth_dj * dph_di # ∂(θ,φ)/∂(i,j)
109
+ dOmega = np.sin(th) * np.abs(J)
110
+
111
+ return float(r**2 * np.sum(Sr * dOmega))
112
+
113
+ @property
114
+ def E(self) -> np.ndarray:
115
+ return FarFieldComponent(self._E)
116
+
117
+ @property
118
+ def H(self) -> np.ndarray:
119
+ return FarFieldComponent(self._H)
120
+
121
+ @property
122
+ def Ex(self) -> np.ndarray:
123
+ return self._E[0,:]
124
+
125
+ @property
126
+ def Ey(self) -> np.ndarray:
127
+ return self._E[1,:]
128
+
129
+ @property
130
+ def Ez(self) -> np.ndarray:
131
+ return self._E[2,:]
132
+
133
+ @property
134
+ def Hx(self) -> np.ndarray:
135
+ return self._H[0,:]
136
+
137
+ @property
138
+ def Hy(self) -> np.ndarray:
139
+ return self._H[1,:]
140
+
141
+ @property
142
+ def Hz(self) -> np.ndarray:
143
+ return self._H[2,:]
144
+
145
+ @property
146
+ def Etheta(self) -> np.ndarray:
147
+ thx = np.cos(self.theta)*np.cos(self.phi)
148
+ thy = np.cos(self.theta)*np.sin(self.phi)
149
+ thz = -np.sin(self.theta)
150
+ return thx*self._E[0,:] + thy*self._E[1,:] + thz*self._E[2,:]
151
+
152
+ @property
153
+ def Ephi(self) -> np.ndarray:
154
+ phx = -np.sin(self.phi)
155
+ phy = np.cos(self.phi)
156
+ phz = np.zeros_like(self.theta)
157
+ return phx*self._E[0,:] + phy*self._E[1,:] + phz*self._E[2,:]
158
+
159
+ @property
160
+ def Erhcp(self) -> np.ndarray:
161
+ return (self.Etheta + 1j*self.Ephi)/np.sqrt(2)
162
+
163
+ @property
164
+ def Elhcp(self) -> np.ndarray:
165
+ return (self.Etheta - 1j*self.Ephi)/np.sqrt(2)
166
+
167
+ @property
168
+ def AR(self) -> np.ndarray:
169
+ R = np.abs(self.Erhcp)
170
+ L = np.abs(self.Elhcp)
171
+ return (R+L)/(R-L)
172
+
173
+ @property
174
+ def gain(self, kind: Literal['iso','omni'] = 'iso') -> FarFieldComponent:
175
+ if kind=='iso':
176
+ return FarFieldComponent(self._E/EISO)
177
+ else:
178
+ return FarFieldComponent(self._E/EOMNI)
179
+
180
+ @property
181
+ def dir(self, kind: Literal['iso','omni'] = 'iso') -> FarFieldComponent:
182
+ if kind=='iso':
183
+ return FarFieldComponent(self._E/(EISO*(self.Ptot)**0.5))
184
+ else:
185
+ return FarFieldComponent(self._E/(EOMNI*(self.Ptot)**0.5))
186
+
187
+ @property
188
+ def normE(self) -> np.ndarray:
189
+ return np.sqrt(np.abs(self._E[0,:])**2 + np.abs(self._E[1,:])**2 + np.abs(self._E[2,:])**2)
190
+
191
+ @property
192
+ def normH(self) -> np.ndarray:
193
+ return np.sqrt(np.abs(self._H[0,:])**2 + np.abs(self._H[1,:])**2 + np.abs(self._H[2,:])**2)
194
+
195
+
196
+ def surfplot(self,
197
+ polarization: Literal['Ex','Ey','Ez','Etheta','Ephi','normE','Erhcp','Elhcp','AR'],
198
+ quantity: Literal['abs','real','imag','angle'] = 'abs',
199
+ isotropic: bool = True, dB: bool = False, dBfloor: float = -30, rmax: float | None = None,
200
+ offset: tuple[float, float, float] = (0,0,0)) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
201
+ """Returns the parameters to be used as positional arguments for the display.add_surf() function.
202
+
203
+ Example:
204
+ >>> model.display.add_surf(*dataset.field[n].farfield_3d(...).surfplot())
205
+
206
+ Args:
207
+ polarization ('Ex','Ey','Ez','Etheta','Ephi','normE'): What quantity to plot
208
+ isotropic (bool, optional): Whether to look at the ratio with isotropic antennas. Defaults to True.
209
+ dB (bool, optional): Whether to plot in dB's. Defaults to False.
210
+ dBfloor (float, optional): The dB value to take as R=0. Defaults to -10.
211
+
212
+ Returns:
213
+ tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: The X, Y, Z, F values
214
+ """
215
+ fmap = {
216
+ 'abs': np.abs,
217
+ 'real': np.real,
218
+ 'imag': np.imag,
219
+ 'angle': np.angle,
220
+ }
221
+ mapping = fmap.get(quantity.lower(),np.abs)
222
+
223
+ F = mapping(getattr(self, polarization))
224
+
225
+ if isotropic:
226
+ F = F/np.sqrt(Z0/(2*np.pi))
227
+ if dB:
228
+ F = 20*np.log10(np.clip(np.abs(F), a_min=10**(dBfloor/20), a_max = 1e9))-dBfloor
229
+ if rmax is not None:
230
+ F = rmax * F/np.max(F)
231
+ xs = F*np.sin(self.theta)*np.cos(self.phi) + offset[0]
232
+ ys = F*np.sin(self.theta)*np.sin(self.phi) + offset[1]
233
+ zs = F*np.cos(self.theta) + offset[2]
234
+
235
+ return xs, ys, zs, F
236
+
237
+ @dataclass
238
+ class EHField:
239
+ x: np.ndarray
240
+ y: np.ndarray
241
+ z: np.ndarray
242
+ Ex: np.ndarray
243
+ Ey: np.ndarray
244
+ Ez: np.ndarray
245
+ Hx: np.ndarray
246
+ Hy: np.ndarray
247
+ Hz: np.ndarray
248
+ freq: float
249
+ er: np.ndarray
250
+ ur: np.ndarray
251
+ aux: dict[str, np.ndarray] = field(default_factory=dict)
252
+
253
+
254
+ @property
255
+ def k0(self) -> float:
256
+ return self.freq*2*np.pi/299792458
257
+
258
+ @property
259
+ def Px(self) -> np.ndarray:
260
+ return EPS0*(self.er-1)*self.Ex
261
+
262
+ @property
263
+ def Py(self) -> np.ndarray:
264
+ return EPS0*(self.er-1)*self.Ey
265
+
266
+ @property
267
+ def Pz(self) -> np.ndarray:
268
+ return EPS0*(self.er-1)*self.Ez
269
+
270
+ @property
271
+ def Dx(self) -> np.ndarray:
272
+ return self.Ex*self.er
273
+
274
+ @property
275
+ def Dy(self) -> np.ndarray:
276
+ return self.Ey*self.er
277
+
278
+ @property
279
+ def Dz(self) -> np.ndarray:
280
+ return self.Ez*self.er
281
+
282
+ @property
283
+ def Bx(self) -> np.ndarray:
284
+ return self.Hx/self.ur
285
+
286
+ @property
287
+ def By(self) -> np.ndarray:
288
+ return self.Hy/self.ur
289
+
290
+ @property
291
+ def Bz(self) -> np.ndarray:
292
+ return self.Hz/self.ur
293
+
294
+ @property
295
+ def Emat(self) -> np.ndarray:
296
+ return np.array([self.Ex, self.Ey, self.Ez])
297
+
298
+ @property
299
+ def Hmat(self) -> np.ndarray:
300
+ return np.array([self.Hx, self.Hy, self.Hz])
301
+
302
+ @property
303
+ def Pmat(self) -> np.ndarray:
304
+ return np.array([self.Px, self.Py, self.Pz])
305
+
306
+ @property
307
+ def Bmat(self) -> np.ndarray:
308
+ return np.array([self.Bx, self.By, self.Bz])
309
+
310
+ @property
311
+ def Dmat(self) -> np.ndarray:
312
+ return np.array([self.Dx, self.Dy, self.Dz])
313
+
314
+ @property
315
+ def Smat(self) -> np.ndarray:
316
+ return np.array([self.Sx, self.Sy, self.Sz])
317
+
318
+ @property
319
+ def Smmat(self) -> np.ndarray:
320
+ return np.array([self.Smx, self.Smy, self.Smz])
321
+
322
+ @property
323
+ def EH(self) -> tuple[np.ndarray, np.ndarray]:
324
+ ''' Return the electric and magnetic field as a tuple of numpy arrays '''
325
+ return np.array([self.Ex, self.Ey, self.Ez]), np.array([self.Hx, self.Hy, self.Hz])
326
+
327
+ @property
328
+ def E(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
329
+ ''' Return the electric field as a tuple of numpy arrays '''
330
+ return self.Ex, self.Ey, self.Ez
331
+
332
+ @property
333
+ def Sx(self) -> np.ndarray:
334
+ return self.Ey*self.Hz - self.Ez*self.Hy
335
+
336
+ @property
337
+ def Sy(self) -> np.ndarray:
338
+ return self.Ez*self.Hx - self.Ex*self.Hz
339
+
340
+ @property
341
+ def Sz(self) -> np.ndarray:
342
+ return self.Ex*self.Hy - self.Ey*self.Hx
343
+
344
+ @property
345
+ def Smx(self) -> np.ndarray:
346
+ return 0.5*(self.Ey*np.conj(self.Hz) - self.Ez*np.conj(self.Hy))
347
+
348
+ @property
349
+ def Smy(self) -> np.ndarray:
350
+ return 0.5*(self.Ez*np.conj(self.Hx) - self.Ex*np.conj(self.Hz))
351
+
352
+ @property
353
+ def Smz(self) -> np.ndarray:
354
+ return 0.5*(self.Ex*np.conj(self.Hy) - self.Ey*np.conj(self.Hx))
355
+
356
+ @property
357
+ def B(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
358
+ ''' Return the magnetic field as a tuple of numpy arrays '''
359
+ return self.Bx, self.By, self.Bz
360
+
361
+ @property
362
+ def P(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
363
+ ''' Return the polarization field as a tuple of numpy arrays '''
364
+ return self.Px, self.Py, self.Pz
365
+
366
+ @property
367
+ def D(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
368
+ ''' Return the electric displacement field as a tuple of numpy arrays '''
369
+ return self.Bx, self.By, self.Bz
370
+
371
+ @property
372
+ def H(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
373
+ ''' Return the magnetic field as a tuple of numpy arrays '''
374
+ return self.Hx, self.Hy, self.Hz
375
+
376
+ @property
377
+ def S(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
378
+ ''' Return the poynting vector field as a tuple of numpy arrays '''
379
+ return self.Sx, self.Sy, self.Sz
380
+
381
+ @property
382
+ def Sm(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
383
+ ''' Return the poynting vector field as a tuple of numpy arrays '''
384
+ return self.Smx, self.Smy, self.Smz
385
+
386
+ @property
387
+ def normE(self) -> np.ndarray:
388
+ """The complex norm of the E-field
389
+ """
390
+ return np.sqrt(np.abs(self.Ex)**2 + np.abs(self.Ey)**2 + np.abs(self.Ez)**2)
391
+
392
+ @property
393
+ def normH(self) -> np.ndarray:
394
+ """The complex norm of the H-field"""
395
+ return np.sqrt(np.abs(self.Hx)**2 + np.abs(self.Hy)**2 + np.abs(self.Hz)**2)
396
+
397
+ @property
398
+ def normP(self) -> np.ndarray:
399
+ """The complex norm of the P-field
400
+ """
401
+ return np.sqrt(np.abs(self.Px)**2 + np.abs(self.Py)**2 + np.abs(self.Pz)**2)
402
+
403
+ @property
404
+ def normB(self) -> np.ndarray:
405
+ """The complex norm of the B-field
406
+ """
407
+ return np.sqrt(np.abs(self.Bx)**2 + np.abs(self.By)**2 + np.abs(self.Bz)**2)
408
+
409
+ @property
410
+ def normD(self) -> np.ndarray:
411
+ """The complex norm of the D-field
412
+ """
413
+ return np.sqrt(np.abs(self.Dx)**2 + np.abs(self.Dy)**2 + np.abs(self.Dz)**2)
414
+
415
+ @property
416
+ def normS(self) -> np.ndarray:
417
+ """The complex norm of the S-field
418
+ """
419
+ return np.sqrt(np.abs(self.Sx)**2 + np.abs(self.Sy)**2 + np.abs(self.Sz)**2)
420
+
421
+ def _get_quantity(self, field: str, metric: str) -> np.ndarray:
422
+ field_arry = getattr(self, field)
423
+ if metric=='abs':
424
+ field = np.abs(field_arry)
425
+ elif metric=='real':
426
+ field = field_arry.real
427
+ elif metric=='imag':
428
+ field = field_arry.imag
429
+ elif metric=='complex':
430
+ field = field_arry
431
+ else:
432
+ field = field_arry
433
+ return field
434
+
435
+ def vector(self, field: Literal['E','H'], metric: Literal['real','imag','complex'] = 'real') -> tuple[np.ndarray, np.ndarray,np.ndarray,np.ndarray,np.ndarray,np.ndarray]:
436
+ """Returns the X,Y,Z,Fx,Fy,Fz data to be directly cast into plot functions.
437
+
438
+ The field can be selected by a string literal. The metric of the complex vector field by the metric.
439
+ For animations, make sure to always use the complex metric.
440
+
441
+ Args:
442
+ field ('E','H'): The field to return
443
+ metric ([]'real','imag','complex'], optional): the metric to impose on the field. Defaults to 'real'.
444
+
445
+ Returns:
446
+ tuple[np.ndarray,...]: The X,Y,Z,Fx,Fy,Fz arrays
447
+ """
448
+ Fx, Fy, Fz = getattr(self, field)
449
+
450
+ if metric=='real':
451
+ Fx, Fy, Fz = Fx.real, Fy.real, Fz.real
452
+ elif metric=='imag':
453
+ Fx, Fy, Fz = Fx.imag, Fy.imag, Fz.imag
454
+
455
+ return self.x, self.y, self.z, Fx, Fy, Fz
456
+
457
+ def scalar(self, field: Literal['Ex','Ey','Ez','Hx','Hy','Hz','normE','normH'] | str, metric: Literal['abs','real','imag','complex'] = 'real') -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
458
+ """Returns the data X, Y, Z, Field based on the interpolation
459
+
460
+ For animations, make sure to select the complex metric.
461
+
462
+ Args:
463
+ field (str): The field to plot
464
+ metric (str, optional): The metric to impose on the plot. Defaults to 'real'.
465
+
466
+ Returns:
467
+ (X,Y,Z,Field): The coordinates plus field scalar
468
+ """
469
+ if field in self.aux:
470
+ field_arry = self.aux[field]
471
+ else:
472
+ field_arry = getattr(self, field)
473
+
474
+ if metric=='abs':
475
+ field = np.abs(field_arry)
476
+ elif metric=='real':
477
+ field = field_arry.real
478
+ elif metric=='imag':
479
+ field = field_arry.imag
480
+ elif metric=='complex':
481
+ field = field_arry
482
+ return self.x, self.y, self.z, field_arry
483
+
484
+ def int(self, field: str | Callable, metric: Literal['abs','real','imag','complex',''] = '') -> float | complex:
485
+ if isinstance(field, Callable):
486
+ field = field(self)
487
+ else:
488
+ field = self._get_quantity(field, metric)
489
+ if len(field.shape)==2:
490
+ axis = 1
491
+ else:
492
+ axis = 0
493
+ return np.sum(field*self.aux['areas']*self.aux['weights'], axis=axis)