From 4b9c2db4e016d3e0fe239e8695f4d03e75a0918e Mon Sep 17 00:00:00 2001 From: CharlesCNorton Date: Sun, 6 Sep 2026 17:25:14 -0400 Subject: [PATCH 1/2] Say why the lasym cos halves are not compared in the vacuum tests --- .../free_boundary/laplace_solver/laplace_solver_test.cc | 6 ++---- .../singular_integrals/singular_integrals_test.cc | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/laplace_solver/laplace_solver_test.cc b/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/laplace_solver/laplace_solver_test.cc index 845c9c392..0ce9c2587 100644 --- a/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/laplace_solver/laplace_solver_test.cc +++ b/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/laplace_solver/laplace_solver_test.cc @@ -137,8 +137,7 @@ TEST_P(FourPTest, CheckFourP) { static_cast(vac1n_fourp["grpmn"][m][nf - n][k][l]) - grpmn_sin_singular_negn; - // TODO(jons): for lasym, need cos-part of grpmn from - // educational_VMEC + // Only the sin part; educational_VMEC dumps no cos part of grpmn. EXPECT_TRUE(IsCloseRelAbs(grpmn_sin_reference_posn, grpmn_sin_regular_posn, tolerance)); EXPECT_TRUE(IsCloseRelAbs(grpmn_sin_reference_negn, @@ -309,8 +308,7 @@ TEST_P(FourIAccumulateGrpmnTest, CheckFourIAccumulateGrpmn) { scale_to_match_fortran_regular * ls.grpmn_sin[idx_m_negn * numLocal + klRel]; - // TODO(jons): for lasym, need cos-part of grpmn from - // educational_VMEC + // Only the sin part; educational_VMEC dumps no cos part of grpmn. EXPECT_TRUE(IsCloseRelAbs(vac1n_fourp["grpmn"][m][nf + n][k][l], grpmn_sin_regular_posn, tolerance)); EXPECT_TRUE(IsCloseRelAbs(vac1n_fourp["grpmn"][m][nf - n][k][l], diff --git a/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/singular_integrals/singular_integrals_test.cc b/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/singular_integrals/singular_integrals_test.cc index 04441ee78..8470d5c8c 100644 --- a/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/singular_integrals/singular_integrals_test.cc +++ b/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/singular_integrals/singular_integrals_test.cc @@ -205,8 +205,7 @@ TEST_P(AnalytTest, CheckAnalyt) { // cmns in Fortran has alp (= 2 pi / nfp) in it; VMEC++ does not const double scale_to_match_fortran = 2.0 * M_PI / s.nfp; - // TODO(jons): for lasym, need cos-part of grpmn from - // educational_VMEC + // Only the sin part; educational_VMEC dumps no cos part of grpmn. EXPECT_TRUE( IsCloseRelAbs(vac1n_analyt["grpmn"][m][nf + n][k][l], scale_to_match_fortran * @@ -233,7 +232,7 @@ TEST_P(AnalytTest, CheckAnalyt) { const double scale_to_match_fortran = 2.0 * M_PI / s.nfp * 4.0 * M_PI * M_PI; - // TODO(jons): for lasym, need cos-part of bvec from educational_VMEC + // Only the sin part; educational_VMEC dumps no cos part of bvec. // Fortran order along n in bvec: -nf, -nf+1, ..., -1, 0, 1, ..., nf-1, // nf From 64579a6279d834e91b6745f2044230c778782d27 Mon Sep 17 00:00:00 2001 From: CharlesCNorton Date: Mon, 7 Sep 2026 09:53:37 -0400 Subject: [PATCH 2/2] Cover the lasym cos part of the vacuum grpmn and bvec --- .../free_boundary/laplace_solver/BUILD.bazel | 2 + .../laplace_solver/laplace_solver_test.cc | 70 +++++++++++++++++-- .../singular_integrals/BUILD.bazel | 2 + .../singular_integrals_test.cc | 39 +++++++++-- .../test_data/BUILD.bazel | 6 ++ ...0015_000053_01.cth_like_free_bdy_asym.json | 3 + ...0015_000054_01.cth_like_free_bdy_asym.json | 3 + ...0015_000053_01.cth_like_free_bdy_asym.json | 3 + ...0015_000053_01.cth_like_free_bdy_asym.json | 3 + 9 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/cth_like_free_bdy_asym/vac1n_analyt/vac1n_analyt_00015_000053_01.cth_like_free_bdy_asym.json create mode 100644 src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/cth_like_free_bdy_asym/vac1n_analyt/vac1n_analyt_00015_000054_01.cth_like_free_bdy_asym.json create mode 100644 src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/cth_like_free_bdy_asym/vac1n_fouri/vac1n_fouri_00015_000053_01.cth_like_free_bdy_asym.json create mode 100644 src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/cth_like_free_bdy_asym/vac1n_fourp/vac1n_fourp_00015_000053_01.cth_like_free_bdy_asym.json diff --git a/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/laplace_solver/BUILD.bazel b/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/laplace_solver/BUILD.bazel index 433ea4c97..f690e3f36 100644 --- a/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/laplace_solver/BUILD.bazel +++ b/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/laplace_solver/BUILD.bazel @@ -6,7 +6,9 @@ cc_test( srcs = ["laplace_solver_test.cc"], data = [ "//vmecpp/test_data:cth_like_free_bdy", + "//vmecpp/test_data:cth_like_free_bdy_asym", "//vmecpp_large_cpp_tests/test_data:cth_like_free_bdy", + "//vmecpp_large_cpp_tests/test_data:cth_like_free_bdy_asym", ], deps = [ "//vmecpp/free_boundary/laplace_solver", diff --git a/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/laplace_solver/laplace_solver_test.cc b/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/laplace_solver/laplace_solver_test.cc index 0ce9c2587..4ae4d4bf3 100644 --- a/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/laplace_solver/laplace_solver_test.cc +++ b/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/laplace_solver/laplace_solver_test.cc @@ -137,11 +137,44 @@ TEST_P(FourPTest, CheckFourP) { static_cast(vac1n_fourp["grpmn"][m][nf - n][k][l]) - grpmn_sin_singular_negn; - // Only the sin part; educational_VMEC dumps no cos part of grpmn. EXPECT_TRUE(IsCloseRelAbs(grpmn_sin_reference_posn, grpmn_sin_regular_posn, tolerance)); EXPECT_TRUE(IsCloseRelAbs(grpmn_sin_reference_negn, grpmn_sin_regular_negn, tolerance)); + + if (!s.lasym) { + continue; + } + + // The cos part exists only for an asymmetric equilibrium, where + // educational_VMEC writes it as grpmn_cos. + const double grpmn_cos_singular_posn = + scale_to_match_fortran_singular * + si.grpmn_cos[idx_m_posn * numLocal + klRel]; + const double grpmn_cos_singular_negn = + scale_to_match_fortran_singular * + si.grpmn_cos[idx_m_negn * numLocal + klRel]; + const double grpmn_cos_regular_posn = + scale_to_match_fortran_regular * + ls.grpmn_cos[idx_m_posn * numLocal + klRel]; + const double grpmn_cos_regular_negn = + scale_to_match_fortran_regular * + ls.grpmn_cos[idx_m_negn * numLocal + klRel]; + const double grpmn_cos_reference_posn = + static_cast( + vac1n_fourp["grpmn_cos"][m][nf + n][k][l]) - + grpmn_cos_singular_posn; + const double grpmn_cos_reference_negn = + static_cast( + vac1n_fourp["grpmn_cos"][m][nf - n][k][l]) - + grpmn_cos_singular_negn; + + EXPECT_TRUE(IsCloseRelAbs(grpmn_cos_reference_posn, + grpmn_cos_regular_posn, tolerance)) + << "m = " << m << ", n = " << n << ", kl = " << kl; + EXPECT_TRUE(IsCloseRelAbs(grpmn_cos_reference_negn, + grpmn_cos_regular_negn, tolerance)) + << "m = " << m << ", n = " << n << ", kl = " << kl; } // kl } // m } // n @@ -153,7 +186,11 @@ TEST_P(FourPTest, CheckFourP) { INSTANTIATE_TEST_SUITE_P(TestLaplaceSolver, FourPTest, Values(DataSource{.identifier = "cth_like_free_bdy", .tolerance = 1.0e-9, - .iter2_to_test = {53, 54}})); + .iter2_to_test = {53, 54}}, + DataSource{ + .identifier = "cth_like_free_bdy_asym", + .tolerance = 1.0e-9, + .iter2_to_test = {53}})); class FourISymmTest : public TestWithParam { protected: @@ -308,11 +345,32 @@ TEST_P(FourIAccumulateGrpmnTest, CheckFourIAccumulateGrpmn) { scale_to_match_fortran_regular * ls.grpmn_sin[idx_m_negn * numLocal + klRel]; - // Only the sin part; educational_VMEC dumps no cos part of grpmn. EXPECT_TRUE(IsCloseRelAbs(vac1n_fourp["grpmn"][m][nf + n][k][l], grpmn_sin_regular_posn, tolerance)); EXPECT_TRUE(IsCloseRelAbs(vac1n_fourp["grpmn"][m][nf - n][k][l], grpmn_sin_regular_negn, tolerance)); + + if (!s.lasym) { + continue; + } + + // The cos part exists only for an asymmetric equilibrium, where + // educational_VMEC writes it as grpmn_cos. + const double grpmn_cos_regular_posn = + scale_to_match_fortran_regular * + ls.grpmn_cos[idx_m_posn * numLocal + klRel]; + const double grpmn_cos_regular_negn = + scale_to_match_fortran_regular * + ls.grpmn_cos[idx_m_negn * numLocal + klRel]; + + EXPECT_TRUE( + IsCloseRelAbs(vac1n_fourp["grpmn_cos"][m][nf + n][k][l], + grpmn_cos_regular_posn, tolerance)) + << "m = " << m << ", n = " << n << ", kl = " << kl; + EXPECT_TRUE( + IsCloseRelAbs(vac1n_fourp["grpmn_cos"][m][nf - n][k][l], + grpmn_cos_regular_negn, tolerance)) + << "m = " << m << ", n = " << n << ", kl = " << kl; } // kl } // m } // n @@ -324,7 +382,11 @@ TEST_P(FourIAccumulateGrpmnTest, CheckFourIAccumulateGrpmn) { INSTANTIATE_TEST_SUITE_P(TestLaplaceSolver, FourIAccumulateGrpmnTest, Values(DataSource{.identifier = "cth_like_free_bdy", .tolerance = 1.0e-9, - .iter2_to_test = {53, 54}})); + .iter2_to_test = {53, 54}}, + DataSource{ + .identifier = "cth_like_free_bdy_asym", + .tolerance = 1.0e-9, + .iter2_to_test = {53}})); class FourIKvDftTest : public TestWithParam { protected: diff --git a/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/singular_integrals/BUILD.bazel b/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/singular_integrals/BUILD.bazel index 5bd5ec559..33084e6b4 100644 --- a/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/singular_integrals/BUILD.bazel +++ b/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/singular_integrals/BUILD.bazel @@ -6,7 +6,9 @@ cc_test( srcs = ["singular_integrals_test.cc"], data = [ "//vmecpp/test_data:cth_like_free_bdy", + "//vmecpp/test_data:cth_like_free_bdy_asym", "//vmecpp_large_cpp_tests/test_data:cth_like_free_bdy", + "//vmecpp_large_cpp_tests/test_data:cth_like_free_bdy_asym", ], deps = [ "//vmecpp/free_boundary/singular_integrals", diff --git a/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/singular_integrals/singular_integrals_test.cc b/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/singular_integrals/singular_integrals_test.cc index 8470d5c8c..43fdda4dd 100644 --- a/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/singular_integrals/singular_integrals_test.cc +++ b/src/vmecpp/cpp/vmecpp_large_cpp_tests/free_boundary/singular_integrals/singular_integrals_test.cc @@ -145,6 +145,7 @@ TEST_P(AnalytTest, CheckAnalyt) { const int mf = s.mpol + 1; const int mnfull = (2 * nf + 1) * (mf + 1); std::vector bvec_sin(mnfull, 0.0); + std::vector bvec_cos(mnfull, 0.0); for (int thread_id = 0; thread_id < vmec.vac_num_threads_; ++thread_id) { const Nestor& n = static_cast(*vmec.fb_vac_[thread_id]); @@ -172,6 +173,9 @@ TEST_P(AnalytTest, CheckAnalyt) { // --> accumulate contributions to Fourier transform from all threads for (int mn = 0; mn < mnfull; ++mn) { bvec_sin[mn] += si.bvec_sin[mn]; + if (s.lasym) { + bvec_cos[mn] += si.bvec_cos[mn]; + } } if (vmec.m_[0]->get_ivacskip() == 0) { @@ -205,7 +209,6 @@ TEST_P(AnalytTest, CheckAnalyt) { // cmns in Fortran has alp (= 2 pi / nfp) in it; VMEC++ does not const double scale_to_match_fortran = 2.0 * M_PI / s.nfp; - // Only the sin part; educational_VMEC dumps no cos part of grpmn. EXPECT_TRUE( IsCloseRelAbs(vac1n_analyt["grpmn"][m][nf + n][k][l], scale_to_match_fortran * @@ -216,6 +219,21 @@ TEST_P(AnalytTest, CheckAnalyt) { scale_to_match_fortran * si.grpmn_sin[idx_m_negn * numLocal + klRel], tolerance)); + + if (s.lasym) { + EXPECT_TRUE(IsCloseRelAbs( + vac1n_analyt["grpmn_cos"][m][nf + n][k][l], + scale_to_match_fortran * + si.grpmn_cos[idx_m_posn * numLocal + klRel], + tolerance)) + << "m = " << m << ", n = " << n << ", kl = " << kl; + EXPECT_TRUE(IsCloseRelAbs( + vac1n_analyt["grpmn_cos"][m][nf - n][k][l], + scale_to_match_fortran * + si.grpmn_cos[idx_m_negn * numLocal + klRel], + tolerance)) + << "m = " << m << ", n = " << n << ", kl = " << kl; + } } // kl } // m } // n @@ -232,8 +250,6 @@ TEST_P(AnalytTest, CheckAnalyt) { const double scale_to_match_fortran = 2.0 * M_PI / s.nfp * 4.0 * M_PI * M_PI; - // Only the sin part; educational_VMEC dumps no cos part of bvec. - // Fortran order along n in bvec: -nf, -nf+1, ..., -1, 0, 1, ..., nf-1, // nf EXPECT_TRUE(IsCloseRelAbs(vac1n_analyt["bvec"][m][nf + n], @@ -242,6 +258,17 @@ TEST_P(AnalytTest, CheckAnalyt) { EXPECT_TRUE(IsCloseRelAbs(vac1n_analyt["bvec"][m][nf - n], scale_to_match_fortran * bvec_sin[idx_m_negn], tolerance)); + + if (s.lasym) { + EXPECT_TRUE(IsCloseRelAbs( + vac1n_analyt["bvec_cos"][m][nf + n], + scale_to_match_fortran * bvec_cos[idx_m_posn], tolerance)) + << "m = " << m << ", n = " << n; + EXPECT_TRUE(IsCloseRelAbs( + vac1n_analyt["bvec_cos"][m][nf - n], + scale_to_match_fortran * bvec_cos[idx_m_negn], tolerance)) + << "m = " << m << ", n = " << n; + } } // m } // n } @@ -250,6 +277,10 @@ TEST_P(AnalytTest, CheckAnalyt) { INSTANTIATE_TEST_SUITE_P(TestSingularIntegrals, AnalytTest, Values(DataSource{.identifier = "cth_like_free_bdy", .tolerance = 1.0e-9, - .iter2_to_test = {53, 54}})); + .iter2_to_test = {53, 54}}, + DataSource{ + .identifier = "cth_like_free_bdy_asym", + .tolerance = 1.0e-9, + .iter2_to_test = {53}})); } // namespace vmecpp diff --git a/src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/BUILD.bazel b/src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/BUILD.bazel index ff0e1adef..67aa24e2e 100644 --- a/src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/BUILD.bazel +++ b/src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/BUILD.bazel @@ -92,6 +92,12 @@ filegroup( ] + glob(["cth_like_free_bdy/**/*.json"]), ) +filegroup( + name = "cth_like_free_bdy_asym", + visibility = ["//visibility:public"], + srcs = glob(["cth_like_free_bdy_asym/**/*.json"]), +) + filegroup( name = "w7x", visibility = ["//visibility:public"], diff --git a/src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/cth_like_free_bdy_asym/vac1n_analyt/vac1n_analyt_00015_000053_01.cth_like_free_bdy_asym.json b/src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/cth_like_free_bdy_asym/vac1n_analyt/vac1n_analyt_00015_000053_01.cth_like_free_bdy_asym.json new file mode 100644 index 000000000..8d5475fb2 --- /dev/null +++ b/src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/cth_like_free_bdy_asym/vac1n_analyt/vac1n_analyt_00015_000053_01.cth_like_free_bdy_asym.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9080e29fb6db7462bbf5141a382315df4c884f54881fe78f704b9641c2d99a28 +size 2249119 diff --git a/src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/cth_like_free_bdy_asym/vac1n_analyt/vac1n_analyt_00015_000054_01.cth_like_free_bdy_asym.json b/src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/cth_like_free_bdy_asym/vac1n_analyt/vac1n_analyt_00015_000054_01.cth_like_free_bdy_asym.json new file mode 100644 index 000000000..3b056d0af --- /dev/null +++ b/src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/cth_like_free_bdy_asym/vac1n_analyt/vac1n_analyt_00015_000054_01.cth_like_free_bdy_asym.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3696baeb42d0678100ec3dfa48f0ba0c78555e00d7a0fa1989a13cb66f14b6e5 +size 281811 diff --git a/src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/cth_like_free_bdy_asym/vac1n_fouri/vac1n_fouri_00015_000053_01.cth_like_free_bdy_asym.json b/src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/cth_like_free_bdy_asym/vac1n_fouri/vac1n_fouri_00015_000053_01.cth_like_free_bdy_asym.json new file mode 100644 index 000000000..4189fd1fe --- /dev/null +++ b/src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/cth_like_free_bdy_asym/vac1n_fouri/vac1n_fouri_00015_000053_01.cth_like_free_bdy_asym.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b07f5d1d68add4ee8fa136f93591dea82aea7e2d2a0caddcf7a73f1c25d86106 +size 600039 diff --git a/src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/cth_like_free_bdy_asym/vac1n_fourp/vac1n_fourp_00015_000053_01.cth_like_free_bdy_asym.json b/src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/cth_like_free_bdy_asym/vac1n_fourp/vac1n_fourp_00015_000053_01.cth_like_free_bdy_asym.json new file mode 100644 index 000000000..1cc25412b --- /dev/null +++ b/src/vmecpp/cpp/vmecpp_large_cpp_tests/test_data/cth_like_free_bdy_asym/vac1n_fourp/vac1n_fourp_00015_000053_01.cth_like_free_bdy_asym.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38f6a3fc2d2220e526b06a565ff0a32df08746f00f4abf4212245e9ab30ba181 +size 1679288