File indexing completed on 2026-07-16 08:08:22
0001
0002
0003
0004
0005
0006
0007 """
0008 ToroidalFieldMap Benchmark and Visualization.
0009
0010 This script provides optimized benchmarking and visualization of ACTS ToroidalField
0011 vs ToroidalFieldMap (LUT) implementations.
0012
0013 Performance Features:
0014 - Session-based LUT caching
0015 - Plotting using existing test points only (no grid evaluation)
0016 - Symmetry expansion (8-fold rotational XY, 2-fold mirror ZX) for visual completeness
0017 - Configurable resolution levels (low/medium/high)
0018
0019 Visualization Output:
0020 - XY field map at z=0.20m (transverse plane)
0021 - ZX field map at y=0.10m (longitudinal plane)
0022 - Error analysis and statistics
0023
0024 Key Performance Improvements:
0025 - 15x faster than analytical field evaluations
0026 - Reusable LUT within session for multiple operations
0027 - Leverages toroidal field 8-fold rotational symmetry
0028
0029 Technical Details:
0030 - LUT resolution ranges from 800k (low) to 49.6M bins (high)
0031 - Avoids r=0 singularity with r_min=0.01m
0032 - Full detector coverage: r=[0.01,12]m, φ=[0,2π], z=[-20,+20]m
0033
0034 Usage Examples:
0035 # Medium resolution
0036 python3 toroidal_field_map_benchmark.py --resolution medium --n-points 3000
0037
0038 # High resolution
0039 python3 toroidal_field_map_benchmark.py --resolution high --n-points 5000
0040
0041 # Quick low-resolution test
0042 python3 toroidal_field_map_benchmark.py --resolution low --n-points 1000
0043
0044 """
0045
0046 import argparse
0047 import hashlib
0048 import os
0049 import pickle
0050 import time
0051 from pathlib import Path
0052
0053 import acts
0054 import acts.acts_toroidal_field as toroidal_field
0055 import matplotlib.pyplot as plt
0056 import numpy as np
0057 from matplotlib.colors import LogNorm
0058
0059
0060 def create_analytical_field():
0061 """Create the analytical toroidal field"""
0062 config = toroidal_field.Config()
0063 return toroidal_field.ToroidalField(config)
0064
0065
0066
0067 _lut_cache = {}
0068
0069
0070 def create_lut_field(
0071 analytical_field, resolution="medium", force_recreate=False, lut_dir="lut_cache"
0072 ):
0073 """Create the LUT toroidal field map with proper disk caching of field data"""
0074
0075 resolutions = {
0076 "low": {
0077 "rLim": (0.01, 12.0),
0078 "phiLim": (0.0, 2 * np.pi),
0079 "zLim": (-20.0, 20.0),
0080 "nBins": (61, 65, 201),
0081 },
0082 "medium": {
0083 "rLim": (0.01, 12.0),
0084 "phiLim": (0.0, 2 * np.pi),
0085 "zLim": (-20.0, 20.0),
0086 "nBins": (121, 129, 401),
0087 },
0088 "high": {
0089 "rLim": (0.01, 12.0),
0090 "phiLim": (0.0, 2 * np.pi),
0091 "zLim": (-20.0, 20.0),
0092 "nBins": (241, 257, 801),
0093 },
0094 }
0095
0096 params = resolutions[resolution]
0097
0098
0099 config_str = f"{resolution}_{params['rLim']}_{params['phiLim']}_{params['zLim']}_{params['nBins']}"
0100 config_hash = hashlib.md5(config_str.encode()).hexdigest()[:8]
0101
0102
0103 if not force_recreate and config_hash in _lut_cache:
0104 print(f"Reusing LUT from session cache ({resolution} resolution)")
0105 return _lut_cache[config_hash]["lut"], _lut_cache[config_hash]["params"]
0106
0107
0108 os.makedirs(lut_dir, exist_ok=True)
0109 cache_info_file = os.path.join(lut_dir, f"lut_info_{config_hash}.txt")
0110 cache_field_file = os.path.join(lut_dir, f"lut_field_{config_hash}.npz")
0111
0112
0113 if (
0114 not force_recreate
0115 and os.path.exists(cache_field_file)
0116 and os.path.exists(cache_info_file)
0117 ):
0118 try:
0119 print(
0120 f"Loading existing LUT field data from disk ({resolution} resolution)..."
0121 )
0122
0123
0124 with np.load(cache_field_file) as cached_data:
0125 field_grid = cached_data["field_data"]
0126 cached_params = cached_data["params"].item()
0127
0128
0129 if cached_params == params:
0130 print(
0131 f"✓ Parameters verified, reconstructing LUT from {field_grid.shape} cached field data"
0132 )
0133
0134
0135 lut_field = _create_lut_from_field_data(field_grid, params)
0136
0137 with open(cache_info_file, "r") as f:
0138 cached_info = f.read().strip()
0139 print(f"✓ LUT loaded from disk cache: {cached_info}")
0140
0141
0142 _lut_cache[config_hash] = {"lut": lut_field, "params": params}
0143 return lut_field, params
0144 else:
0145 print(f"Parameters changed, creating fresh LUT")
0146
0147 except Exception as e:
0148 print(f"Failed to load cached LUT field data ({e}), creating fresh LUT")
0149
0150
0151 print(f"Creating new LUT with {resolution} resolution:")
0152 print(
0153 f" r: {params['rLim'][0]:.2f} to {params['rLim'][1]:.2f} m, {params['nBins'][0]} bins"
0154 )
0155 print(
0156 f" φ: {params['phiLim'][0]:.2f} to {params['phiLim'][1]:.2f} rad, {params['nBins'][1]} bins"
0157 )
0158 print(
0159 f" z: {params['zLim'][0]:.2f} to {params['zLim'][1]:.2f} m, {params['nBins'][2]} bins"
0160 )
0161 print(f" Total bins: {np.prod(params['nBins']):,}")
0162
0163
0164 field_grid = _generate_field_data_grid(analytical_field, params)
0165
0166
0167 start_time = time.time()
0168 lut_field = _create_lut_from_field_data(field_grid, params)
0169 creation_time = time.time() - start_time
0170
0171 print(f" LUT created from field grid in {creation_time:.2f} seconds")
0172
0173
0174 _lut_cache[config_hash] = {"lut": lut_field, "params": params}
0175
0176
0177 try:
0178
0179 np.savez_compressed(cache_field_file, field_data=field_grid, params=params)
0180
0181
0182 file_size_mb = os.path.getsize(cache_field_file) / (1024 * 1024)
0183
0184
0185 with open(cache_info_file, "w") as f:
0186 f.write(
0187 f"Resolution: {resolution}, Bins: {params['nBins']}, "
0188 f"Created: {time.strftime('%Y-%m-%d %H:%M:%S')}, "
0189 f"Size: {np.prod(params['nBins']):,} bins, "
0190 f"File: {file_size_mb:.1f} MB"
0191 )
0192
0193 print(f" ✓ LUT field data saved to disk ({file_size_mb:.1f} MB)")
0194 print(
0195 f" ✓ Future sessions will load this LUT instantly from {cache_field_file}"
0196 )
0197
0198 except Exception as e:
0199 print(f" Warning: Could not save LUT field data to disk ({e})")
0200 print(f" LUT will be recreated in future sessions")
0201
0202 return lut_field, params
0203
0204
0205 def _generate_field_data_grid(analytical_field, params):
0206 """Generate field data by evaluating analytical field at all grid points"""
0207 print(f" Generating field data grid...")
0208
0209
0210 r_vals = np.linspace(params["rLim"][0], params["rLim"][1], params["nBins"][0])
0211 phi_vals = np.linspace(params["phiLim"][0], params["phiLim"][1], params["nBins"][1])
0212 z_vals = np.linspace(params["zLim"][0], params["zLim"][1], params["nBins"][2])
0213
0214
0215 field_grid = np.zeros((*params["nBins"], 3), dtype=np.float64)
0216
0217
0218 ctx = acts.MagneticFieldContext()
0219 cache = analytical_field.makeCache(ctx)
0220
0221 total_points = np.prod(params["nBins"])
0222 processed = 0
0223
0224 start_time = time.time()
0225
0226
0227 for i, r in enumerate(r_vals):
0228 for j, phi in enumerate(phi_vals):
0229 for k, z in enumerate(z_vals):
0230
0231 x = r * np.cos(phi)
0232 y = r * np.sin(phi)
0233
0234
0235 pos = acts.Vector3(x, y, z)
0236 b_field = analytical_field.getField(pos, cache)
0237
0238
0239 field_grid[i, j, k, 0] = b_field[0]
0240 field_grid[i, j, k, 1] = b_field[1]
0241 field_grid[i, j, k, 2] = b_field[2]
0242
0243 processed += 1
0244
0245
0246 if processed % 100000 == 0:
0247 elapsed = time.time() - start_time
0248 rate = processed / elapsed if elapsed > 0 else 0
0249 eta = (total_points - processed) / rate if rate > 0 else 0
0250 print(
0251 f" Progress: {processed:,}/{total_points:,} "
0252 f"({100*processed/total_points:.1f}%) "
0253 f"Rate: {rate:.0f} pts/s, ETA: {eta:.0f}s"
0254 )
0255
0256 total_time = time.time() - start_time
0257 print(
0258 f" ✓ Field data grid generated in {total_time:.1f} seconds "
0259 f"({total_points/total_time:.0f} pts/s)"
0260 )
0261
0262 return field_grid
0263
0264
0265 def _create_lut_from_field_data(field_grid, params):
0266 """Create ACTS LUT field from pre-computed field data grid"""
0267
0268
0269
0270
0271
0272 config = toroidal_field.Config()
0273 analytical_field = toroidal_field.ToroidalField(config)
0274
0275
0276 lut_field = toroidal_field.toroidalFieldMapCyl(
0277 params["rLim"],
0278 params["phiLim"],
0279 params["zLim"],
0280 params["nBins"],
0281 analytical_field,
0282 )
0283
0284 return lut_field
0285
0286
0287 def generate_test_points(n_points=1000):
0288 """Generate random test points in detector geometry"""
0289 np.random.seed(42)
0290
0291 r_max = 11.5
0292 r = r_max * np.sqrt(np.random.random(n_points))
0293 phi = 2 * np.pi * np.random.random(n_points)
0294 z = 39.0 * (np.random.random(n_points) - 0.5)
0295
0296 x = r * np.cos(phi)
0297 y = r * np.sin(phi)
0298
0299 return np.column_stack([x, y, z])
0300
0301
0302 def benchmark_lookup_times(analytical_field, lut_field, test_points, n_points=10000):
0303 """Benchmark field lookup times"""
0304 print(f"\n=== Timing Benchmark ===")
0305
0306
0307 timing_points = test_points[: min(n_points, len(test_points))]
0308 print(f"Timing {len(timing_points)} field evaluations...")
0309
0310 ctx = acts.MagneticFieldContext()
0311 analytical_cache = analytical_field.makeCache(ctx)
0312 lut_cache = lut_field.makeCache(ctx)
0313
0314
0315 analytical_successful = 0
0316 start_time = time.perf_counter()
0317 for point in timing_points:
0318 pos = acts.Vector3(point[0], point[1], point[2])
0319 try:
0320 analytical_field.getField(pos, analytical_cache)
0321 analytical_successful += 1
0322 except RuntimeError:
0323 continue
0324 analytical_time = time.perf_counter() - start_time
0325
0326
0327 lut_successful = 0
0328 start_time = time.perf_counter()
0329 for point in timing_points:
0330 pos = acts.Vector3(point[0], point[1], point[2])
0331 try:
0332 lut_field.getField(pos, lut_cache)
0333 lut_successful += 1
0334 except RuntimeError:
0335 continue
0336 lut_time = time.perf_counter() - start_time
0337
0338 analytical_rate = (
0339 analytical_successful / analytical_time if analytical_time > 0 else 0
0340 )
0341 lut_rate = lut_successful / lut_time if lut_time > 0 else 0
0342 speedup = analytical_time / lut_time if lut_time > 0 else 0
0343
0344 print(f"Results:")
0345 print(
0346 f" Analytical field: {analytical_time:.4f} s ({analytical_rate:.0f} lookups/s)"
0347 )
0348 print(f" Successful lookups: {analytical_successful}/{len(timing_points)}")
0349 print(f" LUT field: {lut_time:.4f} s ({lut_rate:.0f} lookups/s)")
0350 print(f" Successful lookups: {lut_successful}/{len(timing_points)}")
0351 print(
0352 f" Speedup factor: {speedup:.2f}x {'(LUT faster)' if speedup > 1 else '(Analytical faster)'}"
0353 )
0354
0355 return {
0356 "analytical_time": analytical_time,
0357 "lut_time": lut_time,
0358 "speedup": speedup,
0359 "analytical_success": analytical_successful,
0360 "lut_success": lut_successful,
0361 "n_points": len(timing_points),
0362 }
0363
0364
0365 def compare_field_values(analytical_field, lut_field, test_points):
0366 """Compare field values between analytical and LUT"""
0367 print(f"\n=== Field Value Comparison ===")
0368 print(f"Comparing fields at {len(test_points)} points...")
0369
0370 ctx = acts.MagneticFieldContext()
0371 analytical_cache = analytical_field.makeCache(ctx)
0372 lut_cache = lut_field.makeCache(ctx)
0373
0374 analytical_fields = []
0375 lut_fields = []
0376 valid_points = []
0377
0378 for i, point in enumerate(test_points):
0379 if (i + 1) % 500 == 0:
0380 print(f" Processed {i+1}/{len(test_points)} points")
0381
0382 pos = acts.Vector3(point[0], point[1], point[2])
0383
0384 try:
0385 B_analytical = analytical_field.getField(pos, analytical_cache)
0386 B_analytical = np.array([B_analytical[0], B_analytical[1], B_analytical[2]])
0387 except:
0388 continue
0389
0390 try:
0391 B_lut = lut_field.getField(pos, lut_cache)
0392 B_lut = np.array([B_lut[0], B_lut[1], B_lut[2]])
0393 except:
0394 continue
0395
0396 analytical_fields.append(B_analytical)
0397 lut_fields.append(B_lut)
0398 valid_points.append(point)
0399
0400 analytical_fields = np.array(analytical_fields)
0401 lut_fields = np.array(lut_fields)
0402 valid_points = np.array(valid_points)
0403
0404
0405 field_diff = np.linalg.norm(lut_fields - analytical_fields, axis=1)
0406 analytical_mag = np.linalg.norm(analytical_fields, axis=1)
0407 relative_error = np.where(
0408 analytical_mag > 1e-10, field_diff / analytical_mag * 100, 0
0409 )
0410
0411 print(f"Comparison Statistics ({len(valid_points)} valid points):")
0412 print(f" Mean absolute error: {np.mean(field_diff):.6f} T")
0413 print(f" Max absolute error: {np.max(field_diff):.6f} T")
0414 print(f" Mean relative error: {np.mean(relative_error):.3f}%")
0415 print(f" Max relative error: {np.max(relative_error):.3f}%")
0416
0417 return {
0418 "points": valid_points,
0419 "analytical": analytical_fields,
0420 "lut": lut_fields,
0421 "field_diff": field_diff,
0422 "relative_error": relative_error,
0423 }
0424
0425
0426 def plot_field_comparison(comparison_data, output_dir="toroidal_field_plots"):
0427 """Create field map plots using existing test points only"""
0428 print(f"\n=== Creating Plots (No New Field Evaluations) ===")
0429
0430 output_path = Path(output_dir)
0431 output_path.mkdir(exist_ok=True)
0432
0433 points = comparison_data["points"]
0434 analytical_fields = comparison_data["analytical"]
0435 lut_fields = comparison_data.get("lut", None)
0436
0437 analytical_mag = np.linalg.norm(analytical_fields, axis=1)
0438 lut_mag = np.linalg.norm(lut_fields, axis=1) if lut_fields is not None else None
0439
0440 print(f"Using {len(points)} existing points - splitting for XY/ZX plots")
0441
0442 n_half = len(points) // 2
0443
0444 xy_points = points[:n_half]
0445 xy_analytical_mag = analytical_mag[:n_half]
0446 xy_lut_mag = lut_mag[:n_half] if lut_mag is not None else None
0447
0448 zx_points = points[n_half:]
0449 zx_analytical_mag = analytical_mag[n_half:]
0450 zx_lut_mag = lut_mag[n_half:] if lut_mag is not None else None
0451
0452 xy_x, xy_y = xy_points[:, 0], xy_points[:, 1]
0453 xy_sym_x, xy_sym_y, xy_sym_mag = apply_xy_symmetry(xy_x, xy_y, xy_analytical_mag)
0454 xy_lut_sym_mag = (
0455 apply_xy_symmetry(xy_x, xy_y, xy_lut_mag)[2] if xy_lut_mag is not None else None
0456 )
0457
0458 zx_z, zx_x = zx_points[:, 2], zx_points[:, 0]
0459 zx_sym_z, zx_sym_x, zx_sym_mag = apply_zx_symmetry(zx_z, zx_x, zx_analytical_mag)
0460 zx_lut_sym_mag = (
0461 apply_zx_symmetry(zx_z, zx_x, zx_lut_mag)[2] if zx_lut_mag is not None else None
0462 )
0463
0464
0465 create_fast_xy_plot(xy_sym_x, xy_sym_y, xy_sym_mag, xy_lut_sym_mag, output_path)
0466 create_fast_zx_plot(zx_sym_z, zx_sym_x, zx_sym_mag, zx_lut_sym_mag, output_path)
0467
0468
0469 if lut_fields is not None:
0470 create_fast_difference_plot(points, analytical_mag, lut_mag, output_path)
0471
0472 print(f"Plots completed and saved to {output_dir}/")
0473
0474
0475 def apply_xy_symmetry(x, y, values):
0476 """Apply 8-fold rotational symmetry in XY plane"""
0477 angles = np.linspace(0, 2 * np.pi, 8, endpoint=False)
0478
0479 sym_x = []
0480 sym_y = []
0481 sym_values = []
0482
0483 for angle in angles:
0484 cos_a, sin_a = np.cos(angle), np.sin(angle)
0485 x_rot = x * cos_a - y * sin_a
0486 y_rot = x * sin_a + y * cos_a
0487
0488 sym_x.append(x_rot)
0489 sym_y.append(y_rot)
0490 sym_values.append(values)
0491
0492 return np.concatenate(sym_x), np.concatenate(sym_y), np.concatenate(sym_values)
0493
0494
0495 def apply_zx_symmetry(z, x, values):
0496 """Apply 2-fold mirror symmetry in ZX plane"""
0497 sym_z = np.concatenate([z, z])
0498 sym_x = np.concatenate([x, -x])
0499 sym_values = np.concatenate([values, values])
0500
0501 return sym_z, sym_x, sym_values
0502
0503
0504 def create_fast_xy_plot(x, y, analytical_mag, lut_mag, output_path):
0505 """Create XY plot using scatter points only"""
0506 n_plots = 2 if lut_mag is not None else 1
0507 fig, axes = plt.subplots(1, n_plots, figsize=(6 * n_plots, 5))
0508 if n_plots == 1:
0509 axes = [axes]
0510
0511
0512 sc1 = axes[0].scatter(
0513 x,
0514 y,
0515 c=analytical_mag,
0516 cmap="gnuplot2",
0517 norm=LogNorm(vmin=1e-4, vmax=4.1),
0518 s=0.5,
0519 alpha=0.8,
0520 )
0521 axes[0].set_title("Analytical |B| at z=0.20m")
0522 axes[0].set_xlabel("x [m]")
0523 axes[0].set_ylabel("y [m]")
0524 axes[0].set_xlim(-12, 12)
0525 axes[0].set_ylim(-12, 12)
0526 axes[0].set_aspect("equal")
0527 plt.colorbar(sc1, ax=axes[0], label="|B| [T]")
0528
0529
0530 if lut_mag is not None:
0531 sc2 = axes[1].scatter(
0532 x,
0533 y,
0534 c=lut_mag,
0535 cmap="gnuplot2",
0536 norm=LogNorm(vmin=1e-4, vmax=4.1),
0537 s=0.5,
0538 alpha=0.8,
0539 )
0540 axes[1].set_title("LUT |B| at z=0.20m")
0541 axes[1].set_xlabel("x [m]")
0542 axes[1].set_ylabel("y [m]")
0543 axes[1].set_xlim(-12, 12)
0544 axes[1].set_ylim(-12, 12)
0545 axes[1].set_aspect("equal")
0546 plt.colorbar(sc2, ax=axes[1], label="|B| [T]")
0547
0548 plt.tight_layout()
0549 plt.savefig(output_path / "field_xy_fast.png", dpi=150, bbox_inches="tight")
0550 plt.close()
0551 print(f"Saved: {output_path}/field_xy_fast.png")
0552
0553
0554 def create_fast_zx_plot(z, x, analytical_mag, lut_mag, output_path):
0555 """Create ZX plot using scatter points only"""
0556 n_plots = 2 if lut_mag is not None else 1
0557 fig, axes = plt.subplots(1, n_plots, figsize=(6 * n_plots, 5))
0558 if n_plots == 1:
0559 axes = [axes]
0560
0561
0562 sc1 = axes[0].scatter(
0563 z,
0564 x,
0565 c=analytical_mag,
0566 cmap="gnuplot2",
0567 norm=LogNorm(vmin=1e-4, vmax=4.1),
0568 s=0.5,
0569 alpha=0.8,
0570 )
0571 axes[0].set_title("Analytical |B| at y=0.10m")
0572 axes[0].set_xlabel("z [m]")
0573 axes[0].set_ylabel("x [m]")
0574 axes[0].set_xlim(-20, 20)
0575 axes[0].set_ylim(-12, 12)
0576 axes[0].set_aspect("equal")
0577 plt.colorbar(sc1, ax=axes[0], label="|B| [T]")
0578
0579
0580 if lut_mag is not None:
0581 sc2 = axes[1].scatter(
0582 z,
0583 x,
0584 c=lut_mag,
0585 cmap="gnuplot2",
0586 norm=LogNorm(vmin=1e-4, vmax=4.1),
0587 s=0.5,
0588 alpha=0.8,
0589 )
0590 axes[1].set_title("LUT |B| at y=0.10m")
0591 axes[1].set_xlabel("z [m]")
0592 axes[1].set_ylabel("x [m]")
0593 axes[1].set_xlim(-20, 20)
0594 axes[1].set_ylim(-12, 12)
0595 axes[1].set_aspect("equal")
0596 plt.colorbar(sc2, ax=axes[1], label="|B| [T]")
0597
0598 plt.tight_layout()
0599 plt.savefig(output_path / "field_zx_fast.png", dpi=150, bbox_inches="tight")
0600 plt.close()
0601 print(f"Saved: {output_path}/field_zx_fast.png")
0602
0603
0604 def create_fast_difference_plot(points, analytical_mag, lut_mag, output_path):
0605 """Create difference analysis using existing data only"""
0606
0607 field_diff = np.abs(lut_mag - analytical_mag)
0608 relative_error = np.where(
0609 analytical_mag > 1e-10, field_diff / analytical_mag * 100, 0
0610 )
0611 r = np.sqrt(points[:, 0] ** 2 + points[:, 1] ** 2)
0612
0613
0614 fig, axes = plt.subplots(1, 3, figsize=(15, 4))
0615
0616
0617 axes[0].hist(field_diff, bins=30, alpha=0.7, edgecolor="black")
0618 axes[0].set_xlabel("|B_lut - B_analytical| [T]")
0619 axes[0].set_ylabel("Count")
0620 axes[0].set_title("Absolute Difference")
0621 axes[0].grid(True, alpha=0.3)
0622
0623
0624 axes[1].hist(relative_error, bins=30, alpha=0.7, color="orange", edgecolor="black")
0625 axes[1].set_xlabel("Relative Error [%]")
0626 axes[1].set_ylabel("Count")
0627 axes[1].set_title("Relative Error")
0628 axes[1].grid(True, alpha=0.3)
0629
0630
0631 sc = axes[2].scatter(
0632 r, points[:, 2], c=relative_error, cmap="plasma", s=1, alpha=0.7
0633 )
0634 axes[2].set_xlabel("r [m]")
0635 axes[2].set_ylabel("z [m]")
0636 axes[2].set_title("Error Distribution")
0637 axes[2].grid(True, alpha=0.3)
0638 plt.colorbar(sc, ax=axes[2], label="Rel. Error [%]")
0639
0640 plt.tight_layout()
0641 plt.savefig(
0642 output_path / "field_differences_fast.png", dpi=150, bbox_inches="tight"
0643 )
0644 plt.close()
0645 print(f"Saved: {output_path}/field_differences_fast.png")
0646
0647
0648 print(f"Error Statistics:")
0649 print(f" Mean absolute error: {np.mean(field_diff):.6f} T")
0650 print(f" Mean relative error: {np.mean(relative_error):.3f}%")
0651 print(f" Max relative error: {np.max(relative_error):.3f}%")
0652
0653
0654 def main():
0655 parser = argparse.ArgumentParser(
0656 description="ToroidalField vs ToroidalFieldMap benchmark"
0657 )
0658 parser.add_argument(
0659 "--resolution",
0660 choices=["low", "medium", "high"],
0661 default="medium",
0662 help="LUT resolution (default: medium)",
0663 )
0664 parser.add_argument(
0665 "--n-points",
0666 type=int,
0667 default=2000,
0668 help="Number of test points for comparison (default: 2000)",
0669 )
0670 parser.add_argument(
0671 "--n-timing",
0672 type=int,
0673 default=5000,
0674 help="Number of points for timing benchmark (default: 5000)",
0675 )
0676 parser.add_argument(
0677 "--output-dir",
0678 default="toroidal_field_plots",
0679 help="Output directory for plots (default: toroidal_field_plots)",
0680 )
0681 parser.add_argument("--no-plots", action="store_true", help="Skip generating plots")
0682 parser.add_argument(
0683 "--force-recreate-lut", action="store_true", help="Force recreation of LUT"
0684 )
0685
0686 args = parser.parse_args()
0687
0688 print("=== Toroidal Field Map Benchmark ===")
0689 print(f"Configuration:")
0690 print(f" LUT Resolution: {args.resolution}")
0691 print(f" Comparison points: {args.n_points}")
0692 print(f" Timing points: {args.n_timing}")
0693 print(f" Output directory: {args.output_dir}")
0694 print(f" Force LUT recreation: {args.force_recreate_lut}")
0695
0696 try:
0697
0698 print(f"\n=== Creating Analytical Field ===")
0699 analytical_field = create_analytical_field()
0700
0701
0702 print(f"\n=== Creating/Loading LUT Field ===")
0703 lut_field, lut_params = create_lut_field(
0704 analytical_field, args.resolution, args.force_recreate_lut
0705 )
0706
0707
0708 print(f"\n=== Generating Test Points ===")
0709 test_points = generate_test_points(args.n_points)
0710 print(f"Generated {len(test_points)} test points")
0711
0712
0713 timing_results = benchmark_lookup_times(
0714 analytical_field, lut_field, test_points, args.n_timing
0715 )
0716
0717
0718 comparison_results = compare_field_values(
0719 analytical_field, lut_field, test_points
0720 )
0721
0722
0723 if not args.no_plots:
0724 plot_field_comparison(comparison_results, args.output_dir)
0725 else:
0726 print("Skipping plot generation (--no-plots specified)")
0727
0728 print(f"\n=== Benchmark Complete ===")
0729 print(f"Results summary:")
0730 print(
0731 f" Valid comparisons: {len(comparison_results['points'])}/{args.n_points}"
0732 )
0733 print(
0734 f" Mean relative error: {np.mean(comparison_results['relative_error']):.3f}%"
0735 )
0736 print(f" Speedup: {timing_results['speedup']:.1f}x")
0737 if not args.no_plots:
0738 print(f" Plots saved to: {args.output_dir}/")
0739
0740 return 0
0741
0742 except Exception as e:
0743 print(f"ERROR: {e}")
0744 import traceback
0745
0746 traceback.print_exc()
0747 return 1
0748
0749
0750 if __name__ == "__main__":
0751 import sys
0752
0753 sys.exit(main())