-
Notifications
You must be signed in to change notification settings - Fork 111
Expression unit_integral to normalize sources
#3481
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||
|
|
||||||
| #include "fieldgenerators.hxx" | ||||||
|
|
||||||
| #include <bout/boutcomm.hxx> | ||||||
| #include <memory> | ||||||
|
|
||||||
| #include <bout/constants.hxx> | ||||||
|
|
@@ -47,6 +48,69 @@ BoutReal FieldHeaviside::generate(const Context& ctx) { | |||||
| return (gen->generate(ctx) > 0.0) ? 1.0 : 0.0; | ||||||
| } | ||||||
|
|
||||||
| FieldGeneratorPtr FieldUnitIntegral::clone(const std::list<FieldGeneratorPtr> args) { | ||||||
| if (args.size() != 1) { | ||||||
| throw ParseException( | ||||||
| "Incorrect number of arguments to unit_integral function. Expecting 1, got {:d}", | ||||||
| args.size()); | ||||||
| } | ||||||
|
|
||||||
| return std::make_shared<FieldUnitIntegral>(args.front()); | ||||||
| } | ||||||
|
|
||||||
| bool FieldUnitIntegral::cacheMatches(const Context& ctx) const { | ||||||
| return cache_valid && (cached_mesh == ctx.getMesh()) && (cached_time == ctx.t()) | ||||||
| && (cached_location == ctx.location()); | ||||||
| } | ||||||
|
|
||||||
| void FieldUnitIntegral::populateCache(const Context& ctx) { | ||||||
| Mesh* localmesh = ctx.getMesh(); | ||||||
| ASSERT0(localmesh != nullptr); | ||||||
| Coordinates* coords = localmesh->getCoordinates(ctx.location()); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: no header providing "Coordinates" is directly included [misc-include-cleaner] src/field/fieldgenerators.cxx:3: - #include <bout/boutcomm.hxx>
+ #include "bout/coordinates.hxx"
+ #include <bout/boutcomm.hxx>
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: pointee of variable 'coords' of type 'Coordinates *' can be declared 'const' [misc-const-correctness]
Suggested change
|
||||||
| if (coords == nullptr) { | ||||||
| throw BoutException("unit_integral function needs coordinates at {}", | ||||||
| toString(ctx.location())); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: no header providing "toString" is directly included [misc-include-cleaner] toString(ctx.location()));
^ |
||||||
| } | ||||||
|
|
||||||
| cached_values = Field3D(localmesh).setLocation(ctx.location()).allocate(); | ||||||
|
|
||||||
| BOUT_FOR(i, cached_values.getRegion("RGN_ALL")) { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: no header providing "BOUT_FOR" is directly included [misc-include-cleaner] src/field/fieldgenerators.cxx:3: - #include <bout/boutcomm.hxx>
+ #include "bout/region.hxx"
+ #include <bout/boutcomm.hxx> |
||||||
| cached_values[i] = gen->generate(Context(i, ctx.location(), localmesh, ctx.t())); | ||||||
| } | ||||||
|
|
||||||
| BoutReal local_integral = 0.0; | ||||||
| BOUT_FOR(i, cached_values.getRegion("RGN_NOBNDRY")) { | ||||||
| local_integral += cached_values[i] * coords->J()[i] * coords->dx()[i] | ||||||
| * coords->dy()[i] * coords->dz()[i]; | ||||||
| } | ||||||
|
|
||||||
| BoutReal integral = 0.0; | ||||||
| MPI_Allreduce(&local_integral, &integral, 1, MPI_DOUBLE, MPI_SUM, BoutComm::get()); | ||||||
|
|
||||||
| if (integral == 0.0) { | ||||||
| throw BoutException("unit_integral function integral is zero"); | ||||||
| } | ||||||
|
|
||||||
| BOUT_FOR(i, cached_values.getRegion("RGN_ALL")) { cached_values[i] /= integral; } | ||||||
|
|
||||||
| cached_mesh = localmesh; | ||||||
| cached_time = ctx.t(); | ||||||
| cached_location = ctx.location(); | ||||||
| cache_valid = true; | ||||||
| } | ||||||
|
|
||||||
| BoutReal FieldUnitIntegral::generate(const Context& ctx) { | ||||||
| if (!cacheMatches(ctx)) { | ||||||
| std::lock_guard<std::mutex> guard(cache_mutex); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: no header providing "std::lock_guard" is directly included [misc-include-cleaner] src/field/fieldgenerators.cxx:5: + #include <mutex>
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: no header providing "std::mutex" is directly included [misc-include-cleaner] std::lock_guard<std::mutex> guard(cache_mutex);
^
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: variable 'guard' of type 'std::lock_guardstd::mutex' can be declared 'const' [misc-const-correctness]
Suggested change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this AI by any chance? We don't have usual threads, we use OpenMP, so this should be |
||||||
| if (!cacheMatches(ctx)) { | ||||||
| populateCache(ctx); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| ASSERT1(ctx.location() == cached_location); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: no header providing "ASSERT1" is directly included [misc-include-cleaner] ASSERT1(ctx.location() == cached_location);
^ |
||||||
| return cached_values(ctx.ix(), ctx.jy(), ctx.kz()); | ||||||
| } | ||||||
|
|
||||||
| ////////////////////////////////////////////////////////// | ||||||
| // Ballooning transform | ||||||
| // Use a truncated Ballooning transform to enforce periodicity in y and z | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -265,6 +265,30 @@ private: | |||||
| FieldGeneratorPtr gen; | ||||||
| }; | ||||||
|
|
||||||
| /// Scale an expression so its volume integral over RGN_NOBNDRY is 1 | ||||||
| class FieldUnitIntegral : public FieldGenerator { | ||||||
| public: | ||||||
| explicit FieldUnitIntegral(FieldGeneratorPtr g = nullptr) : gen(std::move(g)) {} | ||||||
|
|
||||||
| FieldGeneratorPtr clone(const std::list<FieldGeneratorPtr> args) override; | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: parameter 'args' is const-qualified in the function declaration; const-qualification of parameters only has an effect in function definitions [readability-avoid-const-params-in-decls]
Suggested change
|
||||||
| BoutReal generate(const bout::generator::Context& pos) override; | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: function 'FieldUnitIntegral::generate' has a definition with different parameter names [readability-inconsistent-declaration-parameter-name] BoutReal generate(const bout::generator::Context& pos) override;
^Additional contextsrc/field/fieldgenerators.cxx:101: the definition seen here BoutReal FieldUnitIntegral::generate(const Context& ctx) {
^src/field/fieldgenerators.hxx:273: differing parameters are named here: ('pos'), in definition: ('ctx') BoutReal generate(const bout::generator::Context& pos) override;
^ |
||||||
| std::string str() const override { | ||||||
| return std::string("unit_integral(") + gen->str() + std::string(")"); | ||||||
| } | ||||||
|
|
||||||
| private: | ||||||
| void populateCache(const bout::generator::Context& ctx); | ||||||
| bool cacheMatches(const bout::generator::Context& ctx) const; | ||||||
|
|
||||||
| FieldGeneratorPtr gen; | ||||||
| Field3D cached_values{}; | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: initializer for member 'cached_values' is redundant [readability-redundant-member-init]
Suggested change
|
||||||
| Mesh* cached_mesh{nullptr}; | ||||||
| BoutReal cached_time{0.0}; | ||||||
| CELL_LOC cached_location{CELL_CENTRE}; | ||||||
| bool cache_valid{false}; | ||||||
| std::mutex cache_mutex; | ||||||
| }; | ||||||
|
|
||||||
| ////////////////////////////////////////////////////////// | ||||||
| // Ballooning transform | ||||||
| // Use a truncated Ballooning transform to enforce periodicity | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |||||
| #include "fake_mesh.hxx" | ||||||
| #include "test_extras.hxx" | ||||||
| #include "bout/bout_types.hxx" | ||||||
| #include "bout/boutcomm.hxx" | ||||||
| #include "bout/boutexception.hxx" | ||||||
| #include "bout/constants.hxx" | ||||||
| #include "bout/coordinates.hxx" | ||||||
|
|
@@ -29,6 +30,43 @@ | |||||
| using namespace bout::globals; | ||||||
| using bout::generator::Context; | ||||||
|
|
||||||
| namespace { | ||||||
| BoutReal volumeIntegral(const Field3D& field) { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm surprised that we don't already have a We could then use this in the generator implementation? |
||||||
| const auto* coords = field.getCoordinates(); | ||||||
| if (coords == nullptr) { | ||||||
| throw BoutException("Field has no coordinates"); | ||||||
| } | ||||||
|
|
||||||
| BoutReal local = 0.0; | ||||||
| BOUT_FOR(i, field.getRegion("RGN_NOBNDRY")) { | ||||||
| local += | ||||||
| field[i] * coords->J()[i] * coords->dx()[i] * coords->dy()[i] * coords->dz()[i]; | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| } | ||||||
|
|
||||||
| BoutReal global = 0.0; | ||||||
| MPI_Allreduce(&local, &global, 1, MPI_DOUBLE, MPI_SUM, BoutComm::get()); | ||||||
| return global; | ||||||
| } | ||||||
|
|
||||||
| BoutReal totalVolume(Mesh* localmesh, CELL_LOC location = CELL_CENTRE) { | ||||||
| auto* coords = localmesh->getCoordinates(location); | ||||||
| if (coords == nullptr) { | ||||||
| throw BoutException("Mesh has no coordinates"); | ||||||
| } | ||||||
|
|
||||||
| BoutReal local = 0.0; | ||||||
| Field3D ones{localmesh}; | ||||||
| ones.allocate(); | ||||||
| BOUT_FOR(i, ones.getRegion("RGN_NOBNDRY")) { | ||||||
| local += coords->J()[i] * coords->dx()[i] * coords->dy()[i] * coords->dz()[i]; | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| } | ||||||
|
|
||||||
| BoutReal global = 0.0; | ||||||
| MPI_Allreduce(&local, &global, 1, MPI_DOUBLE, MPI_SUM, BoutComm::get()); | ||||||
| return global; | ||||||
| } | ||||||
| } // namespace | ||||||
|
|
||||||
| // Reuse the "standard" fixture for FakeMesh | ||||||
| template <typename T> | ||||||
| class FieldFactoryCreationTest : public FakeMeshFixture { | ||||||
|
|
@@ -60,6 +98,8 @@ using Fields = ::testing::Types<Field2D, Field3D>; | |||||
|
|
||||||
| TYPED_TEST_SUITE(FieldFactoryCreationTest, Fields); | ||||||
|
|
||||||
| using FieldFactory3DCreationTest = FieldFactoryCreationTest<Field3D>; | ||||||
|
|
||||||
| TYPED_TEST(FieldFactoryCreationTest, CreateFromValueGenerator) { | ||||||
| auto value = BoutReal{4.}; | ||||||
| auto output = this->create(generator(value)); | ||||||
|
|
@@ -179,6 +219,40 @@ TYPED_TEST(FieldFactoryCreationTest, CreateZ) { | |||||
| EXPECT_TRUE(IsFieldEqual(output, expected)); | ||||||
| } | ||||||
|
|
||||||
| TEST_F(FieldFactory3DCreationTest, CreateUnitIntegralConstant) { | ||||||
| const auto expected = 1.0 / totalVolume(mesh); | ||||||
|
|
||||||
| auto output = this->factory.create3D("unit_integral(1)"); | ||||||
|
|
||||||
| EXPECT_TRUE(IsFieldEqual(output, expected)); | ||||||
| EXPECT_NEAR(volumeIntegral(output), 1.0, 1e-12); | ||||||
| } | ||||||
|
|
||||||
| TEST_F(FieldFactory3DCreationTest, CreateUnitIntegralUsesCellVolume) { | ||||||
| Field2D dy{mesh}; | ||||||
| dy.allocate(); | ||||||
| BOUT_FOR(i, dy.getRegion("RGN_ALL")) { dy[i] = static_cast<BoutReal>(i.y() + 1); } | ||||||
| mesh->getCoordinates()->setDy(dy); | ||||||
|
|
||||||
| const auto expected = 1.0 / totalVolume(mesh); | ||||||
| auto output = this->factory.create3D("unit_integral(1)"); | ||||||
|
|
||||||
| EXPECT_TRUE(IsFieldEqual(output, expected)); | ||||||
| EXPECT_NEAR(volumeIntegral(output), 1.0, 1e-12); | ||||||
| } | ||||||
|
|
||||||
| TEST_F(FieldFactory3DCreationTest, CreateUnitIntegralRefreshesCachedParseAtNewTime) { | ||||||
| auto output_t0 = | ||||||
| this->factory.create3D("unit_integral(y + t)", nullptr, mesh, CELL_CENTRE, 0.0); | ||||||
| auto output_t1 = | ||||||
| this->factory.create3D("unit_integral(y + t)", nullptr, mesh, CELL_CENTRE, 1.0); | ||||||
|
|
||||||
| EXPECT_NEAR(volumeIntegral(output_t0), 1.0, 1e-12); | ||||||
| EXPECT_NEAR(volumeIntegral(output_t1), 1.0, 1e-12); | ||||||
| EXPECT_NE(output_t0(mesh->xstart, mesh->ystart, 0), | ||||||
| output_t1(mesh->xstart, mesh->ystart, 0)); | ||||||
| } | ||||||
|
|
||||||
| TYPED_TEST(FieldFactoryCreationTest, CreateXStaggered) { | ||||||
| // Need this->mesh_staggered to access member of base FakeMeshFixture because | ||||||
| // derived FieldFactoryCreationTest is a template clas | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: no header providing "ASSERT0" is directly included [misc-include-cleaner]
src/field/fieldgenerators.cxx:3: