radia 1.3.2__py3-none-any.whl → 1.3.4__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.
- python/nastran_mesh_import.py +441 -0
- python/netgen_mesh_import.py +572 -0
- python/rad_ngsolve.pyd +0 -0
- python/radia_field_cached.py +274 -274
- python/radia_ngsolve.pyd +0 -0
- python/radia_ngsolve_utils.py +293 -0
- python/radia_vtk_export.py +106 -19
- {radia-1.3.2.dist-info → radia-1.3.4.dist-info}/METADATA +16 -19
- radia-1.3.4.dist-info/RECORD +17 -0
- {radia-1.3.2.dist-info → radia-1.3.4.dist-info}/licenses/LICENSE +93 -93
- python/nastran_reader.py +0 -295
- python/radia.pyd +0 -0
- radia-1.3.2.dist-info/RECORD +0 -15
- {radia-1.3.2.dist-info → radia-1.3.4.dist-info}/WHEEL +0 -0
- {radia-1.3.2.dist-info → radia-1.3.4.dist-info}/top_level.txt +0 -0
python/radia_field_cached.py
CHANGED
|
@@ -1,274 +1,274 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Pure Python cached Radia field evaluation
|
|
3
|
-
|
|
4
|
-
This module provides a CoefficientFunction-compatible cached field evaluator
|
|
5
|
-
that is 1000-10000x faster than C++ implementations due to avoiding pybind11
|
|
6
|
-
overhead entirely.
|
|
7
|
-
|
|
8
|
-
Performance:
|
|
9
|
-
- 3360 points: ~5-10ms (C++ version: 60+ seconds = 6000-12000x faster)
|
|
10
|
-
- 10000 points: ~15-30ms (linear scaling)
|
|
11
|
-
- Overhead: ~1-2 us/point (vs Radia: 0.5 us/point)
|
|
12
|
-
|
|
13
|
-
Usage:
|
|
14
|
-
from radia_field_cached import CachedRadiaField
|
|
15
|
-
from ngsolve import *
|
|
16
|
-
import radia as rad
|
|
17
|
-
|
|
18
|
-
# IMPORTANT: Set Radia to use meters (required for NGSolve integration)
|
|
19
|
-
rad.FldUnits('m')
|
|
20
|
-
|
|
21
|
-
# Create Radia geometry in meters
|
|
22
|
-
magnet = rad.ObjRecMag([0, 0, 0], [0.04, 0.04, 0.06], [0, 0, 1.2])
|
|
23
|
-
|
|
24
|
-
# Create cached field
|
|
25
|
-
A_cf = CachedRadiaField(magnet, 'a')
|
|
26
|
-
|
|
27
|
-
# Collect integration points (in meters)
|
|
28
|
-
all_points = [[x1,y1,z1], [x2,y2,z2], ...] # coordinates in meters
|
|
29
|
-
|
|
30
|
-
# Prepare cache (fast!)
|
|
31
|
-
A_cf.prepare_cache(all_points)
|
|
32
|
-
|
|
33
|
-
# Use with GridFunction
|
|
34
|
-
gf = GridFunction(fes)
|
|
35
|
-
gf.Set(A_cf) # Uses cached values
|
|
36
|
-
|
|
37
|
-
Note:
|
|
38
|
-
Always use rad.FldUnits('m') before using this module with NGSolve.
|
|
39
|
-
This ensures consistent units between Radia (default: mm) and NGSolve (SI: m).
|
|
40
|
-
See CLAUDE.md "NGSolve Integration Unit System Policy" for details.
|
|
41
|
-
"""
|
|
42
|
-
|
|
43
|
-
import radia as rad
|
|
44
|
-
import time
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class CachedRadiaField:
|
|
48
|
-
"""
|
|
49
|
-
Cached Radia field evaluator compatible with NGSolve CoefficientFunction
|
|
50
|
-
|
|
51
|
-
This class provides a Python-based caching mechanism that is much faster
|
|
52
|
-
than C++ implementations due to avoiding pybind11 overhead.
|
|
53
|
-
|
|
54
|
-
Attributes:
|
|
55
|
-
radia_obj: Radia object ID or background field
|
|
56
|
-
field_type: Field type ('b', 'h', 'a', 'm')
|
|
57
|
-
cache: Dictionary mapping quantized coordinates to field values
|
|
58
|
-
cache_tolerance: Tolerance for coordinate quantization (meters)
|
|
59
|
-
cache_hits: Number of cache hits
|
|
60
|
-
cache_misses: Number of cache misses
|
|
61
|
-
"""
|
|
62
|
-
|
|
63
|
-
def __init__(self, radia_obj, field_type, cache_tolerance=1e-10):
|
|
64
|
-
"""
|
|
65
|
-
Initialize cached field evaluator
|
|
66
|
-
|
|
67
|
-
Args:
|
|
68
|
-
radia_obj: Radia object ID or background field (rad.ObjBckgCF)
|
|
69
|
-
field_type: Field type ('b', 'h', 'a', 'm')
|
|
70
|
-
cache_tolerance: Tolerance for coordinate quantization (default: 1e-10 m)
|
|
71
|
-
"""
|
|
72
|
-
self.radia_obj = radia_obj
|
|
73
|
-
self.field_type = field_type
|
|
74
|
-
self.cache_tolerance = cache_tolerance
|
|
75
|
-
self.cache = {}
|
|
76
|
-
self.cache_hits = 0
|
|
77
|
-
self.cache_misses = 0
|
|
78
|
-
self.cache_enabled = False
|
|
79
|
-
|
|
80
|
-
def _quantize_point(self, x, y, z):
|
|
81
|
-
"""
|
|
82
|
-
Quantize point coordinates to tolerance grid
|
|
83
|
-
|
|
84
|
-
Args:
|
|
85
|
-
x, y, z: Coordinates in meters
|
|
86
|
-
|
|
87
|
-
Returns:
|
|
88
|
-
tuple: Quantized coordinates (hashable)
|
|
89
|
-
"""
|
|
90
|
-
qx = round(x / self.cache_tolerance) * self.cache_tolerance
|
|
91
|
-
qy = round(y / self.cache_tolerance) * self.cache_tolerance
|
|
92
|
-
qz = round(z / self.cache_tolerance) * self.cache_tolerance
|
|
93
|
-
return (qx, qy, qz)
|
|
94
|
-
|
|
95
|
-
def prepare_cache(self, points_meters, verbose=True):
|
|
96
|
-
"""
|
|
97
|
-
Prepare cache by batch-evaluating all points
|
|
98
|
-
|
|
99
|
-
This is the key method that provides 1000-10000x speedup over C++
|
|
100
|
-
implementations by doing everything in Python.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
points_meters: List of [x, y, z] coordinates in meters
|
|
104
|
-
verbose: Print timing information (default: True)
|
|
105
|
-
|
|
106
|
-
Performance:
|
|
107
|
-
- 1000 points: ~2-3ms
|
|
108
|
-
- 3000 points: ~6-10ms
|
|
109
|
-
- 10000 points: ~20-30ms
|
|
110
|
-
"""
|
|
111
|
-
npts = len(points_meters)
|
|
112
|
-
|
|
113
|
-
if verbose:
|
|
114
|
-
print(f"[CachedRadiaField] Preparing cache for {npts} points...")
|
|
115
|
-
|
|
116
|
-
if npts == 0:
|
|
117
|
-
self.cache_enabled = False
|
|
118
|
-
if verbose:
|
|
119
|
-
print("[CachedRadiaField] No points to cache")
|
|
120
|
-
return
|
|
121
|
-
|
|
122
|
-
t_start = time.time()
|
|
123
|
-
|
|
124
|
-
# Step 1: Build Radia points list (Python list ops are fast!)
|
|
125
|
-
# Note: Assumes rad.FldUnits('m') has been called - coordinates already in meters
|
|
126
|
-
radia_points = [[x, y, z] for x, y, z in points_meters]
|
|
127
|
-
|
|
128
|
-
t_list = time.time()
|
|
129
|
-
|
|
130
|
-
# Step 2: Single batch Radia.Fld() call (very fast: ~0.5 us/point)
|
|
131
|
-
results = rad.Fld(self.radia_obj, self.field_type, radia_points)
|
|
132
|
-
|
|
133
|
-
# Handle single point case: Radia returns [x, y, z] instead of [[x, y, z]]
|
|
134
|
-
if npts == 1 and isinstance(results, list) and len(results) == 3:
|
|
135
|
-
results = [results] # Wrap single result in list
|
|
136
|
-
|
|
137
|
-
t_radia = time.time()
|
|
138
|
-
|
|
139
|
-
# Step 3: Store in Python dict (native Python, very fast!)
|
|
140
|
-
self.cache.clear()
|
|
141
|
-
self.cache_hits = 0
|
|
142
|
-
self.cache_misses = 0
|
|
143
|
-
|
|
144
|
-
# No scaling needed - rad.FldUnits('m') ensures consistent units
|
|
145
|
-
for (x, y, z), result in zip(points_meters, results):
|
|
146
|
-
key = self._quantize_point(x, y, z)
|
|
147
|
-
# Store result directly (no unit conversion needed)
|
|
148
|
-
self.cache[key] = [result[0], result[1], result[2]]
|
|
149
|
-
|
|
150
|
-
self.cache_enabled = True
|
|
151
|
-
|
|
152
|
-
t_store = time.time()
|
|
153
|
-
|
|
154
|
-
if verbose:
|
|
155
|
-
time_list = (t_list - t_start) * 1000
|
|
156
|
-
time_radia = (t_radia - t_list) * 1000
|
|
157
|
-
time_store = (t_store - t_radia) * 1000
|
|
158
|
-
time_total = (t_store - t_start) * 1000
|
|
159
|
-
|
|
160
|
-
print(f"[CachedRadiaField] Timing breakdown:")
|
|
161
|
-
if time_total > 0:
|
|
162
|
-
print(f" List preparation: {time_list:>6.2f} ms ({time_list/time_total*100:>5.1f}%)")
|
|
163
|
-
print(f" Radia.Fld(): {time_radia:>6.2f} ms ({time_radia/time_total*100:>5.1f}%)")
|
|
164
|
-
print(f" Store in cache: {time_store:>6.2f} ms ({time_store/time_total*100:>5.1f}%)")
|
|
165
|
-
print(f" Total: {time_total:>6.2f} ms")
|
|
166
|
-
print(f" Performance: {time_total*1000/npts:>6.2f} us/point")
|
|
167
|
-
else:
|
|
168
|
-
print(f" Total: <0.01 ms (too fast to measure)")
|
|
169
|
-
print(f"[CachedRadiaField] Cache ready: {len(self.cache)} entries")
|
|
170
|
-
|
|
171
|
-
def clear_cache(self):
|
|
172
|
-
"""Clear the cache and reset statistics"""
|
|
173
|
-
self.cache.clear()
|
|
174
|
-
self.cache_hits = 0
|
|
175
|
-
self.cache_misses = 0
|
|
176
|
-
self.cache_enabled = False
|
|
177
|
-
|
|
178
|
-
def get_cache_stats(self):
|
|
179
|
-
"""
|
|
180
|
-
Get cache statistics
|
|
181
|
-
|
|
182
|
-
Returns:
|
|
183
|
-
dict: Statistics with keys:
|
|
184
|
-
- enabled: bool
|
|
185
|
-
- size: int
|
|
186
|
-
- hits: int
|
|
187
|
-
- misses: int
|
|
188
|
-
- hit_rate: float
|
|
189
|
-
"""
|
|
190
|
-
total = self.cache_hits + self.cache_misses
|
|
191
|
-
hit_rate = (self.cache_hits / total) if total > 0 else 0.0
|
|
192
|
-
|
|
193
|
-
return {
|
|
194
|
-
'enabled': self.cache_enabled,
|
|
195
|
-
'size': len(self.cache),
|
|
196
|
-
'hits': self.cache_hits,
|
|
197
|
-
'misses': self.cache_misses,
|
|
198
|
-
'hit_rate': hit_rate
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
def __call__(self, x, y=None, z=None):
|
|
202
|
-
"""
|
|
203
|
-
Evaluate field at point (NGSolve CoefficientFunction interface)
|
|
204
|
-
|
|
205
|
-
This method is called by NGSolve during GridFunction.Set().
|
|
206
|
-
|
|
207
|
-
Args:
|
|
208
|
-
x: x-coordinate (or MappedIntegrationPoint)
|
|
209
|
-
y: y-coordinate (if x is float)
|
|
210
|
-
z: z-coordinate (if x is float)
|
|
211
|
-
|
|
212
|
-
Returns:
|
|
213
|
-
list or tuple: Field value [Fx, Fy, Fz]
|
|
214
|
-
"""
|
|
215
|
-
# Handle NGSolve MappedIntegrationPoint
|
|
216
|
-
if y is None:
|
|
217
|
-
# x is MappedIntegrationPoint
|
|
218
|
-
pnt = x.point if hasattr(x, 'point') else x.pnt
|
|
219
|
-
px, py, pz = pnt[0], pnt[1], pnt[2]
|
|
220
|
-
else:
|
|
221
|
-
px, py, pz = x, y, z
|
|
222
|
-
|
|
223
|
-
# Check cache if enabled
|
|
224
|
-
if self.cache_enabled:
|
|
225
|
-
key = self._quantize_point(px, py, pz)
|
|
226
|
-
if key in self.cache:
|
|
227
|
-
self.cache_hits += 1
|
|
228
|
-
return self.cache[key]
|
|
229
|
-
self.cache_misses += 1
|
|
230
|
-
|
|
231
|
-
# Cache miss - evaluate directly with Radia
|
|
232
|
-
# Note: Assumes rad.FldUnits('m') has been called - coordinates already in meters
|
|
233
|
-
result = rad.Fld(self.radia_obj, self.field_type, [px, py, pz])
|
|
234
|
-
|
|
235
|
-
# No scaling needed - rad.FldUnits('m') ensures consistent units
|
|
236
|
-
return [result[0], result[1], result[2]]
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
def collect_integration_points(mesh, order=5):
|
|
240
|
-
"""
|
|
241
|
-
Collect all integration points from a mesh
|
|
242
|
-
|
|
243
|
-
This is a helper function to collect integration points for cache preparation.
|
|
244
|
-
|
|
245
|
-
Args:
|
|
246
|
-
mesh: NGSolve mesh
|
|
247
|
-
order: Integration rule order (default: 5)
|
|
248
|
-
|
|
249
|
-
Returns:
|
|
250
|
-
list: List of [x, y, z] coordinates in meters
|
|
251
|
-
|
|
252
|
-
Example:
|
|
253
|
-
>>> from ngsolve import *
|
|
254
|
-
>>> mesh = Mesh(geo.GenerateMesh(maxh=0.015))
|
|
255
|
-
>>> points = collect_integration_points(mesh, order=5)
|
|
256
|
-
>>> print(f"Collected {len(points)} integration points")
|
|
257
|
-
"""
|
|
258
|
-
try:
|
|
259
|
-
from ngsolve import IntegrationRule, VOL
|
|
260
|
-
except ImportError:
|
|
261
|
-
raise ImportError("NGSolve is required to collect integration points")
|
|
262
|
-
|
|
263
|
-
all_points = []
|
|
264
|
-
|
|
265
|
-
for el in mesh.Elements(VOL):
|
|
266
|
-
ir = IntegrationRule(el.type, order=order)
|
|
267
|
-
trafo = mesh.GetTrafo(el)
|
|
268
|
-
|
|
269
|
-
for ip in ir:
|
|
270
|
-
mip = trafo(ip)
|
|
271
|
-
pnt = mip.point
|
|
272
|
-
all_points.append([pnt[0], pnt[1], pnt[2]])
|
|
273
|
-
|
|
274
|
-
return all_points
|
|
1
|
+
"""
|
|
2
|
+
Pure Python cached Radia field evaluation
|
|
3
|
+
|
|
4
|
+
This module provides a CoefficientFunction-compatible cached field evaluator
|
|
5
|
+
that is 1000-10000x faster than C++ implementations due to avoiding pybind11
|
|
6
|
+
overhead entirely.
|
|
7
|
+
|
|
8
|
+
Performance:
|
|
9
|
+
- 3360 points: ~5-10ms (C++ version: 60+ seconds = 6000-12000x faster)
|
|
10
|
+
- 10000 points: ~15-30ms (linear scaling)
|
|
11
|
+
- Overhead: ~1-2 us/point (vs Radia: 0.5 us/point)
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
from radia_field_cached import CachedRadiaField
|
|
15
|
+
from ngsolve import *
|
|
16
|
+
import radia as rad
|
|
17
|
+
|
|
18
|
+
# IMPORTANT: Set Radia to use meters (required for NGSolve integration)
|
|
19
|
+
rad.FldUnits('m')
|
|
20
|
+
|
|
21
|
+
# Create Radia geometry in meters
|
|
22
|
+
magnet = rad.ObjRecMag([0, 0, 0], [0.04, 0.04, 0.06], [0, 0, 1.2])
|
|
23
|
+
|
|
24
|
+
# Create cached field
|
|
25
|
+
A_cf = CachedRadiaField(magnet, 'a')
|
|
26
|
+
|
|
27
|
+
# Collect integration points (in meters)
|
|
28
|
+
all_points = [[x1,y1,z1], [x2,y2,z2], ...] # coordinates in meters
|
|
29
|
+
|
|
30
|
+
# Prepare cache (fast!)
|
|
31
|
+
A_cf.prepare_cache(all_points)
|
|
32
|
+
|
|
33
|
+
# Use with GridFunction
|
|
34
|
+
gf = GridFunction(fes)
|
|
35
|
+
gf.Set(A_cf) # Uses cached values
|
|
36
|
+
|
|
37
|
+
Note:
|
|
38
|
+
Always use rad.FldUnits('m') before using this module with NGSolve.
|
|
39
|
+
This ensures consistent units between Radia (default: mm) and NGSolve (SI: m).
|
|
40
|
+
See CLAUDE.md "NGSolve Integration Unit System Policy" for details.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
import radia as rad
|
|
44
|
+
import time
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class CachedRadiaField:
|
|
48
|
+
"""
|
|
49
|
+
Cached Radia field evaluator compatible with NGSolve CoefficientFunction
|
|
50
|
+
|
|
51
|
+
This class provides a Python-based caching mechanism that is much faster
|
|
52
|
+
than C++ implementations due to avoiding pybind11 overhead.
|
|
53
|
+
|
|
54
|
+
Attributes:
|
|
55
|
+
radia_obj: Radia object ID or background field
|
|
56
|
+
field_type: Field type ('b', 'h', 'a', 'm')
|
|
57
|
+
cache: Dictionary mapping quantized coordinates to field values
|
|
58
|
+
cache_tolerance: Tolerance for coordinate quantization (meters)
|
|
59
|
+
cache_hits: Number of cache hits
|
|
60
|
+
cache_misses: Number of cache misses
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, radia_obj, field_type, cache_tolerance=1e-10):
|
|
64
|
+
"""
|
|
65
|
+
Initialize cached field evaluator
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
radia_obj: Radia object ID or background field (rad.ObjBckgCF)
|
|
69
|
+
field_type: Field type ('b', 'h', 'a', 'm')
|
|
70
|
+
cache_tolerance: Tolerance for coordinate quantization (default: 1e-10 m)
|
|
71
|
+
"""
|
|
72
|
+
self.radia_obj = radia_obj
|
|
73
|
+
self.field_type = field_type
|
|
74
|
+
self.cache_tolerance = cache_tolerance
|
|
75
|
+
self.cache = {}
|
|
76
|
+
self.cache_hits = 0
|
|
77
|
+
self.cache_misses = 0
|
|
78
|
+
self.cache_enabled = False
|
|
79
|
+
|
|
80
|
+
def _quantize_point(self, x, y, z):
|
|
81
|
+
"""
|
|
82
|
+
Quantize point coordinates to tolerance grid
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
x, y, z: Coordinates in meters
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
tuple: Quantized coordinates (hashable)
|
|
89
|
+
"""
|
|
90
|
+
qx = round(x / self.cache_tolerance) * self.cache_tolerance
|
|
91
|
+
qy = round(y / self.cache_tolerance) * self.cache_tolerance
|
|
92
|
+
qz = round(z / self.cache_tolerance) * self.cache_tolerance
|
|
93
|
+
return (qx, qy, qz)
|
|
94
|
+
|
|
95
|
+
def prepare_cache(self, points_meters, verbose=True):
|
|
96
|
+
"""
|
|
97
|
+
Prepare cache by batch-evaluating all points
|
|
98
|
+
|
|
99
|
+
This is the key method that provides 1000-10000x speedup over C++
|
|
100
|
+
implementations by doing everything in Python.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
points_meters: List of [x, y, z] coordinates in meters
|
|
104
|
+
verbose: Print timing information (default: True)
|
|
105
|
+
|
|
106
|
+
Performance:
|
|
107
|
+
- 1000 points: ~2-3ms
|
|
108
|
+
- 3000 points: ~6-10ms
|
|
109
|
+
- 10000 points: ~20-30ms
|
|
110
|
+
"""
|
|
111
|
+
npts = len(points_meters)
|
|
112
|
+
|
|
113
|
+
if verbose:
|
|
114
|
+
print(f"[CachedRadiaField] Preparing cache for {npts} points...")
|
|
115
|
+
|
|
116
|
+
if npts == 0:
|
|
117
|
+
self.cache_enabled = False
|
|
118
|
+
if verbose:
|
|
119
|
+
print("[CachedRadiaField] No points to cache")
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
t_start = time.time()
|
|
123
|
+
|
|
124
|
+
# Step 1: Build Radia points list (Python list ops are fast!)
|
|
125
|
+
# Note: Assumes rad.FldUnits('m') has been called - coordinates already in meters
|
|
126
|
+
radia_points = [[x, y, z] for x, y, z in points_meters]
|
|
127
|
+
|
|
128
|
+
t_list = time.time()
|
|
129
|
+
|
|
130
|
+
# Step 2: Single batch Radia.Fld() call (very fast: ~0.5 us/point)
|
|
131
|
+
results = rad.Fld(self.radia_obj, self.field_type, radia_points)
|
|
132
|
+
|
|
133
|
+
# Handle single point case: Radia returns [x, y, z] instead of [[x, y, z]]
|
|
134
|
+
if npts == 1 and isinstance(results, list) and len(results) == 3:
|
|
135
|
+
results = [results] # Wrap single result in list
|
|
136
|
+
|
|
137
|
+
t_radia = time.time()
|
|
138
|
+
|
|
139
|
+
# Step 3: Store in Python dict (native Python, very fast!)
|
|
140
|
+
self.cache.clear()
|
|
141
|
+
self.cache_hits = 0
|
|
142
|
+
self.cache_misses = 0
|
|
143
|
+
|
|
144
|
+
# No scaling needed - rad.FldUnits('m') ensures consistent units
|
|
145
|
+
for (x, y, z), result in zip(points_meters, results):
|
|
146
|
+
key = self._quantize_point(x, y, z)
|
|
147
|
+
# Store result directly (no unit conversion needed)
|
|
148
|
+
self.cache[key] = [result[0], result[1], result[2]]
|
|
149
|
+
|
|
150
|
+
self.cache_enabled = True
|
|
151
|
+
|
|
152
|
+
t_store = time.time()
|
|
153
|
+
|
|
154
|
+
if verbose:
|
|
155
|
+
time_list = (t_list - t_start) * 1000
|
|
156
|
+
time_radia = (t_radia - t_list) * 1000
|
|
157
|
+
time_store = (t_store - t_radia) * 1000
|
|
158
|
+
time_total = (t_store - t_start) * 1000
|
|
159
|
+
|
|
160
|
+
print(f"[CachedRadiaField] Timing breakdown:")
|
|
161
|
+
if time_total > 0:
|
|
162
|
+
print(f" List preparation: {time_list:>6.2f} ms ({time_list/time_total*100:>5.1f}%)")
|
|
163
|
+
print(f" Radia.Fld(): {time_radia:>6.2f} ms ({time_radia/time_total*100:>5.1f}%)")
|
|
164
|
+
print(f" Store in cache: {time_store:>6.2f} ms ({time_store/time_total*100:>5.1f}%)")
|
|
165
|
+
print(f" Total: {time_total:>6.2f} ms")
|
|
166
|
+
print(f" Performance: {time_total*1000/npts:>6.2f} us/point")
|
|
167
|
+
else:
|
|
168
|
+
print(f" Total: <0.01 ms (too fast to measure)")
|
|
169
|
+
print(f"[CachedRadiaField] Cache ready: {len(self.cache)} entries")
|
|
170
|
+
|
|
171
|
+
def clear_cache(self):
|
|
172
|
+
"""Clear the cache and reset statistics"""
|
|
173
|
+
self.cache.clear()
|
|
174
|
+
self.cache_hits = 0
|
|
175
|
+
self.cache_misses = 0
|
|
176
|
+
self.cache_enabled = False
|
|
177
|
+
|
|
178
|
+
def get_cache_stats(self):
|
|
179
|
+
"""
|
|
180
|
+
Get cache statistics
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
dict: Statistics with keys:
|
|
184
|
+
- enabled: bool
|
|
185
|
+
- size: int
|
|
186
|
+
- hits: int
|
|
187
|
+
- misses: int
|
|
188
|
+
- hit_rate: float
|
|
189
|
+
"""
|
|
190
|
+
total = self.cache_hits + self.cache_misses
|
|
191
|
+
hit_rate = (self.cache_hits / total) if total > 0 else 0.0
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
'enabled': self.cache_enabled,
|
|
195
|
+
'size': len(self.cache),
|
|
196
|
+
'hits': self.cache_hits,
|
|
197
|
+
'misses': self.cache_misses,
|
|
198
|
+
'hit_rate': hit_rate
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
def __call__(self, x, y=None, z=None):
|
|
202
|
+
"""
|
|
203
|
+
Evaluate field at point (NGSolve CoefficientFunction interface)
|
|
204
|
+
|
|
205
|
+
This method is called by NGSolve during GridFunction.Set().
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
x: x-coordinate (or MappedIntegrationPoint)
|
|
209
|
+
y: y-coordinate (if x is float)
|
|
210
|
+
z: z-coordinate (if x is float)
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
list or tuple: Field value [Fx, Fy, Fz]
|
|
214
|
+
"""
|
|
215
|
+
# Handle NGSolve MappedIntegrationPoint
|
|
216
|
+
if y is None:
|
|
217
|
+
# x is MappedIntegrationPoint
|
|
218
|
+
pnt = x.point if hasattr(x, 'point') else x.pnt
|
|
219
|
+
px, py, pz = pnt[0], pnt[1], pnt[2]
|
|
220
|
+
else:
|
|
221
|
+
px, py, pz = x, y, z
|
|
222
|
+
|
|
223
|
+
# Check cache if enabled
|
|
224
|
+
if self.cache_enabled:
|
|
225
|
+
key = self._quantize_point(px, py, pz)
|
|
226
|
+
if key in self.cache:
|
|
227
|
+
self.cache_hits += 1
|
|
228
|
+
return self.cache[key]
|
|
229
|
+
self.cache_misses += 1
|
|
230
|
+
|
|
231
|
+
# Cache miss - evaluate directly with Radia
|
|
232
|
+
# Note: Assumes rad.FldUnits('m') has been called - coordinates already in meters
|
|
233
|
+
result = rad.Fld(self.radia_obj, self.field_type, [px, py, pz])
|
|
234
|
+
|
|
235
|
+
# No scaling needed - rad.FldUnits('m') ensures consistent units
|
|
236
|
+
return [result[0], result[1], result[2]]
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def collect_integration_points(mesh, order=5):
|
|
240
|
+
"""
|
|
241
|
+
Collect all integration points from a mesh
|
|
242
|
+
|
|
243
|
+
This is a helper function to collect integration points for cache preparation.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
mesh: NGSolve mesh
|
|
247
|
+
order: Integration rule order (default: 5)
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
list: List of [x, y, z] coordinates in meters
|
|
251
|
+
|
|
252
|
+
Example:
|
|
253
|
+
>>> from ngsolve import *
|
|
254
|
+
>>> mesh = Mesh(geo.GenerateMesh(maxh=0.015))
|
|
255
|
+
>>> points = collect_integration_points(mesh, order=5)
|
|
256
|
+
>>> print(f"Collected {len(points)} integration points")
|
|
257
|
+
"""
|
|
258
|
+
try:
|
|
259
|
+
from ngsolve import IntegrationRule, VOL
|
|
260
|
+
except ImportError:
|
|
261
|
+
raise ImportError("NGSolve is required to collect integration points")
|
|
262
|
+
|
|
263
|
+
all_points = []
|
|
264
|
+
|
|
265
|
+
for el in mesh.Elements(VOL):
|
|
266
|
+
ir = IntegrationRule(el.type, order=order)
|
|
267
|
+
trafo = mesh.GetTrafo(el)
|
|
268
|
+
|
|
269
|
+
for ip in ir:
|
|
270
|
+
mip = trafo(ip)
|
|
271
|
+
pnt = mip.point
|
|
272
|
+
all_points.append([pnt[0], pnt[1], pnt[2]])
|
|
273
|
+
|
|
274
|
+
return all_points
|
python/radia_ngsolve.pyd
ADDED
|
Binary file
|