Skip to content

diamagnetic_drift: Boundary flux changes - #539

Open
bendudson wants to merge 6 commits into
masterfrom
diamagnetic-drift
Open

diamagnetic_drift: Boundary flux changes#539
bendudson wants to merge 6 commits into
masterfrom
diamagnetic-drift

Conversation

@bendudson

@bendudson bendudson commented Apr 2, 2026

Copy link
Copy Markdown
Collaborator

Purpose

Ensure that the divergence of the diamagnetic drift is zero, and that fluxes through the core boundary doesn't result in unphysical boundary layers.

Change Summary

  • Replaces blending of divergence and gradient form with a switch
  • Adds a core averaging option that averages diamagnetic terms over the x surface next to the core boundary
  • Refactors the transform_impl function into public functions
  • Adds unit tests of public functions

Validation

Unit tests of steps in the calculation, following refactor. Includes tests of the conservation properties.

AI Assistance

Codex/GPT-5.4 wrote most of the code. I planned, reviewed and adjusted the changes.

Documentation

The diamagnetic drift equation documentation has been updated.

Review Notes

bendudson and others added 4 commits April 1, 2026 21:37
Previous method, using a varying weighting between divergence
and gradient form, may not have preserved fluxes.
Introducing a new method that averages over the core boundary
and prevents fluxes through boundaries by default.
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 71.25000% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 49.44%. Comparing base (dde4a9e) to head (dbc3f01).
⚠️ Report is 20 commits behind head on master.

Files with missing lines Patch % Lines
src/diamagnetic_drift.cxx 71.25% 12 Missing and 11 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #539      +/-   ##
==========================================
+ Coverage   49.09%   49.44%   +0.34%     
==========================================
  Files          96       97       +1     
  Lines       10037    10102      +65     
  Branches     1452     1466      +14     
==========================================
+ Hits         4928     4995      +67     
+ Misses       4604     4591      -13     
- Partials      505      516      +11     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Split the transform function into pieces and add unit tests.
Includes checks that the operator conserves fluxes in both
diamagnetic and gradient form.
@bendudson bendudson changed the title WIP on diamagnetic_drift: Boundary flux changes diamagnetic_drift: Boundary flux changes Jul 2, 2026
No longer blending divergence and gradient forms.
@bendudson
bendudson requested review from bshanahan and cmacmackin July 2, 2026 22:29

@cmacmackin cmacmackin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the way public functions have been introduced in this. They are not necessary to test the behaviour of the component and will increase code maintenance burdens.

Even if you decide to keep calculateDivergenceForm and calculateGradientForm public, I would strongly urge you to make addDiamagneticDrift private. I see no value in calling it directly in your unit tests and doing so requires setting up GuardedOptions objects. Doing that will require you to keep track of the permissions set by DiamagneticDrift and make your tests significantly more fragile as a result..

Comment thread src/diamagnetic_drift.cxx

divergence_form = options["divergence_form"]
.doc("Use divergence form of diamagnetic drift?")
.withDefault<bool>(true);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Call this option "form" and use BoutEnum to provide options with meaningful names (e.g., divergence and gradient).

Comment thread src/diamagnetic_drift.cxx
if (!average_core) {
throw BoutException("DiamagneticDrift::coreAverage requires average_core=true");
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can avoid this check if we make coreAverage is a standalone function that take core_ring_volume and cell_volume as arguments.

Comment thread src/diamagnetic_drift.cxx
Comment on lines +122 to +125
if (fabs(charge) < 1e-5) {
throw BoutException(
"DiamagneticDrift::calculateGradientForm requires a non-zero charge");
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need this, as we already check the charge before calling.

Comment thread src/diamagnetic_drift.cxx
Comment on lines +110 to +113
if (fabs(charge) < 1e-5) {
throw BoutException(
"DiamagneticDrift::calculateDivergenceForm requires a non-zero charge");
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need this, as we already check the charge before calling.

Comment on lines +46 to +52
for (int jx = mesh->xstart; jx <= mesh->xend; ++jx) {
for (int jy = mesh->ystart; jy <= mesh->yend; ++jy) {
for (int jz = mesh->zstart; jz <= mesh->zend; ++jz) {
result += cell_volume(jx, jy) * field(jx, jy, jz);
}
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (int jx = mesh->xstart; jx <= mesh->xend; ++jx) {
for (int jy = mesh->ystart; jy <= mesh->yend; ++jy) {
for (int jz = mesh->zstart; jz <= mesh->zend; ++jz) {
result += cell_volume(jx, jy) * field(jx, jy, jz);
}
}
}
BOUT_FOR(i, mesh->getRegion3D("RGN_NOBNDRY")) {
result += cell_volume[i] * field[i];
}

Comment on lines +205 to +215
FakeMesh div_mesh(6, 5, 7, *bout::globals::mpi);
div_mesh.xstart = 2;
div_mesh.xend = div_mesh.LocalNx - 3;
div_mesh.createDefaultRegions();
div_mesh.setCoordinates(nullptr);
auto div_coords = makeUnitCoordinates(&div_mesh);
div_mesh.setCoordinates(div_coords);
div_mesh.setGridDataSource(new FakeGridDataSource());
div_mesh.createBoundaryRegions();

ScopedGlobalMesh use_div_mesh(&div_mesh);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, consider moving this logic into a fixture, to keep the body of the test cleaner.

Comment thread src/diamagnetic_drift.cxx
Comment on lines +31 to +33
bndry_flux = options["bndry_flux"]
.doc("Allow fluxes through boundary?")
.withDefault<bool>(false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing as we're making breaking changes anyway, we might as well rename this "boundary_flux".

});
Field3D temperature{1.0};

Field3D sink = component.calculateDivergenceForm(quantity, temperature, 1.0);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As described previously, I think it is better to do this with calls to DiamagneticDirft::transform. If you really want to do it this way, however, there is no need to set average_core or diveregence_form.

ASSERT_NEAR(volumeIntegral(sink), 0.0, 1e-12);
}

TEST_F(WideDiamagneticDriftTest, GradientFormIsConservativeWithConstantBoundaryProduct) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is meant by BoundaryProduct in this test name. I see the constant boundary, but where is the product?

Comment thread docs/sphinx/equations.rst
a flux through the boundary, while the gradient form can be used to avoid
those unphysical boundary fluxes.

In Hermes-3 these are selected with the boolean option ``divergence_form``:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use an enum for this, instead of a boolean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants