OPALX (Object Oriented Parallel Accelerator Library for Exascal) master (dc2a29eed580)
OPALX
Loading...
Searching...
No Matches
TestFM2DDynamic.cpp
Go to the documentation of this file.
1
49#include <gtest/gtest.h>
50
51#include "Fields/FM2DDynamic.h"
52#include "Fields/Fieldmap.h"
53#include "Ippl.h"
54#include "Physics/Units.h"
56
57#include <cmath>
58#include <filesystem>
59#include <fstream>
60#include <vector>
61
62namespace {
63
64 // ---------------------------------------------------------------------------
65 // Helper: write dynamic XZ fieldmap
66 // Format:
67 // 2DDynamic XZ
68 // zbegin zend nz
69 // freq (MHz)
70 // rbegin rend nr
71 // data: Ez Er dummy Bt
72 // ---------------------------------------------------------------------------
73 std::string writeXZFieldmap(
74 const std::string& path, double zbegin_cm, double zend_cm, int nz, double rbegin_cm,
75 double rend_cm, int nr, double freq_MHz = 100.0, double uniformEz = 1.0,
76 double uniformEr = 0.0, double uniformBt = 0.0) {
77 std::ofstream f(path);
78 f << "2DDynamic XZ\n";
79 f << zbegin_cm << " " << zend_cm << " " << nz << "\n";
80 f << freq_MHz << "\n";
81 f << rbegin_cm << " " << rend_cm << " " << nr << "\n";
82
83 for (int jr = 0; jr <= nr; ++jr) {
84 for (int iz = 0; iz <= nz; ++iz) {
85 f << uniformEz << " " << uniformEr << " 0.0 " << uniformBt << "\n";
86 }
87 }
88 return path;
89 }
90
91 // ---------------------------------------------------------------------------
92 // Helper: write dynamic fieldmap in ZX orientation
93 // Format:
94 // 2DDynamic ZX
95 // rbegin rend nr
96 // freq
97 // zbegin zend nz
98 // data: Er Ez Bt dummy
99 // ---------------------------------------------------------------------------
100 std::string writeZXFieldmap(
101 const std::string& path, double zbegin_cm, double zend_cm, int nz, double rbegin_cm,
102 double rend_cm, int nr, double freq_MHz = 100.0, double uniformEz = 1.0,
103 double uniformEr = 0.0, double uniformBt = 0.0) {
104 std::ofstream f(path);
105 f << "2DDynamic ZX\n";
106 f << rbegin_cm << " " << rend_cm << " " << nr << "\n";
107 f << freq_MHz << "\n";
108 f << zbegin_cm << " " << zend_cm << " " << nz << "\n";
109
110 for (int i = 0; i <= nz; ++i) {
111 for (int j = 0; j <= nr; ++j) {
112 f << uniformEr << " " << uniformEz << " " << uniformBt << " 0.0\n";
113 }
114 }
115 return path;
116 }
117
118 // ---------------------------------------------------------------------------
119 // Helper: Write a fieldmap with spatially varying field for interpolation tests.
120 // Uses XZ orientation. Field: Ez = z [MV/m], Er = r [MV/m], Bt = r [T],
121 // where r and z are in meters.
122 // ---------------------------------------------------------------------------
123 std::string writeVaryingXZFieldmap(
124 const std::string& path, double zbegin_cm, double zend_cm, int nz, double rbegin_cm,
125 double rend_cm, int nr, double freq_MHz) {
126 const double hz_cm = (zend_cm - zbegin_cm) / nz;
127 const double hr_cm = (rend_cm - rbegin_cm) / nr;
128
129 std::ofstream f(path);
130 f << "2DDynamic XZ\n";
131 f << zbegin_cm << " " << zend_cm << " " << nz << "\n";
132 f << freq_MHz << "\n";
133 f << rbegin_cm << " " << rend_cm << " " << nr << "\n";
134
135 for (int jr = 0; jr <= nr; ++jr) {
136 double r_m = (rbegin_cm + jr * hr_cm) * Units::cm2m;
137 for (int iz = 0; iz <= nz; ++iz) {
138 double z_m = (zbegin_cm + iz * hz_cm) * Units::cm2m;
139 f << z_m << " " << r_m << " 0.0 " << r_m << "\n";
140 }
141 }
142 return path;
143 }
144
145} // namespace
146
147// ===========================================================================
148// Fixture
149// ===========================================================================
150class FM2DDynamicTest : public ::testing::Test {
151protected:
152 static void SetUpTestSuite() {
153 int argc = 0;
154 char** argv = nullptr;
155 ippl::initialize(argc, argv);
156 }
157
158 static void TearDownTestSuite() {
159 Fieldmap::clearDictionary();
160 ippl::finalize();
161 }
162
163 void SetUp() override {
164 tmpDir_ = std::filesystem::temp_directory_path() / "opalx_fm2d_test";
165 std::filesystem::create_directories(tmpDir_);
166 }
167
168 void TearDown() override {
169 Fieldmap::clearDictionary();
170 std::filesystem::remove_all(tmpDir_);
171 }
172
173 std::string tmpFile(const std::string& name) const { return (tmpDir_ / name).string(); }
174
175 std::filesystem::path tmpDir_;
176};
177
178// ===========================================================================
179// Test: Parse XZ orientation and verify field dimensions
180// ===========================================================================
181TEST_F(FM2DDynamicTest, ParseXZOrientation) {
182 const double zb = 0.0, ze = 10.0; // cm
183 const double rb = 0.0, re = 5.0; // cm
184 const double freq = 100.0; // MHz
185 const int nz = 4, nr = 2;
186
187 std::string fname = writeXZFieldmap(tmpFile("xz.map"), zb, ze, nz, rb, re, nr, freq);
188
189 Fieldmap* fm = Fieldmap::getFieldmap(fname);
190 ASSERT_NE(fm, nullptr);
191 EXPECT_EQ(fm->getType(), T2DDynamic);
192
193 Fieldmap::readMap(fname);
194
195 double zBegin, zEnd;
196 fm->getFieldDimensions(zBegin, zEnd);
197
198 EXPECT_NEAR(zBegin, zb * Units::cm2m, 1e-12);
199 EXPECT_NEAR(zEnd, ze * Units::cm2m, 1e-12);
200}
201
202// ===========================================================================
203// Test: Parse ZX orientation and verify field dimensions
204// ===========================================================================
205TEST_F(FM2DDynamicTest, ParseZXOrientation) {
206 const double zb = -5.0, ze = 15.0; // cm
207 const double rb = 0.0, re = 3.0; // cm
208 const double freq = 100.0; // MHz
209 const int nz = 3, nr = 2;
210
211 std::string fname = writeZXFieldmap(tmpFile("zx.map"), zb, ze, nz, rb, re, nr, freq);
212
213 Fieldmap* fm = Fieldmap::getFieldmap(fname);
214 ASSERT_NE(fm, nullptr);
215
216 Fieldmap::readMap(fname);
217
218 double zBegin, zEnd;
219 fm->getFieldDimensions(zBegin, zEnd);
220
221 EXPECT_NEAR(zBegin, zb * Units::cm2m, 1e-12);
222 EXPECT_NEAR(zEnd, ze * Units::cm2m, 1e-12);
223}
224
225// ===========================================================================
226// Test: Frequency parsing and conversion
227// ===========================================================================
228TEST_F(FM2DDynamicTest, FrequencyParsing) {
229 const double zb = 0.0, ze = 10.0; // cm
230 const double rb = 0.0, re = 5.0; // cm
231 const double freq = 100.0; // MHz
232 const int nz = 4, nr = 2;
233
234 std::string fname = writeXZFieldmap(tmpFile("freq.map"), zb, ze, nz, rb, re, nr, freq);
235
236 auto* fm = dynamic_cast<FM2DDynamic*>(Fieldmap::getFieldmap(fname));
237 ASSERT_NE(fm, nullptr);
238
239 Fieldmap::readMap(fname);
240
241 // Constructor converts: MHz → Hz → angular frequency
242 double expected =
243 freq * Physics::two_pi * Units::MHz2Hz; // Should be angular frequency in rad/s
244
245 EXPECT_NEAR(fm->getFrequency(), expected, 1e-6);
246}
247
248// ===========================================================================
249// Test: Uniform field – getFieldstrength returns correct value at grid centre
250// ===========================================================================
251TEST_F(FM2DDynamicTest, UniformFieldStrength) {
252 const double zb = 0.0, ze = 20.0;
253 const double rb = 0.0, re = 5.0;
254 const double freq = 100.0;
255 const int nz = 4, nr = 2;
256
257 const double Ez_val = 1.0;
258 const double Er_val = 0.0;
259 const double Bt_val = 0.0;
260
261 std::string fname = writeXZFieldmap(
262 tmpFile("uni.map"), zb, ze, nz, rb, re, nr, freq, Ez_val, Er_val, Bt_val);
263
264 Fieldmap* fm = Fieldmap::getFieldmap(fname);
265 ASSERT_NE(fm, nullptr);
266
267 Fieldmap::readMap(fname);
268
269 // Query at centre of field (on-axis)
270 Vector_t<double, 3> R = {0.0, 0.0, 0.05}; // z=0.05m
271 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
272 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
273
274 bool outside = fm->getFieldstrength(R, E, B);
275
276 EXPECT_FALSE(outside);
277
278 // On-axis: Er = 0 → no transverse E
279 EXPECT_NEAR(E[0], 0.0, 1e-10);
280 EXPECT_NEAR(E[1], 0.0, 1e-10);
281
282 // Longitudinal electric field
283 EXPECT_NEAR(E[2], Ez_val * 1e6, 1e-10); // MV/m → V/m scaling
284
285 // No magnetic field
286 EXPECT_NEAR(B[0], 0.0, 1e-10);
287 EXPECT_NEAR(B[1], 0.0, 1e-10);
288 EXPECT_NEAR(B[2], 0.0, 1e-10);
289}
290
291// ===========================================================================
292// Test: Field outside z range returns without modifying E/B
293// ===========================================================================
294TEST_F(FM2DDynamicTest, OutsideZRange) {
295 const double zb = 0.0, ze = 10.0;
296 const double rb = 0.0, re = 5.0;
297 const double freq = 100.0;
298 const int nz = 4, nr = 2;
299
300 std::string fname = writeXZFieldmap(tmpFile("oz.map"), zb, ze, nz, rb, re, nr, freq);
301
302 Fieldmap* fm = Fieldmap::getFieldmap(fname);
303 ASSERT_NE(fm, nullptr);
304
305 Fieldmap::readMap(fname);
306
307 // Before z start
308 {
309 Vector_t<double, 3> R = {0.0, 0.0, -0.01};
310 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
311 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
312
313 bool outside = fm->getFieldstrength(R, E, B);
314
315 EXPECT_TRUE(outside);
316
317 // E and B should remain unchanged
318 EXPECT_NEAR(E[0], 0.0, 1e-15);
319 EXPECT_NEAR(E[1], 0.0, 1e-15);
320 EXPECT_NEAR(E[2], 0.0, 1e-15);
321
322 EXPECT_NEAR(B[0], 0.0, 1e-15);
323 EXPECT_NEAR(B[1], 0.0, 1e-15);
324 EXPECT_NEAR(B[2], 0.0, 1e-15);
325 }
326
327 // After z end
328 {
329 Vector_t<double, 3> R = {0.0, 0.0, 0.20}; // 20cm > 10cm
330 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
331 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
332
333 bool outside = fm->getFieldstrength(R, E, B);
334
335 EXPECT_TRUE(outside);
336
337 EXPECT_NEAR(E[0], 0.0, 1e-15);
338 EXPECT_NEAR(E[1], 0.0, 1e-15);
339 EXPECT_NEAR(E[2], 0.0, 1e-15);
340
341 EXPECT_NEAR(B[0], 0.0, 1e-15);
342 EXPECT_NEAR(B[1], 0.0, 1e-15);
343 EXPECT_NEAR(B[2], 0.0, 1e-15);
344 }
345}
346
347// ===========================================================================
348// Test: Field outside radial range returns true (outside flag)
349// ===========================================================================
350TEST_F(FM2DDynamicTest, OutsideRadialRange) {
351 const double zb = 0.0, ze = 10.0;
352 const double rb = 0.0, re = 2.0;
353 const double freq = 100.0;
354 const int nz = 4, nr = 2;
355
356 std::string fname = writeXZFieldmap(tmpFile("or.map"), zb, ze, nz, rb, re, nr, freq);
357
358 Fieldmap* fm = Fieldmap::getFieldmap(fname);
359 ASSERT_NE(fm, nullptr);
360
361 Fieldmap::readMap(fname);
362
363 // r = 0.05 m = 5 cm > re = 2 cm
364 Vector_t<double, 3> R = {0.05, 0.0, 0.05};
365 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
366 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
367
368 bool outside = fm->getFieldstrength(R, E, B);
369
370 EXPECT_TRUE(outside);
371
372 EXPECT_NEAR(E[0], 0.0, 1e-15);
373 EXPECT_NEAR(E[1], 0.0, 1e-15);
374 EXPECT_NEAR(E[2], 0.0, 1e-15);
375
376 EXPECT_NEAR(B[0], 0.0, 1e-15);
377 EXPECT_NEAR(B[1], 0.0, 1e-15);
378 EXPECT_NEAR(B[2], 0.0, 1e-15);
379}
380
381// ===========================================================================
382// Test: isInside() helper
383// ===========================================================================
385 const double zb = 0.0, ze = 10.0;
386 const double rb = 0.0, re = 5.0;
387 const double freq = 100.0;
388 const int nz = 4, nr = 2;
389
390 std::string fname = writeXZFieldmap(tmpFile("ins.map"), zb, ze, nz, rb, re, nr, freq);
391
392 auto* fm = dynamic_cast<FM2DDynamic*>(Fieldmap::getFieldmap(fname));
393 ASSERT_NE(fm, nullptr);
394
395 // Inside
396 EXPECT_TRUE(fm->isInside({0.0, 0.0, 0.05}));
397
398 // On zbegin boundary
399 EXPECT_TRUE(fm->isInside({0.0, 0.0, 0.0}));
400
401 // Just before zend (zend = 0.10 m)
402 EXPECT_TRUE(fm->isInside({0.0, 0.0, 0.099}));
403
404 // At zend – should be outside (< zend, not <=)
405 EXPECT_FALSE(fm->isInside({0.0, 0.0, 0.10}));
406
407 // Before zbegin
408 EXPECT_FALSE(fm->isInside({0.0, 0.0, -0.01}));
409
410 // Outside radial
411 EXPECT_FALSE(fm->isInside({0.10, 0.0, 0.05}));
412}
413
414// ===========================================================================
415// Test: swap() toggles the swap state
416// ===========================================================================
418 const double zb = 0.0, ze = 10.0;
419 const double rb = 0.0, re = 5.0;
420 const double freq = 100.0;
421 const int nz = 4, nr = 2;
422
423 std::string fname = writeXZFieldmap(tmpFile("sw.map"), zb, ze, nz, rb, re, nr, freq);
424
425 auto* fm = dynamic_cast<FM2DDynamic*>(Fieldmap::getFieldmap(fname));
426 ASSERT_NE(fm, nullptr);
427
428 // swap_m starts false for XZ
429 fm->swap(); // now true
430 fm->swap(); // back to false
431
432 // No crash – just verify the toggle doesn't explode
433}
434
435// ===========================================================================
436// Test: Normalization flag
437// ===========================================================================
438TEST_F(FM2DDynamicTest, Normalization) {
439 // normalize_m defaults to true.
440 // With uniform Ez = 3.0 (MV/m), Ezmax = 3.0,
441 // so after normalization Ez -> 1.0 * 1e6 V/m
442
443 const double zb = 0.0, ze = 10.0;
444 const double rb = 0.0, re = 5.0;
445 const double freq = 100.0;
446 const int nz = 4, nr = 2;
447
448 const double Ez_val = 3.0; // MV/m
449
450 std::string fname = writeXZFieldmap(
451 tmpFile("norm.map"), zb, ze, nz, rb, re, nr, freq,
452 /*Ez=*/Ez_val,
453 /*Er=*/0.0,
454 /*Bt=*/0.0);
455
456 Fieldmap* fm = Fieldmap::getFieldmap(fname);
457 Fieldmap::readMap(fname);
458
459 Vector_t<double, 3> R = {0.0, 0.0, 0.05};
460 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
461 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
462
463 fm->getFieldstrength(R, E, B);
464
465 // After normalization:
466 // Ez_normalized = Ez / Ezmax = 1.0
467 // Then scaled: 1.0 * 1e6 V/m
468 EXPECT_NEAR(E[2], 1e6, 1e-6);
469}
470
471// ===========================================================================
472// Test: Bilinear interpolation with spatially varying field
473// ===========================================================================
474TEST_F(FM2DDynamicTest, BilinearInterpolation) {
475 // Field before normalization:
476 // Ez(r,z) = z [MV/m], Er(r,z) = r [MV/m], Bt(r,z) = r
477 //
478 // After normalization + scaling:
479 // Ez = (z / Ezmax) * 1e6
480 // Er = (r / Ezmax) * 1e6
481 // Bt = (r / Ezmax) * mu0
482
483 const double zb = 0.0, ze = 10.0; // cm
484 const double rb = 0.0, re = 5.0; // cm
485 const double freq = 100.0;
486 const int nz = 4, nr = 4;
487
488 const double zend_m = ze * Units::cm2m; // 0.10 m
489 const double Ezmax = zend_m; // same logic as magnetostatic
490
491 std::string fname = writeVaryingXZFieldmap(tmpFile("var.map"), zb, ze, nz, rb, re, nr, freq);
492
493 Fieldmap* fm = Fieldmap::getFieldmap(fname);
494 Fieldmap::readMap(fname);
495
496 const double scaleE = 1e6 / Ezmax;
497
498 // ----------------------------------------------------------------------
499 // On-axis: z = 5cm = 0.05m
500 // ----------------------------------------------------------------------
501 {
502 Vector_t<double, 3> R = {0.0, 0.0, 0.05};
503 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
504 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
505
506 fm->getFieldstrength(R, E, B);
507
508 EXPECT_NEAR(E[2], 0.05 * scaleE, 1e-6);
509 EXPECT_NEAR(E[0], 0.0, 1e-10);
510 EXPECT_NEAR(E[1], 0.0, 1e-10);
511 }
512
513 // ----------------------------------------------------------------------
514 // Off-axis along x
515 // ----------------------------------------------------------------------
516 {
517 double r_m = 0.025; // 2.5 cm
518 Vector_t<double, 3> R = {r_m, 0.0, 0.05};
519 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
520 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
521
522 fm->getFieldstrength(R, E, B);
523
524 EXPECT_NEAR(E[2], 0.05 * scaleE, 1e-6);
525
526 // Er projected onto x
527 EXPECT_NEAR(E[0], r_m * scaleE, 1e-6);
528 EXPECT_NEAR(E[1], 0.0, 1e-10);
529
530 // Bt produces transverse B
531 EXPECT_NEAR(B[1], r_m * Physics::mu_0 / Ezmax, 1e-10);
532 }
533
534 // ----------------------------------------------------------------------
535 // Off-axis along y
536 // ----------------------------------------------------------------------
537 {
538 double r_m = 0.025;
539 Vector_t<double, 3> R = {0.0, r_m, 0.05};
540 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
541 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
542
543 fm->getFieldstrength(R, E, B);
544
545 EXPECT_NEAR(E[2], 0.05 * scaleE, 1e-6);
546
547 EXPECT_NEAR(E[0], 0.0, 1e-10);
548 EXPECT_NEAR(E[1], r_m * scaleE, 1e-6);
549
550 EXPECT_NEAR(B[0], -r_m * Physics::mu_0 / Ezmax, 1e-10);
551 }
552
553 // ----------------------------------------------------------------------
554 // Non-grid z value
555 // ----------------------------------------------------------------------
556 {
557 Vector_t<double, 3> R = {0.0, 0.0, 0.03};
558 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
559 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
560
561 fm->getFieldstrength(R, E, B);
562
563 EXPECT_NEAR(E[2], 0.03 * scaleE, 1e-6);
564 }
565}
566
567// ===========================================================================
568// Test: computeField static function directly with host views
569// ===========================================================================
570TEST_F(FM2DDynamicTest, ComputeFieldStatic) {
571 // Build a small grid manually: 3 z-points, 2 r-points
572 // Ez = 1.0 everywhere, Er = 0.5 everywhere, Bt = 0.25 everywhere
573
574 const int npz = 3, npr = 2;
575 const double hz = 0.05, hr = 0.025;
576 const double zbegin = 0.0;
577
578 Kokkos::View<double*, Kokkos::HostSpace> Ez("Ez", npz * npr);
579 Kokkos::View<double*, Kokkos::HostSpace> Er("Er", npz * npr);
580 Kokkos::View<double*, Kokkos::HostSpace> Bt("Bt", npz * npr);
581
582 for (int j = 0; j < npr; ++j)
583 for (int i = 0; i < npz; ++i) {
584 Ez(j * npz + i) = 1.0;
585 Er(j * npz + i) = 0.5;
586 Bt(j * npz + i) = 0.25;
587 }
588
589 // ----------------------------------------------------------------------
590 // On-axis: R = (0, 0, 0.025)
591 // ----------------------------------------------------------------------
592 {
593 Vector_t<double, 3> R = {0.0, 0.0, 0.025};
594 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
595 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
596
597 FM2DDynamic::computeField(R, E, B, Ez, Er, Bt, hr, hz, zbegin, npr, npz);
598
599 EXPECT_NEAR(E[2], 1.0, 1e-10);
600
601 // On-axis RR=0 → no transverse components
602 EXPECT_NEAR(E[0], 0.0, 1e-10);
603 EXPECT_NEAR(E[1], 0.0, 1e-10);
604 EXPECT_NEAR(B[0], 0.0, 1e-10);
605 EXPECT_NEAR(B[1], 0.0, 1e-10);
606 }
607
608 // ----------------------------------------------------------------------
609 // Off-axis: R = (0.01, 0, 0.025)
610 // ----------------------------------------------------------------------
611 {
612 Vector_t<double, 3> R = {0.01, 0.0, 0.025};
613 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
614 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
615
616 FM2DDynamic::computeField(R, E, B, Ez, Er, Bt, hr, hz, zbegin, npr, npz);
617
618 EXPECT_NEAR(E[2], 1.0, 1e-10);
619
620 // Er projected → x
621 EXPECT_NEAR(E[0], 0.5, 1e-10);
622 EXPECT_NEAR(E[1], 0.0, 1e-10);
623
624 // Bt projected → y
625 EXPECT_NEAR(B[1], 0.25, 1e-10);
626 EXPECT_NEAR(B[0], 0.0, 1e-10);
627 }
628
629 // ----------------------------------------------------------------------
630 // Outside z range (before): no modification
631 // ----------------------------------------------------------------------
632 {
633 Vector_t<double, 3> R = {0.0, 0.0, -0.01};
634 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
635 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
636
637 FM2DDynamic::computeField(R, E, B, Ez, Er, Bt, hr, hz, zbegin, npr, npz);
638
639 EXPECT_NEAR(E[2], 0.0, 1e-15);
640 EXPECT_NEAR(B[0], 0.0, 1e-15);
641 }
642
643 // ----------------------------------------------------------------------
644 // Outside r range: no modification
645 // ----------------------------------------------------------------------
646 {
647 Vector_t<double, 3> R = {0.05, 0.0, 0.025}; // outside radial grid
648 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
649 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
650
651 FM2DDynamic::computeField(R, E, B, Ez, Er, Bt, hr, hz, zbegin, npr, npz);
652
653 EXPECT_NEAR(E[0], 0.0, 1e-15);
654 EXPECT_NEAR(E[1], 0.0, 1e-15);
655 EXPECT_NEAR(E[2], 0.0, 1e-15);
656 EXPECT_NEAR(B[0], 0.0, 1e-15);
657 EXPECT_NEAR(B[1], 0.0, 1e-15);
658 }
659}
660
661// ===========================================================================
662// Test: getFieldDerivative throws
663// ===========================================================================
664TEST_F(FM2DDynamicTest, GetFieldDerivativeThrows) {
665 const double zb = 0.0, ze = 10.0;
666 const double rb = 0.0, re = 5.0;
667 const double freq = 100.0;
668 const int nz = 4, nr = 2;
669
670 std::string fname = writeXZFieldmap(tmpFile("deriv.map"), zb, ze, nz, rb, re, nr, freq);
671
672 Fieldmap* fm = Fieldmap::getFieldmap(fname);
673 ASSERT_NE(fm, nullptr);
674
675 Vector_t<double, 3> R = {0.0, 0.0, 0.05};
676 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
677 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
678
679 EXPECT_THROW(fm->getFieldDerivative(R, E, B, DX), GeneralOpalException);
680}
681
682// ===========================================================================
683// Test: getFieldDimensions 6-arg throws
684// ===========================================================================
685TEST_F(FM2DDynamicTest, GetFieldDimensions6ArgThrows) {
686 const double zb = 0.0, ze = 10.0;
687 const double rb = 0.0, re = 5.0;
688 const double freq = 100.0;
689 const int nz = 4, nr = 2;
690
691 std::string fname = writeXZFieldmap(tmpFile("dim6.map"), zb, ze, nz, rb, re, nr, freq);
692
693 Fieldmap* fm = Fieldmap::getFieldmap(fname);
694 ASSERT_NE(fm, nullptr);
695
696 double a, b, c, d, e, f;
697
698 EXPECT_THROW(fm->getFieldDimensions(a, b, c, d, e, f), GeneralOpalException);
699}
700
701// ===========================================================================
702// Test: getInfo does not crash
703// ===========================================================================
704TEST_F(FM2DDynamicTest, GetInfoNoCrash) {
705 const double zb = 0.0, ze = 10.0;
706 const double rb = 0.0, re = 5.0;
707 const double freq = 100.0;
708 const int nz = 4, nr = 2;
709
710 std::string fname = writeXZFieldmap(tmpFile("info.map"), zb, ze, nz, rb, re, nr, freq);
711
712 Fieldmap* fm = Fieldmap::getFieldmap(fname);
713 ASSERT_NE(fm, nullptr);
714
715 Inform msg("test");
716 EXPECT_NO_THROW(fm->getInfo(&msg));
717}
718
719// ===========================================================================
720// Test: Missing file
721// ===========================================================================
722TEST_F(FM2DDynamicTest, MissingFile) {
723 std::string fname = tmpFile("nonexistent.map");
724 EXPECT_THROW(Fieldmap::getFieldmap(fname), GeneralOpalException);
725}
726
727// ===========================================================================
728// Test: Fieldmap dictionary caching – second getFieldmap returns same pointer
729// ===========================================================================
730TEST_F(FM2DDynamicTest, DictionaryCaching) {
731 const double zb = 0.0, ze = 10.0;
732 const double rb = 0.0, re = 5.0;
733 const double freq = 100.0;
734 const int nz = 4, nr = 2;
735
736 std::string fname = writeXZFieldmap(tmpFile("cache.map"), zb, ze, nz, rb, re, nr, freq);
737
738 Fieldmap* fm1 = Fieldmap::getFieldmap(fname);
739 Fieldmap* fm2 = Fieldmap::getFieldmap(fname);
740
741 EXPECT_EQ(fm1, fm2);
742}
743
744// ===========================================================================
745// Test: readMap / freeMap cycle – reading after free should work
746// ===========================================================================
747TEST_F(FM2DDynamicTest, ReadFreeCycle) {
748 const double zb = 0.0, ze = 10.0;
749 const double rb = 0.0, re = 5.0;
750 const double freq = 100.0;
751 const int nz = 4, nr = 2;
752
753 std::string fname = writeXZFieldmap(tmpFile("cycle.map"), zb, ze, nz, rb, re, nr, freq);
754
755 Fieldmap* fm = Fieldmap::getFieldmap(fname);
756 ASSERT_NE(fm, nullptr);
757
758 Fieldmap::readMap(fname);
759
760 Vector_t<double, 3> R = {0.0, 0.0, 0.05};
761 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
762 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
763
764 fm->getFieldstrength(R, E, B);
765
766 // sanity check: field exists (not zero / not crashing)
767 EXPECT_NE(E[2], 0.0);
768
769 fm->freeMap();
770
771 fm->readMap();
772
773 E = {0.0, 0.0, 0.0};
774 B = {0.0, 0.0, 0.0};
775
776 fm->getFieldstrength(R, E, B);
777
778 EXPECT_NE(E[2], 0.0);
779}
780
781// ===========================================================================
782// Test: On-axis at grid points matches exact data (dynamic fieldmap)
783// ===========================================================================
784TEST_F(FM2DDynamicTest, GridPointAccuracy) {
785 // Field before normalization: Ez(r,z) = z, Er = r
786 // With normalization: Ezmax = zend_m, so normalised Ez = z / zend_m
787
788 const double zb = 0.0, ze = 10.0;
789 const double rb = 0.0, re = 5.0;
790 const double freq = 100.0;
791 const int nz = 10, nr = 5;
792
793 const double zb_m = zb * Units::cm2m;
794 const double ze_m = ze * Units::cm2m;
795 // const double zend_m = ze_m;
796
797 std::string fname = writeVaryingXZFieldmap(tmpFile("grid.map"), zb, ze, nz, rb, re, nr, freq);
798
799 Fieldmap* fm = Fieldmap::getFieldmap(fname);
800 ASSERT_NE(fm, nullptr);
801
802 Fieldmap::readMap(fname);
803
804 const double hz_m = (ze_m - zb_m) / nz;
805
806 double Ezmax = 1.0;
807 double scaleE = 1e6 / Ezmax;
808
809 for (int iz = 0; iz < nz; ++iz) {
810 double z = zb_m + iz * hz_m;
811
812 Vector_t<double, 3> R = {0.0, 0.0, z};
813 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
814 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
815
816 fm->getFieldstrength(R, E, B);
817
818 // Dynamic: electric field carries the longitudinal component
819 EXPECT_NEAR(E[2], (z / ze_m) * scaleE, 1e-9) << "Mismatch at iz=" << iz << " z=" << z;
820
821 // No transverse components on-axis
822 EXPECT_NEAR(E[0], 0.0, 1e-12);
823 EXPECT_NEAR(E[1], 0.0, 1e-12);
824 EXPECT_NEAR(B[0], 0.0, 1e-12);
825 EXPECT_NEAR(B[1], 0.0, 1e-12);
826 }
827}
828
829// ===========================================================================
830// Test: ZX orientation gives same results as XZ for identical physical field
831// ===========================================================================
832TEST_F(FM2DDynamicTest, XZvsZXConsistency) {
833 const double zb = 0.0, ze = 10.0;
834 const double rb = 0.0, re = 5.0;
835 const double freq = 100.0;
836 const int nz = 4, nr = 2;
837 const double Ez_val = 2.0;
838
839 std::string fnameXZ = writeXZFieldmap(tmpFile("xz2.map"), zb, ze, nz, rb, re, nr, freq, Ez_val);
840 std::string fnameZX = writeZXFieldmap(tmpFile("zx2.map"), zb, ze, nz, rb, re, nr, freq, Ez_val);
841
842 Fieldmap* fmXZ = Fieldmap::getFieldmap(fnameXZ);
843 Fieldmap* fmZX = Fieldmap::getFieldmap(fnameZX);
844 Fieldmap::readMap(fnameXZ);
845 Fieldmap::readMap(fnameZX);
846
847 ASSERT_NE(fmXZ, nullptr);
848 ASSERT_NE(fmZX, nullptr);
849
850 Vector_t<double, 3> R = {0.0, 0.0, 0.05};
851 Vector_t<double, 3> E_xz = {0.0, 0.0, 0.0};
852 Vector_t<double, 3> E_zx = {0.0, 0.0, 0.0};
853 Vector_t<double, 3> B_xz = {0.0, 0.0, 0.0};
854 Vector_t<double, 3> B_zx = {0.0, 0.0, 0.0};
855
856 fmXZ->getFieldstrength(R, E_xz, B_xz);
857 fmZX->getFieldstrength(R, E_zx, B_zx);
858
859 EXPECT_NEAR(E_xz[0], E_zx[0], 1e-10);
860 EXPECT_NEAR(E_xz[1], E_zx[1], 1e-10);
861 EXPECT_NEAR(E_xz[2], E_zx[2], 1e-10);
862
863 EXPECT_NEAR(B_xz[0], B_zx[0], 1e-10);
864 EXPECT_NEAR(B_xz[1], B_zx[1], 1e-10);
865 EXPECT_NEAR(B_xz[2], B_zx[2], 1e-10);
866}
867
868// ===========================================================================
869// Test: Er and Bt project correctly into Cartesian components (Dynamic)
870// ===========================================================================
871TEST_F(FM2DDynamicTest, FieldProjection) {
872 const double zb = 0.0, ze = 10.0;
873 const double rb = 0.0, re = 5.0;
874 const double freq = 100.0;
875 const int nz = 4, nr = 2;
876
877 // Define uniform fields
878 const double Ez_val = 1.0;
879 const double Er_val = 1.0;
880 const double Bt_val = 1.0;
881
882 std::string fname = writeXZFieldmap(
883 tmpFile("proj_dyn.map"), zb, ze, nz, rb, re, nr, freq, Ez_val, Er_val, Bt_val);
884
885 Fieldmap* fm = dynamic_cast<FM2DDynamic*>(Fieldmap::getFieldmap(fname));
886 ASSERT_NE(fm, nullptr);
887
888 Fieldmap::readMap(fname);
889
890 // 45 degrees in transverse plane
891 double r = 0.02;
892 double x = r / std::sqrt(2.0);
893 double y = r / std::sqrt(2.0);
894
895 Vector_t<double, 3> R = {x, y, 0.05};
896 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
897 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
898
899 fm->getFieldstrength(R, E, B);
900
901 double Ezmax = Ez_val; // uniform field → max = value
902 double scaleE = 1e6 / Ezmax;
903 double scaleB = Physics::mu_0 / Ezmax;
904
905 double expected_Er = (Er_val / std::sqrt(2.0)) * scaleE;
906 double expected_Ez = Ez_val * scaleE;
907 double expected_Bt = (Bt_val / std::sqrt(2.0)) * scaleB;
908
909 // --- Electric field (from Er) ---
910 EXPECT_NEAR(E[0], expected_Er, 1e-10);
911 EXPECT_NEAR(E[1], expected_Er, 1e-10);
912
913 // --- Longitudinal electric ---
914 EXPECT_NEAR(E[2], expected_Ez, 1e-10);
915
916 // --- Magnetic field (from Bt) ---
917 // Note the rotation!
918 EXPECT_NEAR(B[0], -expected_Bt, 1e-10); // -Bt * y/R
919 EXPECT_NEAR(B[1], expected_Bt, 1e-10); // +Bt * x/R
920
921 // No longitudinal magnetic component
922 EXPECT_NEAR(B[2], 0.0, 1e-10);
923}
924
925// ===========================================================================
926// Test: Accumulation semantics – E and B are accumulated, not overwritten
927// ===========================================================================
928TEST_F(FM2DDynamicTest, FieldAccumulation) {
929 const double zb = 0.0, ze = 10.0;
930 const double rb = 0.0, re = 5.0;
931 const double freq = 100.0;
932 const int nz = 4, nr = 2;
933
934 // Uniform fields
935 const double Ez_val = 1.0;
936 const double Er_val = 0.0;
937 const double Bt_val = 0.0;
938
939 std::string fname = writeXZFieldmap(
940 tmpFile("accum_dyn.map"), zb, ze, nz, rb, re, nr, freq, Ez_val, Er_val, Bt_val);
941
942 Fieldmap* fm = dynamic_cast<FM2DDynamic*>(Fieldmap::getFieldmap(fname));
943 ASSERT_NE(fm, nullptr);
944
945 Fieldmap::readMap(fname);
946
947 Vector_t<double, 3> R = {0.0, 0.0, 0.05};
948 Vector_t<double, 3> E = {0.0, 0.0, 5.0}; // Pre-existing Efields
949 Vector_t<double, 3> B = {0.0, 0.0, 3.0}; // Pre-existing Bfields
950
951 fm->getFieldstrength(R, E, B);
952
953 // --- Expected scaling ---
954 double Ezmax = Ez_val;
955 double scaleE = 1e6 / Ezmax;
956
957 // Only Ez contributes here
958 double expected_added_Ez = Ez_val * scaleE;
959
960 // --- Check accumulation ---
961 EXPECT_NEAR(E[0], 0.0, 1e-10);
962 EXPECT_NEAR(E[1], 0.0, 1e-10);
963 EXPECT_NEAR(E[2], 5.0 + expected_added_Ez, 1e-10);
964
965 // No magnetic contribution (Bt = 0)
966 EXPECT_NEAR(B[0], 0.0, 1e-10);
967 EXPECT_NEAR(B[1], 0.0, 1e-10);
968 EXPECT_NEAR(B[2], 3.0, 1e-10);
969}
ippl::Vector< T, Dim > Vector_t
const int nr
@ T2DDynamic
Definition Fieldmap.h:24
@ DX
Definition Fieldmap.h:54
TEST_F(FM2DDynamicTest, ParseXZOrientation)
void TearDown() override
std::filesystem::path tmpDir_
std::string tmpFile(const std::string &name) const
void SetUp() override
static void TearDownTestSuite()
static void SetUpTestSuite()
Abstract base class for all field maps. It acts as a factory for creating specific field map types ba...
Definition Fieldmap.h:62
virtual bool getFieldstrength(const Vector_t< double, 3 > &R, Vector_t< double, 3 > &E, Vector_t< double, 3 > &B) const =0
Get the field strength at a given point.
virtual void getInfo(Inform *msg)=0
Print info about the field map.
virtual void getFieldDimensions(double &zBegin, double &zEnd) const =0
Get the longitudinal dimensions of the field.
virtual bool getFieldDerivative(const Vector_t< double, 3 > &R, Vector_t< double, 3 > &E, Vector_t< double, 3 > &B, const DiffDirection &dir) const =0
Get the field derivative with respect to a direction.
MapType getType()
Definition Fieldmap.h:206
static void freeMap(std::string Filename)
Decrease reference count or delete field map if unused.
Definition Fieldmap.cpp:311
static void readMap(std::string Filename)
Trigger the actual reading of the field map data.
Definition Fieldmap.cpp:301
constexpr double two_pi
The value of.
Definition Physics.h:40
constexpr double mu_0
The permeability of vacuum in Vs/Am.
Definition Physics.h:63
constexpr double MHz2Hz
Definition Units.h:113
constexpr double cm2m
Definition Units.h:35