OPALX (Object Oriented Parallel Accelerator Library for Exascal) master (dc2a29eed580)
OPALX
Loading...
Searching...
No Matches
TestFM2DMagnetoStatic.cpp
Go to the documentation of this file.
1
19#include <gtest/gtest.h>
20
22#include "Fields/Fieldmap.h"
23#include "Ippl.h"
24#include "Physics/Units.h"
26
27#include <cmath>
28#include <filesystem>
29#include <fstream>
30#include <sstream>
31#include <string>
32#include <vector>
33
34namespace {
35
36 // ---------------------------------------------------------------------------
37 // Helper: write a minimal 2D magnetostatic fieldmap file in XZ orientation.
38 //
39 // Grid: nr+1 radial points from rbegin..rend (cm)
40 // nz+1 longitudinal points from zbegin..zend (cm)
41 //
42 // NOTE: normalize_m defaults to true in the Fieldmap base class when the
43 // header has only two strings ("2DMagnetoStatic XZ"). The three-string
44 // form ("2DMagnetoStatic XZ TRUE/FALSE") is broken in the current parser
45 // (all three output references alias the same variable, clobbering the
46 // orientation). Therefore this helper always writes a two-string header
47 // and normalization is always active.
48 //
49 // Default: uniform Bz = 1.0 T, Br = 0.0 T (unless overridden)
50 // ---------------------------------------------------------------------------
51 std::string writeXZFieldmap(
52 const std::string& path, double zbegin_cm, double zend_cm, int nz, double rbegin_cm,
53 double rend_cm, int nr, double uniformBz = 1.0, double uniformBr = 0.0) {
54 std::ofstream f(path);
55 f << "2DMagnetoStatic XZ\n";
56 f << zbegin_cm << " " << zend_cm << " " << nz << "\n";
57 f << rbegin_cm << " " << rend_cm << " " << nr << "\n";
58 // XZ: outer loop r (nr+1), inner loop z (nz+1), columns: Bz Br
59 for (int jr = 0; jr <= nr; ++jr) {
60 for (int iz = 0; iz <= nz; ++iz) {
61 f << uniformBz << " " << uniformBr << "\n";
62 }
63 }
64 f.close();
65 return path;
66 }
67
68 // ---------------------------------------------------------------------------
69 // Helper: write a minimal 2D magnetostatic fieldmap file in ZX orientation.
70 // ---------------------------------------------------------------------------
71 std::string writeZXFieldmap(
72 const std::string& path, double zbegin_cm, double zend_cm, int nz, double rbegin_cm,
73 double rend_cm, int nr, double uniformBz = 1.0, double uniformBr = 0.0) {
74 std::ofstream f(path);
75 f << "2DMagnetoStatic ZX\n";
76 f << rbegin_cm << " " << rend_cm << " " << nr << "\n";
77 f << zbegin_cm << " " << zend_cm << " " << nz << "\n";
78 // ZX: outer loop z (nz+1), inner loop r (nr+1), columns: Br Bz
79 for (int iz = 0; iz <= nz; ++iz) {
80 for (int jr = 0; jr <= nr; ++jr) {
81 f << uniformBr << " " << uniformBz << "\n";
82 }
83 }
84 f.close();
85 return path;
86 }
87
88 // ---------------------------------------------------------------------------
89 // Helper: write a fieldmap with spatially varying field for interpolation tests.
90 // Uses XZ orientation. Field: Bz(r,z) = z [T], Br(r,z) = r [T]
91 // where r and z are in metres.
92 // ---------------------------------------------------------------------------
93 std::string writeVaryingXZFieldmap(
94 const std::string& path, double zbegin_cm, double zend_cm, int nz, double rbegin_cm,
95 double rend_cm, int nr) {
96 const double hz_cm = (zend_cm - zbegin_cm) / nz;
97 const double hr_cm = (rend_cm - rbegin_cm) / nr;
98
99 std::ofstream f(path);
100 f << "2DMagnetoStatic XZ\n";
101 f << zbegin_cm << " " << zend_cm << " " << nz << "\n";
102 f << rbegin_cm << " " << rend_cm << " " << nr << "\n";
103 for (int jr = 0; jr <= nr; ++jr) {
104 double r_m = (rbegin_cm + jr * hr_cm) * Units::cm2m;
105 for (int iz = 0; iz <= nz; ++iz) {
106 double z_m = (zbegin_cm + iz * hz_cm) * Units::cm2m;
107 // Bz = z, Br = r
108 f << z_m << " " << r_m << "\n";
109 }
110 }
111 f.close();
112 return path;
113 }
114
115} // anonymous namespace
116
117// ===========================================================================
118// Test fixture
119// ===========================================================================
120class FM2DMagnetoStaticTest : public ::testing::Test {
121protected:
122 static void SetUpTestSuite() {
123 int argc = 0;
124 char** argv = nullptr;
125 ippl::initialize(argc, argv);
126 }
127
128 static void TearDownTestSuite() {
129 Fieldmap::clearDictionary();
130 ippl::finalize();
131 }
132
133 void SetUp() override {
134 tmpDir_ = std::filesystem::temp_directory_path() / "opalx_fm2d_test";
135 std::filesystem::create_directories(tmpDir_);
136 }
137
138 void TearDown() override {
139 Fieldmap::clearDictionary();
140 std::filesystem::remove_all(tmpDir_);
141 }
142
144 std::string tmpFile(const std::string& name) const { return (tmpDir_ / name).string(); }
145
146 std::filesystem::path tmpDir_;
147};
148
149// ===========================================================================
150// Test: Parse XZ orientation and verify field dimensions
151// ===========================================================================
152TEST_F(FM2DMagnetoStaticTest, ParseXZOrientation) {
153 const double zb = 0.0, ze = 10.0; // cm
154 const double rb = 0.0, re = 5.0; // cm
155 const int nz = 4, nr = 2;
156
157 std::string fname = writeXZFieldmap(tmpFile("xz.map"), zb, ze, nz, rb, re, nr);
158
159 Fieldmap* fm = Fieldmap::getFieldmap(fname);
160 ASSERT_NE(fm, nullptr);
161 EXPECT_EQ(fm->getType(), T2DMagnetoStatic);
162
163 Fieldmap::readMap(fname);
164
165 double zBegin, zEnd;
166 fm->getFieldDimensions(zBegin, zEnd);
167 EXPECT_NEAR(zBegin, zb * Units::cm2m, 1e-12);
168 EXPECT_NEAR(zEnd, ze * Units::cm2m, 1e-12);
169}
170
171// ===========================================================================
172// Test: Parse ZX orientation and verify field dimensions
173// ===========================================================================
174TEST_F(FM2DMagnetoStaticTest, ParseZXOrientation) {
175 const double zb = -5.0, ze = 15.0; // cm
176 const double rb = 0.0, re = 3.0; // cm
177 const int nz = 3, nr = 2;
178
179 std::string fname = writeZXFieldmap(tmpFile("zx.map"), zb, ze, nz, rb, re, nr);
180
181 Fieldmap* fm = Fieldmap::getFieldmap(fname);
182 ASSERT_NE(fm, nullptr);
183
184 Fieldmap::readMap(fname);
185
186 double zBegin, zEnd;
187 fm->getFieldDimensions(zBegin, zEnd);
188 EXPECT_NEAR(zBegin, zb * Units::cm2m, 1e-12);
189 EXPECT_NEAR(zEnd, ze * Units::cm2m, 1e-12);
190}
191
192// ===========================================================================
193// Test: Uniform field – getFieldstrength returns correct value at grid centre
194// ===========================================================================
195TEST_F(FM2DMagnetoStaticTest, UniformFieldStrength) {
196 const double zb = 0.0, ze = 20.0;
197 const double rb = 0.0, re = 5.0;
198 const int nz = 4, nr = 2;
199 // Use Bz_val = 1.0 so that normalization (Bz /= Bzmax) is a no-op.
200 const double Bz_val = 1.0, Br_val = 0.0;
201
202 std::string fname = writeXZFieldmap(tmpFile("uni.map"), zb, ze, nz, rb, re, nr, Bz_val, Br_val);
203
204 Fieldmap* fm = Fieldmap::getFieldmap(fname);
205 Fieldmap::readMap(fname);
206
207 // Query at centre of field (on-axis)
208 Vector_t<double, 3> R = {0.0, 0.0, 0.05}; // z=5cm=0.05m, on axis
209 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
210 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
211
212 bool outside = fm->getFieldstrength(R, E, B);
213 EXPECT_FALSE(outside);
214 EXPECT_NEAR(B[2], Bz_val, 1e-10);
215 EXPECT_NEAR(B[0], 0.0, 1e-10);
216 EXPECT_NEAR(B[1], 0.0, 1e-10);
217}
218
219// ===========================================================================
220// Test: Field outside z range returns without modifying B (early return
221// from computeField when indexz out of bounds)
222// ===========================================================================
224 const double zb = 0.0, ze = 10.0;
225 const double rb = 0.0, re = 5.0;
226 const int nz = 4, nr = 2;
227
228 std::string fname = writeXZFieldmap(tmpFile("oz.map"), zb, ze, nz, rb, re, nr);
229 Fieldmap* fm = Fieldmap::getFieldmap(fname);
230 Fieldmap::readMap(fname);
231
232 // Before z start
233 {
234 Vector_t<double, 3> R = {0.0, 0.0, -0.01};
235 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
236 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
237 fm->getFieldstrength(R, E, B);
238 // B should remain 0 – computeField returns without writing
239 EXPECT_NEAR(B[0], 0.0, 1e-15);
240 EXPECT_NEAR(B[1], 0.0, 1e-15);
241 EXPECT_NEAR(B[2], 0.0, 1e-15);
242 }
243
244 // After z end
245 {
246 Vector_t<double, 3> R = {0.0, 0.0, 0.20}; // 20cm > 10cm
247 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
248 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
249 fm->getFieldstrength(R, E, B);
250 EXPECT_NEAR(B[0], 0.0, 1e-15);
251 EXPECT_NEAR(B[1], 0.0, 1e-15);
252 EXPECT_NEAR(B[2], 0.0, 1e-15);
253 }
254}
255
256// ===========================================================================
257// Test: Field outside radial range returns true (outside flag)
258// ===========================================================================
259TEST_F(FM2DMagnetoStaticTest, OutsideRadialRange) {
260 const double zb = 0.0, ze = 10.0;
261 const double rb = 0.0, re = 2.0;
262 const int nz = 4, nr = 2;
263
264 std::string fname = writeXZFieldmap(tmpFile("or.map"), zb, ze, nz, rb, re, nr);
265 Fieldmap* fm = Fieldmap::getFieldmap(fname);
266 Fieldmap::readMap(fname);
267
268 // r = 0.05 m = 5 cm > re = 2 cm
269 Vector_t<double, 3> R = {0.05, 0.0, 0.05};
270 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
271 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
272
273 bool outside = fm->getFieldstrength(R, E, B);
274 EXPECT_TRUE(outside);
275}
276
277// ===========================================================================
278// Test: isInside() helper
279// ===========================================================================
281 const double zb = 0.0, ze = 10.0;
282 const double rb = 0.0, re = 5.0;
283 const int nz = 4, nr = 2;
284
285 std::string fname = writeXZFieldmap(tmpFile("ins.map"), zb, ze, nz, rb, re, nr);
286 auto* fm = dynamic_cast<FM2DMagnetoStatic*>(Fieldmap::getFieldmap(fname));
287 ASSERT_NE(fm, nullptr);
288
289 // Inside
290 EXPECT_TRUE(fm->isInside({0.0, 0.0, 0.05}));
291 // On zbegin boundary
292 EXPECT_TRUE(fm->isInside({0.0, 0.0, 0.0}));
293 // Just before zend (zend = 0.10 m)
294 EXPECT_TRUE(fm->isInside({0.0, 0.0, 0.099}));
295 // At zend – should be outside (< zend, not <=)
296 EXPECT_FALSE(fm->isInside({0.0, 0.0, 0.10}));
297 // Before zbegin
298 EXPECT_FALSE(fm->isInside({0.0, 0.0, -0.01}));
299 // Outside radial
300 EXPECT_FALSE(fm->isInside({0.10, 0.0, 0.05}));
301}
302
303// ===========================================================================
304// Test: swap() toggles the swap state
305// ===========================================================================
307 const double zb = 0.0, ze = 10.0;
308 const double rb = 0.0, re = 5.0;
309 const int nz = 4, nr = 2;
310
311 std::string fname = writeXZFieldmap(tmpFile("sw.map"), zb, ze, nz, rb, re, nr);
312 auto* fm = dynamic_cast<FM2DMagnetoStatic*>(Fieldmap::getFieldmap(fname));
313 ASSERT_NE(fm, nullptr);
314 // swap_m starts false for XZ
315 fm->swap(); // now true
316 fm->swap(); // back to false
317 // No crash – just verify the toggle doesn't explode
318}
319
320// ===========================================================================
321// Test: Normalization flag
322// ===========================================================================
324 // normalize_m defaults to true. With a uniform field of Bz_val = 3.0,
325 // Bzmax = 3.0, so after normalization every Bz value becomes 1.0.
326 const double zb = 0.0, ze = 10.0;
327 const double rb = 0.0, re = 5.0;
328 const int nz = 4, nr = 2;
329 const double Bz_val = 3.0;
330
331 std::string fname = writeXZFieldmap(tmpFile("norm.map"), zb, ze, nz, rb, re, nr, Bz_val);
332
333 Fieldmap* fm = Fieldmap::getFieldmap(fname);
334 Fieldmap::readMap(fname);
335
336 Vector_t<double, 3> R = {0.0, 0.0, 0.05};
337 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
338 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
339 fm->getFieldstrength(R, E, B);
340
341 // All Bz values are the same (uniform), so Bzmax = Bz_val.
342 // Normalized: every Bz /= Bzmax => 1.0
343 EXPECT_NEAR(B[2], 1.0, 1e-10);
344}
345
346// ===========================================================================
347// Test: Bilinear interpolation with spatially varying field
348// ===========================================================================
349TEST_F(FM2DMagnetoStaticTest, BilinearInterpolation) {
350 // Field before normalization: Bz(r,z) = z [T], Br(r,z) = r [T]
351 // Normalization (always active) divides all values by Bzmax.
352 // On-axis z values range from zbegin_m=0 to zend_m, so Bzmax = zend_m.
353 const double zb = 0.0, ze = 10.0; // cm
354 const double rb = 0.0, re = 5.0; // cm
355 const int nz = 4, nr = 4;
356 const double zend_m = ze * Units::cm2m; // 0.10 m
357 const double Bzmax = zend_m; // normalization factor
358
359 std::string fname = writeVaryingXZFieldmap(tmpFile("var.map"), zb, ze, nz, rb, re, nr);
360
361 Fieldmap* fm = Fieldmap::getFieldmap(fname);
362 Fieldmap::readMap(fname);
363
364 // Test at a mid-grid point: z = 5cm = 0.05m, on-axis r = 0
365 {
366 Vector_t<double, 3> R = {0.0, 0.0, 0.05};
367 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
368 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
369 fm->getFieldstrength(R, E, B);
370 EXPECT_NEAR(B[2], 0.05 / Bzmax, 1e-10);
371 EXPECT_NEAR(B[0], 0.0, 1e-10);
372 EXPECT_NEAR(B[1], 0.0, 1e-10);
373 }
374
375 // Test off-axis: r = 2.5cm = 0.025m along x, z = 5cm = 0.05m
376 {
377 double r_m = 0.025;
378 Vector_t<double, 3> R = {r_m, 0.0, 0.05};
379 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
380 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
381 fm->getFieldstrength(R, E, B);
382 EXPECT_NEAR(B[2], 0.05 / Bzmax, 1e-10);
383 // Br(r=0.025) = r/Bzmax, projected onto x: Br*x/R = r_m/Bzmax
384 EXPECT_NEAR(B[0], r_m / Bzmax, 1e-10);
385 EXPECT_NEAR(B[1], 0.0, 1e-10);
386 }
387
388 // Test off-axis along y
389 {
390 double r_m = 0.025;
391 Vector_t<double, 3> R = {0.0, r_m, 0.05};
392 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
393 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
394 fm->getFieldstrength(R, E, B);
395 EXPECT_NEAR(B[2], 0.05 / Bzmax, 1e-10);
396 EXPECT_NEAR(B[0], 0.0, 1e-10);
397 EXPECT_NEAR(B[1], r_m / Bzmax, 1e-10);
398 }
399
400 // Test at a non-grid z value: z = 3cm = 0.03m
401 {
402 Vector_t<double, 3> R = {0.0, 0.0, 0.03};
403 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
404 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
405 fm->getFieldstrength(R, E, B);
406 EXPECT_NEAR(B[2], 0.03 / Bzmax, 1e-10);
407 }
408}
409
410// ===========================================================================
411// Test: computeField static function directly with host views
412// ===========================================================================
413TEST_F(FM2DMagnetoStaticTest, ComputeFieldStatic) {
414 // Build a small grid manually: 3 z-points, 2 r-points
415 // Bz = 1.0 everywhere, Br = 0.5 everywhere
416 const int npz = 3, npr = 2;
417 const double hz = 0.05, hr = 0.025;
418 const double zbegin = 0.0;
419
420 Kokkos::View<double*, Kokkos::HostSpace> Bz("Bz", npz * npr);
421 Kokkos::View<double*, Kokkos::HostSpace> Br("Br", npz * npr);
422
423 for (int j = 0; j < npr; ++j)
424 for (int i = 0; i < npz; ++i) {
425 Bz(j * npz + i) = 1.0;
426 Br(j * npz + i) = 0.5;
427 }
428
429 // On-axis evaluation: R = (0, 0, 0.025)
430 {
431 Vector_t<double, 3> R = {0.0, 0.0, 0.025};
432 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
433 FM2DMagnetoStatic::computeField(R, B, Bz, Br, hr, hz, zbegin, npr, npz);
434 EXPECT_NEAR(B[2], 1.0, 1e-10);
435 // On-axis RR=0, so Br contribution skipped
436 EXPECT_NEAR(B[0], 0.0, 1e-10);
437 }
438
439 // Off-axis: R = (0.01, 0, 0.025)
440 {
441 Vector_t<double, 3> R = {0.01, 0.0, 0.025};
442 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
443 FM2DMagnetoStatic::computeField(R, B, Bz, Br, hr, hz, zbegin, npr, npz);
444 EXPECT_NEAR(B[2], 1.0, 1e-10);
445 EXPECT_NEAR(B[0], 0.5, 1e-10); // Br * x/R = 0.5 * 1.0
446 EXPECT_NEAR(B[1], 0.0, 1e-10);
447 }
448
449 // Outside z range (before): should return false with B unchanged
450 {
451 Vector_t<double, 3> R = {0.0, 0.0, -0.01};
452 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
453 FM2DMagnetoStatic::computeField(R, B, Bz, Br, hr, hz, zbegin, npr, npz);
454 EXPECT_NEAR(B[2], 0.0, 1e-15);
455 }
456
457 // Outside r range: should return true
458 {
459 Vector_t<double, 3> R = {0.05, 0.0, 0.025}; // r = 0.05 > hr*(npr-1)=0.025
460 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
461 FM2DMagnetoStatic::computeField(R, B, Bz, Br, hr, hz, zbegin, npr, npz);
462 EXPECT_NEAR(B[0], 0.0, 1e-15);
463 EXPECT_NEAR(B[1], 0.0, 1e-15);
464 EXPECT_NEAR(B[2], 0.0, 1e-15);
465 }
466}
467
468// ===========================================================================
469// Test: getFieldDerivative throws
470// ===========================================================================
471TEST_F(FM2DMagnetoStaticTest, GetFieldDerivativeThrows) {
472 const double zb = 0.0, ze = 10.0;
473 const double rb = 0.0, re = 5.0;
474 const int nz = 4, nr = 2;
475
476 std::string fname = writeXZFieldmap(tmpFile("deriv.map"), zb, ze, nz, rb, re, nr);
477 Fieldmap* fm = Fieldmap::getFieldmap(fname);
478
479 Vector_t<double, 3> R = {0.0, 0.0, 0.05};
480 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
481 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
482
483 EXPECT_THROW(fm->getFieldDerivative(R, E, B, DX), GeneralOpalException);
484}
485
486// ===========================================================================
487// Test: getFieldDimensions 6-arg returns the full cylindrical bounding box
488// ===========================================================================
489TEST_F(FM2DMagnetoStaticTest, GetFieldDimensions6ArgReturnsBoundingBox) {
490 const double zb = 0.0, ze = 10.0;
491 const double rb = 0.0, re = 5.0;
492 const int nz = 4, nr = 2;
493
494 std::string fname = writeXZFieldmap(tmpFile("dim6.map"), zb, ze, nz, rb, re, nr);
495 Fieldmap* fm = Fieldmap::getFieldmap(fname);
496
497 double xIni = 0.0, xFinal = 0.0, yIni = 0.0, yFinal = 0.0, zIni = 0.0, zFinal = 0.0;
498 fm->getFieldDimensions(xIni, xFinal, yIni, yFinal, zIni, zFinal);
499
500 EXPECT_NEAR(xIni, -re * Units::cm2m, 1e-12);
501 EXPECT_NEAR(xFinal, re * Units::cm2m, 1e-12);
502 EXPECT_NEAR(yIni, -re * Units::cm2m, 1e-12);
503 EXPECT_NEAR(yFinal, re * Units::cm2m, 1e-12);
504 EXPECT_NEAR(zIni, zb * Units::cm2m, 1e-12);
505 EXPECT_NEAR(zFinal, ze * Units::cm2m, 1e-12);
506}
507
508// ===========================================================================
509// Test: getFrequency / setFrequency throw
510// ===========================================================================
511TEST_F(FM2DMagnetoStaticTest, FrequencyThrows) {
512 const double zb = 0.0, ze = 10.0;
513 const double rb = 0.0, re = 5.0;
514 const int nz = 4, nr = 2;
515
516 std::string fname = writeXZFieldmap(tmpFile("freq.map"), zb, ze, nz, rb, re, nr);
517 Fieldmap* fm = Fieldmap::getFieldmap(fname);
518
519 EXPECT_THROW(fm->getFrequency(), GeneralOpalException);
520 EXPECT_THROW(fm->setFrequency(100.0), GeneralOpalException);
521}
522
523// ===========================================================================
524// Test: getInfo does not crash
525// ===========================================================================
527 const double zb = 0.0, ze = 10.0;
528 const double rb = 0.0, re = 5.0;
529 const int nz = 4, nr = 2;
530
531 std::string fname = writeXZFieldmap(tmpFile("info.map"), zb, ze, nz, rb, re, nr);
532 Fieldmap* fm = Fieldmap::getFieldmap(fname);
533
534 Inform msg("test");
535 EXPECT_NO_THROW(fm->getInfo(&msg));
536}
537
538// ===========================================================================
539// Test: Missing file
540// ===========================================================================
542 std::string fname = tmpFile("nonexistent.map");
543 EXPECT_THROW(Fieldmap::getFieldmap(fname), GeneralOpalException);
544}
545
546// ===========================================================================
547// Test: Fieldmap dictionary caching – second getFieldmap returns same pointer
548// ===========================================================================
549TEST_F(FM2DMagnetoStaticTest, DictionaryCaching) {
550 const double zb = 0.0, ze = 10.0;
551 const double rb = 0.0, re = 5.0;
552 const int nz = 4, nr = 2;
553
554 std::string fname = writeXZFieldmap(tmpFile("cache.map"), zb, ze, nz, rb, re, nr);
555 Fieldmap* fm1 = Fieldmap::getFieldmap(fname);
556 Fieldmap* fm2 = Fieldmap::getFieldmap(fname);
557 EXPECT_EQ(fm1, fm2);
558}
559
560// ===========================================================================
561// Test: readMap / freeMap cycle – reading after free should work
562// ===========================================================================
564 // Use Bz_val = 1.0 so normalization is a no-op (uniform: Bzmax = 1.0)
565 const double zb = 0.0, ze = 10.0;
566 const double rb = 0.0, re = 5.0;
567 const int nz = 4, nr = 2;
568 const double Bz_val = 1.0;
569
570 std::string fname = writeXZFieldmap(tmpFile("cycle.map"), zb, ze, nz, rb, re, nr, Bz_val);
571
572 Fieldmap* fm = Fieldmap::getFieldmap(fname);
573 Fieldmap::readMap(fname);
574
575 // Verify field works
576 Vector_t<double, 3> R = {0.0, 0.0, 0.05};
577 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
578 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
579 fm->getFieldstrength(R, E, B);
580 EXPECT_NEAR(B[2], Bz_val, 1e-10);
581
582 // Free and re-read
583 fm->freeMap();
584
585 fm->readMap();
586 B = {0.0, 0.0, 0.0};
587 fm->getFieldstrength(R, E, B);
588 EXPECT_NEAR(B[2], Bz_val, 1e-10);
589}
590
591// ===========================================================================
592// Test: On-axis at grid points matches exact data
593// ===========================================================================
594TEST_F(FM2DMagnetoStaticTest, GridPointAccuracy) {
595 // Field before normalization: Bz(r,z) = z, Br = r
596 // With normalization: Bzmax = zend_m, so normalised Bz = z / zend_m
597 const double zb = 0.0, ze = 10.0;
598 const double rb = 0.0, re = 5.0;
599 const int nz = 10, nr = 5;
600 const double zend_m = ze * Units::cm2m;
601 const double Bzmax = zend_m;
602
603 std::string fname = writeVaryingXZFieldmap(tmpFile("grid.map"), zb, ze, nz, rb, re, nr);
604 Fieldmap* fm = Fieldmap::getFieldmap(fname);
605 Fieldmap::readMap(fname);
606
607 const double hz_m = (ze - zb) * Units::cm2m / nz;
608
609 // Check at each z grid point on axis
610 for (int iz = 0; iz < nz; ++iz) {
611 double z = zb * Units::cm2m + iz * hz_m;
612 Vector_t<double, 3> R = {0.0, 0.0, z};
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 fm->getFieldstrength(R, E, B);
617 EXPECT_NEAR(B[2], z / Bzmax, 1e-10) << "Mismatch at iz=" << iz << " z=" << z;
618 }
619}
620
621// ===========================================================================
622// Test: ZX orientation gives same results as XZ for identical physical field
623// ===========================================================================
624TEST_F(FM2DMagnetoStaticTest, XZvsZXConsistency) {
625 const double zb = 0.0, ze = 10.0;
626 const double rb = 0.0, re = 5.0;
627 const int nz = 4, nr = 2;
628 const double Bz_val = 2.0;
629
630 std::string fnameXZ = writeXZFieldmap(tmpFile("xz2.map"), zb, ze, nz, rb, re, nr, Bz_val);
631 std::string fnameZX = writeZXFieldmap(tmpFile("zx2.map"), zb, ze, nz, rb, re, nr, Bz_val);
632
633 Fieldmap* fmXZ = Fieldmap::getFieldmap(fnameXZ);
634 Fieldmap* fmZX = Fieldmap::getFieldmap(fnameZX);
635 Fieldmap::readMap(fnameXZ);
636 Fieldmap::readMap(fnameZX);
637
638 Vector_t<double, 3> R = {0.0, 0.0, 0.05};
639 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
640 Vector_t<double, 3> B_xz = {0.0, 0.0, 0.0};
641 Vector_t<double, 3> B_zx = {0.0, 0.0, 0.0};
642
643 fmXZ->getFieldstrength(R, E, B_xz);
644 fmZX->getFieldstrength(R, E, B_zx);
645
646 EXPECT_NEAR(B_xz[0], B_zx[0], 1e-10);
647 EXPECT_NEAR(B_xz[1], B_zx[1], 1e-10);
648 EXPECT_NEAR(B_xz[2], B_zx[2], 1e-10);
649}
650
651// ===========================================================================
652// Test: Field with both Br and Bz components projects correctly in x-y
653// ===========================================================================
654TEST_F(FM2DMagnetoStaticTest, RadialFieldProjection) {
655 // Uniform Bz = 1.0 (needed to avoid division by zero in normalization),
656 // Br = 1.0. After normalization: Bzmax = 1.0, so values unchanged.
657 const double zb = 0.0, ze = 10.0;
658 const double rb = 0.0, re = 5.0;
659 const int nz = 4, nr = 2;
660
661 std::string fname = writeXZFieldmap(
662 tmpFile("proj.map"), zb, ze, nz, rb, re, nr,
663 /*Bz=*/1.0, /*Br=*/1.0);
664 Fieldmap* fm = Fieldmap::getFieldmap(fname);
665 Fieldmap::readMap(fname);
666
667 // At 45 degrees in x-y plane
668 double r = 0.02; // 2 cm
669 double x = r / std::sqrt(2.0);
670 double y = r / std::sqrt(2.0);
671 Vector_t<double, 3> R = {x, y, 0.05};
672 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
673 Vector_t<double, 3> B = {0.0, 0.0, 0.0};
674
675 fm->getFieldstrength(R, E, B);
676 // Br = 1.0, projected: Bx = Br * x/R, By = Br * y/R
677 double expected_Bxy = 1.0 / std::sqrt(2.0);
678 EXPECT_NEAR(B[0], expected_Bxy, 1e-10);
679 EXPECT_NEAR(B[1], expected_Bxy, 1e-10);
680 EXPECT_NEAR(B[2], 1.0, 1e-10);
681}
682
683// ===========================================================================
684// Test: Accumulation semantics – B is accumulated, not overwritten
685// ===========================================================================
686TEST_F(FM2DMagnetoStaticTest, FieldAccumulation) {
687 const double zb = 0.0, ze = 10.0;
688 const double rb = 0.0, re = 5.0;
689 const int nz = 4, nr = 2;
690 const double Bz_val = 1.0;
691
692 std::string fname = writeXZFieldmap(tmpFile("accum.map"), zb, ze, nz, rb, re, nr, Bz_val);
693 Fieldmap* fm = Fieldmap::getFieldmap(fname);
694 Fieldmap::readMap(fname);
695
696 Vector_t<double, 3> R = {0.0, 0.0, 0.05};
697 Vector_t<double, 3> E = {0.0, 0.0, 0.0};
698 Vector_t<double, 3> B = {0.0, 0.0, 5.0}; // pre-existing B
699
700 fm->getFieldstrength(R, E, B);
701 // Bz should be 5.0 (initial) + 1.0 (field) = 6.0
702 EXPECT_NEAR(B[2], 6.0, 1e-10);
703}
ippl::Vector< T, Dim > Vector_t
const int nr
@ T2DMagnetoStatic
Definition Fieldmap.h:28
@ DX
Definition Fieldmap.h:54
TEST_F(FM2DMagnetoStaticTest, ParseXZOrientation)
std::string tmpFile(const std::string &name) const
Helper to build a full path inside the temp directory.
std::filesystem::path tmpDir_
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
virtual void setFrequency(double freq)=0
Set the frequency.
virtual double getFrequency() const =0
Get the frequency.
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 cm2m
Definition Units.h:35