pytem 0.1.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.
- pytem/__init__.py +219 -0
- pytem/backends.py +94 -0
- pytem/benchmarks.py +108 -0
- pytem/data_io.py +941 -0
- pytem/euler.py +54 -0
- pytem/forward.py +866 -0
- pytem/gerda_io.py +805 -0
- pytem/inversion.py +1724 -0
- pytem/ip_models.py +158 -0
- pytem/kernels_gpu.py +251 -0
- pytem/kernels_jacobian.py +764 -0
- pytem/kernels_numba.py +315 -0
- pytem/plotter.py +279 -0
- pytem/recursion.py +131 -0
- pytem/survey.py +287 -0
- pytem/system_filter.py +56 -0
- pytem/transform_weights.py +469 -0
- pytem/waveform.py +612 -0
- pytem-0.1.0.dist-info/METADATA +316 -0
- pytem-0.1.0.dist-info/RECORD +22 -0
- pytem-0.1.0.dist-info/WHEEL +5 -0
- pytem-0.1.0.dist-info/top_level.txt +1 -0
pytem/__init__.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pyTEM: 1-D layered-earth TEM modelling.
|
|
3
|
+
|
|
4
|
+
Supports circular and square loop geometries, central and offset receivers,
|
|
5
|
+
DLF and Euler transforms, with NumPy / Numba / CuPy backends.
|
|
6
|
+
|
|
7
|
+
================================================================================
|
|
8
|
+
Package map (table of contents)
|
|
9
|
+
================================================================================
|
|
10
|
+
|
|
11
|
+
The package is organised in layers. Lower layers hold pure data and physics
|
|
12
|
+
with no project-internal dependencies; higher layers compose them into the
|
|
13
|
+
public forward, inversion, and plotting API. Arrows below read "imports from".
|
|
14
|
+
|
|
15
|
+
Layer 0 - Data and constants
|
|
16
|
+
transform_weights.py MU0 and the digital-filter coefficient tables:
|
|
17
|
+
Key (2009) 201/101-pt Hankel J0/J1 filters, Key
|
|
18
|
+
81/101-pt Fourier sine/cosine filters, and the
|
|
19
|
+
Euler weights. Exposes the HANKEL_FILTERS,
|
|
20
|
+
FOURIER_FILTERS, EULER_PARAMS registries. No
|
|
21
|
+
internal dependencies; everything else builds on it.
|
|
22
|
+
|
|
23
|
+
Layer 1 - Core physics and backend detection
|
|
24
|
+
recursion.py te_reflection_coeff / te_reflection_coeff_grad:
|
|
25
|
+
the TE reflection coefficient of the
|
|
26
|
+
layer stack and its log-resistivity gradient (NumPy,
|
|
27
|
+
complex). This is the physical heart of the model.
|
|
28
|
+
-> transform_weights
|
|
29
|
+
backends.py CuPy/CUDA detection (HAS_CUDA) and transfer of the
|
|
30
|
+
filter tables to device memory (GPU_HANKEL,
|
|
31
|
+
GPU_FOURIER).
|
|
32
|
+
-> transform_weights
|
|
33
|
+
|
|
34
|
+
Layer 2 - Compute kernels (one physics, three backends)
|
|
35
|
+
kernels_numba.py Numba JIT forward kernels (circular/square, DLF and
|
|
36
|
+
Euler). Sets HAS_NUMBA.
|
|
37
|
+
kernels_gpu.py CuPy/CUDA forward kernels (mirror of kernels_numba).
|
|
38
|
+
-> transform_weights, backends
|
|
39
|
+
kernels_jacobian.py Adjoint upward recursion: forward+backward pass giving
|
|
40
|
+
d(r_TE)/d(ln rho_j) for all layers at once, in both
|
|
41
|
+
Numba and CuPy variants. Backs the analytical
|
|
42
|
+
Jacobian.
|
|
43
|
+
-> transform_weights, backends
|
|
44
|
+
|
|
45
|
+
Layer 3 - Forward modelling
|
|
46
|
+
forward.py Public fwd_circle_* / fwd_square_* functions, the
|
|
47
|
+
analytical half-space references, and the geometry
|
|
48
|
+
builders + backend dispatchers (CUDA > Numba >
|
|
49
|
+
pure Python). Also exports the filter-precompute
|
|
50
|
+
helpers reused by the inversion module.
|
|
51
|
+
-> transform_weights, backends, recursion,
|
|
52
|
+
kernels_numba, kernels_gpu
|
|
53
|
+
|
|
54
|
+
Layer 4 - Inversion
|
|
55
|
+
inversion.py Analytical (getJ_ana) and finite-difference
|
|
56
|
+
(getJ_fd) Jacobians, regularisation helpers (getR,
|
|
57
|
+
getAlpha[s], getRMS), apparent-resistivity
|
|
58
|
+
conversion, and the Gauss-Newton invert() loop.
|
|
59
|
+
-> forward (+ its filter helpers),
|
|
60
|
+
kernels_jacobian, kernels_numba, backends,
|
|
61
|
+
transform_weights
|
|
62
|
+
|
|
63
|
+
Utilities and add-ons (composable, mostly standalone)
|
|
64
|
+
waveform.py Convolution of the step response with a piecewise-
|
|
65
|
+
linear transmitter waveform (setup_waveform,
|
|
66
|
+
convolve_waveform). -> kernels_numba
|
|
67
|
+
system_filter.py Butterworth and WalkTEM cascade H(omega) transfer
|
|
68
|
+
functions fed in as system_filter=. (standalone)
|
|
69
|
+
ip_models.py Complex-resistivity IP models (Pelton, Cole-Cole,
|
|
70
|
+
double-Pelton, MPA) and tem_forward_ip.
|
|
71
|
+
-> transform_weights, recursion
|
|
72
|
+
euler.py Standalone Euler inverse-Laplace transform, used to
|
|
73
|
+
verify the production Euler path. (standalone)
|
|
74
|
+
plotter.py Matplotlib helpers: plot_sounding, plot_model,
|
|
75
|
+
plot_inversion. (standalone)
|
|
76
|
+
data_io.py Field-data import: read_tem_xyz / read_kenbec_xyz /
|
|
77
|
+
read_xyz parse TEM Data Manager and TEMImage-Beta
|
|
78
|
+
`.xyz` exports into TEMData / KenbecTEMData, with
|
|
79
|
+
shared dbdt/snr/to_pytem and line/UTM/distance
|
|
80
|
+
helpers. (standalone)
|
|
81
|
+
survey.py Survey: quick-look sounding, transect and station-
|
|
82
|
+
map plots built on data_io containers. LM/HM are
|
|
83
|
+
always plotted separately (different waveforms).
|
|
84
|
+
-> data_io
|
|
85
|
+
|
|
86
|
+
__init__.py Re-exports the public API listed in __all__.
|
|
87
|
+
|
|
88
|
+
--------------------------------------------------------------------------------
|
|
89
|
+
Dependency flow
|
|
90
|
+
--------------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
transform_weights
|
|
93
|
+
|-> backends
|
|
94
|
+
|-> recursion
|
|
95
|
+
|-> kernels_numba / kernels_gpu / kernels_jacobian
|
|
96
|
+
|
|
|
97
|
+
v
|
|
98
|
+
forward ----> inversion
|
|
99
|
+
^ ^
|
|
100
|
+
| |
|
|
101
|
+
(waveform, system_filter, ip_models feed in here as options)
|
|
102
|
+
|
|
103
|
+
--------------------------------------------------------------------------------
|
|
104
|
+
Typical call chains
|
|
105
|
+
--------------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
Forward:
|
|
108
|
+
fwd_circle_central() (forward.py)
|
|
109
|
+
_resolve_filters() -> transform_weights tables
|
|
110
|
+
_filter_weights() -> optional system_filter samples
|
|
111
|
+
_build_circular_geometry()-> per-wavenumber weights
|
|
112
|
+
_run_circular() -> CUDA | Numba | pure-Python kernel
|
|
113
|
+
te_reflection_coeff() -> upward recursion (recursion.py / kernels)
|
|
114
|
+
_apply_signal_scaling() -> current and step-off/on/impulse sign
|
|
115
|
+
|
|
116
|
+
Inversion:
|
|
117
|
+
invert() (inversion.py)
|
|
118
|
+
loop:
|
|
119
|
+
forward call (one of fwd_*) (forward.py)
|
|
120
|
+
getJ_ana() -> adjoint kernels (kernels_jacobian.py)
|
|
121
|
+
or getJ_fd() -> repeated forward calls
|
|
122
|
+
_alpha_search() / _gn_solve() / _backtrack() Gauss-Newton step
|
|
123
|
+
optional: convolve_waveform(), system_filter=H, IP rho(omega)
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
__version__ = "0.1.0"
|
|
127
|
+
|
|
128
|
+
from .transform_weights import MU0, HANKEL_FILTERS, FOURIER_FILTERS, EULER_PARAMS
|
|
129
|
+
from .backends import HAS_CUDA
|
|
130
|
+
from .kernels_numba import HAS_NUMBA
|
|
131
|
+
from .recursion import te_reflection_coeff, te_reflection_coeff_grad
|
|
132
|
+
|
|
133
|
+
from .forward import (
|
|
134
|
+
fwd_circle_central,
|
|
135
|
+
fwd_circle_offset,
|
|
136
|
+
fwd_square_central,
|
|
137
|
+
fwd_square_offset,
|
|
138
|
+
fwd_analytical_central,
|
|
139
|
+
fwd_analytical_offset,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
from .waveform import convolve_waveform, setup_waveform, setup_waveform_matrix, setup_shared_gate_matrices
|
|
143
|
+
from .system_filter import butterworth_filter, cascade_filter
|
|
144
|
+
from .euler import euler_invert
|
|
145
|
+
from .inversion import (getJ_ana, getJ_fd, getR, dbdt_to_apprho, getRMS,
|
|
146
|
+
getAlpha, getAlphas, invert, invert_joint, invert_stations,
|
|
147
|
+
compute_doi)
|
|
148
|
+
from .plotter import (plot_sounding, plot_model, plot_inversion,
|
|
149
|
+
plot_survey_models, plot_survey_responses)
|
|
150
|
+
from .benchmarks import benchmark_step_grids
|
|
151
|
+
from .data_io import (TEMData, KenbecTEMData, TunoeTEMData, read_tem_xyz, read_kenbec_xyz,
|
|
152
|
+
read_xyz, read_usf)
|
|
153
|
+
from .survey import Survey
|
|
154
|
+
|
|
155
|
+
from .ip_models import (
|
|
156
|
+
pelton_res_rho,
|
|
157
|
+
cole_cole_rho,
|
|
158
|
+
double_pelton_rho,
|
|
159
|
+
get_m_taur_MPA,
|
|
160
|
+
mpa_rho,
|
|
161
|
+
tem_forward_ip,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
__all__ = [
|
|
165
|
+
# Constants & flags
|
|
166
|
+
'MU0', 'HAS_CUDA', 'HAS_NUMBA',
|
|
167
|
+
'HANKEL_FILTERS', 'FOURIER_FILTERS', 'EULER_PARAMS',
|
|
168
|
+
# Core
|
|
169
|
+
'te_reflection_coeff',
|
|
170
|
+
'te_reflection_coeff_grad',
|
|
171
|
+
# Forward models
|
|
172
|
+
'fwd_circle_central',
|
|
173
|
+
'fwd_circle_offset',
|
|
174
|
+
'fwd_square_central',
|
|
175
|
+
'fwd_square_offset',
|
|
176
|
+
# Analytical
|
|
177
|
+
'fwd_analytical_central',
|
|
178
|
+
'fwd_analytical_offset',
|
|
179
|
+
# Waveform & system filter
|
|
180
|
+
'convolve_waveform',
|
|
181
|
+
'setup_waveform',
|
|
182
|
+
'setup_waveform_matrix',
|
|
183
|
+
'setup_shared_gate_matrices',
|
|
184
|
+
'butterworth_filter',
|
|
185
|
+
'cascade_filter',
|
|
186
|
+
# IP models
|
|
187
|
+
'pelton_res_rho',
|
|
188
|
+
'cole_cole_rho',
|
|
189
|
+
'double_pelton_rho',
|
|
190
|
+
'get_m_taur_MPA',
|
|
191
|
+
'mpa_rho',
|
|
192
|
+
'tem_forward_ip',
|
|
193
|
+
# Euler (verification)
|
|
194
|
+
'euler_invert',
|
|
195
|
+
# Inversion
|
|
196
|
+
'getJ_ana',
|
|
197
|
+
'getJ_fd',
|
|
198
|
+
'getR',
|
|
199
|
+
'dbdt_to_apprho',
|
|
200
|
+
'getRMS',
|
|
201
|
+
'getAlpha',
|
|
202
|
+
'getAlphas',
|
|
203
|
+
'invert',
|
|
204
|
+
'invert_joint',
|
|
205
|
+
'compute_doi',
|
|
206
|
+
# Plotting
|
|
207
|
+
'plot_sounding',
|
|
208
|
+
'plot_model',
|
|
209
|
+
'plot_inversion',
|
|
210
|
+
# Field-data import & survey plotting
|
|
211
|
+
'TEMData',
|
|
212
|
+
'KenbecTEMData',
|
|
213
|
+
'TunoeTEMData',
|
|
214
|
+
'read_tem_xyz',
|
|
215
|
+
'read_kenbec_xyz',
|
|
216
|
+
'read_xyz',
|
|
217
|
+
'read_usf',
|
|
218
|
+
'Survey',
|
|
219
|
+
]
|
pytem/backends.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
backends.py - CuPy/CUDA detection and GPU filter array transfer.
|
|
3
|
+
|
|
4
|
+
Provides:
|
|
5
|
+
- HAS_CUDA flag
|
|
6
|
+
- GPU_HANKEL, GPU_FOURIER registries (CuPy arrays on device)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import glob
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from .transform_weights import (
|
|
16
|
+
_HANKEL_BASE_201, _HANKEL_J0_201, _HANKEL_J1_201,
|
|
17
|
+
_HANKEL_BASE_101, _HANKEL_J0_101, _HANKEL_J1_101,
|
|
18
|
+
_FOURIER_BASE_81, _FOURIER_SIN_81, _FOURIER_COS_81,
|
|
19
|
+
_FOURIER_BASE_101, _FOURIER_SIN_101, _FOURIER_COS_101,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _register_cuda_dll_dirs():
|
|
24
|
+
"""Make CuPy find the CUDA runtime shipped as ``nvidia-*-cu12`` pip wheels.
|
|
25
|
+
|
|
26
|
+
On Windows the wheels install their DLLs under ``site-packages/nvidia/*/bin``.
|
|
27
|
+
CuPy's bundled loader does not search there, and nvrtc loads its builtins
|
|
28
|
+
DLL via the process PATH, so we add those directories to both PATH and the
|
|
29
|
+
DLL search path before CuPy is imported. No-op on non-Windows or when the
|
|
30
|
+
wheels are absent.
|
|
31
|
+
"""
|
|
32
|
+
if os.name != "nt":
|
|
33
|
+
return
|
|
34
|
+
bins = []
|
|
35
|
+
for site_dir in {os.path.dirname(os.path.dirname(__file__)), *sys.path}:
|
|
36
|
+
base = os.path.join(site_dir, "nvidia")
|
|
37
|
+
if os.path.isdir(base):
|
|
38
|
+
bins.extend(glob.glob(os.path.join(base, "*", "bin")))
|
|
39
|
+
bins = sorted(set(p for p in bins if os.path.isdir(p)))
|
|
40
|
+
if not bins:
|
|
41
|
+
return
|
|
42
|
+
os.environ["PATH"] = os.pathsep.join(bins) + os.pathsep + os.environ.get("PATH", "")
|
|
43
|
+
for p in bins:
|
|
44
|
+
try:
|
|
45
|
+
os.add_dll_directory(p)
|
|
46
|
+
except (OSError, AttributeError):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
_register_cuda_dll_dirs()
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
import cupy as cp
|
|
54
|
+
HAS_CUDA = cp.cuda.is_available()
|
|
55
|
+
except ImportError:
|
|
56
|
+
HAS_CUDA = False
|
|
57
|
+
|
|
58
|
+
GPU_HANKEL = {}
|
|
59
|
+
GPU_FOURIER = {}
|
|
60
|
+
|
|
61
|
+
if HAS_CUDA:
|
|
62
|
+
import cupy as cp
|
|
63
|
+
|
|
64
|
+
_GPU_DEVICE = cp.cuda.Device(0)
|
|
65
|
+
_GPU_DEVICE.use()
|
|
66
|
+
|
|
67
|
+
# 201-pt Hankel
|
|
68
|
+
_d_HANKEL_BASE_201 = cp.asarray(_HANKEL_BASE_201)
|
|
69
|
+
_d_HANKEL_J0_201 = cp.asarray(_HANKEL_J0_201, dtype=cp.complex128)
|
|
70
|
+
_d_HANKEL_J1_201 = cp.asarray(_HANKEL_J1_201, dtype=cp.complex128)
|
|
71
|
+
|
|
72
|
+
# 101-pt Hankel
|
|
73
|
+
_d_HANKEL_BASE_101 = cp.asarray(_HANKEL_BASE_101)
|
|
74
|
+
_d_HANKEL_J0_101 = cp.asarray(_HANKEL_J0_101, dtype=cp.complex128)
|
|
75
|
+
_d_HANKEL_J1_101 = cp.asarray(_HANKEL_J1_101, dtype=cp.complex128)
|
|
76
|
+
|
|
77
|
+
# 81-pt Fourier
|
|
78
|
+
_d_FOURIER_BASE_81 = cp.asarray(_FOURIER_BASE_81)
|
|
79
|
+
_d_FOURIER_SIN_81 = cp.asarray(_FOURIER_SIN_81)
|
|
80
|
+
_d_FOURIER_COS_81 = cp.asarray(_FOURIER_COS_81)
|
|
81
|
+
|
|
82
|
+
# 101-pt Fourier
|
|
83
|
+
_d_FOURIER_BASE_101 = cp.asarray(_FOURIER_BASE_101)
|
|
84
|
+
_d_FOURIER_SIN_101 = cp.asarray(_FOURIER_SIN_101)
|
|
85
|
+
_d_FOURIER_COS_101 = cp.asarray(_FOURIER_COS_101)
|
|
86
|
+
|
|
87
|
+
GPU_HANKEL = {
|
|
88
|
+
'key_201': (_d_HANKEL_BASE_201, _d_HANKEL_J0_201, _d_HANKEL_J1_201),
|
|
89
|
+
'key_101': (_d_HANKEL_BASE_101, _d_HANKEL_J0_101, _d_HANKEL_J1_101),
|
|
90
|
+
}
|
|
91
|
+
GPU_FOURIER = {
|
|
92
|
+
'key_81': (_d_FOURIER_BASE_81, _d_FOURIER_SIN_81, _d_FOURIER_COS_81),
|
|
93
|
+
'key_101': (_d_FOURIER_BASE_101, _d_FOURIER_SIN_101, _d_FOURIER_COS_101),
|
|
94
|
+
}
|
pytem/benchmarks.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Numerical benchmarks for pyTEM forward-model workflows."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
from .forward import fwd_circle_offset, fwd_square_offset
|
|
11
|
+
from .waveform import setup_shared_gate_matrices
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def benchmark_step_grids(
|
|
15
|
+
systems,
|
|
16
|
+
thicknesses,
|
|
17
|
+
models,
|
|
18
|
+
geometry,
|
|
19
|
+
tx_size,
|
|
20
|
+
rx_x=0.0,
|
|
21
|
+
rx_y=0.0,
|
|
22
|
+
grid_points=(20, 70, 120, 170, 220, 270, 320, 370, 400),
|
|
23
|
+
reference_points=1200,
|
|
24
|
+
repeats=3,
|
|
25
|
+
tolerance=1.0,
|
|
26
|
+
system_filter=None,
|
|
27
|
+
n_quad=5,
|
|
28
|
+
transform='dlf',
|
|
29
|
+
use_numba=True,
|
|
30
|
+
use_cuda=False,
|
|
31
|
+
):
|
|
32
|
+
"""Benchmark shared step-time grids against a high-resolution reference."""
|
|
33
|
+
if geometry not in ('circle_offset', 'square_offset'):
|
|
34
|
+
raise ValueError("geometry must be 'circle_offset' or 'square_offset'.")
|
|
35
|
+
if repeats < 1 or reference_points < 1:
|
|
36
|
+
raise ValueError('repeats and reference_points must be positive.')
|
|
37
|
+
|
|
38
|
+
thicknesses = np.asarray(thicknesses, dtype=float)
|
|
39
|
+
models = {str(name): np.asarray(model, dtype=float)
|
|
40
|
+
for name, model in models.items()}
|
|
41
|
+
points = sorted(set(int(value) for value in grid_points))
|
|
42
|
+
if not points or min(points) < 1:
|
|
43
|
+
raise ValueError('grid_points must contain positive integers.')
|
|
44
|
+
|
|
45
|
+
def predict(resistivities, step_times, gate_matrices):
|
|
46
|
+
options = dict(current=1.0, signal=-1, use_numba=use_numba,
|
|
47
|
+
use_cuda=use_cuda, transform=transform,
|
|
48
|
+
system_filter=system_filter)
|
|
49
|
+
if geometry == 'square_offset':
|
|
50
|
+
response = -fwd_square_offset(
|
|
51
|
+
thicknesses, resistivities, tx_size, rx_x, rx_y, step_times,
|
|
52
|
+
n_quad=n_quad, **options)
|
|
53
|
+
else:
|
|
54
|
+
response = -fwd_circle_offset(
|
|
55
|
+
thicknesses, resistivities, tx_size, rx_x, step_times, **options)
|
|
56
|
+
return {moment: gate_matrices[moment] @ response
|
|
57
|
+
for moment in systems}
|
|
58
|
+
|
|
59
|
+
reference_times, reference_matrices = setup_shared_gate_matrices(
|
|
60
|
+
systems, n_step=reference_points)
|
|
61
|
+
check_times, check_matrices = setup_shared_gate_matrices(
|
|
62
|
+
systems, n_step=2 * reference_points)
|
|
63
|
+
references = {name: predict(model, reference_times, reference_matrices)
|
|
64
|
+
for name, model in models.items()}
|
|
65
|
+
reference_errors = []
|
|
66
|
+
for name in models:
|
|
67
|
+
check = predict(models[name], check_times, check_matrices)
|
|
68
|
+
for moment in systems:
|
|
69
|
+
scale = np.maximum(np.abs(check[moment]),
|
|
70
|
+
max(np.max(np.abs(check[moment])) * 1e-10, 1e-300))
|
|
71
|
+
reference_errors.append(
|
|
72
|
+
np.max(100 * np.abs(references[name][moment] - check[moment]) / scale))
|
|
73
|
+
|
|
74
|
+
rows = []
|
|
75
|
+
for points_count in points:
|
|
76
|
+
started = time.perf_counter()
|
|
77
|
+
step_times, gate_matrices = setup_shared_gate_matrices(
|
|
78
|
+
systems, n_step=points_count)
|
|
79
|
+
setup_seconds = time.perf_counter() - started
|
|
80
|
+
for name, model in models.items():
|
|
81
|
+
predict(model, step_times, gate_matrices)
|
|
82
|
+
durations = []
|
|
83
|
+
prediction = None
|
|
84
|
+
for _ in range(repeats):
|
|
85
|
+
started = time.perf_counter()
|
|
86
|
+
prediction = predict(model, step_times, gate_matrices)
|
|
87
|
+
durations.append(time.perf_counter() - started)
|
|
88
|
+
for moment in systems:
|
|
89
|
+
reference = references[name][moment]
|
|
90
|
+
scale = np.maximum(np.abs(reference),
|
|
91
|
+
max(np.max(np.abs(reference)) * 1e-10, 1e-300))
|
|
92
|
+
error = 100 * np.abs(prediction[moment] - reference) / scale
|
|
93
|
+
rows.append({
|
|
94
|
+
'Points': points_count,
|
|
95
|
+
'Model': name,
|
|
96
|
+
'Moment': moment,
|
|
97
|
+
'RMS error [%]': np.sqrt(np.mean(error ** 2)),
|
|
98
|
+
'Max error [%]': error.max(),
|
|
99
|
+
'Prediction [ms]': 1000 * np.median(durations),
|
|
100
|
+
'Setup [s]': setup_seconds,
|
|
101
|
+
'Below tolerance': bool(error.max() <= tolerance),
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
result = pd.DataFrame(rows)
|
|
105
|
+
result.attrs['reference_max_error_percent'] = max(reference_errors, default=0.0)
|
|
106
|
+
result.attrs['reference_points'] = reference_points
|
|
107
|
+
result.attrs['tolerance'] = tolerance
|
|
108
|
+
return result
|